3 * this is a game prototype modeled after "händler des nordens", aka "patrizier online"
4 * it uses a ncurses based gui capable of 256 colors.
6 * author (C) 2011 rofl0r
8 * all source code files are
12 * creative content such as graphics or world describing texts are CC by SA (Creative Commons License)
18 wenn goodsreq4good > gesamtlager kapa, lager kaufen
19 bug workers hamburgpc total > worker players
20 gui.c menus for players
22 bug repeated resizing of the terminal will crash game
35 #include "../lib/include/iniparser.h"
42 // gcc -Wall -Wextra -g -DNOKDEV pato.c ../lib/stringptr.c ../lib/iniparser.c -lm -o pato
44 size_t GAME_SPEED
= 1;
46 const unsigned long long goodcat_minprice
[] = {
54 #ifndef IN_KDEVELOP_PARSER
55 const ShipProps shipProps
[] = {
57 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
58 {SPLITERAL("schnigge"),
59 50 , 20, 15000, 10, 81, 70 , 8 , 4 , 24, 30, 10, 5 , 5 , 80 },
61 80 , 30, 40000, 14, 90, 80 , 12, 12, 42, 50, 20, 10, 10, 240 },
63 130, 30, 30000, 8 , 60, 100, 16, 6 , 56, 50, 15, 15, 10, 120 },
65 140, 40, 70000, 12, 75, 120, 20, 16, 76, 80, 30, 15, 20, 320 }
69 #ifndef IN_KDEVELOP_PARSER
70 const stringptr
* populationDesc
[] = {
74 SPLITERAL("commoner"),
75 SPLITERAL("aristocrat"),
79 const unsigned long long rentTax
[PT_MAX
] = {
87 FactoryCommon factoryProps
;
89 Factory Factories
[GT_MAX
];
91 char externalGoodRequiredForProduction
[GT_MAX
]; // if another good is req. for the production of this
92 char goodRequiredForProduction
[GT_MAX
]; // if this good is required for the production of another good
95 Warehouse Branchoffice
;
97 #ifndef IN_KDEVELOP_PARSER
99 const Good Goods
[] = {
100 { SPLITERAL("none"), GT_NONE
, GC_NONE
},
101 { SPLITERAL("wood"), GT_WOOD
, GC_A
},
102 { SPLITERAL("meat"), GT_MEAT
, GC_A
},
103 { SPLITERAL("veg"), GT_VEG
, GC_A
},
104 { SPLITERAL("barleyjuice"), GT_BARLEYJUICE
, GC_A
},
105 { SPLITERAL("crop"), GT_CROP
, GC_A
},
106 { SPLITERAL("brick"), GT_BRICK
, GC_A
},
107 { SPLITERAL("ore"), GT_ORE
, GC_B
},
108 { SPLITERAL("hemp"), GT_HEMP
, GC_B
},
109 { SPLITERAL("honey"), GT_HONEY
, GC_B
},
110 { SPLITERAL("copper"), GT_COPPER
, GC_B
},
111 { SPLITERAL("salt"), GT_SALT
, GC_B
},
112 { SPLITERAL("wool"), GT_WOOL
, GC_B
},
113 { SPLITERAL("tool"), GT_TOOL
, GC_C
},
114 { SPLITERAL("rope"), GT_ROPE
, GC_C
},
115 { SPLITERAL("met"), GT_MET
, GC_C
},
116 { SPLITERAL("ceramics"), GT_CERAMICS
, GC_C
},
117 { SPLITERAL("ham"), GT_HAM
, GC_C
},
118 { SPLITERAL("cloth"), GT_CLOTH
, GC_C
},
119 { SPLITERAL("beer"), GT_BEER
, GC_D
},
120 { SPLITERAL("fur"), GT_FUR
, GC_D
},
121 { SPLITERAL("cheese"), GT_CHEESE
, GC_D
},
122 { SPLITERAL("pitch"), GT_PITCH
, GC_D
},
123 { SPLITERAL("fish"), GT_FISH
, GC_D
},
124 { SPLITERAL("wine"), GT_WINE
, GC_D
},
127 const stringptr
* NotificationNames
[NT_MAX
] = {
129 [NT_NO_MONEY
] = SPL("out of money"),
130 [NT_BAD_MOOD
] = SPL("bad mood!"),
131 [NT_OUT_OF_PRODUCTIONGOODS
] = SPL("out of prod. goods"),
132 [NT_STOCK_FULL
] = SPL("stock full!"),
133 [NT_SHIPS_BOUGHT
] = SPL("bought ships"),
139 const Notification emptyNotification
= {NT_NONE
, 0, 0, 0.f
, 0.f
};
140 Player Players
[MAX_PLAYERS
];
142 City Cities
[MAX_CITIES
];
144 //lookup table for producer cities for certain goods.
145 size_t producers
[GT_MAX
][MAX_CITIES
];
146 size_t producerCount
[GT_MAX
];
151 float populationConsumation
[PT_MAX
][GT_MAX
];
154 PlayerType
playerTypeFromString(stringptr
* pt
) {
155 if(stringptr_eq(pt
, SPLITERAL("pc")))
157 else return PLT_USER
;
161 Notification
makeNotification(NotificationType nt
, size_t val1
, size_t val2
, float fval1
, float fval2
) {
166 result
.fval1
= fval1
;
167 result
.fval2
= fval2
;
171 stringptr
* getNotificationName(NotificationType x
) {
172 return (stringptr
*) NotificationNames
[x
];
175 void notify(size_t player
, Notification n
) {
177 last
= &Players
[player
].inbox
.notifications
[Players
[player
].inbox
.last
];
178 if(last
->nt
== n
.nt
&& last
->val1
== n
.val1
&& last
->val2
== n
.val2
)
180 gui_notify(gui
, player
, n
);
181 Players
[player
].inbox
.last
++;
182 if(Players
[player
].inbox
.last
>= MAX_NOTIFICATIONS
)
183 Players
[player
].inbox
.last
= 0;
184 Players
[player
].inbox
.notifications
[Players
[player
].inbox
.last
] = n
;
187 stringptr
* stringFromPopulationType(populationType p
) {
188 if (p
< PT_MAX
) return (stringptr
*) populationDesc
[p
];
189 return (stringptr
*) populationDesc
[0];
192 Goodtype
goodTypeFromString(stringptr
* good
) {
194 for(i
= 0; i
< GT_MAX
; i
++) {
195 if(stringptr_eq(good
, Goods
[i
].name
))
196 return Goods
[i
].type
;
201 stringptr
* stringFromGoodType(Goodtype g
) {
202 if(g
< GT_MAX
) return Goods
[g
].name
;
203 return Goods
[0].name
;
206 void initWorld(void) {
207 stringptr
* cf
= stringptr_fromfile("world.txt");
208 stringptrlist
* lines
= stringptr_linesplit(cf
);
211 sec
= iniparser_get_section(lines
, SPLITERAL("main"));
212 iniparser_getvalue(lines
, &sec
, SPLITERAL("date"), &inibuf
);
213 world
.date
= atoi(inibuf
.ptr
);
214 iniparser_getvalue(lines
, &sec
, SPLITERAL("monthsperyear"), &inibuf
);
215 world
.monthsperyear
= atoi(inibuf
.ptr
);
216 iniparser_getvalue(lines
, &sec
, SPLITERAL("dayspermonth"), &inibuf
);
217 world
.dayspermonth
= atoi(inibuf
.ptr
);
218 iniparser_getvalue(lines
, &sec
, SPLITERAL("hoursperday"), &inibuf
);
219 world
.hoursperday
= atoi(inibuf
.ptr
);
220 iniparser_getvalue(lines
, &sec
, SPLITERAL("minutesperhour"), &inibuf
);
221 world
.minutesperhour
= atoi(inibuf
.ptr
);
222 iniparser_getvalue(lines
, &sec
, SPLITERAL("secondsperminute"), &inibuf
);
223 world
.secondsperminute
= atoi(inibuf
.ptr
);
225 world
._dayseconds
= world
.secondsperminute
* world
.minutesperhour
* world
.hoursperday
;
226 world
._fdaysperyear
= (float) world
.dayspermonth
* (float) world
.monthsperyear
;
229 stringptrlist_free(lines
);
232 void initConsumationTable(void) {
236 stringptr
* cf
= stringptr_fromfile("consumation.txt");
237 stringptrlist
* lines
= stringptr_linesplit(cf
);
241 for(g
= 1; g
< PT_MAX
; g
++) {
242 sec
= iniparser_get_section(lines
, stringFromPopulationType(g
));
243 for(i
= 1; i
< GT_MAX
; i
++) {
244 bufptr
.size
= snprintf(buf
, sizeof(buf
), "consumation_%s", stringFromGoodType(i
)->ptr
);
245 iniparser_getvalue(lines
, &sec
, &bufptr
, &inibuf
);
246 populationConsumation
[g
][i
] = atof(inibuf
.ptr
);
250 stringptrlist_free(lines
);
253 void freeCities(void) {
255 for (i
= 0; i
< numCities
; i
++) {
257 stringptr_free(Cities
[i
].name
);
261 void initCities(void) {
265 stringptr
* cf
= stringptr_fromfile("cities.txt");
266 stringptrlist
* lines
= stringptr_linesplit(cf
);
267 ini_section sec
= iniparser_get_section(lines
, SPLITERAL("main"));
269 iniparser_getvalue(lines
, &sec
, SPLITERAL("citycount"), &inibuf
);
270 numCities
= atoi(inibuf
.ptr
);
272 memset(producerCount
, 0, sizeof(producerCount
));
273 for (i
= 0; i
< numCities
; i
++) {
274 bufptr
.size
= snprintf(buf
, sizeof(buf
), "city%.3d", (int) i
);
275 sec
= iniparser_get_section(lines
, &bufptr
);
276 iniparser_getvalue(lines
, &sec
, SPLITERAL("name"), &inibuf
);
277 Cities
[i
].name
= stringptr_copy(&inibuf
);
278 iniparser_getvalue(lines
, &sec
, SPLITERAL("x"), &inibuf
);
279 Cities
[i
].coords
.x
= atof(inibuf
.ptr
);
280 iniparser_getvalue(lines
, &sec
, SPLITERAL("y"), &inibuf
);
281 Cities
[i
].coords
.y
= atof(inibuf
.ptr
);
283 iniparser_getvalue(lines
, &sec
, SPLITERAL("tax"), &inibuf
);
284 Cities
[i
].tax
= atof(inibuf
.ptr
);
285 iniparser_getvalue(lines
, &sec
, SPLITERAL("money"), &inibuf
);
286 Cities
[i
].money
= atoll(inibuf
.ptr
);
288 // available industry
289 for(g
= 0; g
< CITY_MAX_INDUSTRYTYPES
; g
++) {
290 bufptr
.size
= snprintf(buf
, sizeof(buf
), "industry%.3d", (int) g
);
291 iniparser_getvalue(lines
, &sec
, &bufptr
, &inibuf
);
292 Cities
[i
].industry
[g
] = goodTypeFromString(&inibuf
);
293 producers
[Cities
[i
].industry
[g
]][producerCount
[Cities
[i
].industry
[g
]]++] = i
;
296 for(g
= 1; g
< GT_MAX
; g
++) {
297 bufptr
.size
= snprintf(buf
, sizeof(buf
), "stock_%s", stringFromGoodType(g
)->ptr
);
298 iniparser_getvalue(lines
, &sec
, &bufptr
, &inibuf
);
299 Cities
[i
].market
.stock
[g
] = atoi(inibuf
.ptr
);
301 bufptr
.size
= snprintf(buf
, sizeof(buf
), "avgprice_%s", stringFromGoodType(g
)->ptr
);
302 iniparser_getvalue(lines
, &sec
, &bufptr
, &inibuf
);
303 Cities
[i
].market
.avgPricePayed
[g
] = atof(inibuf
.ptr
);
306 for(g
= 1; g
< PT_MAX
; g
++) {
307 bufptr
.size
= snprintf(buf
, sizeof(buf
), "%s_mood", stringFromPopulationType(g
)->ptr
);
308 iniparser_getvalue(lines
, &sec
, &bufptr
, &inibuf
);
309 Cities
[i
].populationMood
[g
] = atof(inibuf
.ptr
);
310 if(Cities
[i
].populationMood
[g
] > 100)
311 Cities
[i
].populationMood
[g
] = 100.0f
;
313 Cities
[i
].population
[g
] = 0;
316 bufptr
.size
= snprintf(buf
, sizeof(buf
), "%s_count", stringFromPopulationType(g
)->ptr
);
317 iniparser_getvalue(lines
, &sec
, &bufptr
, &inibuf
);
318 Cities
[i
].population
[g
] = atoi(inibuf
.ptr
);
322 for (g
= 1; g
< ST_MAX
; g
++) {
323 bufptr
.size
= snprintf(buf
, sizeof(buf
), "shiptype%dcount", (int) (g
- 1));
324 iniparser_getvalue(lines
, &sec
, &bufptr
, &inibuf
);
325 Cities
[i
].shipyard
.availableShips
.numShips
[g
] = atoi(inibuf
.ptr
);
329 stringptrlist_free(lines
);
332 ptrdiff_t findCityFromString(stringptr
* name
) {
334 for(i
= 0; i
< numCities
; i
++) {
335 if(stringptr_eq(Cities
[i
].name
, name
))
341 ShipLocationType
shipLocationTypeFromString(stringptr
* loc
) {
342 if(stringptr_eq(loc
, SPLITERAL("sea")))
348 stringptr
* getPlayerName(size_t playerid
) {
349 return Players
[playerid
].name
;
352 void freePlayers(void) {
354 for(p
= 0; p
< numPlayers
; p
++) {
356 stringptr_free(Players
[p
].name
);
357 for (s
= 0; s
< Players
[p
].numConvoys
; s
++) {
358 if(Players
[p
].convoys
[s
].name
)
359 stringptr_free(Players
[p
].convoys
[s
].name
);
364 void initPlayers(void) {
368 stringptr
* cf
= stringptr_fromfile("players.txt");
369 stringptrlist
* lines
= stringptr_linesplit(cf
);
370 ini_section sec
= iniparser_get_section(lines
, SPLITERAL("main"));
372 iniparser_getvalue(lines
, &sec
, SPLITERAL("playercount"), &inibuf
);
373 numPlayers
= atoi(inibuf
.ptr
);
376 stringptrlist_free(lines
);
377 for(p
= 0; p
< numPlayers
; p
++) {
378 bufptr
.size
= snprintf(buf
, sizeof(buf
), "players/player%.6d.txt", (int) p
);
379 cf
= stringptr_fromfile(buf
);
380 lines
= stringptr_linesplit(cf
);
381 sec
= iniparser_get_section(lines
, SPLITERAL("main"));
382 iniparser_getvalue(lines
, &sec
, SPLITERAL("name"), &inibuf
);
383 Players
[p
].name
= stringptr_copy(&inibuf
);
384 iniparser_getvalue(lines
, &sec
, SPLITERAL("type"), &inibuf
);
385 Players
[p
].type
= playerTypeFromString(&inibuf
);
386 iniparser_getvalue(lines
, &sec
, SPLITERAL("branches"), &inibuf
);
387 Players
[p
].numBranches
= atoi(inibuf
.ptr
);
388 iniparser_getvalue(lines
, &sec
, SPLITERAL("convoys"), &inibuf
);
389 Players
[p
].numConvoys
= atoi(inibuf
.ptr
);
390 iniparser_getvalue(lines
, &sec
, SPLITERAL("othershiplocations"), &inibuf
);
391 Players
[p
].numShipLocations
= atoi(inibuf
.ptr
);
392 iniparser_getvalue(lines
, &sec
, SPLITERAL("money"), &inibuf
);
393 Players
[p
].money
= atoll(inibuf
.ptr
);
395 for(s
= 0; s
< Players
[p
].numBranches
; s
++) {
396 bufptr
.size
= snprintf(buf
, sizeof(buf
), "branch%.3d", (int) s
);
397 sec
= iniparser_get_section(lines
, &bufptr
);
398 iniparser_getvalue(lines
, &sec
, SPLITERAL("city"), &inibuf
);
399 Players
[p
].branchCity
[s
] = findCityFromString(&inibuf
);
400 assert((ptrdiff_t) Players
[p
].branchCity
[s
] >= 0);
401 iniparser_getvalue(lines
, &sec
, SPLITERAL("workers"), &inibuf
);
402 Players
[p
].branchWorkers
[s
] = atoi(inibuf
.ptr
);
403 Cities
[Players
[p
].branchCity
[s
]].population
[PT_WORKER
] += Players
[p
].branchWorkers
[s
];
404 for(i
= 0; i
< CITY_MAX_INDUSTRYTYPES
+ 1; i
++) {
405 bufptr
.size
= snprintf(buf
, sizeof(buf
), "factorycount%.3d", (int) i
);
406 iniparser_getvalue(lines
, &sec
, &bufptr
, &inibuf
);
407 Players
[p
].branchFactories
[s
][i
] = atoi(inibuf
.ptr
);
410 for(i
= 1; i
< GT_MAX
; i
++) {
411 bufptr
.size
= snprintf(buf
, sizeof(buf
), "stock_%s", stringFromGoodType(i
)->ptr
);
412 iniparser_getvalue(lines
, &sec
, &bufptr
, &inibuf
);
413 Players
[p
].branchStock
[s
].stock
[i
] = atoi(inibuf
.ptr
);
415 bufptr
.size
= snprintf(buf
, sizeof(buf
), "avgprice_%s", stringFromGoodType(i
)->ptr
);
416 iniparser_getvalue(lines
, &sec
, &bufptr
, &inibuf
);
417 Players
[p
].branchStock
[s
].avgPricePayed
[i
] = atof(inibuf
.ptr
);
422 for (s
= 0; s
< Players
[p
].numConvoys
; s
++) {
423 bufptr
.size
= snprintf(buf
, sizeof(buf
), "convoy%.3d", (int) s
);
424 sec
= iniparser_get_section(lines
, &bufptr
);
426 iniparser_getvalue(lines
, &sec
, SPLITERAL("name"), &inibuf
);
427 Players
[p
].convoys
[s
].name
= stringptr_copy(&inibuf
);
429 iniparser_getvalue(lines
, &sec
, SPLITERAL("captainsalary"), &inibuf
);
430 Players
[p
].convoys
[s
].captainSalary
= atoi(inibuf
.ptr
);
432 iniparser_getvalue(lines
, &sec
, SPLITERAL("sailors"), &inibuf
);
433 Players
[p
].convoys
[s
].numSailors
= atoi(inibuf
.ptr
);
435 iniparser_getvalue(lines
, &sec
, SPLITERAL("condition"), &inibuf
);
436 Players
[p
].convoys
[s
].condition
= atof(inibuf
.ptr
);
438 iniparser_getvalue(lines
, &sec
, SPLITERAL("location"), &inibuf
);
439 Players
[p
].convoys
[s
].loc
= shipLocationTypeFromString(&inibuf
);
441 if(Players
[p
].convoys
[s
].loc
== SLT_CITY
) {
442 Players
[p
].convoys
[s
].locCity
= findCityFromString(&inibuf
);
444 iniparser_getvalue(lines
, &sec
, SPLITERAL("x"), &inibuf
);
445 Players
[p
].convoys
[s
].coords
.x
= atof(inibuf
.ptr
);
447 iniparser_getvalue(lines
, &sec
, SPLITERAL("y"), &inibuf
);
448 Players
[p
].convoys
[s
].coords
.y
= atof(inibuf
.ptr
);
450 for(i
= 1; i
< ST_MAX
; i
++) {
451 bufptr
.size
= snprintf(buf
, sizeof(buf
), "shiptype%dcount", (int) (i
- 1));
452 iniparser_getvalue(lines
, &sec
, &bufptr
, &inibuf
);
453 Players
[p
].convoys
[s
].shipcounter
.numShips
[i
] = atoi(inibuf
.ptr
);
456 Players
[p
].convoys
[s
].totalload
= 0;
457 for(i
= 1; i
< GT_MAX
; i
++) {
458 bufptr
.size
= snprintf(buf
, sizeof(buf
), "stock_%s", stringFromGoodType(i
)->ptr
);
459 iniparser_getvalue(lines
, &sec
, &bufptr
, &inibuf
);
460 Players
[p
].convoys
[s
].load
.stock
[i
] = atoi(inibuf
.ptr
);
461 Players
[p
].convoys
[s
].totalload
+= Players
[p
].convoys
[s
].load
.stock
[i
];
463 bufptr
.size
= snprintf(buf
, sizeof(buf
), "avgprice_%s", stringFromGoodType(i
)->ptr
);
464 iniparser_getvalue(lines
, &sec
, &bufptr
, &inibuf
);
465 Players
[p
].convoys
[s
].load
.avgPricePayed
[i
] = atof(inibuf
.ptr
);
470 // single ships (well, kinda)
471 // size_t shipLocations[MAX_CITIES]; // stores the city "id" with single ships (those not in a convoy)
472 // ShipCounter singleShips[MAX_CITIES]; // stored in the order of shipLocations
475 for (s
= 0; s
< Players
[p
].numShipLocations
; s
++) {
476 bufptr
.size
= snprintf(buf
, sizeof(buf
), "shiplocation%.3d", (int) s
);
477 sec
= iniparser_get_section(lines
, &bufptr
);
478 iniparser_getvalue(lines
, &sec
, SPLITERAL("city"), &inibuf
);
479 t
= findCityFromString(&inibuf
);
481 assert((ptrdiff_t) t
>= 0);
483 Players
[p
].shipLocations
[s
] = t
;
484 Players
[p
].singleShips
[s
].total
= 0;
486 for(i
= 1; i
< ST_MAX
; i
++) {
487 bufptr
.size
= snprintf(buf
, sizeof(buf
), "shiptype%dcount", (int) (i
- 1));
488 iniparser_getvalue(lines
, &sec
, &bufptr
, &inibuf
);
489 Players
[p
].singleShips
[s
].numShips
[i
] = atoi(inibuf
.ptr
);
490 Players
[p
].singleShips
[s
].total
+= Players
[p
].singleShips
[s
].numShips
[i
];
492 bufptr
.size
= snprintf(buf
, sizeof(buf
), "shiptype%dcondition", (int) (i
- 1));
493 iniparser_getvalue(lines
, &sec
, &bufptr
, &inibuf
);
494 Players
[p
].singleShipConditions
[s
].condition
[i
] = atof(inibuf
.ptr
);
499 stringptrlist_free(lines
);
503 void initBuildings(void) {
509 stringptr
* cf
= stringptr_fromfile("buildings.txt");
510 stringptrlist
* lines
= stringptr_linesplit(cf
);
514 sec
= iniparser_get_section(lines
, SPLITERAL("main"));
515 iniparser_getvalue(lines
, &sec
, SPLITERAL("maxworkersperfactory"), &inibuf
);
516 factoryProps
.maxworkersperfactory
= atoi(inibuf
.ptr
);
517 iniparser_getvalue(lines
, &sec
, SPLITERAL("workersalary"), &inibuf
);
518 factoryProps
.workersalary
= atoi(inibuf
.ptr
);
520 sec
= iniparser_get_section(lines
, SPLITERAL("branch"));
521 iniparser_getvalue(lines
, &sec
, SPLITERAL("buildcost"), &inibuf
);
522 Branchoffice
.buildcost
= atoi(inibuf
.ptr
);
523 iniparser_getvalue(lines
, &sec
, SPLITERAL("storage"), &inibuf
);
524 Branchoffice
.storage
= atoi(inibuf
.ptr
);
526 sec
= iniparser_get_section(lines
, SPLITERAL("warehouse"));
527 iniparser_getvalue(lines
, &sec
, SPLITERAL("buildcost"), &inibuf
);
528 Storage
.buildcost
= atoi(inibuf
.ptr
);
529 iniparser_getvalue(lines
, &sec
, SPLITERAL("storage"), &inibuf
);
530 Storage
.storage
= atoi(inibuf
.ptr
);
532 memset(goodRequiredForProduction
, 0, sizeof(goodRequiredForProduction
));
534 for(i
= 1; i
< GT_MAX
; i
++) {
535 bufptr
.size
= snprintf(buf
, sizeof(buf
), "factory_%s", stringFromGoodType(i
)->ptr
);
536 sec
= iniparser_get_section(lines
, &bufptr
);
537 iniparser_getvalue(lines
, &sec
, SPLITERAL("buildcost"), &inibuf
);
538 Factories
[i
].buildcost
= atoi(inibuf
.ptr
);
539 iniparser_getvalue(lines
, &sec
, SPLITERAL("yield"), &inibuf
);
540 Factories
[i
].yield
= atof(inibuf
.ptr
);
542 for(g
= 1; g
< GT_MAX
; g
++) {
543 bufptr
.size
= snprintf(buf
, sizeof(buf
), "consumation_%s", stringFromGoodType(g
)->ptr
);
544 iniparser_getvalue(lines
, &sec
, &bufptr
, &inibuf
);
545 Factories
[i
].consumation
[g
] = inibuf
.ptr
? atof(inibuf
.ptr
) : 0.0f
;
546 if(Factories
[i
].consumation
[g
] > 0.f
) {
548 goodRequiredForProduction
[g
] = 1;
552 externalGoodRequiredForProduction
[i
] = 1;
554 externalGoodRequiredForProduction
[i
] = 0;
557 stringptrlist_free(lines
);
560 size_t getMaxWorkerCount(size_t branch
, size_t player
) {
563 for(i
= 0; i
< CITY_MAX_INDUSTRYTYPES
; i
++) {
564 result
+= Players
[player
].branchFactories
[branch
][i
] * 10;
569 size_t getFreeJobs(size_t branch
, size_t player
) {
570 size_t result
= getMaxWorkerCount(branch
, player
);
571 if(result
) result
-= Players
[player
].branchWorkers
[branch
];
575 void getPlayersWithFreeWorkCapacity(size_t city
, size_t* totalcapacity
, size_t* num_players
, size_t* affected_players
, size_t* free_jobs
) {
580 for(p
= 0; p
< numPlayers
; p
++) {
581 if(Players
[p
].money
) {
582 for(b
= 0; b
< Players
[p
].numBranches
; b
++) {
583 if(Players
[p
].branchCity
[b
] == city
) {
584 c
= getFreeJobs(b
, p
);
588 affected_players
[*num_players
- 1] = p
;
589 free_jobs
[*num_players
- 1] = c
;
598 ptrdiff_t getShipLocationIDFromCity(size_t city
, size_t player
) {
600 for(i
= 0; i
< Players
[player
].numShipLocations
; i
++) {
601 if(Players
[player
].shipLocations
[i
] == city
)
607 ptrdiff_t getCityIDFromBranch(size_t branch
, size_t player
) {
608 if(branch
>= Players
[player
].numBranches
) return -1;
609 return Players
[player
].branchCity
[branch
];
612 ptrdiff_t getBranchIDFromCity(size_t city
, size_t player
) {
614 for(b
= 0; b
< Players
[player
].numBranches
; b
++) {
615 if(Players
[player
].branchCity
[b
] == city
) {
622 size_t getPlayerMaxBranchStorage(size_t branch
, size_t player
) {
624 result
+= Players
[player
].branchFactories
[branch
][CITY_MAX_INDUSTRYTYPES
] * Storage
.storage
;
625 result
+= Branchoffice
.storage
;
629 float getPlayerFreeBranchStorage(size_t branch
, size_t player
) {
630 float result
= (float) getPlayerMaxBranchStorage(branch
, player
);
632 for(i
= 1; i
< GT_MAX
; i
++) {
633 result
-= Players
[player
].branchStock
[branch
].stock
[i
];
638 size_t getPlayerFactoryCount(size_t branch
, size_t player
) {
641 for (i
= 0; i
< CITY_MAX_INDUSTRYTYPES
; i
++) {
642 result
+= Players
[player
].branchFactories
[branch
][i
];
647 size_t getCityPopulation(size_t city
) {
648 size_t i
, result
= 0;
649 for(i
= 1; i
< PT_MAX
; i
++) {
650 result
+= Cities
[city
].population
[i
];
655 // return price for 1 ton
656 float calculatePrice(size_t city
, Goodtype g
, float amount
, unsigned sell
) {
657 if(amount
> Cities
[city
].market
.stock
[g
])
658 amount
= Cities
[city
].market
.stock
[g
];
660 float minprice
= goodcat_minprice
[Goods
[g
].cat
];
661 float maxprice
= minprice
* (sell
? 4 : 3);
663 float consumationperday
= getPopulationConsumation(g
, city
);
664 float remaining_stock_lasts_days
= (Cities
[city
].market
.stock
[g
] - (sell
? amount
: -amount
) ) / consumationperday
;
665 /* if the stock lasts for less than 10 days, the maximum price holds */
666 const float lo_tresh
= 10.f
;
667 /* if it lasts for longer than this, it's sold for the minimum */
668 const float hi_tresh
= 100.f
;
669 const float scale
= hi_tresh
- lo_tresh
;
671 if(remaining_stock_lasts_days
< lo_tresh
)
673 else if(remaining_stock_lasts_days
> hi_tresh
)
676 float x
= remaining_stock_lasts_days
- lo_tresh
;
677 float y
= x
/ scale
; //returns a value between 0 and 1. 1 for the full span (bestand == hitresh)
678 price
= minprice
+ ( 1 - y
) * (maxprice
- minprice
);
682 //Cities[city].market.avgPricePayed[g];
683 float factor = (float) ((Cities[city].market.stock[g] - amount) * 2) + 0.99f / (float) (getCityPopulation(city) + 1);
684 float value = minprice + ((minprice * (sell ? 3 : 2)) * (1.f - factor));
685 if (value < (float) minprice)
686 value = (float) minprice;
688 value = value * (1.f + (Cities[city].tax / 100.f));
689 return (unsigned long long) value; */
691 if(sell
) price
*= (1.f
+ (Cities
[city
].tax
/ 100.f
));
697 void sell(size_t city
, size_t player
, size_t branch
, size_t convoy
, Goodtype g
, float amount
, sellFlags flags
) {
700 Market
* sellermarket
;
701 long long* buyerportemonnaie
;
702 long long* sellerportemonnaie
;
703 unsigned citysells
= 0;
704 if(flags
& SELL_TO_CITY
) {
705 buyermarket
= &Cities
[city
].market
;
706 buyerportemonnaie
= &Cities
[city
].money
;
707 } else if (flags
& SELL_TO_CONVOY
) {
709 buyermarket
= &Players
[player
].convoys
[convoy
].load
;
710 Players
[player
].convoys
[convoy
].totalload
+= amount
;
711 buyerportemonnaie
= &Players
[player
].money
;
712 } else if (flags
& SELL_TO_PLAYERSTOCK
) {
714 buyermarket
= &Players
[player
].branchStock
[branch
];
715 buyerportemonnaie
= &Players
[player
].money
;
716 } else if (flags
& SELL_TO_POPULATION
) {
719 buyerportemonnaie
= NULL
;
724 if(flags
& SELL_FROM_OVERPRODUCTION
) {
726 sellerportemonnaie
= &Players
[player
].money
;
727 } else if (flags
& SELL_FROM_CONVOY
) {
728 sellermarket
= &Players
[player
].convoys
[convoy
].load
;
729 Players
[player
].convoys
[convoy
].totalload
-= amount
;
730 sellerportemonnaie
= &Players
[player
].money
;
731 } else if (flags
& SELL_FROM_STOCK
) {
732 sellermarket
= &Players
[player
].branchStock
[branch
];
733 sellerportemonnaie
= &Players
[player
].money
;
734 } else if (flags
& SELL_FROM_CITY
) {
736 if(player
!= (size_t) -1)
737 printf("player %d buys %f %s from %d!\n", (int) player
, amount
, Goods
[g
].name
->ptr
, (int) city
);
739 sellermarket
= &Cities
[city
].market
;
740 sellerportemonnaie
= &Cities
[city
].money
;
745 price
= calculatePrice(city
, g
, amount
, citysells
);
748 if(buyerportemonnaie
) {
749 *buyerportemonnaie
-= (unsigned long long) ((float) price
* amount
);
750 assert(*buyerportemonnaie
>= 0);
753 if(sellerportemonnaie
) {
754 *sellerportemonnaie
+= (unsigned long long) ((float) price
* amount
);
755 assert(*sellerportemonnaie
>= 0);
759 sellermarket
->stock
[g
] -= amount
;
760 assert(sellermarket
->stock
[g
] >= 0.0);
764 buyermarket
->stock
[g
] += amount
;
765 assert(buyermarket
->stock
[g
] >= 0.0);
771 void showDate(void) {
772 puts("=================================================");
773 printf("day: %lu\n", world
.date
/ world
._dayseconds
);
776 void showPlayerStatus(size_t player
, size_t branch
) {
777 puts("=================================================");
778 printf("status for %s\n", Players
[player
].name
->ptr
);
779 printf("money: %llu\n", Players
[player
].money
);
780 printf("workers: %d\n", (int) Players
[player
].branchWorkers
[branch
]);
781 printf("free stock: %d\n", (int) getPlayerFreeBranchStorage(branch
, player
));
784 void showCityStatus(size_t city
) {
786 puts("=================================================");
787 printf("status for %s\n", Cities
[city
].name
->ptr
);
788 printf("money: %llu\n", Cities
[city
].money
);
789 printf("population:\n");
790 for(i
= 1; i
< PT_MAX
; i
++) {
791 printf("%s: count: %d, mood: %f\n", stringFromPopulationType(i
)->ptr
, (int) Cities
[city
].population
[i
], Cities
[city
].populationMood
[i
]);
793 printf("\nmarket:\n");
794 for(i
= 1; i
< GT_MAX
; i
++) {
795 printf("%s: stock %f\n", stringFromGoodType(i
)->ptr
, Cities
[city
].market
.stock
[i
]);
801 float getPopulationConsumationPerPopulationType(Goodtype g
, size_t c
, populationType p
) {
802 return (populationConsumation
[p
][g
] / world
._fdaysperyear
) * Cities
[c
].population
[p
];
805 float getPopulationConsumation(Goodtype g
, size_t c
) {
808 // consumation of goods.
809 for(p
= 1; p
< PT_MAX
; p
++) {
810 res
+= getPopulationConsumationPerPopulationType(g
, c
, p
);
816 size_t i
, g
, p
, pl
, plsave
, b
, f
;
818 float maxconsumption
[PT_MAX
];
819 float consumed
[PT_MAX
];
821 float percent
, leavepercent
;
822 size_t leaving
, leavingp
;
823 size_t affected_players
[MAX_PLAYERS
];
824 size_t free_jobs
[MAX_PLAYERS
];
825 size_t totalcapacity
;
826 size_t num_players_affected
;
828 ptrdiff_t branchid
= -1;
830 float workersPerFactory
;
831 float produced
, required
;
834 for (i
= 0; i
< numCities
; i
++) {
836 if(Cities
[i
].shipyard
.availableShips
.numShips
[ST_A
] < 2) {
837 Cities
[i
].shipyard
.availableShips
.numShips
[ST_A
] = 2;
838 // TODO: decrease stock/money for goods necessary to build ships...
841 // cash-in rent-tax...
842 for(p
= 1; p
< PT_MAX
; p
++) {
843 Cities
[i
].money
+= rentTax
[p
] * Cities
[i
].population
[p
];
846 // consumation of goods.
847 for(p
= 1; p
< PT_MAX
; p
++) {
848 maxconsumption
[p
] = 0.0f
;
850 for(g
= 1; g
< GT_MAX
; g
++) {
851 maxconsumption
[p
] += (populationConsumation
[p
][g
] / world
._fdaysperyear
) * Cities
[i
].population
[p
];
855 for(g
= 1; g
< GT_MAX
; g
++) {
856 for(p
= 1; p
< PT_MAX
; p
++) {
857 if(Cities
[i
].population
[p
] && populationConsumation
[p
][g
] > 0.f
) {
858 stock
= Cities
[i
].market
.stock
[g
];
859 wantsconsume
= (populationConsumation
[p
][g
] / world
._fdaysperyear
) * Cities
[i
].population
[p
];
860 if(stock
> wantsconsume
) {
861 sell(i
, -1, -1, -1, g
, wantsconsume
, SELL_TO_POPULATION
| SELL_FROM_CITY
);
862 //Cities[i].market.stock[g] -= wantsconsume;
863 consumed
[p
] += wantsconsume
;
866 consumed
[p
] = Cities
[i
].market
.stock
[g
];
867 sell(i
, -1, -1, -1, g
, consumed
[p
], SELL_TO_POPULATION
| SELL_FROM_CITY
);
868 //Cities[i].market.stock[g] = 0;
875 // decrease mood by max 2%, if no wares consumed, and let a random number between 0 and 10% percent leave the city if mood below 10%
876 for(p
= 1; p
< PT_MAX
; p
++) {
878 // only do mood adjustement if there ARE some ppl of that type
879 if(Cities
[i
].population
[p
]) {
880 percent
= maxconsumption
[p
] / 100;
881 percent
= consumed
[p
] / percent
;
883 Cities
[i
].populationMood
[p
] += (1.0f
/ 100.0f
) * percent
;
885 Cities
[i
].populationMood
[p
] -= 2.0f
- ((2.0f
/ 100.0f
) * percent
);
886 // notify players with branches...
889 for(pl
= 0; pl
< numPlayers
; pl
++) {
890 b
= getBranchIDFromCity(i
, pl
);
891 if((ptrdiff_t) b
>= 0)
892 notify(pl
, makeNotification(NT_BAD_MOOD
, b
, 0, 0.f
, 0.f
));
896 if(Cities
[i
].populationMood
[p
] > 100.0f
)
897 Cities
[i
].populationMood
[p
] = 100.0f
;
898 else if(Cities
[i
].populationMood
[p
] < 0.0f
)
899 Cities
[i
].populationMood
[p
] = 0.0f
;
903 // if there are no ppl we take the average of the other folk moods, and increase it a bit.
905 for(pl
= 1; pl
< PT_MAX
; pl
++) {
907 wantsconsume
+= Cities
[i
].populationMood
[pl
];
909 Cities
[i
].populationMood
[p
] += (wantsconsume
/ (float) (PT_MAX
-2)) + 3.f
; // PT_MAX -2 == number of effective population types.
910 if(Cities
[i
].populationMood
[p
] > 100.f
)
911 Cities
[i
].populationMood
[p
] = 100.f
;
914 if(Cities
[i
].population
[p
] && Cities
[i
].populationMood
[p
] < 10.0f
) {
915 leaving
= Cities
[i
].population
[p
] > 20 ? rand() % (Cities
[i
].population
[p
] / 10) : 1;
916 if(leaving
> Cities
[i
].population
[p
])
917 leaving
= Cities
[i
].population
[p
];
919 leavepercent
= leaving
/ ((float) Cities
[i
].population
[p
] / 100.0f
);
920 Cities
[i
].population
[p
] -= leaving
;
924 for(pl
= 0; pl
< numPlayers
; pl
++) {
925 for(b
= 0; b
< Players
[pl
].numBranches
; b
++) {
926 if(Players
[pl
].branchCity
[b
] == i
&& Players
[pl
].branchWorkers
[b
]) {
927 leavingp
= (size_t) (((float) Players
[pl
].branchWorkers
[b
] / 100.0f
) * leavepercent
) + 0.99f
;
928 if (leavingp
> leaving
)
931 Players
[pl
].branchWorkers
[b
] -= leavingp
;
937 } else if (p
!= PT_WORKER
&& Cities
[i
].populationMood
[p
] > 85.0f
) {
938 // mood is good, so they recommend the city to their friends.
939 percent
= (float) Cities
[i
].population
[p
] / 100.0f
;
940 // lets say 2 percent talk to one outside friend each day, from those a random number will join the city.
941 // read "leaving" in this context as "joining"
942 if(!Cities
[i
].population
[p
] && p
== PT_BEGGAR
)
943 percent
= (float) Cities
[i
].population
[PT_WORKER
] / 100.0f
;
945 leaving
= rand() % (1 + (int) ((2.f
* percent
) + 0.999f
));
947 leaving
= rand() % 2;
949 Cities
[i
].population
[p
] += leaving
;
952 if (Cities
[i
].population
[PT_BEGGAR
] > 0 && Cities
[i
].populationMood
[PT_WORKER
] > 60.f
) {
953 // see if we have work for beggars...
954 leaving
= Cities
[i
].population
[PT_BEGGAR
];
955 // we need a list of players with free resources, and their number n
956 // then we pick a random one for n times, and give them a random number of workers from the "pool".
957 // if there are some left, we'll iterate the list and distribute them evenly.
958 getPlayersWithFreeWorkCapacity(i
, &totalcapacity
, &num_players_affected
, affected_players
, free_jobs
);
959 if(totalcapacity
< leaving
)
960 leaving
= totalcapacity
;
962 totalcapacity
= leaving
; // we use that variable now for reminding how many ppl we have left to distribute.
963 for(pl
= 0; pl
< num_players_affected
; pl
++) {
964 leavingp
= rand() % totalcapacity
;
965 plsave
= rand() % num_players_affected
;
966 luckyplayer
= affected_players
[plsave
];
967 if(leavingp
> free_jobs
[plsave
])
968 leavingp
= free_jobs
[plsave
];
969 branchid
= getBranchIDFromCity(i
, luckyplayer
);
970 Players
[luckyplayer
].branchWorkers
[branchid
] += leavingp
;
971 totalcapacity
-= leavingp
;
972 Cities
[i
].population
[PT_WORKER
] += leavingp
;
973 Cities
[i
].population
[PT_BEGGAR
] -= leavingp
;
974 if(!totalcapacity
) break;
977 getPlayersWithFreeWorkCapacity(i
, &leavingp
, &num_players_affected
, affected_players
, free_jobs
);
978 leaving
= totalcapacity
/ num_players_affected
;
979 for(pl
= 0; pl
< num_players_affected
; pl
++) {
981 luckyplayer
= affected_players
[pl
];
982 if(leavingp
> free_jobs
[pl
]) {
983 leavingp
= free_jobs
[pl
];
984 totalcapacity
-= free_jobs
[pl
];
985 leaving
= totalcapacity
/ (num_players_affected
- (pl
+ 1));
987 totalcapacity
-= leavingp
;
988 Players
[luckyplayer
].branchWorkers
[branchid
] += leavingp
;
989 Cities
[i
].population
[PT_WORKER
] += leavingp
;
990 Cities
[i
].population
[PT_BEGGAR
] -= leavingp
;
995 for(pl
= 0; pl
< numPlayers
; pl
++) {
997 branchid
= getBranchIDFromCity(i
, pl
);
999 // now to the factories.
1000 if(Players
[pl
].branchWorkers
[branchid
]) {
1001 workersPerFactory
= (float) Players
[pl
].branchWorkers
[branchid
] / (float) getPlayerFactoryCount(branchid
, pl
);
1002 percent
= workersPerFactory
/ ((float) factoryProps
.maxworkersperfactory
/ 100.f
);
1003 for(f
= 0; f
< CITY_MAX_INDUSTRYTYPES
; f
++) {
1004 if(Players
[pl
].branchFactories
[branchid
][f
]) {
1005 produced
= (Factories
[Cities
[i
].industry
[f
]].yield
/ 100.f
) * percent
* Players
[pl
].branchFactories
[branchid
][f
];
1007 if(externalGoodRequiredForProduction
[Cities
[i
].industry
[f
]]) {
1008 // check if all required goods are in stock, and reduce produced amount if not
1009 for(g
= 1; g
< GT_MAX
; g
++) {
1010 if(Factories
[Cities
[i
].industry
[f
]].consumation
[g
] > 0.f
) {
1011 required
= Factories
[Cities
[i
].industry
[f
]].consumation
[g
] * percent
* Players
[pl
].branchFactories
[branchid
][f
];
1012 if(required
> 0.f
) {
1013 if(Players
[pl
].branchStock
[branchid
].stock
[g
] < required
) {
1014 produced
= Players
[pl
].branchStock
[branchid
].stock
[g
] / required
* produced
;
1015 notify(pl
, makeNotification(NT_OUT_OF_PRODUCTIONGOODS
, branchid
, g
, required
, 0.f
));
1020 if(produced
> 0.f
) {
1021 for(g
= 1; g
< GT_MAX
; g
++) {
1022 if(Factories
[Cities
[i
].industry
[f
]].consumation
[g
] > 0.f
) {
1023 required
= (Factories
[Cities
[i
].industry
[f
]].yield
/ Factories
[Cities
[i
].industry
[f
]].consumation
[g
]) * produced
;
1024 Players
[pl
].branchStock
[branchid
].stock
[g
] -= required
;
1029 if(produced
> 0.f
) {
1030 freestorage
= getPlayerFreeBranchStorage(branchid
, pl
);
1031 if(freestorage
>= produced
)
1032 Players
[pl
].branchStock
[branchid
].stock
[Cities
[i
].industry
[f
]] += produced
;
1034 Players
[pl
].branchStock
[branchid
].stock
[Cities
[i
].industry
[f
]] += freestorage
;
1035 sell(i
, pl
, branchid
, (size_t) -1, Cities
[i
].industry
[f
], produced
- freestorage
, SELL_FROM_OVERPRODUCTION
| SELL_TO_CITY
);
1036 notify(pl
, makeNotification(NT_STOCK_FULL
, branchid
, 0, produced
- freestorage
, 0.f
));
1042 // and to the money.
1043 cost
= Players
[pl
].branchWorkers
[branchid
] * factoryProps
.workersalary
;
1044 if(Players
[pl
].money
>= cost
)
1045 Players
[pl
].money
-= cost
;
1047 leaving
= Players
[pl
].branchWorkers
[branchid
];
1048 produced
= (float) Players
[pl
].money
/ (float) factoryProps
.workersalary
;
1049 if(produced
>= 1.f
) {
1050 leaving
-= (size_t) produced
;
1052 Players
[pl
].money
= 0;
1053 Players
[pl
].branchWorkers
[branchid
] -= leaving
;
1054 Cities
[i
].population
[PT_WORKER
] -= leaving
;
1055 Cities
[i
].population
[PT_BEGGAR
] += leaving
;
1056 notify(pl
, makeNotification(NT_NO_MONEY
, 0, 0, 0.f
, 0.f
));
1061 #ifdef CONSOLE_DEBUG
1063 showPlayerStatus(0, 0);
1068 size_t buyShips(size_t city
, size_t player
, size_t shipcount
, ShipTypes t
) {
1069 size_t singleShipCost
= (size_t) ((float) shipProps
[t
].buildCost
* 1.6f
);
1073 if(shipcount
* singleShipCost
> Players
[player
].money
)
1074 shipcount
= Players
[player
].money
/ singleShipCost
;
1076 price
= singleShipCost
* shipcount
;
1077 Players
[player
].money
-= price
;
1078 Cities
[city
].money
+= price
;
1079 Cities
[city
].shipyard
.availableShips
.numShips
[t
] -= shipcount
;
1080 for(i
= 0; i
< Players
[player
].numShipLocations
; i
++) {
1081 if(Players
[player
].shipLocations
[i
] == city
) {
1087 loc
= Players
[player
].numShipLocations
;
1088 Players
[player
].numShipLocations
++;
1089 Players
[player
].shipLocations
[loc
] = city
;
1091 Players
[player
].singleShips
[loc
].numShips
[t
] += shipcount
;
1092 Players
[player
].singleShips
[loc
].total
+= shipcount
;
1093 notify(player
, makeNotification(NT_SHIPS_BOUGHT
, shipcount
, (size_t) t
, (float) price
, (float) Players
[player
].money
));
1098 void addToConvoy(Convoy
* c
, size_t shiploc
, size_t player
, size_t numships
, ShipTypes t
) {
1101 if(numships
> Players
[player
].singleShips
[shiploc
].numShips
[t
])
1102 numships
= Players
[player
].singleShips
[shiploc
].numShips
[t
];
1103 c
->shipcounter
.numShips
[t
] += numships
;
1104 c
->shipcounter
.total
+= numships
;
1105 Players
[player
].singleShips
[shiploc
].numShips
[t
] -= numships
;
1106 Players
[player
].singleShips
[shiploc
].total
-= numships
;
1108 // ST_NONE acts as Wildcard here
1109 for (i
= 1; i
< ST_MAX
; i
++) {
1110 c
->shipcounter
.numShips
[i
] += Players
[player
].singleShips
[shiploc
].numShips
[i
];
1111 c
->shipcounter
.total
+= Players
[player
].singleShips
[shiploc
].numShips
[i
];
1112 Players
[player
].singleShips
[shiploc
].numShips
[i
] = 0;
1113 Players
[player
].singleShips
[shiploc
].total
= 0;
1116 if(!Players
[player
].singleShips
[shiploc
].total
&& Players
[player
].numShipLocations
== 1)
1117 Players
[player
].numShipLocations
= 0;
1120 ptrdiff_t makeConvoy(size_t city
, size_t shiploc
, size_t player
, size_t numships
, ShipTypes t
) {
1125 ptrdiff_t result
= -1;
1126 if(Players
[player
].numConvoys
< MAX_CONVOYS
) {
1127 c
= &Players
[player
].convoys
[Players
[player
].numConvoys
];
1128 memset(c
, 0, sizeof(Convoy
));
1129 bufptr
.size
= snprintf(buf
, sizeof(buf
), "Convoy%.3d", (int) Players
[player
].numConvoys
);
1130 c
->name
= stringptr_copy(&bufptr
);
1133 addToConvoy(c
, shiploc
, player
, numships
, t
);
1135 result
= Players
[player
].numConvoys
;
1136 Players
[player
].numConvoys
++;
1141 size_t getMinCrew(size_t convoy
, size_t player
) {
1144 if(convoy
>= MAX_CONVOYS
) return 0;
1145 for(i
= 1; i
< ST_MAX
; i
++) {
1146 result
+= shipProps
[i
].minCrew
* Players
[player
].convoys
[convoy
].shipcounter
.numShips
[i
];;
1151 void hireCrew(size_t convoy
, size_t player
, size_t numsailors
) {
1152 if(convoy
>= MAX_CONVOYS
) return;
1153 Players
[player
].convoys
[convoy
].numSailors
+= numsailors
;
1156 void hireCaptain(size_t convoy
, size_t player
) {
1157 if(convoy
>= MAX_CONVOYS
) return;
1158 Players
[player
].convoys
[convoy
].captainSalary
= (rand() % 30) + 10;
1161 unsigned requireProductionGood(size_t player
, size_t branch
, Goodtype t
) {
1163 if(!goodRequiredForProduction
[t
]) return 0;
1164 ptrdiff_t city
= getCityIDFromBranch(branch
, player
);
1165 if(city
== -1) return 0;
1166 // look if we have factories needing that good.
1167 for(i
= 0; i
< CITY_MAX_INDUSTRYTYPES
; i
++) {
1168 if(Players
[player
].branchFactories
[i
] && Factories
[Cities
[city
].industry
[i
]].consumation
[t
] > 0.0f
) {
1175 void queueSellOrder(size_t player
, size_t from_branch
, size_t to_city
, Goodtype g
, float amount
) {
1176 size_t c
= getCityIDFromBranch(from_branch
, player
);
1178 sell(to_city
, player
, from_branch
, -1, g
, amount
, SELL_TO_CITY
| SELL_FROM_STOCK
);
1185 int sellBestSellingProductionGoods(size_t player
, size_t branch
) {
1186 size_t c
= getCityIDFromBranch(branch
, player
);
1189 float bestprofit
= 0.f
;
1190 ptrdiff_t bestgood
= -1, bestcity
= -1;
1191 float bestamount
= 0.f
;
1192 Player
* p
= &Players
[player
];
1193 for (i
= 0; i
< CITY_MAX_INDUSTRYTYPES
; i
++) {
1194 g
= Cities
[c
].industry
[i
];
1195 float stock
= p
->branchStock
[branch
].stock
[g
];
1197 for(ct
= 0; ct
< numCities
; ct
++) {
1198 float price
= calculatePrice(ct
, g
, stock
, 1);
1199 float res
= price
* stock
;
1200 if (res
> bestprofit
) {
1209 if(bestprofit
> 0.f
) {
1210 queueSellOrder(player
, branch
, bestcity
, bestgood
, bestamount
);
1216 void sellWholeStock(size_t player
, size_t branch
, unsigned skiprequired
) {
1218 for (g
= 1; g
< GT_MAX
; g
++) {
1219 if (Players
[player
].branchStock
[branch
].stock
[g
] > 0.f
) {
1220 if(!skiprequired
|| !requireProductionGood(player
, branch
, g
))
1221 sell(Players
[player
].branchCity
[branch
], player
, branch
, -1, g
, Players
[player
].branchStock
[branch
].stock
[g
], SELL_TO_CITY
| SELL_FROM_STOCK
);
1226 unsigned convoysAvailable(size_t player
, size_t branch
) {
1229 city
= getCityIDFromBranch(branch
, player
);
1230 for(i
= 0; i
< Players
[player
].numConvoys
; i
++) {
1231 if(Players
[player
].convoys
[i
].loc
== SLT_CITY
&& Players
[player
].convoys
[i
].locCity
== city
)
1237 float calculateDistance(Coords
* a
, Coords
* b
) {
1238 // TODO do proper pathfinding
1239 float x
= a
->x
- b
->x
;
1240 float y
= a
->y
- b
->y
;
1241 return (float) sqrtf((x
* x
) + (y
* y
));
1244 ptrdiff_t findNearestCityWithGood(size_t city
, Goodtype neededgood
) {
1245 float mindistance
= 1000000000000.f
;
1248 for(g
= 0; g
< producerCount
[neededgood
]; g
++) {
1249 distance
= calculateDistance(&Cities
[city
].coords
, &Cities
[producers
[neededgood
][g
]].coords
);
1250 if(distance
< mindistance
)
1251 mindistance
= distance
;
1253 for(g
= 0; g
< producerCount
[neededgood
]; g
++) {
1254 distance
= calculateDistance(&Cities
[city
].coords
, &Cities
[producers
[neededgood
][g
]].coords
);
1255 if(distance
== mindistance
) {
1256 return producers
[neededgood
][g
];
1262 void embark(Convoy
* convoy
, size_t to_city
) {
1263 convoy
->fromCity
= convoy
->locCity
;
1264 assert(convoy
->locCity
!= to_city
);
1265 convoy
->loc
= SLT_SEA
;
1266 convoy
->locCity
= to_city
;
1267 convoy
->coords
= Cities
[convoy
->fromCity
].coords
;
1268 convoy
->stepsdone
= 0;
1271 void land(Convoy
* convoy
) {
1272 convoy
->loc
= SLT_CITY
;
1273 convoy
->coords
= Cities
[convoy
->locCity
].coords
;
1276 float getConvoyMaxStorage(Convoy
* c
) {
1279 for(s
= 1; s
< ST_MAX
; s
++) {
1280 result
+= shipProps
[s
].maxload
* c
->shipcounter
.numShips
[s
];
1285 // blindly moves the goods from/to a market to a convoy. no checks of any sort.
1286 void moveGoodsConvoy(Convoy
* c
, Market
* m
, Goodtype g
, float amount
, unsigned fromConvoy
) {
1290 c
->load
.stock
[g
] += amount
;
1291 c
->totalload
+= amount
;
1292 m
->stock
[g
] -= amount
;
1295 float getSaneAmount(size_t player
, size_t city
, float amount
, Goodtype g
) {
1296 float price
= calculatePrice(city
, g
, amount
, 1);
1297 if(amount
* price
> Players
[player
].money
) amount
= Players
[player
].money
/ price
;
1298 while(amount
> 2.f
&& (price
= calculatePrice(city
, g
, amount
, 1)) &&
1299 ((long long) (price
* amount
) > Players
[player
].money
1300 || price
> (goodcat_minprice
[Goods
[g
].cat
] * 2)
1303 if(amount
* price
> Players
[player
].money
) amount
= 0.f
;
1307 void purchaseFactories(size_t player
, size_t city
, Goodtype g
, size_t amount
) {
1308 long long price
= 0;
1309 size_t industry_id
= 0;
1310 ptrdiff_t branch_id
= -1;
1313 // check if the city can produce the good...
1314 for(i
= 0; i
< CITY_MAX_INDUSTRYTYPES
; i
++) {
1315 if(Cities
[city
].industry
[i
] == g
) {
1322 // check if the player has a branch there...
1323 for (i
=0; i
< Players
[player
].numBranches
; i
++) {
1324 if(Players
[player
].branchCity
[i
] == city
) {
1332 while ((price
= (long long) (Factories
[i
].buildcost
* amount
)) && price
> Players
[player
].money
)
1335 else amount
= amount
/ 2;
1337 Players
[player
].money
-= price
;
1338 Players
[player
].branchFactories
[branch_id
][industry_id
] += amount
;
1341 unsigned canProduce(size_t city
, Goodtype g
) {
1343 for (i
= 0; i
< CITY_MAX_INDUSTRYTYPES
; i
++) {
1344 if(Cities
[city
].industry
[i
] == g
)
1350 void aiThink(void) {
1355 ptrdiff_t city
, shiploc
;
1356 ptrdiff_t new_convoy
;
1358 float freestorage
= 0.f
;
1359 size_t neededgood
= GT_NONE
;
1360 ptrdiff_t nearestcity
;
1361 ptrdiff_t branchOfInterest
;
1362 unsigned jumpedhere
;
1368 for(p
= 0; p
< numPlayers
; p
++) {
1371 if(dude
->type
== PLT_CPU
) {
1373 // go through notifications and setup a plan accordingly, or act directy.
1374 while((last
= &dude
->inbox
.notifications
[dude
->inbox
.last
]) && last
->nt
!= NT_NONE
) {
1377 for (b
= 0; b
< dude
->numBranches
; b
++) {
1378 sellWholeStock(p
, b
, 0);
1383 if(!(dude
->plan
& AIP_SATISFY_MOODS
)) {
1384 if(dude
->numConvoys
== 0) {
1385 // cpu needs at least one convoy to organise goods
1387 for(g
= 1; g
< ST_MAX
; g
++) {
1388 if(Cities
[dude
->branchCity
[b
]].shipyard
.availableShips
.numShips
[g
])
1389 bought
+= buyShips(dude
->branchCity
[b
], p
, Cities
[dude
->branchCity
[b
]].shipyard
.availableShips
.numShips
[g
], g
);
1392 city
= getCityIDFromBranch(b
, p
);
1394 shiploc
= getShipLocationIDFromCity(city
, p
);
1395 assert(shiploc
!= -1);
1396 new_convoy
= makeConvoy(city
, shiploc
, p
, bought
, ST_NONE
);
1397 hireCrew(new_convoy
, p
, getMinCrew(new_convoy
, p
));
1398 hireCaptain(new_convoy
, p
);
1401 dude
->plan
|= AIP_SATISFY_MOODS
;
1402 dude
->plandata
.moodybranch
= b
; // if theres more than one moody branch, the others have to wait...
1405 case NT_OUT_OF_PRODUCTIONGOODS
:
1406 dude
->plan
|= AIP_PURCHASE_PRODUCTION_GOODS
;
1407 dude
->plandata
.branch_req_goods
= last
->val1
;
1408 dude
->plandata
.req_production_good
= last
->val2
;
1409 dude
->plandata
.amount_required
= last
->fval1
;
1413 if(!convoysAvailable(p
, b
))
1414 sellWholeStock(p
, b
, 1);
1416 dude
->plan
|= AIP_SELL_GOODS_FROM_STOCK
;
1422 *last
= emptyNotification
;
1423 if(!dude
->inbox
.last
)
1424 dude
->inbox
.last
= MAX_NOTIFICATIONS
;
1428 if(dude
->plan
& AIP_SATISFY_MOODS
) {
1429 // easy route: see if we got the stuff in our own stock and sell it to the city.
1431 for(g
= 1; g
< GT_MAX
; g
++) {
1432 if(Cities
[dude
->branchCity
[dude
->plandata
.moodybranch
]].market
.stock
[g
] < 1.f
) {
1433 if(dude
->branchStock
[dude
->plandata
.moodybranch
].stock
[g
] > 0.f
) {
1434 sell(dude
->branchCity
[dude
->plandata
.moodybranch
], p
, dude
->plandata
.moodybranch
, -1, g
, dude
->branchStock
[dude
->plandata
.moodybranch
].stock
[g
], SELL_FROM_STOCK
| SELL_TO_CITY
);
1436 } else if (canProduce(dude
->branchCity
[dude
->plandata
.moodybranch
], g
)) {
1437 purchaseFactories(p
, dude
->branchCity
[dude
->plandata
.moodybranch
], g
, 1);
1442 dude
->plan
= dude
->plan
& ~AIP_SATISFY_MOODS
;
1446 // iterate through convoys, if they're in a branch city put load into stock, otherwise sell to city and try to purchase required goods.
1447 for(c
= 0; c
< dude
->numConvoys
; c
++) {
1449 convoy
= &dude
->convoys
[c
];
1451 if(convoy
->loc
== SLT_SEA
)
1454 here
= convoy
->locCity
; // location of the convoi we're dealing with.
1456 if(dude
->numShipLocations
) {
1457 for(x
= 0; x
< dude
->numShipLocations
; x
++) {
1458 if(dude
->singleShips
[x
].total
&& dude
->shipLocations
[x
] == here
)
1459 addToConvoy(convoy
, x
, p
, dude
->singleShips
[x
].total
, ST_NONE
);
1464 // check if the convoy is in a branch of ours
1465 for(b
= 0; b
< dude
->numBranches
; b
++) {
1466 if(here
== dude
->branchCity
[b
]) {
1467 if(convoy
->totalload
> 0.f
) {
1468 // try to put load into warehouse, or sell to city if no space.
1469 for(g
= 1; g
< GT_MAX
; g
++) {
1470 if(convoy
->load
.stock
[g
] > 0.f
) {
1471 freestorage
= getPlayerFreeBranchStorage(b
, p
);
1472 if((dude
->plan
& AIP_SATISFY_MOODS
) && b
== dude
->plandata
.moodybranch
) {
1473 // sell everything to city.
1475 dude
->plan
= dude
->plan
& ~ AIP_SATISFY_MOODS
;
1477 if(freestorage
< convoy
->load
.stock
[g
]) {
1478 sellWholeStock(p
, b
, 1);
1479 freestorage
= getPlayerFreeBranchStorage(b
, p
);
1481 if(freestorage
> convoy
->load
.stock
[g
])
1482 freestorage
= convoy
->load
.stock
[g
];
1484 convoy
->load
.stock
[g
] -= freestorage
;
1485 convoy
->totalload
-= freestorage
;
1486 dude
->branchStock
[b
].stock
[g
] += freestorage
;
1487 if(convoy
->load
.stock
[g
] > 0.f
)
1488 sell(dude
->branchCity
[b
], p
, b
, c
, g
, convoy
->load
.stock
[g
], SELL_FROM_CONVOY
| SELL_TO_CITY
);
1490 if((dude
->plan
& AIP_PURCHASE_PRODUCTION_GOODS
) && g
== dude
->plandata
.req_production_good
&& b
== dude
->plandata
.branch_req_goods
)
1491 dude
->plan
= dude
->plan
& ~ AIP_PURCHASE_PRODUCTION_GOODS
;
1496 // now that the load is cleared, use the convoy for our plans...
1497 // in the order of importance.
1502 branchOfInterest
= -1;
1503 if(dude
->plan
& AIP_SATISFY_MOODS
) {
1504 //check which good we need
1505 neededgood
= GT_NONE
;
1506 for(g
= 1; g
< GT_MAX
; g
++) {
1507 if(Cities
[dude
->branchCity
[dude
->plandata
.moodybranch
]].market
.stock
[g
] < 1.f
) {
1512 if(neededgood
== GT_NONE
) {
1513 dude
->plan
= dude
->plan
& ~AIP_SATISFY_MOODS
;
1515 branchOfInterest
= dude
->plandata
.moodybranch
;
1518 if(dude
->plan
& AIP_SATISFY_MOODS
|| dude
->plan
& AIP_PURCHASE_PRODUCTION_GOODS
) {
1519 if(branchOfInterest
== -1) {
1520 branchOfInterest
= dude
->plandata
.branch_req_goods
;
1521 neededgood
= dude
->plandata
.req_production_good
;
1524 //calculate daily required amount for production, and if convoy maxload is below that, buy additional ships
1525 if ((dude
->plan
& AIP_PURCHASE_PRODUCTION_GOODS
) && getConvoyMaxStorage(convoy
) < dude
->plandata
.amount_required
)
1526 buyShips(here
, p
, 1, ST_A
);
1528 if (jumpedhere
) goto jump2
;
1530 // the branch were the convoy is the branch which needs stuff.
1531 if(b
== (size_t) branchOfInterest
) {
1532 // choose a city which produces the required good, and send convoy there.
1533 if(producerCount
[neededgood
] == 0 || rand() % 2)
1534 nearestcity
= findNearestCityWithGood(here
, neededgood
);
1536 nearestcity
= producers
[neededgood
][rand() % producerCount
[neededgood
]];
1537 if(nearestcity
== -1) {
1538 fprintf(stderr
, "warning, there's no supplier for good %d", (int) neededgood
);
1540 } else if ((size_t) nearestcity
== dude
->branchCity
[branchOfInterest
]) {
1541 // it's not useful to send a ship to where it already is...
1542 for(x
= 0; x
< producerCount
[neededgood
]; x
++) {
1543 if(producers
[neededgood
][x
] != (size_t) nearestcity
) {
1544 nearestcity
= producers
[neededgood
][x
];
1548 if ((size_t) nearestcity
== dude
->branchCity
[branchOfInterest
]) {
1549 // only city producing the good...so lets increase production
1550 purchaseFactories(p
, dude
->branchCity
[branchOfInterest
], neededgood
, 1);
1551 dude
->plan
= dude
->plan
& ~ AIP_PURCHASE_PRODUCTION_GOODS
;
1555 // plan to take regional wares with us - unfortunately that gives us soon moods
1557 freestorage = getConvoyMaxStorage(convoy) - convoy->totalload;
1558 if(freestorage > 0.f) {
1559 for(g = 0; g < CITY_MAX_INDUSTRYTYPES; g++) {
1560 if(dude->branchStock[b].stock[Cities[dude->branchCity[b]].industry[g]] > 0.f)
1561 moveGoodsConvoy(convoy, &dude->branchStock[b], Cities[dude->branchCity[b]].industry[g], freestorage / (float) CITY_MAX_INDUSTRYTYPES, 0);
1563 freestorage = getConvoyMaxStorage(convoy) - convoy->totalload;
1564 if(freestorage > 0.f) {
1565 // see if we can take a regional good with us...
1566 if(Cities[nearestcity].industry[CITY_MAX_INDUSTRYTYPES -1] != Cities[dude->branchCity[b]].industry[CITY_MAX_INDUSTRYTYPES -1]
1567 && Cities[dude->branchCity[b]].market.stock[CITY_MAX_INDUSTRYTYPES -1] > 0.f
1570 freestorage = getSaneAmount(p, dude->branchCity[b], freestorage, Cities[dude->branchCity[b]].industry[CITY_MAX_INDUSTRYTYPES -1]);
1571 sell(dude->branchCity[b], p, b, c, Cities[dude->branchCity[b]].industry[CITY_MAX_INDUSTRYTYPES -1], freestorage, SELL_FROM_CITY | SELL_TO_CONVOY);
1576 embark(convoy
, nearestcity
);
1578 // see if our branch here or the city has the good we want,
1580 if(dude
->branchStock
[b
].stock
[neededgood
] > 0.f
) {
1581 if(dude
->branchStock
[b
].stock
[neededgood
] < freestorage
)
1582 freestorage
= dude
->branchStock
[b
].stock
[neededgood
];
1584 freestorage
= getConvoyMaxStorage(convoy
) - convoy
->totalload
;
1586 moveGoodsConvoy(convoy
, &dude
->branchStock
[b
], neededgood
, freestorage
, 0);
1592 freestorage
= getConvoyMaxStorage(convoy
) - convoy
->totalload
;
1593 if(freestorage
> 0.f
&& Cities
[here
].market
.stock
[neededgood
] > 0.f
) {
1594 if(Cities
[here
].market
.stock
[neededgood
] < freestorage
)
1595 freestorage
= Cities
[here
].market
.stock
[neededgood
];
1597 freestorage
= getSaneAmount(p
, here
, freestorage
, neededgood
);
1598 // FIXME this loop might impact performance. also we should check if the price is much higher than the minprice.
1599 //while(freestorage >= 1.f && (price = calculatePrice(here, neededgood, freestorage, 1)) && (price * freestorage > dude->money)) freestorage /= 2.f;
1600 sell(here
, p
, b
, c
, neededgood
, freestorage
, SELL_FROM_CITY
| SELL_TO_CONVOY
);
1602 embark(convoy
, dude
->branchCity
[branchOfInterest
]);
1606 // no specific plan, buy a random good and gtfo
1615 if(jumpedhere
) // remove once the TODO 5 lines above is removed
1618 // if the convoy was in a branch of ours, we certainly sent it back to sea
1619 if(convoy
->loc
== SLT_SEA
)
1621 // so we'll get here only if it is in a city without a branch.
1622 // means we sell our load, buy something useful and be gone.
1623 if(convoy
->totalload
> 0.f
) {
1624 for(g
= 1; g
< GT_MAX
; g
++) {
1625 if(convoy
->load
.stock
[g
] > 0.f
)
1626 sell(here
, p
, -1, c
, g
, convoy
->load
.stock
[g
], SELL_FROM_CONVOY
| SELL_TO_CITY
);
1636 ShipTypes
getSlowestShip(Convoy
* convoy
) {
1637 size_t slowest
= 10000000;
1639 ShipTypes result
= ST_NONE
;
1640 for(s
= 1; s
< ST_MAX
; s
++) {
1641 if(convoy
->shipcounter
.numShips
[s
] && shipProps
[s
].speed
< slowest
) {
1642 slowest
= shipProps
[s
].speed
;
1654 float milesperminute
[ST_MAX
];
1657 ShipTypes slowestship
;
1661 world
.date
+= GAME_SPEED
+ (world
.date
% GAME_SPEED
);
1663 if(world
.date
% world
.secondsperminute
== 0) {
1664 for(s
= 1; s
< ST_MAX
; s
++) {
1665 milesperminute
[s
] = ((float) shipProps
[s
].speed
) / ((float) world
.minutesperhour
);
1667 // calculate position of ships
1668 for(p
= 0; p
< numPlayers
; p
++) {
1669 player
= &Players
[p
];
1670 for(c
= 0; c
< player
->numConvoys
; c
++) {
1671 convoy
= &player
->convoys
[c
];
1672 if(convoy
->loc
== SLT_SEA
) {
1673 slowestship
= getSlowestShip(convoy
);
1674 if(shipProps
[slowestship
].speed
!= 10)
1675 fprintf(dbg
, "slowest: %d, speed: %d\n", slowestship
, (int) shipProps
[slowestship
].speed
);
1676 dist
= calculateDistance(&Cities
[convoy
->fromCity
].coords
, &Cities
[convoy
->locCity
].coords
);
1677 fprintf(dbg
, "dist from %s to %s: %f\n", Cities
[convoy
->fromCity
].name
->ptr
, Cities
[convoy
->locCity
].name
->ptr
, dist
);
1678 stepsneeded
= dist
/ milesperminute
[slowestship
];
1679 dir
.x
= Cities
[convoy
->fromCity
].coords
.x
- Cities
[convoy
->locCity
].coords
.x
;
1680 dir
.y
= Cities
[convoy
->fromCity
].coords
.y
- Cities
[convoy
->locCity
].coords
.y
;
1681 diff
.x
= dir
.x
/ (dist
/ milesperminute
[slowestship
]); // movement on x axis per turn
1682 diff
.y
= dir
.y
/ (dist
/ milesperminute
[slowestship
]); // -"- y
1683 convoy
->stepsdone
++;
1684 convoy
->condition
-= 0.01;
1685 if (convoy
->stepsdone
>= stepsneeded
) {
1688 convoy
->coords
.x
= Cities
[convoy
->fromCity
].coords
.x
- convoy
->stepsdone
* diff
.x
;
1689 convoy
->coords
.y
= Cities
[convoy
->fromCity
].coords
.y
- convoy
->stepsdone
* diff
.y
;
1696 if(world
.date
% world
._dayseconds
== 0) {
1700 if(world
.date
% (world
.secondsperminute
* (world
.minutesperhour
/ 4)) == 0)
1701 aiThink(); // let the ai think only every 15 minutes... so that we can see its ships at the coast...
1705 int microsleep(long microsecs
) {
1706 struct timespec req
, rem
;
1707 req
.tv_sec
= microsecs
/ 1000000;
1708 req
.tv_nsec
= (microsecs
% 1000000) * 1000;
1710 while((ret
= nanosleep(&req
, &rem
)) == -1 && errno
== EINTR
) req
= rem
;
1714 int main(int argc
, char** argv
) {
1720 dbg
= fopen("debug.pato", "w");
1724 initConsumationTable();
1730 // lets cheat... to speed up
1731 //world.date += world.secondsperminute - 1;
1734 if(gui_processInput(gui
) == -1) break;
1735 if(world
.date
% (world
.secondsperminute
* GAME_SPEED
) == 0)