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