[Add] Initial work on implementing a dynamic economy.

This commit is contained in:
Allanis 2014-04-11 02:08:28 +01:00
parent da58e41da9
commit 6285fb0629
11 changed files with 384 additions and 42 deletions

View File

@ -2,6 +2,8 @@
#include <string.h>
#include <stdint.h>
#include "cs.h"
#include "lephisto.h"
#include "xml.h"
#include "pack.h"
@ -9,18 +11,41 @@
#include "spfx.h"
#include "pilot.h"
#include "rng.h"
#include "space.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 30. /**< 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. */
/* 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 Commodity* commodity_parse(xmlNodePtr parent);
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. */
@ -51,11 +76,16 @@ Commodity* commodity_get(const char* name) {
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));
}
static Commodity* commodity_parse(xmlNodePtr parent) {
static int commodity_parse(Commodity* tmp, xmlNodePtr parent) {
xmlNodePtr node;
Commodity* tmp = CALLOC_L(Commodity);
/* Clear memory. */
memset(tmp, 0, sizeof(Commodity));
tmp->name = (char*)xmlGetProp(parent, (xmlChar*)"name");
if(tmp->name == NULL)
@ -65,9 +95,7 @@ static Commodity* commodity_parse(xmlNodePtr parent) {
do {
xmlr_strd(node, "description", tmp->description);
xmlr_strd(node, "high", tmp->high);
xmlr_strd(node, "medium", tmp->medium);
xmlr_strd(node, "low", tmp->low);
xmlr_int( node, "price", tmp->price);
} while((node = node->next));
#if 0 /* Let's kill this for now. */
#define MELEMENT(o,s)if(o)WARN("Commodity '%s' missing '"s"' element",tmp->name)
@ -78,7 +106,7 @@ static Commodity* commodity_parse(xmlNodePtr parent) {
#undef MELEMENT
#endif
return tmp;
return 0;
}
/* Throw cargo out into space (graphically). */
@ -119,8 +147,6 @@ int commodity_load(void) {
xmlNodePtr node;
xmlDocPtr doc = xmlParseMemory(buf, bufsize);
Commodity* tmp = NULL;
node = doc->xmlChildrenNode; /* Commoditys node. */
if(strcmp((char*)node->name, XML_COMMODITY_ID)) {
ERR("Malformed "COMMODITY_DATA
@ -135,13 +161,19 @@ int commodity_load(void) {
}
do {
if(node->type == XML_NODE_START) {
if(strcmp((char*)node->name, XML_COMMODITY_TAG)==0) {
tmp = commodity_parse(node);
if(xml_isNode(node, XML_COMMODITY_TAG)) {
/* Make room for commodity. */
commodity_stack = realloc(commodity_stack,
sizeof(Commodity)*(++commodity_nstack));
memcpy(commodity_stack+commodity_nstack-1, tmp, sizeof(Commodity));
free(tmp);
/* 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));
@ -164,3 +196,270 @@ void commodity_free(void) {
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 p;
Planet* planet;
/* Calculate production level. */
p = 0.;
for(i = 0; i < sys->nplanets; i++) {
planet = sys->planets[i];
if(planet_hasService(planet, PLANET_SERVICE_BASIC))
p += planet->prodfactor * sqrt(planet->population);
}
/* Intensity is the production minus the consumption. */
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. */
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. */
cs_entry(M, i, i, Rsum + 1./ECON_SELF_RES);
}
/* 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.
*/
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));
}
/* Create the resistance matrix. */
if(econ_createGMatrix())
return -1;
/* Initialize the prices. */
economy_update(0);
/* Mark economy as initialized. */
econ_initialized = 1;
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];
}
/*
* 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. / (max - min);
offset = 0.5 - min * scale;*/
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;
}

View File

@ -1,9 +1,9 @@
#pragma once
typedef struct Commodity_ {
char* name;
char* description;
unsigned int low, medium, high; /* Prices. */
char* name; /**< Name of the commodity. */
char* description; /**< Description of the commodity. */
double price; /**< Base price of the commodity. */
} Commodity;
/* Commidity stuff. */
@ -11,6 +11,12 @@ Commodity* commodity_get(const char* name);
int commodity_load(void);
void commodity_free(void);
/* Economy stuff. */
int economy_init(void);
int economy_update(unsigned int dt);
int economy_refresh(void);
void economy_destroy(void);
/* Misc. */
void credits2str(char* str, unsigned int credits, int decimals);
void commodity_Jettison(int pilot, Commodity* com, int quantity);

View File

@ -112,6 +112,9 @@ static void misn_update(unsigned int wid, char* str);
static void land_checkAddRefuel(void);
static unsigned int refuel_price(void);
static void spaceport_refuel(unsigned int wid, char* str);
/* External. */
static unsigned int economy_getPrice(const Commodity* com,
const StarSystem* sys, const Planet* p);
/* The local market. */
static void commodity_exchange_open(void) {
@ -182,7 +185,7 @@ static void commodity_update(unsigned int wid, char* str) {
"\n"
"%d tons\n",
player_cargoOwned(comname),
com->medium,
economy_getPrice(com, cur_system, land_planet),
pilot_cargoFree(player));
window_modifyText(wid, "txtDInfo", buf);
@ -193,13 +196,14 @@ static void commodity_buy(unsigned int wid, char* str) {
(void)str;
char* comname;
Commodity* com;
int q;
unsigned int q, price;
q = 10;
comname = toolkit_getList(wid, "lstGoods");
com = commodity_get(comname);
price = economy_getPrice(com, cur_system, land_planet);
if(player->credits < q * com->medium) {
if(player->credits < q * price) {
dialogue_alert("Not enough Scred!");
return;
}
@ -209,7 +213,7 @@ static void commodity_buy(unsigned int wid, char* str) {
}
q = pilot_addCargo(player, com, q);
player->credits -= q * com->medium;
player->credits -= q * price;
land_checkAddRefuel();
commodity_update(wid, NULL);
}
@ -218,14 +222,15 @@ static void commodity_sell(unsigned int wid, char* str) {
(void)str;
char* comname;
Commodity* com;
int q;
unsigned int q, price;
q = 10;
comname = toolkit_getList(wid, "lstGoods");
com = commodity_get(comname);
price = economy_getPrice(com, cur_system, land_planet);
q = pilot_rmCargo(player, com, q);
player->credits += q * com->medium;
player->credits += q * price;
land_checkAddRefuel();
commodity_update(wid, NULL);
}

View File

@ -255,7 +255,6 @@ int main(int argc, char** argv) {
gui_free(); /* Free up the gui. */
weapon_exit(); /* Destroy all active weapons. */
pilots_free(); /* Free the pilots, they where locked up D: */
space_exit(); /* Cleans up the universe itself. */
/* Unload data. */
unload_all();
@ -413,7 +412,9 @@ void load_all(void) {
* @brief Unloads all data, simplifies main().
*/
void unload_all(void) {
/* Data unloading - order should not matter, but inverse load_all is good. */
/* Data unloading - Inverse load_all is a good order. */
economy_destroy(); /* Must be called before space_exit. */
space_exit(); /* Cleans up the universe. */
fleet_free();
ships_free();
outfit_free();

View File

@ -3,6 +3,7 @@
#include "lephisto.h"
#include "hook.h"
#include "economy.h"
#include "ltime.h"
static unsigned int lephisto_time = 0;
@ -43,6 +44,8 @@ void ltime_inc(unsigned int t) {
lephisto_time += t;
hooks_run("time");
economy_update(t);
}

View File

@ -11,15 +11,15 @@
#include "colour.h"
#include "map.h"
#define MAP_WDWNAME "Star Map"
#define WINDOW_WIDTH 650
#define WINDOW_HEIGHT 540
#define MAP_WDWNAME "Star Map" /**< Map window name. */
#define WINDOW_WIDTH 650 /**< Map window width. */
#define WINDOW_HEIGHT 540 /**< Map window height. */
#define MAP_WIDTH (WINDOW_WIDTH-150)
#define MAP_HEIGHT (WINDOW_HEIGHT-100)
#define MAP_WIDTH (WINDOW_WIDTH-150) /**< Map window width. */
#define MAP_HEIGHT (WINDOW_HEIGHT-100) /**< Map height. */
#define BUTTON_WIDTH 60
#define BUTTON_HEIGHT 30
#define BUTTON_WIDTH 60 /**< Map button width. */
#define BUTTON_HEIGHT 30 /**< Map button height. */
static double map_zoom = 1.; /**< Zoom of the map. */
static double map_xpos = 0.; /**< Map X position. */
@ -252,6 +252,7 @@ static void map_update(unsigned int wid) {
for(i = 0; i < sys->nplanets; i++)
services |= sys->planets[i]->services;
buf[0] = '\0';
/*snprintf(buf, sizeof(buf), "%f\n", sys->prices[0]); */
if(services & PLANET_SERVICE_COMMODITY)
strcat(buf, "Commodity\n");
if(services & PLANET_SERVICE_OUTFITS)

View File

@ -411,6 +411,9 @@ static int player_newMake(void) {
/* Clear the map. */
map_clear();
/* Start the economy. */
economy_init();
return 0;
}

View File

@ -221,6 +221,9 @@ static int load_game(char* file) {
hook_load(node);
space_sysLoad(node);
/* Initialize the economy. */
economy_init();
/* Need to run takeoff hooks since player just "took off". */
hooks_run("takeoff");
hooks_run("enter");

View File

@ -415,6 +415,7 @@ void ships_free(void) {
if(ship_stack[i].description) free(ship_stack[i].description);
if(ship_stack[i].gui) free(ship_stack[i].gui);
if(ship_stack[i].fabricator) free(ship_stack[i].fabricator);
if(ship_stack[i].license != NULL) free(ship_stack[i].license);
so = ship_stack[i].outfit;
while(so) { /* free the ship outfit. */

View File

@ -712,16 +712,18 @@ static int planet_parse(Planet* planet, const xmlNodePtr parent) {
else if(xml_isNode(node, "general")) {
cur = node->children;
do {
/* Direct reads. */
xmlr_strd( cur, "bar", planet->bar_description);
xmlr_strd( cur, "description", planet->description);
xmlr_long( cur, "population", planet->population);
xmlr_float( cur, "prodfactor", planet->prodfactor);
if(xml_isNode(cur, "class"))
planet->class = planetclass_get(cur->children->content[0]);
else if(xml_isNode(cur, "faction")) {
flags |= FLAG_FACTIONSET;
planet->faction = faction_get(xml_get(cur));
}
else if(xml_isNode(cur, "description"))
planet->description = xml_getStrd(cur);
else if(xml_isNode(cur, "bar"))
planet->bar_description = xml_getStrd(cur);
else if(xml_isNode(cur, "services")) {
flags |= FLAG_SERVICESET;
planet->services = xml_getInt(cur); /* Flags gotten by data. */
@ -765,6 +767,10 @@ static int planet_parse(Planet* planet, const xmlNodePtr parent) {
MELEMENT(planet->gfx_space==NULL, "GFX space");
MELEMENT(planet_hasService(planet, PLANET_SERVICE_LAND) &&
planet->gfx_exterior==NULL, "GFX exterior");
MELEMENT(planet_hasService(planet, PLANET_SERVICE_BASIC) &&
(planet->population==0), "population");
MELEMENT(planet_hasService(planet, PLANET_SERVICE_BASIC) &&
(planet->prodfactor==0.), "prodfactor");
MELEMENT((flags&FLAG_XSET)==0, "x");
MELEMENT((flags&FLAG_YSET)==0, "y");
MELEMENT(planet->class==PLANET_CLASS_NULL, "class");
@ -824,6 +830,9 @@ int system_addPlanet(StarSystem* sys, char* planetname) {
system_setFaction(sys);
/* Regenerate the economy stuff. */
economy_refresh();
return 0;
}
@ -878,6 +887,9 @@ int system_rmPlanet(StarSystem* sys, char* planetname) {
system_setFaction(sys);
/* Regenerate economy stuff. */
economy_refresh();
return 0;
}

View File

@ -61,9 +61,14 @@ typedef struct Planet_ {
char* name; /**< Planet name */
Vec2 pos; /**< Position in star system. */
/* Planet details. */
PlanetClass class; /**< Planet type. */
int faction; /**< Planet faction. */
int population; /**< Population of the planet. */
double prodfactor; /**< Production factor of the planet. */
/* Landing details. */
char* description; /**< Planet description. */
char* bar_description; /**< Spaceport bar description. */
unsigned int services; /**< Offered services. */
@ -75,6 +80,7 @@ typedef struct Planet_ {
tech[1-PLANET_TECH_MAX] store the
"unique" tech levels (only matches) */
/* Graphics. */
glTexture* gfx_space; /**< Graphics in space. */
char* gfx_exterior; /**< Don't actually load the texture. */
} Planet;
@ -127,6 +133,8 @@ typedef struct StarSystem_ {
double nebu_density; /**< Nebulae density (0. - 1000.).*/
double nebu_volatility; /**< Nebulae volatility (0. - 1000.). */
double* prices; /**< Handles the prices in the system. */
unsigned int flags; /**< Flags for system properties. */
} StarSystem;