559 lines
13 KiB
ActionScript
559 lines
13 KiB
ActionScript
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);
|
|
}
|