Lephisto/src/economy.c

505 lines
13 KiB
C

/**
* @file economy.c
*
* @brief Handles economy stuff.
*
* Economy is handled with Nodal Analysis. Systems are modelled as nodes,
* jump routes are resistances and production is modeled as node intensity.
* This is then solved with linear algebra after each time increment.
*/
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "cs.h"
#include "lephisto.h"
#include "lxml.h"
#include "ldata.h"
#include "log.h"
#include "spfx.h"
#include "pilot.h"
#include "rng.h"
#include "space.h"
#include "ltime.h"
#include "economy.h"
#define XML_COMMODITY_ID "Commodities" /* XML section identifier. */
#define XML_COMMODITY_TAG "commodity"
#define COMMODITY_DATA "../dat/commodity.xml"
/* Economy Nodal Analysis parameters. */
#define ECON_BASE_RES 30. /**< Base resistance value for any system. */
#define ECON_SELF_RES 3. /**< Additional resistance for the self node. */
#define ECON_FACTION_MOD 0.1 /**< Modifier on Base for faction standings. */
#define ECON_PROD_MODIFIER 500000. /**< Production modifier, divide production by this amount. */
#define ECON_PROD_VAR 0.01 /**< Define the variability of production. */
/* Commodity stack. */
static Commodity* commodity_stack = NULL;
static int commodity_nstack = 0;
/* Systems stack. */
extern StarSystem* systems_stack; /**< Star system stack. */
extern int systems_nstack; /**< Number of star systems. */
/* Nodal analysis simulation for dynamic economies. */
static int econ_initialized = 0; /**< Is economy system initialized? */
static int* econ_comm = NULL; /**< Commodities to calculate. */
static int econ_nprices = 0; /**< Number of prices to calculate. */
static cs* econ_G = NULL; /**< Admittance matrix. */
/* Commodity. */
static void commodity_freeOne(Commodity* com);
static int commodity_parse(Commodity* tmp, xmlNodePtr parent);
/* Economy. */
static double econ_calcJumpR(StarSystem* A, StarSystem* B);
static int econ_createGMatrix(void);
unsigned int economy_getPrice(const Commodity* com,
const StarSystem* sys, const Planet* p); /* Externed in land.c. */
/* Convert credits to a usable string for displaying. */
/* str must have 10 characters allocated. */
void credits2str(char* str, unsigned int credits, int decimals) {
if(decimals < 0)
snprintf(str, 32, "%d", credits);
else if(credits >= 1000000000)
snprintf(str, 16, "%.*fB", decimals, (double)credits / 1000000000.);
else if(credits >= 1000000)
snprintf(str, 16, "%*fM", decimals, (double)credits / 1000000.);
else if(credits >= 1000)
snprintf(str, 16, "%.*fK", decimals, (double)credits / 1000.);
else snprintf(str, 16, "%d", credits);
}
/* Get a commodity. */
Commodity* commodity_get(const char* name) {
int i;
for(i = 0; i < commodity_nstack; i++)
if(strcmp(commodity_stack[i].name, name)==0)
return &commodity_stack[i];
WARN("Commodity '%s' not found in stack", name);
return NULL;
}
/* Free a commodity. */
static void commodity_freeOne(Commodity* com) {
if(com->name) free(com->name);
if(com->description) free(com->description);
/* Clear the memory. */
memset(com, 0, sizeof(Commodity));
}
/**
* @brief Loads a commodity.
* @param tmp Commodity to load data into.
* @param parent XML node to load from.
* @return Commodity loaded from parent.
*/
static int commodity_parse(Commodity* tmp, xmlNodePtr parent) {
xmlNodePtr node;
/* Clear memory. */
memset(tmp, 0, sizeof(Commodity));
tmp->name = (char*)xmlGetProp(parent, (xmlChar*)"name");
if(tmp->name == NULL)
WARN("Commodity from "COMMODITY_DATA" has invalid or noname");
node = parent->xmlChildrenNode;
do {
xmlr_strd(node, "description", tmp->description);
xmlr_int( node, "price", tmp->price);
} while(xml_nextNode(node));
#if 0 /* Let's kill this for now. */
#define MELEMENT(o,s)if(o)WARN("Commodity '%s' missing '"s"' element",tmp->name)
MELEMENT(tmp->high==0, "high");
MELEMENT(tmp->description==NULL, "description");
MELEMENT(tmp->medium==0, "medium");
MELEMENT(tmp->low==0, "low");
#undef MELEMENT
#endif
return 0;
}
/* Throw cargo out into space (graphically). */
void commodity_Jettison(int pilot, Commodity* com, int quantity) {
(void)com;
int i;
Pilot* p;
int n, effect;
double px, py, bvx, bvy, r, a, vx, vy;
p = pilot_get(pilot);
n = MAX(1, RNG(quantity/10, quantity/5));
px = p->solid->pos.x;
py = p->solid->pos.y;
bvx = p->solid->vel.x;
bvy = p->solid->vel.y;
for(i = 0; i < n; i++) {
effect = spfx_get("cargo");
/* Radial distribution gives much nicer results. */
r = RNGF()*25 - 12.5;
a = (double)RNG(0,259);
vx = bvx + r*cos(a);
vy = bvy + r*sin(a);
/* Add the cargo effect. */
spfx_add(effect, px, py, vx, vy, SPFX_LAYER_BACK);
}
}
/* Init/exit */
int commodity_load(void) {
uint32_t bufsize;
char* buf = ldata_read(COMMODITY_DATA, &bufsize);
xmlNodePtr node;
xmlDocPtr doc = xmlParseMemory(buf, bufsize);
node = doc->xmlChildrenNode; /* Commoditys node. */
if(strcmp((char*)node->name, XML_COMMODITY_ID)) {
ERR("Malformed "COMMODITY_DATA
" file: Missing root element '"XML_COMMODITY_ID"'");
return -1;
}
node = node->xmlChildrenNode; /* First faction node. */
if(node == NULL) {
ERR("Malformed "COMMODITY_DATA" file: does not contain elements");
return -1;
}
do {
if(xml_isNode(node, XML_COMMODITY_TAG)) {
/* Make room for commodity. */
commodity_stack = realloc(commodity_stack,
sizeof(Commodity)*(++commodity_nstack));
/* Load commodity. */
commodity_parse(&commodity_stack[commodity_nstack-1], node);
/* See if it should get added to commodity list. */
if(commodity_stack[commodity_nstack-1].price > 0.) {
econ_nprices++;
econ_comm = realloc(econ_comm, econ_nprices * sizeof(int));
econ_comm[econ_nprices-1] = commodity_nstack-1;
}
}
} while((node = node->next));
xmlFreeDoc(doc);
free(buf);
DEBUG("Loaded %d commodit%s",
commodity_nstack, (commodity_nstack==1) ? "y" : "ies");
return 0;
}
void commodity_free(void) {
int i;
for(i = 0; i < commodity_nstack; i++)
commodity_freeOne(&commodity_stack[i]);
free(commodity_stack);
commodity_stack = NULL;
commodity_nstack = 0;
}
/**
* @brief Get the price of a good on a planet in a system.
* @param com Commodity to get price of.
* @param sys System to get price of commodity.
* @param p Planet to get price of commodity.
* @return The price of the commodity.
*/
unsigned int economy_getPrice(const Commodity* com,
const StarSystem* sys, const Planet* p) {
(void)p;
int i, k;
double price;
/* Get position in stack. */
k = com - commodity_stack;
/* Find what commodity that is. */
for(i = 0; i < econ_nprices; i++)
if(econ_comm[i] == k)
break;
/* Check if found. */
if(i >= econ_nprices) {
WARN("Price for commodity '%s' not known.", com->name);
return 0;
}
/* Calculate price. */
price = (double)com->price;
price *= sys->prices[i];
return (unsigned int) price;
}
/**
* @brief Calculates the resistance between two star systems.
* @param A Star system to calculate the resistance between.
* @param B Star system to calculate the resistance between.
* @return Resistance between A and B.
*/
static double econ_calcJumpR(StarSystem* A, StarSystem* B) {
double R;
/* Set to base to ensure price change. */
R = ECON_BASE_RES;
/* Modify based on system conditions. */
R += (A->nebu_density + B->nebu_density) / 1000.; /* Density shouldn't affect much. */
R += (A->nebu_volatility + B->nebu_volatility) / 100.; /* Volatility should. */
/* Modify based on global faction. */
if((A->faction != -1) && (B->faction != -1)) {
if(areEnemies(A->faction, B->faction))
R += ECON_FACTION_MOD * ECON_BASE_RES;
else if(areAllies(A->faction, B->faction))
R -= ECON_FACTION_MOD * ECON_BASE_RES;
}
/* @todo Modify based on fleets. */
return R;
}
/**
* @brief Calculates the intensity in a system node.
*
* @todo Make it time/item dependent.
*/
static double econ_calcSysI(unsigned int dt, StarSystem* sys, int price) {
(void)dt;
(void)price;
int i;
double I;
double prodfactor, p, pp;
double ddt;
Planet* planet;
ddt = (double)(dt / LTIME_UNIT_LENGTH);
/* Calculate production level. */
p = 0.;
for(i = 0; i < sys->nplanets; i++) {
planet = sys->planets[i];
if(planet_hasService(planet, PLANET_SERVICE_BASIC)) {
/* Calculate production.
*
* We base off the current production.
*/
prodfactor = planet->cur_prodfactor;
/* Add a variability factor based on the gaussian distribution. */
prodfactor += ECON_PROD_VAR *
NormalInverse(RNGF()*0.90 + 0.05)*ddt;
/* Add a tendency to return to the planets base production. */
prodfactor -= ECON_PROD_VAR *
(planet->cur_prodfactor - prodfactor)*ddt;
/* Save for next iteration. */
planet->cur_prodfactor = prodfactor;
/* We base off the sqrt of the population otherwise it changes too fast. */
p += prodfactor * sqrt(planet->population);
/* Add it to the current system production. */
p += pp;
}
}
/* The intensity is basically the modified production. */
I = p / ECON_PROD_MODIFIER;
return I;
}
/**
* @brief Create the admitance matrix.
* @return 0 on suceess.
*/
static int econ_createGMatrix(void) {
int ret;
int i, j;
double R, Rsum;
cs* M;
StarSystem* sys;
/* Create the matrix. */
M = cs_spalloc(systems_nstack, systems_nstack, 1, 1, 1);
if(M == NULL)
ERR("Unable to create CSparse Matrix.");
/* Fill the matrix. */
for(i = 0; i < systems_nstack; i++) {
sys = &systems_stack[i];
Rsum = 0.;
/* Set some values. */
for(j = 0; j < sys->njumps; j++) {
/* Get the resistance. */
R = econ_calcJumpR(sys, &systems_stack[sys->jumps[j]]);
R = 1./R; /* Must be inverted. */
Rsum += R;
/* Matrix is symetrical and non-diagonal is negative. */
ret = cs_entry(M, i, sys->jumps[j], -R);
if(ret != 1)
WARN("Unable to enter CSparse Matrix Cell.");
ret = cs_entry(M, sys->jumps[j], i, -R);
if(ret != 1)
WARN("Unable to enter CSparse Matrix Cell.");
}
/* Set the diagonal. */
Rsum += 1./ECON_SELF_RES; /* We add a resistence for dampening. */
cs_entry(M, i, i, Rsum);
}
/* Compress M matrix and put into G. */
if(econ_G != NULL)
cs_spfree(econ_G);
econ_G = cs_compress(M);
if(econ_G == NULL)
ERR("Unable to create economy G Matrix.");
/* Clean up. */
cs_spfree(M);
return 0;
}
/**
* @brief Initializes the economy.
* @return 0 on success.
*/
int economy_init(void) {
int i;
/* Allocate price space. */
for(i = 0; i < systems_nstack; i++) {
if(systems_stack[i].prices != NULL)
free(systems_stack[i].prices);
systems_stack[i].prices = calloc(econ_nprices, sizeof(double));
}
/* Mark economy as initialized. */
econ_initialized = 1;
/* Refresh economy. */
economy_refresh();
return 0;
}
/**
* @brief Regenerates the economy matrix. Should be used if the universe
* changes in any permanent way.
*/
int economy_refresh(void) {
/* Economy must be initialized. */
if(econ_initialized == 0)
return 0;
/* Create the resistance matrix. */
if(econ_createGMatrix())
return -1;
/* Initialize the prices. */
economy_update(0);
return 0;
}
/**
* @brief Updates the economy.
* @param dt Deltatick in LTIME.
*/
int economy_update(unsigned int dt) {
int ret;
int i, j;
double *X;
double scale, offset;
/*double min, max;*/
/* Create the vector to solve the system. */
X = malloc(sizeof(double)*systems_nstack);
if(X == NULL)
return -1;
/* Calculate the results for each price set. */
for(j = 0; j < econ_nprices; j++) {
/* First we must load the vector with intensities. */
for(i = 0; i < systems_nstack; i++)
X[i] = econ_calcSysI(dt, &systems_stack[i], j);
/* Solve the system. */
ret = cs_lsolve(econ_G, X);
if(ret != 1)
WARN("Failed to solve the Economy System.");
/* Get the minimum and maximum to scale. */
/*
min = +HUGE_VALF;
max = -HUGE_VALF;
for(i = 0; i < systems_nstack; i++) {
if(X[i] < min)
min = X[i];
if(X[i] > max)
max = X[i];
}
scale = 1. / (max - min);
offset = 0.5 - min * scale;
*/
/*
* I'm not sure I like the filtering of the results, but it would take
* much more work to get a sane system working without the need of post
* filtering.
*/
scale = 1.;
offset = 1.;
for(i = 0; i < systems_nstack; i++) {
systems_stack[i].prices[j] = X[i] * scale + offset;
}
}
/* Clean up. */
free(X);
return 0;
}
/**
* @brief Destroys the economy.
*/
void economy_destroy(void) {
int i;
/* Clean up the prices in the systems stack. */
for(i = 0; i < systems_nstack; i++) {
if(systems_stack[i].prices != NULL) {
free(systems_stack[i].prices);
systems_stack[i].prices = NULL;
}
}
/* Destroy the economy matrix. */
if(econ_G != NULL) {
cs_spfree(econ_G);
econ_G = NULL;
}
/* Economy is now deinitialized. */
econ_initialized = 0;
}