Lephisto/src/unidiff.c
Allanis 08ec271629 [Add] Added more complicated xml parse functions.
[Change] Renamed xml.h to lxml.h
2014-04-23 20:02:50 +01:00

574 lines
14 KiB
C

/**
* @file unidiff.c
*
* @brief Handles the application and removal of 'diffs' to the universe.
*
* Diffs allow changing planets, fleets, factions etc.. in the universe.
* These are meant to be applied after the player triggers them, mostly
* through missions.
*/
#include <stdlib.h>
#include <string.h>
#include "lephisto.h"
#include "log.h"
#include "lxml.h"
#include "space.h"
#include "pack.h"
#include "unidiff.h"
#define CHUNK_SIZE 32 /**< Size of chunk to allocate. */
#define DIFF_DATA "../dat/unidiff.xml" /**< Unidff XML file. */
/**
* @enum UniHunkTargetType_t
*
* @brief Represents the possible hunk targets.
*/
typedef enum UniHunkTargetType_ {
HUNK_TARGET_NONE,
HUNK_TARGET_SYSTEM
} UniHunkTargetType_t;
/**
* @struct UniHunkTarget_t
*
* @brief Represents the hunks target.
*/
typedef struct UniHunkTarget_ {
UniHunkTargetType_t type;
union {
char* name;
} u;
} UniHunkTarget_t;
/**
* @enum UniHunkType_t
*
* @brief Represents the different type of hunk actions.
*/
typedef enum UniHunkType_ {
HUNK_TYPE_NONE,
/* Target should be system. */
HUNK_TYPE_PLANET_ADD,
HUNK_TYPE_PLANET_REMOVE,
HUNK_TYPE_FLEET_ADD,
HUNK_TYPE_FLEET_REMOVE
} UniHunkType_t;
/**
* @struct UniHunk_t
*
* @brief Represents a single hunk in the diff.
*/
typedef struct UniHunk_ {
UniHunkTarget_t target; /**< Hunk's target. */
UniHunkType_t type; /**< Type of hunk it is. */
union {
char* name;
SystemFleet fleet;
} u; /* Actual data to patch. */
} UniHunk_t;
/**
* @struct UniDiff_t
*
* @brief Represents each Universe Diff.
*/
typedef struct UniDiff_ {
char* name; /**< Name of the diff. */
UniHunk_t* applied; /**< Applied hunks. */
int napplied; /**< Number of applied hunks. */
int mapplied; /**< Memory of applied hunks. */
UniHunk_t* failed; /**< Failed hunks. */
int nfailed; /**< Number of failed hunks. */
int mfailed; /**< Memory of failed hunks. */
} UniDiff_t;
/* Diff stack. */
static UniDiff_t* diff_stack = NULL; /**< Currently applied universe diffs. */
static int diff_nstack = 0; /**< Number of diffs in the stack. */
static int diff_mstack = 0; /**< Currently allocated diffs. */
static UniDiff_t* diff_get(char* name);
static UniDiff_t* diff_newDiff(void);
static int diff_removeDiff(UniDiff_t* diff);
static int diff_patch(xmlNodePtr parent);
static int diff_patchHunk(UniHunk_t* hunk);
static void diff_hunkFailed(UniDiff_t* diff, UniHunk_t* hunk);
static void diff_hunkSuccess(UniDiff_t* diff, UniHunk_t* hunk);
static void diff_cleanup(UniDiff_t* diff);
static void diff_cleanupHunk(UniHunk_t* hunk);
/* Externed. */
int diff_save(xmlTextWriterPtr writer);
int diff_load(xmlNodePtr parent);
/**
* @fn int diff_isApplied(char* name)
*
* @brief Check if a diff is currently applied.
* @param name Diff to check.
* @return 0 if it's not applied, 1 if it is.
*/
int diff_isApplied(char* name) {
if(diff_get(name) != NULL)
return 1;
return 0;
}
/**
* @fn static UniDiff_t* diff_get(char* name)
*
* @brief Get a diff by name.
* @param name Name of the diff to get.
* @return The diff if found or NULL if not found.
*/
static UniDiff_t* diff_get(char* name) {
int i;
for(i = 0; i < diff_nstack; i++)
if(strcmp(diff_stack[i].name, name)==0)
return &diff_stack[i];
return NULL;
}
/**
* @fn int diff_apply(char* name)
*
* @brief Applies a diff to the universe.
* @param name Diff to apply.
* @return 0 on success.
*/
int diff_apply(char* name) {
xmlNodePtr node;
xmlDocPtr doc;
uint32_t bufsize;
char* buf;
char* diffname;
/* Check if already applied. */
if(diff_isApplied(name))
return 0;
buf = pack_readfile(DATA, DIFF_DATA, &bufsize);
doc = xmlParseMemory(buf, bufsize);
node = doc->xmlChildrenNode;
if(strcmp((char*)node->name, "unidiffs")) {
WARN("Malformed unidiff file: missing root element 'unidiffs'");
return 0;
}
node = node->xmlChildrenNode; /* First system node. */
if(node == NULL) {
ERR("Malformed unidiff file: does not contain elements");
return 0;
}
do {
if(xml_isNode(node, "unidiff")) {
/* Check to see if it's the diff we're looking for. */
xmlr_attr(node, "name", diffname);
if(strcmp(diffname, name)==0) {
/* Apply it. */
diff_patch(node);
/* Clean up. */
free(diffname);
xmlFreeDoc(doc);
free(buf);
return 0;
}
free(diffname);
}
} while(xml_nextNode(node));
/* More clean up. */
xmlFreeDoc(doc);
free(buf);
WARN("UniDiff '%s' not found in "DIFF_DATA".", name);
return -1;
}
/**
* @fn static int diff_patch(xmlNodePtr parent)
*
* @brief Actually applies a diff in XML node form.
* @param parent Node containing the diff information.
* @return 0 on success.
*/
static int diff_patch(xmlNodePtr parent) {
int i;
UniDiff_t* diff;
UniHunk_t base, hunk, *fail;
xmlNodePtr node, cur;
char* buf, *target;
/* Prepare it. */
diff = diff_newDiff();
memset(diff, 0, sizeof(UniDiff_t));
xmlr_attr(parent, "name", diff->name);
node = parent->xmlChildrenNode;
do {
if(xml_isNode(node, "system")) {
/* Set the target. */
memset(&base, 0, sizeof(UniHunk_t));
base.target.type = HUNK_TARGET_SYSTEM;
xmlr_attr(node, "name", base.target.u.name);
if(base.target.u.name == NULL)
WARN("UniDiff '%s' has a system node without a 'name' tag", diff->name);
/* Now parse the possible changes. */
cur = node->xmlChildrenNode;
do {
if(xml_isNode(cur, "planet")) {
hunk.target.type = base.target.type;
hunk.target.u.name = strdup(base.target.u.name);
/* Get the planet to modify. */
xmlr_attr(cur, "name", hunk.u.name);
/* Get the type. */
buf = xml_get(cur);
if(strcmp(buf, "add")==0) {
hunk.type = HUNK_TYPE_PLANET_ADD;
}
else if(strcmp(buf, "remove")==0) {
hunk.type = HUNK_TYPE_PLANET_REMOVE;
}
if(diff_patchHunk(&hunk) < 0)
diff_hunkFailed(diff, &hunk);
else
diff_hunkSuccess(diff, &hunk);
}
else if(xml_isNode(cur, "fleet")) {
hunk.target.type = base.target.type;
hunk.target.u.name = strdup(base.target.u.name);
/* Get the fleet properties. */
xmlr_attr(cur, "name", buf);
hunk.u.fleet.fleet = fleet_get(buf);
free(buf);
xmlr_attr(cur, "chance", buf);
hunk.u.fleet.chance = atoi(buf);
free(buf);
/* Get the type. */
buf = xml_get(cur);
if(strcmp(buf, "add")==0) {
hunk.type = HUNK_TYPE_FLEET_ADD;
}
else if(strcmp(buf, "remove")==0) {
hunk.type = HUNK_TYPE_FLEET_REMOVE;
}
if(diff_patchHunk(&hunk) < 0)
diff_hunkFailed(diff, &hunk);
else
diff_hunkSuccess(diff, &hunk);
}
} while(xml_nextNode(cur));
/* Clean up some stuff. */
free(base.target.u.name);
base.target.u.name = NULL;
}
} while(xml_nextNode(node));
if(diff->nfailed > 0) {
DEBUG("Unidiff '%s' failed %d hunks.", diff->name, diff->nfailed);
for(i = 0; i < diff->nfailed; i++) {
fail = &diff->failed[i];
target = fail->target.u.name;
switch(fail->type) {
case HUNK_TYPE_PLANET_ADD:
DEBUG(" [%s] planet add: '%s'", target, fail->u.name);
break;
case HUNK_TYPE_PLANET_REMOVE:
DEBUG(" [%s] planet remove: '%s'", target, fail->u.name);
break;
case HUNK_TYPE_FLEET_ADD:
DEBUG(" [%s] fleet add: '%s' (%d%% chance)", target,
fail->u.fleet.fleet->name, fail->u.fleet.chance);
break;
case HUNK_TYPE_FLEET_REMOVE:
DEBUG(" [%s] fleet remove: '%s' (%d%% chance)", target,
fail->u.fleet.fleet->name, fail->u.fleet.chance);
break;
default:
DEBUG(" Unkown hunk '%d'", fail->type);
break;
}
}
}
return 0;
}
/**
* @fn static int diff_patchHunk(UniHunk_t* hunk)
*
* @brief Applies a hunk and adds it to the diff.
* @param diff Diff to which the hunk belongs.
* @param hunk Hunk to apply.
*/
static int diff_patchHunk(UniHunk_t* hunk) {
switch(hunk->type) {
/* Adding a planet. */
case HUNK_TYPE_PLANET_ADD:
return system_addPlanet(system_get(hunk->target.u.name), hunk->u.name);
/* Removing a planet. */
case HUNK_TYPE_PLANET_REMOVE:
return system_rmPlanet(system_get(hunk->target.u.name), hunk->u.name);
/* Adding a fleet. */
case HUNK_TYPE_FLEET_ADD:
return system_addFleet(system_get(hunk->target.u.name), &hunk->u.fleet);
/* Removing a fleet. */
case HUNK_TYPE_FLEET_REMOVE:
return system_rmFleet(system_get(hunk->target.u.name), &hunk->u.fleet);
default:
WARN("Unkown hunk type '%d'.", hunk->type);
break;
}
return -1;
}
/**
* @fn static void diff_hunkFailed(UniDiff_t* diff, UniHunk_t* hunk)
*
* @brief Add a hunk to the failed list.
* @param diff Diff to add hunk to.
* @param hunk Hunk that failed to apply.
*/
static void diff_hunkFailed(UniDiff_t* diff, UniHunk_t* hunk) {
if(diff == NULL)
return;
diff->nfailed++;
if(diff->nfailed > diff->mfailed) {
diff->mfailed += CHUNK_SIZE;
diff->failed = realloc(diff->failed, sizeof(UniHunk_t) * diff->mfailed);
}
memcpy(&diff->failed[diff->nfailed-1], hunk, sizeof(UniHunk_t));
}
/**
* @fn static void diff_hunkSuccess(UniDiff_t* diff, UniHunk_t* hunk)
*
* @brief Add a hunk to the applied list.
* @param diff Diff to add hunk to.
* @param hunk Hunk that applied correctly.
*/
static void diff_hunkSuccess(UniDiff_t* diff, UniHunk_t* hunk) {
if(diff == NULL)
return;
diff->napplied++;
if(diff->napplied > diff->mapplied) {
diff->mapplied += CHUNK_SIZE;
diff->applied = realloc(diff->applied, sizeof(UniHunk_t) * diff->mapplied);
}
memcpy(&diff->applied[diff->napplied-1], hunk, sizeof(UniHunk_t));
}
/**
* @fn void diff_remove(char* name)
*
* @brief Remove a diff from the universe.
* @param name Diff to remove.
*/
void diff_remove(char* name) {
UniDiff_t* diff;
/* check if already applied. */
diff = diff_get(name);
if(diff == NULL)
return;
diff_removeDiff(diff);
}
/**
* @fn void diff_clear(void)
*/
void diff_clear(void) {
while(diff_nstack > 0) {
diff_removeDiff(&diff_stack[diff_nstack-1]);
}
}
/**
* @fn static UniDiff_t* diff_newDiff(void)
*
* @brief Creates a new UniDiff_t for usage.
* @return A newly created UniDiff_t.
*/
static UniDiff_t* diff_newDiff(void) {
/* Check if needs initialization. */
if(diff_stack == NULL) {
diff_mstack = CHUNK_SIZE;
diff_stack = malloc(diff_mstack * sizeof(UniDiff_t));
diff_nstack = 1;
return &diff_stack[0];
}
diff_nstack++;
/* Check if need to grow. */
if(diff_nstack > diff_mstack) {
diff_mstack += CHUNK_SIZE;
diff_stack = realloc(diff_stack, diff_mstack * sizeof(UniDiff_t));
}
return &diff_stack[diff_nstack-1];
}
/**
* @fn static int diff_removeDiff(UniDiff_t* diff)
*
* @brief Removes a diff.
* @param diff Diff to remove.
* @return 0 on success.
*/
static int diff_removeDiff(UniDiff_t* diff) {
int i;
UniHunk_t hunk;
for(i = 0; i < diff->napplied; i++) {
memcpy(&hunk, &diff->applied[i], sizeof(UniHunk_t));
/* Invert the type for reverting. */
switch(hunk.type) {
case HUNK_TYPE_PLANET_ADD:
hunk.type = HUNK_TYPE_PLANET_REMOVE;
break;
case HUNK_TYPE_PLANET_REMOVE:
hunk.type = HUNK_TYPE_PLANET_ADD;
break;
case HUNK_TYPE_FLEET_ADD:
hunk.type = HUNK_TYPE_FLEET_REMOVE;
break;
case HUNK_TYPE_FLEET_REMOVE:
hunk.type = HUNK_TYPE_FLEET_ADD;
break;
default:
WARN("Unkown Hunk type '%d'.", hunk.type);
continue;
}
diff_patchHunk(&hunk);
}
diff_cleanup(diff);
diff_nstack--;
i = diff - diff_stack;
memmove(&diff_stack[i], &diff_stack[i+1], sizeof(UniDiff_t) * (diff_nstack-i));
return 0;
}
/**
* @fn static void diff_cleanup(UniDiff_t* diff)
*
* @brief Clean up a diff.
* @param diff Diff to clean up.
*/
static void diff_cleanup(UniDiff_t* diff) {
int i;
free(diff->name);
for(i = 0; i < diff->napplied; i++)
diff_cleanupHunk(&diff->applied[i]);
if(diff->applied != NULL)
free(diff->applied);
for(i = 0; i < diff->nfailed; i++)
diff_cleanupHunk(&diff->failed[i]);
if(diff->failed != NULL)
free(diff->failed);
memset(diff, 0, sizeof(UniDiff_t));
}
/**
* @fn static void diff_cleanupHunk(UniHunk_t* hunk)
*
* @brief Clean up a hunk.
* @param hunk Hunk to clean up.
*/
static void diff_cleanupHunk(UniHunk_t* hunk) {
if(hunk->target.u.name != NULL)
free(hunk->target.u.name);
switch(hunk->type) {
case HUNK_TYPE_PLANET_ADD:
case HUNK_TYPE_PLANET_REMOVE:
if(hunk->u.name != NULL)
free(hunk->u.name);
break;
default:
break;
}
memset(hunk, 0, sizeof(UniHunk_t));
}
/**
* @fn int diff_save(xmlTextWriterPtr writer)
*
* @brief Save the active diffs.
* @param writer XML Writer to use.
* @return 0 on success.
*/
int diff_save(xmlTextWriterPtr writer) {
int i;
UniDiff_t* diff;
xmlw_startElem(writer, "diffs");
for(i = 0; i < diff_nstack; i++) {
diff = &diff_stack[i];
xmlw_elem(writer, "diff", diff->name);
}
xmlw_endElem(writer); /* "diffs" */
return 0;
}
/**
* @fn int diff_load(xmlNodePtr parent)
*
* @brief Loads the diffs.
* @param parent Parent node containing diffs.
* @return 0 on success.
*/
int diff_load(xmlNodePtr parent) {
xmlNodePtr node, cur;
diff_clear();
node = parent->xmlChildrenNode;
do {
if(xml_isNode(node, "diffs")) {
cur = node->xmlChildrenNode;
do {
if(xml_isNode(cur, "diff"))
diff_apply(xml_get(cur));
} while(xml_nextNode(cur));
}
} while(xml_nextNode(node));
return 0;
}