// 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(type.ai[i]); else @ai = cast(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 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(ai.systems); } Considerer@ get_consider() { return cast(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(); }