1 /* $Id$ */ |
|
2 |
|
3 /** @file players.c |
|
4 * @todo Cleanup the messy DrawPlayerFace function asap |
|
5 */ |
|
6 #include "stdafx.h" |
|
7 #include "openttd.h" |
|
8 #include "engine.h" |
|
9 #include "functions.h" |
|
10 #include "string.h" |
|
11 #include "strings.h" |
|
12 #include "table/strings.h" |
|
13 #include "table/sprites.h" |
|
14 #include "map.h" |
|
15 #include "player.h" |
|
16 #include "town.h" |
|
17 #include "vehicle.h" |
|
18 #include "station.h" |
|
19 #include "gfx.h" |
|
20 #include "news.h" |
|
21 #include "saveload.h" |
|
22 #include "command.h" |
|
23 #include "sound.h" |
|
24 #include "network/network.h" |
|
25 #include "variables.h" |
|
26 #include "engine.h" |
|
27 #include "ai/ai.h" |
|
28 #include "date.h" |
|
29 #include "window.h" |
|
30 |
|
31 /** |
|
32 * Sets the local player and updates the patch settings that are set on a |
|
33 * per-company (player) basis to reflect the core's state in the GUI. |
|
34 * @param new_player the new player |
|
35 * @pre IsValidPlayer(new_player) || new_player == PLAYER_SPECTATOR || new_player == OWNER_NONE |
|
36 */ |
|
37 void SetLocalPlayer(PlayerID new_player) |
|
38 { |
|
39 /* Player could also be PLAYER_SPECTATOR or OWNER_NONE */ |
|
40 assert(IsValidPlayer(new_player) || new_player == PLAYER_SPECTATOR || new_player == OWNER_NONE); |
|
41 |
|
42 _local_player = new_player; |
|
43 |
|
44 /* Do not update the patches if we are in the intro GUI */ |
|
45 if (IsValidPlayer(new_player) && _game_mode != GM_MENU) { |
|
46 const Player *p = GetPlayer(new_player); |
|
47 _patches.autorenew = p->engine_renew; |
|
48 _patches.autorenew_months = p->engine_renew_months; |
|
49 _patches.autorenew_money = p->engine_renew_money; |
|
50 InvalidateWindow(WC_GAME_OPTIONS, 0); |
|
51 } |
|
52 } |
|
53 |
|
54 |
|
55 uint16 GetDrawStringPlayerColor(PlayerID player) |
|
56 { |
|
57 /* Get the color for DrawString-subroutines which matches the color |
|
58 * of the player */ |
|
59 if (!IsValidPlayer(player)) return _colour_gradient[COLOUR_WHITE][4] | IS_PALETTE_COLOR; |
|
60 return (_colour_gradient[_player_colors[player]][4]) | IS_PALETTE_COLOR; |
|
61 } |
|
62 |
|
63 |
|
64 static const SpriteID cheeks_table[4] = { |
|
65 0x325, 0x326, |
|
66 0x390, 0x3B0, |
|
67 }; |
|
68 |
|
69 static const SpriteID mouth_table[3] = { |
|
70 0x34C, 0x34D, 0x34F |
|
71 }; |
|
72 |
|
73 void DrawPlayerFace(uint32 face, int color, int x, int y) |
|
74 { |
|
75 byte flag = 0; |
|
76 |
|
77 if ( (int32)face < 0) |
|
78 flag |= 1; |
|
79 if ((((((face >> 7) ^ face) >> 7) ^ face) & 0x8080000) == 0x8000000) |
|
80 flag |= 2; |
|
81 |
|
82 /* draw the gradient */ |
|
83 DrawSprite(GENERAL_SPRITE_COLOR(color) + SPRITE_PALETTE(SPR_GRADIENT), x, y); |
|
84 |
|
85 /* draw the cheeks */ |
|
86 DrawSprite(cheeks_table[flag&3], x, y); |
|
87 |
|
88 /* draw the chin */ |
|
89 /* FIXME: real code uses -2 in zoomlevel 1 */ |
|
90 { |
|
91 uint val = GB(face, 4, 2); |
|
92 if (!(flag & 2)) { |
|
93 DrawSprite(0x327 + (flag&1?0:val), x, y); |
|
94 } else { |
|
95 DrawSprite((flag&1?0x3B1:0x391) + (val>>1), x, y); |
|
96 } |
|
97 } |
|
98 /* draw the eyes */ |
|
99 { |
|
100 uint val1 = GB(face, 6, 4); |
|
101 uint val2 = GB(face, 20, 3); |
|
102 uint32 high = 0x314 << PALETTE_SPRITE_START; |
|
103 |
|
104 if (val2 >= 6) { |
|
105 high = 0x30F << PALETTE_SPRITE_START; |
|
106 if (val2 != 6) |
|
107 high = 0x30D << PALETTE_SPRITE_START; |
|
108 } |
|
109 |
|
110 if (!(flag & 2)) { |
|
111 if (!(flag & 1)) { |
|
112 DrawSprite(high+((val1 * 12 >> 4) + SPRITE_PALETTE(0x32B)), x, y); |
|
113 } else { |
|
114 DrawSprite(high+(val1 + SPRITE_PALETTE(0x337)), x, y); |
|
115 } |
|
116 } else { |
|
117 if (!(flag & 1)) { |
|
118 DrawSprite(high+((val1 * 11 >> 4) + SPRITE_PALETTE(0x39A)), x, y); |
|
119 } else { |
|
120 DrawSprite(high+(val1 + SPRITE_PALETTE(0x3B8)), x, y); |
|
121 } |
|
122 } |
|
123 } |
|
124 |
|
125 /* draw the mouth */ |
|
126 { |
|
127 uint val = GB(face, 10, 6); |
|
128 uint val2; |
|
129 |
|
130 if (!(flag&1)) { |
|
131 val2 = ((val&0xF) * 15 >> 4); |
|
132 |
|
133 if (val2 < 3) { |
|
134 DrawSprite((flag&2 ? 0x397 : 0x367) + val2, x, y); |
|
135 /* skip the rest */ |
|
136 goto skip_mouth; |
|
137 } |
|
138 |
|
139 val2 -= 3; |
|
140 if (flag & 2) { |
|
141 if (val2 > 8) val2 = 0; |
|
142 val2 += 0x3A5 - 0x35B; |
|
143 } |
|
144 DrawSprite(val2 + 0x35B, x, y); |
|
145 } else if (!(flag&2)) { |
|
146 DrawSprite(((val&0xF) * 10 >> 4) + 0x351, x, y); |
|
147 } else { |
|
148 DrawSprite(((val&0xF) * 9 >> 4) + 0x3C8, x, y); |
|
149 } |
|
150 |
|
151 val >>= 3; |
|
152 |
|
153 if (!(flag&2)) { |
|
154 if (!(flag&1)) { |
|
155 DrawSprite(0x349 + val, x, y); |
|
156 } else { |
|
157 DrawSprite( mouth_table[(val*3>>3)], x, y); |
|
158 } |
|
159 } else { |
|
160 if (!(flag&1)) { |
|
161 DrawSprite(0x393 + (val&3), x, y); |
|
162 } else { |
|
163 DrawSprite(0x3B3 + (val*5>>3), x, y); |
|
164 } |
|
165 } |
|
166 |
|
167 skip_mouth:; |
|
168 } |
|
169 |
|
170 |
|
171 /* draw the hair */ |
|
172 { |
|
173 uint val = GB(face, 16, 4); |
|
174 if (flag & 2) { |
|
175 if (flag & 1) { |
|
176 DrawSprite(0x3D9 + (val * 5 >> 4), x, y); |
|
177 } else { |
|
178 DrawSprite(0x3D4 + (val * 5 >> 4), x, y); |
|
179 } |
|
180 } else { |
|
181 if (flag & 1) { |
|
182 DrawSprite(0x38B + (val * 5 >> 4), x, y); |
|
183 } else { |
|
184 DrawSprite(0x382 + (val * 9 >> 4), x, y); |
|
185 } |
|
186 } |
|
187 } |
|
188 |
|
189 /* draw the tie */ |
|
190 { |
|
191 uint val = GB(face, 20, 8); |
|
192 |
|
193 if (!(flag&1)) { |
|
194 DrawSprite(0x36B + (GB(val, 0, 2) * 3 >> 2), x, y); |
|
195 DrawSprite(0x36E + (GB(val, 2, 2) * 4 >> 2), x, y); |
|
196 DrawSprite(0x372 + (GB(val, 4, 4) * 6 >> 4), x, y); |
|
197 } else { |
|
198 DrawSprite(0x378 + (GB(val, 0, 2) * 3 >> 2), x, y); |
|
199 DrawSprite(0x37B + (GB(val, 2, 2) * 4 >> 2), x, y); |
|
200 |
|
201 val >>= 4; |
|
202 if (val < 3) DrawSprite((flag & 2 ? 0x3D1 : 0x37F) + val, x, y); |
|
203 } |
|
204 } |
|
205 |
|
206 /* draw the glasses */ |
|
207 { |
|
208 uint val = GB(face, 28, 3); |
|
209 |
|
210 if (flag & 2) { |
|
211 if (val <= 1) DrawSprite(0x3AE + val, x, y); |
|
212 } else { |
|
213 if (val <= 1) DrawSprite(0x347 + val, x, y); |
|
214 } |
|
215 } |
|
216 } |
|
217 |
|
218 void InvalidatePlayerWindows(const Player *p) |
|
219 { |
|
220 PlayerID pid = p->index; |
|
221 |
|
222 if (pid == _local_player) InvalidateWindow(WC_STATUS_BAR, 0); |
|
223 InvalidateWindow(WC_FINANCES, pid); |
|
224 } |
|
225 |
|
226 bool CheckPlayerHasMoney(int32 cost) |
|
227 { |
|
228 if (cost > 0) { |
|
229 PlayerID pid = _current_player; |
|
230 if (IsValidPlayer(pid) && cost > GetPlayer(pid)->player_money) { |
|
231 SetDParam(0, cost); |
|
232 _error_message = STR_0003_NOT_ENOUGH_CASH_REQUIRES; |
|
233 return false; |
|
234 } |
|
235 } |
|
236 return true; |
|
237 } |
|
238 |
|
239 static void SubtractMoneyFromAnyPlayer(Player *p, int32 cost) |
|
240 { |
|
241 p->money64 -= cost; |
|
242 UpdatePlayerMoney32(p); |
|
243 |
|
244 p->yearly_expenses[0][_yearly_expenses_type] += cost; |
|
245 |
|
246 if (HASBIT(1<<7|1<<8|1<<9|1<<10, _yearly_expenses_type)) { |
|
247 p->cur_economy.income -= cost; |
|
248 } else if (HASBIT(1<<2|1<<3|1<<4|1<<5|1<<6|1<<11, _yearly_expenses_type)) { |
|
249 p->cur_economy.expenses -= cost; |
|
250 } |
|
251 |
|
252 InvalidatePlayerWindows(p); |
|
253 } |
|
254 |
|
255 void SubtractMoneyFromPlayer(int32 cost) |
|
256 { |
|
257 PlayerID pid = _current_player; |
|
258 |
|
259 if (IsValidPlayer(pid)) SubtractMoneyFromAnyPlayer(GetPlayer(pid), cost); |
|
260 } |
|
261 |
|
262 void SubtractMoneyFromPlayerFract(PlayerID player, int32 cost) |
|
263 { |
|
264 Player *p = GetPlayer(player); |
|
265 byte m = p->player_money_fraction; |
|
266 |
|
267 p->player_money_fraction = m - (byte)cost; |
|
268 cost >>= 8; |
|
269 if (p->player_money_fraction > m) cost++; |
|
270 if (cost != 0) SubtractMoneyFromAnyPlayer(p, cost); |
|
271 } |
|
272 |
|
273 // the player_money field is kept as it is, but money64 contains the actual amount of money. |
|
274 void UpdatePlayerMoney32(Player *p) |
|
275 { |
|
276 if (p->money64 < -2000000000) { |
|
277 p->player_money = -2000000000; |
|
278 } else if (p->money64 > 2000000000) { |
|
279 p->player_money = 2000000000; |
|
280 } else { |
|
281 p->player_money = (int32)p->money64; |
|
282 } |
|
283 } |
|
284 |
|
285 void GetNameOfOwner(Owner owner, TileIndex tile) |
|
286 { |
|
287 SetDParam(2, owner); |
|
288 |
|
289 if (owner != OWNER_TOWN) { |
|
290 if (!IsValidPlayer(owner)) { |
|
291 SetDParam(0, STR_0150_SOMEONE); |
|
292 } else { |
|
293 const Player* p = GetPlayer(owner); |
|
294 |
|
295 SetDParam(0, p->name_1); |
|
296 SetDParam(1, p->name_2); |
|
297 } |
|
298 } else { |
|
299 const Town* t = ClosestTownFromTile(tile, (uint)-1); |
|
300 |
|
301 SetDParam(0, STR_TOWN); |
|
302 SetDParam(1, t->index); |
|
303 } |
|
304 } |
|
305 |
|
306 |
|
307 bool CheckOwnership(PlayerID owner) |
|
308 { |
|
309 assert(owner < OWNER_END); |
|
310 |
|
311 if (owner == _current_player) return true; |
|
312 _error_message = STR_013B_OWNED_BY; |
|
313 GetNameOfOwner(owner, 0); |
|
314 return false; |
|
315 } |
|
316 |
|
317 bool CheckTileOwnership(TileIndex tile) |
|
318 { |
|
319 Owner owner = GetTileOwner(tile); |
|
320 |
|
321 assert(owner < OWNER_END); |
|
322 |
|
323 if (owner == _current_player) return true; |
|
324 _error_message = STR_013B_OWNED_BY; |
|
325 |
|
326 // no need to get the name of the owner unless we're the local player (saves some time) |
|
327 if (IsLocalPlayer()) GetNameOfOwner(owner, tile); |
|
328 return false; |
|
329 } |
|
330 |
|
331 static void GenerateCompanyName(Player *p) |
|
332 { |
|
333 TileIndex tile; |
|
334 Town *t; |
|
335 StringID str; |
|
336 Player *pp; |
|
337 uint32 strp; |
|
338 char buffer[100]; |
|
339 |
|
340 if (p->name_1 != STR_SV_UNNAMED) return; |
|
341 |
|
342 tile = p->last_build_coordinate; |
|
343 if (tile == 0) return; |
|
344 |
|
345 t = ClosestTownFromTile(tile, (uint)-1); |
|
346 |
|
347 if (IS_INT_INSIDE(t->townnametype, SPECSTR_TOWNNAME_START, SPECSTR_TOWNNAME_LAST+1)) { |
|
348 str = t->townnametype - SPECSTR_TOWNNAME_START + SPECSTR_PLAYERNAME_START; |
|
349 strp = t->townnameparts; |
|
350 |
|
351 verify_name:; |
|
352 // No player must have this name already |
|
353 FOR_ALL_PLAYERS(pp) { |
|
354 if (pp->name_1 == str && pp->name_2 == strp) goto bad_town_name; |
|
355 } |
|
356 |
|
357 GetString(buffer, str, lastof(buffer)); |
|
358 if (strlen(buffer) >= 32 || GetStringBoundingBox(buffer).width >= 150) |
|
359 goto bad_town_name; |
|
360 |
|
361 set_name:; |
|
362 p->name_1 = str; |
|
363 p->name_2 = strp; |
|
364 |
|
365 MarkWholeScreenDirty(); |
|
366 |
|
367 if (!IsHumanPlayer(p->index)) { |
|
368 SetDParam(0, t->index); |
|
369 AddNewsItem((StringID)(p->index | NB_BNEWCOMPANY), NEWS_FLAGS(NM_CALLBACK, NF_TILE, NT_COMPANY_INFO, DNC_BANKRUPCY), p->last_build_coordinate, 0); |
|
370 } |
|
371 return; |
|
372 } |
|
373 bad_town_name:; |
|
374 |
|
375 if (p->president_name_1 == SPECSTR_PRESIDENT_NAME) { |
|
376 str = SPECSTR_ANDCO_NAME; |
|
377 strp = p->president_name_2; |
|
378 goto set_name; |
|
379 } else { |
|
380 str = SPECSTR_ANDCO_NAME; |
|
381 strp = Random(); |
|
382 goto verify_name; |
|
383 } |
|
384 } |
|
385 |
|
386 #define COLOR_SWAP(i,j) do { byte t=colors[i];colors[i]=colors[j];colors[j]=t; } while(0) |
|
387 |
|
388 static const byte _color_sort[16] = {2, 2, 3, 2, 3, 2, 3, 2, 3, 2, 2, 2, 3, 1, 1, 1}; |
|
389 static const byte _color_similar_1[16] = {8, 6, 255, 12, 255, 0, 1, 1, 0, 13, 11, 10, 3, 9, 15, 14}; |
|
390 static const byte _color_similar_2[16] = {5, 7, 255, 255, 255, 8, 7, 6, 5, 12, 255, 255, 9, 255, 255, 255}; |
|
391 |
|
392 static byte GeneratePlayerColor(void) |
|
393 { |
|
394 byte colors[16], pcolor, t2; |
|
395 int i,j,n; |
|
396 uint32 r; |
|
397 Player *p; |
|
398 |
|
399 // Initialize array |
|
400 for (i = 0; i != 16; i++) colors[i] = i; |
|
401 |
|
402 // And randomize it |
|
403 n = 100; |
|
404 do { |
|
405 r = Random(); |
|
406 COLOR_SWAP(GB(r, 0, 4), GB(r, 4, 4)); |
|
407 } while (--n); |
|
408 |
|
409 // Bubble sort it according to the values in table 1 |
|
410 i = 16; |
|
411 do { |
|
412 for (j = 0; j != 15; j++) { |
|
413 if (_color_sort[colors[j]] < _color_sort[colors[j + 1]]) { |
|
414 COLOR_SWAP(j, j + 1); |
|
415 } |
|
416 } |
|
417 } while (--i); |
|
418 |
|
419 // Move the colors that look similar to each player's color to the side |
|
420 FOR_ALL_PLAYERS(p) if (p->is_active) { |
|
421 pcolor = p->player_color; |
|
422 for (i=0; i!=16; i++) if (colors[i] == pcolor) { |
|
423 colors[i] = 0xFF; |
|
424 |
|
425 t2 = _color_similar_1[pcolor]; |
|
426 if (t2 == 0xFF) break; |
|
427 for (i=0; i!=15; i++) { |
|
428 if (colors[i] == t2) { |
|
429 do COLOR_SWAP(i,i+1); while (++i != 15); |
|
430 break; |
|
431 } |
|
432 } |
|
433 |
|
434 t2 = _color_similar_2[pcolor]; |
|
435 if (t2 == 0xFF) break; |
|
436 for (i = 0; i != 15; i++) { |
|
437 if (colors[i] == t2) { |
|
438 do COLOR_SWAP(i, i + 1); while (++i != 15); |
|
439 break; |
|
440 } |
|
441 } |
|
442 break; |
|
443 } |
|
444 } |
|
445 |
|
446 // Return the first available color |
|
447 for (i = 0;; i++) { |
|
448 if (colors[i] != 0xFF) return colors[i]; |
|
449 } |
|
450 } |
|
451 |
|
452 static void GeneratePresidentName(Player *p) |
|
453 { |
|
454 Player *pp; |
|
455 char buffer[100], buffer2[40]; |
|
456 |
|
457 for (;;) { |
|
458 restart:; |
|
459 |
|
460 p->president_name_2 = Random(); |
|
461 p->president_name_1 = SPECSTR_PRESIDENT_NAME; |
|
462 |
|
463 SetDParam(0, p->president_name_2); |
|
464 GetString(buffer, p->president_name_1, lastof(buffer)); |
|
465 if (strlen(buffer) >= 32 || GetStringBoundingBox(buffer).width >= 94) |
|
466 continue; |
|
467 |
|
468 FOR_ALL_PLAYERS(pp) { |
|
469 if (pp->is_active && p != pp) { |
|
470 SetDParam(0, pp->president_name_2); |
|
471 GetString(buffer2, pp->president_name_1, lastof(buffer2)); |
|
472 if (strcmp(buffer2, buffer) == 0) |
|
473 goto restart; |
|
474 } |
|
475 } |
|
476 return; |
|
477 } |
|
478 } |
|
479 |
|
480 static Player *AllocatePlayer(void) |
|
481 { |
|
482 Player *p; |
|
483 // Find a free slot |
|
484 FOR_ALL_PLAYERS(p) { |
|
485 if (!p->is_active) { |
|
486 int i = p->index; |
|
487 memset(p, 0, sizeof(Player)); |
|
488 p->index = i; |
|
489 return p; |
|
490 } |
|
491 } |
|
492 return NULL; |
|
493 } |
|
494 |
|
495 void ResetPlayerLivery(Player *p) |
|
496 { |
|
497 LiveryScheme scheme; |
|
498 |
|
499 for (scheme = 0; scheme < LS_END; scheme++) { |
|
500 p->livery[scheme].in_use = false; |
|
501 p->livery[scheme].colour1 = p->player_color; |
|
502 p->livery[scheme].colour2 = p->player_color; |
|
503 } |
|
504 } |
|
505 |
|
506 Player *DoStartupNewPlayer(bool is_ai) |
|
507 { |
|
508 Player *p; |
|
509 |
|
510 p = AllocatePlayer(); |
|
511 if (p == NULL) return NULL; |
|
512 |
|
513 // Make a color |
|
514 p->player_color = GeneratePlayerColor(); |
|
515 ResetPlayerLivery(p); |
|
516 _player_colors[p->index] = p->player_color; |
|
517 p->name_1 = STR_SV_UNNAMED; |
|
518 p->is_active = true; |
|
519 |
|
520 p->money64 = p->player_money = p->current_loan = 100000; |
|
521 |
|
522 p->is_ai = is_ai; |
|
523 p->ai.state = 5; /* AIS_WANT_NEW_ROUTE */ |
|
524 p->share_owners[0] = p->share_owners[1] = p->share_owners[2] = p->share_owners[3] = PLAYER_SPECTATOR; |
|
525 |
|
526 p->avail_railtypes = GetPlayerRailtypes(p->index); |
|
527 p->inaugurated_year = _cur_year; |
|
528 p->face = Random(); |
|
529 |
|
530 /* Engine renewal settings */ |
|
531 p->engine_renew_list = NULL; |
|
532 p->renew_keep_length = false; |
|
533 p->engine_renew = false; |
|
534 p->engine_renew_months = -6; |
|
535 p->engine_renew_money = 100000; |
|
536 |
|
537 GeneratePresidentName(p); |
|
538 |
|
539 InvalidateWindow(WC_GRAPH_LEGEND, 0); |
|
540 InvalidateWindow(WC_TOOLBAR_MENU, 0); |
|
541 InvalidateWindow(WC_CLIENT_LIST, 0); |
|
542 |
|
543 if (is_ai && (!_networking || _network_server) && _ai.enabled) |
|
544 AI_StartNewAI(p->index); |
|
545 |
|
546 memset(p->num_engines, 0, sizeof(p->num_engines)); |
|
547 |
|
548 return p; |
|
549 } |
|
550 |
|
551 void StartupPlayers(void) |
|
552 { |
|
553 // The AI starts like in the setting with +2 month max |
|
554 _next_competitor_start = _opt.diff.competitor_start_time * 90 * DAY_TICKS + RandomRange(60 * DAY_TICKS) + 1; |
|
555 } |
|
556 |
|
557 static void MaybeStartNewPlayer(void) |
|
558 { |
|
559 uint n; |
|
560 Player *p; |
|
561 |
|
562 // count number of competitors |
|
563 n = 0; |
|
564 FOR_ALL_PLAYERS(p) { |
|
565 if (p->is_active && p->is_ai) n++; |
|
566 } |
|
567 |
|
568 // when there's a lot of computers in game, the probability that a new one starts is lower |
|
569 if (n < (uint)_opt.diff.max_no_competitors && |
|
570 n < (_network_server ? |
|
571 InteractiveRandomRange(_opt.diff.max_no_competitors + 2) : |
|
572 RandomRange(_opt.diff.max_no_competitors + 2) |
|
573 )) { |
|
574 /* Send a command to all clients to start up a new AI. |
|
575 * Works fine for Multiplayer and Singleplayer */ |
|
576 DoCommandP(0, 1, 0, NULL, CMD_PLAYER_CTRL); |
|
577 } |
|
578 |
|
579 // The next AI starts like the difficulty setting said, with +2 month max |
|
580 _next_competitor_start = _opt.diff.competitor_start_time * 90 * DAY_TICKS + 1; |
|
581 _next_competitor_start += _network_server ? InteractiveRandomRange(60 * DAY_TICKS) : RandomRange(60 * DAY_TICKS); |
|
582 } |
|
583 |
|
584 void InitializePlayers(void) |
|
585 { |
|
586 uint i; |
|
587 |
|
588 memset(_players, 0, sizeof(_players)); |
|
589 for (i = 0; i != MAX_PLAYERS; i++) _players[i].index = i; |
|
590 _cur_player_tick_index = 0; |
|
591 } |
|
592 |
|
593 void OnTick_Players(void) |
|
594 { |
|
595 Player *p; |
|
596 |
|
597 if (_game_mode == GM_EDITOR) return; |
|
598 |
|
599 p = GetPlayer(_cur_player_tick_index); |
|
600 _cur_player_tick_index = (_cur_player_tick_index + 1) % MAX_PLAYERS; |
|
601 if (p->name_1 != 0) GenerateCompanyName(p); |
|
602 |
|
603 if (AI_AllowNewAI() && _game_mode != GM_MENU && !--_next_competitor_start) |
|
604 MaybeStartNewPlayer(); |
|
605 } |
|
606 |
|
607 // index is the next parameter in _decode_parameters to set up |
|
608 StringID GetPlayerNameString(PlayerID player, uint index) |
|
609 { |
|
610 if (IsHumanPlayer(player) && IsValidPlayer(player)) { |
|
611 SetDParam(index, player+1); |
|
612 return STR_7002_PLAYER; |
|
613 } |
|
614 return STR_EMPTY; |
|
615 } |
|
616 |
|
617 extern void ShowPlayerFinances(int player); |
|
618 |
|
619 void PlayersYearlyLoop(void) |
|
620 { |
|
621 Player *p; |
|
622 |
|
623 // Copy statistics |
|
624 FOR_ALL_PLAYERS(p) { |
|
625 if (p->is_active) { |
|
626 memmove(&p->yearly_expenses[1], &p->yearly_expenses[0], sizeof(p->yearly_expenses) - sizeof(p->yearly_expenses[0])); |
|
627 memset(&p->yearly_expenses[0], 0, sizeof(p->yearly_expenses[0])); |
|
628 InvalidateWindow(WC_FINANCES, p->index); |
|
629 } |
|
630 } |
|
631 |
|
632 if (_patches.show_finances && _local_player != PLAYER_SPECTATOR) { |
|
633 ShowPlayerFinances(_local_player); |
|
634 p = GetPlayer(_local_player); |
|
635 if (p->num_valid_stat_ent > 5 && p->old_economy[0].performance_history < p->old_economy[4].performance_history) { |
|
636 SndPlayFx(SND_01_BAD_YEAR); |
|
637 } else { |
|
638 SndPlayFx(SND_00_GOOD_YEAR); |
|
639 } |
|
640 } |
|
641 } |
|
642 |
|
643 byte GetPlayerRailtypes(PlayerID p) |
|
644 { |
|
645 byte rt = 0; |
|
646 EngineID i; |
|
647 |
|
648 for (i = 0; i != TOTAL_NUM_ENGINES; i++) { |
|
649 const Engine* e = GetEngine(i); |
|
650 const EngineInfo *ei = EngInfo(i); |
|
651 |
|
652 if (e->type == VEH_Train && HASBIT(ei->climates, _opt.landscape) && |
|
653 (HASBIT(e->player_avail, p) || _date >= (e->intro_date + 365)) && |
|
654 !(RailVehInfo(i)->flags & RVI_WAGON)) { |
|
655 assert(e->railtype < RAILTYPE_END); |
|
656 SETBIT(rt, e->railtype); |
|
657 } |
|
658 } |
|
659 |
|
660 return rt; |
|
661 } |
|
662 |
|
663 static void DeletePlayerStuff(PlayerID pi) |
|
664 { |
|
665 Player *p; |
|
666 |
|
667 DeletePlayerWindows(pi); |
|
668 p = GetPlayer(pi); |
|
669 DeleteName(p->name_1); |
|
670 DeleteName(p->president_name_1); |
|
671 p->name_1 = 0; |
|
672 p->president_name_1 = 0; |
|
673 } |
|
674 |
|
675 /** Change engine renewal parameters |
|
676 * @param tile unused |
|
677 * @param p1 bits 0-3 command |
|
678 * - p1 = 0 - change auto renew bool |
|
679 * - p1 = 1 - change auto renew months |
|
680 * - p1 = 2 - change auto renew money |
|
681 * - p1 = 3 - change auto renew array |
|
682 * - p1 = 4 - change bool, months & money all together |
|
683 * - p1 = 5 - change renew_keep_length |
|
684 * @param p2 value to set |
|
685 * if p1 = 0, then: |
|
686 * - p2 = enable engine renewal |
|
687 * if p1 = 1, then: |
|
688 * - p2 = months left before engine expires to replace it |
|
689 * if p1 = 2, then |
|
690 * - p2 = minimum amount of money available |
|
691 * if p1 = 3, then: |
|
692 * - p2 bits 0-15 = old engine type |
|
693 * - p2 bits 16-31 = new engine type |
|
694 * if p1 = 4, then: |
|
695 * - p1 bit 15 = enable engine renewal |
|
696 * - p1 bits 16-31 = months left before engine expires to replace it |
|
697 * - p2 bits 0-31 = minimum amount of money available |
|
698 * if p1 = 5, then |
|
699 * - p2 = enable renew_keep_length |
|
700 */ |
|
701 int32 CmdSetAutoReplace(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) |
|
702 { |
|
703 Player *p; |
|
704 if (!IsValidPlayer(_current_player)) return CMD_ERROR; |
|
705 |
|
706 p = GetPlayer(_current_player); |
|
707 switch (GB(p1, 0, 3)) { |
|
708 case 0: |
|
709 if (p->engine_renew == (bool)GB(p2, 0, 1)) |
|
710 return CMD_ERROR; |
|
711 |
|
712 if (flags & DC_EXEC) { |
|
713 p->engine_renew = (bool)GB(p2, 0, 1); |
|
714 if (IsLocalPlayer()) { |
|
715 _patches.autorenew = p->engine_renew; |
|
716 InvalidateWindow(WC_GAME_OPTIONS, 0); |
|
717 } |
|
718 } |
|
719 break; |
|
720 case 1: |
|
721 if (p->engine_renew_months == (int16)p2) |
|
722 return CMD_ERROR; |
|
723 |
|
724 if (flags & DC_EXEC) { |
|
725 p->engine_renew_months = (int16)p2; |
|
726 if (IsLocalPlayer()) { |
|
727 _patches.autorenew_months = p->engine_renew_months; |
|
728 InvalidateWindow(WC_GAME_OPTIONS, 0); |
|
729 } |
|
730 } |
|
731 break; |
|
732 case 2: |
|
733 if (p->engine_renew_money == (uint32)p2) |
|
734 return CMD_ERROR; |
|
735 |
|
736 if (flags & DC_EXEC) { |
|
737 p->engine_renew_money = (uint32)p2; |
|
738 if (IsLocalPlayer()) { |
|
739 _patches.autorenew_money = p->engine_renew_money; |
|
740 InvalidateWindow(WC_GAME_OPTIONS, 0); |
|
741 } |
|
742 } |
|
743 break; |
|
744 case 3: { |
|
745 EngineID old_engine_type = GB(p2, 0, 16); |
|
746 EngineID new_engine_type = GB(p2, 16, 16); |
|
747 int32 cost; |
|
748 |
|
749 if (new_engine_type != INVALID_ENGINE) { |
|
750 /* First we make sure that it's a valid type the user requested |
|
751 * check that it's an engine that is in the engine array */ |
|
752 if (!IsEngineIndex(new_engine_type)) |
|
753 return CMD_ERROR; |
|
754 |
|
755 // check that the new vehicle type is the same as the original one |
|
756 if (GetEngine(old_engine_type)->type != GetEngine(new_engine_type)->type) |
|
757 return CMD_ERROR; |
|
758 |
|
759 // make sure that we do not replace a plane with a helicopter or vise versa |
|
760 if (GetEngine(new_engine_type)->type == VEH_Aircraft && |
|
761 (AircraftVehInfo(old_engine_type)->subtype & AIR_CTOL) != (AircraftVehInfo(new_engine_type)->subtype & AIR_CTOL)) |
|
762 return CMD_ERROR; |
|
763 |
|
764 // make sure that the player can actually buy the new engine |
|
765 if (!HASBIT(GetEngine(new_engine_type)->player_avail, _current_player)) |
|
766 return CMD_ERROR; |
|
767 |
|
768 cost = AddEngineReplacementForPlayer(p, old_engine_type, new_engine_type, flags); |
|
769 } else { |
|
770 cost = RemoveEngineReplacementForPlayer(p, old_engine_type, flags); |
|
771 } |
|
772 |
|
773 if (IsLocalPlayer()) InvalidateWindow(WC_REPLACE_VEHICLE, GetEngine(old_engine_type)->type); |
|
774 |
|
775 return cost; |
|
776 } |
|
777 |
|
778 case 4: |
|
779 if (flags & DC_EXEC) { |
|
780 p->engine_renew = (bool)GB(p1, 15, 1); |
|
781 p->engine_renew_months = (int16)GB(p1, 16, 16); |
|
782 p->engine_renew_money = (uint32)p2; |
|
783 |
|
784 if (IsLocalPlayer()) { |
|
785 _patches.autorenew = p->engine_renew; |
|
786 _patches.autorenew_months = p->engine_renew_months; |
|
787 _patches.autorenew_money = p->engine_renew_money; |
|
788 InvalidateWindow(WC_GAME_OPTIONS, 0); |
|
789 } |
|
790 } |
|
791 break; |
|
792 case 5: |
|
793 if (p->renew_keep_length == (bool)GB(p2, 0, 1)) |
|
794 return CMD_ERROR; |
|
795 |
|
796 if (flags & DC_EXEC) { |
|
797 p->renew_keep_length = (bool)GB(p2, 0, 1); |
|
798 if (IsLocalPlayer()) { |
|
799 InvalidateWindow(WC_REPLACE_VEHICLE, VEH_Train); |
|
800 } |
|
801 } |
|
802 break; |
|
803 |
|
804 } |
|
805 return 0; |
|
806 } |
|
807 |
|
808 /** Control the players: add, delete, etc. |
|
809 * @param tile unused |
|
810 * @param p1 various functionality |
|
811 * - p1 = 0 - create a new player, Which player (network) it will be is in p2 |
|
812 * - p1 = 1 - create a new AI player |
|
813 * - p1 = 2 - delete a player. Player is identified by p2 |
|
814 * - p1 = 3 - merge two companies together. Player to merge #1 with player #2. Identified by p2 |
|
815 * @param p2 various functionality, dictated by p1 |
|
816 * - p1 = 0 - ClientID of the newly created player |
|
817 * - p1 = 2 - PlayerID of the that is getting deleted |
|
818 * - p1 = 3 - #1 p2 = (bit 0-15) - player to merge (p2 & 0xFFFF) |
|
819 * - #2 p2 = (bit 16-31) - player to be merged into ((p2>>16)&0xFFFF) |
|
820 * @todo In the case of p1=0, create new player, the clientID of the new player is in parameter |
|
821 * p2. This parameter is passed in at function DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_COMMAND) |
|
822 * on the server itself. First of all this is unbelievably ugly; second of all, well, |
|
823 * it IS ugly! <b>Someone fix this up :)</b> So where to fix?@n |
|
824 * @arg - network_server.c:838 DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_COMMAND)@n |
|
825 * @arg - network_client.c:536 DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_MAP) from where the map has been received |
|
826 */ |
|
827 int32 CmdPlayerCtrl(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) |
|
828 { |
|
829 if (flags & DC_EXEC) _current_player = OWNER_NONE; |
|
830 |
|
831 switch (p1) { |
|
832 case 0: { /* Create a new player */ |
|
833 /* Joining Client: |
|
834 * _local_player: PLAYER_SPECTATOR |
|
835 * _network_playas/cid = requested company/player |
|
836 * |
|
837 * Other client(s)/server: |
|
838 * _local_player/_network_playas: what they play as |
|
839 * cid = requested company/player of joining client */ |
|
840 Player *p; |
|
841 uint16 cid = p2; // ClientID |
|
842 |
|
843 /* This command is only executed in a multiplayer game */ |
|
844 if (!_networking) return CMD_ERROR; |
|
845 |
|
846 /* Has the network client a correct ClientID? */ |
|
847 if (!(flags & DC_EXEC)) return 0; |
|
848 #ifdef ENABLE_NETWORK |
|
849 if (cid >= MAX_CLIENT_INFO) return 0; |
|
850 #endif /* ENABLE_NETWORK */ |
|
851 |
|
852 /* Delete multiplayer progress bar */ |
|
853 DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0); |
|
854 |
|
855 p = DoStartupNewPlayer(false); |
|
856 |
|
857 /* A new player could not be created, revert to being a spectator */ |
|
858 if (p == NULL) { |
|
859 #ifdef ENABLE_NETWORK |
|
860 if (_network_server) { |
|
861 NetworkClientInfo *ci = &_network_client_info[cid]; |
|
862 ci->client_playas = PLAYER_SPECTATOR; |
|
863 NetworkUpdateClientInfo(ci->client_index); |
|
864 } else |
|
865 #endif /* ENABLE_NETWORK */ |
|
866 { |
|
867 _network_playas = PLAYER_SPECTATOR; |
|
868 SetLocalPlayer(PLAYER_SPECTATOR); |
|
869 } |
|
870 break; |
|
871 } |
|
872 |
|
873 /* This is the joining client who wants a new company */ |
|
874 if (_local_player != _network_playas) { |
|
875 assert(_local_player == PLAYER_SPECTATOR && _network_playas == p->index); |
|
876 SetLocalPlayer(p->index); |
|
877 MarkWholeScreenDirty(); |
|
878 } |
|
879 |
|
880 /* Now that we have a new player, broadcast its autorenew settings to |
|
881 * all clients so everything is in sync */ |
|
882 DoCommand(0, |
|
883 (_patches.autorenew << 15 ) | (_patches.autorenew_months << 16) | 4, |
|
884 _patches.autorenew_money, |
|
885 DC_EXEC, |
|
886 CMD_SET_AUTOREPLACE |
|
887 ); |
|
888 |
|
889 #ifdef ENABLE_NETWORK |
|
890 if (_network_server) { |
|
891 /* XXX - UGLY! p2 (pid) is mis-used to fetch the client-id, done at |
|
892 * server-side in network_server.c:838, function |
|
893 * DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_COMMAND) */ |
|
894 NetworkClientInfo *ci = &_network_client_info[cid]; |
|
895 ci->client_playas = p->index; |
|
896 NetworkUpdateClientInfo(ci->client_index); |
|
897 |
|
898 if (IsValidPlayer(ci->client_playas)) { |
|
899 PlayerID player_backup = _local_player; |
|
900 _network_player_info[p->index].months_empty = 0; |
|
901 |
|
902 /* XXX - When a client joins, we automatically set its name to the |
|
903 * player's name (for some reason). As it stands now only the server |
|
904 * knows the client's name, so it needs to send out a "broadcast" to |
|
905 * do this. To achieve this we send a network command. However, it |
|
906 * uses _local_player to execute the command as. To prevent abuse |
|
907 * (eg. only yourself can change your name/company), we 'cheat' by |
|
908 * impersonation _local_player as the server. Not the best solution; |
|
909 * but it works. |
|
910 * TODO: Perhaps this could be improved by when the client is ready |
|
911 * with joining to let it send itself the command, and not the server? |
|
912 * For example in network_client.c:534? */ |
|
913 _cmd_text = ci->client_name; |
|
914 _local_player = ci->client_playas; |
|
915 NetworkSend_Command(0, 0, 0, CMD_CHANGE_PRESIDENT_NAME, NULL); |
|
916 _local_player = player_backup; |
|
917 } |
|
918 } |
|
919 #endif /* ENABLE_NETWORK */ |
|
920 } break; |
|
921 |
|
922 case 1: /* Make a new AI player */ |
|
923 if (!(flags & DC_EXEC)) return 0; |
|
924 |
|
925 DoStartupNewPlayer(true); |
|
926 break; |
|
927 |
|
928 case 2: { /* Delete a player */ |
|
929 Player *p; |
|
930 |
|
931 if (!IsValidPlayer(p2)) return CMD_ERROR; |
|
932 |
|
933 if (!(flags & DC_EXEC)) return 0; |
|
934 |
|
935 p = GetPlayer(p2); |
|
936 |
|
937 /* Only allow removal of HUMAN companies */ |
|
938 if (IsHumanPlayer(p->index)) { |
|
939 /* Delete any open window of the company */ |
|
940 DeletePlayerWindows(p->index); |
|
941 |
|
942 /* Show the bankrupt news */ |
|
943 SetDParam(0, p->name_1); |
|
944 SetDParam(1, p->name_2); |
|
945 AddNewsItem( (StringID)(p->index | NB_BBANKRUPT), NEWS_FLAGS(NM_CALLBACK, 0, NT_COMPANY_INFO, DNC_BANKRUPCY),0,0); |
|
946 |
|
947 /* Remove the company */ |
|
948 ChangeOwnershipOfPlayerItems(p->index, PLAYER_SPECTATOR); |
|
949 p->is_active = false; |
|
950 } |
|
951 RemoveAllEngineReplacementForPlayer(p); |
|
952 |
|
953 } break; |
|
954 |
|
955 case 3: { /* Merge a company (#1) into another company (#2), elimination company #1 */ |
|
956 PlayerID pid_old = GB(p2, 0, 16); |
|
957 PlayerID pid_new = GB(p2, 16, 16); |
|
958 |
|
959 if (!IsValidPlayer(pid_old) || !IsValidPlayer(pid_new)) return CMD_ERROR; |
|
960 |
|
961 if (!(flags & DC_EXEC)) return CMD_ERROR; |
|
962 |
|
963 ChangeOwnershipOfPlayerItems(pid_old, pid_new); |
|
964 DeletePlayerStuff(pid_old); |
|
965 } break; |
|
966 |
|
967 default: return CMD_ERROR; |
|
968 } |
|
969 |
|
970 return 0; |
|
971 } |
|
972 |
|
973 static const StringID _endgame_perf_titles[] = { |
|
974 STR_0213_BUSINESSMAN, |
|
975 STR_0213_BUSINESSMAN, |
|
976 STR_0213_BUSINESSMAN, |
|
977 STR_0213_BUSINESSMAN, |
|
978 STR_0213_BUSINESSMAN, |
|
979 STR_0214_ENTREPRENEUR, |
|
980 STR_0214_ENTREPRENEUR, |
|
981 STR_0215_INDUSTRIALIST, |
|
982 STR_0215_INDUSTRIALIST, |
|
983 STR_0216_CAPITALIST, |
|
984 STR_0216_CAPITALIST, |
|
985 STR_0217_MAGNATE, |
|
986 STR_0217_MAGNATE, |
|
987 STR_0218_MOGUL, |
|
988 STR_0218_MOGUL, |
|
989 STR_0219_TYCOON_OF_THE_CENTURY |
|
990 }; |
|
991 |
|
992 StringID EndGameGetPerformanceTitleFromValue(uint value) |
|
993 { |
|
994 value = minu(value / 64, lengthof(_endgame_perf_titles) - 1); |
|
995 |
|
996 return _endgame_perf_titles[value]; |
|
997 } |
|
998 |
|
999 /* Return true if any cheat has been used, false otherwise */ |
|
1000 static bool CheatHasBeenUsed(void) |
|
1001 { |
|
1002 const Cheat* cht = (Cheat*)&_cheats; |
|
1003 const Cheat* cht_last = &cht[sizeof(_cheats) / sizeof(Cheat)]; |
|
1004 |
|
1005 for (; cht != cht_last; cht++) { |
|
1006 if (cht->been_used) return true; |
|
1007 } |
|
1008 |
|
1009 return false; |
|
1010 } |
|
1011 |
|
1012 /* Save the highscore for the player */ |
|
1013 int8 SaveHighScoreValue(const Player *p) |
|
1014 { |
|
1015 HighScore *hs = _highscore_table[_opt.diff_level]; |
|
1016 uint i; |
|
1017 uint16 score = p->old_economy[0].performance_history; |
|
1018 |
|
1019 /* Exclude cheaters from the honour of being in the highscore table */ |
|
1020 if (CheatHasBeenUsed()) return -1; |
|
1021 |
|
1022 for (i = 0; i < lengthof(_highscore_table[0]); i++) { |
|
1023 /* You are in the TOP5. Move all values one down and save us there */ |
|
1024 if (hs[i].score <= score) { |
|
1025 // move all elements one down starting from the replaced one |
|
1026 memmove(&hs[i + 1], &hs[i], sizeof(HighScore) * (lengthof(_highscore_table[0]) - i - 1)); |
|
1027 SetDParam(0, p->president_name_1); |
|
1028 SetDParam(1, p->president_name_2); |
|
1029 SetDParam(2, p->name_1); |
|
1030 SetDParam(3, p->name_2); |
|
1031 GetString(hs[i].company, STR_HIGHSCORE_NAME, lastof(hs[i].company)); // get manager/company name string |
|
1032 hs[i].score = score; |
|
1033 hs[i].title = EndGameGetPerformanceTitleFromValue(score); |
|
1034 return i; |
|
1035 } |
|
1036 } |
|
1037 |
|
1038 return -1; // too bad; we did not make it into the top5 |
|
1039 } |
|
1040 |
|
1041 /* Sort all players given their performance */ |
|
1042 static int CDECL HighScoreSorter(const void *a, const void *b) |
|
1043 { |
|
1044 const Player *pa = *(const Player* const*)a; |
|
1045 const Player *pb = *(const Player* const*)b; |
|
1046 |
|
1047 return pb->old_economy[0].performance_history - pa->old_economy[0].performance_history; |
|
1048 } |
|
1049 |
|
1050 /* Save the highscores in a network game when it has ended */ |
|
1051 #define LAST_HS_ITEM lengthof(_highscore_table) - 1 |
|
1052 int8 SaveHighScoreValueNetwork(void) |
|
1053 { |
|
1054 const Player* p; |
|
1055 const Player* pl[MAX_PLAYERS]; |
|
1056 size_t count = 0; |
|
1057 int8 player = -1; |
|
1058 |
|
1059 /* Sort all active players with the highest score first */ |
|
1060 FOR_ALL_PLAYERS(p) if (p->is_active) pl[count++] = p; |
|
1061 qsort((Player*)pl, count, sizeof(pl[0]), HighScoreSorter); |
|
1062 |
|
1063 { |
|
1064 uint i; |
|
1065 |
|
1066 memset(_highscore_table[LAST_HS_ITEM], 0, sizeof(_highscore_table[0])); |
|
1067 |
|
1068 /* Copy over Top5 companies */ |
|
1069 for (i = 0; i < lengthof(_highscore_table[LAST_HS_ITEM]) && i < count; i++) { |
|
1070 HighScore* hs = &_highscore_table[LAST_HS_ITEM][i]; |
|
1071 |
|
1072 SetDParam(0, pl[i]->president_name_1); |
|
1073 SetDParam(1, pl[i]->president_name_2); |
|
1074 SetDParam(2, pl[i]->name_1); |
|
1075 SetDParam(3, pl[i]->name_2); |
|
1076 GetString(hs->company, STR_HIGHSCORE_NAME, lastof(hs->company)); // get manager/company name string |
|
1077 hs->score = pl[i]->old_economy[0].performance_history; |
|
1078 hs->title = EndGameGetPerformanceTitleFromValue(hs->score); |
|
1079 |
|
1080 // get the ranking of the local player |
|
1081 if (pl[i]->index == _local_player) player = i; |
|
1082 } |
|
1083 } |
|
1084 |
|
1085 /* Add top5 players to highscore table */ |
|
1086 return player; |
|
1087 } |
|
1088 |
|
1089 /* Save HighScore table to file */ |
|
1090 void SaveToHighScore(void) |
|
1091 { |
|
1092 FILE *fp = fopen(_highscore_file, "wb"); |
|
1093 |
|
1094 if (fp != NULL) { |
|
1095 uint i; |
|
1096 HighScore *hs; |
|
1097 |
|
1098 for (i = 0; i < LAST_HS_ITEM; i++) { // don't save network highscores |
|
1099 for (hs = _highscore_table[i]; hs != endof(_highscore_table[i]); hs++) { |
|
1100 /* First character is a command character, so strlen will fail on that */ |
|
1101 byte length = min(sizeof(hs->company), (hs->company[0] == '\0') ? 0 : (int)strlen(&hs->company[1]) + 1); |
|
1102 |
|
1103 fwrite(&length, sizeof(length), 1, fp); // write away string length |
|
1104 fwrite(hs->company, length, 1, fp); |
|
1105 fwrite(&hs->score, sizeof(hs->score), 1, fp); |
|
1106 fwrite("", 2, 1, fp); /* XXX - placeholder for hs->title, not saved anymore; compatibility */ |
|
1107 } |
|
1108 } |
|
1109 fclose(fp); |
|
1110 } |
|
1111 } |
|
1112 |
|
1113 /* Initialize the highscore table to 0 and if any file exists, load in values */ |
|
1114 void LoadFromHighScore(void) |
|
1115 { |
|
1116 FILE *fp = fopen(_highscore_file, "rb"); |
|
1117 |
|
1118 memset(_highscore_table, 0, sizeof(_highscore_table)); |
|
1119 |
|
1120 if (fp != NULL) { |
|
1121 uint i; |
|
1122 HighScore *hs; |
|
1123 |
|
1124 for (i = 0; i < LAST_HS_ITEM; i++) { // don't load network highscores |
|
1125 for (hs = _highscore_table[i]; hs != endof(_highscore_table[i]); hs++) { |
|
1126 byte length; |
|
1127 fread(&length, sizeof(length), 1, fp); |
|
1128 |
|
1129 fread(hs->company, 1, length, fp); |
|
1130 fread(&hs->score, sizeof(hs->score), 1, fp); |
|
1131 fseek(fp, 2, SEEK_CUR); /* XXX - placeholder for hs->title, not saved anymore; compatibility */ |
|
1132 hs->title = EndGameGetPerformanceTitleFromValue(hs->score); |
|
1133 } |
|
1134 } |
|
1135 fclose(fp); |
|
1136 } |
|
1137 |
|
1138 /* Initialize end of game variable (when to show highscore chart) */ |
|
1139 _patches.ending_year = 2051; |
|
1140 } |
|
1141 |
|
1142 // Save/load of players |
|
1143 static const SaveLoad _player_desc[] = { |
|
1144 SLE_VAR(Player, name_2, SLE_UINT32), |
|
1145 SLE_VAR(Player, name_1, SLE_STRINGID), |
|
1146 |
|
1147 SLE_VAR(Player, president_name_1,SLE_UINT16), |
|
1148 SLE_VAR(Player, president_name_2,SLE_UINT32), |
|
1149 |
|
1150 SLE_VAR(Player, face, SLE_UINT32), |
|
1151 |
|
1152 // money was changed to a 64 bit field in savegame version 1. |
|
1153 SLE_CONDVAR(Player, money64, SLE_VAR_I64 | SLE_FILE_I32, 0, 0), |
|
1154 SLE_CONDVAR(Player, money64, SLE_INT64, 1, SL_MAX_VERSION), |
|
1155 |
|
1156 SLE_VAR(Player, current_loan, SLE_INT32), |
|
1157 |
|
1158 SLE_VAR(Player, player_color, SLE_UINT8), |
|
1159 SLE_VAR(Player, player_money_fraction, SLE_UINT8), |
|
1160 SLE_VAR(Player, avail_railtypes, SLE_UINT8), |
|
1161 SLE_VAR(Player, block_preview, SLE_UINT8), |
|
1162 |
|
1163 SLE_VAR(Player, cargo_types, SLE_UINT16), |
|
1164 SLE_CONDVAR(Player, location_of_house, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), |
|
1165 SLE_CONDVAR(Player, location_of_house, SLE_UINT32, 6, SL_MAX_VERSION), |
|
1166 SLE_CONDVAR(Player, last_build_coordinate, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), |
|
1167 SLE_CONDVAR(Player, last_build_coordinate, SLE_UINT32, 6, SL_MAX_VERSION), |
|
1168 SLE_CONDVAR(Player, inaugurated_year, SLE_FILE_U8 | SLE_VAR_I32, 0, 30), |
|
1169 SLE_CONDVAR(Player, inaugurated_year, SLE_INT32, 31, SL_MAX_VERSION), |
|
1170 |
|
1171 SLE_ARR(Player, share_owners, SLE_UINT8, 4), |
|
1172 |
|
1173 SLE_VAR(Player, num_valid_stat_ent, SLE_UINT8), |
|
1174 |
|
1175 SLE_VAR(Player, quarters_of_bankrupcy, SLE_UINT8), |
|
1176 SLE_VAR(Player, bankrupt_asked, SLE_UINT8), |
|
1177 SLE_VAR(Player, bankrupt_timeout, SLE_INT16), |
|
1178 SLE_VAR(Player, bankrupt_value, SLE_INT32), |
|
1179 |
|
1180 // yearly expenses was changed to 64-bit in savegame version 2. |
|
1181 SLE_CONDARR(Player, yearly_expenses, SLE_FILE_I32 | SLE_VAR_I64, 3 * 13, 0, 1), |
|
1182 SLE_CONDARR(Player, yearly_expenses, SLE_INT64, 3 * 13, 2, SL_MAX_VERSION), |
|
1183 |
|
1184 SLE_CONDVAR(Player, is_ai, SLE_BOOL, 2, SL_MAX_VERSION), |
|
1185 SLE_CONDVAR(Player, is_active, SLE_BOOL, 4, SL_MAX_VERSION), |
|
1186 |
|
1187 // Engine renewal settings |
|
1188 SLE_CONDNULL(512, 16, 18), |
|
1189 SLE_CONDREF(Player, engine_renew_list, REF_ENGINE_RENEWS, 19, SL_MAX_VERSION), |
|
1190 SLE_CONDVAR(Player, engine_renew, SLE_BOOL, 16, SL_MAX_VERSION), |
|
1191 SLE_CONDVAR(Player, engine_renew_months, SLE_INT16, 16, SL_MAX_VERSION), |
|
1192 SLE_CONDVAR(Player, engine_renew_money, SLE_UINT32, 16, SL_MAX_VERSION), |
|
1193 SLE_CONDVAR(Player, renew_keep_length, SLE_BOOL, 2, SL_MAX_VERSION), // added with 16.1, but was blank since 2 |
|
1194 |
|
1195 // reserve extra space in savegame here. (currently 63 bytes) |
|
1196 SLE_CONDNULL(63, 2, SL_MAX_VERSION), |
|
1197 |
|
1198 SLE_END() |
|
1199 }; |
|
1200 |
|
1201 static const SaveLoad _player_economy_desc[] = { |
|
1202 // these were changed to 64-bit in savegame format 2 |
|
1203 SLE_CONDVAR(PlayerEconomyEntry, income, SLE_INT32, 0, 1), |
|
1204 SLE_CONDVAR(PlayerEconomyEntry, expenses, SLE_INT32, 0, 1), |
|
1205 SLE_CONDVAR(PlayerEconomyEntry, company_value, SLE_FILE_I32 | SLE_VAR_I64, 0, 1), |
|
1206 SLE_CONDVAR(PlayerEconomyEntry, income, SLE_FILE_I64 | SLE_VAR_I32, 2, SL_MAX_VERSION), |
|
1207 SLE_CONDVAR(PlayerEconomyEntry, expenses, SLE_FILE_I64 | SLE_VAR_I32, 2, SL_MAX_VERSION), |
|
1208 SLE_CONDVAR(PlayerEconomyEntry, company_value, SLE_INT64, 2, SL_MAX_VERSION), |
|
1209 |
|
1210 SLE_VAR(PlayerEconomyEntry, delivered_cargo, SLE_INT32), |
|
1211 SLE_VAR(PlayerEconomyEntry, performance_history, SLE_INT32), |
|
1212 |
|
1213 SLE_END() |
|
1214 }; |
|
1215 |
|
1216 static const SaveLoad _player_ai_desc[] = { |
|
1217 SLE_VAR(PlayerAI, state, SLE_UINT8), |
|
1218 SLE_VAR(PlayerAI, tick, SLE_UINT8), |
|
1219 SLE_CONDVAR(PlayerAI, state_counter, SLE_FILE_U16 | SLE_VAR_U32, 0, 12), |
|
1220 SLE_CONDVAR(PlayerAI, state_counter, SLE_UINT32, 13, SL_MAX_VERSION), |
|
1221 SLE_VAR(PlayerAI, timeout_counter, SLE_UINT16), |
|
1222 |
|
1223 SLE_VAR(PlayerAI, state_mode, SLE_UINT8), |
|
1224 SLE_VAR(PlayerAI, banned_tile_count, SLE_UINT8), |
|
1225 SLE_VAR(PlayerAI, railtype_to_use, SLE_UINT8), |
|
1226 |
|
1227 SLE_VAR(PlayerAI, cargo_type, SLE_UINT8), |
|
1228 SLE_VAR(PlayerAI, num_wagons, SLE_UINT8), |
|
1229 SLE_VAR(PlayerAI, build_kind, SLE_UINT8), |
|
1230 SLE_VAR(PlayerAI, num_build_rec, SLE_UINT8), |
|
1231 SLE_VAR(PlayerAI, num_loco_to_build, SLE_UINT8), |
|
1232 SLE_VAR(PlayerAI, num_want_fullload, SLE_UINT8), |
|
1233 |
|
1234 SLE_VAR(PlayerAI, route_type_mask, SLE_UINT8), |
|
1235 |
|
1236 SLE_CONDVAR(PlayerAI, start_tile_a, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), |
|
1237 SLE_CONDVAR(PlayerAI, start_tile_a, SLE_UINT32, 6, SL_MAX_VERSION), |
|
1238 SLE_CONDVAR(PlayerAI, cur_tile_a, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), |
|
1239 SLE_CONDVAR(PlayerAI, cur_tile_a, SLE_UINT32, 6, SL_MAX_VERSION), |
|
1240 SLE_VAR(PlayerAI, start_dir_a, SLE_UINT8), |
|
1241 SLE_VAR(PlayerAI, cur_dir_a, SLE_UINT8), |
|
1242 |
|
1243 SLE_CONDVAR(PlayerAI, start_tile_b, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), |
|
1244 SLE_CONDVAR(PlayerAI, start_tile_b, SLE_UINT32, 6, SL_MAX_VERSION), |
|
1245 SLE_CONDVAR(PlayerAI, cur_tile_b, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), |
|
1246 SLE_CONDVAR(PlayerAI, cur_tile_b, SLE_UINT32, 6, SL_MAX_VERSION), |
|
1247 SLE_VAR(PlayerAI, start_dir_b, SLE_UINT8), |
|
1248 SLE_VAR(PlayerAI, cur_dir_b, SLE_UINT8), |
|
1249 |
|
1250 SLE_REF(PlayerAI, cur_veh, REF_VEHICLE), |
|
1251 |
|
1252 SLE_ARR(PlayerAI, wagon_list, SLE_UINT16, 9), |
|
1253 SLE_ARR(PlayerAI, order_list_blocks, SLE_UINT8, 20), |
|
1254 SLE_ARR(PlayerAI, banned_tiles, SLE_UINT16, 16), |
|
1255 |
|
1256 SLE_CONDNULL(64, 2, SL_MAX_VERSION), |
|
1257 SLE_END() |
|
1258 }; |
|
1259 |
|
1260 static const SaveLoad _player_ai_build_rec_desc[] = { |
|
1261 SLE_CONDVAR(AiBuildRec, spec_tile, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), |
|
1262 SLE_CONDVAR(AiBuildRec, spec_tile, SLE_UINT32, 6, SL_MAX_VERSION), |
|
1263 SLE_CONDVAR(AiBuildRec, use_tile, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), |
|
1264 SLE_CONDVAR(AiBuildRec, use_tile, SLE_UINT32, 6, SL_MAX_VERSION), |
|
1265 SLE_VAR(AiBuildRec, rand_rng, SLE_UINT8), |
|
1266 SLE_VAR(AiBuildRec, cur_building_rule, SLE_UINT8), |
|
1267 SLE_VAR(AiBuildRec, unk6, SLE_UINT8), |
|
1268 SLE_VAR(AiBuildRec, unk7, SLE_UINT8), |
|
1269 SLE_VAR(AiBuildRec, buildcmd_a, SLE_UINT8), |
|
1270 SLE_VAR(AiBuildRec, buildcmd_b, SLE_UINT8), |
|
1271 SLE_VAR(AiBuildRec, direction, SLE_UINT8), |
|
1272 SLE_VAR(AiBuildRec, cargo, SLE_UINT8), |
|
1273 SLE_END() |
|
1274 }; |
|
1275 |
|
1276 static const SaveLoad _player_livery_desc[] = { |
|
1277 SLE_CONDVAR(Livery, in_use, SLE_BOOL, 34, SL_MAX_VERSION), |
|
1278 SLE_CONDVAR(Livery, colour1, SLE_UINT8, 34, SL_MAX_VERSION), |
|
1279 SLE_CONDVAR(Livery, colour2, SLE_UINT8, 34, SL_MAX_VERSION), |
|
1280 SLE_END() |
|
1281 }; |
|
1282 |
|
1283 static void SaveLoad_PLYR(Player* p) |
|
1284 { |
|
1285 int i; |
|
1286 |
|
1287 SlObject(p, _player_desc); |
|
1288 |
|
1289 // Write AI? |
|
1290 if (!IsHumanPlayer(p->index)) { |
|
1291 SlObject(&p->ai, _player_ai_desc); |
|
1292 for (i = 0; i != p->ai.num_build_rec; i++) { |
|
1293 SlObject(&p->ai.src + i, _player_ai_build_rec_desc); |
|
1294 } |
|
1295 } |
|
1296 |
|
1297 // Write economy |
|
1298 SlObject(&p->cur_economy, _player_economy_desc); |
|
1299 |
|
1300 // Write old economy entries. |
|
1301 for (i = 0; i < p->num_valid_stat_ent; i++) { |
|
1302 SlObject(&p->old_economy[i], _player_economy_desc); |
|
1303 } |
|
1304 |
|
1305 // Write each livery entry. |
|
1306 for (i = 0; i < LS_END; i++) { |
|
1307 SlObject(&p->livery[i], _player_livery_desc); |
|
1308 } |
|
1309 } |
|
1310 |
|
1311 static void Save_PLYR(void) |
|
1312 { |
|
1313 Player *p; |
|
1314 FOR_ALL_PLAYERS(p) { |
|
1315 if (p->is_active) { |
|
1316 SlSetArrayIndex(p->index); |
|
1317 SlAutolength((AutolengthProc*)SaveLoad_PLYR, p); |
|
1318 } |
|
1319 } |
|
1320 } |
|
1321 |
|
1322 static void Load_PLYR(void) |
|
1323 { |
|
1324 int index; |
|
1325 while ((index = SlIterateArray()) != -1) { |
|
1326 Player *p = GetPlayer(index); |
|
1327 SaveLoad_PLYR(p); |
|
1328 _player_colors[index] = p->player_color; |
|
1329 UpdatePlayerMoney32(p); |
|
1330 |
|
1331 /* This is needed so an AI is attached to a loaded AI */ |
|
1332 if (p->is_ai && (!_networking || _network_server) && _ai.enabled) |
|
1333 AI_StartNewAI(p->index); |
|
1334 } |
|
1335 } |
|
1336 |
|
1337 const ChunkHandler _player_chunk_handlers[] = { |
|
1338 { 'PLYR', Save_PLYR, Load_PLYR, CH_ARRAY | CH_LAST}, |
|
1339 }; |
|