439 lines
13 KiB
C
439 lines
13 KiB
C
// Woot, LUA!!!!!!
|
|
#include <lua.h>
|
|
#include <lauxlib.h>
|
|
#include <lualib.h>
|
|
|
|
#include <math.h>
|
|
|
|
#include "main.h"
|
|
#include "log.h"
|
|
#include "pilot.h"
|
|
#include "player.h"
|
|
#include "physics.h"
|
|
#include "pack.h"
|
|
#include "rng.h"
|
|
#include "ai.h"
|
|
|
|
// == 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).
|
|
// ============================================================
|
|
|
|
// Call the AI function with name f.
|
|
#define AI_LCALL(f) (lua_getglobal(L, f), lua_pcall(L, 0, 0, 0))
|
|
|
|
// Don't run the function if (n) params aren't passed.
|
|
#define MIN_ARGS(n) if(lua_gettop(L) < n) return 0
|
|
|
|
#define MAX_DIR_ERR 5.0*M_PI/180.
|
|
#define MIN_VEL_ERR 2.5
|
|
|
|
static int ai_minbrakedist(lua_State* L); // Minimal breaking distance.
|
|
static int ai_accel(lua_State* L); // Accelerate.
|
|
|
|
// Internal C routines.
|
|
static void ai_freetask(Task* t);
|
|
// 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_getdistance(lua_State* L); // Number getdist(Vec2)
|
|
static int ai_getpos(lua_State* L); // getpos(number/pilot)
|
|
static int ai_minbrakedist(lua_State* L); // Number minbrakedist()
|
|
// Boolean expressions.
|
|
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); // bool isenemy(pointer).
|
|
static int ai_isally(lua_State* L); // bool isally(pointer).
|
|
// 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_brake(lua_State* L); // Brake()
|
|
// Combat.
|
|
static int ai_shoot(lua_State* L); // shoot(number) number = 1,2,3.
|
|
static int ai_getenemy(lua_State* L); // pointer getenemy().
|
|
// Misc.
|
|
static int ai_createvect(lua_State* L); // createvect(number, number)
|
|
static int ai_say(lua_State* L); // say(string)
|
|
static int ai_rng(lua_State* L); // rng(number, number)
|
|
|
|
// Global Lua interpreter.
|
|
static lua_State* L = NULL;
|
|
|
|
// Current pilot "thinking" and assorted variables.
|
|
static Pilot* cur_pilot = NULL;
|
|
static double pilot_acc = 0.;
|
|
static double pilot_turn = 0.;
|
|
static int pilot_primary = 0;
|
|
|
|
// Destroy the AI part of the pilot.
|
|
void ai_destroy(Pilot* p) {
|
|
if(p->task)
|
|
ai_freetask(p->task);
|
|
}
|
|
|
|
// Init the AI stuff. Which is basically Lua.
|
|
int ai_init(void) {
|
|
L = luaL_newstate();
|
|
if(L == NULL) {
|
|
ERR("Unable to create a new Lua state");
|
|
return -1;
|
|
}
|
|
|
|
// Open the standard Lua libraries.
|
|
luaL_openlibs(L);
|
|
|
|
// Register C funstions in Lua.
|
|
// Tasks.
|
|
lua_register(L, "pushtask", ai_pushtask);
|
|
lua_register(L, "poptask", ai_poptask);
|
|
lua_register(L, "taskname", ai_taskname);
|
|
// Consult.
|
|
lua_register(L, "gettarget", ai_gettarget);
|
|
lua_register(L, "gettargetid", ai_gettargetid);
|
|
lua_register(L, "getdist", ai_getdistance);
|
|
lua_register(L, "getpos", ai_getpos);
|
|
lua_register(L, "minbrakedist", ai_minbrakedist);
|
|
// Boolean.
|
|
lua_register(L, "ismaxvel", ai_ismaxvel);
|
|
lua_register(L, "isstopped", ai_isstopped);
|
|
lua_register(L, "isenemy", ai_isenemy);
|
|
lua_register(L, "isally", ai_isally);
|
|
// Movement.
|
|
lua_register(L, "accel", ai_accel);
|
|
lua_register(L, "turn", ai_turn);
|
|
lua_register(L, "face", ai_face);
|
|
lua_register(L, "brake", ai_brake);
|
|
// Combat.
|
|
lua_register(L, "shoot", ai_shoot);
|
|
lua_register(L, "getenemy", ai_getenemy);
|
|
// Misc.
|
|
lua_register(L, "createvect", ai_createvect);
|
|
lua_register(L, "say", ai_say);
|
|
lua_register(L, "rng", ai_rng);
|
|
|
|
char* buf = pack_readfile(DATA, "../scripts/ai/test.lua", NULL);
|
|
|
|
if(luaL_dostring(L, buf) != 0) {
|
|
ERR("loading AI file: %s", "../scripts/ai/test.lua");
|
|
WARN("Most likely Lua file has improper syntax, please check it.");
|
|
return -1;
|
|
}
|
|
|
|
free(buf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Clean up global AI
|
|
void ai_exit(void) {
|
|
lua_close(L);
|
|
}
|
|
|
|
// Heart of hearts of the ai!! Brains of the pilot.
|
|
void ai_think(Pilot* pilot) {
|
|
cur_pilot = pilot; // Set current pilot being processed.
|
|
pilot_acc = pilot_turn = 0.; // Clean up some variables.
|
|
if(cur_pilot->task == NULL)
|
|
// Idle git!
|
|
AI_LCALL("control");
|
|
else
|
|
// Pilot has a currently running task.
|
|
AI_LCALL(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->ship->turn * pilot_turn;
|
|
vect_pset(&cur_pilot->solid->force, cur_pilot->ship->thrust * pilot_acc, cur_pilot->solid->dir);
|
|
|
|
if(pilot_primary) pilot_shoot(pilot, 0); // AMG, he's gunna shoot!
|
|
}
|
|
|
|
// =====================
|
|
// 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->target) free(t->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->target = NULL;
|
|
|
|
if(lua_gettop(L) > 2) {
|
|
if(lua_isnumber(L, 3)) {
|
|
t->dtype = TYPE_INT;
|
|
t->ID = (unsigned int) lua_tonumber(L, 3);
|
|
}
|
|
else if(lua_islightuserdata(L, 3)) {
|
|
t-dtype = TYPE_PTR;
|
|
t->target = (void*)lua_topointer(L, 3);
|
|
} 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;
|
|
cur_pilot->task = t->next;
|
|
t->next = NULL;
|
|
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_pushnil(L);
|
|
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->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->ID);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Get the distance from the pointer.
|
|
static int ai_getdistance(lua_State* L) {
|
|
MIN_ARGS(1);
|
|
Vec2* vect = (Vec2*)lua_topointer(L,1);
|
|
lua_pushnumber(L, 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.
|
|
else if(lua_islightuserdata(L, 1)) p = (Pilot*)lua_topointer(L, 1); // Pilot pointer.
|
|
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->ship->turn
|
|
// Add it to general euler equation x = v*t + 0.5 * a * t^2
|
|
// Have fun.
|
|
// ========================================================
|
|
static int ai_minbrakedist(lua_State* L) {
|
|
double time = VMOD(cur_pilot->solid->vel) /
|
|
(cur_pilot->ship->thrust / cur_pilot->solid->mass);
|
|
|
|
double dist = VMOD(cur_pilot->solid->vel) * (time + cur_pilot->ship->turn/360.) -
|
|
0.5 * (cur_pilot->ship->thrust / cur_pilot->solid->mass)*time*time;
|
|
|
|
lua_pushnumber(L, dist); // return
|
|
return 1;
|
|
}
|
|
|
|
// Are we at max velocity?
|
|
static int ai_ismaxvel(lua_State* L) {
|
|
lua_pushboolean(L, VMOD(cur_pilot->solid->vel) == cur_pilot->ship->speed);
|
|
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) {
|
|
return 1;
|
|
}
|
|
|
|
// Check if the pilot is an ally.
|
|
static int ai_isally(lua_State* L) {
|
|
return 1;
|
|
}
|
|
|
|
// Accelerate the pilot based on a param.
|
|
static int ai_accel(lua_State* L) {
|
|
pilot_acc = (lua_gettop(L) > 1 && lua_isnumber(L, 1)) ? ABS((double)lua_tonumber(L, 1)) : 1.;
|
|
return 0;
|
|
}
|
|
|
|
// Turn the pilot based on a param.
|
|
static int ai_turn(lua_State* L) {
|
|
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) {
|
|
MIN_ARGS(1);
|
|
Vec2* v; // Grab the position to face.
|
|
if(lua_isnumber(L,1)) v = &pilot_get((unsigned int)lua_tonumber(L,1))->solid->pos;
|
|
else if(lua_islightuserdata(L,1)) v = (Vec2*)lua_topointer(L,1);
|
|
|
|
double mod = -10;
|
|
if(lua_gettop(L) > 1 && lua_isnumber(L,2))
|
|
switch((int)lua_tonumber(L,2)) {
|
|
case 0: break;
|
|
case 1: mod *= -1; break;
|
|
case 2: break;
|
|
}
|
|
double diff = angle_diff(cur_pilot->solid->dir, 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 = angle_diff(cur_pilot->solid->dir, VANGLE(cur_pilot->solid->vel));
|
|
pilot_turn = 10*diff;
|
|
if(diff < MAX_DIR_ERR && VMOD(cur_pilot->solid->vel) > MIN_VEL_ERR)
|
|
pilot_acc = 1.;
|
|
return 0;
|
|
}
|
|
|
|
// 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) pilot_primary = 1;
|
|
//else if(n == 2) pilot_secondary = 1;
|
|
//else if(n == 3) pilot_primary = pilot_secondary = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Get the nearest enemy.
|
|
static int ai_getenemy(lua_State* L) {
|
|
return 0;
|
|
}
|
|
|
|
// Create a vector.
|
|
static int ai_createvect(lua_State* L) {
|
|
MIN_ARGS(2);
|
|
Vec2* v = MALLOC_L(Vec2);
|
|
double x = (lua_isnumber(L, 1)) ? (double)lua_tonumber(L,1) : 0.;
|
|
double y = (lua_isnumber(L, 2)) ? (double)lua_tonumber(L,2) : 0.;
|
|
|
|
vect_cset(v, x, y);
|
|
|
|
lua_pushlightuserdata(L, (void*)v);
|
|
return 1;
|
|
}
|
|
|
|
// Have the pilot say something to player.
|
|
static int ai_say(lua_State* L) {
|
|
MIN_ARGS(1);
|
|
|
|
if(lua_isstring(L, 1))
|
|
player_message("Comm %s> \"%s\"", cur_pilot->name, lua_tostring(L, 1));
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Return a number between low and high.
|
|
static int ai_rng(lua_State* L) {
|
|
MIN_ARGS(2);
|
|
|
|
int l, h;
|
|
|
|
if(lua_isnumber(L,1)) l = (int)lua_tonumber(L, 1);
|
|
if(lua_isnumber(L,1)) h = (int)lua_tonumber(L, 2);
|
|
|
|
lua_pushnumber(L, RNG(l,h));
|
|
return 1;
|
|
}
|
|
|