from empire import Pirates; import saving; import object_creation; import statuses; import settings.map_lib; import civilians; from game_start import getClosestSystem, getRandomSystem, systemCount, getSystem; from regions.regions import extentMin, extentMax; from statuses import StatusHook; const double PIRATE_GAMETIME = 30.0 * 60.0; const double HOARD_TIME = 30.0; const double SYSTEM_MAX_TIME = 80.0; const int HOARD_MONEY = 500; const int MAX_CARRYING = 2000; const double NORMAL_ACCEL = 3.0; const double CHASE_ACCEL = 10.0; enum PirateState { RESTING, TARGETING, PILLAGING, RETURNING }; class PirateData : Savable { array hoards; PirateState state = RESTING; double timer = 0.0; double checkTimer = 5.0; int totalCollected = 0; int currentStored = 0; double accel = 1.0; Civilian@ returningTo; Civilian@ chasing; int chasingAmount = 0; void save(SaveFile& file) { uint st = uint(state); file << st; file << timer << checkTimer; file << totalCollected << currentStored; file << accel; file << returningTo; file << chasing << chasingAmount; uint cnt = hoards.length; file << cnt; for(uint i = 0; i < cnt; ++i) file << hoards[i]; } void load(SaveFile& file) { uint st = 0; file >> st; state = PirateState(st); file >> timer >> checkTimer; file >> totalCollected >> currentStored; file >> accel; file >> returningTo; file >> chasing >> chasingAmount; uint cnt = 0; file >> cnt; hoards.length = cnt; for(uint i = 0; i < cnt; ++i) @hoards[i] = cast(file.readObject()); } }; class PirateStatus : StatusHook { void onCreate(Object& obj, Status@ status, any@ data) override { PirateData dat; data.store(@dat); obj.setHoldPosition(true); } void onObjectDestroy(Object& obj, Status@ status, any@ data) override { PirateData@ dat; data.retrieve(@dat); Ship@ ship = cast(obj); auto@ credit = ship.getKillCredit(); if(credit !is null) credit.addBonusBudget(dat.currentStored + dat.totalCollected / 2); spawnPirateShip(); } bool onTick(Object& obj, Status@ status, any@ data, double time) override { Ship@ ship = cast(obj); PirateData@ dat; data.retrieve(@dat); double thrustPct = ship.blueprint.getEfficiencySum(SV_Thrust) / ship.blueprint.design.total(SV_Thrust); obj.maxAcceleration = dat.accel * thrustPct; if(obj.orderCount != 0) { if(dat.state == PILLAGING) dat.timer -= time * 0.5 * sqr(ship.blueprint.design.totalHP / ship.blueprint.currentHP); return true; } switch(dat.state) { case RESTING: { if(dat.timer <= 0.0) { //Find a new system to pillage const SystemDesc@ targetSys = findJuicySystem(status.originEmpire); if(targetSys is null) { dat.timer = HOARD_TIME; } else { obj.addGotoOrder(targetSys.object); dat.accel = NORMAL_ACCEL; dat.state = TARGETING; } } else { dat.timer -= time; dat.accel = NORMAL_ACCEL; } } break; case TARGETING: { dat.state = PILLAGING; dat.timer = SYSTEM_MAX_TIME; dat.accel = NORMAL_ACCEL; dat.checkTimer = 0; } break; case RETURNING: { dat.state = RESTING; dat.timer = HOARD_TIME; dat.accel = NORMAL_ACCEL; if(dat.returningTo !is null) { dat.returningTo.modCargoWorth(dat.currentStored * 2); dat.currentStored = 0; @dat.returningTo = null; } if(!ship.inCombat) { ship.Supply = ship.MaxSupply; ship.repairShip(10000000); } } break; case PILLAGING: { if(dat.chasing !is null) { if(!dat.chasing.valid) { dat.totalCollected += dat.chasingAmount; dat.currentStored += dat.chasingAmount; } @dat.chasing = null; dat.chasingAmount = 0; } if(dat.timer <= 0.0 || dat.currentStored > MAX_CARRYING) { //Prune old hoards @dat.returningTo = null; for(int i = dat.hoards.length - 1; i >= 0; --i) { if(!dat.hoards[i].valid) dat.hoards.removeAt(i); } //Check if we should create a new hoard double newChance = pow(0.5, double(dat.hoards.length) / (double(dat.totalCollected) / double(HOARD_MONEY))); if(dat.hoards.length == 0 || randomd() < newChance) { vec3d pos = vec3d(randomd(extentMin.x, extentMax.x), 0, randomd(extentMin.z, extentMax.z)); pos.y = getClosestSystem(pos).position.y; Civilian@ civ = createCivilian(pos, Pirates, CiT_Station, radius=25.0); civ.name = locale::PIRATE_HOARD; civ.named = true; civ.setCargoType(CT_Goods); dat.hoards.insertLast(civ); @dat.returningTo = civ; } //Find a hoard to return to if(dat.returningTo is null) { double dist = INFINITY; for(uint i = 0, cnt = dat.hoards.length; i < cnt; ++i) { double d = dat.hoards[i].position.distanceTo(obj.position) * randomd(0.5, 1.5); if(d < dist) { dist = d; @dat.returningTo = dat.hoards[i]; } } } obj.addGotoOrder(dat.returningTo); dat.state = RETURNING; dat.accel = NORMAL_ACCEL; } else { //Find civilian ships to kill if(dat.checkTimer <= 0.0) { vec3d center = obj.position; Region@ region = obj.region; vec3d bound(2000.0); if(region !is null) { center = region.position; bound = region.radius; } array@ objs = findInBox(center - bound, center + bound, Pirates.hostileMask); Civilian@ found; for(uint i = 0, cnt = objs.length; i < cnt; ++i) { Civilian@ civ = cast(objs[i]); if(civ is null) continue; Empire@ owner = civ.owner; if(owner is null || !Pirates.isHostile(owner)) continue; @found = civ; break; } if(found !is null) { dat.accel = CHASE_ACCEL; obj.addAttackOrder(found); @dat.chasing = found; dat.chasingAmount = found.getCargoWorth(); dat.checkTimer = 10.0; } else { dat.accel = NORMAL_ACCEL; dat.checkTimer = randomd(2.0, 5.0); } } else { dat.checkTimer -= time; dat.accel = NORMAL_ACCEL; } dat.timer -= time * sqr(ship.blueprint.design.totalHP / ship.blueprint.currentHP); } } break; } return true; } void save(Status@ status, any@ data, SaveFile& file) override { PirateData@ dat; data.retrieve(@dat); file << dat; } void load(Status@ status, any@ data, SaveFile& file) override { PirateData dat; file >> dat; data.store(@dat); } }; const SystemDesc@ findJuicySystem(Empire@ limitEmpire = null) { //Find an occupied system with low defenses double totalFreq = 0.0; double roll = randomd(); const SystemDesc@ result; for(uint i = 0, cnt = systemCount; i < cnt; ++i) { auto@ sys = getSystem(i); uint plMask = sys.object.PlanetsMask; if(plMask == 0) continue; if(limitEmpire !is null && plMask & limitEmpire.mask == 0) continue; double freq = randomd(0.6, 1.0); double totStr = 0; for(uint n = 0, ncnt = getEmpireCount(); n < ncnt; ++n) { Empire@ other = getEmpire(n); if(!other.major) continue; if(limitEmpire !is null && other !is limitEmpire) continue; if(plMask & other.mask == 0) continue; freq *= sqr(double(sys.object.getPlanetCount(other))); freq /= double(sys.object.getStrength(other) + 1); } if(sys.object.hasTradeStations()) freq *= 4.0; totalFreq += freq; double chance = freq / totalFreq; if(roll < chance) { @result = sys; roll /= chance; } else { roll = (roll - chance) / (1.0 - chance); } } return result; } bool shouldSpawn = false; bool hasLoaded = false; void init() { if(!isLoadedSave) shouldSpawn = true; } void tick(double time) { if(isLoadedSave && !hasLoaded) { shouldSpawn = START_VERSION >= SV_0084 && gameTime < PIRATE_GAMETIME; hasLoaded = true; } if(!shouldSpawn) return; if(gameTime < PIRATE_GAMETIME) return; spawnPirateShip(); shouldSpawn = false; } bool isHalloween() { //I'm sorry I'm sorry I'm sorry I'm sorry int64 now = getSystemTime(); int month = toInt(strftime("%m", now)); int day = toInt(strftime("%d", now)); return month == 10 && day >= 29 && day <= 31; } void spawnPirateShip(Empire@ limitEmpire = null) { if(config::ENABLE_DREAD_PIRATE == 0) return; const Design@ dsg; if(isHalloween()) @dsg = Pirates.getDesign("The Flying Dutchman"); else @dsg = Pirates.getDesign("Dread Pirate"); vec3d pos = vec3d(randomd(extentMin.x, extentMax.x), 0, randomd(extentMin.z, extentMax.z)); pos.y = getClosestSystem(pos).position.y; Ship@ ship = createShip(pos, dsg, Pirates, free=true); auto@ status = getStatusType("PirateShip"); if(status !is null) ship.addStatus(status.id, originEmpire=limitEmpire); else error("Error: Could not find 'PirateShip' status for managing pirate vessel."); }