import empire_ai.weasel.WeaselAI; import empire_ai.weasel.ImportData; import resources; import planet_levels; import system_pathing; import systems; interface RaceResources { void levelRequirements(Object& obj, int targetLevel, array& specs); }; final class Resources : AIComponent { RaceResources@ race; array requested; array active; int nextImportId = 0; array available; array used; int nextExportId = 0; void create() { @race = cast(ai.race); } void save(SaveFile& file) { file << nextImportId; file << nextExportId; uint cnt = 0; cnt = requested.length; file << cnt; for(uint i = 0; i < cnt; ++i) { saveImport(file, requested[i]); file << requested[i]; } cnt = active.length; file << cnt; for(uint i = 0; i < cnt; ++i) { saveImport(file, active[i]); file << active[i]; } cnt = available.length; file << cnt; for(uint i = 0; i < cnt; ++i) { saveExport(file, available[i]); file << available[i]; saveImport(file, available[i].request); } cnt = used.length; file << cnt; for(uint i = 0; i < cnt; ++i) { saveExport(file, used[i]); file << used[i]; saveImport(file, used[i].request); } } void load(SaveFile& file) { file >> nextImportId; file >> nextExportId; uint cnt = 0; file >> cnt; for(uint i = 0; i < cnt; ++i) { auto@ data = loadImport(file); file >> data; requested.insertLast(data); } file >> cnt; for(uint i = 0; i < cnt; ++i) { auto@ data = loadImport(file); file >> data; active.insertLast(data); } file >> cnt; for(uint i = 0; i < cnt; ++i) { auto@ data = loadExport(file); file >> data; @data.request = loadImport(file); available.insertLast(data); } file >> cnt; for(uint i = 0; i < cnt; ++i) { auto@ data = loadExport(file); file >> data; @data.request = loadImport(file); used.insertLast(data); } } array importIds; ImportData@ loadImport(int id) { if(id == -1) return null; for(uint i = 0, cnt = importIds.length; i < cnt; ++i) { if(importIds[i].id == id) return importIds[i]; } ImportData data; data.id = id; importIds.insertLast(data); return data; } ImportData@ loadImport(SaveFile& file) { int id = -1; file >> id; if(id == -1) return null; else return loadImport(id); } void saveImport(SaveFile& file, ImportData@ data) { int id = -1; if(data !is null) id = data.id; file << id; } array exportIds; ExportData@ loadExport(int id) { if(id == -1) return null; for(uint i = 0, cnt = exportIds.length; i < cnt; ++i) { if(exportIds[i].id == id) return exportIds[i]; } ExportData data; data.id = id; exportIds.insertLast(data); return data; } ExportData@ loadExport(SaveFile& file) { int id = -1; file >> id; if(id == -1) return null; else return loadExport(id); } void saveExport(SaveFile& file, ExportData@ data) { int id = -1; if(data !is null) id = data.id; file << id; } void postLoad(AI& ai) { importIds.length = 0; exportIds.length = 0; } void start() { focusTick(0); } void tick(double time) { } uint checkIdx = 0; void focusTick(double time) { //Do a check to make sure our resource export setup is still correct if(used.length != 0) { checkIdx = (checkIdx+1) % used.length; ExportData@ res = used[checkIdx]; if(res.request !is null && res.request.obj !is null && !res.isExportedTo(res.request.obj)) { if(log) ai.print("Break export to "+res.request.obj.name+": link changed underfoot", res.obj); breakImport(res); } else { bool valid = true; if(res.obj is null || res.obj.owner !is ai.empire || !res.obj.valid) valid = false; //Don't break these imports, we want to wait for the decay to happen else if((res.request is null || !res.request.obj.hasSurfaceComponent || res.request.obj.decayTime <= 0) && !res.obj.isAsteroid && !res.usable) { valid = false; } else if(res.request !is null) { if(res.request.obj.owner !is ai.empire || !res.request.obj.valid) valid = false; } if(!valid) breakImport(res); } } //TODO: Make sure universal unique only applies once per planet //Match requested with available for(uint i = 0, cnt = requested.length; i < cnt; ++i) { auto@ req = requested[i]; req.cycled = true; if(req.obj is null) continue; if(req.beingMet) { ai.print("Error: Requested is being met", req.obj); continue; } ExportData@ source; double sourceWeight = 0.0; for(uint j = 0, jcnt = available.length; j < jcnt; ++j) { auto@ av = available[j]; if(av.request !is null) { ai.print("Error: Available is being used", av.obj); continue; } if(!req.spec.meets(av.resource, av.obj, req.obj)) continue; if(!av.usable || av.obj is null || !av.obj.valid || av.obj.owner !is ai.empire) continue; if(!canTradeBetween(av.obj, req.obj)) continue; if(av.localOnly && av.obj !is req.obj) continue; double weight = 1.0; if(req.obj is av.obj) weight = INFINITY; if(weight > sourceWeight) { sourceWeight = weight; @source = av; } } if(source !is null) { link(req, source); --i; --cnt; } } } void turn() { } bool get_hasOpenRequests() { for(uint i = 0, cnt = requested.length; i < cnt; ++i) { auto@ req = requested[i]; if(req.isOpen) return true; } return false; } TradePath tradePather; int tradeDistance(Region& fromRegion, Region& toRegion) { @tradePather.forEmpire = ai.empire; tradePather.generate(getSystem(fromRegion), getSystem(toRegion), keepCache=true); if(!tradePather.valid) return -1; return tradePather.pathSize - 1; } bool canTradeBetween(Object& fromObj, Object& toObj) { Region@ fromRegion = fromObj.region; if(fromRegion is null) return false; Region@ toRegion = toObj.region; if(toRegion is null) return false; return canTradeBetween(fromRegion, toRegion); } bool canTradeBetween(Region& fromRegion, Region& toRegion) { if(fromRegion.sharesTerritory(ai.empire, toRegion)) return true; int dist = tradeDistance(fromRegion, toRegion); if(dist < 0) return false; return true; } void link(ImportData@ req, ExportData@ source) { //Manage the data @source.request = req; @source.developUse = null; req.set(source); requested.remove(req); active.insertLast(req); req.beingMet = true; available.remove(source); used.insertLast(source); if(log) ai.print("link "+source.resource.name+" from "+source.obj.name+" to "+req.obj.name); //Perform the actual export if(source.obj !is req.obj) source.obj.exportResourceByID(source.resourceId, req.obj); else source.obj.exportResourceByID(source.resourceId, null); } ImportData@ requestResource(Object& toObject, ResourceSpec& spec, bool forLevel = false, bool activate = true, bool prioritize = false) { ImportData data; data.idleSince = gameTime; data.id = nextImportId++; @data.obj = toObject; @data.spec = spec; data.forLevel = forLevel; if(log) ai.print("requested resource: "+spec.dump(), toObject); if(activate) { if(prioritize) requested.insertAt(0, data); else requested.insertLast(data); } return data; } ExportData@ availableResource(Object& fromObject, const ResourceType& resource, int id) { ExportData data; data.id = nextExportId++; @data.obj = fromObject; @data.resource = resource; data.resourceId = id; if(log) ai.print("available resource: "+resource.name, fromObject); available.insertLast(data); return data; } void checkReplaceCurrent(ExportData@ res) { //If the planet that this resource is on is currently importing this same resource, switch it around if(res.request !is null) return; for(uint i = 0, cnt = used.length; i < cnt; ++i) { auto@ other = used[i]; auto@ request = other.request; if(request is null) continue; if(request.obj !is res.obj) continue; if(request.spec.meets(res.resource, res.obj, res.obj)) { //Swap the import with using the local resource if(other.resource.exportable) { breakImport(other); link(request, res); return; } } } } array checkResources; array@ availableResource(Object& fromObject) { array list; checkResources.syncFrom(fromObject.getNativeResources()); uint nativeCount = checkResources.length; for(uint i = 0; i < nativeCount; ++i) { auto@ r = checkResources[i].type; if(r !is null) list.insertLast(availableResource(fromObject, r, checkResources[i].id)); } return list; } ExportData@ findResource(Object@ obj, int resourceId) { for(uint i = 0, cnt = available.length; i < cnt; ++i) { if(available[i].obj is obj && available[i].resourceId == resourceId) return available[i]; } for(uint i = 0, cnt = used.length; i < cnt; ++i) { if(used[i].obj is obj && used[i].resourceId == resourceId) return used[i]; } return null; } ImportData@ findUnclaimed(ExportData@ forResource) { for(uint i = 0, cnt = requested.length; i < cnt; ++i) { auto@ req = requested[i]; if(req.claimedFor) continue; if(req.beingMet) continue; if(!req.spec.meets(forResource.resource, forResource.obj, req.obj)) continue; if(!canTradeBetween(req.obj, forResource.obj)) continue; return req; } return null; } void breakImport(ImportData@ data) { if(data.fromObject !is null) { auto@ source = findResource(data.fromObject, data.resourceId); if(source !is null) { breakImport(source); return; } } @data.fromObject = null; data.resourceId = -1; data.beingMet = false; data.idleSince = gameTime; active.remove(data); requested.insertAt(0, data); } void breakImport(ExportData@ data) { if(data.request !is null) { if(data.request.obj !is data.obj) data.obj.exportResource(data.resourceId, null); data.request.beingMet = false; @data.request.fromObject = null; data.request.resourceId = -1; data.request.idleSince = gameTime; active.remove(data.request); requested.insertAt(0, data.request); @data.request = null; } used.remove(data); available.insertLast(data); } void cancelRequest(ImportData@ data) { if(data.beingMet) { breakImport(data); active.remove(data); } else { requested.remove(data); } } void removeResource(ExportData@ data) { if(data.request !is null) { breakImport(data); used.remove(data); @data.obj = null; } else { available.remove(data); @data.obj = null; } } ImportData@ claimImport(ImportData@ data) { data.beingMet = true; requested.remove(data); active.insertLast(data); return data; } void relinquishImport(ImportData@ data) { data.beingMet = false; active.remove(data); requested.insertLast(data); } void organizeImports(Object& obj, int targetLevel, ImportData@ before = null) { //Organize any imports for this object so it tries to get to a particular target level if(log) ai.print("Organizing imports for level", obj, targetLevel); //Get the requirement list const PlanetLevel@ lvl = getPlanetLevel(obj, targetLevel); if(lvl is null) { ai.print("Error: could not find planet level", obj, targetLevel); return; //Welp, can't do nothing here } //Collect all the requests this planet currently has outstanding array activeRequests; for(uint i = 0, cnt = requested.length; i < cnt; ++i) { auto@ req = requested[i]; if(req.obj !is obj) continue; if(!req.forLevel) continue; activeRequests.insertLast(req); } for(uint i = 0, cnt = active.length; i < cnt; ++i) { auto@ req = active[i]; if(req.obj !is obj) continue; if(!req.forLevel) continue; activeRequests.insertLast(req); } //TODO: This needs to be able to deal with dummy resources //Match import requests with level requirements array addSpecs; const ResourceRequirements@ reqs = lvl.reqs; for(uint i = 0, cnt = reqs.reqs.length; i < cnt; ++i) { auto@ need = reqs.reqs[i]; for(uint n = 0; n < need.amount; ++n) addSpecs.insertLast(implementSpec(need)); } if(race !is null) race.levelRequirements(obj, targetLevel, addSpecs); for(uint i = 0, cnt = addSpecs.length; i < cnt; ++i) { auto@ spec = addSpecs[i]; bool foundMatch = false; for(uint j = 0, jcnt = activeRequests.length; j < jcnt; ++j) { if(activeRequests[j].spec == spec) { foundMatch = true; activeRequests.removeAt(j); break; } } if(foundMatch) { addSpecs.removeAt(i); --i; --cnt; } } //Cancel any import requests that we don't need anymore for(uint i = 0, cnt = activeRequests.length; i < cnt; ++i) cancelRequest(activeRequests[i]); //Insert any imports above any imports of the planet we're exporting to int place = -1; if(before !is null) { for(uint i = 0, cnt = requested.length; i < cnt; ++i) { if(requested[i] is before) { place = int(i); break; } } } //Insert everything we need to add addSpecs.sortDesc(); for(uint i = 0, cnt = addSpecs.length; i < cnt; ++i) { ImportData@ req = requestResource(obj, addSpecs[i], forLevel=true, activate=false); if(place == -1) { requested.insertLast(req); } else { requested.insertAt(place, req); place += 1; } } } void killImportsTo(Object& obj) { for(uint i = 0, cnt = requested.length; i < cnt; ++i) { if(requested[i].obj is obj) { cancelRequest(requested[i]); --i; --cnt; } } for(uint i = 0, cnt = active.length; i < cnt; ++i) { if(active[i].obj is obj) { cancelRequest(active[i]); --i; --cnt; } } } void killResourcesFrom(Object& obj) { for(uint i = 0, cnt = available.length; i < cnt; ++i) { if(available[i].obj is obj) { removeResource(available[i]); --i; --cnt; } } for(uint i = 0, cnt = used.length; i < cnt; ++i) { if(used[i].obj is obj) { removeResource(used[i]); --i; --cnt; } } } ImportData@ getImport(const string& fromName, uint index = 0) { for(uint i = 0, cnt = requested.length; i < cnt; ++i) { if(requested[i].obj.name.equals_nocase(fromName)) { if(index == 0) return requested[i]; else index -= 1; } } for(uint i = 0, cnt = active.length; i < cnt; ++i) { if(active[i].obj.name.equals_nocase(fromName)) { if(index == 0) return active[i]; else index -= 1; } } return null; } ExportData@ getExport(const string& fromName, uint index = 0) { for(uint i = 0, cnt = available.length; i < cnt; ++i) { if(available[i].obj.name.equals_nocase(fromName)) { if(index == 0) return available[i]; else index -= 1; } } for(uint i = 0, cnt = used.length; i < cnt; ++i) { if(used[i].obj.name.equals_nocase(fromName)) { if(index == 0) return used[i]; else index -= 1; } } return null; } void getImportsOf(array& output, uint resType, Planet@ toPlanet = null) { for(uint i = 0, cnt = active.length; i < cnt; ++i) { auto@ req = active[i]; if(req.spec.type != RST_Specific) continue; if(req.spec.resource.id != resType) continue; if(toPlanet !is null && req.obj !is toPlanet) continue; output.insertLast(req); } for(uint i = 0, cnt = requested.length; i < cnt; ++i) { auto@ req = requested[i]; if(req.spec.type != RST_Specific) continue; if(req.spec.resource.id != resType) continue; if(toPlanet !is null && req.obj !is toPlanet) continue; output.insertLast(req); } } void dumpRequests(Object@ forObject = null) { for(uint i = 0, cnt = requested.length; i < cnt; ++i) { if(forObject !is null && requested[i].obj !is forObject) continue; print(requested[i].obj.name+" requests "+requested[i].spec.dump()); } if(forObject !is null) { for(uint i = 0, cnt = used.length; i < cnt; ++i) { if(used[i].request is null || used[i].request.obj !is forObject) continue; print(used[i].request.obj.name+" is getting "+used[i].request.spec.dump()+" from "+used[i].obj.name); } } } }; AIComponent@ createResources() { return Resources(); }