Lephisto/src/faction.c

466 lines
11 KiB
C

#include <malloc.h>
#include <string.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"
#define PLAYER_ALLY 70 /* Above this, player is considered ally. */
typedef struct Faction_ {
char* name; /* Normal name. */
char* longname; /* Long name. */
/* Enemies. */
int* enemies;
int nenemies;
/* Allies. */
int* allies;
int nallies;
int player; /* Standing with player - from -100 to 100. */
} Faction;
static Faction* faction_stack = NULL;
static int faction_nstack = 0;
/* 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);
/* Return the faction of name "name". */
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;
DEBUG("Faction '%s' not found in stack.", name);
return -1;
}
/* Return the faction's name. */
char* faction_name(int f) {
return faction_stack[f].name;
}
/* Return the faction's long name (formal). */
char* faction_longname(int f) {
if(faction_stack[f].longname != NULL)
return faction_stack[f].longname;
return faction_stack[f].name;
}
/* Sanitize player faction standing. */
static void faction_sanitizePlayer(Faction* faction) {
if(faction->player > 100)
faction->player = 100;
else if(faction->player < -100)
faction->player = -100;
}
/* Modify the player's standing with a faction. */
void faction_modPlayer(int f, int mod) {
int i;
Faction* faction, *ally, *enemy;
DEBUG("MOD: %d ALLY/ENEMY MOD: %d", mod, mod/2);
if(faction_isFaction(f)) {
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);
}
} else {
DEBUG("%d is an invalid faction.", f);
return;
}
}
/* Get the player's standing with a faction. */
int faction_getPlayer(int f) {
if(faction_isFaction(f)) {
return faction_stack[f].player;
} else {
DEBUG("%d is an invalid faction.", f);
return -1000;
}
}
/* Get the colour of the faction based on its standing with the player. */
glColour* faction_getColour(int f) {
if(f == -1) return &cInert;
else if(areAllies(FACTION_PLAYER, f)) return &cFriend;
else if(areEnemies(FACTION_PLAYER, f)) return &cHostile;
else return &cNeutral;
}
/* Return the 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
/* Return 1 if Faction a and b are enemies. */
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 {
DEBUG("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 {
DEBUG("areEnemies: %d is an invalid faction.", a);
return 0;
}
}
/* Handle a. */
if(faction_isFaction(a)) fa = &faction_stack[a];
else {
/* a isn't valid. */
DEBUG("areEnemies: %d is an invalid faction.", a);
return 0;
}
/* Handle b. */
if(faction_isFaction(b)) fb = &faction_stack[b];
else {
/* b isn't valid. */
DEBUG("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;
}
/* Return 1 if Faction a and b are allies. */
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 {
DEBUG("%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 {
DEBUG("%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. */
DEBUG("%d is an invalid faction.", a);
return 0;
}
/* Handle b. */
if(faction_isFaction(b)) fb = &faction_stack[b];
else {
/* b isn't valid. */
DEBUG("%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;
}
/* Return true if f is a faction. */
static int faction_isFaction(int f) {
if((f < 0) || (f >= faction_nstack))
return 0;
return 1;
}
/* Parses a single faction, but does not set the allies/enemies. */
static Faction* faction_parse(xmlNodePtr parent) {
xmlNodePtr node;
int player;
Faction* tmp;
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 {
if(xml_isNode(node, "player")) {
tmp->player = xml_getInt(node);
player = 1;
continue;
}
xmlr_strd(node, "longname", tmp->longname);
} while(xml_nextNode(node));
if(player == 0) DEBUG("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));
}
/* Load all the factions. */
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));
faction_stack[0].name = strdup("Player");
faction_stack[0].longname = NULL;
faction_stack[0].nallies = 0;
faction_stack[0].nenemies = 0;
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;
}
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].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;
}
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;
}
int pfaction_load(xmlNodePtr parent) {
xmlNodePtr node, cur;
char* str;
node = parent->xmlChildrenNode;
do {
if(xml_isNode(node, "factions")) {
cur = node->xmlChildrenNode;
do {
if(xml_isNode(cur, "factions")) {
xmlr_attr(cur, "name", str);
faction_stack[faction_get(str)].player = xml_getInt(cur);
free(str);
}
} while(xml_nextNode(cur));
}
} while(xml_nextNode(node));
return 0;
}