381 lines
8.0 KiB
ActionScript
381 lines
8.0 KiB
ActionScript
// Energy
|
|
// ------
|
|
// Manage the use of energy on artifacts and other things.
|
|
//
|
|
|
|
import empire_ai.weasel.WeaselAI;
|
|
import empire_ai.weasel.Systems;
|
|
|
|
import ai.consider;
|
|
|
|
import artifacts;
|
|
import abilities;
|
|
import systems;
|
|
|
|
from ai.artifacts import Artifacts, ArtifactConsider, ArtifactAI;
|
|
|
|
double effCostEstimate(double cost, double freeStorage) {
|
|
double free = min(cost, freeStorage);
|
|
cost -= free;
|
|
|
|
double effStep = config::ENERGY_EFFICIENCY_STEP;
|
|
double eff = 0.0;
|
|
double step = 1.0;
|
|
while(cost > 0) {
|
|
eff += (cost / effStep) * step;
|
|
cost -= effStep;
|
|
step *= 2.0;
|
|
}
|
|
return eff * effStep + free;
|
|
}
|
|
|
|
class ConsiderEnergy : ArtifactConsider {
|
|
int id = -1;
|
|
const ArtifactType@ type;
|
|
Artifact@ artifact;
|
|
Ability@ ability;
|
|
Object@ target;
|
|
vec3d pointTarget;
|
|
double cost = 0.0;
|
|
double value = 0.0;
|
|
|
|
void save(AI& ai, SaveFile& file) {
|
|
file << id;
|
|
file << artifact;
|
|
file << target;
|
|
file << cost;
|
|
file << value;
|
|
file << pointTarget;
|
|
}
|
|
|
|
void load(AI& ai, SaveFile& file) {
|
|
file >> id;
|
|
file >> artifact;
|
|
file >> target;
|
|
file >> cost;
|
|
file >> value;
|
|
file >> pointTarget;
|
|
|
|
if(artifact !is null)
|
|
init(ai, artifact);
|
|
}
|
|
|
|
void setTarget(Object@ obj) {
|
|
@target = obj;
|
|
}
|
|
|
|
Object@ getTarget() {
|
|
return target;
|
|
}
|
|
|
|
bool canTarget(Object@ obj) {
|
|
if(ability.targets.length != 0) {
|
|
auto@ targ = ability.targets[0];
|
|
@targ.obj = obj;
|
|
targ.filled = true;
|
|
return ability.isValidTarget(0, targ);
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
void setTargetPosition(const vec3d& point) {
|
|
pointTarget = point;
|
|
}
|
|
|
|
vec3d getTargetPosition() {
|
|
return pointTarget;
|
|
}
|
|
|
|
bool canTargetPosition(const vec3d& point) {
|
|
if(ability.targets.length != 0) {
|
|
auto@ targ = ability.targets[0];
|
|
targ.point = point;
|
|
targ.filled = true;
|
|
return ability.isValidTarget(0, targ);
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
void init(AI& ai, Artifact@ artifact) {
|
|
@this.artifact = artifact;
|
|
@type = getArtifactType(artifact.ArtifactType);
|
|
|
|
if(ability is null)
|
|
@ability = Ability();
|
|
if(type.secondaryChance > 0 && type.abilities.length >= 2
|
|
&& randomd() < type.secondaryChance) {
|
|
ability.id = 1;
|
|
@ability.type = type.abilities[1];
|
|
}
|
|
else {
|
|
ability.id = 0;
|
|
@ability.type = type.abilities[0];
|
|
}
|
|
ability.targets = Targets(ability.type.targets);
|
|
@ability.obj = artifact;
|
|
@ability.emp = ai.empire;
|
|
}
|
|
|
|
bool isValid(AI& ai, Energy& energy) {
|
|
return energy.canUse(artifact);
|
|
}
|
|
|
|
void considerEnergy(AI& ai, Energy& energy) {
|
|
if(type !is null && type.abilities.length != 0) {
|
|
value = 1.0;
|
|
|
|
for(uint i = 0, cnt = type.ai.length; i < cnt; ++i) {
|
|
ArtifactAI@ ai;
|
|
if(ability.id == 0)
|
|
@ai = cast<ArtifactAI>(type.ai[i]);
|
|
else
|
|
@ai = cast<ArtifactAI>(type.secondaryAI[i]);
|
|
if(ai !is null) {
|
|
if(!ai.consider(energy, this, value)) {
|
|
value = 0.0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if(type.ai.length == 0)
|
|
value = 0.0;
|
|
|
|
if(ability.targets.length != 0) {
|
|
if(ability.targets[0].type == TT_Object) {
|
|
@ability.targets[0].obj = target;
|
|
ability.targets[0].filled = true;
|
|
|
|
if(target is null)
|
|
value = 0.0;
|
|
}
|
|
else if(ability.targets[0].type == TT_Point) {
|
|
ability.targets[0].point = pointTarget;
|
|
ability.targets[0].filled = true;
|
|
}
|
|
}
|
|
|
|
if(value > 0.0) {
|
|
if(!ability.canActivate(ability.targets, ignoreCost=true)) {
|
|
value = 0.0;
|
|
}
|
|
else {
|
|
cost = ability.getEnergyCost(ability.targets);
|
|
if(cost != 0.0) {
|
|
//Estimate the amount of turns it would take to trigger this,
|
|
//and devalue it based on that. This is ceiled in order to allow
|
|
//for artifacts of similar cost to not be affected by cost differences.
|
|
double effCost = effCostEstimate(cost, energy.freeStorage);
|
|
double estTime = effCost / max(energy.baseIncome, 0.01);
|
|
double turns = ceil(estTime / (3.0 * 60.0));
|
|
value /= turns;
|
|
}
|
|
else {
|
|
value *= 1000.0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
value = 0.0;
|
|
}
|
|
}
|
|
|
|
void execute(AI& ai, Energy& energy) {
|
|
if(artifact !is null && type.abilities.length != 0) {
|
|
if(energy.log)
|
|
ai.print("Activate artifact "+artifact.name, artifact.region);
|
|
|
|
if(ability.type.targets.length != 0) {
|
|
if(ability.type.targets[0].type == TT_Object)
|
|
artifact.activateAbilityTypeFor(ai.empire, ability.type.id, target);
|
|
else if(ability.type.targets[0].type == TT_Point)
|
|
artifact.activateAbilityTypeFor(ai.empire, ability.type.id, pointTarget);
|
|
}
|
|
else {
|
|
artifact.activateAbilityTypeFor(ai.empire, ability.type.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
int opCmp(const ConsiderEnergy@ other) const {
|
|
if(value < other.value)
|
|
return -1;
|
|
if(value > other.value)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
class Energy : AIComponent, Artifacts {
|
|
Systems@ systems;
|
|
|
|
double baseIncome;
|
|
double freeStorage;
|
|
|
|
array<ConsiderEnergy@> queue;
|
|
int nextEnergyId = 0;
|
|
|
|
void save(SaveFile& file) {
|
|
file << nextEnergyId;
|
|
|
|
uint cnt = queue.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
queue[i].save(ai, file);
|
|
}
|
|
|
|
void load(SaveFile& file) {
|
|
file >> nextEnergyId;
|
|
|
|
uint cnt = 0;
|
|
file >> cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
ConsiderEnergy c;
|
|
c.load(ai, file);
|
|
|
|
if(c.artifact !is null)
|
|
queue.insertLast(c);
|
|
}
|
|
}
|
|
|
|
void create() {
|
|
@systems = cast<Systems>(ai.systems);
|
|
}
|
|
|
|
Considerer@ get_consider() {
|
|
return cast<Considerer>(ai.consider);
|
|
}
|
|
|
|
Empire@ get_empire() {
|
|
return ai.empire;
|
|
}
|
|
|
|
bool canUse(Artifact@ artifact) {
|
|
if(artifact is null || !artifact.valid)
|
|
return false;
|
|
Empire@ owner = artifact.owner;
|
|
if(owner.valid && owner !is ai.empire)
|
|
return false;
|
|
Region@ reg = artifact.region;
|
|
if(reg is null)
|
|
return false;
|
|
if(reg.PlanetsMask != 0)
|
|
return reg.PlanetsMask & ai.mask != 0;
|
|
else
|
|
return hasTradeAdjacent(ai.empire, reg);
|
|
}
|
|
|
|
ConsiderEnergy@ registerArtifact(Artifact@ artifact) {
|
|
if(!canUse(artifact))
|
|
return null;
|
|
|
|
for(uint i = 0, cnt = queue.length; i < cnt; ++i) {
|
|
if(queue[i].artifact is artifact)
|
|
return queue[i];
|
|
}
|
|
|
|
ConsiderEnergy c;
|
|
c.id = nextEnergyId++;
|
|
c.init(ai, artifact);
|
|
|
|
if(log)
|
|
ai.print("Detect artifact "+artifact.name, artifact.region);
|
|
|
|
queue.insertLast(c);
|
|
return c;
|
|
}
|
|
|
|
uint updateIdx = 0;
|
|
bool update() {
|
|
if(queue.length == 0)
|
|
return false;
|
|
updateIdx = (updateIdx+1) % queue.length;
|
|
auto@ c = queue[updateIdx];
|
|
double prevValue = c.value;
|
|
|
|
//Make sure this is still valid
|
|
if(!c.isValid(ai, this)) {
|
|
queue.removeAt(updateIdx);
|
|
return false;
|
|
}
|
|
|
|
//Update the current target and value
|
|
c.considerEnergy(ai, this);
|
|
|
|
/*if(log)*/
|
|
/* ai.print(c.artifact.name+": consider "+c.value+" for cost "+c.cost, c.target);*/
|
|
|
|
//Only re-sort when needed
|
|
bool changed = false;
|
|
if(prevValue != c.value) {
|
|
if(updateIdx > 0) {
|
|
if(c.value > queue[updateIdx-1].value)
|
|
changed = true;
|
|
}
|
|
if(updateIdx < queue.length-1) {
|
|
if(c.value < queue[updateIdx+1].value)
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
uint sysIdx = 0;
|
|
void updateSystem() {
|
|
uint totCnt = systems.owned.length + systems.outsideBorder.length;
|
|
if(totCnt == 0)
|
|
return;
|
|
|
|
sysIdx = (sysIdx+1) % totCnt;
|
|
SystemAI@ sys;
|
|
if(sysIdx < systems.owned.length)
|
|
@sys = systems.owned[sysIdx];
|
|
else
|
|
@sys = systems.outsideBorder[sysIdx - systems.owned.length];
|
|
|
|
for(uint i = 0, cnt = sys.artifacts.length; i < cnt; ++i)
|
|
registerArtifact(sys.artifacts[i]);
|
|
}
|
|
|
|
void tick(double time) {
|
|
//Update current income
|
|
baseIncome = empire.EnergyIncome;
|
|
freeStorage = empire.FreeEnergyStorage;
|
|
|
|
//See if we can use anything right now
|
|
if(queue.length != 0) {
|
|
auto@ c = queue[0];
|
|
if(!c.isValid(ai, this)) {
|
|
queue.removeAt(0);
|
|
}
|
|
else if(c.value > 0.0 && ai.empire.EnergyStored >= c.cost) {
|
|
c.execute(ai, this);
|
|
queue.removeAt(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void focusTick(double time) {
|
|
//Consider artifact usage
|
|
bool changed = false;
|
|
for(uint n = 0; n < min(queue.length, max(ai.behavior.artifactFocusConsiderCount, queue.length/20)); ++n) {
|
|
if(update())
|
|
changed = true;
|
|
}
|
|
|
|
//Re-sort consideration
|
|
if(changed)
|
|
queue.sortDesc();
|
|
|
|
//Try to find new artifacts
|
|
updateSystem();
|
|
}
|
|
};
|
|
|
|
AIComponent@ createEnergy() {
|
|
return Energy();
|
|
}
|