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_UNIT.CPP
22 //Description: AI - unit related functions
23 
24 #include <ALL.h>
25 #include <OUNIT.h>
26 #include <OF_INN.h>
27 #include <OTOWN.h>
28 #include <ONATION.h>
29 
30 
31 //-------- Begin of function Nation::get_skilled_unit -------//
32 //
33 // <int>		skillId			-	the skill the selected unit should have
34 // <int>		raceId			-	the race the selected unit should have
35 //										(0 for any races)
36 // <ActionNode*> actionNode - the ActionNode of the action that needs this skilled unit.
37 //
38 // return: <Unit*> skilledUnit - pointer to the skilled unit.
39 //
get_skilled_unit(int skillId,int raceId,ActionNode * actionNode)40 Unit* Nation::get_skilled_unit(int skillId, int raceId, ActionNode* actionNode)
41 {
42 	//--------- get a skilled unit --------//
43 
44 	Unit* skilledUnit;
45 
46 	if(actionNode->unit_recno) // a unit has started training previously
47 	{
48 		if( unit_array.is_deleted(actionNode->unit_recno) )
49 			return NULL; // but unit is lost already and action has failed
50 		skilledUnit = unit_array[actionNode->unit_recno];
51 	}
52 	else
53 	{
54 		char resultFlag;
55 		int  xLoc, yLoc;
56 
57 		//---- for harbor, we have to get the land region id. instead of the sea region id. ----//
58 
59 		if( actionNode->action_mode==ACTION_AI_BUILD_FIRM &&
60 			 actionNode->action_para==FIRM_HARBOR )
61 		{
62 			int rc=0;
63 
64 			for( yLoc=actionNode->action_y_loc ; yLoc<actionNode->action_y_loc+3 ; yLoc++ )
65 			{
66 				for( xLoc=actionNode->action_x_loc ; xLoc<actionNode->action_x_loc+3 ; xLoc++ )
67 				{
68 					if( region_array[ world.get_region_id(xLoc,yLoc) ]->region_type == REGION_LAND )
69 					{
70 						rc=1;
71 						break;
72 					}
73 				}
74 
75 				if( rc )
76 					break;
77 			}
78 		}
79 		else
80 		{
81 			xLoc = actionNode->action_x_loc;
82 			yLoc = actionNode->action_y_loc;
83 		}
84 
85 		//-----------------------------------------//
86 
87 		skilledUnit = find_skilled_unit(skillId, raceId, xLoc, yLoc, resultFlag, actionNode->action_id);
88 
89 		if( !skilledUnit )		// skilled unit not found
90 			return NULL;
91 	}
92 
93 	//------ if the unit is still in training -----//
94 
95 	if( !skilledUnit->is_visible() )
96 	{
97 		actionNode->next_retry_date = info.game_date + TOTAL_TRAIN_DAYS + 1;
98 		return NULL;		// continue processing this action after this date, this is used when training a unit for construction
99 	}
100 
101 	err_when( !skilledUnit->race_id );
102 
103 	return skilledUnit;
104 }
105 //-------- End of function Nation::get_skilled_unit -------//
106 
107 
108 //--------- Begin of function Nation::find_skilled_unit --------//
109 //
110 // <int>		skillId			-	the skill the selected unit should have
111 // <int>		raceId			-	the race the selected unit should have
112 //										(0 for any races)
113 // <short>	destX, destY	-	location the unit move to
114 // <char&>	resultFlag		-	describle how to find the skilled unit
115 //										0 - for unable to train unit,
116 //										1 - for existing skilled unit
117 //										2 - for unit hired from inn
118 //										3 - for training unit in town (training is required)
119 //
120 // [int]	   actionId - the action id. of the action which
121 //							  the unit should do after it has finished training.
122 //
123 // return the unit pointer pointed to the skilled unit
124 //
find_skilled_unit(int skillId,int raceId,short destX,short destY,char & resultFlag,int actionId)125 Unit* Nation::find_skilled_unit(int skillId, int raceId, short destX, short destY, char& resultFlag, int actionId)
126 {
127 	//----- try to find an existing unit with the required skill -----//
128 
129 	Unit	*skilledUnit = NULL;
130 	Unit	*unitPtr;
131 	Firm	*firmPtr;
132 	short	curDist, minDist=0x1000;
133 	int   destRegionId = world.get_region_id(destX, destY);
134 
135 	for(int i=unit_array.size(); i>0; i--)
136 	{
137 		if(unit_array.is_deleted(i))
138 			continue;
139 
140 		unitPtr = unit_array[i];
141 
142 		if( unitPtr->nation_recno!=nation_recno || !unitPtr->race_id )
143 			continue;
144 
145 		if( raceId && unitPtr->race_id != raceId )
146 			continue;
147 
148 		//---- if this unit is on a mission ----//
149 
150 		if( unitPtr->home_camp_firm_recno )
151 			continue;
152 
153 		if( unitPtr->region_id() != destRegionId )
154 			continue;
155 
156 		//----- if this is a mobile unit ------//
157 
158 		if( unitPtr->is_visible() )
159 		{
160 			if( !unitPtr->is_ai_all_stop() )
161 				continue;
162 
163 			if( unitPtr->skill.skill_id==skillId &&
164 				 unitPtr->cur_action!=SPRITE_ATTACK && !unitPtr->ai_action_id )
165 			{
166 				curDist = misc.points_distance(unitPtr->next_x_loc(), unitPtr->next_y_loc(), destX, destY);
167 
168 				if(curDist < minDist)
169 				{
170 					skilledUnit = unitPtr;
171 					minDist = curDist;
172 				}
173 			}
174 		}
175 
176 		//------- if this is an overseer ------//
177 
178 		else if( skillId==SKILL_LEADING && unitPtr->unit_mode==UNIT_MODE_OVERSEE )
179 		{
180 			firmPtr = firm_array[unitPtr->unit_mode_para];
181 
182 			if( firmPtr->region_id != destRegionId )
183 				continue;
184 
185 			if( firmPtr->firm_id == FIRM_CAMP )
186 			{
187 				//--- if this military camp is going to be closed, use this overseer ---//
188 
189 				if( firmPtr->should_close_flag )
190 				{
191 					firmPtr->mobilize_overseer();
192 					skilledUnit = unitPtr;       	// pick this overseer
193 					break;
194 				}
195 			}
196 		}
197 		else if( skillId==SKILL_CONSTRUCTION && unitPtr->unit_mode==UNIT_MODE_CONSTRUCT )		// the unit is a residental builder for repairing the firm
198 		{
199 			firmPtr = firm_array[unitPtr->unit_mode_para];
200 
201 			if( !firmPtr->under_construction )		// only if the unit is repairing instead of constructing the firm
202 			{
203 				if( firmPtr->set_builder(0) )			// return 1 if the builder is mobilized successfully, 0 if the builder was killed because of out of space on the map
204 				{
205 					skilledUnit = unitPtr;
206 					break;
207 				}
208 			}
209 		}
210 	}
211 
212 	//---------------------------------------------------//
213 
214 	if(skilledUnit)
215 	{
216 		resultFlag = 1;
217 	}
218 	else
219 	{
220 		//--- if no existing skilled unit found, try to hire one from inns ---//
221 
222 		int unitRecno = hire_unit(skillId, raceId, destX, destY);	// this function will try going with hiring units that are better than training your own ones
223 
224 		if( unitRecno )
225 		{
226 			skilledUnit = unit_array[unitRecno];
227 			resultFlag = 2;
228 		}
229 		else	//--- if still cannot get a skilled unit, train one ---//
230 		{
231 			int trainTownRecno;
232 
233 			if( train_unit(skillId, raceId, destX, destY, trainTownRecno, actionId) )
234 				resultFlag = 3;
235 			else
236 				resultFlag = 0;
237 		}
238 	}
239 
240 	err_when(skilledUnit && !skilledUnit->is_visible());
241 	err_when(skilledUnit && skilledUnit->rank_id==RANK_KING && (skillId!=SKILL_CONSTRUCTION && skillId!=SKILL_LEADING));
242 	err_when(skilledUnit && (skilledUnit->cur_action==SPRITE_DIE || skilledUnit->action_mode==ACTION_DIE));
243 
244 	return skilledUnit;
245 }
246 //---------- End of function Nation::find_skilled_unit --------//
247 
248 
249 //--------- Begin of function Nation::hire_unit --------//
250 //
251 // <int> importantRating - importance of hiring the unit.
252 //
ai_should_hire_unit(int importanceRating)253 int Nation::ai_should_hire_unit(int importanceRating)
254 {
255 	if( !ai_inn_count )		// don't hire any body in the cash is too low
256 		return 0;
257 
258 	return ai_should_spend(importanceRating + pref_hire_unit/5 - 10 );		// -10 to +10 depending on pref_hire_unit
259 }
260 //---------- End of function Nation::hire_unit --------//
261 
262 
263 //--------- Begin of function Nation::hire_unit --------//
264 //
265 // <int> 	skillId - the skill the unit should have
266 // <int>		raceId  - the race the selected unit should have
267 //							 (0 for any races)
268 //	<short>	destX	  - the x location the unit will move to
269 //	<short>	destY	  - the y location the unit will move to
270 //
271 // hire unit with specified skill from an inn
272 // return the unit pointer pointed to the skilled unit
273 //
274 // return: <int> recno of the unit recruited.
275 //
hire_unit(int skillId,int raceId,short destX,short destY)276 int Nation::hire_unit(int skillId, int raceId, short destX, short destY)
277 {
278 	if( !ai_should_hire_unit(20) )			// 20 - importance rating
279 		return 0;
280 
281 	//-------------------------------------------//
282 
283 	FirmInn	*firmInnPtr;
284 	Firm		*firmPtr;
285 	InnUnit *innUnit;
286 	Skill		*innUnitSkill;
287 	int		i, j, innUnitCount, curRating, curFirmDist;
288 	int		bestRating=0, bestInnRecno=0, bestInnUnitId=0;
289 	int		destRegionId = world.get_region_id(destX, destY);
290 
291 	for(i=0; i<ai_inn_count; i++)
292 	{
293 		firmPtr = firm_array[ai_inn_array[i]];
294 
295 		if( firmPtr->region_id != destRegionId )
296 			continue;
297 
298 		firmInnPtr = firmPtr->cast_to_FirmInn();
299 
300 		innUnitCount=firmInnPtr->inn_unit_count;
301 
302 		if( !innUnitCount )
303 			continue;
304 
305 		innUnit = firmInnPtr->inn_unit_array + innUnitCount - 1;
306 
307 		curFirmDist = misc.points_distance(firmPtr->center_x, firmPtr->center_y, destX, destY);
308 
309 		//------- check units in the inn ---------//
310 
311 		for(j=innUnitCount; j>0; j--, innUnit--)
312 		{
313 			innUnitSkill = &(innUnit->skill);
314 
315 			if( innUnitSkill->skill_id==skillId &&
316 				 (!raceId || unit_res[innUnit->unit_id]->race_id == raceId) &&
317 				 cash >= innUnit->hire_cost )
318 			{
319 				//----------------------------------------------//
320 				// evalute a unit on:
321 				// -its race, whether it's the same as the nation's race
322 				// -the inn's distance from the destination
323 				// -the skill level of the unit.
324 				//----------------------------------------------//
325 
326 				curRating = innUnitSkill->skill_level
327 								- (100-100*curFirmDist/MAX_WORLD_X_LOC);
328 
329 				if( unit_res[innUnit->unit_id]->race_id == race_id )
330 					curRating += 50;
331 
332 				if( curRating > bestRating )
333 				{
334 					bestRating = curRating;
335 
336 					bestInnRecno  = firmInnPtr->firm_recno;
337 					bestInnUnitId = j;
338 				}
339 			}
340 		}
341 	}
342 
343 	//----------------------------------------------------//
344 
345 	if( bestInnUnitId )
346 	{
347 		firmPtr = firm_array[bestInnRecno];
348 		firmInnPtr = firmPtr->cast_to_FirmInn();
349 
350 		return firmInnPtr->hire(bestInnUnitId);
351 	}
352 
353 	return 0;
354 }
355 //---------- End of function Nation::hire_unit --------//
356 
357 
358 //--------- Begin of function Nation::train_unit --------//
359 //
360 // <int> 	skillId - the skill the unit should have
361 // <int>		raceId  - the race the selected unit should have
362 //							 (0 for any races)
363 //	<short>	destX	  - the x location the unit will move to
364 //	<short>	destY	  - the y location the unit will move to
365 //
366 // <int&>   trainTownRecno - the recno of the town where this unit is trained.
367 //
368 // [int]	   actionId - the action id. of the action which
369 //							  the unit should do after it has finished training.
370 //
371 // return: <int> recno of the unit trained.
372 //
train_unit(int skillId,int raceId,short destX,short destY,int & trainTownRecno,int actionId)373 int Nation::train_unit(int skillId, int raceId, short destX, short destY, int& trainTownRecno, int actionId)
374 {
375 	//----- locate the best town for training the unit -----//
376 
377 	int 	 i;
378 	Town	 *townPtr;
379 	int	 curDist, curRating, bestRating=0;
380 	int    destRegionId = world.get_loc(destX, destY)->region_id;
381 
382 	trainTownRecno = 0;
383 
384 	for(i=0; i<ai_town_count; i++)
385 	{
386 		townPtr = town_array[ai_town_array[i]];
387 
388 		if( !townPtr->jobless_population || townPtr->train_unit_recno ||	// no jobless population or currently a unit is being trained
389 			 !townPtr->has_linked_own_camp )
390 		{
391 			continue;
392 		}
393 
394 		if( townPtr->region_id != destRegionId )
395 			continue;
396 
397 		//--------------------------------------//
398 
399 		curDist = misc.points_distance(townPtr->center_x, townPtr->center_y, destX, destY);
400 
401 		curRating = 100-100*curDist/MAX_WORLD_X_LOC;
402 
403 		if( curRating > bestRating )
404 		{
405 			bestRating 	  = curRating;
406 			trainTownRecno = townPtr->town_recno;
407 		}
408 	}
409 
410 	if( !trainTownRecno )
411 		return 0;
412 
413 	//---------- train the unit ------------//
414 
415 	townPtr = town_array[trainTownRecno];
416 
417 	if( !raceId )
418 		raceId = townPtr->pick_random_race(1, 1);		// 1-pick has job units also, 1-pick spy units
419 
420 	if( !raceId )
421 		return 0;
422 
423 	int unitRecno = townPtr->recruit(skillId, raceId, COMMAND_AI);
424 
425 	if( !unitRecno )
426 		return 0;
427 
428 	townPtr->train_unit_action_id = actionId;		// set train_unit_action_id so the unit can immediately execute the action when he has finished training.
429 	return unitRecno;
430 }
431 //---------- End of function Nation::train_unit --------//
432 
433 
434 //--------- Begin of function Nation::recruit_jobless_worker --------//
435 //
436 // <Firm*> destFirmPtr  - the firm which the workers are recruited for.
437 // [int] preferedRaceId - the prefered race id.
438 //
439 // return: <int> recno of the unit recruited.
440 //
recruit_jobless_worker(Firm * destFirmPtr,int preferedRaceId)441 int Nation::recruit_jobless_worker(Firm* destFirmPtr, int preferedRaceId)
442 {
443 	#define MIN_AI_TOWN_POP		8
444 
445 	int needSpecificRace, raceId;		// the race of the needed unit
446 
447 	if( preferedRaceId )
448 	{
449 		raceId = preferedRaceId;
450 		needSpecificRace = 1;
451 	}
452 	else
453 	{
454 		if( destFirmPtr->firm_id == FIRM_BASE )          // for seat of power, the race must be specific
455 		{
456 			raceId = firm_res.get_build(destFirmPtr->firm_build_id)->race_id;
457 			needSpecificRace = 1;
458 		}
459 		else
460 		{
461 			raceId = destFirmPtr->majority_race();
462 			needSpecificRace = 0;
463 		}
464 	}
465 
466 	if( !raceId )
467 		return 0;
468 
469 	//----- locate the best town for recruiting the unit -----//
470 
471 	Town	 *townPtr;
472 	int	 curDist, curRating, bestRating=0, bestTownRecno=0;
473 
474 	for( int i=0; i<ai_town_count; i++ )
475 	{
476 		townPtr = town_array[ai_town_array[i]];
477 
478 		err_when( townPtr->nation_recno != nation_recno );
479 
480 		if( !townPtr->jobless_population )	// no jobless population or currently a unit is being recruited
481 			continue;
482 
483 		if( !townPtr->should_ai_migrate() )		// if the town is going to migrate, disregard the minimum population consideration
484 		{
485 			if( townPtr->population < MIN_AI_TOWN_POP )		// don't recruit workers if the population is low
486 				continue;
487 		}
488 
489 		if( !townPtr->has_linked_own_camp && townPtr->has_linked_enemy_camp )		// cannot recruit from this town if there are enemy camps but no own camps
490 			continue;
491 
492 		if( townPtr->region_id != destFirmPtr->region_id )
493 			continue;
494 
495 		//--- get the distance beteween town & the destination firm ---//
496 
497 		curDist = misc.points_distance(townPtr->center_x, townPtr->center_y, destFirmPtr->center_x, destFirmPtr->center_y);
498 
499 		curRating = 100-100*curDist/MAX_WORLD_X_LOC;
500 
501 		//--- recruit units from non-base town first ------//
502 
503 		if( !townPtr->is_base_town )
504 			curRating += 100;
505 
506 		//---- if the town has the race that the firm needs most ----//
507 
508 		if( townPtr->can_recruit(raceId) )
509 		{
510 			curRating += 50 + (int) townPtr->race_loyalty_array[raceId-1];
511 		}
512 		else
513 		{
514 			if( needSpecificRace )			// if the firm must need this race, don't consider the town if it doesn't have the race.
515 				continue;
516 		}
517 
518 		if( curRating > bestRating )
519 		{
520 			bestRating 	  = curRating;
521 			bestTownRecno = townPtr->town_recno;
522 		}
523 	}
524 
525 	if( !bestTownRecno )
526 		return 0;
527 
528 	//---------- recruit the unit ------------//
529 
530 	townPtr = town_array[bestTownRecno];
531 
532 	if( townPtr->recruitable_race_pop(raceId, 1) == 0 )
533 	{
534 		err_when( needSpecificRace );
535 		raceId = townPtr->pick_random_race(0, 1);				// 0-pick jobless only, 1-pick spy units
536 
537 		if( !raceId )
538 			return 0;
539 	}
540 
541 	//--- if the chosen race is not recruitable, pick any recruitable race ---//
542 
543 	if( !townPtr->can_recruit(raceId) )
544 	{
545 		//---- if the loyalty is too low to recruit, grant the town people first ---//
546 
547 		if( cash > 0 && townPtr->accumulated_reward_penalty < 10 )
548 			townPtr->reward(COMMAND_AI);
549 
550 		//---- if the loyalty is still too low, return now ----//
551 
552 		if( !townPtr->can_recruit(raceId) )
553 			return 0;
554 	}
555 
556 	return townPtr->recruit(-1, raceId, COMMAND_AI);
557 }
558 //---------- End of function Nation::recruit_jobless_worker --------//
559 
560 
561 //--------- Begin of function Nation::recruit_on_job_worker --------//
562 //
563 // Get skilled workers from existing firms who have labor more than
564 // it really needs.
565 //
566 // <Firm*> destFirmPtr - the firm which the workers are recruited for.
567 // [int] preferedRaceId - the prefered race id.
568 //
569 // return: <int> recno of the unit recruited.
570 //
recruit_on_job_worker(Firm * destFirmPtr,int preferedRaceId)571 int Nation::recruit_on_job_worker(Firm* destFirmPtr, int preferedRaceId)
572 {
573 	err_when( !firm_res[destFirmPtr->firm_id]->need_worker );
574 
575 	err_when( destFirmPtr->firm_id == FIRM_BASE );	// seat of power shouldn't call this function at all, as it doesn't handle the racial issue.
576 
577 	if( !preferedRaceId )
578 	{
579 		preferedRaceId = destFirmPtr->majority_race();
580 
581 		if( !preferedRaceId )
582 			return 0;
583 	}
584 
585 	//--- scan existing firms to see if any of them have excess workers ---//
586 
587 	int    aiFirmCount;		// reference vars for returning result
588 	short* aiFirmArray = update_ai_firm_array(destFirmPtr->firm_id, 0, 0, aiFirmCount);
589 	Firm*   firmPtr, *bestFirmPtr=NULL;
590 	int	  bestRating=0, curRating, curDistance;
591 	int	  hasHuman;
592 	Worker* workerPtr;
593 
594 	int i;
595 	for( i=0 ; i<aiFirmCount ; i++ )
596 	{
597 		firmPtr = firm_array[aiFirmArray[i]];
598 
599 		err_when( firmPtr->firm_id != destFirmPtr->firm_id );
600 
601 		if( firmPtr->firm_recno == destFirmPtr->firm_recno )
602 			continue;
603 
604 		if( firmPtr->region_id != destFirmPtr->region_id )
605 			continue;
606 
607 		if( !firmPtr->ai_has_excess_worker() )
608 			continue;
609 
610 		//-----------------------------------//
611 
612 		curDistance = misc.points_distance( firmPtr->center_x, firmPtr->center_y,
613 						  destFirmPtr->center_x, destFirmPtr->center_y );
614 
615 		curRating = 100 - 100 * curDistance / MAX_WORLD_X_LOC;
616 
617 		workerPtr = firmPtr->worker_array;
618 		hasHuman  = 0;
619 
620 		for( int j=0 ; j<firmPtr->worker_count ; j++, workerPtr++ )
621 		{
622 			if( workerPtr->race_id )
623 				hasHuman = 1;
624 
625 			if( workerPtr->race_id == preferedRaceId )
626 			{
627 				//---- can't recruit this unit if he lives in a foreign town ----//
628 
629 				if( workerPtr->town_recno &&
630 					 town_array[workerPtr->town_recno]->nation_recno != nation_recno )
631 				{
632 					continue;
633 				}
634 
635 				//--------------------------//
636 
637 				curRating += 100;
638 				break;
639 			}
640 		}
641 
642 		if( hasHuman && curRating > bestRating )
643 		{
644 			bestRating = curRating;
645 			bestFirmPtr = firmPtr;
646 		}
647 	}
648 
649 	if( !bestFirmPtr )
650 		return 0;
651 
652 	//------ mobilize a worker form the selected firm ----//
653 
654 	int workerId=0;
655 
656 	workerPtr = bestFirmPtr->worker_array;
657 
658 	for( i=1 ; i<=bestFirmPtr->worker_count ; i++, workerPtr++ )
659 	{
660 		//---- can't recruit this unit if he lives in a foreign town ----//
661 
662 		if( workerPtr->town_recno &&
663 			 town_array[workerPtr->town_recno]->nation_recno != nation_recno )
664 		{
665 			continue;
666 		}
667 
668 		//--------------------------------//
669 
670 		if( workerPtr->race_id )		// if this is a human unit, take it first
671 			workerId = i;
672 
673 		if( workerPtr->race_id == preferedRaceId )  // if we have a better one, take the better one
674 		{
675 			workerId = i;
676 			break;
677 		}
678 	}
679 
680 	if( !workerId )		// this can happen if all the workers are foreign workers.
681 		return 0;
682 
683 	return bestFirmPtr->mobilize_worker( workerId, COMMAND_AI );
684 }
685 //---------- End of function Nation::recruit_on_job_worker --------//
686 
687