Open source Star Ruler 2 source code!
This commit is contained in:
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
};
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user