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
+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;
}
};