import resources; import regions.regions; import saving; import cargo; import attributes; from systems import hasTradeAdjacent; Asteroid@ createAsteroid(const vec3d& position, Region@ region = null, bool delay = false) { ObjectDesc desc; desc.type = OT_Asteroid; desc.radius = 5.0; desc.position = position; desc.name = locale::ASTEROID; desc.flags |= objNoDamage; if(region !is null) desc.delayedCreation = true; Asteroid@ obj = Asteroid(desc); if(region !is null) { @obj.region = region; obj.finalizeCreation(); region.enterRegion(obj); } if(!delay) obj.initMesh(); return obj; } tidy class AsteroidScript { StrategicIconNode@ icon; MeshNode@ baseNode; array available; array costs; array exploited; array nativeIds; bool delta = false; uint resourceLimit = 1; uint currentResources = 0; uint limitMod = 0; void load(Asteroid& obj, SaveFile& file) { loadObjectStates(obj, file); if(file >= SV_0122) { file >> cast(obj.Orbit); file >> cast(obj.Cargo); } else { file >> cast(obj.Resources); file >> cast(obj.Orbit); } if(file >= SV_0125) file >> cast(obj.Resources); if(file < SV_0122 || file >= SV_0125) { Object@ origin; @origin = file.readObject(); uint cnt = 0; file >> cnt; available.length = cnt; costs.length = cnt; exploited.length = cnt; nativeIds.length = cnt; for(uint i = 0; i < cnt; ++i) { @available[i] = getResource(file.readIdentifier(SI_Resource)); file >> costs[i]; file >> exploited[i]; file >> nativeIds[i]; } file >> resourceLimit; file >> limitMod; file >> currentResources; } obj.HasBase = (obj.owner !is null && obj.owner.valid) ? 1.f : 0.f; } void postLoad(Asteroid& obj) { makeMesh(obj); } void save(Asteroid& obj, SaveFile& file) { saveObjectStates(obj, file); file << cast(obj.Orbit); file << cast(obj.Cargo); file << cast(obj.Resources); file << obj.origin; uint cnt = available.length; file << cnt; for(uint i = 0; i < cnt; ++i) { file.writeIdentifier(SI_Resource, available[i].id); file << costs[i]; file << exploited[i]; file << nativeIds[i]; } file << resourceLimit; file << limitMod; file << currentResources; } void postInit(Asteroid& obj) { obj.setImportEnabled(false); obj.setResourceLevel(4); obj.sightRange = 0; obj.modCargoStorage(+INFINITY); } void destroy(Asteroid& obj) { obj.destroyObjResources(); if(obj.region !is null) obj.region.removeStrategicIcon(-1, icon); icon.markForDeletion(); @icon = null; if(baseNode !is null) { baseNode.markForDeletion(); @baseNode = null; } if(obj.owner !is null && obj.owner.valid) obj.owner.unregisterAsteroid(obj); leaveRegion(obj); } bool onOwnerChange(Asteroid& obj, Empire@ prevOwner) { regionOwnerChange(obj, prevOwner); if(prevOwner !is null && prevOwner.valid) prevOwner.unregisterAsteroid(obj); if(obj.owner !is null && obj.owner.valid) obj.owner.registerAsteroid(obj); obj.changeResourceOwner(prevOwner); bool hasBase = obj.owner !is null && obj.owner.valid; obj.HasBase = hasBase ? 1.f : 0.f; if(hasBase && baseNode is null) { @baseNode = MeshNode(model::MiningBase, material::GenericPBR_MiningBase); nodeSyncObject(baseNode, obj); } else if(baseNode !is null && !hasBase) { baseNode.markForDeletion(); @baseNode = null; } return false; } void initMesh(Asteroid& obj) { makeMesh(obj); } void makeMesh(Asteroid& obj) { MeshDesc mesh; switch(obj.id % 4) { case 0: @mesh.model = model::Asteroid1; break; case 1: @mesh.model = model::Asteroid2; break; case 2: @mesh.model = model::Asteroid3; break; case 3: @mesh.model = model::Asteroid4; break; } switch(obj.id % 3) { case 0: @mesh.material = material::AsteroidPegmatite; break; case 1: @mesh.material = material::AsteroidMagnetite; break; case 2: @mesh.material = material::AsteroidTonalite; break; } mesh.memorable = true; @icon = StrategicIconNode(); if(obj.cargoTypes != 0) icon.establish(obj, 0.015, spritesheet::OreAsteroidIcon, 0); else icon.establish(obj, 0.015, spritesheet::AsteroidIcon, 0); icon.memorable = true; bindMesh(obj, mesh); if(obj.region !is null) { if(!obj.region.initialized) @obj.region = null; else obj.region.addStrategicIcon(-1, obj, icon); if(obj.region !is null) { Node@ node = obj.getNode(); node.hintParentObject(obj.region, false); } } bool hasBase = obj.owner !is null && obj.owner.valid; if(hasBase && baseNode is null) { @baseNode = MeshNode(model::MiningBase, material::GenericPBR_MiningBase); nodeSyncObject(baseNode, obj); } } float timer = 0.f; void occasional_tick(Asteroid& obj) { Region@ region = obj.region; bool engaged = obj.engaged; obj.inCombat = engaged; obj.engaged = false; if(engaged && region !is null) region.EngagedMask |= obj.owner.mask; } double tick(Asteroid& obj, double time) { Region@ prevRegion = obj.region; if(updateRegion(obj)) { Region@ newRegion = obj.region; if(prevRegion !is null) prevRegion.removeStrategicIcon(-1, icon); if(newRegion !is null) newRegion.addStrategicIcon(-1, obj, icon); @prevRegion = newRegion; Node@ node = obj.getNode(); if(node !is null) node.hintParentObject(newRegion, false); } if(prevRegion is null && isOutsideUniverseExtents(obj.position)) limitToUniverseExtents(obj.position); icon.visible = obj.isVisibleTo(playerEmpire); obj.orbitTick(time); obj.resourceTick(time); //Tick occasional stuff timer -= float(time); if(timer <= 0.f) { occasional_tick(obj); timer = 1.f; } //Asteroids lose ownership if not in an owned or neutral system if(obj.owner.valid) { if(prevRegion !is null && prevRegion.PlanetsMask != 0 && prevRegion.PlanetsMask & obj.owner.mask == 0) { if(!hasTradeAdjacent(obj.owner, prevRegion)) { clearSetup(obj); return 0.2; } } } //Asteroids are destroyed when they run out of cargo or resources if(obj.cargoTypes == 0 && obj.nativeResourceCount == 0) obj.destroy(); return 0.2; } vec3d get_strategicIconPosition(Asteroid& obj) { if(icon is null) return obj.position; return icon.position; } bool canDevelop(Asteroid& obj, Empire@ emp) { return (!obj.owner.valid || obj.owner is emp) && currentResources < resourceLimit; } bool canGainLimit(Asteroid& obj, Empire@ emp) { if(!obj.owner.valid || obj.owner !is emp) return false; return resourceLimit < available.length; } void addAvailable(Asteroid& obj, uint resource, double cost) { const ResourceType@ type = getResource(resource); if(type is null) return; available.insertLast(type); costs.insertLast(cost); exploited.insertLast(false); int id = obj.addResource(type.id); obj.setResourceDisabled(id, true); nativeIds.insertLast(id); delta = true; } uint getAvailableCount() { if(currentResources >= resourceLimit) return 0; return available.length; } uint getAvailable(uint index) { if(index >= available.length) return uint(-1); if(exploited[index]) return uint(-1); return available[index].id; } double getAvailableCost(uint index) { if(index >= costs.length) return -1.0; if(exploited[index]) return -1.0; return costs[index]; } double getAvailableCostFor(uint resId) { const ResourceType@ type = getResource(resId); if(type is null) return -1.0; for(uint i = 0, cnt = available.length; i < cnt; ++i) { if(exploited[i]) continue; if(available[i] is type) return costs[i]; } return -1.0; } void setup(Asteroid& obj, Object@ origin, Empire@ emp, uint resource) { if(obj.owner.valid && emp !is obj.owner) return; if(currentResources >= resourceLimit) return; const ResourceType@ type = getResource(resource); if(type is null) return; bool found = false; uint foundIndex = 0; for(uint i = 0, cnt = available.length; i < cnt; ++i) { if(exploited[i]) continue; if(available[i] is type) { found = true; foundIndex = i; break; } } if(!found) return; Object@ queued; exploited[foundIndex] = true; obj.setResourceDisabled(nativeIds[foundIndex], false); if(!obj.owner.valid) { @obj.owner = emp; @obj.origin = origin; obj.name = type.name+" "+locale::ASTEROID; emp.modAttribute(EA_MiningBasesBuilt, AC_Add, 1); } //Remove all the fake resources and remember the queue currentResources += 1; if(currentResources >= resourceLimit) { for(uint i = 0, cnt = available.length; i < cnt; ++i) { if(exploited[i]) continue; obj.removeResource(nativeIds[i]); nativeIds[i] = -1; } } delta = true; } void clearSetup(Asteroid& obj) { if(currentResources == 0) return; delta = true; currentResources = 0; Object@ queued = obj.nativeResourceDestination[0]; uint qtype = obj.nativeResourceType[0]; for(uint i = 0, cnt = obj.nativeResourceCount; i < cnt; ++i) obj.removeResource(obj.nativeResourceId[i]); Empire@ prevOwner = obj.owner; @obj.owner = defaultEmpire; @obj.origin = null; obj.name = locale::ASTEROID; for(uint i = 0, cnt = available.length; i < cnt; ++i) { exploited[i] = false; if(costs[i] <= 0) costs[i] = available[i].asteroidCost; int id = obj.addResource(available[i].id); obj.setResourceDisabled(id, true); nativeIds[i] = id; if(queued !is null && qtype == available[i].id && prevOwner.valid) obj.exportResource(prevOwner, id, queued); } } void checkLimit(Asteroid& obj, uint prevLimit) { bool wasLimited = currentResources >= prevLimit; bool nowLimited = currentResources >= resourceLimit; if(wasLimited) { if(!nowLimited) { for(uint i = 0, cnt = available.length; i < cnt; ++i) { if(exploited[i]) { obj.setResourceDisabled(nativeIds[i], false); } else { nativeIds[i] = obj.addResource(available[i].id); obj.setResourceDisabled(nativeIds[i], true); } } } } else { if(nowLimited) { for(uint i = 0, cnt = available.length; i < cnt; ++i) { if(exploited[i]) continue; obj.removeResource(nativeIds[i]); nativeIds[i] = -1; } } } if(nowLimited) { uint requireDisable = currentResources - resourceLimit; for(uint i = 0, cnt = available.length; i < cnt; ++i) { if(!exploited[i]) continue; Object@ dest = obj.getNativeResourceDestinationByID(obj.owner, nativeIds[i]); if(dest !is null) { obj.setResourceDisabled(nativeIds[i], false); continue; } if(requireDisable > 0) { obj.setResourceDisabled(nativeIds[i], true); requireDisable -= 1; } else { obj.setResourceDisabled(nativeIds[i], false); } } for(uint i = 0, cnt = available.length; i < cnt && requireDisable > 0; ++i) { if(!exploited[i]) continue; Object@ dest = obj.getNativeResourceDestinationByID(obj.owner, nativeIds[i]); if(dest is null) continue; obj.setResourceDisabled(nativeIds[i], true); requireDisable -= 1; } } } void morphTo(Asteroid& obj, uint resId, double cost) { auto@ resource = getResource(resId); if(resource is null) return; for(uint i = 0, cnt = available.length; i < cnt; ++i) { if(nativeIds[i] != -1) obj.removeResource(nativeIds[i]); nativeIds[i] = -1; } currentResources = 0; available.length = 0; exploited.length = 0; nativeIds.length = 0; costs.length = 0; addAvailable(obj, resource.id, cost); if(obj.owner !is null && obj.owner.valid) setup(obj, obj.origin, obj.owner, resource.id); } void setResourceLimit(Asteroid& obj, uint newLimit) { if(newLimit + limitMod == resourceLimit) return; uint prevLimit = resourceLimit; resourceLimit = newLimit + limitMod; checkLimit(obj, prevLimit); delta = true; } void modResourceLimitMod(Asteroid& obj, int mod) { limitMod += mod; uint prevLimit = resourceLimit; resourceLimit += mod; checkLimit(obj, prevLimit); delta = true; } void _writeAsteroid(const Asteroid& obj, Message& msg) { msg << obj.origin; uint cnt = available.length; msg.writeSmall(cnt); for(uint i = 0; i < cnt; ++i) { msg.writeLimited(available[i].id, getResourceCount()-1); msg << costs[i]; msg << exploited[i]; } msg.writeSmall(resourceLimit); msg.writeSmall(currentResources); } void syncInitial(const Asteroid& obj, Message& msg) { _writeAsteroid(obj, msg); obj.writeResources(msg); obj.writeCargo(msg); obj.writeOrbit(msg); } bool syncDelta(const Asteroid& obj, Message& msg) { bool used = false; if(delta) { used = true; delta = false; msg.write1(); _writeAsteroid(obj, msg); } else msg.write0(); if(obj.writeResourceDelta(msg)) used = true; else msg.write0(); if(obj.writeCargoDelta(msg)) used = true; else msg.write0(); if(obj.writeOrbitDelta(msg)) used = true; else msg.write0(); return used; } void syncDetailed(const Asteroid& obj, Message& msg) { _writeAsteroid(obj, msg); obj.writeResources(msg); obj.writeCargo(msg); obj.writeOrbit(msg); } };