#include #include #include #include "lauxlib.h" #include "lualib.h" #include #include "lephisto.h" #include "log.h" #include "pilot.h" #include "player.h" #include "physics.h" #include "pack.h" #include "rng.h" #include "space.h" #include "faction.h" #include "llua.h" #include "lluadef.h" #include "ai.h" /** * @file ai.c * * @brief Controls the Pilot AI. * * == AI ====================================================== * * -- Goal (Task) based AI with additional optimization. * AI uses the goal (task) based AI approach with tasks scripted * in lua. Additionally there is a task that is hardcoded and * obligatory in any AI script. The 'control' task, whose only * purpose is to assign tasks if there is none, and optimize * or change tasks if there are. * * Eg.. Pilot A is attacking Pilot B. Pilot C then comes along * the same system and is of the same faction as Pilot B. and * therefor attacks Pilot A. Pilot A would keep fighting pilot * B and until the control task comes in. Then the pilot could * run if it deems fit that Pilot C and Pilot B together are * both too strong for A. Or.. Attack C as it is an easy target * to finish. * Basically, there is many possibilities, and it's down to the * Lua fanatics to decide what to do. * * -- AI will follow basic tasks defined from Lua AI scripts. * -- If task is NULL, AI will run "control" task. * -- Task is continued every frame. * -- "control" task is a special task that *must* exist in * any given Pilot AI (missiles, and suck will use "seek". * -- "control" task is not permanent, but transitory. * -- "control" task sets another task. * -- "control" task is also run at a set rate (depending on * Lua global "control_rate") to choose optimal behaviour * (task). */ /** * @def lua_regnumber(l, s, n) * * @brief Register a number constant n to name s (syntax is just like lua_regfunc). */ #define lua_regnumber(l,s,n) (lua_pushnumber(l,n), lua_setglobal(l,s)) /* Ai flags. */ #define ai_setFlag(f) (pilot_flags |= f) /**< Set pilot flag f */ #define ai_isFlag(f) (pilot_flags & f) /**< Check pilot flag f */ /* Flags. */ #define AI_PRIMARY (1<<0) /**< Firing primary weapon. */ #define AI_SECONDARY (1<<1) /**< Firing secondary weapon. */ /* file info. */ #define AI_PREFIX "../scripts/ai/" /**< AI file prefix. */ #define AI_SUFFIX ".lua" /**< AI file suffix. */ #define AI_INCLUDE "include/" /**< Where to search for includes. */ /* AI profiles. */ static AI_Profile* profiles = NULL; /**< Array of AI_Profiles loaded. */ static int nprofiles = 0; /**< Number of AI_Profiles loaded. */ /* Extern pilot hacks. */ extern Pilot** pilot_stack; extern int pilot_nstack; static int ai_minbrakedist(lua_State* L); /* Minimal breaking distance. */ static int ai_accel(lua_State* L); /* Accelerate. */ /* Internal C routines. */ static void ai_run(lua_State* L, const char* funcname); static int ai_loadProfile(char* filename); static void ai_freetask(Task* t); /* External C routines. */ void ai_attacked(Pilot* attacked, const unsigned int attacker); /* weapon.c */ void ai_create(Pilot* pilot); /* C routines made external. */ void ai_destroy(Pilot* p); void ai_think(Pilot* pilot); /* Ai routines for Lua. */ /* Tasks. */ static int ai_pushtask(lua_State* L); /* pushtask(string, number/pointer, number) */ static int ai_poptask(lua_State* L); /* poptask() */ static int ai_taskname(lua_State* L); /* Number taskname. */ /* Consult values. */ static int ai_gettarget(lua_State* L); /* Pointer gettarget() */ static int ai_gettargetid(lua_State* L); /* Number gettargetis() */ static int ai_getrndpilot(lua_State* L); /* Number getrndpilot() */ static int ai_armour(lua_State* L); /* armour() */ static int ai_shield(lua_State* L); /* shield() */ static int ai_parmour(lua_State* L); /* parmour() */ static int ai_pshield(lua_State* L); /* pshield() */ static int ai_getdistance(lua_State* L); /* Number getdist(Vec2) */ static int ai_getpos(lua_State* L); /* getpos(number) */ static int ai_minbrakedist(lua_State* L); /* Number minbrakedist() */ static int ai_cargofree(lua_State* L); /* Number cargofree(). */ /* Boolean expressions. */ static int ai_exists(lua_State* L); /* boolean exists */ static int ai_ismaxvel(lua_State* L); /* Boolean ismaxvel() */ static int ai_isstopped(lua_State* L); /* Boolean isstopped() */ static int ai_isenemy(lua_State* L); /* boolean isenemy(number). */ static int ai_isally(lua_State* L); /* boolean isally(number). */ static int ai_incombat(lua_State* L); /* boolean incombat([number]) */ static int ai_haslockon(lua_State* L); /* boolean haslockon() */ /* Movement. */ static int ai_accel(lua_State* L); /* accel(number); nuimber <= 1. */ static int ai_turn(lua_State* L); /* turn(number); abs(number) <= 1. */ static int ai_face(lua_State* L); /* face(number/pointer) */ static int ai_aim(lua_State* L); /* aim(number). */ static int ai_brake(lua_State* L); /* Brake() */ static int ai_getnearestplanet(lua_State* L); /* pointer getnearestplanet() */ static int ai_getrndplanet(lua_State* L); /* pointer getrndplanet() */ static int ai_getlandplanet(lua_State* L); /* pointer getlandplanet() */ static int ai_hyperspace(lua_State* L); /* [number] hyperspace() */ static int ai_stop(lua_State* L); /* stop() */ /* Combat. */ static int ai_combat(lua_State* L); /* combat(number) */ static int ai_settarget(lua_State* L); /* settarget(number) */ static int ai_secondary(lua_State* L); /* string secondary() */ static int ai_hasturrets(lua_State* L); /* bool hasturrets() */ static int ai_shoot(lua_State* L); /* shoot(number) number = 1,2,3. */ static int ai_getenemy(lua_State* L); /* number getenemy(). */ static int ai_hostile(lua_State* L); /* hostile(number). */ static int ai_getweaprange(lua_State* L); /* number getweaprange(). */ /* Timers. */ static int ai_settimer(lua_State* L); /* settimer(number, number) */ static int ai_timeup(lua_State* L); /* boolean timeup(number) */ /* Messages. */ static int ai_comm(lua_State* L); /* comm(string) */ static int ai_broadcast(lua_State* L); /* broadcast(string) */ /* Loot. */ static int ai_credits(lua_State* L); /* credits(number). */ static int ai_cargo(lua_State* L); /* cargo(name, quantity). */ static int ai_shipprice(lua_State* L); /* shipprice(). */ static const luaL_Reg ai_methods[] = { /* Tasks. */ { "pushtask", ai_pushtask }, { "poptask", ai_poptask }, { "taskname", ai_taskname }, /* Sanity checks. */ { "exists", ai_exists }, { "ismaxvel", ai_ismaxvel }, { "isstopped", ai_isstopped }, { "isenemy", ai_isenemy }, { "isally", ai_isally }, /* Get's. */ { "incombat", ai_incombat }, { "haslockon", ai_haslockon }, { "target", ai_gettarget }, { "targetid", ai_gettargetid }, { "rndpilot", ai_getrndpilot }, { "armour", ai_armour }, { "shield", ai_shield }, { "parmour", ai_parmour }, { "pshield", ai_pshield }, { "dist", ai_getdistance }, { "pos", ai_getpos }, { "minbrakedist", ai_minbrakedist }, { "cargofree", ai_cargofree }, { "nearestplanet", ai_getnearestplanet }, { "rndplanet", ai_getrndplanet }, { "landplanet", ai_getlandplanet }, /* Movement. */ { "accel", ai_accel }, { "turn", ai_turn }, { "face", ai_face }, { "brake", ai_brake }, { "stop", ai_stop }, { "hyperspace", ai_hyperspace }, /* Combat. */ { "aim", ai_aim }, { "combat", ai_combat }, { "settarget", ai_settarget }, { "secondary", ai_secondary }, { "hasturrets", ai_hasturrets }, { "shoot", ai_shoot }, { "getenemy", ai_getenemy }, { "hostile", ai_hostile }, { "getweaprange", ai_getweaprange }, /* Timers. */ { "settime", ai_settimer }, { "timeup", ai_timeup }, /* Messages. */ { "comm", ai_comm }, { "broadcast", ai_broadcast }, /* Loot. */ { "setcredits", ai_credits }, { "setcargo", ai_cargo }, { "shipprice", ai_shipprice }, { 0, 0 } /* End. */ }; /* Current pilot "thinking" and assorted variables. */ static Pilot* cur_pilot = NULL; static double pilot_acc = 0.; static double pilot_turn = 0.; static int pilot_flags = 0; static int pilot_target = 0; /* Ai status: 'Create' functions that can't be used elsewhere. */ #define AI_STATUS_NORMAL 1 /**< Normal AI function behaviour. */ #define AI_STATUS_CREATE 2 /**< AI is running create function. */ static int ai_status = AI_STATUS_NORMAL; /** * @fn static void ai_run(lua_State* L, const char* funcname) * * @brief Attemps to run a function. * * @param[in] L Lua state to run function on. * @param[in] funcname Function to run. */ static void ai_run(lua_State* L, const char* funcname) { lua_getglobal(L, funcname); if(lua_pcall(L, 0, 0, 0)) /* Errors accured. */ WARN("Pilot '%s' ai -> '%s' : %s", cur_pilot->name, funcname, lua_tostring(L,-1)); } /** * @fn void ai_destroy(Pilot* p) * * @brief Destroys the ai part of the pilot. * * @param[in] p Pilot to destroy it's AI part. */ void ai_destroy(Pilot* p) { if(p->task) ai_freetask(p->task); } /** * @fn int ai_init(void) * * @brief Initializes the AI stuff which is basically Lua. * * @return 0 on no errors. */ int ai_init(void) { char** files; uint32_t nfiles, i; /* Get the file list. */ files = pack_listfiles(data, &nfiles); /* Load the profiles. */ for(i = 0; i < nfiles; i++) if((strncmp(files[i], AI_PREFIX, strlen(AI_PREFIX))==0) && /* Prefixed. */ (strncmp(files[i] + strlen(AI_PREFIX), AI_INCLUDE, /* Not an include. */ strlen(AI_INCLUDE)) != 0) && (strncmp(files[i] + strlen(files[i]) - strlen(AI_SUFFIX), /* Suffixed. */ AI_SUFFIX, strlen(AI_SUFFIX))==0)) if(ai_loadProfile(files[i])) /* Load the profiles. */ WARN("Error loading AI profile '%s'", files[i]); /* Free the char allocated by pack. */ for(i = 0; i < nfiles; i++) free(files[i]); free(files); DEBUG("Loaded %d AI profile%c", nprofiles, (nprofiles==1)?' ':'s'); return 0; } /** * @fn static int ai_loadProfile(char* filename) * * @brief Initializes an AI_Profile and adds it to the stack. * * @param[in] filename File to create the profile from. * @return 0 on no error. */ static int ai_loadProfile(char* filename) { char* buf = NULL; uint32_t bufsize = 0; lua_State* L; profiles = realloc(profiles, sizeof(AI_Profile)*(++nprofiles)); profiles[nprofiles-1].name = malloc(sizeof(char)* (strlen(filename)-strlen(AI_PREFIX)-strlen(AI_SUFFIX))+1); snprintf(profiles[nprofiles-1].name, strlen(filename)-strlen(AI_PREFIX)-strlen(AI_SUFFIX)+1, "%s", filename+strlen(AI_PREFIX)); profiles[nprofiles-1].L = llua_newState(); if(profiles[nprofiles-1].L == NULL) { ERR("Unable to create a new Lua state"); return -1; } L = profiles[nprofiles-1].L; /* Open basic Lua stuff. */ llua_loadBasic(L); /* Constants. */ lua_regnumber(L, "player", PLAYER_ID); /* Player id. */ /* Register C funstions in Lua. */ luaL_register(L, "ai", ai_methods); lua_loadRnd(L); /* Now load the file, since all the functions have been previously loaded. */ buf = pack_readfile(DATA, filename, &bufsize); if(luaL_dobuffer(L, buf, bufsize, filename) != 0) { ERR("Error loading AI file: %s\n" "%s\n" "Most likely Lua file has improper syntax, please check.", filename, lua_tostring(L, -1)); return -1; } free(buf); return 0; } /* @fn AI_Profile* ai_getProfile(char* name) * * @brief Get the AI_Profile by name. * * @param[in] name Name of the profile to get. * @return The profile or NULL on error. */ AI_Profile* ai_getProfile(char* name) { if(profiles == NULL) return NULL; int i; for(i = 0; i < nprofiles; i++) if(strcmp(name, profiles[i].name)==0) return &profiles[i]; WARN("AI Profile '%s' not found in AI stack", name); return NULL; } /** * @fn void ai_exit(void) * * @brief Clean up the gloabl AI. */ void ai_exit(void) { int i; for(i = 0; i < nprofiles; i++) { free(profiles[i].name); lua_close(profiles[i].L); } free(profiles); } /** * @fn void ai_think(Pilot* pilot) * * @brief Heart of the AI, brains of the pilot. * * @param pilot Pilot that needs to think. */ void ai_think(Pilot* pilot) { lua_State* L; cur_pilot = pilot; /* Set current pilot being processed. */ L = cur_pilot->ai->L; /* Set the AI profile to the current pilot's. */ /* Clean up some variables. */ pilot_acc = 0.; pilot_turn = 0.; pilot_flags = 0; pilot_target = 0; /* Control function if pilot is idle or tick is up. */ if((cur_pilot->tcontrol < SDL_GetTicks()) || (cur_pilot->task == NULL)) { ai_run(L, "control"); /* Run control. */ lua_getglobal(L, "control_rate"); cur_pilot->tcontrol = SDL_GetTicks() + (int)(1000.*lua_tonumber(L, -1)); } if(cur_pilot->task) /* Pilot has a currently running task. */ ai_run(L, cur_pilot->task->name); /* Make sure pilot_acc and pilot_turn are legal moves. */ if(pilot_acc > 1.) pilot_acc = 1.; /* Value must be <= 1. */ if(pilot_turn > 1.) pilot_turn = 1.; /* Value must be between -1 and 1. */ else if(pilot_turn < -1.) pilot_turn = -1.; cur_pilot->solid->dir_vel = 0.; if(pilot_turn) /* Set the turning velocity. */ cur_pilot->solid->dir_vel += cur_pilot->turn * pilot_turn; vect_pset(&cur_pilot->solid->force, cur_pilot->thrust * pilot_acc, cur_pilot->solid->dir); /* Fire weapons if needs be. */ if(ai_isFlag(AI_PRIMARY)) pilot_shoot(pilot, pilot_target, 0); /* Primary. */ if(ai_isFlag(AI_SECONDARY)) pilot_shoot(pilot, pilot_target, 1); /* Secondary. */ } /** * @fn void ai_attacked(Pilot* attacked, const unsigned int attacker) * * @brief Trigger the attacked() function in the Pilots AI. * * @param attacked Pilot that is attacked. * @param[in] attacker ID of the attacker. */ void ai_attacked(Pilot* attacked, const unsigned int attacker) { lua_State* L; cur_pilot = attacked; L = cur_pilot->ai->L; lua_getglobal(L, "attacked"); lua_pushnumber(L, attacker); if(lua_pcall(L, 1, 0, 0)) WARN("Pilot '%s' ai -> 'attacked' : %s", cur_pilot->name, lua_tostring(L, -1)); } /** * @fn void ai_create(Pilot* pilot) * * @brief Run the create() function in the pilot. * * Should create all the gear and such the pilot has. * * @param pilot Pilot to "create". */ void ai_create(Pilot* pilot) { lua_State* L; cur_pilot = pilot; L = cur_pilot->ai->L; ai_status = AI_STATUS_CREATE; ai_run(L, "create"); ai_status = AI_STATUS_NORMAL; } /* ===================== */ /* INTERNAL C FUNCTIONS. */ /* ===================== */ /* Free the task. */ static void ai_freetask(Task* t) { if(t->next) ai_freetask(t->next); /* Woot, recursive freeing! */ if(t->name) free(t->name); if(t->dtype == TYPE_PTR) free(t->dat.target); free(t); } /* ======================================================== */ /* C functions to call from Lua. */ /* ======================================================== */ /* Push the current stack. */ static int ai_pushtask(lua_State* L) { int pos; if(lua_isnumber(L, 1)) pos = (int) lua_tonumber(L, 1); else return 0; /* Invalid param. */ Task* t = MALLOC_L(Task); t->name = (lua_isstring(L, 2)) ? strdup((char*) lua_tostring(L, 2)) : NULL; t->next = NULL; t->dat.target = NULL; if(lua_gettop(L) > 2) { if(lua_isnumber(L, 3)) { t->dtype = TYPE_INT; t->dat.ID = (unsigned int) lua_tonumber(L, 3); } else if(lua_islightuserdata(L, 3)) { /* Only pointer valid is Vec2* in Lua. */ t->dtype = TYPE_PTR; t->dat.target = MALLOC_L(Vec2); /* No idea why vectcpy doesn't work here.. */ ((Vec2*)t->dat.target)->x = ((Vec2*)lua_topointer(L,3))->x; ((Vec2*)t->dat.target)->y = ((Vec2*)lua_topointer(L,3))->y; ((Vec2*)t->dat.target)->mod = ((Vec2*)lua_topointer(L,3))->mod; ((Vec2*)t->dat.target)->angle = ((Vec2*)lua_topointer(L,3))->angle; } else t->dtype = TYPE_NULL; } if(cur_pilot->task == NULL) /* No other tasks. */ cur_pilot->task = t; else if(pos == 1) { /* Put at the end. */ Task* pointer; for(pointer = cur_pilot->task; pointer->next; pointer = pointer->next); pointer->next = t; } else { /* Default put at the beginning. */ t->next = cur_pilot->task; cur_pilot->task = t; } return 0; } /* Pop the current task. */ static int ai_poptask(lua_State* L) { (void)L; /* Just a hack to avoid -W -Wall warnings. */ Task* t = cur_pilot->task; if(t != NULL) { cur_pilot->task = t->next; t->next = NULL; } else { LLUA_DEBUG("Trying to pop task when there are no tasks in stack"); return 0; } ai_freetask(t); return 0; } /* Grab the current tasks name. */ static int ai_taskname(lua_State* L) { if(cur_pilot->task) lua_pushstring(L, cur_pilot->task->name); else lua_pushstring(L, "none"); return 1; } /* Grab the target pointer. */ static int ai_gettarget(lua_State* L) { if(cur_pilot->task->dtype == TYPE_PTR) { lua_pushlightuserdata(L, cur_pilot->task->dat.target); return 1; } return 0; } /* Get the ID. */ static int ai_gettargetid(lua_State* L) { if(cur_pilot->task->dtype == TYPE_INT) { lua_pushnumber(L, cur_pilot->task->dat.ID); return 1; } return 0; } static int ai_getrndpilot(lua_State* L) { lua_pushnumber(L, pilot_stack[RNG(0, pilot_nstack-1)]->id); return 1; } /* Get the pilots armour. */ static int ai_armour(lua_State* L) { double d; if(lua_isnumber(L,1)) d = pilot_get((unsigned int)lua_tonumber(L,1))->armour; else d = cur_pilot->armour; lua_pushnumber(L, d); return 1; } /* Get pilots shield. */ static int ai_shield(lua_State* L) { double d; if(lua_isnumber(L,1)) d = pilot_get((unsigned int)lua_tonumber(L,1))->shield; else d = cur_pilot->shield; lua_pushnumber(L, d); return 1; } /* Get the pilot's armour in percentage. */ static int ai_parmour(lua_State* L) { double d; Pilot* p; if(lua_isnumber(L,1)) { p = pilot_get((unsigned int)lua_tonumber(L,1)); d = p->armour / p->armour_max * 100.; } else d = cur_pilot->armour / cur_pilot->armour_max * 100.; lua_pushnumber(L, d); return 1; } /* Get the pilot's shield in percentage. */ static int ai_pshield(lua_State* L) { double d; Pilot* p; if(lua_isnumber(L,1)) { p = pilot_get((unsigned int)lua_tonumber(L,1)); d = p->shield / p->shield_max * 100.; } else d = cur_pilot->shield / cur_pilot->shield_max * 100.; lua_pushnumber(L, d); return 1; } /* Get the distance from the pointer. */ static int ai_getdistance(lua_State* L) { Vec2* vect; Pilot* pilot; unsigned int n; LLUA_MIN_ARGS(1); /* Vector as a parameter. */ if(lua_islightuserdata(L, 1)) vect = (Vec2*)lua_topointer(L, 1); /* Pilot id as parameter. */ else if(lua_isnumber(L, 1)) { n = (unsigned int)lua_tonumber(L,1); pilot = pilot_get((unsigned int) lua_tonumber(L, 1)); if(pilot == NULL) { LLUA_DEBUG("Pilot '%d' not found in stack", n); return 0; } vect = &pilot->solid->pos; } else /* Wrong parameter. */ LLUA_INVALID_PARAMETER(); lua_pushnumber(L, vect_dist(vect, &cur_pilot->solid->pos)); return 1; } /* Get the pilots position. */ static int ai_getpos(lua_State* L) { Pilot* p; if(lua_isnumber(L, 1)) { p = pilot_get((int)lua_tonumber(L,1)); /* Pilot ID. */ if(p == NULL) return 0; } else p = cur_pilot; /* Default to ones self. */ lua_pushlightuserdata(L, &p->solid->pos); return 1; } /* * Get the minimum braking distance. * * Braking vel ==> 0 = v - a*dt * Add turn around time (to initial velocity) : * ==> 180.*360./cur_pilot->turn * Add it to general euler equation x = v*t + 0.5 * a * t^2 * Have fun. * * I really hate this function. Why isn't it depricated yet? */ static int ai_minbrakedist(lua_State* L) { double time, dist, vel; time = VMOD(cur_pilot->solid->vel) / (cur_pilot->thrust / cur_pilot->solid->mass); vel = MIN(cur_pilot->speed, VMOD(cur_pilot->solid->vel)); dist = vel*(time+1.1*180./cur_pilot->turn) - 0.5 * (cur_pilot->thrust / cur_pilot->solid->mass)*time*time; lua_pushnumber(L, dist); /* return */ return 1; } static int ai_cargofree(lua_State* L) { lua_pushnumber(L, pilot_cargoFree(cur_pilot)); return 1; } static int ai_exists(lua_State* L) { Pilot* p; int i; if(lua_isnumber(L,1)) { i = 1; p = pilot_get((unsigned int)lua_tonumber(L,1)); if(p == NULL) i = 0; else if(pilot_isFlag(p, PILOT_DEAD)) i = 0; lua_pushboolean(L, i); return 1; } /* Default to false for everything that isn't a pilot. */ lua_pushboolean(L, 0); return 0; } /* Are we at max velocity? */ static int ai_ismaxvel(lua_State* L) { lua_pushboolean(L,(VMOD(cur_pilot->solid->vel)>cur_pilot->speed-MIN_VEL_ERR)); return 1; } /* Have we stopped? */ static int ai_isstopped(lua_State* L) { lua_pushboolean(L, VMOD(cur_pilot->solid->vel) < MIN_VEL_ERR); return 1; } /* Check if the pilot is an enemy. */ static int ai_isenemy(lua_State* L) { if(lua_isnumber(L,1)) lua_pushboolean(L, areEnemies(cur_pilot->faction, pilot_get(lua_tonumber(L,1))->faction)); return 1; } /* Check if the pilot is an ally. */ static int ai_isally(lua_State* L) { lua_pushboolean(L, areAllies(cur_pilot->faction, pilot_get(lua_tonumber(L,1))->faction)); return 1; } /* Check to see if the pilot is in combat. Defaults to self. */ static int ai_incombat(lua_State* L) { Pilot* p; if(lua_isnumber(L, 1)) p = pilot_get((unsigned int)lua_tonumber(L,1)); else p = cur_pilot; lua_pushboolean(L,pilot_isFlag(p, PILOT_COMBAT)); return 1; } static int ai_haslockon(lua_State* L) { lua_pushboolean(L, cur_pilot->lockons > 0); return 1; } /* Accelerate the pilot based on a param. */ static int ai_accel(lua_State* L) { double n; if(lua_gettop(L) > 1 && lua_isnumber(L, 1)) { n = (double)lua_tonumber(L,1); if(n > 1.) n = 1.; else if(n < 0.) n = 0.; if(VMOD(cur_pilot->solid->vel) > (n * cur_pilot->speed)) pilot_acc = 0.; } else pilot_acc = 1.; return 0; } /* Turn the pilot based on a param. */ static int ai_turn(lua_State* L) { LLUA_MIN_ARGS(1); pilot_turn = (lua_isnumber(L, 1)) ? (double)lua_tonumber(L, 1) : 0.; return 0; } /* Face the target. */ static int ai_face(lua_State* L) { LLUA_MIN_ARGS(1); Vec2* v, sv, tv; /* Grab the position to face. */ Pilot* p; double mod, diff; int invert = 0; int n = -2; if(lua_isnumber(L, 1)) n = (int)lua_tonumber(L, 1); if(n >= 0) { p = pilot_get(n); if(p == NULL) return 0; /* Make sure pilot is valid. */ vect_cset(&tv, VX(p->solid->pos), VY(p->solid->pos)); v = NULL; } else if(lua_islightuserdata(L,1)) v = (Vec2*)lua_topointer(L,1); mod = 10; if(lua_gettop(L) > 1 && lua_isnumber(L,2)) invert = (int)lua_tonumber(L,2); if(invert) mod *= -1; vect_cset(&sv, VX(cur_pilot->solid->pos), VY(cur_pilot->solid->pos)); if(v == NULL) /* Target is dynamic. */ diff = angle_diff(cur_pilot->solid->dir, (n==-1) ? VANGLE(sv) : vect_angle(&sv, &tv)); else /* Target is static. */ diff = angle_diff(cur_pilot->solid->dir, (n==-1) ? VANGLE(cur_pilot->solid->pos) : vect_angle(&cur_pilot->solid->pos, v)); pilot_turn = mod*diff; lua_pushnumber(L, ABS(diff*180./M_PI)); return 1; } /* This is generally good for coming to a halt. */ static int ai_brake(lua_State* L) { (void)L; /* Just a hack to avoid -W -Wall warnings. */ double diff, d; d = cur_pilot->solid->dir+M_PI; if(d >= 2*M_PI) d = fmodf(d, 2*M_PI); diff = angle_diff(d, VANGLE(cur_pilot->solid->vel)); pilot_turn = 10*diff; if(ABS(diff) < MAX_DIR_ERR && VMOD(cur_pilot->solid->vel) > MIN_VEL_ERR) pilot_acc = 1.; return 0; } /* Return the nearest friendly planet's position to the pilot. */ static int ai_getnearestplanet(lua_State* L) { if(cur_system->nplanets == 0) return 0; /* No planets. */ double dist, d; int i, j; /* Cycle through planets. */ for(dist = 0., j = -1, i = 0; i < cur_system->nplanets; i++) { d = vect_dist(&cur_system->planets[i].pos, &cur_pilot->solid->pos); if((!areEnemies(cur_pilot->faction, cur_system->planets[i].faction)) && (d < dist)) { /* Closer friendly planet. */ j = i; dist = d; } } /* No friendly planet found. */ if(j == -1) return 0; lua_pushlightuserdata(L, &cur_system->planets[j].pos); return 1; } /* Return a random planet's position to the pilot. */ static int ai_getrndplanet(lua_State* L) { Vec2 v; int p; if(cur_system->nplanets == 0) return 0; /* No planets. */ /* Get a random planet. */ p = RNG(0, cur_system->nplanets-1); /* Copy the data into a vector. */ vectcpy(&v, &cur_system->planets[p].pos); lua_pushlightuserdata(L, &v); return 1; } /* Return a random friendly planet's position to the pilot. */ static int ai_getlandplanet(lua_State* L) { Planet** planets; int nplanets, i; Vec2 v; planets = malloc(sizeof(Planet*) * cur_system->nplanets); if(cur_system->nplanets == 0) return 0; /* No planets. */ for(nplanets = 0, i = 0; i < cur_system->nplanets; i++) if(planet_hasService(&cur_system->planets[i], PLANET_SERVICE_BASIC) && !areEnemies(cur_pilot->faction, cur_system->planets[i].faction)) planets[nplanets++] = &cur_system->planets[i]; /* No planet to land on found. */ if(nplanets == 0) { free(planets); return 0; } /* We can actually get a random planet now. */ i = RNG(0,nplanets-1); vectcpy(&v, &planets[i]->pos); vect_cadd(&v, RNG(0, planets[i]->gfx_space->sw)-planets[i]->gfx_space->sw/2., RNG(0, planets[i]->gfx_space->sh)-planets[i]->gfx_space->sh/2.); lua_pushlightuserdata(L, &v); free(planets); return 1; } /* Attempt to enter the pilot hyperspace. Return the distance if too far away. */ static int ai_hyperspace(lua_State* L) { int dist; dist = space_hyperspace(cur_pilot); if(dist == 0.) return 0; lua_pushnumber(L,dist); return 1; } /* Completely stop the pilot if it is below minimum vel error. (No instant stops.) */ static int ai_stop(lua_State* L) { (void)L; /* Just avoid a gcc warning. */ if(VMOD(cur_pilot->solid->vel) < MIN_VEL_ERR) vect_pset(&cur_pilot->solid->vel, 0., 0.); return 0; } /* Aim at the pilot, trying to hit it. */ static int ai_aim(lua_State* L) { int id; double x, y; double t; Pilot* p; Vec2 tv; double dist, diff; double mod; LLUA_MIN_ARGS(1); /* Only acceptable parameter is pilot id. */ if(lua_isnumber(L,1)) id = lua_tonumber(L,1); else LLUA_INVALID_PARAMETER(); /* Get the pilot. */ p = pilot_get(id); if(p == NULL) { WARN("Pilot is invalid"); return 0; } /* Get the distance. */ dist = vect_dist(&cur_pilot->solid->pos, &p->solid->pos); /* Time for shots to reach distance. */ t = dist / cur_pilot->weap_speed; /* Position is calculated on where it should be. */ x = p->solid->pos.x + p->solid->vel.x*t - (cur_pilot->solid->pos.x + cur_pilot->solid->vel.x*t); y = p->solid->pos.y + p->solid->vel.y*t - (cur_pilot->solid->pos.y + cur_pilot->solid->vel.y*t); vect_cset(&tv, x, y); /* Calculate what we need to turn. */ mod = 10.; diff = angle_diff(cur_pilot->solid->dir, VANGLE(tv)); pilot_turn = mod * diff; /* Return distance to target (in grad). */ lua_pushnumber(L, ABS(diff*180./M_PI)); return 1; } /* Toggle combat flag. Default is on. */ static int ai_combat(lua_State* L) { int i; if(lua_isnumber(L, 1)) { i = (int)lua_tonumber(L,1); if(i == 1) pilot_setFlag(cur_pilot, PILOT_COMBAT); else if(i == 0) pilot_rmFlag(cur_pilot, PILOT_COMBAT); } else pilot_setFlag(cur_pilot, PILOT_COMBAT); return 0; } /* Set the pilots target. */ static int ai_settarget(lua_State* L) { LLUA_MIN_ARGS(1); if(lua_isnumber(L,1)) { pilot_target = (int)lua_tonumber(L,1); return 1; } LLUA_INVALID_PARAMETER(); } /* Set the secondary weapon. Biassed towards launchers.. */ static int ai_secondary(lua_State* L) { PilotOutfit* co, *po; int i; char* type; const char* otype; po = NULL; /* Search for a type. */ type = NULL; if(lua_isstring(L, 1)) type = (char*) lua_tostring(L, 1); /* Pilot has secondary selected - use that. */ if(cur_pilot->secondary != NULL) { co = cur_pilot->secondary; otype = outfit_getTypeBroad(co->outfit); /* If we aren't looking for a type or if it matches what we want. */ if((type == NULL) || (strcmp(otype, type) == 0)) po = co; } /* Need to get new secondary. */ if(po == NULL) { /* Iterate over the list. */ po = NULL; for(i = 0; i < cur_pilot->noutfits; i++) { co = &cur_pilot->outfits[i]; /* Not a secondary weapon. */ if(!outfit_isProp(co->outfit, OUTFIT_PROP_WEAP_SECONDARY) || outfit_isAmmo(co->outfit)) continue; /* Searching for type. */ if(type != NULL) { otype = outfit_getTypeBroad(co->outfit); if(strcmp(otype, type)==0) { po = co; break; } /* We'll grab the first weapon in case we don't find what we want. */ if((po == NULL) && (outfit_isWeapon(co->outfit) || outfit_isLauncher(co->outfit))) po = co; } /* Just grabbing best weapon. */ else { /* Grab first weapon or launcher it finds. */ if((po == NULL) && (outfit_isWeapon(co->outfit) || outfit_isLauncher(co->outfit))) po = co; /* Grab launcher over weapon by default. */ else if((po != NULL) && outfit_isWeapon(po->outfit) && outfit_isLauncher(co->outfit)) po = co; } } } if(po != NULL) { cur_pilot->secondary = po; pilot_setAmmo(cur_pilot); otype = outfit_getTypeBroad(po->outfit); lua_pushstring(L, otype); /* Set special flags. */ if((strcmp(otype, "Launcher")==0) && (po->outfit->type != OUTFIT_TYPE_MISSILE_DUMB)) { lua_pushstring(L, "Smart"); return 2; } return 1; } /* Nothing found. */ return 0; } static int ai_hasturrets(lua_State* L) { lua_pushboolean(L, pilot_isFlag(cur_pilot, PILOT_HASTURRET)); return 1; } /* Pew pew.. Says the pilot. */ static int ai_shoot(lua_State* L) { int n = 1; if(lua_isnumber(L, 1)) n = (int)lua_tonumber(L,1); if(n == 1) ai_setFlag(AI_PRIMARY); else if(n == 2) ai_setFlag(AI_SECONDARY); else if(n == 3) ai_setFlag(AI_PRIMARY | AI_SECONDARY); return 0; } /* Get the nearest enemy. */ static int ai_getenemy(lua_State* L) { unsigned int p; p = pilot_getNearestEnemy(cur_pilot); if(p == 0) /* No enemy found. */ return 0; lua_pushnumber(L,p); return 1; } /* Set the enemy hostile. (Simply notifies of an impending attack). */ static int ai_hostile(lua_State* L) { LLUA_MIN_ARGS(1); if(lua_isnumber(L,1) && ((unsigned int)lua_tonumber(L,1) == PLAYER_ID)) pilot_setFlag(cur_pilot, PILOT_HOSTILE); return 0; } /* Return the maximum range of weapons if parameter is 1, then do secondary. */ static int ai_getweaprange(lua_State* L) { double range; /* If 1 is passed as parameter, secondary weapon is checked. */ if(lua_isnumber(L, 1) && ((int)lua_tonumber(L, 1) == 1)) if(cur_pilot->secondary != NULL) { /* Get range, launchers use ammo's range. */ if(outfit_isLauncher(cur_pilot->secondary->outfit) && (cur_pilot->ammo != NULL)) range = outfit_range(cur_pilot->ammo->outfit); else range = outfit_range(cur_pilot->secondary->outfit); if(range < 0.) return 0; /* Secondary doesn't have range. */ /* Secondary does have range. */ lua_pushnumber(L, range); return 1; } lua_pushnumber(L, cur_pilot->weap_range); return 1; } /* Set the timer. */ static int ai_settimer(lua_State* L) { LLUA_MIN_ARGS(2); int n; /* Get the timer. */ if(lua_isnumber(L, 1)) n = lua_tonumber(L,1); cur_pilot->timer[n] = (lua_isnumber(L,2)) ? lua_tonumber(L,2) + SDL_GetTicks() : 0; return 0; } /* Check the timer. */ static int ai_timeup(lua_State* L) { LLUA_MIN_ARGS(1); int n; /* Get the timer. */ if(lua_isnumber(L,1)) n = lua_tonumber(L,1); lua_pushboolean(L, cur_pilot->timer[n] < SDL_GetTicks()); return 1; } /* Have the pilot say something to player. */ static int ai_comm(lua_State* L) { LLUA_MIN_ARGS(2); if(lua_isnumber(L,1) && (lua_tonumber(L,1)==PLAYER_ID) && lua_isstring(L,2)) player_message("Comm: %s> \"%s\"", cur_pilot->name, lua_tostring(L,2)); return 0; } /* Broadcasts to the entire area. */ static int ai_broadcast(lua_State* L) { LLUA_MIN_ARGS(1); if(lua_isstring(L, 1)) player_message("Broadcast: %s> \"%s\"", cur_pilot->name, lua_tostring(L,1)); return 0; } /* Set the pilots credits. */ static int ai_credits(lua_State* L) { LLUA_MIN_ARGS(1); if(ai_status != AI_STATUS_CREATE) return 0; if(lua_isnumber(L,1)) cur_pilot->credits = (int)lua_tonumber(L,1); return 0; } /* Set the pilots cargo. */ static int ai_cargo(lua_State* L) { LLUA_MIN_ARGS(2); if(ai_status != AI_STATUS_CREATE) return 0; if(lua_isstring(L,1) && lua_isnumber(L,2)) pilot_addCargo(cur_pilot, commodity_get(lua_tostring(L,1)), (int)lua_tonumber(L,2)); return 0; } /* Get the pilot's ship value. */ static int ai_shipprice(lua_State* L) { lua_pushnumber(L, cur_pilot->ship->price); return 1; }