source: ps/trunk/binaries/data/mods/public/simulation/ai/petra/entityExtend.js@ 24989

Last change on this file since 24989 was 24989, checked in by Freagarach, 3 years ago

Split treasures from ResourceSupply.

Removes some hardcoding and allows for easier modding of treasures.

Refs.: #5888
Differential revision: D3303
Comments by: @Imarok, @Nescio, @Stan, @wraitii

  • Property svn:eol-style set to native
File size: 13.7 KB
Line 
1/** returns true if this unit should be considered as a siege unit */
2PETRA.isSiegeUnit = function(ent)
3{
4 return ent.hasClass("Siege") || ent.hasClass("Elephant") && ent.hasClass("Melee");
5};
6
7/** returns true if this unit should be considered as "fast". */
8PETRA.isFastMoving = function(ent)
9{
10 // TODO: use clever logic based on walkspeed comparisons.
11 return ent.hasClass("FastMoving");
12};
13
14/** returns some sort of DPS * health factor. If you specify a class, it'll use the modifiers against that class too. */
15PETRA.getMaxStrength = function(ent, debugLevel, DamageTypeImportance, againstClass)
16{
17 let strength = 0;
18 let attackTypes = ent.attackTypes();
19 let damageTypes = Object.keys(DamageTypeImportance);
20 if (!attackTypes)
21 return strength;
22
23 for (let type of attackTypes)
24 {
25 if (type == "Slaughter")
26 continue;
27
28 let attackStrength = ent.attackStrengths(type);
29 for (let str in attackStrength)
30 {
31 let val = parseFloat(attackStrength[str]);
32 if (againstClass)
33 val *= ent.getMultiplierAgainst(type, againstClass);
34 if (DamageTypeImportance[str])
35 strength += DamageTypeImportance[str] * val / damageTypes.length;
36 else if (debugLevel > 0)
37 API3.warn("Petra: " + str + " unknown attackStrength in getMaxStrength (please add " + str + " to config.js).");
38 }
39
40 let attackRange = ent.attackRange(type);
41 if (attackRange)
42 strength += attackRange.max * 0.0125;
43
44 let attackTimes = ent.attackTimes(type);
45 for (let str in attackTimes)
46 {
47 let val = parseFloat(attackTimes[str]);
48 switch (str)
49 {
50 case "repeat":
51 strength += val / 100000;
52 break;
53 case "prepare":
54 strength -= val / 100000;
55 break;
56 default:
57 API3.warn("Petra: " + str + " unknown attackTimes in getMaxStrength");
58 }
59 }
60 }
61
62 let resistanceStrength = ent.resistanceStrengths();
63
64 if (resistanceStrength.Damage)
65 for (let str in resistanceStrength.Damage)
66 {
67 let val = +resistanceStrength.Damage[str];
68 if (DamageTypeImportance[str])
69 strength += DamageTypeImportance[str] * val / damageTypes.length;
70 else if (debugLevel > 0)
71 API3.warn("Petra: " + str + " unknown resistanceStrength in getMaxStrength (please add " + str + " to config.js).");
72 }
73
74 // ToDo: Add support for StatusEffects and Capture.
75
76 return strength * ent.maxHitpoints() / 100.0;
77};
78
79/** Get access and cache it (except for units as it can change) in metadata if not already done */
80PETRA.getLandAccess = function(gameState, ent)
81{
82 if (ent.hasClass("Unit"))
83 {
84 let pos = ent.position();
85 if (!pos)
86 {
87 let holder = PETRA.getHolder(gameState, ent);
88 if (holder)
89 return PETRA.getLandAccess(gameState, holder);
90
91 API3.warn("Petra error: entity without position, but not garrisoned");
92 PETRA.dumpEntity(ent);
93 return undefined;
94 }
95 return gameState.ai.accessibility.getAccessValue(pos);
96 }
97
98 let access = ent.getMetadata(PlayerID, "access");
99 if (!access)
100 {
101 access = gameState.ai.accessibility.getAccessValue(ent.position());
102 // Docks are sometimes not as expected
103 if (access < 2 && ent.buildPlacementType() == "shore")
104 {
105 let halfDepth = 0;
106 if (ent.get("Footprint/Square"))
107 halfDepth = +ent.get("Footprint/Square/@depth") / 2;
108 else if (ent.get("Footprint/Circle"))
109 halfDepth = +ent.get("Footprint/Circle/@radius");
110 let entPos = ent.position();
111 let cosa = Math.cos(ent.angle());
112 let sina = Math.sin(ent.angle());
113 for (let d = 3; d < halfDepth; d += 3)
114 {
115 let pos = [ entPos[0] - d * sina,
116 entPos[1] - d * cosa];
117 access = gameState.ai.accessibility.getAccessValue(pos);
118 if (access > 1)
119 break;
120 }
121 }
122 ent.setMetadata(PlayerID, "access", access);
123 }
124 return access;
125};
126
127/** Sea access always cached as it never changes */
128PETRA.getSeaAccess = function(gameState, ent)
129{
130 let sea = ent.getMetadata(PlayerID, "sea");
131 if (!sea)
132 {
133 sea = gameState.ai.accessibility.getAccessValue(ent.position(), true);
134 // Docks are sometimes not as expected
135 if (sea < 2 && ent.buildPlacementType() == "shore")
136 {
137 let entPos = ent.position();
138 let cosa = Math.cos(ent.angle());
139 let sina = Math.sin(ent.angle());
140 for (let d = 3; d < 15; d += 3)
141 {
142 let pos = [ entPos[0] + d * sina,
143 entPos[1] + d * cosa];
144 sea = gameState.ai.accessibility.getAccessValue(pos, true);
145 if (sea > 1)
146 break;
147 }
148 }
149 ent.setMetadata(PlayerID, "sea", sea);
150 }
151 return sea;
152};
153
154PETRA.setSeaAccess = function(gameState, ent)
155{
156 PETRA.getSeaAccess(gameState, ent);
157};
158
159/** Decide if we should try to capture (returns true) or destroy (return false) */
160PETRA.allowCapture = function(gameState, ent, target)
161{
162 if (!target.isCapturable() || !ent.canCapture(target))
163 return false;
164 if (target.isInvulnerable())
165 return true;
166 // always try to recapture capture points from an allied, except if it's decaying
167 if (gameState.isPlayerAlly(target.owner()))
168 return !target.decaying();
169
170 let antiCapture = target.defaultRegenRate();
171 if (target.isGarrisonHolder() && target.garrisoned())
172 antiCapture += target.garrisonRegenRate() * target.garrisoned().length;
173 if (target.decaying())
174 antiCapture -= target.territoryDecayRate();
175
176 let capture;
177 let capturableTargets = gameState.ai.HQ.capturableTargets;
178 if (!capturableTargets.has(target.id()))
179 {
180 capture = ent.captureStrength() * PETRA.getAttackBonus(ent, target, "Capture");
181 capturableTargets.set(target.id(), { "strength": capture, "ents": new Set([ent.id()]) });
182 }
183 else
184 {
185 let capturable = capturableTargets.get(target.id());
186 if (!capturable.ents.has(ent.id()))
187 {
188 capturable.strength += ent.captureStrength() * PETRA.getAttackBonus(ent, target, "Capture");
189 capturable.ents.add(ent.id());
190 }
191 capture = capturable.strength;
192 }
193 capture *= 1 / (0.1 + 0.9*target.healthLevel());
194 let sumCapturePoints = target.capturePoints().reduce((a, b) => a + b);
195 if (target.hasDefensiveFire() && target.isGarrisonHolder() && target.garrisoned())
196 return capture > antiCapture + sumCapturePoints/50;
197 return capture > antiCapture + sumCapturePoints/80;
198};
199
200PETRA.getAttackBonus = function(ent, target, type)
201{
202 let attackBonus = 1;
203 if (!ent.get("Attack/" + type) || !ent.get("Attack/" + type + "/Bonuses"))
204 return attackBonus;
205 let bonuses = ent.get("Attack/" + type + "/Bonuses");
206 for (let key in bonuses)
207 {
208 let bonus = bonuses[key];
209 if (bonus.Civ && bonus.Civ !== target.civ())
210 continue;
211 if (bonus.Classes && bonus.Classes.split(/\s+/).some(cls => !target.hasClass(cls)))
212 continue;
213 attackBonus *= bonus.Multiplier;
214 }
215 return attackBonus;
216};
217
218/** Makes the worker deposit the currently carried resources at the closest accessible dropsite */
219PETRA.returnResources = function(gameState, ent)
220{
221 if (!ent.resourceCarrying() || !ent.resourceCarrying().length || !ent.position())
222 return false;
223
224 let resource = ent.resourceCarrying()[0].type;
225
226 let closestDropsite;
227 let distmin = Math.min();
228 let access = PETRA.getLandAccess(gameState, ent);
229 let dropsiteCollection = gameState.playerData.hasSharedDropsites ?
230 gameState.getAnyDropsites(resource) : gameState.getOwnDropsites(resource);
231 for (let dropsite of dropsiteCollection.values())
232 {
233 if (!dropsite.position())
234 continue;
235 let owner = dropsite.owner();
236 // owner !== PlayerID can only happen when hasSharedDropsites === true, so no need to test it again
237 if (owner !== PlayerID && (!dropsite.isSharedDropsite() || !gameState.isPlayerMutualAlly(owner)))
238 continue;
239 if (PETRA.getLandAccess(gameState, dropsite) != access)
240 continue;
241 let dist = API3.SquareVectorDistance(ent.position(), dropsite.position());
242 if (dist > distmin)
243 continue;
244 distmin = dist;
245 closestDropsite = dropsite;
246 }
247
248 if (!closestDropsite)
249 return false;
250 ent.returnResources(closestDropsite);
251 return true;
252};
253
254/** is supply full taking into account gatherers affected during this turn */
255PETRA.IsSupplyFull = function(gameState, ent)
256{
257 return ent.isFull() === true ||
258 ent.resourceSupplyNumGatherers() + gameState.ai.HQ.GetTCGatherer(ent.id()) >= ent.maxGatherers();
259};
260
261/**
262 * Get the best base (in terms of distance and accessIndex) for an entity.
263 * It should be on the same accessIndex for structures.
264 * If nothing found, return the base[0] for units and undefined for structures.
265 * If exclude is given, we exclude the base with ID = exclude.
266 */
267PETRA.getBestBase = function(gameState, ent, onlyConstructedBase = false, exclude = false)
268{
269 let pos = ent.position();
270 let accessIndex;
271 if (!pos)
272 {
273 let holder = PETRA.getHolder(gameState, ent);
274 if (!holder || !holder.position())
275 {
276 API3.warn("Petra error: entity without position, but not garrisoned");
277 PETRA.dumpEntity(ent);
278 return gameState.ai.HQ.baseManagers[0];
279 }
280 pos = holder.position();
281 accessIndex = PETRA.getLandAccess(gameState, holder);
282 }
283 else
284 accessIndex = PETRA.getLandAccess(gameState, ent);
285
286 let distmin = Math.min();
287 let dist;
288 let bestbase;
289 for (let base of gameState.ai.HQ.baseManagers)
290 {
291 if (base.ID == gameState.ai.HQ.baseManagers[0].ID || exclude && base.ID == exclude)
292 continue;
293 if (onlyConstructedBase && (!base.anchor || base.anchor.foundationProgress() !== undefined))
294 continue;
295 if (ent.hasClass("Structure") && base.accessIndex != accessIndex)
296 continue;
297 if (base.anchor && base.anchor.position())
298 dist = API3.SquareVectorDistance(base.anchor.position(), pos);
299 else
300 {
301 let found = false;
302 for (let structure of base.buildings.values())
303 {
304 if (!structure.position())
305 continue;
306 dist = API3.SquareVectorDistance(structure.position(), pos);
307 found = true;
308 break;
309 }
310 if (!found)
311 continue;
312 }
313 if (base.accessIndex != accessIndex)
314 dist += 50000000;
315 if (!base.anchor)
316 dist += 50000000;
317 if (dist > distmin)
318 continue;
319 distmin = dist;
320 bestbase = base;
321 }
322 if (!bestbase && !ent.hasClass("Structure"))
323 bestbase = gameState.ai.HQ.baseManagers[0];
324 return bestbase;
325};
326
327PETRA.getHolder = function(gameState, ent)
328{
329 for (let holder of gameState.getEntities().values())
330 {
331 if (holder.isGarrisonHolder() && holder.garrisoned().indexOf(ent.id()) !== -1)
332 return holder;
333 }
334 return undefined;
335};
336
337/** return the template of the built foundation if a foundation, otherwise return the entity itself */
338PETRA.getBuiltEntity = function(gameState, ent)
339{
340 if (ent.foundationProgress() !== undefined)
341 return gameState.getBuiltTemplate(ent.templateName());
342
343 return ent;
344};
345
346/**
347 * return true if it is not worth finishing this building (it would surely decay)
348 * TODO implement the other conditions
349 */
350PETRA.isNotWorthBuilding = function(gameState, ent)
351{
352 if (gameState.ai.HQ.territoryMap.getOwner(ent.position()) !== PlayerID)
353 {
354 let buildTerritories = ent.buildTerritories();
355 if (buildTerritories && (!buildTerritories.length || buildTerritories.length === 1 && buildTerritories[0] === "own"))
356 return true;
357 }
358 return false;
359};
360
361/**
362 * Check if the straight line between the two positions crosses an enemy territory
363 */
364PETRA.isLineInsideEnemyTerritory = function(gameState, pos1, pos2, step=70)
365{
366 let n = Math.floor(Math.sqrt(API3.SquareVectorDistance(pos1, pos2))/step) + 1;
367 let stepx = (pos2[0] - pos1[0]) / n;
368 let stepy = (pos2[1] - pos1[1]) / n;
369 for (let i = 1; i < n; ++i)
370 {
371 let pos = [pos1[0]+i*stepx, pos1[1]+i*stepy];
372 let owner = gameState.ai.HQ.territoryMap.getOwner(pos);
373 if (owner && gameState.isPlayerEnemy(owner))
374 return true;
375 }
376 return false;
377};
378
379PETRA.gatherTreasure = function(gameState, ent, water = false)
380{
381 if (!gameState.ai.HQ.treasures.hasEntities())
382 return false;
383 if (!ent || !ent.position())
384 return false;
385 if (!ent.isTreasureCollecter)
386 return false;
387 let treasureFound;
388 let distmin = Math.min();
389 let access = water ? PETRA.getSeaAccess(gameState, ent) : PETRA.getLandAccess(gameState, ent);
390 for (let treasure of gameState.ai.HQ.treasures.values())
391 {
392 // let some time for the previous gatherer to reach the treasure before trying again
393 let lastGathered = treasure.getMetadata(PlayerID, "lastGathered");
394 if (lastGathered && gameState.ai.elapsedTime - lastGathered < 20)
395 continue;
396 if (!water && access != PETRA.getLandAccess(gameState, treasure))
397 continue;
398 if (water && access != PETRA.getSeaAccess(gameState, treasure))
399 continue;
400 let territoryOwner = gameState.ai.HQ.territoryMap.getOwner(treasure.position());
401 if (territoryOwner != 0 && !gameState.isPlayerAlly(territoryOwner))
402 continue;
403 let dist = API3.SquareVectorDistance(ent.position(), treasure.position());
404 if (dist > 120000 || territoryOwner != PlayerID && dist > 14000) // AI has no LOS, so restrict it a bit
405 continue;
406 if (dist > distmin)
407 continue;
408 distmin = dist;
409 treasureFound = treasure;
410 }
411 if (!treasureFound)
412 return false;
413 treasureFound.setMetadata(PlayerID, "lastGathered", gameState.ai.elapsedTime);
414 ent.collectTreasure(treasureFound);
415 ent.setMetadata(PlayerID, "treasure", treasureFound.id());
416 return true;
417};
418
419PETRA.dumpEntity = function(ent)
420{
421 if (!ent)
422 return;
423 API3.warn(" >>> id " + ent.id() + " name " + ent.genericName() + " pos " + ent.position() +
424 " state " + ent.unitAIState());
425 API3.warn(" base " + ent.getMetadata(PlayerID, "base") + " >>> role " + ent.getMetadata(PlayerID, "role") +
426 " subrole " + ent.getMetadata(PlayerID, "subrole"));
427 API3.warn("owner " + ent.owner() + " health " + ent.hitpoints() + " healthMax " + ent.maxHitpoints() +
428 " foundationProgress " + ent.foundationProgress());
429 API3.warn(" garrisoning " + ent.getMetadata(PlayerID, "garrisoning") +
430 " garrisonHolder " + ent.getMetadata(PlayerID, "garrisonHolder") +
431 " plan " + ent.getMetadata(PlayerID, "plan") + " transport " + ent.getMetadata(PlayerID, "transport"));
432 API3.warn(" stance " + ent.getStance() + " transporter " + ent.getMetadata(PlayerID, "transporter") +
433 " gather-type " + ent.getMetadata(PlayerID, "gather-type") +
434 " target-foundation " + ent.getMetadata(PlayerID, "target-foundation") +
435 " PartOfArmy " + ent.getMetadata(PlayerID, "PartOfArmy"));
436};
Note: See TracBrowser for help on using the repository browser.