574 lines
14 KiB
C
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;
|
|
}
|
|
|