Files
2018-07-17 14:15:37 +02:00

618 lines
13 KiB
ActionScript

// Fleets
// ------
// Manages data about fleets and missions, as well as making sure fleets
// return to their station after a mission.
//
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.Systems;
import empire_ai.weasel.Designs;
import empire_ai.weasel.Movement;
enum FleetClass {
FC_Scout,
FC_Combat,
FC_Slipstream,
FC_Mothership,
FC_ALL
};
enum MissionPriority {
MiP_Background,
MiP_Normal,
MiP_High,
MiP_Critical,
}
class Mission {
int id = -1;
bool completed = false;
bool canceled = false;
uint priority = MiP_Normal;
void _save(Fleets& fleets, SaveFile& file) {
file << completed;
file << canceled;
file << priority;
save(fleets, file);
}
void _load(Fleets& fleets, SaveFile& file) {
file >> completed;
file >> canceled;
file >> priority;
load(fleets, file);
}
void save(Fleets& fleets, SaveFile& file) {
}
void load(Fleets& fleets, SaveFile& file) {
}
bool get_isActive() {
return true;
}
double getPerformWeight(AI& ai, FleetAI& fleet) {
return 1.0;
}
void start(AI& ai, FleetAI& fleet) {
}
void cancel(AI& ai, FleetAI& fleet) {
}
void tick(AI& ai, FleetAI& fleet, double time) {
}
};
final class FleetAI {
uint fleetClass;
Object@ obj;
Mission@ mission;
Region@ stationed;
bool stationedFactory = true;
double filled = 0.0;
double idleSince = 0.0;
double fillStaticSince = 0.0;
void save(Fleets& fleets, SaveFile& file) {
file << fleetClass;
file << stationed;
file << filled;
file << idleSince;
file << fillStaticSince;
file << stationedFactory;
fleets.saveMission(file, mission);
}
void load(Fleets& fleets, SaveFile& file) {
file >> fleetClass;
file >> stationed;
file >> filled;
file >> idleSince;
file >> fillStaticSince;
file >> stationedFactory;
@mission = fleets.loadMission(file);
}
bool get_isHome() {
if(stationed is null)
return true;
return obj.region is stationed;
}
bool get_busy() {
return mission !is null;
}
double get_strength() {
return obj.getFleetStrength();
}
double get_supplies() {
Ship@ ship = cast<Ship>(obj);
if(ship is null)
return 1.0;
double maxSupply = ship.MaxSupply;
if(maxSupply <= 0)
return 1.0;
return ship.Supply / maxSupply;
}
double get_remainingSupplies() {
Ship@ ship = cast<Ship>(obj);
if(ship is null)
return 0.0;
return ship.Supply;
}
double get_radius() {
return obj.getFormationRadius();
}
double get_fleetHealth() {
return obj.getFleetStrength() / obj.getFleetMaxStrength();
}
double get_flagshipHealth() {
Ship@ ship = cast<Ship>(obj);
if(ship is null)
return 1.0;
return ship.blueprint.currentHP / ship.blueprint.design.totalHP;
}
bool get_actionableState() {
if(isHome && obj.hasOrderedSupports && stationedFactory)
return false;
if(supplies < 0.75)
return false;
if(filled < 0.5)
return false;
if(filled < 1.0 && gameTime < fillStaticSince + 90.0)
return false;
return true;
}
bool get_readyForAction() {
if(mission !is null)
return false;
if(isHome && obj.hasOrderedSupports && stationedFactory)
return false;
if(supplies < 0.75)
return false;
if(filled < 0.5)
return false;
if(filled < 1.0 && gameTime < fillStaticSince + 90.0)
return false;
if(obj.isMoving) {
if(obj.velocity.length / obj.maxAcceleration > 16.0)
return false;
}
return true;
}
bool tick(AI& ai, Fleets& fleets, double time) {
//Make sure we still exist
if(!obj.valid || obj.owner !is ai.empire) {
if(mission !is null) {
mission.canceled = true;
@mission = null;
}
return false;
}
//Record data
int supUsed = obj.SupplyUsed;
int supCap = obj.SupplyCapacity;
int supGhost = obj.SupplyGhost;
int supOrdered = obj.SupplyOrdered;
double newFill = 1.0;
if(supCap > 0.0)
newFill = double(supUsed - supGhost - supOrdered) / double(supCap);
if(newFill != filled) {
fillStaticSince = gameTime;
filled = newFill;
}
//Perform our mission
if(mission !is null) {
if(!mission.completed && !mission.canceled)
mission.tick(ai, this, time);
if(mission.completed || mission.canceled) {
@mission = null;
idleSince = gameTime;
}
}
//Return to where we're stationed if we're not doing anything
if(mission is null && stationed !is null && fleetClass != FC_Scout) {
if(gameTime >= idleSince + ai.behavior.fleetIdleReturnStationedTime) {
if(obj.region !is stationed && !obj.hasOrders) {
if(fleets.log)
ai.print("Returning to station in "+stationed.name, obj);
fleets.movement.move(obj, stationed, spread=true);
}
}
}
return true;
}
};
class Fleets : AIComponent {
Systems@ systems;
Designs@ designs;
Movement@ movement;
array<FleetAI@> fleets;
int nextMissionId = 0;
double totalStrength = 0;
double totalMaxStrength = 0;
void create() {
@systems = cast<Systems>(ai.systems);
@designs = cast<Designs>(ai.designs);
@movement = cast<Movement>(ai.movement);
}
void save(SaveFile& file) {
file << nextMissionId;
file << totalStrength;
file << totalMaxStrength;
uint cnt = fleets.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
saveAI(file, fleets[i]);
fleets[i].save(this, file);
}
}
void load(SaveFile& file) {
file >> nextMissionId;
file >> totalStrength;
file >> totalMaxStrength;
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
FleetAI@ flAI = loadAI(file);
if(flAI !is null)
flAI.load(this, file);
else
FleetAI().load(this, file);
}
}
void saveAI(SaveFile& file, FleetAI@ flAI) {
if(flAI is null) {
file.write0();
return;
}
file.write1();
file << flAI.obj;
}
FleetAI@ loadAI(SaveFile& file) {
if(!file.readBit())
return null;
Object@ obj;
file >> obj;
if(obj is null)
return null;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
if(fleets[i].obj is obj)
return fleets[i];
}
FleetAI flAI;
@flAI.obj = obj;
fleets.insertLast(flAI);
return flAI;
}
array<Mission@> savedMissions;
array<Mission@> loadedMissions;
void postSave(AI& ai) {
savedMissions.length = 0;
}
void postLoad(AI& ai) {
loadedMissions.length = 0;
}
void saveMission(SaveFile& file, Mission@ mission) {
if(mission is null) {
file.write0();
return;
}
file.write1();
file << mission.id;
if(mission.id == -1) {
storeMission(file, mission);
}
else {
bool found = false;
for(uint i = 0, cnt = savedMissions.length; i < cnt; ++i) {
if(savedMissions[i] is mission) {
found = true;
break;
}
}
if(!found) {
storeMission(file, mission);
savedMissions.insertLast(mission);
}
}
}
Mission@ loadMission(SaveFile& file) {
if(!file.readBit())
return null;
int id = 0;
file >> id;
if(id == -1) {
Mission@ miss = createMission(file);
miss.id = id;
return miss;
}
else {
for(uint i = 0, cnt = loadedMissions.length; i < cnt; ++i) {
if(loadedMissions[i].id == id)
return loadedMissions[i];
}
Mission@ miss = createMission(file);
miss.id = id;
loadedMissions.insertLast(miss);
return miss;
}
}
void storeMission(SaveFile& file, Mission@ mission) {
auto@ cls = getClass(mission);
auto@ mod = cls.module;
file << mod.name;
file << cls.name;
mission._save(this, file);
}
Mission@ createMission(SaveFile& file) {
string modName;
string clsName;
file >> modName;
file >> clsName;
auto@ mod = getScriptModule(modName);
if(mod is null) {
error("ERROR: AI Load could not find module for mission "+modName+"::"+clsName);
return null;
}
auto@ cls = mod.getClass(clsName);
if(cls is null) {
error("ERROR: AI Load could not find class for mission "+modName+"::"+clsName);
return null;
}
auto@ miss = cast<Mission>(cls.create());
if(miss is null) {
error("ERROR: AI Load could not create class instance for mission "+modName+"::"+clsName);
return null;
}
miss._load(this, file);
return miss;
}
void checkForFleets() {
auto@ data = ai.empire.getFlagships();
Object@ obj;
while(receive(data, obj)) {
if(obj !is null)
register(obj);
}
}
bool haveCombatReadyFleets() {
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
auto@ flAI = fleets[i];
if(flAI.fleetClass != FC_Combat)
continue;
if(!flAI.readyForAction)
continue;
return true;
}
return false;
}
uint countCombatReadyFleets() {
uint count = 0;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
auto@ flAI = fleets[i];
if(flAI.fleetClass != FC_Combat)
continue;
if(!flAI.readyForAction)
continue;
count += 1;
}
return count;
}
bool allFleetsCombatReady() {
bool have = false;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
auto@ flAI = fleets[i];
if(flAI.fleetClass != FC_Combat)
continue;
if(!flAI.readyForAction)
return false;
have = true;
}
return have;
}
uint prevFleetCount = 0;
double checkTimer = 0;
void focusTick(double time) override {
//Check for any newly obtained fleets
uint curFleetCount = ai.empire.fleetCount;
checkTimer += time;
if(curFleetCount != prevFleetCount || checkTimer > 60.0) {
checkForFleets();
prevFleetCount = curFleetCount;
checkTimer = 0;
}
//Calculate our current strengths
totalStrength = 0;
totalMaxStrength = 0;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
totalStrength += sqrt(fleets[i].obj.getFleetStrength());
totalMaxStrength += sqrt(fleets[i].obj.getFleetMaxStrength());
}
totalStrength = sqr(totalStrength);
totalMaxStrength = sqr(totalMaxStrength);
}
double getTotalStrength(uint checkClass, bool idleOnly = false, bool readyOnly = false) {
double str = 0.0;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
auto@ flAI = fleets[i];
if((flAI.fleetClass == checkClass || checkClass == FC_ALL)
&& (!idleOnly || flAI.mission is null)
&& (!readyOnly || flAI.readyForAction))
str += sqrt(fleets[i].obj.getFleetStrength());
}
return str*str;
}
void tick(double time) override {
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
if(!fleets[i].tick(ai, this, time)) {
fleets.removeAt(i);
--i; --cnt;
continue;
}
Region@ reg = fleets[i].obj.region;
if(reg !is null)
systems.focus(reg);
}
}
MoveOrder@ returnToBase(FleetAI@ fleet, uint priority = MP_Normal) {
if(fleet.stationed !is null)
return movement.move(fleet.obj, fleet.stationed, priority, spread=true);
return null;
}
FleetAI@ register(Object@ obj) {
FleetAI@ flAI = getAI(obj);
if(flAI is null) {
@flAI = FleetAI();
@flAI.obj = obj;
@flAI.stationed = obj.region;
obj.setHoldPosition(true);
uint designClass = designs.classify(obj);
if(designClass == DP_Scout)
flAI.fleetClass = FC_Scout;
else if(designClass == DP_Slipstream)
flAI.fleetClass = FC_Slipstream;
else if(designClass == DP_Mothership)
flAI.fleetClass = FC_Mothership;
else
flAI.fleetClass = FC_Combat;
fleets.insertLast(flAI);
}
return flAI;
}
void register(Mission@ mission) {
if(mission.id == -1)
mission.id = nextMissionId++;
}
FleetAI@ getAI(Object@ obj) {
if(obj is null)
return null;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
if(fleets[i].obj is obj)
return fleets[i];
}
return null;
}
uint count(uint checkClass) {
uint amount = 0;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
auto@ flAI = fleets[i];
if(flAI.fleetClass == checkClass || checkClass == FC_ALL)
amount += 1;
}
return amount;
}
bool haveIdle(uint checkClass) {
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
auto@ flAI = fleets[i];
if((flAI.fleetClass == checkClass || checkClass == FC_ALL) && flAI.mission is null)
return true;
}
return false;
}
double closestIdleTo(uint checkClass, const vec3d& position) {
double closest = INFINITY;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
auto@ flAI = fleets[i];
if((flAI.fleetClass != checkClass && checkClass != FC_ALL) || flAI.mission !is null)
continue;
double d = flAI.obj.position.distanceTo(position);
if(d < closest)
closest = d;
}
return closest;
}
FleetAI@ performMission(Mission@ mission) {
FleetAI@ perform;
double bestWeight = 0.0;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
auto@ flAI = fleets[i];
if(flAI.mission !is null)
continue;
double w = mission.getPerformWeight(ai, flAI);
if(w > bestWeight) {
bestWeight = w;
@perform = flAI;
}
}
if(perform !is null) {
@perform.mission = mission;
register(mission);
mission.start(ai, perform);
}
return perform;
}
FleetAI@ performMission(FleetAI@ fleet, Mission@ mission) {
if(fleet.mission !is null) {
fleet.mission.cancel(ai, fleet);
fleet.mission.canceled = true;
}
@fleet.mission = mission;
register(mission);
mission.start(ai, fleet);
return fleet;
}
};
AIComponent@ createFleets() {
return Fleets();
}