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