Lephisto/src/misn_lua.c

786 lines
18 KiB
C

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "lua.h"
#include "lauxlib.h"
#include "llua.h"
#include "lluadef.h"
#include "hook.h"
#include "mission.h"
#include "log.h"
#include "lephisto.h"
#include "rng.h"
#include "space.h"
#include "toolkit.h"
#include "land.h"
#include "pilot.h"
#include "player.h"
#include "ltime.h"
#include "xml.h"
#include "misn_lua.h"
// Similar to lua vars, but with less variety.
#define MISN_VAR_NIL 0
#define MISN_VAR_NUM 1
#define MISN_VAR_BOOL 2
#define MISN_VAR_STR 3
typedef struct misn_var_ {
char* name;
char type;
union {
double num;
char* str;
int b;
} d;
} misn_var;
// Variable stack.
static misn_var* var_stack = NULL;
static int var_nstack = 0;
static int var_mstack = 0;
// Current mission.
static Mission* cur_mission = NULL;
static int misn_delete = 0; // If 1 delete current mission.
// Static.
static int var_add(misn_var* var);
static void var_free(misn_var* var);
static unsigned int hook_generic(lua_State* L, char* stack);
// Extern.
int misn_run(Mission* misn, char* func);
int var_save(xmlTextWriterPtr writer);
int var_load(xmlNodePtr parent);
// -- Libraries. --
// Mission.
static int misn_setTitle(lua_State* L);
static int misn_setDesc(lua_State* L);
static int misn_setReward(lua_State* L);
static int misn_factions(lua_State* L);
static int misn_accept(lua_State* L);
static int misn_finish(lua_State* L);
static const luaL_reg misn_methods[] = {
{ "setTitle", misn_setTitle },
{ "setDesc", misn_setDesc },
{ "setReward", misn_setReward },
{ "factions", misn_factions },
{ "accept", misn_accept },
{ "finish", misn_finish },
{ 0, 0 }
};
// Var.
static int var_peek(lua_State* L);
static int var_pop(lua_State* L);
static int var_push(lua_State* L);
static const luaL_reg var_methods[] = {
{ "peek", var_peek },
{ "pop", var_pop},
{ "push", var_push},
{0, 0}
};
// Only conditional.
static const luaL_reg var_cond_methods[] = {
{ "peek", var_peek },
{0, 0 }
};
// Player.
static int player_getname(lua_State* L);
static int player_shipname(lua_State* L);
static int player_freeSpace(lua_State* L);
static int player_addCargo(lua_State* L);
static int player_rmCargo(lua_State* L);
static int player_pay(lua_State* L);
static int player_msg(lua_State* L);
static int player_modFaction(lua_State* L);
static int player_getFaction(lua_State* L);
static const luaL_reg player_methods[] = {
{ "name", player_getname },
{ "ship", player_shipname },
{ "freeCargo", player_freeSpace },
{ "addCargo", player_addCargo },
{ "rmCargo", player_rmCargo },
{ "pay", player_pay },
{ "msg", player_msg },
{ "modFaction", player_modFaction },
{ "getFaction", player_getFaction },
{ 0, 0 }
};
// Hooks.
static int hook_land(lua_State* L);
static int hook_takeoff(lua_State* L);
static int hook_time(lua_State* L);
static int hook_enter(lua_State* L);
static int hook_pilotDeath(lua_State* L);
static const luaL_reg hook_methods[] = {
{ "land", hook_land },
{ "takeoff", hook_takeoff },
{ "time", hook_time },
{ "enter", hook_enter },
{ "pilotDeath", hook_pilotDeath },
{ 0, 0 }
};
// Pilots.
static int pilot_addFleet(lua_State* L);
static int pilot_rename(lua_State* L);
static const luaL_reg pilot_methods[] = {
{ "add", pilot_addFleet },
{ "rename", pilot_rename },
{ 0, 0 }
};
// Register all the libaries.
int misn_loadLibs(lua_State* L) {
lua_loadLephisto(L);
lua_loadMisn(L);
lua_loadVar(L, 0);
lua_loadSpace(L, 0);
lua_loadTime(L, 0);
lua_loadPlayer(L);
lua_loadRnd(L);
lua_loadTk(L);
lua_loadHook(L);
lua_loadPilot(L);
return 0;
}
int misn_loadCondLibs(lua_State* L) {
lua_loadTime(L, 1);
lua_loadVar(L, 1);
return 0;
}
// Individual libarary loading.
int lua_loadMisn(lua_State* L) {
luaL_register(L, "misn", misn_methods);
return 0;
}
int lua_loadVar(lua_State* L, int readonly) {
if(readonly == 0)
luaL_register(L, "var", var_methods);
else
luaL_register(L, "var", var_cond_methods);
return 0;
}
int lua_loadPlayer(lua_State* L) {
luaL_register(L, "player", player_methods);
return 0;
}
int lua_loadHook(lua_State* L) {
luaL_register(L, "hook", hook_methods);
return 0;
}
int lua_loadPilot(lua_State* L) {
luaL_register(L, "pilot", pilot_methods);
return 0;
}
// Run a mission function.
//
// -1 on error, 1 on misn.finish() call and 0 normally.
int misn_run(Mission* misn, char* func) {
int i, ret;
char* err;
cur_mission = misn;
misn_delete = 0;
lua_getglobal(misn->L, func);
if((ret = lua_pcall(misn->L, 0, 0, 0))) {
// Did an oops.
err = (lua_isstring(misn->L, -1)) ? (char*) lua_tostring(misn->L, -1) : NULL;
if(strcmp(err, "Mission Done"))
WARN("Mission '%s' -> '%s' : %s",
cur_mission->data->name, func, (err) ? err : "Unknown Error");
else ret = 1;
}
// Mission is finished.
if(misn_delete) {
mission_cleanup(cur_mission);
for(i = 0; i < MISSION_MAX; i++)
if(cur_mission == &player_missions[i]) {
memmove(&player_missions[i], &player_missions[i+1],
sizeof(Mission) * (MISSION_MAX-i-1));
break;
}
}
cur_mission = NULL;
return ret;
}
// Save the mission variables.
int var_save(xmlTextWriterPtr writer) {
int i;
xmlw_startElem(writer, "vars");
for(i = 0; i < var_nstack; i++) {
xmlw_startElem(writer, "vars");
xmlw_attr(writer, "name", var_stack[i].name);
switch(var_stack[i].type) {
case MISN_VAR_NIL:
xmlw_attr(writer, "type", "nil");
break;
case MISN_VAR_NUM:
xmlw_attr(writer, "type", "num");
xmlw_str(writer, "%d", var_stack[i].d.num);
break;
case MISN_VAR_BOOL:
xmlw_attr(writer, "type", "bool");
xmlw_str(writer, "%d", var_stack[i].d.b);
break;
case MISN_VAR_STR:
xmlw_attr(writer, "type", "str");
xmlw_str(writer, var_stack[i].d.str);
break;
}
xmlw_endElem(writer); // var.
}
xmlw_endElem(writer); // vars.
return 0;
}
// Load the vars.
int var_load(xmlNodePtr parent) {
char* str;
xmlNodePtr node, cur;
misn_var var;
var_cleanup();
node = parent->xmlChildrenNode;
do {
if(xml_isNode(node, "vars")) {
cur = node->xmlChildrenNode;
do {
if(xml_isNode(cur, "var")) {
xmlr_attr(cur, "name", var.name);
xmlr_attr(cur, "type", str);
if(strcmp(str, "nil")==0)
var.type = MISN_VAR_NIL;
else if(strcmp(str, "num")==0) {
var.type = MISN_VAR_NUM;
var.d.num = atoi(xml_get(cur));
}
else if(strcmp(str, "bool")) {
var.type = MISN_VAR_BOOL;
var.d.b = atoi(xml_get(cur));
}
else if(strcmp(str, "str")) {
var.type = MISN_VAR_STR;
var.d.str = atoi(xml_get(cur));
} else {
// Supeh error checking.
WARN("Unknown var type '%s'", str);
free(var.name);
continue;
}
free(str);
var_add(&var);
}
} while(xml_nextNode(cur));
}
} while(xml_nextNode(node));
return 0;
}
// Add a var to the stack, strings will be SHARED, don't free!!!
static int var_add(misn_var* new_var) {
int i;
if(var_nstack+1 > var_mstack) {
// More memory.
var_mstack += 64; // Overkill much??
var_stack = realloc(var_stack, var_mstack * sizeof(misn_var));
}
// Check if already exists.
for(i = 0; i < var_nstack; i++)
if(strcmp(new_var->name, var_stack[i].name)==0) {
var_free(&var_stack[i]);
memcpy(&var_stack[i], new_var, sizeof(misn_var));
return 0;
}
memcpy(&var_stack[var_nstack], new_var, sizeof(misn_var));
var_nstack++;
return 0;
}
// -- Mission. --
static int misn_setTitle(lua_State* L) {
LLUA_MIN_ARGS(1);
if(lua_isstring(L, -1)) {
if(cur_mission->title)
// Cleanup the old title.
free(cur_mission->title);
cur_mission->title = strdup((char*)lua_tostring(L, -1));
}
return 0;
}
static int misn_setDesc(lua_State* L) {
LLUA_MIN_ARGS(1);
if(lua_isstring(L, -1)) {
if(cur_mission->desc)
// Cleanup the old description.
free(cur_mission->desc);
cur_mission->desc = strdup((char*)lua_tostring(L, -1));
}
return 0;
}
static int misn_setReward(lua_State* L) {
LLUA_MIN_ARGS(1);
if(lua_isstring(L, -1)) {
if(cur_mission->reward)
// Cleanup the old reward.
free(cur_mission->reward);
cur_mission->reward = strdup((char*)lua_tostring(L, -1));
}
return 0;
}
static int misn_factions(lua_State* L) {
int i;
MissionData* dat;
dat = cur_mission->data;
// We'll push all the factions in table form.
lua_newtable(L);
for(i = 0; i < dat->avail.nfactions; i++) {
lua_pushnumber(L, i+1); // Index, starts with 1.
lua_pushnumber(L, dat->avail.factions[i]); // Value.
lua_rawset(L, -3); // Store the value in the table.
}
return 1;
}
static int misn_accept(lua_State* L) {
int i, ret;
ret = 0;
// Find the last mission.
for(i = 0; i < MISSION_MAX; i++)
if(player_missions[i].data == NULL) break;
// No missions left.
if(i >= MISSION_MAX) ret = 1;
else {
memcpy(&player_missions[i], cur_mission, sizeof(Mission));
memset(cur_mission, 0, sizeof(Mission));
cur_mission = &player_missions[i];
}
lua_pushboolean(L, !ret); // We'll convert C style return to lua.
return 1;
}
static int misn_finish(lua_State* L) {
int b;
if(lua_isboolean(L, -1)) b = lua_toboolean(L, -1);
else {
lua_pushstring(L, "Mission Done");
lua_error(L); // THERE IS NO RETURN!
return 0;
}
misn_delete = 1;
if(b && mis_isFlag(cur_mission->data, MISSION_UNIQUE))
player_missionFinished(mission_getID(cur_mission->data->name));
lua_pushstring(L, "Mission Done");
lua_error(L); // Should not return..
return 0;
}
// -- Var. --
// Check if a variable exists.
int var_checkflag(char* str) {
int i;
for(i = 0; i < var_nstack; i++)
if(strcmp(var_stack[i].name, str)==0)
return 1;
return 0;
}
static int var_peek(lua_State* L) {
LLUA_MIN_ARGS(1);
int i;
char* str;
if(lua_isstring(L, -1)) str = (char*) lua_tostring(L, -1);
else {
LLUA_DEBUG("Trying to peek a var with non-string name");
return 0;
}
for(i = 0; i < var_nstack; i++)
if(strcmp(str, var_stack[i].name)==0) {
switch(var_stack[i].type) {
case MISN_VAR_NIL:
lua_pushnil(L);
break;
case MISN_VAR_NUM:
lua_pushnumber(L, var_stack[i].d.num);
break;
case MISN_VAR_BOOL:
lua_pushboolean(L, var_stack[i].d.b);
break;
case MISN_VAR_STR:
lua_pushstring(L, var_stack[i].d.str);
break;
}
return 1;
}
lua_pushnil(L);
return 1;
}
static int var_pop(lua_State* L) {
LLUA_MIN_ARGS(1);
int i;
char* str;
if(lua_isstring(L, -1)) str = (char*) lua_tostring(L, -1);
else {
LLUA_DEBUG("Trying to pop a var with non-string name");
return 0;
}
for(i = 0; i < var_nstack; i++)
if(strcmp(str, var_stack[i].name)==0) {
var_free(&var_stack[i]);
memmove(&var_stack[i], &var_stack[i+1], sizeof(misn_var)*(var_nstack-i-1));
var_stack--;
return 0;
}
LLUA_DEBUG("Var '%s' not found in stack", str);
return 0;
}
static int var_push(lua_State* L) {
LLUA_MIN_ARGS(2);
char* str;
misn_var var;
if(lua_isstring(L, -2)) str = (char*) lua_tostring(L, -2);
else {
LLUA_DEBUG("Trying to push a var with non-string name");
return 0;
}
var.name = strdup(str);
// Store appropriate data.
if(lua_isnil(L, -1))
var.type = MISN_VAR_NIL;
else if(lua_isnumber(L, -1)) {
var.type = MISN_VAR_NUM;
var.d.num = (double)lua_tonumber(L, -1);
}
else if(lua_isboolean(L, -1)) {
var.type = MISN_VAR_BOOL;
var.d.b = lua_toboolean(L, -1);
}
else if(lua_isstring(L, -1)) {
var.type = MISN_VAR_STR;
var.d.str = strdup((char*)lua_tostring(L, -1));
} else {
LLUA_DEBUG("Trying to push a var of invalid data type to stack");
return 0;
}
var_add(&var);
return 0;
}
static void var_free(misn_var* var) {
switch(var->type) {
case MISN_VAR_STR:
if(var->d.str != NULL) {
free(var->d.str);
var->d.str = NULL;
}
break;
case MISN_VAR_NIL:
case MISN_VAR_NUM:
case MISN_VAR_BOOL:
break;
}
if(var->name != NULL) {
free(var->name);
var->name = NULL;
}
}
void var_cleanup(void) {
int i;
for(i = 0; i < var_nstack; i++)
var_free(&var_stack[i]);
if(var_stack != NULL) free(var_stack);
var_stack = NULL;
var_nstack = 0;
var_mstack = 0;
}
// -- Player. --
static int player_getname(lua_State* L) {
lua_pushstring(L, player_name);
return 1;
}
static int player_shipname(lua_State* L) {
lua_pushstring(L, player->name);
return 1;
}
static int player_freeSpace(lua_State* L) {
lua_pushnumber(L, pilot_cargoFree(player));
return 1;
}
static int player_addCargo(lua_State* L) {
Commodity* cargo;
int quantity, ret;
LLUA_MIN_ARGS(2);
if(lua_isstring(L, -2)) cargo = commodity_get((char*) lua_tostring(L, -2));
else return 0;
if(lua_isnumber(L, -1)) quantity = (int)lua_tonumber(L, -1);
else return 0;
ret = pilot_addMissionCargo(player, cargo, quantity);
mission_linkCargo(cur_mission, ret);
lua_pushnumber(L, ret);
return 1;
}
static int player_rmCargo(lua_State* L) {
int ret;
unsigned int id;
LLUA_MIN_ARGS(1);
if(lua_isnumber(L, -1)) id = (unsigned int) lua_tonumber(L, -1);
else return 0;
ret = pilot_rmMissionCargo(player, id);
mission_unlinkCargo(cur_mission, id);
lua_pushboolean(L, !ret);
return 1;
}
static int player_pay(lua_State* L) {
int money;
LLUA_MIN_ARGS(1);
if(lua_isnumber(L, -1)) money = (int) lua_tonumber(L, -1);
else return 0;
player->credits += money;
return 0;
}
static int player_msg(lua_State* L) {
LLUA_MIN_ARGS(1);
char* str;
if(lua_isstring(L, -1)) str = (char*) lua_tostring(L, -1);
else return 0;
player_message(str);
return 0;
}
static int player_modFaction(lua_State* L) {
LLUA_MIN_ARGS(2);
int f, mod;
if(lua_isstring(L, -2)) f = faction_get(lua_tostring(L, -2));
else LLUA_INVALID_PARAMETER();
if(lua_isnumber(L, -1)) mod = (int)lua_tonumber(L, -1);
else LLUA_INVALID_PARAMETER();
faction_modPlayer(f, mod);
return 0;
}
static int player_getFaction(lua_State* L) {
LLUA_MIN_ARGS(1);
int f;
if(lua_isstring(L, -1)) f = faction_get(lua_tostring(L, -1));
else LLUA_INVALID_PARAMETER();
lua_pushnumber(L, faction_getPlayer(f));
return 1;
}
// -- HOOK --
static unsigned int hook_generic(lua_State* L, char* stack) {
int i;
char* func;
LLUA_MIN_ARGS(1);
// Make sure mission is a player mission.
for(i = 0; i < MISSION_MAX; i++)
if(player_missions[i].id == cur_mission->id)
break;
if(i >= MISSION_MAX) {
WARN("Mission not in stack trying to hook");
return 0;
}
if(lua_isstring(L, -1)) func = (char*)lua_tostring(L, -1);
else {
WARN("Mission '%s': trying to push non-valid function hook",
cur_mission->data->name);
return 0;
}
return hook_add(cur_mission->id, func, stack);
}
static int hook_land(lua_State* L) {
hook_generic(L, "land");
return 0;
}
static int hook_takeoff(lua_State* L) {
hook_generic(L, "takeoff");
return 0;
}
static int hook_time(lua_State* L) {
hook_generic(L, "time");
return 0;
}
static int hook_enter(lua_State* L) {
hook_generic(L, "enter");
return 0;
}
static int hook_pilotDeath(lua_State* L) {
LLUA_MIN_ARGS(2);
unsigned int h, p;
if(lua_isnumber(L, -2)) p = (unsigned int) lua_tonumber(L, -2);
else LLUA_INVALID_PARAMETER();
h = hook_generic(L, "death"); // We won't actually call the death stack directly.
pilot_addHook(pilot_get(p), PILOT_HOOK_DEATH, h);
return 0;
}
// -- Pilot. --
static int pilot_addFleet(lua_State* L) {
LLUA_MIN_ARGS(1);
Fleet* flt;
char* fltname;
int i, j;
unsigned int p;
double a;
Vec2 vv, vp, vn;
if(lua_isstring(L, -1)) fltname = (char*) lua_tostring(L, -1);
else LLUA_INVALID_PARAMETER();
// Pull the fleet.
flt = fleet_get(fltname);
if(flt == NULL) {
LLUA_DEBUG("Fleet not found!");
return 0;
}
// This should probably be done better..
vect_pset(&vp, RNG(MIN_HYPERSPACE_DIST, MIN_HYPERSPACE_DIST*1.5),
RNG(0, 360)*M_PI/180.);
vectnull(&vn);
// Now we start adding pilots and toss ids into the table we return.
j = 0;
for(i = 0; i < flt->npilots; i++) {
if(RNG(0, 100) <= flt->pilots[i].chance) {
// Fleet displacement.
vect_cadd(&vp, RNG(75, 150) & (RNG(0, 1) ? 1: -1),
RNG(75, 150) * (RNG(0, 1) ? 1 : -1));
a = vect_angle(&vp, &vn);
vectnull(&vv);
p = pilot_create(flt->pilots[i].ship,
flt->pilots[i].name,
flt->faction,
flt->ai,
a,
&vp,
&vv,
0);
// We push each pilot created into a table and return it.
lua_pushnumber(L, ++j); // Index start with 1.
lua_pushnumber(L, p); // value = pilot id.
lua_rawset(L, -3); // Store the value in the table.
}
}
return 1;
}
static int pilot_rename(lua_State* L) {
LLUA_MIN_ARGS(2);
char* name;
unsigned int id;
Pilot* p;
if(lua_isnumber(L, -2)) id = (unsigned int) lua_tonumber(L, -2);
else LLUA_INVALID_PARAMETER();
if(lua_isstring(L, -1)) name = (char*) lua_tostring(L, -1);
else LLUA_INVALID_PARAMETER();
p = pilot_get(id);
free(p->name);
p->name = strdup(name);
return 0;
}