Open source Star Ruler 2 source code!

This commit is contained in:
Lucas de Vries
2018-07-17 14:15:37 +02:00
commit cc307720ff
4342 changed files with 2365070 additions and 0 deletions
+372
View File
@@ -0,0 +1,372 @@
import abilities;
import saving;
import hooks;
import systems;
import attributes;
import achievements;
tidy class Abilities : Component_Abilities, Savable {
int nextAbilityId = 0;
Ability@[] abilities;
bool delta = false;
bool neutralAbilities = false;
bool abilityDestroy = false;
void setNeutralAbilities(bool value) {
neutralAbilities = value;
}
void setAbilityDestroy(bool value) {
abilityDestroy = value;
}
void initAbilities(Object& obj, const Design@ fromDesign) {
array<Ability@> subsysAbilities;
for(uint i = 0, cnt = abilities.length; i < cnt; ++i) {
if(abilities[i].subsystem !is null) {
subsysAbilities.insertLast(abilities[i]);
abilities.removeAt(i);
--i; --cnt;
}
}
uint sysCnt = fromDesign.subsystemCount;
for(uint i = 0; i < sysCnt; ++i) {
const Subsystem@ sys = fromDesign.subsystems[i];
uint cnt = sys.type.getTagValueCount(ST_Ability);
for(uint i = 0; i < cnt; ++i) {
const AbilityType@ type = getAbilityType(sys.type.getTagValue(ST_Ability, i));
if(type !is null) {
bool found = false;
for(uint i = 0, cnt = subsysAbilities.length; i < cnt; ++i) {
auto@ abl = subsysAbilities[i];
if(abl.type is type) {
@abl.subsystem = sys;
found = true;
abilities.insertLast(abl);
subsysAbilities.removeAt(i);
break;
}
}
if(!found)
addAbility(obj, type, sys);
}
}
}
for(uint i = 0, cnt = subsysAbilities.length; i < cnt; ++i) {
auto@ abl = subsysAbilities[i];
if(!abl.disabled)
abl.disable();
abl.destroy();
}
delta = true;
}
void destroyAbilities() {
for(uint i = 0, cnt = abilities.length; i < cnt; ++i)
abilities[i].destroy();
}
uint get_abilityTypes(int id) {
Ability@ abl = getAbility(id);
if(abl is null)
return uint(-1);
return abl.type.id;
}
Ability@ getAbilityOfType(int type) {
for(uint i = 0, cnt = abilities.length; i < cnt; ++i) {
auto@ abl = abilities[i];
if(abl.disabled || abl.cooldown > 0)
continue;
if(abl.type.id == uint(type))
return abl;
}
return null;
}
void createAbility(Object& obj, uint id) {
addAbility(obj, id);
}
int addAbility(Object& obj, uint id) {
const AbilityType@ type = getAbilityType(id);
if(type !is null) {
delta = true;
return addAbility(obj, type).id;
}
return -1;
}
void removeAbility(Object& obj, int id) {
Ability@ abl = getAbility(id);
if(abl is null)
return;
if(!abl.disabled)
abl.disable();
abl.destroy();
abilities.remove(abl);
delta = true;
}
void disableAbility(Object& obj, int id) {
Ability@ abl = getAbility(id);
if(abl is null)
return;
if(!abl.disabled) {
abl.disable();
abl.disabled = true;
delta = true;
}
}
void enableAbility(Object& obj, int id) {
Ability@ abl = getAbility(id);
if(abl is null)
return;
if(abl.disabled) {
abl.enable();
abl.disabled = false;
delta = true;
}
}
Ability@ addAbility(Object& obj, const AbilityType@ type, const Subsystem@ sys = null) {
Ability abl(type);
abl.id = nextAbilityId++;
@abl.subsystem = sys;
@abl.obj = obj;
@abl.emp = obj.owner;
abilities.insertLast(abl);
abl.create();
abl.enable();
return abl;
}
void setCooldownForType(int typeId, double cooldown) {
for(uint i = 0, cnt = abilities.length; i < cnt; ++i) {
if(int(abilities[i].type.id) == typeId) {
abilities[i].cooldown = cooldown;
delta = true;
}
}
}
void abilityOwnerChange(Object& obj, Empire@ prevOwner, Empire@ newOwner) {
for(uint i = 0, cnt = abilities.length; i < cnt; ++i)
@abilities[i].emp = newOwner;
}
void save(SaveFile& file) {
uint cnt = abilities.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << abilities[i];
file << nextAbilityId;
file << neutralAbilities;
file << abilityDestroy;
}
void load(SaveFile& file) {
uint cnt = 0;
file >> cnt;
abilities.length = cnt;
for(uint i = 0; i < cnt; ++i) {
Ability abl;
file >> abl;
@abilities[i] = abl;
}
file >> nextAbilityId;
file >> neutralAbilities;
file >> abilityDestroy;
}
void abilityTick(Object& obj, double time) {
for(uint i = 0, cnt = abilities.length; i < cnt; ++i) {
auto@ abl = abilities[i];
if(!abl.disabled) {
if(abl.cooldown > 0) {
abl.cooldown = max(0.0, abl.cooldown - time);
delta = true;
}
abl.tick(time);
}
}
}
Ability@ getAbility(int id) {
for(uint i = 0, cnt = abilities.length; i < cnt; ++i) {
if(abilities[i].id == id)
return abilities[i];
}
return null;
}
uint get_abilityCount() const {
return abilities.length;
}
void getAbilities() const {
for(uint i = 0, cnt = abilities.length; i < cnt; ++i)
yield(abilities[i]);
}
int findAbilityOfType(int type) const {
auto@ abl = getAbilityOfType(type);
if(abl is null)
return -1;
else
return abl.id;
}
void triggerAbility(Empire@ emp, Object& obj, int id, Targets@ targs) {
if(!obj.valid || obj.destroying)
return;
Ability@ abl = getAbility(id);
if(abl is null)
return;
if(abl.disabled || abl.cooldown > 0)
return;
if(emp !is null) {
if(neutralAbilities) {
//Check for trade access for cast
Region@ reg = obj.region;
if(reg is null)
return;
if(reg.PlanetsMask != 0) {
if(reg.PlanetsMask & emp.mask == 0)
return;
}
else {
const SystemDesc@ sys = getSystem(reg);
bool found = false;
for(uint i = 0, cnt = sys.adjacent.length; i < cnt; ++i) {
const SystemDesc@ other = getSystem(sys.adjacent[i]);
if(other.object.TradeMask & emp.mask != 0) {
found = true;
break;
}
}
if(!found)
return;
}
}
else {
//Only the owner can cast
if(emp !is obj.owner)
return;
}
if(obj.isArtifact) {
emp.modAttribute(EA_ArtifactsActivated, AC_Add, 1.0);
giveAchievement(emp, "ACH_USE_ARTIFACT");
}
@abl.emp = emp;
}
if(abl.activate(targs)) {
if(abilityDestroy)
obj.destroy();
}
delta = true;
}
void triggerAbility(Player& pl, Object& obj, int id, Targets@ targs) {
triggerAbility(pl != SERVER_PLAYER ? pl.emp : null, obj, id, targs);
}
bool isAbilityOnCooldown(int id) {
Ability@ abl = getAbility(id);
if(abl is null)
return false;
return abl.cooldown > 0;
}
void activateAbility(Player& pl, Object& obj, int id) {
triggerAbility(pl, obj, id, null);
}
void activateAbility(Player& pl, Object& obj, int id, vec3d point) {
Targets targs;
targs.add(TT_Point, fill=true).point = point;
triggerAbility(pl, obj, id, targs);
}
void activateAbility(Player& pl, Object& obj, int id, Object@ target) {
Targets targs;
@targs.add(TT_Object, fill=true).obj = target;
triggerAbility(pl, obj, id, targs);
}
void activateAbilityFor(Object& obj, Empire& emp, int id) {
triggerAbility(emp, obj, id, null);
}
void activateAbilityFor(Object& obj, Empire& emp, int id, vec3d point) {
Targets targs;
targs.add(TT_Point, fill=true).point = point;
triggerAbility(emp, obj, id, targs);
}
void activateAbilityFor(Object& obj, Empire& emp, int id, Object@ target) {
Targets targs;
@targs.add(TT_Object, fill=true).obj = target;
triggerAbility(emp, obj, id, targs);
}
void activateAbilityTypeFor(Object& obj, Empire& emp, int type) {
auto@ abl = getAbilityOfType(type);
if(abl !is null)
activateAbilityFor(obj, emp, abl.id);
}
void activateAbilityTypeFor(Object& obj, Empire& emp, int type, Object@ target) {
auto@ abl = getAbilityOfType(type);
if(abl !is null)
activateAbilityFor(obj, emp, abl.id, target);
}
void activateAbilityTypeFor(Object& obj, Empire& emp, int type, vec3d point) {
auto@ abl = getAbilityOfType(type);
if(abl !is null)
activateAbilityFor(obj, emp, abl.id, point);
}
bool isChanneling(int id) {
Ability@ abl = getAbility(id);
if(abl is null)
return false;
return abl.isChanneling();
}
double getAbilityRange(int id, Object@ target) {
Ability@ abl = getAbility(id);
if(abl is null)
return 0;
return abl.getRange();
}
double getAbilityRange(int id, vec3d target) {
Ability@ abl = getAbility(id);
if(abl is null)
return 0;
return abl.getRange();
}
void writeAbilities(Message& msg) const {
uint cnt = abilities.length;
msg << cnt;
for(uint i = 0; i < cnt; ++i)
msg << abilities[i];
}
bool writeAbilityDelta(Message& msg) {
if(!delta)
return false;
msg.write1();
writeAbilities(msg);
delta = false;
return true;
}
};
+254
View File
@@ -0,0 +1,254 @@
import attributes;
tidy class AttributeMod : Savable {
int id;
uint mode;
double amount;
double timer;
void save(SaveFile& file) {
file << id << mode << amount << timer;
}
void load(SaveFile& file) {
file >> id >> mode >> amount >> timer;
}
};
tidy class Attribute : Savable {
uint index = 0;
double value = 0.0;
double base = 0.0;
bool delta = false;
int permAdd = 0;
int permAddBase = 0;
int permAddFactor = 0;
double permMultiply = 1.0;
array<AttributeMod@> mods;
int nextModId = 1;
void init(Empire& emp, uint ind) {
index = ind;
if(ind < EA_COUNT) {
base = emp.attributes[ind];
value = base;
}
}
void update(Empire& emp) {
double add = double(permAdd) / 1000.0;
double addBase = double(permAddBase) / 1000.0;
double addFactor = double(permAddFactor) / 1000.0;
double mult = permMultiply;
for(uint i = 0, cnt = mods.length; i < cnt; ++i) {
switch(mods[i].mode) {
case AC_Add: add += mods[i].amount; break;
case AC_AddBase: addBase += mods[i].amount; break;
case AC_AddFactor: addFactor += mods[i].amount; break;
case AC_Multiply: mult *= mods[i].amount; break;
}
}
value = ((base + addBase) * (1.0 + addFactor) + add) * mult;
if(index < EA_COUNT)
emp.attributes[index] = value;
delta = true;
}
void mod(Empire& emp, uint mode, double amount) {
switch(mode) {
case AC_Add: permAdd += int(amount * 1000.0); break;
case AC_AddBase: permAddBase += int(amount * 1000.0); break;
case AC_AddFactor: permAddFactor += int(amount * 1000.0); break;
case AC_Multiply: permMultiply *= amount; break;
}
update(emp);
}
AttributeMod@ createMod(Empire& emp, uint mode, double amount, double timer = -1.0) {
AttributeMod mod;
mod.id = nextModId++;
mod.mode = mode;
mod.amount = amount;
mod.timer = timer;
mods.insertLast(mod);
update(emp);
return mod;
}
void removeMod(Empire& emp, int id) {
for(int i = mods.length - 1; i >= 0; --i) {
if(mods[i].id == id) {
mods.removeAt(i);
update(emp);
break;
}
}
}
void tick(Empire& emp, double time) {
bool changed = false;
for(int i = mods.length - 1; i >= 0; --i) {
auto@ mod = mods[i];
if(mod.timer >= 0) {
mod.timer -= time;
if(mod.timer < 0) {
mods.removeAt(i);
changed = true;
}
}
}
if(changed)
update(emp);
}
void save(SaveFile& file) {
file << index;
file << value;
file << base;
file << permAdd;
file << permAddBase;
file << permAddFactor;
file << permMultiply;
file << nextModId;
uint cnt = mods.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << mods[i];
}
void load(SaveFile& file) {
file >> index;
if(file >= SV_0098)
file >> value;
file >> base;
file >> permAdd;
file >> permAddBase;
file >> permAddFactor;
file >> permMultiply;
file >> nextModId;
uint cnt = 0;
file >> cnt;
mods.length = cnt;
for(uint i = 0; i < cnt; ++i) {
@mods[i] = AttributeMod();
file >> mods[i];
}
}
};
tidy class Attributes : Component_Attributes, Savable {
Mutex mtx;
array<Attribute> attributes(getEmpAttributeCount());
double getAttribute(Empire& emp, uint id) {
if(id < EA_COUNT)
return emp.attributes[id];
if(id >= attributes.length)
return 0;
return attributes[id].value;
}
void initAttributes(Empire& emp) {
Lock lock(mtx);
for(uint i = 0, cnt = attributes.length; i < cnt; ++i)
attributes[i].init(emp, i);
}
void syncAttributes(Empire& emp) {
for(uint i = 0, cnt = attributes.length; i < cnt; ++i)
attributes[i].update(emp);
}
double stored = randomd();
void attributesTick(Empire& emp, double time) {
Lock lock(mtx);
stored += time;
if(stored > 1.0) {
for(uint i = 0, cnt = attributes.length; i < cnt; ++i)
attributes[i].tick(emp, time);
stored = 0.0;
}
}
int createAttributeMod(Empire& emp, uint attrib, uint mode, double amount, double timer = -1.0) {
if(attrib >= attributes.length)
return -1;
auto@ attr = attributes[attrib];
Lock lock(mtx);
auto@ mod = attr.createMod(emp, mode, amount, timer);
if(mod is null)
return -1;
return mod.id;
}
void removeAttributeMod(Empire& emp, uint attrib, int id) {
if(attrib >= attributes.length)
return;
auto@ attr = attributes[attrib];
Lock lock(mtx);
attr.removeMod(emp, id);
}
void modAttribute(Empire& emp, uint attrib, uint mode, double amount) {
if(attrib >= attributes.length)
return;
auto@ attr = attributes[attrib];
Lock lock(mtx);
attr.mod(emp, mode, amount);
}
void save(SaveFile& file) {
uint cnt = attributes.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
file.writeIdentifier(SI_EmpAttribute, i);
file << attributes[i];
}
}
void load(SaveFile& file) {
uint cnt = 0;
file >> cnt;
attributes.length = max(getEmpAttributeCount(), cnt);
for(uint i = 0; i < cnt; ++i) {
uint id = i;
if(file >= SV_0098)
id = file.readIdentifier(SI_EmpAttribute);
if(id < attributes.length)
file >> attributes[id];
else
file >> Attribute();
}
}
void writeAttributes(Empire& emp, Message& msg, bool initial) {
Lock lock(mtx);
msg.writeAlign();
uint pos = msg.reserve();
uint n = 0;
for(uint i = 0, cnt = attributes.length; i < cnt; ++i) {
if(!attributes[i].delta && !initial)
continue;
if(!initial)
attributes[i].delta = false;
msg.writeLimited(i,cnt-1);
msg << attributes[i].value;
++n;
}
msg.fill(pos, n);
}
};
+122
View File
@@ -0,0 +1,122 @@
import cargo;
tidy class Cargo : CargoStorage, Component_Cargo {
void getCargo() {
yield(this);
}
double get_cargoCapacity() {
return capacity;
}
double get_cargoStored() {
return filled;
}
double getCargoStored(uint typeId) {
auto@ type = getCargoType(typeId);
if(type is null)
return -1.0;
return get(type);
}
uint get_cargoTypes() {
if(types is null)
return 0;
return types.length;
}
uint get_cargoType(uint index) {
if(types is null)
return uint(-1);
if(index >= types.length)
return uint(-1);
return types[index].id;
}
void modCargoStorage(double amount) {
capacity += amount;
delta = true;
}
void addCargo(uint typeId, double amount) {
auto@ type = getCargoType(typeId);
if(type is null)
return;
add(type, amount);
}
void removeCargo(uint typeId, double amount) {
auto@ type = getCargoType(typeId);
if(type is null)
return;
consume(type, amount, true);
}
double consumeCargo(uint typeId, double amount, bool partial = false) {
auto@ type = getCargoType(typeId);
if(type is null)
return 0.0;
return consume(type, amount, partial);
}
void transferAllCargoTo(Object@ other) {
if(types is null || !other.hasCargo)
return;
double cap = other.cargoCapacity - other.cargoStored;
while(cap > 0 && types.length > 0) {
auto@ type = types[0];
double cons = min(cap / type.storageSize, amounts[0]);
cons = consume(type, cons, partial=true);
if(cons > 0) {
other.addCargo(type.id, cons);
cap -= cons;
}
else {
break;
}
}
}
void transferPrimaryCargoTo(Object@ other, double rate) {
if(types is null || types.length == 0)
return;
auto@ type = types[0];
double realAmount = rate / type.storageSize;
realAmount = consume(type, realAmount, partial=true);
if(realAmount > 0)
other.addCargo(type.id, realAmount);
}
void transferCargoTo(uint typeId, Object@ other) {
if(types is null || types.length == 0)
return;
auto@ type = getCargoType(typeId);
if(type is null)
return;
double stored = getCargoStored(typeId);
if(stored != 0.0) {
double cap = other.cargoCapacity - other.cargoStored;
double cons = min(cap / type.storageSize, stored);
cons = consume(type, cons, partial=true);
if(cons > 0) {
other.addCargo(type.id, cons);
cap -= cons;
}
}
}
void writeCargo(Message& msg) {
msg << this;
}
bool writeCargoDelta(Message& msg) {
if(!delta)
return false;
msg.write1();
writeCargo(msg);
delta = false;
return true;
}
};
+193
View File
@@ -0,0 +1,193 @@
import abilities;
from saving import SaveVersion;
tidy class EnergyManager : Component_EnergyManager, Savable {
Mutex ablMutex;
int nextAbilityId = 0;
array<Ability@> abilities;
bool delta = false;
uint get_abilityTypes(int id) {
Lock lck(ablMutex);
Ability@ abl = getAbility(id);
if(abl is null)
return uint(-1);
return abl.type.id;
}
int addAbility(Empire& emp, uint id) {
Lock lck(ablMutex);
const AbilityType@ type = getAbilityType(id);
if(type !is null) {
delta = true;
return addAbility(emp, type).id;
}
return -1;
}
void removeAbility(Empire& emp, int id) {
Lock lck(ablMutex);
Ability@ abl = getAbility(id);
if(abl is null)
return;
if(!abl.disabled)
abl.disable();
abl.destroy();
abilities.remove(abl);
delta = true;
}
void disableAbility(Empire& emp, int id) {
Lock lck(ablMutex);
Ability@ abl = getAbility(id);
if(abl is null)
return;
if(!abl.disabled) {
abl.disable();
abl.disabled = true;
delta = true;
}
}
void enableAbility(Empire& emp, int id) {
Lock lck(ablMutex);
Ability@ abl = getAbility(id);
if(abl is null)
return;
if(abl.disabled) {
abl.enable();
abl.disabled = false;
delta = true;
}
}
Ability@ addAbility(Empire& emp, const AbilityType@ type, const Subsystem@ sys = null) {
Lock lck(ablMutex);
Ability abl(type);
abl.id = nextAbilityId++;
@abl.subsystem = sys;
@abl.obj = null;
@abl.emp = emp;
abilities.insertLast(abl);
abl.create();
abl.enable();
return abl;
}
void save(SaveFile& file) {
Lock lck(ablMutex);
uint cnt = abilities.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << abilities[i];
file << nextAbilityId;
}
void load(SaveFile& file) {
Lock lck(ablMutex);
uint cnt = 0;
file >> cnt;
abilities.length = cnt;
for(uint i = 0; i < cnt; ++i) {
Ability abl;
file >> abl;
@abilities[i] = abl;
}
file >> nextAbilityId;
}
void powerTick(Empire& emp, double time) {
Lock lck(ablMutex);
for(uint i = 0, cnt = abilities.length; i < cnt; ++i) {
if(!abilities[i].disabled) {
abilities[i].cooldown = max(0.0, abilities[i].cooldown - time);
abilities[i].tick(time);
}
}
}
Ability@ getAbility(int id) {
Lock lck(ablMutex);
for(uint i = 0, cnt = abilities.length; i < cnt; ++i) {
if(abilities[i].id == id)
return abilities[i];
}
return null;
}
void getAbility(int id) const {
Lock lck(ablMutex);
for(uint i = 0, cnt = abilities.length; i < cnt; ++i) {
if(abilities[i].id == id) {
yield(abilities[i]);
return;
}
}
}
void getAbilityOfType(uint id) const {
Lock lck(ablMutex);
for(uint i = 0, cnt = abilities.length; i < cnt; ++i) {
if(abilities[i].type.id == id) {
yield(abilities[i]);
return;
}
}
}
uint get_abilityCount() const {
return abilities.length;
}
void getAbilities() const {
Lock lck(ablMutex);
for(uint i = 0, cnt = abilities.length; i < cnt; ++i)
yield(abilities[i]);
}
void triggerAbility(Empire& emp, int id, Targets@ targs) {
Lock lck(ablMutex);
Ability@ abl = getAbility(id);
if(abl is null)
return;
if(abl.disabled || abl.cooldown > 0)
return;
abl.activate(targs);
delta = true;
}
void activateAbility(Empire& emp, int id) {
triggerAbility(emp, id, null);
}
void activateAbility(Empire& emp, int id, vec3d point) {
Targets targs;
targs.add(TT_Point, fill=true).point = point;
triggerAbility(emp, id, targs);
}
void activateAbility(Empire& emp, int id, Object@ target) {
Targets targs;
@targs.add(TT_Object, fill=true).obj = target;
triggerAbility(emp, id, targs);
}
void writeAbilities(Message& msg) const {
Lock lck(ablMutex);
uint cnt = abilities.length;
msg << cnt;
for(uint i = 0; i < cnt; ++i)
msg << abilities[i];
}
bool writeAbilityDelta(Message& msg) {
if(!delta)
return false;
msg.write1();
writeAbilities(msg);
delta = false;
return true;
}
};
+208
View File
@@ -0,0 +1,208 @@
import saving;
int points(Object& fleet, double strength) {
if(!fleet.isShip)
return 0;
double pts = ceil(sqrt(strength) * 5.0);
if(cast<Ship>(fleet).isStation)
pts *= 0.25;
return pts;
}
tidy class FleetManager : Component_FleetManager, Savable {
ReadWriteMutex fleetMutex;
Object@[] fleetList;
double[] strengths;
int militaryPoints = 0;
FleetManager() {
}
uint get_fleetCount() {
return fleetList.length;
}
Object@ get_fleets(uint index) {
ReadLock lock(fleetMutex);
if(index >= fleetList.length)
return null;
return fleetList[index];
}
Ship@ getStrongestFleet() {
ReadLock lock(fleetMutex);
double str = 0;
Ship@ strongest;
for(uint i = 0, cnt = fleetList.length; i < cnt; ++i) {
if(strengths[i] < str)
continue;
if(!fleetList[i].isShip)
continue;
str = strengths[i];
@strongest = cast<Ship>(fleetList[i]);
}
return strongest;
}
double getTotalFleetStrength(Empire& emp) {
WriteLock lock(fleetMutex);
uint fltCnt = fleetList.length;
if(fltCnt == 0)
return 0.0;
for(uint n = 0; n < fltCnt; ++n) {
uint updateInd = n;
Object@ flt = fleetList[updateInd];
if(flt is null || !flt.valid)
continue;
int prevPoints = points(flt, strengths[updateInd]);
strengths[updateInd] = sqrt(flt.getFleetMaxStrength());
int newPoints = points(flt, strengths[updateInd]);
if(newPoints != prevPoints) {
militaryPoints += (newPoints - prevPoints);
emp.points += (newPoints - prevPoints);
}
}
double total = 0;
for(uint i = 0; i < fltCnt; ++i)
total += strengths[i];
return total;
}
void load(SaveFile& msg) {
uint cnt = 0;
msg >> cnt;
fleetList.length = cnt;
strengths.length = cnt;
for(uint i = 0; i < cnt; ++i) {
msg >> fleetList[i];
if(msg >= SV_0065)
msg >> strengths[i];
else
strengths[i] = 0.0;
}
if(msg >= SV_0124)
msg >> militaryPoints;
}
void save(SaveFile& msg) {
uint cnt = fleetList.length;
msg << cnt;
for(uint i = 0; i < cnt; ++i) {
msg << fleetList[i];
msg << strengths[i];
}
msg << militaryPoints;
}
Object@ getFleetFromPosition(vec3d pos) {
ReadLock lock(fleetMutex);
for(uint i = 0, cnt = fleetList.length; i < cnt; ++i) {
Object@ leader = fleetList[i];
double rad = leader.getFormationRadius();
if(leader.position.distanceToSQ(pos) < rad * rad)
return leader;
}
return null;
}
void registerFleet(Empire& emp, Object@ obj) {
WriteLock lock(fleetMutex);
fleetList.insertLast(obj);
strengths.insertLast(0);
}
void unregisterFleet(Empire& emp, Object@ obj) {
WriteLock lock(fleetMutex);
int ind = fleetList.find(obj);
if(ind != -1) {
int pts = points(obj, strengths[ind]);
if(pts != 0) {
militaryPoints -= pts;
emp.points -= pts;
}
fleetList.removeAt(ind);
strengths.removeAt(ind);
}
}
void getFlagships() {
ReadLock lock(fleetMutex);
for(uint i = 0, cnt = fleetList.length; i < cnt; ++i) {
Object@ leader = fleetList[i];
if(leader.isShip && !cast<Ship>(leader).isStation)
yield(leader);
}
}
void getStations() {
ReadLock lock(fleetMutex);
for(uint i = 0, cnt = fleetList.length; i < cnt; ++i) {
Object@ leader = fleetList[i];
if(leader.isShip && cast<Ship>(leader).isStation)
yield(leader);
}
}
void giveFleetVisionTo(Empire@ toEmpire, bool systemSpace = true, bool deepSpace = true, bool inFTL = true, bool flagships = true, bool stations = false, int statusReq = -1, Region@ toSystem = null) {
if(toEmpire is null)
return;
array<Ship@>@ pending = null;
if(statusReq != -1)
@pending = array<Ship@>();
{
ReadLock lock(fleetMutex);
for(uint i = 0, cnt = fleetList.length; i < cnt; ++i) {
Ship@ ship = cast<Ship>(fleetList[i]);
if(ship is null)
continue;
if(!stations || !flagships) {
if(ship.isStation) {
if(!stations)
continue;
}
else {
if(!flagships)
continue;
}
}
if(!inFTL || !ship.inFTL) {
if(ship.region is null) {
if(!deepSpace)
continue;
}
else {
if(!systemSpace)
continue;
}
}
if(toSystem !is null) {
if(!ship.isMoving)
continue;
if(ship.computedDestination.distanceToSQ(toSystem.position) > (toSystem.radius * toSystem.radius * 2.0))
continue;
}
if(pending !is null)
pending.insertLast(ship);
else
ship.donatedVision |= toEmpire.mask;
}
}
if(pending !is null) {
for(uint i = 0, cnt = pending.length; i < cnt; ++i) {
Ship@ ship = pending[i];
if(statusReq != -1) {
if(!ship.hasStatusEffect(statusReq))
continue;
}
ship.donatedVision |= toEmpire.mask;
}
}
}
};
@@ -0,0 +1,643 @@
import influence;
import saving;
from influence import InfluenceStore;
from influence_global import influenceLock;
import void modGalacticInfluence(int mod) from "influence_global";
import double getInfluenceIncome(int stock, int stored, double factor) from "influence_global";
import double getInfluenceEfficiency(int stock, int stored) from "influence_global";
import double getInfluenceStorage(int stock) from "influence_global";
import double getInfluencePercentage(Empire& emp) from "influence_global";
const double INTELLIGENCE_TIMEOUT = 80.0;
tidy class InfluenceReservation : Savable {
int id = -1;
double timer = -1.0;
double factor = 1.0;
InfluenceReservation() {
}
InfluenceReservation(SaveFile& file) {
load(file);
}
void save(SaveFile& file) {
file << id << timer << factor;
}
void load(SaveFile& file) {
file >> id >> timer >> factor;
}
}
tidy class InfluenceManager : Component_InfluenceManager, InfluenceStore, Savable {
Mutex inflMtx;
Mutex cardMtx;
//Current influence stored
int influence = 0;
int influenceIncome = 0;
//Stored partial generation
double partialInfluence = 0.0;
double StatRecordDelay = 5.0;
//Reservations on influence income
array<InfluenceReservation@> reservations;
int nextReservationId = 1;
double inflFactor = 1.0;
double inflFactorMod = 0.0;
//Available cards to use
array<InfluenceCard@> cards;
int nextCardId = 1;
bool cardDelta = false;
array<double>@ intelligenceTimer;
//Edicts
DiplomacyEdict edict;
InfluenceManager() {
}
void load(SaveFile& file) {
file >> influence;
file >> influenceIncome;
file >> partialInfluence;
file >> StatRecordDelay;
if(partialInfluence != partialInfluence)
partialInfluence = 0.0;
uint cnt = 0;
file >> cnt;
cards.length = cnt;
for(uint i = 0; i < cnt; ++i)
@cards[i] = InfluenceCard(file);
file >> nextCardId;
file >> inflFactor;
file >> inflFactorMod;
file >> nextReservationId;
file >> cnt;
reservations.length = cnt;
for(uint i = 0; i < cnt; ++i)
@reservations[i] = InfluenceReservation(file);
if(file >= SV_0018) {
file >> cnt;
@intelligenceTimer = array<double>(cnt, -1.0);
for(uint i = 0; i < cnt; ++i)
file >> intelligenceTimer[i];
}
if(file >= SV_0070)
file >> edict;
}
void save(SaveFile& file) {
file << influence;
file << influenceIncome;
file << partialInfluence;
file << StatRecordDelay;
uint cnt = cards.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << cards[i];
file << nextCardId;
file << inflFactor;
file << inflFactorMod;
file << nextReservationId;
cnt = reservations.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << reservations[i];
cnt = intelligenceTimer.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << intelligenceTimer[i];
file << edict;
}
int get_Influence() {
return influence;
}
int getInfluenceStock() {
return max(influenceIncome, 0);
}
double get_InfluenceIncome() {
return getInfluenceIncome(max(influenceIncome,0), influence, inflFactor);
}
double get_InfluenceEfficiency() {
return getInfluenceEfficiency(max(influenceIncome,0), influence);
}
double get_InfluenceCap() {
return getInfluenceStorage(max(influenceIncome,0));
}
double get_InfluenceFactor() {
return inflFactor;
}
void modInfluenceFactor(double amount) {
inflFactorMod += amount;
_calcReservation();
}
double get_InfluencePercentage(Empire& emp) {
return getInfluencePercentage(emp);
}
uint getEdictType() {
return edict.type;
}
Empire@ getEdictEmpire() {
return edict.empTarget;
}
Object@ getEdictObject() {
return edict.objTarget;
}
void clearEdict(Empire& emp) {
Lock lck(inflMtx);
edict.clear();
}
void conquerEdict(Empire& emp, Empire@ onEmpire) {
Lock lck(inflMtx);
edict.type = DET_Conquer;
@edict.empTarget = onEmpire;
}
void addInfluence(double amount) {
Lock lock(inflMtx);
partialInfluence += amount;
int take = floor(partialInfluence);
if(take != 0) {
influence += take;
partialInfluence -= double(take);
}
}
void modInfluenceIncome(int amount) {
Lock lock(inflMtx);
modGalacticInfluence(amount);
influenceIncome += amount;
}
int reserveInfluence(double factor, double timer = -1.0) {
int id = 0;
{
Lock lock(inflMtx);
InfluenceReservation res;
res.factor = (1.0 - factor);
res.timer = timer;
res.id = nextReservationId++;
reservations.insertLast(res);
inflFactor *= (1.0 - factor);
id = res.id;
}
return id;
}
void _calcReservation() {
double factor = max(1.0 + inflFactorMod, 0.0);
for(uint i = 0, cnt = reservations.length; i < cnt; ++i)
factor *= reservations[i].factor;
inflFactor = factor;
}
void removeInfluenceReservation(int id) {
Lock lock(inflMtx);
for(uint i = 0, cnt = reservations.length; i < cnt; ++i) {
if(reservations[i].id == id) {
reservations.removeAt(i);
_calcReservation();
return;
}
}
}
bool consumeInfluence(int amount) {
//Consume an amount
Lock lock(inflMtx);
if(influence >= amount) {
influence -= amount;
return true;
}
return false;
}
void modInfluence(int amount) {
Lock lock(inflMtx);
influence = max(influence + amount, 0);
}
void influenceTick(Empire& emp, double time) {
StatRecordDelay -= time;
bool recordStats = StatRecordDelay <= 0;
if(recordStats)
StatRecordDelay += 5.0;
{
Lock lock(inflMtx);
//Tick down timed reservations
bool changed = false;
for(uint i = 0, cnt = reservations.length; i < cnt; ++i) {
InfluenceReservation@ res = reservations[i];
if(res.timer < 0)
continue;
res.timer -= time;
if(res.timer <= 0) {
reservations.removeAt(i);
changed = true;
--i; --cnt;
}
}
if(changed)
_calcReservation();
//Generate passive influence
double income = getInfluenceIncome(max(influenceIncome,0), influence, inflFactor);
double generate = time * income;
if(generate != 0)
addInfluence(generate);
if(recordStats) {
emp.recordStat(stat::Influence, influence);
emp.recordStat(stat::InfluenceIncome, income);
}
//Edicts
if(edict.type == DET_Conquer) {
if(!emp.isHostile(edict.empTarget)) {
emp.clearEdict();
}
}
}
{
Lock glock(influenceLock);
Lock lock(cardMtx);
for(uint i = 0, cnt = cards.length; i < cnt; ++i) {
if(cards[i].tick(time))
cardDelta = true;
}
}
{
if(intelligenceTimer is null)
@intelligenceTimer = array<double>(getEmpireCount(), -1.0);
for(uint i = 0, cnt = intelligenceTimer.length; i < cnt; ++i) {
if(intelligenceTimer[i] < 0)
continue;
auto@ other = getEmpire(i);
intelligenceTimer[i] -= time;
if(intelligenceTimer[i] <= 0.0) {
//Find the intelligence card
auto@ type = ::getInfluenceCardType("Intelligence");
if(type is null)
break;
auto@ card = type.create(uses=1);
@card.owner = emp;
auto@ targ = card.targets.fill("onEmpire");
if(targ is null)
break;
@targ.emp = other;
for(uint j = 0, cnt = cards.length; j < cnt; ++j) {
auto@ check = cards[j];
if(check.canCollapseUses(card)) {
check.uses -= 1;
check.lose(1, false);
if(check.uses == 0) {
cards.removeAt(j);
cards.sortAsc();
intelligenceTimer[i] = -1.0;
break;
}
intelligenceTimer[i] = INTELLIGENCE_TIMEOUT;
cardDelta = true;
break;
}
}
}
}
}
}
void gainCard(Empire& emp, uint typeId, int uses = 1, int quality = 0) {
auto@ type = ::getInfluenceCardType(typeId);
if(type is null)
return;
InfluenceCard@ card = type.create(uses=uses, quality=1+quality);
addCard(emp, card);
}
int addCard(Empire& emp, InfluenceCard@ fromCard, bool wasBuy = true) {
InfluenceCard card = fromCard;
@card.owner = emp;
Lock glock(influenceLock);
Lock lock(cardMtx);
if(card.type.collapseUses && card.uses > 0) {
for(uint i = 0, cnt = cards.length; i < cnt; ++i) {
if(cards[i].canCollapseUses(card)) {
cardDelta = true;
cards[i].uses += card.uses;
cards[i].gain(card.uses, wasBuy);
if(card.uses == 0) {
cards.removeAt(i);
cards.sortAsc();
return -1;
}
return cards[i].id;
}
}
}
card.id = nextCardId++;
cards.insertLast(card);
cards.sortAsc();
cardDelta = true;
card.gain(card.uses, wasBuy);
if(card.uses == 0) {
cards.remove(card);
cards.sortAsc();
return -1;
}
return card.id;
}
void playCard(Empire& emp, int id, Targets@ targets, bool pay = true, InfluenceVote@ vote = null) {
Lock glock(influenceLock);
Lock lock(cardMtx);
InfluenceCard@ card;
uint index = 0;
for(uint i = 0, cnt = cards.length; i < cnt; ++i) {
if(cards[i].id == id) {
index = i;
@card = cards[i];
break;
}
}
if(card is null)
return;
if(vote !is null) {
if(!card.canPlay(vote, targets))
return;
}
else {
if(!card.canPlay(targets))
return;
}
if(pay) {
if(!card.playConsume(targets, vote))
return;
int cost = card.getPlayCost(vote, targets);
if(cost > 0 && !emp.consumeInfluence(cost))
return;
}
if(vote !is null)
card.play(vote, targets);
else
card.play(targets);
if(card.uses == 0) {
cards.remove(card);
cards.sortAsc();
}
cardDelta = true;
}
uint getInfluenceCardType(int id) {
Lock lock(cardMtx);
for(uint i = 0, cnt = cards.length; i < cnt; ++i) {
if(cards[i].id == id)
return cards[i].type.id;
}
return uint(-1);
}
int getInfluenceCardUses(int id) {
Lock lock(cardMtx);
for(uint i = 0, cnt = cards.length; i < cnt; ++i) {
if(cards[i].id == id)
return cards[i].uses;
}
return 0;
}
uint getUsesOfCardType(uint id) {
Lock lock(cardMtx);
uint uses = 0;
for(uint i = 0, cnt = cards.length; i < cnt; ++i) {
if(cards[i].type.id == id && cards[i].uses != -1)
uses += uint(cards[i].uses);
}
return uses;
}
int getInfluenceCardQuality(int id) {
Lock lock(cardMtx);
for(uint i = 0, cnt = cards.length; i < cnt; ++i) {
if(cards[i].id == id)
return cards[i].quality;
}
return 0;
}
int getCostOfCard(Empire& emp, int id) {
Lock lock(cardMtx);
for(uint i = 0, cnt = cards.length; i < cnt; ++i) {
if(cards[i].id == id)
return cards[i].getPurchaseCost(emp);
}
return 0;
}
void getInfluenceCard(int id) {
Lock lock(cardMtx);
for(uint i = 0, cnt = cards.length; i < cnt; ++i) {
if(cards[i].id == id)
yield(cards[i]);
}
}
uint getInfluenceCardCount() {
return cards.length;
}
void getInfluenceCards() {
Lock lock(cardMtx);
for(uint i = 0, cnt = cards.length; i < cnt; ++i)
yield(cards[i]);
}
void takeCardUse(int id, uint amount = 1) {
Lock glock(influenceLock);
Lock lock(cardMtx);
for(uint i = 0, cnt = cards.length; i < cnt; ++i) {
auto@ card = cards[i];
if(card.id == id) {
if(amount == uint(-1)) {
card.lose(card.uses, false);
card.uses = 0;
}
else if(card.uses > 0) {
card.uses = max(0, card.uses - amount);
card.lose(amount, false);
}
if(card.uses == 0) {
cards.remove(card);
cards.sortAsc();
cardDelta = true;
}
return;
}
}
}
void copyCardTo(int id, Empire@ otherEmp, int uses = 0, bool maxQuality = false, int addQuality = 0) {
InfluenceCard copy;
bool found = false;
{
Lock glock(influenceLock);
Lock lock(cardMtx);
for(uint i = 0, cnt = cards.length; i < cnt; ++i) {
auto@ card = cards[i];
if(card.id == id) {
copy = card;
found = true;
break;
}
}
}
if(!found)
return;
if(maxQuality)
copy.quality = copy.type.maxQuality;
if(addQuality != 0) {
copy.quality += addQuality;
if(!copy.type.canOverquality)
copy.quality = clamp(copy.quality, copy.type.minQuality, copy.type.maxQuality);
else
copy.quality = max(copy.quality, copy.type.minQuality);
}
if(uses != 0)
copy.uses = uses;
cast<InfluenceStore>(otherEmp.InfluenceManager).addCard(otherEmp, copy);
}
void gainRandomLeverage(Empire& emp, Empire@ towards, double qualityFactor = 1.0) {
if(!emp.valid || !emp.major)
return;
int cardAmount = 1;
if(qualityFactor > 3.0) {
cardAmount = randomi(1, ceil(qualityFactor / 3.0));
qualityFactor /= double(cardAmount);
}
auto@ type = ::getInfluenceCardType("Leverage");
if(type is null)
return;
qualityFactor = pow(randomd(), (2.0 / qualityFactor));
int quality = type.minQuality + floor(double(type.maxQuality - type.minQuality + 1) * qualityFactor);
auto@ card = type.create(uses=cardAmount, quality=quality);
auto@ targ = card.targets.fill("onEmpire");
if(targ is null)
return;
@targ.emp = towards;
if(card !is null)
addCard(emp, card);
}
bool gainIntelligence(Empire& emp, Empire@ towards, uint amount = 1) {
if(!emp.valid || !emp.major)
return false;
auto@ type = ::getInfluenceCardType("Intelligence");
if(type is null)
return true;
auto@ card = type.create(uses=amount);
@card.owner = emp;
auto@ targ = card.targets.fill("onEmpire");
if(targ is null)
return true;
@targ.emp = towards;
intelligenceTimer[towards.index] = INTELLIGENCE_TIMEOUT;
Lock glock(influenceLock);
Lock lock(cardMtx);
for(uint i = 0, cnt = cards.length; i < cnt; ++i) {
auto@ other = cards[i];
if(other.canCollapseUses(card)) {
if(other.uses < 3) {
auto prevUses = other.uses;
other.uses = min(3, other.uses + amount);
other.gain(other.uses - prevUses, false);
cardDelta = true;
return true;
}
else {
return false;
}
}
}
addCard(emp, card);
return true;
}
void writeInfluenceManager(Message& msg, bool initial) {
if(initial || cardDelta) {
msg.write1();
Lock lock(cardMtx);
uint cnt = cards.length;
msg.writeSmall(cnt);
for(uint i = 0; i < cnt; ++i)
msg << cards[i];
if(!initial)
cardDelta = false;
}
else {
msg.write0();
}
msg << inflFactor;
msg.writeSignedSmall(influence);
msg.writeSignedSmall(influenceIncome);
msg << edict;
}
};
File diff suppressed because it is too large Load Diff
+168
View File
@@ -0,0 +1,168 @@
import notifications;
from notifications import NotificationStore;
import influence;
import buildings;
from influence_global import getInfluenceVoteByID, getTreatyDesc;
tidy class Notifications : Component_Notifications, Savable, NotificationStore {
Mutex mtx;
array<Notification@> list;
void save(SaveFile& file) {
uint cnt = list.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
list[i].save(file);
}
void load(SaveFile& file) {
uint cnt = 0;
file >> cnt;
list.length = cnt;
for(uint i = 0; i < cnt; ++i)
@list[i] = loadNotification(file);
}
uint get_notificationCount() const {
return list.length;
}
void getNotifications(uint limit, int beforeId = -1, bool reverse = true) {
Lock lock(mtx);
if(reverse) {
if(beforeId == -1 || beforeId > int(list.length))
beforeId = list.length;
if(beforeId == 0)
return;
for(int i = beforeId - 1; i >= 0; --i) {
Notification@ n = list[i];
yieldNotification(n);
if(--limit == 0)
break;
}
}
else {
int cnt = list.length;
if(beforeId == -1 || beforeId > cnt)
beforeId = 0;
if(beforeId >= cnt)
return;
for(int i = beforeId; i < cnt; ++i) {
Notification@ n = list[i];
yieldNotification(n);
if(--limit == 0)
break;
}
}
}
void addNotification(Empire& emp, Notification@ n) {
Lock lock(mtx);
list.insertLast(n);
}
void notifyVote(Empire& emp, int voteId, int eventId) {
VoteNotification n;
InfluenceVote@ vote = getInfluenceVoteByID(voteId);
n.vote = InfluenceVoteStub(vote);
n.event = vote.events[eventId];
addNotification(emp, n);
}
void notifyGeneric(Empire& emp, string title, string desc, string icon = "", Empire@ fromEmp = null, Object@ forObject = null) {
GenericNotification n;
@n.fromEmp = fromEmp;
@n.obj = forObject;
n.title = title;
n.desc = desc;
n.iconDesc = icon;
addNotification(emp, n);
}
void notifyWarStatus(Empire& emp, Empire@ withEmpire, uint type) {
WarStatusNotification n;
n.statusType = type;
@n.withEmpire = withEmpire;
addNotification(emp, n);
}
void notifyWarEvent(Empire& emp, Object@ obj, uint type) {
WarEventNotification n;
n.eventType = type;
@n.obj = obj;
addNotification(emp, n);
}
void notifyRename(Empire& emp, Object@ obj, string fromName, string toName) {
RenameNotification n;
@n.obj = obj;
@n.fromEmpire = obj.owner;
n.fromName = fromName;
n.toName = toName;
addNotification(emp, n);
}
void notifyAnomaly(Empire& emp, Object@ obj) {
AnomalyNotification n;
@n.obj = obj;
addNotification(emp, n);
}
void notifyFlagship(Empire& emp, Object@ obj) {
FlagshipBuiltNotification n;
@n.obj = obj;
addNotification(emp, n);
}
void notifyStructure(Empire& emp, Object@ obj, uint type) {
StructureBuiltNotification n;
@n.obj = obj;
@n.bldg = getBuildingType(type);
addNotification(emp, n);
}
void notifyEmpireMet(Empire& emp, Object@ obj, Empire@ metEmp, bool gainsBonus = false) {
EmpireMetNotification n;
@n.region = obj;
@n.metEmpire = metEmp;
n.gainsBonus = gainsBonus;
addNotification(emp, n);
}
void notifyTreaty(Empire& emp, uint treatyId, uint eventType, Empire@ empOne = null, Empire@ empTwo = null) {
auto@ treaty = getTreatyDesc(treatyId);
if(treaty is null)
return;
TreatyEventNotification n;
n.treaty = treaty;
n.eventType = eventType;
@n.empOne = empOne;
@n.empTwo = empTwo;
addNotification(emp, n);
}
uint prevSynced = 0;
void writeNotifications(Message& msg, bool delta) {
Lock lock(mtx);
uint start, cnt;
if(delta) {
start = prevSynced;
cnt = list.length - prevSynced;
}
else {
start = 0;
cnt = list.length;
}
msg << cnt;
for(uint i = 0; i < cnt; ++i)
msg << list[start + i];
if(delta)
prevSynced = list.length;
}
};
File diff suppressed because it is too large Load Diff
+288
View File
@@ -0,0 +1,288 @@
const double orbitSpeedFactor = 2.0;
tidy class Orbit : Component_Orbit, Savable {
Object@ center_obj;
vec3d center_pos;
double radius;
double yearPos;
double yearLen;
double dayPos;
double dayLen;
bool delta = false;
Orbit() {
dayLen = 0;
yearLen = 0;
}
void load(SaveFile& data) {
data >> center_obj;
if(center_obj is null)
data >> center_pos;
data >> radius;
data >> yearPos;
data >> yearLen;
data >> dayPos;
data >> dayLen;
}
void save(SaveFile& data) {
data << center_obj;
if(center_obj is null)
data << center_pos;
data << radius;
data << yearPos;
data << yearLen;
data << dayPos;
data << dayLen;
}
void orbitTick(Object& obj, double time) {
if(yearLen != 0) {
yearPos = (yearPos + time) % yearLen;
vec3d position;
quaterniond rotation = quaterniond_fromAxisAngle(vec3d_up(), yearPos / yearLen * twopi);
position = rotation * vec3d_front(radius);
if(center_obj is null) {
position += center_pos;
}
else {
if(!center_obj.initialized)
return;
position += center_obj.position;
}
if(time > 0.01) {
vec3d newVel = (position - obj.position) / time;
obj.acceleration = (newVel - obj.velocity) / time;
obj.velocity = newVel;
}
obj.position = position;
if(obj.hasMover)
obj.clearMovement();
}
else {
obj.position += obj.velocity * time;
obj.velocity += obj.acceleration * time;
}
if(dayLen != 0) {
if(dayLen < 0) {
vec3d center = obj.position;
if(center_obj is null)
center = center_pos;
else
center = center_obj.position;
obj.rotation = quaterniond_fromVecToVec(vec3d_front(), obj.position - center);
}
else {
dayPos = (dayPos + time) % dayLen;
obj.rotation = quaterniond_fromAxisAngle(vec3d_up(), dayPos / dayLen * twopi);
}
}
}
void setOrbitPct(Object& obj, double pct) {
yearPos = yearLen * pct;
orbitTick(obj, 0);
delta = true;
}
void orbitRadius(Object& obj, double newRadius) {
radius = newRadius;
orbitTick(obj, 0);
delta = true;
}
void orbitAround(Object& obj, vec3d point) {
orbitAround_minRad(obj, point, 0.0);
}
void orbitAround(Object& obj, double minRadius, vec3d point) {
orbitAround_minRad(obj, point, minRadius);
}
void stopOrbit() {
yearLen = 0;
}
bool get_inOrbit() {
return yearLen != 0;
}
void remakeStandardOrbit(Object& obj, bool orbitPlanets = true) {
Region@ reg = obj.region;
yearLen = 0;
if(reg is null)
return;
if(reg.starCount == 0)
return;
if(obj.position.distanceTo(reg.position) < obj.radius)
return;
Object@ orbObj;
if(orbitPlanets && !obj.isPlanet)
@orbObj = reg.getOrbitObject(obj.position);
if(orbObj !is null)
obj.orbitAround(orbObj);
else
obj.orbitAround(200, reg.position);
}
Object@ getOrbitingAround() {
return center_obj;
}
bool get_hasOrbitCenter() const {
return center_obj !is null;
}
bool isOrbitingAround(Object@ around) const {
if(around is center_obj)
return true;
return false;
}
void orbitAround(Object& obj, vec3d position, vec3d origin) {
obj.position = position;
orbitAround(obj, origin);
}
void orbitAround_minRad(Object& obj, vec3d point, double minRadius = 0) {
vec3d offset = (obj.position - point);
center_pos = point;
@center_obj = null;
radius = max(offset.length, minRadius);
double angle = (vec2d(obj.position.x, -obj.position.z) - vec2d(point.x, -point.z)).radians();
if(angle < 0)
angle += twopi;
yearLen = sqrt(pow(radius, 3.0)) / orbitSpeedFactor;
yearPos = yearLen * angle / twopi;
orbitTick(obj, 0);
delta = true;
}
void orbitAround(Object& obj, vec3d point, double orbRadius) {
center_pos = point;
@center_obj = null;
radius = orbRadius;
yearLen = sqrt(pow(radius, 3.0)) / orbitSpeedFactor;
double angle = (vec2d(obj.position.x, -obj.position.z) - vec2d(point.x, -point.z)).radians();
if(angle < 0)
angle += twopi;
yearPos = yearLen * angle / twopi;
orbitTick(obj, 0);
delta = true;
}
void orbitAround(Object& obj, Object& around, double orbRadius, double angle) {
@center_obj = around;
radius = orbRadius;
yearLen = sqrt(pow(radius, 3.0)) / orbitSpeedFactor;
yearPos = yearLen * angle / twopi;
orbitTick(obj, 0);
delta = true;
}
void orbitAround(Object& obj, Object& around, double orbRadius) {
@center_obj = around;
radius = orbRadius;
yearLen = sqrt(pow(radius, 3.0)) / orbitSpeedFactor;
double angle = (vec2d(obj.position.x, -obj.position.z) - vec2d(around.position.x, -around.position.z)).radians();
if(angle < 0)
angle += twopi;
yearPos = yearLen * angle / twopi;
orbitTick(obj, 0);
delta = true;
}
void orbitAround(Object& obj, Object& around) {
@center_obj = around;
radius = max(obj.position.distanceTo(around.position), obj.radius + around.radius);
yearLen = sqrt(pow(radius, 3.0)) / orbitSpeedFactor;
double angle = (vec2d(obj.position.x, -obj.position.z) - vec2d(around.position.x, -around.position.z)).radians();
if(angle < 0)
angle += twopi;
yearPos = yearLen * angle / twopi;
orbitTick(obj, 0);
delta = true;
}
void orbitSpin(Object& obj, double dayLength, bool staticPos) {
dayLen = dayLength;
if(staticPos && dayLen > 0)
dayPos = gameTime % dayLen;
else
dayPos = 0;
orbitTick(obj, 0);
delta = true;
}
void orbitDuration(double duration) {
yearLen = duration;
}
void writeOrbit(const Object& obj, Message& msg) {
msg << float(yearLen);
msg << float(dayLen);
msg.writeFixed(dayPos, 0.0, dayLen);
if(center_obj !is null) {
msg.write1();
msg << center_obj;
}
else {
msg.write0();
msg.writeMedVec3(center_pos);
}
msg << float(radius);
msg.writeFixed(yearPos, 0.0, yearLen);
if(yearLen == 0) {
msg.writeMedVec3(obj.position);
msg.writeSmallVec3(obj.velocity);
}
}
void readOrbit(Object& obj, Message& msg) {
yearLen = msg.read_float();
dayLen = msg.read_float();
dayPos = msg.readFixed(0.0, dayLen);
if(msg.readBit()) {
msg >> center_obj;
}
else {
center_pos = msg.readMedVec3();
@center_obj = null;
}
radius = msg.read_float();
yearPos = msg.readFixed(0.0, yearLen);
if(yearLen == 0) {
obj.position = msg.readMedVec3();
obj.velocity = msg.readSmallVec3();
}
}
bool writeOrbitDelta(const Object& obj, Message& msg) {
if(!delta && yearLen != 0)
return false;
delta = false;
msg.write1();
writeOrbit(obj, msg);
return true;
}
void readOrbitDelta(Object& obj, Message& msg) {
readOrbit(obj, msg);
}
}
+232
View File
@@ -0,0 +1,232 @@
import random_events;
tidy class RandomEvents : Component_RandomEvents, EventContainer, Savable {
Mutex mtx;
array<CurrentEvent@> events;
int nextEventId = 0;
double nextRandomEvent = -1;
array<const RandomEvent@> considering;
set_int eventsEncountered;
array<int> encounteredList;
CurrentEvent consEvt;
void save(SaveFile& file) {
file << nextEventId;
file << nextRandomEvent;
uint cnt = events.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << events[i];
cnt = considering.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file.writeIdentifier(SI_RandomEvent, considering[i].id);
cnt = encounteredList.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file.writeIdentifier(SI_RandomEvent, encounteredList[i]);
}
void load(SaveFile& file) {
file >> nextEventId;
file >> nextRandomEvent;
uint cnt = 0;
file >> cnt;
events.length = cnt;
for(uint i = 0; i < cnt; ++i) {
@events[i] = CurrentEvent();
file >> events[i];
}
file >> cnt;
considering.length = 0;
considering.reserve(cnt);
for(uint i = 0; i < cnt; ++i) {
auto@ type = getRandomEvent(file.readIdentifier(SI_RandomEvent));
if(type !is null)
considering.insertLast(type);
}
file >> cnt;
encounteredList.length = cnt;
for(uint i = 0; i < cnt; ++i) {
encounteredList[i] = file.readIdentifier(SI_RandomEvent);
eventsEncountered.insert(encounteredList[i]);
}
}
CurrentEvent@ getEventByID(int id) {
for(uint i = 0, cnt = events.length; i < cnt; ++i) {
if(events[i].id == id)
return events[i];
}
return null;
}
int get_currentEventID() {
if(events.length == 0)
return -1;
Lock lck(mtx);
if(events.length == 0)
return -1;
return events[0].id;
}
bool hasCurrentEvents() {
return events.length != 0;
}
void getCurrentEvents() {
Lock lck(mtx);
for(uint i = 0, cnt = events.length; i < cnt; ++i)
yield(events[i]);
}
void getEvent(int id) {
Lock lck(mtx);
auto@ evt = getEventByID(id);
if(evt !is null)
yield(evt);
}
void create(CurrentEvent@ evt) {
CurrentEvent newEvent = evt;
Lock lck(mtx);
newEvent.id = nextEventId++;
eventsEncountered.insert(evt.type.id);
encounteredList.insertLast(evt.type.id);
events.insertLast(newEvent);
}
void setNextEvent() {
if(config::RANDOM_EVENT_OCCURRENCE == 0) {
nextRandomEvent = INFINITY;
return;
}
double time = 600.0 / config::RANDOM_EVENT_OCCURRENCE;
double mod = max(time - config::RANDOM_EVENT_MIN_INTERVAL, 0.0);
nextRandomEvent = gameTime + normald(time-mod, time+mod);
}
void spawnRandomEvent(Empire& emp, uint typeId) {
auto@ type = getRandomEvent(typeId);
if(type is null)
return;
CurrentEvent evt;
evt.clear(type);
@evt.owner = emp;
if(evt.consider()) {
evt.create();
create(evt);
}
}
void eventsTick(Empire& emp, double time) {
if(emp.isAI && emp !is playerEmpire) {
nextRandomEvent = -1;
return;
}
//Tick existing events
for(uint i = 0, cnt = events.length; i < cnt; ++i) {
auto@ evt = events[i];
if(evt.timer > 0) {
evt.timer -= time;
if(evt.timer <= 0) {
int optId = -1;
for(uint i = 0, cnt = evt.options.length; i < cnt; ++i) {
if(evt.options[i].defaultOption) {
optId = evt.options[i].id;
break;
}
}
if(optId != -1)
chooseEventOption(emp, evt.id, optId);
else
events.removeAt(i);
--i; --cnt;
}
}
}
//Consider new events
if(nextRandomEvent < 0) {
setNextEvent();
}
else if(considering.length != 0) {
const RandomEvent@ checkType;
double tot = 0;
for(uint i = 0, cnt = considering.length; i < cnt; ++i) {
double freq = considering[i].frequency;
tot += freq;
if(randomd() < freq / tot)
@checkType = considering[i];
}
if(checkType !is null) {
consEvt.clear(checkType);
@consEvt.owner = emp;
if(consEvt.consider()) {
consEvt.create();
create(consEvt);
considering.length = 0;
}
else {
considering.remove(checkType);
}
if(considering.length == 0)
setNextEvent();
}
else {
setNextEvent();
}
}
else if(nextRandomEvent < gameTime) {
for(uint i = 0, cnt = getRandomEventCount(); i < cnt; ++i) {
auto@ type = getRandomEvent(i);
if(type.mode != RTM_Random)
continue;
if(type.frequency <= 0)
continue;
if(type.unique && eventsEncountered.contains(type.id))
continue;
considering.insertLast(type);
}
if(considering.length == 0)
setNextEvent();
}
}
void chooseEventOption(Empire& emp, int evtId, uint optId) {
Lock lck(mtx);
auto@ evt = getEventByID(evtId);
if(evt !is null) {
for(uint i = 0, cnt = evt.options.length; i < cnt; ++i) {
if(evt.options[i].id == optId) {
evt.options[i].trigger(evt);
break;
}
}
events.remove(evt);
}
}
void writeEvents(Message& msg) {
if(events.length == 0) {
msg.write0();
return;
}
Lock lck(mtx);
msg.write1();
msg.writeSmall(events.length);
for(uint i = 0, cnt = events.length; i < cnt; ++i)
msg << events[i];
}
};
+526
View File
@@ -0,0 +1,526 @@
import research;
import saving;
import unlock_tags;
import achievements;
import unlock_tags;
tidy class ResearchGrid : Component_ResearchGrid, Savable {
ReadWriteMutex mtx;
TechnologyGrid@ grid;
double researchRate = 0;
double points = 0;
double totalGenerated = 0;
bool delta = false;
bool gridDelta = false;
Mutex unlockMtx;
array<int> tagUnlocks;
bool unlockDelta = false;
double StatRecordDelay = 5.0;
ResearchGrid() {}
void save(SaveFile& file) {
file << researchRate;
file << points;
file << grid;
file << totalGenerated;
uint cnt = tagUnlocks.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
file.writeIdentifier(SI_UnlockTag, i);
file << tagUnlocks[i];
}
}
void load(SaveFile& file) {
if(file < SV_0085) {
loadOld(file);
return;
}
file >> researchRate;
file >> points;
@grid = TechnologyGrid();
file >> grid;
file >> totalGenerated;
tagUnlocks.length = getUnlockTagCount();
for(uint i = 0, cnt = tagUnlocks.length; i < cnt; ++i)
tagUnlocks[i] = 0;
if(file >= SV_0091) {
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
int id = file.readIdentifier(SI_UnlockTag);
int val = 0;
file >> val;
if(id >= 0 && uint(id) < tagUnlocks.length)
tagUnlocks[id] = val;
}
}
}
double get_ResearchRate(Empire& emp) {
return researchRate * ResearchEfficiency * emp.ResearchGenerationFactor;
}
double get_ResearchPoints() {
return points;
}
double get_ResearchEfficiency() {
return 2000.0 / (2000.0 + totalGenerated);
}
void modResearchRate(double mod) {
WriteLock lock(mtx);
researchRate += mod;
}
bool gaveAchievement = false;
void researchTick(Empire& emp, double time) {
{
WriteLock lock(mtx);
double genPts = researchRate * time * ResearchEfficiency * emp.ResearchGenerationFactor;
totalGenerated += genPts;
points += genPts;
for(uint i = 0; i < grid.nodes.length; ++i) {
if(grid.nodes[i].timer >= 0)
delta = true;
grid.nodes[i].tick(emp, grid, time);
}
}
StatRecordDelay -= time;
bool recordStats = StatRecordDelay <= 0;
if(recordStats) {
emp.recordStat(stat::ResearchIncome, float(researchRate * ResearchEfficiency * emp.ResearchGenerationFactor));
emp.recordStat(stat::ResearchTotal, totalGenerated);
StatRecordDelay += 5.0;
if(!gaveAchievement && totalGenerated >= 25000.0) {
gaveAchievement = true;
giveAchievement(emp, "ACH_MAX_TECH");
}
}
}
void generatePoints(Empire& emp, double pts, bool modified = true, bool penalized = true) {
WriteLock lock(mtx);
double genPts = pts;
if(modified)
genPts *= ResearchEfficiency;
points += genPts;
if(penalized)
totalGenerated += genPts;
}
bool consumeResearchPoints(int amount) {
WriteLock lock(mtx);
if(points < amount)
return false;
points -= amount;
return true;
}
void freeResearchPoints(int amount) {
WriteLock lock(mtx);
points += amount;
}
void reduceResearchPenalty(int points) {
WriteLock lock(mtx);
totalGenerated = max(0.0, totalGenerated - points);
}
void initResearch(Empire& emp) {
WriteLock lock(mtx);
if(hasDLC("Heralds"))
@grid = getTechnologyGridSpec("Heralds").create();
else
@grid = getTechnologyGridSpec("Base").create();
tagUnlocks.length = getUnlockTagCount();
for(uint i = 0, cnt = tagUnlocks.length; i < cnt; ++i)
tagUnlocks[i] = 0;
//DLC unlock tags
for(uint i = 0, cnt = dlcs.length; i < cnt; ++i) {
if(!hasDLC(dlcs[i]))
continue;
int tag = getUnlockTag(dlcs[i]+"DLC");
tagUnlocks[tag] = 1;
}
//Pick some secret projects
for(uint n = 0; n < uint(config::PICK_SECRET_PROJECTS); ++n) {
TechnologyNode@ node;
double total = 0.0;
for(uint i = 0, cnt = grid.nodes.length; i < cnt; ++i) {
auto@ other = grid.nodes[i];
if(!other.secret || other.type.secretFrequency <= 0.0)
continue;
if(other.secretPicked)
continue;
if(!other.canBeSecret(emp))
continue;
total += other.type.secretFrequency;
if(randomd() < other.type.secretFrequency / total)
@node = other;
}
if(node is null)
break;
node.secretPicked = true;
}
}
void getTechnologyNodes() {
if(grid is null)
return;
ReadLock lock(mtx);
for(uint i = 0, cnt = grid.nodes.length; i < cnt; ++i)
yield(grid.nodes[i]);
}
void getResearchingNodes() {
if(grid is null)
return;
ReadLock lock(mtx);
for(uint i = 0, cnt = grid.nodes.length; i < cnt; ++i) {
auto@ node = grid.nodes[i];
if(!node.bought)
continue;
if(node.unlocked)
continue;
if(!node.unlockable)
continue;
yield(grid.nodes[i]);
}
}
void getTechnologyNode(int id) {
ReadLock lock(mtx);
for(uint i = 0, cnt = grid.nodes.length; i < cnt; ++i) {
if(id == grid.nodes[i].id) {
yield(grid.nodes[i]);
return;
}
}
}
void getTechnologyNode(vec2i pos) {
ReadLock lock(mtx);
for(uint i = 0, cnt = grid.nodes.length; i < cnt; ++i) {
if(pos == grid.nodes[i].position) {
yield(grid.nodes[i]);
return;
}
}
}
TechnologyNode@ getNode(int id) {
for(uint i = 0, cnt = grid.nodes.length; i < cnt; ++i) {
if(grid.nodes[i].id == id)
return grid.nodes[i];
}
return null;
}
void setResearchQueued(Empire& emp, int id, bool queued) {
WriteLock lock(mtx);
auto@ node = getNode(id);
if(node is null)
return;
if(node.bought)
return;
node.queued = queued;
}
void research(Empire& emp, int id, bool secondary = false, bool queue = false) {
WriteLock lock(mtx);
auto@ node = getNode(id);
if(node is null)
return;
if(node.bought)
return;
if(!node.canUnlock(emp)) {
if(queue && !secondary) {
node.queued = true;
delta = true;
}
return;
}
auto cost = node.getPointCost(emp);
if(secondary) {
if(emp.ForbidSecondaryUnlock != 0 && cost != 0)
return;
if(!node.canSecondaryUnlock(emp))
return;
if(!node.consumeSecondary(emp))
return;
node.secondaryUnlock = true;
totalGenerated += node.getPointCost(emp);
}
else {
if(cost == 0)
return;
if(cost > points) {
if(queue) {
node.queued = true;
delta = true;
}
return;
}
points -= cost;
}
node.buy(emp);
grid.markBought(node.position, emp);
delta = true;
}
bool isTagUnlocked(int id) {
if(id < 0 || uint(id) >= tagUnlocks.length)
return false;
return tagUnlocks[id] > 0;
}
void setTagUnlocked(int id, bool unlocked) {
if(id < 0 || uint(id) >= tagUnlocks.length) {
error("Error: cannot set unlocked for out-of-range tag "+id+" - "+getUnlockTagIdent(id));
return;
}
Lock lck(unlockMtx);
if(unlocked)
tagUnlocks[id] += 1;
else
tagUnlocks[id] -= 1;
unlockDelta = true;
}
void removeResearchOfType(int typeId) {
WriteLock lock(mtx);
for(uint i = 0, cnt = grid.nodes.length; i < cnt; ++i) {
auto@ node = grid.nodes[i];
if(int(node.type.id) == typeId) {
if(!node.unlocked) {
grid.nodes.removeAt(i);
--i;
--cnt;
}
}
}
grid.regenGrid();
gridDelta = true;
}
void replaceResearchAt(vec2i pos, int replaceWith) {
auto@ otherType = getTechnology(replaceWith);
if(otherType is null)
return;
WriteLock lock(mtx);
for(uint i = 0, cnt = grid.nodes.length; i < cnt; ++i) {
auto@ node = grid.nodes[i];
if(node.position == pos) {
if(!node.unlocked) {
@node.type = otherType;
if(otherType.defaultUnlock)
grid.markUnlocked(node.position);
}
}
}
gridDelta = true;
}
void replaceResearchOfType(int typeId, int replaceWith) {
auto@ otherType = getTechnology(replaceWith);
if(otherType is null)
return;
WriteLock lock(mtx);
for(uint i = 0, cnt = grid.nodes.length; i < cnt; ++i) {
auto@ node = grid.nodes[i];
if(int(node.type.id) == typeId) {
if(!node.unlocked) {
@node.type = otherType;
if(otherType.defaultUnlock)
grid.markUnlocked(node.position);
}
}
}
gridDelta = true;
}
void replaceResearchGrid(string name) {
auto@ gridType = getTechnologyGridSpec(name);
if(gridType is null)
return;
WriteLock lock(mtx);
@grid = gridType.create();
gridDelta = true;
}
void overlayResearchGrid(string name) {
auto@ gridType = getTechnologyGridSpec(name);
if(gridType is null)
return;
WriteLock lock(mtx);
@grid = gridType.create();
gridDelta = true;
}
void revealSecretProject(Empire& emp, bool pickedOnly) {
WriteLock lock(mtx);
TechnologyNode@ pick;
double count = 0;
for(uint i = 0, cnt = grid.nodes.length; i < cnt; ++i) {
auto@ node = grid.nodes[i];
if(!node.secret)
continue;
if(pickedOnly && !node.secretPicked)
continue;
if(node.available)
continue;
if(!node.canBeSecret(emp))
continue;
count += 1.0;
if(randomd() < 1.0 / count)
@pick = node;
}
if(pick !is null) {
pick.secretPicked = true;
pick.secret = false;
}
}
//Networking
void writeResearch(Message& msg, bool initial) {
ReadLock lock(mtx);
msg << researchRate;
msg << points;
msg << totalGenerated;
if(initial) {
msg.write1();
msg.write1();
}
else {
msg.writeBit(delta);
msg.writeBit(gridDelta);
}
if(initial || unlockDelta) {
msg.write1();
uint cnt = tagUnlocks.length;
msg.writeSmall(cnt);
for(uint i = 0; i < cnt; ++i)
msg.writeBit(tagUnlocks[i] > 0);
if(!initial)
unlockDelta = false;
}
else {
msg.write0();
}
if(initial || delta || gridDelta) {
if(initial || gridDelta) {
msg << grid.minPos;
msg << grid.maxPos;
msg << grid.nodes.length;
}
for(uint i = 0, cnt = grid.nodes.length; i < cnt; ++i) {
if(initial || gridDelta)
grid.nodes[i].write(msg);
else
grid.nodes[i].writeStatus(msg);
}
if(!initial) {
delta = false;
gridDelta = false;
}
}
}
//Skip over data from old savegames
void loadOld(SaveFile& file) {
int tmp = 0;
int64 tmp64 = 0;
double tmpD = 0;
bool tmpB = false;
Object@ tmpO;
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
//load field
file >> tmp;
file >> tmp;
if(file < SV_0072)
file >> tmp;
file >> tmpD;
file >> tmpD;
}
if(file.readBit())
file >> tmp;
file >> researchRate;
file >> tmpD;
file >> tmp;
file >> tmp;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
//load project
file >> tmp;
uint type = 0;
file >> type;
for(uint n = 0; n < 7; ++n)
file >> tmp;
file >> tmpB;
//load hooks
if(tmpB) {
if(type == 24 || type == 25) {
file >> tmpD;
}
else if(type == 27) {
uint sub = 0;
file >> sub;
for(uint j = 0; j < sub; ++j) {
file >> tmpO;
file >> tmpD;
file >> tmpB;
if(tmpB && file >= SV_0013)
file >> tmp64;
}
}
}
}
file >> tmp;
for(uint i = 0; i < 7; ++i)
file >> tmp;
}
};
@@ -0,0 +1,734 @@
#include "include/resource_constants.as"
import resources;
import attributes;
from saving import SaveVersion;
//Amount of money spent in welfare that gives you one influence stock
const int MONEY_PER_INFLUENCE = 350;
//Amount of money spent in welfare that gives you one energy stock
const int MONEY_PER_ENERGY = 350;
//Amount of money spent in welfare that gives you one research stock
const int MONEY_PER_RESEARCH = 350;
//Amount of money spent in welfare that gives you one labor generation on the homeworld
const int MONEY_PER_HW_LABOR = 350;
//Amount of money spent in welfare that gives you one global defense generation
const int MONEY_PER_DEFENSE = 350;
tidy class EnergyFloat {
Empire@ forEmp;
double amount;
};
tidy class ResourceManager : Component_ResourceManager, Savable {
Mutex popMutex;
Mutex budgetMutex;
Mutex ftlMutex;
Mutex energyMutex;
double Population = 0;
double FTL_Capacity = 0;
double FTL_Stored = 0;
double FTL_Income = 0;
double FTL_Use = 0;
double Energy_Stored = 0;
double Energy_Income = 0;
double Energy_Use = 0;
double Energy_Allocated = 0;
array<EnergyFloat@> floatedEnergy;
int Budget_Total = 0;
int Maintenance = 0;
int PrevBudget = 0;
int PrevMaintenance = 0;
int Budget_Remaining = 0;
int Budget_Forward = 0;
int Budget_CycleId = 0;
int Budget_Bonus = 0;
int Budget_CycleBonus = 0;
double Budget_Cycle = 3.0 * 60.0;
double Budget_Tick = Budget_Cycle - 0.2;
double Borrow_Rate = 1.5;
double StatRecordDelay = 5.0;
uint welfareMode = WM_Influence;
int welfareInfluence = 0, welfareEnergy = 0, welfareResearch = 0, welfareHWLabor = 0, welfareDefense = 0;
int storedWelfare = 0;
array<int> moneyTypes = array<int>(MoT_COUNT, 0);
void load(SaveFile& msg) {
msg >> Population;
msg >> FTL_Capacity;
msg >> FTL_Stored;
msg >> FTL_Income;
if(msg >= SV_0009)
msg >> FTL_Use;
msg >> Energy_Stored;
msg >> Energy_Income;
msg >> Energy_Use;
if(msg >= SV_0055)
msg >> Energy_Allocated;
msg >> Budget_Total;
msg >> Maintenance;
msg >> PrevBudget;
msg >> PrevMaintenance;
msg >> Budget_Remaining;
msg >> Budget_Forward;
msg >> Budget_CycleId;
msg >> Budget_Cycle;
msg >> Budget_Tick;
msg >> Borrow_Rate;
msg >> Budget_Bonus;
msg >> Budget_CycleBonus;
msg >> StatRecordDelay;
msg >> welfareMode;
msg >> welfareInfluence;
if(msg > SV_0003) {
msg >> welfareEnergy;
msg >> welfareResearch;
msg >> welfareHWLabor;
}
if(msg >= SV_0125)
msg >> welfareDefense;
msg >> storedWelfare;
for(uint i = 0; i < MoT_COUNT-1; ++i)
msg >> moneyTypes[i];
if(msg >= SV_0070)
msg >> moneyTypes[MoT_Vassals];
if(msg >= SV_0067) {
uint cnt = 0;
msg >> cnt;
floatedEnergy.length = cnt;
for(uint i = 0; i < cnt; ++i) {
EnergyFloat flt;
msg >> flt.forEmp;
msg >> flt.amount;
@floatedEnergy[i] = flt;
}
}
}
void save(SaveFile& msg) {
msg << Population;
msg << FTL_Capacity;
msg << FTL_Stored;
msg << FTL_Income;
msg << FTL_Use;
msg << Energy_Stored;
msg << Energy_Income;
msg << Energy_Use;
msg << Energy_Allocated;
msg << Budget_Total;
msg << Maintenance;
msg << PrevBudget;
msg << PrevMaintenance;
msg << Budget_Remaining;
msg << Budget_Forward;
msg << Budget_CycleId;
msg << Budget_Cycle;
msg << Budget_Tick;
msg << Borrow_Rate;
msg << Budget_Bonus;
msg << Budget_CycleBonus;
msg << StatRecordDelay;
msg << welfareMode;
msg << welfareInfluence;
msg << welfareEnergy;
msg << welfareResearch;
msg << welfareHWLabor;
msg << welfareDefense;
msg << storedWelfare;
for(uint i = 0; i < MoT_COUNT; ++i)
msg << moneyTypes[i];
uint cnt = floatedEnergy.length;
msg << cnt;
for(uint i = 0; i < cnt; ++i) {
msg << floatedEnergy[i].forEmp;
msg << floatedEnergy[i].amount;
}
}
//Population
double get_EstTotalPopulation() const {
return max(round(Population / 10.0), 1.0) * 10.0;
}
double get_TotalPopulation() const {
return Population;
}
void modTotalPopulation(Empire& emp, double amount) {
Lock lock(popMutex);
Population += amount;
}
//FTL
double get_FTLIncome() {
return FTL_Income;
}
double get_FTLStored() {
return FTL_Stored;
}
double get_FTLCapacity() {
return FTL_Capacity;
}
double get_FTLUse(const Empire& emp) {
return FTL_Use * emp.FTLCostFactor;
}
double consumeFTL(Empire& emp, double amount, bool consumePartial = true, bool record = true) {
if(!consumePartial && FTL_Stored < amount)
return 0.0;
Lock lock(ftlMutex);
amount = min(FTL_Stored, amount);
FTL_Stored -= amount;
if(amount > 0 && record)
emp.modAttribute(EA_FTLEnergySpent, AC_Add, amount);
return amount;
}
void modFTLCapacity(double amount) {
Lock lock(ftlMutex);
FTL_Capacity += amount;
}
void modFTLStored(double amount, bool obeyMaximum = false) {
Lock lock(ftlMutex);
if(obeyMaximum)
FTL_Stored = clamp(FTL_Stored + amount, 0.0, max(FTL_Capacity, FTL_Stored));
else
FTL_Stored = max(FTL_Stored + amount, 0.0);
}
void modFTLIncome(double amount) {
Lock lock(ftlMutex);
FTL_Income += amount;
}
bool get_FTLShortage(const Empire& emp) const {
return FTL_Stored <= 0.0001 && (FTL_Use * emp.FTLCostFactor) > FTL_Income + 0.0001;
}
bool isFTLShortage(const Empire& emp, double amt) const {
if(FTL_Use + amt <= FTL_Income + 0.0001)
return false;
//Only not a shortage if we can run it for at least a minute
double cons = (FTL_Use + amt) * emp.FTLCostFactor * 60.0;
double have = FTL_Stored + FTL_Income * 60.0;
return cons >= have;
}
bool consumeFTLUse(Empire& emp, double amt) {
Lock lock(ftlMutex);
if(FTL_Use + amt <= FTL_Income + 0.0001) {
FTL_Use += amt;
return true;
}
//Only not a shortage if we can run it for at least a minute
double cons = (FTL_Use + amt) * emp.FTLCostFactor * 60.0;
double have = FTL_Stored + FTL_Income * 60.0;
if(cons >= have)
return false;
FTL_Use += amt;
return true;
}
void modFTLUse(double amount) {
Lock lock(ftlMutex);
FTL_Use += amount;
}
//Energy
double get_EnergyIncome(Empire& emp) {
return Energy_Income * emp.EnergyGenerationFactor;
}
double get_EnergyStored() {
return Energy_Stored;
}
double get_EnergyUse() {
return Energy_Use;
}
double get_EnergyEfficiency(Empire& emp) {
return pow(0.5, max(Energy_Stored + Energy_Allocated - emp.FreeEnergyStorage, 0.0) / config::ENERGY_EFFICIENCY_STEP);
}
bool get_EnergyShortage(Empire& emp) {
return Energy_Stored <= 0.0001 && Energy_Use > (Energy_Income * emp.EnergyGenerationFactor) + 0.0001;
}
bool isEnergyShortage(Empire& emp, double amt) {
if(Energy_Use + amt <= (Energy_Income * emp.EnergyGenerationFactor) + 0.0001)
return false;
//Only not a shortage if we can run it for at least a minute
double cons = (Energy_Use + amt) * 60.0;
double have = Energy_Stored + Energy_Income * 60.0 * emp.EnergyGenerationFactor;
return cons >= have;
}
bool consumeEnergyUse(Empire& emp, double amt) {
Lock lock(energyMutex);
if(Energy_Use + amt <= (Energy_Income * emp.EnergyGenerationFactor) + 0.0001) {
Energy_Use += amt;
return true;
}
//Only not a shortage if we can run it for at least a minute
double cons = (Energy_Use + amt) * 60.0;
double have = Energy_Stored + Energy_Income * 60.0 * emp.EnergyGenerationFactor;
if(cons >= have)
return false;
Energy_Use += amt;
return true;
}
double consumeEnergy(double amount, bool consumePartial = true) {
if(!consumePartial && Energy_Stored < amount)
return 0.0;
Lock lock(energyMutex);
amount = min(Energy_Stored, amount);
Energy_Stored -= amount;
for(uint i = 0, cnt = floatedEnergy.length; i < cnt && amount > 0; ++i) {
auto@ flt = floatedEnergy[i];
double take = min(flt.amount, amount);
if(take != 0) {
flt.amount -= take;
amount -= take;
flt.forEmp.modEnergyAllocated(-take);
if(flt.amount < 0.001) {
floatedEnergy.removeAt(i);
--i; --cnt;
}
}
}
return amount;
}
void addFloatedEnergy(Empire@ forEmp, double value) {
EnergyFloat flt;
@flt.forEmp = forEmp;
flt.amount = value;
floatedEnergy.insertLast(flt);
}
void modEnergyAllocated(double amount) {
Lock lock(energyMutex);
Energy_Allocated += amount;
}
void modEnergyStored(double amount) {
Lock lock(energyMutex);
Energy_Stored = max(Energy_Stored + amount, 0.0);
}
void modEnergyIncome(double amount) {
Lock lock(energyMutex);
Energy_Income += amount;
}
void modEnergyUse(double amount) {
Lock lock(energyMutex);
Energy_Use += amount;
}
//Budget
int get_TotalBudget() {
return Budget_Total;
}
int get_MaintenanceBudget() {
return Maintenance;
}
int get_RemainingBudget() {
return Budget_Remaining;
}
int get_ForwardBudget() {
return Budget_Forward;
}
int get_BonusBudget() {
return Budget_Bonus;
}
double get_BorrowRate() {
return Borrow_Rate;
}
void multBorrowPenalty(double multiply) {
Borrow_Rate = 1.0 + (Borrow_Rate - 1.0) * multiply;
}
double get_BudgetCycle() {
return Budget_Cycle;
}
double get_BudgetTimer() {
return Budget_Tick;
}
float get_DebtFactor() {
auto remaining = Budget_Remaining - Budget_Bonus;
if(remaining >= 0)
return 0.f;
if(Budget_Total < 100)
return float(-remaining) / 100.f;
return float(-remaining) / float(Budget_Total);
}
int getMoneyFromType(uint type) {
if(type < MoT_COUNT)
return moneyTypes[type];
return 0;
}
int get_EstNextBudget() const {
int budget = Budget_Total - Maintenance + Budget_Forward;
budget += min(Budget_Remaining - min(PrevBudget - PrevMaintenance, 0), 0);
return budget;
}
int getEstBudgetConsuming(int amount) const {
int budget = Budget_Total - Maintenance + Budget_Forward;
budget += min(Budget_Remaining - amount - min(PrevBudget - PrevMaintenance, 0), 0);
return budget;
}
void addBonusBudget(Empire& emp, int amount) {
amount = floor(double(amount) * emp.SpecialFundsFactor);
Lock lock(budgetMutex);
Budget_Bonus += amount;
Budget_CycleBonus += amount;
Budget_Remaining += amount;
}
int get_BudgetCycleId() {
return Budget_CycleId;
}
uint get_WelfareMode() const {
return welfareMode;
}
void set_WelfareMode(uint mode) {
welfareMode = mode;
}
int consumeBudget(int amount, bool borrow) {
if(amount == 0)
return Budget_CycleId;
Lock lock(budgetMutex);
if(Budget_Remaining >= amount) {
Budget_Remaining -= amount;
Budget_Bonus = max(Budget_Bonus - amount, 0);
return Budget_CycleId;
}
else {
if(!borrow)
return -1;
int borrowAmount = amount;
if(Budget_Remaining > 0)
borrowAmount -= Budget_Remaining;
int borrowCost = ceil(double(borrowAmount) * Borrow_Rate);
if(getEstBudgetConsuming(borrowCost) < borrowCost)
return -1;
Budget_Remaining -= amount;
Budget_Bonus = max(Budget_Bonus - amount, 0);
Budget_Forward -= (borrowCost - borrowAmount);
return Budget_CycleId;
}
}
int lowerBudget(int amount) {
Lock lock(budgetMutex);
if(Budget_Remaining >= amount) {
Budget_Remaining -= amount;
Budget_Bonus = max(Budget_Bonus - amount, 0);
return Budget_CycleId;
}
else {
int borrowAmount = amount;
if(Budget_Remaining > 0)
borrowAmount -= Budget_Remaining;
int borrowCost = ceil(double(borrowAmount) * Borrow_Rate);
Budget_Remaining -= amount;
Budget_Bonus = max(Budget_Bonus - amount, 0);
Budget_Forward -= (borrowCost - borrowAmount);
return Budget_CycleId;
}
}
bool canBorrow(int amount) const {
if(amount == 0)
return true;
amount = ceil(double(amount) * Borrow_Rate);
return getEstBudgetConsuming(amount) >= amount;
}
bool canPay(int amount) const {
if(amount == 0)
return true;
if(amount <= Budget_Remaining)
return true;
return canBorrow(amount - Budget_Remaining);
}
void refundBudget(int amount, int cycleId) {
Lock lock(budgetMutex);
if(cycleId != Budget_CycleId)
return;
int refundedBorrow = min(amount, -Budget_Remaining);
Budget_Remaining += amount;
Budget_Bonus = min(Budget_Bonus + amount, Budget_CycleBonus);
if(refundedBorrow > 0)
Budget_Forward += round(double(refundedBorrow) * (Borrow_Rate - 1.0));
}
void modMaintenance(int amount, uint type = 0) {
Lock lock(budgetMutex);
Maintenance += amount;
if(type < MoT_COUNT)
moneyTypes[type] -= amount;
}
void modTotalBudget(Empire& emp, int amount, uint type = 0) {
Lock lock(budgetMutex);
Budget_Total += amount;
if(type < MoT_COUNT)
moneyTypes[type] += amount;
}
void modForwardBudget(int amount) {
Lock lock(budgetMutex);
Budget_Forward += amount;
}
void modRemainingBudget(int amount) {
Lock lock(budgetMutex);
Budget_Remaining += amount;
}
void resetBudget(Empire& emp) {
if(Budget_CycleId != 0) {
int remaining = Budget_Remaining + storedWelfare - Budget_Bonus;
//New values for each welfare type
int nwf_influence = 0, nwf_energy = 0, nwf_research = 0, nwf_hw_labor = 0, nwf_defense = 0;
if(remaining > 0) {
switch(welfareMode) {
case WM_Influence:
{
double fact = MONEY_PER_INFLUENCE / emp.WelfareEfficiency;
nwf_influence = floor(double(remaining) / fact);
storedWelfare = remaining - (nwf_influence * fact);
} break;
case WM_Energy:
{
double fact = MONEY_PER_ENERGY / emp.WelfareEfficiency;
nwf_energy = floor(double(remaining) / fact);
storedWelfare = remaining - (nwf_energy * fact);
} break;
case WM_Research:
{
double fact = MONEY_PER_RESEARCH / emp.WelfareEfficiency;
nwf_research = floor(double(remaining) / fact);
storedWelfare = remaining - (nwf_research * fact);
} break;
case WM_HW_Labor:
{
double fact = MONEY_PER_HW_LABOR / emp.WelfareEfficiency;
nwf_hw_labor = floor(double(remaining) / fact);
storedWelfare = remaining - (nwf_hw_labor * fact);
} break;
case WM_Defense:
{
double fact = MONEY_PER_DEFENSE / emp.WelfareEfficiency;
nwf_defense = floor(double(remaining) / fact);
storedWelfare = remaining - (nwf_defense * fact);
} break;
default:
storedWelfare = remaining;
break;
}
}
else {
storedWelfare = 0;
}
if(nwf_influence != welfareInfluence) {
emp.modInfluenceIncome(nwf_influence - welfareInfluence);
welfareInfluence = nwf_influence;
}
if(nwf_energy != welfareEnergy) {
emp.modEnergyIncome(double(nwf_energy - welfareEnergy) * TILE_ENERGY_RATE);
welfareEnergy = nwf_energy;
}
if(nwf_research != welfareResearch) {
emp.modResearchRate(double(nwf_research - welfareResearch) * TILE_RESEARCH_RATE);
welfareResearch = nwf_research;
}
if(nwf_defense != welfareDefense) {
emp.modDefenseRate(double(nwf_defense - welfareDefense) * (1.0 / 60.0));
welfareDefense = nwf_defense;
}
if(nwf_hw_labor != welfareHWLabor) {
Object@ home = emp.Homeworld;
if(home is null) {
@home = emp.HomeObj;
if(home !is null && !home.hasConstruction)
@home = null;
}
if(home !is null && home.valid) {
if(home.owner !is emp) {
//In case we lose access to our homeworld, reset the labor being sent
nwf_hw_labor = 0;
welfareMode = WM_Influence;
}
home.modLaborIncome(double(nwf_hw_labor - welfareHWLabor) * TILE_LABOR_RATE);
}
welfareHWLabor = nwf_hw_labor;
}
}
Budget_CycleBonus = Budget_Bonus;
Budget_Remaining = EstNextBudget + Budget_Bonus;
PrevBudget = Budget_Total;
PrevMaintenance = Maintenance;
Budget_Forward = 0;
Budget_Tick = 0;
++Budget_CycleId;
}
void resourceTick(Empire& emp, double time) {
StatRecordDelay -= time;
bool recordStats = StatRecordDelay <= 0;
if(recordStats)
StatRecordDelay += 5.0;
Object@ home = emp.HomeObj;
if(home is null || !home.valid || home.owner !is emp)
@emp.HomeObj = null;
//Handle FTL income rate
{
Lock lock(ftlMutex);
FTL_Stored = clamp(FTL_Stored + time * (FTL_Income - FTL_Use * emp.FTLCostFactor), 0, max(FTL_Capacity, FTL_Stored));
if(FTL_Use > 0) {
double usedFTL = time * min(FTL_Use * emp.FTLCostFactor, FTL_Stored + FTL_Income);
if(usedFTL > 0)
emp.modAttribute(EA_FTLEnergySpent, AC_Add, usedFTL);
}
if(recordStats)
emp.recordStat(stat::FTL, float(FTL_Stored));
}
//Handle Energy income rate
{
Lock lock(energyMutex);
double netEnergy = ((Energy_Income * emp.EnergyGenerationFactor) - Energy_Use);
if(netEnergy > 0)
netEnergy *= emp.EnergyEfficiency;
Energy_Stored = max(Energy_Stored + time * netEnergy, 0.0);
if(recordStats)
emp.recordStat(stat::EnergyIncome, netEnergy);
}
//Handle budget ticks
{
Lock lock(budgetMutex);
if(Budget_Tick >= Budget_Cycle) {
double remainder = Budget_Tick - Budget_Cycle;
resetBudget(emp);
Budget_Tick += remainder;
}
else
Budget_Tick += time;
if(recordStats) {
emp.recordStat(stat::Budget, float(Budget_Total));
emp.recordStat(stat::NetBudget, float(EstNextBudget));
}
}
//Handle extra stats
if(recordStats) {
emp.recordStat(stat::Points, emp.points.value);
emp.recordStat(stat::Military, float(sqr(emp.TotalMilitary) * 0.001));
}
}
//Networking
void writeResources(Message& msg) {
msg << float(Population);
msg << float(FTL_Capacity);
msg << float(FTL_Stored);
msg << float(FTL_Income);
msg << float(FTL_Use);
msg << float(Energy_Stored);
msg << float(Energy_Income);
msg << float(Energy_Use);
msg << float(Energy_Allocated);
msg << Budget_Total;
msg << Maintenance;
msg << PrevMaintenance;
msg << PrevBudget;
msg << Budget_Remaining;
msg << Budget_Forward;
msg << Budget_Bonus;
msg << Budget_CycleId;
msg << float(Budget_Cycle);
msg << float(Budget_Tick);
msg << float(Borrow_Rate);
for(uint i = 0; i < MoT_COUNT; ++i)
msg.writeSignedSmall(moneyTypes[i]);
msg.writeLimited(welfareMode, WM_COUNT-1);
}
};
File diff suppressed because it is too large Load Diff
+354
View File
@@ -0,0 +1,354 @@
import statuses;
import saving;
tidy class Statuses : Component_Statuses, Savable {
array<Status@> statuses;
array<StatusInstance@> instances;
int nextInstanceId = 1;
bool delta = false;
void save(SaveFile& file) {
file << nextInstanceId;
uint cnt = statuses.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << statuses[i];
cnt = instances.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
file << statuses.find(instances[i].status);
file << instances[i];
}
}
void load(SaveFile& file) {
if(file < SV_0013)
return;
file >> nextInstanceId;
uint cnt = 0;
file >> cnt;
statuses.length = cnt;
for(uint i = 0; i < cnt; ++i) {
@statuses[i] = Status();
file >> statuses[i];
}
file >> cnt;
instances.length = cnt;
for(uint i = 0; i < cnt; ++i) {
@instances[i] = StatusInstance();
int index = 0;
file >> index;
@instances[i].status = statuses[index];
file >> instances[i];
}
}
void getStatusEffects(Player& pl, Object& obj) {
Empire@ plEmp = pl.emp;
for(uint i = 0, cnt = statuses.length; i < cnt; ++i) {
if(!statuses[i].isVisibleTo(obj, plEmp))
continue;
yield(statuses[i]);
}
}
uint get_statusEffectCount() {
return statuses.length;
}
uint get_statusEffectType(uint index) {
if(index >= statuses.length)
return uint(-1);
return statuses[index].type.id;
}
uint get_statusEffectStacks(uint index) {
if(index >= statuses.length)
return 0;
return statuses[index].stacks;
}
Object@ get_statusEffectOriginObject(uint index) {
if(index >= statuses.length)
return null;
return statuses[index].originObject;
}
Empire@ get_statusEffectOriginEmpire(uint index) {
if(index >= statuses.length)
return null;
return statuses[index].originEmpire;
}
uint getStatusStackCountAny(uint typeId) {
uint count = 0;
for(uint i = 0, cnt = statuses.length; i < cnt; ++i) {
if(statuses[i].type.id == typeId)
count += statuses[i].stacks;
}
return count;
}
uint getStatusStackCount(uint typeId, Object@ originObject = null, Empire@ originEmpire = null) {
uint count = 0;
for(uint i = 0, cnt = statuses.length; i < cnt; ++i) {
if(statuses[i].type.id == typeId && statuses[i].originObject is originObject && statuses[i].originEmpire is originEmpire)
count += statuses[i].stacks;
}
return count;
}
uint get_statusInstanceCount() {
return instances.length;
}
uint get_statusInstanceType(uint index) {
if(index >= instances.length)
return uint(-1);
return instances[index].status.type.id;
}
int get_statusInstanceId(uint index) {
if(index >= instances.length)
return -1;
return instances[index].id;
}
bool hasStatusEffect(uint typeId) {
for(uint i = 0, cnt = statuses.length; i < cnt; ++i) {
if(statuses[i].type.id == typeId)
return true;
}
return false;
}
int addStatus(Object& obj, double timer, uint typeId, Empire@ boundEmpire = null, Region@ boundRegion = null, Empire@ originEmpire = null, Object@ originObject = null) {
const StatusType@ type = getStatusType(typeId);
if(type is null)
return -1;
Status@ status;
if(type.collapses) {
for(uint i = 0, cnt = statuses.length; i < cnt; ++i) {
auto@ cur = statuses[i];
if(cur.type is type && originEmpire is cur.originEmpire && originObject is cur.originObject) {
@status = cur;
break;
}
}
}
if(status is null) {
@status = Status(type);
@status.originEmpire = originEmpire;
@status.originObject = originObject;
status.create(obj);
statuses.insertLast(status);
}
auto@ instance = status.instance(obj);
instance.id = nextInstanceId++;
instance.timer = timer;
@instance.boundEmpire = boundEmpire;
@instance.boundRegion = boundRegion;
instances.insertLast(instance);
delta = true;
return instance.id;
}
void addStatus(Object& obj, uint typeId, double timer = -1.0, Empire@ boundEmpire = null, Region@ boundRegion = null, Empire@ originEmpire = null, Object@ originObject = null) {
addStatus(obj, timer, typeId, boundEmpire, boundRegion, originEmpire, originObject);
}
void addRandomCondition(Object& obj) {
if(!obj.isPlanet)
return;
auto@ type = getRandomCondition(cast<Planet>(obj));
if(type !is null)
addStatus(obj, type.id);
}
void removeStatus(Object& obj, int id) {
StatusInstance@ instance;
for(uint i = 0, cnt = instances.length; i < cnt; ++i) {
if(instances[i].id == id) {
@instance = instances[i];
instances.removeAt(i);
break;
}
}
if(instance is null)
return;
instance.remove(obj);
if(instance.status.stacks <= 0) {
statuses.remove(instance.status);
instance.status.destroy(obj);
}
delta = true;
}
bool isStatusInstanceActive(int id) {
for(uint i = 0, cnt = instances.length; i < cnt; ++i) {
if(instances[i].id == id)
return true;
}
return false;
}
void removeStatusInstanceOfType(Object& obj, uint typeId) {
StatusInstance@ instance;
for(uint i = 0, cnt = instances.length; i < cnt; ++i) {
auto@ inst = instances[i];
if(inst.boundEmpire !is null)
continue;
if(inst.boundRegion !is null)
continue;
if(inst.timer >= 0)
continue;
if(inst.status.type.id == typeId) {
@instance = inst;
instances.removeAt(i);
break;
}
}
if(instance is null)
return;
instance.remove(obj);
if(instance.status.stacks <= 0) {
statuses.remove(instance.status);
instance.status.destroy(obj);
}
delta = true;
}
void removeStatusType(Object& obj, uint typeId) {
uint index = uint(-1);
for(uint i = 0, cnt = statuses.length; i < cnt; ++i) {
auto@ cur = statuses[i];
if(cur.type.id == typeId) {
index = i;
break;
}
}
if(index == uint(-1))
return;
removeStatusTypeByIndex(obj, index);
}
void removeStatusTypeByIndex(Object& obj, uint index) {
Status@ status;
if(index < statuses.length)
@status = statuses[index];
if(status is null)
return;
for(int j = instances.length - 1; j >= 0; --j) {
if(instances[j].status is status) {
status.remove(obj, instances[j]);
instances.removeAt(j);
}
}
statuses.removeAt(index);
status.destroy(obj);
delta = true;
}
void changeStatusOwner(Object& obj, Empire@ prevOwner, Empire@ newOwner) {
for(int i = instances.length - 1; i >= 0; --i) {
auto@ instance = instances[i];
if(instance.boundEmpire !is null && instance.boundEmpire is prevOwner) {
instance.remove(obj);
instances.removeAt(i);
}
}
for(int i = statuses.length - 1; i >= 0; --i) {
auto@ status = statuses[i];
if(status.stacks <= 0 || !status.ownerChange(obj, prevOwner, newOwner))
removeStatusTypeByIndex(obj, i);
}
}
void changeStatusRegion(Object& obj, Region@ prevRegion, Region@ newRegion) {
for(int i = instances.length - 1; i >= 0; --i) {
auto@ instance = instances[i];
if(instance.boundRegion !is null && instance.boundRegion is prevRegion) {
instance.remove(obj);
instances.removeAt(i);
}
}
for(int i = statuses.length - 1; i >= 0; --i) {
auto@ status = statuses[i];
if(status.stacks <= 0 || !status.regionChange(obj, prevRegion, newRegion))
removeStatusTypeByIndex(obj, i);
}
}
void removeRegionBoundStatus(Object& obj, Region@ region, uint typeId, double timer = -1.0) {
for(int i = instances.length - 1; i >= 0; --i) {
auto@ instance = instances[i];
if(instance.boundRegion is region && instance.status.type.id == typeId
&& abs(instance.timer - timer) < 0.9) {
instances.removeAt(i);
instance.remove(obj);
for(int j = statuses.length - 1; j >= 0; --j) {
auto@ status = statuses[j];
if(status.stacks <= 0)
removeStatusTypeByIndex(obj, j);
}
break;
}
}
}
void statusTick(Object& obj, double time) {
for(int i = instances.length - 1; i >= 0; --i) {
auto@ instance = instances[i];
if(!instance.tick(obj, time))
instances.removeAt(i);
}
for(int i = statuses.length - 1; i >= 0; --i) {
auto@ status = statuses[i];
if(status.stacks <= 0 || !status.tick(obj, time))
removeStatusTypeByIndex(obj, i);
}
}
void destroyStatus(Object& obj) {
for(int i = statuses.length - 1; i >= 0; --i) {
auto@ status = statuses[i];
status.objectDestroy(obj);
}
for(uint i = 0; i < instances.length; ++i) {
auto@ instance = instances[i];
instance.remove(obj);
if(instance.status.stacks <= 0) {
statuses.remove(instance.status);
instance.status.destroy(obj);
}
}
statuses.length = 0;
instances.length = 0;
}
void writeStatuses(Message& msg) const {
uint cnt = statuses.length;
msg.writeSmall(cnt);
for(uint i = 0; i < cnt; ++i)
msg << statuses[i];
}
bool writeStatusDelta(Message& msg) {
if(delta) {
delta = false;
msg.write1();
writeStatuses(msg);
return true;
}
return false;
}
};
+360
View File
@@ -0,0 +1,360 @@
import traits;
import attitudes;
import attributes;
import saving;
tidy class TraitData {
const Trait@ trait;
array<any> data;
};
tidy class Traits : Component_Traits, Savable {
array<TraitData@> traits;
array<bool> hasTraits(getTraitCount(), false);
array<Attitude@> attitudes;
ReadWriteMutex attMtx;
bool hasTrait(uint id) {
if(id >= hasTraits.length)
return false;
return hasTraits[id];
}
uint get_traitCount() const {
return traits.length;
}
uint getTraitType(uint index) const {
if(index >= traits.length)
return uint(-1);
return traits[index].trait.id;
}
void addTrait(Empire& emp, uint id, bool doPreInit = false) {
auto@ trait = getTrait(id);
if(trait is null)
throw("Invalid trait.");
TraitData dat;
@dat.trait = trait;
traits.insertLast(dat);
hasTraits[trait.id] = true;
if(doPreInit)
dat.trait.preInit(emp, dat.data);
}
void replaceTrait(Empire& emp, uint fromId, uint toId, bool doPreInit = true) {
auto@ fromType = getTrait(fromId);
auto@ toType = getTrait(toId);
if(fromType is null || toType is null)
return;
for(uint i = 0, cnt = traits.length; i < cnt; ++i) {
if(traits[i].trait is fromType) {
@traits[i].trait = toType;
if(doPreInit)
toType.preInit(emp, traits[i].data);
hasTraits[fromType.id] = false;
hasTraits[toType.id] = true;
break;
}
}
}
void preInitTraits(Empire& emp) {
for(uint i = 0, cnt = traits.length; i < cnt; ++i)
traits[i].trait.preInit(emp, traits[i].data);
}
void initTraits(Empire& emp) {
for(uint i = 0, cnt = traits.length; i < cnt; ++i)
traits[i].trait.init(emp, traits[i].data);
}
void postInitTraits(Empire& emp) {
for(uint i = 0, cnt = traits.length; i < cnt; ++i)
traits[i].trait.postInit(emp, traits[i].data);
}
void traitsTick(Empire& emp, double time) {
for(uint i = 0, cnt = traits.length; i < cnt; ++i)
traits[i].trait.tick(emp, traits[i].data, time);
{
WriteLock lck(attMtx);
for(uint i = 0, cnt = attitudes.length; i < cnt; ++i)
attitudes[i].tick(emp, time);
}
}
uint get_attitudeCount() {
return attitudes.length;
}
void getAttitudes() {
ReadLock lck(attMtx);
for(uint i = 0, cnt = attitudes.length; i < cnt; ++i)
yield(attitudes[i]);
}
bool hasAttitude(uint id) {
ReadLock lck(attMtx);
for(uint i = 0, cnt = attitudes.length; i < cnt; ++i)
if(attitudes[i].type.id == id)
return true;
return false;
}
int getNextAttitudeCost(Empire& emp) {
if(emp.FreeAttitudes > 0)
return 0;
return config::ATTITUDE_BASE_COST + config::ATTITUDE_INC_COST * max(int(attitudes.length)-1, 0);
}
void takeAttitude(Empire& emp, uint id) {
WriteLock lck(attMtx);
if(hasAttitude(id))
return;
auto@ type = getAttitudeType(id);
if(type is null)
return;
if(!type.canTake(emp))
return;
if(emp.FreeAttitudes > 0) {
emp.modAttribute(EA_FreeAttitudes, AC_Add, -1.0);
}
else {
int cost = getNextAttitudeCost(emp);
if(emp.Influence < cost)
return;
if(!emp.consumeInfluence(cost))
return;
}
forceAttitude(emp, id);
if(emp.AttitudeStartLevel > 0)
levelAttitude(emp, id, int(emp.AttitudeStartLevel));
}
void forceAttitude(Empire& emp, uint id) {
WriteLock lck(attMtx);
if(hasAttitude(id))
return;
auto@ type = getAttitudeType(id);
if(type is null)
return;
Attitude att;
@att.type = type;
attitudes.insertLast(att);
for(uint i = 0, cnt = attitudes.length; i < cnt; ++i)
attitudes[i].delta = true;
att.start(emp);
}
void discardAttitude(Empire& emp, uint id) {
WriteLock lck(attMtx);
Attitude@ att;
for(uint i = 0, cnt = attitudes.length; i < cnt; ++i) {
if(attitudes[i].type.id == id) {
@att = attitudes[i];
break;
}
}
if(att is null)
return;
int cost = att.getDiscardCost(emp);
if(emp.Influence < cost)
return;
if(!emp.consumeInfluence(cost))
return;
forceDiscardAttitude(emp, id);
}
uint getAttitudeLevel(uint id) const {
ReadLock lck(attMtx);
Attitude@ att;
for(uint i = 0, cnt = attitudes.length; i < cnt; ++i) {
if(attitudes[i].type.id == id)
return attitudes[i].level;
}
return 0;
}
void forceDiscardAttitude(Empire& emp, uint id) {
WriteLock lck(attMtx);
Attitude@ att;
for(uint i = 0, cnt = attitudes.length; i < cnt; ++i) {
if(attitudes[i].type.id == id) {
@att = attitudes[i];
break;
}
}
if(att is null)
return;
att.end(emp);
attitudes.remove(att);
for(uint i = 0, cnt = attitudes.length; i < cnt; ++i)
attitudes[i].delta = true;
}
void levelAttitude(Empire& emp, uint id, int levels) {
WriteLock lck(attMtx);
Attitude@ att;
for(uint i = 0, cnt = attitudes.length; i < cnt; ++i) {
if(attitudes[i].type.id == id) {
@att = attitudes[i];
break;
}
}
if(att is null)
return;
uint newLevel = clamp(int(att.level) + levels, 0, att.type.levels.length);
if(newLevel == att.level)
return;
if(newLevel == 0)
att.progress = 0;
else
att.progress = att.levels[newLevel].threshold;
att.delta = true;
att.checkProgress(emp);
}
void progressAttitude(Empire& emp, uint id, double progress, double pct) {
WriteLock lck(attMtx);
Attitude@ att;
for(uint i = 0, cnt = attitudes.length; i < cnt; ++i) {
if(attitudes[i].type.id == id) {
@att = attitudes[i];
break;
}
}
if(att is null)
return;
uint curLevel = att.level;
uint nextLevel = att.nextLevel;
if(curLevel == nextLevel)
return;
double prevThres = 0;
if(curLevel != 0)
prevThres = att.levels[curLevel].threshold;
double nextThres = att.levels[nextLevel].threshold;
att.progress += progress + (nextThres - prevThres) * pct;
att.delta = true;
att.checkProgress(emp);
}
void resetAttitude(Empire& emp, uint id) {
WriteLock lck(attMtx);
Attitude@ att;
for(uint i = 0, cnt = attitudes.length; i < cnt; ++i) {
if(attitudes[i].type.id == id) {
@att = attitudes[i];
break;
}
}
if(att is null)
return;
att.progress = 0.0;
att.delta = true;
att.checkProgress(emp);
}
uint getLevelAttitudeCount(uint level) {
ReadLock lck(attMtx);
uint count = 0;
for(uint i = 0, cnt = attitudes.length; i < cnt; ++i) {
if(attitudes[i].level >= level)
count += 1;
}
return count;
}
void save(SaveFile& file) {
uint cnt = traits.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
file.writeIdentifier(SI_Trait, traits[i].trait.id);
traits[i].trait.save(traits[i].data, file);
}
cnt = attitudes.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << attitudes[i];
}
void load(SaveFile& file) {
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ trait = getTrait(file.readIdentifier(SI_Trait));
if(trait !is null) {
TraitData dat;
@dat.trait = trait;
trait.load(dat.data, file);
hasTraits[trait.id] = true;
traits.insertLast(dat);
}
}
if(file >= SV_0147) {
file >> cnt;
attitudes.length = cnt;
for(uint i = 0; i < cnt; ++i) {
Attitude att;
file >> att;
@attitudes[i] = att;
}
}
}
void writeTraits(Message& msg) {
uint cnt = traits.length;
msg.writeSmall(cnt);
for(uint i = 0; i < cnt; ++i)
msg.writeSmall(traits[i].trait.id);
}
void writeAttitudes(Message& msg, bool initial) {
uint cnt = attitudes.length;
msg.writeSmall(cnt);
for(uint i = 0; i < cnt; ++i) {
if(initial || attitudes[i].delta) {
msg.write1();
msg << attitudes[i];
if(!initial)
attitudes[i].delta = false;
}
else {
msg.write0();
}
}
}
};