Files
2018-07-17 14:15:37 +02:00

838 lines
20 KiB
ActionScript

#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<EndSystem@> inner;
array<EndSystem@> 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<Ship@> defenses;
array<Ship@> 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@> playerData;
Empire@ Invaders;
double symbolicStrength = 0.0;
double waveTimer = 0.0;
array<double> 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<InvasionMap>(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<RemnantComposition@> 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<InvasionMap>(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();
}