import saving; import object_creation; import statuses; from statuses import StatusHook; from map_generation import generatedGalaxies, GalaxyData; from empire import Creeps; import systems; import system_pathing; import artifacts; import Artifact@ createArtifact(const vec3d&, const ArtifactType@, Region@ region = null) from "objects.Artifact"; import bool getCheatsEverOn() from "cheats"; locked_int ArtifactCount = 0; double storedEnergy = 0.0; const double SPAWN_OFFSET = 1000.0; const double MOVE_OFFSET = 500.0; const double MOVE_VARIANCE = 200.0; const double ARTIF_OFFSET = 350.0; const double ARTIF_VARIANCE = 50.0; void tick(double time) { double factor = double(systemCount) * config::TARGET_ARTIFACTS_PER_SYSTEM; double count = ArtifactCount.value; if(count != 0) factor /= count; for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) { Empire@ other = getEmpire(i); storedEnergy += other.EnergyIncome * time * factor * other.EnergyEfficiency; } if(storedEnergy >= config::ENERGY_PER_SEEDSHIP) { storedEnergy = 0.0; Ship@ ship = createShip(vec3d(1e10, 1e10, 1e10), Creeps.getDesign("Seed Ship"), Creeps, free = true); ship.addStatus(getStatusID("SeedShip")); } } void save(SaveFile& file) { file << ArtifactCount; file << storedEnergy; } void load(SaveFile& file) { if(file >= SV_0026) { file >> ArtifactCount; file >> storedEnergy; } } //SeedShip() // This behaves as a seed ship. class SeedData { array targets; }; class SeedShip : StatusHook { void onCreate(Object& obj, Status@ status, any@ data) override { SeedData seed; data.store(@seed); //Find target empire Empire@ targEmp; double totalGen = 0.0; for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) { Empire@ other = getEmpire(i); if(!other.major) continue; totalGen += other.EnergyIncome; } double roll = randomd(0.0, totalGen); for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) { Empire@ other = getEmpire(i); if(!other.major) continue; @targEmp = other; double gen = targEmp.EnergyIncome; if(roll <= gen) break; roll -= gen; } //Find target system DataList@ objs = targEmp.getPlanets(); array planets; Object@ rec; while(receive(objs, rec)) { Planet@ pl = cast(rec); if(pl is null) continue; planets.insertLast(pl); } if(planets.length == 0) { obj.destroy(); return; } Planet@ targPl = planets[randomi(0, planets.length - 1)]; SystemDesc@ targSys = getSystem(targPl.region); if(targSys is null) { obj.destroy(); return; } //Find the target galaxy const GalaxyData@ targGlx; for(uint i = 0, cnt = generatedGalaxies.length; i < cnt; ++i) { auto@ glx = generatedGalaxies[i]; if(targSys.position.distanceTo(glx.origin) < glx.radius) { @targGlx = glx; break; } } if(targGlx is null) { obj.destroy(); return; } //Create line for path vec3d offset = (targSys.position - targGlx.origin).normalized(targGlx.radius); vec3d startPos = targGlx.origin + offset; vec3d endPos = targGlx.origin - offset; //Find path start and end SystemDesc@ pathStart; double startDist = INFINITY; SystemDesc@ pathEnd; double endDist = INFINITY; for(uint i = 0, cnt = targGlx.systems.length; i < cnt; ++i) { auto@ other = targGlx.systems[i]; double d = other.position.distanceToSQ(startPos); if(d < startDist) { startDist = d; @pathStart = other; } d = other.position.distanceToSQ(endPos); if(d < endDist) { endDist = d; @pathEnd = other; } } //Initial position vec3d prevPos = pathStart.position; prevPos += (startPos - prevPos).normalized(pathStart.radius + SPAWN_OFFSET); obj.teleportTo(prevPos); //Path towards target SystemPath path; path.generate(pathStart, targSys); array visited; if(path.valid) { for(uint i = 0, cnt = path.pathSize; i < cnt; ++i) { auto@ other = path.pathNode[i]; vec3d newPos = other.position; newPos += (prevPos - newPos).normalized(other.radius - MOVE_OFFSET) + random3d(0.0, MOVE_VARIANCE); obj.addMoveOrder(newPos, append=true); prevPos = newPos; if(other !is targSys) visited.insertLast(other); } } else { vec3d newPos = targSys.position; newPos += (prevPos - newPos).normalized(targSys.radius - MOVE_OFFSET) + random3d(0.0, MOVE_VARIANCE); obj.addMoveOrder(newPos, append=true); prevPos = newPos; } path.generate(targSys, pathEnd); if(path.valid) { for(uint i = 1, cnt = path.pathSize; i < cnt; ++i) { auto@ other = path.pathNode[i]; vec3d newPos = other.position; newPos += (prevPos - newPos).normalized(other.radius - MOVE_OFFSET) + random3d(0.0, MOVE_VARIANCE); obj.addMoveOrder(newPos, append=true); prevPos = newPos; if(other !is targSys) visited.insertLast(other); } } vec3d newPos = pathEnd.position; newPos += (endPos - prevPos).normalized(pathEnd.radius + SPAWN_OFFSET); obj.addMoveOrder(newPos, append=true); seed.targets.insertLast(targSys.index); if(visited.length > 0) { for(uint i = 1, cnt = config::ARTIFACTS_PER_SEEDSHIP; i < cnt; ++i) seed.targets.insertLast(visited[randomi(0, visited.length-1)].index); } } bool onTick(Object& obj, Status@ status, any@ data, double time) override { if(obj.orderCount == 0) { playParticleSystem("SeedShipBoom", obj.position, obj.rotation, obj.radius, obj.visibleMask); obj.destroy(); return false; } Region@ reg = obj.region; if(reg is null) return true; SystemDesc@ sys = getSystem(reg); if(sys is null) return true; SeedData@ seed; data.retrieve(@seed); for(uint i = 0, cnt = seed.targets.length; i < cnt; ++i) { if(seed.targets[i] == sys.index) { if(sys.position.distanceTo(obj.position) < sys.radius - ARTIF_OFFSET) { const ArtifactType@ type = getSeedArtifactType(); auto@ artifact = createArtifact(obj.position + random3d(obj.radius + 1.0, ARTIF_VARIANCE), type); artifact.orbitAround(reg.position); seed.targets.removeAt(i); } break; } } return true; } void onObjectDestroy(Object& obj, Status@ status, any@ data) override { for(uint i = 0, cnt = config::ARTIFACTS_SEEDSHIP_DEATH; i < cnt; ++i) { const ArtifactType@ type = getSeedArtifactType(); vec3d pos = obj.position + random3d(type.physicalSize * 1.5, type.physicalSize * 3.0); auto@ artifact = createArtifact(pos, type); if(obj.region !is null) artifact.orbitAround(obj.region.position); } if(obj.type == OT_Ship) { Empire@ killer = cast(obj).getKillCredit(); if(killer !is null && killer.valid && !getCheatsEverOn()) { if(killer is playerEmpire) unlockAchievement("ACH_SPILLED_SEED"); else if(mpServer && killer.player !is null) clientAchievement(killer.player, "ACH_SPILLED_SEED"); } } } void save(Status@ status, any@ data, SaveFile& file) override { SeedData@ seed; data.retrieve(@seed); uint cnt = seed.targets.length; file << cnt; for(uint i = 0; i < cnt; ++i) { uint id = seed.targets[i]; file << id; } } void load(Status@ status, any@ data, SaveFile& file) override { SeedData seed; data.store(@seed); uint cnt = 0; file >> cnt; seed.targets.length = cnt; for(uint i = 0; i < cnt; ++i) { uint id = 0; file >> id; seed.targets[i] = id; } } }; class GravitarShip : StatusHook { bool onTick(Object& obj, Status@ status, any@ data, double time) override { if(obj.orderCount == 0 && randomi(0,10) == 0) { playParticleSystem("SeedShipBoom", obj.position, obj.rotation, obj.radius, obj.visibleMask); obj.destroy(); return false; } return true; } };