/* Implementation of the lobotomized "iplayer" AI in Xconq. Copyright (C) 1987-1989, 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 "kpublic.h" #include "ai.h" extern void register_iplayer(AI_ops *ops); /* Local function declarations. */ static void iplayer_init(Side *side); static void iplayer_init_turn(Side *side); static void iplayer_create_strategy(Side *side); static void iplayer_review_units(Side *side); static void iplayer_decide_plan(Side *side, Unit *unit); static int iplayer_adjust_plan(Side *side, Unit *unit); static void iplayer_react_to_task_result(Side *side, Unit *unit, Task *task, TaskOutcome rslt); static void iplayer_react_to_new_side(Side *side, Side *side2); static void iplayer_finish_movement(Side *side); static void iplayer_rethink_plan(Unit *unit); void register_iplayer(AI_ops *ops) { ops->name = "iplayer"; ops->help = "AI for independent units only"; ops->to_init = iplayer_init; ops->to_init_turn = iplayer_init_turn; ops->to_decide_plan = iplayer_decide_plan; ops->to_react_to_task_result = iplayer_react_to_task_result; ops->to_react_to_new_side = iplayer_react_to_new_side; ops->to_adjust_plan = iplayer_adjust_plan; ops->to_finish_movement = iplayer_finish_movement; } static void iplayer_init(Side *side) { Unit *unit; /* Delete any old strategy object in case we just switched AI type. */ if (side->ai != NULL) { free(side->ai); side->ai = NULL; } iplayer_create_strategy(side); /* If the side has no units at the moment, it doesn't really need to plan. */ if (!side_has_units(side)) return; /* Reset plans of any units that were not doing anything. */ for_all_side_units(side, unit) { if (in_play(unit) && ai_controlled(unit)) { net_force_replan(side, unit, TRUE); } } } /* At the beginning of each turn, make plans and review the situation. */ static void iplayer_init_turn(Side *side) { /* Cases where we no longer need to run. */ if (!side->ingame) return; /* A side without units hasn't got anything to do but wait. */ /* (should account for possible units on controlled sides) */ if (!side_has_units(side)) return; /* Iplayers in a hacked game will not play, unless they're being debugged. */ if (compromised && !DebugM) return; update_all_progress_displays("ai turn init start", side->id); DMprintf("%s iplayer init turn\n", side_desig(side)); /* Check out all of our units. */ iplayer_review_units(side); /* (should be integrated better) */ iplayer_finish_movement(side); /* Update unit plans. */ update_unit_plans(side); update_all_progress_displays("", side->id); DMprintf("%s iplayer init turn done\n", side_desig(side)); } /* Create and install an entirely new strategy object for the side. */ static void iplayer_create_strategy(Side *side) { Strategy *strategy = (Strategy *) xmalloc(sizeof(Strategy)); /* Put the specific structure into a generic slot. */ side->ai = (struct a_ai *) strategy; } /* Go through all our units (and allied ones?). */ static void iplayer_review_units(Side *side) { int u; int numoffensive[MAXUTYPES], numdefensive[MAXUTYPES]; Unit *unit; Plan *plan; for_all_unit_types(u) { numoffensive[u] = numdefensive[u] = 0; } for_all_side_units(side, unit) { if (in_play(unit) && ai_controlled(unit)) { /* Count plan types. */ switch (unit->plan->type) { case PLAN_OFFENSIVE: case PLAN_EXPLORATORY: ++numoffensive[unit->type]; break; case PLAN_DEFENSIVE: ++numdefensive[unit->type]; break; default: break; } } } for_all_side_units(side, unit) { if (in_play(unit) && ai_controlled(unit)) { plan = unit->plan; /* Goal might have become satisfied. */ if (plan->maingoal) { if (goal_truth(side, plan->maingoal) == 100) { DMprintf("%s %s satisfied, removing\n", unit_desig(unit), goal_desig(plan->maingoal)); net_force_replan(side, unit, FALSE); } } /* Don't let defense-only units pile up. */ if (plan->type == PLAN_DEFENSIVE && mobile(unit->type) && (numoffensive[unit->type] / 3) < numdefensive[unit->type] /* However, don't mess with units that have specific defensive goals. */ && (plan->maingoal == NULL || (plan->maingoal->type != GOAL_UNIT_OCCUPIED && plan->maingoal->type != GOAL_CELL_OCCUPIED)) && flip_coin()) { DMprintf("%s one of too many on defense (%d off, %d def), replanning\n", unit_desig(unit), numoffensive[unit->type], numdefensive[unit->type]); net_force_replan(side, unit, FALSE); } } } } /* This is for when a unit needs a plan and asks its side for one. */ static void iplayer_decide_plan(Side *side, Unit *unit) { Plan *plan = unit->plan; int u = unit->type; switch (plan->type) { case PLAN_PASSIVE: case PLAN_NONE: if (mobile(u)) { if (u_colonizer_worth(u) > 0) { assign_to_colonize(side, unit); return; } /* Assign most units to offense, save some for defense. */ if (u_offensive_worth(u) > 0 && probability(75)) { assign_to_offense(side, unit); } else if (u_defensive_worth(u) > 0) { assign_to_defense(side, unit); /* In the unlikely case that mobile units can build anything. */ } else if (can_build_attackers(side, u)) { assign_to_offense_support(side, unit); } else if (can_build_defenders(side, u)) { assign_to_defense_support(side, unit); } else { } } else { /* Unit doesn't move. */ if (can_build_colonizers(side, u) /* (should fine-tune this test) */ && probability(60)) { assign_to_colonization_support(side, unit); } else if (can_build_attackers(side, u)) { assign_to_offense_support(side, unit); } else if (can_build_defenders(side, u)) { assign_to_defense_support(side, unit); } else if (u_defensive_worth(u) > 0) { assign_to_defense(side, unit); } } break; case PLAN_OFFENSIVE: /* leave plan alone */ break; case PLAN_COLONIZING: /* leave plan alone */ break; case PLAN_IMPROVING: /* leave plan alone */ break; case PLAN_EXPLORATORY: /* leave plan alone */ break; case PLAN_DEFENSIVE: /* leave plan alone */ break; default: break; } } static int iplayer_adjust_plan(Side *side, Unit *unit) { int u3; Task *task; if ((unit->plan->type == PLAN_OFFENSIVE || unit->plan->type == PLAN_COLONIZING || unit->plan->type == PLAN_IMPROVING || unit->plan->type == PLAN_EXPLORATORY) && !mobile(unit->type) && unit->plan->aicontrol && !unit->plan->asleep && unit->plan->tasks == NULL ) { u3 = preferred_build_type(side, unit, unit->plan->type); if (is_unit_type(u3)) { task = unit->plan->tasks; if (task == NULL || task->type != TASK_BUILD) { DMprintf("%s directed to build %s\n", unit_desig(unit), u_type_name(u3)); net_set_build_task(unit, u3, 2, 0, 0); } else { DMprintf("%s already building, leaving alone\n", unit_desig(unit)); } /* Only do one at a time, wait for next go-around for next unit. */ return FALSE; } } if (unit->plan->waitingfortasks && unit->plan->aicontrol ) { net_force_replan(side, unit, FALSE); } if (!unit->plan->reserve && g_units_may_go_into_reserve() && unit->plan->execs_this_turn > 10 * max(1, u_acp(unit->type))) { net_set_unit_reserve(side, unit, TRUE, FALSE); } /* Look at more units. */ return TRUE; } /* This is a hook that runs after each task is executed. */ static void iplayer_react_to_task_result(Side *side, Unit *unit, Task *task, TaskOutcome rslt) { int dx, dy, x1, y1, fact; Unit *occ; /* React to an apparent blockage. */ if (rslt == TASK_FAILED && task != NULL && task->type == TASK_MOVE_TO && task->retrynum > 2) { if (desired_direction_impassable(unit, task->args[0], task->args[1])) { if (unit->occupant) { DMprintf("%s blocked while transporting, will sit briefly\n", unit_desig(unit)); net_set_unit_reserve(side, unit, TRUE, FALSE); for_all_occupants(unit, occ) { net_wake_unit(side, occ, FALSE); } return; } /* Try moving sideways. */ if (probability(80)) { dx = task->args[0] - unit->x; dy = task->args[1] - unit->y; fact = (flip_coin() ? 50 : -50); x1 = unit->x - ((fact * dy) / 100); y1 = unit->y + ((fact * dx) / 100); if (inside_area(x1, y1)) net_push_move_to_task(unit, x1, y1, 1); } return; } else if (blocked_by_enemy(unit, task->args[0], task->args[1], TRUE)) { /* (should decide if allowable risk to passengers) */ DMprintf("%s blocked by enemy\n", unit_desig(unit)); if (!attack_blockage(side, unit, task->args[0], task->args[1], TRUE)) { if (blocked_by_enemy(unit, task->args[0], task->args[1], FALSE)) { attack_blockage(side, unit, task->args[0], task->args[1], FALSE); } else { /* (should move sideways?) */ } } } else { /* what to do about other failures? */ } return; } /* React to inability to resupply by trying to build a base. */ if (rslt == TASK_FAILED && task != NULL && task->type == TASK_RESUPPLY && task->retrynum > 2) { net_set_unit_reserve(side, unit, FALSE, FALSE); build_depot_for_self(side, unit); } } /* This function is called whenever a new side appears in the game. It mainly needs to make that any allocated data is resized appropriately. */ static void iplayer_react_to_new_side(Side *side, Side *side2) { } /* At the end of a turn, re-evaluate the plans of some units in case the situation changed. */ static void iplayer_finish_movement(Side *side) { Unit *unit; for_all_side_units(side, unit) { if (is_active(unit) && ai_controlled(unit)) { iplayer_rethink_plan(unit); } } } /* For units with plans and that are under AI control, consider changing the current plan/tasks. */ static void iplayer_rethink_plan(Unit *unit) { int x1, y1; Task *toptask = unit->plan->tasks, *nexttask = NULL; if (toptask) nexttask = toptask->next; if (unit->plan->type == PLAN_OFFENSIVE && toptask != NULL && toptask->type == TASK_MOVE_TO && distance(unit->x, unit->y, toptask->args[0], toptask->args[1]) >= min(2, u_acp(unit->type)) && enemy_close_by(unit->side, unit, 1 /* 2 would be better? */, &x1, &y1) ) { net_push_hit_unit_task(unit, x1, y1, NONUTYPE, -1); DMprintf("%s sees enemy close by, will attack it\n", unit_desig(unit)); } /* (should also notice fire opportunities) */ /* If we see somebody that could be captured and help us explore, set up to produce capturers. */ if (!mobile(unit->type) && (unit->plan->type == PLAN_EXPLORATORY || unit->plan->type == PLAN_OFFENSIVE) ) { int range = 4, rslt, x, y; DMprintf("%s searching for useful capture within %d in order to choose build; found ", unit_desig(unit), range); tmpunit = unit; rslt = search_around(unit->x, unit->y, range, useful_captureable_here, &x, &y, 1); if (rslt && is_unit_type(tmputype)) { DMprintf("%s at %d,%d", u_type_name(tmputype), x, y); if (toptask != NULL && toptask->type == TASK_BUILD && uu_capture(toptask->args[0], tmputype) ) { /* Already doing the right thing. */ DMprintf(" - already building %s", u_type_name(toptask->args[0])); } else { /* (should find best type that can capture quickly, schedule to build it) */ DMprintf(" - duhhh, what now?"); } } else { DMprintf("nothing"); } DMprintf("\n"); } }