diff --git a/dat/SHIP b/dat/SHIP new file mode 100644 index 0000000..5e84f0c --- /dev/null +++ b/dat/SHIP @@ -0,0 +1,14 @@ +// ================ +// NOTES. +// ================ + +Units: + Thrust, speed is in pixels/second. + turn is in degrees per half a second. + + energy, armor and shield regen are in points a minute. + + crew is in, uh.. people. + mass is in tons. + cargo and weapon capacity are int tons. + diff --git a/dat/fleet.xml b/dat/fleet.xml index a95ab57..7a64d5d 100644 --- a/dat/fleet.xml +++ b/dat/fleet.xml @@ -3,7 +3,7 @@ <fleet name="Test"> <faction>2</faction> <pilots> - <pilot chance='100'>Mr. Test</pilot> + <pilot chance='100'>Miss. Test</pilot> </pilots> </fleet> <fleet name="Merchant Ship"> diff --git a/bin/ai_test.lua b/scripts/ai/test.lua similarity index 100% rename from bin/ai_test.lua rename to scripts/ai/test.lua diff --git a/src/ai.c b/src/ai.c index 944b73a..1b049e1 100644 --- a/src/ai.c +++ b/src/ai.c @@ -3,6 +3,8 @@ #include <lauxlib.h> #include <lualib.h> +#include <math.h> + #include "def.h" #include "log.h" #include "pilot.h" @@ -25,19 +27,31 @@ // Call the AI function with name f. #define AI_LCALL(f) (lua_getglobal(L, f), lua_call(L, 0, 0)) +// Don't run the function if (n) params aren't passed. +#define MIN_ARGS(n) if(lua_gettop(L) < n) return 0 + static int ai_minbrakedist(lua_State* L); // Minimal breaking distance. static int ai_accel(lua_State* L); // Accelerate. -// Basic task. -// name : Tasks name (Lua function.) -// target : Target, this will depend on the task itself. -typedef struct { - char* name; - union { - void* target; - unsigned int ID; - }; -} Task; +// 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() +// 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) +// Misc. +static int ai_createvect(lua_State* L); // createvect(number, number) // Global Lua interpreter. static lua_State* L = NULL; @@ -52,11 +66,24 @@ int ai_init(void) { if(L == NULL) return -1; - // Register C funstions in Lua. - lua_register(L, "minbrakedist", ai_minbrakedist); - lua_register(L, "accel", ai_accel); + // Open the standard Lua libraries. + luaL_openlibs(L); - if(luaL_dofile(L, "ai_test.lua") != 0) { + // Register C funstions in Lua. + lua_register(L, "pushtask", ai_pushtask); + lua_register(L, "poptask", ai_poptask); + lua_register(L, "taskname", ai_taskname); + lua_register(L, "gettarget", ai_gettarget); + lua_register(L, "gettargetid", ai_gettargetid); + lua_register(L, "getdistance", ai_getdistance); + lua_register(L, "getpos", ai_getpos); + lua_register(L, "minbrakedist", ai_minbrakedist); + lua_register(L, "accel", ai_accel); + lua_register(L, "turn", ai_turn); + lua_register(L, "face", ai_face); + lua_register(L, "createvect", ai_createvect); + + if(luaL_dofile(L, "../scripts/ai/test.lua") != 0) { WARN("Unable to load AI file: %s", "ai_test.lua"); return -1; } @@ -72,41 +99,184 @@ void ai_exit(void) { void ai_think(Pilot* pilot) { cur_pilot = pilot; // Set current pilot being processed. pilot_acc = pilot_turn = 0.; // Clean up some variables. - if(pilot->action == NULL) { + 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) + 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); } +// ===================== +// 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; + + if(lua_gettop(L) > 2) { + if(lua_isnumber(L, 3)) + t->ID = (unsigned int) lua_tonumber(L, 3); + else if(lua_islightuserdata(L, 3)) + t->target = (void*)lua_topointer(L, 3); + } + + 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) { + 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 targer pointer. +static int ai_gettarget(lua_State* L) { + lua_pushlightuserdata(L, cur_pilot->task->target); + return 1; +} + +// Get the ID. +static int ai_gettargetid(lua_State* L) { + lua_pushnumber(L, cur_pilot->task->ID); + return 1; +} + +// 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, MOD(vect->x-cur_pilot->solid->pos.x, vect->y-cur_pilot->solid->pos.y)); + return 1; +} + +// Get the pilots position. +static int ai_getpos(lua_State* L) { + Pilot* p; + if(lua_isnumber(L, 1)) p = get_pilot((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 ==> v*t = 0.5 a * t^2 => t = 2*v / a +// 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 = 2. * VMOD(cur_pilot->solid->vel) / + double time = VMOD(cur_pilot->solid->vel) / (cur_pilot->ship->thrust / cur_pilot->solid->mass); - double dist = VMOD(cur_pilot->solid->vel) * (time + 0.5 * (180. * 360. / cur_pilot->ship->turn)) - + 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; } +// Accelerate the pilot based on a param. static int ai_accel(lua_State* L) { - pilot_acc = (lua_isnumber(L, 1)) ? (double)lua_tonumber(L, 1) : 1.; + MIN_ARGS(1); + pilot_acc = (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 = &get_pilot((unsigned int)lua_tonumber(L,1))->solid->pos; + else if(lua_islightuserdata(L,1)) v = (Vec2*)lua_topointer(L,1); + + double mod; + if(lua_gettop(L) > 1 && lua_isnumber(L,2)) + switch((int)lua_tonumber(L,2)) { + case 1: mod *= -1; break; + case 2: break; + } + + pilot_turn = mod * angle_diff(cur_pilot->solid->dir, vect_angle(&cur_pilot->solid->pos, v)); + + 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; +} + diff --git a/src/ai.h b/src/ai.h index a5f3029..4bcb1c1 100644 --- a/src/ai.h +++ b/src/ai.h @@ -1,5 +1,16 @@ #pragma once +struct Task { + struct Task* next; + char* name; + + union { + void* target; // Vec2 etc. + unsigned int ID; // Pilot ID etc. + }; +}; +typedef struct Task Task; + int ai_init(void); void ai_exit(void); diff --git a/src/def.h b/src/def.h index d027c8b..afd8def 100644 --- a/src/def.h +++ b/src/def.h @@ -4,7 +4,6 @@ #define CALLOC_L(type)(calloc(1, sizeof(type))) #define ABS(X) ((X<0)?-X:X) -#define FABS(X) ((X<0.)?-X:X) #define DATA "data" diff --git a/src/physics.c b/src/physics.c index f0a0fa8..2318a2b 100644 --- a/src/physics.c +++ b/src/physics.c @@ -4,12 +4,21 @@ #include "physics.h" -#ifndef M_PI -#define M_PI 3.14159265358979323846f -#endif +// ================ +// MISC +// ================ +double angle_diff(const double ref, double a) { + if(a < M_PI) a += 2*M_PI; + double d = fmod((a-ref), 2*M_PI); + return (d <= M_PI) ? d : d - 2*M_PI; +} + +// ================ +// VEC2 +// ================ // Set the vector value using cartesian coords. -void vect_cset(Vec2* v, double x, double y) { +void vect_cset(Vec2* v, const double x, const double y) { v->x = x; v->y = y; v->mod = MOD(x,y); @@ -17,7 +26,7 @@ void vect_cset(Vec2* v, double x, double y) { } // Set the vector value using polar coords. -void vect_pset(Vec2* v, double mod, double angle) { +void vect_pset(Vec2* v, const double mod, const double angle) { v->mod = mod; v->angle = angle; v->x = v->mod*cos(v->angle); @@ -37,6 +46,16 @@ void vectnull(Vec2* v) { v->x = v->y = v->mod = v->angle = 0.; } +// Get the direction pointed to by two vectors (from ref to v). +double vect_angle(const Vec2* ref, const Vec2* v) { + return ANGLE(VX(*v)-VX(*ref), VY(*v)-VY(*ref)); +} + + +// ================ +// SOLID! +// ================ + // ==Update method.======================================== // d^2 x(t) / d t^2 = a, a = constant (acceleration) // x'(0) = v, x(0) = p @@ -50,7 +69,7 @@ void vectnull(Vec2* v) { #if 0 // Simply commenting this out to avoid silly warnings. static void simple_update(Solid* obj, const double dt) { // Make sure angle doesn't flip. - obj->dir += obj->dir_vel/360.*dt; + obj->dir += M_PI/360.*obj->dir_vel*dt; if(obj->dir > 2*M_PI) obj->dir -= 2*M_PI; if(obj->dir < 0.) obj->dir += 2*M_PI; @@ -100,7 +119,7 @@ static void simple_update(Solid* obj, const double dt) { #define RK4_MIN_H 0.01 // Minimal pass we want. static void rk4_update(Solid* obj, const double dt) { // Make sure angle doesn't flip. - obj->dir += obj->dir_vel/360.0*dt; + obj->dir += M_PI/360.*obj->dir_vel*dt; if(obj->dir > 2*M_PI) obj->dir -= 2*M_PI; if(obj->dir < 0.0) obj->dir += 2*M_PI; @@ -123,20 +142,20 @@ static void rk4_update(Solid* obj, const double dt) { for(i = 0; i < N; i++) { // X component. tx = ix = vx; - tx += 2*ix + h*tx; - tx += 2*ix + h*tx; + tx += 2.*ix + h*tx; + tx += 2.*ix + h*tx; tx += ix + h*tx; - tx *= h/6; + tx *= h/6.; px += tx; vx += ax*h; // Y component. ty = iy = vy; - ty += 2*(iy + h/2*ty); - ty += 2*(iy + h/2*ty); + ty += 2.*(iy + h/2.*ty); + ty += 2.*(iy + h/2.*ty); ty += iy +h*ty; - ty *= h/6; + ty *= h/6.; py += ty; vy += ay*h; @@ -153,8 +172,8 @@ static void rk4_update(Solid* obj, const double dt) { void solid_init(Solid* dest, const double mass, const Vec2* vel, const Vec2* pos) { dest->mass = mass; - dest->force.mod = 0; - dest->dir = 0; + vect_cset(&dest->force, 0., 0.); + dest->dir = 0.; if(vel == NULL) vectnull(&dest->vel); else vectcpy(&dest->vel, vel); diff --git a/src/physics.h b/src/physics.h index a939652..e47ead4 100644 --- a/src/physics.h +++ b/src/physics.h @@ -6,8 +6,11 @@ #define VMOD(v) ((v).mod) #define VANGLE(v) ((v).angle) -#define MOD(x,y) (sqrt(x*x + y*y)) -#define ANGLE(x,y)((x==0.) ? 0. : ((x<0.)?atan(y/x)+M_PI:atan(y/x))) +#define MOD(x,y) (sqrt((x)*(x) + (y)*(y))) +#define ANGLE(x,y)(((x)==0.) ? 0. : (((x)<0.)?atan((y)/(x))+M_PI:atan((y)/(x)))) + +// Misc +double angle_diff(const double ref, double a); // Base of 2D vectors. typedef struct { @@ -16,10 +19,11 @@ typedef struct { } Vec2; // Vector manupulation. -void vect_cset(Vec2* v, double x, double y); -void vect_pset(Vec2* v, double mod, double angle); +void vect_cset(Vec2* v, const double x, const double y); +void vect_pset(Vec2* v, const double mod, const double angle); void vectcpy(Vec2* dest, const Vec2* src); void vectnull(Vec2* v); +double vect_angle(const Vec2* ref, const Vec2* v); // Describe any solid in 2D space. struct Solid { diff --git a/src/pilot.c b/src/pilot.c index d7077b0..7256c4a 100644 --- a/src/pilot.c +++ b/src/pilot.c @@ -83,7 +83,7 @@ void pilot_init(Pilot* pilot, Ship* ship, char* name, const Vec2* vel, const Vec pilot->energy = ship->energy; // Initially idle. - pilot->action = NULL; + pilot->task = NULL; if(flags & PILOT_PLAYER) { pilot->think = (void*)player_think; // Players don't need to thing! :P diff --git a/src/pilot.h b/src/pilot.h index f8d3cb4..16f75d7 100644 --- a/src/pilot.h +++ b/src/pilot.h @@ -1,25 +1,11 @@ #pragma once #include "def.h" #include "physics.h" +#include "ai.h" #include "ship.h" #define PILOT_PLAYER 1 // Pilot is a player. -// ========================================================= -// AI: -// Ai is based on an action list which contains the current -// action (FIFO). Actions will run the appropriate Lua code. -// ========================================================= -typedef enum { ACT_ATTACK, ACT_TRAVEL, ACT_BRAKE } action_type; - -// Actions. -struct Action { - struct Action* next; - action_type type; - void* target; -}; -typedef struct Action Action; - // Primary pilot structure. struct Pilot { unsigned int id; // Pilots id. @@ -38,7 +24,7 @@ struct Pilot { // AI. void (*think)(struct Pilot*); // Ai thinking for the pilot. - Action* action; // Current action. + Task* task; // Current action. }; typedef struct Pilot Pilot;