Open source Star Ruler 2 source code!
This commit is contained in:
@@ -0,0 +1,331 @@
|
||||
import resources;
|
||||
import regions.regions;
|
||||
import saving;
|
||||
import anomalies;
|
||||
import bool getCheatsEverOn() from "cheats";
|
||||
|
||||
Anomaly@ createAnomaly(const vec3d& position, uint typeID) {
|
||||
ObjectDesc desc;
|
||||
desc.type = OT_Anomaly;
|
||||
desc.radius = 15.0;
|
||||
desc.position = position;
|
||||
desc.name = getAnomalyType(typeID).name;
|
||||
desc.flags |= objNoDamage;
|
||||
|
||||
Anomaly@ anomaly = Anomaly(desc);
|
||||
anomaly.rotation = quaterniond_fromAxisAngle(random3d(1.0), randomd(-pi,pi));
|
||||
anomaly.setup(typeID);
|
||||
return anomaly;
|
||||
}
|
||||
|
||||
tidy class AnomalyScript {
|
||||
const AnomalyType@ type;
|
||||
const AnomalyState@ state;
|
||||
array<const AnomalyOption@> options;
|
||||
array<float> progresses(getEmpireCount(), 0.0);
|
||||
bool delta = false;
|
||||
bool choiceDelta = false;
|
||||
StrategicIconNode@ icon;
|
||||
|
||||
float get_progress(Player& player, const Anomaly& obj) {
|
||||
if(player == SERVER_PLAYER)
|
||||
return 1.0;
|
||||
else if(player.emp is null || !player.emp.valid)
|
||||
return 0.0;
|
||||
else
|
||||
return progresses[player.emp.index];
|
||||
}
|
||||
|
||||
float getEmpireProgress(Empire@ emp) {
|
||||
if(emp is null || !emp.valid)
|
||||
return 0.0;
|
||||
else
|
||||
return progresses[emp.index];
|
||||
}
|
||||
|
||||
string get_narrative(Player& player, const Anomaly& obj) {
|
||||
if(get_progress(player, obj) < 1.f)
|
||||
return type.desc;
|
||||
else if(state !is null && state.narrative.length > 0)
|
||||
return state.narrative;
|
||||
else
|
||||
return type.narrative;
|
||||
}
|
||||
|
||||
uint get_anomalyType(Player& player, const Anomaly& obj) {
|
||||
if(player != SERVER_PLAYER && (get_progress(player, obj) < 1.f || type is null))
|
||||
return 0;
|
||||
else
|
||||
return type.id;
|
||||
}
|
||||
|
||||
bool get_isOptionSafe(uint index) {
|
||||
if(index < options.length)
|
||||
return options[index].isSafe;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
uint getOptionCount() const {
|
||||
return options.length;
|
||||
}
|
||||
|
||||
uint get_optionCount(Player& player, const Anomaly& obj) {
|
||||
if(get_progress(player, obj) < 1.f)
|
||||
return 0;
|
||||
else
|
||||
return options.length;
|
||||
}
|
||||
|
||||
uint get_option(Player& player, const Anomaly& obj, uint index) {
|
||||
if(get_progress(player, obj) < 1.f || index >= options.length)
|
||||
return 0;
|
||||
else
|
||||
return options[index].id;
|
||||
}
|
||||
|
||||
void clearOptions() {
|
||||
options.length = 0;
|
||||
}
|
||||
|
||||
void addOption(uint id) {
|
||||
options.insertLast(type.options[id]);
|
||||
}
|
||||
|
||||
string get_model(Player& player, const Anomaly& obj) {
|
||||
if(type is null)
|
||||
return "";
|
||||
else if(get_progress(player, obj) >= 1.f && state !is null && state.modelName.length > 0)
|
||||
return state.modelName;
|
||||
else
|
||||
return type.modelName;
|
||||
}
|
||||
|
||||
string get_material(Player& player, const Anomaly& obj) {
|
||||
if(type is null)
|
||||
return "";
|
||||
else if(get_progress(player, obj) >= 1.f && state !is null && state.matName.length > 0)
|
||||
return state.matName;
|
||||
else
|
||||
return type.matName;
|
||||
}
|
||||
|
||||
void setup(Anomaly& obj, uint typeID) {
|
||||
@type = getAnomalyType(typeID);
|
||||
if(type is null)
|
||||
@type = getAnomalyType(0);
|
||||
|
||||
auto@ state = type.getState();
|
||||
progressToState(obj, state.id);
|
||||
|
||||
makeMesh(obj);
|
||||
}
|
||||
|
||||
void progressToState(Anomaly& obj, uint stateID) {
|
||||
@state = type.states[stateID];
|
||||
if(state !is null) {
|
||||
for(uint i = 0; i < state.options.length; ++i) {
|
||||
auto option = state.options[i];
|
||||
if(option.chance < 1.f && randomd() > option.chance)
|
||||
continue;
|
||||
float chance = state.option_chances[i];
|
||||
if(chance < 1.f && randomd() > chance)
|
||||
continue;
|
||||
options.insertLast(option);
|
||||
}
|
||||
}
|
||||
if(options.length == 0) {
|
||||
playParticleSystem("AnomalyCollapse", obj.position, obj.rotation, obj.radius, obj.visibleMask);
|
||||
obj.destroy();
|
||||
}
|
||||
choiceDelta = true;
|
||||
}
|
||||
|
||||
void choose(Anomaly& obj, Empire@ emp, uint option, Object@ target = null) {
|
||||
if(option >= options.length || getEmpireProgress(emp) < 1.f)
|
||||
return;
|
||||
|
||||
auto@ opt = options[option];
|
||||
|
||||
Targets@ targs;
|
||||
if(opt.targets.length != 0) {
|
||||
@targs = Targets(opt.targets);
|
||||
@targs.fill(0).obj = target;
|
||||
}
|
||||
|
||||
options.length = 0;
|
||||
opt.choose(obj, emp, targs);
|
||||
|
||||
if(options.length == 0) {
|
||||
playParticleSystem("AnomalyCollapse", obj.position, obj.rotation, obj.radius, obj.visibleMask);
|
||||
obj.destroy();
|
||||
}
|
||||
choiceDelta = true;
|
||||
}
|
||||
|
||||
void choose(Player& player, Anomaly& obj, uint option, Object@ target = null) {
|
||||
choose(obj, player.emp, option, target);
|
||||
}
|
||||
|
||||
void addProgress(Anomaly& obj, Empire@ emp, float amount) {
|
||||
uint index = uint(emp.index);
|
||||
if(index < progresses.length) {
|
||||
float p = progresses[index];
|
||||
if(p < 1.f) {
|
||||
p += amount / type.scanTime;
|
||||
delta = true;
|
||||
if(p >= 1.f) {
|
||||
emp.notifyAnomaly(obj);
|
||||
progresses[index] = 1.f;
|
||||
|
||||
if(emp.valid && !getCheatsEverOn()) {
|
||||
if(emp is playerEmpire) {
|
||||
unlockAchievement("ACH_SCAN_ANOMALY");
|
||||
modStat("STAT_ANOMS", 1);
|
||||
}
|
||||
|
||||
if(mpServer && emp.player !is null)
|
||||
clientAchievement(emp.player, "ACH_SCAN_ANOMALY");
|
||||
}
|
||||
}
|
||||
else {
|
||||
progresses[index] = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void load(Anomaly& obj, SaveFile& file) {
|
||||
loadObjectStates(obj, file);
|
||||
@type = getAnomalyType(file.readIdentifier(SI_AnomalyType));
|
||||
if(type is null)
|
||||
setup(obj, getDistributedAnomalyType().id);
|
||||
else
|
||||
makeMesh(obj);
|
||||
|
||||
uint count = 0;
|
||||
file >> count;
|
||||
options.length = count;
|
||||
for(uint i = 0; i < count; ++i) {
|
||||
uint id = 0;
|
||||
file >> id;
|
||||
@options[i] = type.options[id % type.options.length];
|
||||
}
|
||||
|
||||
for(uint i = 0; i < progresses.length; ++i)
|
||||
file >> progresses[i];
|
||||
|
||||
uint stateId = 0;
|
||||
file >> stateId;
|
||||
if(stateId < type.states.length)
|
||||
@state = type.states[stateId];
|
||||
}
|
||||
|
||||
void save(Anomaly& obj, SaveFile& file) {
|
||||
saveObjectStates(obj, file);
|
||||
file.writeIdentifier(SI_AnomalyType, type.id);
|
||||
file << uint(options.length);
|
||||
for(uint i = 0; i < options.length; ++i)
|
||||
file << options[i].id;
|
||||
for(uint i = 0; i < progresses.length; ++i)
|
||||
file << progresses[i];
|
||||
if(state is null) {
|
||||
uint id = uint(-1);
|
||||
file << id;
|
||||
}
|
||||
else
|
||||
file << state.id;
|
||||
}
|
||||
|
||||
void postInit(Anomaly& obj) {
|
||||
updateRegion(obj);
|
||||
}
|
||||
|
||||
void destroy(Anomaly& obj) {
|
||||
if(obj.region !is null)
|
||||
obj.region.removeStrategicIcon(-1, icon);
|
||||
icon.markForDeletion();
|
||||
leaveRegion(obj);
|
||||
}
|
||||
|
||||
void makeMesh(Anomaly& obj) {
|
||||
MeshDesc mesh;
|
||||
if(type !is null) {
|
||||
@mesh.model = getModel(type.modelName);
|
||||
@mesh.material = getMaterial(type.matName);
|
||||
}
|
||||
else {
|
||||
@mesh.model = model::Debris;
|
||||
@mesh.material = material::Asteroid;
|
||||
}
|
||||
mesh.memorable = true;
|
||||
bindMesh(obj, mesh);
|
||||
|
||||
@icon = StrategicIconNode();
|
||||
icon.establish(obj, 0.0225, spritesheet::AnomalyIcons, 0);
|
||||
icon.memorable = true;
|
||||
|
||||
if(obj.region !is null) {
|
||||
obj.region.addStrategicIcon(-1, obj, icon);
|
||||
Node@ node = obj.getNode();
|
||||
if(node !is null)
|
||||
node.hintParentObject(obj.region, false);
|
||||
}
|
||||
}
|
||||
|
||||
double tick(Anomaly& obj, double time) {
|
||||
if(icon !is null)
|
||||
icon.visible = obj.isVisibleTo(playerEmpire);
|
||||
Region@ reg = obj.region;
|
||||
if(reg !is null)
|
||||
obj.donatedVision |= reg.DonateVisionMask;
|
||||
return 0.2;
|
||||
}
|
||||
|
||||
void writeProgress(Message& msg) {
|
||||
for(uint i = 0; i < progresses.length; ++i) {
|
||||
float p = progresses[i];
|
||||
if(p <= 0.0) {
|
||||
msg.write0();
|
||||
}
|
||||
else {
|
||||
msg.write1();
|
||||
msg.writeFixed(p, 0.0, 1.0, 7);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void writeChoices(Message& msg) {
|
||||
msg.writeSmall(type.id);
|
||||
msg.writeSmall(state.id);
|
||||
msg.writeSmall(options.length);
|
||||
for(uint i = 0; i < options.length; ++i)
|
||||
msg.writeLimited(options[i].id, type.options.length);
|
||||
}
|
||||
|
||||
void syncInitial(const Anomaly& obj, Message& msg) {
|
||||
writeChoices(msg);
|
||||
writeProgress(msg);
|
||||
}
|
||||
|
||||
bool syncDelta(const Anomaly& obj, Message& msg) {
|
||||
if(!delta && !choiceDelta)
|
||||
return false;
|
||||
writeProgress(msg);
|
||||
if(choiceDelta) {
|
||||
msg.write1();
|
||||
writeChoices(msg);
|
||||
}
|
||||
else {
|
||||
msg.write0();
|
||||
}
|
||||
delta = false;
|
||||
choiceDelta = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void syncDetailed(const Anomaly& obj, Message& msg) {
|
||||
writeProgress(msg);
|
||||
writeChoices(msg);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,264 @@
|
||||
import artifacts;
|
||||
import regions.regions;
|
||||
import saving;
|
||||
import systems;
|
||||
import object_creation;
|
||||
import statuses;
|
||||
from artifact_seeding import ArtifactCount;
|
||||
from empire import Creeps;
|
||||
|
||||
Artifact@ createArtifact(const vec3d& position, const ArtifactType@ type, Region@ region = null) {
|
||||
if(type is null)
|
||||
return null;
|
||||
ObjectDesc desc;
|
||||
desc.type = OT_Artifact;
|
||||
desc.radius = type.physicalSize;
|
||||
desc.position = position;
|
||||
desc.name = type.name;
|
||||
desc.delayedCreation = true;
|
||||
desc.flags |= objNoDamage;
|
||||
|
||||
Artifact@ obj = Artifact(desc);
|
||||
obj.ArtifactType = type.id;
|
||||
if(region !is null)
|
||||
@obj.region = region;
|
||||
obj.finalizeCreation();
|
||||
if(region !is null)
|
||||
region.enterRegion(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
tidy class ArtifactScript {
|
||||
const ArtifactType@ type;
|
||||
StrategicIconNode@ icon;
|
||||
uint regMask = 0;
|
||||
double deepSpaceTime = 0;
|
||||
double expire = -1.0;
|
||||
|
||||
void postInit(Artifact& obj) {
|
||||
@type = getArtifactType(obj.ArtifactType);
|
||||
if(type is null) {
|
||||
error("Invalid artifact created...");
|
||||
printTrace();
|
||||
obj.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
obj.setNeutralAbilities(true);
|
||||
obj.setAbilityDestroy(type.singleUse);
|
||||
for(uint i = 0, cnt = type.abilities.length; i < cnt; ++i)
|
||||
obj.createAbility(type.abilities[i].id);
|
||||
|
||||
if(type.orbit && obj.region !is null)
|
||||
obj.orbitAround(obj.region.position);
|
||||
|
||||
makeMesh(obj);
|
||||
ArtifactCount += 1;
|
||||
}
|
||||
|
||||
void makeMesh(Artifact& obj) {
|
||||
MeshDesc mesh;
|
||||
@mesh.model = type.model;
|
||||
@mesh.material = type.material;
|
||||
mesh.memorable = true;
|
||||
|
||||
bindMesh(obj, mesh);
|
||||
|
||||
if(type.strategicIcon.valid) {
|
||||
@icon = StrategicIconNode();
|
||||
if(type.strategicIcon.sheet !is null)
|
||||
icon.establish(obj, type.iconSize, type.strategicIcon.sheet, type.strategicIcon.index);
|
||||
else if(type.strategicIcon.mat !is null)
|
||||
icon.establish(obj, type.iconSize, type.strategicIcon.mat);
|
||||
icon.memorable = true;
|
||||
|
||||
if(obj.region !is null) {
|
||||
if(!obj.region.initialized)
|
||||
@obj.region = null;
|
||||
else
|
||||
obj.region.addStrategicIcon(-1, obj, icon);
|
||||
|
||||
if(obj.region !is null) {
|
||||
Node@ node = obj.getNode();
|
||||
node.hintParentObject(obj.region, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void destroy(Artifact& obj) {
|
||||
if(icon !is null) {
|
||||
if(obj.region !is null)
|
||||
obj.region.removeStrategicIcon(-1, icon);
|
||||
icon.markForDeletion();
|
||||
@icon = null;
|
||||
}
|
||||
leaveRegion(obj);
|
||||
ArtifactCount -= 1;
|
||||
|
||||
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
|
||||
Empire@ other = getEmpire(i);
|
||||
if(!other.major)
|
||||
continue;
|
||||
if(regMask & other.mask != 0) {
|
||||
regMask &= ~other.mask;
|
||||
other.unregisterArtifact(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setExpire(double time) {
|
||||
expire = time;
|
||||
}
|
||||
|
||||
bool onOwnerChange(Artifact& obj, Empire@ prevOwner) {
|
||||
regionOwnerChange(obj, prevOwner);
|
||||
if(obj.owner !is null && obj.owner.valid)
|
||||
obj.setNeutralAbilities(false);
|
||||
else
|
||||
obj.setNeutralAbilities(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
double tick(Artifact& obj, double time) {
|
||||
Region@ prevRegion = obj.region;
|
||||
if(updateRegion(obj)) {
|
||||
Region@ newRegion = obj.region;
|
||||
if(icon !is null) {
|
||||
if(prevRegion !is null)
|
||||
prevRegion.removeStrategicIcon(-1, icon);
|
||||
if(newRegion !is null)
|
||||
newRegion.addStrategicIcon(-1, obj, icon);
|
||||
}
|
||||
@prevRegion = newRegion;
|
||||
|
||||
Node@ node = obj.getNode();
|
||||
if(node !is null)
|
||||
node.hintParentObject(newRegion, false);
|
||||
}
|
||||
|
||||
if(obj.region is null) {
|
||||
deepSpaceTime += time;
|
||||
if(deepSpaceTime > 14.0 * 60.0) {
|
||||
deepSpaceTime = randomd(-30.0,30.0);
|
||||
|
||||
Ship@ ship = createShip(obj.position + random3d(4000.0),
|
||||
Creeps.getDesign("Gravitar"), Creeps, free = true);
|
||||
ship.addAbilityOrder(0, obj, obj.radius + ship.radius + 5.0, append=true);
|
||||
auto@ reg = findNearestRegion(obj.position);
|
||||
ship.addMoveOrder(reg.position + (obj.position - reg.position).normalize(reg.radius * 0.8) + random3d(10.0), append=true);
|
||||
ship.addAbilityOrder(0, ship, obj.radius + ship.radius + 5.0, append=true);
|
||||
ship.addMoveOrder(obj.position + random3d(10.0), append=true);
|
||||
ship.addStatus(getStatusID("GravitarShip"));
|
||||
}
|
||||
}
|
||||
else if(deepSpaceTime > 0.0) {
|
||||
deepSpaceTime -= time * 0.5;
|
||||
}
|
||||
|
||||
if(expire > 0) {
|
||||
expire -= time;
|
||||
if(expire <= 0.001)
|
||||
obj.destroy();
|
||||
}
|
||||
|
||||
if(prevRegion is null && isOutsideUniverseExtents(obj.position))
|
||||
limitToUniverseExtents(obj.position);
|
||||
|
||||
Empire@ owner = obj.owner;
|
||||
if(owner !is null && owner.valid && !type.canOwn)
|
||||
@obj.owner = defaultEmpire;
|
||||
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
|
||||
Empire@ other = getEmpire(i);
|
||||
if(!other.major)
|
||||
continue;
|
||||
|
||||
bool usable = false;
|
||||
if(owner !is null && owner.valid && owner !is other) {
|
||||
usable = false;
|
||||
}
|
||||
else if(prevRegion !is null) {
|
||||
if(prevRegion.PlanetsMask != 0) {
|
||||
usable = prevRegion.PlanetsMask & other.mask != 0;
|
||||
}
|
||||
else {
|
||||
usable = hasPlanetsAdjacent(other, prevRegion)
|
||||
&& obj.memoryMask & other.mask != 0;
|
||||
}
|
||||
}
|
||||
if(usable) {
|
||||
if(regMask & other.mask == 0) {
|
||||
regMask |= other.mask;
|
||||
other.registerArtifact(obj);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(regMask & other.mask != 0) {
|
||||
regMask &= ~other.mask;
|
||||
other.unregisterArtifact(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
icon.visible = obj.isVisibleTo(playerEmpire);
|
||||
obj.abilityTick(time);
|
||||
obj.orbitTick(time);
|
||||
return 0.5;
|
||||
}
|
||||
|
||||
vec3d get_strategicIconPosition(Artifact& obj) {
|
||||
if(icon is null)
|
||||
return obj.position;
|
||||
return icon.position;
|
||||
}
|
||||
|
||||
void load(Artifact& obj, SaveFile& file) {
|
||||
loadObjectStates(obj, file);
|
||||
@type = getArtifactType(file.readIdentifier(SI_Artifact));
|
||||
obj.ArtifactType = type.id;
|
||||
file >> cast<Savable>(obj.Abilities);
|
||||
file >> cast<Savable>(obj.Orbit);
|
||||
if(file >= SV_0029)
|
||||
file >> regMask;
|
||||
if(file >= SV_0121)
|
||||
file >> expire;
|
||||
}
|
||||
|
||||
void postLoad(Artifact& obj) {
|
||||
makeMesh(obj);
|
||||
}
|
||||
|
||||
void save(Artifact& obj, SaveFile& file) {
|
||||
saveObjectStates(obj, file);
|
||||
file.writeIdentifier(SI_Artifact, type.id);
|
||||
file << cast<Savable>(obj.Abilities);
|
||||
file << cast<Savable>(obj.Orbit);
|
||||
file << regMask;
|
||||
file << expire;
|
||||
}
|
||||
|
||||
void syncInitial(const Artifact& obj, Message& msg) {
|
||||
msg.writeSmall(type.id);
|
||||
obj.writeOrbit(msg);
|
||||
obj.writeAbilities(msg);
|
||||
}
|
||||
|
||||
bool syncDelta(const Artifact& obj, Message& msg) {
|
||||
bool used = false;
|
||||
if(obj.writeAbilityDelta(msg))
|
||||
used = true;
|
||||
else
|
||||
msg.write0();
|
||||
if(obj.writeOrbitDelta(msg))
|
||||
used = true;
|
||||
else
|
||||
msg.write0();
|
||||
|
||||
return used;
|
||||
}
|
||||
|
||||
void syncDetailed(const Artifact& obj, Message& msg) {
|
||||
obj.writeOrbit(msg);
|
||||
obj.writeAbilities(msg);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,572 @@
|
||||
import resources;
|
||||
import regions.regions;
|
||||
import saving;
|
||||
import cargo;
|
||||
import attributes;
|
||||
from systems import hasTradeAdjacent;
|
||||
|
||||
Asteroid@ createAsteroid(const vec3d& position, Region@ region = null, bool delay = false) {
|
||||
ObjectDesc desc;
|
||||
desc.type = OT_Asteroid;
|
||||
desc.radius = 5.0;
|
||||
desc.position = position;
|
||||
desc.name = locale::ASTEROID;
|
||||
desc.flags |= objNoDamage;
|
||||
|
||||
if(region !is null)
|
||||
desc.delayedCreation = true;
|
||||
|
||||
Asteroid@ obj = Asteroid(desc);
|
||||
|
||||
if(region !is null) {
|
||||
@obj.region = region;
|
||||
obj.finalizeCreation();
|
||||
region.enterRegion(obj);
|
||||
}
|
||||
if(!delay)
|
||||
obj.initMesh();
|
||||
return obj;
|
||||
}
|
||||
|
||||
tidy class AsteroidScript {
|
||||
StrategicIconNode@ icon;
|
||||
MeshNode@ baseNode;
|
||||
|
||||
array<const ResourceType@> available;
|
||||
array<float> costs;
|
||||
array<bool> exploited;
|
||||
array<int> nativeIds;
|
||||
bool delta = false;
|
||||
uint resourceLimit = 1;
|
||||
uint currentResources = 0;
|
||||
uint limitMod = 0;
|
||||
|
||||
void load(Asteroid& obj, SaveFile& file) {
|
||||
loadObjectStates(obj, file);
|
||||
|
||||
if(file >= SV_0122) {
|
||||
file >> cast<Savable>(obj.Orbit);
|
||||
file >> cast<Savable>(obj.Cargo);
|
||||
}
|
||||
else {
|
||||
file >> cast<Savable>(obj.Resources);
|
||||
file >> cast<Savable>(obj.Orbit);
|
||||
}
|
||||
if(file >= SV_0125)
|
||||
file >> cast<Savable>(obj.Resources);
|
||||
|
||||
if(file < SV_0122 || file >= SV_0125) {
|
||||
Object@ origin;
|
||||
@origin = file.readObject();
|
||||
|
||||
uint cnt = 0;
|
||||
file >> cnt;
|
||||
available.length = cnt;
|
||||
costs.length = cnt;
|
||||
exploited.length = cnt;
|
||||
nativeIds.length = cnt;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
@available[i] = getResource(file.readIdentifier(SI_Resource));
|
||||
file >> costs[i];
|
||||
file >> exploited[i];
|
||||
file >> nativeIds[i];
|
||||
}
|
||||
|
||||
file >> resourceLimit;
|
||||
file >> limitMod;
|
||||
file >> currentResources;
|
||||
}
|
||||
|
||||
obj.HasBase = (obj.owner !is null && obj.owner.valid) ? 1.f : 0.f;
|
||||
}
|
||||
|
||||
void postLoad(Asteroid& obj) {
|
||||
makeMesh(obj);
|
||||
}
|
||||
|
||||
void save(Asteroid& obj, SaveFile& file) {
|
||||
saveObjectStates(obj, file);
|
||||
file << cast<Savable>(obj.Orbit);
|
||||
file << cast<Savable>(obj.Cargo);
|
||||
file << cast<Savable>(obj.Resources);
|
||||
file << obj.origin;
|
||||
|
||||
uint cnt = available.length;
|
||||
file << cnt;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
file.writeIdentifier(SI_Resource, available[i].id);
|
||||
file << costs[i];
|
||||
file << exploited[i];
|
||||
file << nativeIds[i];
|
||||
}
|
||||
|
||||
file << resourceLimit;
|
||||
file << limitMod;
|
||||
file << currentResources;
|
||||
}
|
||||
|
||||
void postInit(Asteroid& obj) {
|
||||
obj.setImportEnabled(false);
|
||||
obj.setResourceLevel(4);
|
||||
obj.sightRange = 0;
|
||||
|
||||
obj.modCargoStorage(+INFINITY);
|
||||
}
|
||||
|
||||
void destroy(Asteroid& obj) {
|
||||
obj.destroyObjResources();
|
||||
if(obj.region !is null)
|
||||
obj.region.removeStrategicIcon(-1, icon);
|
||||
icon.markForDeletion();
|
||||
@icon = null;
|
||||
|
||||
if(baseNode !is null) {
|
||||
baseNode.markForDeletion();
|
||||
@baseNode = null;
|
||||
}
|
||||
|
||||
if(obj.owner !is null && obj.owner.valid)
|
||||
obj.owner.unregisterAsteroid(obj);
|
||||
|
||||
leaveRegion(obj);
|
||||
}
|
||||
|
||||
bool onOwnerChange(Asteroid& obj, Empire@ prevOwner) {
|
||||
regionOwnerChange(obj, prevOwner);
|
||||
if(prevOwner !is null && prevOwner.valid)
|
||||
prevOwner.unregisterAsteroid(obj);
|
||||
if(obj.owner !is null && obj.owner.valid)
|
||||
obj.owner.registerAsteroid(obj);
|
||||
obj.changeResourceOwner(prevOwner);
|
||||
|
||||
bool hasBase = obj.owner !is null && obj.owner.valid;
|
||||
obj.HasBase = hasBase ? 1.f : 0.f;
|
||||
if(hasBase && baseNode is null) {
|
||||
@baseNode = MeshNode(model::MiningBase, material::GenericPBR_MiningBase);
|
||||
nodeSyncObject(baseNode, obj);
|
||||
}
|
||||
else if(baseNode !is null && !hasBase) {
|
||||
baseNode.markForDeletion();
|
||||
@baseNode = null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void initMesh(Asteroid& obj) {
|
||||
makeMesh(obj);
|
||||
}
|
||||
|
||||
void makeMesh(Asteroid& obj) {
|
||||
MeshDesc mesh;
|
||||
switch(obj.id % 4) {
|
||||
case 0:
|
||||
@mesh.model = model::Asteroid1; break;
|
||||
case 1:
|
||||
@mesh.model = model::Asteroid2; break;
|
||||
case 2:
|
||||
@mesh.model = model::Asteroid3; break;
|
||||
case 3:
|
||||
@mesh.model = model::Asteroid4; break;
|
||||
}
|
||||
switch(obj.id % 3) {
|
||||
case 0:
|
||||
@mesh.material = material::AsteroidPegmatite; break;
|
||||
case 1:
|
||||
@mesh.material = material::AsteroidMagnetite; break;
|
||||
case 2:
|
||||
@mesh.material = material::AsteroidTonalite; break;
|
||||
}
|
||||
|
||||
mesh.memorable = true;
|
||||
|
||||
@icon = StrategicIconNode();
|
||||
if(obj.cargoTypes != 0)
|
||||
icon.establish(obj, 0.015, spritesheet::OreAsteroidIcon, 0);
|
||||
else
|
||||
icon.establish(obj, 0.015, spritesheet::AsteroidIcon, 0);
|
||||
icon.memorable = true;
|
||||
|
||||
bindMesh(obj, mesh);
|
||||
|
||||
if(obj.region !is null) {
|
||||
if(!obj.region.initialized)
|
||||
@obj.region = null;
|
||||
else
|
||||
obj.region.addStrategicIcon(-1, obj, icon);
|
||||
|
||||
if(obj.region !is null) {
|
||||
Node@ node = obj.getNode();
|
||||
node.hintParentObject(obj.region, false);
|
||||
}
|
||||
}
|
||||
|
||||
bool hasBase = obj.owner !is null && obj.owner.valid;
|
||||
if(hasBase && baseNode is null) {
|
||||
@baseNode = MeshNode(model::MiningBase, material::GenericPBR_MiningBase);
|
||||
nodeSyncObject(baseNode, obj);
|
||||
}
|
||||
}
|
||||
|
||||
float timer = 0.f;
|
||||
void occasional_tick(Asteroid& obj) {
|
||||
Region@ region = obj.region;
|
||||
bool engaged = obj.engaged;
|
||||
obj.inCombat = engaged;
|
||||
obj.engaged = false;
|
||||
|
||||
if(engaged && region !is null)
|
||||
region.EngagedMask |= obj.owner.mask;
|
||||
}
|
||||
|
||||
double tick(Asteroid& obj, double time) {
|
||||
Region@ prevRegion = obj.region;
|
||||
if(updateRegion(obj)) {
|
||||
Region@ newRegion = obj.region;
|
||||
if(prevRegion !is null)
|
||||
prevRegion.removeStrategicIcon(-1, icon);
|
||||
if(newRegion !is null)
|
||||
newRegion.addStrategicIcon(-1, obj, icon);
|
||||
@prevRegion = newRegion;
|
||||
|
||||
Node@ node = obj.getNode();
|
||||
if(node !is null)
|
||||
node.hintParentObject(newRegion, false);
|
||||
}
|
||||
|
||||
if(prevRegion is null && isOutsideUniverseExtents(obj.position))
|
||||
limitToUniverseExtents(obj.position);
|
||||
|
||||
icon.visible = obj.isVisibleTo(playerEmpire);
|
||||
|
||||
obj.orbitTick(time);
|
||||
obj.resourceTick(time);
|
||||
|
||||
//Tick occasional stuff
|
||||
timer -= float(time);
|
||||
if(timer <= 0.f) {
|
||||
occasional_tick(obj);
|
||||
timer = 1.f;
|
||||
}
|
||||
|
||||
//Asteroids lose ownership if not in an owned or neutral system
|
||||
if(obj.owner.valid) {
|
||||
if(prevRegion !is null && prevRegion.PlanetsMask != 0 && prevRegion.PlanetsMask & obj.owner.mask == 0) {
|
||||
if(!hasTradeAdjacent(obj.owner, prevRegion)) {
|
||||
clearSetup(obj);
|
||||
return 0.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Asteroids are destroyed when they run out of cargo or resources
|
||||
if(obj.cargoTypes == 0 && obj.nativeResourceCount == 0)
|
||||
obj.destroy();
|
||||
|
||||
return 0.2;
|
||||
}
|
||||
|
||||
vec3d get_strategicIconPosition(Asteroid& obj) {
|
||||
if(icon is null)
|
||||
return obj.position;
|
||||
return icon.position;
|
||||
}
|
||||
|
||||
bool canDevelop(Asteroid& obj, Empire@ emp) {
|
||||
return (!obj.owner.valid || obj.owner is emp) && currentResources < resourceLimit;
|
||||
}
|
||||
|
||||
bool canGainLimit(Asteroid& obj, Empire@ emp) {
|
||||
if(!obj.owner.valid || obj.owner !is emp)
|
||||
return false;
|
||||
return resourceLimit < available.length;
|
||||
}
|
||||
|
||||
void addAvailable(Asteroid& obj, uint resource, double cost) {
|
||||
const ResourceType@ type = getResource(resource);
|
||||
if(type is null)
|
||||
return;
|
||||
|
||||
available.insertLast(type);
|
||||
costs.insertLast(cost);
|
||||
exploited.insertLast(false);
|
||||
int id = obj.addResource(type.id);
|
||||
obj.setResourceDisabled(id, true);
|
||||
nativeIds.insertLast(id);
|
||||
|
||||
delta = true;
|
||||
}
|
||||
|
||||
uint getAvailableCount() {
|
||||
if(currentResources >= resourceLimit)
|
||||
return 0;
|
||||
return available.length;
|
||||
}
|
||||
|
||||
uint getAvailable(uint index) {
|
||||
if(index >= available.length)
|
||||
return uint(-1);
|
||||
if(exploited[index])
|
||||
return uint(-1);
|
||||
return available[index].id;
|
||||
}
|
||||
|
||||
double getAvailableCost(uint index) {
|
||||
if(index >= costs.length)
|
||||
return -1.0;
|
||||
if(exploited[index])
|
||||
return -1.0;
|
||||
return costs[index];
|
||||
}
|
||||
|
||||
double getAvailableCostFor(uint resId) {
|
||||
const ResourceType@ type = getResource(resId);
|
||||
if(type is null)
|
||||
return -1.0;
|
||||
|
||||
for(uint i = 0, cnt = available.length; i < cnt; ++i) {
|
||||
if(exploited[i])
|
||||
continue;
|
||||
if(available[i] is type)
|
||||
return costs[i];
|
||||
}
|
||||
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
void setup(Asteroid& obj, Object@ origin, Empire@ emp, uint resource) {
|
||||
if(obj.owner.valid && emp !is obj.owner)
|
||||
return;
|
||||
if(currentResources >= resourceLimit)
|
||||
return;
|
||||
|
||||
const ResourceType@ type = getResource(resource);
|
||||
if(type is null)
|
||||
return;
|
||||
|
||||
bool found = false;
|
||||
uint foundIndex = 0;
|
||||
for(uint i = 0, cnt = available.length; i < cnt; ++i) {
|
||||
if(exploited[i])
|
||||
continue;
|
||||
if(available[i] is type) {
|
||||
found = true;
|
||||
foundIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!found)
|
||||
return;
|
||||
Object@ queued;
|
||||
exploited[foundIndex] = true;
|
||||
obj.setResourceDisabled(nativeIds[foundIndex], false);
|
||||
|
||||
if(!obj.owner.valid) {
|
||||
@obj.owner = emp;
|
||||
@obj.origin = origin;
|
||||
obj.name = type.name+" "+locale::ASTEROID;
|
||||
emp.modAttribute(EA_MiningBasesBuilt, AC_Add, 1);
|
||||
}
|
||||
|
||||
//Remove all the fake resources and remember the queue
|
||||
currentResources += 1;
|
||||
if(currentResources >= resourceLimit) {
|
||||
for(uint i = 0, cnt = available.length; i < cnt; ++i) {
|
||||
if(exploited[i])
|
||||
continue;
|
||||
obj.removeResource(nativeIds[i]);
|
||||
nativeIds[i] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
delta = true;
|
||||
}
|
||||
|
||||
void clearSetup(Asteroid& obj) {
|
||||
if(currentResources == 0)
|
||||
return;
|
||||
delta = true;
|
||||
currentResources = 0;
|
||||
|
||||
Object@ queued = obj.nativeResourceDestination[0];
|
||||
uint qtype = obj.nativeResourceType[0];
|
||||
|
||||
for(uint i = 0, cnt = obj.nativeResourceCount; i < cnt; ++i)
|
||||
obj.removeResource(obj.nativeResourceId[i]);
|
||||
|
||||
Empire@ prevOwner = obj.owner;
|
||||
@obj.owner = defaultEmpire;
|
||||
@obj.origin = null;
|
||||
obj.name = locale::ASTEROID;
|
||||
|
||||
for(uint i = 0, cnt = available.length; i < cnt; ++i) {
|
||||
exploited[i] = false;
|
||||
if(costs[i] <= 0)
|
||||
costs[i] = available[i].asteroidCost;
|
||||
|
||||
int id = obj.addResource(available[i].id);
|
||||
obj.setResourceDisabled(id, true);
|
||||
nativeIds[i] = id;
|
||||
|
||||
if(queued !is null && qtype == available[i].id && prevOwner.valid)
|
||||
obj.exportResource(prevOwner, id, queued);
|
||||
}
|
||||
}
|
||||
|
||||
void checkLimit(Asteroid& obj, uint prevLimit) {
|
||||
bool wasLimited = currentResources >= prevLimit;
|
||||
bool nowLimited = currentResources >= resourceLimit;
|
||||
|
||||
if(wasLimited) {
|
||||
if(!nowLimited) {
|
||||
for(uint i = 0, cnt = available.length; i < cnt; ++i) {
|
||||
if(exploited[i]) {
|
||||
obj.setResourceDisabled(nativeIds[i], false);
|
||||
}
|
||||
else {
|
||||
nativeIds[i] = obj.addResource(available[i].id);
|
||||
obj.setResourceDisabled(nativeIds[i], true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(nowLimited) {
|
||||
for(uint i = 0, cnt = available.length; i < cnt; ++i) {
|
||||
if(exploited[i])
|
||||
continue;
|
||||
obj.removeResource(nativeIds[i]);
|
||||
nativeIds[i] = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(nowLimited) {
|
||||
uint requireDisable = currentResources - resourceLimit;
|
||||
for(uint i = 0, cnt = available.length; i < cnt; ++i) {
|
||||
if(!exploited[i])
|
||||
continue;
|
||||
Object@ dest = obj.getNativeResourceDestinationByID(obj.owner, nativeIds[i]);
|
||||
if(dest !is null) {
|
||||
obj.setResourceDisabled(nativeIds[i], false);
|
||||
continue;
|
||||
}
|
||||
if(requireDisable > 0) {
|
||||
obj.setResourceDisabled(nativeIds[i], true);
|
||||
requireDisable -= 1;
|
||||
}
|
||||
else {
|
||||
obj.setResourceDisabled(nativeIds[i], false);
|
||||
}
|
||||
}
|
||||
for(uint i = 0, cnt = available.length; i < cnt && requireDisable > 0; ++i) {
|
||||
if(!exploited[i])
|
||||
continue;
|
||||
Object@ dest = obj.getNativeResourceDestinationByID(obj.owner, nativeIds[i]);
|
||||
if(dest is null)
|
||||
continue;
|
||||
obj.setResourceDisabled(nativeIds[i], true);
|
||||
requireDisable -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void morphTo(Asteroid& obj, uint resId, double cost) {
|
||||
auto@ resource = getResource(resId);
|
||||
if(resource is null)
|
||||
return;
|
||||
|
||||
for(uint i = 0, cnt = available.length; i < cnt; ++i) {
|
||||
if(nativeIds[i] != -1)
|
||||
obj.removeResource(nativeIds[i]);
|
||||
nativeIds[i] = -1;
|
||||
}
|
||||
|
||||
currentResources = 0;
|
||||
available.length = 0;
|
||||
exploited.length = 0;
|
||||
nativeIds.length = 0;
|
||||
costs.length = 0;
|
||||
addAvailable(obj, resource.id, cost);
|
||||
|
||||
if(obj.owner !is null && obj.owner.valid)
|
||||
setup(obj, obj.origin, obj.owner, resource.id);
|
||||
}
|
||||
|
||||
void setResourceLimit(Asteroid& obj, uint newLimit) {
|
||||
if(newLimit + limitMod == resourceLimit)
|
||||
return;
|
||||
uint prevLimit = resourceLimit;
|
||||
resourceLimit = newLimit + limitMod;
|
||||
checkLimit(obj, prevLimit);
|
||||
|
||||
delta = true;
|
||||
}
|
||||
|
||||
void modResourceLimitMod(Asteroid& obj, int mod) {
|
||||
limitMod += mod;
|
||||
uint prevLimit = resourceLimit;
|
||||
resourceLimit += mod;
|
||||
|
||||
checkLimit(obj, prevLimit);
|
||||
delta = true;
|
||||
}
|
||||
|
||||
void _writeAsteroid(const Asteroid& obj, Message& msg) {
|
||||
msg << obj.origin;
|
||||
uint cnt = available.length;
|
||||
msg.writeSmall(cnt);
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
msg.writeLimited(available[i].id, getResourceCount()-1);
|
||||
msg << costs[i];
|
||||
msg << exploited[i];
|
||||
}
|
||||
msg.writeSmall(resourceLimit);
|
||||
msg.writeSmall(currentResources);
|
||||
}
|
||||
|
||||
void syncInitial(const Asteroid& obj, Message& msg) {
|
||||
_writeAsteroid(obj, msg);
|
||||
obj.writeResources(msg);
|
||||
obj.writeCargo(msg);
|
||||
obj.writeOrbit(msg);
|
||||
}
|
||||
|
||||
bool syncDelta(const Asteroid& obj, Message& msg) {
|
||||
bool used = false;
|
||||
|
||||
if(delta) {
|
||||
used = true;
|
||||
delta = false;
|
||||
msg.write1();
|
||||
_writeAsteroid(obj, msg);
|
||||
}
|
||||
else
|
||||
msg.write0();
|
||||
|
||||
if(obj.writeResourceDelta(msg))
|
||||
used = true;
|
||||
else
|
||||
msg.write0();
|
||||
|
||||
if(obj.writeCargoDelta(msg))
|
||||
used = true;
|
||||
else
|
||||
msg.write0();
|
||||
|
||||
if(obj.writeOrbitDelta(msg))
|
||||
used = true;
|
||||
else
|
||||
msg.write0();
|
||||
|
||||
return used;
|
||||
}
|
||||
|
||||
void syncDetailed(const Asteroid& obj, Message& msg) {
|
||||
_writeAsteroid(obj, msg);
|
||||
obj.writeResources(msg);
|
||||
obj.writeCargo(msg);
|
||||
obj.writeOrbit(msg);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,558 @@
|
||||
import regions.regions;
|
||||
import saving;
|
||||
import systems;
|
||||
import resources;
|
||||
import civilians;
|
||||
import statuses;
|
||||
|
||||
const double ACC_SYSTEM = 2.0;
|
||||
const double ACC_INTERSYSTEM = 65.0;
|
||||
const int GOODS_WORTH = 8;
|
||||
const double CIV_HEALTH = 25.0;
|
||||
const double CIV_REPAIR = 1.0;
|
||||
const double BLOCKADE_TIMER = 3.0 * 60.0;
|
||||
const double DEST_RANGE = 4.0;
|
||||
|
||||
tidy class CivilianScript {
|
||||
uint type = 0;
|
||||
Object@ origin;
|
||||
Object@ pathTarget;
|
||||
Object@ intermediate;
|
||||
Region@ prevRegion;
|
||||
Region@ nextRegion;
|
||||
int moveId = -1;
|
||||
bool leavingRegion = false, awaitingIntermediate = false;
|
||||
bool pickedUp = false;
|
||||
double Health = CIV_HEALTH;
|
||||
int stepCount = 0;
|
||||
int income = 0;
|
||||
bool delta = false;
|
||||
|
||||
uint cargoType = CT_Goods;
|
||||
const ResourceType@ cargoResource;
|
||||
int cargoWorth = 0;
|
||||
|
||||
double get_health() {
|
||||
return Health;
|
||||
}
|
||||
|
||||
double get_maxHealth(const Civilian& obj) {
|
||||
return CIV_HEALTH * obj.radius * obj.owner.ModHP.value;
|
||||
}
|
||||
|
||||
void load(Civilian& obj, SaveFile& msg) {
|
||||
loadObjectStates(obj, msg);
|
||||
if(msg.readBit()) {
|
||||
obj.activateMover();
|
||||
msg >> cast<Savable>(obj.Mover);
|
||||
}
|
||||
msg >> type;
|
||||
msg >> origin;
|
||||
msg >> pathTarget;
|
||||
msg >> intermediate;
|
||||
msg >> prevRegion;
|
||||
msg >> nextRegion;
|
||||
msg >> moveId;
|
||||
msg >> leavingRegion;
|
||||
msg >> pickedUp;
|
||||
msg >> Health;
|
||||
msg >> stepCount;
|
||||
msg >> income;
|
||||
msg >> cargoType;
|
||||
if(msg.readBit())
|
||||
@cargoResource = getResource(msg.readIdentifier(SI_Resource));
|
||||
msg >> cargoWorth;
|
||||
|
||||
makeMesh(obj);
|
||||
}
|
||||
|
||||
void save(Civilian& obj, SaveFile& msg) {
|
||||
saveObjectStates(obj, msg);
|
||||
if(obj.hasMover) {
|
||||
msg.write1();
|
||||
msg << cast<Savable>(obj.Mover);
|
||||
}
|
||||
else {
|
||||
msg.write0();
|
||||
}
|
||||
msg << type;
|
||||
msg << origin;
|
||||
msg << pathTarget;
|
||||
msg << intermediate;
|
||||
msg << prevRegion;
|
||||
msg << nextRegion;
|
||||
msg << moveId;
|
||||
msg << leavingRegion;
|
||||
msg << pickedUp;
|
||||
msg << Health;
|
||||
msg << stepCount;
|
||||
msg << income;
|
||||
msg << cargoType;
|
||||
if(cargoResource is null) {
|
||||
msg.write0();
|
||||
}
|
||||
else {
|
||||
msg.write1();
|
||||
msg.writeIdentifier(SI_Resource, cargoResource.id);
|
||||
}
|
||||
msg << cargoWorth;
|
||||
}
|
||||
|
||||
uint getCargoType() {
|
||||
if(cargoType == CT_Resource && !pickedUp)
|
||||
return CT_Goods;
|
||||
return cargoType;
|
||||
}
|
||||
|
||||
uint getCargoResource() {
|
||||
if(cargoResource is null)
|
||||
return uint(-1);
|
||||
return cargoResource.id;
|
||||
}
|
||||
|
||||
int getCargoWorth() {
|
||||
return cargoWorth;
|
||||
}
|
||||
|
||||
void setCargoType(Civilian& obj, uint type) {
|
||||
cargoType = type;
|
||||
@cargoResource = null;
|
||||
if(type == CT_Goods)
|
||||
cargoWorth = GOODS_WORTH * obj.radius * CIV_RADIUS_WORTH;
|
||||
delta = true;
|
||||
}
|
||||
|
||||
void setCargoResource(Civilian& obj, uint id) {
|
||||
cargoType = CT_Resource;
|
||||
@cargoResource = getResource(id);
|
||||
if(pickedUp)
|
||||
cargoWorth = cargoResource.cargoWorth * obj.radius * CIV_RADIUS_WORTH;
|
||||
else
|
||||
cargoWorth = GOODS_WORTH * obj.radius * CIV_RADIUS_WORTH;
|
||||
delta = true;
|
||||
}
|
||||
|
||||
void modCargoWorth(int diff) {
|
||||
cargoWorth += diff;
|
||||
delta = true;
|
||||
}
|
||||
|
||||
int getStepCount() {
|
||||
return stepCount;
|
||||
}
|
||||
|
||||
void modStepCount(int mod) {
|
||||
stepCount += mod;
|
||||
}
|
||||
|
||||
void resetStepCount() {
|
||||
stepCount = 0;
|
||||
}
|
||||
|
||||
void init(Civilian& obj) {
|
||||
obj.sightRange = 0;
|
||||
}
|
||||
|
||||
uint getCivilianType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
void setCivilianType(uint type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
void modIncome(Civilian& obj, int mod) {
|
||||
if(obj.owner !is null && obj.owner.valid)
|
||||
obj.owner.modTotalBudget(+mod, MoT_Trade);
|
||||
income += mod;
|
||||
}
|
||||
|
||||
void postInit(Civilian& obj) {
|
||||
if(type == CiT_Freighter && obj.owner !is null)
|
||||
obj.owner.CivilianTradeShips += 1;
|
||||
if(type == CiT_Freighter) {
|
||||
obj.activateMover();
|
||||
obj.maxAcceleration = ACC_SYSTEM;
|
||||
obj.rotationSpeed = 1.0;
|
||||
}
|
||||
makeMesh(obj);
|
||||
Health = get_maxHealth(obj);
|
||||
delta = true;
|
||||
}
|
||||
|
||||
void makeMesh(Civilian& obj) {
|
||||
MeshDesc mesh;
|
||||
@mesh.model = getCivilianModel(obj.owner, type, obj.radius);
|
||||
@mesh.material = getCivilianMaterial(obj.owner, type, obj.radius);
|
||||
@mesh.iconSheet = getCivilianIcon(obj.owner, type, obj.radius).sheet;
|
||||
mesh.iconIndex = getCivilianIcon(obj.owner, type, obj.radius).index;
|
||||
|
||||
bindMesh(obj, mesh);
|
||||
}
|
||||
|
||||
bool onOwnerChange(Civilian& obj, Empire@ prevOwner) {
|
||||
if(income != 0 && prevOwner !is null && prevOwner.valid)
|
||||
prevOwner.modTotalBudget(-income, MoT_Trade);
|
||||
if(type == CiT_Freighter && prevOwner !is null)
|
||||
prevOwner.CivilianTradeShips -= 1;
|
||||
regionOwnerChange(obj, prevOwner);
|
||||
if(type == CiT_Freighter && obj.owner !is null)
|
||||
obj.owner.CivilianTradeShips += 1;
|
||||
if(income != 0 && prevOwner !is null && obj.owner.valid)
|
||||
obj.owner.modTotalBudget(-income, MoT_Trade);
|
||||
return false;
|
||||
}
|
||||
|
||||
void destroy(Civilian& obj) {
|
||||
if((obj.inCombat || obj.engaged) && !game_ending) {
|
||||
playParticleSystem("ShipExplosion", obj.position, obj.rotation, obj.radius, obj.visibleMask);
|
||||
}
|
||||
else {
|
||||
if(cargoResource !is null) {
|
||||
for(uint i = 0, cnt = cargoResource.hooks.length; i < cnt; ++i)
|
||||
cargoResource.hooks[i].onTradeDestroy(obj, origin, pathTarget, null);
|
||||
}
|
||||
}
|
||||
if(origin !is null && origin.hasResources)
|
||||
origin.setAssignedCivilian(null);
|
||||
if(pathTarget !is null && pathTarget.isPlanet && pathTarget.owner is obj.owner) {
|
||||
auto@ status = getStatusType("Blockaded");
|
||||
if(status !is null)
|
||||
pathTarget.addStatus(status.id, timer=BLOCKADE_TIMER);
|
||||
}
|
||||
leaveRegion(obj);
|
||||
if(obj.owner !is null && obj.owner.valid) {
|
||||
if(type == CiT_Freighter)
|
||||
obj.owner.CivilianTradeShips -= 1;
|
||||
if(income != 0)
|
||||
obj.owner.modTotalBudget(-income, MoT_Trade);
|
||||
}
|
||||
}
|
||||
|
||||
void freeCivilian(Civilian& obj) {
|
||||
if(origin !is null && origin.hasResources)
|
||||
origin.setAssignedCivilian(null);
|
||||
|
||||
Region@ region = obj.region;
|
||||
if(region !is null) {
|
||||
@origin = null;
|
||||
@pathTarget = null;
|
||||
@prevRegion = null;
|
||||
@nextRegion = null;
|
||||
region.freeUpCivilian(obj);
|
||||
}
|
||||
else {
|
||||
@origin = null;
|
||||
@pathTarget = null;
|
||||
@prevRegion = null;
|
||||
@nextRegion = null;
|
||||
obj.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
float timer = 0.f;
|
||||
void occasional_tick(Civilian& obj) {
|
||||
//Update in combat flags
|
||||
bool engaged = obj.engaged;
|
||||
obj.inCombat = engaged;
|
||||
obj.engaged = false;
|
||||
|
||||
if(engaged && obj.region !is null)
|
||||
obj.region.EngagedMask |= obj.owner.mask;
|
||||
}
|
||||
|
||||
void gotoTradeStation(Civilian@ station) {
|
||||
if(!awaitingIntermediate)
|
||||
return;
|
||||
awaitingIntermediate = false;
|
||||
@intermediate = station;
|
||||
}
|
||||
|
||||
void gotoTradePlanet(Planet@ planet) {
|
||||
if(!awaitingIntermediate)
|
||||
return;
|
||||
awaitingIntermediate = false;
|
||||
@intermediate = planet;
|
||||
}
|
||||
|
||||
double tick(Civilian& obj, double time) {
|
||||
//Update normal stuff
|
||||
updateRegion(obj);
|
||||
if(obj.hasMover)
|
||||
obj.moverTick(time);
|
||||
|
||||
//Tick occasional stuff
|
||||
timer -= float(time);
|
||||
if(timer <= 0.f) {
|
||||
occasional_tick(obj);
|
||||
timer = 1.f;
|
||||
}
|
||||
|
||||
//Do repair
|
||||
double maxHP = get_maxHealth(obj);
|
||||
if(!obj.inCombat && Health < maxHP) {
|
||||
Health = min(Health + (CIV_REPAIR * time * obj.radius), maxHP);
|
||||
delta = true;
|
||||
}
|
||||
|
||||
if(awaitingIntermediate)
|
||||
return 0.25;
|
||||
|
||||
//Update pathing
|
||||
Region@ curRegion = obj.region;
|
||||
if(pathTarget !is null) {
|
||||
if(origin !is null && !pickedUp) {
|
||||
if(obj.moveTo(origin, moveId, distance=10.0, enterOrbit=false)) {
|
||||
pickedUp = true;
|
||||
if(cargoResource !is null)
|
||||
cargoWorth = cargoResource.cargoWorth * obj.radius * CIV_RADIUS_WORTH;
|
||||
delta = true;
|
||||
moveId = -1;
|
||||
return 0.5;
|
||||
}
|
||||
else {
|
||||
return 0.2;
|
||||
}
|
||||
}
|
||||
Region@ destRegion;
|
||||
if(pathTarget.isRegion)
|
||||
@destRegion = cast<Region>(pathTarget);
|
||||
else
|
||||
@destRegion = pathTarget.region;
|
||||
if(nextRegion is null) {
|
||||
if(curRegion is null)
|
||||
@nextRegion = findNearestRegion(obj.position);
|
||||
else
|
||||
@nextRegion = curRegion;
|
||||
}
|
||||
if(nextRegion is null || destRegion is null) {
|
||||
freeCivilian(obj);
|
||||
return 0.4;
|
||||
}
|
||||
if(leavingRegion) {
|
||||
vec3d enterDest;
|
||||
if(nextRegion !is destRegion || destRegion is pathTarget || getSystem(prevRegion).isSpatialAdjacent(getSystem(nextRegion))) {
|
||||
enterDest = nextRegion.position + (prevRegion.position - nextRegion.position).normalized(nextRegion.radius * 0.85);
|
||||
enterDest += random3d(0.0, DEST_RANGE);
|
||||
}
|
||||
else {
|
||||
enterDest = pathTarget.position + vec3d(0,0,pathTarget.radius+10.0);
|
||||
}
|
||||
obj.maxAcceleration = ACC_INTERSYSTEM;
|
||||
if(!obj.moveTo(enterDest, moveId, enterOrbit=false))
|
||||
return 0.2;
|
||||
if(cargoType == CT_Resource)
|
||||
prevRegion.bumpTradeCounter(obj.owner);
|
||||
moveId = -1;
|
||||
leavingRegion = false;
|
||||
}
|
||||
if(curRegion is null || (nextRegion !is null && nextRegion is curRegion)) {
|
||||
if(nextRegion is destRegion) {
|
||||
//Move to destination
|
||||
obj.maxAcceleration = ACC_SYSTEM;
|
||||
if(curRegion is pathTarget || obj.moveTo(pathTarget, moveId, distance=10.0, enterOrbit=false)) {
|
||||
moveId = -1;
|
||||
if(cargoType == CT_Resource)
|
||||
destRegion.bumpTradeCounter(obj.owner);
|
||||
if(cargoResource !is null && !pathTarget.isRegion) {
|
||||
for(uint i = 0, cnt = cargoResource.hooks.length; i < cnt; ++i)
|
||||
cargoResource.hooks[i].onTradeDeliver(obj, origin, pathTarget);
|
||||
}
|
||||
freeCivilian(obj);
|
||||
return 0.4;
|
||||
}
|
||||
else {
|
||||
return 0.2;
|
||||
}
|
||||
}
|
||||
else if(curRegion is null) {
|
||||
//Move to closest region
|
||||
vec3d pos = nextRegion.position + (nextRegion.position - obj.position).normalized(nextRegion.radius * 0.85);
|
||||
obj.maxAcceleration = ACC_INTERSYSTEM;
|
||||
if(obj.moveTo(pos, moveId, enterOrbit=false)) {
|
||||
moveId = -1;
|
||||
return 0.4;
|
||||
}
|
||||
else {
|
||||
return 0.2;
|
||||
}
|
||||
}
|
||||
else {
|
||||
//Find the next region to path to
|
||||
TradePath path(obj.owner);
|
||||
path.generate(getSystem(curRegion), getSystem(destRegion));
|
||||
|
||||
if(path.pathSize < 2 || !path.valid) {
|
||||
freeCivilian(obj);
|
||||
return 0.4;
|
||||
}
|
||||
else {
|
||||
@prevRegion = curRegion;
|
||||
@nextRegion = path.pathNode[1].object;
|
||||
awaitingIntermediate = true;
|
||||
@intermediate = null;
|
||||
if(curRegion.hasTradeStation(obj.owner))
|
||||
curRegion.getTradeStation(obj, obj.owner, obj.position);
|
||||
else if(cargoType == CT_Goods)
|
||||
curRegion.getTradePlanet(obj, obj.owner);
|
||||
else
|
||||
awaitingIntermediate = false;
|
||||
leavingRegion = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!leavingRegion) {
|
||||
if(intermediate !is null) {
|
||||
if(obj.moveTo(intermediate, moveId, distance=10.0, enterOrbit=false)) {
|
||||
moveId = -1;
|
||||
@intermediate = null;
|
||||
return 0.4;
|
||||
}
|
||||
else {
|
||||
return 0.2;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(moveId == -1 && !getSystem(prevRegion).isSpatialAdjacent(getSystem(nextRegion))) {
|
||||
leavingRegion = true;
|
||||
return 0.5;
|
||||
}
|
||||
vec3d leaveDest;
|
||||
if(prevRegion is null)
|
||||
leaveDest = obj.position;
|
||||
else
|
||||
leaveDest = prevRegion.position + (nextRegion.position - prevRegion.position).normalized(prevRegion.radius * 0.85) + random3d(0, DEST_RANGE);
|
||||
obj.maxAcceleration = ACC_SYSTEM;
|
||||
if(obj.moveTo(leaveDest, moveId, enterOrbit=false)) {
|
||||
moveId = -1;
|
||||
leavingRegion = true;
|
||||
return 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0.2;
|
||||
}
|
||||
|
||||
void setOrigin(Object@ origin) {
|
||||
@this.origin = origin;
|
||||
delta = true;
|
||||
}
|
||||
|
||||
void pathTo(Civilian& obj, Object@ origin, Object@ target, Object@ stopAt = null) {
|
||||
@pathTarget = target;
|
||||
@prevRegion = null;
|
||||
@nextRegion = null;
|
||||
@intermediate = stopAt;
|
||||
@this.origin = origin;
|
||||
pickedUp = false;
|
||||
delta = true;
|
||||
leavingRegion = false;
|
||||
}
|
||||
|
||||
void pathTo(Civilian& obj, Object@ target) {
|
||||
@pathTarget = target;
|
||||
@prevRegion = null;
|
||||
@nextRegion = null;
|
||||
@origin = null;
|
||||
@intermediate = null;
|
||||
pickedUp = true;
|
||||
delta = true;
|
||||
leavingRegion = false;
|
||||
}
|
||||
|
||||
void damage(Civilian& obj, DamageEvent& evt, double position, const vec2d& direction) {
|
||||
if(!obj.valid || obj.destroying)
|
||||
return;
|
||||
obj.engaged = true;
|
||||
Health = max(0.0, Health - evt.damage);
|
||||
delta = true;
|
||||
if(Health <= 0.0) {
|
||||
if(cargoWorth > 0) {
|
||||
Empire@ other = evt.obj.owner;
|
||||
if(other !is null && other.major) {
|
||||
other.addBonusBudget(cargoWorth);
|
||||
cargoWorth = 0;
|
||||
}
|
||||
}
|
||||
if(cargoResource !is null) {
|
||||
for(uint i = 0, cnt = cargoResource.hooks.length; i < cnt; ++i)
|
||||
cargoResource.hooks[i].onTradeDestroy(obj, origin, pathTarget, evt.obj);
|
||||
}
|
||||
obj.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void _writeDelta(const Civilian& obj, Message& msg) {
|
||||
msg.writeSmall(cargoType);
|
||||
msg.writeSmall(cargoWorth);
|
||||
msg.writeBit(pickedUp);
|
||||
msg.writeFixed(obj.health/obj.maxHealth);
|
||||
if(cargoResource !is null) {
|
||||
msg.write1();
|
||||
msg.writeLimited(cargoResource.id, getResourceCount()-1);
|
||||
}
|
||||
else {
|
||||
msg.write0();
|
||||
}
|
||||
}
|
||||
|
||||
void syncInitial(const Civilian& obj, Message& msg) {
|
||||
if(obj.hasMover) {
|
||||
msg.write1();
|
||||
obj.writeMover(msg);
|
||||
}
|
||||
else {
|
||||
msg.write0();
|
||||
}
|
||||
msg << type;
|
||||
_writeDelta(obj, msg);
|
||||
}
|
||||
|
||||
void syncDetailed(const Civilian& obj, Message& msg) {
|
||||
if(obj.hasMover) {
|
||||
msg.write1();
|
||||
obj.writeMover(msg);
|
||||
}
|
||||
else {
|
||||
msg.write0();
|
||||
}
|
||||
_writeDelta(obj, msg);
|
||||
}
|
||||
|
||||
bool syncDelta(const Civilian& obj, Message& msg) {
|
||||
bool used = false;
|
||||
if(obj.hasMover && obj.writeMoverDelta(msg))
|
||||
used = true;
|
||||
else
|
||||
msg.write0();
|
||||
if(delta) {
|
||||
used = true;
|
||||
delta = false;
|
||||
msg.write1();
|
||||
_writeDelta(obj, msg);
|
||||
}
|
||||
else {
|
||||
msg.write0();
|
||||
}
|
||||
return used;
|
||||
}
|
||||
};
|
||||
|
||||
void dumpPlanetWaitTimes() {
|
||||
uint cnt = playerEmpire.planetCount;
|
||||
double avg = 0.0, maxTime = 0.0;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
Planet@ pl = playerEmpire.planetList[i];
|
||||
if(pl !is null && pl.getNativeResourceDestination(playerEmpire, 0) !is null) {
|
||||
double timer = pl.getCivilianTimer();
|
||||
print(pl.name+" -- "+timer);
|
||||
avg += timer;
|
||||
if(timer > maxTime)
|
||||
maxTime = timer;
|
||||
}
|
||||
}
|
||||
avg /= double(cnt);
|
||||
print(" AVERAGE: "+avg);
|
||||
print(" MAX: "+maxTime);
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
from resources import MoneyType;
|
||||
import regions.regions;
|
||||
import saving;
|
||||
|
||||
tidy class ColonyShipScript {
|
||||
int moveId;
|
||||
|
||||
ColonyShipScript() {
|
||||
moveId = -1;
|
||||
}
|
||||
|
||||
void load(ColonyShip& obj, SaveFile& msg) {
|
||||
loadObjectStates(obj, msg);
|
||||
msg >> cast<Savable>(obj.Mover);
|
||||
@obj.Origin = msg.readObject();
|
||||
@obj.Target = msg.readObject();
|
||||
msg >> obj.CarriedPopulation;
|
||||
|
||||
if(msg < SV_0033 && obj.Target !is null)
|
||||
obj.Target.modIncomingPop(obj.CarriedPopulation);
|
||||
|
||||
msg >> moveId;
|
||||
|
||||
}
|
||||
|
||||
void makeMesh(ColonyShip& obj) {
|
||||
MeshDesc shipMesh;
|
||||
const Shipset@ ss = obj.owner.shipset;
|
||||
const ShipSkin@ skin;
|
||||
if(ss !is null)
|
||||
@skin = ss.getSkin("Colonizer");
|
||||
|
||||
if(obj.owner.ColonizerModel.length != 0) {
|
||||
@shipMesh.model = getModel(obj.owner.ColonizerModel);
|
||||
@shipMesh.material = getMaterial(obj.owner.ColonizerMaterial);
|
||||
}
|
||||
else if(skin !is null) {
|
||||
@shipMesh.model = skin.model;
|
||||
@shipMesh.material = skin.material;
|
||||
}
|
||||
else {
|
||||
@shipMesh.model = model::ColonyShip;
|
||||
@shipMesh.material = material::VolkurGenericPBR;
|
||||
}
|
||||
|
||||
@shipMesh.iconSheet = spritesheet::HullIcons;
|
||||
shipMesh.iconIndex = 0;
|
||||
|
||||
bindMesh(obj, shipMesh);
|
||||
}
|
||||
|
||||
void postLoad(ColonyShip& obj) {
|
||||
//Create the graphics
|
||||
makeMesh(obj);
|
||||
}
|
||||
|
||||
void save(ColonyShip& obj, SaveFile& msg) {
|
||||
saveObjectStates(obj, msg);
|
||||
msg << cast<Savable>(obj.Mover);
|
||||
msg << obj.Origin;
|
||||
msg << obj.Target;
|
||||
msg << obj.CarriedPopulation;
|
||||
|
||||
msg << moveId;
|
||||
}
|
||||
|
||||
void init(ColonyShip& ship) {
|
||||
//Create the graphics
|
||||
ship.sightRange = 0;
|
||||
makeMesh(ship);
|
||||
}
|
||||
|
||||
void postInit(ColonyShip& ship) {
|
||||
if(ship.Target !is null)
|
||||
ship.Target.modIncomingPop(ship.CarriedPopulation);
|
||||
if(ship.owner !is null && ship.owner.valid)
|
||||
ship.owner.modMaintenance(round(80.0 * ship.CarriedPopulation * ship.owner.ColonizerMaintFactor), MoT_Colonizers);
|
||||
}
|
||||
|
||||
bool onOwnerChange(ColonyShip& ship, Empire@ prevOwner) {
|
||||
if(prevOwner !is null && prevOwner.valid)
|
||||
prevOwner.modMaintenance(-round(80.0 * ship.CarriedPopulation * prevOwner.ColonizerMaintFactor), MoT_Colonizers);
|
||||
if(ship.owner !is null && ship.owner.valid)
|
||||
ship.owner.modMaintenance(round(80.0 * ship.CarriedPopulation * ship.owner.ColonizerMaintFactor), MoT_Colonizers);
|
||||
regionOwnerChange(ship, prevOwner);
|
||||
return false;
|
||||
}
|
||||
|
||||
void destroy(ColonyShip& ship) {
|
||||
if(ship.owner !is null && ship.owner.valid && ship.CarriedPopulation > 0)
|
||||
ship.owner.modMaintenance(-round(80.0 * ship.CarriedPopulation * ship.owner.ColonizerMaintFactor), MoT_Colonizers);
|
||||
if(ship.CarriedPopulation > 0 && ship.Origin !is null && ship.Target !is null) {
|
||||
if(ship.Origin.hasSurfaceComponent)
|
||||
ship.Origin.reducePopInTransit(ship.Target, ship.CarriedPopulation);
|
||||
ship.Target.modIncomingPop(-ship.CarriedPopulation);
|
||||
}
|
||||
leaveRegion(ship);
|
||||
}
|
||||
|
||||
double tick(ColonyShip& ship, double time) {
|
||||
Object@ target = ship.Target;
|
||||
|
||||
if(moveId == -1 && target is null)
|
||||
return 0.2;
|
||||
ship.moverTick(time);
|
||||
|
||||
updateRegion(ship);
|
||||
|
||||
if(target is null) {
|
||||
ship.destroy();
|
||||
}
|
||||
else if(ship.position.distanceTo(target.position) <= (ship.radius + target.radius + 0.1) || ship.moveTo(target, moveId, enterOrbit=false)) {
|
||||
ship.destroy();
|
||||
if(target.isPlanet) {
|
||||
target.colonyShipArrival(ship.owner, ship.CarriedPopulation);
|
||||
if(ship.Origin !is null && ship.Origin.hasSurfaceComponent)
|
||||
ship.Origin.reducePopInTransit(ship.Target, ship.CarriedPopulation);
|
||||
if(ship.owner !is null && ship.owner.valid)
|
||||
ship.owner.modMaintenance(-round(80.0 * ship.CarriedPopulation * ship.owner.ColonizerMaintFactor), MoT_Colonizers);
|
||||
ship.CarriedPopulation = 0;
|
||||
}
|
||||
}
|
||||
else if(target.isPlanet && ship.owner !is null && ship.owner.major && !target.isEmpireColonizing(ship.owner)) {
|
||||
if(ship.Origin !is null && ship.Origin.hasSurfaceComponent && ship.position.distanceTo(ship.Origin.position) < 3000.0)
|
||||
ship.destroy();
|
||||
}
|
||||
return 0.2;
|
||||
}
|
||||
|
||||
void damage(ColonyShip& ship, DamageEvent& evt, double position, const vec2d& direction) {
|
||||
ship.Health -= evt.damage;
|
||||
if(ship.Health <= 0)
|
||||
ship.destroy();
|
||||
}
|
||||
|
||||
void syncInitial(const ColonyShip& ship, Message& msg) {
|
||||
ship.writeMover(msg);
|
||||
}
|
||||
|
||||
void syncDetailed(const ColonyShip& ship, Message& msg) {
|
||||
ship.writeMover(msg);
|
||||
}
|
||||
|
||||
bool syncDelta(const ColonyShip& ship, Message& msg) {
|
||||
bool used = false;
|
||||
if(ship.writeMoverDelta(msg))
|
||||
used = true;
|
||||
else
|
||||
msg.write0();
|
||||
return used;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,170 @@
|
||||
from resources import MoneyType;
|
||||
import regions.regions;
|
||||
import saving;
|
||||
import statuses;
|
||||
import systems;
|
||||
|
||||
const int COLONY_SHIP_MAINTENANCE = 5;
|
||||
|
||||
tidy class FreighterScript {
|
||||
int moveId = -1;
|
||||
double idleTimer = 180.0;
|
||||
|
||||
FreighterScript() {
|
||||
}
|
||||
|
||||
void makeMesh(Freighter& obj) {
|
||||
MeshDesc shipMesh;
|
||||
const Shipset@ ss = obj.owner.shipset;
|
||||
const ShipSkin@ skin;
|
||||
if(ss !is null)
|
||||
@skin = ss.getSkin(obj.skin);
|
||||
|
||||
if(skin !is null) {
|
||||
@shipMesh.model = skin.model;
|
||||
@shipMesh.material = skin.material;
|
||||
}
|
||||
else {
|
||||
@shipMesh.model = model::Fighter;
|
||||
@shipMesh.material = material::Ship10;
|
||||
}
|
||||
|
||||
@shipMesh.iconSheet = spritesheet::HullIcons;
|
||||
shipMesh.iconIndex = 0;
|
||||
|
||||
bindMesh(obj, shipMesh);
|
||||
}
|
||||
|
||||
void load(Freighter& obj, SaveFile& msg) {
|
||||
loadObjectStates(obj, msg);
|
||||
msg >> cast<Savable>(obj.Mover);
|
||||
@obj.Origin = msg.readObject();
|
||||
@obj.Target = msg.readObject();
|
||||
|
||||
msg >> moveId;
|
||||
|
||||
if(msg >= SV_0109) {
|
||||
msg >> obj.StatusId;
|
||||
msg >> obj.StatusDuration;
|
||||
msg >> obj.SetOrigin;
|
||||
msg >> obj.MinLevel;
|
||||
}
|
||||
if(msg >= SV_0110)
|
||||
msg >> obj.VisitHostile;
|
||||
if(msg >= SV_0145)
|
||||
msg >> obj.skin;
|
||||
|
||||
//Create the graphics
|
||||
makeMesh(obj);
|
||||
}
|
||||
|
||||
void save(Freighter& obj, SaveFile& msg) {
|
||||
saveObjectStates(obj, msg);
|
||||
msg << cast<Savable>(obj.Mover);
|
||||
msg << obj.Origin;
|
||||
msg << obj.Target;
|
||||
|
||||
msg << moveId;
|
||||
|
||||
msg << obj.StatusId;
|
||||
msg << obj.StatusDuration;
|
||||
msg << obj.SetOrigin;
|
||||
msg << obj.MinLevel;
|
||||
msg << obj.VisitHostile;
|
||||
msg << obj.skin;
|
||||
}
|
||||
|
||||
void init(Freighter& ship) {
|
||||
ship.maxAcceleration = 2.5;
|
||||
}
|
||||
|
||||
void postInit(Freighter& ship) {
|
||||
makeMesh(ship);
|
||||
}
|
||||
|
||||
bool onOwnerChange(Freighter& ship, Empire@ prevOwner) {
|
||||
regionOwnerChange(ship, prevOwner);
|
||||
return false;
|
||||
}
|
||||
|
||||
void destroy(Freighter& ship) {
|
||||
leaveRegion(ship);
|
||||
}
|
||||
|
||||
double tick(Freighter& ship, double time) {
|
||||
Object@ target = ship.Target;
|
||||
ship.moverTick(time);
|
||||
updateRegion(ship);
|
||||
|
||||
if(target is null) {
|
||||
idleTimer -= time;
|
||||
if(ship.region !is null && idleTimer > 0.0) {
|
||||
Region@ lookIn = ship.region;
|
||||
bool local = randomi(0,1) == 0;
|
||||
if(!local) {
|
||||
SystemDesc@ desc = getSystem(lookIn.SystemId);
|
||||
if(desc.adjacent.length > 0)
|
||||
@lookIn = getSystem(desc.adjacent[randomi(0, desc.adjacent.length-1)]).object;
|
||||
}
|
||||
|
||||
uint plCount = lookIn.planetCount;
|
||||
if(plCount > 0) {
|
||||
Planet@ targ = lookIn.planets[randomi(0, plCount-1)];
|
||||
if(targ !is null && targ.owner.valid && (ship.VisitHostile || !ship.owner.isHostile(targ.owner)) && targ.level >= uint(ship.MinLevel)) {
|
||||
@target = targ;
|
||||
@ship.Target = targ;
|
||||
ship.moveTo(target, moveId, enterOrbit=false);
|
||||
}
|
||||
}
|
||||
|
||||
return 0.2;
|
||||
}
|
||||
else {
|
||||
ship.destroy();
|
||||
return 0.2;
|
||||
}
|
||||
}
|
||||
|
||||
if(!target.owner.valid || (!ship.VisitHostile && ship.owner.isHostile(target.owner))) {
|
||||
@target = null;
|
||||
@ship.Target = null;
|
||||
}
|
||||
else if(ship.position.distanceTo(target.position) <= (ship.radius + target.radius + 0.1) || ship.moveTo(target, moveId, enterOrbit=false)) {
|
||||
if(target.isPlanet) {
|
||||
int status = ship.StatusId;
|
||||
if(status == -1)
|
||||
status = getStatusID("Happy");
|
||||
Empire@ originEmp;
|
||||
if(ship.SetOrigin)
|
||||
@originEmp = ship.owner;
|
||||
target.addStatus(status, ship.StatusDuration, originEmpire=originEmp);
|
||||
}
|
||||
ship.destroy();
|
||||
}
|
||||
return 0.2;
|
||||
}
|
||||
|
||||
void damage(Freighter& ship, DamageEvent& evt, double position, const vec2d& direction) {
|
||||
ship.Health -= evt.damage;
|
||||
if(ship.Health <= 0)
|
||||
ship.destroy();
|
||||
}
|
||||
|
||||
void syncInitial(const Freighter& ship, Message& msg) {
|
||||
msg << ship.skin;
|
||||
ship.writeMover(msg);
|
||||
}
|
||||
|
||||
void syncDetailed(const Freighter& ship, Message& msg) {
|
||||
ship.writeMover(msg);
|
||||
}
|
||||
|
||||
bool syncDelta(const Freighter& ship, Message& msg) {
|
||||
bool used = false;
|
||||
if(ship.writeMoverDelta(msg))
|
||||
used = true;
|
||||
else
|
||||
msg.write0();
|
||||
return used;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,355 @@
|
||||
import regions.regions;
|
||||
import saving;
|
||||
import oddity_navigation;
|
||||
import systems;
|
||||
import oddities;
|
||||
|
||||
void createSlipstream(const vec3d& from, const vec3d& to, double timer = -1.0, Empire@ revealEmp = null) {
|
||||
ObjectDesc desc;
|
||||
desc.type = OT_Oddity;
|
||||
desc.radius = 15.0;
|
||||
desc.position = from;
|
||||
desc.flags |= objNoPhysics;
|
||||
desc.flags |= objNoDamage;
|
||||
if(revealEmp !is null)
|
||||
@desc.owner = revealEmp;
|
||||
desc.name = locale::SLIPSTREAM_TEAR;
|
||||
|
||||
auto@ input = Oddity(desc);
|
||||
input.noCollide = true;
|
||||
|
||||
desc.position = to;
|
||||
auto@ output = Oddity(desc);
|
||||
output.noCollide = true;
|
||||
|
||||
input.linkTo(output);
|
||||
output.linkTo(input);
|
||||
|
||||
input.setGate(true);
|
||||
output.setGate(true);
|
||||
|
||||
input.setTimer(timer);
|
||||
output.setTimer(timer);
|
||||
|
||||
input.makeVisuals(Odd_Slipstream);
|
||||
output.makeVisuals(Odd_Slipstream);
|
||||
|
||||
input.linkVision(true);
|
||||
output.linkVision(true);
|
||||
}
|
||||
|
||||
void createWormhole(SystemDesc@ from, SystemDesc@ to) {
|
||||
vec3d fromPos = from.position;
|
||||
vec2d offset = random2d(100.0, from.radius - 100.0);
|
||||
fromPos.x += offset.x;
|
||||
fromPos.z += offset.y;
|
||||
|
||||
vec3d toPos = to.position;
|
||||
offset = random2d(100.0, to.radius - 100.0);
|
||||
toPos.x += offset.x;
|
||||
toPos.z += offset.y;
|
||||
|
||||
ObjectDesc desc;
|
||||
desc.type = OT_Oddity;
|
||||
desc.radius = 25.0;
|
||||
desc.position = fromPos;
|
||||
desc.flags |= objNoPhysics;
|
||||
desc.flags |= objNoDamage;
|
||||
desc.name = locale::WORMHOLE;
|
||||
|
||||
auto@ input = Oddity(desc);
|
||||
input.noCollide = true;
|
||||
|
||||
desc.position = toPos;
|
||||
auto@ output = Oddity(desc);
|
||||
output.noCollide = true;
|
||||
|
||||
input.linkTo(output);
|
||||
output.linkTo(input);
|
||||
|
||||
input.setGate(true);
|
||||
output.setGate(true);
|
||||
|
||||
input.makeVisuals(Odd_Wormhole);
|
||||
output.makeVisuals(Odd_Wormhole);
|
||||
|
||||
input.linkVision(true);
|
||||
output.linkVision(true);
|
||||
|
||||
input.setSuperior(true);
|
||||
output.setSuperior(true);
|
||||
}
|
||||
|
||||
Oddity@ createNebula(const vec3d& position, double radius, uint color = 0xf0c87040, Region@ region = null) {
|
||||
ObjectDesc desc;
|
||||
desc.type = OT_Oddity;
|
||||
desc.radius = radius;
|
||||
desc.position = position;
|
||||
desc.flags |= objNoPhysics;
|
||||
desc.flags |= objNoDamage;
|
||||
|
||||
auto@ nebula = Oddity(desc);
|
||||
if(region !is null) {
|
||||
@nebula.region = region;
|
||||
region.enterRegion(nebula);
|
||||
}
|
||||
nebula.noCollide = true;
|
||||
nebula.makeVisuals(Odd_Nebula, color=color);
|
||||
|
||||
return nebula;
|
||||
}
|
||||
|
||||
tidy class OddityScript {
|
||||
StrategicIconNode@ icon;
|
||||
bool gate = false;
|
||||
bool superior = false;
|
||||
double timer = -1.0;
|
||||
uint visualType = uint(-1);
|
||||
uint visualColor = 0xffffffff;
|
||||
Object@ link;
|
||||
|
||||
bool visionLinked = false;
|
||||
Region@ visionRegion;
|
||||
uint visionMask = 0;
|
||||
uint prevMemory = 0;
|
||||
|
||||
bool delta = false;
|
||||
|
||||
void save(Oddity& obj, SaveFile& file) {
|
||||
saveObjectStates(obj, file);
|
||||
file << gate;
|
||||
file << timer;
|
||||
file << visualType;
|
||||
file << link;
|
||||
file << visionLinked;
|
||||
file << visionRegion;
|
||||
file << visionMask;
|
||||
file << superior;
|
||||
file << prevMemory;
|
||||
file << visualColor;
|
||||
}
|
||||
|
||||
void load(Oddity& obj, SaveFile& file) {
|
||||
loadObjectStates(obj, file);
|
||||
file >> gate;
|
||||
file >> timer;
|
||||
file >> visualType;
|
||||
file >> link;
|
||||
file >> visionLinked;
|
||||
file >> visionRegion;
|
||||
file >> visionMask;
|
||||
if(file >= SV_0020)
|
||||
file >> superior;
|
||||
if(file >= SV_0067)
|
||||
file >> prevMemory;
|
||||
if(file >= SV_0099)
|
||||
file >> visualColor;
|
||||
|
||||
makeVisuals(obj, visualType, fromCreation=false, color=visualColor);
|
||||
if(gate)
|
||||
addOddityGate(obj);
|
||||
}
|
||||
|
||||
uint getVisualType() {
|
||||
return visualType;
|
||||
}
|
||||
|
||||
uint getVisualColor() {
|
||||
return visualColor;
|
||||
}
|
||||
|
||||
bool isGate() {
|
||||
return gate;
|
||||
}
|
||||
|
||||
vec3d getGateDest() {
|
||||
if(link !is null)
|
||||
return link.position;
|
||||
return vec3d();
|
||||
}
|
||||
|
||||
Object@ getLink() {
|
||||
return link;
|
||||
}
|
||||
|
||||
double getTimer() {
|
||||
return timer;
|
||||
}
|
||||
|
||||
void setGate(Oddity& obj, bool value) {
|
||||
if(value == gate)
|
||||
return;
|
||||
gate = value;
|
||||
if(gate)
|
||||
addOddityGate(obj);
|
||||
else
|
||||
removeOddityGate(obj);
|
||||
delta = true;
|
||||
}
|
||||
|
||||
void linkVision(bool value) {
|
||||
visionLinked = value;
|
||||
delta = true;
|
||||
}
|
||||
|
||||
void linkTo(Oddity& obj, Object@ other) {
|
||||
@link = other;
|
||||
obj.rotation = quaterniond_fromVecToVec(vec3d_front(), (other.position - obj.position).normalized(), vec3d_up());
|
||||
delta = true;
|
||||
}
|
||||
|
||||
void setTimer(double value) {
|
||||
timer = value;
|
||||
delta = true;
|
||||
}
|
||||
|
||||
void setSuperior(bool value) {
|
||||
superior = value;
|
||||
}
|
||||
|
||||
vec3d get_strategicIconPosition(Oddity& obj) {
|
||||
if(icon is null)
|
||||
return obj.position;
|
||||
return icon.position;
|
||||
}
|
||||
|
||||
void makeVisuals(Oddity& obj, uint type, bool fromCreation = true, uint color = 0xffffffff) {
|
||||
visualType = type;
|
||||
visualColor = color;
|
||||
@icon = makeOddityVisuals(obj, type, fromCreation, color=color);
|
||||
}
|
||||
|
||||
void destroy(Oddity& obj) {
|
||||
if(obj.region !is null)
|
||||
obj.region.removeStrategicIcon(-1, icon);
|
||||
if(icon !is null)
|
||||
icon.markForDeletion();
|
||||
leaveRegion(obj);
|
||||
updateVision(visionRegion, visionMask, 0);
|
||||
if(gate)
|
||||
removeOddityGate(obj);
|
||||
removeAmbientSource(CURRENT_PLAYER, obj.id);
|
||||
}
|
||||
|
||||
void updateVision(Region@ onRegion, uint fromMask, uint toMask) {
|
||||
if(onRegion is null)
|
||||
return;
|
||||
if(fromMask == toMask)
|
||||
return;
|
||||
|
||||
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
|
||||
Empire@ emp = getEmpire(i);
|
||||
if(fromMask & emp.mask != 0) {
|
||||
if(toMask & emp.mask == 0)
|
||||
onRegion.revokeVision(emp);
|
||||
}
|
||||
else {
|
||||
if(toMask & emp.mask != 0)
|
||||
onRegion.grantVision(emp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double tick(Oddity& obj, double time) {
|
||||
//Handle region changes
|
||||
Region@ prevRegion = obj.region;
|
||||
if(updateRegion(obj)) {
|
||||
Region@ newRegion = obj.region;
|
||||
if(icon !is null) {
|
||||
if(prevRegion !is null)
|
||||
prevRegion.removeStrategicIcon(-1, icon);
|
||||
if(newRegion !is null)
|
||||
newRegion.addStrategicIcon(-1, obj, icon);
|
||||
}
|
||||
updateVision(visionRegion, visionMask, 0);
|
||||
visionMask = 0;
|
||||
@visionRegion = null;
|
||||
@prevRegion = newRegion;
|
||||
}
|
||||
|
||||
if(icon !is null)
|
||||
icon.visible = obj.isVisibleTo(playerEmpire);
|
||||
|
||||
if(link !is null && !link.valid)
|
||||
@link = null;
|
||||
|
||||
//Handle vision sharing
|
||||
Region@ newRegion;
|
||||
uint newMask = 0;
|
||||
if(visionLinked && link !is null) {
|
||||
@newRegion = link.region;
|
||||
if(prevRegion is null)
|
||||
obj.donatedVision |= link.visibleMask;
|
||||
}
|
||||
if(prevRegion !is null)
|
||||
newMask = prevRegion.BasicVisionMask;
|
||||
|
||||
if(newRegion !is visionRegion) {
|
||||
updateVision(visionRegion, visionMask, 0);
|
||||
updateVision(newRegion, 0, newMask);
|
||||
@visionRegion = newRegion;
|
||||
visionMask = newMask;
|
||||
}
|
||||
else {
|
||||
updateVision(visionRegion, visionMask, newMask);
|
||||
visionMask = newMask;
|
||||
}
|
||||
|
||||
//Handle memories
|
||||
if(obj.memoryMask != prevMemory) {
|
||||
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
|
||||
auto@ emp = getEmpire(i);
|
||||
if((prevMemory & emp.mask) != (obj.memoryMask & emp.mask))
|
||||
emp.PathId += 1;
|
||||
}
|
||||
prevMemory = obj.memoryMask;
|
||||
}
|
||||
|
||||
//Kill the oddity gate if the region blocks ftl at all
|
||||
if(gate && !superior && prevRegion !is null) {
|
||||
if(prevRegion.BlockFTLMask.value & obj.owner.mask != 0) {
|
||||
if(link !is null)
|
||||
link.destroy();
|
||||
obj.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
//Handle timer
|
||||
if(timer >= 0.0) {
|
||||
timer -= time;
|
||||
if(timer <= 0.0)
|
||||
obj.destroy();
|
||||
}
|
||||
|
||||
return 0.25;
|
||||
}
|
||||
|
||||
void _write(const Oddity& obj, Message& msg) {
|
||||
msg << gate;
|
||||
msg << timer;
|
||||
msg << visualType;
|
||||
msg << visualColor;
|
||||
msg << link;
|
||||
}
|
||||
|
||||
void syncInitial(const Oddity& obj, Message& msg) {
|
||||
_write(obj, msg);
|
||||
}
|
||||
|
||||
bool syncDelta(const Oddity& obj, Message& msg) {
|
||||
bool used = false;
|
||||
if(delta) {
|
||||
used = true;
|
||||
delta = false;
|
||||
msg.write1();
|
||||
_write(obj, msg);
|
||||
}
|
||||
else
|
||||
msg.write0();
|
||||
return used;
|
||||
}
|
||||
|
||||
void syncDetailed(const Oddity& obj, Message& msg) {
|
||||
_write(obj, msg);
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,214 @@
|
||||
import pickups;
|
||||
import camps;
|
||||
import saving;
|
||||
import regions.regions;
|
||||
import attributes;
|
||||
|
||||
tidy class PickupScript {
|
||||
void postInit(Pickup& obj) {
|
||||
obj.initPickup();
|
||||
}
|
||||
|
||||
void syncInitial(const Pickup& obj, Message& msg) {
|
||||
msg << obj.PickupType;
|
||||
obj.writePickup(msg);
|
||||
}
|
||||
|
||||
bool syncDelta(const Pickup& obj, Message& msg) {
|
||||
if(obj.claimPickupDelta()) {
|
||||
obj.writePickup(msg);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void destroy(Pickup& obj) {
|
||||
leaveRegion(obj);
|
||||
}
|
||||
|
||||
void load(Pickup& obj, SaveFile& msg) {
|
||||
cast<PickupControl>(obj.PickupControl).load(obj, msg);
|
||||
}
|
||||
|
||||
void postLoad(Pickup& obj) {
|
||||
cast<PickupControl>(obj.PickupControl).postLoad(obj);
|
||||
}
|
||||
|
||||
void save(Pickup& obj, SaveFile& msg) {
|
||||
cast<PickupControl>(obj.PickupControl).save(obj, msg);
|
||||
}
|
||||
|
||||
double tick(Pickup& obj, double time) {
|
||||
updateRegion(obj);
|
||||
obj.tickPickup(time);
|
||||
return 0.5;
|
||||
}
|
||||
};
|
||||
|
||||
tidy class PickupControl : Component_PickupControl {
|
||||
const PickupType@ type;
|
||||
Object@[] protectors;
|
||||
vec3d offset;
|
||||
bool pickupDelta = false;
|
||||
|
||||
const CampType@ campType;
|
||||
|
||||
PickupControl() {
|
||||
}
|
||||
|
||||
void generateMesh(Object& obj) {
|
||||
MeshDesc mesh;
|
||||
@mesh.model = type.model;
|
||||
@mesh.material = type.material;
|
||||
@mesh.iconSheet = type.iconSheet;
|
||||
mesh.iconIndex = type.iconIndex;
|
||||
mesh.memorable = true;
|
||||
bindMesh(obj, mesh);
|
||||
|
||||
Node@ node = obj.getNode();
|
||||
node.customColor = true;
|
||||
node.color = Color(0x998888ff);
|
||||
}
|
||||
|
||||
void setCampType(uint id) {
|
||||
@campType = getCreepCamp(id);
|
||||
}
|
||||
|
||||
void load(Pickup& obj, SaveFile& msg) {
|
||||
uint tid = msg.readIdentifier(SI_Pickup);
|
||||
@type = getPickupType(tid);
|
||||
|
||||
uint cnt = 0;
|
||||
msg >> cnt;
|
||||
protectors.length = cnt;
|
||||
for(uint i = 0; i < cnt; ++i)
|
||||
msg >> protectors[i];
|
||||
|
||||
obj.PickupType = type.id;
|
||||
generateMesh(obj);
|
||||
|
||||
if(msg >= SV_0095) {
|
||||
if(msg.readBit())
|
||||
@campType = getCreepCamp(msg.readIdentifier(SI_CreepCamp));
|
||||
}
|
||||
}
|
||||
|
||||
void postLoad(Pickup& obj) {
|
||||
if(protectors.length != 0)
|
||||
offset = protectors[0].position - obj.position;
|
||||
}
|
||||
|
||||
void save(Pickup& obj, SaveFile& msg) {
|
||||
msg.writeIdentifier(SI_Pickup, type.id);
|
||||
uint cnt = protectors.length;
|
||||
msg << cnt;
|
||||
for(uint i = 0; i < cnt; ++i)
|
||||
msg << protectors[i];
|
||||
|
||||
msg.writeBit(campType !is null);
|
||||
if(campType !is null)
|
||||
msg.writeIdentifier(SI_CreepCamp, campType.id);
|
||||
}
|
||||
|
||||
void initPickup(Object& obj) {
|
||||
Pickup@ pickup = cast<Pickup>(obj);
|
||||
@type = getPickupType(pickup.PickupType);
|
||||
|
||||
pickup.name = type.getName(pickup);
|
||||
pickup.radius = type.physicalSize;
|
||||
generateMesh(obj);
|
||||
}
|
||||
|
||||
bool claimPickupDelta() {
|
||||
if(pickupDelta) {
|
||||
pickupDelta = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool get_isPickupProtected() {
|
||||
return protectors.length != 0;
|
||||
}
|
||||
|
||||
void addPickupProtector(Object& obj, Object& protector) {
|
||||
protectors.insertLast(protector);
|
||||
if(protectors.length == 1)
|
||||
offset = protectors[0].position - obj.position;
|
||||
}
|
||||
|
||||
Object@ getProtector() {
|
||||
if(protectors.length == 0)
|
||||
return null;
|
||||
return protectors[0];
|
||||
}
|
||||
|
||||
void tickPickup(Object& pickup, double time) {
|
||||
bool wasProtected = protectors.length != 0;
|
||||
if(protectors.length != 0) {
|
||||
auto@ prot = protectors[0];
|
||||
pickup.position = prot.position + offset;
|
||||
pickup.velocity = prot.velocity;
|
||||
pickup.acceleration = prot.acceleration;
|
||||
}
|
||||
else {
|
||||
pickup.velocity = vec3d();
|
||||
pickup.acceleration = vec3d();
|
||||
}
|
||||
|
||||
Object@ lastHit;
|
||||
for(uint i = 0, cnt = protectors.length; i < cnt; ++i) {
|
||||
Object@ prot = protectors[i];
|
||||
if(!prot.valid || prot.owner.major) {
|
||||
protectors.removeAt(i);
|
||||
pickupDelta = true;
|
||||
--i; --cnt;
|
||||
if(cnt == 0) {
|
||||
if(prot.isShip)
|
||||
@lastHit = getObjectByID(cast<Ship>(prot).lastHit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(wasProtected && protectors.length == 0) {
|
||||
if(gameTime <= 5.0) {
|
||||
pickup.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
if(lastHit !is null) {
|
||||
Empire@ hitEmp = lastHit.owner;
|
||||
if(hitEmp !is null)
|
||||
hitEmp.modAttribute(EA_RemnantsCleared, AC_Add, 1.0);
|
||||
|
||||
if(lastHit.hasSupportAI)
|
||||
@lastHit = cast<Ship>(lastHit).Leader;
|
||||
if(lastHit !is null)
|
||||
type.onClear(cast<Pickup>(pickup), lastHit);
|
||||
}
|
||||
|
||||
Region@ reg = pickup.region;
|
||||
if(campType !is null && reg !is null) {
|
||||
for(uint i = 0, cnt = campType.region_statuses.length; i < cnt; ++i)
|
||||
reg.removeRegionStatus(null, campType.region_statuses[i].id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pickupPickup(Object& obj, Object& by) {
|
||||
Pickup@ pickup = cast<Pickup>(obj);
|
||||
if(!type.canPickup(pickup, by))
|
||||
return;
|
||||
if(isPickupProtected)
|
||||
return;
|
||||
type.onPickup(pickup, by);
|
||||
obj.destroy();
|
||||
}
|
||||
|
||||
void writePickup(Message& msg) {
|
||||
uint cnt = protectors.length;
|
||||
msg << cnt;
|
||||
for(uint i = 0; i < cnt; ++i)
|
||||
msg << protectors[i];
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,567 @@
|
||||
import object_creation;
|
||||
import regions.regions;
|
||||
import planet_types;
|
||||
import saving;
|
||||
import util.target_search;
|
||||
import cargo;
|
||||
from objects.Asteroid import createAsteroid;
|
||||
|
||||
tidy class MoonData {
|
||||
uint style = 0;
|
||||
float size = 0.f;
|
||||
};
|
||||
|
||||
tidy class PlanetScript {
|
||||
float timer = 0.f;
|
||||
float bonusSupply = 0.0;
|
||||
float bonusPop = 0.0;
|
||||
bool hpDelta = false;
|
||||
uint ringStyle = 0;
|
||||
array<MoonData@>@ moons;
|
||||
|
||||
void init(Planet& planet) {
|
||||
timer = -float(uint8(planet.id)) / 255.0;
|
||||
planet.owner.recordStatDelta(stat::Planets, 1);
|
||||
planet.canBuildShips = true;
|
||||
planet.canBuildOrbitals = true;
|
||||
planet.leaderInit();
|
||||
}
|
||||
|
||||
void postInit(Planet& planet) {
|
||||
planet.modCargoStorage(+INFINITY);
|
||||
if(planet.owner !is null) {
|
||||
planet.owner.TotalPlanets += 1;
|
||||
planet.owner.registerPlanet(planet);
|
||||
}
|
||||
}
|
||||
|
||||
void save(Planet& planet, SaveFile& file) {
|
||||
saveObjectStates(planet, file);
|
||||
file << cast<Savable>(planet.Orbit);
|
||||
file << cast<Savable>(planet.Construction);
|
||||
file << cast<Savable>(planet.SurfaceComponent);
|
||||
file << cast<Savable>(planet.Resources);
|
||||
file << cast<Savable>(planet.LeaderAI);
|
||||
file << cast<Savable>(planet.Statuses);
|
||||
file << cast<Savable>(planet.Cargo);
|
||||
file << planet.ResearchRate;
|
||||
file << planet.OrbitSize;
|
||||
file << planet.Population;
|
||||
file << planet.renamed;
|
||||
file << bonusSupply << bonusPop;
|
||||
file << planet.Health << planet.MaxHealth;
|
||||
file << ringStyle;
|
||||
file.writeIdentifier(SI_PlanetType, planet.PlanetType);
|
||||
|
||||
file << planet.hasAbilities;
|
||||
if(planet.hasAbilities)
|
||||
file << cast<Savable>(planet.Abilities);
|
||||
|
||||
if(moons !is null && moons.length != 0) {
|
||||
file.write1();
|
||||
file << moons.length;
|
||||
for(uint i = 0, cnt = moons.length; i < cnt; ++i) {
|
||||
file << moons[i].size;
|
||||
file << moons[i].style;
|
||||
}
|
||||
}
|
||||
else {
|
||||
file.write0();
|
||||
}
|
||||
|
||||
if(planet.hasMover) {
|
||||
file.write1();
|
||||
file << cast<Savable>(planet.Mover);
|
||||
}
|
||||
else {
|
||||
file.write0();
|
||||
}
|
||||
}
|
||||
|
||||
void load(Planet& planet, SaveFile& file) {
|
||||
timer = -float(uint8(planet.id)) / 255.0;
|
||||
loadObjectStates(planet, file);
|
||||
file >> cast<Savable>(planet.Orbit);
|
||||
file >> cast<Savable>(planet.Construction);
|
||||
file >> cast<Savable>(planet.SurfaceComponent);
|
||||
file >> cast<Savable>(planet.Resources);
|
||||
file >> cast<Savable>(planet.LeaderAI);
|
||||
file >> cast<Savable>(planet.Statuses);
|
||||
if(file >= SV_0122)
|
||||
file >> cast<Savable>(planet.Cargo);
|
||||
else
|
||||
planet.modCargoStorage(+INFINITY);
|
||||
file >> planet.ResearchRate;
|
||||
file >> planet.OrbitSize;
|
||||
file >> planet.Population;
|
||||
file >> planet.renamed;
|
||||
file >> bonusSupply >> bonusPop;
|
||||
if(file >= SV_0083) {
|
||||
if(file >= SV_0102) {
|
||||
file >> planet.Health >> planet.MaxHealth;
|
||||
}
|
||||
else {
|
||||
float hp = 0, maxhp = 0;
|
||||
file >> hp >> maxhp;
|
||||
planet.Health = hp;
|
||||
planet.MaxHealth = maxhp;
|
||||
}
|
||||
}
|
||||
if(file >= SV_0086)
|
||||
file >> ringStyle;
|
||||
planet.PlanetType = file.readIdentifier(SI_PlanetType);
|
||||
|
||||
if(planet.PlanetType == -1)
|
||||
planet.PlanetType = 0;
|
||||
|
||||
bool hasAbl = false;
|
||||
file >> hasAbl;
|
||||
if(hasAbl) {
|
||||
planet.activateAbilities();
|
||||
file >> cast<Savable>(planet.Abilities);
|
||||
}
|
||||
|
||||
PlanetNode@ plNode = cast<PlanetNode>(bindNode(planet, "PlanetNode"));
|
||||
plNode.establish(planet);
|
||||
plNode.planetType = planet.PlanetType;
|
||||
plNode.colonized = planet.owner.valid;
|
||||
plNode.flags = planet.planetGraphicsFlags;
|
||||
if(ringStyle != 0)
|
||||
plNode.addRing(ringStyle);
|
||||
|
||||
if(file >= SV_0110) {
|
||||
if(file.readBit()) {
|
||||
if(moons is null)
|
||||
@moons = array<MoonData@>();
|
||||
|
||||
uint cnt = 0;
|
||||
file >> cnt;
|
||||
moons.length = cnt;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
MoonData dat;
|
||||
file >> dat.size;
|
||||
file >> dat.style;
|
||||
@moons[i] = dat;
|
||||
|
||||
if(plNode !is null)
|
||||
plNode.addMoon(dat.size, dat.style);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(file >= SV_0133) {
|
||||
if(file.readBit()) {
|
||||
planet.activateMover();
|
||||
file >> cast<Savable>(planet.Mover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void postLoad(Planet& planet) {
|
||||
planet.resourcesPostLoad();
|
||||
planet.surfacePostLoad();
|
||||
planet.leaderPostLoad();
|
||||
|
||||
Node@ node = planet.getNode();
|
||||
if(node !is null)
|
||||
node.hintParentObject(planet.region, false);
|
||||
}
|
||||
|
||||
bool quietDestruction = false;
|
||||
void destroyQuiet(Planet& planet) {
|
||||
quietDestruction = true;
|
||||
planet.destroy();
|
||||
}
|
||||
|
||||
void destroy(Planet& planet) {
|
||||
if(!game_ending && !quietDestruction) {
|
||||
playParticleSystem("PlanetExplosion", planet.position, planet.rotation, planet.radius, planet.visibleMask);
|
||||
|
||||
double totChance = config::ASTEROID_OCCURANCE + config::RESOURCE_ASTEROID_OCCURANCE;
|
||||
double resChance = config::RESOURCE_ASTEROID_OCCURANCE;
|
||||
if(totChance > 0) {
|
||||
for(uint i = 0, cnt = randomi(0,4); i < cnt; ++i) {
|
||||
vec3d pos = planet.position;
|
||||
pos += random3d(80 + planet.radius);
|
||||
|
||||
Asteroid@ roid = createAsteroid(pos);
|
||||
Region@ reg = planet.region;
|
||||
if(reg !is null) {
|
||||
roid.orbitAround(reg.position);
|
||||
roid.orbitSpin(randomd(20.0, 60.0));
|
||||
}
|
||||
|
||||
double roll = randomd(0, totChance);
|
||||
|
||||
if(roll >= resChance) {
|
||||
auto@ cargo = getCargoType("Ore");
|
||||
if(cargo !is null)
|
||||
roid.addCargo(cargo.id, randomd(500, 5000));
|
||||
}
|
||||
else {
|
||||
do {
|
||||
const ResourceType@ type = getDistributedAsteroidResource();
|
||||
roid.addAvailable(type.id, type.asteroidCost);
|
||||
}
|
||||
while(randomd() < 0.4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
planet.destroyConstruction();
|
||||
planet.destroyObjResources();
|
||||
planet.destroySurface();
|
||||
planet.leaderDestroy();
|
||||
planet.destroyStatus();
|
||||
leaveRegion(planet);
|
||||
|
||||
if(planet.owner !is null) {
|
||||
planet.owner.recordStatDelta(stat::Planets, -1);
|
||||
planet.owner.TotalPlanets -= 1;
|
||||
planet.owner.unregisterPlanet(planet);
|
||||
}
|
||||
}
|
||||
|
||||
bool onOwnerChange(Planet& planet, Empire@ prevOwner) {
|
||||
if(prevOwner !is null) {
|
||||
prevOwner.recordStatDelta(stat::Planets, -1);
|
||||
prevOwner.TotalPlanets -= 1;
|
||||
prevOwner.unregisterPlanet(planet);
|
||||
}
|
||||
if(planet.owner !is null) {
|
||||
planet.owner.recordStatDelta(stat::Planets, 1);
|
||||
planet.owner.TotalPlanets += 1;
|
||||
planet.owner.registerPlanet(planet);
|
||||
}
|
||||
planet.clearRally();
|
||||
if(planet.hasAbilities)
|
||||
planet.abilityOwnerChange(prevOwner, planet.owner);
|
||||
planet.changeSurfaceOwner(prevOwner);
|
||||
planet.changeResourceOwner(prevOwner);
|
||||
planet.changeStatusOwner(prevOwner, planet.owner);
|
||||
regionOwnerChange(planet, prevOwner);
|
||||
planet.leaderChangeOwner(prevOwner, planet.owner);
|
||||
|
||||
if(!planet.hasMover && planet.owner.hasFlingBeacons) {
|
||||
planet.activateMover();
|
||||
planet.maxAcceleration = 0;
|
||||
}
|
||||
|
||||
auto@ node = cast<PlanetNode>(planet.getNode());
|
||||
if(node !is null && planet.owner !is null)
|
||||
node.colonized = planet.owner.valid;
|
||||
return false;
|
||||
}
|
||||
|
||||
void occasional_tick(Planet& obj) {
|
||||
Region@ region = obj.region;
|
||||
bool engaged = obj.engaged;
|
||||
obj.inCombat = engaged;
|
||||
obj.engaged = false;
|
||||
|
||||
if(engaged && region !is null)
|
||||
region.EngagedMask |= obj.owner.mask;
|
||||
|
||||
float newSupply = obj.owner.PlanetSupplyMod + obj.owner.PlanetLevelSupport * float(obj.level);
|
||||
if(newSupply != bonusSupply) {
|
||||
obj.modSupplyCapacity(newSupply - bonusSupply);
|
||||
bonusSupply = newSupply;
|
||||
}
|
||||
|
||||
float newPop = 0.f;
|
||||
uint lev = obj.level;
|
||||
if(lev == 2)
|
||||
newPop += obj.owner.PopulationLevel2Mod;
|
||||
else if(lev >= 3)
|
||||
newPop += obj.owner.PopulationLevel3Mod;
|
||||
if(int(newPop) != int(bonusPop)) {
|
||||
obj.modMaxPopulation(int(newPop) - int(bonusPop));
|
||||
bonusPop = newPop;
|
||||
}
|
||||
|
||||
if(!obj.hasMover && obj.owner.hasFlingBeacons) {
|
||||
obj.activateMover();
|
||||
obj.maxAcceleration = 0;
|
||||
}
|
||||
|
||||
//Order support ships to attack
|
||||
if(engaged) {
|
||||
if(obj.supportCount > 0) {
|
||||
Object@ target = findEnemy(obj, obj, obj.owner, obj.position, 700.0);
|
||||
if(target !is null) {
|
||||
//Always target the fleet as a whole
|
||||
{
|
||||
Ship@ othership = cast<Ship>(target);
|
||||
if(othership !is null) {
|
||||
Object@ leader = othership.Leader;
|
||||
if(leader !is null)
|
||||
@target = leader;
|
||||
}
|
||||
}
|
||||
|
||||
//Order a random support to assist
|
||||
uint cnt = obj.supportCount;
|
||||
if(cnt > 0) {
|
||||
uint attackWith = max(1, cnt / 8);
|
||||
for(uint i = 0, off = randomi(0,cnt-1); i < attackWith; ++i) {
|
||||
Object@ sup = obj.supportShip[(i+off) % cnt];
|
||||
if(sup !is null)
|
||||
sup.supportAttack(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
obj.updateFleetStrength();
|
||||
}
|
||||
|
||||
double tick(Planet& planet, double time) {
|
||||
//Update region
|
||||
Region@ prevRegion = planet.region;
|
||||
if(updateRegion(planet)) {
|
||||
Node@ node = planet.getNode();
|
||||
if(node !is null)
|
||||
node.hintParentObject(planet.region, false);
|
||||
planet.changeResourceRegion(prevRegion, planet.region);
|
||||
planet.changeSurfaceRegion(prevRegion, planet.region);
|
||||
planet.changeStatusRegion(prevRegion, planet.region);
|
||||
@prevRegion = planet.region;
|
||||
}
|
||||
|
||||
//Take vision from region
|
||||
if(prevRegion !is null)
|
||||
planet.donatedVision |= prevRegion.DonateVisionMask;
|
||||
else
|
||||
planet.donatedVision |= ~0;
|
||||
|
||||
//Tick components
|
||||
if(planet.hasMover) {
|
||||
planet.moverTick(time);
|
||||
|
||||
if(prevRegion is null && isOutsideUniverseExtents(planet.position))
|
||||
limitToUniverseExtents(planet.position);
|
||||
}
|
||||
else
|
||||
planet.orbitTick(time);
|
||||
planet.leaderTick(time);
|
||||
planet.orderTick(time);
|
||||
|
||||
if(planet.hasAbilities)
|
||||
planet.abilityTick(time);
|
||||
|
||||
//Tick occasional stuff
|
||||
timer += float(time);
|
||||
if(timer >= 0.9f) {
|
||||
occasional_tick(planet);
|
||||
planet.surfaceTick(timer);
|
||||
planet.resourceTick(timer);
|
||||
planet.statusTick(time);
|
||||
timer = 0.f;
|
||||
}
|
||||
|
||||
//Update biome population
|
||||
planet.Population = planet.population;
|
||||
|
||||
Empire@ owner = planet.owner;
|
||||
if(owner !is null && owner.valid) {
|
||||
planet.constructionTick(time);
|
||||
if(planet.hasConstructionUnder(0.2))
|
||||
return 0.0;
|
||||
else
|
||||
return 0.2;
|
||||
}
|
||||
else {
|
||||
return 0.2;
|
||||
}
|
||||
}
|
||||
|
||||
void syncInitial(const Planet& planet, Message& msg) {
|
||||
msg << float(planet.Health);
|
||||
msg << float(planet.MaxHealth);
|
||||
msg.writeSmall(planet.PlanetType);
|
||||
msg << planet.renamed;
|
||||
msg << planet.OrbitSize;
|
||||
planet.writeResources(msg);
|
||||
planet.writeSurface(msg);
|
||||
planet.writeOrbit(msg);
|
||||
planet.writeLeaderAI(msg);
|
||||
planet.writeStatuses(msg);
|
||||
planet.writeCargo(msg);
|
||||
|
||||
msg.writeBit(planet.hasAbilities);
|
||||
if(planet.hasAbilities)
|
||||
planet.writeAbilities(msg);
|
||||
|
||||
msg.writeBit(ringStyle != 0);
|
||||
if(ringStyle != 0)
|
||||
msg << ringStyle;
|
||||
|
||||
if(planet.hasMover) {
|
||||
msg.write1();
|
||||
planet.writeMover(msg);
|
||||
}
|
||||
else {
|
||||
msg.write0();
|
||||
}
|
||||
|
||||
if(moons !is null && moons.length != 0) {
|
||||
msg.write1();
|
||||
msg.writeSmall(moons.length);
|
||||
for(uint i = 0, cnt = moons.length; i < cnt; ++i) {
|
||||
msg << moons[i].size;
|
||||
msg << moons[i].style;
|
||||
}
|
||||
}
|
||||
else {
|
||||
msg.write0();
|
||||
}
|
||||
}
|
||||
|
||||
bool syncDelta(const Planet& planet, Message& msg) {
|
||||
bool used = false;
|
||||
if(planet.writeResourceDelta(msg))
|
||||
used = true;
|
||||
else
|
||||
msg.write0();
|
||||
|
||||
if(planet.writeSurfaceDelta(msg))
|
||||
used = true;
|
||||
else
|
||||
msg.write0();
|
||||
|
||||
if(planet.writeConstructionDelta(msg))
|
||||
used = true;
|
||||
else
|
||||
msg.write0();
|
||||
|
||||
if(planet.writeLeaderAIDelta(msg))
|
||||
used = true;
|
||||
else
|
||||
msg.write0();
|
||||
|
||||
if(planet.hasAbilities && planet.writeAbilityDelta(msg))
|
||||
used = true;
|
||||
else
|
||||
msg.write0();
|
||||
|
||||
if(planet.writeStatusDelta(msg))
|
||||
used = true;
|
||||
else
|
||||
msg.write0();
|
||||
|
||||
if(planet.writeCargoDelta(msg))
|
||||
used = true;
|
||||
else
|
||||
msg.write0();
|
||||
|
||||
if(hpDelta) {
|
||||
used = true;
|
||||
hpDelta = false;
|
||||
msg.write1();
|
||||
msg << float(planet.Health);
|
||||
msg << float(planet.MaxHealth);
|
||||
}
|
||||
else
|
||||
msg.write0();
|
||||
|
||||
if(planet.writeMoverDelta(msg))
|
||||
used = true;
|
||||
else
|
||||
msg.write0();
|
||||
|
||||
if(planet.writeOrbitDelta(msg))
|
||||
used = true;
|
||||
else
|
||||
msg.write0();
|
||||
|
||||
return used;
|
||||
}
|
||||
|
||||
void syncDetailed(const Planet& planet, Message& msg) {
|
||||
msg << float(planet.Health);
|
||||
msg << float(planet.MaxHealth);
|
||||
planet.writeResources(msg);
|
||||
planet.writeSurface(msg);
|
||||
planet.writeConstruction(msg);
|
||||
planet.writeStatuses(msg);
|
||||
planet.writeCargo(msg);
|
||||
|
||||
msg.writeBit(planet.hasAbilities);
|
||||
if(planet.hasAbilities)
|
||||
planet.writeAbilities(msg);
|
||||
|
||||
if(planet.hasMover) {
|
||||
msg.write1();
|
||||
planet.writeMover(msg);
|
||||
}
|
||||
else {
|
||||
msg.write0();
|
||||
}
|
||||
}
|
||||
|
||||
void dealPlanetDamage(Planet& planet, double amount) {
|
||||
double curPop = planet.population;
|
||||
|
||||
double hpDmg = 0;
|
||||
if(curPop > 1.0)
|
||||
hpDmg = amount * pow(0.4, curPop/5.0);
|
||||
else
|
||||
hpDmg = amount;
|
||||
double popDmg = amount - hpDmg;
|
||||
|
||||
if(hpDmg > 0) {
|
||||
hpDelta = true;
|
||||
planet.Health -= hpDmg;
|
||||
|
||||
if(planet.Health <= 0) {
|
||||
planet.Health = 0;
|
||||
planet.destroy();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(popDmg > 0) {
|
||||
double popLost = popDmg / planet.MaxHealth * 3.0 * planet.maxPopulation;
|
||||
planet.removePopulation(popLost, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
void giveHistoricMemory(Planet& planet, Empire@ emp) {
|
||||
if(planet.memoryMask & emp.mask != 0)
|
||||
return;
|
||||
|
||||
planet.memoryMask |= emp.mask;
|
||||
planet.giveBasicIconVision(emp);
|
||||
//TODO: Make this force a vision update on the client
|
||||
}
|
||||
|
||||
void setRing(uint ring) {
|
||||
ringStyle = ring;
|
||||
}
|
||||
|
||||
void addMoon(Planet& planet, float size = 0, uint style = 0) {
|
||||
if(moons is null)
|
||||
@moons = array<MoonData@>();
|
||||
if(style == 0)
|
||||
style = randomi();
|
||||
if(size == 0)
|
||||
size = randomd(planet.radius * 0.15, planet.radius * 0.3);
|
||||
|
||||
MoonData dat;
|
||||
dat.size = size;
|
||||
dat.style = style;
|
||||
moons.insertLast(dat);
|
||||
|
||||
PlanetNode@ plNode = cast<PlanetNode>(planet.getNode());
|
||||
if(plNode !is null)
|
||||
plNode.addMoon(size, style);
|
||||
}
|
||||
|
||||
uint get_moonCount() {
|
||||
if(moons is null)
|
||||
return 0;
|
||||
return moons.length;
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,152 @@
|
||||
import regions.regions;
|
||||
import saving;
|
||||
import systems;
|
||||
|
||||
LightDesc lightDesc;
|
||||
|
||||
tidy class StarScript {
|
||||
bool hpDelta = false;
|
||||
|
||||
void syncInitial(const Star& star, Message& msg) {
|
||||
msg << float(star.temperature);
|
||||
star.writeOrbit(msg);
|
||||
}
|
||||
|
||||
void save(Star& star, SaveFile& file) {
|
||||
saveObjectStates(star, file);
|
||||
file << star.temperature;
|
||||
file << cast<Savable>(star.Orbit);
|
||||
file << star.Health;
|
||||
file << star.MaxHealth;
|
||||
}
|
||||
|
||||
void load(Star& star, SaveFile& file) {
|
||||
loadObjectStates(star, file);
|
||||
file >> star.temperature;
|
||||
|
||||
if(star.owner is null)
|
||||
@star.owner = defaultEmpire;
|
||||
|
||||
lightDesc.att_quadratic = 1.f/(2000.f*2000.f);
|
||||
|
||||
double temp = star.temperature;
|
||||
Node@ node;
|
||||
double soundRadius = star.radius;
|
||||
if(temp > 0.0) {
|
||||
@node = bindNode(star, "StarNode");
|
||||
node.color = blackBody(temp, max((temp + 15000.0) / 40000.0, 1.0));
|
||||
}
|
||||
else {
|
||||
@node = bindNode(star, "BlackholeNode");
|
||||
node.color = blackBody(16000.0, max((16000.0 + 15000.0) / 40000.0, 1.0));
|
||||
cast<BlackholeNode>(node).establish(star);
|
||||
soundRadius *= 10.0;
|
||||
}
|
||||
|
||||
addAmbientSource(CURRENT_PLAYER, "star_rumble", star.id, star.position, soundRadius);
|
||||
|
||||
if(file >= SV_0028)
|
||||
file >> cast<Savable>(star.Orbit);
|
||||
|
||||
if(file >= SV_0102) {
|
||||
file >> star.Health;
|
||||
file >> star.MaxHealth;
|
||||
}
|
||||
|
||||
lightDesc.position = vec3f(star.position);
|
||||
lightDesc.radius = star.radius;
|
||||
lightDesc.diffuse = node.color * 1.0f;
|
||||
if(temp <= 0)
|
||||
lightDesc.diffuse.a = 0.f;
|
||||
lightDesc.specular = lightDesc.diffuse;
|
||||
|
||||
if(star.inOrbit)
|
||||
makeLight(lightDesc, node);
|
||||
else
|
||||
makeLight(lightDesc);
|
||||
}
|
||||
|
||||
void syncDetailed(const Star& star, Message& msg) {
|
||||
msg << float(star.Health);
|
||||
msg << float(star.MaxHealth);
|
||||
}
|
||||
|
||||
bool syncDelta(const Star& star, Message& msg) {
|
||||
if(!hpDelta)
|
||||
return false;
|
||||
|
||||
msg << float(star.Health);
|
||||
msg << float(star.MaxHealth);
|
||||
hpDelta = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void postLoad(Star& star) {
|
||||
Node@ node = star.getNode();
|
||||
if(node !is null)
|
||||
node.hintParentObject(star.region, false);
|
||||
}
|
||||
|
||||
void postInit(Star& star) {
|
||||
double soundRadius = star.radius;
|
||||
//Blackholes need a 'bigger' sound
|
||||
if(star.temperature == 0.0)
|
||||
soundRadius *= 10.0;
|
||||
addAmbientSource(CURRENT_PLAYER, "star_rumble", star.id, star.position, soundRadius);
|
||||
}
|
||||
|
||||
void dealStarDamage(Star& star, double amount) {
|
||||
hpDelta = true;
|
||||
star.Health -= amount;
|
||||
if(star.Health <= 0) {
|
||||
star.Health = 0;
|
||||
star.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void destroy(Star& star) {
|
||||
if(!game_ending) {
|
||||
double explRad = star.radius;
|
||||
if(star.temperature == 0.0) {
|
||||
explRad *= 20.0;
|
||||
|
||||
for(uint i = 0, cnt = systemCount; i < cnt; ++i) {
|
||||
auto@ sys = getSystem(i);
|
||||
double dist = star.position.distanceTo(sys.position);
|
||||
if(dist < 100000.0) {
|
||||
double factor = sqr(1.0 - (dist / 100000));
|
||||
sys.object.addStarDPS(factor * star.MaxHealth * 0.08);
|
||||
}
|
||||
}
|
||||
}
|
||||
playParticleSystem("StarExplosion", star.position, star.rotation, explRad);
|
||||
|
||||
//auto@ node = createNode("NovaNode");
|
||||
//if(node !is null)
|
||||
// node.position = star.position;
|
||||
removeAmbientSource(CURRENT_PLAYER, star.id);
|
||||
if(star.region !is null)
|
||||
star.region.addSystemDPS(star.MaxHealth * 0.12);
|
||||
}
|
||||
leaveRegion(star);
|
||||
}
|
||||
|
||||
/*void damage(Star& star, DamageEvent& evt, double position, const vec2d& direction) {
|
||||
evt.damage -= 100.0;
|
||||
if(evt.damage > 0.0)
|
||||
star.HP -= evt.damage;
|
||||
}*/
|
||||
|
||||
double tick(Star& obj, double time) {
|
||||
updateRegion(obj);
|
||||
obj.orbitTick(time);
|
||||
|
||||
Region@ reg = obj.region;
|
||||
uint mask = ~0;
|
||||
if(reg !is null && obj.temperature > 0)
|
||||
mask = reg.ExploredMask.value;
|
||||
obj.donatedVision = mask;
|
||||
|
||||
return 1.0;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,237 @@
|
||||
import systems;
|
||||
|
||||
tidy class TerritoryScript {
|
||||
TerritoryNode@ node;
|
||||
set_int inner;
|
||||
set_int edges;
|
||||
|
||||
bool regionDelta = false;
|
||||
array<Region@> regions;
|
||||
array<Region@> visionPending;
|
||||
array<bool> visionPendingOp;
|
||||
|
||||
void postInit(Territory& obj) {
|
||||
obj.sightRange = 0.0;
|
||||
@node = TerritoryNode();
|
||||
node.setOwner(obj.owner);
|
||||
}
|
||||
|
||||
void destroy(Territory& obj) {
|
||||
node.markForDeletion();
|
||||
@node = null;
|
||||
}
|
||||
|
||||
void save(Territory& obj, SaveFile& file) {
|
||||
uint cnt = regions.length;
|
||||
file << cnt;
|
||||
for(uint i = 0; i < cnt; ++i)
|
||||
file << regions[i];
|
||||
|
||||
cnt = visionPending.length;
|
||||
file << cnt;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
file << visionPending[i];
|
||||
file << visionPendingOp[i];
|
||||
}
|
||||
}
|
||||
|
||||
void load(Territory& obj, SaveFile& file) {
|
||||
uint cnt = 0;
|
||||
file >> cnt;
|
||||
regions.length = cnt;
|
||||
for(uint i = 0; i < cnt; ++i)
|
||||
@regions[i] = cast<Region>(file.readObject());
|
||||
|
||||
file >> cnt;
|
||||
visionPending.length = cnt;
|
||||
visionPendingOp.length = cnt;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
@visionPending[i] = cast<Region>(file.readObject());
|
||||
file >> visionPendingOp[i];
|
||||
}
|
||||
}
|
||||
|
||||
void postLoad(Territory& obj) {
|
||||
@node = TerritoryNode();
|
||||
node.setOwner(obj.owner);
|
||||
|
||||
for(uint i = 0, cnt = regions.length; i < cnt; ++i) {
|
||||
Region@ region = regions[i];
|
||||
node.addInner(region.id, region.position, region.radius);
|
||||
inner.insert(region.id);
|
||||
|
||||
if(edges.contains(region.id)) {
|
||||
node.removeEdge(region.id);
|
||||
edges.erase(region.id);
|
||||
}
|
||||
|
||||
//Add edges from this region
|
||||
SystemDesc@ desc = getSystem(region.SystemId);
|
||||
for(uint i = 0, cnt = desc.adjacent.length; i < cnt; ++i) {
|
||||
uint adj = desc.adjacent[i];
|
||||
SystemDesc@ other = getSystem(adj);
|
||||
|
||||
if(inner.contains(other.object.id))
|
||||
continue;
|
||||
if(edges.contains(other.object.id))
|
||||
continue;
|
||||
|
||||
edges.insert(other.object.id);
|
||||
node.addEdge(other.object.id, other.position, other.radius);
|
||||
}
|
||||
}
|
||||
|
||||
//Reverse pending vision to get to old state
|
||||
for(int i = visionPending.length - 1; i >= 0; --i) {
|
||||
Region@ region = visionPending[i];
|
||||
if(visionPendingOp[i])
|
||||
node.removeInner(region.id);
|
||||
else
|
||||
node.addInner(region.id, region.position, region.radius);
|
||||
}
|
||||
}
|
||||
|
||||
double tick(Territory& obj, double time) {
|
||||
for(uint i = 0, cnt = visionPending.length; i < cnt; ++i) {
|
||||
Region@ region = visionPending[i];
|
||||
if(obj.owner is playerEmpire || region.VisionMask & playerEmpire.visionMask != 0) {
|
||||
if(visionPendingOp[i])
|
||||
node.addInner(region.id, region.position, region.radius);
|
||||
else
|
||||
node.removeInner(region.id);
|
||||
|
||||
visionPending.removeAt(i);
|
||||
visionPendingOp.removeAt(i);
|
||||
--i; --cnt;
|
||||
}
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
bool canTradeTo(Region@ region) const {
|
||||
return inner.contains(region.id) || edges.contains(region.id);
|
||||
}
|
||||
|
||||
uint getRegionCount() const {
|
||||
return regions.length;
|
||||
}
|
||||
|
||||
Region@ getRegion(uint i) const {
|
||||
if(i >= regions.length)
|
||||
return null;
|
||||
return regions[i];
|
||||
}
|
||||
|
||||
void add(Territory& obj, Region@ region) {
|
||||
if(obj.owner is playerEmpire || region.VisionMask & playerEmpire.visionMask != 0) {
|
||||
node.addInner(region.id, region.position, region.radius);
|
||||
}
|
||||
else {
|
||||
visionPending.insertLast(region);
|
||||
visionPendingOp.insertLast(true);
|
||||
}
|
||||
|
||||
inner.insert(region.id);
|
||||
regions.insertLast(region);
|
||||
regionDelta = true;
|
||||
|
||||
if(edges.contains(region.id)) {
|
||||
node.removeEdge(region.id);
|
||||
edges.erase(region.id);
|
||||
}
|
||||
|
||||
//Add edges from this region
|
||||
SystemDesc@ desc = getSystem(region.SystemId);
|
||||
for(uint i = 0, cnt = desc.adjacent.length; i < cnt; ++i) {
|
||||
uint adj = desc.adjacent[i];
|
||||
SystemDesc@ other = getSystem(adj);
|
||||
|
||||
if(inner.contains(other.object.id))
|
||||
continue;
|
||||
if(edges.contains(other.object.id))
|
||||
continue;
|
||||
|
||||
edges.insert(other.object.id);
|
||||
node.addEdge(other.object.id, other.position, other.radius);
|
||||
}
|
||||
}
|
||||
|
||||
void remove(Territory& obj, Region@ region) {
|
||||
if(obj.owner is playerEmpire || region.VisionMask & playerEmpire.visionMask != 0) {
|
||||
node.removeInner(region.id);
|
||||
}
|
||||
else {
|
||||
visionPending.insertLast(region);
|
||||
visionPendingOp.insertLast(false);
|
||||
}
|
||||
|
||||
inner.erase(region.id);
|
||||
regions.remove(region);
|
||||
regionDelta = true;
|
||||
|
||||
//Remove edges from this region
|
||||
SystemDesc@ desc = getSystem(region.SystemId);
|
||||
bool isEdge = false;
|
||||
for(uint i = 0, cnt = desc.adjacent.length; i < cnt; ++i) {
|
||||
uint adj = desc.adjacent[i];
|
||||
SystemDesc@ other = getSystem(adj);
|
||||
|
||||
if(inner.contains(other.object.id))
|
||||
isEdge = true;
|
||||
|
||||
if(!edges.contains(other.object.id))
|
||||
continue;
|
||||
|
||||
bool found = false;
|
||||
for(uint j = 0, jcnt = other.adjacent.length; j < jcnt; ++j) {
|
||||
SystemDesc@ chk = getSystem(other.adjacent[j]);
|
||||
if(inner.contains(chk.object.id)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!found) {
|
||||
edges.erase(other.object.id);
|
||||
node.removeEdge(other.object.id);
|
||||
}
|
||||
}
|
||||
|
||||
//Check if this should be added back as an edge
|
||||
if(isEdge) {
|
||||
edges.insert(region.id);
|
||||
node.addEdge(region.id, region.position, region.radius);
|
||||
}
|
||||
}
|
||||
|
||||
void _writeRegions(const Territory& obj, Message& msg) {
|
||||
uint cnt = regions.length;
|
||||
msg << cnt;
|
||||
for(uint i = 0; i < cnt; ++i)
|
||||
msg << regions[i];
|
||||
}
|
||||
|
||||
void syncInitial(const Territory& obj, Message& msg) {
|
||||
_writeRegions(obj, msg);
|
||||
}
|
||||
|
||||
void syncDetailed(const Territory& obj, Message& msg) {
|
||||
_writeRegions(obj, msg);
|
||||
}
|
||||
|
||||
bool syncDelta(const Territory& obj, Message& msg) {
|
||||
if(!regionDelta)
|
||||
return false;
|
||||
|
||||
if(regionDelta) {
|
||||
msg.write1();
|
||||
_writeRegions(obj, msg);
|
||||
regionDelta = false;
|
||||
}
|
||||
else {
|
||||
msg.write0();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user