/* * $Id: ai.c,v 1.2 2005/08/03 21:40:53 av1-op Exp $ * * Author: Markus Stenberg <fingon@iki.fi> * * Copyright (c) 1998 Markus Stenberg * Copyright (c) 1998-2002 Thomas Wouters * Copyright (c) 2000-2002 Cord Awtry * All rights reserved * * Created: Thu Mar 12 18:36:33 1998 fingon * Last modified: Thu Dec 10 21:46:30 1998 fingon * */ /* Point of the excercise : move from point a,b to point c,d while eliminating opponents and stuff, avoiding enemies in rear/side arc and generally having fun */ #include <math.h> #include "mech.h" #include "autopilot.h" #include "p.autopilot_command.h" #include "p.mech.utils.h" #include "p.map.obj.h" #include "spath.h" #include "p.mech.combat.h" #include "p.glue.hcode.h" #define SCORE_MOD 100 #define SAFE_SCORE SCORE_MOD * 1000 #define MIN_SAFE 8 #define NORM_SAFE 32 #define MAX_SIM_PATHS 40 typedef struct { int e, t; float s, ds; float fx, fy; short x, y, lx, ly; int h; int dh; } LOC; enum { SP_OPT_NORM, SP_OPT_FASTER, SP_OPT_SLOWER, SP_OPT_C }; int sp_opt[SP_OPT_C] = { 0, 1, -1 }; /* We prefer to keep at same speed, however */ #define MNORM_COUNT 37 int move_norm_opt[MNORM_COUNT][2] = { {0, SP_OPT_FASTER}, {0, SP_OPT_NORM}, {0, SP_OPT_SLOWER}, {-1, SP_OPT_NORM}, {1, SP_OPT_NORM}, {-2, SP_OPT_NORM}, {2, SP_OPT_NORM}, {-3, SP_OPT_NORM}, {3, SP_OPT_NORM}, {-5, SP_OPT_NORM}, {5, SP_OPT_NORM}, {-10, SP_OPT_NORM}, {10, SP_OPT_NORM}, {-15, SP_OPT_NORM}, {15, SP_OPT_NORM}, {-20, SP_OPT_NORM}, {20, SP_OPT_NORM}, {-30, SP_OPT_NORM}, {30, SP_OPT_NORM}, {-40, SP_OPT_NORM}, {40, SP_OPT_NORM}, {-60, SP_OPT_NORM}, {60, SP_OPT_NORM}, {-60, SP_OPT_SLOWER}, {60, SP_OPT_SLOWER}, {-60, SP_OPT_FASTER}, {60, SP_OPT_FASTER}, {-80, SP_OPT_NORM}, {80, SP_OPT_NORM}, {-120, SP_OPT_NORM}, {120, SP_OPT_NORM}, {-160, SP_OPT_NORM}, {160, SP_OPT_NORM}, {-160, SP_OPT_SLOWER}, {160, SP_OPT_SLOWER}, {-160, SP_OPT_FASTER}, {160, SP_OPT_FASTER} }; #define CFAST_COUNT 9 /* Update: Just do subset if we're in silly mood */ int combat_fast_opt[CFAST_COUNT][2] = { {0, SP_OPT_FASTER}, {0, SP_OPT_NORM}, {0, SP_OPT_SLOWER}, {-10, SP_OPT_NORM}, {10, SP_OPT_NORM}, {-30, SP_OPT_NORM}, {30, SP_OPT_NORM}, {-60, SP_OPT_NORM}, {60, SP_OPT_NORM} }; void sendAIM(AUTO * a, MECH * m, char *msg) { auto_reply(m, msg); SendAI(msg); } char *AI_Info(MECH * m, AUTO * a) { static char buf[MBUF_SIZE]; sprintf(buf, "Unit#%d on #%d [A#%d]:", m->mynum, m->mapindex, a->mynum); return buf; } extern float terrain_speed(MECH * mech, float tempspeed, float maxspeed, int terrain, int elev); static int ai_crash(MAP * map, MECH * m, LOC * l) { float newx = 0.0, newy = 0.0; float tempspeed, maxspeed, acc; int offset; int normangle; int mw_mod = 1; int ch = 0; if (!map) return 0; maxspeed = MMaxSpeed(m); /* UpdateHeading */ if (l->h != l->dh && !(MechCritStatus(m) & GYRO_DESTROYED)) { ch = 1; if (is_aero(m)) maxspeed = maxspeed * ACCEL_MOD; normangle = l->h - l->dh; if (MechType(m) == CLASS_MW || MechType(m) == CLASS_BSUIT) mw_mod = 60; /* XXX Jumping */ if (fabs(l->s) < 1.0) offset = 3 * maxspeed * MP_PER_KPH * mw_mod; else { offset = 2 * maxspeed * MP_PER_KPH * mw_mod; if ((abs(normangle) > offset) && (l->s > (maxspeed * 2 / 3))) offset -= offset / 2 * (3.0 * l->s / maxspeed - 2.0); } offset = offset * MOVE_MOD; if (normangle < 0) normangle += 360; if (IsDS(m) && offset >= 10) offset = 10; if (normangle > 180) { l->h += offset; if (l->h >= 360) l->h -= 360; normangle += offset; if (normangle >= 360) l->h = l->dh; } else { l->h -= offset; if (l->h < 0) l->h += 360; normangle -= offset; if (normangle < 0) l->h = l->dh; } } /* UpdateSpeed */ /* XXX MASC */ /* XXX heat (_hard_ to track) */ tempspeed = l->ds; if (MechType(m) != CLASS_MW && MechMove(m) != MOVE_VTOL && (MechMove(m) != MOVE_FLY || Landed(m))) tempspeed = terrain_speed(m, tempspeed, maxspeed, l->t, l->e); if (ch) { if (mudconf.btech_slowdown == 2) { int dif = MechFacing(m) - MechDesiredFacing(m); if (dif < 0) dif = -dif; if (dif > 180) dif = 360 - dif; if (dif) { dif = (dif - 1) / 30 + 2; tempspeed = tempspeed * (10 - dif) / 10; } } else if (mudconf.btech_slowdown == 1) { if (l->h != l->dh) tempspeed = tempspeed * 2.0 / 3.0; else tempspeed = tempspeed * 3.0 / 4.0; } } if (MechIsQuad(m) && MechLateral(m)) tempspeed -= MP1; if (tempspeed <= 0.0) { tempspeed = 0.0; } if (l->ds < 0.) tempspeed = -tempspeed; if (tempspeed != l->s) { if (MechIsQuad(m)) acc = maxspeed / 10.; else acc = maxspeed / 20.; if (tempspeed < l->s) { /* Decelerating */ l->s -= acc; if (tempspeed > l->s) l->s = tempspeed; } else { /* Accelerating */ l->s += acc; if (tempspeed < l->s) l->s = tempspeed; } } /* move_mech + NewHexEntered */ /* XXX Jumping mechs [aeros,VTOLs] */ /* XXX Non-mechs */ /* XXX Quads */ FindComponents(l->s * MOVE_MOD, l->h, &newx, &newy); l->fx += newx; l->fy += newy; l->lx = l->x; l->ly = l->y; RealCoordToMapCoord(&l->x, &l->y, l->fx, l->fy); /* Ensure we don't run off map */ if (BOUNDED(0, l->x, map->map_width - 1) != l->x) return 1; if (BOUNDED(0, l->y, map->map_height - 1) != l->y) return 1; if (l->lx != l->x || l->ly != l->y) { int oz = l->e; /* Ensure if mech m can do transition auto_lx,auto_ly => auto_x,auto_y */ /* XXX Decent handling of terrain choosing and stuff */ switch (GetRTerrain(map, l->x, l->y)) { case HEAVY_FOREST: if (MechType(m) != CLASS_MECH) return 1; break; case WATER: if (MechMove(m) == MOVE_TRACK || MechMove(m) == MOVE_WHEEL) return 1; else { /* Check floodability for mechs */ if (MechType(m) == CLASS_MECH) { int t = Elevation(map, l->x, l->y); if (t < 0) { if (t == -1) { /* Check feet */ #define Floodable(loc) (!GetSectArmor(m,loc) || (!GetSectRArmor(m,loc) && GetSectORArmor(m,loc))) #define FloodCheck(loc) if (GetSectInt(m,loc)) if (Floodable(loc)) return 1; FloodCheck(LLEG); FloodCheck(RLEG); if (MechIsQuad(m)) { FloodCheck(LARM); FloodCheck(RARM); } } else { int i; for (i = 0; i < NUM_SECTIONS; i++) FloodCheck(i); } } } } break; case HIGHWATER: return 1; break; } l->e = Elevation(map, l->x, l->y); if (MechMove(m) == MOVE_HOVER) l->e = MAX(0, l->e); l->t = GetRTerrain(map, l->x, l->y); if (MechType(m) == CLASS_MECH) if ((l->e - oz) > 2 || (oz - l->e) > 2) return 1; /* XXX Check flooding etc, should avoid _that_ */ if (MechType(m) == CLASS_VEH_GROUND) if ((l->e - oz) > 1 || (oz - l->e) > 1) return 1; } return 0; } static void LOCInit(LOC * l, MECH * m) { l->fx = MechFX(m); l->fy = MechFY(m); l->e = MechElevation(m); l->h = MechFacing(m); l->dh = MechDesiredFacing(m); l->s = MechSpeed(m); l->t = MechRTerrain(m); l->ds = MechDesiredSpeed(m); l->x = MechX(m); l->y = MechY(m); l->lx = MechX(m); l->ly = MechY(m); } static LOC enemy_l[MAX_MECHS_PER_MAP]; static MECH *enemy_m[MAX_MECHS_PER_MAP]; /* Hopefully no more mechs on map */ static int enemy_o[MAX_MECHS_PER_MAP]; /* 'out' = not moving anymore (crashed?) */ static int enemy_i[MAX_MECHS_PER_MAP]; /* dbref */ static int enemy_c = 0; /* Number of enemies */ static LOC friend_l[MAX_MECHS_PER_MAP]; static MECH *friend_m[MAX_MECHS_PER_MAP]; /* Hopefully no more mechs on map */ static int friend_o[MAX_MECHS_PER_MAP]; /* 'out' = not moving anymore (crashed?) */ static int friend_i[MAX_MECHS_PER_MAP]; /* dbref */ static int friend_c = 0; /* Number of enemies */ int getEnemies(MECH * mech, MAP * map, int reset) { MECH *tempMech; int i; if (reset) { for (i = 0; i < enemy_c; i++) { LOCInit(&enemy_l[i], enemy_m[i]); /* Just reset location */ enemy_o[i] = 0; } return 0; } enemy_c = 0; for (i = 0; i < map->first_free; i++) { tempMech = FindObjectsData(map->mechsOnMap[i]); if (!tempMech) continue; if (Destroyed(tempMech)) continue; if (MechStatus(tempMech) & COMBAT_SAFE) continue; if (MechTeam(tempMech) == MechTeam(mech)) continue; if (FlMechRange(map, mech, tempMech) > 50.0) continue; /* Inconsequential */ /* Something that is _possibly_ unnerving */ LOCInit(&enemy_l[enemy_c], tempMech); /* Location */ enemy_m[enemy_c] = tempMech; /* Mech data */ enemy_i[enemy_c++] = i; } return enemy_c; } int getFriends(MECH * mech, MAP * map, int reset) { MECH *tempMech; int i; if (reset) { for (i = 0; i < friend_c; i++) { LOCInit(&friend_l[i], friend_m[i]); /* Just reset location */ friend_o[i] = 0; } return 0; } friend_c = 0; for (i = 0; i < map->first_free; i++) { tempMech = FindObjectsData(map->mechsOnMap[i]); if (!tempMech) continue; if (Destroyed(tempMech)) continue; if (MechTeam(tempMech) != MechTeam(mech)) continue; if (MechType(tempMech) != CLASS_MECH) continue; if (FlMechRange(map, mech, tempMech) > 50.0) continue; /* Inconsequential */ LOCInit(&friend_l[friend_c], tempMech); /* Location */ friend_m[friend_c] = tempMech; /* Mech data */ friend_i[friend_c++] = i; } return friend_c; } /* This has been made .. slightly more complicated. Basic idea (now): Calculate all the [n] states _simultaneously_ ; our movement isn't affecting enemy/friend movement after all, just our own, therefore this is _almost_ [n] times faster than the old code (approx. 1/30 P60 per 'sheep outside combat, perhaps 1/10 P60 inside) */ #define MAGIC_NUM -123456 #define U1(a,b) if (a < b || a == MAGIC_NUM) a = b #define U2(a,b) if (a > b || a == MAGIC_NUM) a = b #define FU(a,b,c,s) ((b) == (c) ? 0 : (s) * ((a) - (b)) / ((c) - (b))) void ai_path_score(MECH * m, MAP * map, AUTO * a, int opts[][2], int num_o, int gotenemy, float dx, float dy, float delx, float dely, int *rl, int *bd, int *bscore) { int i, j, k, l, bearing; int sd, sc; LOC lo[MAX_SIM_PATHS]; int dan[MAX_SIM_PATHS], tdan[MAX_SIM_PATHS], msc[MAX_SIM_PATHS], bsc[MAX_SIM_PATHS]; int out[MAX_SIM_PATHS], stack[MAX_SIM_PATHS], br[MAX_SIM_PATHS]; for (i = 0; i < num_o; i++) { msc[i] = 0; bsc[i] = 0; dan[i] = 0; tdan[i] = 0; out[i] = 0; stack[i] = 0; br[i] = 9999; LOCInit(&lo[i], m); lo[i].dh = AcceptableDegree(lo[i].dh + opts[i][0]); sd = sp_opt[opts[i][1]]; if (sd) { if (sd < 0) { lo[i].ds = lo[i].ds * 2.0 / 3.0; } else if (sd == 1) { float ms = MMaxSpeed(m); lo[i].ds = (lo[i].ds < MP1 ? MP1 : lo[i].ds) * 4.0 / 3.0; if (lo[i].ds > ms) lo[i].ds = ms; } else { float ms = MMaxSpeed(m); lo[i].ds = ms; } } } for (i = 0; i < NORM_SAFE; i++) { dx += delx; dy += dely; for (k = 0; k < num_o; k++) { if (out[k]) continue; if (ai_crash(map, m, &lo[k])) { /* Simulate _one_ step */ out[k] = i + 1; continue; } /* Base target-acquisition stuff */ if ((l = FindXYRange(lo[k].fx, lo[k].fy, dx, dy)) < br[k]) br[k] = l; /* Generally speaking we're going to the point spesified */ msc[k] += 4 * (2 * (50 - br[k]) + (100 - l)); /* Heading change's inherently [slightly] evil */ if (lo[k].h != lo[k].dh) msc[k] -= 1; /* Moving is a good thing */ if (lo[k].x != lo[k].lx || lo[k].y != lo[k].ly) { if (lo[k].t == WATER) msc[k] -= 5; msc[k] += 10; } /* Punish for not utilizing full speed (this is .. hm, flaky) */ if (opts[k][1] != SP_OPT_FASTER && MMaxSpeed(m) > 0.1) { sc = BOUNDED(0, 100 * lo[k].ds / MMaxSpeed(m), 100); msc[k] -= (100 - sc) / 30; /* Basically, unused speed is bad */ } } if (MechType(m) == CLASS_MECH) { /* Simulate friends */ for (j = 0; j < friend_c; j++) { if (friend_o[j]) continue; if (ai_crash(map, friend_m[j], &friend_l[j])) friend_o[j] = 1; } for (k = 0; k < num_o; k++) { int sc = 0; if (out[k] || stack[k]) continue; /* Meaning of stack: Someone moves _into_ the hex */ for (j = 0; j < friend_c; j++) if (!friend_o[j]) if (lo[k].x == friend_l[j].x && lo[k].y == friend_l[j].y) sc++; if (sc > 1) { /* Possible stackage */ int osc = sc; for (j = 0; j < friend_c; j++) if (!friend_o[j]) if (lo[k].x == friend_l[j].x && lo[k].y == friend_l[j].y) if ((lo[k].lx != lo[k].x || lo[k].ly != lo[k].y) || (friend_l[j].lx != friend_l[j].x || friend_l[j].ly != friend_l[j].y)) osc--; if (osc != sc) stack[k] = i + 1; } if (gotenemy) tdan[k] = 0; } } if (gotenemy) { /* Update enemy locations as well */ for (j = 0; j < enemy_c; j++) { if (enemy_o[j]) continue; if (ai_crash(map, enemy_m[j], &enemy_l[j])) enemy_o[j] = 1; for (k = 0; k < num_o; k++) { if (out[k]) continue; if ((l = MyHexDist(lo[k].x, lo[k].y, enemy_l[j].x, enemy_l[j].y, 0)) >= 100) continue; switch (a->auto_cmode) { case 0: /* Withdraw */ if (l > a->auto_cdist) bsc[k] += 5 * a->auto_cdist + l - a->auto_cdist; else bsc[k] += 5 * l; break; case 1: /* Score = fulfilling goal (=> distance from cdist) */ if (l < a->auto_cdist) bsc[k] -= 10 * (a->auto_cdist - l); /* Not too close */ else bsc[k] -= 2 * (l - a->auto_cdist); break; case 2: if (l < a->auto_cdist) bsc[k] -= 2 * (a->auto_cdist - l); else bsc[k] -= 10 * (l - a->auto_cdist); } if (l > 28) continue; /* Danger modifier ; it's _always_ dangerous to be close */ tdan[k] += (40 - MIN(40, l)); /* Arcs can be .. dangerous */ if (MechType(m) == CLASS_MECH) { bearing = FindBearing(lo[k].fx, lo[k].fy, enemy_l[j].fx, enemy_l[j].fy); bearing = lo[k].h - bearing; if (bearing < 0) bearing += 360; if (bearing >= 90 && bearing <= 270) { /* Sides are moderately dangerous [potential rear arcs] */ tdan[k] += 5 * (29 - MIN(29, l)); if (bearing >= 120 && bearing <= 240) { /* Rear arc is VERY dangerous */ tdan[k] += 20 * (29 - MIN(29, l)); } } } else if (MechType(m) == CLASS_VEH_GROUND) { bearing = FindBearing(lo[k].fx, lo[k].fy, enemy_l[j].fx, enemy_l[j].fy); bearing = lo[k].h - bearing; if (bearing < 0) bearing += 360; if (bearing >= 45 && bearing <= 315) { if (bearing >= 135 && bearing <= 225) { /* Rear arc is VERY dangerous */ tdan[k] += 10 * (29 - MIN(29, l)) * (100 - 100 * GetSectArmor(m, BSIDE) / MAX(1, GetSectOArmor(m, BSIDE))) / 100; } else if (bearing < 135) { /* right side */ tdan[k] += 7 * (29 - MIN(29, l)) * (100 - 100 * GetSectArmor(m, RSIDE) / MAX(1, GetSectOArmor(m, RSIDE))) / 100; } else { tdan[k] += 7 * (29 - MIN(29, l)) * (100 - 100 * GetSectArmor(m, LSIDE) / MAX(1, GetSectOArmor(m, LSIDE))) / 100; } } else tdan[k] += 5 * (29 - MIN(29, l)) * (100 - 100 * GetSectArmor(m, FSIDE) / MAX(1, GetSectOArmor(m, FSIDE))) / 100; } } for (k = 0; k < num_o; k++) { if (out[k]) continue; /* Dangerous to be far from buddy in fight */ l = FindXYRange(lo[k].fx, lo[k].fy, dx, dy); if (gotenemy && (delx != 0.0 || dely != 0.0)) tdan[k] += MIN(100, l * l); if (enemy_c) tdan[k] = tdan[k] / enemy_c; /* It's inherently dangerous to move slowly: */ if (lo[k].s <= MP2) tdan[k] += 400; else if (lo[k].s <= MP4) tdan[k] += 300; else if (lo[k].s <= MP6) tdan[k] += 200; else if (lo[k].s <= MP9) tdan[k] += 100; dan[k] += tdan[k] * (NORM_SAFE - i) / (NORM_SAFE / 2); } } } } for (i = 0; i < num_o; i++) { U2(a->w_msc, msc[i]); U1(a->b_msc, msc[i]); if (gotenemy) { U2(a->w_bsc, bsc[i]); U1(a->b_bsc, bsc[i]); U2(a->w_dan, dan[i]); U1(a->b_dan, dan[i]); } } /* Now we have been.. calibrated */ *bscore = 0; *bd = -1; /* Find best overall score */ for (i = 0; i < num_o; i++) { if (!out[i]) out[i] = NORM_SAFE + 1; if (!stack[i]) stack[i] = out[i]; sc = (out[i] - (out[i] - stack[i]) / 2) * SAFE_SCORE + FU(msc[i], a->w_msc, a->b_msc, SCORE_MOD * a->auto_goweight); if (gotenemy) sc += FU(bsc[i], a->w_bsc, a->b_bsc, SCORE_MOD * a->auto_fweight) - FU(dan[i], a->w_dan, a->b_dan, SCORE_MOD * (a->auto_fweight + a->auto_goweight)); if (sc > *bscore || (*bd >= 0 && sc == *bscore && sp_opt[opts[i][1]] > sp_opt[opts[*bd][1]])) { *bscore = sc; *bd = i; *rl = out[i] - 1; } } } int ai_max_speed(MECH * m, AUTO * a) { float ms; if (MechDesiredSpeed(m) != (ms = MMaxSpeed(m))) return 0; return 1; } int ai_opponents(AUTO * a, MECH * m) { if (a->auto_nervous) { a->auto_nervous--; return 1; } if (MechNumSeen(m)) a->auto_nervous = 30; /* We'll stay frisky for awhile even if cons are lost for one reason or another */ return MechNumSeen(m); } void ai_run_speed(MECH * mech, AUTO * a) { /* Warp speed, cap'n! */ char buf[128]; if (!ai_max_speed(mech, a)) { strcpy(buf, "run"); mech_speed(a->mynum, mech, buf); } } void ai_stop(MECH * mech, AUTO * a) { char buf[128]; if (MechDesiredSpeed(mech) > 0.1) { strcpy(buf, "stop"); mech_speed(a->mynum, mech, buf); } } #if 0 void ai_set_speed(MECH * mech, AUTO * a, int s) { float ms; char buf[SBUF_SIZE]; if (s >= 99) { /* To cover rounding errors */ ai_run_speed(mech, a); return; } if (!s) { ai_stop(mech, a); return; } /* Send in percentage-based speed */ ms = MMaxSpeed(mech); ms = ms * s / 100.0; if (MechDesiredSpeed(mech) != ms) { sprintf(buf, "%f", ms); mech_speed(a->mynum, mech, buf); } } #endif void ai_set_speed(MECH * mech, AUTO *a, float spd) { char buf[SBUF_SIZE]; float newspeed; if (!mech || !a) return; newspeed = FBOUNDED(0, spd, ((MMaxSpeed(mech) * a->speed) / 100.0)); if (MechDesiredSpeed(mech) != newspeed) { sprintf(buf, "%f", newspeed); mech_speed(a->mynum, mech, buf); } } void ai_set_heading(MECH * mech, AUTO * a, int dir) { char buf[128]; if (dir == MechDesiredFacing(mech)) return; sprintf(buf, "%d", dir); mech_heading(a->mynum, mech, buf); } #define UNREF(a,b,mod) if (a != MAGIC_NUM && b != MAGIC_NUM) { int c = a, d = b; a = (c * (mod - 1) + d) / mod; b = (c + d * (mod - 1)) / mod; } void ai_adjust_move(AUTO * a, MECH * m, char *text, int hmod, int smod, int b_score) { float ms; ai_set_heading(m, a, MechDesiredFacing(m) + hmod); switch (smod) { default: SendAI("%s state: %s (hmod:%d) sc:%d", AI_Info(m, a), text, hmod, b_score); break; case SP_OPT_FASTER: SendAI("%s state: %s+accelerating (hmod:%d) sc:%d", AI_Info(m, a), text, hmod, b_score); ms = MMaxSpeed(m); ai_set_speed(m, a, (float) ((MechDesiredSpeed(m) < MP1 ? MP1 : MechDesiredSpeed(m)) * 4.0 / 3.0)); break; case SP_OPT_SLOWER: SendAI("%s state: %s+decelerating (hmod:%d) sc:%d", AI_Info(m, a), text, hmod, b_score); ms = MMaxSpeed(m); ai_set_speed(m, a, (int) (MechDesiredSpeed(m) * 2.0 / 3.0)); break; } } int ai_check_path(MECH * m, AUTO * a, float dx, float dy, float delx, float dely) { int o; int b_len, bl, b, b_score; MAP *map = getMap(m->mapindex); o = ai_opponents(a, m); if (a->last_upd > mudstate.now || (mudstate.now - a->last_upd) > AUTO_GOET) { if ((muxevent_tick - a->last_upd) > AUTO_GOTT) { a->b_msc = MAGIC_NUM; a->w_msc = MAGIC_NUM; a->b_bsc = MAGIC_NUM; a->w_bsc = MAGIC_NUM; a->b_dan = MAGIC_NUM; a->b_dan = (40 + 20 * 29 + 100) * 30; /* To stay focused */ } else { /* Slight update ; Un-refine the goals somewhat */ UNREF(a->w_msc, a->b_msc, 3); UNREF(a->w_bsc, a->b_bsc, 5); UNREF(a->w_dan, a->b_dan, 8); a->b_dan = MAX(a->b_dan, (40 + 20 * 29 + 100) * 30); /* To stay focused */ } a->last_upd = mudstate.now; } /* Got either opponents (nasty) or [possibly] blocked path (slightly nasty), i.e. 12sec */ if (MechType(m) == CLASS_MECH) getFriends(m, map, 0); if (o) { getEnemies(m, map, 0); if (!((muxevent_tick / AUTOPILOT_GOTO_TICK) % 4)) { /* Just every fourth tick, i.e. 12sec */ /* Thorough check */ ai_path_score(m, map, a, move_norm_opt, MNORM_COUNT, 1, dx, dy, delx, dely, &bl, &b, &b_score); b_len = b_score / SAFE_SCORE; if (b_len >= MIN_SAFE) ai_adjust_move(a, m, "combat(/twitchy)", move_norm_opt[b][0], move_norm_opt[b][1], b_score); } else { ai_path_score(m, map, a, combat_fast_opt, CFAST_COUNT, 1, dx, dy, delx, dely, &bl, &b, &b_score); b_len = b_score / SAFE_SCORE; if (b_len >= MIN_SAFE) ai_adjust_move(a, m, "[f]combat(/twitchy)", combat_fast_opt[b][0], combat_fast_opt[b][1], b_score); } return 1; /* We want to keep fighting near foes */ } if (!((muxevent_tick / AUTOPILOT_GOTO_TICK) % 4)) { /* Just every fourth tick, i.e. 12sec */ /* Thorough check */ ai_path_score(m, map, a, move_norm_opt, MNORM_COUNT, 0, dx, dy, delx, dely, &bl, &b, &b_score); b_len = b_score / SAFE_SCORE; if (b_len >= MIN_SAFE) ai_adjust_move(a, m, "moving", move_norm_opt[b][0], move_norm_opt[b][1], b_score); } else { ai_path_score(m, map, a, combat_fast_opt, CFAST_COUNT, 0, dx, dy, delx, dely, &bl, &b, &b_score); b_len = b_score / SAFE_SCORE; if (b_len >= MIN_SAFE) ai_adjust_move(a, m, "[f]moving", combat_fast_opt[b][0], combat_fast_opt[b][1], b_score); } if (b_len >= MIN_SAFE) return 1; /* Slow down + stop - no sense in dying needlessly */ ai_stop(m, a); SendAI("%s state: panic", AI_Info(m, a)); sendAIM(a, m, "PANIC! Unable to comply with order."); return 0; } void ai_init(AUTO * a, MECH * m) { /* XXX Analyze our unit type ; set basic combat tactic */ a->auto_cmode = 1; /* CHARGE! */ a->auto_cdist = 2; /* Attempt to avoid kicking distance */ a->auto_nervous = 0; a->auto_goweight = 44; /* We're mainly concentrating on fighting */ a->auto_fweight = 55; a->speed = 100; /* Reset to full speed */ a->flags = 0; a->target = -1; } static MECH *target_mech; int artillery_round_flight_time(float fx, float fy, float tx, float ty); static int mech_snipe_func(MECH * mech, dbref player, int index, int high) { /* Simulate mech movements until flight_time <= now */ int now = 0, crashed = 0; int flt_time; LOC t; MAP *map = getMap(mech->mapindex); LOCInit(&t, target_mech); while ((flt_time = artillery_round_flight_time(MechFX(mech), MechFY(mech), t.fx, t.fy)) > now) { if (!crashed) if (ai_crash(map, target_mech, &t)) crashed = 1; now++; } /* Fire at t.x, t.y */ if (MechTargX(mech) != t.x || MechTargY(mech) != t.y) mech_settarget(player, mech, tprintf("%d %d", t.x, t.y)); mech_fireweapon(player, mech, tprintf("%d", index)); return 0; } void mech_snipe(dbref player, MECH * mech, char *buffer) { char *args[3]; dbref d; DOCHECK(!WizRoy(player), "Permission denied."); DOCHECK(mech_parseattributes(buffer, args, 3) != 2, "Please supply target ID _and_ weapon(s) to use"); DOCHECK((d = FindTargetDBREFFromMapNumber(mech, args[0])) <= 0, "Invalid target!"); target_mech = getMech(d); multi_weap_sel(mech, player, args[1], 1, mech_snipe_func); } /* Experimental (highly) path finding system based on the A* 'a-star' * system used in many typical games. * * Dany - 08/2005 */ /* * Create an astar node and return a pointer to it */ static astar_node *auto_create_astar_node(short x, short y, short x_parent, short y_parent, short g_score, short h_score) { astar_node *temp; temp = malloc(sizeof(astar_node)); if (temp == NULL) return NULL; memset(temp, 0, sizeof(astar_node)); temp->x = x; temp->y = y; temp->x_parent = x_parent; temp->y_parent = y_parent; temp->g_score = g_score; temp->h_score = h_score; temp->f_score = g_score + h_score; temp->hexoffset = x * MAPY + y; return temp; } /* * Destroy the astar node */ static void auto_destroy_astar_node(astar_node *victim) { free(victim); return; } /* * The compare function for the astar pathfinding */ static int auto_astar_compare(void *a, void *b, void *token) { int *one, *two; one = (int *) a; two = (int *) b; return (*one - *two); } /* * Function we use to destroy the astar lists using * the rbtree walk function */ static int auto_astar_callback(void *key, void *data, int depth, void *arg) { astar_node *temp; temp = (astar_node *) data; auto_destroy_astar_node(temp); return 1; } /* * The A* (A-Star) path finding function for the AI * * Returns 1 if it found a path and 0 if it doesn't */ int auto_astar_generate_path(AUTO *autopilot, MECH *mech, short end_x, short end_y) { MAP * map = getMap(autopilot->mapindex); int found_path = 0; /* Our bit arrays */ unsigned char closed_list_bitfield[MAPX*MAPY/8]; unsigned char open_list_bitfield[MAPX*MAPY/8]; float x1, y1, x2, y2; /* Floating point vars for real cords */ short map_x1, map_y1, map_x2, map_y2; /* The actual map 'hexes' */ int i; int child_g_score, child_h_score; /* the score values for the child hexes */ int hexoffset; /* temp int to pass around as the hexoffset */ /* Our lists using Hag's rbtree */ /* Using two rbtree's to store the open_list so we can sort two * different ways */ rbtree *open_list_by_score; /* open list sorted by score */ rbtree *open_list_by_xy; /* open list sorted by hexoffset */ rbtree *closed_list; /* closed list sorted by hexoffset */ /* Helper node for the final path */ dllist_node *astar_path_node; /* Our astar_node helpers */ astar_node *temp_astar_node; astar_node *parent_astar_node; #ifdef DEBUG_ASTAR /* Log File */ FILE *logfile; char log_msg[MBUF_SIZE]; /* Open the logfile */ logfile = fopen("astar.log", "a"); /* Write first message */ snprintf(log_msg, MBUF_SIZE, "\nStarting ASTAR Path finding for AI #%d from " "%d, %d to %d, %d\n", autopilot->mynum, MechX(mech), MechY(mech), end_x, end_y); fprintf(logfile, "%s", log_msg); #endif /* Zero the bitfields */ memset(closed_list_bitfield, 0, sizeof(closed_list_bitfield)); memset(open_list_bitfield, 0, sizeof(open_list_bitfield)); /* Setup the trees */ open_list_by_score = rb_init(&auto_astar_compare, NULL); open_list_by_xy = rb_init(&auto_astar_compare, NULL); closed_list = rb_init(&auto_astar_compare, NULL); /* Setup the path */ /* Destroy any existing path first */ auto_destroy_astar_path(autopilot); autopilot->astar_path = dllist_create_list(); /* Setup the start hex */ temp_astar_node = auto_create_astar_node(MechX(mech), MechY(mech), -1, -1, 0, 0); if (temp_astar_node == NULL) { /*! \todo {Add code here to break if we can't alloc memory} */ #ifdef DEBUG_ASTAR /* Write Log Message */ snprintf(log_msg, MBUF_SIZE, "AI ERROR - Unable to malloc astar node for " "hex %d, %d\n", MechX(mech), MechY(mech)); fprintf(logfile, "%s", log_msg); #endif } /* Add start hex to open list */ rb_insert(open_list_by_score, &temp_astar_node->f_score, temp_astar_node); rb_insert(open_list_by_xy, &temp_astar_node->hexoffset, temp_astar_node); SetHexBit(open_list_bitfield, temp_astar_node->hexoffset); #ifdef DEBUG_ASTAR /* Log it */ snprintf(log_msg, MBUF_SIZE, "Added hex %d, %d (%d %d) to open list\n", temp_astar_node->x, temp_astar_node->y, temp_astar_node->g_score, temp_astar_node->h_score); fprintf(logfile, "%s", log_msg); #endif /* Now loop till we find path */ while (!found_path) { /* Check to make sure there is still stuff in the open list * if not, means we couldn't find a path so quit */ if (rb_size(open_list_by_score) == 0) { break; } /* Get lowest cost node, then remove it from the open list */ parent_astar_node = (astar_node *) rb_search(open_list_by_score, SEARCH_FIRST, NULL); rb_delete(open_list_by_score, &parent_astar_node->f_score); rb_delete(open_list_by_xy, &parent_astar_node->hexoffset); ClearHexBit(open_list_bitfield, parent_astar_node->hexoffset); #ifdef DEBUG_ASTAR /* Log it */ snprintf(log_msg, MBUF_SIZE, "Removed hex %d, %d (%d %d) from open " "list - lowest cost node\n", parent_astar_node->x, parent_astar_node->y, parent_astar_node->g_score, parent_astar_node->h_score); fprintf(logfile, "%s", log_msg); #endif /* Add it to the closed list */ rb_insert(closed_list, &parent_astar_node->hexoffset, parent_astar_node); SetHexBit(closed_list_bitfield, parent_astar_node->hexoffset); #ifdef DEBUG_ASTAR /* Log it */ snprintf(log_msg, MBUF_SIZE, "Added hex %d, %d (%d %d) to closed list" " - lowest cost node\n", parent_astar_node->x, parent_astar_node->y, parent_astar_node->g_score, parent_astar_node->h_score); fprintf(logfile, "%s", log_msg); #endif /* Now we check to see if we added the end hex to the closed list. * When this happens it means we are done */ if (CheckHexBit(closed_list_bitfield, HexOffSet(end_x, end_y))) { found_path = 1; #ifdef DEBUG_ASTAR fprintf(logfile, "Found path for the AI\n"); #endif break; } /* Update open list */ /* Loop through the hexes around current hex and see if we can add * them to the open list */ /* Set the parent hex of the new nodes */ map_x1 = parent_astar_node->x; map_y1 = parent_astar_node->y; /* Going around clockwise direction */ for (i = 0; i < 360; i += 60) { /* Map coord to Real */ MapCoordToRealCoord(map_x1, map_y1, &x1, &y1); /* Calc new hex */ FindXY(x1, y1, i, 1.0, &x2, &y2); /* Real coord to Map */ RealCoordToMapCoord(&map_x2, &map_y2, x2, y2); /* Make sure the hex is sane */ if (map_x2 < 0 || map_y2 < 0 || map_x2 >= map->map_width || map_y2 >= map->map_height) continue; /* Generate hexoffset for the child node */ hexoffset = HexOffSet(map_x2, map_y2); /* Check to see if its in the closed list * if so just ignore it */ if (CheckHexBit(closed_list_bitfield, hexoffset)) continue; /* Check to see if we can enter it */ if ((MechType(mech) == CLASS_MECH) && (abs(Elevation(map, map_x1, map_y1) - Elevation(map, map_x2, map_y2)) > 2)) continue; if ((MechType(mech) == CLASS_VEH_GROUND) && (abs(Elevation(map, map_x1, map_y1) - Elevation(map, map_x2, map_y2)) > 1)) continue; /* Score the hex */ /* Right now just assume movement cost from parent to child hex is * the same (so 100) no matter which dir we go*/ /*! \todo {Possibly add in code to make turning less desirable} */ child_g_score = 100; /* Now add the g score from the parent */ child_g_score += parent_astar_node->g_score; /* Next get range */ /* Using a varient of the Manhattan method since its perfectly * logical for us to go diagonally * * Basicly just going to get the range, * and multiply by 100 */ /*! \todo {Add in something for elevation cost} */ /* Get the end hex in real coords, using the old variables * to store the values */ MapCoordToRealCoord(end_x, end_y, &x1, &y1); /* Re-using the x2 and y2 values we calc'd for the child hex * to find the range between the child hex and end hex */ child_h_score = 100 * FindHexRange(x2, y2, x1, y1); /* Now add in some modifiers for terrain */ switch(GetTerrain(map, map_x2, map_y2)) { case LIGHT_FOREST: /* Don't bother trying to enter a light forest * hex unless we can */ if ((MechType(mech) == CLASS_VEH_GROUND) && (MechMove(mech) != MOVE_TRACK)) continue; child_g_score += 50; break; case ROUGH: child_g_score += 50; break; case HEAVY_FOREST: /* Don't bother trying to enter a heavy forest * hex unless we can */ if (MechType(mech) == CLASS_VEH_GROUND) continue; child_g_score += 100; break; case MOUNTAINS: child_g_score += 100; break; case WATER: /* Don't bother trying to enter a water hex * unless we can */ if ((MechType(mech) == CLASS_VEH_GROUND) && (MechMove(mech) != MOVE_HOVER)) continue; /* We really don't want them trying to enter water */ child_g_score += 200; break; case HIGHWATER: /* Don't bother trying to enter a water hex * unless we can */ if ((MechType(mech) == CLASS_VEH_GROUND) && (MechMove(mech) != MOVE_HOVER)) continue; /* We really don't want them trying to enter water */ child_g_score += 200; break; default: break; } /* Is it already on the openlist */ if (CheckHexBit(open_list_bitfield, hexoffset)) { /* Ok need to compare the scores and if necessary recalc * and change stuff */ /* Get the node off the open_list */ temp_astar_node = (astar_node *) rb_find(open_list_by_xy, &hexoffset); /* Now compare the 'g_scores' to determine shortest path */ /* If g_score is lower, this means better path * from the current parent node */ if (child_g_score < temp_astar_node->g_score) { /* Remove from open list */ rb_delete(open_list_by_score, &temp_astar_node->f_score); rb_delete(open_list_by_xy, &temp_astar_node->hexoffset); ClearHexBit(open_list_bitfield, temp_astar_node->hexoffset); #ifdef DEBUG_ASTAR /* Log it */ snprintf(log_msg, MBUF_SIZE, "Removed hex %d, %d (%d %d) from " "open list - score recal\n", temp_astar_node->x, temp_astar_node->y, temp_astar_node->g_score, temp_astar_node->h_score); fprintf(logfile, "%s", log_msg); #endif /* Recalc score */ /* H-Score should be the same since the hex doesn't move */ temp_astar_node->g_score = child_g_score; temp_astar_node->f_score = temp_astar_node->g_score + temp_astar_node->h_score; /* Change parent hex */ temp_astar_node->x_parent = map_x1; temp_astar_node->y_parent = map_y1; /* Will re-add the node below */ } else { /* Don't need to do anything so we can skip * to the next node */ continue; } } else { /* Node isn't on the open list so we have to create it */ temp_astar_node = auto_create_astar_node(map_x2, map_y2, map_x1, map_y1, child_g_score, child_h_score); if (temp_astar_node == NULL) { /*! \todo {Add code here to break if we can't alloc memory} */ #ifdef DEBUG_ASTAR /* Log it */ snprintf(log_msg, MBUF_SIZE, "AI ERROR - Unable to malloc astar" " node for hex %d, %d\n", map_x2, map_y2); fprintf(logfile, "%s", log_msg); #endif } } /* Now add (or re-add) the node to the open list */ /* Hack to check to make sure its score is not already on the open * list. This slightly skews the results towards nodes found earlier * then those found later */ while (1) { if (rb_exists(open_list_by_score, &temp_astar_node->f_score)) { temp_astar_node->f_score++; #ifdef DEBUG_ASTAR fprintf(logfile, "Adjusting score for hex %d, %d - same" " fscore already exists\n", temp_astar_node->x, temp_astar_node->y); #endif } else { break; } } rb_insert(open_list_by_score, &temp_astar_node->f_score, temp_astar_node); rb_insert(open_list_by_xy, &temp_astar_node->hexoffset, temp_astar_node); SetHexBit(open_list_bitfield, temp_astar_node->hexoffset); #ifdef DEBUG_ASTAR /* Log it */ snprintf(log_msg, MBUF_SIZE, "Added hex %d, %d (%d %d) to open list\n", temp_astar_node->x, temp_astar_node->y, temp_astar_node->g_score, temp_astar_node->h_score); fprintf(logfile, "%s", log_msg); #endif } /* End of looking for hexes next to us */ } /* End of looking for path */ /* We Done lets go */ /* Lets first see if we found a path */ if (found_path) { #ifdef DEBUG_ASTAR /* Log Message */ fprintf(logfile, "Building Path from closed list for AI\n"); #endif /* Found a path so we need to go through the closed list * and generate it */ /* Get the end hex, find its parent hex and work back to * start hex while building list */ /* Get end hex from closed list */ hexoffset = HexOffSet(end_x, end_y); temp_astar_node = rb_find(closed_list, &hexoffset); /* Add end hex to path list */ astar_path_node = dllist_create_node(temp_astar_node); dllist_insert_beginning(autopilot->astar_path, astar_path_node); #ifdef DEBUG_ASTAR /* Log it */ fprintf(logfile, "Added hex %d, %d to path list\n", temp_astar_node->x, temp_astar_node->y); #endif /* Remove it from closed list */ rb_delete(closed_list, &temp_astar_node->hexoffset); #ifdef DEBUG_ASTAR /* Log it */ fprintf(logfile, "Removed hex %d, %d from closed list - path list work\n", temp_astar_node->x, temp_astar_node->y); #endif /* Check if the end hex is the start hex */ if (!(temp_astar_node->x == MechX(mech) && temp_astar_node->y == MechY(mech))) { /* Its not so lets loop through the closed list * building the path */ /* Loop */ while (1) { /* Get Parent Node Offset*/ hexoffset = HexOffSet(temp_astar_node->x_parent, temp_astar_node->y_parent); /*! \todo {Possibly add check here incase the node we're * looking for some how did not end up on the list} */ /* Get Parent Node from closed list */ parent_astar_node = rb_find(closed_list, &hexoffset); /* Check if start hex */ /* If start hex quit */ if (parent_astar_node->x == MechX(mech) && parent_astar_node->y == MechY(mech)) { break; } /* Add to path list */ astar_path_node = dllist_create_node(parent_astar_node); dllist_insert_beginning(autopilot->astar_path, astar_path_node); #ifdef DEBUG_ASTAR /* Log it */ fprintf(logfile, "Added hex %d, %d to path list\n", parent_astar_node->x, parent_astar_node->y); #endif /* Remove from closed list */ rb_delete(closed_list, &parent_astar_node->hexoffset); #ifdef DEBUG_ASTAR /* Log it */ fprintf(logfile, "Removed hex %d, %d from closed list - path list work\n", parent_astar_node->x, parent_astar_node->y); #endif /* Make parent new child */ temp_astar_node = parent_astar_node; } /* End of while loop */ } /* Done with the path its cleanup time */ } /* Make sure we destroy all the objects we dont need any more */ #ifdef DEBUG_ASTAR /* Log Message */ fprintf(logfile, "Destorying the AI lists\n"); #endif /* Destroy the open lists */ rb_walk(open_list_by_score, WALK_INORDER, &auto_astar_callback, NULL); rb_destroy(open_list_by_score); rb_destroy(open_list_by_xy); /* Destroy the closed list */ rb_walk(closed_list, WALK_INORDER, &auto_astar_callback, NULL); rb_destroy(closed_list); #ifdef DEBUG_ASTAR /* Close Log file */ fclose(logfile); #endif /* End */ if (found_path) { return 1; } else { return 0; } } /* Function to Smooth out the AI path and remove * nodes we don't need */ /* Not even close to being finished yet */ void astar_smooth_path(AUTO *autopilot) { dllist_node *current, *next, *prev; float x1, y1, x2, y2; int degrees; /* Get the n node off the list */ /* Get the n+1 node off the list */ /* Get bearing from n to n+1 */ /* Get n+2 node off list */ /* Get bearing from n to n+2 node */ /* Compare bearings */ /* If in same direction as previous * don't need n+1 node */ /* Keep looping till bearing doesn't match */ /* Then reset n node to final node and continue */ return; } void auto_destroy_astar_path(AUTO *autopilot) { astar_node *temp_astar_node; /* Make sure there is a path if not quit */ if (!(autopilot->astar_path)) return; /* There is a path lets kill it */ if (dllist_size(autopilot->astar_path) > 0) { while (dllist_size(autopilot->astar_path)) { temp_astar_node = dllist_remove_node_at_pos(autopilot->astar_path, 1); auto_destroy_astar_node(temp_astar_node); } } /* Finally destroying the path */ dllist_destroy_list(autopilot->astar_path); autopilot->astar_path = NULL; }