using System; using System.Collections.Generic; using Sandbox.ModAPI.Ingame; using VRage.Game.ModAPI.Ingame.Utilities; using VRageMath; namespace AutoMiningScript { public partial class Program { // All coordinates are frozen in world space. A moving base cannot drag a mine. public class MiningRegion { public string Id = "", Parent = "", Ore = "", Status = "Pending"; public Vector3D Center, Direction = Data.Forward, Up = Data.Up; public double Radius, Width, Height, Depth; public long EntityId; public bool Gravity, Search, Patch, RingFound; public int Ring, EmptyRings; // A persisted cursor replaces command-time loops over hundreds of holes. public string Generation=""; public int Cursor, Total, Columns, Rows, Added; public double Spacing, ScanDistance, BaseClearance; public void WriteGeneration(MyIni i,string s) { i.Set(s,"Generation",Generation);i.Set(s,"Cursor",Cursor);i.Set(s,"Total",Total);i.Set(s,"Columns",Columns);i.Set(s,"Rows",Rows);i.Set(s,"Added",Added); i.Set(s,"Spacing",Spacing);i.Set(s,"ScanDistance",ScanDistance);i.Set(s,"BaseClearance",BaseClearance); } public void Write(MyIni i, string s) { i.Set(s,"Id",Id); i.Set(s,"Parent",Parent); i.Set(s,"Ore",Ore); i.Set(s,"Status",Status); Data.PutVector(i,s,"Center",Center); Data.PutVector(i,s,"Direction",Direction); Data.PutVector(i,s,"Up",Up); i.Set(s,"Radius",Radius); i.Set(s,"Width",Width); i.Set(s,"Height",Height); i.Set(s,"Depth",Depth); i.Set(s,"EntityId",EntityId);WriteGeneration(i,s); i.Set(s,"Gravity",Gravity); i.Set(s,"Search",Search); i.Set(s,"Patch",Patch); i.Set(s,"RingFound",RingFound); i.Set(s,"Ring",Ring); i.Set(s,"EmptyRings",EmptyRings); } public static MiningRegion Read(MyIni i,string s) => new MiningRegion { Id=Data.Text(i,s,"Id"),Parent=Data.Text(i,s,"Parent"),Ore=Data.Text(i,s,"Ore"),Status=Data.Text(i,s,"Status"), Center=Data.ReadVector(i,s,"Center"),Direction=Data.Unit(Data.ReadVector(i,s,"Direction",Data.Forward),Data.Forward),Up=Data.ReadVector(i,s,"Up",Data.Up), Radius=Data.ReadNumber(i,s,"Radius",300),Width=Data.ReadNumber(i,s,"Width",60),Height=Data.ReadNumber(i,s,"Height",60),Depth=Data.ReadNumber(i,s,"Depth",40),EntityId=Data.ReadLong(i,s,"EntityId"), Generation=Data.Text(i,s,"Generation"),Cursor=(int)Data.ReadLong(i,s,"Cursor"),Total=(int)Data.ReadLong(i,s,"Total"),Columns=(int)Data.ReadLong(i,s,"Columns"),Rows=(int)Data.ReadLong(i,s,"Rows"),Added=(int)Data.ReadLong(i,s,"Added"),Spacing=Data.ReadNumber(i,s,"Spacing"),ScanDistance=Data.ReadNumber(i,s,"ScanDistance"),BaseClearance=Data.ReadNumber(i,s,"BaseClearance"), Gravity=Data.Flag(i,s,"Gravity"),Search=Data.Flag(i,s,"Search"),Patch=Data.Flag(i,s,"Patch"),RingFound=Data.Flag(i,s,"RingFound"),Ring=(int)Data.ReadLong(i,s,"Ring"),EmptyRings=(int)Data.ReadLong(i,s,"EmptyRings") }; } public class TaskPlanner { readonly Program p; public readonly List Jobs = new List(); public readonly List Regions = new List(); readonly HashSet discoveries = new HashSet(); readonly Queue advanceQueue=new Queue(); readonly HashSet advanceQueued=new HashSet(); MyIni checkpointTarget; int checkpointJobs,checkpointRegions,checkpointJobCursor,checkpointRegionCursor; readonly HashSet checkpointDirtyJobs=new HashSet(); readonly HashSet checkpointDirtyRegions=new HashSet(); readonly Dictionary checkpointRegionIndexes=new Dictionary(); readonly Queue generation=new Queue(); readonly Dictionary jobsById=new Dictionary(); readonly Dictionary> surfaceCells=new Dictionary>(); readonly Dictionary regionsById=new Dictionary(); int reserved,serial,advanceCursor,advanceJobCount,advanceRegionCount; MiningRegion advancing; bool advancePending,advanceBlocked; public bool Generating => generation.Count>0; public int PendingCount {get;private set;} public readonly Dictionary OwnedJobs=new Dictionary(); public void SetOwner(Job j,string owner) {if(j.Owner.Length>0)OwnedJobs.Remove(j.Owner);j.Owner=owner;if(owner.Length>0) {OwnedJobs[owner]=j;j.LastOwner=owner;Dirty(j);}} public int PendingGeneration => reserved; public int TaskSerial => serial; public void PreserveSerial(long value) { if(value<0 || value>1000000000)throw Data.Invalid(L.FleetPlannerSerialInvalid); serial=Data.Max(serial,(int)value); } public void ClearTasks() { // Task identifiers remain monotonic across clear/reload, so late packets // cannot alias a newly generated hole. Configuration is not touched. ClearLoadedTasks();advanceCursor=advanceJobCount=advanceRegionCount=0;advancePending=advanceBlocked=false; if(checkpointTarget!=null)checkpointTarget.Clear();checkpointTarget=null; checkpointJobs=checkpointRegions=checkpointJobCursor=checkpointRegionCursor=0; checkpointDirtyJobs.Clear();checkpointDirtyRegions.Clear();checkpointRegionIndexes.Clear(); recordSaving=false;recordJobCursor=recordRegionCursor=0;savedJobs.Clear();savedRegions.Clear();restoreHeader=null; } void AddRegion(MiningRegion r) {Regions.Add(r);regionsById.Add(r.Id,r);} void Generate(MiningRegion r) {generation.Enqueue(r);reserved+=r.Total-r.Cursor;} void Dirty(MiningRegion r) {if(checkpointTarget!=null||recordSaving)checkpointDirtyRegions.Add(r);} void Dirty(Job j) {if(checkpointTarget!=null||recordSaving)checkpointDirtyJobs.Add(j);} public TaskPlanner(Program program) { p=program; } double N(string section,string key,double fallback,double min,double max) => p.Config.Number(section,key,fallback,min,max); int Limit => p.Config.Integer("Survey","MaxJobs",2048,32,4096); public double FootprintWidth => N("Mining","FootprintWidth",6,0.5,100); public double FootprintHeight => N("Mining","FootprintHeight",6,0.5,100); double StepX => FootprintWidth*(1-N("Mining","Overlap",0.15,0,0.8)); double StepY => FootprintHeight*(1-N("Mining","Overlap",0.15,0,0.8)); public string[] Ores => p.Config.Text("Mining","TargetOres","").Split(','); string Id(string type) => p.Config.Id+"-"+type+"-"+(++serial).ToString(Data.Culture); public Job Find(string id) {Job j;return jobsById.TryGetValue(id,out j)?j:null;} public MiningRegion Region(string id) {MiningRegion r;return regionsById.TryGetValue(id,out r)?r:null;} public static bool Finished(Job j) => j.Outcome=="Complete" || j.Outcome=="Blocked" || j.Outcome=="SurveyHit" || j.Outcome=="SurveyEmpty" || j.Outcome=="InvalidSample"; public int OrePriority(string ore) { string[] ores=p.Config.Text("Mining","PriorityOres","Iron,Cobalt,Nickel,Silicon").Split(','), found=ore.Split(','); for(int n=0;n0 && IsTarget(found[f].Trim()) && string.Equals(ores[n].Trim(),found[f].Trim(),Data.IgnoreCase)) return ores.Length-n; return 0; } bool IsTarget(string ore) { bool any=false;foreach(var raw in Ores) {var target=raw.Trim();if(target.Length==0)continue;any=true;if(string.Equals(target,ore,Data.IgnoreCase))return true;}return !any; } public string PreferredOre(string ore) { string best="";int priority=-1; foreach(var raw in ore.Split(',')) {var found=raw.Trim();if(found.Length==0||!IsTarget(found))continue;var score=OrePriority(found);if(score>priority){best=found;priority=score;}} return best; } public int JobPriority(Job j) { if(j.Kind==JobKind.Manual)return 100;if(j.Kind==JobKind.Survey)return 0; var r=Region(j.RegionId);var ore=r!=null&&r.Patch?r.Ore:j.TargetOre; return ore.Length>0&&PreferredOre(ore).Length==0?-1:10+OrePriority(ore); } static string MergeOres(string first,string second) { var result=new List();var seen=new HashSet(StringComparer.OrdinalIgnoreCase); foreach(var raw in (first+","+second).Split(',')) {var ore=raw.Trim();if(ore.Length>0&&seen.Add(ore))result.Add(ore);}return string.Join(",",result); } string SurfaceKey(long entity,double x,double y,double z) => entity.ToString(Data.Culture)+":"+x.ToString(Data.Culture)+":"+y.ToString(Data.Culture)+":"+z.ToString(Data.Culture); void IndexSurface(Job j) { if(j.Kind==JobKind.Survey||j.EntityId==0)return; var cell=Data.Max(StepX,StepY);var key=SurfaceKey(j.EntityId,Math.Floor(j.Entry.X/cell),Math.Floor(j.Entry.Y/cell),Math.Floor(j.Entry.Z/cell));List bucket; if(!surfaceCells.TryGetValue(key,out bucket))surfaceCells[key]=bucket=new List();bucket.Add(j); } bool KnownSurface(JobReport report) { double cell=Data.Max(StepX,StepY),x=Math.Floor(report.Position.X/cell),y=Math.Floor(report.Position.Y/cell),z=Math.Floor(report.Position.Z/cell); for(int dx=-1;dx<=1;dx++)for(int dy=-1;dy<=1;dy++)for(int dz=-1;dz<=1;dz++) { List bucket;if(!surfaceCells.TryGetValue(SurfaceKey(report.EntityId,x+dx,y+dy,z+dz),out bucket))continue; foreach(Job j in bucket)if(Data.DistanceSquared(j.Entry,report.Position)=Limit) { r.Status="Sampling incomplete: job budget limit"; return null; } var j=new Job {Id=Id("job"),RegionId=r.Id,Kind=kind,Entry=entry,Direction=Data.Unit(direction,Data.Forward),Up=Data.Perpendicular(direction,r.Up),Depth=depth, Radius=N("Mining","HoleRadius",3,0.25,50),HoleIndex=Jobs.Count,EntityId=entity==0?r.EntityId:entity,Priority=kind==JobKind.Manual?100:kind==JobKind.Probe?10+OrePriority(r.Ore):0,TargetOre=r.Ore}; Jobs.Add(j);jobsById.Add(j.Id,j);IndexSurface(j);PendingCount++; return j; } public string Manual(Vector3D entry,Vector3D inward,Vector3D up,double width,double height,double depth) { if(!Data.Finite(width)||!Data.Finite(height)||!Data.Finite(depth)||width<=0||height<=0||depth<=0||width>1000||height>1000||depth>1000) throw Data.Invalid(L.FleetMarkDimensions); if(inward.LengthSquared()<0.01) throw Data.Invalid(L.FleetGpsPointsDiffer); int nx=Data.Max(1,1+(int)Math.Ceiling(Data.Max(0,width-FootprintWidth)/StepX)); int ny=Data.Max(1,1+(int)Math.Ceiling(Data.Max(0,height-FootprintHeight)/StepY)); if((long)nx*ny+Jobs.Count+reserved>Limit) throw Data.Invalid(L.FleetManualJobLimit); var r=new MiningRegion {Id=Id("manual"),Center=entry,Direction=Data.Unit(inward,Data.Forward),Up=Data.Perpendicular(inward,up),Width=width,Height=height,Depth=depth}; r.Generation="Manual";r.Columns=nx;r.Rows=ny;r.Total=nx*ny; AddRegion(r);Generate(r);GenerateStep(); return L.F(L.FleetManualQueued,r.Id,nx*ny); } public string Survey(Vector3D center,Vector3D gravity,Vector3D forward,Vector3D up,double requestedRadius) { var planet=gravity.LengthSquared()>0.01; var radius=requestedRadius>0?requestedRadius:N("Survey","Radius",planet?300:2000,20,20000); if(!Data.Finite(radius)||radius<20||radius>20000) throw Data.Invalid(L.FleetSurveyRadius); var r=new MiningRegion {Id=Id("search"),Center=center,Direction=Data.Unit(planet?gravity:forward,Data.Forward),Up=Data.Perpendicular(planet?gravity:forward,up),Radius=radius,Gravity=planet,Search=true,Depth=N("Survey","ProbeDepth",planet?60:40,1,100),BaseClearance=N("Survey","BaseClearance",80,10,2000)}; if(planet) { r.Spacing=N("Survey","Spacing",60,5,2000);int cells=(int)Math.Floor(radius/r.Spacing); var estimate=(long)(cells*2+1)*(cells*2+1); if(estimate>Limit-Jobs.Count-reserved) throw Data.Invalid(L.FleetSurveyGridLimit); r.Generation="Ground";r.Columns=cells;r.Total=(int)estimate;r.ScanDistance=N("Survey","ScanDistance",Data.Min(10000,Data.Max(300,radius)),50,10000); } else { r.Columns=p.Config.Integer("Survey","SpaceRays",42,12,128);r.Total=7*r.Columns; if(Jobs.Count+reserved+r.Total>Limit) throw Data.Invalid(L.FleetSpaceSurveyLimit); r.Generation="Space"; } AddRegion(r);Generate(r);GenerateStep(); return L.F(L.FleetSurveyQueued,r.Id); } // Enumerate each ring perimeter once, rather than scanning every inner square. static void RingPoint(int index,out int x,out int y) { if(index==0) {x=y=0;return;} int ring=(int)Math.Ceiling((Math.Sqrt(index+1)-1)/2),side=ring*2; int k=index-(side-1)*(side-1); if(k0&&work++<8&&p.HasBudget(0.55)) { var r=generation.Peek();int index=r.Cursor++;reserved--;Dirty(r); Vector3D right=Data.Cross(r.Direction,r.Up),entry=r.Center,dir=r.Direction;var depth=r.Depth;var kind=JobKind.Probe;var add=true; if(r.Generation=="Manual") { int y=index/r.Columns,column=index%r.Columns,x=y%2==0?column:r.Columns-column-1; double spanX=Data.Max(0,r.Width-FootprintWidth),spanY=Data.Max(0,r.Height-FootprintHeight); entry+=right*(r.Columns==1?0:-spanX/2+spanX*x/(r.Columns-1))+r.Up*(r.Rows==1?0:-spanY/2+spanY*y/(r.Rows-1));kind=JobKind.Manual; } else if(r.Generation=="Ground") { int x,y;RingPoint(index,out x,out y);add=x*x+y*y<=r.Columns*r.Columns; entry+=-r.Direction*r.BaseClearance+right*x*r.Spacing+r.Up*y*r.Spacing;depth=r.ScanDistance;kind=JobKind.Survey; } else if(r.Generation=="Space") { int point=index/r.Columns,ray=index%r.Columns;var basis=Data.Frame(r.Center,r.Direction,r.Up); var offset=point==1?basis.Right:point==2?basis.Left:point==4?basis.Down:point==5?basis.Forward:point==6?basis.Backward:basis.Up; double y=1-2*(ray+0.5)/r.Columns,phi=ray*2.399963229728653,radial=Math.Sqrt(Data.Max(0,1-y*y)); dir=basis.Right*(Math.Cos(phi)*radial)+basis.Up*y+basis.Forward*(Math.Sin(phi)*radial); entry+=offset*(point==0?Data.Min(r.BaseClearance,r.Radius*0.2):r.Radius*0.35); var local=entry-r.Center;var dot=Data.Dot(local,dir);depth=Data.Min(10000,-dot+Math.Sqrt(dot*dot+r.Radius*r.Radius-local.LengthSquared()));kind=JobKind.Survey; } else if(r.Generation=="Ring") { int x,y;RingPoint((2*r.Ring-1)*(2*r.Ring-1)+index,out x,out y); entry+=right*x*StepX+r.Up*y*StepY;var search=Region(r.Parent); add=Math.Abs(x)<=r.Columns&&Math.Abs(y)<=r.Rows&&(search==null||Inside(search,entry));depth=BoundDepth(search,entry,dir,depth);add=add&&depth>=1; } else throw Data.Invalid(L.FleetStoredRegionInvalid); if(add&&Add(r,kind,entry,dir,depth)!=null)r.Added++; if(r.Cursor>=r.Total) { generation.Dequeue();var emptyRing=r.Generation=="Ring"&&r.Added==0;r.Generation=""; if(emptyRing) {r.Status="Sampling complete: boundary";QueueAdvance(r.Parent);} else QueueAdvance(r.Id); } } } bool Inside(MiningRegion r,Vector3D point) { var d=point-r.Center; if(r.Gravity) {d-=r.Direction*Data.Dot(d,r.Direction); return d.LengthSquared()<=r.Radius*r.Radius;} return d.LengthSquared()<=r.Radius*r.Radius; } double BoundDepth(MiningRegion r,Vector3D entry,Vector3D direction,double depth) { if(r==null||r.Gravity) return depth; var d=entry-r.Center;double along=Data.Dot(d,direction),disc=along*along+r.Radius*r.Radius-d.LengthSquared(); return Data.Min(depth,Data.Max(0,-along+Math.Sqrt(Data.Max(0,disc)))); } public void Report(Job j,JobReport report) { Dirty(j); j.ConfirmedOres=MergeOres(j.ConfirmedOres,report.Ore); if(j.EntityId==0&&report.EntityId!=0){j.EntityId=report.EntityId;IndexSurface(j);} var existing=Region(j.RegionId); if(report.Ore.Length>0&&existing!=null&&existing.Patch) {existing.Ore=MergeOres(existing.Ore,j.ConfirmedOres);Dirty(existing);} if(Finished(j)) return; if (j.Kind == JobKind.Survey && report.Scan != null) j.Scan = report.Scan; j.Progress=Data.Max(j.Progress,Data.Clamp(report.Progress,0,j.Depth)); // A terminal report carries accumulated ores, so a reordered/dropped Discovery // message cannot erase a confirmed find. Processing is idempotent per hole. if(report.Outcome=="Complete"&&report.Ore.Length>0) Report(j,new JobReport {JobId=j.Id,Outcome="Discovery",Ore=report.Ore,Progress=report.Progress,Position=report.Position,Direction=report.Direction,Up=report.Up,EntityId=report.EntityId}); if(report.Outcome=="Discovery") { var ore=PreferredOre(report.Ore); if(ore.Length==0) return; var first=discoveries.Add(j.Id); if(existing!=null && existing.Patch) {existing.RingFound=true;Dirty(existing);return;} if(!first) return; if(j.Kind!=JobKind.Probe || existing==null) return; var patch=new MiningRegion {Id=Id("deposit"),Parent=existing.Id,Center=j.Entry,Direction=j.Direction,Up=j.Up,Patch=true,RingFound=true,Ore=j.ConfirmedOres, Width=N("Survey","ExpansionWidth",60,1,100),Height=N("Survey","ExpansionHeight",60,1,100),Depth=N("Survey","MaxDepth",100,1,100),Radius=existing.Radius,Gravity=existing.Gravity,EntityId=j.EntityId}; AddRegion(patch); j.RegionId=patch.Id; j.TargetOre=ore; return; } if(report.Outcome=="Paused") return; if(report.Outcome=="SurveyHit" && j.Kind==JobKind.Survey) { var r=Region(j.RegionId); var known=KnownSurface(report); if(r!=null&&!known&&Inside(r,report.Position)&&report.EntityId!=0&&report.Direction.LengthSquared()>0.1) { var direction=Data.Unit(report.Direction,Data.Forward);var depth=BoundDepth(r,report.Position,direction,r.Depth); if(depth>=1) Add(r,JobKind.Probe,report.Position,direction,depth,report.EntityId); } } j.Outcome=report.Outcome; if(Finished(j)) {PendingCount--;SetOwner(j,"");QueueAdvance(j.RegionId);} } void QueueAdvance(string id) {if(id.Length>0&&advanceQueued.Add(id)) advanceQueue.Enqueue(id);} public void Advance() { GenerateStep(); if(!p.HasBudget(0.55))return; if(advancing==null) { if(advanceQueue.Count==0)return; var id=advanceQueue.Dequeue();advanceQueued.Remove(id);advancing=Region(id);advanceCursor=0;advanceJobCount=Jobs.Count;advanceRegionCount=Regions.Count;advancePending=advanceBlocked=false; if(advancing==null||advancing.Status!="Pending"||advancing.Generation.Length>0) {advancing=null;return;} } // Appended jobs must not move the boundary under a cursor already scanning // child regions. Restart the summary before considering a region complete. if(advanceJobCount!=Jobs.Count||advanceRegionCount!=Regions.Count) {advanceCursor=0;advanceJobCount=Jobs.Count;advanceRegionCount=Regions.Count;advancePending=advanceBlocked=false;} var r=advancing;int work=0; while(advanceCursor=0)advanceBlocked=true;} } } if(advanceCursor=2) {r.Status="Sampling complete: two empty rings";QueueAdvance(r.Parent);return;} r.RingFound=false;r.Ring++; r.Columns=(int)Math.Floor(r.Width/(2*StepX));r.Rows=(int)Math.Floor(r.Height/(2*StepY)); if(r.Ring>Data.Max(r.Columns,r.Rows)) {r.Status="Sampling complete: range limit";QueueAdvance(r.Parent);return;} r.Total=8*r.Ring;r.Cursor=r.Added=0; if(Jobs.Count+reserved+r.Total>Limit) {r.Status="Sampling incomplete: job budget limit";QueueAdvance(r.Parent);return;} r.Generation="Ring";Generate(r); } public void Save(MyIni ini) { for(int n=0;n(discoveries))); // This small final ownership table overrides owners copied in earlier frames. var ownership=includeDiscoveries?(IEnumerable)Jobs:OwnedJobs.Values; int owners=0;foreach(Job j in ownership) if(j.Owner.Length>0&&!Finished(j)) {ini.Set("planner","OwnerJob"+owners,j.Id);ini.Set("planner","OwnerId"+owners,j.Owner);owners++;} ini.Set("planner","Owners",owners); } public bool Checkpoint(MyIni ini) { if(checkpointTarget!=ini) { checkpointTarget=ini;checkpointJobs=Jobs.Count;checkpointRegions=Regions.Count;checkpointJobCursor=checkpointRegionCursor=0;checkpointDirtyJobs.Clear();checkpointDirtyRegions.Clear();checkpointRegionIndexes.Clear(); } // Lists are append-only: extend the tail without restarting already copied work. checkpointJobs=Jobs.Count;checkpointRegions=Regions.Count; int work=0; while(checkpointJobCursor0) { var dirty=new List(checkpointDirtyJobs);int count=Data.Min(4,dirty.Count); for(int n=0;n=0&&index0) { var dirty=new List(checkpointDirtyRegions);int count=Data.Min(4,dirty.Count); for(int n=0;n savedJobs=new List(),savedRegions=new List(); int recordJobCursor,recordRegionCursor; MyIni restoreHeader; public static string StorageRecord(MyIni ini,string kind) { ini.Set("record","Kind",kind);var value=ini.ToString();if(value.Length>32768)throw Data.Invalid(L.FleetStorageRecordInvalid);return value.Length.ToString(Data.Culture)+":"+value; } string JobRecord(Job j) { var ini=new MyIni();j.Write(ini,"job");ini.Set("record","Discovered",discoveries.Contains(j.Id));return StorageRecord(ini,"Job"); } string RegionRecord(MiningRegion r) {var ini=new MyIni();r.Write(ini,"region");return StorageRecord(ini,"Region");} public bool CheckpointRecords(out List records) { records=null; if(!recordSaving) { recordSaving=true;recordJobCursor=recordRegionCursor=0;savedJobs.Clear();savedRegions.Clear();checkpointDirtyJobs.Clear();checkpointDirtyRegions.Clear();checkpointRegionIndexes.Clear(); } int work=0; while(recordJobCursor0) { var dirty=new List(checkpointDirtyJobs);work=0; foreach(Job j in dirty) {if(work++>=4||!p.HasBudget(0.55))break;savedJobs[j.HoleIndex]=JobRecord(j);checkpointDirtyJobs.Remove(j);}return false; } if(checkpointDirtyRegions.Count>0) { var dirty=new List(checkpointDirtyRegions);work=0; foreach(var r in dirty) {if(work++>=4||!p.HasBudget(0.55))break;savedRegions[checkpointRegionIndexes[r]]=RegionRecord(r);checkpointDirtyRegions.Remove(r);}return false; } if(!p.HasBudget(0.35))return false; var header=new MyIni();WriteCheckpointHeader(header,false);records=new List();records.Add(StorageRecord(header,"Planner"));records.AddRange(savedJobs);records.AddRange(savedRegions);recordSaving=false;return true; } public void BeginRecordLoad(MyIni header) { var storedSerial=Data.ReadLong(header,"planner","Serial");int jobs=(int)Data.ReadLong(header,"planner","Jobs"),regions=(int)Data.ReadLong(header,"planner","Regions"); if(storedSerial<0||storedSerial>1000000000||jobs<0||jobs>4096||regions<0||regions>4096)throw Data.Invalid(L.FleetPlannerCountsInvalid); serial=(int)storedSerial;restoreHeader=header;ClearLoadedTasks(); } void ClearLoadedTasks() { Jobs.Clear();Regions.Clear();jobsById.Clear();regionsById.Clear();surfaceCells.Clear();OwnedJobs.Clear();PendingCount=0;generation.Clear();reserved=0;advancing=null;discoveries.Clear();advanceQueue.Clear();advanceQueued.Clear(); } void RestoreJob(Job j) { Jobs.Add(j);jobsById.Add(j.Id,j);IndexSurface(j); if(!Finished(j)){PendingCount++;if(j.Owner.Length>0)OwnedJobs[j.Owner]=j;} } void RestoreRegion(MiningRegion r) { if(r.Id.Length==0||r.Width<0||r.Height<0||r.Depth<0||r.Radius<0||r.Ring<0||r.Ring>1000)throw Data.Invalid(L.FleetStoredRegionInvalid); AddRegion(r);RestoreGeneration(r);if(r.Status=="Pending")QueueAdvance(r.Id); } public void LoadRecord(MyIni ini,string kind) { if(restoreHeader==null)throw Data.Invalid(L.FleetStoredJobInvalid); if(kind=="Job") { Job j=Job.Read(ini,"job");if(jobsById.ContainsKey(j.Id)||j.HoleIndex!=Jobs.Count||Jobs.Count>=Data.ReadLong(restoreHeader,"planner","Jobs"))throw Data.Invalid(L.FleetStoredJobInvalid); j.Owner="";RestoreJob(j);if(Data.Flag(ini,"record","Discovered"))discoveries.Add(j.Id); } else { var r=MiningRegion.Read(ini,"region");if(regionsById.ContainsKey(r.Id)||Regions.Count>=Data.ReadLong(restoreHeader,"planner","Regions"))throw Data.Invalid(L.FleetStoredRegionInvalid); RestoreRegion(r); } } public void FinishRecordLoad() { if(restoreHeader==null||Jobs.Count!=Data.ReadLong(restoreHeader,"planner","Jobs")||Regions.Count!=Data.ReadLong(restoreHeader,"planner","Regions")||Jobs.Count+reserved>4096)throw Data.Invalid(L.FleetPlannerCountsInvalid); LoadOwners(restoreHeader); restoreHeader=null; } void LoadOwners(MyIni ini) { var ids=new HashSet();int owners=(int)Data.ReadLong(ini,"planner","Owners");if(owners<0||owners>16)throw Data.Invalid(L.FleetCheckpointOwnerCountInvalid); for(int n=0;n4096||r.Cursor>=r.Total||r.Columns<0||r.Columns>4096||r.Rows<0||r.Rows>4096||r.Spacing<0||r.BaseClearance<0||r.ScanDistance<0)throw Data.Invalid(L.FleetStoredRegionInvalid); if((r.Generation=="Manual"&&(r.Columns<1||r.Rows<1||(long)r.Columns*r.Rows!=r.Total))||(r.Generation=="Space"&&(r.Columns<12||r.Columns>128||r.Total!=7*r.Columns)))throw Data.Invalid(L.FleetStoredRegionInvalid); Generate(r); } public void Load(MyIni ini) { var storedSerial=Data.ReadLong(ini,"planner","Serial");if(storedSerial<0||storedSerial>1000000000)throw Data.Invalid(L.FleetPlannerSerialInvalid);serial=(int)storedSerial; int count=(int)Data.ReadLong(ini,"planner","Jobs"),regions=(int)Data.ReadLong(ini,"planner","Regions"); if(count<0||count>4096||regions<0||regions>4096) throw Data.Invalid(L.FleetPlannerCountsInvalid); ClearLoadedTasks(); var ids=new HashSet(); for(int n=0;n0) discoveries.Add(key); } } } }