Differential D140 Diff 529 ps/trunk/binaries/data/mods/public/simulation/ai/petra/gameTypeManager.js
Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/binaries/data/mods/public/simulation/ai/petra/gameTypeManager.js
var PETRA = function(m) | var PETRA = function(m) | ||||
{ | { | ||||
/** | /** | ||||
* Handle events that are important to specific gameTypes | * Handle events that are important to specific gameTypes | ||||
* In regicide, train and manage healer guards for the hero | * In regicide, train and manage healer guards for the hero | ||||
* TODO: Assign military units to guard the hero in regicide | * TODO: Assign military units to guard the hero in regicide | ||||
* TODO: Assign guards to the wonder in a wonder game | |||||
*/ | */ | ||||
m.GameTypeManager = function(Config) | m.GameTypeManager = function(Config) | ||||
{ | { | ||||
this.Config = Config; | this.Config = Config; | ||||
this.criticalEnts = new Map(); | this.criticalEnts = new Map(); | ||||
// Holds ids of all ents who are (or can be) guarding and if the ent is currently guarding | // Holds ids of all ents who are (or can be) guarding and if the ent is currently guarding | ||||
this.guardEnts = new Map(); | this.guardEnts = new Map(); | ||||
Show All 13 Lines | m.GameTypeManager.prototype.init = function(gameState) | ||||
for (let hero of heroEnts) | for (let hero of heroEnts) | ||||
{ | { | ||||
let heroStance = hero.hasClass("Soldier") ? "aggressive" : "passive"; | let heroStance = hero.hasClass("Soldier") ? "aggressive" : "passive"; | ||||
hero.setStance(heroStance); | hero.setStance(heroStance); | ||||
this.criticalEnts.set(hero.id(), { | this.criticalEnts.set(hero.id(), { | ||||
"garrisonEmergency": false, | "garrisonEmergency": false, | ||||
"stance": heroStance, | "stance": heroStance, | ||||
"healersAssigned": 0, | "healersAssigned": 0, | ||||
"guards": new Set() // ids of ents who are currently guarding this hero | "guards": new Map() // ids of ents who are currently guarding this hero | ||||
}); | }); | ||||
} | } | ||||
}; | }; | ||||
/** | /** | ||||
* In regicide mode, if the hero has less than 70% health, try to garrison it in a healing structure | * In regicide mode, if the hero has less than 70% health, try to garrison it in a healing structure | ||||
* If it is less than 40%, try to garrison in the closest possible structure | * If it is less than 40%, try to garrison in the closest possible structure | ||||
* If the hero cannot garrison, retreat it to the closest base | * If the hero cannot garrison, retreat it to the closest base | ||||
*/ | */ | ||||
m.GameTypeManager.prototype.checkEvents = function(gameState, events) | m.GameTypeManager.prototype.checkEvents = function(gameState, events) | ||||
{ | { | ||||
if (gameState.getGameType() === "wonder") | if (gameState.getGameType() === "wonder") | ||||
{ | |||||
for (let evt of events.Create) | for (let evt of events.Create) | ||||
{ | { | ||||
let ent = gameState.getEntityById(evt.entity); | let ent = gameState.getEntityById(evt.entity); | ||||
if (!ent || !ent.isOwn(PlayerID) || ent.foundationProgress() === undefined || | if (!ent || !ent.isOwn(PlayerID) || ent.foundationProgress() === undefined || | ||||
!ent.hasClass("Wonder")) | !ent.hasClass("Wonder")) | ||||
continue; | continue; | ||||
// Let's get a few units from other bases to build the wonder. | // Let's get a few units from other bases to build the wonder. | ||||
let base = gameState.ai.HQ.getBaseByID(ent.getMetadata(PlayerID, "base")); | let base = gameState.ai.HQ.getBaseByID(ent.getMetadata(PlayerID, "base")); | ||||
let builders = gameState.ai.HQ.bulkPickWorkers(gameState, base, 10); | let builders = gameState.ai.HQ.bulkPickWorkers(gameState, base, 10); | ||||
if (builders) | if (builders) | ||||
for (let worker of builders.values()) | for (let worker of builders.values()) | ||||
{ | { | ||||
worker.setMetadata(PlayerID, "base", base.ID); | worker.setMetadata(PlayerID, "base", base.ID); | ||||
worker.setMetadata(PlayerID, "subrole", "builder"); | worker.setMetadata(PlayerID, "subrole", "builder"); | ||||
worker.setMetadata(PlayerID, "target-foundation", ent.id()); | worker.setMetadata(PlayerID, "target-foundation", ent.id()); | ||||
} | } | ||||
} | } | ||||
if (gameState.getGameType() !== "regicide") | for (let evt of events.ConstructionFinished) | ||||
return; | { | ||||
if (!evt || !evt.newentity) | |||||
continue; | |||||
let ent = gameState.getEntityById(evt.newentity); | |||||
if (ent && ent.isOwn(PlayerID) && ent.hasClass("Wonder")) | |||||
this.criticalEnts.set(ent.id(), { "guardsAssigned": 0, "guards": new Map() }); | |||||
} | |||||
} | |||||
if (gameState.getGameType() === "regicide") | |||||
{ | |||||
for (let evt of events.Attacked) | for (let evt of events.Attacked) | ||||
{ | { | ||||
if (!this.criticalEnts.has(evt.target)) | if (!this.criticalEnts.has(evt.target)) | ||||
continue; | continue; | ||||
let target = gameState.getEntityById(evt.target); | let target = gameState.getEntityById(evt.target); | ||||
if (!target || !target.position() || target.healthLevel() > 0.7) | if (!target || !target.position() || target.healthLevel() > 0.7) | ||||
continue; | continue; | ||||
let plan = target.getMetadata(PlayerID, "plan"); | let plan = target.getMetadata(PlayerID, "plan"); | ||||
let hero = this.criticalEnts.get(evt.target); | let hero = this.criticalEnts.get(evt.target); | ||||
if (plan !== -2 && plan !== -3) | if (plan !== -2 && plan !== -3) | ||||
{ | { | ||||
target.stopMoving(); | target.stopMoving(); | ||||
if (plan >= 0) | if (plan >= 0) | ||||
{ | { | ||||
let attackPlan = gameState.ai.HQ.attackManager.getPlan(plan); | let attackPlan = gameState.ai.HQ.attackManager.getPlan(plan); | ||||
if (attackPlan) | if (attackPlan) | ||||
attackPlan.removeUnit(target, true); | attackPlan.removeUnit(target, true); | ||||
} | } | ||||
if (target.getMetadata(PlayerID, "PartOfArmy")) | if (target.getMetadata(PlayerID, "PartOfArmy")) | ||||
{ | { | ||||
let army = gameState.ai.HQ.defenseManager.getArmy(target.getMetadata(PlayerID, "PartOfArmy")); | let army = gameState.ai.HQ.defenseManager.getArmy(target.getMetadata(PlayerID, "PartOfArmy")); | ||||
if (army) | if (army) | ||||
army.removeOwn(gameState, target.id()); | army.removeOwn(gameState, target.id()); | ||||
} | } | ||||
hero.garrisonEmergency = target.healthLevel() < 0.4; | hero.garrisonEmergency = target.healthLevel() < 0.4; | ||||
this.pickCriticalEntRetreatLocation(gameState, target, hero.garrisonEmergency); | this.pickCriticalEntRetreatLocation(gameState, target, hero.garrisonEmergency); | ||||
} | } | ||||
else if (target.healthLevel() < 0.4 && !hero.garrisonEmergency) | else if (target.healthLevel() < 0.4 && !hero.garrisonEmergency) | ||||
{ | { | ||||
// the hero is severely wounded, try to retreat/garrison quicker | // the hero is severely wounded, try to retreat/garrison quicker | ||||
gameState.ai.HQ.garrisonManager.cancelGarrison(target); | gameState.ai.HQ.garrisonManager.cancelGarrison(target); | ||||
this.pickCriticalEntRetreatLocation(gameState, target, true); | this.pickCriticalEntRetreatLocation(gameState, target, true); | ||||
hero.garrisonEmergency = true; | hero.garrisonEmergency = true; | ||||
} | } | ||||
} | } | ||||
// Check if new healers/guards need to be assigned to an ent | |||||
for (let evt of events.Destroy) | |||||
{ | |||||
if (!evt.entityObj || evt.entityObj.owner() !== PlayerID) | |||||
continue; | |||||
let entId = evt.entityObj.id(); | |||||
if (this.criticalEnts.has(entId)) | |||||
{ | |||||
for (let guardId of this.criticalEnts.get(entId).guards) | |||||
this.guardEnts.set(guardId, false); | |||||
this.criticalEnts.delete(entId); | |||||
continue; | |||||
} | |||||
if (!this.guardEnts.has(entId)) | |||||
continue; | |||||
for (let data of this.criticalEnts.values()) | |||||
if (data.guards.has(entId)) | |||||
{ | |||||
data.guards.delete(entId); | |||||
if (evt.entityObj.hasClass("Healer")) | |||||
--data.healersAssigned; | |||||
} | |||||
this.guardEnts.delete(entId); | |||||
} | |||||
for (let evt of events.TrainingFinished) | for (let evt of events.TrainingFinished) | ||||
for (let entId of evt.entities) | for (let entId of evt.entities) | ||||
{ | { | ||||
let ent = gameState.getEntityById(entId); | let ent = gameState.getEntityById(entId); | ||||
if (ent && ent.isOwn(PlayerID) && ent.getMetadata(PlayerID, "role") === "criticalEntHealer") | if (ent && ent.isOwn(PlayerID) && ent.getMetadata(PlayerID, "role") === "criticalEntHealer") | ||||
this.assignGuardToCriticalEnt(gameState, ent); | this.assignGuardToCriticalEnt(gameState, ent); | ||||
} | } | ||||
for (let evt of events.Garrison) | for (let evt of events.Garrison) | ||||
{ | { | ||||
if (!this.criticalEnts.has(evt.entity)) | if (!this.criticalEnts.has(evt.entity)) | ||||
continue; | continue; | ||||
let hero = this.criticalEnts.get(evt.entity); | let hero = this.criticalEnts.get(evt.entity); | ||||
if (hero.garrisonEmergency) | if (hero.garrisonEmergency) | ||||
hero.garrisonEmergency = false; | hero.garrisonEmergency = false; | ||||
let holderEnt = gameState.getEntityById(evt.holder); | let holderEnt = gameState.getEntityById(evt.holder); | ||||
if (!holderEnt || !holderEnt.hasClass("Ship")) | if (!holderEnt || !holderEnt.hasClass("Ship")) | ||||
continue; | continue; | ||||
// If the hero is garrisoned on a ship, remove its guards | // If the hero is garrisoned on a ship, remove its guards | ||||
for (let guardId of hero.guards) | for (let guardId of hero.guards) | ||||
{ | { | ||||
let guardEnt = gameState.getEntityById(guardId); | let guardEnt = gameState.getEntityById(guardId); | ||||
if (!guardEnt) | if (!guardEnt) | ||||
continue; | continue; | ||||
guardEnt.removeGuard(); | guardEnt.removeGuard(); | ||||
this.guardEnts.set(guardId, false); | this.guardEnts.set(guardId, false); | ||||
} | } | ||||
hero.guards.clear(); | hero.guards.clear(); | ||||
} | } | ||||
} | |||||
// Check if new healers/guards need to be assigned to an ent | |||||
for (let evt of events.Destroy) | |||||
{ | |||||
if (!evt.entityObj || evt.entityObj.owner() !== PlayerID) | |||||
continue; | |||||
let entId = evt.entityObj.id(); | |||||
if (this.criticalEnts.has(entId)) | |||||
{ | |||||
for (let [guardId, role] of this.criticalEnts.get(entId).guards) | |||||
{ | |||||
let guardEnt = gameState.getEntityById(guardId); | |||||
if (!guardEnt) | |||||
continue; | |||||
if (role === "healer") | |||||
this.guardEnts.set(guardId, false); | |||||
else | |||||
{ | |||||
guardEnt.setMetadata(PlayerID, "plan", -1); | |||||
guardEnt.setMetadata(PlayerID, "role", undefined); | |||||
this.guardEnts.delete(guardId); | |||||
} | |||||
} | |||||
this.criticalEnts.delete(entId); | |||||
continue; | |||||
} | |||||
if (!this.guardEnts.has(entId)) | |||||
continue; | |||||
for (let data of this.criticalEnts.values()) | |||||
if (data.guards.has(entId)) | |||||
{ | |||||
data.guards.delete(entId); | |||||
if (evt.entityObj.hasClass("Healer")) | |||||
--data.healersAssigned; | |||||
else | |||||
--data.guardsAssigned; | |||||
} | |||||
this.guardEnts.delete(entId); | |||||
} | |||||
for (let evt of events.UnGarrison) | for (let evt of events.UnGarrison) | ||||
{ | { | ||||
if (!this.guardEnts.has(evt.entity) && !this.criticalEnts.has(evt.entity)) | if (!this.guardEnts.has(evt.entity) && !this.criticalEnts.has(evt.entity)) | ||||
continue; | continue; | ||||
let ent = gameState.getEntityById(evt.entity); | let ent = gameState.getEntityById(evt.entity); | ||||
if (!ent) | if (!ent) | ||||
continue; | continue; | ||||
// If this ent travelled to a hero's accessValue, try again to assign as a guard | // If this ent travelled to a criticalEnt's accessValue, try again to assign as a guard | ||||
if (ent.getMetadata(PlayerID, "role") === "criticalEntHealer" && !this.guardEnts.get(evt.entity)) | if ((ent.getMetadata(PlayerID, "role") === "criticalEntHealer" || | ||||
ent.getMetadata(PlayerID, "role") === "criticalEntGuard") && !this.guardEnts.get(evt.entity)) | |||||
{ | { | ||||
this.assignGuardToCriticalEnt(gameState, ent); | this.assignGuardToCriticalEnt(gameState, ent); | ||||
continue; | continue; | ||||
} | } | ||||
if (!this.criticalEnts.has(evt.entity)) | if (!this.criticalEnts.has(evt.entity)) | ||||
continue; | continue; | ||||
// If this is a hero, try to assign ents that should be guarding it, but couldn't previously | // If this is a hero, try to assign ents that should be guarding it, but couldn't previously | ||||
let criticalEnt = this.criticalEnts.get(evt.entity); | let criticalEnt = this.criticalEnts.get(evt.entity); | ||||
for (let [id, isGuarding] of this.guardEnts) | for (let [id, isGuarding] of this.guardEnts) | ||||
{ | { | ||||
if (criticalEnt.guards.size >= this.healersPerCriticalEnt) | if (criticalEnt.guards.size >= this.healersPerCriticalEnt) | ||||
break; | break; | ||||
if (!isGuarding) | if (!isGuarding) | ||||
{ | { | ||||
let guardEnt = gameState.getEntityById(id); | let guardEnt = gameState.getEntityById(id); | ||||
if (guardEnt) | if (guardEnt) | ||||
this.assignGuardToCriticalEnt(gameState, guardEnt); | this.assignGuardToCriticalEnt(gameState, guardEnt, evt.entity); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
}; | }; | ||||
m.GameTypeManager.prototype.buildWonder = function(gameState, queues) | m.GameTypeManager.prototype.buildWonder = function(gameState, queues) | ||||
{ | { | ||||
if (queues.wonder && queues.wonder.hasQueuedUnits() || | if (queues.wonder && queues.wonder.hasQueuedUnits() || | ||||
gameState.getOwnEntitiesByClass("Wonder", true).hasEntities() || | gameState.getOwnEntitiesByClass("Wonder", true).hasEntities() || | ||||
!gameState.ai.HQ.canBuild(gameState, "structures/{civ}_wonder")) | !gameState.ai.HQ.canBuild(gameState, "structures/{civ}_wonder")) | ||||
return; | return; | ||||
if (!queues.wonder) | if (!queues.wonder) | ||||
gameState.ai.queueManager.addQueue("wonder", 1000); | gameState.ai.queueManager.addQueue("wonder", 1000); | ||||
queues.wonder.addPlan(new m.ConstructionPlan(gameState, "structures/{civ}_wonder")); | queues.wonder.addPlan(new m.ConstructionPlan(gameState, "structures/{civ}_wonder")); | ||||
}; | }; | ||||
/** | |||||
* Try to keep some military units guarding any criticalEnts. | |||||
* TODO: Prefer champion units, and swap citizen soldier guards with champions if they become available. | |||||
*/ | |||||
m.GameTypeManager.prototype.manageCriticalEntGuards = function(gameState) | |||||
{ | |||||
for (let [id, data] of this.criticalEnts) | |||||
{ | |||||
let criticalEnt = gameState.getEntityById(id); | |||||
if (!criticalEnt) | |||||
continue; | |||||
let militaryGuardsPerCriticalEnt = (criticalEnt.hasClass("Wonder") ? 10 : 4) + | |||||
Math.round(this.Config.personality.defensive * 5); | |||||
if (data.guardsAssigned >= militaryGuardsPerCriticalEnt) | |||||
continue; | |||||
// First try to pick guards in the criticalEnt's accessIndex, to avoid unnecessary transports | |||||
for (let checkForSameAccess of [true, false]) | |||||
{ | |||||
for (let entity of gameState.ai.HQ.attackManager.outOfPlan.values()) | |||||
{ | |||||
if (entity.getMetadata(PlayerID, "transport") !== undefined || | |||||
checkForSameAccess && (!entity.position() || !criticalEnt.position() || | |||||
m.getLandAccess(gameState, criticalEnt) !== m.getLandAccess(gameState, entity))) | |||||
continue; | |||||
this.assignGuardToCriticalEnt(gameState, entity, id); | |||||
entity.setMetadata(PlayerID, "plan", -2); | |||||
entity.setMetadata(PlayerID, "role", "criticalEntGuard"); | |||||
if (++data.guardsAssigned >= militaryGuardsPerCriticalEnt) | |||||
break; | |||||
} | |||||
if (data.guardsAssigned >= militaryGuardsPerCriticalEnt) | |||||
break; | |||||
for (let entity of gameState.getOwnEntitiesByClass("Soldier", true).values()) | |||||
{ | |||||
if (entity.getMetadata(PlayerID, "plan") !== undefined || | |||||
entity.getMetadata(PlayerID, "transport") !== undefined || | |||||
checkForSameAccess && (!entity.position() || !criticalEnt.position() || | |||||
m.getLandAccess(gameState, criticalEnt) !== m.getLandAccess(gameState, entity))) | |||||
continue; | |||||
this.assignGuardToCriticalEnt(gameState, entity, id); | |||||
entity.setMetadata(PlayerID, "plan", -2); | |||||
entity.setMetadata(PlayerID, "role", "criticalEntGuard"); | |||||
if (++data.guardsAssigned >= militaryGuardsPerCriticalEnt) | |||||
break; | |||||
} | |||||
if (data.guardsAssigned >= militaryGuardsPerCriticalEnt) | |||||
break; | |||||
} | |||||
} | |||||
}; | |||||
m.GameTypeManager.prototype.pickCriticalEntRetreatLocation = function(gameState, criticalEnt, emergency) | m.GameTypeManager.prototype.pickCriticalEntRetreatLocation = function(gameState, criticalEnt, emergency) | ||||
{ | { | ||||
gameState.ai.HQ.defenseManager.garrisonAttackedUnit(gameState, criticalEnt, emergency); | gameState.ai.HQ.defenseManager.garrisonAttackedUnit(gameState, criticalEnt, emergency); | ||||
let plan = criticalEnt.getMetadata(PlayerID, "plan"); | let plan = criticalEnt.getMetadata(PlayerID, "plan"); | ||||
if (plan === -2 || plan === -3) | if (plan === -2 || plan === -3) | ||||
return; | return; | ||||
Show All 20 Lines | m.GameTypeManager.prototype.trainCriticalEntHealer = function(gameState, queues, id) | ||||
++this.criticalEnts.get(id).healersAssigned; | ++this.criticalEnts.get(id).healersAssigned; | ||||
}; | }; | ||||
/** | /** | ||||
* Only send the guard command if the guard's accessIndex is the same as the critical ent | * Only send the guard command if the guard's accessIndex is the same as the critical ent | ||||
* and the critical ent has a position (i.e. not garrisoned). | * and the critical ent has a position (i.e. not garrisoned). | ||||
* Request a transport if the accessIndex value is different | * Request a transport if the accessIndex value is different | ||||
*/ | */ | ||||
m.GameTypeManager.prototype.assignGuardToCriticalEnt = function(gameState, guardEnt) | m.GameTypeManager.prototype.assignGuardToCriticalEnt = function(gameState, guardEnt, criticalEntId) | ||||
{ | { | ||||
if (guardEnt.getMetadata(PlayerID, "transport") !== undefined || !guardEnt.canGuard()) | if (guardEnt.getMetadata(PlayerID, "transport") !== undefined || !guardEnt.canGuard()) | ||||
return; | return; | ||||
if (!criticalEntId) | |||||
{ | |||||
// Assign to the critical ent with the fewest guards | // Assign to the critical ent with the fewest guards | ||||
let min = Math.min(); | let min = Math.min(); | ||||
let criticalEntId; | |||||
for (let [id, data] of this.criticalEnts) | for (let [id, data] of this.criticalEnts) | ||||
{ | { | ||||
if (data.guards.size > min) | if (data.guards.size > min) | ||||
continue; | continue; | ||||
criticalEntId = id; | criticalEntId = id; | ||||
min = data.guards.size; | min = data.guards.size; | ||||
} | } | ||||
} | |||||
if (!criticalEntId) | if (!criticalEntId) | ||||
return; | return; | ||||
let criticalEnt = gameState.getEntityById(criticalEntId); | let criticalEnt = gameState.getEntityById(criticalEntId); | ||||
if (!criticalEnt || !criticalEnt.position() || !guardEnt.position()) | if (!criticalEnt || !criticalEnt.position() || !guardEnt.position()) | ||||
{ | { | ||||
this.guardEnts.set(guardEnt.id(), false); | this.guardEnts.set(guardEnt.id(), false); | ||||
return; | return; | ||||
} | } | ||||
let guardEntAccess = gameState.ai.accessibility.getAccessValue(guardEnt.position()); | let guardEntAccess = gameState.ai.accessibility.getAccessValue(guardEnt.position()); | ||||
let criticalEntAccess = gameState.ai.accessibility.getAccessValue(criticalEnt.position()); | let criticalEntAccess = gameState.ai.accessibility.getAccessValue(criticalEnt.position()); | ||||
if (guardEntAccess === criticalEntAccess) | if (guardEntAccess === criticalEntAccess) | ||||
{ | { | ||||
guardEnt.guard(criticalEnt); | let queued = m.returnResources(gameState, guardEnt); | ||||
this.criticalEnts.get(criticalEntId).guards.add(guardEnt.id()); | guardEnt.guard(criticalEnt, queued); | ||||
let guardRole = guardEnt.getMetadata(PlayerID, "role") === "criticalEntHealer" ? "healer" : "guard"; | |||||
this.criticalEnts.get(criticalEntId).guards.set(guardEnt.id(), guardRole); | |||||
} | } | ||||
else | else | ||||
gameState.ai.HQ.navalManager.requireTransport(gameState, guardEnt, guardEntAccess, criticalEntAccess, criticalEnt.position()); | gameState.ai.HQ.navalManager.requireTransport(gameState, guardEnt, guardEntAccess, criticalEntAccess, criticalEnt.position()); | ||||
this.guardEnts.set(guardEnt.id(), guardEntAccess === criticalEntAccess); | this.guardEnts.set(guardEnt.id(), guardEntAccess === criticalEntAccess); | ||||
}; | }; | ||||
m.GameTypeManager.prototype.update = function(gameState, events, queues) | m.GameTypeManager.prototype.update = function(gameState, events, queues) | ||||
{ | { | ||||
// Wait a turn for trigger scripts to spawn any critical ents (i.e. in regicide) | // Wait a turn for trigger scripts to spawn any critical ents (i.e. in regicide) | ||||
if (gameState.ai.playedTurn === 1) | if (gameState.ai.playedTurn === 1) | ||||
this.init(gameState); | this.init(gameState); | ||||
this.checkEvents(gameState, events); | this.checkEvents(gameState, events); | ||||
if (gameState.getGameType() === "wonder") | if (gameState.getGameType() === "wonder") | ||||
{ | |||||
this.buildWonder(gameState, queues); | this.buildWonder(gameState, queues); | ||||
this.manageCriticalEntGuards(gameState); | |||||
} | |||||
if (gameState.getGameType() !== "regicide") | if (gameState.getGameType() !== "regicide") | ||||
return; | return; | ||||
if (gameState.ai.playedTurn % 50 === 0) | if (gameState.ai.playedTurn % 50 === 0) | ||||
for (let [id, data] of this.criticalEnts) | for (let [id, data] of this.criticalEnts) | ||||
{ | { | ||||
let ent = gameState.getEntityById(id); | let ent = gameState.getEntityById(id); | ||||
Show All 28 Lines |
Wildfire Games · Phabricator