int x, y; /* position that is this group's center of interest */ #endregion Fields #region Methods public static void ai_group_add_unit(AI_Group group, Unit unit) { /* sort unit into units list in the order in which units are * supposed to move. artillery fire is first but moves last * thus artillery comes last in this list. that it fires first * is handled by ai_group_handle_next_unit. list order is: * bombers, ground units, fighters, artillery */ if (((unit.prop.flags & UnitFlags.ARTILLERY) == UnitFlags.ARTILLERY) || ((unit.prop.flags & UnitFlags.AIR_DEFENSE) == UnitFlags.AIR_DEFENSE)) group.units.Add(unit); else if (unit.prop.unit_class == 9 || unit.prop.unit_class == 10) { /* tactical and high level bomber */ group.units.Insert(0, unit); group.bomber_count++; } else if ((unit.prop.flags & UnitFlags.FLYING) == UnitFlags.FLYING) { /* airborne ground units are not in this section ... */ group.units.Insert(group.bomber_count + group.ground_count, unit); group.aircraft_count++; } else { /* everything else: ships and ground units */ group.units.Insert(group.bomber_count, unit); group.ground_count++; } /* HACK: set hold_pos flag for those units that should not move due to high entrenchment or being close to artillery and such; but these units will attack, too and may move if's really worth it */ if ((unit.sel_prop.flags & UnitFlags.FLYING) != UnitFlags.FLYING) if ((unit.sel_prop.flags & UnitFlags.SWIMMING) != UnitFlags.SWIMMING) { int i, nx, ny; bool no_move = false; if (Engine.map.map[unit.x, unit.y].obj) no_move = true; if (group.order < 0) { if (Engine.map.map[unit.x, unit.y].nation != null) no_move = true; if (unit.entr >= 6) no_move = true; if ((unit.sel_prop.flags & UnitFlags.ARTILLERY) == UnitFlags.ARTILLERY) no_move = true; if ((unit.sel_prop.flags & UnitFlags.AIR_DEFENSE) == UnitFlags.AIR_DEFENSE) no_move = true; for (i = 0; i < 6; i++) if (Misc.get_close_hex_pos(unit.x, unit.y, i, out nx, out ny)) if (Engine.map.map[nx, ny].g_unit != null) { if ((Engine.map.map[nx, ny].g_unit.sel_prop.flags & UnitFlags.ARTILLERY) == UnitFlags.ARTILLERY) { no_move = true; break; } if ((Engine.map.map[nx, ny].g_unit.sel_prop.flags & UnitFlags.AIR_DEFENSE) == UnitFlags.AIR_DEFENSE) { no_move = true; break; } } } if (no_move) unit.cur_mov = 0; } }
/* ==================================================================== Get the best target for unit if any. ==================================================================== */ static bool ai_get_best_target(Unit unit, int x, int y, AI_Group group, out Unit target, out int score) { int old_x = unit.x, old_y = unit.y; int pos_targets; List<Unit> targets; int score_atk_base, score_rugged, score_kill, score_loss; /* scores */ score_atk_base = 20 + group.order * 10; score_rugged = -1; score_kill = (group.order + 3) * 10; score_loss = (2 - group.order) * -10; unit.x = x; unit.y = y; target = null; score = -999999; /* if the transporter is needed attacking is suicide */ if (Engine.map.mask[x, y].mount != 0 && !string.IsNullOrEmpty(unit.trsp_prop.id)) return false; /* gather targets */ targets = ai_gather_targets(unit, x, y); /* evaluate all attacks */ if (targets != null) { foreach (Unit entry in targets) if (!AI_Group.unit_evaluate_attack(unit, entry, score_atk_base, score_rugged, score_kill, score_loss, out entry.target_score)) targets.Remove(entry); /* erroneous entry: can't be attacked */ /* check whether any positive targets exist */ pos_targets = 0; foreach (Unit entry in targets) if (entry.target_score > 0) { pos_targets = 1; break; } /* get best target */ foreach (Unit entry in targets) { /* if unit is on an objective or center of interest give a bonus as this tile must be captured by all means. but only do so if there is no other target with a positive value */ if (pos_targets == 0) if ((entry.x == group.x && entry.y == group.y) || Engine.map.map[entry.x, entry.y].obj) { entry.target_score += 100; #if DEBUG Console.WriteLine(" + 100 for {0}: capture by all means", entry.name); #endif } if (entry.target_score > score) { target = entry; score = entry.target_score; } } targets.Clear(); } unit.x = old_x; unit.y = old_y; return (target != null); }
/* ==================================================================== Choose and store the best tactical action of a unit (found by use of ai_evaluate_hex). If there is none AI_SUPPLY is stored. ==================================================================== */ static void ai_handle_unit(Unit unit, AI_Group group) { int x, y, nx, ny, i, action = 0; Queue<AI_Eval> list = new Queue<AI_Eval>(); AI_Eval eval; Unit target = null; int score = -999999; /* get move mask */ Engine.map.map_get_unit_move_mask(unit); x = unit.x; y = unit.y; target = null; /* evaluate all positions */ list.Enqueue(ai_create_eval(unit, group, unit.x, unit.y)); while (list.Count > 0) { eval = list.Dequeue(); if (ai_evaluate_hex(ref eval)) { /* movement evaluation */ if (eval.mov_score > score) { score = eval.mov_score; target = null; x = eval.x; y = eval.y; } /* movement + attack evaluation. ignore for attack_first * units which already fired */ if ((unit.sel_prop.flags & UnitFlags.ATTACK_FIRST) != UnitFlags.ATTACK_FIRST) if (eval.target != null && eval.atk_score > score) { score = eval.atk_score; target = eval.target; x = eval.x; y = eval.y; } } /* store next hex tiles */ for (i = 0; i < 6; i++) if (Misc.get_close_hex_pos(eval.x, eval.y, i, out nx, out ny)) if ((Engine.map.mask[nx, ny].in_range != 0 && !Engine.map.mask[nx, ny].blocked) || Engine.map.mask[nx, ny].sea_embark) { Engine.map.mask[nx, ny].in_range = 0; Engine.map.mask[nx, ny].sea_embark = false; list.Enqueue(ai_create_eval(unit, group, nx, ny)); } } list.Clear(); /* check result and store appropiate action */ if (unit.x != x || unit.y != y) { if (Engine.map.map_check_unit_debark(unit, x, y, UnitEmbarkTypes.EMBARK_SEA, false)) { Action.action_queue_debark_sea(unit, x, y); action = 1; #if DEBUG Console.WriteLine("{0} debarks at {1},{2}", unit.name, x, y); #endif } else { Action.action_queue_move(unit, x, y); action = 1; #if DEBUG Console.WriteLine("{0} moves to {1},{2}", unit.name, x, y); #endif } } if (target != null) { Action.action_queue_attack(unit, target); action = 1; #if DEBUG Console.WriteLine("{0} attacks {1}", unit.name, target.name); #endif } if (action == 0) { Action.action_queue_supply(unit); #if DEBUG Console.WriteLine("{0} supplies: {1},{2}", unit.name, unit.cur_ammo, unit.cur_fuel); #endif } }
static AI_Eval ai_create_eval(Unit unit, AI_Group group, int x, int y) { AI_Eval eval = new AI_Eval(); eval.unit = unit; eval.group = group; eval.x = x; eval.y = y; return eval; }
/* ==================================================================== Get the best target and attack by range. Do not try to move the unit yet. If there is no target at all do nothing. ==================================================================== */ static void ai_fire_artillery(Unit unit, AI_Group group) { AI_Eval eval = ai_create_eval(unit, group, unit.x, unit.y); if (ai_evaluate_hex(ref eval) && eval.target != null) { Action.action_queue_attack(unit, eval.target); #if DEBUG Console.WriteLine("{0} attacks first {1}", unit.name, eval.target.name); #endif } }
/* ==================================================================== Handle next unit of a group to follow order. Stores all nescessary unit actions. If group is completely handled, it returns False. ==================================================================== */ public static bool ai_group_handle_next_unit(AI_Group group) { Unit unit = null; if (group_units_pos < group.units.Count) unit = group.units[group_units_pos++]; if (unit == null) { if (group.state == GS.GS_MOVE) return false; else { group.state = GS.GS_MOVE; group_units_pos = 0; if (group_units_pos < group.units.Count) unit = group.units[group_units_pos++]; else return false; } } if (unit == null) { Console.WriteLine("ERROR: ai_group_handle_next_unit: null unit detected"); return false; } /* Unit is dead? Can only be attacker that was killed by defender */ if (unit.killed != 0) { Console.WriteLine("Removingkilled attacker %s(%d,%d) from group\n", unit.name, unit.x, unit.y); ai_group_delete_unit(group, unit); return ai_group_handle_next_unit(group); } if (group.state == GS.GS_ART_FIRE) { if ((unit.sel_prop.flags & UnitFlags.ATTACK_FIRST) == UnitFlags.ATTACK_FIRST) ai_fire_artillery(unit, group); /* does not check optimal movement but simply fires */ } else ai_handle_unit(unit, group); /* checks to the full tactical extend */ return true; }
public static void ai_group_delete_unit(AI_Group group, Unit unit) { /* remove unit */ bool contained_unit = (group.units[group_units_pos] == unit); group.units.Remove(unit); if (contained_unit) return; /* update respective counter */ if (((unit.prop.flags & UnitFlags.ARTILLERY) == UnitFlags.ARTILLERY) || (unit.prop.flags & UnitFlags.AIR_DEFENSE) == UnitFlags.AIR_DEFENSE) /* nothing to be done */ ; else if (unit.prop.unit_class == 9 || unit.prop.unit_class == 10) { /* tactical and high level bomber */ group.bomber_count--; } else if ((unit.prop.flags & UnitFlags.FLYING) == UnitFlags.FLYING) { /* airborne ground units are not in this section ... */ group.aircraft_count--; } else { /* everything else: ships and ground units */ group.ground_count--; } }
public static void ai_group_delete(AI_Group ptr) { AI_Group group = ptr; if (group != null) { if (group.units != null) group.units.Clear(); } }
/* ==================================================================== PUBLICS ==================================================================== */ /* ==================================================================== CreateAction/Delete a group ==================================================================== */ public static AI_Group ai_group_create(int order, int x, int y) { AI_Group group = new AI_Group(); group.state = GS.GS_ART_FIRE; group.order = order; group.x = x; group.y = y; group.units = new List<Unit>(); return group; }
/* ==================================================================== Queue next actions (if these actions were handled by the engine this function is called again and again until the end_turn action is received). ==================================================================== */ public static bool ai_run() { bool result = false; Unit[] partners = new Unit[Map.MAP_MERGE_UNIT_LIMIT]; int partner_count; int i, j, x = 0, y = 0, dx, dy, dist; bool found; Unit unit = null; Unit best; switch (ai_status) { case AI_STATUS.AI_STATUS_DEPLOY: /* deploy unit? */ if (Scenario.avail_units.Count > 0 && avail_unitsIterator.MoveNext() && (unit = avail_unitsIterator.Current) != null) { if (Engine.deploy_turn) { x = unit.x; y = unit.y; //assert(x >= 0 && y >= 0); Engine.map.map_remove_unit(unit); found = true; } else { Engine.map.map_get_deploy_mask(Engine.cur_player, unit, false); Engine.map.map_clear_mask(MAP_MASK.F_AUX); for (i = 0; i < Engine.map.map_w; i++) for (j = 0; j < Engine.map.map_h; j++) if (Engine.map.mask[i, j].deploy) if (ai_get_dist(unit, i, j, AI_FIND.AI_FIND_ENEMY_OBJ, out x, out y, out dist)) Engine.map.mask[i, j].aux = dist + 1; dist = 1000; found = false; for (i = 0; i < Engine.map.map_w; i++) for (j = 0; j < Engine.map.map_h; j++) if (Engine.map.mask[i, j].aux > 0 && Engine.map.mask[i, j].aux < dist) { dist = Engine.map.mask[i, j].aux; x = i; y = j; found = true; /* deploy close to enemy */ } } if (found) { Action.action_queue_deploy(unit, x, y); avail_unitsIterator = Scenario.avail_units.GetEnumerator(); ai_units.Add(unit); #if DEBUG Console.WriteLine("{0} deployed to {1},{2}", unit.name, x, y); #endif return false; } } else { ai_status = Engine.deploy_turn ? AI_STATUS.AI_STATUS_END : AI_STATUS.AI_STATUS_MERGE; ai_unitsIterator = ai_units.GetEnumerator(); #if DEBUG Console.WriteLine(Engine.deploy_turn ? "*** END TURN ***" : "*** MERGE ***"); #endif } break; case AI_STATUS.AI_STATUS_SUPPLY: /* get next unit */ ai_unitsIterator.MoveNext(); unit = ai_unitsIterator.Current; if (unit == null) { ai_status = AI_STATUS.AI_STATUS_GROUP; /* build a group with all units, -1,-1 as destination means it will simply attack/defend the nearest target. later on this should split up into several groups with different target and strategy */ ai_group = AI_Group.ai_group_create(Engine.cur_player.strat, -1, -1); ai_unitsIterator = ai_units.GetEnumerator(); while (ai_unitsIterator.MoveNext() && (unit = ai_unitsIterator.Current) != null) AI_Group.ai_group_add_unit(ai_group, unit); #if DEBUG Console.WriteLine("*** MOVE & ATTACK ***"); #endif } else { /* check if unit needs supply and remove it from ai_units if so */ if ((unit.CheckLowFuel() || unit.CheckLowAmmo())) { if (unit.supply_level > 0) { Action.action_queue_supply(unit); ai_units.Remove(unit); #if DEBUG Console.WriteLine("{0} supplies", unit.name); #endif break; } else { #if DEBUG Console.WriteLine("{0} searches depot", unit.name); #endif if (ai_get_dist(unit, unit.x, unit.y, AI_FIND.AI_FIND_DEPOT, out dx, out dy, out dist)) if (ai_approximate(unit, dx, dy, out x, out y)) { Action.action_queue_move(unit, x, y); ai_units.Remove(unit); #if DEBUG Console.WriteLine("{0} moves to {1},{2}", unit.name, x, y); #endif break; } } } } break; case AI_STATUS.AI_STATUS_MERGE: if (ai_unitsIterator.MoveNext() && (unit = ai_unitsIterator.Current) != null) { Engine.map.map_get_merge_units(unit, out partners, out partner_count); best = null; /* merge with the one that has the most strength points */ for (i = 0; i < partner_count; i++) if (best == null) best = partners[i]; else if (best.str < partners[i].str) best = partners[i]; if (best != null) { #if DEBUG Console.WriteLine("{0} merges with {1}", unit.name, best.name); #endif Action.action_queue_merge(unit, best); /* both units are handled now */ ai_units.Remove(unit); ai_units.Remove(best); } } else { ai_status = AI_STATUS.AI_STATUS_SUPPLY; ai_unitsIterator = ai_units.GetEnumerator(); #if DEBUG Console.WriteLine("*** SUPPLY ***"); #endif } break; case AI_STATUS.AI_STATUS_GROUP: if (!AI_Group.ai_group_handle_next_unit(ai_group)) { AI_Group.ai_group_delete(ai_group); ai_status = AI_STATUS.AI_STATUS_END; #if DEBUG Console.WriteLine("*** END TURN ***"); #endif } break; case AI_STATUS.AI_STATUS_END: Action.action_queue_end_turn(); ai_status = AI_STATUS.AI_STATUS_FINALIZE; result = true; break; } return result; }