2 * See Licensing and Copyright notice in naev.h
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.
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. */
52 static Commodity
* commodity_stack
= NULL
; /**< Contains all the commodities. */
53 static int commodity_nstack
= 0; /**< Number of commodities in the 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. */
74 static void commodity_freeOne( Commodity
* com
);
75 static int commodity_parse( Commodity
*temp
, xmlNodePtr parent
);
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 */
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
88 * @param credits Credits to display.
89 * @param decimals Decimals to use.
91 void credits2str( char *str
, uint64_t credits
, int decimals
)
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. );
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
)
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
);
129 * @brief Frees a commodity.
131 * @param com Commodity to free.
133 static void commodity_freeOne( Commodity
* com
)
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
)
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
;
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");
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
)
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();
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)
232 buf
= ndata_read( COMMODITY_DATA
, &bufsize
);
236 /* Handle the XML. */
237 doc
= xmlParseMemory( buf
, bufsize
);
239 WARN("'%s' is not valid XML.", COMMODITY_DATA
);
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
"'");
249 node
= node
->xmlChildrenNode
; /* first faction node */
251 ERR("Malformed "COMMODITY_DATA
" file: does not contain elements");
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.) {
268 econ_comm
= realloc(econ_comm
, econ_nprices
* sizeof(int));
269 econ_comm
[econ_nprices
-1] = commodity_nstack
-1;
272 } while (xml_nextNode(node
));
277 DEBUG("Loaded %d Commodit%s", commodity_nstack
, (commodity_nstack
==1) ? "y" : "ies" );
286 * @brief Frees all the loaded commodities.
288 void commodity_free (void)
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
)
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
)
322 /* Check if found. */
323 if (i
>= econ_nprices
) {
324 WARN("Price for commodity '%s' not known.", com
->name
);
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
)
346 /* Set to base to ensure price change. */
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. */
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
)
377 double prodfactor
, p
;
381 ddt
= (double)(dt
/ NTIME_UNIT_LENGTH
);
383 /* Calculate production level. */
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
;
413 * @brief Creates the admittance matrix.
415 * @return 0 on success.
417 static int econ_createGMatrix (void)
425 /* Create the matrix. */
426 M
= cs_spalloc( systems_nstack
, systems_nstack
, 1, 1, 1 );
428 ERR("Unable to create CSparse Matrix.");
430 /* Fill the matrix. */
431 for (i
=0; i
< systems_nstack
; i
++) {
432 sys
= &systems_stack
[i
];
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. */
443 /* Matrix is symetrical and non-diagonal is negative. */
444 ret
= cs_entry( M
, i
, sys
->jumps
[j
], -R
);
446 WARN("Unable to enter CSparse Matrix Cell.");
447 ret
= cs_entry( M
, sys
->jumps
[j
], i
, -R
);
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. */
460 econ_G
= cs_compress( M
);
462 ERR("Unable to create economy G Matrix.");
472 * @brief Initializes the economy.
474 * @return 0 on success.
476 int economy_init (void)
480 /* Must not be initialized. */
481 if (econ_initialized
)
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. */
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)
511 /* Create the resistence matrix. */
512 if (econ_createGMatrix())
515 /* Initialize the prices. */
523 * @brief Updates the economy.
525 * @param dt Deltatick in NTIME.
527 int economy_update( unsigned int dt
)
532 double scale
, offset
;
535 /* Economy must be initialized. */
536 if (econ_initialized
== 0)
539 /* Create the vector to solve the system. */
540 X
= malloc(sizeof(double)*systems_nstack
);
542 WARN("Out of Memory!");
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
);
556 WARN("Failed to solve the Economy System.");
559 * Get the minimum and maximum to scale.
564 for (i=0; i<systems_nstack; 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
581 for (i
=0; i
<systems_nstack
; i
++) {
582 systems_stack
[i
].prices
[j
] = X
[i
] * scale
+ offset
;
594 * @brief Destroys the economy.
596 void economy_destroy (void)
600 /* Must be initialized. */
601 if (!econ_initialized
)
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
) {
618 /* Economy is now deinitialized. */
619 econ_initialized
= 0;