using System; using System.Collections.Generic; using Sandbox.ModAPI.Ingame; using VRage.Game.ModAPI.Ingame.Utilities; namespace AutoMiningScript { public partial class Program : MyGridProgram { public Settings Config; public Wire Bus; public double Now; public readonly Queue Events=new Queue(); RoleLogic role; Dashboard dashboard; string failure=""; bool storageFailure; double nextDraw,nextSave,nextBudgetNotice; public int BudgetDeferrals {get;private set;} #if !FLEET MyIni checkpoint; #endif public const string Version="2.0.0"; public Program() { Runtime.UpdateFrequency=UpdateFrequency.Update1; Initialize(); } static string CompiledRole() { // Preserve every preprocessor branch when compacting shared source. #if FLEET return "fleet"; #elif MARKER return "marker"; #elif WATCHDOG return "watchdog"; #else return "miner"; #endif } void Initialize() { dashboard=null;role=null;storageFailure=false; try { var defaultRole=CompiledRole();var id=defaultRole=="fleet"?"base":defaultRole+"-"+Me.EntityId; if(string.IsNullOrWhiteSpace(Me.CustomData)) {id=Settings.DefaultId(defaultRole,Me.EntityId);Me.CustomData=Settings.Template(defaultRole,id);} Config=new Settings(Me.CustomData,defaultRole,id);Me.CustomName=Config.BlockName;dashboard=new Dashboard(this);Bus=new Wire(this); #if FLEET role=new FleetController(this); #elif MARKER role=new MarkerController(this); #elif WATCHDOG role=new WatchdogController(this); #else role=new MinerController(this); #endif failure=""; storageFailure=true;Restore();storageFailure=false; #if FLEET if(((FleetController)role).Restoring)Log(((FleetController)role).RestoreStatus); else #endif Log(L.F(L.CommonReady,L.Brand,Version,Config.Id)); } catch(Exception e) {failure=e.Message;SafeClear();role=null;Echo(L.F(L.CommonInitError,failure));DrawInitializationFault();} } // Hardware self-check errors still leave a useful local console after INI parsing. sealed class InitializationFault : RoleLogic { readonly string reason; public InitializationFault(Program p,string message):base(p) {reason=message;} public override string Diagnostics => L.F(L.CommonInitError,reason); public override List GetTelemetry() => new List {new Telemetry {Id=P.Config.Id,State=FlightState.Fault,Reason=reason,ReceivedAt=P.Now}}; } void DrawInitializationFault() { if(dashboard==null || Now=nextDraw&&HasBudget(0.3)) {dashboard.Draw(fleet);nextDraw=Now+0.5;} } catch(Exception e) {storageFailure=true;failure=L.F(L.CommonStorageInvalid,e.Message);role=null;Echo(L.F(StorageHelp,failure));} return; } #endif if(!string.IsNullOrWhiteSpace(argument) && argument!="igc") { if(argument=="init") {Me.CustomName=Config.BlockName;role.Command("rescan");Log(L.CommonConfigReload);} else if(argument=="page" || argument.StartsWith("page ",Data.Ordinal))dashboard.Command(argument); else role.Command(argument.Trim()); } // Miner physics and fleet admission precede optional background work. // The engine resets its counter on the next invocation; it cannot be reset here. #if WATCHDOG || FLEET // Dock requests and watchdog heartbeats precede optional scheduling work. Bus.Drain(role.Receive); #elif TESTS if(Config.Role=="fleet")Bus.Drain(role.Receive); #endif role.Tick(dt); if(!HasBudget()) {DeferBackground();return;} #if !WATCHDOG && !FLEET #if TESTS if(Config.Role!="fleet") #endif Bus.Drain(role.Receive); #endif if(!HasBudget()) {DeferBackground();return;} Bus.Tick(); if(!HasBudget()) {DeferBackground();return;} dashboard.Update(role); if(Now>=nextDraw && HasBudget(0.45)) { dashboard.Draw(role);nextDraw=Now+0.5; if(HasBudget())Echo(L.F(L.CommonRuntime,L.Brand,Version,Config.Id,role.Diagnostics,Runtime.CurrentInstructionCount,Runtime.MaxInstructionCount)); } if(!HasBudget()) {DeferBackground();return;} if(Now>=nextSave && HasBudget(0.35))Checkpoint(); } catch(Exception e) { failure=e.Message;Log(L.F(L.CommonFault,failure)); try {if(role!=null)role.Emergency(failure);else SafeClear();}catch {SafeClear();} Echo(L.F(L.CommonFault,failure)); } } public bool HasBudget(double fraction=0.65) => Runtime.CurrentInstructionCount=nextBudgetNotice && HasBudget(0.85)) { Log(L.CommonBudgetDeferred);nextBudgetNotice=Now+10; } } public void Log(string text) { if(text==null)return;if(text.Length>240)text=text.Substring(0,240); Events.Enqueue(Now.ToString("F1",Data.Culture)+" "+text);while(Events.Count>12)Events.Dequeue(); } public void SafeClear() { #if !FLEET && !MARKER var blocks=new List();GridTerminalSystem.GetBlocksOfType(blocks,b=>b.CubeGrid==Me.CubeGrid); foreach(var block in blocks) { var thrust=block as IMyThrust;if(thrust!=null)thrust.ThrustOverridePercentage=0; var gyro=block as IMyGyro;if(gyro!=null) {gyro.Pitch=0;gyro.Yaw=0;gyro.Roll=0;gyro.GyroOverride=false;} var drill=block as IMyShipDrill;if(drill!=null)drill.Enabled=false; var control=block as IMyShipController;if(control!=null)control.DampenersOverride=true; var remote=block as IMyRemoteControl;if(remote!=null)remote.SetAutoPilotEnabled(false); } #endif } public void Save() { if(role==null)return; #if !FLEET // Small local state can be committed synchronously at world-save/calibration. var ini=NewCheckpoint();role.Save(ini);Commit(ini); #endif // Fleet Storage is the last fully completed checkpoint. Never serialize thousands // of jobs in a world-save callback or overwrite it with an incomplete snapshot. } MyIni NewCheckpoint() {var ini=new MyIni();ini.Set("storage","Version",2);ini.Set("storage","Role",Config.Role);ini.Set("storage","Id",Config.Id);return ini;} void Checkpoint() { #if FLEET string value; if(!((FleetController)role).CheckpointStorage(out value))return; if(value.Length<=1500000)Storage=value;else Log(L.CommonStorageBudget); nextSave=Now+2; #else if(checkpoint==null)checkpoint=NewCheckpoint(); if(!role.Checkpoint(checkpoint))return; Commit(checkpoint);checkpoint=null;nextSave=Now+2; #endif } void Commit(MyIni ini) { var value=ini.ToString();if(value.Length<=1500000)Storage=value;else Log(L.CommonStorageBudget); } void Restore() { if(string.IsNullOrWhiteSpace(Storage))return; #if FLEET if(((FleetController)role).BeginRestoreStorage(Storage))return; #endif var ini=new MyIni(); if(!ini.TryParse(Storage) || Data.ReadLong(ini,"storage","Version")!=2 || Data.Text(ini,"storage","Role")!=Config.Role || Data.Text(ini,"storage","Id")!=Config.Id) {Log(L.CommonStorageIncompatible);return;} try {role.Load(ini);}catch(Exception e) {role.Emergency(L.F(L.CommonStorageInvalid,e.Message));} } } }