/* Unit plan handling for Xconq. Copyright (C) 1991-2000 Stanley T. Shebs. Xconq is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. See the file COPYING. */ #include "conq.h" #include "kernel.h" extern short any_auto_repair; static void plan_passive(Unit *unit); static void plan_offense(Unit *unit); static void plan_offense_support(Unit *unit); static void plan_defense(Unit *unit); static void plan_exploration(Unit *unit); static void plan_explorer_support(Unit *unit); static void plan_improve(Unit *unit); static void plan_colonize(Unit *unit); static void plan_colonize_support(Unit *unit); static int good_cell_to_colonize(int x, int y); static void plan_random(Unit *unit); static void wake_at(int x, int y); static int resupply_if_low(Unit *unit); static int rearm_if_low(Unit *unit); static int repair_if_damaged(Unit *unit); static int repairs_here(Unit *unit, int x, int y); static int alternate_target_here(int x, int y); static int side_planning_to_capture(Side *side, int u, int x, int y); static int do_for_occupants(Unit *unit); static int go_after_victim(Unit *unit, int range); static int fire_at_opportunity(Unit *unit); static int explore_reachable_cell(Unit *unit, int range); static int capture_useful_if_nearby(Unit *unit); static int capture_indep_if_nearby(Unit *unit); static void random_walk(Unit *unit); static int worth_capturing(Side *side, int u2, Side *oside, int x, int y); static int supplies_here(Unit *unit, int x, int y, int m); static int indep_captureable_here(int x, int y); static int useful_type(Side *side, int u); static int could_capture_any(int u); static Plan *create_plan(void); static int might_be_captured(Unit *unit); static int occupant_could_capture(Unit *unit, int etype); static int can_capture_neighbor(Unit *unit); static int occupant_can_capture_neighbor(Unit *unit); static int find_closest_unit(Side *side, int x0, int y0, int maxdist, int (*pred)(int x, int y), int *rxp, int *ryp); static int reachable_unknown(int x, int y); static int adj_known_ok_terrain(int x, int y, Side *side, int u); static int normal_completion_time(int u, int u2); static int self_build_base_for_self(Unit *unit); #if 0 static int go_after_captive(Unit *unit, int range); static int range_left(Unit *unit); static int find_worths(int range); static int attack_worth(Unit *unit, int e); static int threat(Side *side, int u, int x0, int y0); static int move_patrol(Unit *unit); static int build_time(Unit *unit, int prod); static int out_of_ammo(Unit *unit); static int explorable_cell(int x, int y); static int should_capture_maker(Unit *unit); extern int adj_unit(int x, int y); #endif #define CLEAR_AGENDA 99 /* (should have a generic struct for all plan type attrs) */ char *plantypenames[] = { #undef DEF_PLAN #define DEF_PLAN(NAME,code) NAME, #include "plan.def" NULL }; /* Every unit that can act needs a plan object, types that can't act should have it cleared out. Note that incomplete units are expected to be able to act in the future, so it's acceptable to run this for incomplete units to give them a plan. */ void init_unit_plan(Unit *unit) { if (u_acp(unit->type) > 0 /* Acp-independent units still need a plan. */ || acp_indep(unit)) { /* Might already have a plan, so don't always realloc. */ if (unit->plan == NULL) { unit->plan = create_plan(); } /* Put the plan into a default state, side will work it up later. */ /* (should release goals also) */ clear_task_agenda(unit->plan); /* Zero the plan just as xmalloc would. */ memset(unit->plan, 0, sizeof(Plan)); unit->plan->type = PLAN_PASSIVE; unit->plan->creation_turn = g_turn(); /* Allow AIs to make this unit do things. */ unit->plan->aicontrol = TRUE; /* Enable supply alarms by default. */ unit->plan->supply_alarm = TRUE; /* Clear the task outcome. */ unit->plan->last_task_outcome = TASK_UNKNOWN; } else { /* Brainless units don't need anything, can free up plan. */ if (unit->plan != NULL) { free_plan(unit->plan); } unit->plan = NULL; } } void set_unit_plan_type(Side *side, Unit *unit, int type) { int oldtype; if (unit->plan) { oldtype = unit->plan->type; if (type != oldtype) { if (type == PLAN_NONE) { type = PLAN_PASSIVE; force_replan(side, unit, FALSE); } unit->plan->type = type; if (side != NULL) update_unit_display(side, unit, TRUE); } } } void set_unit_asleep(Side *side, Unit *unit, int flag, int recurse) { int oldflag; Unit *occ; if (unit->plan) { oldflag = unit->plan->asleep; if (flag != oldflag) { unit->plan->asleep = flag; if (side != NULL) update_unit_display(side, unit, TRUE); } } if (recurse) { for_all_occupants(unit, occ) { set_unit_asleep(side, occ, flag, recurse); } } } void set_unit_reserve(Side *side, Unit *unit, int flag, int recurse) { int oldflag; Unit *occ; if (unit->plan) { oldflag = unit->plan->reserve; if (flag != oldflag) { unit->plan->reserve = flag; if (side != NULL) update_unit_display(side, unit, TRUE); } } if (recurse) { for_all_occupants(unit, occ) { set_unit_reserve(side, occ, flag, recurse); } } } void set_unit_ai_control(Side *side, Unit *unit, int flag, int recurse) { int oldflag; Unit *occ; if (unit->plan) { oldflag = unit->plan->aicontrol; if (flag != oldflag) { unit->plan->aicontrol = flag; if (side != NULL) update_unit_display(side, unit, TRUE); } } if (recurse) { for_all_occupants(unit, occ) { set_unit_ai_control(side, occ, flag, recurse); } } } void set_unit_curadvance(Side *side, Unit *unit, int a) { unit->curadvance = a; } void set_unit_autoplan(Side *side, Unit *unit, int flag) { unit->autoplan = flag; } void set_unit_autoresearch(Side *side, Unit *unit, int flag) { unit->autoresearch = flag; } void set_unit_autobuild(Side *side, Unit *unit, int flag) { unit->autobuild = flag; } void set_unit_buildingdone(Side *side, Unit *unit, int flag) { unit->buildingdone = flag; } void set_unit_researchdone(Side *side, Unit *unit, int flag) { unit->researchdone = flag; } void set_unit_main_goal(Side *side, Unit *unit, Goal *goal) { if (unit->plan) { unit->plan->maingoal = goal; } } void set_unit_waiting_for_transport(Side *side, Unit *unit, int flag) { if (unit->plan) { unit->plan->waitingfortransport = flag; } } /* Execute the plan. */ int execute_plan(Unit *unit) { Plan *plan = unit->plan; if (!in_play(unit) || !completed(unit)) { DMprintf("%s shouldn't be planning yet\n", unit_desig(unit)); return 0; } DMprintf("%s using plan %s\n", unit_desig(unit), plan_desig(plan)); /* Units that are asleep or in reserve do nothing. */ /* (This never happens according to debugging). */ if (plan->asleep || plan->reserve) { return 0; } if (plan->type == PLAN_PASSIVE && plan->execs_this_turn > 10) { DMprintf(" not found\n"); } if (plan->execs_this_turn > 1000 && g_units_may_go_into_reserve()) { DMprintf("%s executed plan 1000 times this turn, going into reserve\n", unit_desig(unit)); plan->reserve = TRUE; return 1; } /* Unit actually has a plan, dispatch on its type. */ switch (plan->type) { case PLAN_NONE: /* Unit has not gotten a plan yet, leave it alone. */ break; case PLAN_PASSIVE: plan_passive(unit); break; case PLAN_OFFENSIVE: plan_offense(unit); break; case PLAN_DEFENSIVE: plan_defense(unit); break; case PLAN_EXPLORATORY: plan_exploration(unit); break; case PLAN_COLONIZING: plan_colonize(unit); break; case PLAN_IMPROVING: plan_improve(unit); break; case PLAN_RANDOM: plan_random(unit); break; default: case_panic("plan type", plan->type); break; } ++(plan->execs_this_turn); return 1; } /* See if we're too far away from an assigned position, set a task to move back if so. */ int move_into_formation(Unit *unit) { int nx, ny, dist; Plan *plan = unit->plan; Goal *goal; Unit *leader; leader = plan->funit; if (leader != NULL) { goal = plan->formation; /* Ensure that the leader is still someone we want to follow. */ if (!in_play(leader) || !unit_trusts_unit(unit, leader) || goal->args[0] != leader->id) { notify(unit->side, "%s leader is gone, cancelling formation", unit_handle(unit->side, unit)); /* (should free goal object?) */ plan->formation = NULL; plan->funit = NULL; /* Unit is available to do something else. */ return FALSE; } nx = leader->x + goal->args[1]; ny = leader->y + goal->args[2]; dist = goal->args[3]; if (distance(unit->x, unit->y, nx, ny) > dist) { /* (should perhaps insert after current task?) */ set_move_to_task(unit, nx, ny, dist); return TRUE; } } return FALSE; } int task_is_in_agenda(Plan *plan, Task *task); /* See if there are any standing orders that currently apply to the given unit, and schedule a task if so. Return TRUE if a task was added. */ int execute_standing_order(Unit *unit, int addtask) { Unit *transport; Side *side = unit->side; StandingOrder *sorder; for (sorder = side->orders; sorder != NULL; sorder = sorder->next) { if (sorder->types[unit->type] && unit->plan) { switch (sorder->condtype) { case sorder_at: if (unit->x == sorder->a1 && unit->y == sorder->a2) { /* If the task is already in the plan, don't do anything. */ if (task_is_in_agenda(unit->plan, sorder->task)) return FALSE; if (addtask) add_task(unit, 0, clone_task(sorder->task)); return TRUE; } break; case sorder_in: transport = unit->transport; if (transport != NULL && transport->id == sorder->a1) { /* If the task is already in the plan, don't do anything. */ if (task_is_in_agenda(unit->plan, sorder->task)) return FALSE; if (addtask) add_task(unit, 0, clone_task(sorder->task)); return TRUE; } break; case sorder_near: if (distance(unit->x, unit->y, sorder->a1, sorder->a2) <= sorder->a3) { /* If the task is already in the plan, don't do anything. */ if (task_is_in_agenda(unit->plan, sorder->task)) return FALSE; if (addtask) add_task(unit, 0, clone_task(sorder->task)); return TRUE; } break; default: run_warning("Unknown order condition type"); break; } } } return FALSE; } int tasks_match(Task *task1, Task *task2); int task_is_in_agenda(Plan *plan, Task *task) { Task *task2; for (task2 = plan->tasks; task2 != NULL; task2 = task2->next) { if (tasks_match(task, task2)) return TRUE; } return FALSE; } int tasks_match(Task *task1, Task *task2) { int i; if (task1->type != task2->type) return FALSE; for (i = 0; i < MAXTASKARGS; ++i) if (task1->args[i] != task2->args[i]) return FALSE; return TRUE; } /* Passive units just work from the task queue or else wait to be told what to do. */ static void plan_passive(Unit *unit) { Plan *plan = unit->plan; /* Don't allow passive units under ai control. */ if (ai_controlled(unit)) { force_replan(unit->side, unit, FALSE); return; } /* Special-case human cities/towns in the intro game to automatically start producing infantry initially. */ /* (would be more efficient to put in a once-per-turn location, should look for one) */ if (g_turn() <= 1 && mainmodule != NULL && ((mainmodule->name != NULL && strcmp(mainmodule->name, INTRO_GAME) == 0) || (mainmodule->origmodulename != NULL && strcmp(mainmodule->origmodulename, INTRO_GAME) == 0)) && ((strcmp(u_type_name(unit->type), "city") == 0) || (strcmp(u_type_name(unit->type), "town") == 0))) { push_build_task(unit, 0, 99, 0, 0); } if (plan->supply_is_low && plan->supply_alarm) { plan->supply_alarm = FALSE; if (0 /* auto resupply */) { set_resupply_task(unit, NONMTYPE); } else if (plan->tasks && (plan->tasks->type == TASK_RESUPPLY || (plan->tasks->type == TASK_MOVE_TO && plan->tasks->next && plan->tasks->next->type == TASK_RESUPPLY))) { /* do nothing */ } else { clear_task_agenda(plan); set_waiting_for_tasks(unit, TRUE); } } if (plan->tasks) { /* (should check that doctrine being followed correctly) */ execute_task(unit); } else if (unit->side && unit->side->orders && execute_standing_order(unit, TRUE)) { execute_task(unit); } else if (plan->formation && move_into_formation(unit)) { execute_task(unit); } else { /* Our goal is now to get guidance from the side. */ set_waiting_for_tasks(unit, TRUE); } } /* A unit operating offensively advances and attacks when possible. */ int find_alternate_hit_target(Unit *unit, Task *task, int *xp, int *yp); static void plan_offense(Unit *unit) { int u = unit->type; int x, y, w, h, range, x1, y1, nx, ny; Plan *plan = unit->plan; Task *lasttask; Unit *unit2; if (resupply_if_low(unit)) { if (plan->tasks) execute_task(unit); return; } if (rearm_if_low(unit)) { if (plan->tasks) execute_task(unit); return; } if (repair_if_damaged(unit)) { if (plan->tasks) execute_task(unit); return; } if (plan->tasks) { execute_task(unit); if (plan->last_task_outcome == TASK_FAILED) { lasttask = &(plan->last_task); if (lasttask->type == TASK_HIT_UNIT && lasttask->args[2] != NONUTYPE && !target_visible(unit, lasttask)) { /* Target seems to have disappeared, look around for it. */ DMprintf("%s hit target has disappeared, looking for it; ", unit_desig(unit)); if (find_alternate_hit_target(unit, lasttask, &nx, &ny)) { if (plan->tasks && plan->tasks->type == lasttask->type && plan->tasks->args[0] == lasttask->args[0] && plan->tasks->args[1] == lasttask->args[1] && plan->tasks->args[2] == lasttask->args[2] && plan->tasks->args[3] == lasttask->args[3] ) { pop_task(plan); } push_hit_unit_task(unit, nx, ny, lasttask->args[2], lasttask->args[3]); DMprintf(" found at %d,%d\n", nx, ny); } else { DMprintf(" not found\n"); } } } /* Irrespective of what happened, we don't want to step on the task yet. */ return; } if (plan->maingoal && mobile(u)) { switch (plan->maingoal->type) { case GOAL_UNIT_OCCUPIED: /* Move to occupy our goal if necessary. */ unit2 = find_unit(plan->maingoal->args[0]); if (in_play(unit2) && unit->transport != unit2) set_occupy_task(unit, unit2); break; case GOAL_CELL_OCCUPIED: /* Move to occupy our goal if necessary. */ x = plan->maingoal->args[0]; y = plan->maingoal->args[1]; if (unit->x != x || unit->y != y) set_move_to_task(unit, x, y, 0); break; case GOAL_VICINITY_HELD: x = plan->maingoal->args[0]; y = plan->maingoal->args[1]; w = plan->maingoal->args[2]; h = plan->maingoal->args[3]; if (distance(x, y, unit->x, unit->y) > max(w, h)) { /* Outside the goal area - move in towards it. */ if (random_point_near(x, y, w / 2, &x1, &y1)) { x = x1; y = y1; } DMprintf("%s to go on offensive to %d,%d\n", unit_desig(unit), x, y); set_move_to_task(unit, x, y, max(w, h) / 2); if (unit->transport && mobile(unit->transport->type) && unit->transport->plan) { set_move_to_task(unit->transport, x, y, max(w, h) / 2); } } else { range = max(w, h); /* No special goal, look for something to fight with. */ /* Sometimes be willing to look a little farther out. */ if (probability(50)) range *= 2; if (do_for_occupants(unit)) { /* Occupants have decided for us, fall through. */ } else if (go_after_victim(unit, range)) { /* Found a victim to go after, fall through. */ } else if (probability(20) && self_build_base_for_self(unit)) { } else if (!g_see_all()) { DMprintf("%s will explore instead\n", unit_desig(unit)); plan_exploration(unit); /* or patrol */ /* Running under exploration rules now. */ return; } else { /* Do a random walk instead of just sitting there. */ DMprintf("%s to walk randomly\n", unit_desig(unit)); random_walk(unit); } } break; default: DMprintf("offensive unit has some goal\n"); break; } } else if (mobile(u)) { /* Play it safe. Search every cell within 3 times the tactical range. But don't search the whole world! */ range = min(3 * u_ai_tactical_range(u), operating_range_best(u)); if (probability(50)) range = min(range, 2 * u_acp(u)); if (do_for_occupants(unit)) { } else if (go_after_victim(unit, range)) { /* No special goal, but found something to fight with. */ } else if (!g_see_all()) { DMprintf("%s will explore instead\n", unit_desig(unit)); plan_exploration(unit); /* or patrol */ /* Running under exploration rules now. */ return; } else { /* should go to a "best location" if possible. */ /* (should do a sentry task) */ } } else if (can_fire(unit) && fire_at_opportunity(unit)) { } else { plan_offense_support(unit); } if (plan->tasks) { execute_task(unit); } else { DMprintf("%s found nothing to do offensively", unit_desig(unit)); /* Only do this if units may go into reserve. */ if (flip_coin() && g_units_may_go_into_reserve()) { DMprintf("- going into reserve"); plan->reserve = TRUE; } DMprintf("\n"); } } /* Look through list of occupants to see if an occupant needs the transport to do something. */ int do_for_occupants(Unit *unit) { Unit *occ; Goal *goal; Task *task; for_all_occupants(unit, occ) { if (occ->plan) { /* Get the unit towards its goal, if it has one. */ goal = occ->plan->maingoal; if (goal != NULL && goal->type == GOAL_VICINITY_HELD && (distance(goal->args[0], goal->args[1], unit->x, unit->y) > goal->args[2])) { set_move_to_task(unit, goal->args[0], goal->args[1], max(goal->args[2] / 2, 1)); DMprintf("%s will go where occupant %s wants to go (goal %s)\n", unit_desig(unit), unit_desig(occ), goal_desig(goal)); return TRUE; } /* If the unit does not have a goal, see if it has a task. */ for_all_tasks(occ->plan, task) { if ((task->type == TASK_MOVE_TO || task->type == TASK_HIT_UNIT) && (task->args[0] != unit->x || task->args[1] != unit->y) && distance(task->args[0], task->args[1], unit->x, unit->y) > 1 ) { /* Note that we assume the transport is mobile, which is OK currently because of where this routine is called from. */ set_move_to_task(unit, task->args[0], task->args[1], 1); DMprintf("%s will go where occupant %s wants to go (task %s)\n", unit_desig(unit), unit_desig(occ), task_desig(task)); return TRUE; } } } } return FALSE; } int self_build_base_for_self(Unit *unit) { int u = unit->type, u2, cando = FALSE; for_all_unit_types(u2) { if (uu_acp_to_create(u, u2) > 0 && ((uu_creation_cp(u, u2) >= u_cp(u2) && side_can_build(unit->side, u2)) || uu_acp_to_build(u, u2) > 0) /* (should check if any advantage to building) */ ) { cando = TRUE; break; } } if (cando) { DMprintf("%s building %s as a base for itself\n", unit_desig(unit), u_type_name(u2)); set_build_task(unit, u2, 1, 0, 0); return TRUE; } return FALSE; } static void plan_offense_support(Unit *unit) { int u = unit->type, u2, u3 = NONUTYPE, backup = NONUTYPE; Task *task; for_all_unit_types(u2) { if (mobile(u2) && (type_can_attack(u2) || type_can_fire(u2)) && side_can_build(unit->side, u2) && uu_acp_to_create(u, u2) > 0) { backup = u2; if (flip_coin()) { u3 = u2; break; } } } if (u3 == NONUTYPE) u3 = backup; if (is_unit_type(u3)) { task = unit->plan->tasks; if (task == NULL || task->type != TASK_BUILD) { DMprintf("%s supporting offense by building %s\n", unit_desig(unit), u_type_name(u3)); set_build_task(unit, u3, 2, 0, 0); } else { DMprintf("%s already building, leaving alone\n", unit_desig(unit)); } } else { DMprintf("%s has no way to support an offensive\n", unit_desig(unit)); } } int find_alternate_hit_target(Unit *unit, Task *task, int *xp, int *yp) { int range; tmpunit = unit; tmputype = task->args[2]; tmpside = side_n(task->args[3]); /* (should adjust search radius for speed?) */ range = u_acp(tmputype) + 1; return search_around(task->args[0], task->args[1], range, alternate_target_here, xp, yp, 1); } static int alternate_target_here(int x, int y) { UnitView *uview; for_all_view_stack(tmpunit->side, x, y, uview) { if (view_type(uview) == tmputype && view_side(uview) == tmpside) return TRUE; } return FALSE; } /* Defensive units don't go out looking for trouble, but they should react strongly to threats. */ static void plan_defense(Unit *unit) { int u = unit->type, range, x, y, w, h, x1, y1; Plan *plan = unit->plan; Unit *unit2; if (resupply_if_low(unit)) { if (plan->tasks) execute_task(unit); return; } if (rearm_if_low(unit)) { if (plan->tasks) execute_task(unit); return; } if (repair_if_damaged(unit)) { if (plan->tasks) execute_task(unit); return; } if (plan->tasks) { /* (should analyze and maybe decide to change task) */ execute_task(unit); return; } if (plan->maingoal) { switch (plan->maingoal->type) { case GOAL_UNIT_OCCUPIED: /* Move to occupy our goal if necessary. */ unit2 = find_unit(plan->maingoal->args[0]); if (in_play(unit2) && unit->transport != unit2) set_occupy_task(unit, unit2); break; case GOAL_CELL_OCCUPIED: /* Move to occupy our goal if necessary. */ x = plan->maingoal->args[0]; y = plan->maingoal->args[1]; if (unit->x != x || unit->y != y) set_move_to_task(unit, x, y, 0); break; case GOAL_VICINITY_HELD: x = plan->maingoal->args[0]; y = plan->maingoal->args[1]; w = plan->maingoal->args[2]; h = plan->maingoal->args[3]; if (distance(x, y, unit->x, unit->y) > max(w, h)) { /* Outside the goal area - move in towards it. */ if (random_point_near(x, y, w / 2, &x1, &y1)) { x = x1; y = y1; } DMprintf("%s to go on defensive to %d,%d\n", unit_desig(unit), x, y); set_move_to_task(unit, x, y, max(w, h) / 2); if (unit->transport && mobile(unit->transport->type) && unit->transport->plan) { set_move_to_task(unit->transport, x, y, max(w, h) / 2); } } else { range = max(w, h); /* No special goal, look for something to fight with. */ /* Sometimes be willing to look a little farther out. */ if (probability(50)) range *= 2; if (do_for_occupants(unit)) { /* Occupants have decided for us, fall through. */ } else if (go_after_victim(unit, range)) { /* Found a victim to go after, fall through. */ } else if (probability(20) && self_build_base_for_self(unit)) { } else if (!g_see_all()) { DMprintf("%s will explore instead\n", unit_desig(unit)); plan_exploration(unit); /* or patrol */ /* Running under exploration rules now. */ return; } } break; default: DMprintf("defensive unit has some goal\n"); break; } /* (might be able to defend by interposing self?) */ return; } /* Generally useful to capture things, so even units on defense should take opportunities if they can. */ if (capture_indep_if_nearby(unit)) return; if (capture_useful_if_nearby(unit)) return; if (can_fire(unit)) { /* No special goal, look for something to shoot at. */ if (fire_at_opportunity(unit)) { execute_task(unit); return; } /* Nothing to shoot at, so just hang out. */ /* (should consider moving to better shooting position if possible) */ /* Also go for out-of-range victim that we can fire at. */ } else if (can_attack(unit) || can_fire(unit)) { /* Use the tactical range. */ if (go_after_victim(unit, u_ai_tactical_range(unit->type))) { execute_task(unit); return; } /* Nobody close by, just hang out, shifting around a bit occasionally. */ if (mobile(unit->type) && probability(10)) { if (random_point_near(unit->x, unit->y, u_acp(u), &x1, &y1)) { DMprintf("%s to shift defensive position to %d,%d\n", unit_desig(unit), x1, y1); set_move_to_task(unit, x1, y1, 0); execute_task(unit); return; } } } else { /* No specific goal, no combat ability - so nothing to do! */ /* (but should test for ability to detonate or capture first) */ } if (plan->tasks) { execute_task(unit); } else if (!plan->reserve && g_units_may_go_into_reserve()) { /* Just stay in reserve for now. */ DMprintf("%s going into defensive reserve\n", unit_desig(unit)); plan->reserve = TRUE; } else { /* can never get here? */ } } static void plan_colonize(Unit *unit) { Plan *plan = unit->plan; Unit *unit2; int x, y, u2; if (!mobile(unit->type)) { plan_colonize_support(unit); } if (resupply_if_low(unit)) { if (plan->tasks) execute_task(unit); return; } if (repair_if_damaged(unit)) { if (plan->tasks) execute_task(unit); return; } /* Set build task if we are colonizing and have found a good spot. */ if (plan->maingoal && plan->maingoal->type == GOAL_COLONIZE) { u2 = plan->maingoal->args[0]; if (good_cell_to_colonize(unit->x, unit->y) /* Check for nexthere prevents two colonizers from trying to colonize the same cell and blocking each other from doing so again and again due to terrain capacity restrictions. */ && unit->nexthere == NULL) { set_build_task(unit, u2, 1, 0, 0); DMprintf("%s colonizing by building %s\n", unit_desig(unit), u_type_name(u2)); } } /* Then proceed with the tasks. */ if (plan->tasks) { execute_task(unit); return; } /* The task queue was empty. Get a new task. */ if (plan->maingoal) { switch (plan->maingoal->type) { case GOAL_UNIT_OCCUPIED: /* Move to occupy our goal if necessary. */ unit2 = find_unit(plan->maingoal->args[0]); if (in_play(unit2) && unit->transport != unit2) set_occupy_task(unit, unit2); break; case GOAL_CELL_OCCUPIED: /* Move to our goal if necessary. */ x = plan->maingoal->args[0]; y = plan->maingoal->args[1]; if (unit->x != x || unit->y != y) set_move_to_task(unit, x, y, 0); break; case GOAL_COLONIZE: /* Move in a random dir along a straight line. */ set_move_dir_task(unit, random_dir(), xrandom(20)); break; default: /* Should never happen! */ DMprintf("Colonizer %s has weird goal %s\n", unit_desig(unit), goal_desig(plan->maingoal)); break; } } else { /* Should never happen! */ DMprintf("Colonizer %s has no goal\n", unit_desig(unit)); } /* Execute the new task. */ if (plan->tasks) { execute_task(unit); return; } else { /* Holding occupied unit and waiting for something to happen. */ DMprintf("Colonizer %s with goal %s unable to find new task\n", unit_desig(unit), goal_desig(plan->maingoal)); } } static void plan_colonize_support(Unit *unit) { int u, type[MAXUTYPES], numtypes = 0; /* Load all colonizing types into type vector. */ for_all_unit_types(u) { if (side_can_build(unit->side, u) && could_create(unit->type, u) && u_colonizer_worth(u) > 0) { type[numtypes] = u; numtypes++; } } /* Then pick one of them at random. */ if (numtypes) { u = type[xrandom(numtypes)]; DMprintf("%s supporting colonization by building %s\n", unit_desig(unit), u_type_name(u)); push_build_task(unit, u, 1, 0, 0); return; } else { /* Load all possible types instead if we failed. */ for_all_unit_types(u) { if (side_can_build(unit->side, u) && could_create(unit->type, u)) { type[numtypes] = u; numtypes++; } } } /* Then pick one of them at random. */ if (numtypes) { u = type[xrandom(numtypes)]; DMprintf("%s can't build colonizers, building %s instead\n", unit_desig(unit), u_type_name(u)); push_build_task(unit, u, 1, 0, 0); return; /* Nothing can be built. */ } else { DMprintf("Sorry, %s can't build anything!\n", unit_desig(unit)); } } static void plan_improve(Unit *unit) { #if 0 int u, type[MAXUTYPES], numtypes = 0; #endif /* Execute the task if we have one. */ if (unit->plan->tasks) { execute_task(unit); return; } #if 0 /* Load all immobile types (facilities) into type vector. */ for_all_unit_types(u) { if (side_can_build(unit->side, u) && could_create(unit->type, u) && u_facility(u)) { type[numtypes] = u; numtypes++; } } /* Then pick one of them at random. */ if (numtypes) { u = type[xrandom(numtypes)]; DMprintf("%s improving itself by building %s\n", unit_desig(unit), u_type_name(u)); push_build_task(unit, u, 1, 0, 0); return; /* Load all possible types instead if we failed. */ } else for_all_unit_types(u) { if (side_can_build(unit->side, u) && could_create(unit->type, u)) { type[numtypes] = u; numtypes++; } } /* Then pick one of them at random. */ if (numtypes) { u = type[xrandom(numtypes)]; DMprintf("%s can't build facility, building %s instead\n", unit_desig(unit), u_type_name(u)); push_build_task(unit, u, 1, 0, 0); return; /* Nothing can be built. */ } else DMprintf("Sorry, %s can't build anything!\n", unit_desig(unit)); #endif } static int good_cell_to_colonize(int x, int y) { int x1, y1, u, m, supply[MAXMTYPES], maxsize; Unit *unit2; /* Then check if another advanced unit is too close to this cell. */ for_all_units(unit2) { if (u_advanced(unit2->type) && (distance(x, y, unit2->x, unit2->y) < g_ai_advanced_unit_separation())) return FALSE; } /* Then check that the minimal size goal can be attained for all possible advanced units (even if we start with a village it may eventually grow into a metropolis). */ for_all_unit_types(u) { if (!u_advanced(u)) continue; maxsize = PROPHI; /* First zero the supply vector. */ for_all_material_types (m) supply[m] = 0; /* Go through all cells within reach and compute supply of materials. */ for_all_cells_within_range(x, y, u_reach(u), x1, y1) { if (!inside_area(x1, y1)) continue; /* Don't count supply in cells already used by others. */ if (user_defined() && user_at(x1, y1) != NOUSER) continue; /* Add the supply of materials from this cell. */ for_all_material_types (m) supply[m] += production_at(x1, y1, m); } /* Calculate max sustainable city size at this location. */ for_all_material_types (m) { if (um_consumption_per_size(u, m) > 0) maxsize = min(maxsize, supply[m] / um_consumption_per_size(u, m)); } /* Return FALSE if there is not enough food around to sustain the minimal size goal for this unit type. */ if (maxsize < u_minimal_size_goal(u)) return FALSE; } return TRUE; } static void plan_exploration(Unit *unit) { Unit *unit2; Plan *plan = unit->plan; int u = unit->type; int x, y, w, h, range, x1, y1; /* If the world has no secrets, exploration is sort of pointless. */ if (g_see_all() && g_units_may_go_into_reserve()) { plan->reserve = TRUE; return; } if (resupply_if_low(unit)) { if (plan->tasks) execute_task(unit); return; } if (repair_if_damaged(unit)) { if (plan->tasks) execute_task(unit); return; } if (capture_indep_if_nearby(unit)) return; if (capture_useful_if_nearby(unit)) return; if (plan->tasks) { /* (should see if a change of task is worthwhile) */ execute_task(unit); return; } if (plan->maingoal) { switch (plan->maingoal->type) { case GOAL_UNIT_OCCUPIED: /* Move to occupy our goal if necessary. */ unit2 = find_unit(plan->maingoal->args[0]); if (in_play(unit2) && unit->transport != unit2) set_occupy_task(unit, unit2); break; case GOAL_CELL_OCCUPIED: /* Move to occupy our goal if necessary. */ x = plan->maingoal->args[0]; y = plan->maingoal->args[1]; if (unit->x != x || unit->y != y) set_move_to_task(unit, x, y, 0); break; case GOAL_VICINITY_KNOWN: case GOAL_VICINITY_HELD: if (mobile(u)) { x = plan->maingoal->args[0]; y = plan->maingoal->args[1]; w = plan->maingoal->args[2]; h = plan->maingoal->args[3]; if (distance(x, y, unit->x, unit->y) > max(w, h)) { /* Out of the area, move into it. */ if (random_point_near(x, y, max(w, h) / 2, &x1, &y1)) { x = x1; y = y1; } DMprintf("%s to explore towards %d,%d\n", unit_desig(unit), x, y); set_move_to_task(unit, x, y, max(w, h) / 2); } else { if (explore_reachable_cell(unit, max(w, h) + 2)) { /* Found a cell to explore. */ } else { if (flip_coin()) { DMprintf("%s clearing goal\n", unit_desig(unit)); plan->maingoal = NULL; } DMprintf("%s to walk randomly\n", unit_desig(unit)); random_walk(unit); } } } else { plan_explorer_support(unit); } break; case GOAL_WORLD_KNOWN: if (mobile(u)) { if (explore_reachable_cell(unit, area.maxdim)) { /* Found a cell to explore. */ } else { DMprintf("%s to walk randomly\n", unit_desig(unit)); random_walk(unit); } } else { plan_explorer_support(unit); } break; default: DMprintf("%s goal %s?\n", unit_desig(unit), goal_desig(unit->plan->maingoal)); break; } } else { /* No specific goal, just poke around. */ if (mobile(u)) { range = area.maxdim / 2; if (explore_reachable_cell(unit, range)) { } else if (flip_coin()) { /* (should call a plan eraser) */ unit->plan->type = PLAN_PASSIVE; } else { DMprintf("%s to walk randomly\n", unit_desig(unit)); random_walk(unit); } } else { plan_explorer_support(unit); } } if (plan->tasks) { execute_task(unit); } } static void plan_explorer_support(Unit *unit) { int u = unit->type, u2, u3, backup; Task *task; for_all_unit_types(u2) { if (mobile(u2) && side_can_build(unit->side, u2) && 1 /* better on more kinds of terrain? */ && uu_acp_to_create(u, u2) > 0) { backup = u2; if (flip_coin()) { u3 = u2; break; } } } if (u3 == NONUTYPE) u3 = backup; if (u3 != NONUTYPE) { task = unit->plan->tasks; if (task == NULL || task->type != TASK_BUILD) { DMprintf("%s supporting exploration by building %s\n", unit_desig(unit), u_type_name(u3)); push_build_task(unit, u3, 2, 0, 0); } else { DMprintf("%s already building, leaving alone\n", unit_desig(unit)); } } else { DMprintf("%s has no way to support exploration\n", unit_desig(unit)); } } /* These are used by AIs as well. */ int victim_x, victim_y, victim_rating, victim_utype, victim_sidenum; int victim_here(int x, int y) { int u2 = NONUTYPE, rating, dist; Unit *unit3; Side *side = tmpunit->side, *oside = NULL; UnitView *uview; for_all_view_stack(side, x, y, uview) { u2 = view_type(uview); oside = view_side(uview); if (enemy_side(side, oside) && ((could_hit(tmpunit->type, u2) /* Also consider damage by fire. Moreover, if the unit can carry occupants they may be vulnerable even though the unit itself is not. */ && (uu_damage(tmpunit->type, u2) > 0 || uu_fire_damage(tmpunit->type, u2) > 0 || type_can_carry(u2)) && (!worth_capturing(side, u2, oside, x, y) || capture_chance(tmpunit->type, u2, oside) > 0)) || capture_chance(tmpunit->type, u2, oside) > 0) ) { /* If our unit can't capture the prospective victim, but somebody else is planning to do so, leave it alone and keep looking for a victim. */ if ((uu_acp_to_attack(tmpunit->type, u2) < 1 || capture_chance(tmpunit->type, u2, oside) <= 0) && side_planning_to_capture(side, u2, x, y)) continue; /* If we can capture this unit, get excited and go for it right away (perhaps too hasty though? what if a stack protector is here also?) */ if (capture_chance(tmpunit->type, u2, oside) > 0) { victim_utype = u2; victim_sidenum = side_number(oside); return TRUE; } /* If we're carrying occupants that might be hurt by the intended victim, lose interest. */ if (tmpunit->occupant != NULL && could_hit(u2, tmpunit->type) && uu_damage(u2, tmpunit->type) > 0 /* (should) and valuable occupants not protected... */ ) continue; /* Use the basic hit worth as a relative rating. */ rating = uu_zz_bhw(tmpunit->type, u2); /* Further-away units are less interesting than closer ones. */ dist = distance(tmpunit->x, tmpunit->y, x, y); if (dist > u_acp(tmpunit->type)) rating /= max(1, isqrt(dist - u_acp(tmpunit->type))); /* A large city is more worth capturing. */ rating *= view_size(uview); /* Real enemies are more important targets. */ if (oside != indepside) rating *= 2; /* Always attack units that threaten one of our own cities. */ /* (should not scan if no advanced units in game) */ for_all_side_units(side, unit3) { if (u_advanced(unit3->type) && distance(unit3->x, unit3->y, x, y) < 5) rating *= 5 - distance(unit3->x, unit3->y, x, y); } if (rating > victim_rating || (rating == victim_rating && flip_coin())) { victim_x = x; victim_y = y; victim_rating = rating; victim_utype = u2; victim_sidenum = oside->id; } } } return FALSE; } /* This decides whether a given unit type seen at a given location is worth trying to capture. */ int worth_capturing(Side *side, int u2, Side *side2, int x, int y) { int u, bestchance = 0; /* See how likely we are to be able to capture the type. */ for_all_unit_types(u) { bestchance = max(capture_chance(u, u2, side2), bestchance); } if (bestchance == 0) return FALSE; /* (should account for other considerations too, like which types of units we have) */ return TRUE; } /* Note: go_after_victim can be used for both attacking and firing units since victim_here only tests for could_hit. */ /* This routine looks for somebody, anybody to attack. */ int go_after_victim(Unit *unit, int range) { int x, y, rslt; tmpunit = unit; DMprintf("%s seeking victim within %d; found ", unit_desig(unit), range); victim_rating = -9999; rslt = search_around(unit->x, unit->y, range, victim_here, &x, &y, 1); if (rslt) { DMprintf("s%d %s at %d,%d\n", victim_sidenum, u_type_name(victim_utype), x, y); /* Set up a task to go after the unit found. */ /* (should be able to set capture task if better) */ set_hit_unit_task(unit, x, y, victim_utype, victim_sidenum); if (unit->transport != NULL && mobile(unit->transport->type) && unit->transport->plan) { set_move_to_task(unit->transport, x, y, 1); } } else if (victim_rating > -9999) { DMprintf("s%d %s (rated %d) at %d,%d\n", victim_sidenum, u_type_name(victim_utype), victim_rating, victim_x, victim_y); /* Set up a task to go after the unit found. */ /* (should be able to set capture task if better) */ set_hit_unit_task(unit, victim_x, victim_y, victim_utype, victim_sidenum); if (unit->transport != NULL && mobile(unit->transport->type) && unit->transport->plan) { set_move_to_task(unit->transport, victim_x, victim_y, 1); } /* We succeeded after all. */ rslt = TRUE; } else { DMprintf("nothing\n"); } return rslt; } #if 0 /* Unused. */ /* The point of this new function is to limit the search for captives to a given range. */ int go_after_captive(Unit *unit, int range) { int x, y, rslt; tmpunit = unit; DMprintf("%s searching for useful capture within %d; found ", unit_desig(unit), range); rslt = search_around(unit->x, unit->y, range, useful_captureable_here, &x, &y, 1); if (rslt) { DMprintf("one at %d,%d\n", x, y); /* Set up a task to go after the unit found. */ set_capture_task(unit, x, y); if (unit->transport && mobile(unit->transport->type) && unit->transport->plan) { set_move_to_task(unit->transport, x, y, 1); } return (execute_task(unit) != TASK_FAILED); } else { DMprintf("nothing\n"); return FALSE; } } #endif /* Given a location and a unit (in tmpunit), try to identify a target. */ /* (should move to place to share with AIs) */ int target_x, target_y, target_rating, target_utype, target_sidenum; int target_here(int x, int y) { int u2 = NONUTYPE, rating, dist; Side *side = tmpunit->side, *oside = NULL; Unit *unit3; UnitView *uview; for_all_view_stack(side, x, y, uview) { u2 = view_type(uview); oside = view_side(uview); /* (should move all tests inside loop) */ if (is_unit_type(u2) && enemy_side(side, oside) && could_hit(tmpunit->type, u2) /* Also consider damage by fire. Moreover, if the unit can carry occupants they may be vulnerable even though the unit itself is not. */ && (uu_damage(tmpunit->type, u2) > 0 || uu_fire_damage(tmpunit->type, u2) > 0 || type_can_carry(u2)) /* and have correct ammo */ && !side_planning_to_capture(side, u2, x, y) ) { rating = uu_zz_bfw(tmpunit->type, u2); /* Further-away units are less interesting than closer ones. */ dist = distance(tmpunit->x, tmpunit->y, x, y); if (dist > 0) rating /= dist; /* A larger city is more worth capturing. */ rating *= view_size(uview); /* Real enemies are more important targets. */ if (oside != NULL) rating *= 2; /* Always attack units that threaten one of our own cities. */ for_all_side_units(side, unit3) { if (u_advanced(unit3->type) && distance(unit3->x, unit3->y, x, y) < 5) rating *= 5 - distance(unit3->x, unit3->y, x, y); } if (rating > target_rating || (rating == target_rating && flip_coin())) { target_x = x; target_y = y; target_rating = rating; target_utype = u2; target_sidenum = side_number(oside); } } } return FALSE; } int fire_at_opportunity(Unit *unit) { int x, y, range, rslt; tmpunit = unit; range = u_range(unit->type); target_rating = -9999; DMprintf("%s seeking target within %d; found ", unit_desig(unit), range); rslt = search_around(unit->x, unit->y, range, target_here, &x, &y, 1); if (rslt) { DMprintf("s%d %s at %d,%d\n", target_sidenum, u_type_name(target_utype), x, y); /* Set up a task to shoot at the unit found. */ set_hit_unit_task(unit, x, y, target_utype, target_sidenum); } else if (target_rating > -9999) { DMprintf("s%d %s (rated %d) at %d,%d\n", target_sidenum, u_type_name(target_utype), target_rating, x, y); /* Set up a task to shoot at the unit found. */ set_hit_unit_task(unit, target_x, target_y, target_utype, target_sidenum); } else { DMprintf("nothing\n"); } return rslt; } /* This is called by individual plans to see if a particular unit type at a particular location is scheduled to be captured. */ static int side_planning_to_capture(Side *side, int u, int x, int y) { Task *task; Unit *unit; for_all_side_units(side, unit) { if (in_play(unit) && unit->plan) { for_all_tasks(unit->plan, task) { if (task->type == TASK_CAPTURE && task->args[0] == x && task->args[1] == y && (task->args[2] == NONUTYPE || task->args[2] == u)) return TRUE; if (task->type == TASK_HIT_UNIT && task->args[0] == x && task->args[1] == y && (task->args[2] == NONUTYPE || task->args[2] == u) && uu_capture(unit->type, u) > 0) return TRUE; } } } return FALSE; } /* Check to see if our grand plans are at risk of being sideswiped by lack of supply, and set up a resupply task if so. */ int resupply_if_low(Unit *unit) { int u = unit->type, lowm; Task *curtask = unit->plan->tasks; if (!mobile(u)) return FALSE; lowm = low_on_supplies_one(unit); if (lowm != NONMTYPE) { /* See if we're already moving to a supply source. */ if (curtask != NULL && curtask->type == TASK_MOVE_TO /* or other types? */ && supplies_here(unit, curtask->args[0], curtask->args[1], lowm)) /* Let the movement task execute. */ return FALSE; /* See if we already have a resupply task for the same material. */ if (curtask != NULL && curtask->type == TASK_RESUPPLY && curtask->args[0] == lowm) return (execute_task(unit) != TASK_FAILED); /* Otherwise set up a task. */ DMprintf("%s low on %s, looking for source\n", unit_desig(unit), m_type_name(lowm)); set_resupply_task(unit, lowm); return (execute_task(unit) != TASK_FAILED); } return FALSE; } /* Return a type of essential material that the unit is running out of. */ int low_on_supplies_one(Unit *unit) { int u = unit->type, m; for_all_material_types(m) { if ((um_base_consumption(u, m) > 0 || um_consumption_per_move(u, m) > 0) && um_storage_x(u, m) > 0 && unit->supply[m] <= (unit_doctrine(unit)->resupply_percent * um_storage_x(u, m)) / 100 ) { return m; } } return NONMTYPE; } int rearm_if_low(Unit *unit) { int u = unit->type, lowm; Task *curtask = unit->plan->tasks; if (!mobile(u)) return FALSE; lowm = low_on_ammo_one(unit); if (lowm != NONMTYPE) { if (curtask != NULL && curtask->type == TASK_MOVE_TO /* or other types? */ && supplies_here(unit, curtask->args[0], curtask->args[1], lowm)) /* Let the movement task execute. */ return FALSE; /* See if we already have a resupply task for the same material. */ if (curtask != NULL && curtask->type == TASK_RESUPPLY && curtask->args[0] == lowm) return (execute_task(unit) != TASK_FAILED); /* Otherwise set up a task. */ DMprintf("%s low on %s, looking for source\n", unit_desig(unit), m_type_name(lowm)); set_resupply_task(unit, lowm); return (execute_task(unit) != TASK_FAILED); } return FALSE; } /* Return a type of material that we want to use in combat. */ int low_on_ammo_one(Unit *unit) { int u = unit->type, m; for_all_material_types(m) { if (um_consumption_per_attack(u, m) > 0 && um_storage_x(u, m) > 0 && unit->supply[m] <= (unit_doctrine(unit)->rearm_percent * um_storage_x(u, m)) / 100 ) { return m; } } return NONMTYPE; } int supplies_here(Unit *unit, int x, int y, int m) { Unit *unit2; for_all_stack(x, y, unit2) { if (unit2 != unit && unit_trusts_unit(unit2, unit) && unit2->supply[m] > 0 && um_outlength(unit2->type, m) >= 0) { return TRUE; } } return FALSE; } int repair_if_damaged(Unit *unit) { int u = unit->type; Task *curtask = unit->plan->tasks; if (unit->hp > (u_hp_max(u) * unit_doctrine(unit)->repair_percent) / 100) return FALSE; if (u_hp_recovery(u) <= 0 && !any_auto_repair) return FALSE; /* See if we already have a repair task. */ if (curtask != NULL && curtask->type == TASK_REPAIR) return (execute_task(unit) != TASK_FAILED); if (curtask != NULL && curtask->type == TASK_MOVE_TO /* or other types? */ && repairs_here(unit, curtask->args[0], curtask->args[1])) /* Just let the movement task execute. */ return FALSE; /* (should test whether any repairing units in existence) */ /* Otherwise set up a task. */ DMprintf("%s badly damaged, looking for repairs\n", unit_desig(unit)); set_repair_task(unit); return (execute_task(unit) != TASK_FAILED); } int repairs_here(Unit *unit, int x, int y) { Unit *unit2; for_all_stack(x, y, unit2) { if (unit2 != unit && unit_trusts_unit(unit2, unit) && (uu_auto_repair(unit2->type, unit->type) > 0 || 0 /* (should allow for explicit repair actions) */)) { return TRUE; } } return FALSE; } /* Look within a limited distance for any independent unit that could be captured, and set up tasks to go get it. */ int capture_indep_if_nearby(Unit *unit) { /* Use the unit's tactical range instead. */ int u = unit->type; int range = u_ai_tactical_range(u); int x, y, rslt; Task *curtask = unit->plan->tasks; if (!mobile(u)) return FALSE; if (!could_capture_any(unit->type)) return FALSE; tmpunit = unit; /* See if we're already doing such a task. */ if (curtask != NULL && ((curtask->type == TASK_MOVE_TO && indep_captureable_here(curtask->args[0], curtask->args[1])) || (curtask->type == TASK_CAPTURE && indep_captureable_here(curtask->args[0], curtask->args[1])))) return FALSE; DMprintf("%s searching for easy capture within %d; found ", unit_desig(unit), range); rslt = search_around(unit->x, unit->y, range, indep_captureable_here, &x, &y, 1); if (rslt) { DMprintf("one at %d,%d\n", x, y); /* Set up a task to go after the unit found. */ set_capture_task(unit, x, y, tmputype, tmpside->id); if (unit->transport && mobile(unit->transport->type) && unit->transport->plan) { set_move_to_task(unit->transport, x, y, 1); } return (execute_task(unit) != TASK_FAILED); } else { DMprintf("nothing\n"); } return FALSE; } int indep_captureable_here(int x, int y) { int u2; Side *side = tmpunit->side, *side2; UnitView *uview; for_all_view_stack(side, x, y, uview) { u2 = view_type(uview); side2 = view_side(uview); if (side2 == indepside && side != indepside && capture_chance(tmpunit->type, u2, side2) > 10) { tmputype = u2; tmpside = side2; return TRUE; } } return FALSE; } /* Look within a limited distance for any type of unit that would be good to own, and set up tasks to go get it. */ int capture_useful_if_nearby(Unit *unit) { /* Use the unit's tactical range instead. */ int u = unit->type; int range = u_ai_tactical_range(u); int x, y, rslt; Task *curtask = unit->plan->tasks; if (!mobile(u)) return FALSE; if (!could_capture_any(unit->type)) return FALSE; tmpunit = unit; /* See if we're already doing such a task. */ if (curtask != NULL && ((curtask->type == TASK_MOVE_TO && useful_captureable_here(curtask->args[0], curtask->args[1])) || (curtask->type == TASK_CAPTURE && useful_captureable_here(curtask->args[0], curtask->args[1])))) return FALSE; DMprintf("%s searching for useful capture within %d; found ", unit_desig(unit), range); rslt = search_around(unit->x, unit->y, range, useful_captureable_here, &x, &y, 1); if (rslt) { DMprintf("one at %d,%d\n", x, y); /* Set up a task to go after the unit found. */ set_capture_task(unit, x, y, tmputype, tmpside->id); if (unit->transport && mobile(unit->transport->type) && unit->transport->plan) { set_move_to_task(unit->transport, x, y, 1); } return (execute_task(unit) != TASK_FAILED); } else { DMprintf("nothing\n"); } return FALSE; } int useful_captureable_here(int x, int y) { int u2; Side *side = tmpunit->side, *side2; UnitView *uview; for_all_view_stack(side, x, y, uview) { u2 = view_type(uview); side2 = view_side(uview); if (!trusted_side(side, side2) && capture_chance(tmpunit->type, u2, side2) > 0 && useful_type(side, u2) ) { tmputype = u2; tmpside = side2; return TRUE; } } return FALSE; } /* Return true if the given type of unit is useful in some way to the given side. This is almost always true. */ int useful_type(Side *side, int u) { if (!type_allowed_on_side(u, side)) return FALSE; return TRUE; } int could_capture_any(int u) { int u2; for_all_unit_types(u2) { if (uu_capture(u, u2) > 0 || uu_indep_capture(u, u2) > 0) return TRUE; /* also check if u2 in game, on other side, etc? */ } return FALSE; } /* This is a semi-testing routine that basically picks something for the unit to do without worrying about its validity. The rest of the system should function correctly no matter what this thing comes up with. Piles of warnings are likely, but that's OK. */ /* (should actually have two modes for this - testing, which allows bad data, and normal usage with no garbage generated, which is used with unintelligent AIs) */ static void plan_random(Unit *unit) { int dir, x1, y1, n, randmtype; TaskType tasktype; Task *task; Action action; char *argtypestr; if (flip_coin()) { if (unit->plan->tasks) { execute_task(unit); return; } /* Pick a random task. */ tasktype = xrandom((int) NUMTASKTYPES); switch (tasktype) { case TASK_NONE: task = create_task(tasktype); add_task(unit, CLEAR_AGENDA, task); break; case TASK_BUILD: if (flip_coin()) random_point_near(unit->x, unit->y, 4, &x1, &y1); else random_point(&x1, &y1); task = create_build_task(unit, xrandom(numutypes), xrandom(99), x1, y1); add_task(unit, CLEAR_AGENDA, task); break; case TASK_CAPTURE: dir = random_dir(); point_in_dir(unit->x, unit->y, dir, &x1, &y1); set_capture_task(unit, x1, y1, NONUTYPE, ALLSIDES); break; case TASK_COLLECT: if (nummtypes > 0) { randmtype = xrandom(nummtypes); random_point_near(unit->x, unit->y, 4, &x1, &y1); set_collect_task(unit, randmtype, x1, y1); } else { set_sentry_task(unit, xrandom(5)); } break; case TASK_DISBAND: set_disband_task(unit); break; case TASK_HIT_POSITION: task = create_task(tasktype); if (flip_coin()) random_point_near(unit->x, unit->y, 4, &x1, &y1); else random_point(&x1, &y1); task->args[0] = x1; task->args[1] = y1; add_task(unit, CLEAR_AGENDA, task); break; case TASK_HIT_UNIT: task = create_task(tasktype); add_task(unit, CLEAR_AGENDA, task); break; case TASK_MOVE_DIR: dir = random_dir(); n = xrandom(10); set_move_dir_task(unit, dir, n); break; case TASK_MOVE_TO: dir = random_dir(); point_in_dir(unit->x, unit->y, dir, &x1, &y1); set_move_to_task(unit, x1, y1, 0); break; case TASK_OCCUPY: task = create_task(tasktype); add_task(unit, CLEAR_AGENDA, task); break; case TASK_PICKUP: task = create_task(tasktype); add_task(unit, CLEAR_AGENDA, task); break; case TASK_PRODUCE: if (nummtypes > 0) { randmtype = xrandom(nummtypes); task = create_produce_task(unit, randmtype, xrandom(100)); add_task(unit, CLEAR_AGENDA, task); } else { set_sentry_task(unit, xrandom(5)); } break; case TASK_REPAIR: set_repair_task(unit); break; case TASK_DEVELOP: task = create_task(tasktype); add_task(unit, CLEAR_AGENDA, task); break; case TASK_RESUPPLY: if (nummtypes > 0) { randmtype = xrandom(nummtypes); set_resupply_task(unit, randmtype); } else { set_sentry_task(unit, xrandom(5)); } break; case TASK_SENTRY: set_sentry_task(unit, xrandom(5)); break; default: case_panic("task type", tasktype); break; } } if (unit->plan && unit->plan->tasks) return; /* Otherwise go for a random action. */ memset(&action, 0, sizeof(Action)); action.type = (ActionType) xrandom((int) NUMACTIONTYPES); argtypestr = actiondefns[(int) action.type].argtypes; make_plausible_random_args(argtypestr, 0, &(action.args[0]), unit); if (flip_coin()) { action.actee = unit->id; } else { while (find_unit(action.actee = xrandom(numunits)+1) == NULL && probability(98)); } unit->act->nextaction = action; DMprintf("%s will randomly try %s\n", unit_desig(unit), action_desig(&action)); } /* This attempts to make some vaguely plausible arguments for an action, using the types of each arg as a guide. It also generates *invalid* arguments occasionally, which tests error checking in the actions' code. This is mainly useful for testing. */ void make_plausible_random_args(char *argtypestr, int i, int *args, Unit *unit) { char argch; int slen, arg; slen = strlen(argtypestr); while (i < slen && i < 10) { argch = argtypestr[i]; switch (argch) { case 'n': arg = (flip_coin() ? xrandom(10) : (flip_coin() ? xrandom(100) : (xrandom(20000) - 10000))); break; case 'u': /* Go a little outside range, so as to get some invalid types. */ arg = xrandom(numutypes + 2) - 1; break; case 'm': arg = xrandom(nummtypes + 2) - 1; break; case 't': arg = xrandom(numttypes + 2) - 1; break; case 'a': arg = xrandom(numatypes + 2) - 1; break; case 'x': arg = (unit != NULL && flip_coin() ? (unit->x + xrandom(5) - 2) : (xrandom(area.width + 4) - 2)); break; case 'y': arg = (unit != NULL && flip_coin() ? (unit->y + xrandom(5) - 2) : (xrandom(area.height + 4) - 2)); break; case 'z': arg = (flip_coin() ? 0 : xrandom(10)); break; case 'd': arg = random_dir(); break; case 'U': /* Cast around for a valid unit. */ while (find_unit(arg = xrandom(numunits)+1) == NULL && probability(98)); break; case 'S': arg = xrandom(numsides + 3) - 1; break; default: run_warning("Garbled action arg type '%c'\n", argch); arg = 0; break; } args[i++] = arg; } } /* Random walking just attempts to move around. */ void random_walk(Unit *unit) { int dir = random_dir(), x1, y1, tries = 0; while (!interior_point_in_dir(unit->x, unit->y, dir, &x1, &y1)) { if (++tries > 500) { run_warning("something is wrong"); break; } dir = random_dir(); } set_move_to_task(unit, x1, y1, 0); } /* Record the unit as waiting for orders about what to do. */ void set_waiting_for_tasks(Unit *unit, int flag) { unit->plan->waitingfortasks = flag; if (unit->side != NULL) unit->side->numwaiting += (unit->plan->waitingfortasks ? 1 : -1); } /* General routine to wake a unit up (and maybe all its cargo). */ void wake_unit(Side *side, Unit *unit, int wakeocc) { Unit *occ; /* (should test that side is permitted to wake) */ if (unit->plan) { unit->plan->asleep = FALSE; unit->plan->reserve = FALSE; update_unit_display(side, unit, TRUE); } if (wakeocc) { for_all_occupants(unit, occ) wake_unit(side, occ, wakeocc); } } /* The area wakeup. */ static int tmpflag; static void wake_at(int x, int y) { Unit *unit; for_all_stack(x, y, unit) { if (side_controls_unit(tmpside, unit)) { wake_unit(tmpside, unit, tmpflag); } } } void wake_area(Side *side, int x, int y, int n, int occs) { tmpside = side; tmpflag = occs; apply_to_area(x, y, n, wake_at); } void set_formation(Unit *unit, Unit *leader, int x, int y, int dist, int flex) { Plan *plan = unit->plan; Goal *goal; if (plan == NULL) return; if (!in_play(unit)) return; if (leader != NULL) { if (!in_play(leader)) return; goal = create_goal(GOAL_KEEP_FORMATION, unit->side, TRUE); goal->args[0] = leader->id; goal->args[1] = x; goal->args[2] = y; goal->args[3] = dist; goal->args[4] = flex; plan->formation = goal; plan->funit = leader; } else { /* A NULL leader means to clear the formation goal. */ plan->formation = NULL; plan->funit = NULL; } } void delay_unit(Unit *unit, int flag) { if (in_play(unit) && unit->plan) { unit->plan->delayed = TRUE; } } #if 0 /* The four functions below are not used anywhere. */ /* Return the distance that we can go by shortest path before running out of important supplies. Will return at least 1, since we can *always* move one cell to safety. This is a worst-case routine, too complicated to worry about units getting refreshed by terrain or whatever. */ int range_left(Unit *unit) { int u = unit->type, m, least = 12345; /* bigger than any real value */ for_all_material_types(m) { if (um_consumption_per_move(u, m) > 0) { least = min(least, unit->supply[m] / um_consumption_per_move(u, m)); } #if 0 /* This code is too pessimistic if no account taken of supply line or production, so leave out for now. */ if (um_base_consumption(u, m) > 0) { tmp = (u_speed(u) * unit->supply[m]) / um_base_consumption(u, m); least = min(least, tmp); } #endif } return (least == 12345 ? 1 : least); } /* Estimate the goodness and badness of cells in the immediate vicinity. */ int find_worths(range) int range; { return 0; } /* This is a heuristic estimation of the value of one unit type hitting on another. Should take cost of production into account as well as the chance and significance of any effect. */ int attack_worth(unit, e) Unit *unit; int e; { int u = unit->type, worth; worth = uu_zz_bhw(u, e); /* Risk of death? */ /* if (uu_damage(e, u) >= unit->hp) worth /= (could_capture(u, e) ? 1 : 4); if (could_capture(u, e)) worth *= 4; */ return worth; } /* Support functions. */ /* Return true if the given position is threatened by the given unit type. */ int threat(Side *side, int u, int x0, int y0) { #if 0 int d, x, y, thr = 0; Side *side2; int view; for_all_directions(d) { point_in_dir(x0, y0, d, &x, &y); view = 0 /* side_view(side, x, y) */; if (view != UNSEEN && view != EMPTY) { side2 = side_n(vside(view)); if (allied_side(side, side2)) { if (uu_capture(u, vtype(view)) > 0) thr += 1000; if (uu_zz_bhw(u, vtype(view)) > 0) thr += 100; } } } return thr; #endif return 0; } #endif void pop_task(Plan *plan) { Task *oldtask; if (plan->tasks) { oldtask = plan->tasks; plan->tasks = plan->tasks->next; free_task(oldtask); } } #if 0 /* The two functions below are unused. */ /* Patrol just does move_to, but cycling waypoints around when the first */ /* one has been reached. */ int move_patrol(Unit *unit) { #if 0 int tx, ty; if (unit->plan->orders.rept-- > 0) { if (unit->x == unit->plan->orders.p.pt[0].x && unit->y == unit->plan->orders.p.pt[0].y) { tx = unit->plan->orders.p.pt[0].x; ty = unit->plan->orders.p.pt[0].y; unit->plan->orders.p.pt[0].x = unit->plan->orders.p.pt[1].x; unit->plan->orders.p.pt[0].y = unit->plan->orders.p.pt[1].y; unit->plan->orders.p.pt[1].x = tx; unit->plan->orders.p.pt[1].y = ty; } return move_to(unit, unit->plan->orders.p.pt[0].x, unit->plan->orders.p.pt[0].y, (unit->plan->orders.flags & SHORTESTPATH)); } #endif return TRUE; } /* Basic routine to compute how long a unit will take to build something. */ int build_time(Unit *unit, int prod) { int schedule = 1 /* uu_make(unit->type, prod) */; int u, develop_delay = 0; /* Add penalty (or unpenalty!) for first unit of a type. */ /* is "counts" a reliable way to test? */ if (unit->side->counts[prod] <= 1) { /* develop_delay = ((schedule * u_develop(prod)) / 100); */ for_all_unit_types(u) { if (unit->side->counts[u] > 1) { develop_delay -= (1 /*uu_make(unit->type, u)*/ * uu_tech_crossover(prod, u)) / 100; } if (develop_delay > 0) { schedule += develop_delay; } } } return schedule; } #endif int clear_task_agenda(Plan *plan) { int numcleared; Task *oldtask; if (plan == NULL || plan->tasks == NULL) return 0; numcleared = 0; while (plan->tasks != NULL) { oldtask = plan->tasks; plan->tasks = plan->tasks->next; free_task(oldtask); ++numcleared; } return numcleared; } Plan * create_plan(void) { Plan *plan = (Plan *) xmalloc(sizeof(Plan)); return plan; } void free_plan(Plan *plan) { if (plan == NULL) run_error("no plan here?"); /* Make tasks available for reallocation. */ clear_task_agenda(plan); free(plan); } /* Describe a plan succinctly. This is primarily for debugging, not for normal user display. */ char *planbuf = NULL; char * plan_desig(Plan *plan) { Task *task; int extra = 0; if (planbuf == NULL) planbuf = xmalloc(1000); if (plan == NULL) { sprintf(planbuf, "no plan"); } else if (plan->type == PLAN_NONE) { sprintf(planbuf, "unformed plan"); } else { if (plan->tasks) { tmpbuf[0] = '\0'; for_all_tasks(plan, task) { if (strlen(tmpbuf) < 100) { strcat(tmpbuf, " "); strcat(tmpbuf, task_desig(task)); } else { ++extra; } } if (extra > 0) { tprintf(tmpbuf, " ... %d more ...", extra); } } else { sprintf(tmpbuf, " no tasks"); } sprintf(planbuf, "type %s %s", plantypenames[plan->type], goal_desig(plan->maingoal)); if (plan->formation) { strcat(planbuf, " "); strcat(planbuf, goal_desig(plan->formation)); } if (plan->asleep) strcat(planbuf, " asleep"); if (plan->reserve) strcat(planbuf, " reserve"); if (plan->delayed) strcat(planbuf, " delayed"); if (plan->waitingfortasks) strcat(planbuf, " waiting"); if (plan->supply_alarm) strcat(planbuf, " supply_alarm"); if (plan->supply_is_low) strcat(planbuf, " supply_is_low"); strcat(planbuf, tmpbuf); } return planbuf; } /* True if unit is in immediate danger of being captured. */ /* Needs check on capturer transport being seen. */ int might_be_captured(Unit *unit) { int d, x, y; Unit *unit2; for_all_directions(d) { if (interior_point_in_dir(unit->x, unit->y, d, &x, &y)) { if (((unit2 = unit_at(x, y)) != NULL) && (enemy_side(unit->side, unit2->side)) && (uu_capture(unit2->type, unit->type) > 0)) return TRUE; } } return FALSE; } /* Clear a unit's plan out. */ void force_replan(Side *side, Unit *unit, int passive_only) { extern int need_ai_planning; if (unit->plan == NULL) return; if ((passive_only ? unit->plan->type == PLAN_PASSIVE : TRUE)) { unit->plan->type = PLAN_PASSIVE; clear_task_agenda(unit->plan); } unit->plan->maingoal = NULL; unit->plan->formation = NULL; unit->plan->funit = NULL; unit->plan->asleep = FALSE; unit->plan->reserve = FALSE; set_waiting_for_tasks(unit, FALSE); unit->plan->delayed = FALSE; unit->plan->last_task_outcome = TASK_UNKNOWN; need_ai_planning = TRUE; } /* Auxiliary functions for unit planning in Xconq. */ /* router flags */ #define SAMEPATH 1 #define EXPLORE_PATH 2 /* These macros are a cache used for planning purposes by machines. */ #define markloc(x, y) (set_tmp1_at(x, y, mark)) #define markedloc(x, y) (tmp1_at(x, y) == mark) #define get_fromdir(x, y) (tmp2_at(x, y)) #define set_fromdir(x, y, dir) (set_tmp2_at(x, y, dir)) #define get_dist(x, y) (tmp3_at(x, y)) #define set_dist(x, y, d) (set_tmp3_at(x, y, d)) int occupant_could_capture(Unit *unit, int u2) { Unit *occ; for_all_occupants(unit, occ) if (uu_capture(occ->type, u2) > 0) return TRUE; return FALSE; } /* Check to see if there is anyone around to capture. */ int can_capture_neighbor(Unit *unit) { int d, x, y; Side *side2; UnitView *uview; for_all_directions(d) { if (interior_point_in_dir(unit->x, unit->y, d, &x, &y)) { for_all_view_stack(unit->side, x, y, uview) { side2 = view_side(uview); if (!allied_side(unit->side, side2)) { if (uu_capture(unit->type, view_type(uview)) > 0) { /* need some other way to change move order quickly */ return TRUE; } } } } } return FALSE; } /* check if our first occupant can capture something. Doesn't look at other occupants. */ int occupant_can_capture_neighbor(Unit *unit) { Unit *occ = unit->occupant; if (occ != NULL && has_acp_left(occ) && occ->side == unit->side) { if (can_capture_neighbor(occ)) { return TRUE; } } return FALSE; } /* Find the closes unit, first prefering bases, and then transports. */ int find_closest_unit(side, x0, y0, maxdist, pred, rxp, ryp) Side *side; int x0, y0, maxdist, (*pred)(int x, int y), *rxp, *ryp; { #if 0 Unit *unit; int u, dist; int found = FALSE; for_all_unit_types(u) { if (u_is_base(u)) { for (unit = NULL /* side_strategy(side)->unitlist[u]*/; unit != NULL; unit = unit->mlist) { if (alive(unit) && (dist = distance(x0, y0, unit->x, unit->y)) <= maxdist) { if ((*pred)(unit->x, unit->y)) { maxdist = dist - 1; *rxp = unit->x; *ryp = unit->y; found = TRUE; } } } } } if (found) { return TRUE; } for_all_unit_types(u) { if (!u_is_base(u) && u_is_transport(u)) { for (unit = NULL /*side_strategy(side)->unitlist[u]*/; unit != NULL; unit = unit->mlist) { if (alive(unit) && distance(x0, y0, unit->x, unit->y) <= maxdist) { if ((*pred)(unit->x, unit->y)) { maxdist = dist - 1; *rxp = unit->x; *ryp = unit->y; found = TRUE; } } } } } if (found) { return TRUE; } /* (What's the point of finding a non-base/non-transport?) */ for_all_unit_types(u) { if (!u_is_base(u) && !u_is_transport(u)) { for (unit = NULL/*side_strategy(side)->unitlist[u]*/; unit != NULL; unit = unit->mlist) { if (alive(unit) && distance(x0, y0, unit->x, unit->y) <= maxdist) { if ((*pred)(unit->x, unit->y)) { maxdist = dist - 1; *rxp = unit->x; *ryp = unit->y; found = TRUE; } } } } } if (found) { return TRUE; } #endif return FALSE; } #if 0 /* Unused. */ /* Returns the type of missing supplies. */ int out_of_ammo(Unit *unit) { int u = unit->type, m; for_all_material_types(m) { if (um_consumption_per_attack(u, m) > 0 && unit->supply[m] <= 0) return m; } return (-1); } #endif #if 0 /* The two functions below are unused. */ int usable_cell(Unit *unit, int x, int y) { int u = unit->type; UnitView *uview; if (!could_live_on(u, terrain_at(x, y))) return FALSE; for_all_view_stack(unit->side, x, y, uview) { if (allied_side(view_side(uview), unit->side) && could_carry(view_type(uview), u)) return TRUE; } return FALSE; } Task *explorechain; int explorable_cell(x, y) int x, y; { return (terrain_view(tmpside, x, y) == UNSEEN); } #endif /* Test whether the given location is an unknown cell that we can get next to. */ /* (should consider testing "within vision range", but that might require LOS tests and be expensive) */ static int reachable_unknown(int x, int y) { /* Only interior cells are reachable. */ if (!inside_area(x, y)) return FALSE; if (terrain_view(tmpside, x, y) == UNSEEN) { if (adj_known_ok_terrain(x, y, tmpside, tmpunit->type)) { return TRUE; } else { return FALSE; } } else { return FALSE; } } /* Test whether the given location has an adjacent cell that is ok for the given type to be out in the open. */ static int adj_known_ok_terrain(int x, int y, Side *side, int u) { int dir, x1, y1, t; if (!inside_area(x, y)) return FALSE; for_all_directions(dir) { if (interior_point_in_dir(x, y, dir, &x1, &y1)) { if (terrain_view(side, x1, y1) == UNSEEN) continue; t = terrain_at(x1, y1); if (!terrain_always_impassable(u, t)) return TRUE; } } return FALSE; } /* Go to the nearest cell that we can see how to get to. */ static int explore_reachable_cell(Unit *unit, int range) { int x, y; if (g_see_all() || g_terrain_seen()) return FALSE; tmpunit = unit; tmpside = unit->side; DMprintf("%s searching within %d for cell to explore -", unit_desig(unit), range); if (search_around(unit->x, unit->y, range, reachable_unknown, &x, &y, 1)) { set_move_to_task(unit, x, y, 1); DMprintf("found one at %d,%d\n", x, y); return TRUE; } DMprintf("found nothing\n"); return FALSE; } #if 0 /* The two functions below are unused. */ /* Check for any makers this unit should be capturing. */ int should_capture_maker(unit) Unit *unit; { return 0; } /* Returns true if the given unit can't leave its cell for some reason. */ int no_possible_moves(unit) Unit *unit; { int fx = unit->x, fy = unit->y, ut = unit->type; int d, x, y; int view; Side *side = unit->side; for_all_directions(d) { x = wrapx(fx + dirx[d]); y = limity(fy + diry[d]); view = unit_view(side, x, y); if (view == EMPTY) { if (could_move(ut, terrain_at(x, y))) return FALSE; } else if (enemy_side(side_n(vside(view)) , side) && could_hit(ut, vtype(view))) { return FALSE; } else if (could_carry(vtype(view), ut) && allied_side(side_n(vside(view)), side)) return FALSE; } return TRUE; } #endif /* Estimate the usual number of turns to finish construction. */ int normal_completion_time(u, u2) int u, u2; { if (u_acp(u) == 0 || uu_cp_per_build(u, u2) == 0) return (-1); return (u_cp(u2) - uu_creation_cp(u, u2)) / (uu_cp_per_build(u, u2) * u_acp(u)); } /* Similar, but using a specific unit and also accounting for toolup time. */ int est_completion_time(Unit *unit, int u2) { int u, tooluptime, tp; u = unit->type; if (uu_acp_to_create(u, u2) < 1) return (-1); tooluptime = 0; tp = (unit->tooling ? unit->tooling[u2] : 0); if (tp < uu_tp_to_build(u, u2)) { if (uu_acp_to_toolup(u, u2) < 1 || uu_tp_per_toolup(u, u2) <= 0 || u_acp(u) <= 0) return (-1); tooluptime = ((uu_tp_to_build(u, u2) - tp) * uu_acp_to_toolup(u, u2)) / (uu_tp_per_toolup(u, u2) * u_acp(u)); } return tooluptime + normal_completion_time(unit->type, u2); } /* A unit runs low on supplies at the halfway point. Formula is the same no matter how/if occupants eat transports' supplies. */ /* (should reconcile with doctrine's resupply-percent) */ int past_halfway_point(Unit *unit) { int u = unit->type, m; for_all_material_types(m) { if (((um_base_consumption(u, m) > 0) || (um_consumption_per_move(u, m) > 0)) /* should check that the transport is adequate for supplying the fuel */ && (unit->transport == NULL)) { if (2 * unit->supply[m] <= um_storage_x(u, m)) return TRUE; } } return FALSE; }