diff --git a/src/economy.c b/src/economy.c index ff75dba..ba3ee71 100644 --- a/src/economy.c +++ b/src/economy.c @@ -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); - commodity_stack = realloc(commodity_stack, - sizeof(Commodity)*(++commodity_nstack)); - memcpy(commodity_stack+commodity_nstack-1, tmp, sizeof(Commodity)); - free(tmp); + 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)); @@ -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; +} + diff --git a/src/economy.h b/src/economy.h index cc44583..8b46d4a 100644 --- a/src/economy.h +++ b/src/economy.h @@ -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); diff --git a/src/land.c b/src/land.c index 6e908d4..384d5bf 100644 --- a/src/land.c +++ b/src/land.c @@ -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); } diff --git a/src/lephisto.c b/src/lephisto.c index c74eb10..97a75a0 100644 --- a/src/lephisto.c +++ b/src/lephisto.c @@ -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(); diff --git a/src/ltime.c b/src/ltime.c index 25690d0..4918bef 100644 --- a/src/ltime.c +++ b/src/ltime.c @@ -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); } diff --git a/src/map.c b/src/map.c index 39f74c9..a9d4e98 100644 --- a/src/map.c +++ b/src/map.c @@ -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) diff --git a/src/player.c b/src/player.c index 643b002..c41b073 100644 --- a/src/player.c +++ b/src/player.c @@ -411,6 +411,9 @@ static int player_newMake(void) { /* Clear the map. */ map_clear(); + /* Start the economy. */ + economy_init(); + return 0; } diff --git a/src/save.c b/src/save.c index fd2fd06..b78fce3 100644 --- a/src/save.c +++ b/src/save.c @@ -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"); diff --git a/src/ship.c b/src/ship.c index 5f29bcc..df46f33 100644 --- a/src/ship.c +++ b/src/ship.c @@ -411,10 +411,11 @@ void ships_free(void) { int i; for(i = 0; i < ship_nstack; i++) { /* Free stored strings. */ - if(ship_stack[i].name) free(ship_stack[i].name); - 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].name) free(ship_stack[i].name); + 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. */ diff --git a/src/space.c b/src/space.c index e6cbd49..f2bee9b 100644 --- a/src/space.c +++ b/src/space.c @@ -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; } diff --git a/src/space.h b/src/space.h index b4d6fab..00a66d9 100644 --- a/src/space.h +++ b/src/space.h @@ -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;