Line data Source code
1 : // These integers must be sequential
2 0 : PETRA.DIFFICULTY_SANDBOX = 0;
3 0 : PETRA.DIFFICULTY_VERY_EASY = 1;
4 0 : PETRA.DIFFICULTY_EASY = 2;
5 0 : PETRA.DIFFICULTY_MEDIUM = 3;
6 0 : PETRA.DIFFICULTY_HARD = 4;
7 0 : PETRA.DIFFICULTY_VERY_HARD = 5;
8 :
9 0 : PETRA.Config = function(difficulty = PETRA.DIFFICULTY_MEDIUM, behavior)
10 : {
11 0 : this.difficulty = difficulty;
12 :
13 : // for instance "balanced", "aggressive" or "defensive"
14 0 : this.behavior = behavior || "random";
15 :
16 : // debug level: 0=none, 1=sanity checks, 2=debug, 3=detailed debug, -100=serializatio debug
17 0 : this.debug = 0;
18 :
19 0 : this.chat = true; // false to prevent AI's chats
20 :
21 0 : this.popScaling = 1; // scale factor depending on the max population
22 :
23 0 : this.Military = {
24 : "towerLapseTime": 90, // Time to wait between building 2 towers
25 : "fortressLapseTime": 390, // Time to wait between building 2 fortresses
26 : "popForBarracks1": 25,
27 : "popForBarracks2": 95,
28 : "popForForge": 65,
29 : "numSentryTowers": 1
30 : };
31 :
32 0 : this.DamageTypeImportance = {
33 : "Hack": 0.085,
34 : "Pierce": 0.075,
35 : "Crush": 0.065,
36 : "Fire": 0.095
37 : };
38 :
39 0 : this.Economy = {
40 : "popPhase2": 38, // How many units we want before aging to phase2.
41 : "workPhase3": 65, // How many workers we want before aging to phase3.
42 : "workPhase4": 80, // How many workers we want before aging to phase4 or higher.
43 : "popForDock": 25,
44 : "targetNumWorkers": 40, // dummy, will be changed later
45 : "targetNumTraders": 5, // Target number of traders
46 : "targetNumFishers": 1, // Target number of fishers per sea
47 : "supportRatio": 0.35, // fraction of support workers among the workforce
48 : "provisionFields": 2
49 : };
50 :
51 : // Note: attack settings are set directly in attack_plan.js
52 : // defense
53 0 : this.Defense =
54 : {
55 : "defenseRatio": { "ally": 1.4, "neutral": 1.8, "own": 2 }, // ratio of defenders/attackers.
56 : "armyCompactSize": 2000, // squared. Half-diameter of an army.
57 : "armyBreakawaySize": 3500, // squared.
58 : "armyMergeSize": 1400 // squared.
59 : };
60 :
61 : // Additional buildings that the AI does not yet know when to build
62 : // and that it will try to build on phase 3 when enough resources.
63 0 : this.buildings =
64 : {
65 : "default": [],
66 : "athen": [
67 : "structures/{civ}/gymnasium",
68 : "structures/{civ}/prytaneion",
69 : "structures/{civ}/theater"
70 : ],
71 : "brit": [],
72 : "cart": [
73 : "structures/{civ}/embassy_celtic",
74 : "structures/{civ}/embassy_iberian",
75 : "structures/{civ}/embassy_italic"
76 : ],
77 : "gaul": [
78 : "structures/{civ}/assembly"
79 : ],
80 : "han": [
81 : "structures/{civ}/academy"
82 : ],
83 : "iber": [
84 : "structures/{civ}/monument"
85 : ],
86 : "kush": [
87 : "structures/{civ}/camp_blemmye",
88 : "structures/{civ}/camp_noba",
89 : "structures/{civ}/pyramid_large",
90 : "structures/{civ}/pyramid_small",
91 : "structures/{civ}/temple_amun"
92 : ],
93 : "mace": [
94 : "structures/{civ}/theater"
95 : ],
96 : "maur": [
97 : "structures/{civ}/palace",
98 : "structures/{civ}/pillar_ashoka"
99 : ],
100 : "pers": [
101 : "structures/{civ}/apadana"
102 : ],
103 : "ptol": [
104 : "structures/{civ}/library",
105 : "structures/{civ}/theater"
106 : ],
107 : "rome": [
108 : "structures/{civ}/army_camp",
109 : "structures/{civ}/temple_vesta"
110 : ],
111 : "sele": [
112 : "structures/{civ}/theater"
113 : ],
114 : "spart": [
115 : "structures/{civ}/syssiton",
116 : "structures/{civ}/theater"
117 : ]
118 : };
119 :
120 0 : this.priorities =
121 : {
122 : "villager": 30, // should be slightly lower than the citizen soldier one to not get all the food
123 : "citizenSoldier": 60,
124 : "trader": 50,
125 : "healer": 20,
126 : "ships": 70,
127 : "house": 350,
128 : "dropsites": 200,
129 : "field": 400,
130 : "dock": 90,
131 : "corral": 100,
132 : "economicBuilding": 90,
133 : "militaryBuilding": 130,
134 : "defenseBuilding": 70,
135 : "civilCentre": 950,
136 : "majorTech": 700,
137 : "minorTech": 250,
138 : "wonder": 1000,
139 : "emergency": 1000 // used only in emergency situations, should be the highest one
140 : };
141 :
142 : // Default personality (will be updated in setConfig)
143 0 : this.personality =
144 : {
145 : "aggressive": 0.5,
146 : "cooperative": 0.5,
147 : "defensive": 0.5
148 : };
149 :
150 : // See PETRA.QueueManager.prototype.wantedGatherRates()
151 0 : this.queues =
152 : {
153 : "firstTurn": {
154 : "food": 10,
155 : "wood": 10,
156 : "default": 0
157 : },
158 : "short": {
159 : "food": 200,
160 : "wood": 200,
161 : "default": 100
162 : },
163 : "medium": {
164 : "default": 0
165 : },
166 : "long": {
167 : "default": 0
168 : }
169 : };
170 :
171 0 : this.garrisonHealthLevel = { "low": 0.4, "medium": 0.55, "high": 0.7 };
172 :
173 0 : this.unusedNoAllyTechs = [
174 : "Player/sharedLos",
175 : "Market/InternationalBonus",
176 : "Player/sharedDropsites"
177 : ];
178 :
179 0 : this.criticalPopulationFactors = [
180 : 0.8,
181 : 0.8,
182 : 0.7,
183 : 0.6,
184 : 0.5,
185 : 0.35
186 : ];
187 :
188 0 : this.criticalStructureFactors = [
189 : 0.8,
190 : 0.8,
191 : 0.7,
192 : 0.6,
193 : 0.5,
194 : 0.35
195 : ];
196 :
197 0 : this.criticalRootFactors = [
198 : 0.8,
199 : 0.8,
200 : 0.67,
201 : 0.5,
202 : 0.35,
203 : 0.2
204 : ];
205 : };
206 :
207 0 : PETRA.Config.prototype.setConfig = function(gameState)
208 : {
209 0 : if (this.difficulty > PETRA.DIFFICULTY_SANDBOX)
210 : {
211 : // Setup personality traits according to the user choice:
212 : // The parameter used to define the personality is basically the aggressivity or (1-defensiveness)
213 : // as they are anticorrelated, although some small smearing to decorelate them will be added.
214 : // And for each user choice, this parameter can vary between min and max
215 0 : let personalityList = {
216 : "random": { "min": 0, "max": 1 },
217 : "defensive": { "min": 0, "max": 0.27 },
218 : "balanced": { "min": 0.37, "max": 0.63 },
219 : "aggressive": { "min": 0.73, "max": 1 }
220 : };
221 0 : let behavior = randFloat(-0.5, 0.5);
222 : // make agressive and defensive quite anticorrelated (aggressive ~ 1 - defensive) but not completelety
223 0 : let variation = 0.15 * randFloat(-1, 1) * Math.sqrt(Math.square(0.5) - Math.square(behavior));
224 0 : let aggressive = Math.max(Math.min(behavior + variation, 0.5), -0.5) + 0.5;
225 0 : let defensive = Math.max(Math.min(-behavior + variation, 0.5), -0.5) + 0.5;
226 0 : let min = personalityList[this.behavior].min;
227 0 : let max = personalityList[this.behavior].max;
228 0 : this.personality = {
229 : "aggressive": min + aggressive * (max - min),
230 : "defensive": 1 - max + defensive * (max - min),
231 : "cooperative": randFloat(0, 1)
232 : };
233 : }
234 : // Petra usually uses the continuous values of personality.aggressive and personality.defensive
235 : // to define its behavior according to personality. But when discontinuous behavior is needed,
236 : // it uses the following personalityCut which should be set such that:
237 : // behavior="aggressive" => personality.aggressive > personalityCut.strong &&
238 : // personality.defensive < personalityCut.weak
239 : // and inversely for behavior="defensive"
240 0 : this.personalityCut = { "weak": 0.3, "medium": 0.5, "strong": 0.7 };
241 :
242 0 : if (gameState.playerData.teamsLocked)
243 0 : this.personality.cooperative = Math.min(1, this.personality.cooperative + 0.30);
244 0 : else if (gameState.getAlliedVictory())
245 0 : this.personality.cooperative = Math.min(1, this.personality.cooperative + 0.15);
246 :
247 : // changing settings based on difficulty or personality
248 0 : this.Military.towerLapseTime = Math.round(this.Military.towerLapseTime * (1.1 - 0.2 * this.personality.defensive));
249 0 : this.Military.fortressLapseTime = Math.round(this.Military.fortressLapseTime * (1.1 - 0.2 * this.personality.defensive));
250 0 : this.priorities.defenseBuilding = Math.round(this.priorities.defenseBuilding * (0.9 + 0.2 * this.personality.defensive));
251 :
252 0 : if (this.difficulty < PETRA.DIFFICULTY_EASY)
253 : {
254 0 : this.popScaling = 0.5;
255 0 : this.Economy.supportRatio = 0.5;
256 0 : this.Economy.provisionFields = 1;
257 0 : this.Military.numSentryTowers = this.personality.defensive > this.personalityCut.strong ? 1 : 0;
258 : }
259 0 : else if (this.difficulty < PETRA.DIFFICULTY_MEDIUM)
260 : {
261 0 : this.popScaling = 0.7;
262 0 : this.Economy.supportRatio = 0.4;
263 0 : this.Economy.provisionFields = 1;
264 0 : this.Military.numSentryTowers = this.personality.defensive > this.personalityCut.strong ? 1 : 0;
265 : }
266 : else
267 : {
268 0 : if (this.difficulty == PETRA.DIFFICULTY_MEDIUM)
269 0 : this.Military.numSentryTowers = 1;
270 : else
271 0 : this.Military.numSentryTowers = 2;
272 0 : if (this.personality.defensive > this.personalityCut.strong)
273 0 : ++this.Military.numSentryTowers;
274 0 : else if (this.personality.defensive < this.personalityCut.weak)
275 0 : --this.Military.numSentryTowers;
276 :
277 0 : if (this.personality.aggressive > this.personalityCut.strong)
278 : {
279 0 : this.Military.popForBarracks1 = 12;
280 0 : this.Economy.popPhase2 = 50;
281 0 : this.priorities.healer = 10;
282 : }
283 : }
284 :
285 0 : let maxPop = gameState.getPopulationMax();
286 0 : if (this.difficulty < PETRA.DIFFICULTY_EASY)
287 0 : this.Economy.targetNumWorkers = Math.max(1, Math.min(40, maxPop));
288 0 : else if (this.difficulty < PETRA.DIFFICULTY_MEDIUM)
289 0 : this.Economy.targetNumWorkers = Math.max(1, Math.min(60, Math.floor(maxPop/2)));
290 : else
291 0 : this.Economy.targetNumWorkers = Math.max(1, Math.min(120, Math.floor(maxPop/3)));
292 0 : this.Economy.targetNumTraders = 2 + this.difficulty;
293 :
294 :
295 0 : if (gameState.getVictoryConditions().has("wonder"))
296 : {
297 0 : this.Economy.workPhase3 = Math.floor(0.9 * this.Economy.workPhase3);
298 0 : this.Economy.workPhase4 = Math.floor(0.9 * this.Economy.workPhase4);
299 : }
300 :
301 0 : if (maxPop < 300)
302 0 : this.popScaling *= Math.sqrt(maxPop / 300);
303 :
304 0 : this.Military.popForBarracks1 = Math.min(Math.max(Math.floor(this.Military.popForBarracks1 * this.popScaling), 12), Math.floor(maxPop/5));
305 0 : this.Military.popForBarracks2 = Math.min(Math.max(Math.floor(this.Military.popForBarracks2 * this.popScaling), 45), Math.floor(maxPop*2/3));
306 0 : this.Military.popForForge = Math.min(Math.max(Math.floor(this.Military.popForForge * this.popScaling), 30), Math.floor(maxPop/2));
307 0 : this.Economy.popPhase2 = Math.min(Math.max(Math.floor(this.Economy.popPhase2 * this.popScaling), 20), Math.floor(maxPop/2));
308 0 : this.Economy.workPhase3 = Math.min(Math.max(Math.floor(this.Economy.workPhase3 * this.popScaling), 40), Math.floor(maxPop*2/3));
309 0 : this.Economy.workPhase4 = Math.min(Math.max(Math.floor(this.Economy.workPhase4 * this.popScaling), 45), Math.floor(maxPop*2/3));
310 0 : this.Economy.targetNumTraders = Math.round(this.Economy.targetNumTraders * this.popScaling);
311 0 : this.Economy.targetNumWorkers = Math.max(this.Economy.targetNumWorkers, this.Economy.popPhase2);
312 0 : this.Economy.workPhase3 = Math.min(this.Economy.workPhase3, this.Economy.targetNumWorkers);
313 0 : this.Economy.workPhase4 = Math.min(this.Economy.workPhase4, this.Economy.targetNumWorkers);
314 0 : if (this.difficulty < PETRA.DIFFICULTY_EASY)
315 0 : this.Economy.workPhase3 = Infinity; // prevent the phasing to city phase
316 :
317 0 : this.emergencyValues = {
318 : "population": this.criticalPopulationFactors[this.difficulty],
319 : "structures": this.criticalStructureFactors[this.difficulty],
320 : "roots": this.criticalRootFactors[this.difficulty],
321 : };
322 :
323 0 : this.Cheat(gameState);
324 :
325 0 : if (this.debug < 2)
326 0 : return;
327 0 : API3.warn(" >>> Petra bot: personality = " + uneval(this.personality));
328 : };
329 :
330 0 : PETRA.Config.prototype.Cheat = function(gameState)
331 : {
332 : // Sandbox, Very Easy, Easy, Medium, Hard, Very Hard
333 : // rate apply on resource stockpiling as gathering and trading
334 : // time apply on building, upgrading, packing, training and technologies
335 0 : const rate = [ 0.42, 0.56, 0.75, 1.00, 1.25, 1.56 ];
336 0 : const time = [ 1.40, 1.25, 1.10, 1.00, 1.00, 1.00 ];
337 0 : const AIDiff = Math.min(this.difficulty, rate.length - 1);
338 0 : SimEngine.QueryInterface(Sim.SYSTEM_ENTITY, Sim.IID_ModifiersManager).AddModifiers("AI Bonus", {
339 : "ResourceGatherer/BaseSpeed": [{ "affects": ["Unit", "Structure"], "multiply": rate[AIDiff] }],
340 : "Trader/GainMultiplier": [{ "affects": ["Unit", "Structure"], "multiply": rate[AIDiff] }],
341 : "Cost/BuildTime": [{ "affects": ["Unit", "Structure"], "multiply": time[AIDiff] }],
342 : }, gameState.playerData.entity);
343 : };
344 :
345 0 : PETRA.Config.prototype.Serialize = function()
346 : {
347 0 : var data = {};
348 0 : for (let key in this)
349 0 : if (this.hasOwnProperty(key) && key != "debug")
350 0 : data[key] = this[key];
351 0 : return data;
352 : };
353 :
354 0 : PETRA.Config.prototype.Deserialize = function(data)
355 : {
356 0 : for (let key in data)
357 0 : this[key] = data[key];
358 : };
|