Advertisement
Guest User

CAT Lifesupport w/o airlock

a guest
Oct 12th, 2019
75
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.44 KB | None | 0 0
  1. /*
  2. * Cats makes the best programmers!
  3. * A start on an Life Support management script
  4. *
  5. * SHIP_PREFIX sets the block name prefix (only blocks with the right prefix will be effected by the script or set null to disable filtering)
  6. * The following are also important to set up the way you want them and have descriptions beside them (they are located at the top of the script)
  7. * O2_SYSTEM_TAG, LIGHT_TAG, SOUND_TAG
  8. * TANK_O2_MIN, TANK_O2_MAX, TANK_O2_CRITICAL
  9. *
  10. * LCD_DISPLAY_TAG The keyword that identifies panels to be used for display (null uses all panels)
  11. *
  12. * LCD_DISPLAY_FONT_COL Sets the colour of the text on LCD panels
  13. * LCD_DISPLAY_COLOUR = Sets the colour of the Background on LCD panels
  14. * LCD_FONT_SIZE Sets the Size of the text on LCD panels
  15. *
  16. * 201903121103
  17. */
  18.  
  19.  
  20.  
  21.  
  22.  
  23.  
  24. bool GB_Debug = false;
  25. bool GB_Terminal = true;
  26.  
  27. // false: controls only the grid that this PB is on
  28. // true: control grids adjasant to grid PB is on by rotors/pistols
  29. bool GB_ControlSubGrids = true;
  30.  
  31. string GS_O2_GenTag = "[O2]"; // tag for controled O2 Generators
  32. string GS_O2_TankTag = "[O2]"; // tag for watched tanks
  33.  
  34. string GS_O2_LCDTankTag = "[O2-T]"; // LCD Tag foor tanks
  35. string GS_O2_LCDVentTag = "[O2-V]"; // LCD Tag for vent
  36. string GS_O2_LCDTag = "[O2]"; // LCD Tag for both
  37.  
  38. Color LCD_DISPLAY_FONT_COL = new Color( 0,255,0 ); // determins the colour of the text
  39. Color LCD_DISPLAY_COLOUR = new Color( 0,1,0 ); // determins the colour of the background.
  40. float LCD_FONT_SIZE = 0.8f; // determins the size of the text.
  41.  
  42. string GS_O2_SountTag = "[O2]"; // sound block Tag
  43. string[] GA_SoundAlert = { "SoundblockAlert2", "Alert 2" }; // alert sound to use
  44.  
  45.  
  46. // INDICATOR LIGHT VARIABLES
  47. string GS_O2GenIndTag = "[O2Gen]"; // tag for Generator Light
  48. string GS_LightTag = "[O2]"; // tag for Tank level Lights
  49.  
  50.  
  51. const int TANK_O2_MAX = 25; // Tank pressure at which to turn off 02 generation (This needs to be equal to or greater than TANK_O2_MIN and below 100% if you want depressurisation to work)
  52. const int TANK_O2_MIN = 15; // Tank pressure level at which to turn on 02 generation (The minimum value you want to have in your tank at all times)
  53. const int TANK_O2_CRITICAL = 10; // Tank Pressure at which the alarms go off. (Be sure this is at least 5% below TANK_O2_MIN)
  54.  
  55.  
  56.  
  57. // Variable Past Here are set by the script
  58.  
  59. int GI_NumOfTanks = 0;
  60. double GD_Tank_Total = 0;
  61.  
  62. double GD_Tank_Store = 0;
  63. int GI_NumOfStore = 0;
  64.  
  65. double GD_Tank_Avail = 0;
  66. int GI_NumOfFree = 0;
  67.  
  68. bool GB_O2Gen_Active = false;
  69.  
  70.  
  71.  
  72. int GI_O2Gen_Reset = 0;
  73.  
  74.  
  75. bool GB_O2_Alert = false;
  76. double GD_Tank_History = -1;
  77. double GD_Tank_CoT = 0;
  78.  
  79.  
  80. // customising this will alter the display info
  81. public string buildFullDisplay() {
  82. var sb = new StringBuilder( );
  83.  
  84. sb.Append( buildTankDisplay() );
  85.  
  86. sb.Append( '\n' );
  87.  
  88. return sb.ToString();
  89. }
  90.  
  91. // customising this will alter the display info
  92. public string buildTankDisplay() {
  93. var sb = new StringBuilder( "___O2_Tank_Data___\n" );
  94.  
  95. sb.Append( "Store: " ).Append( GD_Tank_Store.ToString("000.0") ).Append( "% In " ).Append( GI_NumOfStore ).Append( '\n' );
  96. sb.Append( "Avail: " ).Append( GD_Tank_Avail.ToString("000.0") ).Append( "% In " ).Append( GI_NumOfFree ).Append( '\n' );
  97. sb.Append( "Total: " ).Append( GD_Tank_Total.ToString("000.0") ).Append( "% In " ).Append( GI_NumOfTanks ).Append( '\n' );
  98.  
  99. sb.Append( "\n___O2_Gen_Data___\n" );
  100. sb.Append( "O2 Gens Active: " ).Append( GB_O2Gen_Active ).Append( '\n' );
  101. sb.Append( "O2 Alert Active: " ).Append( GB_O2_Alert ).Append( '\n' );
  102.  
  103. return sb.ToString();
  104. }
  105.  
  106. // customising this will alter the display info
  107.  
  108.  
  109. public Program() {
  110. if( !GB_Debug ) {
  111. Echo = text => {}; // disable echo
  112. }
  113. Runtime.UpdateFrequency = UpdateFrequency.Update100;
  114.  
  115. Setup();
  116.  
  117. Terminal();
  118. }
  119.  
  120.  
  121.  
  122. public void Save() {
  123. Terminal();
  124. }
  125.  
  126.  
  127.  
  128. public void Main( string strIn, UpdateType trig ) {
  129. Echo( "RUN" );
  130. CheckTankStatus(
  131. out GD_Tank_Total, out GI_NumOfTanks,
  132. out GD_Tank_Avail, out GI_NumOfFree,
  133. out GD_Tank_Store, out GI_NumOfStore
  134. );
  135.  
  136. GI_O2Gen_Reset -= 1;
  137. if( GD_Tank_Avail <= TANK_O2_CRITICAL ) {
  138. ProcAlert( GD_Tank_CoT, GD_Tank_History, GD_Tank_Avail );
  139.  
  140. if( !GB_O2Gen_Active ) {
  141. GB_O2Gen_Active = true;
  142. SetOxygenProduction( GB_O2Gen_Active );
  143. SetLightColor( new Color( 255, 0, 0 ), GS_O2GenIndTag, 1f, 50f, 50f ); // set gen light
  144. GI_O2Gen_Reset = 100;
  145. } else if( GI_O2Gen_Reset <= 0 ) {
  146. SetOxygenProduction( GB_O2Gen_Active );
  147. SetLightColor( new Color( 255, 0, 0 ), GS_O2GenIndTag, 1f, 50f, 50f ); // set gen light
  148. GI_O2Gen_Reset = 100;
  149. }
  150.  
  151. } else if( GD_Tank_Avail <= TANK_O2_MIN ) {
  152.  
  153. if( GB_O2_Alert ) {
  154. ProcAlert( GD_Tank_CoT, GD_Tank_History, GD_Tank_Avail );
  155. }
  156.  
  157. if( !GB_O2Gen_Active ) {
  158. GB_O2Gen_Active = true;
  159. SetOxygenProduction( GB_O2Gen_Active );
  160. SetLightColor( new Color( 255, 0, 0 ), GS_O2GenIndTag, 1f, 50f, 50f ); // set gen light
  161. GI_O2Gen_Reset = 100;
  162. } else if( GI_O2Gen_Reset <= 0 ) {
  163. SetOxygenProduction( GB_O2Gen_Active );
  164. SetLightColor( new Color( 255, 0, 0 ), GS_O2GenIndTag, 1f, 50f, 50f ); // set gen light
  165. GI_O2Gen_Reset = 100;
  166. }
  167.  
  168. } else if( GD_Tank_Avail >= TANK_O2_MAX ){
  169.  
  170. if( GB_O2_Alert ) {
  171. ProcAlert( GD_Tank_CoT, GD_Tank_History, GD_Tank_Avail );
  172. }
  173.  
  174. if ( GB_O2Gen_Active ) {
  175. GB_O2Gen_Active = false;
  176. SetOxygenProduction( GB_O2Gen_Active );
  177. SetLightColor( new Color( 0, 255, 0 ), GS_O2GenIndTag, 3f, 33.333f, 33.333f );
  178. GI_O2Gen_Reset = 100;
  179. } else if( GI_O2Gen_Reset <= 0 ) {
  180. SetOxygenProduction( GB_O2Gen_Active );
  181. SetLightColor( new Color( 0, 255, 0 ), GS_O2GenIndTag, 3f, 33.333f, 33.333f );
  182. GI_O2Gen_Reset = 100;
  183. }
  184.  
  185. }
  186.  
  187. GD_Tank_CoT = (GD_Tank_CoT*0.75) + ((GD_Tank_Avail-GD_Tank_History)*0.25);
  188. GD_Tank_History = GD_Tank_Avail;
  189.  
  190. Print();
  191. }
  192.  
  193.  
  194. /*
  195. public string RunLockCheck( string cmd ) {
  196. StringBuilder Output = new StringBuilder();
  197. IMyTerminalBlock Light = GridTerminalSystem.GetBlockWithName( LIGHT_LCK );
  198. IMyTerminalBlock Vent = GridTerminalSystem.GetBlockWithName( VENT_LCK );
  199. IMyTerminalBlock DoorI = GridTerminalSystem.GetBlockWithName( LOCK_DOOR_INT );
  200. IMyTerminalBlock DoorE = GridTerminalSystem.GetBlockWithName( LOCK_DOOR_EXT );
  201.  
  202. if( DoorI == null || DoorE == null ) {
  203. Output.Append( "Airlock: Door(s) Not Found" );
  204. if( AIRLOCK_STATUS != -1 ) {
  205. (DoorI as IMyDoor).CloseDoor();
  206. (DoorE as IMyDoor).CloseDoor();
  207. AIRLOCK_STATUS = -1;
  208. }
  209. } else if( AIRLOCK_STATUS == -1 ) {
  210. AIRLOCK_STATUS = 2;
  211. (DoorI as IMyDoor).CloseDoor();
  212. (DoorE as IMyDoor).CloseDoor();
  213. Output.Append( "Airlock: Priming" );
  214. } else if( Light != null && Vent != null ) {
  215. double Pressure = (Vent as IMyAirVent).GetOxygenLevel() * 100;
  216. bool LightStatus = ((IMyFunctionalBlock)Light).Enabled;
  217. if( AIRLOCK_STATUS == 1 && LightStatus != true ) {
  218. Output.Append( "Airlock: Cycling (Out)" );
  219. AIRLOCK_STATUS = 2;
  220. (DoorI as IMyDoor).CloseDoor();
  221. (DoorE as IMyDoor).CloseDoor();
  222. } else if ( AIRLOCK_STATUS == 2 ) {
  223. Output.Append( "Airlock: Cycling (Out)" );
  224. if( !(Vent as IMyAirVent).Depressurize ) {
  225. (Vent as IMyAirVent).Depressurize = true;
  226. } else if( Pressure == 0 ) {
  227. (DoorE as IMyDoor).CloseDoor();
  228. //SealDoor( DoorE, false );
  229. AIRLOCK_STATUS = 3;
  230. }
  231. } else if( AIRLOCK_STATUS == 3 && LightStatus != false ) {
  232. Output.Append( "Airlock: Cycling (In) A" );
  233. AIRLOCK_STATUS = 4;
  234. (DoorI as IMyDoor).CloseDoor();
  235. (DoorE as IMyDoor).CloseDoor();
  236. } else if( AIRLOCK_STATUS == 4 ) {
  237. Output.Append( "Airlock: Cycling (In) B" );
  238. if( (Vent as IMyAirVent).Depressurize ) {
  239. Output.Append( "\nErr" );
  240. (Vent as IMyAirVent).Depressurize = false;
  241. } else if( Pressure == 100 ) {
  242. Output.Append( "\nGood" );
  243. (DoorI as IMyDoor).OpenDoor();
  244. AIRLOCK_STATUS = 1;
  245. }
  246. } else {
  247. if( AIRLOCK_STATUS == 1 ) {
  248. Output.Append( "Airlock: Pressurized" );
  249. } else if( AIRLOCK_STATUS == 3 ) {
  250. Output.Append( "Airlock: UnPressurized" );
  251. }
  252. }
  253. } else {
  254. if( Light == null ) {
  255. Output.Append( "\nAirlock: Light Not Found" );
  256. }
  257. if( Vent == null ) {
  258. Output.Append( "\nAirlock: Vent Not Found" );
  259. }
  260. }
  261. return Output.ToString();
  262. }
  263. */
  264.  
  265.  
  266.  
  267. public void ProcAlert( double CoT, double his, double cur ) {
  268. Echo( "M: "+ CoT.ToString( "0.00" ) +" : "+ his.ToString( "0.00" ) +" : "+ cur.ToString( "0.00" ) );
  269. if( !GB_O2_Alert && ( CoT < 0 ) ) {
  270. GB_O2_Alert = true;
  271. SetSound( true, GS_O2_SountTag );
  272. SetLightColor( new Color( 255, 0, 0 ), GS_LightTag, 3, 1.5f, 0 );
  273. } else if ( GB_O2_Alert && ( CoT > 0 ) ){
  274. GB_O2_Alert = false;
  275. SetSound( false, GS_O2_SountTag );
  276. SetLightColor( new Color( 0, 0, 255 ), GS_LightTag );
  277. }
  278. }
  279.  
  280.  
  281. /*
  282. public void checkVent() {
  283. GL_VentData = new List<CAT_VentState>();
  284.  
  285. var blocks = new List<IMyTerminalBlock>();
  286. GridTerminalSystem.GetBlocksOfType<IMyAirVent>( blocks, x=> IsSubjectGrid( x ) && x.CustomName.Contains( GS_O2_VentTag ) ); // IMyOxygenGenerator
  287.  
  288. for( int i = 0; i < blocks.Count; ++i ) {
  289. var vent = blocks[i] as IMyAirVent;
  290. var data = new CAT_VentState( vent.CustomName, vent.GetOxygenLevel()*100, !vent.Depressurize, vent.CanPressurize );
  291. GL_VentData.Add( data );
  292. }
  293. }
  294. */
  295.  
  296.  
  297.  
  298. int GI_LCD_Tick = 0;
  299. public void Print() {
  300. var blocks = new List<IMyTerminalBlock>();
  301. GridTerminalSystem.GetBlocksOfType<IMyTextPanel>( blocks,
  302. x=> IsSubjectGrid( x )
  303. && ( x.CustomName.Contains( GS_O2_LCDTag )
  304. || x.CustomName.Contains( GS_O2_LCDTankTag )
  305. || x.CustomName.Contains( GS_O2_LCDVentTag )
  306. ) );
  307.  
  308. IMyTextSurface ref_a = null; // see blow
  309. IMyTextSurface ref_b = null; // see blow
  310.  
  311. if( blocks.Count != 0 ) {
  312. for( int i=0; i<blocks.Count; ++i ) {
  313. var block = blocks[i];
  314. var t = block as IMyTextSurfaceProvider;
  315. if( t == null || t.SurfaceCount <= 0 ) {
  316. // SKIP IF NO SURFACE
  317. continue;
  318. }
  319.  
  320. var lcd = t.GetSurface( 0 ); // non-first surfaces not considered yet
  321. // this may be an issue for displays in cockpits and the like
  322.  
  323. if( block.CustomName.Contains( GS_O2_LCDTankTag ) ) {
  324.  
  325. if( ref_b == null ) {
  326. ref_b = lcd;
  327. lcd.WriteText( buildTankDisplay() );
  328. } else {
  329. lcd.WriteText( ref_b.GetText() );
  330. }
  331.  
  332. }
  333. else {
  334.  
  335. if( ref_a == null ) {
  336. ref_a = lcd;
  337. lcd.WriteText( buildFullDisplay() );
  338. } else {
  339. lcd.WriteText( ref_a.GetText() );
  340. }
  341.  
  342. }
  343.  
  344. if( GI_LCD_Tick == 0 ) {
  345. lcd.ContentType = ContentType.TEXT_AND_IMAGE; // the one line I forgot. Oups.
  346. lcd.FontColor = LCD_DISPLAY_FONT_COL;
  347. lcd.BackgroundColor = LCD_DISPLAY_COLOUR;
  348. lcd.FontSize = LCD_FONT_SIZE;
  349. }
  350. }
  351. } else {
  352. Echo( "Err: Display(s) Not Found" );
  353. }
  354.  
  355. GI_LCD_Tick -= 1;
  356. if( GI_LCD_Tick < 0 ) {
  357. GI_LCD_Tick = 30;
  358. }
  359. }
  360.  
  361.  
  362. // called on program load and game save
  363. // nothing but getting the PB to show what it is running
  364. public void Terminal() {
  365. if( !GB_Terminal ) {
  366. return;
  367. }
  368. try {
  369. var sb = new StringBuilder( "CAT Script\nO2 Management" );
  370. var img = "LCD_Economy_Trinity";
  371.  
  372. var pro = Me as IMyTextSurfaceProvider;
  373. var lcd = pro.GetSurface( 0 );
  374.  
  375. lcd.ClearImagesFromSelection();
  376. lcd.AddImageToSelection( img, true );
  377.  
  378. //sb.Append( "\nA: "+ lcd.Alignment.ToString() );
  379. lcd.Alignment = TextAlignment.CENTER;
  380.  
  381. //sb.Append( "\nB: "+ lcd.Font );
  382. lcd.Font = "Monospace";
  383.  
  384. //sb.Append( "\nT: "+ lcd.ContentType );
  385. lcd.ContentType = ContentType.TEXT_AND_IMAGE;
  386.  
  387. lcd.PreserveAspectRatio = false;
  388. lcd.TextPadding = 10.0f;
  389.  
  390. lcd.FontColor = new Color( 0,255,0 );
  391. lcd.BackgroundColor = new Color( 0,0,0 );
  392. lcd.FontSize = 1.5f;
  393.  
  394. lcd.WriteText( sb.ToString() );
  395. } catch( Exception e ) {
  396. Echo( "Caught: "+ e );
  397. }
  398.  
  399. }
  400.  
  401.  
  402. /*
  403. * Run Once in a perfect world
  404. * Set Default States
  405. */
  406. public void Setup() {
  407. SetOxygenProduction( false );
  408.  
  409. GB_O2_Alert = false;
  410. SetSound( false, GS_O2_SountTag );
  411.  
  412. SetLightColor( new Color( 255, 0, 255 ), GS_LightTag );
  413.  
  414. CheckTankStatus(
  415. out GD_Tank_Total, out GI_NumOfTanks,
  416. out GD_Tank_Avail, out GI_NumOfFree,
  417. out GD_Tank_Store, out GI_NumOfStore
  418. );
  419. GD_Tank_History = GD_Tank_Avail;
  420.  
  421. //if( ENABLE_AIRLOCK_NAMAGER ) {
  422. //DoorTest();
  423. //}
  424. }
  425.  
  426.  
  427.  
  428. // FIX01
  429. public int SetOxygenProduction( bool on ) {
  430. var block = new List<IMyTerminalBlock>();
  431. GridTerminalSystem.GetBlocksOfType<IMyGasGenerator>( block, x=> IsSubjectGrid( x ) && x.CustomName.Contains( GS_O2_GenTag ) );
  432. if( block.Count > 0 ) {
  433. string Act;
  434. if( on ) {
  435. Act = "OnOff_On";
  436. } else {
  437. Act = "OnOff_Off";
  438. }
  439. for( int e = 0; e < block.Count; e++ ) {
  440. //if( (block[e] as IMyFunctionalBlock).Enabled != on ) { // minimuse number of block updates?
  441. block[e].ApplyAction( Act );
  442. //}
  443. }
  444. }
  445. return block.Count;
  446. }
  447.  
  448.  
  449.  
  450. public double CheckTankStatus( out double Total, out int NoT, out double Free, out int NoF, out double Stored, out int NoS ) {
  451. var blocks = new List<IMyTerminalBlock>();
  452. GridTerminalSystem.GetBlocksOfType<IMyGasTank>( blocks, x => IsSubjectGrid( x ) && x.CustomName.Contains( GS_O2_TankTag ) && x.BlockDefinition.TypeIdString.Contains( "OxygenTank" ) );
  453. double TP;
  454. Total = 0;
  455. Free = 0;
  456. Stored = 0;
  457. NoF = 0;
  458. NoS = 0;
  459. for( int e = 0; e < blocks.Count; e++ ) {
  460. var tank = blocks[e] as IMyGasTank;
  461. TP = tank.FilledRatio;
  462. if( tank.Stockpile ) {
  463. Stored += TP;
  464. NoS += 1;
  465. } else {
  466. Free += TP;
  467. NoF += 1;
  468. }
  469. }
  470.  
  471. NoT = NoS+NoF;
  472. Total = 0;
  473. if( NoT != 0 ) {
  474. Total = ((Stored+Free)/NoT) * 100;
  475. }
  476. if( NoF != 0 ) {
  477. Free = (Free/NoF) * 100;
  478. }
  479. if( NoS != 0 ) {
  480. Stored = (Stored/NoS) * 100;
  481. }
  482.  
  483. return Total;
  484. }
  485.  
  486.  
  487.  
  488. void SetSound( bool on, string tag = null ) {
  489. var blocks = new List<IMyTerminalBlock>();
  490. GridTerminalSystem.GetBlocksOfType<IMySoundBlock>( blocks, x => IsSubjectGrid( x ) && x.CustomName.Contains( tag ) );
  491. if( blocks.Count != 0 ) {
  492. string act = "PlaySound";
  493. if( !on ) {
  494. act = "StopSound";
  495. }
  496. for( int e = 0; e < blocks.Count; e++ ) {
  497. var sound = blocks[e] as IMySoundBlock;
  498. Echo( "Sound: "+ sound.SelectedSound );
  499. if( sound.SelectedSound != GA_SoundAlert[0] ) {
  500. sound.SelectedSound = GA_SoundAlert[1];
  501. }
  502. blocks[e].ApplyAction( act );
  503. }
  504. }
  505. }
  506.  
  507.  
  508.  
  509. public void SetLightColor( Color col, string tag = null, float blink=0, float hold=0, float offset=0 ) {
  510. var blocks = new List<IMyTerminalBlock>();
  511. GridTerminalSystem.GetBlocksOfType<IMyLightingBlock>( blocks, x => IsSubjectGrid( x ) && x.CustomName.Contains( tag ) );
  512. for( int e = 0; e < blocks.Count; e++ ) {
  513. var light = blocks[e] as IMyLightingBlock;
  514. light.Color = col;
  515. light.BlinkIntervalSeconds = blink;
  516. light.BlinkLength = hold;
  517. light.BlinkOffset = offset;
  518. }
  519. }
  520.  
  521.  
  522.  
  523. ///////////////////////
  524. //// FILTERS ////
  525. ///////////////////////
  526.  
  527. public bool IsSubjectGrid( IMyTerminalBlock block ) {
  528. // massivly simplified thanks to more current avilable built in grid filtering methods
  529. if( GB_ControlSubGrids ) {
  530. return Me.CubeGrid.IsSameConstructAs( block.CubeGrid );
  531. }
  532. return IsLocalGrid( block );
  533. }
  534.  
  535.  
  536. /* FILTER
  537. * for grid refferane of blocks
  538. */
  539. public bool IsLocalGrid( IMyTerminalBlock block ) { // seperated out to make it clear what it is doing
  540. if( block.CubeGrid == Me.CubeGrid ) {
  541. return true;
  542. }
  543. return false;
  544. }
  545.  
  546.  
  547.  
  548. /*
  549. * DEBUGGING TOOLS
  550. */
  551.  
  552.  
  553.  
  554. private string DumpProps( IMyTerminalBlock block ) {
  555. List<ITerminalProperty> props = new List<ITerminalProperty>();
  556. block.GetProperties( props );
  557. StringBuilder sb = new StringBuilder( "__PROPS__" ).Append( Environment.NewLine );
  558. for( int i = 0; i < props.Count; i++ ) {
  559. sb.Append( props[i].Id +" : "+ props[i].TypeName ).Append( Environment.NewLine );
  560. }
  561. return sb.ToString();
  562. }
  563.  
  564.  
  565. private string DumpActs( IMyTerminalBlock block ) {
  566. List<ITerminalAction> acts = new List<ITerminalAction>();
  567. block.GetActions( acts );
  568. StringBuilder sb = new StringBuilder( "__ACTS__" ).Append( Environment.NewLine );
  569. for( int i = 0; i < acts.Count; i++ ) {
  570. sb.Append( acts[i].Id +" : "+ acts[i].Name.ToString() ).Append( Environment.NewLine );
  571. }
  572. return sb.ToString();
  573. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement