1 /* Unit plan handling for Xconq.
2 Copyright (C) 1991-2000 Stanley T. Shebs.
3 Copyright (C) 2005 Eric A. McDonald.
4
5 Xconq is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2, or (at your option)
8 any later version. See the file COPYING. */
9
10 #include "conq.h"
11 #include "kernel.h"
12 #include "aiutil.h"
13 #include "ai.h"
14 #include "aiscore.h"
15 #include "aiunit.h"
16 #include "aiunit2.h"
17 #include "aitact.h"
18 #include "aioprt.h"
19
20 using namespace Xconq;
21 using namespace Xconq::AI;
22
23 static void plan_passive(Unit *unit);
24 static void plan_offense(Unit *unit);
25 static void plan_defense(Unit *unit);
26 static void plan_exploration(Unit *unit);
27 static void plan_improve(Unit *unit);
28 static void wake_at(int x, int y);
29 static int resupply_if_low(Unit *unit);
30 static int rearm_if_low(Unit *unit);
31 static int plan_resupply(Unit *unit, int m);
32 static int repair_if_damaged(Unit *unit);
33 static int plan_repair(Unit *unit);
34 static int alternate_target_here(int x, int y);
35 static int do_for_occupants(Unit *unit);
36 static int ai_damage_ratio_vs_type(Unit *unit, int u2);
37 static int ai_score_potential_victim_occupants(
38 UnitView *uview, OccStatus occstatus, int victimflags, int dmgtypes);
39 static int ai_score_potential_victim(
40 UnitView *uview, OccStatus occstatus, int victimflags);
41 static int ai_type_choose_best_hit_method(int u, int u2);
42 static int ai_consider_capturing(
43 Unit *unit, UnitView *uview, int dmgthresh, int qthresh);
44 static int ai_consider_shaking(
45 Unit *unit, UnitView *uview, OccStatus occstatus);
46 extern int ai_go_after_victim(Unit *unit, int range, int broadcast);
47 static int fire_at_opportunity(Unit *unit);
48 static int explore_reachable_cell(Unit *unit, int range);
49 static int capture_useful_if_nearby(Unit *unit);
50 static int capture_indep_if_nearby(Unit *unit);
51 static void random_walk(Unit *unit);
52 static int worth_capturing(Side *side, int u2, Side *oside, int x, int y);
53 static int indep_captureable_here(int x, int y);
54 static int useful_type(Side *side, int u);
55 /* static int could_capture_any(int u); */
56 static Plan *create_plan(void);
57 static int might_be_captured(Unit *unit);
58 static int occupant_could_capture(Unit *unit, int etype);
59 static int can_capture_neighbor(Unit *unit);
60 static int occupant_can_capture_neighbor(Unit *unit);
61 static int find_closest_unit(Side *side, int x0, int y0, int maxdist,
62 int (*pred)(int x, int y), int *rxp, int *ryp);
63 static int reachable_unknown(int x, int y);
64 static int adj_known_ok_terrain(int x, int y, Side *side, int u);
65 static int normal_completion_time(int u, int u2);
66 static void maybe_set_materials_goal(Unit *unit, int u2);
67
68 #if 0
69 static int go_after_captive(Unit *unit, int range);
70 static int range_left(Unit *unit);
71 static int find_worths(int range);
72 static int attack_worth(Unit *unit, int e);
73 static int threat(Side *side, int u, int x0, int y0);
74 static int move_patrol(Unit *unit);
75 static int build_time(Unit *unit, int prod);
76 static int out_of_ammo(Unit *unit);
77 static int explorable_cell(int x, int y);
78 static int should_capture_maker(Unit *unit);
79 extern int adj_unit(int x, int y);
80 #endif
81
82 /* (should have a generic struct for all plan type attrs) */
83
84 char *plantypenames[] = {
85
86 #undef DEF_PLAN
87 #define DEF_PLAN(NAME,code) NAME,
88
89 #include "plan.def"
90
91 NULL
92 };
93
94 /* Every unit that can act needs a plan object, types that can't act
95 should have it cleared out. Note that incomplete units are expected
96 to be able to act in the future, so it's acceptable to run this for
97 incomplete units to give them a plan. */
98
99 void
init_unit_plan(Unit * unit)100 init_unit_plan(Unit *unit)
101 {
102 if (can_be_actor(unit)) {
103 /* Might already have a plan, so don't always realloc. */
104 if (unit->plan == NULL) {
105 unit->plan = create_plan();
106 }
107 /* Put the plan into a default state, side will work it up later. */
108 /* (should release goals also) */
109 clear_task_agenda(unit);
110 /* Zero the plan just as xmalloc would. */
111 memset(unit->plan, 0, sizeof(Plan));
112 unit->plan->type = PLAN_PASSIVE;
113 unit->plan->creation_turn = g_turn();
114 /* Allow AIs to make this unit do things. */
115 unit->plan->aicontrol = TRUE;
116 /* Enable supply alarms by default. */
117 unit->plan->supply_alarm = TRUE;
118 /* Clear the task outcome. */
119 unit->plan->last_task_outcome = TASK_UNKNOWN;
120 } else {
121 /* Brainless units don't need anything, can free up plan. */
122 if (unit->plan != NULL) {
123 free_plan(unit);
124 }
125 unit->plan = NULL;
126 }
127 }
128
129 void
set_unit_plan_type(Side * side,Unit * unit,int type)130 set_unit_plan_type(Side *side, Unit *unit, int type)
131 {
132 int oldtype;
133
134 if (unit->plan) {
135 oldtype = unit->plan->type;
136 if (type != oldtype) {
137 if (type == PLAN_NONE) {
138 type = PLAN_PASSIVE;
139 DMprintf( "Forced replan: %s lacks plan.\n", unit_desig(unit));
140 force_replan(unit);
141 }
142 unit->plan->type = (PlanType)type;
143 clear_task_agenda(unit);
144 if (side != NULL)
145 update_unit_display(side, unit, TRUE);
146 }
147 }
148 }
149
150 void
set_unit_asleep(Side * side,Unit * unit,int flag,int recurse)151 set_unit_asleep(Side *side, Unit *unit, int flag, int recurse)
152 {
153 int oldflag;
154 Unit *occ;
155
156 if (unit->plan) {
157 oldflag = unit->plan->asleep;
158 if (flag != oldflag) {
159 unit->plan->asleep = flag;
160 if (side != NULL)
161 update_unit_display(side, unit, TRUE);
162 }
163 }
164 if (recurse) {
165 for_all_occupants(unit, occ) {
166 set_unit_asleep(side, occ, flag, recurse);
167 }
168 }
169 }
170
171 void
set_unit_reserve(Side * side,Unit * unit,int flag,int recurse)172 set_unit_reserve(Side *side, Unit *unit, int flag, int recurse)
173 {
174 int oldflag;
175 Unit *occ;
176
177 if (unit->plan) {
178 oldflag = unit->plan->reserve;
179 if (flag != oldflag) {
180 unit->plan->reserve = flag;
181 if (side != NULL)
182 update_unit_display(side, unit, TRUE);
183 }
184 }
185 if (recurse) {
186 for_all_occupants(unit, occ) {
187 set_unit_reserve(side, occ, flag, recurse);
188 }
189 }
190 }
191
192 void
set_unit_ai_control(Side * side,Unit * unit,int flag,int recurse)193 set_unit_ai_control(Side *side, Unit *unit, int flag, int recurse)
194 {
195 int oldflag;
196 Unit *occ;
197
198 if (unit->plan) {
199 oldflag = unit->plan->aicontrol;
200 if (flag != oldflag) {
201 unit->plan->aicontrol = flag;
202 if (side != NULL)
203 update_unit_display(side, unit, TRUE);
204 }
205 }
206 if (recurse) {
207 for_all_occupants(unit, occ) {
208 set_unit_ai_control(side, occ, flag, recurse);
209 }
210 }
211 }
212
213 void
set_unit_curadvance(Side * side,Unit * unit,int a)214 set_unit_curadvance(Side *side, Unit *unit, int a)
215 {
216 unit->curadvance = a;
217 }
218
219 void
set_unit_researchdone(Side * side,Unit * unit,int flag)220 set_unit_researchdone(Side *side, Unit *unit, int flag)
221 {
222 unit->researchdone = flag;
223 }
224
225 void
set_unit_main_goal(Side * side,Unit * unit,Goal * goal)226 set_unit_main_goal(Side *side, Unit *unit, Goal *goal)
227 {
228 if (unit->plan) {
229 unit->plan->maingoal = goal;
230 }
231 }
232
233 void
set_unit_waiting_for_transport(Side * side,Unit * unit,int flag)234 set_unit_waiting_for_transport(Side *side, Unit *unit, int flag)
235 {
236 if (unit->plan) {
237 unit->plan->waitingfortransport = flag;
238 }
239 }
240
241 /* Execute the plan. */
242
243 int
execute_plan(Unit * unit)244 execute_plan(Unit *unit)
245 {
246 Plan *plan = unit->plan;
247
248 if (!in_play(unit) || !completed(unit)) {
249 DMprintf("%s shouldn't be planning yet\n", unit_desig(unit));
250 return 0;
251 }
252 DMprintf("%s using plan %s\n", unit_desig(unit), plan_desig(plan));
253 /* Units that are asleep or in reserve do nothing. */
254 /* (This never happens according to debugging). */
255 if (plan->asleep || plan->reserve) {
256 return 0;
257 }
258 if (plan->type == PLAN_PASSIVE && plan->execs_this_turn > 10) {
259 DMprintf(" not found\n");
260 }
261 if (plan->execs_this_turn > 100) {
262 DMprintf("%s executed plan 100 times this turn, going into reserve\n",
263 unit_desig(unit));
264 plan->reserve = TRUE;
265 return 1;
266 }
267 /* Unit actually has a plan, dispatch on its type. */
268 switch (plan->type) {
269 case PLAN_NONE:
270 /* Unit has not gotten a plan yet, leave it alone. */
271 break;
272 case PLAN_PASSIVE:
273 plan_passive(unit);
274 break;
275 case PLAN_OFFENSIVE:
276 plan_offense(unit);
277 break;
278 case PLAN_DEFENSIVE:
279 plan_defense(unit);
280 break;
281 case PLAN_EXPLORATORY:
282 plan_exploration(unit);
283 break;
284 case PLAN_IMPROVING:
285 plan_improve(unit);
286 break;
287 default:
288 case_panic("plan type", plan->type);
289 break;
290 }
291 ++(plan->execs_this_turn);
292 run_ui_idler();
293 return 1;
294 }
295
296 /* Check if the unit is in formation or not. */
297
298 int
is_in_formation(Unit * unit)299 is_in_formation(Unit *unit)
300 {
301 int nx, ny, dist;
302 Plan *plan = unit->plan;
303 Goal *goal;
304 Unit *leader;
305
306 goal = plan->formation;
307 leader = plan->funit;
308 if (!leader)
309 return FALSE;
310 if (!in_play(leader)
311 || !unit_trusts_unit(unit, leader)
312 || goal->args[0] != leader->id)
313 return FALSE;
314 nx = leader->x + goal->args[1]; ny = leader->y + goal->args[2];
315 dist = goal->args[3];
316 if (distance(unit->x, unit->y, nx, ny) > dist)
317 return FALSE;
318 return TRUE;
319 }
320
321 /* See if we're too far away from an assigned position, set a task
322 to move back if so. */
323
324 int
move_into_formation(Unit * unit)325 move_into_formation(Unit *unit)
326 {
327 int nx, ny, dist;
328 Plan *plan = unit->plan;
329 Goal *goal;
330 Unit *leader;
331
332 leader = plan->funit;
333 if (leader != NULL) {
334 goal = plan->formation;
335 /* Ensure that the leader is still someone we want to follow. */
336 if (!in_play(leader)
337 || !unit_trusts_unit(unit, leader)
338 || goal->args[0] != leader->id) {
339 notify(unit->side, "%s leader is gone, cancelling formation",
340 unit_handle(unit->side, unit));
341 free(goal);
342 plan->formation = NULL;
343 plan->funit = NULL;
344 /* Unit is available to do something else. */
345 return FALSE;
346 }
347 nx = leader->x + goal->args[1]; ny = leader->y + goal->args[2];
348 dist = goal->args[3];
349 if (distance(unit->x, unit->y, nx, ny) > dist) {
350 /* (should perhaps insert after current task?) */
351 set_move_to_task(unit, nx, ny, dist);
352 return TRUE;
353 }
354 }
355 return FALSE;
356 }
357
358 int task_is_in_agenda(Plan *plan, Task *task);
359
360 /* See if there are any standing orders that currently apply to the given unit,
361 and schedule a task if so. Return TRUE if a task was added. */
362
363 int
execute_standing_order(Unit * unit,int addtask)364 execute_standing_order(Unit *unit, int addtask)
365 {
366 Unit *transport;
367 Side *side = unit->side;
368 StandingOrder *sorder;
369
370 for (sorder = side->orders; sorder != NULL; sorder = sorder->next) {
371 if (sorder->types[unit->type] && unit->plan) {
372 switch (sorder->condtype) {
373 case sorder_at:
374 if (unit->x == sorder->a1 && unit->y == sorder->a2) {
375 /* If the task is already in the plan, don't do
376 anything. */
377 if (task_is_in_agenda(unit->plan, sorder->task))
378 return FALSE;
379 if (addtask)
380 add_task(unit, 0, clone_task(sorder->task));
381 return TRUE;
382 }
383 break;
384 case sorder_in:
385 transport = unit->transport;
386 if (transport != NULL && transport->id == sorder->a1) {
387 /* If the task is already in the plan, don't do
388 anything. */
389 if (task_is_in_agenda(unit->plan, sorder->task))
390 return FALSE;
391 if (addtask)
392 add_task(unit, 0, clone_task(sorder->task));
393 return TRUE;
394 }
395 break;
396 case sorder_near:
397 if (distance(unit->x, unit->y, sorder->a1, sorder->a2) <= sorder->a3) {
398 /* If the task is already in the plan, don't do
399 anything. */
400 if (task_is_in_agenda(unit->plan, sorder->task))
401 return FALSE;
402 if (addtask)
403 add_task(unit, 0, clone_task(sorder->task));
404 return TRUE;
405 }
406 break;
407 default:
408 run_warning("Unknown order condition type");
409 break;
410 }
411 }
412 }
413 return FALSE;
414 }
415
416 int tasks_match(Task *task1, Task *task2);
417
418 int
task_is_in_agenda(Plan * plan,Task * task)419 task_is_in_agenda(Plan *plan, Task *task)
420 {
421 Task *task2;
422
423 for (task2 = plan->tasks; task2 != NULL; task2 = task2->next) {
424 if (tasks_match(task, task2))
425 return TRUE;
426 }
427 return FALSE;
428 }
429
430 int
tasks_match(Task * task1,Task * task2)431 tasks_match(Task *task1, Task *task2)
432 {
433 int i;
434
435 if (task1->type != task2->type)
436 return FALSE;
437 for (i = 0; i < MAXTASKARGS; ++i)
438 if (task1->args[i] != task2->args[i])
439 return FALSE;
440 return TRUE;
441 }
442
443 /* Passive units just work from the task queue or else wait to be told
444 what to do. */
445
446 static void
plan_passive(Unit * unit)447 plan_passive(Unit *unit)
448 {
449 Plan *plan = unit->plan;
450
451 /* Handle AI-controlled passive plans. */
452 if (ai_controlled(unit)) {
453 if (resupply_if_low(unit)) {
454 return;
455 }
456 if (rearm_if_low(unit)) {
457 return;
458 }
459 if (repair_if_damaged(unit)) {
460 return;
461 }
462 /* Execute any old tasks associated with this plan. Return. */
463 if (unit->plan->tasks) {
464 execute_task(unit);
465 return;
466 }
467 /* Else mention that we are replanning. */
468 else {
469 DMprintf("Forced replan: %s is ai-controlled & passive.\n",
470 unit_desig(unit));
471 force_replan(unit);
472 return;
473 }
474 }
475 /* Special-case human cities/towns in the intro game to automatically
476 start producing infantry initially. */
477 /* (would be more efficient to put in a once-per-turn location,
478 should look for one) */
479 if (g_turn() <= 1
480 && mainmodule != NULL
481 && ((mainmodule->name != NULL
482 && strcmp(mainmodule->name, INTRO_GAME) == 0)
483 || (mainmodule->origmodulename != NULL
484 && strcmp(mainmodule->origmodulename, INTRO_GAME) == 0))
485 && ((strcmp(u_type_name(unit->type), "city") == 0)
486 || (strcmp(u_type_name(unit->type), "town") == 0))) {
487 push_construct_task(unit, 0, 99, unit->id, -1, -1);
488 }
489 if (plan->supply_is_low && plan->supply_alarm) {
490 plan->supply_alarm = FALSE;
491 if (0 /* auto resupply */) {
492 set_resupply_task(unit, NONMTYPE);
493 } else if (plan->tasks
494 && (plan->tasks->type == TASK_RESUPPLY
495 || (plan->tasks->type == TASK_MOVE_TO
496 && plan->tasks->next
497 && plan->tasks->next->type == TASK_RESUPPLY))) {
498 /* do nothing */
499 } else {
500 clear_task_agenda(unit);
501 set_waiting_for_tasks(unit, TRUE);
502 }
503 }
504 if (plan->tasks) {
505 /* (should check that doctrine being followed correctly) */
506 execute_task(unit);
507 } else if (unit->side
508 && unit->side->orders
509 && execute_standing_order(unit, TRUE)) {
510 execute_task(unit);
511 } else if (plan->formation && move_into_formation(unit)) {
512 execute_task(unit);
513 } else {
514 /* Our goal is now to get guidance from the side. */
515 set_waiting_for_tasks(unit, TRUE);
516 }
517 }
518
519 /* A unit operating offensively advances and attacks when possible. */
520
521 int find_alternate_hit_target(Unit *unit, Task *task, int *xp, int *yp);
522
523 static void
plan_offense(Unit * unit)524 plan_offense(Unit *unit)
525 {
526 int u = unit->type;
527 int x, y, w, h, range, x1, y1, nx, ny;
528 Plan *plan = unit->plan;
529 Task *lasttask;
530 Unit *unit2;
531
532 if (resupply_if_low(unit)) {
533 return;
534 }
535 if (rearm_if_low(unit)) {
536 return;
537 }
538 if (repair_if_damaged(unit)) {
539 return;
540 }
541 /* Run any 'hit-unit' tasks. */
542 if (plan->tasks) {
543 execute_task(unit);
544 if (plan->last_task_outcome == TASK_FAILED) {
545 lasttask = &(plan->last_task);
546 if (lasttask->type == TASK_HIT_UNIT
547 && lasttask->args[2] != NONUTYPE
548 && !target_visible(unit, lasttask)) {
549 /* Target seems to have disappeared, look around for it. */
550 DMprintf("%s hit target has disappeared, looking for it; ",
551 unit_desig(unit));
552 if (find_alternate_hit_target(unit, lasttask, &nx, &ny)) {
553 if (plan->tasks
554 && plan->tasks->type == lasttask->type
555 && plan->tasks->args[0] == lasttask->args[0]
556 && plan->tasks->args[1] == lasttask->args[1]
557 && plan->tasks->args[2] == lasttask->args[2]
558 && plan->tasks->args[3] == lasttask->args[3]
559 ) {
560 pop_task(plan);
561 }
562 push_hit_unit_task(unit, nx, ny,
563 lasttask->args[2], lasttask->args[3]);
564 DMprintf(" found at %d,%d\n", nx, ny);
565 } else {
566 DMprintf(" not found\n");
567 }
568 }
569 }
570 /* Irrespective of what happened,
571 we don't want to step on the task yet. */
572 return;
573 }
574 /* Follow through with other details of plan. */
575 if (plan->maingoal && mobile(u)) {
576 switch (plan->maingoal->type) {
577 case GOAL_UNIT_OCCUPIED:
578 /* Move to occupy our goal if necessary. */
579 unit2 = find_unit(plan->maingoal->args[0]);
580 range = u_ai_tactical_range(unit->type);
581 if (ai_go_after_victim(unit, range, FALSE));
582 else if (in_play(unit2) && unit->transport != unit2)
583 set_occupy_task(unit, unit2);
584 else {
585 free(plan->maingoal);
586 plan->maingoal = NULL;
587 }
588 break;
589 case GOAL_CELL_OCCUPIED:
590 /* Move to occupy our goal if necessary. */
591 x = plan->maingoal->args[0];
592 y = plan->maingoal->args[1];
593 range = u_ai_tactical_range(unit->type);
594 if (ai_go_after_victim(unit, range, FALSE));
595 else if (unit->x != x || unit->y != y)
596 set_move_to_task(unit, x, y, 0);
597 else {
598 free(plan->maingoal);
599 plan->maingoal = NULL;
600 }
601 break;
602 case GOAL_VICINITY_HELD:
603 x = plan->maingoal->args[0]; y = plan->maingoal->args[1];
604 w = plan->maingoal->args[2]; h = plan->maingoal->args[3];
605 if (distance(x, y, unit->x, unit->y) > max(w, h)) {
606 /* Outside the goal area - move in towards it. */
607 if (random_point_near(x, y, w / 2, &x1, &y1)) {
608 x = x1; y = y1;
609 }
610 DMprintf("%s to go on offensive to %d,%d\n",
611 unit_desig(unit), x, y);
612 set_move_to_task(unit, x, y, max(w, h) / 2);
613 #if (0)
614 if (unit->transport
615 && mobile(unit->transport->type)
616 && unit->transport->plan) {
617 set_move_to_task(unit->transport, x, y, max(w, h) / 2);
618 }
619 #endif
620 } else {
621 range = max(w, h);
622 range = min(u_ai_tactical_range(unit->type), range);
623 #if (0)
624 range = min(real_operating_range_best(unit), range);
625 /* No special goal, look for something to fight with. */
626 /* Sometimes be willing to look a little farther out. */
627 if (probability(50))
628 range *= 2;
629 #endif
630 /* Try to let occs decide for us. */
631 if (mobile(u) && do_for_occupants(unit)) {
632 /* Found a victim to go after, fall through. */
633 } else if (ai_go_after_victim(unit, range, FALSE)) {
634 /*! \todo Should consider transferring to another theater. */
635 } else {
636 /* Do a random walk instead of just sitting there. */
637 DMprintf("%s to walk randomly\n", unit_desig(unit));
638 random_walk(unit);
639 }
640 }
641 break;
642 default:
643 DMprintf("offensive unit has some goal\n");
644 break;
645 }
646 } else if (mobile(u)) {
647 /* Play it safe. Search every cell within the tactical range.
648 But don't search the whole world! */
649 range = u_ai_tactical_range(u);
650 #if (0)
651 if (probability(50))
652 range = min(range, 2 * type_max_acp(u));
653 #endif
654 if (do_for_occupants(unit));
655 /* No special goal, but found something to fight with. */
656 else if (ai_go_after_victim(unit, range, FALSE));
657 /*! \todo Should consider transferring to another theater. */
658 /* Else go into reserve. */
659 else {
660 if (mobile(unit->type) && probability(50)
661 && ((0 < u_defensive_worth(unit->type))
662 || (0 < u_ai_explorer_worth(unit->type))))
663 force_replan(unit);
664 else
665 set_unit_reserve(unit->side, unit, TRUE, FALSE);
666 return;
667 }
668 /* should go to a "best location" if possible. */
669 /* (should do a sentry task) */
670 } else if (ai_go_after_victim(unit, u_ai_tactical_range(u), FALSE));
671 if (plan->tasks)
672 execute_task(unit);
673 else {
674 /* If we cannot find anything sensible to do, we force a replan.
675 This will force the AI to consider if this unit should be moved
676 to another theater of operations. */
677 if (probability(20)) {
678 force_replan(unit);
679 DMprintf("%s found nothing to do offensively, replanning.\n",
680 unit_desig(unit));
681 }
682 else
683 set_unit_reserve(unit->side, unit, TRUE, FALSE);
684 }
685 }
686
687 /* Look through list of occupants to see if an occupant needs the
688 transport to do something. */
689
690 int
do_for_occupants(Unit * unit)691 do_for_occupants(Unit *unit)
692 {
693 Unit *occ = NULL;
694 Goal *goal = NULL;
695 Task *task = NULL;
696
697 /* If transport is not AI-controlled, then do not muck with it. */
698 if (!ai_controlled(unit))
699 return FALSE;
700 /* If transport is immobile, then don't try moving for occs. */
701 if (!mobile(unit->type))
702 return FALSE;
703 /* Do whatever the occs are asking us to do. */
704 for_all_occupants(unit, occ) {
705 if (occ->plan) {
706 /* Get the occ towards its goal, if it has one. */
707 goal = occ->plan->maingoal;
708 if (goal != NULL
709 && goal->type == GOAL_VICINITY_HELD
710 && (distance(goal->args[0], goal->args[1], unit->x, unit->y)
711 > goal->args[2])) {
712 set_move_to_task(unit, goal->args[0], goal->args[1],
713 max(goal->args[2] / 2, 1));
714 DMprintf("%s will go where occupant %s wants to go (goal %s)\n",
715 unit_desig(unit), unit_desig(occ), goal_desig(goal));
716 return TRUE;
717 }
718 /* If the occ does not have a goal, see if it has a task. */
719 for_all_tasks(occ->plan, task) {
720 if ((task->type == TASK_MOVE_TO
721 || task->type == TASK_HIT_UNIT)
722 && (task->args[0] != unit->x
723 || task->args[1] != unit->y)
724 && distance(task->args[0], task->args[1],
725 unit->x, unit->y) > 1) {
726 /* Note that we assume the transport is mobile,
727 which is OK currently because of where this
728 routine is called from. */
729 set_move_to_task(unit, task->args[0], task->args[1], 1);
730 DMprintf(
731 "%s will go where occupant %s wants to go (task %s)\n",
732 unit_desig(unit), unit_desig(occ),
733 task_desig(task));
734 return TRUE;
735 }
736 }
737 }
738 }
739 return FALSE;
740 }
741
742 int
find_alternate_hit_target(Unit * unit,Task * task,int * xp,int * yp)743 find_alternate_hit_target(Unit *unit, Task *task, int *xp, int *yp)
744 {
745 int range;
746
747 tmpunit = unit;
748 tmputype = task->args[2];
749 tmpside = side_n(task->args[3]);
750 /* (should adjust search radius for speed?) */
751 range = type_max_acp(tmputype) + 1;
752 return search_around(task->args[0], task->args[1], range,
753 alternate_target_here, xp, yp, 1);
754 }
755
756 static int
alternate_target_here(int x,int y)757 alternate_target_here(int x, int y)
758 {
759 UnitView *uview;
760
761 for_all_view_stack_with_occs(tmpunit->side, x, y, uview) {
762 if (uview->type == tmputype
763 && uview->siden == tmpside->id)
764 return TRUE;
765 }
766 return FALSE;
767 }
768
769 /* Defensive units don't go out looking for trouble, but they should
770 react strongly to threats. */
771
772 static void
plan_defense(Unit * unit)773 plan_defense(Unit *unit)
774 {
775 int u = unit->type, range, x, y, w, h, x1, y1;
776 Plan *plan = unit->plan;
777 Unit *unit2;
778
779 if (resupply_if_low(unit)) {
780 return;
781 }
782 if (rearm_if_low(unit)) {
783 return;
784 }
785 if (repair_if_damaged(unit)) {
786 return;
787 }
788 if (plan->tasks) {
789 /* (should analyze and maybe decide to change task) */
790 execute_task(unit);
791 return;
792 }
793 /* Listen to what our occs are telling us. */
794 if (mobile(unit->type) && do_for_occupants(unit)) {
795 if (plan->tasks) {
796 execute_task(unit);
797 return;
798 }
799 }
800 /* Proceed with normal planning. */
801 if (plan->maingoal) {
802 switch (plan->maingoal->type) {
803 case GOAL_UNIT_OCCUPIED:
804 /* Move to occupy our goal if necessary. */
805 unit2 = find_unit(plan->maingoal->args[0]);
806 if (in_play(unit2) && unit->transport != unit2)
807 set_occupy_task(unit, unit2);
808 else if ((unit->transport == unit2)
809 && ai_go_after_victim(unit, 1, FALSE));
810 else {
811 free(plan->maingoal);
812 plan->maingoal = NULL;
813 }
814 break;
815 case GOAL_CELL_OCCUPIED:
816 /* Move to occupy our goal if necessary. */
817 x = plan->maingoal->args[0]; y = plan->maingoal->args[1];
818 if (unit->x != x || unit->y != y)
819 set_move_to_task(unit, x, y, 0);
820 else if ((unit->x == x) && (unit->y == y)
821 && ai_go_after_victim(unit,
822 u_ai_tactical_range(unit->type),
823 FALSE));
824 else {
825 free(plan->maingoal);
826 plan->maingoal = NULL;
827 }
828 break;
829 case GOAL_VICINITY_HELD:
830 x = plan->maingoal->args[0]; y = plan->maingoal->args[1];
831 w = plan->maingoal->args[2]; h = plan->maingoal->args[3];
832 if (distance(x, y, unit->x, unit->y) > max(w, h)) {
833 /* Outside the goal area - move in towards it. */
834 if (random_point_near(x, y, w / 2, &x1, &y1)) {
835 x = x1; y = y1;
836 }
837 DMprintf("%s to go on defensive to %d,%d\n",
838 unit_desig(unit), x, y);
839 set_move_to_task(unit, x, y, max(w, h) / 2);
840 if (unit->transport
841 && mobile(unit->transport->type)
842 && unit->transport->plan) {
843 set_move_to_task(unit->transport, x, y, max(w, h) / 2);
844 }
845 } else {
846 range = max(w, h);
847 range = min(u_ai_tactical_range(unit->type), range);
848 /* No special goal, look for something to fight with. */
849 /* Sometimes be willing to look a little farther out. */
850 if (probability(50))
851 range *= 2;
852 /* Occupants have decided for us, fall through. */
853 if (do_for_occupants(unit));
854 /* Found a victim to go after, fall through. */
855 else if (ai_go_after_victim(unit, range, FALSE));
856 /* Else go into reserve. */
857 else
858 set_unit_reserve(unit->side, unit, TRUE, FALSE);
859 /*! \todo Should consider transferring to another theater. */
860 }
861 break;
862 default:
863 DMprintf("defensive unit has some goal\n");
864 break;
865 }
866 /* (might be able to defend by interposing self?) */
867 return;
868 }
869 if (can_attack_any(unit, unit) || can_fire_at_any(unit, unit)
870 || could_capture_any(unit->type)) {
871 /* Use the tactical range. */
872 if (ai_go_after_victim(unit, u_ai_tactical_range(unit->type), FALSE)) {
873 execute_task(unit);
874 return;
875 }
876 /* Nobody close by, just hang out, shifting around a bit
877 occasionally. */
878 if (mobile(unit->type) && probability(10)) {
879 if (random_point_near(unit->x, unit->y, type_max_acp(u),
880 &x1, &y1)) {
881 DMprintf("%s to shift defensive position to %d,%d\n",
882 unit_desig(unit), x1, y1);
883 set_move_to_task(unit, x1, y1, 0);
884 execute_task(unit);
885 return;
886 }
887 }
888 /* Else go into reserve or replan. */
889 else {
890 if (mobile(unit->type) && probability(50)
891 && ((0 < u_offensive_worth(unit->type))
892 || (0 < u_siege_worth(unit->type))
893 || (0 < u_ai_explorer_worth(unit->type))))
894 force_replan(unit);
895 else
896 set_unit_reserve(unit->side, unit, TRUE, FALSE);
897 return;
898 }
899 } else {
900 using namespace Xconq::AI;
901 acquire_oprole(unit->side, unit->id, OR_CONSTRUCTOR);
902 }
903 if (plan->tasks) {
904 execute_task(unit);
905 } else {
906 /* If we cannot find anything sensible to do, we force a replan.
907 This will force the AI to consider
908 if this unit should be moved to another theater of
909 operations. */
910 if (probability(20)) {
911 force_replan(unit);
912 DMprintf("%s found nothing to do defensively, replanning.\n",
913 unit_desig(unit));
914 }
915 else
916 set_unit_reserve(unit->side, unit, TRUE, FALSE);
917 }
918 }
919
920 #if (0) // HACKING NOTE: Saving a snippet we are still interested in.
921 static void
plan_colonize(Unit * unit)922 plan_colonize(Unit *unit)
923 {
924 case GOAL_COLONIZE:
925 x1 = -1; y1 = -1;
926 u2 = plan->maingoal->args[0];
927 /* Try finding a good, unused spot to colonize. */
928 /* (TODO: Ensure that other side units don't have same idea.) */
929 search_around(unit->x, unit->y, u_ai_tactical_range(u),
930 good_cell_to_colonize, &x1, &y1, 1);
931 if (inside_area(x1, y1)
932 && valid(can_construct(unit, unit, u2))
933 && (NODIR !=
934 choose_move_direction(unit, x1, y1,
935 distance(x1, y1,
936 unit->x, unit->y)))) {
937 set_construct_task(unit, u2, 1, -1, x1, y1);
938 push_move_to_task(unit, x1, y1, 0);
939 DMprintf(
940 "%s moving to (%d, %d) to colonize by building %s\n",
941 unit_desig(unit), x1, y1, u_type_name(u2));
942 }
943 /* If we have AI planning and need explorers,
944 then try exploring to a reachable unknown cell. */
945 else if (ai_controlled(unit) && need_explorers(unit->side)
946 && (0 < u_ai_explorer_worth(u))
947 && explore_reachable_cell(unit, u_ai_tactical_range(u)));
948 /* Else, move in a random dir along a straight line. */
949 else
950 set_move_dir_task(unit, random_dir(),
951 u_ai_tactical_range(u) +
952 xrandom(u_ai_tactical_range(u) / 2));
953 break;
954 #endif
955
956 #if (0) // HACKING NOTE: Saving a snippet we are still interested in.
957 static void
958 plan_colonize_support(Unit *unit)
959 {
960 if (ai_controlled(unit))
961 maybe_set_materials_goal(unit, u2);
962 }
963 #endif
964
965 //! Plan: Improve
966 /*!
967 This plan is currently a catch-all for a number of things:
968 (1) Preparing to change utype. [TODO]
969 (2) Changing utype.
970 (3) Building productivity-enhancing units. [TODO]
971 (4) Developing new tech for the side. [TODO]
972
973 \todo Move this code to a new 'aiplan.c' file. Replace with hook invocation.
974 \todo Improve heuristics for selecting a new utype to change into.
975 \todo Allow designer-provided weights for selecting utype to change into.
976 \todo Write change-type prep analysis code.
977 */
978
979 static void
980 plan_improve(Unit *unit)
981 {
982 static int *p_uimprove;
983
984 int u = NONUTYPE, u2 = NONUTYPE;
985 Side *side = NULL;
986 int totchance = 0, luckynum = 0, uval = 0;
987 UnitView *uview = NULL, *uvstack = NULL;
988 Unit *transport = NULL;
989
990 assert_error(in_play(unit),
991 "Attempting to execute plan for out-of-play unit");
992 assert_error(unit->plan, "Trying to run NULL plan");
993 u = unit->type;
994 side = unit->side;
995 /* Always take care of the basics before trying other stuff. */
996 if (resupply_if_low(unit)) {
997 return;
998 }
999 if (rearm_if_low(unit)) {
1000 return;
1001 }
1002 if (repair_if_damaged(unit)) {
1003 return;
1004 }
1005 /* Execute any old tasks associated with this plan. Return. */
1006 if (unit->plan->tasks) {
1007 execute_task(unit);
1008 return;
1009 }
1010 // Initialize the improvement utypes array, if necessary.
1011 if (!p_uimprove)
1012 p_uimprove = (int *)xmalloc(numutypes * sizeof(int));
1013 /* Could unit change type ever? */
1014 if (could_change_type(u)) {
1015 /* Sum the various worths of u. */
1016 uval = total_worth(u);
1017 uval = max(0, uval);
1018 /* Examine possible utypes to change into. */
1019 for_all_unit_types(u2) {
1020 p_uimprove[u2] = 0;
1021 if (!valid(can_change_type_to(u, u2, side)))
1022 continue;
1023 /* Sum the various worths of u2. */
1024 p_uimprove[u2] = total_worth(u2);
1025 p_uimprove[u2] = max(0, p_uimprove[u2]);
1026 /* (TODO: Give bonus for utypes that are needed for victory.) */
1027 /* (TODO: If u2 could change back to u,
1028 then we may need to think more about it.) */
1029 }
1030 /* Zero out entries that are probably not worthwhile. */
1031 for_all_unit_types(u2) {
1032 if (uval >= p_uimprove[u2])
1033 p_uimprove[u2] = 0;
1034 }
1035 /* Sum chances. */
1036 totchance = 0;
1037 for_all_unit_types(u2)
1038 totchance += p_uimprove[u2];
1039 /* Run the lottery, if we should. */
1040 if (0 < totchance) {
1041 luckynum = xrandom(totchance);
1042 totchance = 0;
1043 for_all_unit_types(u2) {
1044 totchance += p_uimprove[u2];
1045 if (luckynum < totchance)
1046 break;
1047 }
1048 assert_warning_return(is_unit_type(u2),
1049 "Picked an invalid utype to change to",);
1050 /* Could unit change type now? */
1051 if (valid(can_change_type_to(unit, unit, u2))) {
1052 if (prep_change_type_action(unit, unit, u2))
1053 return;
1054 }
1055 /* Else, find out what we can do to prepare for a change-type,
1056 if anything. Also determine whether the cost of
1057 changing type is too high in relation to what we could
1058 construct, etc... in the same estimated timeframe. */
1059 else {
1060 /* (TODO: Implement change-type prep analysis.) */
1061 }
1062 } /* run lottery */
1063 } /* could change-type */
1064 /* Can unit construct anything useful inside itself? */
1065 else if (can_construct_any(unit, unit)) {
1066 /* Pick out possible utypes to construct. */
1067 for_all_unit_types(u2) {
1068 p_uimprove[u2] = 0;
1069 if (!type_can_occupy(u2, unit))
1070 continue;
1071 if (!can_construct(unit, unit, u2))
1072 continue;
1073 if (0 < u_ai_prod_enhancer_worth(u2))
1074 p_uimprove[u2] += u_ai_prod_enhancer_worth(u2);
1075 /* (TODO: Handle other cases, such as protection enhancement.) */
1076 totchance += p_uimprove[u2];
1077 }
1078 /* Run the lottery, if we should. */
1079 if (totchance) {
1080 luckynum = xrandom(totchance);
1081 totchance = 0;
1082 for_all_unit_types(u2) {
1083 totchance += p_uimprove[u2];
1084 if (luckynum < totchance)
1085 break;
1086 }
1087 assert_warning_return(is_unit_type(u2),
1088 "Picked an invalid utype to construct",);
1089 // Rummage through potential transports in cell.
1090 // Note: Even if u2 cannot see cell and cannot survive on it,
1091 // we may be able to construct it inside something we have there.
1092 uvstack = query_uvstack_at(unit->x, unit->y);
1093 for_all_uvstack(uvstack, uview) {
1094 transport =
1095 choose_transport_to_construct_in(u2, unit->side, uview);
1096 // TODO: Score all potential transports.
1097 if (transport)
1098 break;
1099 }
1100 // Can survive on cell without transport?
1101 // Note: Rejects cells that we cannot see.
1102 // Note: Rejects cells that are too full.
1103 if (!transport
1104 && !valid(can_survive_on_known(
1105 u2, unit->side, unit->x, unit->y))) {
1106 if (probability(20))
1107 force_replan(unit);
1108 else
1109 set_unit_reserve(unit->side, unit, TRUE, FALSE);
1110 return;
1111 }
1112 if (transport)
1113 set_construct_task(unit, u2, 1, transport->id, -1, -1);
1114 else
1115 set_construct_task(unit, u2, 1, -1, unit->x, unit->y);
1116 } /* lottery */
1117 } /* can construct any */
1118 /* Else, no known improvements can be made. */
1119 else {
1120 if (probability(20)) {
1121 force_replan(unit);
1122 DMprintf("%s could not find a way to improve, replanning.\n",
1123 unit_desig(unit));
1124 }
1125 else
1126 set_unit_reserve(unit->side, unit, TRUE, FALSE);
1127 }
1128 /* Execute any new tasks associated with this plan. Return. */
1129 if (unit->plan->tasks) {
1130 execute_task(unit);
1131 return;
1132 }
1133 }
1134
1135 static void
1136 plan_exploration(Unit *unit)
1137 {
1138 Unit *unit2;
1139 Plan *plan = unit->plan;
1140 int u = unit->type;
1141 int x, y, w, h, range, x1, y1;
1142 int u2 = NONUTYPE;
1143
1144 /* If the world has no secrets, exploration is sort of pointless. */
1145 if (g_see_all()) {
1146 force_replan(unit);
1147 return;
1148 }
1149 if (resupply_if_low(unit)) {
1150 return;
1151 }
1152 if (rearm_if_low(unit)) {
1153 return;
1154 }
1155 if (repair_if_damaged(unit)) {
1156 return;
1157 }
1158 if (plan->tasks) {
1159 /* (should see if a change of task is worthwhile) */
1160 execute_task(unit);
1161 return;
1162 }
1163 /* Listen to what our occs are telling us. */
1164 if (mobile(unit->type) && do_for_occupants(unit)) {
1165 if (plan->tasks) {
1166 execute_task(unit);
1167 return;
1168 }
1169 }
1170 #if (0)
1171 /* If we are a colonizer, we might also want to try colonizing. */
1172 if (0 < u_colonizer_worth(u) && good_cell_to_colonize(unit->x, unit->y)) {
1173 for_all_unit_types(u2) {
1174 if (valid(can_construct(unit, unit, u2)) && !unit->nexthere) {
1175 set_construct_task(unit, u2, 1, -1, unit->x, unit->y);
1176 DMprintf(
1177 "%s decided to colonize while exploring; will build %s\n",
1178 unit_desig(unit), u_type_name(u2));
1179 if (plan->tasks) {
1180 /* (should see if a change of task is worthwhile) */
1181 execute_task(unit);
1182 return;
1183 }
1184 }
1185 }
1186 }
1187 #endif
1188 /* Else, get down to exploration business. */
1189 if (plan->maingoal) {
1190 switch (plan->maingoal->type) {
1191 case GOAL_UNIT_OCCUPIED:
1192 /* Move to occupy our goal if necessary. */
1193 unit2 = find_unit(plan->maingoal->args[0]);
1194 if (in_play(unit2) && unit->transport != unit2)
1195 set_occupy_task(unit, unit2);
1196 else {
1197 free(plan->maingoal);
1198 plan->maingoal = NULL;
1199 }
1200 break;
1201 case GOAL_CELL_OCCUPIED:
1202 /* Move to our goal if necessary. */
1203 x = plan->maingoal->args[0];
1204 y = plan->maingoal->args[1];
1205 if (unit->x != x || unit->y != y)
1206 set_move_to_task(unit, x, y, 0);
1207 else {
1208 free(plan->maingoal);
1209 plan->maingoal = NULL;
1210 }
1211 break;
1212 case GOAL_VICINITY_KNOWN:
1213 case GOAL_VICINITY_HELD:
1214 if (mobile(u)) {
1215 x = plan->maingoal->args[0]; y = plan->maingoal->args[1];
1216 w = plan->maingoal->args[2]; h = plan->maingoal->args[3];
1217 if (distance(x, y, unit->x, unit->y) > max(w, h)) {
1218 /* Out of the area, move into it. */
1219 if (random_point_near(x, y, max(w, h) / 2, &x1, &y1)) {
1220 x = x1; y = y1;
1221 }
1222 DMprintf("%s to explore towards %d,%d\n",
1223 unit_desig(unit), x, y);
1224 set_move_to_task(unit, x, y, max(w, h) / 2);
1225 } else {
1226 /* Found a cell to explore. */
1227 if (explore_reachable_cell(unit, max(w, h) + 2)) {
1228 } else {
1229 if (flip_coin()) {
1230 DMprintf("%s clearing goal\n", unit_desig(unit));
1231 free(plan->maingoal);
1232 plan->maingoal = NULL;
1233 }
1234 DMprintf("%s to walk randomly\n", unit_desig(unit));
1235 random_walk(unit);
1236 }
1237 }
1238 }
1239 else
1240 random_walk(unit);
1241 break;
1242 default:
1243 DMprintf("%s goal %s?\n",
1244 unit_desig(unit), goal_desig(unit->plan->maingoal));
1245 break;
1246 }
1247 } else {
1248 /* No specific goal, just poke around. */
1249 if (mobile(u)) {
1250 range = area.maxdim / 2;
1251 /* Found a cell to explore. */
1252 if (explore_reachable_cell(unit, range)) {
1253 } else {
1254 DMprintf("%s to walk randomly\n", unit_desig(unit));
1255 random_walk(unit);
1256 }
1257 }
1258 else
1259 random_walk(unit);
1260 }
1261 if (plan->tasks) {
1262 execute_task(unit);
1263 } else {
1264 /* If we cannot find anything sensible to do, we force a replan.
1265 This will force the AI to consider if this unit should be moved
1266 to another theater of operations. */
1267 if (probability(20)) {
1268 force_replan(unit);
1269 DMprintf("%s found nothing to do exploring, replanning.\n",
1270 unit_desig(unit));
1271 }
1272 else
1273 set_unit_reserve(unit->side, unit, TRUE, FALSE);
1274 }
1275 }
1276
1277 /* These are used by AIs as well. */
1278 /* (Should be transplanted to 'ai.c'?) */
1279
1280 int victim_x, victim_y, victim_rating, victim_utype, victim_sidenum;
1281 OccStatus victim_occstatus;
1282 int victim_flags;
1283 int victim_dmgtypes;
1284
1285 int
1286 ai_type_choose_best_hit_method(int u1, int u2)
1287 {
1288 int dmgtypes = DAMAGE_TYPE_NONE;
1289 int atkdmgmax1 = -1, firedmgmax1 = -1;
1290 int atkrngidl1 = -1, firerngidl1 = -1, hitrngmax2 = -1;
1291 int atkrngdelta = 0, firerngdelta = 0;
1292 int dmgdelta1 = 0;
1293
1294 dmgtypes = type_possible_damage_methods(u1, u2);
1295 if ((dmgtypes & DAMAGE_TYPE_ATTACK) && !(dmgtypes & DAMAGE_TYPE_FIRE))
1296 return DAMAGE_TYPE_ATTACK;
1297 if (!(dmgtypes & DAMAGE_TYPE_ATTACK) && (dmgtypes & DAMAGE_TYPE_FIRE))
1298 return DAMAGE_TYPE_FIRE;
1299 if (!(dmgtypes & DAMAGE_TYPE_ATTACK) && !(dmgtypes & DAMAGE_TYPE_FIRE))
1300 return DAMAGE_TYPE_NONE;
1301 atkdmgmax1 = type_attack_damage_max(u1, u2);
1302 firedmgmax1 = type_fire_damage_max(u1, u2);
1303 atkrngidl1 = type_ideal_attack_range_max(u1, u2);
1304 firerngidl1 = type_ideal_fire_range_max(u1, u2);
1305 hitrngmax2 = type_hit_range_max(u2, u1);
1306 atkrngdelta = atkrngidl1 - hitrngmax2;
1307 firerngdelta = firerngidl1 - hitrngmax2;
1308 dmgdelta1 = atkdmgmax1 - firedmgmax1;
1309 if (atkrngdelta == firerngdelta) {
1310 return ((dmgdelta1 >= 0) ? DAMAGE_TYPE_ATTACK : DAMAGE_TYPE_FIRE);
1311 }
1312 else if (atkrngdelta > firerngdelta) {
1313 if (dmgdelta1 >= 0)
1314 return DAMAGE_TYPE_ATTACK;
1315 else {
1316 if ((dmgdelta1 / (atkrngdelta - firerngdelta)) <= -2)
1317 return DAMAGE_TYPE_FIRE;
1318 else
1319 return DAMAGE_TYPE_ATTACK;
1320 }
1321 }
1322 else {
1323 if (dmgdelta1 <= 0)
1324 return DAMAGE_TYPE_FIRE;
1325 else {
1326 if ((dmgdelta1 / (atkrngdelta - firerngdelta)) <= -2)
1327 return DAMAGE_TYPE_ATTACK;
1328 else
1329 return DAMAGE_TYPE_FIRE;
1330 }
1331 }
1332 return dmgtypes;
1333 }
1334
1335 int
1336 ai_score_potential_victim_occupants(UnitView *uview, OccStatus occstatus,
1337 int victimflags,
1338 int dmgtypes)
1339 {
1340 int rating = 0, occrating = 0;
1341 int u1 = NONUTYPE, u3 = NONUTYPE;
1342 int x = -1, y = -1;
1343 UnitView *uvocc = NULL;
1344 Side *side = NULL, *oside = NULL;
1345
1346 u1 = tmpunit->type;
1347 side = tmpunit->side;
1348 x = uview->x; y = uview->y;
1349 /* Iterate through all known occupants. */
1350 for_all_occupant_views_with_occs(uview, uvocc) {
1351 occrating = 0;
1352 oside = side_n(uvocc->siden);
1353 u3 = uvocc->type;
1354 /* Penalty for thinking about enemy transport that contains
1355 friendlies. */
1356 if (!enemy_side(side, oside)) {
1357 if ((dmgtypes & DAMAGE_TYPE_ATTACK)
1358 && could_damage_by_attacks(u1, u3))
1359 rating -= uu_hit(u1, u3);
1360 else if ((dmgtypes & DAMAGE_TYPE_FIRE)
1361 && could_damage_by_fire(u1, u3))
1362 rating -= fire_hit_chance(u1, u3);
1363 continue;
1364 }
1365 /* Add in attack worth, if occ can be attacked. */
1366 if ((dmgtypes & DAMAGE_TYPE_ATTACK)
1367 && could_damage_by_attacks(u1, u3)) {
1368 occrating += uu_hit(u1, u3);
1369 }
1370 /* Add in fire-at worth, if occ can be fired at. */
1371 else if ((dmgtypes & DAMAGE_TYPE_FIRE)
1372 && could_damage_by_fire(u1, u3)) {
1373 occrating += fire_hit_chance(u1, u3);
1374 }
1375 /* Add in capture worth, if occ can be captured. */
1376 if (victimflags & VICTIM_CAPTURABLE)
1377 occrating += capture_chance(u1, u3, oside);
1378 /* Adjust occrating if less than or equal to 0. */
1379 if (occrating <= 0) {
1380 rating += occrating;
1381 occrating = 2;
1382 }
1383 /* Add a bonus, if occ is a builder. */
1384 if (type_can_build(u3, oside))
1385 occrating += ((occrating * 50) / 100);
1386 /* Add a huge bonus, if occ might be self unit. */
1387 if (u_can_be_self(u3))
1388 occrating *= 3;
1389 /* Add occ rating to aggregate rating. */
1390 rating += occrating;
1391 }
1392 return rating;
1393 }
1394
1395 int
1396 ai_score_potential_victim(UnitView *uview, OccStatus occstatus,
1397 int victimflags)
1398 {
1399 int rating = 0, baserating = 0, isqrating;
1400 int u1 = NONUTYPE, u2 = NONUTYPE, u3 = NONUTYPE;
1401 int x = -1, y = -1;
1402 Side *side = NULL, *oside = NULL;
1403 int dmgtypes = DAMAGE_TYPE_NONE;
1404 Unit *unit3 = NULL;
1405 int dmgratio = -1;
1406 int popularity = 0;
1407 int dist = INT_MAX, dist2 = INT_MAX;
1408 int strikedist = -1, oprange = -1, strikedist2 = -1;
1409 int costratio = 0, costratiomax = 0;
1410 int numbuildees = 0;
1411
1412 u1 = tmpunit->type;
1413 side = tmpunit->side;
1414 u2 = uview->type;
1415 x = uview->x; y = uview->y;
1416 oside = side_n(uview->siden);
1417 dist = distance(tmpunit->x, tmpunit->y, x, y);
1418 strikedist = type_hit_range_max(u1, u2);
1419 oprange = (new_acp_for_turn(tmpunit) * type_max_speed(u1)) / 100;
1420 dmgtypes = type_possible_damage_methods(u1, u2);
1421 /* Choose better method for u1 to hit u2, if necessary. */
1422 if ((dmgtypes & DAMAGE_TYPE_ATTACK) && (dmgtypes & DAMAGE_TYPE_FIRE))
1423 dmgtypes = ai_type_choose_best_hit_method(u1, u2);
1424 /* Add attack worth. */
1425 rating += max(0, uu_zz_bhw(u1, u2));
1426 /* Add fire worth. */
1427 rating += max(0, uu_zz_bfw(u1, u2));
1428 /* Add capture worth, if we can capture enemy. */
1429 if (victimflags & VICTIM_CAPTURABLE)
1430 rating += uu_zz_bcw(u1, u2);
1431 /* Add other worths that boost our estimation of the enemy. */
1432 rating += u_colonization_support_worth(u2);
1433 rating += u_exploration_support_worth(u2);
1434 rating += u_offensive_support_worth(u2);
1435 rating += u_defensive_support_worth(u2);
1436 /* Add encounter worth, if we can encounter enemy. */
1437 if (victimflags & VICTIM_ENCOUNTERABLE)
1438 rating += 10000;
1439 #if (0)
1440 /* Add a bonus, for each utype that an enemy can build. */
1441 for_all_unit_types(u3) {
1442 if (type_can_build_type(u2, oside, u3))
1443 ++numbuildees;
1444 }
1445 rating += ((numbuildees * 100) / numutypes);
1446 #endif
1447 /* If score is initially <= 1, still give it a chance to build up. */
1448 if (rating <= 1)
1449 baserating = 2;
1450 else
1451 baserating = rating;
1452 /* Add huge bonus if enemy unit might that side's self unit. */
1453 if (u_can_be_self(u2)) {
1454 if (rating <= 0)
1455 rating += (baserating * 3);
1456 else
1457 rating *= 3;
1458 }
1459 /* Update baserating. */
1460 if (rating > baserating)
1461 baserating = rating;
1462 /* Add in total transport worth, if enemy can transport. */
1463 if (CANNOT_HAVE_OCCS != occstatus) {
1464 for_all_unit_types(u3) {
1465 rating += uu_zz_btw(u2, u3);
1466 }
1467 }
1468 #if (0)
1469 /* Add in occupant ratings, if enemy does or may have occupants. */
1470 switch (occstatus) {
1471 case MAYBE_HAS_OCCS:
1472 rating += (baserating * 2);
1473 break;
1474 case DEFINITELY_HAS_OCCS:
1475 rating +=
1476 ai_score_potential_victim_occupants(uview, occstatus,
1477 victimflags, dmgtypes);
1478 break;
1479 case DEFINITELY_HAS_NO_OCCS:
1480 case CANNOT_HAVE_OCCS:
1481 default:
1482 break;
1483 }
1484 #endif
1485 /* Add urgency bonus if enemy unit potentially threatens one of our
1486 builders. */
1487 for_all_side_units(side, unit3) {
1488 u3 = unit3->type;
1489 if (type_can_build(u3, side)) {
1490 dist2 = distance(unit3->x, unit3->y, x, y);
1491 strikedist2 = (could_damage(u2, u3))
1492 ? type_hit_range_max(u2, u3) : -1;
1493 if (dist2 <= strikedist2) {
1494 rating += ((baserating * 25) / 100);
1495 strikedist2 = (could_damage(u2, u3))
1496 ? type_ideal_hit_range_max(u2, u3) : -1;
1497 if (dist2 <= strikedist2)
1498 rating += ((baserating * 50) / 100);
1499 }
1500 /* Urgent. Attempt to stop possible capture or lift siege. */
1501 if ((dist2 <= 1) && could_capture(u2, u3, side))
1502 rating *= 2;
1503 }
1504 }
1505 /* Penalize, if other units on our side are already planning to mess
1506 with the enemy. */
1507 if (side_planning_to_capture_type_at(side, u2, x, y))
1508 popularity = n_planning_to_capture_type_at(side, u2, x, y);
1509 if (side_planning_to_hit_type_at(side, u2, x, y))
1510 popularity += n_planning_to_hit_type_at(side, u2, x, y);
1511 if (planning_to_capture_type_at(tmpunit, u2, x, y))
1512 --popularity;
1513 if (planning_to_hit_type_at(tmpunit, u2, x, y))
1514 --popularity;
1515 popularity = isqrt(popularity);
1516 if (popularity && (rating > 0))
1517 rating /= popularity;
1518 /* Add a bonus if it is more costly than our unit. */
1519 if (u_cp(u1)) {
1520 /* Increase score according to cost ratio. */
1521 if (u_cp(u2))
1522 rating += ((baserating * (u_cp(u2) / u_cp(u1)) * 50) / 100);
1523 }
1524 /* Could add many other score modifiers, such as whether the potential
1525 victim provides a critical material for a side, whether it is
1526 guaranteed to be a side's self unit, or if it has a greater value to
1527 the scorekeeper than our unit. */
1528 /* Also need to consider stack protection and cellwide protection, and
1529 the strengths of any enemy units that may be supporting our victim. */
1530 #if (0)
1531 dmgratio = ai_damage_ratio_vs_type(tmpunit, u2);
1532 /* Add a bonus, if enemy is outgunned by us. */
1533 if (!dmgratio)
1534 rating += ((baserating * 50) / 100);
1535 /* Update baserating. */
1536 if (rating > baserating)
1537 baserating = rating;
1538 if (baserating <= 0)
1539 baserating = 2;
1540 /* Add urgency bonus, if enemy can potentially hit or may actually be
1541 hitting us, and we are equal or superior to it. */
1542 if ((dmgratio <= 1) && could_damage(u2, u1)) {
1543 /* If it can hit us at all, we should be concerned. */
1544 if (type_hit_range_max(u2, u1) >= dist)
1545 rating += ((baserating * 50) / 100);
1546 /* If it can hit us with full effect, then be extra concerned. */
1547 if (type_ideal_hit_range_max(u2, u1) >= dist)
1548 rating += ((baserating * 75) / 100);
1549 /* If it is mobile, there is a greater chance that it moved in to
1550 attack us or that it can pester us in the future. */
1551 /* (Should actually examine the unit's movement rate.) */
1552 if (mobile(u2))
1553 rating += ((baserating * 125) / 100);
1554 /* More likely to be hitting us if it is less vulnerable. */
1555 if (!(victimflags & (VICTIM_CAPTURABLE | VICTIM_ENCOUNTERABLE)))
1556 rating += ((baserating * 50) / 100);
1557 if (!u_advanced(u2) && !type_can_build(u2, oside))
1558 rating += ((baserating * 50) / 100);
1559 if (!u_can_be_self(u2))
1560 rating += ((baserating * 50) / 100);
1561 }
1562 /* Heavily penalize score, if enemy can seriously outgun us. */
1563 if ((dmgratio > 1) && !(victimflags & VICTIM_ENCOUNTERABLE)) {
1564 if (type_ideal_hit_range_max(u1, u2) <=
1565 type_ideal_hit_range_max(u2, u1))
1566 rating -= (baserating - (baserating / dmgratio));
1567 else if (type_ideal_hit_range_max(u1, u2) <=
1568 type_hit_range_max(u2, u1))
1569 rating -= (baserating - (baserating / (dmgratio / 2)));
1570 else if (victimflags & VICTIM_CAPTURABLE)
1571 rating -= (baserating - (baserating / dmgratio));
1572 }
1573 /* Update baserating. */
1574 if (rating > baserating)
1575 baserating = rating;
1576 if (baserating <= 0)
1577 baserating = 2;
1578 #endif
1579 isqrating = rating / (isqrt(dist) + 1);
1580 /* Penalize score according to turns from unit. */
1581 /* Note that this code is _different_ in purpose than the general
1582 distance penalizer later on. It penalizes according to a rough
1583 estimate of turns from the enemy unit rather than distance from
1584 the enemy unit. */
1585 if (oprange && (dist > strikedist) && ((dist - strikedist) > oprange))
1586 rating -= (baserating -
1587 (baserating / (((dist - strikedist) / oprange) + 1)));
1588 /* Add a 50% bonus if enemy is within striking distance. */
1589 if (dist <= strikedist) {
1590 rating += (rating * 50) / 100;
1591 }
1592 /* Cap penalty to inverse square root of distance. */
1593 if (rating < isqrating)
1594 rating = isqrating;
1595 /* Penalize if uview is old. */
1596 if (!g_see_all() && !u_see_always(u2) && (1 < (g_turn() - uview->date)))
1597 rating /= (g_turn() - uview->date);
1598 return rating;
1599 }
1600
1601 int
1602 ai_victim_here(int x, int y, int *numvictims)
1603 {
1604 int u1 = tmpunit->type, u2 = NONUTYPE, rating = 0, dist = 0;
1605 Side *side = tmpunit->side, *oside = NULL;
1606 UnitView *uview = NULL;
1607 Task *hittask = NULL;
1608 int strikedist = -1, moves = 0, omoves = 0;
1609
1610 /* Should iterate also over visible occs? However, the code explicitly
1611 handles occs. */
1612 for_all_view_stack(side, x, y, uview) {
1613 int victimflags = VICTIM_NOTHING_SPECIAL;
1614 OccStatus occstatus = CANNOT_HAVE_OCCS;
1615
1616 run_ui_idler(); /* Lengthy computations may be involved. */
1617 u2 = uview->type;
1618 oside = side_n(uview->siden);
1619 rating = 0;
1620 /* If it's a friendly, then obviously skip it. */
1621 if (!enemy_side(side, oside))
1622 continue;
1623 /* If there is an encounter result associated with it,
1624 then treat it like a capture. */
1625 /* We can be smarter than this, but this helps us out in games
1626 such as Civ2, where many of the encounter results for
1627 Villages are capture-like. */
1628 if (u_encounter_result(u2) != lispnil)
1629 victimflags |= VICTIM_ENCOUNTERABLE;
1630 /* If we cannot hit it, then skip it. */
1631 /* Currently model 1 does not allow for even the capture of
1632 undefended units by attack == 0 units. If this changes, then
1633 we will need to reassess this part of the code. */
1634 if (!could_hit(u1, u2) && !(victimflags & VICTIM_ENCOUNTERABLE))
1635 continue;
1636 /* If we are immobile and it is not in striking range, then skip it. */
1637 strikedist = type_hit_range_max(u1, u2);
1638 dist = distance(tmpunit->x, tmpunit->y, x, y);
1639 if (!mobile(u1) && (dist > strikedist))
1640 continue;
1641 /* If we are slower than it, and cannot reach it this turn. */
1642 if (tmpunit->act) {
1643 omoves = (type_max_acp(u2) * type_max_speed(u2)) / 100;
1644 moves = (tmpunit->act->acp * type_max_speed(u1)) / 100;
1645 /* (Assumes move-range == 1.) */
1646 if (dist > moves) {
1647 moves = (type_max_acp(u1) * type_max_speed(u1)) / 100;
1648 if (moves < omoves)
1649 continue;
1650 }
1651 }
1652 if (capture_chance(u1, u2, oside))
1653 victimflags |= VICTIM_CAPTURABLE;
1654 /* Record the occupancy status. */
1655 occstatus = occ_status(uview);
1656 /* If it cannot be captured or encountered, and... */
1657 if (!victimflags) {
1658 #if (0)
1659 /* ...if it can hurt us much worse than we can hurt it,
1660 then skip it. */
1661 if (ai_damage_ratio_vs_type(tmpunit, u2) >= 2)
1662 continue;
1663 #endif
1664 /* ...if it cannot have occs, and... */
1665 if (CANNOT_HAVE_OCCS == occstatus) {
1666 /* ...if we cannot destroy it, then skip it. */
1667 /* Though in theory, it might be useful to drain the
1668 enemy's ACP with supressing fire. */
1669 if (!could_destroy(u1, u2))
1670 continue;
1671 } /* CANNOT_HAVE_OCCS */
1672 } /* !victimflags */
1673 /* If it cannot be captured or encountered, and... */
1674 if (!victimflags) {
1675 /* ...if it might hurt our occupants, then skip. */
1676 /* (This test could be improved.) */
1677 if ((tmpunit->occupant != NULL) && could_damage(u2, u1))
1678 continue;
1679 /* ...if it is worth capturing by our side, and... */
1680 if (worth_capturing(side, u2, oside, x, y)
1681 && (u_advanced(u2) || type_can_build(u2, oside))) {
1682 /* ...if it belongs to indepside, and indepside has no player
1683 and cannot build, then skip it. */
1684 if (oside == indepside
1685 && !g_indepside_can_build()
1686 && !g_indepside_has_ai()) {
1687 continue;
1688 }
1689 #if (0)
1690 /* Let 2 attackers pound a unit with known occs. */
1691 if (occstatus == DEFINITELY_HAS_OCCS
1692 && (n_planning_to_hit_type_at(side, u2, x, y)
1693 - planning_to_hit_type_at(tmpunit, u2, x, y)) > 1) {
1694 continue;
1695 /* Let 1 attacker pound a unit with possible occs. */
1696 } else if (occstatus == MAYBE_HAS_OCCS
1697 && (n_planning_to_hit_type_at(side, u2, x, y)
1698 - planning_to_hit_type_at(tmpunit, u2, x, y)) > 0) {
1699 continue;
1700 /* Don't go for units without occs. */
1701 } else if (occstatus == DEFINITELY_HAS_NO_OCCS) {
1702 continue;
1703 }
1704 #endif
1705 /* ...if it is too fragile to be shaken down, then skip it. */
1706 if (!ai_consider_shaking(tmpunit, uview, occstatus)) {
1707 continue;
1708 } else {
1709 victimflags |= VICTIM_SHAKEABLE;
1710 }
1711 } else { /* not worth_capturing */
1712 /* ...if we cannot destroy it, then skip it. */
1713 /* Though in theory, it might be useful to drain the
1714 enemy's ACP with supressing fire. */
1715 if (!could_destroy(u1, u2))
1716 continue;
1717 } /* worth_capturing */
1718 } /* !victimflags */
1719 /* Else, can capture it. */
1720 /* Score the potential victim. */
1721 rating = ai_score_potential_victim(uview, occstatus, victimflags);
1722 if (rating > victim_rating
1723 || (rating == victim_rating && flip_coin())) {
1724 /* See if we can get within striking distance. */
1725 if (mobile(u1)
1726 && (dist > strikedist)
1727 && (NODIR ==
1728 choose_move_direction(tmpunit, x, y, max(1, strikedist))))
1729 continue;
1730 /* Tag the new victim. */
1731 DMprintf("\n\t...considering %s at %d,%d; assigned rating %d",
1732 u_type_name(u2), x, y, rating);
1733 if (victimflags)
1734 DMprintf("\n\t\tflags = { %s %s }",
1735 (victimflags & VICTIM_CAPTURABLE) ? "capturable" : "",
1736 (victimflags & VICTIM_SHAKEABLE) ? "shakeable" : "");
1737 if (occstatus)
1738 DMprintf("\n\t\toccupancy status = %s",
1739 (occstatus == MAYBE_HAS_OCCS) ? "maybe_has_occs" :
1740 ((occstatus == DEFINITELY_HAS_OCCS) ?
1741 "definitely_has_occs" : "definitely_has_no_occs"));
1742 victim_x = x; victim_y = y;
1743 victim_rating = rating;
1744 victim_utype = u2;
1745 victim_sidenum = oside->id;
1746 victim_occstatus = occstatus;
1747 victim_flags = victimflags;
1748 victim_dmgtypes = type_possible_damage_methods(u1, u2);
1749 /* Increment the victim counter. */
1750 if (numvictims)
1751 ++(*numvictims);
1752 }
1753 } /* for_all_view_stack */
1754 return FALSE;
1755 }
1756
1757 /* Evaluates the feasibility of shaking up a transport. */
1758 /* (A fancier version of this might take into account
1759 protection a transport provides its occs and vice versa.) */
1760
1761 int
1762 ai_consider_shaking(Unit *unit, UnitView *uview, OccStatus occstatus)
1763 {
1764 int u1 = NONUTYPE, u2 = NONUTYPE, u3 = NONUTYPE;
1765 Side *side = NULL, *oside = NULL, *occside = NULL;
1766 int dmgmax1 = 0, occknocks = 0, occknocksbest = 0, tsptknocks = 0;
1767 UnitView *uvocc = NULL;
1768
1769 u1 = unit->type;
1770 side = unit->side;
1771 u2 = uview->type;
1772 oside = side_n(uview->siden);
1773 dmgmax1 = type_damage_max(u1, u2);
1774 /* If we can damage it. */
1775 if (dmgmax1) {
1776 tsptknocks = u_hp_max(u2) / dmgmax1;
1777 occknocks = 0; /* No occ-knock jokes please. */
1778 /* I'm not occustomed to having my work knocked. */
1779 if (occstatus == DEFINITELY_HAS_OCCS) {
1780 for_all_occupant_views(uview, uvocc) {
1781 u3 = uvocc->type;
1782 occside = side_n(uvocc->siden);
1783 dmgmax1 = type_damage_max(u1, uvocc->type);
1784 if (dmgmax1) {
1785 if (enemy_side(side, occside))
1786 occknocks += u_hp_max(uvocc->type) / dmgmax1;
1787 else
1788 occknocks += 2 * (u_hp_max(uvocc->type) / dmgmax1);
1789 }
1790 if (occknocks >= tsptknocks)
1791 break;
1792 }
1793 occknocksbest = occknocks;
1794 }
1795 else if (occstatus == MAYBE_HAS_OCCS) {
1796 /* This is a fairly expensive test. The results
1797 remain the same throughout the game, and
1798 hence should be precomputed. */
1799 for_all_unit_types(u3) {
1800 if (type_can_occupy_empty_type(u3, u2)) {
1801 dmgmax1 = type_damage_max(u1, u3);
1802 occknocks = 0;
1803 if (dmgmax1)
1804 occknocks = u_hp_max(u3) / dmgmax1;
1805 occknocksbest = max(occknocks, occknocksbest);
1806 if (occknocksbest >= tsptknocks)
1807 break;
1808 }
1809 }
1810 }
1811 /* If transport it too fragile, then don't shake it. */
1812 if (((tsptknocks * (oside->udoctrine)[u2]->repair_percent) / 100)
1813 <= occknocksbest)
1814 return FALSE;
1815 /* If transport is tough enough,
1816 and if it maybe has occs, then maybe shake it. */
1817 else {
1818 if ((occstatus == MAYBE_HAS_OCCS) && probability(50))
1819 return FALSE;
1820 }
1821 } /* dmgmax1 */
1822 /* If we cannot damage the transport we always attack
1823 it on the assumption that it is building occs that we can
1824 damage (the calling code checks that type_can_build or
1825 u_advanced is true). */
1826 return TRUE;
1827 }
1828
1829 /* Evaluates the feasibility of attempting to capture an unit. */
1830
1831 int
1832 ai_consider_capturing(Unit *unit, UnitView *uview, int dmgthresh,
1833 int qthresh)
1834 {
1835 int u1 = NONUTYPE, u2 = NONUTYPE, u3 = NONUTYPE;
1836 Side *side = NULL, *oside = NULL;
1837 int dmgmax1 = 0, ourknocks = 0;
1838 int qprob = 0;
1839 int i = 0;
1840 int ncaptors = 0;
1841
1842 u1 = unit->type;
1843 side = unit->side;
1844 u2 = uview->type;
1845 oside = side_n(uview->siden);
1846 /* Sanity check. Can we even capture it? */
1847 if (!could_capture(u1, u2, oside))
1848 return FALSE;
1849 /* Can we even own the potential captive? */
1850 if (!type_allowed_on_side(u2, side))
1851 return FALSE;
1852 /* Can we take the potential captive without being scathed? */
1853 if ((could_capture_by_attacks(u1, u2, oside)
1854 || could_capture_by_fire(u1, u2, oside))
1855 && (type_hit_range_max(u2, u1) < 1))
1856 return TRUE;
1857 /* If it is a self-unit, can any other unit on our side capture it, or
1858 MUST we perform the task, no matter how unsavory? */
1859 /* (Should consider that destruction might be more palatable.) */
1860 if (u_can_be_self(u2)) {
1861 dmgthresh += 5;
1862 for_all_unit_types(u3) {
1863 if (u3 == u1)
1864 continue;
1865 if (!type_allowed_on_side(u3, side))
1866 continue;
1867 if (could_capture(u3, u2, oside)) {
1868 ++ncaptors;
1869 break;
1870 }
1871 }
1872 if (!ncaptors)
1873 return TRUE;
1874 }
1875 /* If it is a builder, then be willing to take greater risks to
1876 seize it. */
1877 if (type_can_build(u2, oside) || u_advanced(u2))
1878 ++dmgthresh;
1879 if (ai_damage_ratio_vs_type(unit, u2) >= dmgthresh) {
1880 dmgmax1 = type_damage_max(u2, u1);
1881 /* If it can damage us... */
1882 if (dmgmax1) {
1883 ourknocks = unit->hp2 / dmgmax1;
1884 qprob = 100 - capture_chance(u1, u2, oside);
1885 if (qprob > 0) {
1886 for (i = 0; i < ourknocks; ++i) {
1887 qprob = (qprob * qprob) / 100;
1888 if (qprob <= qthresh)
1889 break;
1890 }
1891 /* (Should factor in the value of the target,
1892 and whether we could lose occs in a
1893 shootout/capture attempt.) */
1894 if (qprob > qthresh)
1895 return FALSE;
1896 }
1897 } /* dmgmax1 */
1898 /* Else it cannot damage us, then we must be invincible to one
1899 another. */
1900 else {
1901 if (!could_capture_by_capture(u1, u2, oside))
1902 return FALSE;
1903 } /* !dmgmax1 */
1904 } /* ai_damage_ratio_vs_type(unit, u2) */
1905 return TRUE;
1906 }
1907
1908 /* A generic damage-ratio evaluator. Assumes maximal damage. */
1909 /* (This function should be in 'ai.c'?) */
1910
1911 int
1912 ai_damage_ratio_vs_type(Unit *unit, int u2)
1913 {
1914 int atkval1 = 0, atkval2 = 0;
1915 int u1 = NONUTYPE;
1916
1917 u1 = unit->type;
1918 /* Can it even hit or damage us?
1919 And do we look like Freddy Kreuger to it? */
1920 if (could_destroy(u2, u1)) {
1921 /* (The difference in fire ranges and attack ranges should be
1922 accounted for. Which is to ask: can it hit us before we can
1923 hit it, or vice versa? And how hard are the hits?) */
1924 atkval1 = unit->hp2 * type_damage_max(u1, u2);
1925 if (atkval1) {
1926 atkval2 = u_hp_max(u2) * type_damage_max(u2, u1);
1927 if (!atkval2)
1928 return 0; /* u2 cannot hurt u1 by conventional means. */
1929 /* How much more can u2 hurt u1 than u1 can hurt u2?
1930 If 0, then u1 is superior.
1931 If 1, then roughly equal.
1932 If >1, then u2 is superior. */
1933 return atkval2 / atkval1;
1934 }
1935 else
1936 return INT_MAX; /* u1 cannot hurt u2 by conventional means. */
1937 }
1938 return 0; /* u2 cannot destroy u1 by conventional means. */
1939 }
1940
1941 /* This decides whether a given unit type seen at a given location is worth
1942 trying to capture. */
1943
1944 int
1945 worth_capturing(Side *side, int u2, Side *side2, int x, int y)
1946 {
1947 int u, bestchance = 0;
1948
1949 /* See how likely we are to be able to capture the type. */
1950 for_all_unit_types(u) {
1951 bestchance = max(capture_chance(u, u2, side2), bestchance);
1952 }
1953 return bestchance;
1954 /* (should account for other considerations too, like which types of
1955 units we have) */
1956 }
1957
1958 /* Note: go_after_victim can be used for both attacking and firing units since
1959 ai_victim_here only tests for could_hit. */
1960
1961 /* This routine looks for somebody, anybody to attack. */
1962
1963 #if (0)
1964 int
1965 go_after_victim(Unit *unit, int range)
1966 {
1967 int x, y, rslt;
1968
1969 tmpunit = unit;
1970 DMprintf("%s seeking victim within %d; found ",
1971 unit_desig(unit), range);
1972 victim_rating = -9999;
1973 rslt = limited_search_around(unit->x, unit->y, range, ai_victim_here,
1974 &x, &y, 1, 10);
1975 if (rslt) {
1976 DMprintf("s%d %s at %d,%d\n",
1977 victim_sidenum, u_type_name(victim_utype), x, y);
1978 /* Set up a task to go after the unit found. */
1979 /* (should be able to set capture task if better) */
1980 set_hit_unit_task(unit, x, y, victim_utype, victim_sidenum);
1981 if (unit->transport != NULL
1982 && mobile(unit->transport->type)
1983 && unit->transport->plan) {
1984 set_move_to_task(unit->transport, x, y, 1);
1985 }
1986 } else if (victim_rating > -9999) {
1987 DMprintf("s%d %s (rated %d) at %d,%d\n",
1988 victim_sidenum, u_type_name(victim_utype), victim_rating,
1989 victim_x, victim_y);
1990 /* Set up a task to go after the unit found. */
1991 /* (should be able to set capture task if better) */
1992 set_hit_unit_task(unit, victim_x, victim_y, victim_utype,
1993 victim_sidenum);
1994 if (unit->transport != NULL
1995 && mobile(unit->transport->type)
1996 && unit->transport->plan) {
1997 set_move_to_task(unit->transport, victim_x, victim_y, 1);
1998 }
1999 /* We succeeded after all. */
2000 rslt = TRUE;
2001 } else {
2002 DMprintf("nothing\n");
2003 }
2004 return rslt;
2005 }
2006 #endif
2007
2008 #if 0 /* Unused. */
2009
2010 /* The point of this new function is to limit the search for captives
2011 to a given range. */
2012
2013 int
2014 go_after_captive(Unit *unit, int range)
2015 {
2016 int x, y, rslt;
2017
2018 tmpunit = unit;
2019
2020 DMprintf("%s searching for useful capture within %d; found ",
2021 unit_desig(unit), range);
2022 rslt = search_around(unit->x, unit->y, range,
2023 useful_captureable_here, &x, &y, 1);
2024 if (rslt) {
2025 DMprintf("one at %d,%d\n", x, y);
2026 /* Set up a task to go after the unit found. */
2027 set_capture_task(unit, x, y);
2028 if (unit->transport
2029 && mobile(unit->transport->type)
2030 && unit->transport->plan) {
2031 set_move_to_task(unit->transport, x, y, 1);
2032 }
2033 return (execute_task(unit) != TASK_FAILED);
2034 } else {
2035 DMprintf("nothing\n");
2036 return FALSE;
2037 }
2038 }
2039
2040 #endif
2041
2042 /* Given a location and a unit (in tmpunit), try to identify a target. */
2043 /* (should move to place to share with AIs) */
2044
2045 int target_x, target_y, target_rating, target_utype, target_sidenum;
2046
2047 int
2048 target_here(int x, int y)
2049 {
2050 int u2 = NONUTYPE, rating, dist;
2051 Side *side = tmpunit->side, *oside = NULL;
2052 Unit *unit3;
2053 UnitView *uview;
2054
2055 for_all_view_stack_with_occs(side, x, y, uview) {
2056 u2 = uview->type;
2057 oside = side_n(uview->siden);
2058 /* (should move all tests inside loop) */
2059 if (is_unit_type(u2)
2060 && enemy_side(side, oside)
2061 && could_hit(tmpunit->type, u2)
2062 /* Also consider damage by fire. Moreover, if the unit can
2063 carry occupants they may be vulnerable even though the
2064 unit itself is not. */
2065 && (uu_damage(tmpunit->type, u2) > 0
2066 || fire_damage(tmpunit->type, u2) > 0
2067 || type_can_have_occs(u2))
2068 /* and have correct ammo */
2069 && !side_planning_to_capture_type_at(side, u2, x, y)
2070 ) {
2071 rating = uu_zz_bfw(tmpunit->type, u2);
2072 /* Further-away units are less interesting than closer ones. */
2073 dist = distance(tmpunit->x, tmpunit->y, x, y);
2074 if (dist > 0)
2075 rating /= dist;
2076 /* A larger city is more worth capturing. */
2077 rating *= uview->size;
2078 /* Real enemies are more important targets. */
2079 if (oside != NULL)
2080 rating *= 2;
2081 /* Always attack units that threaten one of our own cities. */
2082 for_all_side_units(side, unit3) {
2083 if (u_advanced(unit3->type)
2084 && distance(unit3->x, unit3->y, x, y) < 5)
2085 rating *= 5 - distance(unit3->x, unit3->y, x, y);
2086 }
2087 if (rating > target_rating
2088 || (rating == target_rating && flip_coin())) {
2089 target_x = x; target_y = y;
2090 target_rating = rating;
2091 target_utype = u2;
2092 target_sidenum = side_number(oside);
2093 }
2094 }
2095 }
2096 return FALSE;
2097 }
2098
2099 int
2100 fire_at_opportunity(Unit *unit)
2101 {
2102 int x, y, range, rslt;
2103
2104 tmpunit = unit;
2105 range = u_range(unit->type);
2106 /* Look further for targets if we are mobile. */
2107 if (mobile(unit->type)) {
2108 range += u_ai_tactical_range(unit->type);
2109 }
2110 target_rating = -9999;
2111 DMprintf("%s seeking target within %d; found ",
2112 unit_desig(unit), range);
2113 rslt = search_around(unit->x, unit->y, range, target_here, &x, &y, 1);
2114 if (rslt) {
2115 DMprintf("s%d %s at %d,%d\n",
2116 target_sidenum, u_type_name(target_utype), x, y);
2117 /* Set up a task to shoot at the unit found. */
2118 set_hit_unit_task(unit, x, y, target_utype, target_sidenum);
2119 } else if (target_rating > -9999) {
2120 DMprintf("s%d %s (rated %d) at %d,%d\n",
2121 target_sidenum, u_type_name(target_utype), target_rating,
2122 x, y);
2123 /* Set up a task to shoot at the unit found. */
2124 set_hit_unit_task(unit, target_x, target_y, target_utype, target_sidenum);
2125 } else {
2126 DMprintf("nothing\n");
2127 }
2128 return rslt;
2129 }
2130
2131 /* Find a unit task by task type. */
2132
2133 Task *
2134 find_unit_task_by_type(Unit *unit, TaskType tt)
2135 {
2136 Task *task = NULL;
2137
2138 if (in_play(unit) && unit->plan) {
2139 for_all_tasks(unit->plan, task) {
2140 if (task->type == tt)
2141 return task;
2142 }
2143 }
2144 return NULL;
2145 }
2146
2147 /* Check to see if our grand plans are at risk of being sideswiped by lack of
2148 supply, and set up a resupply task if so. */
2149
2150 int
2151 resupply_if_low(Unit *unit)
2152 {
2153 int m;
2154
2155 /* Check if any supplies are below the trigger level. */
2156 m = low_on_supplies_one(unit);
2157 if (m != NONMTYPE) {
2158 return plan_resupply(unit, m);
2159 }
2160 return FALSE;
2161 }
2162
2163 /* Return a type of essential material that the unit is running out of. */
2164
2165 int
2166 low_on_supplies_one(Unit *unit)
2167 {
2168 int u = unit->type, m;
2169
2170 for_all_material_types(m) {
2171 if ((um_base_consumption(u, m) > 0
2172 || um_consumption_per_move(u, m) > 0)
2173 && um_storage_x(u, m) > 0
2174 && unit->supply[m] <=
2175 (unit_doctrine(unit)->resupply_percent * um_storage_x(u, m))
2176 / 100) {
2177 return m;
2178 }
2179 }
2180 return NONMTYPE;
2181 }
2182
2183 int
2184 rearm_if_low(Unit *unit)
2185 {
2186 int m;
2187
2188 /* Check if any ammo is below the trigger level. */
2189 m = low_on_ammo_one(unit);
2190 if (m != NONMTYPE) {
2191 return plan_resupply(unit, m);
2192 }
2193 return FALSE;
2194 }
2195
2196 /* Return a type of material that we want to use to attack or fire. */
2197
2198 int
2199 low_on_ammo_one(Unit *unit)
2200 {
2201 int u = unit->type, m, trigger;
2202
2203 for_all_material_types(m) {
2204 /* Skip materials that we cannot store. */
2205 if (um_storage_x(u, m) <= 0) {
2206 continue;
2207 }
2208 /* Compute the rearming trigger level. */
2209 trigger =
2210 (unit_doctrine(unit)->rearm_percent * um_storage_x(u, m)) / 100;
2211 if (could_fire_at_any(u)) {
2212 /* First consider consumables (ammo). */
2213 if ((um_consumption_per_fire(u, m) > 0
2214 /* Used incorrectly instead of um_consumption_per_fire in
2215 some games (should fix this). */
2216 || um_consumption_per_attack(u, m) > 0)
2217 /* We are low either if we are below the trigger,
2218 or if we are unable to fire one more round. */
2219 && unit->supply[m] <=
2220 max(
2221 um_consumption_per_fire(u, m) > 0 ?
2222 um_consumption_per_fire(u, m) :
2223 um_consumption_per_attack(u, m), trigger)) {
2224 return m;
2225 }
2226 /* Then consider special materials (tools & weapons). */
2227 if (um_to_fire(u, m) > 0
2228 /* We are low either if we are below the trigger,
2229 or if we are unable to fire at all. */
2230 && unit->supply[m] <= max(um_to_fire(u, m), trigger)) {
2231 return m;
2232 }
2233 }
2234 if (could_attack_any(u)) {
2235 /* First consider consumables (ammo). */
2236 if (um_consumption_per_attack(u, m) > 0
2237 /* We are low either if we are below the trigger,
2238 or if we are unable to fire one more round. */
2239 && unit->supply[m] <=
2240 max(um_consumption_per_attack(u, m), trigger)) {
2241 return m;
2242 }
2243 /* Then consider special materials (tools & weapons). */
2244 if (um_to_attack(u, m) > 0
2245 /* We are low either if we are below the trigger,
2246 or if we are unable to fire at all. */
2247 && unit->supply[m] <= max(um_to_attack(u, m), trigger)) {
2248 return m;
2249 }
2250 }
2251 }
2252 return NONMTYPE;
2253 }
2254
2255 /* Now common code called by resupply_if_low and rearm_if_low. This is
2256 really a plan since it sets various tasks. */
2257
2258 int
2259 plan_resupply(Unit *unit, int m)
2260 {
2261 int u = unit->type, x = unit->x, y = unit->y, x1, y1, outcome, range;
2262 Task *curtask = unit->plan->tasks;
2263
2264 /* Set up a resupply task if extraction of material from the
2265 terrains is possible. */
2266 if (valid(check_extract_action(unit, unit, x, y, m, 1))) {
2267 set_resupply_task(unit, m);
2268 outcome = execute_task(unit);
2269 /* Clear the task outcome if we failed so that we do not block
2270 repair tasks or normal plan execution. Also clear the agenda
2271 so that we do not execute the failed task again. */
2272 if (outcome == TASK_FAILED) {
2273 clear_task_agenda(unit);
2274 clear_task_outcome(unit);
2275 return FALSE;
2276 } else {
2277 return TRUE;
2278 }
2279 }
2280 /* Setting up other types of resupply tasks makes sense only for mobile
2281 units. A non-mobile unit cannot move to a supply source, and if supplies
2282 are available in the same cell, it will benefit from this even if it is
2283 doing something else such as building. */
2284 if (!mobile(u)) {
2285 return FALSE;
2286 }
2287 /* Now set up things to look for resupply of firstlowm. */
2288 range = real_operating_range_best(unit);
2289 tmpside = unit->side;
2290 tmpunit = unit;
2291 if (lowm == NULL) {
2292 lowm = (int *) xmalloc(nummtypes * sizeof(int));
2293 }
2294 lowm[0] = m;
2295 numlow = 1;
2296 /* Try resupplying in place.
2297 This is preferable to moving to a resupply point. */
2298 if (can_resupply_from_here(unit->x, unit->y)
2299 || (can_survive_on_known(unit->type, unit->side, unit->x, unit->y)
2300 && can_auto_resupply_self(unit, lowm, numlow))) {
2301 set_resupply_task(unit, m);
2302 /* Execute the new resupply task. */
2303 outcome = execute_task(unit);
2304 }
2305 /* See if we are already moving to a supply source. */
2306 else if (curtask != NULL
2307 && curtask->type == TASK_MOVE_TO
2308 && can_resupply_from_here(curtask->args[0], curtask->args[1])) {
2309 /* Proceed with task execution. */
2310 outcome = execute_task(unit);
2311 /* See if we already have a resupply task for the same material. */
2312 } else if (curtask != NULL
2313 && curtask->type == TASK_RESUPPLY
2314 && curtask->args[0] == m) {
2315 /* Proceed with task execution. */
2316 outcome = execute_task(unit);
2317 /* Otherwise set up a task. */
2318 } else if (search_around(x, y, range, can_resupply_from_here,
2319 &x1, &y1, 1)) {
2320 DMprintf("%s low on %s, found resupply point at %d,%d.\n",
2321 unit_desig(unit), m_type_name(m), x1, y1);
2322 set_resupply_task(unit, m);
2323 /* Execute the new resupply task. */
2324 outcome = execute_task(unit);
2325 /* Return, but don't clear the task outcome or the agenda if a
2326 resupply task could not be set, so that execution of any existing
2327 task in the queue may proceed. */
2328 } else {
2329 DMprintf("%s low on %s, found no resupply point within range %d.\n",
2330 unit_desig(unit), m_type_name(m), range);
2331 return FALSE;
2332 }
2333 /* Clear the task outcome if we failed so that we do not block
2334 repair tasks or normal plan execution. Also clear the agenda
2335 so that we do not execute the failed task again. */
2336 if (outcome == TASK_FAILED) {
2337 clear_task_agenda(unit);
2338 clear_task_outcome(unit);
2339 return FALSE;
2340 } else {
2341 return TRUE;
2342 }
2343 }
2344
2345 int
2346 repair_if_damaged(Unit *unit)
2347 {
2348 int u = NONUTYPE;
2349 Task *tasks = NULL;
2350 int hpgoal = -1;
2351
2352 assert_error(in_play(unit),
2353 "AI: Attempted to assess damage to out-of-play unit");
2354 u = unit->type;
2355 hpgoal = (u_hp(u) * unit_doctrine(unit)->repair_complete) / 100;
2356 // Is any repair needed?
2357 if (unit->hp
2358 >= (u_hp_max(u) * unit_doctrine(unit)->repair_percent) / 100)
2359 return FALSE;
2360 // Check if we are already working on repair or resupply,
2361 // and do not interfere if so.
2362 tasks = (unit->plan ? unit->plan->tasks : NULL);
2363 if (tasks) {
2364 if ((TASK_MOVE_TO == tasks->type) || (TASK_OCCUPY == tasks->type))
2365 tasks = tasks->next;
2366 if (tasks) {
2367 if (TASK_RESUPPLY == tasks->type)
2368 return FALSE;
2369 if ((TASK_REPAIR == tasks->type) && (unit->id != tasks->args[0]))
2370 return FALSE;
2371 }
2372 }
2373 // Set repair task on self, if one does not exist already.
2374 if (!tasks || (TASK_REPAIR != tasks->type))
2375 set_repair_task(unit, unit->id, hpgoal);
2376 // Execute the new or existing task.
2377 if (TASK_FAILED != execute_task(unit))
2378 return TRUE;
2379 return FALSE;
2380 }
2381
2382 #if (0)
2383 int
2384 plan_repair(Unit *unit)
2385 {
2386 int u = unit->type, x = unit->x, y = unit->y, x1, y1, outcome, range;
2387 Task *curtask = unit->plan->tasks;
2388
2389 /* Set up a repair task if explicit self-repair if possible.
2390 The task code will then set up a self-repair action, but we
2391 will in addition to this benefit from any auto-repair that is
2392 available in the cell. */
2393 if (valid(check_repair_action(unit, unit, unit))) {
2394 set_repair_task(unit);
2395 outcome = execute_task(unit);
2396 /* Clear the task outcome if we failed so that we do not block
2397 normal plan execution. Also clear the agenda so that we do not
2398 execute the failed task again. */
2399 if (outcome == TASK_FAILED) {
2400 clear_task_agenda(unit);
2401 clear_task_outcome(unit);
2402 return FALSE;
2403 } else {
2404 return TRUE;
2405 }
2406 }
2407 /* Setting up other types of repair tasks makes sense only for mobile
2408 units. A non-mobile unit cannot move to a repair point, and if recovery
2409 or auto-repair is available in its cell,
2410 it will benefit from this,
2411 even if it is doing something else such as building. */
2412 if (!mobile(u)) {
2413 return FALSE;
2414 }
2415 range = real_operating_range_best(unit);
2416 tmpunit = unit;
2417 /* Also set up a repair task if we have self recovery. */
2418 if (u_hp_recovery(u) > 0) {
2419 set_repair_task(unit);
2420 outcome = execute_task(unit);
2421 /* Then test if we already have a repair task. */
2422 } else if (curtask != NULL
2423 && curtask->type == TASK_REPAIR) {
2424 /* Proceed with task execution. */
2425 outcome = execute_task(unit);
2426 /* See if we are already moving to a repair point. */
2427 } else if (curtask != NULL
2428 && curtask->type == TASK_MOVE_TO
2429 && can_repair_from_here(curtask->args[0], curtask->args[1])) {
2430 /* Proceed with task execution. */
2431 outcome = execute_task(unit);
2432 /* Otherwise set up a repair task. */
2433 } else if (search_around(x, y, range, can_repair_from_here, &x1, &y1, 1)) {
2434 DMprintf("%s damaged, found repair point at %d,%d.\n",
2435 unit_desig(unit), x1, y1);
2436 set_repair_task(unit);
2437 /* Execute the new resupply task. */
2438 outcome = execute_task(unit);
2439 /* Return, but don't clear the task outcome or the agenda if a
2440 repair task could not be set, so that execution of any existing
2441 task in the queue may proceed. */
2442 } else {
2443 DMprintf("%s damaged, found no repair point within range %d.\n",
2444 unit_desig(unit), range);
2445 return FALSE;
2446 }
2447 /* Clear the task outcome if we failed so that we do not block
2448 normal plan execution. Also clear the agenda so that we do not
2449 execute the failed task again. */
2450 if (outcome == TASK_FAILED) {
2451 clear_task_agenda(unit);
2452 clear_task_outcome(unit);
2453 return FALSE;
2454 } else {
2455 return TRUE;
2456 }
2457 }
2458 #endif
2459
2460 /* Look within a limited distance for any independent unit that could be
2461 captured, and set up tasks to go get it. */
2462
2463 int
2464 capture_indep_if_nearby(Unit *unit)
2465 {
2466 /* Use the unit's tactical range instead. */
2467 int u = unit->type;
2468 int range = u_ai_tactical_range(u);
2469 int x, y, rslt;
2470 Task *curtask = unit->plan->tasks;
2471
2472 if (!mobile(u))
2473 return FALSE;
2474 if (!could_capture_any(unit->type))
2475 return FALSE;
2476 tmpunit = unit;
2477 /* See if we're already doing such a task. */
2478 if (curtask != NULL
2479 && ((curtask->type == TASK_MOVE_TO
2480 && indep_captureable_here(curtask->args[0], curtask->args[1]))
2481 || (curtask->type == TASK_CAPTURE
2482 && indep_captureable_here(curtask->args[0], curtask->args[1]))))
2483 return FALSE;
2484 DMprintf("%s searching for easy capture within %d; found ",
2485 unit_desig(unit), range);
2486 rslt = search_around(unit->x, unit->y, range, indep_captureable_here,
2487 &x, &y, 1);
2488 if (rslt) {
2489 DMprintf("one at %d,%d\n", x, y);
2490 /* Set up a task to go after the unit found. */
2491 set_capture_task(unit, x, y, tmputype, tmpside->id);
2492 if (unit->transport
2493 && mobile(unit->transport->type)
2494 && unit->transport->plan) {
2495 set_move_to_task(unit->transport, x, y, 1);
2496 }
2497 return (execute_task(unit) != TASK_FAILED);
2498 } else {
2499 DMprintf("nothing\n");
2500 }
2501 return FALSE;
2502 }
2503
2504 int
2505 indep_captureable_here(int x, int y)
2506 {
2507 int u2;
2508 Side *side = tmpunit->side, *side2;
2509 UnitView *uview;
2510
2511 /* Should perhaps iterate over occs also, so that they can be
2512 captured even if the transport is not? */
2513 for_all_view_stack(side, x, y, uview) {
2514 u2 = uview->type;
2515 side2 = side_n(uview->siden);
2516 if (side2 == indepside
2517 && side != indepside
2518 && capture_chance(tmpunit->type, u2, side2) > 10) {
2519 tmputype = u2;
2520 tmpside = side2;
2521 return TRUE;
2522 }
2523 }
2524 return FALSE;
2525 }
2526
2527 /* Look within a limited distance for any type of unit that would be
2528 good to own, and set up tasks to go get it. */
2529
2530 int
2531 capture_useful_if_nearby(Unit *unit)
2532 {
2533 /* Use the unit's tactical range instead. */
2534 int u = unit->type;
2535 int range = u_ai_tactical_range(u);
2536 int x, y, rslt;
2537 Task *curtask = unit->plan->tasks;
2538
2539 if (!mobile(u))
2540 return FALSE;
2541 if (!could_capture_any(unit->type))
2542 return FALSE;
2543 tmpunit = unit;
2544 /* See if we're already doing such a task. */
2545 if (curtask != NULL
2546 && ((curtask->type == TASK_MOVE_TO
2547 && useful_captureable_here(curtask->args[0], curtask->args[1]))
2548 || (curtask->type == TASK_CAPTURE
2549 && useful_captureable_here(curtask->args[0], curtask->args[1]))))
2550 return FALSE;
2551 DMprintf("%s searching for useful capture within %d; found ",
2552 unit_desig(unit), range);
2553 rslt = search_around(unit->x, unit->y, range, useful_captureable_here,
2554 &x, &y, 1);
2555 if (rslt) {
2556 DMprintf("one at %d,%d\n", x, y);
2557 /* Set up a task to go after the unit found. */
2558 if (g_combat_model() == 0) {
2559 if (!could_capture_by_capture(unit->type, tmputype, tmpside))
2560 set_hit_unit_task(unit, x, y, tmputype, tmpside->id);
2561 else
2562 set_capture_task(unit, x, y, tmputype, tmpside->id);
2563 }
2564 else if (g_combat_model() == 1) {
2565 set_hit_unit_task(unit, x, y, tmputype, tmpside->id);
2566 }
2567 if (unit->transport
2568 && mobile(unit->transport->type)
2569 && unit->transport->plan) {
2570 set_move_to_task(unit->transport, x, y, 1);
2571 }
2572 return (execute_task(unit) != TASK_FAILED);
2573 } else {
2574 DMprintf("nothing\n");
2575 }
2576 return FALSE;
2577 }
2578
2579 int
2580 useful_captureable_here(int x, int y)
2581 {
2582 int u2;
2583 Side *side = tmpunit->side, *side2;
2584 UnitView *uview;
2585
2586 /* Should perhaps iterate over occs also, so that they can be
2587 captured even if the transport is not? */
2588 for_all_view_stack(side, x, y, uview) {
2589 u2 = uview->type;
2590 side2 = side_n(uview->siden);
2591 if (!trusted_side(side, side2)
2592 && capture_chance(tmpunit->type, u2, side2) > 0
2593 && useful_type(side, u2)
2594 ) {
2595 tmputype = u2;
2596 tmpside = side2;
2597 return TRUE;
2598 }
2599 }
2600 return FALSE;
2601 }
2602
2603 /* Return true if the given type of unit is useful in some way to the
2604 given side. This is almost always true. */
2605
2606 int
2607 useful_type(Side *side, int u)
2608 {
2609 if (!type_allowed_on_side(u, side))
2610 return FALSE;
2611 return TRUE;
2612 }
2613
2614 #if (0)
2615 int
2616 could_capture_any(int u)
2617 {
2618 int u2;
2619
2620 for_all_unit_types(u2) {
2621 if (uu_capture(u, u2) > 0 || uu_indep_capture(u, u2) > 0)
2622 return TRUE;
2623 /* also check if u2 in game, on other side, etc? */
2624 }
2625 return FALSE;
2626 }
2627 #endif
2628
2629 /* This attempts to make some vaguely plausible arguments for an
2630 action, using the types of each arg as a guide. It also generates
2631 *invalid* arguments occasionally, which tests error checking in the
2632 actions' code. This is mainly useful for testing. */
2633
2634 void
2635 make_plausible_random_args(char *argtypestr, int i, int *args, Unit *unit)
2636 {
2637 char argch;
2638 int slen, arg;
2639
2640 slen = strlen(argtypestr);
2641 while (i < slen && i < 10) {
2642 argch = argtypestr[i];
2643 switch (argch) {
2644 case 'n':
2645 arg = (flip_coin() ? xrandom(10) :
2646 (flip_coin() ? xrandom(100) :
2647 (xrandom(20000) - 10000)));
2648 break;
2649 case 'u':
2650 /* Go a little outside range, so as to get some invalid types. */
2651 arg = xrandom(numutypes + 2) - 1;
2652 break;
2653 case 'm':
2654 arg = xrandom(nummtypes + 2) - 1;
2655 break;
2656 case 't':
2657 arg = xrandom(numttypes + 2) - 1;
2658 break;
2659 case 'a':
2660 arg = xrandom(numatypes + 2) - 1;
2661 break;
2662 case 'x':
2663 arg = (unit != NULL && flip_coin() ? (unit->x + xrandom(5) - 2) :
2664 (xrandom(area.width + 4) - 2));
2665 break;
2666 case 'y':
2667 arg = (unit != NULL && flip_coin() ? (unit->y + xrandom(5) - 2) :
2668 (xrandom(area.height + 4) - 2));
2669 break;
2670 case 'z':
2671 arg = (flip_coin() ? 0 : xrandom(10));
2672 break;
2673 case 'd':
2674 arg = random_dir();
2675 break;
2676 case 'U':
2677 /* Cast around for a valid unit. */
2678 while (find_unit(arg = xrandom(numunits)+1) == NULL
2679 && probability(98)) {
2680 ;
2681 }
2682 break;
2683 case 'S':
2684 arg = xrandom(numsides + 3) - 1;
2685 break;
2686 default:
2687 run_warning("Garbled action arg type '%c'\n", argch);
2688 arg = 0;
2689 break;
2690 }
2691 args[i++] = arg;
2692 }
2693 }
2694
2695 /* Random walking just attempts to move around. */
2696
2697 void
2698 random_walk(Unit *unit)
2699 {
2700 int dir = random_dir(), x1, y1, tries = 0;
2701
2702 while (!interior_point_in_dir(unit->x, unit->y, dir, &x1, &y1)) {
2703 if (++tries > 500) {
2704 run_warning("something is wrong");
2705 break;
2706 }
2707 dir = random_dir();
2708 }
2709 set_move_to_task(unit, x1, y1, 0);
2710 }
2711
2712 /* Record the unit as waiting for orders about what to do. */
2713
2714 void
2715 set_waiting_for_tasks(Unit *unit, int flag)
2716 {
2717 if (unit->plan->waitingfortasks == flag) {
2718 return;
2719 }
2720 unit->plan->waitingfortasks = flag;
2721 if (unit->side != NULL) {
2722 unit->side->numwaiting += (unit->plan->waitingfortasks ? 1 : -1);
2723 update_unit_display(unit->side, unit, FALSE);
2724 }
2725 }
2726
2727 /* General routine to wake a unit up (and maybe all its cargo). */
2728
2729 void
2730 wake_unit(Side *side, Unit *unit, int forcewakeoccs)
2731 {
2732 Unit *occ;
2733
2734 assert_warning_return(in_play(unit),
2735 "Attempted to wake an invalid unit.", );
2736 /* (should test that side is permitted to wake) */
2737 /* Wake the unit. */
2738 if (unit->plan) {
2739 unit->plan->asleep = FALSE;
2740 unit->plan->reserve = FALSE;
2741 update_unit_display(side, unit, TRUE);
2742 }
2743 /* Try waking any occupants. */
2744 if (forcewakeoccs) {
2745 for_all_occupants(unit, occ)
2746 wake_unit(side, occ, forcewakeoccs);
2747 }
2748 }
2749
2750 /* Conditionally attempt to wake up a unit based on restrictions on recursive
2751 waking. */
2752
2753 void
2754 selectively_wake_unit(Side *side, Unit *unit, int wakeoccs, int forcewakeoccs)
2755 {
2756 Unit *occ = NULL;
2757
2758 assert_warning_return(in_play(unit),
2759 "Attempted to wake an invalid unit.", );
2760 /* If on a transport, then probably transport already
2761 has awakened us, but just to cover all the cases... */
2762 if ((unit->transport)
2763 && uu_can_recursively_wake(unit->transport->type,
2764 unit->type))
2765 wake_unit(unit->side, unit, forcewakeoccs);
2766 /* Else not on a transport. */
2767 else if (!(unit->transport))
2768 wake_unit(unit->side, unit, forcewakeoccs);
2769 /* Now wake any occs, if so desired. */
2770 /* Note that if 'forcewakeoccs' is set, then 'wake_unit' will
2771 recursively wake _all_ occs, and so the following code would be
2772 somewhat redundant. */
2773 /* (Need valid test case for this, so that it can be tested and
2774 enabled.) */
2775 #if (0)
2776 if (!forcewakeoccs && wakeoccs) {
2777 for_all_occupants(unit, occ) {
2778 if ((unit->side == occ->side)
2779 && uu_can_recursively_wake(unit->type, occ->type))
2780 selectively_wake_unit(unit->side, occ, TRUE, FALSE);
2781 }
2782 }
2783 #endif
2784 }
2785
2786 /* The area wakeup. */
2787
2788 static int tmpflag;
2789
2790 static void
2791 wake_at(int x, int y)
2792 {
2793 Unit *unit;
2794
2795 for_all_stack(x, y, unit) {
2796 if (side_controls_unit(tmpside, unit)) {
2797 wake_unit(tmpside, unit, tmpflag);
2798 }
2799 }
2800 }
2801
2802 void
2803 wake_area(Side *side, int x, int y, int n, int occs)
2804 {
2805 tmpside = side;
2806 tmpflag = occs;
2807 apply_to_area(x, y, n, wake_at);
2808 }
2809
2810 void
2811 set_formation(Unit *unit, Unit *leader, int x, int y, int dist, int flex)
2812 {
2813 Plan *plan = unit->plan;
2814 Goal *goal;
2815
2816 if (plan == NULL)
2817 return;
2818 if (!in_play(unit))
2819 return;
2820 if (leader != NULL) {
2821 if (!in_play(leader))
2822 return;
2823 goal = create_goal(GOAL_KEEP_FORMATION, unit->side, TRUE);
2824 goal->args[0] = leader->id;
2825 goal->args[1] = x; goal->args[2] = y;
2826 goal->args[3] = dist;
2827 goal->args[4] = flex;
2828 plan->formation = goal;
2829 plan->funit = leader;
2830 } else {
2831 /* A NULL leader means to clear the formation goal. */
2832 plan->formation = NULL;
2833 plan->funit = NULL;
2834 }
2835 }
2836
2837 void
2838 delay_unit(Unit *unit, int flag)
2839 {
2840 if (in_play(unit) && unit->plan) {
2841 unit->plan->delayed = flag;
2842 }
2843 }
2844
2845 #if 0 /* The four functions below are not used anywhere. */
2846
2847 /* Return the distance that we can go by shortest path before running out
2848 of important supplies. Will return at least 1, since we can *always*
2849 move one cell to safety. This is a worst-case routine, too complicated
2850 to worry about units getting refreshed by terrain or whatever. */
2851
2852 int
2853 range_left(Unit *unit)
2854 {
2855 int u = unit->type, m, least = 12345; /* bigger than any real value */
2856
2857 for_all_material_types(m) {
2858 if (um_consumption_per_move(u, m) > 0) {
2859 least = min(least, unit->supply[m] / um_consumption_per_move(u, m));
2860 }
2861 #if 0
2862 /* This code is too pessimistic if no account taken of supply line or
2863 production, so leave out for now. */
2864 if (um_base_consumption(u, m) > 0) {
2865 tmp = (type_max_speed(u) * unit->supply[m]) /
2866 um_base_consumption(u, m);
2867 least = min(least, tmp);
2868 }
2869 #endif
2870 }
2871 return (least == 12345 ? 1 : least);
2872 }
2873
2874 /* Estimate the goodness and badness of cells in the immediate vicinity. */
2875
2876 int
2877 find_worths(range)
2878 int range;
2879 {
2880 return 0;
2881 }
2882
2883 /* This is a heuristic estimation of the value of one unit type
2884 hitting on another. Should take cost of production into account as
2885 well as the chance and significance of any effect. */
2886
2887 int
2888 attack_worth(unit, e)
2889 Unit *unit;
2890 int e;
2891 {
2892 int u = unit->type, worth;
2893
2894 worth = uu_zz_bhw(u, e);
2895 /* Risk of death? */
2896 /* if (uu_damage(e, u) >= unit->hp)
2897 worth /= (could_capture(u, e) ? 1 : 4);
2898 if (could_capture(u, e)) worth *= 4; */
2899 return worth;
2900 }
2901
2902 /* Support functions. */
2903
2904 /* Return true if the given position is threatened by the given unit type. */
2905
2906 int
2907 threat(Side *side, int u, int x0, int y0)
2908 {
2909 #if 0
2910 int d, x, y, thr = 0;
2911 Side *side2;
2912 int view;
2913
2914 for_all_directions(d) {
2915 point_in_dir(x0, y0, d, &x, &y);
2916 view = 0 /* side_view(side, x, y) */;
2917 if (view != UNSEEN && view != EMPTY) {
2918 side2 = side_n(vside(view));
2919 if (allied_side(side, side2)) {
2920 if (uu_capture(u, vtype(view)) > 0) thr += 1000;
2921 if (uu_zz_bhw(u, vtype(view)) > 0) thr += 100;
2922 }
2923 }
2924 }
2925 return thr;
2926 #endif
2927 return 0;
2928 }
2929
2930 #endif
2931
2932 void
2933 pop_task(Plan *plan)
2934 {
2935 Task *oldtask;
2936
2937 if (plan->tasks) {
2938 oldtask = plan->tasks;
2939 plan->tasks = plan->tasks->next;
2940 free_task(oldtask);
2941 }
2942 }
2943
2944 #if 0 /* The two functions below are unused. */
2945
2946 /* Patrol just does move_to, but cycling waypoints around when the first */
2947 /* one has been reached. */
2948
2949 int
2950 move_patrol(Unit *unit)
2951 {
2952 #if 0
2953 int tx, ty;
2954
2955 if (unit->plan->orders.rept-- > 0) {
2956 if (unit->x == unit->plan->orders.p.pt[0].x &&
2957 unit->y == unit->plan->orders.p.pt[0].y) {
2958 tx = unit->plan->orders.p.pt[0].x;
2959 ty = unit->plan->orders.p.pt[0].y;
2960 unit->plan->orders.p.pt[0].x = unit->plan->orders.p.pt[1].x;
2961 unit->plan->orders.p.pt[0].y = unit->plan->orders.p.pt[1].y;
2962 unit->plan->orders.p.pt[1].x = tx;
2963 unit->plan->orders.p.pt[1].y = ty;
2964 }
2965 return move_to(unit, unit->plan->orders.p.pt[0].x, unit->plan->orders.p.pt[0].y,
2966 (unit->plan->orders.flags & SHORTESTPATH));
2967 }
2968 #endif
2969 return TRUE;
2970 }
2971
2972 /* Basic routine to compute how long a unit will take to build something. */
2973
2974 int
2975 build_time(Unit *unit, int prod)
2976 {
2977 int schedule = 1 /* uu_make(unit->type, prod) */;
2978 int u, develop_delay = 0;
2979
2980 /* Add penalty (or unpenalty!) for first unit of a type. */
2981 /* is "counts" a reliable way to test? */
2982 if (unit->side->counts[prod] <= 1) {
2983 /* develop_delay = ((schedule * u_develop(prod)) / 100); */
2984 for_all_unit_types(u) {
2985 if (unit->side->counts[u] > 1) {
2986 develop_delay -=
2987 (1 /*uu_make(unit->type, u)*/ *
2988 uu_tech_crossover(prod, u)) / 100;
2989 }
2990 if (develop_delay > 0) {
2991 schedule += develop_delay;
2992 }
2993 }
2994 }
2995 return schedule;
2996 }
2997
2998 #endif
2999
3000 int
3001 clear_task_agenda(Unit *unit)
3002 {
3003 Plan *plan = unit->plan;
3004 int numcleared;
3005 Task *oldtask;
3006
3007 if (plan == NULL || plan->tasks == NULL)
3008 return 0;
3009 numcleared = 0;
3010 while (plan->tasks != NULL) {
3011 oldtask = plan->tasks;
3012 plan->tasks = plan->tasks->next;
3013 free_task(oldtask);
3014 ++numcleared;
3015 }
3016 return numcleared;
3017 }
3018
3019 void
3020 clear_task_outcome(Unit *unit)
3021 {
3022 if (unit->plan == NULL)
3023 run_error("no plan here?");
3024 unit->plan->last_task_outcome = TASK_UNKNOWN;
3025 }
3026
3027 Plan *
3028 create_plan(void)
3029 {
3030 Plan *plan = (Plan *) xmalloc(sizeof(Plan));
3031 return plan;
3032 }
3033
3034 void
3035 free_plan(Unit *unit)
3036 {
3037 Plan *plan = unit->plan;
3038
3039 if (plan == NULL)
3040 run_error("no plan here?");
3041 /* Make tasks available for reallocation. */
3042 clear_task_agenda(unit);
3043 free(plan);
3044 }
3045
3046 /* Describe a plan succinctly. This is primarily for debugging, not
3047 for normal user display. */
3048
3049 char *planbuf = NULL;
3050
3051 char *
3052 plan_desig(Plan *plan)
3053 {
3054 Task *task;
3055 int extra = 0;
3056
3057 if (planbuf == NULL)
3058 planbuf = (char *)xmalloc(1000);
3059 if (plan == NULL) {
3060 sprintf(planbuf, "no plan");
3061 } else if (plan->type == PLAN_NONE) {
3062 sprintf(planbuf, "unformed plan");
3063 } else {
3064 if (plan->tasks) {
3065 tmpbuf[0] = '\0';
3066 for_all_tasks(plan, task) {
3067 if (strlen(tmpbuf) < 100) {
3068 strcat(tmpbuf, " ");
3069 strcat(tmpbuf, task_desig(task));
3070 } else {
3071 ++extra;
3072 }
3073 }
3074 if (extra > 0) {
3075 tprintf(tmpbuf, " ... %d more ...", extra);
3076 }
3077 } else {
3078 sprintf(tmpbuf, " no tasks");
3079 }
3080 sprintf(planbuf, "type %s %s",
3081 plantypenames[plan->type], goal_desig(plan->maingoal));
3082 if (plan->formation) {
3083 strcat(planbuf, " ");
3084 strcat(planbuf, goal_desig(plan->formation));
3085 }
3086 if (plan->asleep)
3087 strcat(planbuf, " asleep");
3088 if (plan->reserve)
3089 strcat(planbuf, " reserve");
3090 if (plan->delayed)
3091 strcat(planbuf, " delayed");
3092 if (plan->waitingfortasks)
3093 strcat(planbuf, " waiting");
3094 if (plan->supply_alarm)
3095 strcat(planbuf, " supply_alarm");
3096 if (plan->supply_is_low)
3097 strcat(planbuf, " supply_is_low");
3098 strcat(planbuf, tmpbuf);
3099 }
3100 return planbuf;
3101 }
3102
3103 /* True if unit is in immediate danger of being captured. */
3104 /* Needs check on capturer transport being seen. */
3105
3106 int
3107 might_be_captured(Unit *unit)
3108 {
3109 int d, x, y;
3110 Unit *unit2;
3111
3112 for_all_directions(d) {
3113 if (interior_point_in_dir(unit->x, unit->y, d, &x, &y)) {
3114 if (((unit2 = unit_at(x, y)) != NULL) &&
3115 (enemy_side(unit->side, unit2->side)) &&
3116 (uu_capture(unit2->type, unit->type) > 0))
3117 return TRUE;
3118 }
3119 }
3120 return FALSE;
3121 }
3122
3123 /* Clear a unit's plan out. */
3124
3125 void
3126 force_replan(Unit *unit)
3127 {
3128 extern int need_ai_planning;
3129
3130 if (unit->plan == NULL)
3131 return;
3132 unit->plan->type = PLAN_PASSIVE;
3133 clear_task_agenda(unit);
3134 unit->plan->maingoal = NULL;
3135 unit->plan->formation = NULL;
3136 unit->plan->funit = NULL;
3137 unit->plan->asleep = FALSE;
3138 unit->plan->reserve = FALSE;
3139 set_waiting_for_tasks(unit, FALSE);
3140 unit->plan->delayed = FALSE;
3141 unit->plan->last_task_outcome = TASK_UNKNOWN;
3142 need_ai_planning = TRUE;
3143 }
3144
3145 /* Auxiliary functions for unit planning in Xconq. */
3146
3147 /* router flags */
3148
3149 #define SAMEPATH 1
3150 #define EXPLORE_PATH 2
3151
3152 /* These macros are a cache used for planning purposes by machines. */
3153
3154 #define markloc(x, y) (set_tmp1_at(x, y, mark))
3155
3156 #define markedloc(x, y) (tmp1_at(x, y) == mark)
3157
3158 #define get_fromdir(x, y) (tmp2_at(x, y))
3159
3160 #define set_fromdir(x, y, dir) (set_tmp2_at(x, y, dir))
3161
3162 #define get_dist(x, y) (tmp3_at(x, y))
3163
3164 #define set_dist(x, y, d) (set_tmp3_at(x, y, d))
3165
3166 int
3167 occupant_could_capture(Unit *unit, int u2)
3168 {
3169 Unit *occ;
3170
3171 for_all_occupants(unit, occ)
3172 if (uu_capture(occ->type, u2) > 0)
3173 return TRUE;
3174 return FALSE;
3175 }
3176
3177 /* Check to see if there is anyone around to capture. */
3178
3179 int
3180 can_capture_neighbor(Unit *unit)
3181 {
3182 int d, x, y;
3183 Side *side2;
3184 UnitView *uview;
3185
3186 for_all_directions(d) {
3187 if (interior_point_in_dir(unit->x, unit->y, d, &x, &y)) {
3188 /* Should perhaps iterate over occs also, so that they can be
3189 captured even if the transport is not? */
3190 for_all_view_stack(unit->side, x, y, uview) {
3191 side2 = side_n(uview->siden);
3192 if (!allied_side(unit->side, side2)) {
3193 if (uu_capture(unit->type, uview->type) > 0) {
3194 /* need some other way to change move order quickly */
3195 return TRUE;
3196 }
3197 }
3198 }
3199 }
3200 }
3201 return FALSE;
3202 }
3203
3204 /* check if our first occupant can capture something. Doesn't look at
3205 other occupants. */
3206
3207 int
3208 occupant_can_capture_neighbor(Unit *unit)
3209 {
3210 Unit *occ = unit->occupant;
3211
3212 if (occ != NULL && has_acp_left(occ) && occ->side == unit->side) {
3213 if (can_capture_neighbor(occ)) {
3214 return TRUE;
3215 }
3216 }
3217 return FALSE;
3218 }
3219
3220 /* Find the closes unit, first prefering bases, and then transports. */
3221
3222 int
3223 find_closest_unit(Side *side, int x0, int y0, int maxdist, int (*pred)(int x, int y)/*pred*/, int *rxp, int *ryp)
3224 {
3225 #if 0
3226 Unit *unit;
3227 int u, dist;
3228 int found = FALSE;
3229
3230 for_all_unit_types(u) {
3231 if (u_is_base(u)) {
3232 for (unit = NULL /* side_strategy(side)->unitlist[u]*/; unit != NULL; unit = unit->mlist) {
3233 if (alive(unit) &&
3234 (dist = distance(x0, y0, unit->x, unit->y)) <= maxdist) {
3235 if ((*pred)(unit->x, unit->y)) {
3236 maxdist = dist - 1;
3237 *rxp = unit->x; *ryp = unit->y;
3238 found = TRUE;
3239 }
3240 }
3241 }
3242 }
3243 }
3244 if (found) {
3245 return TRUE;
3246 }
3247 for_all_unit_types(u) {
3248 if (!u_is_base(u) && u_is_transport(u)) {
3249 for (unit = NULL /*side_strategy(side)->unitlist[u]*/; unit != NULL; unit = unit->mlist) {
3250 if (alive(unit)
3251 && distance(x0, y0, unit->x, unit->y) <= maxdist) {
3252 if ((*pred)(unit->x, unit->y)) {
3253 maxdist = dist - 1;
3254 *rxp = unit->x; *ryp = unit->y;
3255 found = TRUE;
3256 }
3257 }
3258 }
3259 }
3260 }
3261 if (found) {
3262 return TRUE;
3263 }
3264 /* (What's the point of finding a non-base/non-transport?) */
3265 for_all_unit_types(u) {
3266 if (!u_is_base(u) && !u_is_transport(u)) {
3267 for (unit = NULL/*side_strategy(side)->unitlist[u]*/; unit != NULL; unit = unit->mlist) {
3268 if (alive(unit)
3269 && distance(x0, y0, unit->x, unit->y) <= maxdist) {
3270 if ((*pred)(unit->x, unit->y)) {
3271 maxdist = dist - 1;
3272 *rxp = unit->x; *ryp = unit->y;
3273 found = TRUE;
3274 }
3275 }
3276 }
3277 }
3278 }
3279 if (found) {
3280 return TRUE;
3281 }
3282 #endif
3283 return FALSE;
3284 }
3285
3286 #if 0 /* Unused. */
3287
3288 /* Returns the type of missing supplies. */
3289
3290 int
3291 out_of_ammo(Unit *unit)
3292 {
3293 int u = unit->type, m;
3294
3295 for_all_material_types(m) {
3296 if (um_consumption_per_attack(u, m) > 0 && unit->supply[m] <= 0)
3297 return m;
3298 }
3299 return (-1);
3300 }
3301
3302 #endif
3303
3304 #if 0 /* The two functions below are unused. */
3305 int
3306 usable_cell(Unit *unit, int x, int y)
3307 {
3308 int u = unit->type;
3309 UnitView *uview;
3310
3311 if (!could_live_on(u, terrain_at(x, y)))
3312 return FALSE;
3313 for_all_view_stack(unit->side, x, y, uview) {
3314 if (allied_side(uview->side, unit->side)
3315 && could_carry(uview->type, u))
3316 return TRUE;
3317 }
3318 return FALSE;
3319 }
3320
3321 Task *explorechain;
3322
3323 int
3324 explorable_cell(x, y)
3325 int x, y;
3326 {
3327 return (terrain_view(tmpside, x, y) == UNSEEN);
3328 }
3329
3330 #endif
3331
3332 /* Test whether the given location is an unknown cell that we can get
3333 next to. */
3334 /* (should consider testing "within vision range", but that might
3335 require LOS tests and be expensive) */
3336
3337 static int
3338 reachable_unknown(int x, int y)
3339 {
3340 /* Only interior cells are reachable. */
3341 if (!inside_area(x, y))
3342 return FALSE;
3343 if (terrain_view(tmpside, x, y) == UNSEEN) {
3344 if (adj_known_ok_terrain(x, y, tmpside, tmpunit->type)) {
3345 return TRUE;
3346 } else {
3347 return FALSE;
3348 }
3349 } else {
3350 return FALSE;
3351 }
3352 }
3353
3354 /* Test whether the given location has an adjacent cell that is ok for
3355 the given type to be out in the open. */
3356
3357 static int
3358 adj_known_ok_terrain(int x, int y, Side *side, int u)
3359 {
3360 int dir, x1, y1, t;
3361
3362 if (!inside_area(x, y))
3363 return FALSE;
3364 for_all_directions(dir) {
3365 if (interior_point_in_dir(x, y, dir, &x1, &y1)) {
3366 if (terrain_view(side, x1, y1) == UNSEEN)
3367 continue;
3368 t = terrain_at(x1, y1);
3369 if (!terrain_always_impassable(u, t))
3370 return TRUE;
3371 }
3372 }
3373 return FALSE;
3374 }
3375
3376 /* Go to the nearest cell that we can see how to get to. */
3377
3378 static int
3379 explore_reachable_cell(Unit *unit, int range)
3380 {
3381 int x, y;
3382
3383 if (g_see_all() || g_terrain_seen())
3384 return FALSE;
3385 tmpunit = unit;
3386 tmpside = unit->side;
3387 DMprintf("%s searching within %d for cell to explore -",
3388 unit_desig(unit), range);
3389 if (search_around(unit->x, unit->y, range, reachable_unknown, &x, &y, 1)) {
3390 set_move_to_task(unit, x, y, 1);
3391 DMprintf("found one at %d,%d\n", x, y);
3392 return TRUE;
3393 }
3394 DMprintf("found nothing\n");
3395 return FALSE;
3396 }
3397
3398 /* Estimate the usual number of turns to finish construction. */
3399
3400 int
3401 normal_completion_time(int u, int u2)
3402 {
3403 int acp = 0;
3404
3405 acp = type_max_acp(u); /* (Watch for performance issues here.) */
3406 if (acp == 0 || uu_cp_per_build(u, u2) == 0)
3407 return (-1);
3408 return (u_cp(u2) - uu_creation_cp(u, u2)) /
3409 (uu_cp_per_build(u, u2) * acp);
3410 }
3411
3412 /* Similar, but using a specific unit and also accounting for toolup
3413 time. */
3414
3415 int
3416 est_completion_time(Unit *unit, int u2)
3417 {
3418 int u, tooluptime, tp, acp;
3419
3420 u = unit->type;
3421 if (!could_create(u, u2))
3422 return (-1);
3423 acp = type_max_acp(u); /* (Watch for performance issues here.) */
3424 tooluptime = 0;
3425 tp = (unit->tooling ? unit->tooling[u2] : 0);
3426 if (tp < uu_tp_to_build(u, u2)) {
3427 if (uu_acp_to_toolup(u, u2) < 1
3428 || uu_tp_per_toolup(u, u2) <= 0
3429 || acp <= 0)
3430 return (-1);
3431 tooluptime = ((uu_tp_to_build(u, u2) - tp) * uu_acp_to_toolup(u, u2))
3432 / (uu_tp_per_toolup(u, u2) * acp);
3433 }
3434 return tooluptime + normal_completion_time(unit->type, u2);
3435 }
3436
3437 //! Maybe set side goal for a material, if needed for completion of u.
3438
3439 void
3440 maybe_set_materials_goal(Unit *unit, int u2)
3441 {
3442 int u = NONUTYPE;
3443 int m = NONMTYPE;
3444 Side *side = NULL;
3445 int conc = 0, cpb = 0;
3446 Goal *goal = NULL;
3447
3448 assert_error(in_play(unit), "Attempted to access an out-of-play unit");
3449 assert_error(is_unit_type(u2), "Attempted to use an invalid utype");
3450 u = unit->type;
3451 side = unit->side;
3452 for_all_material_types(m) {
3453 conc = um_consumption_on_creation(u2, m);
3454 cpb = um_consumption_per_built(u2, m);
3455 cpb += (um_consumption_per_cp(u2, m) * uu_cp_per_build(u, u2));
3456 if (((0 < conc) || (0 < cpb)) && side_has_treasury(side, m)
3457 && um_takes_from_treasury(u, m)) {
3458 goal = create_goal(GOAL_HAS_MATERIAL_TYPE, side, TRUE);
3459 goal->args[0] = m;
3460 goal->args[1] = 3 * conc + cpb;
3461 add_goal(side, goal);
3462 }
3463 }
3464 }
3465
3466 /* A unit runs low on supplies at the halfway point. Formula is the same
3467 no matter how/if occupants eat transports' supplies or whether
3468 unit can borrow from treasury. Unless we could put in reservations
3469 on the supplies, we would simply be making assumptions that might
3470 not bear out when it actually comes time to consume the supplies. */
3471
3472 int
3473 past_halfway_point(Unit *unit)
3474 {
3475 int u = unit->type, m = NONMTYPE, rsplypct = -1;
3476
3477 for_all_material_types(m) {
3478 if (((um_base_consumption(u, m) > 0)
3479 || (um_consumption_per_move(u, m) > 0))
3480 && (unit->transport == NULL)) {
3481 if (unit->side)
3482 rsplypct = unit_doctrine(unit)->resupply_percent;
3483 else
3484 rsplypct = 50;
3485 if (unit->supply[m] <= ((rsplypct * um_storage_x(u, m)) / 100))
3486 return TRUE;
3487 }
3488 }
3489 return FALSE;
3490 }
3491