Changeset View
Changeset View
Standalone View
Standalone View
binaries/data/mods/public/simulation/ai/petra/garrisonManager.js
Show All 18 Lines | |||||
{ | { | ||||
// First check for possible upgrade of a structure | // First check for possible upgrade of a structure | ||||
for (let evt of events.EntityRenamed) | for (let evt of events.EntityRenamed) | ||||
{ | { | ||||
for (let id of this.holders.keys()) | for (let id of this.holders.keys()) | ||||
{ | { | ||||
if (id !== evt.entity) | if (id !== evt.entity) | ||||
continue; | continue; | ||||
let list = this.holders.get(id); | let data = this.holders.get(id); | ||||
this.holders.delete(id); | this.holders.delete(id); | ||||
this.holders.set(evt.newentity, list); | this.holders.set(evt.newentity, data); | ||||
} | } | ||||
for (let id of this.decayingStructures.keys()) | for (let id of this.decayingStructures.keys()) | ||||
{ | { | ||||
if (id !== evt.entity) | if (id !== evt.entity) | ||||
continue; | continue; | ||||
this.decayingStructures.delete(id); | this.decayingStructures.delete(id); | ||||
if (this.decayingStructures.has(evt.newentity)) | if (this.decayingStructures.has(evt.newentity)) | ||||
continue; | continue; | ||||
let ent = gameState.getEntityById(evt.newentity); | let ent = gameState.getEntityById(evt.newentity); | ||||
if (!ent || !ent.territoryDecayRate() || !ent.garrisonRegenRate()) | if (!ent || !ent.territoryDecayRate() || !ent.garrisonRegenRate()) | ||||
continue; | continue; | ||||
let gmin = Math.ceil((ent.territoryDecayRate() - ent.defaultRegenRate()) / ent.garrisonRegenRate()); | let gmin = Math.ceil((ent.territoryDecayRate() - ent.defaultRegenRate()) / ent.garrisonRegenRate()); | ||||
this.decayingStructures.set(evt.newentity, gmin); | this.decayingStructures.set(evt.newentity, gmin); | ||||
} | } | ||||
} | } | ||||
for (let [id, list] of this.holders.entries()) | for (let [id, data] of this.holders.entries()) | ||||
{ | { | ||||
let list = data.list; | |||||
let holder = gameState.getEntityById(id); | let holder = gameState.getEntityById(id); | ||||
if (!holder || !gameState.isPlayerAlly(holder.owner())) | if (!holder || !gameState.isPlayerAlly(holder.owner())) | ||||
{ | { | ||||
// this holder was certainly destroyed or captured. Let's remove it | // this holder was certainly destroyed or captured. Let's remove it | ||||
for (let entId of list) | for (let entId of list) | ||||
{ | { | ||||
let ent = gameState.getEntityById(entId); | let ent = gameState.getEntityById(entId); | ||||
if (ent && ent.getMetadata(PlayerID, "garrisonHolder") == id) | if (ent && ent.getMetadata(PlayerID, "garrisonHolder") == id) | ||||
▲ Show 20 Lines • Show All 47 Lines • ▼ Show 20 Lines | for (let [id, data] of this.holders.entries()) | ||||
} | } | ||||
if (!holder.position()) // could happen with siege unit inside a ship | if (!holder.position()) // could happen with siege unit inside a ship | ||||
continue; | continue; | ||||
if (gameState.ai.elapsedTime - holder.getMetadata(PlayerID, "holderTimeUpdate") > 3) | if (gameState.ai.elapsedTime - holder.getMetadata(PlayerID, "holderTimeUpdate") > 3) | ||||
{ | { | ||||
let range = holder.attackRange("Ranged") ? holder.attackRange("Ranged").max : 80; | let range = holder.attackRange("Ranged") ? holder.attackRange("Ranged").max : 80; | ||||
let enemiesAround = false; | let around = { "defenseStructure": false, "inertStructure": false, "meleeSiege": false, "rangeSiege": false, "unit": false }; | ||||
for (let ent of gameState.getEnemyEntities().values()) | for (let ent of gameState.getEnemyEntities().values()) | ||||
{ | { | ||||
if (!ent.position()) | if (!ent.position()) | ||||
continue; | continue; | ||||
if (ent.owner() === 0 && (!ent.unitAIState() || ent.unitAIState().split(".")[1] !== "COMBAT")) | if (ent.owner() === 0 && (!ent.unitAIState() || ent.unitAIState().split(".")[1] !== "COMBAT")) | ||||
continue; | continue; | ||||
let dist = API3.SquareVectorDistance(ent.position(), holder.position()); | let dist = API3.SquareVectorDistance(ent.position(), holder.position()); | ||||
if (dist > range*range) | if (dist > range*range) | ||||
continue; | continue; | ||||
enemiesAround = true; | if (ent.hasClass("Structure")) | ||||
{ | |||||
if (ent.attackRange("Ranged")) // TODO units on wall are not taken into account | |||||
around.defenseStructure = true; | |||||
else | |||||
around.inertStructure = true; | |||||
} | |||||
else if (m.isSiegeUnit(ent)) | |||||
{ | |||||
if (ent.attackTypes().indexOf("Melee") !== -1) | |||||
around.meleeSiege = true; | |||||
else | |||||
around.rangeSiege = true; | |||||
} | |||||
else | |||||
{ | |||||
around.unit = true; | |||||
break; | break; | ||||
} | } | ||||
} | |||||
// Keep defenseManager.garrisonUnitsInside in sync to avoid garrisoning-ungarrisoning some units | |||||
data.allowMelee = around.defenseStructure || around.unit; | |||||
for (let entId of holder.garrisoned()) | for (let entId of holder.garrisoned()) | ||||
{ | { | ||||
let ent = gameState.getEntityById(entId); | let ent = gameState.getEntityById(entId); | ||||
if (ent.owner() === PlayerID && !this.keepGarrisoned(ent, holder, enemiesAround)) | if (ent.owner() === PlayerID && !this.keepGarrisoned(ent, holder, around)) | ||||
holder.unload(entId); | holder.unload(entId); | ||||
} | } | ||||
for (let j = 0; j < list.length; ++j) | for (let j = 0; j < list.length; ++j) | ||||
{ | { | ||||
let ent = gameState.getEntityById(list[j]); | let ent = gameState.getEntityById(list[j]); | ||||
if (this.keepGarrisoned(ent, holder, enemiesAround)) | if (this.keepGarrisoned(ent, holder, around)) | ||||
continue; | continue; | ||||
if (ent.getMetadata(PlayerID, "garrisonHolder") == id) | if (ent.getMetadata(PlayerID, "garrisonHolder") == id) | ||||
{ | { | ||||
this.leaveGarrison(ent); | this.leaveGarrison(ent); | ||||
ent.stopMoving(); | ent.stopMoving(); | ||||
} | } | ||||
list.splice(j--, 1); | list.splice(j--, 1); | ||||
} | } | ||||
if (this.numberOfGarrisonedUnits(holder) === 0) | if (this.numberOfGarrisonedUnits(holder) === 0) | ||||
this.holders.delete(id); | this.holders.delete(id); | ||||
else | else | ||||
holder.setMetadata(PlayerID, "holderTimeUpdate", gameState.ai.elapsedTime); | holder.setMetadata(PlayerID, "holderTimeUpdate", gameState.ai.elapsedTime); | ||||
} | } | ||||
} | } | ||||
// Warning new garrison orders (as in the following lines) should be done after having updated the holders | // Warning new garrison orders (as in the following lines) should be done after having updated the holders | ||||
// (or TODO we should add a test that the garrison order is from a previous turn when updating) | // (or TODO we should add a test that the garrison order is from a previous turn when updating) | ||||
for (let [id, gmin] of this.decayingStructures.entries()) | for (let [id, gmin] of this.decayingStructures.entries()) | ||||
{ | { | ||||
let ent = gameState.getEntityById(id); | let ent = gameState.getEntityById(id); | ||||
if (!ent || ent.owner() !== PlayerID) | if (!ent || ent.owner() !== PlayerID) | ||||
this.decayingStructures.delete(id); | this.decayingStructures.delete(id); | ||||
else if (this.numberOfGarrisonedUnits(ent) < gmin) | else if (this.numberOfGarrisonedUnits(ent) < gmin) | ||||
gameState.ai.HQ.defenseManager.garrisonRangedUnitsInside(gameState, ent, {"min": gmin, "type": "decay"}); | gameState.ai.HQ.defenseManager.garrisonUnitsInside(gameState, ent, {"min": gmin, "type": "decay"}); | ||||
} | } | ||||
}; | }; | ||||
/** TODO should add the units garrisoned inside garrisoned units */ | /** TODO should add the units garrisoned inside garrisoned units */ | ||||
m.GarrisonManager.prototype.numberOfGarrisonedUnits = function(holder) | m.GarrisonManager.prototype.numberOfGarrisonedUnits = function(holder) | ||||
{ | { | ||||
if (!this.holders.has(holder.id())) | if (!this.holders.has(holder.id())) | ||||
return holder.garrisoned().length; | return holder.garrisoned().length; | ||||
return holder.garrisoned().length + this.holders.get(holder.id()).length; | return holder.garrisoned().length + this.holders.get(holder.id()).list.length; | ||||
}; | |||||
m.GarrisonManager.prototype.allowMelee = function(holder) | |||||
{ | |||||
if (!this.holders.has(holder.id())) | |||||
return undefined; | |||||
return this.holders.get(holder.id()).allowMelee; | |||||
}; | }; | ||||
/** This is just a pre-garrison state, while the entity walk to the garrison holder */ | /** This is just a pre-garrison state, while the entity walk to the garrison holder */ | ||||
m.GarrisonManager.prototype.garrison = function(gameState, ent, holder, type) | m.GarrisonManager.prototype.garrison = function(gameState, ent, holder, type) | ||||
{ | { | ||||
if (this.numberOfGarrisonedUnits(holder) >= holder.garrisonMax()) | if (this.numberOfGarrisonedUnits(holder) >= holder.garrisonMax()) | ||||
return; | return; | ||||
this.registerHolder(gameState, holder); | this.registerHolder(gameState, holder); | ||||
this.holders.get(holder.id()).push(ent.id()); | this.holders.get(holder.id()).list.push(ent.id()); | ||||
if (gameState.ai.Config.debug > 2) | if (gameState.ai.Config.debug > 2) | ||||
{ | { | ||||
warn("garrison unit " + ent.genericName() + " in " + holder.genericName() + " with type " + type); | warn("garrison unit " + ent.genericName() + " in " + holder.genericName() + " with type " + type); | ||||
warn(" we try to garrison a unit with plan " + ent.getMetadata(PlayerID, "plan") + " and role " + ent.getMetadata(PlayerID, "role") + | warn(" we try to garrison a unit with plan " + ent.getMetadata(PlayerID, "plan") + " and role " + ent.getMetadata(PlayerID, "role") + | ||||
" and subrole " + ent.getMetadata(PlayerID, "subrole") + " and transport " + ent.getMetadata(PlayerID, "transport")); | " and subrole " + ent.getMetadata(PlayerID, "subrole") + " and transport " + ent.getMetadata(PlayerID, "transport")); | ||||
} | } | ||||
Show All 26 Lines | |||||
/** Cancel a pre-garrison state */ | /** Cancel a pre-garrison state */ | ||||
m.GarrisonManager.prototype.cancelGarrison = function(ent) | m.GarrisonManager.prototype.cancelGarrison = function(ent) | ||||
{ | { | ||||
ent.stopMoving(); | ent.stopMoving(); | ||||
this.leaveGarrison(ent); | this.leaveGarrison(ent); | ||||
let holderId = ent.getMetadata(PlayerID, "garrisonHolder"); | let holderId = ent.getMetadata(PlayerID, "garrisonHolder"); | ||||
if (!holderId || !this.holders.has(holderId)) | if (!holderId || !this.holders.has(holderId)) | ||||
return; | return; | ||||
let list = this.holders.get(holderId); | let list = this.holders.get(holderId).list; | ||||
let index = list.indexOf(ent.id()); | let index = list.indexOf(ent.id()); | ||||
if (index !== -1) | if (index !== -1) | ||||
list.splice(index, 1); | list.splice(index, 1); | ||||
}; | }; | ||||
m.GarrisonManager.prototype.keepGarrisoned = function(ent, holder, enemiesAround) | m.GarrisonManager.prototype.keepGarrisoned = function(ent, holder, around) | ||||
{ | { | ||||
switch (ent.getMetadata(PlayerID, "garrisonType")) | switch (ent.getMetadata(PlayerID, "garrisonType")) | ||||
{ | { | ||||
case 'force': // force the ungarrisoning | case 'force': // force the ungarrisoning | ||||
return false; | return false; | ||||
case 'trade': // trader garrisoned in ship | case 'trade': // trader garrisoned in ship | ||||
return true; | return true; | ||||
case 'protection': // hurt unit for healing or infantry for defense | case 'protection': // hurt unit for healing or infantry for defense | ||||
return ent.needsHeal() && holder.buffHeal() || | if (ent.needsHeal() && holder.buffHeal()) | ||||
enemiesAround && (ent.hasClass("Support") || | return true; | ||||
MatchesClassList(ent.classes(), holder.getGarrisonArrowClasses()) || | if (MatchesClassList(ent.classes(), holder.getGarrisonArrowClasses())) | ||||
MatchesClassList(ent.classes(), "Siege+!Melee")); | { | ||||
if (around.unit || around.defenseStructure) | |||||
return true; | |||||
else if (around.meleeSiege || around.rangeSiege) | |||||
return ent.attackTypes().indexOf("Melee") === -1; | |||||
else | |||||
return false; | |||||
} | |||||
if (ent.attackTypes() && ent.attackTypes().indexOf("Melee") !== -1) | |||||
return false; | |||||
if (around.unit) | |||||
return ent.hasClass("Support") || m.isSiegeUnit(ent); // only ranged siege here and below as melee siege already released above | |||||
if (m.isSiegeUnit(ent)) | |||||
return around.meleeSiege; | |||||
return false; | |||||
case 'decay': | case 'decay': | ||||
return this.decayingStructures.has(holder.id()); | return this.decayingStructures.has(holder.id()); | ||||
case 'emergency': // f.e. hero in regicide mode | case 'emergency': // f.e. hero in regicide mode | ||||
return enemiesAround; | return around.unit || around.defenseStructure || around.meleeSiege; | ||||
default: | default: | ||||
if (ent.getMetadata(PlayerID, "onBoard") === "onBoard") // transport is not (yet ?) managed by garrisonManager | if (ent.getMetadata(PlayerID, "onBoard") === "onBoard") // transport is not (yet ?) managed by garrisonManager | ||||
return true; | return true; | ||||
API3.warn("unknown type in garrisonManager " + ent.getMetadata(PlayerID, "garrisonType") + | API3.warn("unknown type in garrisonManager " + ent.getMetadata(PlayerID, "garrisonType") + | ||||
" for " + ent.id() + " inside " + holder.id()); | " for " + ent.id() + " inside " + holder.id()); | ||||
ent.setMetadata(PlayerID, "garrisonType", "protection"); | ent.setMetadata(PlayerID, "garrisonType", "protection"); | ||||
return true; | return true; | ||||
} | } | ||||
}; | }; | ||||
/** Add this holder in the list managed by the garrisonManager */ | /** Add this holder in the list managed by the garrisonManager */ | ||||
m.GarrisonManager.prototype.registerHolder = function(gameState, holder) | m.GarrisonManager.prototype.registerHolder = function(gameState, holder) | ||||
{ | { | ||||
if (this.holders.has(holder.id())) // already registered | if (this.holders.has(holder.id())) // already registered | ||||
return; | return; | ||||
this.holders.set(holder.id(), []); | this.holders.set(holder.id(), { "list": [], "allowMelee": true }); | ||||
holder.setMetadata(PlayerID, "holderTimeUpdate", gameState.ai.elapsedTime); | holder.setMetadata(PlayerID, "holderTimeUpdate", gameState.ai.elapsedTime); | ||||
}; | }; | ||||
/** | /** | ||||
* Garrison units in decaying structures to stop their decay | * Garrison units in decaying structures to stop their decay | ||||
* do it only for structures useful for defense, except if we are expanding (justCaptured=true) | * do it only for structures useful for defense, except if we are expanding (justCaptured=true) | ||||
* in which case we also do it for structures useful for unit trainings (TODO only Barracks are done) | * in which case we also do it for structures useful for unit trainings (TODO only Barracks are done) | ||||
*/ | */ | ||||
Show All 34 Lines |
Wildfire Games · Phabricator