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