#priority init 5001 #include "include/map.as" #section server import systems; from game_start import galaxies; import camps; from empire import Creeps, Pirates; import empire_data; import object_creation; import research; import remnant_designs; import artifacts; from objects.Artifact import createArtifact; from achievements import giveAchievement; import void addModifierToEmpire(Empire@ emp, const string& spec) from "bonus_effects"; #section all enum MapSetting { M_SysCount, M_SystemSpacing, M_Flatten, }; const double BASE_STRENGTH = 6000.0; const double DOUBLE_WAVES = 3.0; const double HARD_DIFF_MIN = 1.0; class InvasionMap : Map { InvasionMap() { super(); name = locale::INVASION_MAP; description = locale::INVASION_MAP_DESC; color = 0xff4eaeff; icon = "maps/Invasion/invasion.png"; sortIndex = -140; dlc = "Heralds"; eatsPlayers = true; isUnique = true; } #section client void makeSettings() { Toggle(locale::FLATTEN, M_Flatten, false, halfWidth=true); Number(locale::SYSTEM_SPACING, M_SystemSpacing, DEFAULT_SPACING, decimals=0, step=1000, min=MIN_SPACING, halfWidth=true); auto@ diff = Dropdown(locale::INV_DIFFICULTY, "INVASION_DIFFICULTY"); diff.addOption(locale::EASY, 0.25); diff.addOption(locale::NORMAL, 0.5); diff.addOption(locale::HARD, 1.0); diff.set(0.5); Description(locale::INVASION_MAP_TEXT, lines=2); } #section server void modSettings(GameSettings& settings) override { config::ENABLE_INFLUENCE_VICTORY = 0.0; settings.setNamed("ENABLE_INFLUENCE_VICTORY", 0.0); config::ENABLE_DREAD_PIRATE = 0.0; settings.setNamed("ENABLE_DREAD_PIRATE", 0.0); } bool ensureConnectedLinks() override { return true; } void placeSystems() { double spacing = modSpacing(getSetting(M_SystemSpacing, DEFAULT_SPACING)); bool flatten = getSetting(M_Flatten, 0.0) != 0.0; uint players = estPlayerCount; if(players == 0) players = 3; double radSize = spacing * 10.0 * double(players+1); double startDist = (radSize / (2.0 * pi)) + (spacing * 6.0); double heightDiff = spacing * 0.2; double radial = twopi / double(players+1); double startRad = radial / double(players+1); int edgeType = -1; auto@ sysType = getSystemType("AncientSystem"); if(sysType !is null) edgeType = sysType.id; for(uint i = 0; i < players; ++i) { PlayerData dat; playerData.insertLast(dat); vec3d homePos = quaterniond_fromAxisAngle(vec3d_up(), startRad) * vec3d_front(startDist); if(!flatten) homePos.y += randomd(-heightDiff, heightDiff); //Create the homeworld auto@ hwSys = addSystem(homePos); hwSys.assignGroup = i; addPossibleHomeworld(hwSys); EndSystem hwEnd; hwEnd.index = hwSys.index; @dat.homeworld = hwEnd; //Create the edge systems double edgeRad = -0.3 * pi; for(uint n = 0; n < 3; ++n) { vec3d edgePos = homePos; edgePos += quaterniond_fromAxisAngle(vec3d_up(), edgeRad) * homePos.normalized(spacing * 0.6); auto@ sys = addSystem(edgePos, sysType=edgeType); sys.assignGroup = i; addLink(sys, hwSys); EndSystem es; es.index = sys.index; dat.edges.insertLast(es); edgeRad += 0.3 * pi; } //Create the extra layers for(uint l = 0; l < 5; ++l) { uint layCnt = 2+l; double totRad = pi * 0.25 * double(layCnt); double radStep = totRad / double(layCnt); double rad = totRad * -0.5 + radStep * 0.5; double sysDist = spacing * double(l+1); for(uint n = 0; n < layCnt; ++n) { vec3d sysPos = homePos; sysPos -= quaterniond_fromAxisAngle(vec3d_up(), rad) * homePos.normalized(sysDist); if(!flatten) sysPos.y += randomd(-heightDiff, heightDiff); auto@ sys = addSystem(sysPos); sys.assignGroup = i; EndSystem es; es.index = sys.index; dat.inner.insertLast(es); rad += radStep; } } startRad += radial + (radial / double(players+1)); } } void preInit() { //Modify creep empire Pirates.name = locale::REMNANT_DEFENSE_LINE; Pirates.color = Color(0x4fd07bff); //Create invasion empire { @Invaders = Empire(); Invaders.name = locale::INVADER; Invaders.color = Color(0xff4040ff); Invaders.major = false; Invaders.visionMask = ~0; auto@ flag = getEmpireFlag(randomi(0, getEmpireFlagCount()-1)); Invaders.flagDef = flag.flagDef; Invaders.flagID = flag.id; @Invaders.flag = getMaterial(Creeps.flagDef); @Invaders.shipset = getShipset("ALL"); } //Empires are allied with creeps here for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) { Empire@ emp = getEmpire(i); if(!emp.major) continue; Pirates.setHostile(emp, false); emp.setHostile(Pirates, false); Invaders.setHostile(emp, true); emp.setHostile(Invaders, true); emp.modTotalBudget(+500); } Pirates.setHostile(Invaders, true); Invaders.setHostile(Pirates, true); } void init() { if(isLoadedSave) return; //See if we should load strengths loadStrengthList("maps/Invasion/strengths.txt"); //Add the global modifiers uint majorMask = 0; for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) { Empire@ emp = getEmpire(i); if(!emp.major) continue; emp.Victory = -2; addModifierToEmpire(emp, "StationHull::MaintenanceModFactor(0.0)"); addModifierToEmpire(emp, "StationHull::HPModFactor(0.25)"); addModifierToEmpire(emp, "StationHull::RangeModFactor(0.5)"); addModifierToEmpire(emp, "StationHull::LaborModFactor(2.0)"); addModifierToEmpire(emp, "StationHull::MaintCostFactor(0.0)"); addModifierToEmpire(emp, "MaintCostFactor(0.0)"); majorMask |= emp.mask; Object@ hw = emp.Homeworld; if(hw is null) @hw = emp.HomeObj; if(hw !is null) hw.modLaborIncome(+5.0 / 60.0); } //YOU WILL SIT IN THE CIRCLE AND SING KUMBAYA DAMNIT for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) { Empire@ emp = getEmpire(i); if(emp.major) emp.ForcedPeaceMask |= majorMask; } //Spawn remnant defenses const Design@ defStation = Creeps.getDesign("Defense Station"); for(uint i = 0, cnt = playerData.length; i < cnt; ++i) { auto@ dat = playerData[i]; dat.homeworld.index += systems[0].index; @dat.homeworld.desc = getSystem(dat.homeworld.index); for(uint n = 0, ncnt = dat.inner.length; n < ncnt; ++n) { auto@ sys = dat.inner[n]; sys.index += systems[0].index; @sys.desc = getSystem(sys.index); } for(uint n = 0, ncnt = dat.edges.length; n < ncnt; ++n) { auto@ sys = dat.edges[n]; sys.index += systems[0].index; @sys.desc = getSystem(sys.index); double radius = sys.desc.radius; //Give everyone vision over this system for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) sys.desc.object.grantVision(getEmpire(i)); //Spawn defense stations vec3d offset = (sys.desc.position - dat.homeworld.desc.position).normalized(radius * 0.75); auto@ obj = createShip(sys.desc.position + offset, defStation, Pirates, free=true); obj.orbitDuration(1000000000000.0); sys.defenses.insertLast(obj); @obj = createShip(sys.desc.position + quaterniond_fromAxisAngle(vec3d_up(), pi * 0.2) * offset, defStation, Pirates, free=true); obj.orbitDuration(1000000000000.0); sys.defenses.insertLast(obj); @obj = createShip(sys.desc.position + quaterniond_fromAxisAngle(vec3d_up(), -pi * 0.2) * offset, defStation, Pirates, free=true); obj.orbitDuration(1000000000000.0); sys.defenses.insertLast(obj); } } } #section all }; #section server class PlayerData : Savable { Empire@ empire; EndSystem@ homeworld; array inner; array edges; uint previousWave = uint(-1); double stationHealth = 0.0; double stationEff = 0.0; void save(SaveFile& file) { file << homeworld; file << empire; file << stationHealth; file << stationEff; file << previousWave; uint cnt = inner.length; file << cnt; for(uint i = 0; i < cnt; ++i) file << inner[i]; cnt = edges.length; file << cnt; for(uint i = 0; i < cnt; ++i) file << edges[i]; } void load(SaveFile& file) { @homeworld = EndSystem(); file >> homeworld; file >> empire; file >> stationHealth; file >> stationEff; if(file >= SV_0156) file >> previousWave; uint cnt = 0; file >> cnt; inner.length = cnt; for(uint i = 0; i < cnt; ++i) { @inner[i] = EndSystem(); file >> inner[i]; } file >> cnt; edges.length = cnt; for(uint i = 0; i < cnt; ++i) { @edges[i] = EndSystem(); file >> edges[i]; } } }; class EndSystem : Savable { uint index; SystemDesc@ desc; array defenses; array invaders; bool isFighting = false; void save(SaveFile& file) { file << index; file << isFighting; uint cnt = defenses.length; file << cnt; for(uint i = 0; i < cnt; ++i) file << defenses[i]; cnt = invaders.length; file << cnt; for(uint i = 0; i < cnt; ++i) file << invaders[i]; } void load(SaveFile& file) { file >> index; file >> isFighting; uint cnt = 0; file >> cnt; defenses.length = cnt; for(uint i = 0; i < cnt; ++i) file >> defenses[i]; file >> cnt; invaders.length = cnt; for(uint i = 0; i < cnt; ++i) file >> invaders[i]; } }; Mutex mtx; array playerData; Empire@ Invaders; double symbolicStrength = 0.0; double waveTimer = 0.0; array WAVE_STRENGTHS; void save(SaveFile& file) { if(hasInvasionMap()) { file.write1(); file << Invaders; file << symbolicStrength; file << waveTimer; uint cnt = playerData.length; file << cnt; for(uint i = 0; i < cnt; ++i) file << playerData[i]; } else { file.write0(); } } void loadStrengthList(const string& fname) { WAVE_STRENGTHS.length = 0; ReadFile file(fname, true); while(file++) WAVE_STRENGTHS.insertLast(toDouble(file.line)); } void load(SaveFile& file) { if(file.readBit()) { file >> Invaders; file >> symbolicStrength; file >> waveTimer; uint cnt = 0; file >> cnt; playerData.length = cnt; for(uint i = 0; i < cnt; ++i) { @playerData[i] = PlayerData(); file >> playerData[i]; } } loadStrengthList("maps/Invasion/strengths.txt"); } double getInvasionInterval() { return 3.0 * 60.0; } double getInvasionTimer() { return waveTimer; } double getInvasionStrength() { return symbolicStrength; } void increaseInvasionStrength(double amount, bool modify = true) { if(modify) amount /= double(playerData.length); symbolicStrength += amount; } bool hasInvasionMap() { for(uint i = 0, cnt = galaxies.length; i < cnt; ++i) { if(cast(galaxies[i]) !is null) return true; } return false; } bool initialized = false; void tick(double time) { if(!hasInvasionMap()) return; if(!initialized && playerEmpire.valid) { guiDialogueAction(CURRENT_PLAYER, "Invasion.InvasionMap::SetupGUI"); if(!isLoadedSave) { for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) { Empire@ emp = getEmpire(i); if(!emp.major) continue; emp.replaceResearchOfType(getTechnologyID("OrbitalConstruction"), getTechnologyID("SupportCap")); emp.replaceResearchOfType(getTechnologyID("OrbitalConstruction2"), getTechnologyID("Damage")); } } initialized = true; } if(time == 0.0 || gameSpeed == 0.0 || gameTime < 0.5) return; //Check for losing systems for(uint i = 0, cnt = playerData.length; i < cnt; ++i) { auto@ dat = playerData[i]; if(dat.homeworld.desc is null) @dat.homeworld.desc = getSystem(dat.homeworld.index); if(dat.empire is null) { for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) { Empire@ emp = getEmpire(i); Region@ homeReg; if(emp.Homeworld !is null) @homeReg = emp.Homeworld.region; else if(emp.HomeObj !is null) @homeReg = emp.HomeObj.region; if(homeReg !is null) { if(dat.homeworld.desc.object is homeReg) @dat.empire = emp; } } } for(uint j = 0, jcnt = dat.edges.length; j < jcnt; ++j) { auto@ sys = dat.edges[j]; if(sys.desc is null) @sys.desc = getSystem(sys.index); //Remove lost defenses for(uint n = 0, ncnt = sys.defenses.length; n < ncnt; ++n) { if(sys.defenses[n] is null || !sys.defenses[n].valid) { sys.defenses.removeAt(n); --n; --ncnt; } } //Replace invaders with pickups vec3d lastInvader; for(uint n = 0, ncnt = sys.invaders.length; n < ncnt; ++n) { auto@ obj = sys.invaders[n]; if(obj !is null && obj.valid) continue; if(obj !is null) lastInvader = obj.position; sys.invaders.removeAt(n); --n; --ncnt; } if(sys.isFighting && sys.invaders.length == 0) { sys.isFighting = false; const ArtifactType@ type; uint possibs = 0; for(uint n = 0, ncnt = getArtifactTypeCount(); n < ncnt; ++n) { auto@ artif = getArtifactType(n); if(!artif.hasTag("Invasion")) continue; possibs += 1; if(randomd() < 1.0 / double(possibs)) @type = artif; } if(type !is null) { vec3d pos = lastInvader; if(pos.zero) { pos = sys.desc.object.position; vec2d off = random2d(200.0, sys.desc.object.radius * 0.8); pos.x += off.x; pos.y += randomd(-20.0, 20.0); pos.z += off.y; } if(symbolicStrength >= 20.0) giveAchievement(dat.empire, "ACH_INVASION_20"); if(symbolicStrength >= 25.0 && config::INVASION_DIFFICULTY >= HARD_DIFF_MIN) giveAchievement(dat.empire, "ACH_INVASION_40"); Artifact@ artif = createArtifact(pos, type); @artif.owner = dat.empire; } } //Remove lost edges if(sys.defenses.length == 0) { sys.desc.object.dealStarDamage(1000000000000000000); sys.desc.object.addSystemDPS(1000000000000000000); increaseInvasionStrength(2.0); for(uint i = 0, cnt = sys.desc.object.planetCount; i < cnt; ++i) sys.desc.object.planets[i].destroyQuiet(); dat.edges.removeAt(j); --j; --jcnt; } } //Remove lost systems if(dat.edges.length == 0) { for(uint j = 0, jcnt = dat.inner.length; j < jcnt; ++j) { auto@ sys = dat.inner[j]; if(sys.desc is null) @sys.desc = getSystem(sys.index); sys.desc.object.dealStarDamage(1000000000000000000); sys.desc.object.addSystemDPS(1000000000000000000); for(uint i = 0, cnt = sys.desc.object.planetCount; i < cnt; ++i) sys.desc.object.planets[i].destroyQuiet(); } auto@ sys = dat.homeworld; if(sys !is null) { if(sys.desc is null) @sys.desc = getSystem(sys.index); sys.desc.object.dealStarDamage(1000000000000000000); sys.desc.object.addSystemDPS(1000000000000000000); for(uint i = 0, cnt = sys.desc.object.planetCount; i < cnt; ++i) sys.desc.object.planets[i].destroyQuiet(); } dat.inner.length = 0; } //Remove lost players if(dat.inner.length == 0) { playerData.removeAt(i); --i; --cnt; } } //Increase remnant strength waveTimer -= time; if(waveTimer <= 0.0) { waveTimer += 3.0 * 60.0; //Update defense station strength double stHP = -1.0 + 0.5 * pow(1.3, symbolicStrength / DOUBLE_WAVES); double stDPS = min(-1.0+ 0.2 * pow(1.4, symbolicStrength / DOUBLE_WAVES), 0.0); for(uint i = 0, cnt = playerData.length; i < cnt; ++i) { auto@ dat = playerData[i]; for(uint n = 0, ncnt = dat.edges.length; n < ncnt; ++n) { auto@ edge = dat.edges[n]; for(uint j = 0, jcnt = edge.defenses.length; j < jcnt; ++j) { edge.defenses[j].modHPFactor(stHP - dat.stationHealth); edge.defenses[j].modFleetEffectiveness(stDPS - dat.stationEff); } } dat.stationHealth = stHP; dat.stationEff = stDPS; } //Spawn enemies double targetStrength; int waveDown = int(floor(symbolicStrength)); int waveUp = int(ceil(symbolicStrength)); if(uint(waveUp) >= WAVE_STRENGTHS.length) { double finalWave = WAVE_STRENGTHS.length; double baseStrength = BASE_STRENGTH; if(WAVE_STRENGTHS.length != 0) baseStrength = WAVE_STRENGTHS[WAVE_STRENGTHS.length-1]; targetStrength = baseStrength * pow(2.0, double(symbolicStrength - finalWave) / DOUBLE_WAVES); } else { double downStr = WAVE_STRENGTHS[waveDown]; double upStr = WAVE_STRENGTHS[waveUp]; targetStrength = downStr; if(upStr != downStr && waveDown != waveUp) targetStrength += (upStr - downStr) * (symbolicStrength - double(waveDown)) / double(waveUp - waveDown); } if(config::INVASION_DIFFICULTY > 0) targetStrength *= config::INVASION_DIFFICULTY; symbolicStrength += 1.0; uint fleetCount = 1; uint maxFleets = ceil(symbolicStrength / 5.0); while(fleetCount < maxFleets && randomd() < 0.33) fleetCount += 1; double fleetStrength = sqr(sqrt(targetStrength) / double(fleetCount)); array fleets; for(uint f = 0, fcnt = fleetCount; f < fcnt; ++f) { auto@ comp = composeRemnantFleet(fleetStrength, 0.2, emp=Invaders); if(comp is null) continue; fleets.insertLast(comp); } for(uint i = 0, cnt = playerData.length; i < cnt; ++i) { auto@ dat = playerData[i]; if(dat.edges.length == 0) continue; EndSystem@ sys; uint checkCount = 0; for(uint i = 0, cnt = dat.edges.length; i < cnt; ++i) { auto@ edge = dat.edges[i]; if(cnt != 1 && edge.index == dat.previousWave) continue; checkCount += 1; if(randomd() < 1.0 / double(checkCount)) @sys = edge; } sys.isFighting = true; for(uint f = 0, fcnt = fleets.length; f < fcnt; ++f) { vec3d pos = (sys.desc.position - dat.homeworld.desc.position).normalized(); pos = quaterniond_fromAxisAngle(vec3d_up(), randomd(-0.2, 0.2)*pi) * pos; vec3d spawnPos = pos * sys.desc.object.radius * 0.95; spawnPos += sys.desc.position; Ship@ invader = spawnRemnantFleet(spawnPos, fleets[f], emp=Invaders, alwaysVisible=true); sys.invaders.insertLast(invader); if(f == 0 && dat.empire !is null && dat.empire.player !is null) showPing(dat.empire.player, Invaders, spawnPos, 2); } dat.previousWave = sys.index; } } } bool sendPeriodic(Message& msg) { if(!hasInvasionMap()) return false; msg << symbolicStrength; msg << waveTimer; return true; } #section shadow from game_start import galaxies; double symbolicStrength = 0.0; double waveTimer = 0.0; void recvPeriodic(Message& msg) { msg >> symbolicStrength; msg >> waveTimer; } bool hasInvasionMap() { for(uint i = 0, cnt = galaxies.length; i < cnt; ++i) { if(cast(galaxies[i]) !is null) return true; } return false; } double getInvasionInterval() { return 3.0 * 60.0; } double getInvasionTimer() { return waveTimer; } double getInvasionStrength() { return symbolicStrength; } bool initialized = false; void tick(double time) { if(!hasInvasionMap()) return; if(!initialized && playerEmpire.valid) { guiDialogueAction("Invasion.InvasionMap::SetupGUI"); initialized = true; } } #section gui import dialogue; import elements.BaseGuiElement; import elements.GuiText; import elements.GuiMarkupText; import elements.GuiProgressbar; import util.formatting; import tabs.tabbar; class InvasionUI : BaseGuiElement { GuiText@ waveLabel; GuiProgressbar@ bar; GuiText@ strengthLabel; GuiText@ strength; InvasionUI() { super(null, Alignment(Left+0.5f-200, Top+TAB_HEIGHT+GLOBAL_BAR_HEIGHT+1, Width=400, Height=40)); @waveLabel = GuiText(this, Alignment(Left+8, Top+4, Left+90, Bottom-4), locale::NEXT_WAVE); waveLabel.color = Color(0xaaaaaaff); @bar = GuiProgressbar(this, Alignment(Left+90, Top+8, Left+250, Bottom-8)); bar.frontColor = Color(0xff8080ff); bar.font = FT_Small; bar.strokeColor = colors::Black; @strengthLabel = GuiText(this, Alignment(Left+270, Top+4, Left+330, Bottom-4), locale::NEXT_STRENGTH); strengthLabel.color = Color(0xaaaaaaff); @strength = GuiText(this, Alignment(Left+330, Top+6, Left+392, Bottom-4)); strength.horizAlign = 0.5; strength.color = Color(0xff4040ff); strength.font = FT_Bold; updateAbsolutePosition(); } void tick(double time) { visible = ActiveTab.category == TC_Galaxy; double timer = getInvasionTimer(); double interval = getInvasionInterval(); double str = getInvasionStrength(); strength.text = standardize(str, true); bar.text = formatTime(timer); bar.progress = 1.f - (timer / interval); } void draw() { skin.draw(SS_PlainOverlay, SF_Normal, AbsolutePosition.padded(0,-2,0,0)); BaseGuiElement::draw(); } }; class SetupGUI : DialogueAction { void call() { @ui = InvasionUI(); } } InvasionUI@ ui; void tick(double time) { if(ui !is null) ui.tick(time); } void preReload(Message& msg) { msg.writeBit(ui !is null); if(ui !is null) ui.remove(); } void postReload(Message& msg) { if(msg.readBit()) @ui = InvasionUI(); }