640 lines
15 KiB
C
640 lines
15 KiB
C
/**
|
|
* @file faction.c
|
|
*
|
|
* @brief Handle the Lephisto factions.
|
|
*/
|
|
|
|
#include <malloc.h>
|
|
#include <string.h>
|
|
|
|
#include "opengl.h"
|
|
#include "lephisto.h"
|
|
#include "log.h"
|
|
#include "pack.h"
|
|
#include "xml.h"
|
|
#include "rng.h"
|
|
#include "faction.h"
|
|
|
|
#define XML_FACTION_ID "Factions" /* XML section id. */
|
|
#define XML_FACTION_TAG "faction"
|
|
|
|
#define FACTION_DATA "../dat/faction.xml" /**< Faction xml file. */
|
|
#define FACTION_LOGO_PATH "../gfx/logo/" /**< Path to logo gfx. */
|
|
|
|
#define PLAYER_ALLY 70 /**< Above this, player is considered ally. */
|
|
|
|
/**
|
|
* @struct Faction.
|
|
*
|
|
* @brief Represents a faction.
|
|
*/
|
|
typedef struct Faction_ {
|
|
char* name; /**< Normal name. */
|
|
char* longname; /**< Long name. */
|
|
|
|
/* Graphics. */
|
|
glTexture* logo_small; /**< Small logo. */
|
|
|
|
/* Enemies. */
|
|
int* enemies; /**< Enemies by ID of the faction. */
|
|
int nenemies; /**< Number of enemies. */
|
|
|
|
/* Allies. */
|
|
int* allies; /**< Allies by ID of the faction. */
|
|
int nallies; /**< Number of allies. */
|
|
|
|
int player; /**< Standing with player - from -100 to 100. */
|
|
} Faction;
|
|
|
|
static Faction* faction_stack = NULL; /**< Faction stack. */
|
|
static int faction_nstack = 0; /**< Number of factions in the faction stack. */
|
|
|
|
/* Static. */
|
|
static int faction_isFaction(int f);
|
|
static void faction_sanitizePlayer(Faction* faction);
|
|
static Faction* faction_parse(xmlNodePtr parent);
|
|
static void faction_parseSocial(xmlNodePtr parent);
|
|
/* Extern. */
|
|
int pfaction_save(xmlTextWriterPtr writer);
|
|
int pfaction_load(xmlNodePtr parent);
|
|
|
|
/**
|
|
* @fn int faction_get(const char* name)
|
|
*
|
|
* @brief Get a faction ID by name.
|
|
* @param name Name of the faction to seek.
|
|
* @return ID of the faction.
|
|
*/
|
|
int faction_get(const char* name) {
|
|
int i;
|
|
for(i = 0; i < faction_nstack; i++)
|
|
if(strcmp(faction_stack[i].name, name)==0)
|
|
break;
|
|
|
|
if(i != faction_nstack)
|
|
return i;
|
|
|
|
WARN("Faction '%s' not found in stack.", name);
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* @fn char* faction_name(int f)
|
|
*
|
|
* @brief Get a factions short name.
|
|
* @param f Faction to get the name of.
|
|
* @return Name of the faction.
|
|
*/
|
|
char* faction_name(int f) {
|
|
if((f < 0) || (f >= faction_nstack)) {
|
|
WARN("Faction id '%d' is invalid.", f);
|
|
return NULL;
|
|
}
|
|
return faction_stack[f].name;
|
|
}
|
|
|
|
/**
|
|
* @fn char* faction_longname(int f)
|
|
*
|
|
* @brief Get the factions long name (formal).
|
|
* @param f Faction to get the name of.
|
|
* @return The factions long name.
|
|
*/
|
|
char* faction_longname(int f) {
|
|
if((f < 0) || (f >= faction_nstack)) {
|
|
WARN("Faction id '%d' is invalid.", f);
|
|
return NULL;
|
|
}
|
|
if(faction_stack[f].longname != NULL)
|
|
return faction_stack[f].longname;
|
|
return faction_stack[f].name;
|
|
}
|
|
|
|
/**
|
|
* @fn glTexture* faction_logoSmall(int f)
|
|
*
|
|
* @brief Get the factions small logo.
|
|
* @param f Faction to get the logo of.
|
|
* @return The factions small logo image.
|
|
*/
|
|
glTexture* faction_logoSmall(int f) {
|
|
if((f < 0) || (f >= faction_nstack)) {
|
|
WARN("Faction id '%d' is invalid.", f);
|
|
return NULL;
|
|
}
|
|
return faction_stack[f].logo_small;
|
|
}
|
|
|
|
/**
|
|
* @fn static void faction_sanitizePlayer(Faction* faction)
|
|
*
|
|
* @brief Sanitizes player faction standing.
|
|
* @param faction Faction to sanitize.
|
|
*/
|
|
static void faction_sanitizePlayer(Faction* faction) {
|
|
if(faction->player > 100)
|
|
faction->player = 100;
|
|
else if(faction->player < -100)
|
|
faction->player = -100;
|
|
}
|
|
|
|
/**
|
|
* @fn void faction_modPlayer(int f, int mod)
|
|
*
|
|
* @brief Modifies the players standing with a faction.
|
|
*
|
|
* Affects enemies and allies too.
|
|
* @param f Faction to modify players standing.
|
|
* @param mod Modifier to modify by.
|
|
*
|
|
* @sa faction_modPlayerRaw
|
|
*/
|
|
void faction_modPlayer(int f, int mod) {
|
|
int i;
|
|
Faction* faction, *ally, *enemy;
|
|
|
|
if(!faction_isFaction(f)) {
|
|
WARN("%d is an invalid faction.", f);
|
|
return;
|
|
}
|
|
|
|
faction = &faction_stack[f];
|
|
|
|
/* Faction in question gets direct increment. */
|
|
faction->player += mod;
|
|
faction_sanitizePlayer(faction);
|
|
|
|
/* Now mod allies to a lesser degree. */
|
|
for(i = 0; i < faction->nallies; i++) {
|
|
ally = &faction_stack[faction->allies[i]];
|
|
|
|
ally->player += RNG(0, (mod*3)/4);
|
|
faction_sanitizePlayer(ally);
|
|
}
|
|
|
|
/* Now mod enemies. */
|
|
for(i = 0; i < faction->nenemies; i++) {
|
|
enemy = &faction_stack[faction->enemies[i]];
|
|
|
|
enemy->player -= MIN(1, RNG(0, (mod*3)/4));
|
|
faction_sanitizePlayer(enemy);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @fn void faction_modPlayerRaw(int f, int mod)
|
|
*
|
|
* @brief Modifies the players standing without affecting others.
|
|
* @param f Faction whose standing to modify.
|
|
* @param mod Amount to modify standing by.
|
|
*
|
|
* @sa faction_modPlayer
|
|
*/
|
|
void faction_modPlayerRaw(int f, int mod) {
|
|
Faction* faction;
|
|
|
|
if(!faction_isFaction(f)) {
|
|
WARN("%d is an invalid faction.", f);
|
|
return;
|
|
}
|
|
|
|
faction = &faction_stack[f];
|
|
|
|
faction->player += mod;
|
|
faction_sanitizePlayer(faction);
|
|
}
|
|
|
|
/**
|
|
* @fn int faction_getPlayer(int f)
|
|
*
|
|
* @brief Get the players standing with a faction.
|
|
* @param f Faction to get standing from.
|
|
* @return The standing the player has with the faction.
|
|
*/
|
|
int faction_getPlayer(int f) {
|
|
if(faction_isFaction(f)) {
|
|
return faction_stack[f].player;
|
|
} else {
|
|
WARN("%d is an invalid faction.", f);
|
|
return -1000;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @fn glColour* faction_getColour(int f)
|
|
*
|
|
* @brief Get the colour of the faction basewd on its standing with the player.
|
|
*
|
|
* Used to unify the colour checks all over.
|
|
* @param f Faction to get the colour of based on players standing.
|
|
* @return Pointer to the colour.
|
|
*/
|
|
glColour* faction_getColour(int f) {
|
|
if(f < 0) return &cInert;
|
|
else if(areAllies(FACTION_PLAYER, f)) return &cFriend;
|
|
else if(areEnemies(FACTION_PLAYER, f)) return &cHostile;
|
|
else return &cNeutral;
|
|
}
|
|
|
|
/**
|
|
* @fn char* faction_getStanding(int mod)
|
|
*
|
|
* @brief Get the players standing in human readable form.
|
|
* @param mod Players standing.
|
|
* @return Human readable players standing.
|
|
*/
|
|
static char* player_standings[] = {
|
|
"Hero", /* 0 */
|
|
"Admired",
|
|
"Great",
|
|
"Good",
|
|
"Decent",
|
|
"Wanted", /* 5 */
|
|
"Outlaw",
|
|
"Criminal",
|
|
"Enemy"
|
|
};
|
|
|
|
#define STANDING(m, i) if(mod >= m) return player_standings[i];
|
|
char* faction_getStanding(int mod) {
|
|
STANDING( 90, 0);
|
|
STANDING( 70, 1);
|
|
STANDING( 50, 2);
|
|
STANDING( 30, 3);
|
|
STANDING( 0, 4);
|
|
STANDING(-15, 5);
|
|
STANDING(-30, 6);
|
|
STANDING(-50, 7);
|
|
return player_standings[8];
|
|
|
|
}
|
|
#undef STANDING
|
|
|
|
/**
|
|
* @fn int areEnemies(int a, int b)
|
|
*
|
|
* @brief Check whether two factions are enemies.
|
|
* @param a Faction A.
|
|
* @param b Faction B.
|
|
* @return 1 if A and B are enemies, 0 otherwise.
|
|
*/
|
|
int areEnemies(int a, int b) {
|
|
Faction* fa, *fb;
|
|
int i = 0;
|
|
|
|
if(a == b) return 0; /* Luckily our factions aren't masochistic. */
|
|
|
|
/* Player handled seperately. */
|
|
if(a == FACTION_PLAYER) {
|
|
if(faction_isFaction(b)) {
|
|
if(faction_stack[b].player < 0)
|
|
return 1;
|
|
else return 0;
|
|
} else {
|
|
WARN("areEnemies: %d is an invalid faction.", b);
|
|
return 0;
|
|
}
|
|
}
|
|
if(b == FACTION_PLAYER) {
|
|
if(faction_isFaction(a)) {
|
|
if(faction_stack[a].player < 0)
|
|
return 1;
|
|
else return 0;
|
|
} else {
|
|
WARN("areEnemies: %d is an invalid faction.", a);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Handle a. */
|
|
if(faction_isFaction(a)) fa = &faction_stack[a];
|
|
else {
|
|
/* a isn't valid. */
|
|
WARN("areEnemies: %d is an invalid faction.", a);
|
|
return 0;
|
|
}
|
|
|
|
/* Handle b. */
|
|
if(faction_isFaction(b)) fb = &faction_stack[b];
|
|
else {
|
|
/* b isn't valid. */
|
|
WARN("areEnemies: %d is an invalid faction.", b);
|
|
return 0;
|
|
}
|
|
|
|
if(fa && fb) {
|
|
/* Both are factions. */
|
|
for(i = 0; i < fa->nenemies; i++)
|
|
if(fa->enemies[i] == b)
|
|
return 1;
|
|
for(i = 0; i < fb->nenemies; i++)
|
|
if(fb->enemies[i] == a)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @fn int areAllies(int a, int b)
|
|
*
|
|
* @brief Check whether two factions are allies or not.
|
|
* @param a Faction A.
|
|
* @param b Faction B.
|
|
* @return 1 if A and B are allies, 0 otherwise.
|
|
*/
|
|
int areAllies(int a, int b) {
|
|
Faction* fa, *fb;
|
|
int i;
|
|
|
|
/* We assume player becomes allies with high rating. */
|
|
if(a == FACTION_PLAYER) {
|
|
if(faction_isFaction(b)) {
|
|
if(faction_stack[b].player > PLAYER_ALLY) return 1;
|
|
else return 0;
|
|
} else {
|
|
WARN("%d is an invalid faction.", b);
|
|
return 0;
|
|
}
|
|
}
|
|
if(b == FACTION_PLAYER) {
|
|
if(faction_isFaction(a)) {
|
|
if(faction_stack[a].player > PLAYER_ALLY) return 1;
|
|
else return 0;
|
|
} else {
|
|
WARN("%d is an invalid faction.", a);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* D'aww. Player has no allies. */
|
|
if((a == FACTION_PLAYER) || (b == FACTION_PLAYER))
|
|
return 0;
|
|
|
|
/* Handle a. */
|
|
if(faction_isFaction(a)) fa = &faction_stack[a];
|
|
else {
|
|
/* b isn't valid. */
|
|
WARN("%d is an invalid faction.", a);
|
|
return 0;
|
|
}
|
|
|
|
/* Handle b. */
|
|
if(faction_isFaction(b)) fb = &faction_stack[b];
|
|
else {
|
|
/* b isn't valid. */
|
|
WARN("%d is an invalid faction.", b);
|
|
return 0;
|
|
}
|
|
|
|
if(a == b) return 0;
|
|
|
|
if(fa && fb) {
|
|
/* Both are factions. */
|
|
for(i = 0; i < fa->nallies; i++)
|
|
if(fa->allies[i] == b)
|
|
return 1;
|
|
|
|
for(i = 0; i < fb->nallies; i++)
|
|
if(fb->allies[i] == a)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @fn static int faction_isFaction(int f)
|
|
*
|
|
* @brief Check whether or not a faction is valid.
|
|
* @param f Faction to check for validity.
|
|
* @return 1 if faction is valid, 0 otherwise.
|
|
*/
|
|
static int faction_isFaction(int f) {
|
|
if((f < 0) || (f >= faction_nstack))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* @fn static Faction* faction_parse(xmlNodePtr parent)
|
|
*
|
|
* @brief Parses a single faction, but doesn't set the allies/enemies bit.
|
|
* @param parent Parent node to extract faction from.
|
|
* @return Faction created from parent node.
|
|
*/
|
|
static Faction* faction_parse(xmlNodePtr parent) {
|
|
xmlNodePtr node;
|
|
int player;
|
|
Faction* tmp;
|
|
char buf[PATH_MAX];
|
|
|
|
tmp = CALLOC_L(Faction);
|
|
|
|
tmp->name = xml_nodeProp(parent, "name");
|
|
if(tmp->name == NULL)
|
|
WARN("Faction from "FACTION_DATA" has invalid or no name");
|
|
|
|
player = 0;
|
|
node = parent->xmlChildrenNode;
|
|
do {
|
|
/* Can be 0 or negative, so we have to take that into account. */
|
|
if(xml_isNode(node, "player")) {
|
|
tmp->player = xml_getInt(node);
|
|
player = 1;
|
|
continue;
|
|
}
|
|
|
|
xmlr_strd(node, "longname", tmp->longname);
|
|
|
|
if(xml_isNode(node, "logo")) {
|
|
snprintf(buf, PATH_MAX, FACTION_LOGO_PATH"%s_small.png", xml_get(node));
|
|
tmp->logo_small = gl_newImage(buf);
|
|
continue;
|
|
}
|
|
} while(xml_nextNode(node));
|
|
|
|
if(player == 0) WARN("Faction '%s' missing player tag", tmp->name);
|
|
|
|
return tmp;
|
|
}
|
|
|
|
/* Parse the social tidbits: Allies and enemies. */
|
|
static void faction_parseSocial(xmlNodePtr parent) {
|
|
xmlNodePtr node, cur;
|
|
char* buf;
|
|
Faction* base;
|
|
int mod;
|
|
|
|
buf = xml_nodeProp(parent, "name");
|
|
base = &faction_stack[faction_get(buf)];
|
|
free(buf);
|
|
|
|
node = parent->xmlChildrenNode;
|
|
do {
|
|
if(xml_isNode(node, "allies")) {
|
|
cur = node->xmlChildrenNode;
|
|
do {
|
|
if(xml_isNode(cur, "ally")) {
|
|
mod = faction_get(xml_get(cur));
|
|
base->nallies++;
|
|
base->allies = realloc(base->allies, sizeof(int)*base->nallies);
|
|
base->allies[base->nallies-1] = mod;
|
|
}
|
|
} while(xml_nextNode(cur));
|
|
}
|
|
|
|
/* Grab the enemies. */
|
|
if(xml_isNode(node, "enemies")) {
|
|
cur = node->xmlChildrenNode;
|
|
do {
|
|
if(xml_isNode(cur, "enemy")) {
|
|
mod = faction_get(xml_get(cur));
|
|
base->nenemies++;
|
|
base->enemies = realloc(base->enemies, sizeof(int)*base->nenemies);
|
|
base->enemies[base->nenemies-1] = mod;
|
|
}
|
|
} while(xml_nextNode(cur));
|
|
}
|
|
} while(xml_nextNode(node));
|
|
}
|
|
|
|
/**
|
|
* @fn int factions_load(void)
|
|
*
|
|
* @brief Loads up all the factions from the data file.
|
|
* @return 0 on success.
|
|
*/
|
|
int factions_load(void) {
|
|
uint32_t bufsize;
|
|
char* buf = pack_readfile(DATA, FACTION_DATA, &bufsize);
|
|
|
|
xmlNodePtr factions, node;
|
|
xmlDocPtr doc = xmlParseMemory(buf, bufsize);
|
|
|
|
Faction* tmp = NULL;
|
|
|
|
node = doc->xmlChildrenNode; /* Faction node. */
|
|
if(!xml_isNode(node, XML_FACTION_ID)) {
|
|
ERR("Malformed "FACTION_DATA" file: missing root element '"XML_FACTION_ID"'");
|
|
return -1;
|
|
}
|
|
|
|
factions = node->xmlChildrenNode; /* First faction node. */
|
|
if(factions == NULL) {
|
|
ERR("Malformed "FACTION_DATA" file: does not contain elements");
|
|
return -1;
|
|
}
|
|
|
|
/* Player faction is hardcoded. */
|
|
faction_stack = malloc(sizeof(Faction));
|
|
memset(faction_stack, 0, sizeof(Faction));
|
|
faction_stack[0].name = strdup("Player");
|
|
faction_nstack++;
|
|
|
|
/* First pass. */
|
|
node = factions;
|
|
do {
|
|
if(xml_isNode(node, XML_FACTION_TAG)) {
|
|
tmp = faction_parse(node);
|
|
faction_stack = realloc(faction_stack, sizeof(Faction)*(++faction_nstack));
|
|
memcpy(faction_stack + faction_nstack - 1, tmp, sizeof(Faction));
|
|
free(tmp);
|
|
}
|
|
} while(xml_nextNode(node));
|
|
|
|
/* Second pass - Set allies and enemies. */
|
|
node = factions;
|
|
do {
|
|
if (xml_isNode(node,XML_FACTION_TAG))
|
|
faction_parseSocial(node);
|
|
} while (xml_nextNode(node));
|
|
|
|
xmlFreeDoc(doc);
|
|
free(buf);
|
|
xmlCleanupParser();
|
|
|
|
DEBUG("Loaded %d Faction%s", faction_nstack, (faction_nstack==1) ?"": "s");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @fn void factions_free(void)
|
|
*
|
|
* @brief Frees the factions.
|
|
*/
|
|
void factions_free(void) {
|
|
int i;
|
|
|
|
/* Free factions. */
|
|
for(i = 0; i < faction_nstack; i++) {
|
|
free(faction_stack[i].name);
|
|
if(faction_stack[i].longname != NULL) free(faction_stack[i].longname);
|
|
if(faction_stack[i].logo_small != NULL) gl_freeTexture(faction_stack[i].logo_small);
|
|
if(faction_stack[i].nallies > 0) free(faction_stack[i].allies);
|
|
if(faction_stack[i].nenemies > 0) free(faction_stack[i].enemies);
|
|
}
|
|
free(faction_stack);
|
|
faction_stack = NULL;
|
|
faction_nstack = 0;
|
|
}
|
|
|
|
/**
|
|
* @fn int pfaction_save(xmlTextWriterPtr writer)
|
|
*
|
|
* @brief Save players standings with the factions.
|
|
* @param writer The XML writer to use.
|
|
* @return 0 on success.
|
|
*/
|
|
int pfaction_save(xmlTextWriterPtr writer) {
|
|
int i;
|
|
|
|
xmlw_startElem(writer, "factions");
|
|
|
|
/* Player is faction 0. */
|
|
for(i = 1; i < faction_nstack; i++) {
|
|
xmlw_startElem(writer, "faction");
|
|
|
|
xmlw_attr(writer, "name", "%s", faction_stack[i].name);
|
|
xmlw_str(writer, "%d", faction_stack[i].player);
|
|
|
|
xmlw_endElem(writer); /* Faction. */
|
|
}
|
|
|
|
xmlw_endElem(writer); /* Faction. */
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @fn int pfaction_load(xmlNodePtr parent)
|
|
*
|
|
* @brief Load up the players faction standings.
|
|
* @param parent Parent xml node to read from.
|
|
* @return 0 on success.
|
|
*/
|
|
int pfaction_load(xmlNodePtr parent) {
|
|
xmlNodePtr node, cur;
|
|
char* str;
|
|
int faction;
|
|
|
|
node = parent->xmlChildrenNode;
|
|
|
|
do {
|
|
if(xml_isNode(node, "factions")) {
|
|
cur = node->xmlChildrenNode;
|
|
do {
|
|
if(xml_isNode(cur, "factions")) {
|
|
xmlr_attr(cur, "name", str);
|
|
faction = faction_get(str);
|
|
if(faction != -1) /* Faction is valid. */
|
|
faction_stack[faction].player = xml_getInt(cur);
|
|
free(str);
|
|
}
|
|
} while(xml_nextNode(cur));
|
|
}
|
|
} while(xml_nextNode(node));
|
|
|
|
return 0;
|
|
}
|
|
|