/** * @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 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 her. 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). * * Memory: * The AI currently has per-pilot memory which is accessible as "mem". This * memory is actually stored in the table pilotmem[cur_pilot->id]. This allows * the pilot to keep some memory always accessible between runs without having * to rely on the storage space a task has. * * @todo Clean up most of the code, it was written as one of the first * subsystems and is pretty lacking in quite a few aspects. Notably * removing the entire lightuserdata and actually go with full userdata. */ #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 "ldata.h" #include "rng.h" #include "space.h" #include "faction.h" #include "escort.h" #include "llua.h" #include "lluadef.h" #include "llua_space.h" #include "ai.h" /** * @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(const char* filename); static void ai_freetask(Task* t); static void ai_setMemory(void); static void ai_create(Pilot* pilot, char* param); /* External C routines. */ void ai_attacked(Pilot* attacked, const unsigned int attacker); /* weapon.c */ /* C routines made external. */ int ai_pinit(Pilot* p, char* ai); /* pilot.c */ void ai_destroy(Pilot* p); /* pilot.c */ void ai_think(Pilot* pilot); /* escort.c */ /* 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_getplayer(lua_State* L); /* number getPlayer() */ static int ai_gettarget(lua_State* L); /* Pointer gettarget() */ 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(). */ static int ai_shipclass(lua_State* L); /* string shipclass(number). */ static int ai_shipmass(lua_State* L); /* string shipmass(number) */ static int ai_isbribed(lua_State* L); /* bool isbribed(number). */ /* 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); /* Vec2 getnearestplanet() */ static int ai_getrndplanet(lua_State* L); /* Vec2 getrndplanet() */ static int ai_getlandplanet(lua_State* L); /* Vec2 getlandplanet() */ static int ai_hyperspace(lua_State* L); /* [number] hyperspace() */ static int ai_stop(lua_State* L); /* stop() */ static int ai_relvel(lua_State* L); /* relvel(number) */ /* Escorts. */ static int ai_e_attack(lua_State* L); /* bool e_attack() */ static int ai_e_hold(lua_State* L); /* bool e_hold() */ static int ai_e_clear(lua_State* L); /* bool e_clear() */ static int ai_e_return(lua_State* L); /* bool e_return() */ static int ai_dock(lua_State* L); /* dock(number) */ /* 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 }, /* Is. */ { "exists", ai_exists }, { "ismaxvel", ai_ismaxvel }, { "isstopped", ai_isstopped }, { "isenemy", ai_isenemy }, { "isally", ai_isally }, { "incombat", ai_incombat }, { "haslockon", ai_haslockon }, /* Get. */ { "getPlayer", ai_getplayer }, { "target", ai_gettarget }, { "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 }, { "shipclass", ai_shipclass }, { "shipmass", ai_shipmass }, { "isbribed", ai_isbribed }, /* Movement. */ { "nearestplanet", ai_getnearestplanet }, { "rndplanet", ai_getrndplanet }, { "landplanet", ai_getlandplanet }, { "accel", ai_accel }, { "turn", ai_turn }, { "face", ai_face }, { "brake", ai_brake }, { "stop", ai_stop }, { "hyperspace", ai_hyperspace }, { "relvel", ai_relvel }, /* Escorts. */ { "e_attack", ai_e_attack }, { "e_hold", ai_e_hold }, { "e_clear", ai_e_clear }, { "e_return", ai_e_return }, { "dock", ai_dock }, /* 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; /**< Current pilot. All functions use this. */ static double pilot_acc = 0.; /**< Current pilots acceleration. */ static double pilot_turn = 0.; /**< Current pilots turning. */ static int pilot_flags = 0; /**< Handle stuff like weapon firing. */ /* 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; /**< Current AI run statis. */ /** * @brief Set the cur_pilots ai. */ static void ai_setMemory(void) { lua_State* L; L = cur_pilot->ai->L; lua_getglobal(L, "pilotmem"); /* pilotmem */ lua_pushnumber(L, cur_pilot->id); /* pilotmem, id */ lua_gettable(L, -2); /* pilotmem, table */ lua_setglobal(L, "mem"); /* pilotmem */ lua_pop(L, 1); } /** * @brief Set the pilot for further AI calls. * @param p Pilot to set. */ void ai_setPilot(Pilot* p) { cur_pilot = p; ai_setMemory(); } /** * @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)); } /** * @brief Initializes the pilot in the ai. * * Mainly used to create the pilot's memory table. * @param p Pilot to initialize in AI. * @param ai AI to initialize pilot. */ int ai_pinit(Pilot* p, char* ai) { int i, n; AI_Profile* prof; lua_State* L; char buf[PATH_MAX], param[PATH_MAX]; /* Split parameter from ai itself. */ n = 0; for(i = 0; ai[i] != '\0'; i++) { /* Overflow protection. */ if(i > PATH_MAX) break; /* Check to see if we find the splitter. */ if(ai[i] == '*') { buf[i] = '\0'; n = i+1; continue; } if(n == 0) buf[i] = ai[i]; else param[i-n] = ai[i]; } if(n != 0) param[i-n] = '\0'; /* Terminate string if needed. */ else buf[i] = '\0'; prof = ai_getProfile(buf); if(prof == NULL) WARN("AI Profile '%s' not found.", buf); p->ai = prof; L = p->ai->L; /* Adds a new pilot memory in the memory table. */ lua_getglobal(L, "pilotmem"); lua_pushnumber(L, p->id); lua_newtable(L); lua_settable(L, -3); lua_pop(L, 1); /* Create the pilot. */ ai_create(p, (n != 0) ? param : NULL); return 0; } /** * @brief Destroys the ai part of the pilot. * * @param[in] p Pilot to destroy it's AI part. */ void ai_destroy(Pilot* p) { lua_State* L; L = p->ai->L; /* Get rid of pilot's memory. */ lua_getglobal(L, "pilotmem"); lua_pushnumber(L, p->id); lua_pushnil(L); lua_settable(L, -3); lua_pop(L, 1); /* Clean up tasks. */ if(p->task) ai_freetask(p->task); p->task = NULL; } /** * @brief Initializes the AI stuff which is basically Lua. * * @return 0 on no errors. */ int ai_init(void) { char** files; uint32_t nfiles, i; char path[PATH_MAX]; int flen, suflen; /* Get the file list. */ files = ldata_list(AI_PREFIX, &nfiles); /* Load the profiles. */ suflen = strlen(AI_SUFFIX); for(i = 0; i < nfiles; i++) { flen = strlen(files[i]); if((flen > suflen) && strncmp(&files[i][flen-suflen], AI_SUFFIX, suflen)==0) { snprintf(path, PATH_MAX, AI_PREFIX"%s", files[i]); if(ai_loadProfile(path)) /* Load the profile. */ WARN("Error loading AI profile '%s'", path); } /* Clean up. */ free(files[i]); } DEBUG("Loaded %d AI profile%c", nprofiles, (nprofiles==1)?' ':'s'); /* More clean up. */ free(files); return 0; } /** * @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(const 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); llua_load(L, luaopen_string); /* Open string. */ /* Constants. */ lua_regnumber(L, "player", PLAYER_ID); /* Player id. */ /* Register C funstions in Lua. */ luaL_register(L, "ai", ai_methods); lua_loadRnd(L); /* Metatables to register. */ lua_loadVector(L); /* Now load the file, since all the functions have been previously loaded. */ buf = ldata_read(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); /* Add the player memory. */ lua_newtable(L); lua_setglobal(L, "pilotmem"); return 0; } /* * @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; } /** * @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); } /** * @brief Heart of the AI, brains of the pilot. * * @param pilot Pilot that needs to think. */ void ai_think(Pilot* pilot) { lua_State* L; ai_setPilot(pilot); 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; cur_pilot->target = cur_pilot->id; /* Control function if pilot is idle or tick is up. */ if((cur_pilot->tcontrol < 0.) || (cur_pilot->task == NULL)) { ai_run(L, "control"); /* Run control. */ lua_getglobal(L, "control_rate"); cur_pilot->tcontrol = lua_tonumber(L, -1); lua_pop(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(cur_pilot, 0); /* Primary. */ if(ai_isFlag(AI_SECONDARY)) pilot_shoot(cur_pilot, 1); /* Secondary. */ } /** * @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; ai_setPilot(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)); } /** * @brief Run the create() function in the pilot. * * Should create all the gear and such the pilot has. * * @param pilot Pilot to "create". * @param param Parameter to pass to "create" function. */ static void ai_create(Pilot* pilot, char* param) { lua_State* L; ai_setPilot(pilot); L = cur_pilot->ai->L; /* Set creation mode. */ ai_status = AI_STATUS_CREATE; /* Prepare stack. */ lua_getglobal(L, "create"); /* Parse parameters. */ if(param != NULL) { /* Number. */ if(isdigit(param[0])) lua_pushnumber(L, atoi(param)); /* Special case player. */ else if(strcmp(param, "player")==0) lua_pushnumber(L, PLAYER_ID); /* Default. */ else lua_pushstring(L, param); } /* Run function. */ if(lua_pcall(L, (param!=NULL) ? 1 : 0, 0, 0)) /* Error has occured. */ WARN("Pilot '%s' ai -> '%s' : %s", cur_pilot->name, "create", lua_tostring(L, -1)); /* Recover normal mode. */ ai_status = AI_STATUS_NORMAL; } /* ===================== */ /* INTERNAL C FUNCTIONS. */ /* ===================== */ /** * @brief Free an AI task. * @param t Task to free. */ static void ai_freetask(Task* t) { if(t->next != NULL) { ai_freetask(t->next); /* Woot, recursive freeing! */ t->next = NULL; } if(t->name) free(t->name); free(t); } /** * @defgroup AI Lua AI Bindings * * @brief Handles how the AI interacts with the universe. * * Usage: * @code * ai.function(params) * @endcode * * @{ */ /** * @brief Pushes a task onto the pilots task list. * @luaparam pos Position to push into stack, 0 is front, 1 is back. * @luaparam func Function to call for task. * @luaparam data Data to pass to the function. Only lightuserdata or number * is currently supported. * * @luafunc pushtask(pos, func, data) * @param L Lua state. * @return Number of Lua parameters. */ static int ai_pushtask(lua_State* L) { LLUA_MIN_ARGS(2); int pos; char* func; Task* t, *pointer; /* Parse basic parameters. */ if(lua_isnumber(L, 1)) pos = (int) lua_tonumber(L, 1); else LLUA_INVALID_PARAMETER(); if(lua_isstring(L, 2)) func = (char*)lua_tostring(L, 2); else LLUA_INVALID_PARAMETER(); t = malloc(sizeof(Task)); t->next = NULL; t->name = strdup(func); t->dtype = TYPE_NULL; if(lua_gettop(L) > 2) { if(lua_isnumber(L, 3)) { t->dtype = TYPE_INT; t->dat.num = (unsigned int)lua_tonumber(L,3); } else LLUA_INVALID_PARAMETER(); } if(pos == 1) { /* Put at the end. */ for(pointer = cur_pilot->task; pointer->next != NULL; pointer = pointer->next); pointer->next = t; } else { /* Default put at the beginning. */ t->next = cur_pilot->task; cur_pilot->task = t; } return 0; } /** * @brief Pops the current running task. * @luafunc poptask() * @param L Lua state. * @return Number of Lua parameters. */ static int ai_poptask(lua_State* L) { (void)L; /* Just a hack to avoid -W -Wall warnings. */ Task* t = cur_pilot->task; /* Tasks must exist. */ if(t == NULL) { LLUA_DEBUG("Trying to pop task when there are no tasks in stack"); return 0; } cur_pilot->task = t->next; t->next = NULL; ai_freetask(t); return 0; } /** * @brief Get the current task's name. * @return The current task name or "none" if there are no tasks. * * @luafunc taskname() * @param L Lua state. * @return Number of Lua parameters. */ 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; } /** * @brief Get the player. * @return The players ship identifier. * * @luafunc getPlayer() * @param L Lua state. * @return Number of Lua parameters. */ static int ai_getplayer(lua_State* L) { lua_pushnumber(L, PLAYER_ID); return 1; } /** * @brief Get the pilots target. * @return The pilots target ship identifier or nil if no target. * * @luafunc target() * @param L Lua state. * @return Number of Lua parameters. */ static int ai_gettarget(lua_State* L) { switch(cur_pilot->task->dtype) { case TYPE_INT: lua_pushnumber(L, cur_pilot->task->dat.num); return 1; default: return 0; } } /** * @brief Get a random targets id. * @return Gets a random pilot in the system. * * @luafunc rndpilot() * @param L Lua state. * @return Number of Lua parameters. */ static int ai_getrndpilot(lua_State* L) { int p; p = RNG(0, pilot_nstack-1); /* Make sure it can't be the same pilot. */ if(pilot_stack[p]->id == cur_pilot->id) { p++; if(p >= pilot_nstack) p = 0; } /* Last check. */ if(pilot_stack[p]->id == cur_pilot->id) return 0; /* Actually found a pilot. */ lua_pushnumber(L, pilot_stack[p]->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* v; LuaVector* lv; Pilot* pilot; unsigned int n; LLUA_MIN_ARGS(1); /* Vector as a parameter. */ if(lua_isvector(L, 1)) { lv = lua_tovector(L, 1); v = &lv->vec; } else if(lua_islightuserdata(L,1)) v = lua_touserdata(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; } v = &pilot->solid->pos; } else /* Wrong parameter. */ LLUA_INVALID_PARAMETER(); lua_pushnumber(L, vect_dist(v, &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; } /* Get the pilots ship class. */ static int ai_shipclass(lua_State* L) { Pilot* p; if(lua_gettop(L) > 0) { if(lua_isnumber(L, 1)) p = pilot_get((unsigned int)lua_tonumber(L, 1)); else LLUA_INVALID_PARAMETER(); } else p = cur_pilot; if(p == NULL) { LLUA_DEBUG("Trying to get class of unexpectant ship!"); return 0; } lua_pushstring(L, ship_class(p->ship)); return 1; } /* Get the ships mass. */ static int ai_shipmass(lua_State* L) { Pilot* p; if(lua_gettop(L) > 0) { if(lua_isnumber(L, 1)) p = pilot_get((unsigned int)lua_tonumber(L,1)); else LLUA_INVALID_PARAMETER(); } else p = cur_pilot; if(p == NULL) { LLUA_DEBUG("Trying to get class of unexistant ship!"); return 0; } lua_pushnumber(L, p->solid->mass); return 1; } /* Check to see if target has bribed pilot. */ static int ai_isbribed(lua_State* L) { unsigned int target; if(lua_isnumber(L, 1)) target = (unsigned int) lua_tonumber(L, 1); else LLUA_INVALID_PARAMETER(); lua_pushboolean(L, (target == PLAYER_ID) && pilot_isFlag(cur_pilot, PILOT_BRIBED)); 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); LuaVector* lv; Vec2 sv, tv; /* Grab the position to face. */ Pilot* p; double mod, diff; int n; /* Get first parameter, aka what to face. */ n = -2; lv = NULL; 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)); } } else if(lua_isvector(L, 1)) lv = lua_tovector(L, 1); else LLUA_INVALID_PARAMETER(); mod = 10; /* Check if must invert. */ if(lua_gettop(L) > 1) { if(lua_isboolean(L, 2) && lua_toboolean(L, 2)) mod *= -1; } vect_cset(&sv, VX(cur_pilot->solid->pos), VY(cur_pilot->solid->pos)); if(lv == 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, &lv->vec)); /* Make pilot turn. */ pilot_turn = mod*diff; /* Return angle in degrees away from target. */ 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) { double dist, d; int i, j; LuaVector lv; if(cur_system->nplanets == 0) return 0; /* No planets. */ /* 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; vectcpy(&lv.vec, &cur_system->planets[j]->pos); lua_pushvector(L, lv); return 1; } /* Return a random planet's position to the pilot. */ static int ai_getrndplanet(lua_State* L) { LuaVector lv; 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(&lv.vec, &cur_system->planets[p]->pos); lua_pushvector(L, lv); return 1; } /* Return a random friendly planet's position to the pilot. */ static int ai_getlandplanet(lua_State* L) { Planet** planets; int nplanets, i; LuaVector lv; if(cur_system->nplanets == 0) return 0; /* No planets. */ /* Allocate memory. */ planets = malloc(sizeof(Planet*) * cur_system->nplanets); /* Copy friendly 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(&lv.vec, &planets[i]->pos); vect_cadd(&lv, 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_pushvector(L, lv); 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; pilot_shootStop(cur_pilot, 0); pilot_shootStop(cur_pilot, 1); dist = space_hyperspace(cur_pilot); if(dist == 0.) return 0; lua_pushnumber(L,dist); return 1; } /* Get the relative velocity of a pilot. */ static int ai_relvel(lua_State* L) { unsigned int id; double dot, mod; Pilot* p; Vec2 vv, pv; LLUA_MIN_ARGS(1); if(lua_isnumber(L, 1)) id = (unsigned int)lua_tonumber(L, 1); else LLUA_INVALID_PARAMETER(); p = pilot_get(id); if(p == NULL) { LLUA_DEBUG("Invalid pilot identifier."); return 0; } /* Get te projection of target on current velocity. */ vect_cset(&vv, p->solid->vel.x - cur_pilot->solid->vel.x, p->solid->vel.y - cur_pilot->solid->vel.y); vect_cset(&pv, p->solid->pos.x - cur_pilot->solid->pos.x, p->solid->pos.y - cur_pilot->solid->pos.y); dot = vect_dot(&pv, &vv); mod = MAX(VMOD(pv), 1.); /* Avoid /0. */ lua_pushnumber(L, dot / mod); 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; } /* Tell the pilots escort to attack its target. */ static int ai_e_attack(lua_State* L) { int ret; ret = escorts_attack(cur_pilot); lua_pushboolean(L, !ret); return 1; } /* Tell the pilots escort to hold position. */ static int ai_e_hold(lua_State* L) { int ret; ret = escorts_hold(cur_pilot); lua_pushboolean(L, !ret); return 1; } /* Tell the pilots escort to clear orders. */ static int ai_e_clear(lua_State* L) { int ret; ret = escorts_clear(cur_pilot); lua_pushboolean(L, !ret); return 1; } /* Tells the pilots escorts to return to dock. */ static int ai_e_return(lua_State* L) { int ret; ret = escorts_return(cur_pilot); lua_pushboolean(L, !ret); return 1; } /* Docks the ship. */ static int ai_dock(lua_State* L) { Pilot* p; /* Target is another ship. */ if(lua_isnumber(L, 1)) { p = pilot_get(lua_tonumber(L, 1)); if(p == NULL) return 0; pilot_dock(cur_pilot, p); } else LLUA_INVALID_PARAMETER(); 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; double speed; 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); /* Check if we should recalculate weapon speed with secondary weapon. */ if((cur_pilot->secondary != NULL) && outfit_isBolt(cur_pilot->secondary->outfit) && (cur_pilot->secondary->outfit->type == OUTFIT_TYPE_MISSILE_DUMB)) { speed = cur_pilot->weap_speed + outfit_speed(cur_pilot->secondary->outfit); speed /= 2.; } else speed = cur_pilot->weap_speed; /* Time for shots to reach distance. */ t = dist / 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)) { cur_pilot->target = (int)lua_tonumber(L,1); return 1; } LLUA_INVALID_PARAMETER(); } /** * @brief Check to see if an outfit is a melee weapon. * @param p Pilot to check for. * @param o Outfit to check. */ static int outfit_isMelee(Pilot* p, PilotOutfit* o) { (void)p; if(outfit_isBolt(o->outfit) || outfit_isBeam(o->outfit) || (o->outfit->type == OUTFIT_TYPE_MISSILE_DUMB)) return 1; return 0; } /** * @brief Check to see if an outfit is a ranged weapon. * @param p Pilot to check for. * @param o Outfit to check. */ static int outfit_isRanged(Pilot* p, PilotOutfit* o) { if(outfit_isFighterBay(o->outfit) || (outfit_isLauncher(o->outfit) && (o->outfit->type != OUTFIT_TYPE_MISSILE_DUMB))) { /* Must have ammo. */ if(pilot_getAmmo(p, o->outfit) <= 0) return 0; return 1; } return 0; } /* Set the secondary weapon. Biassed towards launchers.. */ static int ai_secondary(lua_State* L) { PilotOutfit* co, *po; int i, melee; char* str; const char* otype; po = NULL; /* Parse the parameters. */ if(lua_isstring(L, 1)) { str = (char*)lua_tostring(L, 1); if(strcmp(str, "melee")==0) melee = 1; else if(strcmp(str, "ranged")==0) melee = 0; else LLUA_INVALID_PARAMETER(); } else LLUA_INVALID_PARAMETER(); /* Pilot has secondary selected - use that. */ if(cur_pilot->secondary != NULL) { co = cur_pilot->secondary; if(melee && outfit_isMelee(cur_pilot, co)) po = co; else if(!melee && outfit_isRanged(cur_pilot, co)) 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; /* Get the first match. */ if(melee && outfit_isMelee(cur_pilot, co)) { po = co; break; } else if(!melee && outfit_isRanged(cur_pilot, co)) { po = co; break; } } } if(po != NULL) { cur_pilot->secondary = po; pilot_setAmmo(cur_pilot); otype = outfit_getTypeBroad(po->outfit); lua_pushstring(L, otype); /* Set special flags. */ if(outfit_isLauncher(po->outfit)) { if((po->outfit->type != OUTFIT_TYPE_MISSILE_DUMB)) lua_pushstring(L, "Smart"); else lua_pushstring(L, "Dumb"); if(cur_pilot->ammo == NULL) lua_pushnumber(L, 0.); else lua_pushnumber(L, cur_pilot->ammo->quantity); return 3; } 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.) { lua_pushnumber(L, 0.); /* Secondary doesn't have range. */ return 1; } /* 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)/1000. : 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] < 0.); 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; } /** * @} */