Merge branch 'master' of git://github.com/BTAxis/naev into testmission
[naev.git] / src / economy.c
blob48aa8a5a49ea8d1b6fb668d85fb11ef874689fc5
1 /*
2 * See Licensing and Copyright notice in naev.h
3 */
5 /**
6 * @file economy.c
8 * @brief Handles economy stuff.
10 * Economy is handled with Nodal Analysis. Systems are modelled as nodes,
11 * jump routes are resistances and production is modeled as node intensity.
12 * This is then solved with linear algebra after each time increment.
16 #include "economy.h"
18 #include "naev.h"
20 #include <stdio.h>
21 #include <string.h>
22 #include <stdint.h>
24 #include "cs.h"
26 #include "nxml.h"
27 #include "ndata.h"
28 #include "log.h"
29 #include "spfx.h"
30 #include "pilot.h"
31 #include "rng.h"
32 #include "space.h"
33 #include "ntime.h"
36 #define XML_COMMODITY_ID "Commodities" /**< XML document identifier */
37 #define XML_COMMODITY_TAG "commodity" /**< XML commodity identifier. */
38 #define COMMODITY_DATA "dat/commodity.xml" /**< Comodity XML file. */
42 * Economy Nodal Analysis parameters.
44 #define ECON_BASE_RES 30. /**< Base resistance value for any system. */
45 #define ECON_SELF_RES 3. /**< Additional resistance for the self node. */
46 #define ECON_FACTION_MOD 0.1 /**< Modifier on Base for faction standings. */
47 #define ECON_PROD_MODIFIER 500000. /**< Production modifier, divide production by this amount. */
48 #define ECON_PROD_VAR 0.01 /**< Defines the variability of production. */
51 /* commodity stack */
52 static Commodity* commodity_stack = NULL; /**< Contains all the commodities. */
53 static int commodity_nstack = 0; /**< Number of commodities in the stack. */
56 /* systems stack. */
57 extern StarSystem *systems_stack; /**< Star system stack. */
58 extern int systems_nstack; /**< Number of star systems. */
62 * Nodal analysis simulation for dynamic economies.
64 static int econ_initialized = 0; /**< Is economy system initialized? */
65 static int *econ_comm = NULL; /**< Commodities to calculate. */
66 static int econ_nprices = 0; /**< Number of prices to calculate. */
67 static cs *econ_G = NULL; /**< Admittance matrix. */
71 * Prototypes.
73 /* Commodity. */
74 static void commodity_freeOne( Commodity* com );
75 static int commodity_parse( Commodity *temp, xmlNodePtr parent );
76 /* Economy. */
77 static double econ_calcJumpR( StarSystem *A, StarSystem *B );
78 static int econ_createGMatrix (void);
79 unsigned int economy_getPrice( const Commodity *com,
80 const StarSystem *sys, const Planet *p ); /* externed in land.c */
83 /**
84 * @brief Converts credits to a usable string for displaying.
86 * @param[out] str Output is stored here, must have at least a length of 10
87 * char.
88 * @param credits Credits to display.
89 * @param decimals Decimals to use.
91 void credits2str( char *str, uint64_t credits, int decimals )
93 if (decimals < 0)
94 snprintf( str, 32, "%"PRIu64, credits );
95 else if (credits >= 1000000000000000LLU)
96 snprintf( str, 16, "%.*fQ", decimals, (double)credits / 1000000000000000. );
97 else if (credits >= 1000000000000LLU)
98 snprintf( str, 16, "%.*fT", decimals, (double)credits / 1000000000000. );
99 else if (credits >= 1000000000LU)
100 snprintf( str, 16, "%.*fB", decimals, (double)credits / 1000000000. );
101 else if (credits >= 1000000U)
102 snprintf( str, 16, "%.*fM", decimals, (double)credits / 1000000. );
103 else if (credits >= 1000U)
104 snprintf( str, 16, "%.*fK", decimals, (double)credits / 1000. );
105 else
106 snprintf (str, 16, "%"PRIu64, credits );
111 * @brief Gets a commoditiy by name.
113 * @param name Name to match.
114 * @return Commodity matching name.
116 Commodity* commodity_get( const char* name )
118 int i;
119 for (i=0; i<commodity_nstack; i++)
120 if (strcmp(commodity_stack[i].name,name)==0)
121 return &commodity_stack[i];
123 WARN("Commodity '%s' not found in stack", name);
124 return NULL;
129 * @brief Frees a commodity.
131 * @param com Commodity to free.
133 static void commodity_freeOne( Commodity* com )
135 if (com->name)
136 free(com->name);
137 if (com->description)
138 free(com->description);
140 /* Clear the memory. */
141 memset(com, 0, sizeof(Commodity));
146 * @brief Loads a commodity.
148 * @param temp Commodity to load data into.
149 * @param parent XML node to load from.
150 * @return Commodity loaded from parent.
152 static int commodity_parse( Commodity *temp, xmlNodePtr parent )
154 xmlNodePtr node;
156 /* Clear memory. */
157 memset( temp, 0, sizeof(Commodity) );
159 temp->name = (char*)xmlGetProp(parent,(xmlChar*)"name");
160 if (temp->name == NULL) WARN("Commodity from "COMMODITY_DATA" has invalid or no name");
162 node = parent->xmlChildrenNode;
164 do {
165 xmlr_strd(node, "description", temp->description);
166 xmlr_int(node, "price", temp->price);
167 } while (xml_nextNode(node));
169 #if 0 /* shouldn't be needed atm */
170 #define MELEMENT(o,s) if (o) WARN("Commodity '%s' missing '"s"' element", temp->name)
171 MELEMENT(temp->description==NULL,"description");
172 MELEMENT(temp->high==0,"high");
173 MELEMENT(temp->medium==0,"medium");
174 MELEMENT(temp->low==0,"low");
175 #undef MELEMENT
176 #endif
178 return 0;
183 * @brief Throws cargo out in space graphically.
185 * @param pilot ID of the pilot throwing the stuff out
186 * @param com Commodity to throw out.
187 * @param quantity Quantity thrown out.
189 void commodity_Jettison( int pilot, Commodity* com, int quantity )
191 (void)com;
192 int i;
193 Pilot* p;
194 int n, effect;
195 double px,py, bvx, bvy, r,a, vx,vy;
197 p = pilot_get( pilot );
199 n = MAX( 1, RNG(quantity/10, quantity/5) );
200 px = p->solid->pos.x;
201 py = p->solid->pos.y;
202 bvx = p->solid->vel.x;
203 bvy = p->solid->vel.y;
204 for (i=0; i<n; i++) {
205 effect = spfx_get("cargo");
207 /* Radial distribution gives much nicer results */
208 r = RNGF()*25 - 12.5;
209 a = 2. * M_PI * RNGF();
210 vx = bvx + r*cos(a);
211 vy = bvy + r*sin(a);
213 /* Add the cargo effect */
214 spfx_add( effect, px, py, vx, vy, SPFX_LAYER_BACK );
220 * @brief Loads all the commodity data.
222 * @return 0 on success.
224 int commodity_load (void)
226 uint32_t bufsize;
227 char *buf;
228 xmlNodePtr node;
229 xmlDocPtr doc;
231 /* Load the file. */
232 buf = ndata_read( COMMODITY_DATA, &bufsize);
233 if (buf == NULL)
234 return -1;
236 /* Handle the XML. */
237 doc = xmlParseMemory( buf, bufsize );
238 if (doc == NULL) {
239 WARN("'%s' is not valid XML.", COMMODITY_DATA);
240 return -1;
243 node = doc->xmlChildrenNode; /* Commoditys node */
244 if (strcmp((char*)node->name,XML_COMMODITY_ID)) {
245 ERR("Malformed "COMMODITY_DATA" file: missing root element '"XML_COMMODITY_ID"'");
246 return -1;
249 node = node->xmlChildrenNode; /* first faction node */
250 if (node == NULL) {
251 ERR("Malformed "COMMODITY_DATA" file: does not contain elements");
252 return -1;
255 do {
256 if (xml_isNode(node, XML_COMMODITY_TAG)) {
258 /* Make room for commodity. */
259 commodity_stack = realloc(commodity_stack,
260 sizeof(Commodity)*(++commodity_nstack));
262 /* Load commodity. */
263 commodity_parse(&commodity_stack[commodity_nstack-1], node);
265 /* See if should get added to commodity list. */
266 if (commodity_stack[commodity_nstack-1].price > 0.) {
267 econ_nprices++;
268 econ_comm = realloc(econ_comm, econ_nprices * sizeof(int));
269 econ_comm[econ_nprices-1] = commodity_nstack-1;
272 } while (xml_nextNode(node));
274 xmlFreeDoc(doc);
275 free(buf);
277 DEBUG("Loaded %d Commodit%s", commodity_nstack, (commodity_nstack==1) ? "y" : "ies" );
279 return 0;
286 * @brief Frees all the loaded commodities.
288 void commodity_free (void)
290 int i;
291 for (i=0; i<commodity_nstack; i++)
292 commodity_freeOne( &commodity_stack[i] );
293 free( commodity_stack );
294 commodity_stack = NULL;
295 commodity_nstack = 0;
300 * @brief Gets the price of a good on a planet in a system.
302 * @param com Commodity to get price of.
303 * @param sys System to get price of commodity.
304 * @param p Planet to get price of commodity.
305 * @return The price of the commodity.
307 unsigned int economy_getPrice( const Commodity *com,
308 const StarSystem *sys, const Planet *p )
310 (void) p;
311 int i, k;
312 double price;
314 /* Get position in stack. */
315 k = com - commodity_stack;
317 /* Find what commodity that is. */
318 for (i=0; i<econ_nprices; i++)
319 if (econ_comm[i] == k)
320 break;
322 /* Check if found. */
323 if (i >= econ_nprices) {
324 WARN("Price for commodity '%s' not known.", com->name);
325 return 0;
328 /* Calculate price. */
329 price = (double) com->price;
330 price *= sys->prices[i];
331 return (unsigned int) price;
336 * @brief Calculates the resistance between two star systems.
338 * @param A Star system to calculate the resistance between.
339 * @param B Star system to calculate the resistance between.
340 * @return Resistance between A and B.
342 static double econ_calcJumpR( StarSystem *A, StarSystem *B )
344 double R;
346 /* Set to base to ensure price change. */
347 R = ECON_BASE_RES;
349 /* Modify based on system conditions. */
350 R += (A->nebu_density + B->nebu_density) / 1000.; /* Density shouldn't affect much. */
351 R += (A->nebu_volatility + B->nebu_volatility) / 100.; /* Volatility should. */
353 /* Modify based on global faction. */
354 if ((A->faction != -1) && (B->faction != -1)) {
355 if (areEnemies(A->faction, B->faction))
356 R += ECON_FACTION_MOD * ECON_BASE_RES;
357 else if (areAllies(A->faction, B->faction))
358 R -= ECON_FACTION_MOD * ECON_BASE_RES;
361 /* @todo Modify based on fleets. */
363 return R;
368 * @brief Calculates the intensity in a system node.
370 * @todo Make it time/item dependent.
372 static double econ_calcSysI( unsigned int dt, StarSystem *sys, int price )
374 (void) price;
375 int i;
376 double I;
377 double prodfactor, p;
378 double ddt;
379 Planet *planet;
381 ddt = (double)(dt / NTIME_UNIT_LENGTH);
383 /* Calculate production level. */
384 p = 0.;
385 for (i=0; i<sys->nplanets; i++) {
386 planet = sys->planets[i];
387 if (planet_hasService(planet, PLANET_SERVICE_INHABITED)) {
389 * Calculate production.
391 /* We base off the current production. */
392 prodfactor = planet->cur_prodfactor;
393 /* Add a variability factor based on the gaussian distribution. */
394 prodfactor += ECON_PROD_VAR * RNG_2SIGMA() * ddt;
395 /* Add a tendency to return to the planet's base production. */
396 prodfactor -= ECON_PROD_VAR *
397 (planet->cur_prodfactor - prodfactor)*ddt;
398 /* Save for next iteration. */
399 planet->cur_prodfactor = prodfactor;
400 /* We base off the sqrt of the population otherwise it changes too fast. */
401 p += prodfactor * sqrt(planet->population);
405 /* The intensity is basically the modified production. */
406 I = p / ECON_PROD_MODIFIER;
408 return I;
413 * @brief Creates the admittance matrix.
415 * @return 0 on success.
417 static int econ_createGMatrix (void)
419 int ret;
420 int i, j;
421 double R, Rsum;
422 cs *M;
423 StarSystem *sys;
425 /* Create the matrix. */
426 M = cs_spalloc( systems_nstack, systems_nstack, 1, 1, 1 );
427 if (M == NULL)
428 ERR("Unable to create CSparse Matrix.");
430 /* Fill the matrix. */
431 for (i=0; i < systems_nstack; i++) {
432 sys = &systems_stack[i];
433 Rsum = 0.;
435 /* Set some values. */
436 for (j=0; j < sys->njumps; j++) {
438 /* Get the resistances. */
439 R = econ_calcJumpR( sys, &systems_stack[sys->jumps[j]] );
440 R = 1./R; /* Must be inverted. */
441 Rsum += R;
443 /* Matrix is symetrical and non-diagonal is negative. */
444 ret = cs_entry( M, i, sys->jumps[j], -R );
445 if (ret != 1)
446 WARN("Unable to enter CSparse Matrix Cell.");
447 ret = cs_entry( M, sys->jumps[j], i, -R );
448 if (ret != 1)
449 WARN("Unable to enter CSparse Matrix Cell.");
452 /* Set the diagonal. */
453 Rsum += 1./ECON_SELF_RES; /* We add a resistence for dampening. */
454 cs_entry( M, i, i, Rsum );
457 /* Compress M matrix and put into G. */
458 if (econ_G != NULL)
459 cs_spfree( econ_G );
460 econ_G = cs_compress( M );
461 if (econ_G == NULL)
462 ERR("Unable to create economy G Matrix.");
464 /* Clean up. */
465 cs_spfree(M);
467 return 0;
472 * @brief Initializes the economy.
474 * @return 0 on success.
476 int economy_init (void)
478 int i;
480 /* Must not be initialized. */
481 if (econ_initialized)
482 return 0;
484 /* Allocate price space. */
485 for (i=0; i<systems_nstack; i++) {
486 if (systems_stack[i].prices != NULL)
487 free(systems_stack[i].prices);
488 systems_stack[i].prices = calloc(econ_nprices, sizeof(double));
491 /* Mark economy as initialized. */
492 econ_initialized = 1;
494 /* Refresh economy. */
495 economy_refresh();
497 return 0;
502 * @brief Regenerates the economy matrix. Should be used if the universe
503 * changes in any permanent way.
505 int economy_refresh (void)
507 /* Economy must be initialized. */
508 if (econ_initialized == 0)
509 return 0;
511 /* Create the resistence matrix. */
512 if (econ_createGMatrix())
513 return -1;
515 /* Initialize the prices. */
516 economy_update( 0 );
518 return 0;
523 * @brief Updates the economy.
525 * @param dt Deltatick in NTIME.
527 int economy_update( unsigned int dt )
529 int ret;
530 int i, j;
531 double *X;
532 double scale, offset;
533 /*double min, max;*/
535 /* Economy must be initialized. */
536 if (econ_initialized == 0)
537 return 0;
539 /* Create the vector to solve the system. */
540 X = malloc(sizeof(double)*systems_nstack);
541 if (X == NULL) {
542 WARN("Out of Memory!");
543 return -1;
546 /* Calculate the results for each price set. */
547 for (j=0; j<econ_nprices; j++) {
549 /* First we must load the vector with intensities. */
550 for (i=0; i<systems_nstack; i++)
551 X[i] = econ_calcSysI( dt, &systems_stack[i], j );
553 /* Solve the system. */
554 ret = cs_lsolve( econ_G, X );
555 if (ret != 1)
556 WARN("Failed to solve the Economy System.");
559 * Get the minimum and maximum to scale.
562 min = +HUGE_VALF;
563 max = -HUGE_VALF;
564 for (i=0; i<systems_nstack; i++) {
565 if (X[i] < min)
566 min = X[i];
567 if (X[i] > max)
568 max = X[i];
570 scale = 1. / (max - min);
571 offset = 0.5 - min * scale;
575 * I'm not sure I like the filtering of the results, but it would take
576 * much more work to get a sane system working without the need of post
577 * filtering.
579 scale = 1.;
580 offset = 1.;
581 for (i=0; i<systems_nstack; i++) {
582 systems_stack[i].prices[j] = X[i] * scale + offset;
586 /* Clean up. */
587 free(X);
589 return 0;
594 * @brief Destroys the economy.
596 void economy_destroy (void)
598 int i;
600 /* Must be initialized. */
601 if (!econ_initialized)
602 return;
604 /* Clean up the prices in the systems stack. */
605 for (i=0; i<systems_nstack; i++) {
606 if (systems_stack[i].prices != NULL) {
607 free(systems_stack[i].prices);
608 systems_stack[i].prices = NULL;
612 /* Destroy the economy matrix. */
613 if (econ_G != NULL) {
614 cs_spfree( econ_G );
615 econ_G = NULL;
618 /* Economy is now deinitialized. */
619 econ_initialized = 0;