Advertisement
blazemonger

TIM modifications

Jul 25th, 2018
166
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 99.92 KB | None | 0 0
  1. /*
  2. Taleden's Inventory Manager
  3. version 1.6.5 hotfix (2018-07-22)
  4.  
  5. Hotfix by Blazemonger to support 1.187 release
  6.  
  7. "There are some who call me... TIM?"
  8.  
  9. Steam Workshop: http://steamcommunity.com/sharedfiles/filedetails/?id=546825757
  10. User's Guide: http://steamcommunity.com/sharedfiles/filedetails/?id=546909551
  11.  
  12.  
  13. **********************
  14. ADVANCED CONFIGURATION
  15.  
  16. The settings below may be changed if you like, but read the notes and remember
  17. that any changes will be reverted when you update the script from the workshop.
  18. */
  19.  
  20. // Each "Type/" section can have multiple "/Subtype"s, which are formatted like
  21. // "/Subtype,MinQta,PctQta,Label,Blueprint". Label and Blueprint specified only
  22. // if different from Subtype, but Ingot and Ore have no Blueprint. Quota values
  23. // are based on material requirements for various blueprints (some built in to
  24. // the game, some from the community workshop).
  25. const string DEFAULT_ITEMS = @"
  26. AmmoMagazine/
  27. /Missile200mm
  28. /NATO_25x184mm,,,,NATO_25x184mmMagazine
  29. /NATO_5p56x45mm,,,,NATO_5p56x45mmMagazine
  30.  
  31. Component/
  32. /BulletproofGlass,50,2%
  33. /Computer,30,5%,,ComputerComponent
  34. /Construction,150,20%,,ConstructionComponent
  35. /Detector,10,0.1%,,DetectorComponent
  36. /Display,10,0.5%
  37. /Explosives,5,0.1%,,ExplosivesComponent
  38. /Girder,10,0.5%,,GirderComponent
  39. /GravityGenerator,1,0.1%,GravityGen,GravityGeneratorComponent
  40. /InteriorPlate,100,10%
  41. /LargeTube,10,2%
  42. /Medical,15,0.1%,,MedicalComponent
  43. /MetalGrid,20,2%
  44. /Motor,20,4%,,MotorComponent
  45. /PowerCell,20,1%
  46. /RadioCommunication,10,0.5%,RadioComm,RadioCommunicationComponent
  47. /Reactor,25,2%,,ReactorComponent
  48. /SmallTube,50,3%
  49. /SolarCell,20,0.1%
  50. /SteelPlate,150,40%
  51. /Superconductor,10,1%
  52. /Thrust,15,5%,,ThrustComponent
  53.  
  54. GasContainerObject/
  55. /HydrogenBottle
  56.  
  57. Ingot/
  58. /Cobalt,50,3.5%
  59. /Gold,5,0.2%
  60. /Iron,200,88%
  61. /Magnesium,5,0.1%
  62. /Nickel,30,1.5%
  63. /Platinum,5,0.1%
  64. /Silicon,50,2%
  65. /Silver,20,1%
  66. /Stone,50,2.5%
  67. /Uranium,1,0.1%
  68.  
  69. Ore/
  70. /Cobalt
  71. /Gold
  72. /Ice
  73. /Iron
  74. /Magnesium
  75. /Nickel
  76. /Platinum
  77. /Scrap
  78. /Silicon
  79. /Silver
  80. /Stone
  81. /Uranium
  82.  
  83. OxygenContainerObject/
  84. /OxygenBottle
  85.  
  86. PhysicalGunObject/
  87. /AngleGrinderItem,,,,AngleGrinder
  88. /AngleGrinder2Item,,,,AngleGrinder2
  89. /AngleGrinder3Item,,,,AngleGrinder3
  90. /AngleGrinder4Item,,,,AngleGrinder4
  91. /AutomaticRifleItem,,,AutomaticRifle,AutomaticRifle
  92. /HandDrillItem,,,,HandDrill
  93. /HandDrill2Item,,,,HandDrill2
  94. /HandDrill3Item,,,,HandDrill3
  95. /HandDrill4Item,,,,HandDrill4
  96. /PreciseAutomaticRifleItem,,,PreciseAutomaticRifle,PreciseAutomaticRifle
  97. /RapidFireAutomaticRifleItem,,,RapidFireAutomaticRifle,RapidFireAutomaticRifle
  98. /UltimateAutomaticRifleItem,,,UltimateAutomaticRifle,UltimateAutomaticRifle
  99. /WelderItem,,,,Welder
  100. /Welder2Item,,,,Welder2
  101. /Welder3Item,,,,Welder3
  102. /Welder4Item,,,,Welder4
  103. ";
  104.  
  105. // Item types which may have quantities which are not whole numbers.
  106. static readonly HashSet<string> FRACTIONAL_TYPES = new HashSet<string> { "INGOT", "ORE" };
  107.  
  108. // Ore subtypes which refine into Ingots with a different subtype name, or
  109. // which cannot be refined at all (if set to "").
  110. static readonly Dictionary<string,string> ORE_PRODUCT = new Dictionary<string,string> { {"ICE",""}, {"ORGANIC",""}, {"SCRAP","IRON"} };
  111.  
  112. // Block types/subtypes which restrict item types/subtypes from their first
  113. // inventory. Missing or "*" subtype indicates all subtypes of the given type.
  114. const string DEFAULT_RESTRICTIONS =
  115. MOB+"Assembler:AmmoMagazine,Component,GasContainerObject,Ore,OxygenContainerObject,PhysicalGunObject\n"+
  116. MOB+"InteriorTurret:AmmoMagazine/Missile200mm,AmmoMagazine/NATO_25x184mm,"+NON_AMMO+
  117. MOB+"LargeGatlingTurret:AmmoMagazine/Missile200mm,AmmoMagazine/NATO_5p56x45mm,"+NON_AMMO+
  118. MOB+"LargeMissileTurret:AmmoMagazine/NATO_25x184mm,AmmoMagazine/NATO_5p56x45mm,"+NON_AMMO+
  119. MOB+"OxygenGenerator:AmmoMagazine,Component,Ingot,Ore/Cobalt,Ore/Gold,Ore/Iron,Ore/Magnesium,Ore/Nickel,Ore/Organic,Ore/Platinum,Ore/Scrap,Ore/Silicon,Ore/Silver,Ore/Stone,Ore/Uranium,PhysicalGunObject\n"+
  120. MOB+"OxygenTank:AmmoMagazine,Component,GasContainerObject,Ingot,Ore,PhysicalGunObject\n"+
  121. MOB+"OxygenTank/LargeHydrogenTank:AmmoMagazine,Component,Ingot,Ore,OxygenContainerObject,PhysicalGunObject\n"+
  122. MOB+"OxygenTank/SmallHydrogenTank:AmmoMagazine,Component,Ingot,Ore,OxygenContainerObject,PhysicalGunObject\n"+
  123. MOB+"Reactor:AmmoMagazine,Component,GasContainerObject,Ingot/Cobalt,Ingot/Gold,Ingot/Iron,Ingot/Magnesium,Ingot/Nickel,Ingot/Platinum,Ingot/Scrap,Ingot/Silicon,Ingot/Silver,Ingot/Stone,Ore,OxygenContainerObject,PhysicalGunObject\n"+
  124. MOB+"Refinery:AmmoMagazine,Component,GasContainerObject,Ingot,Ore/Ice,Ore/Organic,OxygenContainerObject,PhysicalGunObject\n"+
  125. MOB+"Refinery/Blast Furnace:AmmoMagazine,Component,GasContainerObject,Ingot,Ore/Gold,Ore/Ice,Ore/Magnesium,Ore/Organic,Ore/Platinum,Ore/Silicon,Ore/Silver,Ore/Stone,Ore/Uranium,OxygenContainerObject,PhysicalGunObject\n"+
  126. MOB+"SmallGatlingGun:AmmoMagazine/Missile200mm,AmmoMagazine/NATO_5p56x45mm,"+NON_AMMO+
  127. MOB+"SmallMissileLauncher:AmmoMagazine/NATO_25x184mm,AmmoMagazine/NATO_5p56x45mm,"+NON_AMMO+
  128. MOB+"SmallMissileLauncherReload:AmmoMagazine/NATO_25x184mm,AmmoMagazine/NATO_5p56x45mm,"+NON_AMMO
  129. ;
  130.  
  131. /* *************
  132. SCRIPT INTERNALS
  133.  
  134. Do not edit anything below unless you're sure you know what you're doing!
  135. */
  136.  
  137. const int VERS_MAJ = 1, VERS_MIN = 6, VERS_REV = 5;
  138. const string VERS_UPD = "2018-07-25";
  139. const int VERSION = (VERS_MAJ*1000000)+(VERS_MIN*1000)+VERS_REV;
  140.  
  141. const int MAX_CYCLE_STEPS = 11, CYCLE_LENGTH = 1;
  142. const bool REWRITE_TAGS = true, QUOTA_STABLE = true;
  143. const char TAG_OPEN = '[', TAG_CLOSE = ']';
  144. const string TAG_PREFIX = "TIM";
  145. const bool SCAN_COLLECTORS = false, SCAN_DRILLS = false, SCAN_GRINDERS = false, SCAN_WELDERS = false;
  146. const string MOB = "MyObjectBuilder_";
  147. const string NON_AMMO = "Component,GasContainerObject,Ingot,Ore,OxygenContainerObject,PhysicalGunObject\n";
  148. const StringComparison OIC = StringComparison.OrdinalIgnoreCase;
  149. const StringSplitOptions REE = StringSplitOptions.RemoveEmptyEntries;
  150. static readonly char[] SPACE = new char[] {' ','\t','\u00AD'}, COLON = new char[] {':'}, NEWLINE = new char[] {'\r','\n'}, SPACECOMMA = new char[] {' ','\t','\u00AD',','};
  151. struct Quota { public int min; public float ratio; public Quota(int m, float r) { min=m; ratio=r; } }
  152. struct Pair { public int a,b; public Pair(int aa, int bb) { a=aa; b=bb; } }
  153. struct Item { public string itype, isub; public Item(string t, string s) { itype=t; isub=s; } }
  154. struct Work { public Item item; public double qty; public Work(Item i, double q) { item=i; qty=q; } }
  155.  
  156. static int lastVersion = 0;
  157. static string statsHeader = "";
  158. static string[] statsLog = new string[12];
  159. static long numCalls = 0;
  160. static double sinceLast = 0.0;
  161. static int numXfers, numRefs, numAsms;
  162. static int cycleLength = CYCLE_LENGTH, cycleStep = 0;
  163. static bool rewriteTags = REWRITE_TAGS;
  164. static char tagOpen = TAG_OPEN, tagClose = TAG_CLOSE;
  165. static string tagPrefix = TAG_PREFIX;
  166. static System.Text.RegularExpressions.Regex tagRegex = null;
  167. static string panelFiller = "";
  168. static bool foundNewItem = false;
  169.  
  170. static Dictionary<Item,Quota> defaultQuota = new Dictionary<Item,Quota>();
  171. static Dictionary<string,Dictionary<string,Dictionary<string,HashSet<string>>>> blockSubTypeRestrictions = new Dictionary<string,Dictionary<string,Dictionary<string,HashSet<string>>>>();
  172. static HashSet<IMyCubeGrid> dockedgrids = new HashSet<IMyCubeGrid>();
  173. static List<string> types = new List<string>();
  174. static Dictionary<string,string> typeLabel = new Dictionary<string,string>();
  175. static Dictionary<string,List<string>> typeSubs = new Dictionary<string,List<string>>();
  176. static Dictionary<string,long> typeAmount = new Dictionary<string,long>();
  177. static List<string> subs = new List<string>();
  178. static Dictionary<string,string> subLabel = new Dictionary<string,string>();
  179. static Dictionary<string,List<string>> subTypes = new Dictionary<string,List<string>>();
  180. static Dictionary<string,Dictionary<string,ItemData>> typeSubData = new Dictionary<string,Dictionary<string,ItemData>>();
  181. static Dictionary<MyDefinitionId,Item> blueprintItem = new Dictionary<MyDefinitionId,Item>();
  182. static Dictionary<int,Dictionary<string,Dictionary<string,Dictionary<IMyInventory,long>>>> priTypeSubInvenRequest = new Dictionary<int,Dictionary<string,Dictionary<string,Dictionary<IMyInventory,long>>>>();
  183. static Dictionary<IMyTextPanel,int> qpanelPriority = new Dictionary<IMyTextPanel,int>();
  184. static Dictionary<IMyTextPanel,List<string>> qpanelTypes = new Dictionary<IMyTextPanel,List<string>>();
  185. static Dictionary<IMyTextPanel,List<string>> ipanelTypes = new Dictionary<IMyTextPanel,List<string>>();
  186. static List<IMyTextPanel> statusPanels = new List<IMyTextPanel>();
  187. static List<IMyTextPanel> debugPanels = new List<IMyTextPanel>();
  188. static HashSet<string> debugLogic = new HashSet<string>();
  189. static List<string> debugText = new List<string>();
  190. static Dictionary<IMyTerminalBlock,System.Text.RegularExpressions.Match> blockGtag = new Dictionary<IMyTerminalBlock,System.Text.RegularExpressions.Match>();
  191. static Dictionary<IMyTerminalBlock,System.Text.RegularExpressions.Match> blockTag = new Dictionary<IMyTerminalBlock,System.Text.RegularExpressions.Match>();
  192. static HashSet<IMyInventory> invenLocked = new HashSet<IMyInventory>();
  193. static HashSet<IMyInventory> invenHidden = new HashSet<IMyInventory>();
  194. static Dictionary<IMyRefinery,HashSet<string>> refineryOres = new Dictionary<IMyRefinery,HashSet<string>>();
  195. static Dictionary<IMyAssembler,HashSet<Item>> assemblerItems = new Dictionary<IMyAssembler,HashSet<Item>>();
  196. static Dictionary<IMyFunctionalBlock,Work> producerWork = new Dictionary<IMyFunctionalBlock,Work>();
  197. static Dictionary<IMyFunctionalBlock,int> producerJam = new Dictionary<IMyFunctionalBlock,int>();
  198. static Dictionary<IMyTextPanel,Pair> panelSpan = new Dictionary<IMyTextPanel,Pair>();
  199. static Dictionary<IMyTerminalBlock,HashSet<IMyTerminalBlock>> blockErrors = new Dictionary<IMyTerminalBlock,HashSet<IMyTerminalBlock>>();
  200.  
  201.  
  202. private class ItemData {
  203. public string itype, isub, label;
  204. public MyDefinitionId blueprint;
  205. public long amount, avail, locked, quota, minimum;
  206. public float ratio;
  207. public int qpriority, hold, jam;
  208. public Dictionary<IMyInventory,long> invenTotal;
  209. public Dictionary<IMyInventory,int> invenSlot;
  210. public HashSet<IMyFunctionalBlock> producers;
  211. public Dictionary<string,double> prdSpeed;
  212.  
  213. public static void Init(string itype, string isub, long minimum=0L, float ratio=0.0f, string label="", string blueprint="") {
  214. string itypelabel=itype, isublabel=isub;
  215. itype = itype.ToUpper();
  216. isub = isub.ToUpper();
  217.  
  218. // new type?
  219. if (!typeSubs.ContainsKey(itype)) {
  220. types.Add(itype);
  221. typeLabel[itype] = itypelabel;
  222. typeSubs[itype] = new List<string>();
  223. typeAmount[itype] = 0L;
  224. typeSubData[itype] = new Dictionary<string,ItemData>();
  225. }
  226.  
  227. // new subtype?
  228. if (!subTypes.ContainsKey(isub)) {
  229. subs.Add(isub);
  230. subLabel[isub] = isublabel;
  231. subTypes[isub] = new List<string>();
  232. }
  233.  
  234. // new type/subtype pair?
  235. if (!typeSubData[itype].ContainsKey(isub)) {
  236. foundNewItem = true;
  237. typeSubs[itype].Add(isub);
  238. subTypes[isub].Add(itype);
  239. typeSubData[itype][isub] = new ItemData(itype, isub, minimum, ratio, (label == "") ? isublabel : label, (blueprint == "") ? isublabel : blueprint);
  240. if (blueprint != null)
  241. blueprintItem[typeSubData[itype][isub].blueprint] = new Item(itype,isub);
  242. }
  243. } // Init()
  244.  
  245. private ItemData(string itype, string isub, long minimum, float ratio, string label, string blueprint) {
  246. this.itype = itype;
  247. this.isub = isub;
  248. this.label = label;
  249. this.blueprint = (blueprint == null) ? default(MyDefinitionId) : MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/" + blueprint);
  250. this.amount = this.avail = this.locked = this.quota = 0L;
  251. this.minimum = (long)((double)minimum * 1000000.0 + 0.5);
  252. this.ratio = (ratio / 100.0f);
  253. this.qpriority = -1;
  254. this.hold = this.jam = 0;
  255. this.invenTotal = new Dictionary<IMyInventory,long>();
  256. this.invenSlot = new Dictionary<IMyInventory,int>();
  257. this.producers = new HashSet<IMyFunctionalBlock>();
  258. this.prdSpeed = new Dictionary<string,double>();
  259. } // ItemData()
  260. } // ItemData
  261.  
  262.  
  263. /*
  264. * UTILITY FUNCTIONS
  265. */
  266.  
  267.  
  268. void InitItems(string data) {
  269. string itype="";
  270. long minimum;
  271. float ratio;
  272. foreach (string line in data.Split(NEWLINE, REE)) {
  273. string[] words = (line.Trim()+",,,,").Split(SPACECOMMA, 6);
  274. words[0] = words[0].Trim();
  275. if (words[0].EndsWith("/")) {
  276. itype = words[0].Substring(0, words[0].Length - 1);
  277. } else if (itype != "" & words[0].StartsWith("/")) {
  278. long.TryParse(words[1], out minimum);
  279. float.TryParse(words[2].Substring(0, (words[2]+"%").IndexOf("%")), out ratio);
  280. ItemData.Init(itype, words[0].Substring(1), minimum, ratio, words[3].Trim(), (itype == "Ingot" | itype == "Ore") ? null : words[4].Trim());
  281. }
  282. }
  283. } // InitItems()
  284.  
  285.  
  286. void InitBlockRestrictions(string data) {
  287. foreach (string line in data.Split(NEWLINE, REE)) {
  288. string[] blockitems = (line+":").Split(':');
  289. string[] block = (blockitems[0]+"/*").Split('/');
  290. foreach (string item in blockitems[1].Split(',')) {
  291. string[] typesub = item.ToUpper().Split('/');
  292. AddBlockRestriction(block[0].Trim(SPACE), block[1].Trim(SPACE), typesub[0], ((typesub.Length > 1) ? typesub[1] : null), true);
  293. }
  294. }
  295. } // InitBlockRestrictions()
  296.  
  297.  
  298. void AddBlockRestriction(string btype, string bsub, string itype, string isub, bool init=false) {
  299. Dictionary<string,Dictionary<string,HashSet<string>>> bsubItypeRestr;
  300. Dictionary<string,HashSet<string>> itypeRestr;
  301. HashSet<string> restr;
  302.  
  303. if (!blockSubTypeRestrictions.TryGetValue(btype.ToUpper(), out bsubItypeRestr))
  304. blockSubTypeRestrictions[btype.ToUpper()] = bsubItypeRestr = new Dictionary<string,Dictionary<string,HashSet<string>>> { { "*", new Dictionary<string,HashSet<string>>() } };
  305. if (!bsubItypeRestr.TryGetValue(bsub.ToUpper(), out itypeRestr)) {
  306. bsubItypeRestr[bsub.ToUpper()] = itypeRestr = new Dictionary<string,HashSet<string>>();
  307. if (bsub != "*" & !init) {
  308. foreach (KeyValuePair<string,HashSet<string>> pair in bsubItypeRestr["*"])
  309. itypeRestr[pair.Key] = ((pair.Value != null) ? (new HashSet<string>(pair.Value)) : null);
  310. }
  311. }
  312. if (isub == null | isub == "*") {
  313. itypeRestr[itype] = null;
  314. } else {
  315. (itypeRestr.TryGetValue(itype, out restr) ? restr : (itypeRestr[itype] = new HashSet<string>())).Add(isub);
  316. }
  317. if (!init) debugText.Add(btype+"/"+bsub+" does not accept "+typeLabel[itype]+"/"+subLabel[isub]);
  318. } // AddBlockRestriction()
  319.  
  320.  
  321. bool BlockAcceptsTypeSub(IMyCubeBlock block, string itype, string isub) {
  322. Dictionary<string,Dictionary<string,HashSet<string>>> bsubItypeRestr;
  323. Dictionary<string,HashSet<string>> itypeRestr;
  324. HashSet<string> restr;
  325.  
  326. if (blockSubTypeRestrictions.TryGetValue(block.BlockDefinition.TypeIdString.ToUpper(), out bsubItypeRestr)) {
  327. bsubItypeRestr.TryGetValue(block.BlockDefinition.SubtypeName.ToUpper(), out itypeRestr);
  328. if ((itypeRestr ?? bsubItypeRestr["*"]).TryGetValue(itype, out restr))
  329. return !(restr == null || restr.Contains(isub));
  330. }
  331. return true;
  332. } // BlockAcceptsTypeSub()
  333.  
  334.  
  335. HashSet<string> GetBlockAcceptedSubs(IMyCubeBlock block, string itype, HashSet<string> mysubs=null) {
  336. Dictionary<string,Dictionary<string,HashSet<string>>> bsubItypeRestr;
  337. Dictionary<string,HashSet<string>> itypeRestr;
  338. HashSet<string> restr;
  339.  
  340. mysubs = mysubs ?? new HashSet<string>(typeSubs[itype]);
  341. if (blockSubTypeRestrictions.TryGetValue(block.BlockDefinition.TypeIdString.ToUpper(), out bsubItypeRestr)) {
  342. bsubItypeRestr.TryGetValue(block.BlockDefinition.SubtypeName.ToUpper(), out itypeRestr);
  343. if ((itypeRestr ?? bsubItypeRestr["*"]).TryGetValue(itype, out restr))
  344. mysubs.ExceptWith(restr ?? mysubs);
  345. }
  346. return mysubs;
  347. } // GetBlockAcceptedSubs()
  348.  
  349.  
  350. string GetBlockImpliedType(IMyCubeBlock block, string isub) {
  351. string rtype = null;
  352. foreach (string itype in subTypes[isub]) {
  353. if (BlockAcceptsTypeSub(block, itype, isub)) {
  354. if (rtype != null)
  355. return null;
  356. rtype = itype;
  357. }
  358. }
  359. return rtype;
  360. } // GetBlockImpliedType()
  361.  
  362.  
  363. string GetShorthand(long amount) {
  364. long scale;
  365. if (amount <= 0L)
  366. return "0";
  367. if (amount < 10000L)
  368. return "< 0.01";
  369. if (amount >= 100000000000000L)
  370. return "" + (amount / 1000000000000L) + " M";
  371. scale = (long)Math.Pow(10.0, Math.Floor(Math.Log10(amount)) - 2.0);
  372. amount = (long)((double)amount / scale + 0.5) * scale;
  373. if (amount < 1000000000L)
  374. return (amount / 1e6).ToString("0.##");
  375. if (amount < 1000000000000L)
  376. return (amount / 1e9).ToString("0.##") + " K";
  377. return (amount / 1e12).ToString("0.##") + " M";
  378. } // GetShorthand()
  379.  
  380.  
  381. /*
  382. * GRID FUNCTIONS
  383. */
  384.  
  385.  
  386. void ScanGrids() {
  387. List<IMyTerminalBlock> blocks = new List<IMyTerminalBlock>();
  388. IMyCubeGrid g1, g2;
  389. Dictionary<IMyCubeGrid,HashSet<IMyCubeGrid>> gridLinks = new Dictionary<IMyCubeGrid,HashSet<IMyCubeGrid>>();
  390. Dictionary<IMyCubeGrid,int> gridShip = new Dictionary<IMyCubeGrid,int>();
  391. List<HashSet<IMyCubeGrid>> shipGrids = new List<HashSet<IMyCubeGrid>>();
  392. List<string> shipName = new List<string>();
  393. HashSet<IMyCubeGrid> grids;
  394. List<IMyCubeGrid> gqueue = new List<IMyCubeGrid>(); // actual Queue lacks AddRange
  395. int q, s1, s2;
  396. IMyShipConnector conn2;
  397. HashSet<string> tags1 = new HashSet<string>();
  398. HashSet<string> tags2 = new HashSet<string>();
  399. System.Text.RegularExpressions.Match match;
  400. Dictionary<int,Dictionary<int,List<string>>> shipShipDocks = new Dictionary<int,Dictionary<int,List<string>>>();
  401. Dictionary<int,List<string>> shipDocks;
  402. List<string> docks;
  403. HashSet<int> ships = new HashSet<int>();
  404. Queue<int> squeue = new Queue<int>();
  405.  
  406. // find mechanical links
  407. GridTerminalSystem.GetBlocksOfType<IMyMechanicalConnectionBlock>(blocks);
  408. foreach (IMyTerminalBlock block in blocks) {
  409. g1 = block.CubeGrid;
  410. g2 = (block as IMyMechanicalConnectionBlock).TopGrid;
  411. if (g2 == null)
  412. continue;
  413. (gridLinks.TryGetValue(g1, out grids) ? grids : (gridLinks[g1] = new HashSet<IMyCubeGrid>())).Add(g2);
  414. (gridLinks.TryGetValue(g2, out grids) ? grids : (gridLinks[g2] = new HashSet<IMyCubeGrid>())).Add(g1);
  415. }
  416.  
  417. // each connected component of mechanical links is a "ship"
  418. foreach (IMyCubeGrid grid in gridLinks.Keys) {
  419. if (!gridShip.ContainsKey(grid)) {
  420. s1 = (grid.Max - grid.Min + Vector3I.One).Size;
  421. g1 = grid;
  422. gridShip[grid] = shipGrids.Count;
  423. grids = new HashSet<IMyCubeGrid> { grid };
  424. gqueue.Clear();
  425. gqueue.AddRange(gridLinks[grid]);
  426. for (q = 0; q < gqueue.Count; q++) {
  427. g2 = gqueue[q];
  428. if (!grids.Add(g2))
  429. continue;
  430. s2 = (g2.Max - g2.Min + Vector3I.One).Size;
  431. g1 = (s2 > s1) ? g2 : g1;
  432. s1 = (s2 > s1) ? s2 : s1;
  433. gridShip[g2] = shipGrids.Count;
  434. gqueue.AddRange(gridLinks[g2].Except(grids));
  435. }
  436. shipGrids.Add(grids);
  437. shipName.Add(g1.CustomName);
  438. }
  439. }
  440.  
  441. // connectors require at least one shared dock tag, or no tags on either
  442. GridTerminalSystem.GetBlocksOfType<IMyShipConnector>(blocks);
  443. foreach (IMyTerminalBlock block in blocks) {
  444. conn2 = (block as IMyShipConnector).OtherConnector;
  445. if (conn2 != null && (block.EntityId < conn2.EntityId & (block as IMyShipConnector).Status == MyShipConnectorStatus.Connected)) {
  446. tags1.Clear();
  447. tags2.Clear();
  448. if ((match = tagRegex.Match(block.CustomName)).Success) {
  449. foreach (string attr in match.Groups[1].Captures[0].Value.Split(SPACECOMMA, REE)) {
  450. if (attr.StartsWith("DOCK:", OIC))
  451. tags1.UnionWith(attr.Substring(5).ToUpper().Split(COLON, REE));
  452. }
  453. }
  454. if ((match = tagRegex.Match(conn2.CustomName)).Success) {
  455. foreach (string attr in match.Groups[1].Captures[0].Value.Split(SPACECOMMA, REE)) {
  456. if (attr.StartsWith("DOCK:", OIC))
  457. tags2.UnionWith(attr.Substring(5).ToUpper().Split(COLON, REE));
  458. }
  459. }
  460. if ((tags1.Count > 0 | tags2.Count > 0) & !tags1.Overlaps(tags2))
  461. continue;
  462. g1 = block.CubeGrid;
  463. g2 = conn2.CubeGrid;
  464. if (!gridShip.TryGetValue(g1, out s1)) {
  465. gridShip[g1] = s1 = shipGrids.Count;
  466. shipGrids.Add(new HashSet<IMyCubeGrid> { g1 });
  467. shipName.Add(g1.CustomName);
  468. }
  469. if (!gridShip.TryGetValue(g2, out s2)) {
  470. gridShip[g2] = s2 = shipGrids.Count;
  471. shipGrids.Add(new HashSet<IMyCubeGrid> { g2 });
  472. shipName.Add(g2.CustomName);
  473. }
  474. ((shipShipDocks.TryGetValue(s1, out shipDocks) ? shipDocks : (shipShipDocks[s1] = new Dictionary<int,List<string>>())).TryGetValue(s2, out docks) ? docks : (shipShipDocks[s1][s2] = new List<string>())).Add(block.CustomName);
  475. ((shipShipDocks.TryGetValue(s2, out shipDocks) ? shipDocks : (shipShipDocks[s2] = new Dictionary<int,List<string>>())).TryGetValue(s1, out docks) ? docks : (shipShipDocks[s2][s1] = new List<string>())).Add(conn2.CustomName);
  476. }
  477. }
  478.  
  479. // starting "here", traverse all docked ships
  480. dockedgrids.Clear();
  481. dockedgrids.Add(Me.CubeGrid);
  482. if (!gridShip.TryGetValue(Me.CubeGrid, out s1))
  483. return;
  484. ships.Add(s1);
  485. dockedgrids.UnionWith(shipGrids[s1]);
  486. squeue.Enqueue(s1);
  487. while (squeue.Count > 0) {
  488. s1 = squeue.Dequeue();
  489. if (!shipShipDocks.TryGetValue(s1, out shipDocks))
  490. continue;
  491. foreach (int ship2 in shipDocks.Keys) {
  492. if (ships.Add(ship2)) {
  493. dockedgrids.UnionWith(shipGrids[ship2]);
  494. squeue.Enqueue(ship2);
  495. debugText.Add(shipName[ship2]+" docked to "+shipName[s1]+" at "+String.Join(", ",shipDocks[ship2]));
  496. }
  497. }
  498. }
  499. } // ScanGrids()
  500.  
  501.  
  502. /*
  503. * INVENTORY FUNCTIONS
  504. */
  505.  
  506.  
  507. void ScanGroups() {
  508. List<IMyBlockGroup> groups = new List<IMyBlockGroup>();
  509. List<IMyTerminalBlock> blocks = new List<IMyTerminalBlock>();
  510. System.Text.RegularExpressions.Match match;
  511.  
  512. GridTerminalSystem.GetBlockGroups(groups);
  513. foreach (IMyBlockGroup group in groups) {
  514. if ((match = tagRegex.Match(group.Name)).Success) {
  515. group.GetBlocks(blocks);
  516. foreach (IMyTerminalBlock block in blocks)
  517. blockGtag[block] = match;
  518. }
  519. }
  520. } // ScanGroups()
  521.  
  522.  
  523. void ScanBlocks<T>() where T: class {
  524. List<IMyTerminalBlock> blocks = new List<IMyTerminalBlock>();
  525. System.Text.RegularExpressions.Match match;
  526. int i, s, n;
  527. IMyInventory inven;
  528. List<IMyInventoryItem> stacks;
  529. string itype, isub;
  530. ItemData data;
  531. long amount, total;
  532.  
  533. GridTerminalSystem.GetBlocksOfType<T>(blocks);
  534. foreach (IMyTerminalBlock block in blocks) {
  535. if (!dockedgrids.Contains(block.CubeGrid))
  536. continue;
  537. match = tagRegex.Match(block.CustomName);
  538. if (match.Success) {
  539. blockGtag.Remove(block);
  540. blockTag[block] = match;
  541. } else if (blockGtag.TryGetValue(block, out match)) {
  542. blockTag[block] = match;
  543. }
  544.  
  545. if ((block is IMySmallMissileLauncher & !(block is IMySmallMissileLauncherReload | block.BlockDefinition.SubtypeName == "LargeMissileLauncher")) | block is IMyLargeInteriorTurret) {
  546. // can't sort with no conveyor port
  547. invenLocked.Add(block.GetInventory(0));
  548. } else if ((block is IMyFunctionalBlock) && ((block as IMyFunctionalBlock).Enabled & block.IsFunctional)) {
  549. if ((block is IMyRefinery | block is IMyReactor | block is IMyGasGenerator) & !blockTag.ContainsKey(block)) {
  550. // don't touch input of enabled and untagged refineries, reactors or oxygen generators
  551. invenLocked.Add(block.GetInventory(0));
  552. } else if (block is IMyAssembler && !(block as IMyAssembler).IsQueueEmpty) {
  553. // don't touch input of enabled and active assemblers
  554. invenLocked.Add(block.GetInventory(((block as IMyAssembler).Mode == MyAssemblerMode.Disassembly) ? 1 : 0));
  555. }
  556. }
  557.  
  558. i = block.InventoryCount;
  559. while (i-- > 0) {
  560. inven = block.GetInventory(i);
  561. stacks = inven.GetItems();
  562. s = stacks.Count;
  563. while (s-- > 0) {
  564. // identify the stacked item
  565. itype = ""+stacks[s].Content.TypeId;
  566. itype = itype.Substring(itype.LastIndexOf('_') + 1);
  567. isub = stacks[s].Content.SubtypeId.ToString();
  568.  
  569. // new type or subtype?
  570. ItemData.Init(itype, isub, 0L, 0.0f, stacks[s].Content.SubtypeId.ToString(), null);
  571. itype = itype.ToUpper();
  572. isub = isub.ToUpper();
  573.  
  574. // update amounts
  575. amount = (long)((double)stacks[s].Amount * 1e6);
  576. typeAmount[itype] += amount;
  577. data = typeSubData[itype][isub];
  578. data.amount += amount;
  579. data.avail += amount;
  580. data.invenTotal.TryGetValue(inven, out total);
  581. data.invenTotal[inven] = total + amount;
  582. data.invenSlot.TryGetValue(inven, out n);
  583. data.invenSlot[inven] = Math.Max(n, s+1);
  584. }
  585. }
  586. }
  587. } // ScanBlocks()
  588.  
  589.  
  590. void AdjustAmounts() {
  591. string itype, isub;
  592. long amount;
  593. ItemData data;
  594.  
  595. foreach (IMyInventory inven in invenHidden) {
  596. foreach (IMyInventoryItem stack in inven.GetItems()) {
  597. itype = ""+stack.Content.TypeId;
  598. itype = itype.Substring(itype.LastIndexOf('_') + 1).ToUpper();
  599. isub = stack.Content.SubtypeId.ToString().ToUpper();
  600.  
  601. amount = (long)((double)stack.Amount * 1e6);
  602. typeAmount[itype] -= amount;
  603. typeSubData[itype][isub].amount -= amount;
  604. }
  605. }
  606.  
  607. foreach (IMyInventory inven in invenLocked) {
  608. foreach (IMyInventoryItem stack in inven.GetItems()) {
  609. itype = ""+stack.Content.TypeId;
  610. itype = itype.Substring(itype.LastIndexOf('_') + 1).ToUpper();
  611. isub = stack.Content.SubtypeId.ToString().ToUpper();
  612.  
  613. amount = (long)((double)stack.Amount * 1e6);
  614. data = typeSubData[itype][isub];
  615. data.avail -= amount;
  616. data.locked += amount;
  617. }
  618. }
  619. } // AdjustAmounts()
  620.  
  621.  
  622. /*
  623. * TAG FUNCTIONS
  624. */
  625.  
  626.  
  627. void ParseBlockTags() {
  628. StringBuilder name = new StringBuilder();
  629. IMyTextPanel blkPnl;
  630. IMyRefinery blkRfn;
  631. IMyAssembler blkAsm;
  632. System.Text.RegularExpressions.Match match;
  633. int i, priority, spanwide, spantall;
  634. string[] attrs, fields;
  635. string attr, itype, isub;
  636. long amount;
  637. float ratio;
  638. bool grouped, force, egg=false;
  639.  
  640. // loop over all tagged blocks
  641. foreach (IMyTerminalBlock block in blockTag.Keys) {
  642. match = blockTag[block];
  643. attrs = match.Groups[1].Captures[0].Value.Split(SPACECOMMA, REE);
  644. name.Clear();
  645. if (!(grouped = blockGtag.ContainsKey(block))) {
  646. name.Append(block.CustomName, 0, match.Index);
  647. name.Append(tagOpen);
  648. if (tagPrefix != "")
  649. name.Append(tagPrefix + " ");
  650. }
  651.  
  652. // loop over all tag attributes
  653. if ((blkPnl = (block as IMyTextPanel)) != null) {
  654. foreach (string a in attrs) {
  655. attr = a.ToUpper();
  656. if (lastVersion < 1005903 & (i = attr.IndexOf(":P")) > 0 & typeSubData.ContainsKey(attr.Substring(0, Math.Min(attr.Length, Math.Max(0,i))))) {
  657. attr = "QUOTA:" + attr;
  658. } else if (lastVersion < 1005903 & typeSubData.ContainsKey(attr)) {
  659. attr = "INVEN:" + attr;
  660. }
  661. fields = attr.Split(COLON);
  662. attr = fields[0];
  663.  
  664. if (attr.Length >= 4 & "STATUS".StartsWith(attr)) {
  665. if (blkPnl.Enabled) statusPanels.Add(blkPnl);
  666. name.Append("STATUS ");
  667. } else if (attr.Length >= 5 & "DEBUGGING".StartsWith(attr)) {
  668. if (blkPnl.Enabled) debugPanels.Add(blkPnl);
  669. name.Append("DEBUG ");
  670. } else if (attr == "SPAN") {
  671. if (fields.Length >= 3 && (int.TryParse(fields[1], out spanwide) & int.TryParse(fields[2], out spantall) & spanwide >= 1 & spantall >= 1)) {
  672. panelSpan[blkPnl] = new Pair(spanwide, spantall);
  673. name.Append("SPAN:" + spanwide + ":" + spantall + " ");
  674. } else {
  675. name.Append((attr = String.Join(":", fields).ToLower()) + " ");
  676. debugText.Add("Invalid panel span rule: " + attr);
  677. }
  678. } else if (attr == "THE") {
  679. egg = true;
  680. } else if (attr == "ENCHANTER" & egg) {
  681. egg = false;
  682. blkPnl.SetValueFloat("FontSize", 0.2f);
  683. blkPnl.WritePublicTitle("TIM the Enchanter", false);
  684. blkPnl.WritePublicText(panelFiller, false);
  685. blkPnl.ShowPublicTextOnScreen();
  686. name.Append("THE ENCHANTER ");
  687. } else if (attr.Length >= 3 & "QUOTAS".StartsWith(attr)) {
  688. if (blkPnl.Enabled & !qpanelPriority.ContainsKey(blkPnl)) qpanelPriority[blkPnl] = 0;
  689. if (blkPnl.Enabled & !qpanelTypes.ContainsKey(blkPnl)) qpanelTypes[blkPnl] = new List<string>();
  690. name.Append("QUOTA");
  691. i = 0;
  692. while (++i < fields.Length) {
  693. if (ParseItemTypeSub(null, true, fields[i], "", out itype, out isub) & itype != "ORE" & isub == "") {
  694. if (blkPnl.Enabled) qpanelTypes[blkPnl].Add(itype);
  695. name.Append(":" + typeLabel[itype]);
  696. } else if (fields[i].StartsWith("P") & int.TryParse(fields[i].Substring(Math.Min(1, fields[i].Length)), out priority)) {
  697. if (blkPnl.Enabled) qpanelPriority[blkPnl] = Math.Max(0, priority);
  698. if (priority > 0) name.Append(":P" + priority);
  699. } else {
  700. name.Append(":" + fields[i].ToLower());
  701. debugText.Add("Invalid quota panel rule: " + fields[i].ToLower());
  702. }
  703. }
  704. name.Append(" ");
  705. } else if (attr.Length >= 3 & "INVENTORY".StartsWith(attr)) {
  706. if (blkPnl.Enabled & !ipanelTypes.ContainsKey(blkPnl)) ipanelTypes[blkPnl] = new List<string>();
  707. name.Append("INVEN");
  708. i = 0;
  709. while (++i < fields.Length) {
  710. if (ParseItemTypeSub(null, true, fields[i], "", out itype, out isub) & isub == "") {
  711. if (blkPnl.Enabled) ipanelTypes[blkPnl].Add(itype);
  712. name.Append(":" + typeLabel[itype]);
  713. } else {
  714. name.Append(":" + fields[i].ToLower());
  715. debugText.Add("Invalid inventory panel rule: " + fields[i].ToLower());
  716. }
  717. }
  718. name.Append(" ");
  719. } else {
  720. name.Append((attr = String.Join(":", fields).ToLower()) + " ");
  721. debugText.Add("Invalid panel attribute: " + attr);
  722. }
  723. }
  724. } else {
  725. blkRfn = (block as IMyRefinery);
  726. blkAsm = (block as IMyAssembler);
  727. foreach (string a in attrs) {
  728. attr = a.ToUpper();
  729. if (lastVersion < 1005900 & ((blkRfn != null & attr == "ORE") | (blkAsm != null & typeSubData["COMPONENT"].ContainsKey(attr)))) {
  730. attr = "AUTO";
  731. }
  732. fields = attr.Split(COLON);
  733. attr = fields[0];
  734.  
  735. if ((attr.Length >= 4 & "LOCKED".StartsWith(attr)) | attr == "EXEMPT") { // EXEMPT for AIS compat
  736. i = block.InventoryCount;
  737. while (i-- > 0)
  738. invenLocked.Add(block.GetInventory(i));
  739. name.Append(attr+" ");
  740. } else if (attr == "HIDDEN") {
  741. i = block.InventoryCount;
  742. while (i-- > 0)
  743. invenHidden.Add(block.GetInventory(i));
  744. name.Append("HIDDEN ");
  745. } else if ((block is IMyShipConnector) & attr == "DOCK") {
  746. // handled in ScanGrids(), just rewrite
  747. name.Append(String.Join(":", fields) + " ");
  748. } else if ((blkRfn != null | blkAsm != null) & attr == "AUTO") {
  749. name.Append("AUTO");
  750. HashSet<string> ores, autoores = (blkRfn == null | fields.Length > 1) ? (new HashSet<string>()) : GetBlockAcceptedSubs(blkRfn, "ORE");
  751. HashSet<Item> items, autoitems = new HashSet<Item>();
  752. i = 0;
  753. while (++i < fields.Length) {
  754. if (ParseItemTypeSub(null, true, fields[i], (blkRfn != null) ? "ORE" : "", out itype, out isub) & (blkRfn != null) == (itype == "ORE") & (blkRfn != null | itype != "INGOT")) {
  755. if (isub == "") {
  756. if (blkRfn != null) {
  757. autoores.UnionWith(typeSubs[itype]);
  758. } else {
  759. foreach (string s in typeSubs[itype])
  760. autoitems.Add(new Item(itype,s));
  761. }
  762. name.Append(":" + typeLabel[itype]);
  763. } else {
  764. if (blkRfn != null) {
  765. autoores.Add(isub);
  766. } else {
  767. autoitems.Add(new Item(itype,isub));
  768. }
  769. name.Append(":" + ((blkRfn == null & subTypes[isub].Count > 1) ? (typeLabel[itype] + "/") : "") + subLabel[isub]);
  770. }
  771. } else {
  772. name.Append(":" + fields[i].ToLower());
  773. debugText.Add("Unrecognized or ambiguous item: " + fields[i].ToLower());
  774. }
  775. }
  776. if (blkRfn != null) {
  777. if (blkRfn.Enabled)
  778. (refineryOres.TryGetValue(blkRfn, out ores) ? ores : (refineryOres[blkRfn] = new HashSet<string>())).UnionWith(autoores);
  779. } else {
  780. if (lastVersion < 1005900) {
  781. blkAsm.ClearQueue();
  782. blkAsm.Repeating = false;
  783. blkAsm.Enabled = true;
  784. }
  785. if (blkAsm.Enabled)
  786. (assemblerItems.TryGetValue(blkAsm, out items) ? items : (assemblerItems[blkAsm] = new HashSet<Item>())).UnionWith(autoitems);
  787. }
  788. name.Append(" ");
  789. } else if (!ParseItemValueText(block, fields, "", out itype, out isub, out priority, out amount, out ratio, out force)) {
  790. name.Append((attr = String.Join(":", fields).ToLower()) + " ");
  791. debugText.Add("Unrecognized or ambiguous item: " + attr);
  792. } else if (!block.HasInventory | (block is IMySmallMissileLauncher & !(block is IMySmallMissileLauncherReload | block.BlockDefinition.SubtypeName == "LargeMissileLauncher")) | block is IMyLargeInteriorTurret) {
  793. name.Append(String.Join(":", fields).ToLower() + " ");
  794. debugText.Add("Cannot sort items to "+block.CustomName+": no conveyor-connected inventory");
  795. } else {
  796. if (isub == "") {
  797. foreach (string s in (force ? (IEnumerable<string>)typeSubs[itype] : (IEnumerable<string>)GetBlockAcceptedSubs(block, itype)))
  798. AddInvenRequest(block, 0, itype, s, priority, amount);
  799. } else {
  800. AddInvenRequest(block, 0, itype, isub, priority, amount);
  801. }
  802. if (rewriteTags & !grouped) {
  803. if (force) {
  804. name.Append("FORCE:" + typeLabel[itype]);
  805. if (isub != "")
  806. name.Append("/" + subLabel[isub]);
  807. } else if (isub == "") {
  808. name.Append(typeLabel[itype]);
  809. } else if (subTypes[isub].Count == 1 || GetBlockImpliedType(block, isub) == itype) {
  810. name.Append(subLabel[isub]);
  811. } else {
  812. name.Append(typeLabel[itype] + "/" + subLabel[isub]);
  813. }
  814. if (priority > 0 & priority < int.MaxValue)
  815. name.Append(":P" + priority);
  816. if (amount >= 0L)
  817. name.Append(":" + (amount / 1e6));
  818. name.Append(" ");
  819. }
  820. }
  821. }
  822. }
  823.  
  824. if (rewriteTags & !grouped) {
  825. if (name[name.Length - 1] == ' ')
  826. name.Length--;
  827. name.Append(tagClose).Append(block.CustomName, match.Index + match.Length, block.CustomName.Length - match.Index - match.Length);
  828. block.CustomName = name.ToString();
  829. }
  830.  
  831. if (block.GetUserRelationToOwner(Me.OwnerId) != MyRelationsBetweenPlayerAndBlock.Owner & block.GetUserRelationToOwner(Me.OwnerId) != MyRelationsBetweenPlayerAndBlock.FactionShare)
  832. debugText.Add("Cannot control \"" + block.CustomName + "\" due to differing ownership");
  833. }
  834. } // ParseBlockTags()
  835.  
  836.  
  837. void ProcessQuotaPanels(bool quotaStable) {
  838. bool debug = debugLogic.Contains("quotas");
  839. int l, x, y, wide, size, spanx, spany, height, p, priority;
  840. long amount, round, total;
  841. float ratio;
  842. bool force;
  843. string itypeCur, itype, isub;
  844. string[] words, empty = new string[1] {" "};
  845. string[][] spanLines;
  846. IMyTextPanel panel2;
  847. IMySlimBlock slim;
  848. Matrix matrix = new Matrix();
  849. StringBuilder sb = new StringBuilder();
  850. List<string> qtypes = new List<string>(), errors = new List<string>(), scalesubs = new List<string>();
  851. Dictionary<string,SortedDictionary<string,string[]>> qtypeSubCols = new Dictionary<string,SortedDictionary<string,string[]>>();
  852. ItemData data;
  853. ScreenFormatter sf;
  854.  
  855. // reset ore "quotas"
  856. foreach (ItemData d in typeSubData["ORE"].Values)
  857. d.minimum = (d.amount == 0L) ? 0L : Math.Max(d.minimum, d.amount);
  858.  
  859. foreach (IMyTextPanel panel in qpanelPriority.Keys) {
  860. wide = panel.BlockDefinition.SubtypeName.EndsWith("Wide") ? 2 : 1;
  861. size = panel.BlockDefinition.SubtypeName.StartsWith("Small") ? 3 : 1;
  862. spanx = spany = 1;
  863. if (panelSpan.ContainsKey(panel)) {
  864. spanx = panelSpan[panel].a;
  865. spany = panelSpan[panel].b;
  866. }
  867.  
  868. // (re?)assemble (spanned?) user quota text
  869. spanLines = new string[spanx][];
  870. panel.Orientation.GetMatrix(out matrix);
  871. sb.Clear();
  872. for (y = 0; y < spany; y++) {
  873. height = 0;
  874. for (x = 0; x < spanx; x++) {
  875. spanLines[x] = empty;
  876. slim = panel.CubeGrid.GetCubeBlock(new Vector3I(panel.Position + x * wide * size * matrix.Right + y * size * matrix.Down));
  877. panel2 = (slim != null) ? (slim.FatBlock as IMyTextPanel) : null;
  878. if (panel2 != null && (""+panel2.BlockDefinition == ""+panel.BlockDefinition & panel2.GetPublicTitle().ToUpper().Contains("QUOTAS"))) {
  879. spanLines[x] = panel2.GetPublicText().Split('\n');
  880. height = Math.Max(height, spanLines[x].Length);
  881. }
  882. }
  883. for (l = 0; l < height; l++) {
  884. for (x = 0; x < spanx; x++)
  885. sb.Append((l < spanLines[x].Length) ? spanLines[x][l] : " ");
  886. sb.Append("\n");
  887. }
  888. }
  889.  
  890. // parse user quotas
  891. priority = qpanelPriority[panel];
  892. itypeCur = "";
  893. qtypes.Clear();
  894. qtypeSubCols.Clear();
  895. errors.Clear();
  896. foreach (string line in sb.ToString().Split('\n')) {
  897. words = line.ToUpper().Split(SPACE, 4, REE);
  898. if (words.Length < 1) {
  899. } else if (ParseItemValueText(null, words, itypeCur, out itype, out isub, out p, out amount, out ratio, out force) & itype == itypeCur & itype != "" & isub != "") {
  900. data = typeSubData[itype][isub];
  901. qtypeSubCols[itype][isub] = new string[] { data.label, ""+Math.Round(amount / 1e6, 2), ""+Math.Round(ratio * 100.0f, 2)+"%" };
  902. if ((priority > 0 & (priority < data.qpriority | data.qpriority <= 0)) | (priority == 0 & data.qpriority < 0)) {
  903. data.qpriority = priority;
  904. data.minimum = amount;
  905. data.ratio = ratio;
  906. } else if (priority == data.qpriority) {
  907. data.minimum = Math.Max(data.minimum, amount);
  908. data.ratio = Math.Max(data.ratio, ratio);
  909. }
  910. } else if (ParseItemValueText(null, words, "", out itype, out isub, out p, out amount, out ratio, out force) & itype != itypeCur & itype != "" & isub == "") {
  911. if (!qtypeSubCols.ContainsKey(itypeCur = itype)) {
  912. qtypes.Add(itypeCur);
  913. qtypeSubCols[itypeCur] = new SortedDictionary<string,string[]>();
  914. }
  915. } else if (itypeCur != "") {
  916. qtypeSubCols[itypeCur][words[0]] = words;
  917. } else {
  918. errors.Add(line);
  919. }
  920. }
  921.  
  922. // redraw quotas
  923. sf = new ScreenFormatter(4, 2);
  924. sf.SetAlign(1, 1);
  925. sf.SetAlign(2, 1);
  926. if (qtypes.Count == 0 & qpanelTypes[panel].Count == 0)
  927. qpanelTypes[panel].AddRange(types);
  928. foreach (string qtype in qpanelTypes[panel]) {
  929. if (!qtypeSubCols.ContainsKey(qtype)) {
  930. qtypes.Add(qtype);
  931. qtypeSubCols[qtype] = new SortedDictionary<string,string[]>();
  932. }
  933. }
  934. foreach (string qtype in qtypes) {
  935. if (qtype == "ORE")
  936. continue;
  937. if (sf.GetNumRows() > 0)
  938. sf.AddBlankRow();
  939. sf.Add(0, typeLabel[qtype], true);
  940. sf.Add(1, " Min", true);
  941. sf.Add(2, " Pct", true);
  942. sf.Add(3, "", true);
  943. sf.AddBlankRow();
  944. foreach (ItemData d in typeSubData[qtype].Values) {
  945. if (!qtypeSubCols[qtype].ContainsKey(d.isub))
  946. qtypeSubCols[qtype][d.isub] = new string[] { d.label, ""+Math.Round(d.minimum / 1e6, 2), ""+Math.Round(d.ratio * 100.0f, 2)+"%" };
  947. }
  948. foreach (string qsub in qtypeSubCols[qtype].Keys) {
  949. words = qtypeSubCols[qtype][qsub];
  950. sf.Add(0, typeSubData[qtype].ContainsKey(qsub) ? words[0] : words[0].ToLower(), true);
  951. sf.Add(1, (words.Length > 1) ? words[1] : "", true);
  952. sf.Add(2, (words.Length > 2) ? words[2] : "", true);
  953. sf.Add(3, (words.Length > 3) ? words[3] : "", true);
  954. }
  955. }
  956. WriteTableToPanel("TIM Quotas", sf, panel, true, ((errors.Count == 0) ? "" : (String.Join("\n", errors).Trim().ToLower() + "\n\n")), "");
  957. }
  958.  
  959. // update effective quotas
  960. foreach (string qtype in types) {
  961. round = 1L;
  962. if (!FRACTIONAL_TYPES.Contains(qtype))
  963. round = 1000000L;
  964. total = typeAmount[qtype];
  965. if (quotaStable & total > 0L) {
  966. scalesubs.Clear();
  967. foreach (ItemData d in typeSubData[qtype].Values) {
  968. if (d.ratio > 0.0f & total >= (long)(d.minimum / d.ratio))
  969. scalesubs.Add(d.isub);
  970. }
  971. if (scalesubs.Count > 0) {
  972. scalesubs.Sort((string s1, string s2) => {
  973. ItemData d1 = typeSubData[qtype][s1], d2 = typeSubData[qtype][s2];
  974. long q1 = (long)(d1.amount / d1.ratio), q2 = (long)(d2.amount / d2.ratio);
  975. return (q1 == q2) ? d1.ratio.CompareTo(d2.ratio) : q1.CompareTo(q2);
  976. });
  977. isub = scalesubs[(scalesubs.Count - 1) / 2];
  978. data = typeSubData[qtype][isub];
  979. total = (long)(data.amount / data.ratio + 0.5f);
  980. if (debug) {
  981. debugText.Add("median "+typeLabel[qtype]+" is "+subLabel[isub]+", "+(total/1e6)+" -> "+(data.amount/1e6/data.ratio));
  982. foreach (string qsub in scalesubs) {
  983. data = typeSubData[qtype][qsub];
  984. debugText.Add(" "+subLabel[qsub]+" @ "+(data.amount/1e6)+" / "+data.ratio+" => "+(long)(data.amount/1e6/data.ratio+0.5f));
  985. }
  986. }
  987. }
  988. }
  989. foreach (ItemData d in typeSubData[qtype].Values) {
  990. amount = Math.Max(d.quota, Math.Max(d.minimum, (long)(d.ratio * total + 0.5f)));
  991. d.quota = (amount / round) * round;
  992. }
  993. }
  994. } // ProcessQuotaPanels()
  995.  
  996.  
  997. bool ParseItemTypeSub(IMyCubeBlock block, bool force, string typesub, string qtype, out string itype, out string isub) {
  998. int t, s, found;
  999. string[] parts;
  1000.  
  1001. itype = "";
  1002. isub = "";
  1003. found = 0;
  1004. parts = typesub.Trim().Split('/');
  1005. if (parts.Length >= 2) {
  1006. parts[0] = parts[0].Trim();
  1007. parts[1] = parts[1].Trim();
  1008. if (typeSubs.ContainsKey(parts[0]) && (parts[1] == "" | typeSubData[parts[0]].ContainsKey(parts[1]))) {
  1009. // exact type/subtype
  1010. if (force || BlockAcceptsTypeSub(block, parts[0], parts[1])) {
  1011. found = 1;
  1012. itype = parts[0];
  1013. isub = parts[1];
  1014. }
  1015. } else {
  1016. // type/subtype?
  1017. t = types.BinarySearch(parts[0]);
  1018. t = Math.Max(t, ~t);
  1019. while ((found < 2 & t < types.Count) && types[t].StartsWith(parts[0])) {
  1020. s = typeSubs[types[t]].BinarySearch(parts[1]);
  1021. s = Math.Max(s, ~s);
  1022. while ((found < 2 & s < typeSubs[types[t]].Count) && typeSubs[types[t]][s].StartsWith(parts[1])) {
  1023. if (force || BlockAcceptsTypeSub(block, types[t], typeSubs[types[t]][s])) {
  1024. found++;
  1025. itype = types[t];
  1026. isub = typeSubs[types[t]][s];
  1027. }
  1028. s++;
  1029. }
  1030. // special case for gravel
  1031. if (found == 0 & types[t] == "INGOT" & "GRAVEL".StartsWith(parts[1]) & (force || BlockAcceptsTypeSub(block, "INGOT", "STONE"))) {
  1032. found++;
  1033. itype = "INGOT";
  1034. isub = "STONE";
  1035. }
  1036. t++;
  1037. }
  1038. }
  1039. } else if (typeSubs.ContainsKey(parts[0])) {
  1040. // exact type
  1041. if (force || BlockAcceptsTypeSub(block, parts[0], "")) {
  1042. found++;
  1043. itype = parts[0];
  1044. isub = "";
  1045. }
  1046. } else if (subTypes.ContainsKey(parts[0])) {
  1047. // exact subtype
  1048. if (qtype != "" && typeSubData[qtype].ContainsKey(parts[0])) {
  1049. found++;
  1050. itype = qtype;
  1051. isub = parts[0];
  1052. } else {
  1053. t = subTypes[parts[0]].Count;
  1054. while (found < 2 & t-- > 0) {
  1055. if (force || BlockAcceptsTypeSub(block, subTypes[parts[0]][t], parts[0])) {
  1056. found++;
  1057. itype = subTypes[parts[0]][t];
  1058. isub = parts[0];
  1059. }
  1060. }
  1061. }
  1062. } else if (qtype != "") {
  1063. // subtype of a known type
  1064. s = typeSubs[qtype].BinarySearch(parts[0]);
  1065. s = Math.Max(s, ~s);
  1066. while ((found < 2 & s < typeSubs[qtype].Count) && typeSubs[qtype][s].StartsWith(parts[0])) {
  1067. found++;
  1068. itype = qtype;
  1069. isub = typeSubs[qtype][s];
  1070. s++;
  1071. }
  1072. // special case for gravel
  1073. if (found == 0 & qtype == "INGOT" & "GRAVEL".StartsWith(parts[0])) {
  1074. found++;
  1075. itype = "INGOT";
  1076. isub = "STONE";
  1077. }
  1078. } else {
  1079. // type?
  1080. t = types.BinarySearch(parts[0]);
  1081. t = Math.Max(t, ~t);
  1082. while ((found < 2 & t < types.Count) && types[t].StartsWith(parts[0])) {
  1083. if (force || BlockAcceptsTypeSub(block, types[t], "")) {
  1084. found++;
  1085. itype = types[t];
  1086. isub = "";
  1087. }
  1088. t++;
  1089. }
  1090. // subtype?
  1091. s = subs.BinarySearch(parts[0]);
  1092. s = Math.Max(s, ~s);
  1093. while ((found < 2 & s < subs.Count) && subs[s].StartsWith(parts[0])) {
  1094. t = subTypes[subs[s]].Count;
  1095. while (found < 2 & t-- > 0) {
  1096. if (force || BlockAcceptsTypeSub(block, subTypes[subs[s]][t], subs[s])) {
  1097. if (found != 1 || (itype != subTypes[subs[s]][t] | isub != "" | typeSubs[itype].Count != 1))
  1098. found++;
  1099. itype = subTypes[subs[s]][t];
  1100. isub = subs[s];
  1101. }
  1102. }
  1103. s++;
  1104. }
  1105. // special case for gravel
  1106. if (found == 0 & "GRAVEL".StartsWith(parts[0]) & (force || BlockAcceptsTypeSub(block, "INGOT", "STONE"))) {
  1107. found++;
  1108. itype = "INGOT";
  1109. isub = "STONE";
  1110. }
  1111. }
  1112.  
  1113. // fill in implied subtype
  1114. if (!force & block != null & found == 1 & isub == "") {
  1115. HashSet<string> mysubs = GetBlockAcceptedSubs(block, itype);
  1116. if (mysubs.Count == 1)
  1117. isub = mysubs.First();
  1118. }
  1119.  
  1120. return (found == 1);
  1121. } // ParseItemTypeSub()
  1122.  
  1123.  
  1124. bool ParseItemValueText(IMyCubeBlock block, string[] fields, string qtype, out string itype, out string isub, out int priority, out long amount, out float ratio, out bool force) {
  1125. int f, l;
  1126. double val, mul;
  1127.  
  1128. itype = "";
  1129. isub = "";
  1130. priority = 0;
  1131. amount = -1L;
  1132. ratio = -1.0f;
  1133. force = (block == null);
  1134.  
  1135. // identify the item
  1136. f = 0;
  1137. if (fields[0].Trim() == "FORCE") {
  1138. if (fields.Length == 1)
  1139. return false;
  1140. force = true;
  1141. f = 1;
  1142. }
  1143. if (!ParseItemTypeSub(block, force, fields[f], qtype, out itype, out isub))
  1144. return false;
  1145.  
  1146. // parse the remaining fields
  1147. while (++f < fields.Length) {
  1148. fields[f] = fields[f].Trim();
  1149. l = fields[f].Length;
  1150.  
  1151. if (l == 0) {
  1152. } else if (fields[f] == "IGNORE") {
  1153. amount = 0L;
  1154. } else if (fields[f] == "OVERRIDE" | fields[f] == "SPLIT") {
  1155. // these AIS tags are TIM's default behavior anyway
  1156. } else if (fields[f][l-1] == '%' & double.TryParse(fields[f].Substring(0,l-1), out val)) {
  1157. ratio = Math.Max(0.0f, (float)(val / 100.0));
  1158. } else if (fields[f][0] == 'P' & double.TryParse(fields[f].Substring(1), out val)) {
  1159. priority = Math.Max(1, (int)(val + 0.5));
  1160. } else {
  1161. // check for numeric suffixes
  1162. mul = 1.0;
  1163. if (fields[f][l-1] == 'K') {
  1164. l--;
  1165. mul = 1e3;
  1166. } else if (fields[f][l-1] == 'M') {
  1167. l--;
  1168. mul = 1e6;
  1169. }
  1170.  
  1171. // try parsing the field as an amount value
  1172. if (double.TryParse(fields[f].Substring(0,l), out val))
  1173. amount = Math.Max(0L, (long)(val * mul * 1e6 + 0.5));
  1174. }
  1175. }
  1176.  
  1177. return true;
  1178. } // ParseItemValueText()
  1179.  
  1180.  
  1181. void AddInvenRequest(IMyTerminalBlock block, int inv, string itype, string isub, int priority, long amount) {
  1182. long a;
  1183. Dictionary<string,Dictionary<string,Dictionary<IMyInventory,long>>> tsir;
  1184. Dictionary<string,Dictionary<IMyInventory,long>> sir;
  1185. Dictionary<IMyInventory,long> ir;
  1186.  
  1187. // no priority -> last priority
  1188. if (priority == 0)
  1189. priority = int.MaxValue;
  1190.  
  1191. // new priority/type/sub?
  1192. tsir = (priTypeSubInvenRequest.TryGetValue(priority, out tsir) ? tsir : (priTypeSubInvenRequest[priority] = new Dictionary<string,Dictionary<string,Dictionary<IMyInventory,long>>>()));
  1193. sir = (tsir.TryGetValue(itype, out sir) ? sir : (tsir[itype] = new Dictionary<string,Dictionary<IMyInventory,long>>()));
  1194. ir = (sir.TryGetValue(isub, out ir) ? ir : (sir[isub] = new Dictionary<IMyInventory,long>()));
  1195.  
  1196. // update request
  1197. IMyInventory inven = block.GetInventory(inv);
  1198. ir.TryGetValue(inven, out a);
  1199. ir[inven] = amount;
  1200. typeSubData[itype][isub].quota += Math.Min(0L, -a) + Math.Max(0L, amount);
  1201.  
  1202. // disable conveyor for some block types
  1203. // (IMyInventoryOwner is supposedly obsolete but there's no other way to do this for all of these block types at once)
  1204. if (((block is IMyGasGenerator | block is IMyReactor | block is IMyRefinery | block is IMyUserControllableGun) & inven.Owner != null) && inven.Owner.UseConveyorSystem) {
  1205. block.GetActionWithName("UseConveyor").Apply(block);
  1206. debugText.Add("Disabling conveyor system for "+block.CustomName);
  1207. }
  1208. } // AddInvenRequest()
  1209.  
  1210.  
  1211. /*
  1212. * TRANSFER FUNCTIONS
  1213. */
  1214.  
  1215.  
  1216. void AllocateItems(bool limited) {
  1217. List<int> priorities;
  1218.  
  1219. // establish priority order, adding 0 for refinery management
  1220. priorities = new List<int>(priTypeSubInvenRequest.Keys);
  1221. priorities.Sort();
  1222. foreach (int p in priorities) {
  1223. foreach (string itype in priTypeSubInvenRequest[p].Keys) {
  1224. foreach (string isub in priTypeSubInvenRequest[p][itype].Keys)
  1225. AllocateItemBatch(limited, p, itype, isub);
  1226. }
  1227. }
  1228.  
  1229. // if we just finished the unlimited requests, check for leftovers
  1230. if (!limited) {
  1231. foreach (string itype in types) {
  1232. foreach (ItemData data in typeSubData[itype].Values) {
  1233. if (data.avail > 0L)
  1234. debugText.Add("No place to put " + GetShorthand(data.avail) + " " + typeLabel[itype] + "/" + subLabel[data.isub] + ", containers may be full");
  1235. }
  1236. }
  1237. }
  1238. } // AllocateItems()
  1239.  
  1240.  
  1241. void AllocateItemBatch(bool limited, int priority, string itype, string isub) {
  1242. bool debug = debugLogic.Contains("sorting");
  1243. int locked, dropped;
  1244. long totalrequest, totalavail, request, avail, amount, moved, round;
  1245. List<IMyInventory> invens = null;
  1246. Dictionary<IMyInventory,long> invenRequest;
  1247.  
  1248. if (debug) debugText.Add("sorting "+typeLabel[itype]+"/"+subLabel[isub]+" lim="+limited+" p="+priority);
  1249.  
  1250. round = 1L;
  1251. if (!FRACTIONAL_TYPES.Contains(itype))
  1252. round = 1000000L;
  1253. invenRequest = new Dictionary<IMyInventory,long>();
  1254. ItemData data = typeSubData[itype][isub];
  1255.  
  1256. // sum up the requests
  1257. totalrequest = 0L;
  1258. foreach (IMyInventory reqInven in priTypeSubInvenRequest[priority][itype][isub].Keys) {
  1259. request = priTypeSubInvenRequest[priority][itype][isub][reqInven];
  1260. if (request != 0L & limited == (request >= 0L)) {
  1261. if (request < 0L) {
  1262. request = 1000000L;
  1263. if (reqInven.MaxVolume != VRage.MyFixedPoint.MaxValue)
  1264. request = (long)((double)reqInven.MaxVolume * 1e6);
  1265. }
  1266. invenRequest[reqInven] = request;
  1267. totalrequest += request;
  1268. }
  1269. }
  1270. if (debug) debugText.Add("total req="+(totalrequest/1e6));
  1271. if (totalrequest <= 0L)
  1272. return;
  1273. totalavail = data.avail + data.locked;
  1274. if (debug) debugText.Add("total avail="+(totalavail/1e6));
  1275.  
  1276. // disqualify any locked invens which already have their share
  1277. if (totalavail > 0L) {
  1278. invens = new List<IMyInventory>(data.invenTotal.Keys);
  1279. do {
  1280. locked = 0;
  1281. dropped = 0;
  1282. foreach (IMyInventory amtInven in invens) {
  1283. avail = data.invenTotal[amtInven];
  1284. if (avail > 0L & invenLocked.Contains(amtInven)) {
  1285. locked++;
  1286. invenRequest.TryGetValue(amtInven, out request);
  1287. amount = (long)((double)request / totalrequest * totalavail);
  1288. if (limited)
  1289. amount = Math.Min(amount, request);
  1290. amount = (amount / round) * round;
  1291.  
  1292. if (avail >= amount) {
  1293. if (debug) debugText.Add("locked "+(amtInven.Owner==null?"???":(amtInven.Owner as IMyTerminalBlock).CustomName)+" gets "+(amount/1e6)+", has "+(avail/1e6));
  1294. dropped++;
  1295. totalrequest -= request;
  1296. invenRequest[amtInven] = 0L;
  1297. totalavail -= avail;
  1298. data.locked -= avail;
  1299. data.invenTotal[amtInven] = 0L;
  1300. }
  1301. }
  1302. }
  1303. } while (locked > dropped & dropped > 0);
  1304. }
  1305.  
  1306. // allocate the remaining available items
  1307. foreach (IMyInventory reqInven in invenRequest.Keys) {
  1308. // calculate this inven's allotment
  1309. request = invenRequest[reqInven];
  1310. if (request <= 0L | totalrequest <= 0L | totalavail <= 0L) {
  1311. if (limited & request > 0L) debugText.Add("Insufficient "+typeLabel[itype]+"/"+subLabel[isub]+" to satisfy "+(reqInven.Owner==null?"???":(reqInven.Owner as IMyTerminalBlock).CustomName));
  1312. continue;
  1313. }
  1314. amount = (long)((double)request / totalrequest * totalavail);
  1315. if (limited)
  1316. amount = Math.Min(amount, request);
  1317. amount = (amount / round) * round;
  1318. if (debug) debugText.Add((reqInven.Owner==null?"???":(reqInven.Owner as IMyTerminalBlock).CustomName)+" gets "+(request/1e6)+" / "+(totalrequest/1e6)+" of "+(totalavail/1e6)+" = "+(amount/1e6));
  1319. totalrequest -= request;
  1320.  
  1321. // check how much it already has
  1322. if (data.invenTotal.TryGetValue(reqInven, out avail)) {
  1323. avail = Math.Min(avail, amount);
  1324. amount -= avail;
  1325. totalavail -= avail;
  1326. if (invenLocked.Contains(reqInven)) {
  1327. data.locked -= avail;
  1328. } else {
  1329. data.avail -= avail;
  1330. }
  1331. data.invenTotal[reqInven] -= avail;
  1332. }
  1333.  
  1334. // get the rest from other unlocked invens
  1335. moved = 0L;
  1336. foreach (IMyInventory amtInven in invens) {
  1337. avail = Math.Min(data.invenTotal[amtInven], amount);
  1338. moved = 0L;
  1339. if (avail > 0L & invenLocked.Contains(amtInven) == false) {
  1340. moved = TransferItem(itype, isub, avail, amtInven, reqInven);
  1341. amount -= moved;
  1342. totalavail -= moved;
  1343. data.avail -= moved;
  1344. data.invenTotal[amtInven] -= moved;
  1345. }
  1346. // if we moved some but not all, we're probably full
  1347. if (amount <= 0L | (moved != 0L & moved != avail))
  1348. break;
  1349. }
  1350.  
  1351. if (limited & amount > 0L) {
  1352. debugText.Add("Insufficient "+typeLabel[itype]+"/"+subLabel[isub]+" to satisfy "+(reqInven.Owner==null?"???":(reqInven.Owner as IMyTerminalBlock).CustomName));
  1353. continue;
  1354. }
  1355. }
  1356.  
  1357. if (debug) debugText.Add(""+(totalavail/1e6)+" left over");
  1358. } // AllocateItemBatch()
  1359.  
  1360.  
  1361. long TransferItem(string itype, string isub, long amount, IMyInventory fromInven, IMyInventory toInven) {
  1362. bool debug = debugLogic.Contains("sorting");
  1363. List<IMyInventoryItem> stacks;
  1364. int s;
  1365. VRage.MyFixedPoint remaining, moved;
  1366. uint id;
  1367. // double volume;
  1368. string stype, ssub;
  1369.  
  1370. remaining = (VRage.MyFixedPoint)(amount / 1e6);
  1371. stacks = fromInven.GetItems();
  1372. s = Math.Min(typeSubData[itype][isub].invenSlot[fromInven], stacks.Count);
  1373. while (remaining > 0 & s-- > 0) {
  1374. stype = ""+stacks[s].Content.TypeId;
  1375. stype = stype.Substring(stype.LastIndexOf('_') + 1).ToUpper();
  1376. ssub = stacks[s].Content.SubtypeId.ToString().ToUpper();
  1377. if (stype == itype & ssub == isub) {
  1378. moved = stacks[s].Amount;
  1379. id = stacks[s].ItemId;
  1380. // volume = (double)fromInven.CurrentVolume;
  1381. if (fromInven == toInven) {
  1382. remaining -= moved;
  1383. if (remaining < 0)
  1384. remaining = 0;
  1385. } else if (fromInven.TransferItemTo(toInven, s, null, true, remaining)) {
  1386. stacks = fromInven.GetItems();
  1387. if (s < stacks.Count && stacks[s].ItemId == id)
  1388. moved -= stacks[s].Amount;
  1389. if (moved <= 0) {
  1390. if ((double)toInven.CurrentVolume < (double)toInven.MaxVolume / 2 & toInven.Owner != null) {
  1391. var/*SerializableDefinitionId*/ bdef = (toInven.Owner as IMyCubeBlock).BlockDefinition;
  1392. AddBlockRestriction(bdef.TypeIdString, bdef.SubtypeName, itype, isub);
  1393. }
  1394. s = 0;
  1395. } else {
  1396. numXfers++;
  1397. if (debug) debugText.Add(
  1398. "Transferred "+GetShorthand((long)((double)moved*1e6))+" "+typeLabel[itype]+"/"+subLabel[isub]+
  1399. " from "+(fromInven.Owner==null?"???":(fromInven.Owner as IMyTerminalBlock).CustomName)+" to "+(toInven.Owner==null?"???":(toInven.Owner as IMyTerminalBlock).CustomName)
  1400. );
  1401. // volume -= (double)fromInven.CurrentVolume;
  1402. // typeSubData[itype][isub].volume = (1000.0 * volume / (double)moved);
  1403. }
  1404. remaining -= moved;
  1405. } else if (!fromInven.IsConnectedTo(toInven) & fromInven.Owner != null & toInven.Owner != null) {
  1406. if (!blockErrors.ContainsKey(fromInven.Owner as IMyTerminalBlock))
  1407. blockErrors[fromInven.Owner as IMyTerminalBlock] = new HashSet<IMyTerminalBlock>();
  1408. blockErrors[fromInven.Owner as IMyTerminalBlock].Add(toInven.Owner as IMyTerminalBlock);
  1409. s = 0;
  1410. }
  1411. }
  1412. }
  1413.  
  1414. return amount - (long)((double)remaining * 1e6 + 0.5);
  1415. } // TransferItem()
  1416.  
  1417.  
  1418. /*
  1419. * MANAGEMENT FUNCTIONS
  1420. */
  1421.  
  1422.  
  1423. void ManageRefineries() {
  1424. if (!typeSubs.ContainsKey("ORE") | !typeSubs.ContainsKey("INGOT"))
  1425. return;
  1426.  
  1427. bool debug = debugLogic.Contains("refineries");
  1428. string itype, itype2, isub, isub2, isubIngot;
  1429. ItemData data;
  1430. int level, priority;
  1431. List<string> ores = new List<string>();
  1432. Dictionary<string,int> oreLevel = new Dictionary<string,int>();
  1433. List<IMyInventoryItem> stacks;
  1434. double speed, oldspeed;
  1435. Work work;
  1436. bool ready;
  1437. List<IMyRefinery> refineries = new List<IMyRefinery>();
  1438.  
  1439. if (debug) debugText.Add("Refinery management:");
  1440.  
  1441. // scan inventory levels
  1442. foreach (string isubOre in typeSubs["ORE"]) {
  1443. if (!ORE_PRODUCT.TryGetValue(isubOre, out isubIngot))
  1444. isubIngot = isubOre;
  1445. if (isubIngot != "" & typeSubData["ORE"][isubOre].avail > 0L & typeSubData["INGOT"].TryGetValue(isubIngot, out data)) {
  1446. if (data.quota > 0L) {
  1447. level = (int)(100L * data.amount / data.quota);
  1448. ores.Add(isubOre);
  1449. oreLevel[isubOre] = level;
  1450. if (debug) debugText.Add(" "+subLabel[isubIngot]+" @ "+(data.amount/1e6)+"/"+(data.quota/1e6)+","+((isubOre==isubIngot)?"":(" Ore/"+subLabel[isubOre]))+" L="+level+"%");
  1451. }
  1452. }
  1453. }
  1454.  
  1455. // identify refineries that are ready for a new assignment
  1456. foreach (IMyRefinery rfn in refineryOres.Keys) {
  1457. itype = itype2 = isub = isub2 = "";
  1458. stacks = rfn.GetInventory(0).GetItems();
  1459. if (stacks.Count > 0) {
  1460. itype = ""+stacks[0].Content.TypeId;
  1461. itype = itype.Substring(itype.LastIndexOf('_') + 1).ToUpper();
  1462. isub = stacks[0].Content.SubtypeId.ToString().ToUpper();
  1463. if (itype == "ORE" & oreLevel.ContainsKey(isub))
  1464. oreLevel[isub] += Math.Max(1, oreLevel[isub] / refineryOres.Count);
  1465. if (stacks.Count > 1) {
  1466. itype2 = ""+stacks[1].Content.TypeId;
  1467. itype2 = itype2.Substring(itype2.LastIndexOf('_') + 1).ToUpper();
  1468. isub2 = stacks[1].Content.SubtypeId.ToString().ToUpper();
  1469. if (itype2 == "ORE" & oreLevel.ContainsKey(isub2))
  1470. oreLevel[isub2] += Math.Max(1, oreLevel[isub2] / refineryOres.Count);
  1471. AddInvenRequest(rfn, 0, itype2, isub2, -2, (long)((double)stacks[1].Amount*1e6+0.5));
  1472. }
  1473. }
  1474. if (producerWork.TryGetValue(rfn, out work)) {
  1475. data = typeSubData[work.item.itype][work.item.isub];
  1476. oldspeed = (data.prdSpeed.TryGetValue(""+rfn.BlockDefinition, out oldspeed) ? oldspeed : 1.0);
  1477. speed = ((work.item.isub == isub) ? Math.Max(work.qty - (double)stacks[0].Amount, 0.0) : Math.Max(work.qty, oldspeed));
  1478. speed = Math.Min(Math.Max((speed + oldspeed) / 2.0, 0.2), 10000.0);
  1479. data.prdSpeed[""+rfn.BlockDefinition] = speed;
  1480. if (debug & (int)(oldspeed+0.5) != (int)(speed+0.5)) debugText.Add(" Update "+rfn.BlockDefinition.SubtypeName+":"+subLabel[work.item.isub]+" refine speed: "+((int)(oldspeed+0.5))+" -> "+((int)(speed+0.5))+"kg/cycle");
  1481. }
  1482. if (refineryOres[rfn].Count > 0) refineryOres[rfn].IntersectWith(oreLevel.Keys); else refineryOres[rfn].UnionWith(oreLevel.Keys);
  1483. ready = (refineryOres[rfn].Count > 0);
  1484. if (stacks.Count > 0) {
  1485. speed = (itype == "ORE" ? (typeSubData["ORE"][isub].prdSpeed.TryGetValue(""+rfn.BlockDefinition, out speed) ? speed : 1.0) : 1e6);
  1486. AddInvenRequest(rfn, 0, itype, isub, -1, (long)Math.Min((double)stacks[0].Amount*1e6+0.5, 10*speed*1e6+0.5));
  1487. ready = (ready & itype == "ORE" & (double)stacks[0].Amount < 2.5*speed & stacks.Count == 1);
  1488. }
  1489. if (ready)
  1490. refineries.Add(rfn);
  1491. if (debug) debugText.Add(
  1492. " "+rfn.CustomName+((stacks.Count<1)?" idle":(
  1493. " refining "+(int)stacks[0].Amount+"kg "+((isub=="")?"unknown":(
  1494. subLabel[isub]+(!oreLevel.ContainsKey(isub)?"":(" (L="+oreLevel[isub]+"%)"))
  1495. ))+((stacks.Count<2)?"":(
  1496. ", then "+(int)stacks[1].Amount+"kg "+((isub2=="")?"unknown":(
  1497. subLabel[isub2]+(!oreLevel.ContainsKey(isub2)?"":(" (L="+oreLevel[isub2]+"%)"))
  1498. ))
  1499. ))
  1500. ))+"; "+((oreLevel.Count==0)?"nothing to do":(ready?"ready":((refineryOres[rfn].Count==0)?"restricted":"busy")))
  1501. );
  1502. }
  1503.  
  1504. // skip refinery:ore assignment if there are no ores or ready refineries
  1505. if (ores.Count > 0 & refineries.Count > 0) {
  1506. ores.Sort((string o1, string o2) => {
  1507. string i1, i2;
  1508. if (!ORE_PRODUCT.TryGetValue(o1,out i1)) i1=o1;
  1509. if (!ORE_PRODUCT.TryGetValue(o2,out i2)) i2=o2;
  1510. return -1*typeSubData["INGOT"][i1].quota.CompareTo(typeSubData["INGOT"][i2].quota);
  1511. });
  1512. refineries.Sort((IMyRefinery r1, IMyRefinery r2) => refineryOres[r1].Count.CompareTo(refineryOres[r2].Count));
  1513. foreach (IMyRefinery rfn in refineries) {
  1514. isub = "";
  1515. level = int.MaxValue;
  1516. foreach (string isubOre in ores) {
  1517. if ((isub == "" | oreLevel[isubOre] < level) & refineryOres[rfn].Contains(isubOre)) {
  1518. isub = isubOre;
  1519. level = oreLevel[isub];
  1520. }
  1521. }
  1522. if (isub != "") {
  1523. numRefs++;
  1524. rfn.UseConveyorSystem = false;
  1525. priority = rfn.GetInventory(0).IsItemAt(0) ? -4 : -3;
  1526. speed = (typeSubData["ORE"][isub].prdSpeed.TryGetValue(""+rfn.BlockDefinition, out speed) ? speed : 1.0);
  1527. AddInvenRequest(rfn, 0, "ORE", isub, priority, (long)(5*speed*1e6+0.5));
  1528. oreLevel[isub] += Math.Min(Math.Max((int)(oreLevel[isub]*0.41), 1), (100 / refineryOres.Count));
  1529. if (debug) debugText.Add(" "+rfn.CustomName+" assigned "+((int)(5*speed+0.5))+"kg "+subLabel[isub]+" (L="+oreLevel[isub]+"%)");
  1530. } else if (debug) debugText.Add(" "+rfn.CustomName+" unassigned, nothing to do");
  1531. }
  1532. }
  1533.  
  1534. for (priority = -1; priority >= -4; priority--) {
  1535. if (priTypeSubInvenRequest.ContainsKey(priority)) {
  1536. foreach (string isubOre in priTypeSubInvenRequest[priority]["ORE"].Keys)
  1537. AllocateItemBatch(true, priority, "ORE", isubOre);
  1538. }
  1539. }
  1540. } // ManageRefineries()
  1541.  
  1542.  
  1543. void ManageAssemblers() {
  1544. if (!typeSubs.ContainsKey("INGOT"))
  1545. return;
  1546.  
  1547. bool debug = debugLogic.Contains("assemblers");
  1548. long ttlCmp;
  1549. int level, amount;
  1550. ItemData data, data2;
  1551. Item item, item2;
  1552. List<Item> items;
  1553. Dictionary<Item,int> itemLevel = new Dictionary<Item,int>(), itemPar = new Dictionary<Item,int>();
  1554. List<MyProductionItem> queue = new List<MyProductionItem>();
  1555. double speed, oldspeed;
  1556. Work work;
  1557. bool ready, jam;
  1558. List<IMyAssembler> assemblers = new List<IMyAssembler>();
  1559.  
  1560. if (debug) debugText.Add("Assembler management:");
  1561.  
  1562. // scan inventory levels
  1563. typeAmount.TryGetValue("COMPONENT", out ttlCmp);
  1564. amount = 90 + (int)(10 * typeSubData["INGOT"].Values.Min(d => (d.isub != "URANIUM" & (d.minimum > 0L | d.ratio > 0.0f)) ? (d.amount / Math.Max((double)d.minimum, 17.5 * d.ratio * ttlCmp)) : 2.0));
  1565. if (debug) debugText.Add(" Component par L="+amount+"%");
  1566. foreach (string itype in types) {
  1567. if (itype != "ORE" & itype != "INGOT") {
  1568. foreach (string isub in typeSubs[itype]) {
  1569. data = typeSubData[itype][isub];
  1570. data.hold = Math.Max(0, data.hold - 1);
  1571. item = new Item(itype, isub);
  1572. itemPar[item] = ((itype == "COMPONENT" & data.ratio > 0.0f) ? amount : 100);
  1573. level = (int)(100L * data.amount / Math.Max(1L, data.quota));
  1574. if (data.quota > 0L & level < itemPar[item] & data.blueprint != default(MyDefinitionId)) {
  1575. if (data.hold == 0) itemLevel[item] = level;
  1576. if (debug) debugText.Add(" "+typeLabel[itype]+"/"+subLabel[isub]+((data.hold > 0) ? "" : (" @ "+(data.amount/1e6)+"/"+(data.quota/1e6)+", L="+level+"%"))+((data.hold > 0 | data.jam > 0) ? ("; HOLD "+data.hold+"/"+(10*data.jam)) : ""));
  1577. }
  1578. }
  1579. }
  1580. }
  1581.  
  1582. // identify assemblers that are ready for a new assignment
  1583. foreach (IMyAssembler asm in assemblerItems.Keys) {
  1584. ready = jam = false;
  1585. data = data2 = null;
  1586. item = item2 = new Item("","");
  1587. if (!asm.IsQueueEmpty) {
  1588. asm.GetQueue(queue);
  1589. data = (blueprintItem.TryGetValue(queue[0].BlueprintId, out item) ? typeSubData[item.itype][item.isub] : null);
  1590. if (data != null & itemLevel.ContainsKey(item))
  1591. itemLevel[item] += Math.Max(1, (int)(1e8 * (double)queue[0].Amount / data.quota + 0.5));
  1592. if (queue.Count > 1 && (blueprintItem.TryGetValue(queue[1].BlueprintId, out item2) & itemLevel.ContainsKey(item2)))
  1593. itemLevel[item2] += Math.Max(1, (int)(1e8 * (double)queue[1].Amount / typeSubData[item2.itype][item2.isub].quota + 0.5));
  1594. }
  1595. if (producerWork.TryGetValue(asm, out work)) {
  1596. data2 = typeSubData[work.item.itype][work.item.isub];
  1597. oldspeed = (data2.prdSpeed.TryGetValue(""+asm.BlockDefinition, out oldspeed) ? oldspeed : 1.0);
  1598. if (work.item.itype != item.itype | work.item.isub != item.isub) {
  1599. speed = Math.Max(oldspeed, (asm.IsQueueEmpty ? 2 : 1) * work.qty);
  1600. producerJam.Remove(asm);
  1601. } else if (asm.IsProducing) {
  1602. speed = work.qty - (double)queue[0].Amount + asm.CurrentProgress;
  1603. producerJam.Remove(asm);
  1604. } else {
  1605. speed = Math.Max(oldspeed, work.qty - (double)queue[0].Amount + asm.CurrentProgress);
  1606. if ((producerJam[asm] = (producerJam.TryGetValue(asm, out level) ? level : 0) + 1) >= 3) {
  1607. debugText.Add(" "+asm.CustomName+" is jammed by "+subLabel[item.isub]);
  1608. producerJam.Remove(asm);
  1609. asm.ClearQueue();
  1610. data2.hold = 10 * ((data2.jam < 1 | data2.hold < 1) ? (data2.jam = Math.Min(10, data2.jam + 1)) : data2.jam);
  1611. jam = true;
  1612. }
  1613. }
  1614. speed = Math.Min(Math.Max((speed + oldspeed) / 2.0, Math.Max(0.2, 0.5*oldspeed)), Math.Min(1000.0, 2.0*oldspeed));
  1615. data2.prdSpeed[""+asm.BlockDefinition] = speed;
  1616. if (debug & (int)(oldspeed+0.5) != (int)(speed+0.5)) debugText.Add(" Update "+asm.BlockDefinition.SubtypeName+":"+typeLabel[work.item.itype]+"/"+subLabel[work.item.isub]+" assemble speed: "+((int)(oldspeed*100)/100.0)+" -> "+((int)(speed*100)/100.0)+"/cycle");
  1617. }
  1618. if (assemblerItems[asm].Count == 0) assemblerItems[asm].UnionWith(itemLevel.Keys); else assemblerItems[asm].IntersectWith(itemLevel.Keys);
  1619. speed = ((data != null && data.prdSpeed.TryGetValue(""+asm.BlockDefinition, out speed)) ? speed : 1.0);
  1620. if (!jam & (asm.IsQueueEmpty || (((double)queue[0].Amount - asm.CurrentProgress) < 2.5*speed & queue.Count == 1 & asm.Mode == MyAssemblerMode.Assembly))) {
  1621. if (data2 != null) data2.jam = Math.Max(0, data2.jam - ((data2.hold < 1) ? 1 : 0));
  1622. if (ready = (assemblerItems[asm].Count > 0)) assemblers.Add(asm);
  1623. }
  1624. if (debug) debugText.Add(
  1625. " "+asm.CustomName+(asm.IsQueueEmpty?" idle":(
  1626. ((asm.Mode==MyAssemblerMode.Assembly)?" making ":" breaking ")+queue[0].Amount+"x "+((item.itype=="")?"unknown":(
  1627. subLabel[item.isub]+(!itemLevel.ContainsKey(item)?"":(" (L="+itemLevel[item]+"%)"))
  1628. ))+((queue.Count<=1)?"":(
  1629. ", then "+queue[1].Amount+"x "+((item2.itype=="")?"unknown":(
  1630. subLabel[item2.isub]+(!itemLevel.ContainsKey(item2)?"":(" (L="+itemLevel[item2]+"%)"))
  1631. ))
  1632. ))
  1633. ))+"; "+((itemLevel.Count==0)?"nothing to do":(ready?"ready":((assemblerItems[asm].Count==0)?"restricted":"busy")))
  1634. );
  1635. }
  1636.  
  1637. // skip assembler:item assignments if there are no needed items or ready assemblers
  1638. if (itemLevel.Count > 0 & assemblers.Count > 0) {
  1639. items = new List<Item>(itemLevel.Keys);
  1640. items.Sort((i1,i2) => -1*typeSubData[i1.itype][i1.isub].quota.CompareTo(typeSubData[i2.itype][i2.isub].quota));
  1641. assemblers.Sort((IMyAssembler a1, IMyAssembler a2) => assemblerItems[a1].Count.CompareTo(assemblerItems[a2].Count));
  1642. foreach (IMyAssembler asm in assemblers) {
  1643. item = new Item("","");
  1644. level = int.MaxValue;
  1645. foreach (Item i in items) {
  1646. if (itemLevel[i] < Math.Min(level, itemPar[i]) & assemblerItems[asm].Contains(i) & typeSubData[i.itype][i.isub].hold < 1) {
  1647. item = i;
  1648. level = itemLevel[i];
  1649. }
  1650. }
  1651. if (item.itype != "") {
  1652. numAsms++;
  1653. asm.UseConveyorSystem = true;
  1654. asm.CooperativeMode = false;
  1655. asm.Repeating = false;
  1656. asm.Mode = MyAssemblerMode.Assembly;
  1657. data = typeSubData[item.itype][item.isub];
  1658. speed = (data.prdSpeed.TryGetValue(""+asm.BlockDefinition, out speed) ? speed : 1.0);
  1659. amount = Math.Max((int)(5*speed), 1);
  1660. asm.AddQueueItem(data.blueprint, (double)amount);
  1661. itemLevel[item] += (int)Math.Ceiling(1e8 * (double)amount / data.quota);
  1662. if (debug) debugText.Add(" "+asm.CustomName+" assigned "+amount+"x "+subLabel[item.isub]+" (L="+itemLevel[item]+"%)");
  1663. } else if (debug) debugText.Add(" "+asm.CustomName+" unassigned, nothing to do");
  1664. }
  1665. }
  1666. } // ManageAssemblers()
  1667.  
  1668.  
  1669. /*
  1670. * PANEL DISPLAYS
  1671. */
  1672.  
  1673.  
  1674. void ScanProduction() {
  1675. List<IMyTerminalBlock> blocks1 = new List<IMyTerminalBlock>(), blocks2 = new List<IMyTerminalBlock>();
  1676. List<IMyInventoryItem> stacks;
  1677. string itype, isub, isubIng;
  1678. List<MyProductionItem> queue = new List<MyProductionItem>();
  1679. Item item;
  1680.  
  1681. producerWork.Clear();
  1682.  
  1683. GridTerminalSystem.GetBlocksOfType<IMyGasGenerator>(blocks1, blk => dockedgrids.Contains(blk.CubeGrid));
  1684. GridTerminalSystem.GetBlocksOfType<IMyRefinery>(blocks2, blk => dockedgrids.Contains(blk.CubeGrid));
  1685. foreach (IMyFunctionalBlock blk in blocks1.Concat(blocks2)) {
  1686. stacks = blk.GetInventory(0).GetItems();
  1687. if (stacks.Count > 0 & blk.Enabled) {
  1688. itype = ""+stacks[0].Content.TypeId;
  1689. itype = itype.Substring(itype.LastIndexOf('_') + 1).ToUpper();
  1690. isub = stacks[0].Content.SubtypeId.ToString().ToUpper();
  1691. if (typeSubs.ContainsKey(itype) & subTypes.ContainsKey(isub))
  1692. typeSubData[itype][isub].producers.Add(blk);
  1693. if (itype == "ORE" & (ORE_PRODUCT.TryGetValue(isub, out isubIng) ? isubIng : (isubIng = isub)) != "" & typeSubData["INGOT"].ContainsKey(isubIng))
  1694. typeSubData["INGOT"][isubIng].producers.Add(blk);
  1695. producerWork[blk] = new Work(new Item(itype, isub), (double)stacks[0].Amount);
  1696. }
  1697. }
  1698.  
  1699. GridTerminalSystem.GetBlocksOfType<IMyAssembler>(blocks1, blk => dockedgrids.Contains(blk.CubeGrid));
  1700. foreach (IMyAssembler blk in blocks1) {
  1701. if (blk.Enabled & !blk.IsQueueEmpty & blk.Mode == MyAssemblerMode.Assembly) {
  1702. blk.GetQueue(queue);
  1703. if (blueprintItem.TryGetValue(queue[0].BlueprintId, out item)) {
  1704. if (typeSubs.ContainsKey(item.itype) & subTypes.ContainsKey(item.isub))
  1705. typeSubData[item.itype][item.isub].producers.Add(blk);
  1706. producerWork[blk] = new Work(item, (double)queue[0].Amount - blk.CurrentProgress);
  1707. }
  1708. }
  1709. }
  1710. } // ScanProduction()
  1711.  
  1712.  
  1713. void UpdateInventoryPanels() {
  1714. string text, header2, header5;
  1715. Dictionary<string,List<IMyTextPanel>> itypesPanels = new Dictionary<string,List<IMyTextPanel>>();
  1716. ScreenFormatter sf;
  1717. long maxamt, maxqta;
  1718.  
  1719. foreach (IMyTextPanel panel in ipanelTypes.Keys) {
  1720. text = String.Join("/", ipanelTypes[panel]);
  1721. if (itypesPanels.ContainsKey(text)) itypesPanels[text].Add(panel); else itypesPanels[text] = new List<IMyTextPanel>() { panel };
  1722. }
  1723. foreach (List<IMyTextPanel> panels in itypesPanels.Values) {
  1724. sf = new ScreenFormatter(6);
  1725. sf.SetBar(0);
  1726. sf.SetFill(0, 1);
  1727. sf.SetAlign(2, 1);
  1728. sf.SetAlign(3, 1);
  1729. sf.SetAlign(4, 1);
  1730. sf.SetAlign(5, 1);
  1731. maxamt = maxqta = 0L;
  1732. foreach (string itype in ((ipanelTypes[panels[0]].Count > 0) ? ipanelTypes[panels[0]] : types)) {
  1733. header2 = " Asm ";
  1734. header5 = "Quota";
  1735. if (itype == "INGOT") {
  1736. header2 = " Ref ";
  1737. } else if (itype == "ORE") {
  1738. header2 = " Ref ";
  1739. header5 = "Max";
  1740. }
  1741. if (sf.GetNumRows() > 0)
  1742. sf.AddBlankRow();
  1743. sf.Add(0, "");
  1744. sf.Add(1, typeLabel[itype], true);
  1745. sf.Add(2, header2, true);
  1746. sf.Add(3, "Qty", true);
  1747. sf.Add(4, " / ", true);
  1748. sf.Add(5, header5, true);
  1749. sf.AddBlankRow();
  1750. foreach (ItemData data in typeSubData[itype].Values) {
  1751. sf.Add(0, (data.amount == 0L) ? "0.0" : (""+((double)data.amount / data.quota)));
  1752. sf.Add(1, data.label, true);
  1753. text = ((data.producers.Count > 0) ? (data.producers.Count + " " + (data.producers.All(blk => (!(blk is IMyProductionBlock) || (blk as IMyProductionBlock).IsProducing)) ? " " : "!")) : ((data.hold > 0) ? "- " : ""));
  1754. sf.Add(2, text, true);
  1755. sf.Add(3, (data.amount > 0L | data.quota > 0L) ? GetShorthand(data.amount) : "");
  1756. sf.Add(4, (data.quota > 0L) ? " / " : "", true);
  1757. sf.Add(5, (data.quota > 0L) ? GetShorthand(data.quota) : "");
  1758. maxamt = Math.Max(maxamt, data.amount);
  1759. maxqta = Math.Max(maxqta, data.quota);
  1760. }
  1761. }
  1762. sf.SetWidth(3, ScreenFormatter.GetWidth("8.88" + ((maxamt >= 1000000000000L) ? " M" : ((maxamt >= 1000000000L) ? " K" : "")), true));
  1763. sf.SetWidth(5, ScreenFormatter.GetWidth("8.88" + ((maxqta >= 1000000000000L) ? " M" : ((maxqta >= 1000000000L) ? " K" : "")), true));
  1764. foreach (IMyTextPanel panel in panels)
  1765. WriteTableToPanel("TIM Inventory", sf, panel, true);
  1766. }
  1767. } // UpdateInventoryPanels()
  1768.  
  1769.  
  1770. void UpdateStatusPanels() {
  1771. long r;
  1772. StringBuilder sb;
  1773.  
  1774. if (statusPanels.Count > 0) {
  1775. sb = new StringBuilder();
  1776. sb.Append(statsHeader);
  1777. for (r = Math.Max(1, numCalls - statsLog.Length + 1); r <= numCalls; r++)
  1778. sb.Append(statsLog[r % statsLog.Length]);
  1779.  
  1780. foreach (IMyTextPanel panel in statusPanels) {
  1781. panel.WritePublicTitle("Script Status", false);
  1782. if (panelSpan.ContainsKey(panel))
  1783. debugText.Add("Status panels cannot be spanned");
  1784. panel.WritePublicText(sb.ToString(), false);
  1785. panel.ShowPublicTextOnScreen();
  1786. }
  1787. }
  1788.  
  1789. if (debugPanels.Count > 0) {
  1790. foreach (IMyTerminalBlock blockFrom in blockErrors.Keys) {
  1791. foreach (IMyTerminalBlock blockTo in blockErrors[blockFrom])
  1792. debugText.Add("No conveyor connection from " + blockFrom.CustomName + " to " + blockTo.CustomName);
  1793. }
  1794. foreach (IMyTextPanel panel in debugPanels) {
  1795. panel.WritePublicTitle("Script Debugging", false);
  1796. if (panelSpan.ContainsKey(panel))
  1797. debugText.Add("Debug panels cannot be spanned");
  1798. panel.WritePublicText(String.Join("\n", debugText), false);
  1799. panel.ShowPublicTextOnScreen();
  1800. }
  1801. }
  1802. blockErrors.Clear();
  1803. } // UpdateStatusPanels()
  1804.  
  1805.  
  1806. void WriteTableToPanel(string title, ScreenFormatter sf, IMyTextPanel panel, bool allowspan=true, string before="", string after="") {
  1807. int spanx, spany, rows, wide, size, width, height;
  1808. int x, y, r;
  1809. float fontsize;
  1810. string[][] spanLines;
  1811. string text;
  1812. Matrix matrix;
  1813. IMySlimBlock slim;
  1814. IMyTextPanel spanpanel;
  1815.  
  1816. // get the spanning dimensions, if any
  1817. wide = panel.BlockDefinition.SubtypeName.EndsWith("Wide") ? 2 : 1;
  1818. size = panel.BlockDefinition.SubtypeName.StartsWith("Small") ? 3 : 1;
  1819. spanx = spany = 1;
  1820. if (allowspan & panelSpan.ContainsKey(panel)) {
  1821. spanx = panelSpan[panel].a;
  1822. spany = panelSpan[panel].b;
  1823. }
  1824.  
  1825. // reduce font size to fit everything
  1826. x = sf.GetMinWidth();
  1827. x = (x / spanx) + ((x % spanx > 0) ? 1 : 0);
  1828. y = sf.GetNumRows();
  1829. y = (y / spany) + ((y % spany > 0) ? 1 : 0);
  1830. width = 658 * wide; // TODO monospace 26x17.5 chars
  1831. fontsize = panel.GetValueFloat("FontSize");
  1832. if (fontsize < 0.25f)
  1833. fontsize = 1.0f;
  1834. if (x > 0)
  1835. fontsize = Math.Min(fontsize, Math.Max(0.5f, (float)(width * 100 / x) / 100.0f));
  1836. if (y > 0)
  1837. fontsize = Math.Min(fontsize, Math.Max(0.5f, (float)(1760 / y) / 100.0f));
  1838.  
  1839. // calculate how much space is available on each panel
  1840. width = (int)((float)width / fontsize);
  1841. height = (int)(17.6f / fontsize);
  1842.  
  1843. // write to each panel
  1844. if (spanx > 1 | spany > 1) {
  1845. spanLines = sf.ToSpan(width, spanx);
  1846. matrix = new Matrix();
  1847. panel.Orientation.GetMatrix(out matrix);
  1848. for (x = 0; x < spanx; x++) {
  1849. r = 0;
  1850. for (y = 0; y < spany; y++) {
  1851. slim = panel.CubeGrid.GetCubeBlock(new Vector3I(panel.Position + x * wide * size * matrix.Right + y * size * matrix.Down));
  1852. if (slim != null && (slim.FatBlock is IMyTextPanel) && ""+slim.FatBlock.BlockDefinition == ""+panel.BlockDefinition) {
  1853. spanpanel = slim.FatBlock as IMyTextPanel;
  1854. rows = Math.Max(0, spanLines[x].Length - r);
  1855. if (y + 1 < spany)
  1856. rows = Math.Min(rows, height);
  1857. text = "";
  1858. if (r < spanLines[x].Length)
  1859. text = String.Join("\n", spanLines[x], r, rows);
  1860. if (x == 0)
  1861. text += ((y == 0) ? before : (((y + 1) == spany) ? after : ""));
  1862. spanpanel.SetValueFloat("FontSize", fontsize);
  1863. spanpanel.WritePublicTitle(title + " (" + (x+1) + "," + (y+1) + ")", false);
  1864. spanpanel.WritePublicText(text, false);
  1865. spanpanel.ShowPublicTextOnScreen();
  1866. }
  1867. r += height;
  1868. }
  1869. }
  1870. } else {
  1871. panel.SetValueFloat("FontSize", fontsize);
  1872. panel.WritePublicTitle(title, false);
  1873. panel.WritePublicText(before + sf.ToString(width) + after, false);
  1874. panel.ShowPublicTextOnScreen();
  1875. }
  1876. } // WriteTableToPanel()
  1877.  
  1878.  
  1879. /*
  1880. * MAIN
  1881. */
  1882.  
  1883.  
  1884. public Program() {
  1885. int ext;
  1886.  
  1887. // Configure this program to run the Main method every 100 update ticks
  1888. Runtime.UpdateFrequency = UpdateFrequency.Update100;
  1889.  
  1890. // parse stored data
  1891. foreach (string line in Me.CustomData.Split(NEWLINE, REE)) {
  1892. string[] kv = line.Trim().Split('=');
  1893. if (kv[0].Equals("TIM_version", OIC)) {
  1894. if (!int.TryParse(kv[1], out lastVersion) | lastVersion > VERSION) {
  1895. Echo("Invalid prior version: "+lastVersion);
  1896. lastVersion = 0;
  1897. }
  1898. }
  1899. }
  1900.  
  1901. // initialize panel data
  1902. ScreenFormatter.Init();
  1903. statsHeader = (
  1904. "Taleden's Inventory Manager\n" +
  1905. "v"+VERS_MAJ+"."+VERS_MIN+"."+VERS_REV+" ("+VERS_UPD+")\n\n" +
  1906. ScreenFormatter.Format("Run", 80, out ext, 1) +
  1907. ScreenFormatter.Format("Step", 125+ext, out ext, 1) +
  1908. ScreenFormatter.Format("Time", 145+ext, out ext, 1) +
  1909. ScreenFormatter.Format("Load", 105+ext, out ext, 1) +
  1910. ScreenFormatter.Format("S", 65+ext, out ext, 1) +
  1911. ScreenFormatter.Format("R", 65+ext, out ext, 1) +
  1912. ScreenFormatter.Format("A", 65+ext, out ext, 1) +
  1913. "\n\n"
  1914. );
  1915.  
  1916. // initialize default items, quotas, labels and blueprints
  1917. // (TIM can also learn new items it sees in inventory)
  1918. InitItems(DEFAULT_ITEMS);
  1919.  
  1920. // initialize block:item restrictions
  1921. // (TIM can also learn new restrictions whenever item transfers fail)
  1922. InitBlockRestrictions(DEFAULT_RESTRICTIONS);
  1923.  
  1924. Echo("Compiled TIM v"+VERS_MAJ+"."+VERS_MIN+"."+VERS_REV+" ("+VERS_UPD+")");
  1925. } // Program()
  1926.  
  1927.  
  1928. public void Save() {
  1929. } // Save()
  1930.  
  1931.  
  1932. void Main(string argument) {
  1933. // throttle interval
  1934. if (numCalls > 0 & (sinceLast += Runtime.TimeSinceLastRun.TotalSeconds) < 0.5)
  1935. return;
  1936. sinceLast = 0.0;
  1937.  
  1938. DateTime dtStart = DateTime.Now;
  1939. int i, j, argCycle, step, time, load;
  1940. bool argRewriteTags, argScanCollectors, argScanDrills, argScanGrinders, argScanWelders, argQuotaStable, toggle;
  1941. char argTagOpen, argTagClose;
  1942. string argTagPrefix, msg;
  1943. StringBuilder sb = new StringBuilder();
  1944. List<IMyTerminalBlock> blocks;
  1945.  
  1946. // output terminal info
  1947. numCalls++;
  1948. Echo("Taleden's Inventory Manager");
  1949. Echo("v"+VERS_MAJ+"."+VERS_MIN+"."+VERS_REV+" ("+VERS_UPD+")");
  1950. Echo("Last Run: #"+numCalls+" at "+dtStart.ToString("h:mm:ss tt"));
  1951. if (lastVersion > 0 & lastVersion < VERSION)
  1952. Echo("Upgraded from v"+(lastVersion/1000000)+"."+(lastVersion/1000%1000)+"."+(lastVersion%1000));
  1953.  
  1954. // reset status and debugging data every cycle
  1955. debugText.Clear();
  1956. debugLogic.Clear();
  1957. step = numXfers = numRefs = numAsms = 0;
  1958.  
  1959. // parse arguments
  1960. toggle = true;
  1961. argRewriteTags = REWRITE_TAGS;
  1962. argTagOpen = TAG_OPEN;
  1963. argTagClose = TAG_CLOSE;
  1964. argTagPrefix = TAG_PREFIX;
  1965. argCycle = CYCLE_LENGTH;
  1966. argScanCollectors = SCAN_COLLECTORS;
  1967. argScanDrills = SCAN_DRILLS;
  1968. argScanGrinders = SCAN_GRINDERS;
  1969. argScanWelders = SCAN_WELDERS;
  1970. argQuotaStable = QUOTA_STABLE;
  1971. foreach (string arg in argument.Split(SPACE, REE)) {
  1972. if (arg.Equals("rewrite", OIC)) {
  1973. argRewriteTags = true;
  1974. debugText.Add("Tag rewriting enabled");
  1975. } else if (arg.Equals("norewrite", OIC)) {
  1976. argRewriteTags = false;
  1977. debugText.Add("Tag rewriting disabled");
  1978. } else if (arg.StartsWith("tags=", OIC)) {
  1979. msg = arg.Substring(5);
  1980. if (msg.Length != 2) {
  1981. Echo("Invalid 'tags=' delimiters \"" + msg + "\": must be exactly two characters");
  1982. toggle = false;
  1983. } else if (msg[0] == ' ' || msg[1] == ' ') {
  1984. Echo("Invalid 'tags=' delimiters \"" + msg + "\": cannot be spaces");
  1985. toggle = false;
  1986. } else if (char.ToUpper(msg[0]) == char.ToUpper(msg[1])) {
  1987. Echo("Invalid 'tags=' delimiters \"" + msg + "\": characters must be different");
  1988. toggle = false;
  1989. } else {
  1990. argTagOpen = char.ToUpper(msg[0]);
  1991. argTagClose = char.ToUpper(msg[1]);
  1992. debugText.Add("Tags are delimited by \"" + argTagOpen + "\" and \"" + argTagClose + "\"");
  1993. }
  1994. } else if (arg.StartsWith("prefix=", OIC)) {
  1995. argTagPrefix = arg.Substring(7).Trim().ToUpper();
  1996. if (argTagPrefix == "") {
  1997. debugText.Add("Tag prefix disabled");
  1998. } else {
  1999. debugText.Add("Tag prefix is \"" + argTagPrefix + "\"");
  2000. }
  2001. } else if (arg.StartsWith("cycle=", OIC)) {
  2002. if (int.TryParse(arg.Substring(6), out argCycle) == false || argCycle < 1) {
  2003. Echo("Invalid 'cycle=' length \"" + arg.Substring(6) + "\": must be a positive integer");
  2004. toggle = false;
  2005. } else {
  2006. argCycle = Math.Min(Math.Max(argCycle, 1), MAX_CYCLE_STEPS);
  2007. if (argCycle < 2) {
  2008. debugText.Add("Function cycling disabled");
  2009. } else {
  2010. debugText.Add("Cycle length is " + argCycle);
  2011. }
  2012. }
  2013. } else if (arg.StartsWith("scan=", OIC)) {
  2014. msg = arg.Substring(5);
  2015. if (msg.Equals("collectors", OIC)) {
  2016. argScanCollectors = true;
  2017. debugText.Add("Enabled scanning of Collectors");
  2018. } else if (msg.Equals("drills", OIC)) {
  2019. argScanDrills = true;
  2020. debugText.Add("Enabled scanning of Drills");
  2021. } else if (msg.Equals("grinders", OIC)) {
  2022. argScanGrinders = true;
  2023. debugText.Add("Enabled scanning of Grinders");
  2024. } else if (msg.Equals("welders", OIC)) {
  2025. argScanWelders = true;
  2026. debugText.Add("Enabled scanning of Welders");
  2027. } else {
  2028. Echo("Invalid 'scan=' block type '" + msg + "': must be 'collectors', 'drills', 'grinders' or 'welders'");
  2029. toggle = false;
  2030. }
  2031. } else if (arg.StartsWith("quota=", OIC)) {
  2032. msg = arg.Substring(6);
  2033. if (msg.Equals("literal", OIC)) {
  2034. argQuotaStable = false;
  2035. debugText.Add("Disabled stable dynamic quotas");
  2036. } else if (msg.Equals("stable", OIC)) {
  2037. argQuotaStable = true;
  2038. debugText.Add("Enabled stable dynamic quotas");
  2039. } else {
  2040. Echo("Invalid 'quota=' mode '" + msg + "': must be 'literal' or 'stable'");
  2041. toggle = false;
  2042. }
  2043. } else if (arg.StartsWith("debug=", OIC)) {
  2044. msg = arg.Substring(6);
  2045. if (msg.Length >= 1 & "quotas".StartsWith(msg, OIC)) {
  2046. debugLogic.Add("quotas");
  2047. } else if (msg.Length >= 1 & "sorting".StartsWith(msg, OIC)) {
  2048. debugLogic.Add("sorting");
  2049. } else if (msg.Length >= 1 & "refineries".StartsWith(msg, OIC)) {
  2050. debugLogic.Add("refineries");
  2051. } else if (msg.Length >= 1 & "assemblers".StartsWith(msg, OIC)) {
  2052. debugLogic.Add("assemblers");
  2053. } else {
  2054. Echo("Invalid 'debug=' type '" + msg + "': must be 'quotas', 'sorting', 'refineries', or 'assemblers'");
  2055. toggle = false;
  2056. }
  2057. } else {
  2058. Echo("Unrecognized argument: " + arg);
  2059. toggle = false;
  2060. }
  2061. }
  2062. if (toggle == false)
  2063. return;
  2064.  
  2065. // apply changed arguments
  2066. toggle = (tagOpen != argTagOpen) | (tagClose != argTagClose) | (tagPrefix != argTagPrefix);
  2067. if ((toggle | (rewriteTags != argRewriteTags) | (cycleLength != argCycle)) && (cycleStep > 0)) {
  2068. cycleStep = 0;
  2069. Echo(msg = "Options changed; cycle step reset.");
  2070. debugText.Add(msg);
  2071. }
  2072. rewriteTags = argRewriteTags;
  2073. tagOpen = argTagOpen;
  2074. tagClose = argTagClose;
  2075. tagPrefix = argTagPrefix;
  2076. cycleLength = argCycle;
  2077. if (tagRegex == null | toggle) {
  2078. msg = "\\" + tagOpen;
  2079. if (tagPrefix != "") {
  2080. msg += " *" + System.Text.RegularExpressions.Regex.Escape(tagPrefix) + "(|[ ,]+[^\\" + tagClose + "]*)";
  2081. } else {
  2082. msg += "([^\\" + tagClose + "]*)";
  2083. }
  2084. msg += "\\" + tagClose;
  2085. tagRegex = new System.Text.RegularExpressions.Regex(msg, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
  2086. }
  2087.  
  2088. // scan connectors before PGs! if another TIM is on a grid that is *not* correctly docked, both still need to run
  2089. if (cycleStep == step++ * cycleLength / MAX_CYCLE_STEPS) {
  2090. if (cycleLength > 1) {
  2091. Echo(msg = "Scanning grid connectors ...");
  2092. debugText.Add(msg);
  2093. }
  2094. ScanGrids();
  2095. }
  2096.  
  2097. // search for other TIMs
  2098. blocks = new List<IMyTerminalBlock>();
  2099. GridTerminalSystem.GetBlocksOfType<IMyProgrammableBlock>(blocks, (IMyTerminalBlock blk) => (blk == Me) | (tagRegex.IsMatch(blk.CustomName) & dockedgrids.Contains(blk.CubeGrid)));
  2100. i = blocks.IndexOf(Me);
  2101. j = blocks.FindIndex(block => block.IsFunctional & block.IsWorking);
  2102. msg = tagOpen + tagPrefix + ((blocks.Count > 1) ? (" #"+(i+1)) : "") + tagClose;
  2103. Me.CustomName = tagRegex.IsMatch(Me.CustomName) ? tagRegex.Replace(Me.CustomName, msg, 1) : (Me.CustomName + " " + msg);
  2104. if (i != j) {
  2105. Echo("TIM #" + (j + 1) + " is on duty. Standing by.");
  2106. if (("" + (blocks[j] as IMyProgrammableBlock).TerminalRunArgument).Trim() != ("" + Me.TerminalRunArgument).Trim())
  2107. Echo("WARNING: Script arguments do not match TIM #" + (j + 1) + ".");
  2108. return;
  2109. }
  2110.  
  2111. // TODO: API testing
  2112. /**
  2113. GridTerminalSystem.GetBlocksOfType<IMyShipController>(blocks);
  2114. Echo(""+blocks[0].GetInventory(0).Owner);
  2115. /**/
  2116.  
  2117. if (cycleStep == step++ * cycleLength / MAX_CYCLE_STEPS) {
  2118. if (cycleLength > 1) {
  2119. Echo(msg = "Scanning inventories ...");
  2120. debugText.Add(msg);
  2121. }
  2122.  
  2123. // reset everything that we'll check during this step
  2124. foreach (string itype in types) {
  2125. typeAmount[itype] = 0;
  2126. foreach (ItemData data in typeSubData[itype].Values) {
  2127. data.amount = 0L;
  2128. data.avail = 0L;
  2129. data.locked = 0L;
  2130. data.invenTotal.Clear();
  2131. data.invenSlot.Clear();
  2132. }
  2133. }
  2134. blockTag.Clear();
  2135. blockGtag.Clear();
  2136. invenLocked.Clear();
  2137. invenHidden.Clear();
  2138.  
  2139. // scan inventories
  2140. ScanGroups();
  2141. ScanBlocks<IMyAssembler>();
  2142. ScanBlocks<IMyCargoContainer>();
  2143. if (argScanCollectors)
  2144. ScanBlocks<IMyCollector>();
  2145. ScanBlocks<IMyGasGenerator>();
  2146. ScanBlocks<IMyGasTank>();
  2147. ScanBlocks<IMyReactor>();
  2148. ScanBlocks<IMyRefinery>();
  2149. ScanBlocks<IMyShipConnector>();
  2150. ScanBlocks<IMyShipController>();
  2151. if (argScanDrills)
  2152. ScanBlocks<IMyShipDrill>();
  2153. if (argScanGrinders)
  2154. ScanBlocks<IMyShipGrinder>();
  2155. if (argScanWelders)
  2156. ScanBlocks<IMyShipWelder>();
  2157. ScanBlocks<IMyTextPanel>();
  2158. ScanBlocks<IMyUserControllableGun>();
  2159.  
  2160. // if we found any new item type/subtypes, re-sort the lists
  2161. if (foundNewItem) {
  2162. foundNewItem = false;
  2163. types.Sort();
  2164. foreach (string itype in types)
  2165. typeSubs[itype].Sort();
  2166. subs.Sort();
  2167. foreach (string isub in subs)
  2168. subTypes[isub].Sort();
  2169. }
  2170. }
  2171.  
  2172. if (cycleStep == step++ * cycleLength / MAX_CYCLE_STEPS) {
  2173. if (cycleLength > 1) {
  2174. Echo(msg = "Scanning tags ...");
  2175. debugText.Add(msg);
  2176. }
  2177.  
  2178. // reset everything that we'll check during this step
  2179. foreach (string itype in types) {
  2180. foreach (ItemData data in typeSubData[itype].Values) {
  2181. data.qpriority = -1;
  2182. data.quota = 0L;
  2183. data.producers.Clear();
  2184. }
  2185. }
  2186. qpanelPriority.Clear();
  2187. qpanelTypes.Clear();
  2188. ipanelTypes.Clear();
  2189. priTypeSubInvenRequest.Clear();
  2190. statusPanels.Clear();
  2191. debugPanels.Clear();
  2192. refineryOres.Clear();
  2193. assemblerItems.Clear();
  2194. panelSpan.Clear();
  2195.  
  2196. // parse tags
  2197. ParseBlockTags();
  2198. }
  2199.  
  2200. if (cycleStep == step++ * cycleLength / MAX_CYCLE_STEPS) {
  2201. if (cycleLength > 1) {
  2202. Echo(msg = "Adjusting tallies ...");
  2203. debugText.Add(msg);
  2204. }
  2205. AdjustAmounts();
  2206. }
  2207.  
  2208. if (cycleStep == step++ * cycleLength / MAX_CYCLE_STEPS) {
  2209. if (cycleLength > 1) {
  2210. Echo(msg = "Scanning quota panels ...");
  2211. debugText.Add(msg);
  2212. }
  2213. ProcessQuotaPanels(argQuotaStable);
  2214. }
  2215.  
  2216. if (cycleStep == step++ * cycleLength / MAX_CYCLE_STEPS) {
  2217. if (cycleLength > 1) {
  2218. Echo(msg = "Processing limited item requests ...");
  2219. debugText.Add(msg);
  2220. }
  2221. AllocateItems(true); // limited requests
  2222. }
  2223.  
  2224. if (cycleStep == step++ * cycleLength / MAX_CYCLE_STEPS) {
  2225. if (cycleLength > 1) {
  2226. Echo(msg = "Managing refineries ...");
  2227. debugText.Add(msg);
  2228. }
  2229. ManageRefineries();
  2230. }
  2231.  
  2232. if (cycleStep == step++ * cycleLength / MAX_CYCLE_STEPS) {
  2233. if (cycleLength > 1) {
  2234. Echo(msg = "Processing remaining item requests ...");
  2235. debugText.Add(msg);
  2236. }
  2237. AllocateItems(false); // unlimited requests
  2238. }
  2239.  
  2240. if (cycleStep == step++ * cycleLength / MAX_CYCLE_STEPS) {
  2241. if (cycleLength > 1) {
  2242. Echo(msg = "Managing assemblers ...");
  2243. debugText.Add(msg);
  2244. }
  2245. ManageAssemblers();
  2246. }
  2247.  
  2248. if (cycleStep == step++ * cycleLength / MAX_CYCLE_STEPS) {
  2249. if (cycleLength > 1) {
  2250. Echo(msg = "Scanning production ...");
  2251. debugText.Add(msg);
  2252. }
  2253. ScanProduction();
  2254. }
  2255.  
  2256. if (cycleStep == step++ * cycleLength / MAX_CYCLE_STEPS) {
  2257. if (cycleLength > 1) {
  2258. Echo(msg = "Updating inventory panels ...");
  2259. debugText.Add(msg);
  2260. }
  2261. UpdateInventoryPanels();
  2262.  
  2263. // update persistent data after one full cycle
  2264. Me.CustomData = "TIM_version=" + (lastVersion = VERSION);
  2265. }
  2266.  
  2267. if (step != MAX_CYCLE_STEPS)
  2268. debugText.Add("ERROR: step"+step+" of "+MAX_CYCLE_STEPS);
  2269.  
  2270. // update script status and debug panels on every cycle step
  2271. cycleStep++;
  2272. time = (int)((DateTime.Now - dtStart).TotalMilliseconds + 0.5);
  2273. load = (int)(100.0f * Runtime.CurrentInstructionCount / Runtime.MaxInstructionCount + 0.5);
  2274. i = 0;
  2275. statsLog[numCalls % statsLog.Length] = (
  2276. ScreenFormatter.Format(""+numCalls, 80, out i, 1) +
  2277. ScreenFormatter.Format(cycleStep+" / "+cycleLength, 125+i, out i, 1, true) +
  2278. ScreenFormatter.Format(time+" ms", 145+i, out i, 1) +
  2279. ScreenFormatter.Format(load+"%", 105+i, out i, 1, true) +
  2280. ScreenFormatter.Format(""+numXfers, 65+i, out i, 1, true) +
  2281. ScreenFormatter.Format(""+numRefs, 65+i, out i, 1, true) +
  2282. ScreenFormatter.Format(""+numAsms, 65+i, out i, 1, true) +
  2283. "\n"
  2284. );
  2285. Echo(msg = ((cycleLength > 1) ? ("Cycle "+cycleStep+" of "+cycleLength+" completed in ") : "Completed in ")+time+" ms, "+load+"% load ("+Runtime.CurrentInstructionCount+" instructions)");
  2286. debugText.Add(msg);
  2287. UpdateStatusPanels();
  2288. if (cycleStep >= cycleLength)
  2289. cycleStep = 0;
  2290.  
  2291. // if we can spare the cycles, render the filler
  2292. if (panelFiller == "" & numCalls > cycleLength)
  2293. panelFiller = "This easter egg will return when Keen raises the 100kb script code size limit!\n";
  2294. } // Main()
  2295.  
  2296.  
  2297. /*
  2298. * ScreenFormatter
  2299. */
  2300.  
  2301.  
  2302. public class ScreenFormatter
  2303. {
  2304. private static Dictionary<char,byte> charWidth = new Dictionary<char,byte>();
  2305. private static Dictionary<string,int> textWidth = new Dictionary<string,int>();
  2306. private static byte SZ_SPACE;
  2307. private static byte SZ_SHYPH;
  2308.  
  2309. public static int GetWidth(string text, bool memoize=false) {
  2310. int width;
  2311. if (!textWidth.TryGetValue(text, out width)) {
  2312. // this isn't faster (probably slower) but it's less "complex"
  2313. // according to SE's silly branch count metric
  2314. Dictionary<char,byte> cW = charWidth;
  2315. string t = text + "\0\0\0\0\0\0\0";
  2316. int i = t.Length - (t.Length % 8);
  2317. byte w0, w1, w2, w3, w4, w5, w6, w7;
  2318. while (i > 0) {
  2319. cW.TryGetValue(t[i-1], out w0);
  2320. cW.TryGetValue(t[i-2], out w1);
  2321. cW.TryGetValue(t[i-3], out w2);
  2322. cW.TryGetValue(t[i-4], out w3);
  2323. cW.TryGetValue(t[i-5], out w4);
  2324. cW.TryGetValue(t[i-6], out w5);
  2325. cW.TryGetValue(t[i-7], out w6);
  2326. cW.TryGetValue(t[i-8], out w7);
  2327. width += w0+w1+w2+w3+w4+w5+w6+w7;
  2328. i -= 8;
  2329. }
  2330. if (memoize)
  2331. textWidth[text] = width;
  2332. }
  2333. return width;
  2334. } // GetWidth()
  2335.  
  2336. public static string Format(string text, int width, out int unused, int align=-1, bool memoize=false) {
  2337. int spaces, bars;
  2338.  
  2339. // '\u00AD' is a "soft hyphen" in UTF16 but Panels don't wrap lines so
  2340. // it's just a wider space character ' ', useful for column alignment
  2341. unused = width - GetWidth(text, memoize);
  2342. if (unused <= SZ_SPACE / 2)
  2343. return text;
  2344. spaces = unused / SZ_SPACE;
  2345. bars = 0;
  2346. unused -= spaces * SZ_SPACE;
  2347. if (2 * unused <= SZ_SPACE + (spaces * (SZ_SHYPH - SZ_SPACE))) {
  2348. bars = Math.Min(spaces, (int)((float)unused / (SZ_SHYPH - SZ_SPACE) + 0.4999f));
  2349. spaces -= bars;
  2350. unused -= bars * (SZ_SHYPH - SZ_SPACE);
  2351. } else if (unused > SZ_SPACE / 2) {
  2352. spaces++;
  2353. unused -= SZ_SPACE;
  2354. }
  2355. if (align > 0)
  2356. return new String(' ', spaces) + new String('\u00AD', bars) + text;
  2357. if (align < 0)
  2358. return text + new String('\u00AD', bars) + new String(' ', spaces);
  2359. if ((spaces % 2) > 0 & (bars % 2) == 0)
  2360. return new String(' ', spaces / 2) + new String('\u00AD', bars / 2) + text + new String('\u00AD', bars / 2) + new String(' ', spaces - (spaces / 2));
  2361. return new String(' ', spaces - (spaces / 2)) + new String('\u00AD', bars / 2) + text + new String('\u00AD', bars - (bars / 2)) + new String(' ', spaces / 2);
  2362. } // Format()
  2363.  
  2364. public static string Format(double value, int width, out int unused) {
  2365. int spaces, bars;
  2366. value = Math.Min(Math.Max(value, 0.0f), 1.0f);
  2367. spaces = width / SZ_SPACE;
  2368. bars = (int)(spaces * value + 0.5f);
  2369. unused = width - (spaces * SZ_SPACE);
  2370. return new String('I', bars) + new String(' ', spaces - bars);
  2371. } // Format()
  2372.  
  2373. public static void Init() {
  2374. InitChars( 0, "\u2028\u2029\u202F");
  2375. InitChars( 7, "'|\u00A6\u02C9\u2018\u2019\u201A");
  2376. InitChars( 8, "\u0458");
  2377. InitChars( 9, " !I`ijl\u00A0\u00A1\u00A8\u00AF\u00B4\u00B8\u00CC\u00CD\u00CE\u00CF\u00EC\u00ED\u00EE\u00EF\u0128\u0129\u012A\u012B\u012E\u012F\u0130\u0131\u0135\u013A\u013C\u013E\u0142\u02C6\u02C7\u02D8\u02D9\u02DA\u02DB\u02DC\u02DD\u0406\u0407\u0456\u0457\u2039\u203A\u2219");
  2378. InitChars(10, "(),.1:;[]ft{}\u00B7\u0163\u0165\u0167\u021B");
  2379. InitChars(11, "\"-r\u00AA\u00AD\u00BA\u0140\u0155\u0157\u0159");
  2380. InitChars(12, "*\u00B2\u00B3\u00B9");
  2381. InitChars(13, "\\\u00B0\u201C\u201D\u201E");
  2382. InitChars(14, "\u0491");
  2383. InitChars(15, "/\u0133\u0442\u044D\u0454");
  2384. InitChars(16, "L_vx\u00AB\u00BB\u0139\u013B\u013D\u013F\u0141\u0413\u0433\u0437\u043B\u0445\u0447\u0490\u2013\u2022");
  2385. InitChars(17, "7?Jcz\u00A2\u00BF\u00E7\u0107\u0109\u010B\u010D\u0134\u017A\u017C\u017E\u0403\u0408\u0427\u0430\u0432\u0438\u0439\u043D\u043E\u043F\u0441\u044A\u044C\u0453\u0455\u045C");
  2386. InitChars(18, "3FKTabdeghknopqsuy\u00A3\u00B5\u00DD\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E8\u00E9\u00EA\u00EB\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF\u00FF\u0101\u0103\u0105\u010F\u0111\u0113\u0115\u0117\u0119\u011B\u011D\u011F\u0121\u0123\u0125\u0127\u0136\u0137\u0144\u0146\u0148\u0149\u014D\u014F\u0151\u015B\u015D\u015F\u0161\u0162\u0164\u0166\u0169\u016B\u016D\u016F\u0171\u0173\u0176\u0177\u0178\u0219\u021A\u040E\u0417\u041A\u041B\u0431\u0434\u0435\u043A\u0440\u0443\u0446\u044F\u0451\u0452\u045B\u045E\u045F");
  2387. InitChars(19, "+<=>E^~\u00AC\u00B1\u00B6\u00C8\u00C9\u00CA\u00CB\u00D7\u00F7\u0112\u0114\u0116\u0118\u011A\u0404\u040F\u0415\u041D\u042D\u2212");
  2388. InitChars(20, "#0245689CXZ\u00A4\u00A5\u00C7\u00DF\u0106\u0108\u010A\u010C\u0179\u017B\u017D\u0192\u0401\u040C\u0410\u0411\u0412\u0414\u0418\u0419\u041F\u0420\u0421\u0422\u0423\u0425\u042C\u20AC");
  2389. InitChars(21, "$&GHPUVY\u00A7\u00D9\u00DA\u00DB\u00DC\u00DE\u0100\u011C\u011E\u0120\u0122\u0124\u0126\u0168\u016A\u016C\u016E\u0170\u0172\u041E\u0424\u0426\u042A\u042F\u0436\u044B\u2020\u2021");
  2390. InitChars(22, "ABDNOQRS\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D8\u0102\u0104\u010E\u0110\u0143\u0145\u0147\u014C\u014E\u0150\u0154\u0156\u0158\u015A\u015C\u015E\u0160\u0218\u0405\u040A\u0416\u0444");
  2391. InitChars(23, "\u0459");
  2392. InitChars(24, "\u044E");
  2393. InitChars(25, "%\u0132\u042B");
  2394. InitChars(26, "@\u00A9\u00AE\u043C\u0448\u045A");
  2395. InitChars(27, "M\u041C\u0428");
  2396. InitChars(28, "mw\u00BC\u0175\u042E\u0449");
  2397. InitChars(29, "\u00BE\u00E6\u0153\u0409");
  2398. InitChars(30, "\u00BD\u0429");
  2399. InitChars(31, "\u2122");
  2400. InitChars(32, "W\u00C6\u0152\u0174\u2014\u2026\u2030");
  2401. SZ_SPACE = charWidth[' '];
  2402. SZ_SHYPH = charWidth['\u00AD'];
  2403. } // Init()
  2404.  
  2405. private static void InitChars(byte width, string text) {
  2406. // more silly loop-unrolling, as in GetWidth()
  2407. Dictionary<char,byte> cW = charWidth;
  2408. string t = text + "\0\0\0\0\0\0\0";
  2409. byte w = Math.Max((byte)0, width);
  2410. int i = t.Length - (t.Length % 8);
  2411. while (i > 0) {
  2412. cW[t[--i]] = w;
  2413. cW[t[--i]] = w;
  2414. cW[t[--i]] = w;
  2415. cW[t[--i]] = w;
  2416. cW[t[--i]] = w;
  2417. cW[t[--i]] = w;
  2418. cW[t[--i]] = w;
  2419. cW[t[--i]] = w;
  2420. }
  2421. cW['\0'] = 0;
  2422. } // InitChars()
  2423.  
  2424. private int numCols;
  2425. private int numRows;
  2426. private int padding;
  2427. private List<string>[] colRowText;
  2428. private List<int>[] colRowWidth;
  2429. private int[] colAlign;
  2430. private int[] colFill;
  2431. private bool[] colBar;
  2432. private int[] colWidth;
  2433.  
  2434. public ScreenFormatter(int numCols, int padding=1) {
  2435. this.numCols = numCols;
  2436. this.numRows = 0;
  2437. this.padding = padding;
  2438. this.colRowText = new List<string>[numCols];
  2439. this.colRowWidth = new List<int>[numCols];
  2440. this.colAlign = new int[numCols];
  2441. this.colFill = new int[numCols];
  2442. this.colBar = new bool[numCols];
  2443. this.colWidth = new int[numCols];
  2444. for (int c = 0; c < numCols; c++) {
  2445. this.colRowText[c] = new List<string>();
  2446. this.colRowWidth[c] = new List<int>();
  2447. this.colAlign[c] = -1;
  2448. this.colFill[c] = 0;
  2449. this.colBar[c] = false;
  2450. this.colWidth[c] = 0;
  2451. }
  2452. } // ScreenFormatter()
  2453.  
  2454. public void Add(int col, string text, bool memoize=false) {
  2455. int width = 0;
  2456. this.colRowText[col].Add(text);
  2457. if (this.colBar[col] == false) {
  2458. width = GetWidth(text, memoize);
  2459. this.colWidth[col] = Math.Max(this.colWidth[col], width);
  2460. }
  2461. this.colRowWidth[col].Add(width);
  2462. this.numRows = Math.Max(this.numRows, this.colRowText[col].Count);
  2463. } // Add()
  2464.  
  2465. public void AddBlankRow() {
  2466. for (int c = 0; c < this.numCols; c++) {
  2467. this.colRowText[c].Add("");
  2468. this.colRowWidth[c].Add(0);
  2469. }
  2470. this.numRows++;
  2471. } // AddBlankRow()
  2472.  
  2473. public int GetNumRows() {
  2474. return this.numRows;
  2475. } // GetNumRows()
  2476.  
  2477. public int GetMinWidth() {
  2478. int width = this.padding * SZ_SPACE;
  2479. for (int c = 0; c < this.numCols; c++)
  2480. width += this.padding * SZ_SPACE + this.colWidth[c];
  2481. return width;
  2482. } // GetMinWidth()
  2483.  
  2484. public void SetAlign(int col, int align) {
  2485. this.colAlign[col] = align;
  2486. } // SetAlign()
  2487.  
  2488. public void SetFill(int col, int fill = 1) {
  2489. this.colFill[col] = fill;
  2490. } // SetFill()
  2491.  
  2492. public void SetBar(int col, bool bar = true) {
  2493. this.colBar[col] = bar;
  2494. } // SetBar()
  2495.  
  2496. public void SetWidth(int col, int width) {
  2497. this.colWidth[col] = width;
  2498. } // SetWidth()
  2499.  
  2500. public string[][] ToSpan(int width=0, int span=1) {
  2501. int c, r, s, i, j, textwidth, unused, remaining;
  2502. int[] colWidth;
  2503. byte w;
  2504. double value;
  2505. string text;
  2506. StringBuilder sb;
  2507. string[][] spanLines;
  2508.  
  2509. // clone the user-defined widths and tally fill columns
  2510. colWidth = (int[])this.colWidth.Clone();
  2511. unused = width * span - this.padding * SZ_SPACE;
  2512. remaining = 0;
  2513. for (c = 0; c < this.numCols; c++) {
  2514. unused -= this.padding * SZ_SPACE;
  2515. if (this.colFill[c] == 0)
  2516. unused -= colWidth[c];
  2517. remaining += this.colFill[c];
  2518. }
  2519.  
  2520. // distribute remaining width to fill columns
  2521. for (c = 0; c < this.numCols & remaining > 0; c++) {
  2522. if (this.colFill[c] > 0) {
  2523. colWidth[c] = Math.Max(colWidth[c], this.colFill[c] * unused / remaining);
  2524. unused -= colWidth[c];
  2525. remaining -= this.colFill[c];
  2526. }
  2527. }
  2528.  
  2529. // initialize output arrays
  2530. spanLines = new string[span][];
  2531. for (s = 0; s < span; s++)
  2532. spanLines[s] = new string[this.numRows];
  2533. span--; // make "span" inclusive so "s < span" implies one left
  2534.  
  2535. // render all rows and columns
  2536. i = 0;
  2537. sb = new StringBuilder();
  2538. for (r = 0; r < this.numRows; r++) {
  2539. sb.Clear();
  2540. s = 0;
  2541. remaining = width;
  2542. unused = 0;
  2543. for (c = 0; c < this.numCols; c++) {
  2544. unused += this.padding * SZ_SPACE;
  2545. if (r >= this.colRowText[c].Count || colRowText[c][r] == "") {
  2546. unused += colWidth[c];
  2547. } else {
  2548. // render the bar, or fetch the cell text
  2549. text = this.colRowText[c][r];
  2550. charWidth.TryGetValue(text[0], out w);
  2551. textwidth = this.colRowWidth[c][r];
  2552. if (this.colBar[c] == true) {
  2553. value = 0.0;
  2554. if (double.TryParse(text, out value))
  2555. value = Math.Min(Math.Max(value, 0.0), 1.0);
  2556. i = (int)((colWidth[c] / SZ_SPACE) * value + 0.5);
  2557. w = SZ_SPACE;
  2558. textwidth = i * SZ_SPACE;
  2559. }
  2560.  
  2561. // if the column is not left-aligned, calculate left spacing
  2562. if (this.colAlign[c] > 0) {
  2563. unused += (colWidth[c] - textwidth);
  2564. } else if (this.colAlign[c] == 0) {
  2565. unused += (colWidth[c] - textwidth) / 2;
  2566. }
  2567.  
  2568. // while the left spacing leaves no room for text, adjust it
  2569. while (s < span & unused > remaining - w) {
  2570. sb.Append(' ');
  2571. spanLines[s][r] = sb.ToString();
  2572. sb.Clear();
  2573. s++;
  2574. unused -= remaining;
  2575. remaining = width;
  2576. }
  2577.  
  2578. // add left spacing
  2579. remaining -= unused;
  2580. sb.Append(Format("", unused, out unused));
  2581. remaining += unused;
  2582.  
  2583. // if the column is not right-aligned, calculate right spacing
  2584. if (this.colAlign[c] < 0) {
  2585. unused += (colWidth[c] - textwidth);
  2586. } else if (this.colAlign[c] == 0) {
  2587. unused += (colWidth[c] - textwidth) - ((colWidth[c] - textwidth) / 2);
  2588. }
  2589.  
  2590. // while the bar or text runs to the next span, split it
  2591. if (this.colBar[c] == true) {
  2592. while (s < span & textwidth > remaining) {
  2593. j = remaining / SZ_SPACE;
  2594. remaining -= j * SZ_SPACE;
  2595. textwidth -= j * SZ_SPACE;
  2596. sb.Append(new String('I', j));
  2597. spanLines[s][r] = sb.ToString();
  2598. sb.Clear();
  2599. s++;
  2600. unused -= remaining;
  2601. remaining = width;
  2602. i -= j;
  2603. }
  2604. text = new String('I', i);
  2605. } else {
  2606. while (s < span & textwidth > remaining) {
  2607. i = 0;
  2608. while (remaining >= w) {
  2609. remaining -= w;
  2610. textwidth -= w;
  2611. charWidth.TryGetValue(text[++i], out w);
  2612. }
  2613. sb.Append(text, 0, i);
  2614. spanLines[s][r] = sb.ToString();
  2615. sb.Clear();
  2616. s++;
  2617. unused -= remaining;
  2618. remaining = width;
  2619. text = text.Substring(i);
  2620. }
  2621. }
  2622.  
  2623. // add cell text
  2624. remaining -= textwidth;
  2625. sb.Append(text);
  2626. }
  2627. }
  2628. spanLines[s][r] = sb.ToString();
  2629. }
  2630.  
  2631. return spanLines;
  2632. } // ToSpan()
  2633.  
  2634. public string ToString(int width=0) {
  2635. return String.Join("\n", this.ToSpan(width, 1)[0]);
  2636. } // ToString()
  2637.  
  2638. } // ScreenFormatter
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement