|
1 /* $Id$ */ |
|
2 |
|
3 /** @file newgrf_station.c Functions for dealing with station classes and custom stations. */ |
|
4 |
|
5 #include "stdafx.h" |
|
6 #include "openttd.h" |
|
7 #include "variables.h" |
|
8 #include "functions.h" |
|
9 #include "debug.h" |
|
10 #include "sprite.h" |
|
11 #include "table/sprites.h" |
|
12 #include "table/strings.h" |
|
13 #include "station.h" |
|
14 #include "station_map.h" |
|
15 #include "newgrf.h" |
|
16 #include "newgrf_callbacks.h" |
|
17 #include "newgrf_station.h" |
|
18 #include "newgrf_spritegroup.h" |
|
19 #include "date.h" |
|
20 |
|
21 static StationClass station_classes[STAT_CLASS_MAX]; |
|
22 |
|
23 enum { |
|
24 MAX_SPECLIST = 255, |
|
25 }; |
|
26 |
|
27 /** |
|
28 * Reset station classes to their default state. |
|
29 * This includes initialising the Default and Waypoint classes with an empty |
|
30 * entry, for standard stations and waypoints. |
|
31 */ |
|
32 void ResetStationClasses(void) |
|
33 { |
|
34 StationClassID i; |
|
35 for (i = 0; i < STAT_CLASS_MAX; i++) { |
|
36 station_classes[i].id = 0; |
|
37 station_classes[i].name = STR_EMPTY; |
|
38 station_classes[i].stations = 0; |
|
39 |
|
40 free(station_classes[i].spec); |
|
41 station_classes[i].spec = NULL; |
|
42 } |
|
43 |
|
44 // Set up initial data |
|
45 station_classes[0].id = 'DFLT'; |
|
46 station_classes[0].name = STR_STAT_CLASS_DFLT; |
|
47 station_classes[0].stations = 1; |
|
48 station_classes[0].spec = malloc(sizeof(*station_classes[0].spec)); |
|
49 station_classes[0].spec[0] = NULL; |
|
50 |
|
51 station_classes[1].id = 'WAYP'; |
|
52 station_classes[1].name = STR_STAT_CLASS_WAYP; |
|
53 station_classes[1].stations = 1; |
|
54 station_classes[1].spec = malloc(sizeof(*station_classes[1].spec)); |
|
55 station_classes[1].spec[0] = NULL; |
|
56 } |
|
57 |
|
58 /** |
|
59 * Allocate a station class for the given class id. |
|
60 * @param classid A 32 bit value identifying the class. |
|
61 * @return Index into station_classes of allocated class. |
|
62 */ |
|
63 StationClassID AllocateStationClass(uint32 class) |
|
64 { |
|
65 StationClassID i; |
|
66 |
|
67 for (i = 0; i < STAT_CLASS_MAX; i++) { |
|
68 if (station_classes[i].id == class) { |
|
69 // ClassID is already allocated, so reuse it. |
|
70 return i; |
|
71 } else if (station_classes[i].id == 0) { |
|
72 // This class is empty, so allocate it to the ClassID. |
|
73 station_classes[i].id = class; |
|
74 return i; |
|
75 } |
|
76 } |
|
77 |
|
78 grfmsg(2, "StationClassAllocate: already allocated %d classes, using default", STAT_CLASS_MAX); |
|
79 return STAT_CLASS_DFLT; |
|
80 } |
|
81 |
|
82 /** Set the name of a custom station class */ |
|
83 void SetStationClassName(StationClassID sclass, StringID name) |
|
84 { |
|
85 assert(sclass < STAT_CLASS_MAX); |
|
86 station_classes[sclass].name = name; |
|
87 } |
|
88 |
|
89 /** Retrieve the name of a custom station class */ |
|
90 StringID GetStationClassName(StationClassID sclass) |
|
91 { |
|
92 assert(sclass < STAT_CLASS_MAX); |
|
93 return station_classes[sclass].name; |
|
94 } |
|
95 |
|
96 /** Build a list of station class name StringIDs to use in a dropdown list |
|
97 * @return Pointer to a (static) array of StringIDs |
|
98 */ |
|
99 StringID *BuildStationClassDropdown(void) |
|
100 { |
|
101 /* Allow room for all station classes, plus a terminator entry */ |
|
102 static StringID names[STAT_CLASS_MAX + 1]; |
|
103 uint i; |
|
104 |
|
105 /* Add each name */ |
|
106 for (i = 0; i < STAT_CLASS_MAX && station_classes[i].id != 0; i++) { |
|
107 names[i] = station_classes[i].name; |
|
108 } |
|
109 /* Terminate the list */ |
|
110 names[i] = INVALID_STRING_ID; |
|
111 |
|
112 return names; |
|
113 } |
|
114 |
|
115 /** |
|
116 * Get the number of station classes in use. |
|
117 * @return Number of station classes. |
|
118 */ |
|
119 uint GetNumStationClasses(void) |
|
120 { |
|
121 uint i; |
|
122 for (i = 0; i < STAT_CLASS_MAX && station_classes[i].id != 0; i++); |
|
123 return i; |
|
124 } |
|
125 |
|
126 /** |
|
127 * Return the number of stations for the given station class. |
|
128 * @param sclass Index of the station class. |
|
129 * @return Number of stations in the class. |
|
130 */ |
|
131 uint GetNumCustomStations(StationClassID sclass) |
|
132 { |
|
133 assert(sclass < STAT_CLASS_MAX); |
|
134 return station_classes[sclass].stations; |
|
135 } |
|
136 |
|
137 /** |
|
138 * Tie a station spec to its station class. |
|
139 * @param spec The station spec. |
|
140 */ |
|
141 void SetCustomStationSpec(StationSpec *statspec) |
|
142 { |
|
143 StationClass *station_class; |
|
144 int i; |
|
145 |
|
146 /* If the station has already been allocated, don't reallocate it. */ |
|
147 if (statspec->allocated) return; |
|
148 |
|
149 assert(statspec->sclass < STAT_CLASS_MAX); |
|
150 station_class = &station_classes[statspec->sclass]; |
|
151 |
|
152 i = station_class->stations++; |
|
153 station_class->spec = realloc(station_class->spec, station_class->stations * sizeof(*station_class->spec)); |
|
154 |
|
155 station_class->spec[i] = statspec; |
|
156 statspec->allocated = true; |
|
157 } |
|
158 |
|
159 /** |
|
160 * Retrieve a station spec from a class. |
|
161 * @param sclass Index of the station class. |
|
162 * @param station The station index with the class. |
|
163 * @return The station spec. |
|
164 */ |
|
165 const StationSpec *GetCustomStationSpec(StationClassID sclass, uint station) |
|
166 { |
|
167 assert(sclass < STAT_CLASS_MAX); |
|
168 if (station < station_classes[sclass].stations) |
|
169 return station_classes[sclass].spec[station]; |
|
170 |
|
171 // If the custom station isn't defined any more, then the GRF file |
|
172 // probably was not loaded. |
|
173 return NULL; |
|
174 } |
|
175 |
|
176 |
|
177 const StationSpec *GetCustomStationSpecByGrf(uint32 grfid, byte localidx) |
|
178 { |
|
179 StationClassID i; |
|
180 uint j; |
|
181 |
|
182 for (i = STAT_CLASS_DFLT; i < STAT_CLASS_MAX; i++) { |
|
183 for (j = 0; j < station_classes[i].stations; j++) { |
|
184 const StationSpec *statspec = station_classes[i].spec[j]; |
|
185 if (statspec == NULL) continue; |
|
186 if (statspec->grfid == grfid && statspec->localidx == localidx) return statspec; |
|
187 } |
|
188 } |
|
189 |
|
190 return NULL; |
|
191 } |
|
192 |
|
193 |
|
194 /* Evaluate a tile's position within a station, and return the result a bitstuffed format. |
|
195 * if not centred: .TNLcCpP, if centred: .TNL..CP |
|
196 * T = Tile layout number (GetStationGfx), N = Number of platforms, L = Length of platforms |
|
197 * C = Current platform number from start, c = from end |
|
198 * P = Position along platform from start, p = from end |
|
199 * if centred, C/P start from the centre and c/p are not available. |
|
200 */ |
|
201 uint32 GetPlatformInfo(Axis axis, byte tile, int platforms, int length, int x, int y, bool centred) |
|
202 { |
|
203 uint32 retval = 0; |
|
204 |
|
205 if (axis == AXIS_X) { |
|
206 intswap(platforms, length); |
|
207 intswap(x, y); |
|
208 } |
|
209 |
|
210 /* Limit our sizes to 4 bits */ |
|
211 platforms = min(15, platforms); |
|
212 length = min(15, length); |
|
213 x = min(15, x); |
|
214 y = min(15, y); |
|
215 if (centred) { |
|
216 x -= platforms / 2; |
|
217 y -= length / 2; |
|
218 SB(retval, 0, 4, y & 0xF); |
|
219 SB(retval, 4, 4, x & 0xF); |
|
220 } else { |
|
221 SB(retval, 0, 4, y); |
|
222 SB(retval, 4, 4, length - y - 1); |
|
223 SB(retval, 8, 4, x); |
|
224 SB(retval, 12, 4, platforms - x - 1); |
|
225 } |
|
226 SB(retval, 16, 4, length); |
|
227 SB(retval, 20, 4, platforms); |
|
228 SB(retval, 24, 4, tile); |
|
229 |
|
230 return retval; |
|
231 } |
|
232 |
|
233 |
|
234 /* Find the end of a railway station, from the tile, in the direction of delta. |
|
235 * If check_type is set, we stop if the custom station type changes. |
|
236 * If check_axis is set, we stop if the station direction changes. |
|
237 */ |
|
238 static TileIndex FindRailStationEnd(TileIndex tile, TileIndexDiff delta, bool check_type, bool check_axis) |
|
239 { |
|
240 bool waypoint; |
|
241 byte orig_type = 0; |
|
242 Axis orig_axis = AXIS_X; |
|
243 |
|
244 waypoint = IsTileType(tile, MP_RAILWAY); |
|
245 |
|
246 if (waypoint) { |
|
247 if (check_axis) orig_axis = GetWaypointAxis(tile); |
|
248 } else { |
|
249 if (check_type) orig_type = GetCustomStationSpecIndex(tile); |
|
250 if (check_axis) orig_axis = GetRailStationAxis(tile); |
|
251 } |
|
252 |
|
253 while (true) { |
|
254 TileIndex new_tile = TILE_ADD(tile, delta); |
|
255 |
|
256 if (waypoint) { |
|
257 if (!IsTileType(new_tile, MP_RAILWAY)) break; |
|
258 if (!IsRailWaypoint(new_tile)) break; |
|
259 if (check_axis && GetWaypointAxis(new_tile) != orig_axis) break; |
|
260 } else { |
|
261 if (!IsRailwayStationTile(new_tile)) break; |
|
262 if (check_type && GetCustomStationSpecIndex(new_tile) != orig_type) break; |
|
263 if (check_axis && GetRailStationAxis(new_tile) != orig_axis) break; |
|
264 } |
|
265 |
|
266 tile = new_tile; |
|
267 } |
|
268 return tile; |
|
269 } |
|
270 |
|
271 |
|
272 static uint32 GetPlatformInfoHelper(TileIndex tile, bool check_type, bool check_axis, bool centred) |
|
273 { |
|
274 int tx = TileX(tile); |
|
275 int ty = TileY(tile); |
|
276 int sx = TileX(FindRailStationEnd(tile, TileDiffXY(-1, 0), check_type, check_axis)); |
|
277 int sy = TileY(FindRailStationEnd(tile, TileDiffXY( 0, -1), check_type, check_axis)); |
|
278 int ex = TileX(FindRailStationEnd(tile, TileDiffXY( 1, 0), check_type, check_axis)) + 1; |
|
279 int ey = TileY(FindRailStationEnd(tile, TileDiffXY( 0, 1), check_type, check_axis)) + 1; |
|
280 Axis axis = IsTileType(tile, MP_RAILWAY) ? GetWaypointAxis(tile) : GetRailStationAxis(tile); |
|
281 |
|
282 tx -= sx; ex -= sx; |
|
283 ty -= sy; ey -= sy; |
|
284 |
|
285 return GetPlatformInfo(axis, IsTileType(tile, MP_RAILWAY) ? 2 : GetStationGfx(tile), ex, ey, tx, ty, centred); |
|
286 } |
|
287 |
|
288 |
|
289 static uint32 GetRailContinuationInfo(TileIndex tile) |
|
290 { |
|
291 /* Tile offsets and exit dirs for X axis */ |
|
292 static Direction x_dir[8] = { DIR_SW, DIR_NE, DIR_SE, DIR_NW, DIR_S, DIR_E, DIR_W, DIR_N }; |
|
293 static DiagDirection x_exits[8] = { DIAGDIR_SW, DIAGDIR_NE, DIAGDIR_SE, DIAGDIR_NW, DIAGDIR_SW, DIAGDIR_NE, DIAGDIR_SW, DIAGDIR_NE }; |
|
294 |
|
295 /* Tile offsets and exit dirs for Y axis */ |
|
296 static Direction y_dir[8] = { DIR_SE, DIR_NW, DIR_SW, DIR_NE, DIR_S, DIR_W, DIR_E, DIR_N }; |
|
297 static DiagDirection y_exits[8] = { DIAGDIR_SE, DIAGDIR_NW, DIAGDIR_SW, DIAGDIR_NE, DIAGDIR_SE, DIAGDIR_NW, DIAGDIR_SE, DIAGDIR_NW }; |
|
298 |
|
299 Axis axis = IsTileType(tile, MP_RAILWAY) ? GetWaypointAxis(tile) : GetRailStationAxis(tile); |
|
300 |
|
301 /* Choose appropriate lookup table to use */ |
|
302 Direction *dir = axis == AXIS_X ? x_dir : y_dir; |
|
303 DiagDirection *diagdir = axis == AXIS_X ? x_exits : y_exits; |
|
304 |
|
305 uint32 res = 0; |
|
306 uint i; |
|
307 |
|
308 for (i = 0; i < lengthof(x_dir); i++, dir++, diagdir++) { |
|
309 uint32 ts = GetTileTrackStatus(tile + TileOffsByDir(*dir), TRANSPORT_RAIL); |
|
310 if (ts != 0) { |
|
311 /* If there is any track on the tile, set the bit in the second byte */ |
|
312 SETBIT(res, i + 8); |
|
313 |
|
314 /* If any track reaches our exit direction, set the bit in the lower byte */ |
|
315 if (ts & DiagdirReachesTracks(*diagdir)) SETBIT(res, i); |
|
316 } |
|
317 } |
|
318 |
|
319 return res; |
|
320 } |
|
321 |
|
322 |
|
323 /* Station Resolver Functions */ |
|
324 static uint32 StationGetRandomBits(const ResolverObject *object) |
|
325 { |
|
326 const Station *st = object->u.station.st; |
|
327 const TileIndex tile = object->u.station.tile; |
|
328 return (st == NULL ? 0 : st->random_bits) | (tile == INVALID_TILE ? 0 : GetStationTileRandomBits(tile) << 16); |
|
329 } |
|
330 |
|
331 |
|
332 static uint32 StationGetTriggers(const ResolverObject *object) |
|
333 { |
|
334 const Station *st = object->u.station.st; |
|
335 return st == NULL ? 0 : st->waiting_triggers; |
|
336 } |
|
337 |
|
338 |
|
339 static void StationSetTriggers(const ResolverObject *object, int triggers) |
|
340 { |
|
341 Station *st = (Station*)object->u.station.st; |
|
342 assert(st != NULL); |
|
343 st->waiting_triggers = triggers; |
|
344 } |
|
345 |
|
346 |
|
347 static uint32 StationGetVariable(const ResolverObject *object, byte variable, byte parameter, bool *available) |
|
348 { |
|
349 const Station *st = object->u.station.st; |
|
350 TileIndex tile = object->u.station.tile; |
|
351 |
|
352 if (st == NULL) { |
|
353 /* Station does not exist, so we're in a purchase list */ |
|
354 switch (variable) { |
|
355 case 0x40: |
|
356 case 0x41: |
|
357 case 0x46: |
|
358 case 0x47: |
|
359 case 0x49: return 0x2110000; /* Platforms, tracks & position */ |
|
360 case 0x42: return 0; /* Rail type (XXX Get current type from GUI?) */ |
|
361 case 0x43: return _current_player; /* Station owner */ |
|
362 case 0x44: return 2; /* PBS status */ |
|
363 case 0xFA: return max(_date - DAYS_TILL_ORIGINAL_BASE_YEAR, 0); /* Build date */ |
|
364 } |
|
365 |
|
366 *available = false; |
|
367 return -1; |
|
368 } |
|
369 |
|
370 switch (variable) { |
|
371 /* Calculated station variables */ |
|
372 case 0x40: return GetPlatformInfoHelper(tile, false, false, false); |
|
373 case 0x41: return GetPlatformInfoHelper(tile, true, false, false); |
|
374 case 0x42: /* Terrain and rail type */ |
|
375 return ((_opt.landscape == LT_HILLY && GetTileZ(tile) > _opt.snow_line) ? 4 : 0) | |
|
376 (GetRailType(tile) << 8); |
|
377 case 0x43: return st->owner; /* Station owner */ |
|
378 case 0x44: return 2; /* PBS status */ |
|
379 case 0x45: return GetRailContinuationInfo(tile); |
|
380 case 0x46: return GetPlatformInfoHelper(tile, false, false, true); |
|
381 case 0x47: return GetPlatformInfoHelper(tile, true, false, true); |
|
382 case 0x48: { /* Accepted cargo types */ |
|
383 CargoID cargo_type; |
|
384 uint32 value = 0; |
|
385 |
|
386 for (cargo_type = 0; cargo_type < NUM_CARGO; cargo_type++) { |
|
387 if (HASBIT(st->goods[cargo_type].waiting_acceptance, 15)) SETBIT(value, cargo_type); |
|
388 } |
|
389 return value; |
|
390 } |
|
391 case 0x49: return GetPlatformInfoHelper(tile, false, true, false); |
|
392 |
|
393 /* Variables which use the parameter */ |
|
394 case 0x60: return GB(st->goods[parameter].waiting_acceptance, 0, 12); |
|
395 case 0x61: return st->goods[parameter].days_since_pickup; |
|
396 case 0x62: return st->goods[parameter].rating; |
|
397 case 0x63: return st->goods[parameter].enroute_time; |
|
398 case 0x64: return st->goods[parameter].last_speed | (st->goods[parameter].last_age << 8); |
|
399 case 0x65: return GB(st->goods[parameter].waiting_acceptance, 12, 4); |
|
400 |
|
401 /* General station properties */ |
|
402 case 0x82: return 50; |
|
403 case 0x84: return st->string_id; |
|
404 case 0x86: return 0; |
|
405 case 0x8A: return st->had_vehicle_of_type; |
|
406 case 0xF0: return st->facilities; |
|
407 case 0xF1: return st->airport_type; |
|
408 case 0xF2: return st->truck_stops->status; |
|
409 case 0xF3: return st->bus_stops->status; |
|
410 case 0xF6: return st->airport_flags; |
|
411 case 0xF7: return GB(st->airport_flags, 8, 8); |
|
412 case 0xFA: return max(st->build_date - DAYS_TILL_ORIGINAL_BASE_YEAR, 0); |
|
413 } |
|
414 |
|
415 /* Handle cargo variables (deprecated) */ |
|
416 if (variable >= 0x8C && variable <= 0xEC) { |
|
417 const GoodsEntry *g = &st->goods[GB(variable - 0x8C, 3, 4)]; |
|
418 switch (GB(variable - 0x8C, 0, 3)) { |
|
419 case 0: return g->waiting_acceptance; |
|
420 case 1: return GB(g->waiting_acceptance, 8, 8); |
|
421 case 2: return g->days_since_pickup; |
|
422 case 3: return g->rating; |
|
423 case 4: return g->enroute_from; |
|
424 case 5: return g->enroute_time; |
|
425 case 6: return g->last_speed; |
|
426 case 7: return g->last_age; |
|
427 } |
|
428 } |
|
429 |
|
430 DEBUG(grf, 1, "Unhandled station property 0x%X", variable); |
|
431 |
|
432 *available = false; |
|
433 return -1; |
|
434 } |
|
435 |
|
436 |
|
437 static const SpriteGroup *StationResolveReal(const ResolverObject *object, const SpriteGroup *group) |
|
438 { |
|
439 const Station *st = object->u.station.st; |
|
440 const StationSpec *statspec = object->u.station.statspec; |
|
441 uint set; |
|
442 |
|
443 uint cargo = 0; |
|
444 CargoID cargo_type = object->u.station.cargo_type; |
|
445 |
|
446 if (st == NULL || statspec->sclass == STAT_CLASS_WAYP) { |
|
447 return group->g.real.loading[0]; |
|
448 } |
|
449 |
|
450 switch (cargo_type) { |
|
451 case GC_INVALID: |
|
452 case GC_DEFAULT_NA: |
|
453 case GC_PURCHASE: |
|
454 cargo = 0; |
|
455 break; |
|
456 |
|
457 case GC_DEFAULT: |
|
458 for (cargo_type = 0; cargo_type < NUM_CARGO; cargo_type++) { |
|
459 cargo += GB(st->goods[cargo_type].waiting_acceptance, 0, 12); |
|
460 } |
|
461 break; |
|
462 |
|
463 default: |
|
464 cargo = GB(st->goods[_local_cargo_id_ctype[cargo_type]].waiting_acceptance, 0, 12); |
|
465 break; |
|
466 } |
|
467 |
|
468 if (HASBIT(statspec->flags, 1)) cargo /= (st->trainst_w + st->trainst_h); |
|
469 cargo = min(0xfff, cargo); |
|
470 |
|
471 if (cargo > statspec->cargo_threshold) { |
|
472 if (group->g.real.num_loading > 0) { |
|
473 set = ((cargo - statspec->cargo_threshold) * group->g.real.num_loading) / (4096 - statspec->cargo_threshold); |
|
474 return group->g.real.loading[set]; |
|
475 } |
|
476 } else { |
|
477 if (group->g.real.num_loaded > 0) { |
|
478 set = (cargo * group->g.real.num_loaded) / (statspec->cargo_threshold + 1); |
|
479 return group->g.real.loaded[set]; |
|
480 } |
|
481 } |
|
482 |
|
483 return group->g.real.loading[0]; |
|
484 } |
|
485 |
|
486 |
|
487 static void NewStationResolver(ResolverObject *res, const StationSpec *statspec, const Station *st, TileIndex tile) |
|
488 { |
|
489 res->GetRandomBits = StationGetRandomBits; |
|
490 res->GetTriggers = StationGetTriggers; |
|
491 res->SetTriggers = StationSetTriggers; |
|
492 res->GetVariable = StationGetVariable; |
|
493 res->ResolveReal = StationResolveReal; |
|
494 |
|
495 res->u.station.st = st; |
|
496 res->u.station.statspec = statspec; |
|
497 res->u.station.tile = tile; |
|
498 |
|
499 res->callback = 0; |
|
500 res->callback_param1 = 0; |
|
501 res->callback_param2 = 0; |
|
502 res->last_value = 0; |
|
503 res->trigger = 0; |
|
504 res->reseed = 0; |
|
505 } |
|
506 |
|
507 static const SpriteGroup *ResolveStation(const StationSpec *statspec, const Station *st, ResolverObject *object) |
|
508 { |
|
509 const SpriteGroup *group; |
|
510 CargoID ctype = GC_DEFAULT_NA; |
|
511 |
|
512 if (st == NULL) { |
|
513 /* No station, so we are in a purchase list */ |
|
514 ctype = GC_PURCHASE; |
|
515 } else { |
|
516 CargoID cargo; |
|
517 |
|
518 /* Pick the first cargo that we have waiting */ |
|
519 for (cargo = 0; cargo < NUM_GLOBAL_CID; cargo++) { |
|
520 CargoID lcid = _local_cargo_id_ctype[cargo]; |
|
521 if (lcid != CT_INVALID && statspec->spritegroup[cargo] != NULL && GB(st->goods[lcid].waiting_acceptance, 0, 12) != 0) { |
|
522 ctype = cargo; |
|
523 break; |
|
524 } |
|
525 } |
|
526 } |
|
527 |
|
528 group = statspec->spritegroup[ctype]; |
|
529 if (group == NULL) { |
|
530 ctype = GC_DEFAULT; |
|
531 group = statspec->spritegroup[ctype]; |
|
532 } |
|
533 |
|
534 if (group == NULL) return NULL; |
|
535 |
|
536 /* Remember the cargo type we've picked */ |
|
537 object->u.station.cargo_type = ctype; |
|
538 |
|
539 return Resolve(group, object); |
|
540 } |
|
541 |
|
542 SpriteID GetCustomStationRelocation(const StationSpec *statspec, const Station *st, TileIndex tile) |
|
543 { |
|
544 const SpriteGroup *group; |
|
545 ResolverObject object; |
|
546 |
|
547 NewStationResolver(&object, statspec, st, tile); |
|
548 |
|
549 group = ResolveStation(statspec, st, &object); |
|
550 if (group == NULL || group->type != SGT_RESULT) return 0; |
|
551 return group->g.result.sprite - 0x42D; |
|
552 } |
|
553 |
|
554 |
|
555 SpriteID GetCustomStationGroundRelocation(const StationSpec *statspec, const Station *st, TileIndex tile) |
|
556 { |
|
557 const SpriteGroup *group; |
|
558 ResolverObject object; |
|
559 |
|
560 NewStationResolver(&object, statspec, st, tile); |
|
561 object.callback_param1 = 1; /* Indicate we are resolving the ground sprite */ |
|
562 |
|
563 group = ResolveStation(statspec, st, &object); |
|
564 if (group == NULL || group->type != SGT_RESULT) return 0; |
|
565 return group->g.result.sprite - 0x42D; |
|
566 } |
|
567 |
|
568 |
|
569 uint16 GetStationCallback(uint16 callback, uint32 param1, uint32 param2, const StationSpec *statspec, const Station *st, TileIndex tile) |
|
570 { |
|
571 const SpriteGroup *group; |
|
572 ResolverObject object; |
|
573 |
|
574 NewStationResolver(&object, statspec, st, tile); |
|
575 |
|
576 object.callback = callback; |
|
577 object.callback_param1 = param1; |
|
578 object.callback_param2 = param2; |
|
579 |
|
580 group = ResolveStation(statspec, st, &object); |
|
581 if (group == NULL || group->type != SGT_CALLBACK) return CALLBACK_FAILED; |
|
582 return group->g.callback.result; |
|
583 } |
|
584 |
|
585 |
|
586 /** |
|
587 * Allocate a StationSpec to a Station. This is called once per build operation. |
|
588 * @param spec StationSpec to allocate. |
|
589 * @param st Station to allocate it to. |
|
590 * @param exec Whether to actually allocate the spec. |
|
591 * @return Index within the Station's spec list, or -1 if the allocation failed. |
|
592 */ |
|
593 int AllocateSpecToStation(const StationSpec *statspec, Station *st, bool exec) |
|
594 { |
|
595 uint i; |
|
596 |
|
597 if (statspec == NULL) return 0; |
|
598 |
|
599 /* Check if this spec has already been allocated */ |
|
600 for (i = 1; i < st->num_specs && i < MAX_SPECLIST; i++) { |
|
601 if (st->speclist[i].spec == statspec) return i; |
|
602 } |
|
603 |
|
604 for (i = 1; i < st->num_specs && i < MAX_SPECLIST; i++) { |
|
605 if (st->speclist[i].spec == NULL && st->speclist[i].grfid == 0) break; |
|
606 } |
|
607 |
|
608 if (i == MAX_SPECLIST) return -1; |
|
609 |
|
610 if (exec) { |
|
611 if (i >= st->num_specs) { |
|
612 st->num_specs = i + 1; |
|
613 st->speclist = realloc(st->speclist, st->num_specs * sizeof(*st->speclist)); |
|
614 |
|
615 if (st->num_specs == 2) { |
|
616 /* Initial allocation */ |
|
617 st->speclist[0].spec = NULL; |
|
618 st->speclist[0].grfid = 0; |
|
619 st->speclist[0].localidx = 0; |
|
620 } |
|
621 } |
|
622 |
|
623 st->speclist[i].spec = statspec; |
|
624 st->speclist[i].grfid = statspec->grfid; |
|
625 st->speclist[i].localidx = statspec->localidx; |
|
626 } |
|
627 |
|
628 return i; |
|
629 } |
|
630 |
|
631 |
|
632 /** Deallocate a StationSpec from a Station. Called when removing a single station tile. |
|
633 * @param st Station to work with. |
|
634 * @param specindex Index of the custom station within the Station's spec list. |
|
635 * @return Indicates whether the StationSpec was deallocated. |
|
636 */ |
|
637 void DeallocateSpecFromStation(Station* st, byte specindex) |
|
638 { |
|
639 /* specindex of 0 (default) is never freeable */ |
|
640 if (specindex == 0) return; |
|
641 |
|
642 /* Check all tiles over the station to check if the specindex is still in use */ |
|
643 BEGIN_TILE_LOOP(tile, st->trainst_w, st->trainst_h, st->train_tile) { |
|
644 if (IsTileType(tile, MP_STATION) && GetStationIndex(tile) == st->index && IsRailwayStation(tile) && GetCustomStationSpecIndex(tile) == specindex) { |
|
645 return; |
|
646 } |
|
647 } END_TILE_LOOP(tile, st->trainst_w, st->trainst_h, st->train_tile) |
|
648 |
|
649 /* This specindex is no longer in use, so deallocate it */ |
|
650 st->speclist[specindex].spec = NULL; |
|
651 st->speclist[specindex].grfid = 0; |
|
652 st->speclist[specindex].localidx = 0; |
|
653 |
|
654 /* If this was the highest spec index, reallocate */ |
|
655 if (specindex == st->num_specs - 1) { |
|
656 for (; st->speclist[st->num_specs - 1].grfid == 0 && st->num_specs > 1; st->num_specs--); |
|
657 |
|
658 if (st->num_specs > 1) { |
|
659 st->speclist = realloc(st->speclist, st->num_specs * sizeof(*st->speclist)); |
|
660 } else { |
|
661 free(st->speclist); |
|
662 st->num_specs = 0; |
|
663 st->speclist = NULL; |
|
664 } |
|
665 } |
|
666 } |
|
667 |
|
668 /** Draw representation of a station tile for GUI purposes. |
|
669 * @param x, y Position of image. |
|
670 * @param dir Direction. |
|
671 * @param railtype Rail type. |
|
672 * @param sclass, station Type of station. |
|
673 * @return True if the tile was drawn (allows for fallback to default graphic) |
|
674 */ |
|
675 bool DrawStationTile(int x, int y, RailType railtype, Axis axis, StationClassID sclass, uint station) |
|
676 { |
|
677 const StationSpec *statspec; |
|
678 const DrawTileSprites *sprites; |
|
679 const DrawTileSeqStruct *seq; |
|
680 const RailtypeInfo *rti = GetRailTypeInfo(railtype); |
|
681 SpriteID relocation; |
|
682 PalSpriteID image; |
|
683 PalSpriteID colourmod = SPRITE_PALETTE(PLAYER_SPRITE_COLOR(_local_player)); |
|
684 uint tile = 2; |
|
685 |
|
686 statspec = GetCustomStationSpec(sclass, station); |
|
687 if (statspec == NULL) return false; |
|
688 |
|
689 relocation = GetCustomStationRelocation(statspec, NULL, INVALID_TILE); |
|
690 |
|
691 if (HASBIT(statspec->callbackmask, CBM_CUSTOM_LAYOUT)) { |
|
692 uint16 callback = GetStationCallback(CBID_STATION_SPRITE_LAYOUT, 0x2110000, 0, statspec, NULL, INVALID_TILE); |
|
693 if (callback != CALLBACK_FAILED) tile = callback; |
|
694 } |
|
695 |
|
696 if (statspec->renderdata == NULL) { |
|
697 sprites = GetStationTileLayout(tile + axis); |
|
698 } else { |
|
699 sprites = &statspec->renderdata[(tile < statspec->tiles) ? tile + axis : axis]; |
|
700 } |
|
701 |
|
702 image = sprites->ground_sprite; |
|
703 if (HASBIT(image, 31)) { |
|
704 CLRBIT(image, 31); |
|
705 image += GetCustomStationGroundRelocation(statspec, NULL, INVALID_TILE); |
|
706 image += rti->custom_ground_offset; |
|
707 } else { |
|
708 image += rti->total_offset; |
|
709 } |
|
710 |
|
711 if (image & PALETTE_MODIFIER_COLOR) image &= SPRITE_MASK; |
|
712 DrawSprite(image, x, y); |
|
713 |
|
714 foreach_draw_tile_seq(seq, sprites->seq) { |
|
715 Point pt; |
|
716 image = seq->image; |
|
717 if (HASBIT(image, 30)) { |
|
718 CLRBIT(image, 30); |
|
719 image += rti->total_offset; |
|
720 } else { |
|
721 image += relocation; |
|
722 } |
|
723 |
|
724 if ((byte)seq->delta_z != 0x80) { |
|
725 pt = RemapCoords(seq->delta_x, seq->delta_y, seq->delta_z); |
|
726 DrawSprite((image & SPRITE_MASK) | colourmod, x + pt.x, y + pt.y); |
|
727 } |
|
728 } |
|
729 |
|
730 return true; |
|
731 } |
|
732 |
|
733 |
|
734 static const StationSpec* GetStationSpec(TileIndex t) |
|
735 { |
|
736 const Station* st; |
|
737 uint specindex; |
|
738 |
|
739 if (!IsCustomStationSpecIndex(t)) return NULL; |
|
740 |
|
741 st = GetStationByTile(t); |
|
742 specindex = GetCustomStationSpecIndex(t); |
|
743 return specindex < st->num_specs ? st->speclist[specindex].spec : NULL; |
|
744 } |
|
745 |
|
746 |
|
747 /* Check if a rail station tile is traversable. |
|
748 * XXX This could be cached (during build) in the map array to save on all the dereferencing */ |
|
749 bool IsStationTileBlocked(TileIndex tile) |
|
750 { |
|
751 const StationSpec* statspec = GetStationSpec(tile); |
|
752 |
|
753 return statspec != NULL && HASBIT(statspec->blocked, GetStationGfx(tile)); |
|
754 } |
|
755 |
|
756 /* Check if a rail station tile is electrifiable. |
|
757 * XXX This could be cached (during build) in the map array to save on all the dereferencing */ |
|
758 bool IsStationTileElectrifiable(TileIndex tile) |
|
759 { |
|
760 const StationSpec* statspec = GetStationSpec(tile); |
|
761 |
|
762 return |
|
763 statspec == NULL || |
|
764 HASBIT(statspec->pylons, GetStationGfx(tile)) || |
|
765 !HASBIT(statspec->wires, GetStationGfx(tile)); |
|
766 } |