1431 lines
39 KiB
C
1431 lines
39 KiB
C
/**
|
|
* @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 <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "lauxlib.h"
|
|
#include "lualib.h"
|
|
|
|
#include <math.h>
|
|
|
|
#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 "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(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_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(). */
|
|
static int ai_shipclass(lua_State* L); /* string shipclass(). */
|
|
/* 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() */
|
|
/* 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 },
|
|
{ "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 },
|
|
{ "shipclass", ai_shipclass },
|
|
/* 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 },
|
|
/* 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. */
|
|
|
|
/**
|
|
* @fn static void ai_setMemory(void)
|
|
*
|
|
* @brief Set the cur_pilots ai.
|
|
*/
|
|
static void ai_setMemory(void) {
|
|
lua_State* L;
|
|
L = cur_pilot->ai->L;
|
|
|
|
lua_getglobal(L, "pilotmem");
|
|
lua_pushnumber(L, cur_pilot->id);
|
|
lua_gettable(L, -2);
|
|
lua_setglobal(L, "mem");
|
|
}
|
|
|
|
/**
|
|
* @fn void ai_setPilot(Pilot* p)
|
|
*
|
|
* @brief Set the pilot for further AI calls.
|
|
*/
|
|
void ai_setPilot(Pilot* p) {
|
|
cur_pilot = p;
|
|
ai_setMemory();
|
|
}
|
|
|
|
/**
|
|
* @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 int ai_pinit(Pilot* p, char* ai)
|
|
*
|
|
* @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);
|
|
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);
|
|
|
|
/* Create the pilot. */
|
|
ai_create(p, (n != 0) ? param : NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @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) {
|
|
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);
|
|
|
|
/* Clean up tasks. */
|
|
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);
|
|
|
|
/* Metatables to register. */
|
|
lua_loadVector(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);
|
|
|
|
/* Add the player memory. */
|
|
lua_newtable(L);
|
|
lua_setglobal(L, "pilotmem");
|
|
|
|
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;
|
|
|
|
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 < 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, cur_pilot->target, 0); /* Primary. */
|
|
if(ai_isFlag(AI_SECONDARY)) pilot_shoot(pilot, cur_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;
|
|
|
|
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));
|
|
}
|
|
|
|
/**
|
|
* @fn static void ai_create(Pilot* pilot, char* param)
|
|
*
|
|
* @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. */
|
|
/* ===================== */
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/**
|
|
* @group AI Lua AI Bindings
|
|
*
|
|
* @brief Handles how the AI interacts with the universe.
|
|
*
|
|
* Usage:
|
|
* @code
|
|
* ai.function(params)
|
|
* @endcode
|
|
*
|
|
* @{
|
|
*/
|
|
|
|
/**
|
|
* @fn static int ai_pushtask(lua_State* L)
|
|
*
|
|
* @brief pushtask(number pos, string func [, data])
|
|
* @param pos Position to push into stack, 0 is front, 1 is back.
|
|
* @param func Function to call for task.
|
|
* @param data Data to pass to the function. Only lightuserdata or number
|
|
* is currently supported.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* @fn static int ai_poptask(lua_State* L)
|
|
*
|
|
* @brief poptask(nil)
|
|
*
|
|
* Pops the current running 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;
|
|
}
|
|
|
|
static int ai_getplayer(lua_State* L) {
|
|
lua_pushnumber(L, PLAYER_ID);
|
|
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* 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;
|
|
}
|
|
|
|
static int ai_shipclass(lua_State* L) {
|
|
lua_pushstring(L, ship_class(cur_pilot->ship));
|
|
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;
|
|
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));
|
|
lv = NULL;
|
|
}
|
|
}
|
|
else if(lua_isvector(L, 1))
|
|
lv = lua_tovector(L, 1);
|
|
|
|
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;
|
|
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(&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;
|
|
}
|
|
|
|
/* 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)) {
|
|
cur_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;
|
|
|
|
|
|
/* Must have ammo. */
|
|
if(outfit_isLauncher(co->outfit) && pilot_getAmmo(cur_pilot, co->outfit)==0)
|
|
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_isBolt(co->outfit) ||
|
|
outfit_isBeam(co->outfit) ||
|
|
outfit_isLauncher(co->outfit)))
|
|
po = co;
|
|
|
|
/* Grab launcher over weapon by default. */
|
|
else if((po != NULL) && (outfit_isBolt(po->outfit) ||
|
|
outfit_isBeam(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(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) + 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;
|
|
}
|
|
/**
|
|
* @}
|
|
*/
|
|
|