#include #include #include #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; }