Open source Star Ruler 2 source code!

This commit is contained in:
Lucas de Vries
2018-07-17 14:15:37 +02:00
commit cc307720ff
4342 changed files with 2365070 additions and 0 deletions
+119
View File
@@ -0,0 +1,119 @@
import orders.Order;
import saving;
tidy class AbilityOrder : Order {
int abilityId = -1;
int moveId = -1;
double range = 100.0;
vec3d target;
Object@ objTarget;
bool casted = false;
AbilityOrder(int id, vec3d targ, double range) {
abilityId = id;
target = targ;
this.range = range;
}
AbilityOrder(int id, Object@ targ, double range) {
abilityId = id;
@objTarget = targ;
this.range = range;
}
bool get_hasMovement() {
return true;
}
vec3d getMoveDestination(const Object& obj) {
if(objTarget !is null)
return objTarget.position;
return target;
}
AbilityOrder(SaveFile& file) {
Order::load(file);
file >> target;
file >> moveId;
file >> abilityId;
file >> objTarget;
if(file >= SV_0121)
file >> casted;
}
void save(SaveFile& file) {
Order::save(file);
file << target;
file << moveId;
file << abilityId;
file << objTarget;
file << casted;
}
string get_name() {
return "Use Ability";
}
OrderType get_type() {
return OT_Ability;
}
OrderStatus tick(Object& obj, double time) {
if(!obj.hasMover)
return OS_COMPLETED;
double realRange = range;
realRange += obj.radius;
if(objTarget !is null) {
realRange += objTarget.radius;
bool finishedMove = false;
double distance = obj.position.distanceToSQ(objTarget.position);
if(range != INFINITY && distance >= realRange * realRange)
finishedMove = obj.moveTo(objTarget, moveId, realRange * 0.95, enterOrbit=false);
if(casted) {
if(obj.isChanneling(abilityId))
return OS_BLOCKING;
else
return OS_COMPLETED;
}
if(obj.isAbilityOnCooldown(abilityId))
return OS_BLOCKING;
if(distance <= realRange * realRange) {
obj.activateAbility(abilityId, objTarget);
if(moveId != -1) {
moveId = -1;
obj.stopMoving(false, false);
}
casted = true;
}
return OS_BLOCKING;
}
else {
bool finishedMove = false;
double distance = obj.position.distanceToSQ(target);
if(realRange != INFINITY && distance >= realRange * realRange) {
vec3d pt = target;
pt += (obj.position - target).normalized(realRange * 0.95);
finishedMove = obj.moveTo(pt, moveId, enterOrbit=false);
}
if(casted) {
if(obj.isChanneling(abilityId))
return OS_BLOCKING;
else
return OS_COMPLETED;
}
if(obj.isAbilityOnCooldown(abilityId))
return OS_BLOCKING;
if(distance <= realRange * realRange) {
obj.activateAbility(abilityId, target);
if(moveId != -1) {
moveId = -1;
obj.stopMoving(false, false);
}
casted = true;
}
return OS_BLOCKING;
}
}
};
+207
View File
@@ -0,0 +1,207 @@
import orders.Order;
import saving;
tidy class AttackOrder : Order {
Object@ target;
int moveId = -1;
uint flags = TF_Preference;
bool movement = true;
double minRange = 0;
quaterniond facing;
bool isBound = false;
vec3d boundPos;
double boundDistance = 0;
vec3d fleePos;
bool closeIn = false;
bool dodgeObstacle = false;
AttackOrder(Object& targ, double engagementRange) {
minRange = engagementRange;
@target = targ;
}
AttackOrder(Object& targ, double engagementRange, const vec3d bindPosition, double bindDistance, bool closeIn) {
minRange = engagementRange;
@target = targ;
boundPos = bindPosition;
boundDistance = bindDistance;
isBound = true;
this.closeIn = closeIn;
}
bool get_hasMovement() {
return true;
}
vec3d getMoveDestination(const Object& obj) {
return target.position;
}
AttackOrder(SaveFile& msg) {
Order::load(msg);
msg >> target;
msg >> moveId;
msg >> flags;
msg >> movement;
msg >> minRange;
if(msg >= SV_0062) {
msg >> isBound;
msg >> boundPos;
msg >> boundDistance;
msg >> fleePos;
}
if(msg >= SV_0066)
msg >> closeIn;
}
void save(SaveFile& msg) {
Order::save(msg);
msg << target;
msg << moveId;
msg << flags;
msg << movement;
msg << minRange;
msg << isBound;
msg << boundPos;
msg << boundDistance;
msg << fleePos;
msg << closeIn;
}
string get_name() {
return "Attack " + target.name;
}
OrderType get_type() {
return OT_Attack;
}
OrderStatus tick(Object& obj, double time) {
if(!obj.hasMover)
return OS_COMPLETED;
//Switch targets if targeting a group
if(target is null || !target.valid) {
//Only complete the order if we're out
//of combat, so we don't mess up the
//combat positioning
if(obj.inCombat && flags & TF_Group != 0) {
if(moveId != -1) {
obj.stopMoving();
moveId = -1;
}
return OS_BLOCKING;
}
return OS_COMPLETED;
}
if(!target.memorable && !target.isVisibleTo(obj.owner)) {
if(moveId != -1) {
obj.stopMoving();
moveId = -1;
}
return OS_COMPLETED;
}
if(!target.isVisibleTo(obj.owner) && (!target.memorable || !target.isKnownTo(obj.owner))) {
if(moveId != -1) {
obj.stopMoving();
moveId = -1;
}
return OS_COMPLETED;
}
Ship@ ship = cast<Ship>(obj);
if(ship is null)
return OS_COMPLETED;
Empire@ myOwner = obj.owner;
Empire@ targOwner = target.owner;
if(myOwner is null || targOwner is null || !myOwner.isHostile(targOwner)) {
if(moveId != -1) {
obj.stopMoving();
moveId = -1;
}
return OS_COMPLETED;
}
//Set effector targets
ship.blueprint.target(obj, target, flags);
double distSQ = obj.position.distanceToSQ(target.position);
if(distSQ > minRange * minRange) {
if(!movement)
return OS_COMPLETED;
if(moveId == -1)
facing = quaterniond_fromVecToVec(vec3d_front(), target.position - obj.position);
if(obj.moveTo(target, moveId, minRange * 0.9, enterOrbit=false))
obj.setRotation(facing);
fleePos = vec3d();
}
else if(closeIn && distSQ < (minRange * 0.75) * (minRange * 0.75)) {
if(!movement)
return OS_COMPLETED;
if(moveId == -1)
facing = quaterniond_fromVecToVec(vec3d_front(), target.position - obj.position);
//Calculate the position we would be going to
if(!fleePos.zero) {
if(obj.moveTo(fleePos, moveId, doPathing=false, enterOrbit=false))
fleePos = vec3d();
}
else {
if(!isBound) {
Region@ reg = obj.region;
if(reg !is null) {
boundPos = reg.position;
boundDistance = reg.radius;
isBound = true;
}
}
if(isBound) {
vec3d offset = (obj.position - target.position).normalized(minRange);
vec3d destPos = target.position + offset;
if(destPos.distanceToSQ(boundPos) > boundDistance * boundDistance * 0.95 * 0.95) {
double angle = randomd(pi*0.4,pi*0.6) * (randomi(0,1) == 0 ? -1.0 : 1.0);
auto rot = quaterniond_fromAxisAngle(vec3d_up(), angle);
fleePos = target.position + rot * offset;
moveId = -1;
return OS_BLOCKING;
}
}
if(obj.moveTo(target, moveId, minRange, enterOrbit=false))
obj.setRotation(facing);
}
}
else if(dodgeObstacle) {
if(moveId == -1 || !obj.isOnMoveOrder(moveId))
dodgeObstacle = false;
}
else {
fleePos = vec3d();
if(moveId != -1) {
obj.stopMoving(enterOrbit=false);
moveId = -1;
}
else {
line3dd line(obj.position, target.position);
auto@ blocker = trace(line, obj.owner.hostileMask | 0x1);
if(blocker !is null && blocker !is target && (blocker.isPlanet || blocker.isStar)) {
//Move to a position that gets us around the obstacle
double dist = (blocker.radius + obj.radius * 2.0) * 1.2;
vec3d to = line.getClosestPoint(blocker.position, false);
if(to != blocker.position)
to = blocker.position + (to - blocker.position).normalized(dist);
else
to = blocker.position + quaterniond_fromAxisAngle(line.direction, randomd(-pi,pi)) * line.direction.cross(vec3d_up()).normalized(dist);
obj.moveTo(to, moveId, false, false);
dodgeObstacle = true;
}
}
}
return OS_BLOCKING;
}
};
+174
View File
@@ -0,0 +1,174 @@
import orders.Order;
import systems;
import regions.regions;
import ftl;
tidy class AutoExploreOrder : Order {
Region@ dest;
bool ftl = false;
set_int inner;
array<int> edge;
uint scoutMask = 0;
AutoExploreOrder(bool useFTL) {
ftl = useFTL;
}
AutoExploreOrder(SaveFile& msg) {
Order::load(msg);
msg >> dest;
msg >> ftl;
}
~AutoExploreOrder() {
if(dest !is null)
dest.ScoutingMask &= ~scoutMask;
}
void destroy() override {
if(dest !is null) {
dest.ScoutingMask &= ~scoutMask;
@dest = null;
}
}
void save(SaveFile& msg) {
Order::save(msg);
msg << dest;
msg << ftl;
}
OrderType get_type() {
return OT_AutoExplore;
}
string get_name() {
return "Automically Exploring";
}
bool get_hasMovement() {
return false;
}
vec3d getMoveDestination(const Object& obj) override {
return obj.position;
}
void doMoveTo(Object& obj, Region@ to) {
if(to !is null) {
scoutMask = obj.owner.mask;
uint prevMask = (to.ScoutingMask |= scoutMask);
if(prevMask & scoutMask != 0) {
doMoveTo(obj, null);
return;
}
vec3d pt = to.position + (obj.position - to.position).normalize(to.OuterRadius - 100.0);
bool moved = false;
if(ftl && obj.isShip) {
Empire@ owner = obj.owner;
if(canHyperdrive(obj)) {
double cost = hyperdriveCost(obj, pt);
//Hyperdrive if at least half the empire's ftl capacity will be left
if((owner.FTLStored - cost) / owner.FTLCapacity >= 0.5) {
obj.insertHyperdriveOrder(pt, getIndex());
moved = true;
}
}
if(!moved && canJumpdrive(obj)) {
double cost = jumpdriveCost(obj, pt);
double range = jumpdriveRange(obj);
double dist = obj.position.distanceTo(pt);
//Jumpdrive if at least half the empire's ftl capacity will be left
if((owner.FTLStored - cost) / owner.FTLCapacity >= 0.5 && range >= dist) {
obj.insertJumpdriveOrder(pt, getIndex());
moved = true;
}
}
if(!moved && owner.hasFlingBeacons) {
double cost = flingCost(obj, pt);
//Fling if at least half the empire's ftl capacity will be left
if((owner.FTLStored - cost) / owner.FTLCapacity >= 0.5) {
Object@ fling = owner.getFlingBeacon(obj.position);
if(fling !is null) {
obj.insertFlingOrder(fling, pt, getIndex());
moved = true;
}
}
}
}
if(!moved)
obj.insertMoveOrder(pt, getIndex());
}
else
obj.stopMoving(enterOrbit = false);
@dest = to;
}
OrderStatus tick(Object& obj, double time) {
if(!obj.hasMover)
return OS_COMPLETED;
//Search for nearby systems, by number of jumps
if(dest !is null) {
if(obj.region is dest)
@dest = null;
}
if(dest is null) {
if(edge.length == 0) {
uint sysIndex = obj.region !is null ? obj.region.SystemId : findNearestRegion(obj.position).SystemId;
edge.insertLast(sysIndex);
inner.insert(sysIndex);
}
array<int> newEdge;
newEdge.reserve(edge.length * 2);
for(uint i = 0, cnt = edge.length; i < cnt; ++i) {
auto@ sys = getSystem(edge[i]);
for(uint j = 0, jcnt = sys.adjacent.length; j < jcnt; ++j) {
int sysIndex = sys.adjacent[j];
if(inner.contains(sysIndex))
continue;
inner.insert(sysIndex);
newEdge.insertLast(sysIndex);
}
}
//TODO: Notify the player that their auto-explore is done
if(newEdge.length == 0)
return OS_COMPLETED;
uint mask = obj.owner.mask;
Region@ nearest;
double dist = 1.0e35;
for(uint i = 0, cnt = newEdge.length; i < cnt; ++i) {
auto@ reg = getSystem(newEdge[i]).object;
if(reg.SeenMask & mask == 0 && reg.ScoutingMask.value & mask == 0) {
double d = obj.position.distanceTo(reg.position);
if(d < dist) {
@nearest = reg;
dist = d;
}
}
}
if(nearest !is null) {
doMoveTo(obj, nearest);
edge.length = 0;
inner.clear();
}
else {
edge = newEdge;
}
}
return OS_BLOCKING;
}
};
+73
View File
@@ -0,0 +1,73 @@
import orders.Order;
tidy class CaptureOrder : Order {
Planet@ target;
vec3d offset;
int moveId = -1;
CaptureOrder(Planet& targ) {
@target = targ;
double radius = targ.OrbitSize - targ.radius;
vec2d pos = random2d(targ.radius + radius * 0.15, targ.radius + radius*0.85);
offset = vec3d(pos.x, 0, pos.y);
moveId = -1;
}
CaptureOrder(SaveFile& msg) {
Order::load(msg);
msg >> target;
msg >> offset;
msg >> moveId;
}
void save(SaveFile& msg) {
Order::save(msg);
msg << target;
msg << offset;
msg << moveId;
}
OrderType get_type() {
return OT_Capture;
}
string get_name() {
return "Capture " + target.name;
}
bool get_hasMovement() {
return true;
}
vec3d getMoveDestination(const Object& obj) {
return target.position + offset;
}
OrderStatus tick(Object& obj, double time) {
if(!obj.hasMover)
return OS_COMPLETED;
Empire@ targOwner = target.owner;
if(targOwner is obj.owner)
return OS_COMPLETED;
if(!obj.owner.isHostile(targOwner))
return OS_COMPLETED;
if(obj.isShip && cast<Ship>(obj).Supply <= 0.1)
return OS_COMPLETED;
if(target.isProtected(obj.owner))
return OS_COMPLETED;
double capRadius = target.OrbitSize;
if(obj.position.distanceToSQ(target.position) < capRadius * capRadius) {
int loy = target.getLoyaltyFacing(obj.owner);
if(loy <= 0) {
target.annex(obj.owner);
return OS_COMPLETED;
}
}
obj.moveTo(target.position + offset, moveId);
return OS_BLOCKING;
}
};
+198
View File
@@ -0,0 +1,198 @@
import orders.Order;
import resources;
import attributes;
import ftl;
tidy class FlingOrder : Order {
Object@ beacon;
vec3d destination;
quaterniond facing;
double charge = 0.0;
int cost = 0;
double speed = 0.0;
int moveId = -1;
FlingOrder(Object& beacon, vec3d pos) {
@this.beacon = beacon;
destination = pos;
}
FlingOrder(SaveFile& msg) {
Order::load(msg);
msg >> destination;
msg >> facing;
msg >> moveId;
msg >> charge;
msg >> cost;
msg >> beacon;
msg >> speed;
}
void save(SaveFile& msg) override {
Order::save(msg);
msg << destination;
msg << facing;
msg << moveId;
msg << charge;
msg << cost;
msg << beacon;
msg << speed;
}
OrderType get_type() override {
return OT_Fling;
}
string get_name() override {
return "Flinging";
}
bool cancel(Object& obj) override {
//Cannot cancel while already ftling
if(charge >= FLING_CHARGE_TIME || charge < 0.0)
return false;
//Refund a part of the ftl cost
if(cost > 0) {
double pct = 1.0 - min(charge / FLING_CHARGE_TIME, 1.0);
double refund = cost * pct;
obj.owner.modFTLStored(refund);
cost = 0;
}
//Mark ship as no longer FTLing
Ship@ ship = cast<Ship>(obj);
if(ship !is null)
ship.isFTLing = false;
return true;
}
bool get_hasMovement() override {
return true;
}
vec3d getMoveDestination(const Object& obj) override {
return destination;
}
OrderStatus tick(Object& obj, double time) override {
if(!obj.hasMover || !canFling(obj))
return OS_COMPLETED;
//Pay for the FTL
Ship@ ship = cast<Ship>(obj);
if(ship !is null && ship.delayFTL && charge >= 0) {
if(charge > 0)
charge = 0.001;
return OS_BLOCKING;
}
if(charge == 0) {
cost = flingCost(obj, destination);
speed = flingSpeed(obj, destination);
if(cost > 0) {
double consumed = obj.owner.consumeFTL(cost, false, record=false);
if(consumed < cost)
return OS_COMPLETED;
}
charge = 0.001;
//Make sure we have a beacon in range
if(beacon is null || beacon.position.distanceToSQ(obj.position) > FLING_BEACON_RANGE_SQ) {
@beacon = obj.owner.getClosestFlingBeacon(obj.position);
if(beacon is null || beacon.position.distanceToSQ(obj.position) > FLING_BEACON_RANGE_SQ)
return OS_COMPLETED;
}
//Mark ship as FTLing
if(ship !is null)
ship.isFTLing = true;
//Calculate needed facing
facing = quaterniond_fromVecToVec(vec3d_front(), destination - obj.position);
obj.stopMoving();
playParticleSystem("FTLCharge", vec3d(), quaterniond(), obj.radius * 4.0, obj);
}
//Wait for the facing to complete
if(charge > 0.0) {
bool isFacing = obj.rotateTo(facing, moveId);
//Charge up the ftl drive for a while first
if(charge < FLING_CHARGE_TIME)
charge += time;
if(!isFacing) {
return OS_BLOCKING;
}
else {
if(charge < FLING_CHARGE_TIME)
return OS_BLOCKING;
charge = -1.0;
moveId = -1;
if(cost > 0)
obj.owner.modAttribute(EA_FTLEnergySpent, AC_Add, cost);
}
}
//Do actual flinging
bool wasMoving = moveId != -1;
bool arrived = obj.FTLTo(destination, speed, moveId);
if(!wasMoving) {
if(obj.hasOrbit)
obj.stopOrbit();
obj.idleAllSupports();
//Order supports to ftl
uint cnt = obj.supportCount;
for(uint i = 0; i < cnt; ++i) {
Object@ support = obj.supportShip[i];
support.FTLTo(destination + (support.position - obj.position), speed);
}
playParticleSystem("FTLEnter", obj.position, obj.rotation, obj.radius * 4.0, obj.visibleMask);
}
if(arrived) {
if(ship !is null) {
//Flag ship as no longer in ftl
ship.blueprint.clearTracking(ship);
ship.isFTLing = false;
}
//Clear tracking on arrival
uint cnt = obj.supportCount;
for(uint i = 0; i < cnt; ++i) {
Ship@ support = cast<Ship>(obj.supportShip[i]);
support.FTLTo(destination + (support.position - obj.position), speed);
support.blueprint.clearTracking(support);
}
//Set rotation on arrival
obj.setRotation(facing);
return OS_COMPLETED;
}
else {
//Check for dropping out of ftl
Region@ reg = getRegion(obj.position);
if(reg !is null && obj.owner !is null) {
if(reg.BlockFTLMask & obj.owner.mask != 0) {
obj.FTLDrop();
uint cnt = obj.supportCount;
for(uint i = 0; i < cnt; ++i) {
Object@ support = obj.supportShip[i];
if(support !is null)
support.FTLDrop();
}
if(obj.orderCount == 1)
obj.addMoveOrder(destination, true);
return OS_COMPLETED;
}
}
return OS_BLOCKING;
}
}
};
+53
View File
@@ -0,0 +1,53 @@
import orders.Order;
tidy class GotoOrder : Order {
Object@ destination;
float dist;
int moveId;
GotoOrder(Object& dest, float Distance = 0) {
@destination = dest;
dist = Distance;
moveId = -1;
}
GotoOrder(SaveFile& msg) {
Order::load(msg);
msg >> destination;
msg >> dist;
msg >> moveId;
}
void save(SaveFile& msg) {
Order::save(msg);
msg << destination;
msg << dist;
msg << moveId;
}
OrderType get_type() {
return OT_Goto;
}
string get_name() {
return "Go to " + destination.name;
}
bool get_hasMovement() {
return true;
}
vec3d getMoveDestination(const Object& obj) {
return getMoveDestination(obj, destination, dist);
}
OrderStatus tick(Object& obj, double time) {
if(!obj.hasMover)
return OS_COMPLETED;
if(obj.moveTo(destination, moveId, dist))
return OS_COMPLETED;
return OS_BLOCKING;
}
};
+205
View File
@@ -0,0 +1,205 @@
import orders.Order;
import resources;
import attributes;
import ftl;
tidy class HyperdriveOrder : Order {
vec3d destination;
quaterniond facing;
double charge = 0.0;
int cost = 0;
int moveId = -1;
HyperdriveOrder(vec3d pos) {
destination = pos;
}
HyperdriveOrder(SaveFile& msg) {
Order::load(msg);
msg >> destination;
msg >> facing;
msg >> moveId;
msg >> charge;
msg >> cost;
}
void save(SaveFile& msg) override {
Order::save(msg);
msg << destination;
msg << facing;
msg << moveId;
msg << charge;
msg << cost;
}
OrderType get_type() override {
return OT_Hyperdrive;
}
string get_name() override {
return "Hyperdrifting";
}
bool cancel(Object& obj) override {
//Cannot cancel while already ftling
if(charge >= HYPERDRIVE_CHARGE_TIME || charge < 0.0)
return false;
//Refund a part of the ftl cost
if(cost > 0) {
double pct = 1.0 - min(charge / HYPERDRIVE_CHARGE_TIME, 1.0);
double refund = cost * pct;
obj.owner.modFTLStored(refund);
cost = 0;
}
//Mark ship as no longer FTLing
Ship@ ship = cast<Ship>(obj);
if(ship !is null)
ship.isFTLing = false;
return true;
}
bool get_hasMovement() override {
return true;
}
vec3d getMoveDestination(const Object& obj) override {
return destination;
}
OrderStatus tick(Object& obj, double time) override {
if(!obj.hasMover)
return OS_COMPLETED;
//Pay for the FTL
Ship@ ship = cast<Ship>(obj);
if(ship !is null && ship.delayFTL && charge >= 0) {
if(charge > 0)
charge = 0.001;
return OS_BLOCKING;
}
if(charge == 0) {
int scale = 1;
if(ship !is null) {
scale = ship.blueprint.design.size;
if(ship.group !is null)
scale *= ship.group.objectCount;
}
double dist = obj.position.distanceTo(destination);
cost = hyperdriveCost(ship, destination);
if(cost > 0) {
double consumed = obj.owner.consumeFTL(cost, false, record=false);
if(consumed < cost)
return OS_COMPLETED;
}
charge = 0.001;
//Mark ship as FTLing
if(ship !is null)
ship.isFTLing = true;
//Calculate needed facing
facing = quaterniond_fromVecToVec(vec3d_front(), destination - obj.position);
obj.stopMoving();
if(obj.owner.HyperdriveNeedCharge != 0)
playParticleSystem("FTLCharge", vec3d(), quaterniond(), obj.radius * 4.0, obj);
}
//Wait for the facing to complete
if(charge > 0.0) {
bool isFacing = obj.rotateTo(facing, moveId);
double chargeTime = HYPERDRIVE_CHARGE_TIME;
if(obj.owner.HyperdriveNeedCharge == 0)
chargeTime = 0.0;
//Charge up the hyperdrive for a while first
if(charge < chargeTime)
charge += time;
if(!isFacing) {
return OS_BLOCKING;
}
else {
if(charge < chargeTime)
return OS_BLOCKING;
charge = -1.0;
moveId = -1;
if(cost > 0)
obj.owner.modAttribute(EA_FTLEnergySpent, AC_Add, cost);
}
}
//Do actual hyperdriving
double speed = hyperdriveSpeed(obj);
bool wasMoving = moveId != -1;
bool arrived = obj.FTLTo(destination, speed, moveId);
if(!wasMoving) {
obj.idleAllSupports();
//Order supports to ftl
uint cnt = obj.supportCount;
for(uint i = 0; i < cnt; ++i) {
Object@ support = obj.supportShip[i];
support.FTLTo(destination + (support.position - obj.position), speed);
}
playParticleSystem("FTLEnter", obj.position, obj.rotation, obj.radius * 4.0, obj.visibleMask);
}
else {
if(speed != obj.ftlSpeed) {
obj.ftlSpeed = speed;
uint cnt = obj.supportCount;
for(uint i = 0; i < cnt; ++i) {
Object@ support = obj.supportShip[i];
support.ftlSpeed = speed;
}
}
}
if(arrived) {
//Flag ship as no longer in ftl
if(ship !is null) {
ship.blueprint.clearTracking(ship);
ship.isFTLing = false;
}
//Clear tracking on arrival
uint cnt = obj.supportCount;
for(uint i = 0; i < cnt; ++i) {
Ship@ support = cast<Ship>(obj.supportShip[i]);
support.FTLTo(destination + (support.position - obj.position), speed);
support.blueprint.clearTracking(support);
}
//Set rotation on arrival
obj.setRotation(facing);
return OS_COMPLETED;
}
else {
//Check for dropping out of hyperdrive
Region@ reg = getRegion(obj.position);
if(reg !is null && obj.owner !is null) {
if(reg.BlockFTLMask & obj.owner.mask != 0) {
obj.FTLDrop();
uint cnt = obj.supportCount;
for(uint i = 0; i < cnt; ++i) {
Object@ support = obj.supportShip[i];
if(support !is null)
support.FTLDrop();
}
if(obj.orderCount == 1)
obj.addMoveOrder(destination, true);
return OS_COMPLETED;
}
}
return OS_BLOCKING;
}
}
};
+205
View File
@@ -0,0 +1,205 @@
import orders.Order;
from regions.regions import getRegion;
import resources;
import attributes;
import system_flags;
import ftl;
tidy class JumpdriveOrder : Order {
vec3d destination;
quaterniond facing;
double charge = 0.0;
int cost = 0;
int moveId = -1;
JumpdriveOrder(vec3d pos) {
destination = pos;
}
JumpdriveOrder(SaveFile& msg) {
Order::load(msg);
msg >> destination;
msg >> facing;
msg >> moveId;
msg >> charge;
msg >> cost;
}
void save(SaveFile& msg) override {
Order::save(msg);
msg << destination;
msg << facing;
msg << moveId;
msg << charge;
msg << cost;
}
OrderType get_type() override {
return OT_Jumpdrive;
}
string get_name() override {
return "Jumping";
}
bool cancel(Object& obj) override {
//Cannot cancel while already ftling
if(charge >= JUMPDRIVE_CHARGE_TIME || charge < 0.0)
return false;
//Refund a part of the ftl cost
if(cost > 0) {
double pct = 1.0 - min(charge / JUMPDRIVE_CHARGE_TIME, 1.0);
double refund = cost * pct;
obj.owner.modFTLStored(refund);
cost = 0;
}
//Mark ship as no longer FTLing
Ship@ ship = cast<Ship>(obj);
if(ship !is null)
ship.isFTLing = false;
return true;
}
bool get_hasMovement() override {
return true;
}
vec3d getMoveDestination(const Object& obj) override {
return destination;
}
OrderStatus tick(Object& obj, double time) override {
if(!obj.hasMover)
return OS_COMPLETED;
//Pay for the FTL
Ship@ ship = cast<Ship>(obj);
if(ship !is null && ship.delayFTL && charge >= 0) {
if(charge > 0)
charge = 0.001;
return OS_BLOCKING;
}
if(charge == 0) {
int scale = 1;
if(ship !is null) {
scale = ship.blueprint.design.size;
if(ship.group !is null)
scale *= ship.group.objectCount;
}
double dist = obj.position.distanceTo(destination);
cost = jumpdriveCost(ship, destination);
if(cost > 0) {
double consumed = obj.owner.consumeFTL(cost, false, record=false);
if(consumed < cost)
return OS_COMPLETED;
}
charge = 0.001;
//Mark ship as FTLing
if(ship !is null)
ship.isFTLing = true;
//Calculate needed facing
facing = quaterniond_fromVecToVec(vec3d_front(), destination - obj.position);
obj.stopMoving();
playParticleSystem("JumpCharge", vec3d(), quaterniond(), obj.radius * 4.0, obj);
}
//Wait for the facing to complete
if(charge > 0.0) {
bool isFacing = obj.rotateTo(facing, moveId);
//Charge up the jumpdrive for a while first
if(charge < JUMPDRIVE_CHARGE_TIME)
charge += time;
if(!isFacing) {
return OS_BLOCKING;
}
else {
if(charge < JUMPDRIVE_CHARGE_TIME)
return OS_BLOCKING;
charge = -1.0;
moveId = -1;
if(cost > 0)
obj.owner.modAttribute(EA_FTLEnergySpent, AC_Add, cost);
}
}
//Teleport to destination
vec3d exitPos = destination;
double distance = destination.distanceTo(obj.position);
auto@ destRegion = getRegion(destination);
double range = 10000;
if(ship !is null)
range = ship.blueprint.design.total(SV_JumpRange);
if(distance > range && (destRegion is null || !destRegion.getSystemFlag(obj.owner, safetyFlag))) {
//Random offset based on over-distance
double minOffset = 0.0;
double maxOffset = (distance - range) * 0.25;
vec2d offset = random2d(randomd(minOffset, maxOffset));
exitPos += vec3d(offset.x, 0, offset.y);
//Random damage based on over-distance
if(ship !is null) {
int hits = min(int(floor((distance - range) / range * 25.0)), 100);
for(int i = 0; i < hits; ++i) {
DamageEvent dmg;
dmg.damage = randomd(0.01, 0.03) * ship.blueprint.design.totalHP;
@dmg.obj = obj;
@dmg.target = obj;
obj.damage(dmg, -1.0, random2d(1.0));
}
}
}
playParticleSystem("FTLEnter", obj.position, obj.rotation, obj.radius * 4.0, obj.visibleMask);
playParticleSystem("FTLExit", exitPos, obj.rotation, obj.radius * 4.0, obj.visibleMask);
if(obj.hasOrbit && obj.inOrbit) {
obj.stopOrbit();
obj.position = exitPos;
obj.remakeStandardOrbit();
}
else if(obj.hasLeaderAI) {
obj.teleportTo(exitPos, true);
int dummy = -1;
obj.moveTo(exitPos, dummy);
}
//Flag ship as no longer in ftl
if(ship !is null) {
ship.blueprint.clearTracking(ship);
ship.isFTLing = false;
}
obj.idleAllSupports();
//Clear tracking on arrival
uint cnt = obj.supportCount;
for(uint i = 0; i < cnt; ++i) {
Ship@ support = cast<Ship>(obj.supportShip[i]);
support.blueprint.clearTracking(support);
}
//Set rotation on arrival
obj.setRotation(facing);
return OS_COMPLETED;
}
};
int safetyFlag = -1;
void init() {
safetyFlag = getSystemFlag("JumpdriveSafety");
}
File diff suppressed because it is too large Load Diff
+61
View File
@@ -0,0 +1,61 @@
import orders.Order;
tidy class MoveOrder : Order {
vec3d destination;
quaterniond facing;
int moveId = -1;
int rotateId = -1;
MoveOrder(vec3d Destination, quaterniond Facing) {
destination = Destination;
facing = Facing;
facing.normalize();
}
MoveOrder(SaveFile& msg) {
Order::load(msg);
msg >> destination;
msg >> facing;
msg >> moveId;
msg >> rotateId;
}
void save(SaveFile& msg) {
Order::save(msg);
msg << destination;
msg << facing;
msg << moveId;
msg << rotateId;
}
OrderType get_type() {
return OT_Move;
}
string get_name() {
return "Moving";
}
bool get_hasMovement() {
return true;
}
vec3d getMoveDestination(const Object& obj) override {
return destination;
}
OrderStatus tick(Object& obj, double time) {
if(!obj.hasMover)
return OS_COMPLETED;
Ship@ ship = cast<Ship>(obj);
if(ship !is null)
ship.formationDest = facing;
if(obj.moveTo(destination, moveId)) {
obj.setRotation(facing);
return OS_COMPLETED;
}
return OS_BLOCKING;
}
};
+63
View File
@@ -0,0 +1,63 @@
import orders.Order;
tidy class OddityGateOrder : Order {
Oddity@ target;
int moveId = -1;
OddityGateOrder(Oddity& targ) {
@target = targ;
moveId = -1;
}
OddityGateOrder(SaveFile& msg) {
Order::load(msg);
msg >> target;
msg >> moveId;
}
void save(SaveFile& msg) {
Order::save(msg);
msg << target;
msg << moveId;
}
OrderType get_type() {
return OT_OddityGate;
}
string get_name() {
return "Warp with " + target.name;
}
bool get_hasMovement() {
return true;
}
vec3d getMoveDestination(const Object& obj) {
return target.position;
}
OrderStatus tick(Object& obj, double time) {
if(!obj.hasMover)
return OS_COMPLETED;
if(!target.isGate())
return OS_COMPLETED;
if(obj.moveTo(target, moveId, target.radius + obj.radius, enterOrbit=false)) {
vec3d toPos = target.getGateDest();
if(toPos == vec3d())
return OS_COMPLETED;
if(obj.hasLeaderAI) {
obj.teleportTo(toPos);
}
else {
obj.position = toPos;
obj.wake();
}
return OS_COMPLETED;
}
return OS_BLOCKING;
}
};
+97
View File
@@ -0,0 +1,97 @@
import orders;
import regions.regions;
export OrderStatus;
export Order;
export OrderType;
enum OrderStatus {
OS_BLOCKING,
OS_NONBLOCKING,
OS_COMPLETED
};
tidy class Order {
Order@ prev, next;
void destroy() {}
string get_name() {
return "N/A";
}
bool get_hasMovement() {
return false;
}
vec3d getMoveDestination(const Object& obj) {
return vec3d();
}
vec3d getMoveDestination(const Object& obj, Object@ target, double distance = 0.0) {
vec3d objPos = obj.position;
Order@ check = prev;
if(check !is null) {
while(check.prev !is null)
@check = check.prev;
objPos = check.getMoveDestination(obj);
}
double targDist;
if(target.isRegion) {
if(inRegion(cast<Region>(target), objPos))
return objPos;
targDist = target.radius * 0.85;
}
else
targDist = max(distance, obj.radius + target.radius);
vec3d dir = target.position - objPos;
return objPos + dir.normalized(dir.length - targDist);
}
OrderType get_type() {
return OT_INVALID;
}
OrderStatus tick(Object& obj, double time) {
return OS_BLOCKING;
}
uint getIndex() const {
const Order@ o = this;
uint ind = 0;
while(o.prev !is null) {
++ind;
@o = o.prev;
}
return ind;
}
bool cancel(Object& obj) {
return true;
}
void writeDesc(const Object& obj, Message& msg) {
msg << uint(type);
bool hasMove = hasMovement;
if(hasMove) {
msg.write1();
msg << getMoveDestination(obj);
}
else {
msg.write0();
}
}
void load(SaveFile& msg) {
}
void save(SaveFile& msg) {
uint8 tp = type;
msg << tp;
}
};
+70
View File
@@ -0,0 +1,70 @@
import orders.Order;
import pickups;
tidy class PickupOrder : Order {
Object@ target;
int moveId = -1;
PickupOrder(Object& targ) {
@target = targ;
}
PickupOrder(SaveFile& msg) {
Order::load(msg);
msg >> target;
msg >> moveId;
}
void save(SaveFile& msg) {
Order::save(msg);
msg << target;
msg << moveId;
}
string get_name() {
return "Pickup Pickup";
}
OrderType get_type() {
return OT_PickupOrder;
}
bool get_hasMovement() override {
return true;
}
vec3d getMoveDestination(const Object& obj) override {
return target.position;
}
OrderStatus tick(Object& obj, double time) {
Pickup@ pickup = cast<Pickup>(target);
if(pickup is null || !pickup.valid)
return OS_COMPLETED;
const PickupType@ type = getPickupType(pickup.PickupType);
if(!type.canPickup(pickup, obj))
return OS_COMPLETED;
if(obj.position.distanceTo(pickup.position) < 30.0 + obj.radius + pickup.radius) {
if(moveId != -1) {
obj.stopMoving();
moveId = -1;
}
if(!pickup.isPickupProtected) {
pickup.pickupPickup(obj);
return OS_COMPLETED;
}
return OS_BLOCKING;
}
else if(!obj.hasMover) {
return OS_COMPLETED;
}
else {
obj.moveTo(target, moveId);
}
return OS_BLOCKING;
}
};
+57
View File
@@ -0,0 +1,57 @@
import orders.Order;
import pickups;
tidy class RefreshOrder : Order {
Object@ target;
int moveId = -1;
RefreshOrder(Object& targ) {
@target = targ;
}
RefreshOrder(SaveFile& msg) {
Order::load(msg);
msg >> target;
msg >> moveId;
}
void save(SaveFile& msg) {
Order::save(msg);
msg << target;
msg << moveId;
}
string get_name() {
return "Refresh Supports";
}
OrderType get_type() {
return OT_Refresh;
}
bool get_hasMovement() override {
return true;
}
vec3d getMoveDestination(const Object& obj) override {
Object@ moveTo = cast<Region>(target);
if(moveTo is null)
@moveTo = target.region;
if(moveTo is null)
@moveTo = target;
return getMoveDestination(obj, moveTo);
}
OrderStatus tick(Object& obj, double time) {
Object@ moveTo = cast<Region>(target);
if(moveTo is null)
@moveTo = target.region;
if(moveTo is null)
@moveTo = target;
if(moveTo is obj.region || obj.moveTo(moveTo, moveId)) {
obj.refreshSupportsFrom(target);
return OS_COMPLETED;
}
return OS_BLOCKING;
}
};
+72
View File
@@ -0,0 +1,72 @@
import orders.Order;
tidy class ScanOrder : Order {
Anomaly@ target;
int moveId = -1;
int64 beam = 0;
ScanOrder(Anomaly& targ) {
@target = targ;
moveId = -1;
}
ScanOrder(SaveFile& msg) {
Order::load(msg);
msg >> target;
msg >> moveId;
}
~ScanOrder() {
removeBeam();
}
void save(SaveFile& msg) {
Order::save(msg);
msg << target;
msg << moveId;
}
OrderType get_type() {
return OT_Scan;
}
string get_name() {
return "Scan " + target.name;
}
bool get_hasMovement() {
return true;
}
vec3d getMoveDestination(const Object& obj) {
return target.position;
}
void removeBeam() {
if(beam != 0) {
removeGfxEffect(ALL_PLAYERS, beam);
beam = 0;
}
}
OrderStatus tick(Object& obj, double time) {
if(!obj.hasMover || !target.valid || target.getEmpireProgress(obj.owner) >= 1.f) {
removeBeam();
return OS_COMPLETED;
}
if(obj.position.distanceTo(target.position) < 30.0 + target.radius + obj.radius) {
target.addProgress(obj.owner, time);
if(beam == 0) {
beam = (obj.id << 32) | (0x2 << 24);
makeBeamEffect(ALL_PLAYERS, beam, obj, target, 0xffffffff, obj.radius, "Tractor", -1.0);
}
}
else {
obj.moveTo(target, moveId, 15.0 + target.radius + obj.radius, enterOrbit=false);
removeBeam();
}
return OS_BLOCKING;
}
};
+181
View File
@@ -0,0 +1,181 @@
import orders.Order;
import resources;
import attributes;
import ftl;
from objects.Oddity import createSlipstream;
import movement;
tidy class SlipstreamOrder : Order {
vec3d destination;
quaterniond facing;
double charge = 0.0;
int cost = 0;
int moveId = -1;
array<Object@> secondary;
SlipstreamOrder(vec3d pos) {
destination = pos;
}
SlipstreamOrder(SaveFile& msg) {
Order::load(msg);
msg >> destination;
msg >> facing;
msg >> moveId;
msg >> charge;
msg >> cost;
if(msg >= SV_0067) {
uint cnt = 0;
msg >> cnt;
secondary.length = cnt;
for(uint i = 0; i < cnt; ++i)
msg >> secondary[i];
}
}
void save(SaveFile& msg) override {
Order::save(msg);
msg << destination;
msg << facing;
msg << moveId;
msg << charge;
msg << cost;
uint cnt = secondary.length;
msg << cnt;
for(uint i = 0; i < cnt; ++i) {
msg << secondary[i];
}
}
OrderType get_type() override {
return OT_Slipstream;
}
string get_name() override {
return "Generating Slipstream";
}
bool cancel(Object& obj) override {
//Cannot cancel while already ftling
if(charge >= SLIPSTREAM_CHARGE_TIME || charge < 0.0)
return false;
//Refund a part of the ftl cost
if(cost > 0) {
double pct = 1.0 - min(charge / SLIPSTREAM_CHARGE_TIME, 1.0);
double refund = cost * pct;
obj.owner.modFTLStored(refund);
cost = 0;
}
//Mark ship as no longer FTLing
Ship@ ship = cast<Ship>(obj);
if(ship !is null)
ship.isFTLing = false;
return true;
}
bool get_hasMovement() override {
return true;
}
vec3d getMoveDestination(const Object& obj) override {
return destination;
}
OrderStatus tick(Object& obj, double time) override {
if(!obj.hasMover)
return OS_COMPLETED;
if(!canSlipstream(obj))
return OS_COMPLETED;
if(!canSlipstreamTo(obj, destination))
return OS_COMPLETED;
//Pay for the FTL
Ship@ ship = cast<Ship>(obj);
if(ship !is null && ship.delayFTL && charge >= 0) {
if(charge > 0)
charge = 0.001;
return OS_BLOCKING;
}
if(charge == 0) {
int scale = 1;
if(ship !is null) {
scale = ship.blueprint.design.size;
if(ship.group !is null)
scale *= ship.group.objectCount;
}
double dist = obj.position.distanceTo(destination);
cost = slipstreamCost(obj, scale, dist);
if(cost > 0) {
double consumed = obj.owner.consumeFTL(cost, false, record=false);
if(consumed < cost)
return OS_COMPLETED;
}
charge = 0.001;
//Mark ship as FTLing
if(ship !is null)
ship.isFTLing = true;
//Calculate needed facing
facing = quaterniond_fromVecToVec(vec3d_front(), destination - obj.position);
obj.stopMoving();
playParticleSystem("FTLCharge", vec3d(), quaterniond(), obj.radius * 4.0, obj);
playParticleSystem("FTLCharge", destination, quaterniond(), slipstreamInaccuracy(obj, destination) * 0.25);
}
if(charge > 0.0) {
bool isFacing = obj.rotateTo(facing, moveId);
//Charge up the slipstream drive for a while first
if(charge < SLIPSTREAM_CHARGE_TIME)
charge += time;
if(!isFacing) {
return OS_BLOCKING;
}
else {
if(charge < SLIPSTREAM_CHARGE_TIME)
return OS_BLOCKING;
charge = -1.0;
moveId = -1;
if(cost > 0)
obj.owner.modAttribute(EA_FTLEnergySpent, AC_Add, cost);
}
}
//Generate the slipstream
vec3d startPos = obj.position;
startPos += (obj.rotation * vec3d_front()).normalized(obj.radius * 2.5 + 15.0);
vec3d endPos = destination;
slipstreamModifyPosition(obj, endPos);
createSlipstream(startPos, endPos, slipstreamLifetime(obj), obj.owner);
//Flag ship as no longer in ftl
if(ship !is null) {
ship.blueprint.clearTracking(ship);
ship.isFTLing = false;
}
//Set movement
if(secondary.length != 0) {
vec3d movePos = destination;
movePos += facing * vec3d_front() * obj.radius * 15.0;
auto@ positions = getFleetTargetPositions(secondary, movePos, quaterniond_fromAxisAngle(vec3d_up(), pi) * facing);
for(uint i = 0, cnt = secondary.length; i < cnt; ++i)
secondary[i].moveAfterWait(positions[i], obj);
}
return OS_COMPLETED;
}
};
+718
View File
@@ -0,0 +1,718 @@
import saving;
import design_settings;
//Time needed for a support ship to expire if left without a leader
const double SUPPORT_EXPIRE_TIME = 3.0 * 60.0;
//Max distance a support ship can find a leader to attach to
const double MAX_LEADER_RESCUE_DIST = 1000.0;
const double MAX_LEADER_RESCUE_DIST_SQ = MAX_LEADER_RESCUE_DIST * MAX_LEADER_RESCUE_DIST;
const double MAX_SUPPORT_ABANDON_DIST = 2500.0;
const double MAX_SUPPORT_ABANDON_DIST_SQ = MAX_SUPPORT_ABANDON_DIST * MAX_SUPPORT_ABANDON_DIST;
enum SupportOrder {
SO_Idle,
SO_Attack,
SO_Interfere,
SO_Retreat,
SO_Raid,
SO_AttackBlind
};
tidy class SupportAI : Component_SupportAI, Savable {
double expire_progress = 0;
bool findLeader = true;
bool leaderDelta = false;
vec3d newOffset;
Object@ leader;
int leaderId = 0;
float psr = randomf(0.f,1.f);
float returnTimer = 0.f;
uint range = SR_Auto;
bool detached = false, freeToRaid = true;
double engageRange = -1.0;
SupportOrder order = SO_Idle;
uint goal = SG_Cannon;
Object@ target, relTarget, checkTarget;
SupportAI() {
}
void load(SaveFile& msg) {
msg >> expire_progress;
msg >> findLeader;
msg >> newOffset;
if(msg < SV_0054) {
order = SO_Attack; //The ship will automatically reset to the correct state
engageRange = 75.0;
}
else {
uint o = SO_Idle;
msg >> o;
order = SupportOrder(o);
msg >> target >> relTarget;
msg >> engageRange;
if(msg < SV_0068 && engageRange < 0.0)
engageRange = 75.0;
}
if(msg >= SV_0120) {
msg >> goal;
msg >> range;
msg >> detached;
}
else if(range == SR_Auto) {
range = SR_Far;
}
if(msg >= SV_0138)
msg >> leaderId;
}
void supportPostLoad(Object& obj) {
if(obj.isShip)
@leader = cast<Ship>(obj).Leader;
}
void save(SaveFile& msg) {
msg << expire_progress;
msg << findLeader;
msg << newOffset;
msg << uint(order);
msg << target << relTarget;
msg << engageRange;
msg << goal;
msg << range;
msg << detached;
msg << leaderId;
}
int get_LeaderID() {
return leaderId;
}
void supportIdle(Object& obj) {
obj.leaderLock = true;
order = SO_Idle;
@target = null;
@relTarget = null;
}
void supportIdle(Object& obj, bool immediate) {
if(detached && !immediate)
returnTimer = 6.f;
else
obj.leaderLock = true;
order = SO_Idle;
@target = null;
@relTarget = null;
}
void supportAttack(Object& obj, Object@ targ) {
auto@ ship = cast<Ship>(obj);
//Ignore attack orders if there are no weapons
if(engageRange <= 0 || ship.DPS <= 1.0e-4)
return;
switch(goal) {
case SG_Brawler: //Raid supports
freeToRaid = true;
case SG_Cavalry:
if(ship.MaxSupply > 1.0e-4 || (leader !is null && leader.freeRaiding))
order = SO_Raid;
else
order = SO_Attack;
break;
case SG_Artillery:
order = SO_Attack;
break;
case SG_Cannon:
if(leader !is null && leader.freeRaiding) {
order = SO_Raid;
freeToRaid = true;
}
else
order = SO_AttackBlind;
break;
case SG_Satellite:
break;
default:
return;
}
if(obj.isShip) {
Ship@ ship = cast<Ship>(obj);
if(targ !is null)
ship.blueprint.target(obj, targ, TF_Preference);
}
@target = targ;
@relTarget = null;
}
void supportInterfere(Object& obj, Object@ targ, Object@ protect) {
if(goal == SG_Shield) {
order = SO_Interfere;
@target = targ;
@relTarget = protect;
}
}
void cavalryCharge(Object& obj, Object@ targ) {
if(goal == SG_Cavalry) {
freeToRaid = true;
supportAttack(obj, targ);
}
else {
@checkTarget = targ;
}
}
void set_doRaids(bool raid) {
freeToRaid = raid;
}
bool get_isDetached(Object& obj) {
return detached;
}
bool get_isRaiding() {
return detached || order == SO_Raid;
}
void resupplyFromFleet(Object& obj) {
Ship@ ship = cast<Ship>(obj);
Ship@ leaderShip = cast<Ship>(leader);
if(ship.Supply < ship.MaxSupply) {
if(leaderShip is null)
return;
double require = ship.MaxSupply - ship.Supply;
if(leaderShip.Supply < require)
return;
leaderShip.consumeSupply(require);
ship.Supply = min(ship.MaxSupply, ship.Supply + require);
}
}
void dumpSupplyToFleet(Object& obj) {
Ship@ ship = cast<Ship>(obj);
Ship@ leaderShip = cast<Ship>(leader);
if(ship.Supply >= 0.001) {
if(leaderShip is null)
return;
leaderShip.refundSupply(ship.Supply);
ship.Supply = 0;
}
}
void supportRetreat(Object& obj) {
order = SO_Retreat;
@target = null;
@relTarget = null;
}
void set_supportEngageRange(double range) {
engageRange = range;
}
void preventExpire() {
expire_progress = -FLOAT_INFINITY;
}
void supportInit(Object& obj) {
Ship@ ship = cast<Ship>(obj);
const Design@ dsg = ship.blueprint.design;
auto@ settings = cast<const DesignSettings>(dsg.settings);
if(settings !is null) {
if(dsg.hasTag(ST_Satellite))
goal = SG_Satellite;
else
goal = settings.behavior;
range = settings.range;
}
//TODO: Properly resolve auto range here?
if(range == SR_Auto)
range = SR_Far;
}
//Complete a leader change received from the leader
void completeRegisterLeader(Object& obj, Object@ newLeader) {
Ship@ ship = cast<Ship>(obj);
Object@ prevLeader = leader;
if(prevLeader !is null)
prevLeader.unregisterSupport(obj, false);
@ship.Leader = newLeader;
if(newLeader !is null) {
leaderId = newLeader.id;
@leader = newLeader;
}
else {
leaderId = 0;
@leader = null;
}
if(newOffset.lengthSQ > 0.0001)
obj.setFleetOffset(newOffset);
ship.triggerLeaderChange(prevLeader, newLeader);
leaderDelta = true;
}
void clearLeader(Object& obj, Object@ prevLeader) {
Ship@ ship = cast<Ship>(obj);
if(leader is prevLeader) {
@ship.Leader = null;
@leader = null;
leaderId = 0;
ship.triggerLeaderChange(prevLeader, null);
}
}
void transferTo(Object& obj, Object@ newLeader) {
newOffset = vec3d();
newLeader.registerSupport(obj);
Ship@ ship = cast<Ship>(obj);
if(ship !is null && ship.isFree)
ship.makeNotFree();
}
void transferTo(Object& obj, Object@ newLeader, vec3d offset) {
newOffset = offset;
newLeader.registerSupport(obj);
Ship@ ship = cast<Ship>(obj);
if(ship !is null && ship.isFree)
ship.makeNotFree();
}
void setFleetOffset(Object& obj, vec3d offset) {
Ship@ ship = cast<Ship>(obj);
double fradius = leader.getFormationRadius();
if(offset.length > fradius)
return;
ship.formationDest.xyz = offset;
newOffset = offset;
leaderDelta = true;
}
void supportDestroy(Object& obj) {
Ship@ ship = cast<Ship>(obj);
if(leader !is null) {
auto@ prevLeader = leader;
leader.unregisterSupport(obj, true);
@ship.Leader = null;
@leader = null;
leaderId = 0;
ship.triggerLeaderChange(prevLeader, null);
}
@target = null;
@relTarget = null;
}
void supportScuttle(Object& obj) {
Ship@ ship = cast<Ship>(obj);
Object@ prevLeader = leader;
if(prevLeader !is null)
prevLeader.unregisterSupport(obj, false);
findLeader = false;
@ship.Leader = null;
@leader = null;
leaderId = 0;
ship.triggerLeaderChange(prevLeader, null);
obj.destroy();
leaderDelta = true;
}
Object@ findNearbyLeader(Object& obj, Object& findFrom, uint depth, int size) {
--depth;
vec3d pos = obj.position;
for(uint i = 0; i < TARGET_COUNT; ++i) {
Object@ other = findFrom.targets[i];
if(other.owner is obj.owner) {
Object@ check = other;
if(check.isShip && check.hasSupportAI && check.LeaderID != 0)
@check = cast<Ship>(check).Leader;
if(check !is null && check.hasLeaderAI) {
if(pos.distanceToSQ(check.position) < MAX_LEADER_RESCUE_DIST_SQ && check.canTakeSupport(size, pickup=true))
return check;
}
}
if(depth != 0) {
@other = findNearbyLeader(obj, other, depth, size);
if(other !is null)
return other;
}
}
return null;
}
double evaluateTarget(Object& obj, Object& targ, double maxDistSQ) {
Ship@ ship = cast<Ship>(targ);
if(ship is null || !obj.owner.isHostile(targ.owner))
return 0;
if(targ.position.distanceToSQ(leader.position) > maxDistSQ)
return 0;
switch(goal) {
case SG_Brawler:
return (1.0 + ship.DPS) * (ship.hasSupportAI ? 1.0 : 0.001);
case SG_Shield:
return (1.0 + ship.DPS);
case SG_Cavalry:
return (1.0 + ship.DPS) * (ship.hasSupportAI ? 1.0 : 0.001);
case SG_Artillery:
return (1.0 + ship.DPS) * (ship.hasSupportAI ? 1.0 : 0.001);
case SG_Cannon:
return ship.hasSupportAI ? 1.0 : 0.001;
case SG_Support:
return 1.0;
default:
return 1.0;
}
return 0;
}
uint targCycle = 0;
Object@ replaceTarget(Object& obj, Object& prev, double maxDistSQ) {
Object@ targ = obj.targets[++targCycle % 3];
double b = evaluateTarget(obj, targ, maxDistSQ);
if(b <= 0)
return prev;
double a = evaluateTarget(obj, prev, maxDistSQ);
if(a >= b * 0.9)
return prev;
else
return targ;
}
void supportTick(Object& obj, double time) {
Ship@ ship = cast<Ship>(obj);
double abandonDist = MAX_SUPPORT_ABANDON_DIST_SQ;
double raidDist = 0;
if(leader !is null) {
raidDist = leader.raidRange;
if(raidDist < 0) {
Region@ reg = obj.region;
if(reg !is null) {
abandonDist = max(abandonDist, sqr(reg.radius));
raidDist = reg.radius;
}
else
raidDist = 0;
}
else {
abandonDist = max(abandonDist, sqr(raidDist * 1.5));
}
}
//Tick forward the expiration
if(leader is null) {
detached = true;
if(ship.velocity.lengthSQ > 0.01)
ship.stopMoving(false);
expire_progress += time;
if(expire_progress > SUPPORT_EXPIRE_TIME + (double(uint8(obj.id)) / 128.0) - 1.0) {
obj.destroy();
return;
}
}
else if(expire_progress > time) {
expire_progress -= time;
}
else {
expire_progress = 0;
}
if(leader !is null) {
if(leader.owner !is obj.owner
|| (ship.position.distanceToSQ(leader.position) > abandonDist
&& (ship.region is null || ship.region !is leader.region)
&& !ship.isFTLing && (!leader.isShip || !cast<Ship>(leader).isFTLing))
&& (!detached || order == SO_Idle) && !obj.inCombat) {
//We tell the leader we died, as we are being abandoned and will probably die
leader.unregisterSupport(obj, true);
auto@ prevLeader = leader;
@ship.Leader = null;
@leader = null;
leaderId = 0;
ship.triggerLeaderChange(prevLeader, null);
}
else {
double fleetRad = leader.getFormationRadius();
double engageDist = engageRange + fleetRad;
{
//If the target is out of range, return to the fleet
bool wasDetached = detached;
detached = leader.position.distanceToSQ(obj.position) > fleetRad * fleetRad * 1.05;
if(detached && !wasDetached)
resupplyFromFleet(obj);
}
switch(order) {
case SO_Idle:
if(!detached && ship.Supply > 1.0e-4)
dumpSupplyToFleet(obj);
switch(goal) {
case SG_Shield: {
Object@ targ = ship.blueprint.getCombatTarget();
double quality = 0;
for(uint i = 0; i < TARGET_COUNT; ++i) {
Object@ t = obj.targets[i];
if(obj.owner.isHostile(t.owner) && (t.isShip || t.isOrbital)) {
double q = evaluateTarget(obj, t, INFINITY);
if(q > quality) {
@targ = t;
quality = q;
}
break;
}
}
if(targ !is null)
supportInterfere(obj, leader, targ);
}
break;
case SG_Support:
case SG_Cavalry:
break;
default: {
Object@ targ = ship.blueprint.getCombatTarget();
double quality = 0;
double maxDist = max(raidDist*raidDist, engageDist*engageDist);
for(uint i = 0; i < TARGET_COUNT; ++i) {
Object@ t = obj.targets[i];
if(obj.owner.isHostile(t.owner) && (t.isShip || t.isOrbital)) {
double q = evaluateTarget(obj, t, maxDist);
if(q > quality) {
@targ = t;
quality = q;
}
break;
}
}
if(checkTarget !is null) {
double q = evaluateTarget(obj, checkTarget, maxDist);
if(q > quality) {
@targ = checkTarget;
quality = q;
}
@checkTarget = null;
}
if(targ !is null && obj.owner.isHostile(targ.owner))
supportAttack(obj, targ);
}
}
if(order == SO_Idle) {
if(returnTimer > 0.f) {
returnTimer -= time;
if(returnTimer <= 0.f || !detached)
obj.leaderLock = true;
}
}
else {
returnTimer = 0.f;
}
break;
case SO_Attack:
if(target is null || !target.valid || target.position.distanceToSQ(leader.position) > engageDist*engageDist || ship.DPS < 1.0e-4 || !obj.owner.isHostile(target.owner)) {
supportIdle(obj);
}
else {
@target = replaceTarget(obj, target, engageDist*engageDist);
vec3d dest = leader.position + obj.internalDestination;
//If the target is out of range, return to the fleet
if(detached || leader.position.distanceToSQ(target.position) > sqr(engageRange + fleetRad)) {
supportIdle(obj);
break;
}
vec3d fireFrom = obj.leaderLock ? obj.position : dest;
bool relocate = fireFrom.distanceToSQ(target.position) > engageRange * engageRange || fireFrom.distanceToSQ(leader.position) > fleetRad * fleetRad || obj.isColliding;
if(!relocate) {
if(obj.leaderLock || obj.position.distanceToSQ(dest) < obj.radius * obj.radius) {
line3dd line = line3dd(obj.position, target.position);
Object@ hit = trace(line, obj.owner.hostileMask | 0x1);
if(hit !is target)
relocate = true;
}
}
if(relocate) {
double innerDist = leader.radius + obj.radius;
vec3d dest = random3d(innerDist, fleetRad);
if((dest + leader.position).distanceToSQ(target.position) < engageRange * engageRange) {
line3dd line = line3dd(dest + leader.position, target.position);
Object@ hit = trace(line, obj.owner.hostileMask | 0x1);
if(hit is target) {
int moveId = -1;
obj.leaderLock = false;
returnTimer = 0.f;
obj.moveTo(dest, moveId, doPathing=false, enterOrbit=false);
}
}
}
}
break;
case SO_Interfere:
if(target is null || relTarget is null || !target.valid || !relTarget.valid || !obj.owner.isHostile(target.owner) || !obj.inCombat)
supportIdle(obj);
else {
double fleetRad = leader.getFormationRadius();
line3dd path = line3dd(leader.position, target.position);
double targDist = path.length;
if(targDist > fleetRad * 4.0) {
supportIdle(obj);
break;
}
vec3d curDest = leader.position + obj.internalDestination;
vec3d pt = path.getClosestPoint(curDest, false);
if(obj.leaderLock || pt.distanceToSQ(curDest) > obj.radius * obj.radius) {
double outerDist = targDist - obj.radius * 1.5 - target.radius;
if(outerDist > fleetRad)
outerDist = fleetRad;
double innerDist = leader.radius + obj.radius * 1.5;
vec3d dest = path.direction * (innerDist + (outerDist - innerDist) * psr) + random3d(obj.radius * 0.5);
int moveId = -1;
obj.leaderLock = false;
returnTimer = 0.f;
obj.moveTo(dest, moveId, doPathing=false, enterOrbit=false);
}
}
break;
case SO_Raid:
if(target is null || !target.valid || target.position.distanceToSQ(leader.position) > raidDist*raidDist || ship.DPS < 1.0e-4 || !obj.owner.isHostile(target.owner)) {
supportIdle(obj, immediate=false);
}
else if(ship.Supply > 0.0 || leader.freeRaiding) {
if(!freeToRaid)
break;
//TODO: Handle very low supply levels better
@target = replaceTarget(obj, target, raidDist*raidDist);
vec3d dest = leader.position + obj.internalDestination;
vec3d fireFrom = obj.leaderLock ? obj.position : dest;
bool relocate = obj.position.distanceToSQ(dest) < obj.radius * obj.radius ||
fireFrom.distanceToSQ(target.position) > engageRange * engageRange ||
dest.distanceToSQ(leader.position) > abandonDist * 0.95;
if(!relocate) {
if(obj.leaderLock || obj.position.distanceToSQ(dest) < obj.radius * obj.radius) {
line3dd line = line3dd(obj.position, target.position);
Object@ hit = trace(line, obj.owner.hostileMask | 0x1);
if(hit !is target)
relocate = true;
}
}
if(relocate) {
double innerDist = target.radius + obj.radius;
double rMax = (range == SR_Close ? engageRange * 0.52 : engageRange);
vec3d dest = target.position + random3d(rMax * 0.9, rMax * 0.999);
if(dest.distanceToSQ(leader.position) < abandonDist * 0.95) {
line3dd line = line3dd(dest, target.position);
Object@ hit = trace(line, obj.owner.hostileMask | 0x1);
if(hit is target) {
int moveId = -1;
obj.leaderLock = false;
returnTimer = 0.f;
obj.moveTo(dest - leader.position, moveId, doPathing=false, enterOrbit=false);
}
}
}
break;
}
else if(detached) {
supportIdle(obj, immediate=true);
break;
}
else {
resupplyFromFleet(obj);
}
break;
case SO_AttackBlind:
if(target is null || !target.valid || target.position.distanceToSQ(leader.position) > engageDist*engageDist || ship.DPS < 1.0e-4 || !obj.owner.isHostile(target.owner)) {
supportIdle(obj);
}
else {
@target = replaceTarget(obj, target, engageDist*engageDist);
vec3d dest = leader.position + obj.internalDestination;
double fleetRad = leader.getFormationRadius();
//If the target is out of range, return to the fleet
if(detached || leader.position.distanceToSQ(target.position) > sqr(engageRange + fleetRad)) {
supportIdle(obj);
break;
}
vec3d fireFrom = obj.leaderLock ? obj.position : dest;
bool relocate = fireFrom.distanceToSQ(target.position) > engageRange * engageRange || fireFrom.distanceToSQ(leader.position) > fleetRad * fleetRad || obj.isColliding;
if(!relocate) {
if(obj.leaderLock || obj.position.distanceToSQ(dest) < obj.radius * obj.radius) {
line3dd line = line3dd(obj.position, target.position);
Object@ hit = trace(line, obj.owner.hostileMask | 0x1);
if(hit !is null && hit !is target && !(hit.owner is target.owner && hit.type == target.type))
relocate = true;
}
}
if(relocate) {
double innerDist = leader.radius + obj.radius;
vec3d dest = random3d(innerDist, fleetRad);
if((dest + leader.position).distanceToSQ(target.position) < engageRange * engageRange) {
line3dd line = line3dd(dest + leader.position, target.position);
Object@ hit = trace(line, obj.owner.hostileMask | 0x1);
if(hit is target || hit is null || (hit.owner is target.owner && hit.type == target.type)) {
int moveId = -1;
obj.leaderLock = false;
returnTimer = 0.f;
obj.moveTo(dest, moveId, doPathing=false, enterOrbit=false);
}
}
}
}
break;
case SO_Retreat:
supportIdle(obj);
break;
}
}
}
else if(findLeader && obj.hasMover && obj.maxAcceleration > 0) {
//Try to find a new leader
Object@ newLeader = findNearbyLeader(obj, obj, 2, ship.blueprint.design.size);
if(newLeader !is null)
newLeader.registerSupport(obj, true);
}
}
void writeSupportAI(const Object& obj, Message& msg) {
const Ship@ ship = cast<const Ship>(obj);
msg << ship.Leader;
}
bool writeSupportAIDelta(const Object& obj, Message& msg) {
const Ship@ ship = cast<const Ship>(obj);
if(leaderDelta) {
msg.write1();
msg << ship.Leader;
leaderDelta = false;
return true;
}
return false;
}
};
+50
View File
@@ -0,0 +1,50 @@
import orders.Order;
tidy class WaitOrder : Order {
Object@ waitTarget;
bool moveTo = false;
int moveId = -1;
WaitOrder(Object@ waitFor, bool moveTo) {
@waitTarget = waitFor;
this.moveTo = moveTo;
}
WaitOrder(SaveFile& msg) {
Order::load(msg);
msg >> waitTarget;
msg >> moveTo;
msg >> moveId;
}
void save(SaveFile& msg) {
Order::save(msg);
msg << waitTarget;
msg << moveTo;
msg << moveId;
}
OrderType get_type() {
return OT_Wait;
}
string get_name() {
return "Waiting";
}
bool get_hasMovement() {
return waitTarget !is null && waitTarget.valid;
}
vec3d getMoveDestination(const Object& obj) override {
if(waitTarget is null || !waitTarget.valid)
return vec3d();
return waitTarget.position;
}
OrderStatus tick(Object& obj, double time) {
if(moveTo && waitTarget !is null && waitTarget.valid)
obj.moveTo(waitTarget, moveId, 0.0);
return OS_BLOCKING;
}
};