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