Lephisto/src/mission.c
2013-06-23 13:20:22 +01:00

683 lines
18 KiB
C

#include <stdint.h>
#include <string.h>
#include <malloc.h>
#include "lluadef.h"
#include "rng.h"
#include "lephisto.h"
#include "log.h"
#include "hook.h"
#include "pack.h"
#include "xml.h"
#include "faction.h"
#include "player.h"
#include "mission.h"
#define XML_MISSION_ID "Missions" /* XML section identifier. */
#define XML_MISSION_TAG "mission"
#define MISSION_DATA "../dat/mission.xml"
#define MISSION_LUA_PATH "../dat/missions/"
/* Current player missions. */
static unsigned int mission_id = 0;
Mission player_missions[MISSION_MAX];
/* Mission stack. */
static MissionData* mission_stack = NULL; /* Unmuteable after creation. */
static int mission_nstack = 0;
/* Extern. */
extern int misn_run(Mission* misn, char* func);
/* Static. */
static unsigned int mission_genID(void);
static int mission_init(Mission* mission, MissionData* misn, int load);
static void mission_freeData(MissionData* mission);
static int mission_alreadyRunning(MissionData* misn);
static int mission_meetCond(MissionData* misn);
static int mission_meetReq(int mission, int faction, char* planet, char* system);
static int mission_matchFaction(MissionData* misn, int faction);
static int mission_location(char* loc);
static MissionData* mission_parse(const xmlNodePtr parent);
static int missions_parseActive(xmlNodePtr parent);
static int mission_persistData(lua_State* L, xmlTextWriterPtr writer);
static int mission_unpersistData(lua_State* L, xmlNodePtr parent);
/* Extern. */
int missions_saveActive(xmlTextWriterPtr writer);
int missions_loadActive(xmlNodePtr parent);
/* Generate a new id for the mission. */
static unsigned int mission_genID(void) {
unsigned int id;
int i;
id = ++mission_id; /* Default id, not safe if loading. */
/* We save mission id's, so check for collisions with players missions. */
for(i = 0; i < MISSION_MAX; i++)
if(id == player_missions[i].id) /* Mission id was loaded from save. */
return mission_genID(); /* Recursive try again. */
return id;
}
/* Gets the ID from mission name. */
int mission_getID(char* name) {
int i;
for(i = 0; i < mission_nstack; i++)
if(strcmp(name, mission_stack[i].name)==0)
return i;
DEBUG("Mission '%s' not found in stack", name);
return -1;
}
/* Get a MissionData based on ID. */
MissionData* mission_get(int id) {
if((id <= 0) || (mission_nstack < id)) return NULL;
return &mission_stack[id];
}
/* Initialize a mission. */
static int mission_init(Mission* mission, MissionData* misn, int load) {
char* buf;
uint32_t bufsize;
if(load != 0)
mission->id = 0;
else
mission->id = mission_genID();
mission->data = misn;
/* Sane defaults. */
mission->title = NULL;
mission->desc = NULL;
mission->reward = NULL;
mission->cargo = NULL;
mission->ncargo = 0;
/* Init lua. */
mission->L = luaL_newstate();
if(mission->L == NULL) {
ERR("Unable to create a new lua state.");
return -1;
}
luaopen_base(mission->L); /* Can be useful. */
luaopen_string(mission->L); /* string.format can be very useful. */
misn_loadLibs(mission->L); /* Load our custom libraries. */
buf = pack_readfile(DATA, misn->lua, &bufsize);
if(luaL_dobuffer(mission->L, buf, bufsize, misn->lua) != 0) {
ERR("Error loading mission file: %s", misn->lua);
ERR("%s", lua_tostring(mission->L, -1));
WARN("Most likely Lua file has improper syntax, please check");
return -1;
}
free(buf);
/* Run create function. */
if(load == 0) /* Never run when loading. */
misn_run(mission, "create");
return mission->id;
}
/* Small wrapper for misn_run. */
int mission_accept(Mission* mission) {
int ret;
ret = misn_run(mission, "accept");
if(ret == 0) return 1;
return 0;
}
/* Check to see if mission is already running. */
static int mission_alreadyRunning(MissionData* misn) {
int i;
for (i = 0; i < MISSION_MAX; i++)
if(player_missions[i].data == misn)
return 1;
return 0;
}
/* Is the lua condition for misn met? */
static lua_State* mission_cond_L = NULL;
static int mission_meetCond(MissionData* misn) {
int ret;
char buf[256];
if(mission_cond_L == NULL) {
/* Must create the conditional environment. */
mission_cond_L = luaL_newstate();
misn_loadCondLibs(mission_cond_L);
}
snprintf(buf, 256, "return %s", misn->avail.cond);
ret = luaL_loadstring(mission_cond_L, buf);
switch(ret) {
case LUA_ERRSYNTAX:
WARN("Missions '%s' Lua Conditional syntax error", misn->name);
return 0;
case LUA_ERRMEM:
WARN("Mission '%s' Lua Conditional ran out of memory", misn->name);
return 0;
default:
break;
}
ret = lua_pcall(mission_cond_L, 0, 1, 0);
switch(ret) {
case LUA_ERRRUN:
WARN("Mission '%s' Lua Conditional had a runtime error: %s",
misn->name, lua_tostring(mission_cond_L, -1));
return 0;
case LUA_ERRMEM:
WARN("Mission '%s' Lua Conditional ran out of memory", misn->name);
return 0;
case LUA_ERRERR:
WARN("Mission '%s' Lua Conditional had an error while handling error function",
misn->name);
return 0;
default:
break;
}
if(lua_isboolean(mission_cond_L, -1)) {
if(lua_toboolean(mission_cond_L, -1))
return 1;
else
return 0;
}
WARN("Mission '%s' Conditional Lua didn't return a boolean", misn->name);
return 0;
}
/* Does the mission meet the minimum requirements? */
static int mission_meetReq(int mission, int faction, char* planet, char* system) {
MissionData* misn;
misn = &mission_stack[mission];
/* Must match planet, system or faction. */
if(!(((misn->avail.planet && strcmp(misn->avail.planet, planet)==0)) ||
(misn->avail.system && (strcmp(misn->avail.system, system)==0)) ||
mission_matchFaction(misn, faction)))
return 0;
if(mis_isFlag(misn, MISSION_UNIQUE) && /* Mission done, or running. */
(player_missionAlreadyDone(mission) ||
mission_alreadyRunning(misn)))
return 0;
if((misn->avail.cond != NULL) && /* Mission doesn't meet the requirement. */
!mission_meetCond(misn))
return 0;
return 1;
}
/* Runs bar missions, all lua side and one-shot. */
void missions_bar(int faction, char* planet, char* system) {
MissionData* misn;
Mission mission;
int i;
double chance;
for(i = 0; i < mission_nstack; i++) {
misn = &mission_stack[i];
if(misn->avail.loc == MIS_AVAIL_BAR) {
if(!mission_meetReq(i, faction, planet, system))
continue;
chance = (double)(misn->avail.chance % 100)/100.;
if(RNGF() < chance) {
mission_init(&mission, misn, 0);
mission_cleanup(&mission); /* It better clean up for itself or we do it. */
}
}
}
}
/* Links cargo to the mission for posterior cleanup. */
void mission_linkCargo(Mission* misn, unsigned int cargo_id) {
misn->ncargo++;
misn->cargo = realloc(misn->cargo, sizeof(unsigned int) * misn->ncargo);
misn->cargo[misn->ncargo-1] = cargo_id;
}
/* Unlink cargo from the mission, removes it from the player. */
void mission_unlinkCargo(Mission* misn, unsigned int cargo_id) {
int i;
for(i = 0; i < misn->ncargo; i++)
if(misn->cargo[i] == cargo_id)
break;
if(i >= misn->ncargo) { /* Not found. */
DEBUG("Mission '%s' attempting to unlink in existant cargo %d.",
misn->title, cargo_id);
return;
}
/* Shrink cargo size - No need to realloc. */
memmove(&misn->cargo[i], &misn->cargo[i+1],
sizeof(unsigned int) * (misn->ncargo-i-1));
misn->ncargo--;
player_rmMissionCargo(cargo_id);
}
/* Clean up a mission. */
void mission_cleanup(Mission* misn) {
int i;
if(misn->id != 0) {
hook_rmParent(misn->id); /* Remove existing hooks. */
misn->id = 0;
}
if(misn->title != NULL) {
free(misn->title);
misn->title = NULL;
}
if(misn->desc != NULL) {
free(misn->desc);
misn->desc = NULL;
}
if(misn->reward) {
free(misn->reward);
misn->reward = NULL;
}
if(misn->cargo) {
for(i = 0; i < misn->ncargo; i++)
mission_unlinkCargo(misn, misn->cargo[i]);
free(misn->cargo);
misn->cargo = NULL;
misn->ncargo = 0;
}
if(misn->L) {
lua_close(misn->L);
misn->L = NULL;
}
}
/* Free a mission. */
static void mission_freeData(MissionData* mission) {
if(mission->name) free(mission->name);
if(mission->lua) free(mission->lua);
if(mission->avail.planet) free(mission->avail.planet);
if(mission->avail.system) free(mission->avail.system);
if(mission->avail.factions) free(mission->avail.factions);
memset(mission, 0, sizeof(MissionData));
}
/* Does mission match faction requirement? */
static int mission_matchFaction(MissionData* misn, int faction) {
int i;
for(i = 0; i < misn->avail.nfactions; i++) {
if(faction_isFaction(misn->avail.factions[i]) &&
(faction == misn->avail.factions[i]))
return 1;
else if(faction_ofAlliance(faction, misn->avail.factions[i]))
return 1;
}
return 0;
}
/* Generate missions for the computer - special case. */
Mission* missions_computer(int* n, int faction, char* planet, char* system) {
int i, j, m;
double chance;
int rep;
Mission* tmp;
MissionData* misn;
tmp = NULL;
m = 0;
for(i = 0; i < mission_nstack; i++) {
misn = &mission_stack[i];
if(misn->avail.loc == MIS_AVAIL_COMPUTER) {
if(!mission_meetReq(i, faction, planet, system))
continue;
chance = (double)(misn->avail.chance % 100)/100.;
rep = misn->avail.chance/100;
for(j = 0; j < rep; j++)
/* Random chance of rep appearances. */
if(RNGF() < chance) {
tmp = realloc(tmp, sizeof(Mission) * ++m);
mission_init(&tmp[m-1], misn, 0);
}
}
}
(*n) = m;
return tmp;
}
/* Return location based on string. */
static int mission_location(char* loc) {
if(strcmp(loc, "None")==0) return MIS_AVAIL_NONE;
else if(strcmp(loc, "Computer")==0) return MIS_AVAIL_COMPUTER;
else if(strcmp(loc, "Bar")==0) return MIS_AVAIL_BAR;
else if(strcmp(loc, "Outfit")==0) return MIS_AVAIL_OUTFIT;
else if(strcmp(loc, "Shipyard")==0) return MIS_AVAIL_SHIPYARD;
else if(strcmp(loc, "Land")==0) return MIS_AVAIL_LAND;
return -1;
}
/* Parse a node of a mission. */
static MissionData* mission_parse(const xmlNodePtr parent) {
MissionData* tmp;
xmlNodePtr cur, node;
tmp = malloc(sizeof(MissionData));
memset(tmp, 0, sizeof(MissionData));
/* Get the name. */
tmp->name = xml_nodeProp(parent, "name");
if(tmp->name == NULL) WARN("Mission in "MISSION_DATA" has invalid or no name");
node = parent->xmlChildrenNode;
char str[PATH_MAX] = "\0";
/* Load all the data. */
do {
if(xml_isNode(node, "lua")) {
snprintf(str, PATH_MAX, MISSION_LUA_PATH"%s.lua", xml_get(node));
tmp->lua = strdup(str);
str[0] = '\0';
}
else if(xml_isNode(node, "flags")) { /* Set the various flags. */
cur = node->children;
do {
if(xml_isNode(cur, "unique"))
mis_setFlag(tmp, MISSION_UNIQUE);
} while(xml_nextNode(cur));
}
else if(xml_isNode(node, "avail")) { /* Mission availability. */
cur = node->children;
do {
if(xml_isNode(cur, "location"))
tmp->avail.loc = mission_location(xml_get(cur));
else if(xml_isNode(cur, "chance"))
tmp->avail.chance = xml_getInt(cur);
else if(xml_isNode(cur, "planet"))
tmp->avail.planet = strdup(xml_get(cur));
else if(xml_isNode(cur, "system"))
tmp->avail.system = strdup(xml_get(cur));
else if(xml_isNode(cur, "alliance")) {
tmp->avail.factions = realloc(tmp->avail.factions,
sizeof(int) * ++tmp->avail.nfactions);
tmp->avail.factions[tmp->avail.nfactions-1] =
faction_getAlliance(xml_get(cur));
}
else if(xml_isNode(cur, "faction")) {
tmp->avail.factions = realloc(tmp->avail.factions,
sizeof(int) * ++tmp->avail.nfactions);
tmp->avail.factions[tmp->avail.nfactions-1] =
faction_get(xml_get(cur));
}
else if(xml_isNode(cur, "cond"))
tmp->avail.cond = strdup(xml_get(cur));
} while(xml_nextNode(cur));
}
} while(xml_nextNode(node));
#define MELEMENT(o,s) \
if(o) WARN("Mission '%s' missing/invalid '"s"' element", tmp->name)
MELEMENT(tmp->lua==NULL, "lua");
MELEMENT(tmp->avail.loc==-1, "location");
#undef MELEMENT
return tmp;
}
/* Load/Free. */
int missions_load(void) {
uint32_t bufsize;
char* buf = pack_readfile(DATA, MISSION_DATA, &bufsize);
MissionData* tmp;
xmlNodePtr node;
xmlDocPtr doc = xmlParseMemory(buf, bufsize);
node = doc->xmlChildrenNode;
if(!xml_isNode(node, XML_MISSION_ID)) {
ERR("Malformed '"MISSION_DATA"' file: missing root element '" \
XML_MISSION_ID"'");
return -1;
}
node = node->xmlChildrenNode; /* First mission node. */
if(node == NULL) {
ERR("Malformed '"MISSION_DATA"' file: does not contain elements");
return -1;
}
do {
if(xml_isNode(node, XML_MISSION_TAG)) {
tmp = mission_parse(node);
mission_stack =
realloc(mission_stack, sizeof(MissionData)*(++mission_nstack));
memcpy(mission_stack+mission_nstack-1, tmp, sizeof(MissionData));
free(tmp);
}
} while(xml_nextNode(node));
xmlFreeDoc(doc);
free(buf);
xmlCleanupParser();
DEBUG("Loaded %d Mission%s", mission_nstack, (mission_nstack==1) ? "" : "s");
return 0;
}
void missions_free(void) {
int i;
/* Free the mission data. */
for(i = 0; i < mission_nstack; i++)
mission_freeData(&mission_stack[i]);
free(mission_stack);
mission_stack = NULL;
mission_nstack = 0;
if(mission_cond_L != NULL) {
lua_close(mission_cond_L);
mission_cond_L = NULL;
}
}
void missions_cleanup(void) {
int i;
for(i = 0; i < MISSION_MAX; i++)
mission_cleanup(&player_missions[i]);
}
/* Persists partial lua data. */
static int mission_saveData(xmlTextWriterPtr writer, char* type,
char* name, char* value) {
xmlw_startElem(writer, "data");
xmlw_attr(writer, "type", type);
xmlw_attr(writer, "name", name);
xmlw_str(writer, "%s", value);
xmlw_endElem(writer); /* data. */
return 0;
}
static int mission_persistData(lua_State* L, xmlTextWriterPtr writer) {
lua_pushnil(L);
/* nil. */
while(lua_next(L, LUA_GLOBALSINDEX) != 0) {
/* key, value. */
switch(lua_type(L, -1)) {
case LUA_TNUMBER:
mission_saveData(writer, "number",
(char*)lua_tostring(L, -2), (char*)lua_tostring(L, -1));
break;
case LUA_TBOOLEAN:
mission_saveData(writer, "bool",
(char*)lua_tostring(L, -2), (char*)lua_tostring(L, -1));
break;
case LUA_TSTRING:
mission_saveData(writer, "string",
(char*)lua_tostring(L, -2), (char*)lua_tostring(L, -1));
break;
default:
break;
}
lua_pop(L, 1);
/* Key. */
}
return 0;
}
/* Unpersist lua data. */
static int mission_unpersistData(lua_State* L, xmlNodePtr parent) {
xmlNodePtr node;
char* name, *type;
node = parent->xmlChildrenNode;
do {
if(xml_isNode(node, "data")) {
xmlr_attr(node, "name", name);
xmlr_attr(node, "type", type);
/* Handle data types. */
if(strcmp(type, "number")==0)
lua_pushnumber(L, xml_getFloat(node));
else if(strcmp(type, "bool")==0)
lua_pushboolean(L, xml_getInt(node));
else if(strcmp(type, "string")==0)
lua_pushstring(L, xml_get(node));
else {
WARN("Unknown lua data type!");
return -1;
}
lua_setglobal(L, name);
free(type);
free(name);
}
} while(xml_nextNode(node));
return 0;
}
int missions_saveActive(xmlTextWriterPtr writer) {
int i, j;
xmlw_startElem(writer, "missions");
for(i = 0; i < MISSION_MAX; i++) {
if(player_missions[i].id != 0) {
xmlw_startElem(writer, "mission");
/* Data and id are attributes because they must be loaded first. */
xmlw_attr(writer, "data", player_missions[i].data->name);
xmlw_attr(writer, "id", "%u", player_missions[i].id);
xmlw_elem(writer, "title", player_missions[i].title);
xmlw_elem(writer, "desc", player_missions[i].desc);
xmlw_elem(writer, "reward", player_missions[i].reward);
xmlw_startElem(writer, "cargos");
for(j = 0; j < player_missions[i].ncargo; j++)
xmlw_elem(writer, "cargo", "%u", player_missions[i].cargo[j]);
xmlw_endElem(writer); /* Cargo. */
/* Write lua magic. */
xmlw_startElem(writer, "lua");
/* Prepare the data. */
mission_persistData(player_missions[i].L, writer);
xmlw_endElem(writer); /* Lua. */
xmlw_endElem(writer); /* Mission. */
}
}
xmlw_endElem(writer); /* Missions. */
return 0;
}
int missions_loadActive(xmlNodePtr parent) {
xmlNodePtr node;
/* Cleanup old missions. */
missions_cleanup();
node = parent->xmlChildrenNode;
do {
if(xml_isNode(node, "missions"))
if(missions_parseActive(node) < 0) return -1;
} while(xml_nextNode(node));
return 0;
}
static int missions_parseActive(xmlNodePtr parent) {
Mission* misn;
int m;
char* buf;
xmlNodePtr node, cur, nest;
m = 0; /* Start with mission 0. */
node = parent->xmlChildrenNode;
do {
if(xml_isNode(node, "mission")) {
misn = &player_missions[m];
/* Process the attributes to create the mission. */
xmlr_attr(node, "data", buf);
mission_init(misn, mission_get(mission_getID(buf)), 1);
free(buf);
/* This will orphan an identifier. */
xmlr_attr(node, "id", buf);
misn->id = atol(buf);
free(buf);
cur = node->xmlChildrenNode;
do {
xmlr_strd(cur, "title", misn->title);
xmlr_strd(cur, "desc", misn->desc);
xmlr_strd(cur, "reward", misn->reward);
if(xml_isNode(cur, "cargos")) {
nest = cur->xmlChildrenNode;
do {
if(xml_isNode(nest, "cargo"))
mission_linkCargo(misn, xml_getLong(nest));
} while(xml_nextNode(nest));
}
if(xml_isNode(cur, "lua"))
/* Start the unpersist routine. */
mission_unpersistData(misn->L, cur);
} while(xml_nextNode(cur));
m++;
if(m >= MISSION_MAX) break; /* Full of missions, must be an error. */
}
} while(xml_nextNode(node));
return 0;
}