1 /* Operational Roles and Analysis for AIs
2 Copyright (C) 2005 Eric A. McDonald.
3
4 Xconq is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2, or (at your option)
7 any later version. See the file COPYING. */
8
9 /*! \file
10 \brief Operational Roles and Analysis for AIs
11
12 Part of the AI API, Level 4.
13
14 Provides an operational level AI implementation.
15
16 \note Nothing in this file should be required for AI implementation.
17
18 */
19
20 #include "conq.h"
21 #include "kernel.h"
22 #include "kpublic.h"
23 #include "aiscore.h"
24 #include "aiunit.h"
25 #include "aiunit2.h"
26 #include "aitact.h"
27 #include "aioprt.h"
28
29 // NOTE: For now, we also include 'ai.h' for its theater stuff.
30 // TODO: We should probably move the theater stuff to this file.
31 #include "ai.h"
32
33 namespace Xconq {
34 namespace AI {
35
36 /* TEMPORARY: Side-related AI management.
37 Should be moved to new files in the future. */
38
39 //! Create/init master AI structure associated with a side.
40
41 void
create_side_ai(Side * side)42 create_side_ai(Side *side)
43 {
44 assert_error(side, "Attempted to access a NULL side");
45 side->master_ai = (AI_Side *)xmalloc(sizeof(AI_Side));
46 side->master_ai->side = side;
47 }
48
49 //! Return master AI structure associated with a side.
50
51 AI_Side *
get_side_ai(Side * side)52 get_side_ai(Side *side)
53 {
54 assert_error(side, "Attempted to access a NULL side");
55 return side->master_ai;
56 }
57
58 /* Management of operational roles. */
59
60 //! Allocate a new block of oproles in pool.
61
62 void
allocate_oproles(Side * side,int nodesnum)63 allocate_oproles(Side *side, int nodesnum)
64 {
65 AI_Side *ai = NULL;
66 OpRole *newblock = NULL;
67 int i = 0;
68
69 assert_error(side, "Attempted to access a NULL side");
70 assert_error(get_side_ai(side),
71 "Attempted to access a side without a master AI");
72 assert_error(0 < nodesnum,
73 "Attempted to allocate 0 or fewer operation roles");
74 ai = get_side_ai(side);
75 /* Allocate new block of oproles. */
76 newblock = (OpRole *)xmalloc(nodesnum * sizeof(OpRole));
77 /* Fill out new block of oprole info nodes. */
78 for (i = 0; i < (nodesnum - 1); ++i) {
79 newblock[i].next = &(newblock[i+1]);
80 }
81 /* If side's pool is empty, then assign block to it. */
82 if (!ai->oproles_free)
83 ai->oproles_free = newblock;
84 /* Else prepend new block to existing pool of free nodes. */
85 else {
86 newblock[nodesnum - 1].next = ai->oproles_free;
87 ai->oproles_free = newblock;
88 }
89 }
90
91 //! Acquire oprole.
92
93 OpRole *
acquire_oprole(Side * side,int id,OpRole_Type type)94 acquire_oprole(Side *side, int id, OpRole_Type type)
95 {
96 AI_Side *ai = NULL;
97 OpRole *oprole = NULL, *oprole2 = NULL;
98
99 assert_error(side, "Attempted to access a NULL side");
100 assert_error(get_side_ai(side),
101 "Attempted to access a side without a master AI");
102 ai = get_side_ai(side);
103 /* Allocate a new block of free nodes, if necessary. */
104 if (!ai->oproles || !ai->oproles_free)
105 allocate_oproles(side);
106 /* Get oprole. */
107 oprole = ai->oproles_free;
108 ai->oproles_free = oprole->next;
109 /* Insert it into correct location in side's oprole pool. */
110 for_all_oproles(side, oprole2) {
111 /* Somewhere in the list... */
112 if (oprole2->next
113 && between(oprole2->id, id, oprole2->next->id)) {
114 oprole->next = oprole2->next;
115 oprole2->next = oprole;
116 break;
117 }
118 /* At beginning of list... */
119 else if (oprole2->id > id) {
120 oprole->next = oprole2;
121 ai->oproles = oprole;
122 break;
123 }
124 /* At end of list... */
125 else if (!oprole2->next) {
126 oprole2->next = oprole;
127 oprole->next = NULL;
128 break;
129 }
130 }
131 /* Maybe the list was empty. Make node head of list, if so. */
132 if (!ai->oproles) {
133 ai->oproles = oprole;
134 oprole->next = NULL;
135 }
136 /* Allocate hash buckets, if needed. */
137 if (!ai->oprole_buckets)
138 ai->oprole_buckets =
139 (OpRole **)xmalloc(OR_Total * sizeof(OpRole *));
140 /* Find proper place in bucket. */
141 for_all_oproles_by_type(side, type, oprole2) {
142 /* Somewhere in the list... */
143 if (oprole2->next_by_type
144 && between(oprole2->id, id, oprole2->next_by_type->id)) {
145 oprole->next_by_type = oprole2->next_by_type;
146 oprole2->next_by_type = oprole;
147 break;
148 }
149 /* At beginning of list... */
150 else if (oprole2->id > id) {
151 oprole->next_by_type = oprole2;
152 ai->oprole_buckets[type] = oprole;
153 break;
154 }
155 /* At end of list... */
156 else if (!oprole2->next_by_type) {
157 oprole2->next_by_type = oprole;
158 oprole->next_by_type = NULL;
159 break;
160 }
161 }
162 /* Maybe the bucket was empty. Make node head of bucket, if so. */
163 if (!ai->oprole_buckets[type]) {
164 ai->oprole_buckets[type] = oprole;
165 oprole->next_by_type = NULL;
166 }
167 /* Finish filling out the oprole info node. */
168 oprole->id = id;
169 oprole->type = type;
170 oprole->side = side;
171 return oprole;
172 }
173
174 void
release_oprole(OpRole * oprole)175 release_oprole(OpRole *oprole)
176 {
177 AI_Side *ai = NULL;
178 OpRole *oprole2 = NULL;
179 Side *side = NULL;
180
181 assert_error(oprole, "Attempted to release a NULL oprole info node");
182 side = oprole->side;
183 ai = get_side_ai(side);
184 /* Remove from hash bucket. */
185 for_all_oproles_by_type(side, oprole->type, oprole2) {
186 /* If first in bucket... */
187 if (oprole2->id == oprole->id) {
188 ai->oprole_buckets[oprole->type] = oprole->next_by_type;
189 break;
190 }
191 /* If next in bucket... */
192 else if (oprole2->next_by_type
193 && (oprole2->next_by_type->id == oprole->id)) {
194 oprole2->next_by_type = oprole->next_by_type;
195 break;
196 }
197 }
198 oprole->next_by_type = NULL;
199 // Remove from list.
200 for_all_oproles(side, oprole2) {
201 // If first in list...
202 if (oprole2->id == oprole->id) {
203 ai->oproles = oprole->next;
204 break;
205 }
206 // If next in list...
207 else if (oprole2->next && (oprole2->next->id == oprole->id)) {
208 oprole2->next = oprole->next;
209 break;
210 }
211 }
212 oprole->next = NULL;
213 // Add back to pool of unused nodes.
214 if (ai->oproles_free) {
215 oprole->next = ai->oproles_free;
216 ai->oproles_free = oprole;
217 }
218 else
219 ai->oproles_free = oprole;
220 // Reset ID and type.
221 oprole->id = -1;
222 oprole->type = OR_NONE;
223 }
224
225 OpRole *
find_oprole(Side * side,int id)226 find_oprole(Side *side, int id)
227 {
228 OpRole *oprole = NULL;
229
230 assert_error(side, "Attempted to access a NULL side");
231 for_all_oproles(side, oprole) {
232 if (oprole->id == id)
233 return oprole;
234 }
235 return NULL;
236 }
237
238 /* Handling of Operational Roles */
239
240 #if (0)
241 /*
242 \todo Implement.
243 */
244
245 void
handle_shuttle_oprole(OpRole * oprole)246 handle_shuttle_oprole(OpRole *oprole)
247 {
248 Unit *unit = NULL, *occ = NULL;
249 Side *side = NULL;
250 UnitView *uv = NULL, *uvocc = NULL;
251
252 /* Get out if the oprole is invalid or inactive. */
253 assert_warning_return(oprole, "Attempted to evaluate invalid oprole",);
254 if (OR_SHUTTLE != oprole->type)
255 return;
256 /* Find unit associated with oprole. */
257 unit = find_unit(oprole->id);
258 assert_warning_return(in_play(unit),
259 "Attempted to manipulate an out-of-play unit",);
260 /* Get oprole side. Need not be unit side. */
261 side = oprole->side;
262 /* Find uview associated with unit. */
263 uv = find_unit_view(side, unit);
264 assert_warning_return(uv, "Attempted to access a NULL uview",);
265 // TODO: Implement.
266 #if (0)
267 /** Threat Assessment **/
268 /* Scan for immediate threats to shuttle. */
269 if (in_immediate_danger(unit)) {
270 retreat_from_danger(unit);
271 return;
272 }
273 /* Scan for immediate threats to shuttle's occs. */
274 for_all_occupant_views(uv, uvocc) {
275 if (uvocc->siden != side->id)
276 continue;
277 occ = view_unit(uvocc);
278 if (in_immediate_danger(occ)) {
279 retreat_from_occ_danger(unit, occ);
280 return;
281 }
282 }
283 /* (TODO: Scan for shuttle threats along path.) */
284 /* (TODO: Scan for occ threats along path.) */
285 /** TODO: Embark/Disembark Opportunities **/
286 /** TODO: Pathfind/Move-To Next Embark/Disembark Point **/
287 #endif
288 }
289 #endif
290
291 /* Construction */
292
293 int
choose_utype_to_construct(OpRole * oprole,int * uscore)294 choose_utype_to_construct(OpRole *oprole, int *uscore)
295 {
296 static int *uscores = NULL;
297
298 int rslt = A_ANY_OK;
299 Unit *unit = NULL;
300 Side *side = NULL;
301 int u = NONUTYPE, u2 = NONUTYPE, uc = NONUTYPE;
302 int cp = 0, cppt = 0, tp = 0, tppt = 0, ttc = -1;
303 int uscorestot = 0, luckynum = -1;
304
305 // Get out if the oprole is invalid or inactive.
306 assert_warning_return(oprole, "AI: Attempted to handle invalid oprole",
307 uc);
308 // Find unit associated with oprole.
309 unit = find_unit(oprole->id);
310 assert_warning_return(in_play(unit),
311 "AI: Attempted to manipulate an out-of-play unit",
312 uc);
313 // Useful info.
314 side = oprole->side;
315 u = unit->type;
316 // Initialize utype score array, if necessary.
317 if (!uscores)
318 uscores = (int *)xmalloc(numutypes * sizeof(int));
319 // Iterate through utypes, and find out which we can create and score them.
320 for_all_unit_types(u2) {
321 uscores[u2] = -1;
322 // Skip if...
323 // ...cannot create.
324 rslt = can_create_common(unit, unit, u2, unit->x, unit->y);
325 if (!valid(rslt) && (A_CONSTRUCT_NO_TOOLING != rslt))
326 continue;
327 // ...if immobile constructor and no access to suitable cells.
328 // TODO: Alter this test not to depend on info we should not have.
329 if (!u_mobile(u)
330 && (u_naval_mobile(u2) && !u_air_mobile(u2))
331 && !suitable_port(unit))
332 continue;
333 // Estimate turns to complete.
334 ttc = 0;
335 // TODO: Estimate whether creation would add another turn.
336 // Add CP contribution to turns to complete.
337 cp = u_cp(u2) - uu_creation_cp(u, u2);
338 if (0 < cp) {
339 cppt = cp_per_turn_est(unit, u2);
340 if (0 < cppt) {
341 ttc += cp / cppt;
342 if (cp % cppt)
343 ++ttc;
344 }
345 }
346 // Add TP contribution to turns to complete.
347 tp = uu_tp_max(u, u2) - (unit->tooling ? unit->tooling[u2] : 0);
348 if (0 < tp) {
349 tppt = tp_per_turn_est(unit, u2);
350 if (0 < tppt) {
351 ttc += tp / tppt;
352 if (tp % tppt)
353 ++ttc;
354 }
355 }
356 // Calculate score.
357 // TODO: Should consider things such as:
358 // (a) Potential self-unit?
359 // (b) Essential for victory?
360 // (c) Needed in constructor's theater?
361 // (d) Needed in any theater?
362 // Possibly roll such tests into enhanced total worth function.
363 // Total worth of u2 divided by estimated turns to complete it.
364 uscores[u2] = total_worth(u2) / (ttc + 1);
365 uscorestot += uscores[u2];
366 }
367 // Randomly pick an utype to construct.
368 luckynum = xrandom(uscorestot);
369 uscorestot = 0;
370 for_all_unit_types(u2) {
371 // Skip unscored utypes.
372 if (0 > uscores[u2])
373 continue;
374 // Rebuild total uscores, and find what was picked.
375 uscorestot += uscores[u2];
376 if (luckynum < uscorestot) {
377 uc = u2;
378 if (uscore)
379 *uscore = uscores[u2];
380 break;
381 }
382 }
383 return uc;
384 }
385
386 Unit *
choose_transport_to_construct_in(int u2,Side * side,UnitView * uvtspt,int upchain)387 choose_transport_to_construct_in(
388 int u2, Side *side, UnitView *uvtspt, int upchain)
389 {
390 Unit *tsptbest = NULL;
391 UnitView *uvtspt2 = NULL;
392
393 assert_error(is_unit_type(u2),
394 "AI: Encountered an invalid unit type to construct");
395 assert_error(side,
396 "AI: Attempted to access a NULL side");
397 assert_error(uvtspt,
398 "AI: Attempted to access a NULL unit view");
399 // If we are allowed to search upwards, then do so.
400 // Note: We pick the broadest transport to construct in,
401 // hoping that we will be able to enclose other things.
402 // Technically, the action code does not support this yet.
403 if (upchain) {
404 for (uvtspt2 = uvtspt->transport; uvtspt2;
405 uvtspt2 = uvtspt2->transport) {
406 // We cannot construct in an enemy transport.
407 // TODO: Think about "constructing" plague, etc... inside an enemy.
408 if (enemy_side(side, side_n(uvtspt2->siden)))
409 continue;
410 // Does new unit appear likely to fit in transport?
411 if (!valid(can_be_in(u2, side, uvtspt2)))
412 continue;
413 // Mark this as our best transport.
414 tsptbest = query_unit_from_uview(uvtspt2);
415 }
416 if (tsptbest)
417 return tsptbest;
418 }
419 // Get out, if target transport is an enemy.
420 if (enemy_side(side, side_n(uvtspt->siden)))
421 return NULL;
422 // Search downwards.
423 // Note: We pick the narrowest transport to construct in,
424 // to minimize the amount of space being wasted.
425 for_all_occupant_views(uvtspt, uvtspt2) {
426 // We cannot construct in an enemy transport.
427 if (enemy_side(side, side_n(uvtspt2->siden)))
428 continue;
429 // Depth-first recurse into occs.
430 if (uvtspt2->occupant)
431 tsptbest = choose_transport_to_construct_in(u2, side, uvtspt2);
432 // Does new unit appear likely to fit in transport?
433 if (!valid(can_be_in(u2, side, uvtspt2)))
434 continue;
435 // Return the first occ that the new unit can apparently fit in.
436 return query_unit_from_uview(uvtspt2);
437 }
438 // Does new unit appear likely to fit in target transport?
439 if (valid(can_be_in(u2, side, uvtspt)))
440 return query_unit_from_uview(uvtspt);
441 return NULL;
442 }
443
444 Task *
generate_construction_task(Unit * unit,int u2)445 generate_construction_task(Unit *unit, int u2)
446 {
447 Unit *transport = NULL, *tsptbest = NULL;
448 int u = NONUTYPE;
449 Side *side = NULL;
450 UnitView *uvstack = NULL, *uview = NULL;
451 int crange = -1, range = -1, sfx = -1, sfy = -1, x = -1, y = -1;
452 int dist = -1, xbest = -1, ybest = -1;
453 int score = -1, scorebest = -1;
454 Theater *theater = NULL;
455 int xypopularity = 0;
456
457 assert_warning_return(
458 in_play(unit), "AI: Attempted to manipulate an out-of-play unit",
459 NULL);
460 // Useful info.
461 side = unit->side;
462 u = unit->type;
463 uview = query_uvstack_from_unit(unit);
464 // Calculate the search range and origin point.
465 crange = min(uu_create_range(u, u2), uu_build_range(u, u2));
466 // If mobile unit,
467 // and construction can performed outside of trans-occ chain,
468 // then try theater-wide+ search...
469 if (u_mobile(u) && (0 <= crange)) {
470 // Get the theater that the unit is in.
471 // We deliberately allow ourselves to "bleed" into other theaters.
472 // This allows the constructive processes to diffuse without the
473 // need for an explicit theater transfer for the unit.
474 if ((theater = theater_at(side, unit->x, unit->y))) {
475 range =
476 crange
477 + (max(theater->xmax - theater->xmin,
478 theater->ymax - theater->ymin)*125)/100;
479 sfx = theater->x; sfy = theater->y;
480 }
481 // Else, fall back on the unit's tactical range.
482 else {
483 range = crange + u_ai_tactical_range(u);
484 sfx = unit->x; sfy = unit->y;
485 }
486 }
487 // Else, we use limited range.
488 else {
489 range = crange;
490 sfx = unit->x; sfy = unit->y;
491 }
492 // Try the transport-occ chain first, because it is probably quicker.
493 // Find transport in transport-occ chain to construct in.
494 // Note that this need not be the constructor or its transport.
495 transport = choose_transport_to_construct_in(u2, side, uview, TRUE);
496 // If a transport was found, then try to set a construct task in it.
497 if (transport) {
498 net_set_construct_task(unit, u2, 1, transport->id, -1, -1);
499 if (unit->plan)
500 return unit->plan->tasks;
501 }
502 else {
503 // If restricted to transport-occ chain, then we are done.
504 if (0 > range)
505 return NULL;
506 }
507 // Search all cells in range, and determine best one.
508 for_all_cells_within_range(sfx, sfy, range, x, y) {
509 score = 0;
510 transport = NULL;
511 // Skip, if outside of world.
512 if (!inside_area(x, y))
513 continue;
514 // Rummage through potential transports in cell.
515 // Note: Even if u2 cannot see cell and cannot survive on it,
516 // we may be able to construct it inside something we have there.
517 uvstack = query_uvstack_at(x, y);
518 for_all_uvstack(uvstack, uview) {
519 transport =
520 choose_transport_to_construct_in(u2, side, uview);
521 // TODO: Score all potential transports.
522 if (transport)
523 break;
524 }
525 // Can survive on cell without transport?
526 // Note: Rejects cells that we cannot see.
527 // Note: Rejects cells that are too full.
528 if (!transport && !valid(can_survive_on_known(u2, side, x, y)))
529 continue;
530 // TODO: Maybe create deferred construction tasks,
531 // if moving friendly mobile units would allow construction at.
532 // If another unit plans on constructing in the given cell,
533 // and we plan on constructing there, then reject it.
534 if ((xypopularity = n_plan_to_construct_at(side, x, y)))
535 continue;
536 // If there would be resource contention with other units...
537 if (resource_contention_with_any(u2, x, y, side))
538 continue;
539 // If advanced utype cannot meet size goal at location.
540 if (u_advanced(u2) && !could_meet_size_goal(u2, x, y))
541 continue;
542 dist = distance(unit->x, unit->y, x, y);
543 score += isqrt(range - (dist - crange));
544 // TODO: Additional scoring.
545 if ((score > scorebest) || ((score == scorebest) && flip_coin())) {
546 scorebest = score;
547 tsptbest = transport;
548 xbest = x; ybest = y;
549 }
550 } // for all cells within range
551 // If nothing was found, then get out.
552 if (!in_play(tsptbest) && !inside_area(xbest, ybest))
553 return NULL;
554 // Setup tasks based on best cell or transport.
555 // Construct in a transport.
556 if (tsptbest)
557 net_set_construct_task(unit, u2, 1, tsptbest->id, -1, -1);
558 // Construct in a cell.
559 else
560 net_set_construct_task(unit, u2, 1, -1, xbest, ybest);
561 // Move there first, if necessary.
562 if (dist > crange)
563 net_push_move_to_task(unit, xbest, ybest, crange);
564 if (unit->plan)
565 return unit->plan->tasks;
566 return NULL;
567 }
568
569 int
choose_construction_or_repair(OpRole * oprole)570 choose_construction_or_repair(OpRole *oprole)
571 {
572 Unit *unit = NULL, *unit2 = NULL;
573 Unit *unitc = NULL, *unitr = NULL, *unitr2 = NULL;
574 Side *side = NULL;
575 int u = NONUTYPE, u2 = NONUTYPE, uc = NONUTYPE;
576 int dist = -1, cdist = -1, rdist = -1, rrange = -1;
577 int cpreq = -1, hpreq = -1, hpreq2 = -1;
578 int crate = -1, rrate = -1, mycrate = -1, myrrate = -1;
579 int cturns = -1, rturns = -1, rturns2 = -1;
580 int ceta = -1, reta = -1;
581 int worth = -1, uscore = -1;
582 int cscore = -1, cscorebest = -1;
583 int rscore = -1, rscore2 = -1, rscorebest = -1, rscore2best = -1;
584
585 // Get out if the oprole is invalid or inactive.
586 assert_warning_return(
587 oprole, "AI: Attempted to handle invalid oprole", NONUTYPE);
588 // Find unit associated with oprole.
589 unit = find_unit(oprole->id);
590 assert_warning_return(
591 in_play(unit), "AI: Attempted to manipulate an out-of-play unit",
592 NONUTYPE);
593 // Useful info.
594 side = oprole->side;
595 u = unit->type;
596 // Little machine player debug blurb.
597 DMprintf(
598 "%s is looking for something to construct or repair...\n",
599 medium_long_unit_handle(unit));
600 // Choose a new utype to construct.
601 // We may or may not actually use it,
602 // but we want to have it available for later comparison.
603 uc = choose_utype_to_construct(oprole, &uscore);
604 // Iterate through all units on side,
605 // looking for ones that are incomplete or that need repair.
606 // TODO: Need to think about auto-repair.
607 // With auto-repair, it is conceivable that we could move into range
608 // of many units needing repair and heal them all simultaneously.
609 // Probably we should test how many other units could be auto-repaired
610 // if we were at the position of the unit needing auto-repair.
611 // This is somewhat crude, but decent and not too expensive.
612 // Once at the location or near the primary unit being auto-repaired,
613 // the repair task could be smart enough to position the repairer to
614 // optimize auto-repair coverage. This kind of intelligence is not
615 // critical to have at this juncture, but something to keep in mind.
616 // If auto-repairer is immobile, then ignore auto-repair.
617 for_all_side_units(side, unit2) {
618 /* Skip over if... */
619 if (unit == unit2)
620 continue;
621 if (!in_play(unit2))
622 continue;
623 // Useful info.
624 u2 = unit2->type;
625 // Skip complete units, which are not significantly damaged.
626 // TODO: Handle multi-part units or get rid of them.
627 if (completed(unit2) && (unit->hp >= u_hp(u2)))
628 continue;
629 // Skip units that we cannot build or repair upon.
630 mycrate = cp_per_turn_est(unit, u2);
631 myrrate = hp_per_turn_est(unit, u2);
632 if (!mycrate && !myrrate)
633 continue;
634 // Skip units that cannot be constructed upon or repaired.
635 if (!valid(can_construct(unit, unit, u2))
636 && !valid(can_repair(unit, unit, unit2))
637 && !valid(can_auto_repair(unit, unit2)))
638 continue;
639 /* Consider... */
640 // Distance between units.
641 dist = distance(unit->x, unit->y, unit2->x, unit2->y);
642 // Subtract out action ranges.
643 // Note that range < 0 penalizes distance by adding 1.
644 cdist = dist - uu_build_range(u, u2);
645 rrange = max(uu_repair_range(u, u2), uu_auto_repair_range(u, u2));
646 rdist = dist - rrange;
647 // Skip if constructor/repairer is immobile, and out of range.
648 if (!u_mobile(u) && (cdist > 0) && (rdist > 0))
649 continue;
650 // CP needed for completion.
651 cpreq = u_cp(u2) - unit2->cp;
652 // HP needed for full recovery.
653 hpreq = u_hp(u2) - unit2->hp;
654 hpreq2 = (u_hp(u2) * unit_doctrine(unit2)->repair_complete) / 100;
655 // Calculate our estimated times of arrival, if relevant.
656 // Note: Crude estimate. Should compute path.
657 ceta = (u_mobile(u) ? cdist / mp_per_turn_max(u) : 0);
658 reta = (u_mobile(u) ? rdist / mp_per_turn_max(u) : 0);
659 // Estimated CP per turn from current builders.
660 // Note: Does not consider potential builders in transit,
661 // who may contribute before this potential builder arrives.
662 crate = cp_gained_per_turn_est(unit2, side);
663 // Estimated HP per turn from current repairers.
664 // Note: Does not consider potential repairers in transit,
665 // who may contribute before this potential repairer arrives.
666 rrate = hp_gained_per_turn_est(unit2, side);
667 // If other units are building, then we want to know how much longer.
668 cturns = (crate ? cpreq / crate : INT_MAX - 1);
669 // If other units are repairing, then we want to know how much longer.
670 rturns = (rrate ? hpreq / rrate : INT_MAX - 1);
671 rturns2 = (rrate ? hpreq2 / rrate : INT_MAX - 1);
672 // Subtract ETA from estimated turns until completion.
673 if (crate)
674 cturns -= ceta;
675 // Subtract ETA from estimated turns until sufficient and full repairs.
676 if (rrate) {
677 rturns -= reta;
678 rturns2 -= reta;
679 }
680 // If unit is estimated to be completed and fully repaired on or before
681 // turn of arrival, then don't bother with it.
682 // Note that this also correctly covers immobile builders and repairers.
683 if ((unit2->cp < u_cp(u2)) && (cturns <= 0))
684 continue;
685 if ((unit2->hp < u_hp(u2)) && (rturns <= 0))
686 continue;
687 // Adjust the CP left to account for what should have been done
688 // before arrival of potential builder.
689 cpreq -= cturns * crate;
690 // Adjust the HP left to account for what should have been done
691 // before arrival of potential repairer.
692 hpreq -= rturns * rrate;
693 hpreq2 -= rturns2 * rrate;
694 // Add potential builder's own construction rate in.
695 crate += mycrate;
696 // Add potential repairer's own repair rate in.
697 rrate += myrrate;
698 // How much longer after new builder starts building on this unit?
699 cturns = (crate ? cpreq / crate : INT_MAX - 1);
700 // How much longer after new repairer starts repairing this unit?
701 rturns = (rrate ? hpreq / rrate : INT_MAX - 1);
702 rturns2 = (rrate ? hpreq2 / rrate : INT_MAX - 1);
703 // Total the worth of the incomplete or damaged unit.
704 // TODO: Handle other worth modifiers, such as victory conditions.
705 // TODO: Penalize score for out-of-theater units.
706 worth = total_worth(u2);
707 // Score completion importance.
708 cscore = (mycrate && cturns ? worth / (cturns + 1) : 0);
709 // Score repair importances.
710 // But, only if the unit is complete.
711 if (completed(unit2)) {
712 rscore = (myrrate && rturns ? worth / (rturns + 1) : 0);
713 rscore2 = (myrrate && rturns2 ? worth / (rturns2 + 1) : 0);
714 }
715 else {
716 rscore = rscore2 = 0;
717 }
718 // Reject if scores < chosen uscore.
719 if (is_unit_type(uc)
720 && (cscore < uscore) && (rscore < uscore) && (rscore2 < uscore))
721 continue;
722 // Update best scores and unit placeholders, if necessary.
723 if (cscore > cscorebest) {
724 cscorebest = cscore;
725 unitc = unit2;
726 }
727 if (rscore > rscorebest) {
728 rscorebest = rscore;
729 unitr = unit2;
730 }
731 if (rscore2 > rscore2best) {
732 rscore2best = rscore2;
733 unitr2 = unit2;
734 }
735 } // for all unit2
736 /* Decide what to do. */
737 // Should we construct new unit?
738 if (is_unit_type(uc)
739 && (0 > cscorebest) && (0 > rscorebest) && (0 > rscore2best)
740 && generate_construction_task(unit, uc)) {
741 DMprintf("\t...and decided to construct a new %s.\n",
742 u_type_name(uc));
743 }
744 // Should we resume building on existing unit?
745 else if ((cscorebest > rscorebest) && (cscorebest > rscore2best)) {
746 uc = unitc->type;
747 net_set_build_task(unit, unitc->id, u_cp(uc));
748 if (0 < dist)
749 net_push_move_to_task(
750 unit, unitc->x, unitc->y,
751 (-1 < uu_build_range(u, uc)) ? uu_build_range(u, uc) : 0);
752 DMprintf(
753 "\t...and found %s to complete.\n",
754 medium_long_unit_handle(unitr2));
755 }
756 // Should we repair an unit to full health?
757 else if (rscorebest > rscore2best) {
758 uc = unitr->type;
759 net_set_repair_task(unit, unitr->id, u_hp(uc));
760 rrange = max(uu_repair_range(u, uc), uu_auto_repair_range(u, uc));
761 rrange = (0 > rrange ? 0 : rrange);
762 if (0 < dist)
763 net_push_move_to_task(unit, unitr->x, unitr->y, rrange);
764 DMprintf(
765 "\t...and found %s to fully repair.\n",
766 medium_long_unit_handle(unitr2));
767 }
768 // Should we repair an unit to sufficient health?
769 else if (rscore2best >= 0) {
770 uc = unitr2->type;
771 net_set_repair_task(
772 unit, unitr2->id,
773 (u_hp(uc) * unit_doctrine(unitr2)->repair_complete) / 100);
774 rrange = max(uu_repair_range(u, uc), uu_auto_repair_range(u, uc));
775 rrange = (0 > rrange ? 0 : rrange);
776 if (0 < dist)
777 net_push_move_to_task(unit, unitr2->x, unitr2->y, rrange);
778 DMprintf(
779 "\t...and found %s to repair up to %d%% of full (%d) HP.\n",
780 medium_long_unit_handle(unitr2),
781 unit_doctrine(unitr2)->repair_complete, u_hp(uc));
782 }
783 // Else, we could not find anything to do.
784 else {
785 uc = NONUTYPE;
786 DMprintf("\t...and found nothing.\n");
787 }
788 return uc;
789 }
790
791 OpRole_Outcome
handle_constructor_oprole(OpRole * oprole)792 handle_constructor_oprole(OpRole *oprole)
793 {
794 Unit *unit = NULL;
795 Side *side = NULL;
796 Task *tasks = NULL;
797
798 // Get out if the oprole is invalid or inactive.
799 assert_warning_return(oprole, "AI: Attempted to evaluate invalid oprole",
800 ORO_INVALID);
801 if (OR_CONSTRUCTOR != oprole->type)
802 return ORO_INVALID;
803 // Find unit associated with oprole.
804 unit = find_unit(oprole->id);
805 assert_warning_return(in_play(unit),
806 "AI: Attempted to manipulate an out-of-play unit",
807 ORO_FAILED);
808 // Useful info.
809 side = oprole->side;
810 tasks = (unit->plan ? unit->plan->tasks : NULL);
811 // Evaluate tactical situation first.
812 // Includes tactical construction.
813 if (TC_NONE != handle_tactical_situation(unit))
814 return ORO_HANDLED_TACTICAL;
815 // If there are existing tasks to run, then let them run.
816 if (tasks)
817 return ORO_OK;
818 // Choose what to construct next.
819 if (NONUTYPE != choose_construction_or_repair(oprole))
820 return ORO_OK;
821 // TODO: If no construction available,
822 // then perhaps we can decide to fall back to another oprole type.
823 return ORO_FAILED;
824 }
825
826 //! Handle an operational role.
827
828 void
handle_oprole(OpRole * oprole)829 handle_oprole(OpRole *oprole)
830 {
831 Unit *unit = NULL;
832 OpRole_Outcome oro = ORO_OK;
833
834 // Get out if the oprole is invalid or inactive.
835 assert_warning_return(oprole, "Attempted to evaluate invalid oprole",);
836 if (OR_NONE == oprole->type) {
837 release_oprole(oprole);
838 return;
839 }
840 // Remove oproles for dead units.
841 unit = find_unit(oprole->id);
842 if (!unit) {
843 release_oprole(oprole);
844 return;
845 }
846 // TODO: If too many failures, then release oprole and put unit in reserve.
847 // Switch to appropriate evaluator, as necessary.
848 switch (oprole->type) {
849 case OR_SHUTTLE:
850 #if (0)
851 oro = handle_shuttle_oprole(oprole);
852 #endif
853 break;
854 case OR_CONSTRUCTOR:
855 oro = handle_constructor_oprole(oprole);
856 break;
857 default:
858 run_warning("AI: Attempted to evaluate unknown oprole type; weird");
859 return;
860 }
861 // Increment oprole execution counter.
862 ++oprole->execs_this_turn;
863 // Handle oprole outcomes.
864 switch (oro) {
865 case ORO_OK:
866 case ORO_HANDLED_TACTICAL:
867 break;
868 case ORO_FAILED:
869 case ORO_INVALID:
870 default:
871 ++oprole->fails_this_turn;
872 // If too many failures,
873 // then put unit in reserve and release oprole.
874 // Note that each oprole should handle failover to other oproles.
875 if ((10 <= oprole->fails_this_turn)
876 && (oprole->fails_this_turn
877 >= ((oprole->execs_this_turn * 9) / 10))) {
878 if (in_play(unit))
879 net_set_unit_reserve(unit->side, unit, TRUE, FALSE);
880 release_oprole(oprole);
881 }
882 break;
883 }
884 }
885
886 //! Handle all operational roles on a side.
887
888 void
handle_oproles(Side * side)889 handle_oproles(Side *side)
890 {
891 OpRole *oprole = NULL;
892
893 assert_error(side, "Attempted to access a NULL side");
894 assert_error(get_side_ai(side),
895 "Attempted to access non-existent master AI struct");
896 for_all_oproles(side, oprole) {
897 if (OR_NONE == oprole->type)
898 continue;
899 handle_oprole(oprole);
900 }
901 }
902
903 } // namespace Xconq::AI
904 } // namespace Xconq
905