1 /*
2 * Seven Kingdoms: Ancient Adversaries
3 *
4 * Copyright 1997,1998 Enlight Software Ltd.
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 */
20
21 //Filename : OAI_CAPT.CPP
22 //Description: AI - capturing independent towns
23
24 #include <queue>
25 #include <ALL.h>
26 #include <OCONFIG.h>
27 #include <OUNIT.h>
28 #include <OF_CAMP.h>
29 #include <OF_INN.h>
30 #include <ONATION.h>
31
32
33 //------- define struct CaptureTown -------//
34
35 struct CaptureTown
36 {
37 int town_recno;
38 int min_resistance;
39 int capture_unit_recno; // if > 0, the unit that was found to best be able to capture the town.
40
operator <CaptureTown41 bool operator<(const CaptureTown &other) const {return min_resistance < other.min_resistance;}
42 };
43
44
45 //--------- Begin of function Nation::think_capture --------//
46 //
think_capture()47 int Nation::think_capture()
48 {
49 if( ai_camp_count==0 ) // this can happen when a new nation has just emerged
50 return 0;
51
52 //--- don't capture if the AI is using growth and capture strategy (as opposite to build mine strategy) ---//
53
54 if( ai_mine_count==0 && total_population < 25 )
55 return 0;
56
57 //-----------------------------------------//
58
59 if( think_capture_independent() )
60 return 1;
61
62 return 0;
63 }
64 //---------- End of function Nation::think_capture ---------//
65
66
67 //--------- Begin of function Nation::think_capture_independent --------//
68 //
69 // Think about capturing independent towns.
70 //
think_capture_independent()71 int Nation::think_capture_independent()
72 {
73 //------- Capture target choices -------//
74
75 std::priority_queue<CaptureTown> captureTownQueue;
76
77 //--- find the town that makes most sense to capture ---//
78
79 int townRecno;
80 Town* townPtr;
81
82 for(townRecno=town_array.size(); townRecno>0; townRecno--)
83 {
84 if(town_array.is_deleted(townRecno))
85 continue;
86
87 townPtr = town_array[townRecno];
88
89 if( townPtr->nation_recno ) // only capture independent towns
90 continue;
91
92 if( townPtr->no_neighbor_space ) // if there is no space in the neighbor area for building a new firm.
93 continue;
94
95 if( townPtr->rebel_recno ) // towns controlled by rebels will not drop in resistance even if a command base is present
96 continue;
97
98 //------ only if we have a presence/a base town in this region -----//
99
100 if( !has_base_town_in_region(townPtr->region_id) )
101 continue;
102
103 //---- check if there are already camps linked to this town ----//
104
105 int i;
106 for( i=townPtr->linked_firm_count-1 ; i>=0 ; i-- )
107 {
108 Firm* firmPtr = firm_array[ townPtr->linked_firm_array[i] ];
109
110 if( firmPtr->firm_id != FIRM_CAMP )
111 continue;
112
113 //------ if we already have a camp linked to this town -----//
114
115 if( firmPtr->nation_recno == nation_recno )
116 break;
117
118 //--- if there is an overseer with high leadership and right race in the opponent's camp, don't bother to compete with him ---//
119
120 if( firmPtr->overseer_recno )
121 {
122 Unit* unitPtr = unit_array[firmPtr->overseer_recno];
123
124 if( unitPtr->skill.skill_level >= 70 &&
125 unitPtr->race_id == townPtr->majority_race() )
126 {
127 break;
128 }
129 }
130 }
131
132 if( i>=0 ) // there is already a camp linked to this town and we don't want to get involved with its capturing plan
133 continue;
134
135 //------ no linked camps interfering with potential capture ------//
136
137 int captureUnitRecno;
138 int targetResistance = capture_expected_resistance(townRecno, &captureUnitRecno);
139 int averageResistance = townPtr->average_resistance(nation_recno);
140 int minResistance = MIN( averageResistance, targetResistance );
141
142 if( minResistance < 50 - pref_peacefulness/5 ) // 30 to 50 depending on
143 {
144 captureTownQueue.push({townRecno, minResistance, captureUnitRecno});
145 }
146 }
147
148 //------- try to capture the town in their resistance order ----//
149
150 const bool needToCheckDistance = !config.explore_whole_map && info.game_date-info.game_start_date >
151 MAX(MAX_WORLD_X_LOC, MAX_WORLD_Y_LOC) * (5-config.ai_aggressiveness) / 5; // 3 to 5 / 5
152
153 while( captureTownQueue.size() > 0 )
154 {
155 int captureRecno = captureTownQueue.top().town_recno;
156 int captureUnitRecno = captureTownQueue.top().capture_unit_recno;
157 captureTownQueue.pop();
158
159 err_when( town_array.is_deleted(captureRecno) );
160
161 //-------------------------------------------//
162 // If the map is set to unexplored, wait for a
163 // reasonable amount of time before moving out
164 // to build the camp.
165 //-------------------------------------------//
166
167 if( needToCheckDistance )
168 {
169 Town* targetTown = town_array[ captureRecno ];
170
171 int j;
172 for( j=0 ; j<ai_town_count ; j++ )
173 {
174 Town* ownTown = town_array[ ai_town_array[j] ];
175
176 int townDistance = misc.points_distance(targetTown->center_x, targetTown->center_y,
177 ownTown->center_x, ownTown->center_y);
178
179 if( info.game_date-info.game_start_date >
180 townDistance * (5-config.ai_aggressiveness) / 5 ) // 3 to 5 / 5
181 {
182 break;
183 }
184 }
185
186 if( j==ai_town_count )
187 continue;
188 }
189
190 if( start_capture( captureRecno, captureUnitRecno ) )
191 return 1;
192 }
193
194 return 0;
195 }
196 //---------- End of function Nation::think_capture_independent ---------//
197
198
199 //--------- Begin of function Nation::should_use_cash_to_capture --------//
200 //
should_use_cash_to_capture()201 int Nation::should_use_cash_to_capture()
202 {
203 //--- if we have plenty of cash, use cash to decrease the resistance of the villagers ---//
204
205 return military_rank_rating() < 50+pref_peacefulness/5 && // 50 to 70
206 ai_should_spend(pref_loyalty_concern/4);
207 }
208 //---------- End of function Nation::should_use_cash_to_capture ---------//
209
210
211 //--------- Begin of function Nation::capture_expected_resistance --------//
212 //
213 // The lowest resistance can be expected if we are going to capture the
214 // town.
215 //
capture_expected_resistance(int townRecno,int * captureUnitRecno)216 int Nation::capture_expected_resistance(int townRecno, int *captureUnitRecno)
217 {
218 //--- we have plenty of cash, use cash to decrease the resistance of the villagers ---//
219
220 *captureUnitRecno = 0;
221
222 if( should_use_cash_to_capture() )
223 return 0; // return zero resistance
224
225 //----- the average resistance determines the captureRating ------//
226
227 int captureRating = 0;
228 Town* townPtr = town_array[townRecno];
229
230 int averageResistance;
231
232 if( townPtr->nation_recno )
233 averageResistance = townPtr->average_loyalty();
234 else
235 averageResistance = townPtr->average_resistance(nation_recno);
236
237 //---- see if there are general available for capturing this town ---//
238
239 int majorityRace = townPtr->majority_race();
240 int targetResistance;
241
242 *captureUnitRecno = find_best_capturer(townRecno, majorityRace, /*out*/ targetResistance);
243 if( !(*captureUnitRecno) )
244 return 100;
245
246 int resultResistance =
247 ( targetResistance * townPtr->race_pop_array[majorityRace-1] +
248 averageResistance * (townPtr->population - townPtr->race_pop_array[majorityRace-1]) )
249 / townPtr->population;
250
251 return resultResistance;
252 }
253 //---------- End of function Nation::capture_expected_resistance ---------//
254
255
256 //--------- Begin of function Nation::start_capture --------//
257 //
start_capture(int townRecno,int captureUnitRecno)258 int Nation::start_capture(int townRecno, int captureUnitRecno)
259 {
260 //--- find the two races with most population in the town ---//
261
262 Town* townPtr = town_array[townRecno];
263
264 int majorityRace=0;
265
266 //--- if it's an independent town, the race of the commander must match with the race of the town ---//
267
268 if( townPtr->nation_recno == 0 )
269 {
270 majorityRace = townPtr->majority_race();
271 }
272
273 //---- see if we have generals in the most populated race, if so build a camp next to the town ----//
274
275 return capture_build_camp(townRecno, majorityRace, captureUnitRecno);
276 }
277 //---------- End of function Nation::start_capture ---------//
278
279
280 //--------- Begin of function Nation::capture_build_camp --------//
281 //
capture_build_camp(int townRecno,int raceId,int captureUnitRecno)282 int Nation::capture_build_camp(int townRecno, int raceId, int captureUnitRecno)
283 {
284 Town* captureTown = town_array[townRecno];
285
286 //------- locate a place to build the camp -------//
287
288 short buildXLoc, buildYLoc;
289
290 if( !find_best_firm_loc(FIRM_CAMP, captureTown->loc_x1, captureTown->loc_y1, buildXLoc, buildYLoc) )
291 {
292 captureTown->no_neighbor_space = 1;
293 return 0;
294 }
295
296 //---- find the best available general for the capturing action ---//
297
298 int unitRecno = captureUnitRecno;
299 int targetResistance;
300
301 if ( !unitRecno )
302 unitRecno = find_best_capturer(townRecno, raceId, targetResistance);
303
304 if( !unitRecno )
305 unitRecno = hire_best_capturer(townRecno, raceId);
306
307 if( !unitRecno )
308 {
309 //--- if we have plenty of cash and can use cash to decrease the resistance of the independent villagers ---//
310
311 if( should_use_cash_to_capture() )
312 {
313 char resultFlag;
314
315 Unit* skilledUnit = find_skilled_unit(SKILL_LEADING, raceId,
316 captureTown->center_x, captureTown->center_y, resultFlag);
317
318 if( skilledUnit )
319 unitRecno = skilledUnit->sprite_recno;
320 }
321
322 if( !unitRecno )
323 return 0;
324 }
325
326 //--- if the picked unit is an overseer of an existng camp ---//
327
328 if( !mobilize_capturer(unitRecno) )
329 return 0;
330
331 //---------- add the action to the queue now ----------//
332
333 err_when( captureTown->nation_recno==0 &&
334 unit_array[unitRecno]->race_id != captureTown->majority_race() );
335
336 int actionRecno = add_action( buildXLoc, buildYLoc, captureTown->loc_x1, captureTown->loc_y1,
337 ACTION_AI_BUILD_FIRM, FIRM_CAMP, 1, unitRecno );
338
339 if( actionRecno )
340 process_action(actionRecno);
341
342 return 1;
343 }
344 //---------- End of function Nation::capture_build_camp ---------//
345
346
347 //-------- Begin of function Nation::find_best_capturer ------//
348 //
349 // Find an existing unit as the capturer of the town.
350 //
351 // <int> townRecno - recno of the town to capture
352 // <int> raceId - race id. of the capturer. 0 if any races.
353 // <int&> bestTargetResistance - a reference var for returning the target resistance if the returned unit is assigned as the overseer
354 //
355 // return: <int> the recno of the unit found.
356 //
find_best_capturer(int townRecno,int raceId,int & bestTargetResistance)357 int Nation::find_best_capturer(int townRecno, int raceId, int& bestTargetResistance)
358 {
359 #define MIN_CAPTURE_RESISTANCE_DEC 20 // if we assign a unit as the commander, the minimum expected resistance decrease should be 20, otherwise we don't do it.
360
361 Unit* unitPtr;
362 Town* targetTown = town_array[townRecno];
363 Firm* firmPtr;
364 int targetResistance;
365 int bestUnitRecno=0;
366
367 bestTargetResistance = 100;
368
369 for( int i=ai_general_count-1 ; i>=0 ; i-- )
370 {
371 unitPtr = unit_array[ ai_general_array[i] ];
372
373 if( raceId && unitPtr->race_id != raceId )
374 continue;
375
376 err_when( unitPtr->nation_recno != nation_recno );
377 err_when( unitPtr->rank_id != RANK_KING && unitPtr->rank_id != RANK_GENERAL );
378
379 if( unitPtr->nation_recno != nation_recno )
380 continue;
381
382 //---- if this unit is on a mission ----//
383
384 if( unitPtr->home_camp_firm_recno )
385 continue;
386
387 //---- don't use the king to build camps next to capture enemy towns, only next to independent towns ----//
388
389 if( unitPtr->rank_id == RANK_KING && targetTown->nation_recno )
390 continue;
391
392 //----- if this unit is in a camp -------//
393
394 if( unitPtr->unit_mode == UNIT_MODE_OVERSEE )
395 {
396 firmPtr = firm_array[unitPtr->unit_mode_para];
397
398 //--- check if the unit currently in a command base trying to take over an independent town ---//
399
400 int j;
401 for( j=firmPtr->linked_town_count-1 ; j>=0 ; j-- )
402 {
403 Town* townPtr = town_array[ firmPtr->linked_town_array[j] ];
404
405 //--- if the unit is trying to capture an independent town and he is still influencing the town to decrease resistance ---//
406
407 if( townPtr->nation_recno==0 &&
408 townPtr->average_target_resistance(nation_recno) <
409 townPtr->average_resistance(nation_recno) )
410 {
411 break; // then don't use this unit
412 }
413 }
414
415 if( j>=0 ) // if so, don't use this unit
416 continue;
417 }
418
419 //--- if this unit is idle and the region ids are matched ---//
420
421 if( unitPtr->action_mode != ACTION_STOP ||
422 unitPtr->region_id() != targetTown->region_id )
423 {
424 continue;
425 }
426
427 //------- get the unit's influence index --------//
428
429 err_when( unitPtr->skill.skill_id != SKILL_LEADING );
430
431 targetResistance = 100-targetTown->camp_influence(unitPtr->sprite_recno); // influence of this unit if he is assigned as a commander of a military camp
432
433 //-- see if this unit's rating is higher than the current best --//
434
435 if( targetResistance < bestTargetResistance )
436 {
437 bestTargetResistance = targetResistance;
438 bestUnitRecno = unitPtr->sprite_recno;
439 }
440 }
441
442 return bestUnitRecno;
443 }
444 //-------- End of function Nation::find_best_capturer -------//
445
446
447 //-------- Begin of function Nation::mobilize_capturer ------//
448 //
449 // Mobilize the capturer unit if he isn't mobilized yet.
450 //
mobilize_capturer(int unitRecno)451 int Nation::mobilize_capturer(int unitRecno)
452 {
453 //--- if the picked unit is an overseer of an existng camp ---//
454
455 Unit* unitPtr = unit_array[unitRecno];
456
457 if( unitPtr->unit_mode == UNIT_MODE_OVERSEE )
458 {
459 Firm* firmPtr = firm_array[unitPtr->unit_mode_para];
460 Town* townPtr;
461
462 //-- can recruit from either a command base or seat of power --//
463
464 //-- train a villager with leadership to replace current overseer --//
465
466 int i;
467 for( i=0 ; i<firmPtr->linked_town_count ; i++ )
468 {
469 townPtr = town_array[ firmPtr->linked_town_array[i] ];
470
471 if( townPtr->nation_recno != nation_recno )
472 continue;
473
474 //--- first try to train a unit who is racially homogenous to the commander ---//
475
476 int unitRecno = townPtr->recruit( SKILL_LEADING, firmPtr->majority_race(), COMMAND_AI );
477
478 //--- if unsucessful, then try to train a unit whose race is the same as the majority of the town ---//
479
480 if( !unitRecno )
481 unitRecno = townPtr->recruit( SKILL_LEADING, townPtr->majority_race(), COMMAND_AI );
482
483 if( unitRecno )
484 {
485 add_action(townPtr->loc_x1, townPtr->loc_y1, -1, -1, ACTION_AI_ASSIGN_OVERSEER, FIRM_CAMP);
486 break;
487 }
488 }
489
490 if( i==firmPtr->linked_town_count ) // unsuccessful
491 return 0;
492
493 //------- mobilize the current overseer --------//
494
495 firmPtr->mobilize_overseer();
496 }
497
498 return 1;
499 }
500 //-------- End of function Nation::mobilize_capturer -------//
501
502
503 //-------- Begin of function Nation::hire_best_capturer ------//
504 //
505 // Hire the best unit you can find in one of the existing inns.
506 //
507 // <int> townRecno - recno of the town to capture
508 // <int> raceId - race id. of the unit to hire
509 //
510 // return: <int> the recno of the unit hired.
511 //
hire_best_capturer(int townRecno,int raceId)512 int Nation::hire_best_capturer(int townRecno, int raceId)
513 {
514 if( !ai_should_hire_unit(30) ) // 30 - importance rating
515 return 0;
516
517 FirmInn *firmInn;
518 Firm *firmPtr;
519 InnUnit *innUnit;
520 Skill *innUnitSkill;
521 int i, j, innUnitCount, curRating;
522 int bestRating=0, bestInnRecno=0, bestInnUnitId=0;
523 Town* townPtr = town_array[townRecno];
524 int destRegionId = world.get_region_id(townPtr->loc_x1, townPtr->loc_y1);
525
526 for(i=0; i<ai_inn_count; i++)
527 {
528 firmPtr = (FirmInn*) firm_array[ai_inn_array[i]];
529
530 err_when( firmPtr->firm_id != FIRM_INN );
531
532 if( firmPtr->region_id != destRegionId )
533 continue;
534
535 firmInn = firmPtr->cast_to_FirmInn();
536
537 innUnitCount=firmInn->inn_unit_count;
538
539 if( !innUnitCount )
540 continue;
541
542 innUnit = firmInn->inn_unit_array + innUnitCount - 1;
543
544 //------- check units in the inn ---------//
545
546 for(j=innUnitCount; j>0; j--, innUnit--)
547 {
548 innUnitSkill = &(innUnit->skill);
549
550 if( innUnitSkill->skill_id==SKILL_LEADING &&
551 unit_res[innUnit->unit_id]->race_id == raceId &&
552 cash >= innUnit->hire_cost )
553 {
554 //----------------------------------------------//
555 // evalute a unit on:
556 // -its race, whether it's the same as the nation's race
557 // -the inn's distance from the destination
558 // -the skill level of the unit.
559 //----------------------------------------------//
560
561 curRating = innUnitSkill->skill_level;
562
563 if( curRating > bestRating )
564 {
565 bestRating = curRating;
566
567 bestInnRecno = firmInn->firm_recno;
568 bestInnUnitId = j;
569 }
570 }
571 }
572 }
573
574 if( !bestInnUnitId )
575 return 0;
576
577 //----------------------------------------------------//
578
579 firmInn = (FirmInn*) firm_array[bestInnRecno];
580
581 int unitRecno = firmInn->hire(bestInnUnitId);
582
583 if( !unitRecno )
584 return 0;
585
586 unit_array[unitRecno]->set_rank(RANK_GENERAL);
587
588 return unitRecno;
589 }
590 //-------- End of function Nation::hire_best_capturer -------//
591