Mercurial > hg > openttd
comparison src/station_cmd.cpp @ 5584:4b26bd55bd24 draft
(svn r8033) [cpp] - Prepare for merge from branches/cpp (all .c files renamed to .cpp)
author | KUDr <KUDr@openttd.org> |
---|---|
date | Wed, 10 Jan 2007 18:12:09 +0000 |
parents | |
children | c44c070c5032 |
comparison
equal
deleted
inserted
replaced
5583:2d57664077ee | 5584:4b26bd55bd24 |
---|---|
1 /* $Id$ */ | |
2 | |
3 /** @file station_cmd.c */ | |
4 | |
5 #include "stdafx.h" | |
6 #include "openttd.h" | |
7 #include "bridge_map.h" | |
8 #include "debug.h" | |
9 #include "functions.h" | |
10 #include "station_map.h" | |
11 #include "table/sprites.h" | |
12 #include "table/strings.h" | |
13 #include "map.h" | |
14 #include "tile.h" | |
15 #include "station.h" | |
16 #include "gfx.h" | |
17 #include "window.h" | |
18 #include "viewport.h" | |
19 #include "command.h" | |
20 #include "town.h" | |
21 #include "vehicle.h" | |
22 #include "news.h" | |
23 #include "saveload.h" | |
24 #include "economy.h" | |
25 #include "player.h" | |
26 #include "airport.h" | |
27 #include "sprite.h" | |
28 #include "depot.h" | |
29 #include "train.h" | |
30 #include "water_map.h" | |
31 #include "industry_map.h" | |
32 #include "newgrf_callbacks.h" | |
33 #include "newgrf_station.h" | |
34 #include "yapf/yapf.h" | |
35 #include "date.h" | |
36 | |
37 typedef enum StationRectModes | |
38 { | |
39 RECT_MODE_TEST = 0, | |
40 RECT_MODE_TRY, | |
41 RECT_MODE_FORCE | |
42 } StationRectMode; | |
43 | |
44 static void StationRect_Init(Station *st); | |
45 static bool StationRect_IsEmpty(Station *st); | |
46 static bool StationRect_BeforeAddTile(Station *st, TileIndex tile, StationRectMode mode); | |
47 static bool StationRect_BeforeAddRect(Station *st, TileIndex tile, int w, int h, StationRectMode mode); | |
48 static bool StationRect_AfterRemoveTile(Station *st, TileIndex tile); | |
49 static bool StationRect_AfterRemoveRect(Station *st, TileIndex tile, int w, int h); | |
50 | |
51 | |
52 /** | |
53 * Called if a new block is added to the station-pool | |
54 */ | |
55 static void StationPoolNewBlock(uint start_item) | |
56 { | |
57 Station *st; | |
58 | |
59 /* We don't use FOR_ALL here, because FOR_ALL skips invalid items. | |
60 * TODO - This is just a temporary stage, this will be removed. */ | |
61 for (st = GetStation(start_item); st != NULL; st = (st->index + 1U < GetStationPoolSize()) ? GetStation(st->index + 1U) : NULL) st->index = start_item++; | |
62 } | |
63 | |
64 static void StationPoolCleanBlock(uint start_item, uint end_item) | |
65 { | |
66 uint i; | |
67 | |
68 for (i = start_item; i <= end_item; i++) { | |
69 Station *st = GetStation(i); | |
70 free(st->speclist); | |
71 st->speclist = NULL; | |
72 } | |
73 } | |
74 | |
75 /** | |
76 * Called if a new block is added to the roadstop-pool | |
77 */ | |
78 static void RoadStopPoolNewBlock(uint start_item) | |
79 { | |
80 RoadStop *rs; | |
81 | |
82 /* We don't use FOR_ALL here, because FOR_ALL skips invalid items. | |
83 * TODO - This is just a temporary stage, this will be removed. */ | |
84 for (rs = GetRoadStop(start_item); rs != NULL; rs = (rs->index + 1U < GetRoadStopPoolSize()) ? GetRoadStop(rs->index + 1U) : NULL) rs->index = start_item++; | |
85 } | |
86 | |
87 DEFINE_OLD_POOL(Station, Station, StationPoolNewBlock, StationPoolCleanBlock) | |
88 DEFINE_OLD_POOL(RoadStop, RoadStop, RoadStopPoolNewBlock, NULL) | |
89 | |
90 | |
91 extern void UpdateAirplanesOnNewStation(Station *st); | |
92 | |
93 static bool TileBelongsToRailStation(const Station *st, TileIndex tile) | |
94 { | |
95 return IsTileType(tile, MP_STATION) && GetStationIndex(tile) == st->index && IsRailwayStation(tile); | |
96 } | |
97 | |
98 void MarkStationTilesDirty(const Station *st) | |
99 { | |
100 TileIndex tile = st->train_tile; | |
101 int w, h; | |
102 | |
103 // XXX No station is recorded as 0, not INVALID_TILE... | |
104 if (tile == 0) return; | |
105 | |
106 for (h = 0; h < st->trainst_h; h++) { | |
107 for (w = 0; w < st->trainst_w; w++) { | |
108 if (TileBelongsToRailStation(st, tile)) { | |
109 MarkTileDirtyByTile(tile); | |
110 } | |
111 tile += TileDiffXY(1, 0); | |
112 } | |
113 tile += TileDiffXY(-w, 1); | |
114 } | |
115 } | |
116 | |
117 static void MarkStationDirty(const Station* st) | |
118 { | |
119 if (st->sign.width_1 != 0) { | |
120 InvalidateWindowWidget(WC_STATION_VIEW, st->index, 1); | |
121 | |
122 MarkAllViewportsDirty( | |
123 st->sign.left - 6, | |
124 st->sign.top, | |
125 st->sign.left + (st->sign.width_1 << 2) + 12, | |
126 st->sign.top + 48); | |
127 } | |
128 } | |
129 | |
130 static void InitializeRoadStop(RoadStop *road_stop, RoadStop *previous, TileIndex tile, StationID index) | |
131 { | |
132 road_stop->xy = tile; | |
133 road_stop->used = true; | |
134 road_stop->status = 3; //stop is free | |
135 road_stop->next = NULL; | |
136 road_stop->prev = previous; | |
137 road_stop->station = index; | |
138 road_stop->num_vehicles = 0; | |
139 } | |
140 | |
141 RoadStop* GetPrimaryRoadStop(const Station* st, RoadStopType type) | |
142 { | |
143 switch (type) { | |
144 case RS_BUS: return st->bus_stops; | |
145 case RS_TRUCK: return st->truck_stops; | |
146 default: NOT_REACHED(); | |
147 } | |
148 | |
149 return NULL; | |
150 } | |
151 | |
152 RoadStop* GetRoadStopByTile(TileIndex tile, RoadStopType type) | |
153 { | |
154 const Station* st = GetStationByTile(tile); | |
155 RoadStop* rs; | |
156 | |
157 for (rs = GetPrimaryRoadStop(st, type); rs->xy != tile; rs = rs->next) { | |
158 assert(rs->next != NULL); | |
159 } | |
160 | |
161 return rs; | |
162 } | |
163 | |
164 uint GetNumRoadStopsInStation(const Station* st, RoadStopType type) | |
165 { | |
166 uint num = 0; | |
167 const RoadStop *rs; | |
168 | |
169 assert(st != NULL); | |
170 for (rs = GetPrimaryRoadStop(st, type); rs != NULL; rs = rs->next) num++; | |
171 | |
172 return num; | |
173 } | |
174 | |
175 RoadStop *AllocateRoadStop(void) | |
176 { | |
177 RoadStop *rs; | |
178 | |
179 /* We don't use FOR_ALL here, because FOR_ALL skips invalid items. | |
180 * TODO - This is just a temporary stage, this will be removed. */ | |
181 for (rs = GetRoadStop(0); rs != NULL; rs = (rs->index + 1U < GetRoadStopPoolSize()) ? GetRoadStop(rs->index + 1U) : NULL) { | |
182 if (!IsValidRoadStop(rs)) { | |
183 RoadStopID index = rs->index; | |
184 | |
185 memset(rs, 0, sizeof(*rs)); | |
186 rs->index = index; | |
187 | |
188 return rs; | |
189 } | |
190 } | |
191 | |
192 /* Check if we can add a block to the pool */ | |
193 if (AddBlockToPool(&_RoadStop_pool)) return AllocateRoadStop(); | |
194 | |
195 return NULL; | |
196 } | |
197 | |
198 /* Calculate the radius of the station. Basicly it is the biggest | |
199 * radius that is available within the station */ | |
200 static uint FindCatchmentRadius(const Station* st) | |
201 { | |
202 uint ret = 0; | |
203 | |
204 if (st->bus_stops != NULL) ret = max(ret, CA_BUS); | |
205 if (st->truck_stops != NULL) ret = max(ret, CA_TRUCK); | |
206 if (st->train_tile) ret = max(ret, CA_TRAIN); | |
207 if (st->dock_tile) ret = max(ret, CA_DOCK); | |
208 | |
209 if (st->airport_tile) { | |
210 switch (st->airport_type) { | |
211 case AT_OILRIG: ret = max(ret, CA_AIR_OILPAD); break; | |
212 case AT_SMALL: ret = max(ret, CA_AIR_SMALL); break; | |
213 case AT_HELIPORT: ret = max(ret, CA_AIR_HELIPORT); break; | |
214 case AT_LARGE: ret = max(ret, CA_AIR_LARGE); break; | |
215 case AT_METROPOLITAN: ret = max(ret, CA_AIR_METRO); break; | |
216 case AT_INTERNATIONAL: ret = max(ret, CA_AIR_INTER); break; | |
217 case AT_COMMUTER: ret = max(ret, CA_AIR_COMMUTER); break; | |
218 case AT_HELIDEPOT: ret = max(ret, CA_AIR_HELIDEPOT); break; | |
219 case AT_INTERCON: ret = max(ret, CA_AIR_INTERCON); break; | |
220 case AT_HELISTATION: ret = max(ret, CA_AIR_HELISTATION); break; | |
221 } | |
222 } | |
223 | |
224 return ret; | |
225 } | |
226 | |
227 #define CHECK_STATIONS_ERR ((Station*)-1) | |
228 | |
229 static Station* GetStationAround(TileIndex tile, int w, int h, StationID closest_station) | |
230 { | |
231 // check around to see if there's any stations there | |
232 BEGIN_TILE_LOOP(tile_cur, w + 2, h + 2, tile - TileDiffXY(1, 1)) | |
233 if (IsTileType(tile_cur, MP_STATION)) { | |
234 StationID t = GetStationIndex(tile_cur); | |
235 { | |
236 Station *st = GetStation(t); | |
237 // you cannot take control of an oilrig!! | |
238 if (st->airport_type == AT_OILRIG && st->facilities == (FACIL_AIRPORT|FACIL_DOCK)) | |
239 continue; | |
240 } | |
241 | |
242 if (closest_station == INVALID_STATION) { | |
243 closest_station = t; | |
244 } else if (closest_station != t) { | |
245 _error_message = STR_3006_ADJOINS_MORE_THAN_ONE_EXISTING; | |
246 return CHECK_STATIONS_ERR; | |
247 } | |
248 } | |
249 END_TILE_LOOP(tile_cur, w + 2, h + 2, tile - TileDiffXY(1, 1)) | |
250 return (closest_station == INVALID_STATION) ? NULL : GetStation(closest_station); | |
251 } | |
252 | |
253 static Station *AllocateStation(void) | |
254 { | |
255 Station *st = NULL; | |
256 | |
257 /* We don't use FOR_ALL here, because FOR_ALL skips invalid items. | |
258 * TODO - This is just a temporary stage, this will be removed. */ | |
259 for (st = GetStation(0); st != NULL; st = (st->index + 1U < GetStationPoolSize()) ? GetStation(st->index + 1U) : NULL) { | |
260 if (!IsValidStation(st)) { | |
261 StationID index = st->index; | |
262 | |
263 memset(st, 0, sizeof(Station)); | |
264 st->index = index; | |
265 | |
266 return st; | |
267 } | |
268 } | |
269 | |
270 /* Check if we can add a block to the pool */ | |
271 if (AddBlockToPool(&_Station_pool)) return AllocateStation(); | |
272 | |
273 _error_message = STR_3008_TOO_MANY_STATIONS_LOADING; | |
274 return NULL; | |
275 } | |
276 | |
277 | |
278 /** | |
279 * Counts the numbers of tiles matching a specific type in the area around | |
280 * @param tile the center tile of the 'count area' | |
281 * @param type the type of tile searched for | |
282 * @param industry when type == MP_INDUSTRY, the type of the industry, | |
283 * in all other cases this parameter is ignored | |
284 * @result the noumber of matching tiles around | |
285 */ | |
286 static int CountMapSquareAround(TileIndex tile, TileType type, IndustryType industry) | |
287 { | |
288 TileIndex cur_tile; | |
289 int dx, dy; | |
290 int num = 0; | |
291 | |
292 for (dx = -3; dx <= 3; dx++) { | |
293 for (dy = -3; dy <= 3; dy++) { | |
294 cur_tile = TILE_MASK(tile + TileDiffXY(dx, dy)); | |
295 | |
296 if (IsTileType(cur_tile, type)) { | |
297 switch (type) { | |
298 case MP_INDUSTRY: | |
299 if (GetIndustryType(cur_tile) == industry) | |
300 num++; | |
301 break; | |
302 | |
303 case MP_WATER: | |
304 if (!IsWater(cur_tile)) | |
305 break; | |
306 /* FALL THROUGH WHEN WATER TILE */ | |
307 case MP_TREES: | |
308 num++; | |
309 break; | |
310 | |
311 default: | |
312 break; | |
313 } | |
314 } | |
315 } | |
316 } | |
317 | |
318 return num; | |
319 } | |
320 | |
321 #define M(x) ((x) - STR_SV_STNAME) | |
322 | |
323 static bool GenerateStationName(Station *st, TileIndex tile, int flag) | |
324 { | |
325 static const uint32 _gen_station_name_bits[] = { | |
326 0, /* 0 */ | |
327 1 << M(STR_SV_STNAME_AIRPORT), /* 1 */ | |
328 1 << M(STR_SV_STNAME_OILFIELD), /* 2 */ | |
329 1 << M(STR_SV_STNAME_DOCKS), /* 3 */ | |
330 0x1FF << M(STR_SV_STNAME_BUOY_1), /* 4 */ | |
331 1 << M(STR_SV_STNAME_HELIPORT), /* 5 */ | |
332 }; | |
333 | |
334 Town *t = st->town; | |
335 uint32 free_names = (uint32)-1; | |
336 int found; | |
337 uint z,z2; | |
338 unsigned long tmp; | |
339 | |
340 { | |
341 Station *s; | |
342 | |
343 FOR_ALL_STATIONS(s) { | |
344 if (s != st && s->town==t) { | |
345 uint str = M(s->string_id); | |
346 if (str <= 0x20) { | |
347 if (str == M(STR_SV_STNAME_FOREST)) | |
348 str = M(STR_SV_STNAME_WOODS); | |
349 CLRBIT(free_names, str); | |
350 } | |
351 } | |
352 } | |
353 } | |
354 | |
355 /* check default names */ | |
356 tmp = free_names & _gen_station_name_bits[flag]; | |
357 if (tmp != 0) { | |
358 found = FindFirstBit(tmp); | |
359 goto done; | |
360 } | |
361 | |
362 /* check mine? */ | |
363 if (HASBIT(free_names, M(STR_SV_STNAME_MINES))) { | |
364 if (CountMapSquareAround(tile, MP_INDUSTRY, IT_COAL_MINE) >= 2 || | |
365 CountMapSquareAround(tile, MP_INDUSTRY, IT_IRON_MINE) >= 2 || | |
366 CountMapSquareAround(tile, MP_INDUSTRY, IT_COPPER_MINE) >= 2 || | |
367 CountMapSquareAround(tile, MP_INDUSTRY, IT_GOLD_MINE) >= 2 || | |
368 CountMapSquareAround(tile, MP_INDUSTRY, IT_DIAMOND_MINE) >= 2) { | |
369 found = M(STR_SV_STNAME_MINES); | |
370 goto done; | |
371 } | |
372 } | |
373 | |
374 /* check close enough to town to get central as name? */ | |
375 if (DistanceMax(tile,t->xy) < 8) { | |
376 found = M(STR_SV_STNAME); | |
377 if (HASBIT(free_names, M(STR_SV_STNAME))) goto done; | |
378 | |
379 found = M(STR_SV_STNAME_CENTRAL); | |
380 if (HASBIT(free_names, M(STR_SV_STNAME_CENTRAL))) goto done; | |
381 } | |
382 | |
383 /* Check lakeside */ | |
384 if (HASBIT(free_names, M(STR_SV_STNAME_LAKESIDE)) && | |
385 DistanceFromEdge(tile) < 20 && | |
386 CountMapSquareAround(tile, MP_WATER, 0) >= 5) { | |
387 found = M(STR_SV_STNAME_LAKESIDE); | |
388 goto done; | |
389 } | |
390 | |
391 /* Check woods */ | |
392 if (HASBIT(free_names, M(STR_SV_STNAME_WOODS)) && ( | |
393 CountMapSquareAround(tile, MP_TREES, 0) >= 8 || | |
394 CountMapSquareAround(tile, MP_INDUSTRY, IT_FOREST) >= 2) | |
395 ) { | |
396 found = _opt.landscape == LT_DESERT ? | |
397 M(STR_SV_STNAME_FOREST) : M(STR_SV_STNAME_WOODS); | |
398 goto done; | |
399 } | |
400 | |
401 /* check elevation compared to town */ | |
402 z = GetTileZ(tile); | |
403 z2 = GetTileZ(t->xy); | |
404 if (z < z2) { | |
405 found = M(STR_SV_STNAME_VALLEY); | |
406 if (HASBIT(free_names, M(STR_SV_STNAME_VALLEY))) goto done; | |
407 } else if (z > z2) { | |
408 found = M(STR_SV_STNAME_HEIGHTS); | |
409 if (HASBIT(free_names, M(STR_SV_STNAME_HEIGHTS))) goto done; | |
410 } | |
411 | |
412 /* check direction compared to town */ | |
413 { | |
414 static const int8 _direction_and_table[] = { | |
415 ~( (1<<M(STR_SV_STNAME_WEST)) | (1<<M(STR_SV_STNAME_EAST)) | (1<<M(STR_SV_STNAME_NORTH)) ), | |
416 ~( (1<<M(STR_SV_STNAME_SOUTH)) | (1<<M(STR_SV_STNAME_WEST)) | (1<<M(STR_SV_STNAME_NORTH)) ), | |
417 ~( (1<<M(STR_SV_STNAME_SOUTH)) | (1<<M(STR_SV_STNAME_EAST)) | (1<<M(STR_SV_STNAME_NORTH)) ), | |
418 ~( (1<<M(STR_SV_STNAME_SOUTH)) | (1<<M(STR_SV_STNAME_WEST)) | (1<<M(STR_SV_STNAME_EAST)) ), | |
419 }; | |
420 | |
421 free_names &= _direction_and_table[ | |
422 (TileX(tile) < TileX(t->xy)) + | |
423 (TileY(tile) < TileY(t->xy)) * 2]; | |
424 } | |
425 | |
426 tmp = free_names & ((1<<1)|(1<<2)|(1<<3)|(1<<4)|(1<<6)|(1<<7)|(1<<12)|(1<<26)|(1<<27)|(1<<28)|(1<<29)|(1<<30)); | |
427 if (tmp == 0) { | |
428 _error_message = STR_3007_TOO_MANY_STATIONS_LOADING; | |
429 return false; | |
430 } | |
431 found = FindFirstBit(tmp); | |
432 | |
433 done: | |
434 st->string_id = found + STR_SV_STNAME; | |
435 return true; | |
436 } | |
437 #undef M | |
438 | |
439 static Station* GetClosestStationFromTile(TileIndex tile, uint threshold, PlayerID owner) | |
440 { | |
441 Station* best_station = NULL; | |
442 Station* st; | |
443 | |
444 FOR_ALL_STATIONS(st) { | |
445 if ((owner == PLAYER_SPECTATOR || st->owner == owner)) { | |
446 uint cur_dist = DistanceManhattan(tile, st->xy); | |
447 | |
448 if (cur_dist < threshold) { | |
449 threshold = cur_dist; | |
450 best_station = st; | |
451 } | |
452 } | |
453 } | |
454 | |
455 return best_station; | |
456 } | |
457 | |
458 static void StationInitialize(Station *st, TileIndex tile) | |
459 { | |
460 GoodsEntry *ge; | |
461 | |
462 st->xy = tile; | |
463 st->airport_tile = st->dock_tile = st->train_tile = 0; | |
464 st->bus_stops = st->truck_stops = NULL; | |
465 st->had_vehicle_of_type = 0; | |
466 st->time_since_load = 255; | |
467 st->time_since_unload = 255; | |
468 st->delete_ctr = 0; | |
469 st->facilities = 0; | |
470 | |
471 st->last_vehicle_type = VEH_Invalid; | |
472 | |
473 for (ge = st->goods; ge != endof(st->goods); ge++) { | |
474 ge->waiting_acceptance = 0; | |
475 ge->days_since_pickup = 0; | |
476 ge->enroute_from = INVALID_STATION; | |
477 ge->rating = 175; | |
478 ge->last_speed = 0; | |
479 ge->last_age = 0xFF; | |
480 ge->feeder_profit = 0; | |
481 } | |
482 | |
483 st->random_bits = Random(); | |
484 st->waiting_triggers = 0; | |
485 | |
486 StationRect_Init(st); | |
487 } | |
488 | |
489 // Update the virtual coords needed to draw the station sign. | |
490 // st = Station to update for. | |
491 static void UpdateStationVirtCoord(Station *st) | |
492 { | |
493 Point pt = RemapCoords2(TileX(st->xy) * TILE_SIZE, TileY(st->xy) * TILE_SIZE); | |
494 | |
495 pt.y -= 32; | |
496 if (st->facilities & FACIL_AIRPORT && st->airport_type == AT_OILRIG) pt.y -= 16; | |
497 | |
498 SetDParam(0, st->index); | |
499 SetDParam(1, st->facilities); | |
500 UpdateViewportSignPos(&st->sign, pt.x, pt.y, STR_305C_0); | |
501 } | |
502 | |
503 // Update the virtual coords needed to draw the station sign for all stations. | |
504 void UpdateAllStationVirtCoord(void) | |
505 { | |
506 Station* st; | |
507 | |
508 FOR_ALL_STATIONS(st) { | |
509 UpdateStationVirtCoord(st); | |
510 } | |
511 } | |
512 | |
513 // Update the station virt coords while making the modified parts dirty. | |
514 static void UpdateStationVirtCoordDirty(Station *st) | |
515 { | |
516 MarkStationDirty(st); | |
517 UpdateStationVirtCoord(st); | |
518 MarkStationDirty(st); | |
519 } | |
520 | |
521 // Get a mask of the cargo types that the station accepts. | |
522 static uint GetAcceptanceMask(const Station *st) | |
523 { | |
524 uint mask = 0; | |
525 uint i; | |
526 | |
527 for (i = 0; i != NUM_CARGO; i++) { | |
528 if (st->goods[i].waiting_acceptance & 0x8000) mask |= 1 << i; | |
529 } | |
530 return mask; | |
531 } | |
532 | |
533 // Items contains the two cargo names that are to be accepted or rejected. | |
534 // msg is the string id of the message to display. | |
535 static void ShowRejectOrAcceptNews(const Station *st, uint32 items, StringID msg) | |
536 { | |
537 if (items) { | |
538 SetDParam(2, GB(items, 16, 16)); | |
539 SetDParam(1, GB(items, 0, 16)); | |
540 SetDParam(0, st->index); | |
541 AddNewsItem(msg + (GB(items, 16, 16) ? 1 : 0), NEWS_FLAGS(NM_SMALL, NF_VIEWPORT|NF_TILE, NT_ACCEPTANCE, 0), st->xy, 0); | |
542 } | |
543 } | |
544 | |
545 // Get a list of the cargo types being produced around the tile. | |
546 void GetProductionAroundTiles(AcceptedCargo produced, TileIndex tile, | |
547 int w, int h, int rad) | |
548 { | |
549 int x,y; | |
550 int x1,y1,x2,y2; | |
551 int xc,yc; | |
552 | |
553 memset(produced, 0, sizeof(AcceptedCargo)); | |
554 | |
555 x = TileX(tile); | |
556 y = TileY(tile); | |
557 | |
558 // expand the region by rad tiles on each side | |
559 // while making sure that we remain inside the board. | |
560 x2 = min(x + w + rad, MapSizeX()); | |
561 x1 = max(x - rad, 0); | |
562 | |
563 y2 = min(y + h + rad, MapSizeY()); | |
564 y1 = max(y - rad, 0); | |
565 | |
566 assert(x1 < x2); | |
567 assert(y1 < y2); | |
568 assert(w > 0); | |
569 assert(h > 0); | |
570 | |
571 for (yc = y1; yc != y2; yc++) { | |
572 for (xc = x1; xc != x2; xc++) { | |
573 if (!(IS_INSIDE_1D(xc, x, w) && IS_INSIDE_1D(yc, y, h))) { | |
574 GetProducedCargoProc *gpc; | |
575 TileIndex tile = TileXY(xc, yc); | |
576 | |
577 gpc = _tile_type_procs[GetTileType(tile)]->get_produced_cargo_proc; | |
578 if (gpc != NULL) { | |
579 CargoID cargos[2] = { CT_INVALID, CT_INVALID }; | |
580 | |
581 gpc(tile, cargos); | |
582 if (cargos[0] != CT_INVALID) { | |
583 produced[cargos[0]]++; | |
584 if (cargos[1] != CT_INVALID) { | |
585 produced[cargos[1]]++; | |
586 } | |
587 } | |
588 } | |
589 } | |
590 } | |
591 } | |
592 } | |
593 | |
594 // Get a list of the cargo types that are accepted around the tile. | |
595 void GetAcceptanceAroundTiles(AcceptedCargo accepts, TileIndex tile, | |
596 int w, int h, int rad) | |
597 { | |
598 int x,y; | |
599 int x1,y1,x2,y2; | |
600 int xc,yc; | |
601 | |
602 memset(accepts, 0, sizeof(AcceptedCargo)); | |
603 | |
604 x = TileX(tile); | |
605 y = TileY(tile); | |
606 | |
607 // expand the region by rad tiles on each side | |
608 // while making sure that we remain inside the board. | |
609 x2 = min(x + w + rad, MapSizeX()); | |
610 y2 = min(y + h + rad, MapSizeY()); | |
611 x1 = max(x - rad, 0); | |
612 y1 = max(y - rad, 0); | |
613 | |
614 assert(x1 < x2); | |
615 assert(y1 < y2); | |
616 assert(w > 0); | |
617 assert(h > 0); | |
618 | |
619 for (yc = y1; yc != y2; yc++) { | |
620 for (xc = x1; xc != x2; xc++) { | |
621 TileIndex tile = TileXY(xc, yc); | |
622 | |
623 if (!IsTileType(tile, MP_STATION)) { | |
624 AcceptedCargo ac; | |
625 uint i; | |
626 | |
627 GetAcceptedCargo(tile, ac); | |
628 for (i = 0; i < lengthof(ac); ++i) accepts[i] += ac[i]; | |
629 } | |
630 } | |
631 } | |
632 } | |
633 | |
634 typedef struct ottd_Rectangle { | |
635 uint min_x; | |
636 uint min_y; | |
637 uint max_x; | |
638 uint max_y; | |
639 } ottd_Rectangle; | |
640 | |
641 static inline void MergePoint(ottd_Rectangle* rect, TileIndex tile) | |
642 { | |
643 uint x = TileX(tile); | |
644 uint y = TileY(tile); | |
645 | |
646 if (rect->min_x > x) rect->min_x = x; | |
647 if (rect->min_y > y) rect->min_y = y; | |
648 if (rect->max_x < x) rect->max_x = x; | |
649 if (rect->max_y < y) rect->max_y = y; | |
650 } | |
651 | |
652 // Update the acceptance for a station. | |
653 // show_msg controls whether to display a message that acceptance was changed. | |
654 static void UpdateStationAcceptance(Station *st, bool show_msg) | |
655 { | |
656 uint old_acc, new_acc; | |
657 const RoadStop *cur_rs; | |
658 int i; | |
659 ottd_Rectangle rect; | |
660 int rad; | |
661 AcceptedCargo accepts; | |
662 | |
663 rect.min_x = MapSizeX(); | |
664 rect.min_y = MapSizeY(); | |
665 rect.max_x = rect.max_y = 0; | |
666 // Don't update acceptance for a buoy | |
667 if (IsBuoy(st)) return; | |
668 | |
669 /* old accepted goods types */ | |
670 old_acc = GetAcceptanceMask(st); | |
671 | |
672 // Put all the tiles that span an area in the table. | |
673 if (st->train_tile != 0) { | |
674 MergePoint(&rect, st->train_tile); | |
675 MergePoint(&rect, | |
676 st->train_tile + TileDiffXY(st->trainst_w - 1, st->trainst_h - 1) | |
677 ); | |
678 } | |
679 | |
680 if (st->airport_tile != 0) { | |
681 const AirportFTAClass* afc = GetAirport(st->airport_type); | |
682 | |
683 MergePoint(&rect, st->airport_tile); | |
684 MergePoint(&rect, | |
685 st->airport_tile + TileDiffXY(afc->size_x - 1, afc->size_y - 1) | |
686 ); | |
687 } | |
688 | |
689 if (st->dock_tile != 0) MergePoint(&rect, st->dock_tile); | |
690 | |
691 for (cur_rs = st->bus_stops; cur_rs != NULL; cur_rs = cur_rs->next) { | |
692 MergePoint(&rect, cur_rs->xy); | |
693 } | |
694 | |
695 for (cur_rs = st->truck_stops; cur_rs != NULL; cur_rs = cur_rs->next) { | |
696 MergePoint(&rect, cur_rs->xy); | |
697 } | |
698 | |
699 rad = (_patches.modified_catchment) ? FindCatchmentRadius(st) : 4; | |
700 | |
701 // And retrieve the acceptance. | |
702 if (rect.max_x >= rect.min_x) { | |
703 GetAcceptanceAroundTiles( | |
704 accepts, | |
705 TileXY(rect.min_x, rect.min_y), | |
706 rect.max_x - rect.min_x + 1, | |
707 rect.max_y - rect.min_y + 1, | |
708 rad | |
709 ); | |
710 } else { | |
711 memset(accepts, 0, sizeof(accepts)); | |
712 } | |
713 | |
714 // Adjust in case our station only accepts fewer kinds of goods | |
715 for (i = 0; i != NUM_CARGO; i++) { | |
716 uint amt = min(accepts[i], 15); | |
717 | |
718 // Make sure the station can accept the goods type. | |
719 if ((i != CT_PASSENGERS && !(st->facilities & (byte)~FACIL_BUS_STOP)) || | |
720 (i == CT_PASSENGERS && !(st->facilities & (byte)~FACIL_TRUCK_STOP))) | |
721 amt = 0; | |
722 | |
723 SB(st->goods[i].waiting_acceptance, 12, 4, amt); | |
724 } | |
725 | |
726 // Only show a message in case the acceptance was actually changed. | |
727 new_acc = GetAcceptanceMask(st); | |
728 if (old_acc == new_acc) | |
729 return; | |
730 | |
731 // show a message to report that the acceptance was changed? | |
732 if (show_msg && st->owner == _local_player && st->facilities) { | |
733 uint32 accept=0, reject=0; /* these contain two string ids each */ | |
734 const StringID *str = _cargoc.names_s; | |
735 | |
736 do { | |
737 if (new_acc & 1) { | |
738 if (!(old_acc & 1)) accept = (accept << 16) | *str; | |
739 } else { | |
740 if (old_acc & 1) reject = (reject << 16) | *str; | |
741 } | |
742 } while (str++,(new_acc>>=1) != (old_acc>>=1)); | |
743 | |
744 ShowRejectOrAcceptNews(st, accept, STR_3040_NOW_ACCEPTS); | |
745 ShowRejectOrAcceptNews(st, reject, STR_303E_NO_LONGER_ACCEPTS); | |
746 } | |
747 | |
748 // redraw the station view since acceptance changed | |
749 InvalidateWindowWidget(WC_STATION_VIEW, st->index, 4); | |
750 } | |
751 | |
752 static void UpdateStationSignCoord(Station *st) | |
753 { | |
754 Rect *r = &st->rect; | |
755 | |
756 if (StationRect_IsEmpty(st)) return; // no tiles belong to this station | |
757 | |
758 // clamp sign coord to be inside the station rect | |
759 st->xy = TileXY(clampu(TileX(st->xy), r->left, r->right), clampu(TileY(st->xy), r->top, r->bottom)); | |
760 UpdateStationVirtCoordDirty(st); | |
761 } | |
762 | |
763 // This is called right after a station was deleted. | |
764 // It checks if the whole station is free of substations, and if so, the station will be | |
765 // deleted after a little while. | |
766 static void DeleteStationIfEmpty(Station* st) | |
767 { | |
768 if (st->facilities == 0) { | |
769 st->delete_ctr = 0; | |
770 RebuildStationLists(); | |
771 InvalidateWindow(WC_STATION_LIST, st->owner); | |
772 } | |
773 /* station remains but it probably lost some parts - station sign should stay in the station boundaries */ | |
774 UpdateStationSignCoord(st); | |
775 } | |
776 | |
777 static int32 ClearTile_Station(TileIndex tile, byte flags); | |
778 | |
779 // Tries to clear the given area. Returns the cost in case of success. | |
780 // Or an error code if it failed. | |
781 int32 CheckFlatLandBelow(TileIndex tile, uint w, uint h, uint flags, uint invalid_dirs, StationID* station) | |
782 { | |
783 int32 cost = 0, ret; | |
784 | |
785 Slope tileh; | |
786 uint z; | |
787 int allowed_z = -1; | |
788 int flat_z; | |
789 | |
790 BEGIN_TILE_LOOP(tile_cur, w, h, tile) | |
791 if (MayHaveBridgeAbove(tile_cur) && IsBridgeAbove(tile_cur)) { | |
792 return_cmd_error(STR_5007_MUST_DEMOLISH_BRIDGE_FIRST); | |
793 } | |
794 | |
795 if (!EnsureNoVehicle(tile_cur)) return CMD_ERROR; | |
796 | |
797 tileh = GetTileSlope(tile_cur, &z); | |
798 | |
799 /* Prohibit building if | |
800 * 1) The tile is "steep" (i.e. stretches two height levels) | |
801 * -OR- | |
802 * 2) The tile is non-flat if | |
803 * a) the player building is an "old-school" AI | |
804 * -OR- | |
805 * b) the build_on_slopes switch is disabled | |
806 */ | |
807 if (IsSteepSlope(tileh) || | |
808 ((_is_old_ai_player || !_patches.build_on_slopes) && tileh != SLOPE_FLAT)) { | |
809 return_cmd_error(STR_0007_FLAT_LAND_REQUIRED); | |
810 } | |
811 | |
812 flat_z = z; | |
813 if (tileh != SLOPE_FLAT) { | |
814 // need to check so the entrance to the station is not pointing at a slope. | |
815 if ((invalid_dirs&1 && !(tileh & SLOPE_NE) && (uint)w_cur == w) || | |
816 (invalid_dirs&2 && !(tileh & SLOPE_SE) && h_cur == 1) || | |
817 (invalid_dirs&4 && !(tileh & SLOPE_SW) && w_cur == 1) || | |
818 (invalid_dirs&8 && !(tileh & SLOPE_NW) && (uint)h_cur == h)) { | |
819 return_cmd_error(STR_0007_FLAT_LAND_REQUIRED); | |
820 } | |
821 cost += _price.terraform; | |
822 flat_z += TILE_HEIGHT; | |
823 } | |
824 | |
825 // get corresponding flat level and make sure that all parts of the station have the same level. | |
826 if (allowed_z == -1) { | |
827 // first tile | |
828 allowed_z = flat_z; | |
829 } else if (allowed_z != flat_z) { | |
830 return_cmd_error(STR_0007_FLAT_LAND_REQUIRED); | |
831 } | |
832 | |
833 // if station is set, then we have special handling to allow building on top of already existing stations. | |
834 // so station points to INVALID_STATION if we can build on any station. or it points to a station if we're only allowed to build | |
835 // on exactly that station. | |
836 if (station != NULL && IsTileType(tile_cur, MP_STATION)) { | |
837 if (!IsRailwayStation(tile_cur)) { | |
838 return ClearTile_Station(tile_cur, DC_AUTO); // get error message | |
839 } else { | |
840 StationID st = GetStationIndex(tile_cur); | |
841 if (*station == INVALID_STATION) { | |
842 *station = st; | |
843 } else if (*station != st) { | |
844 return_cmd_error(STR_3006_ADJOINS_MORE_THAN_ONE_EXISTING); | |
845 } | |
846 } | |
847 } else { | |
848 ret = DoCommand(tile_cur, 0, 0, flags, CMD_LANDSCAPE_CLEAR); | |
849 if (CmdFailed(ret)) return ret; | |
850 cost += ret; | |
851 } | |
852 END_TILE_LOOP(tile_cur, w, h, tile) | |
853 | |
854 return cost; | |
855 } | |
856 | |
857 static bool CanExpandRailroadStation(Station* st, uint* fin, Axis axis) | |
858 { | |
859 uint curw = st->trainst_w, curh = st->trainst_h; | |
860 TileIndex tile = fin[0]; | |
861 uint w = fin[1]; | |
862 uint h = fin[2]; | |
863 | |
864 if (_patches.nonuniform_stations) { | |
865 // determine new size of train station region.. | |
866 int x = min(TileX(st->train_tile), TileX(tile)); | |
867 int y = min(TileY(st->train_tile), TileY(tile)); | |
868 curw = max(TileX(st->train_tile) + curw, TileX(tile) + w) - x; | |
869 curh = max(TileY(st->train_tile) + curh, TileY(tile) + h) - y; | |
870 tile = TileXY(x, y); | |
871 } else { | |
872 // check so the orientation is the same | |
873 if (GetRailStationAxis(st->train_tile) != axis) { | |
874 _error_message = STR_306D_NONUNIFORM_STATIONS_DISALLOWED; | |
875 return false; | |
876 } | |
877 | |
878 // check if the new station adjoins the old station in either direction | |
879 if (curw == w && st->train_tile == tile + TileDiffXY(0, h)) { | |
880 // above | |
881 curh += h; | |
882 } else if (curw == w && st->train_tile == tile - TileDiffXY(0, curh)) { | |
883 // below | |
884 tile -= TileDiffXY(0, curh); | |
885 curh += h; | |
886 } else if (curh == h && st->train_tile == tile + TileDiffXY(w, 0)) { | |
887 // to the left | |
888 curw += w; | |
889 } else if (curh == h && st->train_tile == tile - TileDiffXY(curw, 0)) { | |
890 // to the right | |
891 tile -= TileDiffXY(curw, 0); | |
892 curw += w; | |
893 } else { | |
894 _error_message = STR_306D_NONUNIFORM_STATIONS_DISALLOWED; | |
895 return false; | |
896 } | |
897 } | |
898 // make sure the final size is not too big. | |
899 if (curw > _patches.station_spread || curh > _patches.station_spread) { | |
900 _error_message = STR_306C_STATION_TOO_SPREAD_OUT; | |
901 return false; | |
902 } | |
903 | |
904 // now tile contains the new value for st->train_tile | |
905 // curw, curh contain the new value for width and height | |
906 fin[0] = tile; | |
907 fin[1] = curw; | |
908 fin[2] = curh; | |
909 return true; | |
910 } | |
911 | |
912 static inline byte *CreateSingle(byte *layout, int n) | |
913 { | |
914 int i = n; | |
915 do *layout++ = 0; while (--i); | |
916 layout[((n-1) >> 1)-n] = 2; | |
917 return layout; | |
918 } | |
919 | |
920 static inline byte *CreateMulti(byte *layout, int n, byte b) | |
921 { | |
922 int i = n; | |
923 do *layout++ = b; while (--i); | |
924 if (n > 4) { | |
925 layout[0-n] = 0; | |
926 layout[n-1-n] = 0; | |
927 } | |
928 return layout; | |
929 } | |
930 | |
931 static void GetStationLayout(byte *layout, int numtracks, int plat_len, const StationSpec *statspec) | |
932 { | |
933 if (statspec != NULL && statspec->lengths >= plat_len && | |
934 statspec->platforms[plat_len - 1] >= numtracks && | |
935 statspec->layouts[plat_len - 1][numtracks - 1]) { | |
936 /* Custom layout defined, follow it. */ | |
937 memcpy(layout, statspec->layouts[plat_len - 1][numtracks - 1], | |
938 plat_len * numtracks); | |
939 return; | |
940 } | |
941 | |
942 if (plat_len == 1) { | |
943 CreateSingle(layout, numtracks); | |
944 } else { | |
945 if (numtracks & 1) layout = CreateSingle(layout, plat_len); | |
946 numtracks >>= 1; | |
947 | |
948 while (--numtracks >= 0) { | |
949 layout = CreateMulti(layout, plat_len, 4); | |
950 layout = CreateMulti(layout, plat_len, 6); | |
951 } | |
952 } | |
953 } | |
954 | |
955 /** Build railroad station | |
956 * @param tile_org starting position of station dragging/placement | |
957 * @param p1 various bitstuffed elements | |
958 * - p1 = (bit 0) - orientation (p1 & 1) | |
959 * - p1 = (bit 8-15) - number of tracks | |
960 * - p1 = (bit 16-23) - platform length | |
961 * @param p2 various bitstuffed elements | |
962 * - p2 = (bit 0- 3) - railtype (p2 & 0xF) | |
963 * - p2 = (bit 8-15) - custom station class | |
964 * - p2 = (bit 16-23) - custom station id | |
965 */ | |
966 int32 CmdBuildRailroadStation(TileIndex tile_org, uint32 flags, uint32 p1, uint32 p2) | |
967 { | |
968 Station *st; | |
969 int w_org, h_org; | |
970 int32 cost, ret; | |
971 StationID est; | |
972 int plat_len, numtracks; | |
973 Axis axis; | |
974 uint finalvalues[3]; | |
975 const StationSpec *statspec; | |
976 int specindex; | |
977 | |
978 SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION); | |
979 | |
980 /* Does the authority allow this? */ | |
981 if (!(flags & DC_NO_TOWN_RATING) && !CheckIfAuthorityAllows(tile_org)) return CMD_ERROR; | |
982 if (!ValParamRailtype(p2 & 0xF)) return CMD_ERROR; | |
983 | |
984 /* unpack parameters */ | |
985 axis = p1 & 1; | |
986 numtracks = GB(p1, 8, 8); | |
987 plat_len = GB(p1, 16, 8); | |
988 /* w = length, h = num_tracks */ | |
989 if (axis == AXIS_X) { | |
990 w_org = plat_len; | |
991 h_org = numtracks; | |
992 } else { | |
993 h_org = plat_len; | |
994 w_org = numtracks; | |
995 } | |
996 | |
997 if (h_org > _patches.station_spread || w_org > _patches.station_spread) return CMD_ERROR; | |
998 | |
999 // these values are those that will be stored in train_tile and station_platforms | |
1000 finalvalues[0] = tile_org; | |
1001 finalvalues[1] = w_org; | |
1002 finalvalues[2] = h_org; | |
1003 | |
1004 // Make sure the area below consists of clear tiles. (OR tiles belonging to a certain rail station) | |
1005 est = INVALID_STATION; | |
1006 // If DC_EXEC is in flag, do not want to pass it to CheckFlatLandBelow, because of a nice bug | |
1007 // for detail info, see: https://sourceforge.net/tracker/index.php?func=detail&aid=1029064&group_id=103924&atid=636365 | |
1008 ret = CheckFlatLandBelow(tile_org, w_org, h_org, flags & ~DC_EXEC, 5 << axis, _patches.nonuniform_stations ? &est : NULL); | |
1009 if (CmdFailed(ret)) return ret; | |
1010 cost = ret + (numtracks * _price.train_station_track + _price.train_station_length) * plat_len; | |
1011 | |
1012 // Make sure there are no similar stations around us. | |
1013 st = GetStationAround(tile_org, w_org, h_org, est); | |
1014 if (st == CHECK_STATIONS_ERR) return CMD_ERROR; | |
1015 | |
1016 // See if there is a deleted station close to us. | |
1017 if (st == NULL) { | |
1018 st = GetClosestStationFromTile(tile_org, 8, _current_player); | |
1019 if (st != NULL && st->facilities) st = NULL; | |
1020 } | |
1021 | |
1022 if (st != NULL) { | |
1023 // Reuse an existing station. | |
1024 if (st->owner != OWNER_NONE && st->owner != _current_player) | |
1025 return_cmd_error(STR_3009_TOO_CLOSE_TO_ANOTHER_STATION); | |
1026 | |
1027 if (st->train_tile != 0) { | |
1028 // check if we want to expanding an already existing station? | |
1029 if (_is_old_ai_player || !_patches.join_stations) | |
1030 return_cmd_error(STR_3005_TOO_CLOSE_TO_ANOTHER_RAILROAD); | |
1031 if (!CanExpandRailroadStation(st, finalvalues, axis)) | |
1032 return CMD_ERROR; | |
1033 } | |
1034 | |
1035 //XXX can't we pack this in the "else" part of the if above? | |
1036 if (!StationRect_BeforeAddRect(st, tile_org, w_org, h_org, RECT_MODE_TEST)) return CMD_ERROR; | |
1037 } else { | |
1038 // Create a new station | |
1039 st = AllocateStation(); | |
1040 if (st == NULL) return CMD_ERROR; | |
1041 | |
1042 st->town = ClosestTownFromTile(tile_org, (uint)-1); | |
1043 if (IsValidPlayer(_current_player) && (flags & DC_EXEC)) | |
1044 SETBIT(st->town->have_ratings, _current_player); | |
1045 | |
1046 if (!GenerateStationName(st, tile_org, 0)) return CMD_ERROR; | |
1047 | |
1048 if (flags & DC_EXEC) StationInitialize(st, tile_org); | |
1049 } | |
1050 | |
1051 /* Check if the given station class is valid */ | |
1052 if (GB(p2, 8, 8) >= STAT_CLASS_MAX) return CMD_ERROR; | |
1053 | |
1054 /* Check if we can allocate a custom stationspec to this station */ | |
1055 statspec = GetCustomStationSpec(GB(p2, 8, 8), GB(p2, 16, 8)); | |
1056 specindex = AllocateSpecToStation(statspec, st, flags & DC_EXEC); | |
1057 if (specindex == -1) return CMD_ERROR; | |
1058 | |
1059 if (statspec != NULL) { | |
1060 /* Perform NewStation checks */ | |
1061 | |
1062 /* Check if the station size is permitted */ | |
1063 if (HASBIT(statspec->disallowed_platforms, numtracks - 1) || HASBIT(statspec->disallowed_lengths, plat_len - 1)) { | |
1064 return CMD_ERROR; | |
1065 } | |
1066 | |
1067 /* Check if the station is buildable */ | |
1068 if (HASBIT(statspec->callbackmask, CBM_STATION_AVAIL) && GetStationCallback(CBID_STATION_AVAILABILITY, 0, 0, statspec, NULL, INVALID_TILE) == 0) { | |
1069 return CMD_ERROR; | |
1070 } | |
1071 } | |
1072 | |
1073 if (flags & DC_EXEC) { | |
1074 TileIndexDiff tile_delta; | |
1075 byte *layout_ptr; | |
1076 byte numtracks_orig; | |
1077 Track track; | |
1078 | |
1079 // Now really clear the land below the station | |
1080 // It should never return CMD_ERROR.. but you never know ;) | |
1081 // (a bit strange function name for it, but it really does clear the land, when DC_EXEC is in flags) | |
1082 ret = CheckFlatLandBelow(tile_org, w_org, h_org, flags, 5 << axis, _patches.nonuniform_stations ? &est : NULL); | |
1083 if (CmdFailed(ret)) return ret; | |
1084 | |
1085 st->train_tile = finalvalues[0]; | |
1086 if (!st->facilities) st->xy = finalvalues[0]; | |
1087 st->facilities |= FACIL_TRAIN; | |
1088 st->owner = _current_player; | |
1089 | |
1090 st->trainst_w = finalvalues[1]; | |
1091 st->trainst_h = finalvalues[2]; | |
1092 | |
1093 st->build_date = _date; | |
1094 | |
1095 StationRect_BeforeAddRect(st, tile_org, w_org, h_org, RECT_MODE_TRY); | |
1096 | |
1097 tile_delta = (axis == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1)); | |
1098 track = AxisToTrack(axis); | |
1099 | |
1100 layout_ptr = alloca(numtracks * plat_len); | |
1101 GetStationLayout(layout_ptr, numtracks, plat_len, statspec); | |
1102 | |
1103 numtracks_orig = numtracks; | |
1104 | |
1105 do { | |
1106 TileIndex tile = tile_org; | |
1107 int w = plat_len; | |
1108 do { | |
1109 byte layout = *layout_ptr++; | |
1110 MakeRailStation(tile, st->owner, st->index, axis, layout, GB(p2, 0, 4)); | |
1111 SetCustomStationSpecIndex(tile, specindex); | |
1112 SetStationTileRandomBits(tile, GB(Random(), 0, 4)); | |
1113 | |
1114 if (statspec != NULL) { | |
1115 /* Use a fixed axis for GetPlatformInfo as our platforms / numtracks are always the right way around */ | |
1116 uint32 platinfo = GetPlatformInfo(AXIS_X, 0, plat_len, numtracks_orig, plat_len - w, numtracks_orig - numtracks, false); | |
1117 uint16 callback = GetStationCallback(CBID_STATION_TILE_LAYOUT, platinfo, 0, statspec, st, tile); | |
1118 if (callback != CALLBACK_FAILED && callback < 8) SetStationGfx(tile, callback + axis); | |
1119 } | |
1120 | |
1121 tile += tile_delta; | |
1122 } while (--w); | |
1123 SetSignalsOnBothDir(tile_org, track); | |
1124 YapfNotifyTrackLayoutChange(tile_org, track); | |
1125 tile_org += tile_delta ^ TileDiffXY(1, 1); // perpendicular to tile_delta | |
1126 } while (--numtracks); | |
1127 | |
1128 MarkStationTilesDirty(st); | |
1129 UpdateStationVirtCoordDirty(st); | |
1130 UpdateStationAcceptance(st, false); | |
1131 RebuildStationLists(); | |
1132 InvalidateWindow(WC_STATION_LIST, st->owner); | |
1133 } | |
1134 | |
1135 return cost; | |
1136 } | |
1137 | |
1138 static void MakeRailwayStationAreaSmaller(Station *st) | |
1139 { | |
1140 uint w = st->trainst_w; | |
1141 uint h = st->trainst_h; | |
1142 TileIndex tile = st->train_tile; | |
1143 uint i; | |
1144 | |
1145 restart: | |
1146 | |
1147 // too small? | |
1148 if (w != 0 && h != 0) { | |
1149 // check the left side, x = constant, y changes | |
1150 for (i = 0; !TileBelongsToRailStation(st, tile + TileDiffXY(0, i));) { | |
1151 // the left side is unused? | |
1152 if (++i == h) { | |
1153 tile += TileDiffXY(1, 0); | |
1154 w--; | |
1155 goto restart; | |
1156 } | |
1157 } | |
1158 | |
1159 // check the right side, x = constant, y changes | |
1160 for (i = 0; !TileBelongsToRailStation(st, tile + TileDiffXY(w - 1, i));) { | |
1161 // the right side is unused? | |
1162 if (++i == h) { | |
1163 w--; | |
1164 goto restart; | |
1165 } | |
1166 } | |
1167 | |
1168 // check the upper side, y = constant, x changes | |
1169 for (i = 0; !TileBelongsToRailStation(st, tile + TileDiffXY(i, 0));) { | |
1170 // the left side is unused? | |
1171 if (++i == w) { | |
1172 tile += TileDiffXY(0, 1); | |
1173 h--; | |
1174 goto restart; | |
1175 } | |
1176 } | |
1177 | |
1178 // check the lower side, y = constant, x changes | |
1179 for (i = 0; !TileBelongsToRailStation(st, tile + TileDiffXY(i, h - 1));) { | |
1180 // the left side is unused? | |
1181 if (++i == w) { | |
1182 h--; | |
1183 goto restart; | |
1184 } | |
1185 } | |
1186 } else { | |
1187 tile = 0; | |
1188 } | |
1189 | |
1190 st->trainst_w = w; | |
1191 st->trainst_h = h; | |
1192 st->train_tile = tile; | |
1193 } | |
1194 | |
1195 /** Remove a single tile from a railroad station. | |
1196 * This allows for custom-built station with holes and weird layouts | |
1197 * @param tile tile of station piece to remove | |
1198 * @param p1 unused | |
1199 * @param p2 unused | |
1200 */ | |
1201 int32 CmdRemoveFromRailroadStation(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) | |
1202 { | |
1203 Station *st; | |
1204 | |
1205 SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION); | |
1206 | |
1207 // make sure the specified tile belongs to the current player, and that it is a railroad station. | |
1208 if (!IsTileType(tile, MP_STATION) || !IsRailwayStation(tile) || !_patches.nonuniform_stations) return CMD_ERROR; | |
1209 st = GetStationByTile(tile); | |
1210 if (_current_player != OWNER_WATER && (!CheckOwnership(st->owner) || !EnsureNoVehicle(tile))) return CMD_ERROR; | |
1211 | |
1212 // if we reached here, it means we can actually delete it. do that. | |
1213 if (flags & DC_EXEC) { | |
1214 uint specindex = GetCustomStationSpecIndex(tile); | |
1215 Track track = GetRailStationTrack(tile); | |
1216 DoClearSquare(tile); | |
1217 StationRect_AfterRemoveTile(st, tile); | |
1218 SetSignalsOnBothDir(tile, track); | |
1219 YapfNotifyTrackLayoutChange(tile, track); | |
1220 | |
1221 DeallocateSpecFromStation(st, specindex); | |
1222 | |
1223 // now we need to make the "spanned" area of the railway station smaller if we deleted something at the edges. | |
1224 // we also need to adjust train_tile. | |
1225 MakeRailwayStationAreaSmaller(st); | |
1226 MarkStationTilesDirty(st); | |
1227 UpdateStationSignCoord(st); | |
1228 | |
1229 // if we deleted the whole station, delete the train facility. | |
1230 if (st->train_tile == 0) { | |
1231 st->facilities &= ~FACIL_TRAIN; | |
1232 UpdateStationVirtCoordDirty(st); | |
1233 DeleteStationIfEmpty(st); | |
1234 } | |
1235 } | |
1236 return _price.remove_rail_station; | |
1237 } | |
1238 | |
1239 // determine the number of platforms for the station | |
1240 uint GetStationPlatforms(const Station *st, TileIndex tile) | |
1241 { | |
1242 TileIndex t; | |
1243 TileIndexDiff delta; | |
1244 Axis axis; | |
1245 uint len; | |
1246 assert(TileBelongsToRailStation(st, tile)); | |
1247 | |
1248 len = 0; | |
1249 axis = GetRailStationAxis(tile); | |
1250 delta = (axis == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1)); | |
1251 | |
1252 // find starting tile.. | |
1253 t = tile; | |
1254 do { | |
1255 t -= delta; | |
1256 len++; | |
1257 } while (TileBelongsToRailStation(st, t) && GetRailStationAxis(t) == axis); | |
1258 | |
1259 // find ending tile | |
1260 t = tile; | |
1261 do { | |
1262 t += delta; | |
1263 len++; | |
1264 } while (TileBelongsToRailStation(st, t) && GetRailStationAxis(t) == axis); | |
1265 | |
1266 return len - 1; | |
1267 } | |
1268 | |
1269 /** Determines the REMAINING length of a platform, starting at (and including) | |
1270 * the given tile. | |
1271 * @param tile the tile from which to start searching. Must be a railway station tile | |
1272 * @param dir The direction in which to search. | |
1273 * @return The platform length | |
1274 */ | |
1275 uint GetPlatformLength(TileIndex tile, DiagDirection dir) | |
1276 { | |
1277 TileIndex start_tile = tile; | |
1278 uint length = 0; | |
1279 assert(IsRailwayStationTile(tile)); | |
1280 assert(dir < DIAGDIR_END); | |
1281 | |
1282 do { | |
1283 length ++; | |
1284 tile += TileOffsByDiagDir(dir); | |
1285 } while (IsCompatibleTrainStationTile(tile, start_tile)); | |
1286 | |
1287 return length; | |
1288 } | |
1289 | |
1290 | |
1291 static int32 RemoveRailroadStation(Station *st, TileIndex tile, uint32 flags) | |
1292 { | |
1293 int w,h; | |
1294 int32 cost = 0; | |
1295 | |
1296 /* if there is flooding and non-uniform stations are enabled, remove platforms tile by tile */ | |
1297 if (_current_player == OWNER_WATER && _patches.nonuniform_stations) | |
1298 return DoCommand(tile, 0, 0, DC_EXEC, CMD_REMOVE_FROM_RAILROAD_STATION); | |
1299 | |
1300 /* Current player owns the station? */ | |
1301 if (_current_player != OWNER_WATER && !CheckOwnership(st->owner)) | |
1302 return CMD_ERROR; | |
1303 | |
1304 /* determine width and height of platforms */ | |
1305 tile = st->train_tile; | |
1306 w = st->trainst_w; | |
1307 h = st->trainst_h; | |
1308 | |
1309 assert(w != 0 && h != 0); | |
1310 | |
1311 /* clear all areas of the station */ | |
1312 do { | |
1313 int w_bak = w; | |
1314 do { | |
1315 // for nonuniform stations, only remove tiles that are actually train station tiles | |
1316 if (TileBelongsToRailStation(st, tile)) { | |
1317 if (!EnsureNoVehicle(tile)) | |
1318 return CMD_ERROR; | |
1319 cost += _price.remove_rail_station; | |
1320 if (flags & DC_EXEC) { | |
1321 Track track = GetRailStationTrack(tile); | |
1322 DoClearSquare(tile); | |
1323 SetSignalsOnBothDir(tile, track); | |
1324 YapfNotifyTrackLayoutChange(tile, track); | |
1325 } | |
1326 } | |
1327 tile += TileDiffXY(1, 0); | |
1328 } while (--w); | |
1329 w = w_bak; | |
1330 tile += TileDiffXY(-w, 1); | |
1331 } while (--h); | |
1332 | |
1333 if (flags & DC_EXEC) { | |
1334 StationRect_AfterRemoveRect(st, st->train_tile, st->trainst_w, st->trainst_h); | |
1335 | |
1336 st->train_tile = 0; | |
1337 st->trainst_w = st->trainst_h = 0; | |
1338 st->facilities &= ~FACIL_TRAIN; | |
1339 | |
1340 free(st->speclist); | |
1341 st->num_specs = 0; | |
1342 st->speclist = NULL; | |
1343 | |
1344 UpdateStationVirtCoordDirty(st); | |
1345 DeleteStationIfEmpty(st); | |
1346 } | |
1347 | |
1348 return cost; | |
1349 } | |
1350 | |
1351 int32 DoConvertStationRail(TileIndex tile, RailType totype, bool exec) | |
1352 { | |
1353 const Station* st = GetStationByTile(tile); | |
1354 | |
1355 if (!CheckOwnership(st->owner) || !EnsureNoVehicle(tile)) return CMD_ERROR; | |
1356 | |
1357 // tile is not a railroad station? | |
1358 if (!IsRailwayStation(tile)) return CMD_ERROR; | |
1359 | |
1360 if (GetRailType(tile) == totype) return CMD_ERROR; | |
1361 | |
1362 // 'hidden' elrails can't be downgraded to normal rail when elrails are disabled | |
1363 if (_patches.disable_elrails && totype == RAILTYPE_RAIL && GetRailType(tile) == RAILTYPE_ELECTRIC) return CMD_ERROR; | |
1364 | |
1365 if (exec) { | |
1366 SetRailType(tile, totype); | |
1367 MarkTileDirtyByTile(tile); | |
1368 YapfNotifyTrackLayoutChange(tile, GetRailStationTrack(tile)); | |
1369 } | |
1370 | |
1371 return _price.build_rail >> 1; | |
1372 } | |
1373 | |
1374 /** Heavy wizardry used to add a roadstop to a station. | |
1375 * To understand the function, lets first look at what is passed around, | |
1376 * especially the last two parameters. CmdBuildRoadStop allocates a road | |
1377 * stop and needs to put that stop into the linked list of road stops. | |
1378 * It (CmdBuildRoadStop) has a **currstop pointer which points to element | |
1379 * in the linked list of stops (each element in this list being a pointer | |
1380 * in itself, hence the double pointer). We (FindRoadStopSpot) need to | |
1381 * modify this pointer (**currstop) thus we need to pass by reference, | |
1382 * obtaining a triple pointer (***currstop). When finished, **currstop | |
1383 * in CmdBuildRoadStop will contain the address of the pointer which will | |
1384 * then point into the global roadstop array. *prev (in CmdBuildRoadStop) | |
1385 * is the pointer tino the global roadstop array which has *currstop in | |
1386 * its ->next element. | |
1387 * @param[in] truck_station Determines whether a stop is RS_BUS or RS_TRUCK | |
1388 * @param[in] station The station to do the whole procedure for | |
1389 * @param[out] currstop See the detailed function description | |
1390 * @param prev See the detailed function description | |
1391 */ | |
1392 static void FindRoadStopSpot(bool truck_station, Station* st, RoadStop*** currstop, RoadStop** prev) | |
1393 { | |
1394 RoadStop **primary_stop = (truck_station) ? &st->truck_stops : &st->bus_stops; | |
1395 assert(*prev == NULL); | |
1396 | |
1397 if (*primary_stop == NULL) { | |
1398 //we have no roadstop of the type yet, so write a "primary stop" | |
1399 *currstop = primary_stop; | |
1400 } else { | |
1401 //there are stops already, so append to the end of the list | |
1402 *prev = *primary_stop; | |
1403 *currstop = &(*primary_stop)->next; | |
1404 while (**currstop != NULL) { | |
1405 *prev = (*prev)->next; | |
1406 *currstop = &(**currstop)->next; | |
1407 } | |
1408 } | |
1409 } | |
1410 | |
1411 /** Build a bus or truck stop | |
1412 * @param tile tile to build the stop at | |
1413 * @param p1 entrance direction (DiagDirection) | |
1414 * @param p2 0 for Bus stops, 1 for truck stops | |
1415 */ | |
1416 int32 CmdBuildRoadStop(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) | |
1417 { | |
1418 Station *st; | |
1419 RoadStop *road_stop; | |
1420 RoadStop **currstop; | |
1421 RoadStop *prev = NULL; | |
1422 int32 cost; | |
1423 int32 ret; | |
1424 bool type = !!p2; | |
1425 | |
1426 /* Saveguard the parameters */ | |
1427 if (!IsValidDiagDirection(p1)) return CMD_ERROR; | |
1428 | |
1429 SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION); | |
1430 | |
1431 if (!(flags & DC_NO_TOWN_RATING) && !CheckIfAuthorityAllows(tile)) | |
1432 return CMD_ERROR; | |
1433 | |
1434 ret = CheckFlatLandBelow(tile, 1, 1, flags, 1 << p1, NULL); | |
1435 if (CmdFailed(ret)) return ret; | |
1436 cost = ret; | |
1437 | |
1438 st = GetStationAround(tile, 1, 1, -1); | |
1439 if (st == CHECK_STATIONS_ERR) return CMD_ERROR; | |
1440 | |
1441 /* Find a station close to us */ | |
1442 if (st == NULL) { | |
1443 st = GetClosestStationFromTile(tile, 8, _current_player); | |
1444 if (st != NULL && st->facilities != 0) st = NULL; | |
1445 } | |
1446 | |
1447 //give us a road stop in the list, and check if something went wrong | |
1448 road_stop = AllocateRoadStop(); | |
1449 if (road_stop == NULL) { | |
1450 return_cmd_error(type ? STR_3008B_TOO_MANY_TRUCK_STOPS : STR_3008A_TOO_MANY_BUS_STOPS); | |
1451 } | |
1452 | |
1453 if (st != NULL && | |
1454 GetNumRoadStopsInStation(st, RS_BUS) + GetNumRoadStopsInStation(st, RS_TRUCK) >= ROAD_STOP_LIMIT) { | |
1455 return_cmd_error(type ? STR_3008B_TOO_MANY_TRUCK_STOPS : STR_3008A_TOO_MANY_BUS_STOPS); | |
1456 } | |
1457 | |
1458 if (st != NULL) { | |
1459 if (st->owner != OWNER_NONE && st->owner != _current_player) { | |
1460 return_cmd_error(STR_3009_TOO_CLOSE_TO_ANOTHER_STATION); | |
1461 } | |
1462 | |
1463 if (!StationRect_BeforeAddTile(st, tile, RECT_MODE_TEST)) return CMD_ERROR; | |
1464 | |
1465 FindRoadStopSpot(type, st, &currstop, &prev); | |
1466 } else { | |
1467 Town *t; | |
1468 | |
1469 st = AllocateStation(); | |
1470 if (st == NULL) return CMD_ERROR; | |
1471 | |
1472 st->town = t = ClosestTownFromTile(tile, (uint)-1); | |
1473 | |
1474 FindRoadStopSpot(type, st, &currstop, &prev); | |
1475 | |
1476 if (IsValidPlayer(_current_player) && (flags & DC_EXEC)) { | |
1477 SETBIT(t->have_ratings, _current_player); | |
1478 } | |
1479 | |
1480 st->sign.width_1 = 0; | |
1481 | |
1482 if (!GenerateStationName(st, tile, 0)) return CMD_ERROR; | |
1483 | |
1484 if (flags & DC_EXEC) StationInitialize(st, tile); | |
1485 } | |
1486 | |
1487 cost += (type) ? _price.build_truck_station : _price.build_bus_station; | |
1488 | |
1489 if (flags & DC_EXEC) { | |
1490 //point to the correct item in the _busstops or _truckstops array | |
1491 *currstop = road_stop; | |
1492 | |
1493 //initialize an empty station | |
1494 InitializeRoadStop(road_stop, prev, tile, st->index); | |
1495 if (!st->facilities) st->xy = tile; | |
1496 st->facilities |= (type) ? FACIL_TRUCK_STOP : FACIL_BUS_STOP; | |
1497 st->owner = _current_player; | |
1498 | |
1499 st->build_date = _date; | |
1500 | |
1501 StationRect_BeforeAddTile(st, tile, RECT_MODE_TRY); | |
1502 | |
1503 MakeRoadStop(tile, st->owner, st->index, type, p1); | |
1504 | |
1505 UpdateStationVirtCoordDirty(st); | |
1506 UpdateStationAcceptance(st, false); | |
1507 RebuildStationLists(); | |
1508 InvalidateWindow(WC_STATION_LIST, st->owner); | |
1509 } | |
1510 return cost; | |
1511 } | |
1512 | |
1513 // Remove a bus station | |
1514 static int32 RemoveRoadStop(Station *st, uint32 flags, TileIndex tile) | |
1515 { | |
1516 RoadStop **primary_stop; | |
1517 RoadStop *cur_stop; | |
1518 bool is_truck = IsTruckStop(tile); | |
1519 | |
1520 if (_current_player != OWNER_WATER && !CheckOwnership(st->owner)) { | |
1521 return CMD_ERROR; | |
1522 } | |
1523 | |
1524 if (is_truck) { // truck stop | |
1525 primary_stop = &st->truck_stops; | |
1526 cur_stop = GetRoadStopByTile(tile, RS_TRUCK); | |
1527 } else { | |
1528 primary_stop = &st->bus_stops; | |
1529 cur_stop = GetRoadStopByTile(tile, RS_BUS); | |
1530 } | |
1531 | |
1532 assert(cur_stop != NULL); | |
1533 | |
1534 if (!EnsureNoVehicle(tile)) return CMD_ERROR; | |
1535 | |
1536 if (flags & DC_EXEC) { | |
1537 //we only had one stop left | |
1538 if (cur_stop->next == NULL && cur_stop->prev == NULL) { | |
1539 //so we remove ALL stops | |
1540 *primary_stop = NULL; | |
1541 st->facilities &= (is_truck) ? ~FACIL_TRUCK_STOP : ~FACIL_BUS_STOP; | |
1542 } else if (cur_stop == *primary_stop) { | |
1543 //removed the first stop in the list | |
1544 //need to set the primary element to the next stop | |
1545 *primary_stop = (*primary_stop)->next; | |
1546 } | |
1547 | |
1548 DeleteRoadStop(cur_stop); | |
1549 DoClearSquare(tile); | |
1550 StationRect_AfterRemoveTile(st, tile); | |
1551 | |
1552 UpdateStationVirtCoordDirty(st); | |
1553 DeleteStationIfEmpty(st); | |
1554 } | |
1555 | |
1556 return (is_truck) ? _price.remove_truck_station : _price.remove_bus_station; | |
1557 } | |
1558 | |
1559 | |
1560 | |
1561 // FIXME -- need to move to its corresponding Airport variable | |
1562 // Country Airfield (small) | |
1563 static const byte _airport_sections_country[] = { | |
1564 54, 53, 52, 65, | |
1565 58, 57, 56, 55, | |
1566 64, 63, 63, 62 | |
1567 }; | |
1568 | |
1569 // City Airport (large) | |
1570 static const byte _airport_sections_town[] = { | |
1571 31, 9, 33, 9, 9, 32, | |
1572 27, 36, 29, 34, 8, 10, | |
1573 30, 11, 35, 13, 20, 21, | |
1574 51, 12, 14, 17, 19, 28, | |
1575 38, 13, 15, 16, 18, 39, | |
1576 26, 22, 23, 24, 25, 26 | |
1577 }; | |
1578 | |
1579 // Metropolitain Airport (large) - 2 runways | |
1580 static const byte _airport_sections_metropolitan[] = { | |
1581 31, 9, 33, 9, 9, 32, | |
1582 27, 36, 29, 34, 8, 10, | |
1583 30, 11, 35, 13, 20, 21, | |
1584 102, 8, 8, 8, 8, 28, | |
1585 83, 84, 84, 84, 84, 83, | |
1586 26, 23, 23, 23, 23, 26 | |
1587 }; | |
1588 | |
1589 // International Airport (large) - 2 runways | |
1590 static const byte _airport_sections_international[] = { | |
1591 88, 89, 89, 89, 89, 89, 88, | |
1592 51, 8, 8, 8, 8, 8, 32, | |
1593 30, 8, 11, 27, 11, 8, 10, | |
1594 32, 8, 11, 27, 11, 8, 114, | |
1595 87, 8, 11, 85, 11, 8, 114, | |
1596 87, 8, 8, 8, 8, 8, 90, | |
1597 26, 23, 23, 23, 23, 23, 26 | |
1598 }; | |
1599 | |
1600 // Intercontinental Airport (vlarge) - 4 runways | |
1601 static const byte _airport_sections_intercontinental[] = { | |
1602 102, 120, 89, 89, 89, 89, 89, 89, 118, | |
1603 120, 22, 22, 22, 22, 22, 22, 119, 117, | |
1604 87, 54, 87, 8, 8, 8, 8, 51, 117, | |
1605 87, 162, 87, 85, 116, 116, 8, 9, 10, | |
1606 87, 8, 8, 11, 31, 11, 8, 160, 32, | |
1607 32, 160, 8, 11, 27, 11, 8, 8, 10, | |
1608 87, 8, 8, 11, 30, 11, 8, 8, 10, | |
1609 87, 142, 8, 11, 29, 11, 10, 163, 10, | |
1610 87, 164, 87, 8, 8, 8, 10, 37, 117, | |
1611 87, 120, 89, 89, 89, 89, 89, 89, 119, | |
1612 121, 22, 22, 22, 22, 22, 22, 119, 37 | |
1613 }; | |
1614 | |
1615 | |
1616 // Commuter Airfield (small) | |
1617 static const byte _airport_sections_commuter[] = { | |
1618 85, 30, 115, 115, 32, | |
1619 87, 8, 8, 8, 10, | |
1620 87, 11, 11, 11, 10, | |
1621 26, 23, 23, 23, 26 | |
1622 }; | |
1623 | |
1624 // Heliport | |
1625 static const byte _airport_sections_heliport[] = { | |
1626 66, | |
1627 }; | |
1628 | |
1629 // Helidepot | |
1630 static const byte _airport_sections_helidepot[] = { | |
1631 124, 32, | |
1632 122, 123 | |
1633 }; | |
1634 | |
1635 // Helistation | |
1636 static const byte _airport_sections_helistation[] = { | |
1637 32, 134, 159, 158, | |
1638 161, 142, 142, 157 | |
1639 }; | |
1640 | |
1641 static const byte * const _airport_sections[] = { | |
1642 _airport_sections_country, // Country Airfield (small) | |
1643 _airport_sections_town, // City Airport (large) | |
1644 _airport_sections_heliport, // Heliport | |
1645 _airport_sections_metropolitan, // Metropolitain Airport (large) | |
1646 _airport_sections_international, // International Airport (xlarge) | |
1647 _airport_sections_commuter, // Commuter Airport (small) | |
1648 _airport_sections_helidepot, // Helidepot | |
1649 _airport_sections_intercontinental, // Intercontinental Airport (xxlarge) | |
1650 _airport_sections_helistation // Helistation | |
1651 }; | |
1652 | |
1653 /** Place an Airport. | |
1654 * @param tile tile where airport will be built | |
1655 * @param p1 airport type, @see airport.h | |
1656 * @param p2 unused | |
1657 */ | |
1658 int32 CmdBuildAirport(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) | |
1659 { | |
1660 Town *t; | |
1661 Station *st; | |
1662 int32 cost; | |
1663 int32 ret; | |
1664 int w, h; | |
1665 bool airport_upgrade = true; | |
1666 const AirportFTAClass* afc; | |
1667 | |
1668 SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION); | |
1669 | |
1670 /* Check if a valid, buildable airport was chosen for construction */ | |
1671 if (p1 > lengthof(_airport_sections) || !HASBIT(GetValidAirports(), p1)) return CMD_ERROR; | |
1672 | |
1673 if (!(flags & DC_NO_TOWN_RATING) && !CheckIfAuthorityAllows(tile)) | |
1674 return CMD_ERROR; | |
1675 | |
1676 t = ClosestTownFromTile(tile, (uint)-1); | |
1677 | |
1678 /* Check if local auth refuses a new airport */ | |
1679 { | |
1680 uint num = 0; | |
1681 FOR_ALL_STATIONS(st) { | |
1682 if (st->town == t && st->facilities&FACIL_AIRPORT && st->airport_type != AT_OILRIG) | |
1683 num++; | |
1684 } | |
1685 if (num >= 2) { | |
1686 SetDParam(0, t->index); | |
1687 return_cmd_error(STR_2035_LOCAL_AUTHORITY_REFUSES); | |
1688 } | |
1689 } | |
1690 | |
1691 afc = GetAirport(p1); | |
1692 w = afc->size_x; | |
1693 h = afc->size_y; | |
1694 | |
1695 ret = CheckFlatLandBelow(tile, w, h, flags, 0, NULL); | |
1696 if (CmdFailed(ret)) return ret; | |
1697 cost = ret; | |
1698 | |
1699 st = GetStationAround(tile, w, h, -1); | |
1700 if (st == CHECK_STATIONS_ERR) return CMD_ERROR; | |
1701 | |
1702 /* Find a station close to us */ | |
1703 if (st == NULL) { | |
1704 st = GetClosestStationFromTile(tile, 8, _current_player); | |
1705 if (st != NULL && st->facilities) st = NULL; | |
1706 } | |
1707 | |
1708 if (w > _patches.station_spread || h > _patches.station_spread) { | |
1709 _error_message = STR_306C_STATION_TOO_SPREAD_OUT; | |
1710 return CMD_ERROR; | |
1711 } | |
1712 | |
1713 if (st != NULL) { | |
1714 if (st->owner != OWNER_NONE && st->owner != _current_player) | |
1715 return_cmd_error(STR_3009_TOO_CLOSE_TO_ANOTHER_STATION); | |
1716 | |
1717 if (!StationRect_BeforeAddRect(st, tile, w, h, RECT_MODE_TEST)) return CMD_ERROR; | |
1718 | |
1719 if (st->airport_tile != 0) | |
1720 return_cmd_error(STR_300D_TOO_CLOSE_TO_ANOTHER_AIRPORT); | |
1721 } else { | |
1722 airport_upgrade = false; | |
1723 | |
1724 st = AllocateStation(); | |
1725 if (st == NULL) return CMD_ERROR; | |
1726 | |
1727 st->town = t; | |
1728 | |
1729 if (IsValidPlayer(_current_player) && (flags & DC_EXEC)) | |
1730 SETBIT(t->have_ratings, _current_player); | |
1731 | |
1732 st->sign.width_1 = 0; | |
1733 | |
1734 // if airport type equals Heliport then generate | |
1735 // type 5 name, which is heliport, otherwise airport names (1) | |
1736 if (!GenerateStationName(st, tile, (p1 == AT_HELIPORT)||(p1 == AT_HELIDEPOT)||(p1 == AT_HELISTATION) ? 5 : 1)) | |
1737 return CMD_ERROR; | |
1738 | |
1739 if (flags & DC_EXEC) StationInitialize(st, tile); | |
1740 } | |
1741 | |
1742 cost += _price.build_airport * w * h; | |
1743 | |
1744 if (flags & DC_EXEC) { | |
1745 st->owner = _current_player; | |
1746 st->airport_tile = tile; | |
1747 if (!st->facilities) st->xy = tile; | |
1748 st->facilities |= FACIL_AIRPORT; | |
1749 st->airport_type = (byte)p1; | |
1750 st->airport_flags = 0; | |
1751 | |
1752 st->build_date = _date; | |
1753 | |
1754 StationRect_BeforeAddRect(st, tile, w, h, RECT_MODE_TRY); | |
1755 | |
1756 /* if airport was demolished while planes were en-route to it, the | |
1757 * positions can no longer be the same (v->u.air.pos), since different | |
1758 * airports have different indexes. So update all planes en-route to this | |
1759 * airport. Only update if | |
1760 * 1. airport is upgraded | |
1761 * 2. airport is added to existing station (unfortunately unavoideable) | |
1762 */ | |
1763 if (airport_upgrade) UpdateAirplanesOnNewStation(st); | |
1764 | |
1765 { | |
1766 const byte *b = _airport_sections[p1]; | |
1767 | |
1768 BEGIN_TILE_LOOP(tile_cur, w, h, tile) { | |
1769 MakeAirport(tile_cur, st->owner, st->index, *b++); | |
1770 } END_TILE_LOOP(tile_cur, w, h, tile) | |
1771 } | |
1772 | |
1773 UpdateStationVirtCoordDirty(st); | |
1774 UpdateStationAcceptance(st, false); | |
1775 RebuildStationLists(); | |
1776 InvalidateWindow(WC_STATION_LIST, st->owner); | |
1777 } | |
1778 | |
1779 return cost; | |
1780 } | |
1781 | |
1782 static int32 RemoveAirport(Station *st, uint32 flags) | |
1783 { | |
1784 TileIndex tile; | |
1785 int w,h; | |
1786 int32 cost; | |
1787 const AirportFTAClass* afc; | |
1788 | |
1789 if (_current_player != OWNER_WATER && !CheckOwnership(st->owner)) | |
1790 return CMD_ERROR; | |
1791 | |
1792 tile = st->airport_tile; | |
1793 | |
1794 afc = GetAirport(st->airport_type); | |
1795 w = afc->size_x; | |
1796 h = afc->size_y; | |
1797 | |
1798 cost = w * h * _price.remove_airport; | |
1799 | |
1800 BEGIN_TILE_LOOP(tile_cur, w, h, tile) { | |
1801 if (!EnsureNoVehicle(tile_cur)) return CMD_ERROR; | |
1802 | |
1803 if (flags & DC_EXEC) { | |
1804 DeleteAnimatedTile(tile_cur); | |
1805 DoClearSquare(tile_cur); | |
1806 } | |
1807 } END_TILE_LOOP(tile_cur, w,h,tile) | |
1808 | |
1809 if (flags & DC_EXEC) { | |
1810 uint i; | |
1811 | |
1812 for (i = 0; i < afc->nof_depots; ++i) { | |
1813 DeleteWindowById( | |
1814 WC_VEHICLE_DEPOT, tile + ToTileIndexDiff(afc->airport_depots[i]) | |
1815 ); | |
1816 } | |
1817 | |
1818 StationRect_AfterRemoveRect(st, tile, w, h); | |
1819 | |
1820 st->airport_tile = 0; | |
1821 st->facilities &= ~FACIL_AIRPORT; | |
1822 | |
1823 UpdateStationVirtCoordDirty(st); | |
1824 DeleteStationIfEmpty(st); | |
1825 } | |
1826 | |
1827 return cost; | |
1828 } | |
1829 | |
1830 /** Build a buoy. | |
1831 * @param tile tile where to place the bouy | |
1832 * @param p1 unused | |
1833 * @param p2 unused | |
1834 */ | |
1835 int32 CmdBuildBuoy(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) | |
1836 { | |
1837 Station *st; | |
1838 | |
1839 SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION); | |
1840 | |
1841 if (!IsClearWaterTile(tile) || tile == 0) return_cmd_error(STR_304B_SITE_UNSUITABLE); | |
1842 | |
1843 st = AllocateStation(); | |
1844 if (st == NULL) return CMD_ERROR; | |
1845 | |
1846 st->town = ClosestTownFromTile(tile, (uint)-1); | |
1847 st->sign.width_1 = 0; | |
1848 | |
1849 if (!GenerateStationName(st, tile, 4)) return CMD_ERROR; | |
1850 | |
1851 if (flags & DC_EXEC) { | |
1852 StationInitialize(st, tile); | |
1853 st->dock_tile = tile; | |
1854 st->facilities |= FACIL_DOCK; | |
1855 /* Buoys are marked in the Station struct by this flag. Yes, it is this | |
1856 * braindead.. */ | |
1857 st->had_vehicle_of_type |= HVOT_BUOY; | |
1858 st->owner = OWNER_NONE; | |
1859 | |
1860 st->build_date = _date; | |
1861 | |
1862 MakeBuoy(tile, st->index); | |
1863 | |
1864 UpdateStationVirtCoordDirty(st); | |
1865 UpdateStationAcceptance(st, false); | |
1866 RebuildStationLists(); | |
1867 InvalidateWindow(WC_STATION_LIST, st->owner); | |
1868 } | |
1869 | |
1870 return _price.build_dock; | |
1871 } | |
1872 | |
1873 /* Checks if any ship is servicing the buoy specified. Returns yes or no */ | |
1874 static bool CheckShipsOnBuoy(Station *st) | |
1875 { | |
1876 const Vehicle *v; | |
1877 FOR_ALL_VEHICLES(v) { | |
1878 if (v->type == VEH_Ship) { | |
1879 const Order *order; | |
1880 FOR_VEHICLE_ORDERS(v, order) { | |
1881 if (order->type == OT_GOTO_STATION && order->dest == st->index) { | |
1882 return true; | |
1883 } | |
1884 } | |
1885 } | |
1886 } | |
1887 return false; | |
1888 } | |
1889 | |
1890 static int32 RemoveBuoy(Station *st, uint32 flags) | |
1891 { | |
1892 TileIndex tile; | |
1893 | |
1894 /* XXX: strange stuff */ | |
1895 if (!IsValidPlayer(_current_player)) return_cmd_error(INVALID_STRING_ID); | |
1896 | |
1897 tile = st->dock_tile; | |
1898 | |
1899 if (CheckShipsOnBuoy(st)) return_cmd_error(STR_BUOY_IS_IN_USE); | |
1900 if (!EnsureNoVehicle(tile)) return CMD_ERROR; | |
1901 | |
1902 if (flags & DC_EXEC) { | |
1903 st->dock_tile = 0; | |
1904 /* Buoys are marked in the Station struct by this flag. Yes, it is this | |
1905 * braindead.. */ | |
1906 st->facilities &= ~FACIL_DOCK; | |
1907 st->had_vehicle_of_type &= ~HVOT_BUOY; | |
1908 | |
1909 MakeWater(tile); | |
1910 MarkTileDirtyByTile(tile); | |
1911 | |
1912 UpdateStationVirtCoordDirty(st); | |
1913 DeleteStationIfEmpty(st); | |
1914 } | |
1915 | |
1916 return _price.remove_truck_station; | |
1917 } | |
1918 | |
1919 static const TileIndexDiffC _dock_tileoffs_chkaround[] = { | |
1920 {-1, 0}, | |
1921 { 0, 0}, | |
1922 { 0, 0}, | |
1923 { 0, -1} | |
1924 }; | |
1925 static const byte _dock_w_chk[4] = { 2, 1, 2, 1 }; | |
1926 static const byte _dock_h_chk[4] = { 1, 2, 1, 2 }; | |
1927 | |
1928 /** Build a dock/haven. | |
1929 * @param tile tile where dock will be built | |
1930 * @param p1 unused | |
1931 * @param p2 unused | |
1932 */ | |
1933 int32 CmdBuildDock(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) | |
1934 { | |
1935 TileIndex tile_cur; | |
1936 DiagDirection direction; | |
1937 int32 cost; | |
1938 Station *st; | |
1939 | |
1940 SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION); | |
1941 | |
1942 switch (GetTileSlope(tile, NULL)) { | |
1943 case SLOPE_SW: direction = DIAGDIR_NE; break; | |
1944 case SLOPE_SE: direction = DIAGDIR_NW; break; | |
1945 case SLOPE_NW: direction = DIAGDIR_SE; break; | |
1946 case SLOPE_NE: direction = DIAGDIR_SW; break; | |
1947 default: return_cmd_error(STR_304B_SITE_UNSUITABLE); | |
1948 } | |
1949 | |
1950 if (!(flags & DC_NO_TOWN_RATING) && !CheckIfAuthorityAllows(tile)) return CMD_ERROR; | |
1951 | |
1952 if (!EnsureNoVehicle(tile)) return CMD_ERROR; | |
1953 | |
1954 cost = DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); | |
1955 if (CmdFailed(cost)) return CMD_ERROR; | |
1956 | |
1957 tile_cur = tile + TileOffsByDiagDir(direction); | |
1958 | |
1959 if (!EnsureNoVehicle(tile_cur)) return CMD_ERROR; | |
1960 | |
1961 if (!IsTileType(tile_cur, MP_WATER) || GetTileSlope(tile_cur, NULL) != SLOPE_FLAT) { | |
1962 return_cmd_error(STR_304B_SITE_UNSUITABLE); | |
1963 } | |
1964 | |
1965 cost = DoCommand(tile_cur, 0, 0, flags, CMD_LANDSCAPE_CLEAR); | |
1966 if (CmdFailed(cost)) return CMD_ERROR; | |
1967 | |
1968 tile_cur += TileOffsByDiagDir(direction); | |
1969 if (!IsTileType(tile_cur, MP_WATER) || GetTileSlope(tile_cur, NULL) != SLOPE_FLAT) { | |
1970 return_cmd_error(STR_304B_SITE_UNSUITABLE); | |
1971 } | |
1972 | |
1973 /* middle */ | |
1974 st = GetStationAround( | |
1975 tile + ToTileIndexDiff(_dock_tileoffs_chkaround[direction]), | |
1976 _dock_w_chk[direction], _dock_h_chk[direction], -1); | |
1977 if (st == CHECK_STATIONS_ERR) return CMD_ERROR; | |
1978 | |
1979 /* Find a station close to us */ | |
1980 if (st == NULL) { | |
1981 st = GetClosestStationFromTile(tile, 8, _current_player); | |
1982 if (st!=NULL && st->facilities) st = NULL; | |
1983 } | |
1984 | |
1985 if (st != NULL) { | |
1986 if (st->owner != OWNER_NONE && st->owner != _current_player) | |
1987 return_cmd_error(STR_3009_TOO_CLOSE_TO_ANOTHER_STATION); | |
1988 | |
1989 if (!StationRect_BeforeAddRect(st, tile, _dock_w_chk[direction], _dock_h_chk[direction], RECT_MODE_TEST)) return CMD_ERROR; | |
1990 | |
1991 if (st->dock_tile != 0) return_cmd_error(STR_304C_TOO_CLOSE_TO_ANOTHER_DOCK); | |
1992 } else { | |
1993 Town *t; | |
1994 | |
1995 st = AllocateStation(); | |
1996 if (st == NULL) return CMD_ERROR; | |
1997 | |
1998 st->town = t = ClosestTownFromTile(tile, (uint)-1); | |
1999 | |
2000 if (IsValidPlayer(_current_player) && (flags & DC_EXEC)) | |
2001 SETBIT(t->have_ratings, _current_player); | |
2002 | |
2003 st->sign.width_1 = 0; | |
2004 | |
2005 if (!GenerateStationName(st, tile, 3)) return CMD_ERROR; | |
2006 | |
2007 if (flags & DC_EXEC) StationInitialize(st, tile); | |
2008 } | |
2009 | |
2010 if (flags & DC_EXEC) { | |
2011 st->dock_tile = tile; | |
2012 if (!st->facilities) st->xy = tile; | |
2013 st->facilities |= FACIL_DOCK; | |
2014 st->owner = _current_player; | |
2015 | |
2016 st->build_date = _date; | |
2017 | |
2018 StationRect_BeforeAddRect(st, tile, _dock_w_chk[direction], _dock_h_chk[direction], RECT_MODE_TRY); | |
2019 | |
2020 MakeDock(tile, st->owner, st->index, direction); | |
2021 | |
2022 UpdateStationVirtCoordDirty(st); | |
2023 UpdateStationAcceptance(st, false); | |
2024 RebuildStationLists(); | |
2025 InvalidateWindow(WC_STATION_LIST, st->owner); | |
2026 } | |
2027 return _price.build_dock; | |
2028 } | |
2029 | |
2030 static int32 RemoveDock(Station *st, uint32 flags) | |
2031 { | |
2032 TileIndex tile1; | |
2033 TileIndex tile2; | |
2034 | |
2035 if (!CheckOwnership(st->owner)) return CMD_ERROR; | |
2036 | |
2037 tile1 = st->dock_tile; | |
2038 tile2 = tile1 + TileOffsByDiagDir(GetDockDirection(tile1)); | |
2039 | |
2040 if (!EnsureNoVehicle(tile1)) return CMD_ERROR; | |
2041 if (!EnsureNoVehicle(tile2)) return CMD_ERROR; | |
2042 | |
2043 if (flags & DC_EXEC) { | |
2044 DoClearSquare(tile1); | |
2045 MakeWater(tile2); | |
2046 | |
2047 StationRect_AfterRemoveTile(st, tile1); | |
2048 StationRect_AfterRemoveTile(st, tile2); | |
2049 | |
2050 MarkTileDirtyByTile(tile2); | |
2051 | |
2052 st->dock_tile = 0; | |
2053 st->facilities &= ~FACIL_DOCK; | |
2054 | |
2055 UpdateStationVirtCoordDirty(st); | |
2056 DeleteStationIfEmpty(st); | |
2057 } | |
2058 | |
2059 return _price.remove_dock; | |
2060 } | |
2061 | |
2062 #include "table/station_land.h" | |
2063 | |
2064 const DrawTileSprites *GetStationTileLayout(byte gfx) | |
2065 { | |
2066 return &_station_display_datas[gfx]; | |
2067 } | |
2068 | |
2069 static void DrawTile_Station(TileInfo *ti) | |
2070 { | |
2071 uint32 image; | |
2072 const DrawTileSeqStruct *dtss; | |
2073 const DrawTileSprites *t = NULL; | |
2074 RailType railtype = GetRailType(ti->tile); | |
2075 const RailtypeInfo *rti = GetRailTypeInfo(railtype); | |
2076 uint32 relocation = 0; | |
2077 const Station *st = NULL; | |
2078 const StationSpec *statspec = NULL; | |
2079 PlayerID owner = GetTileOwner(ti->tile); | |
2080 uint32 palette; | |
2081 | |
2082 if (IsValidPlayer(owner)) { | |
2083 palette = PLAYER_SPRITE_COLOR(owner); | |
2084 } else { | |
2085 // Some stations are not owner by a player, namely oil rigs | |
2086 palette = PALETTE_TO_GREY; | |
2087 } | |
2088 | |
2089 // don't show foundation for docks | |
2090 if (ti->tileh != SLOPE_FLAT && !IsDock(ti->tile)) | |
2091 DrawFoundation(ti, ti->tileh); | |
2092 | |
2093 if (IsCustomStationSpecIndex(ti->tile)) { | |
2094 // look for customization | |
2095 st = GetStationByTile(ti->tile); | |
2096 statspec = st->speclist[GetCustomStationSpecIndex(ti->tile)].spec; | |
2097 | |
2098 //debug("Cust-o-mized %p", statspec); | |
2099 | |
2100 if (statspec != NULL) { | |
2101 uint tile = GetStationGfx(ti->tile); | |
2102 | |
2103 relocation = GetCustomStationRelocation(statspec, st, ti->tile); | |
2104 | |
2105 if (HASBIT(statspec->callbackmask, CBM_CUSTOM_LAYOUT)) { | |
2106 uint16 callback = GetStationCallback(CBID_STATION_SPRITE_LAYOUT, 0, 0, statspec, st, ti->tile); | |
2107 if (callback != CALLBACK_FAILED) tile = (callback & ~1) + GetRailStationAxis(ti->tile); | |
2108 } | |
2109 | |
2110 /* Ensure the chosen tile layout is valid for this custom station */ | |
2111 if (statspec->renderdata != NULL) { | |
2112 t = &statspec->renderdata[tile < statspec->tiles ? tile : GetRailStationAxis(ti->tile)]; | |
2113 } | |
2114 } | |
2115 } | |
2116 | |
2117 if (t == NULL || t->seq == NULL) t = &_station_display_datas[GetStationGfx(ti->tile)]; | |
2118 | |
2119 image = t->ground_sprite; | |
2120 if (HASBIT(image, 31)) { | |
2121 CLRBIT(image, 31); | |
2122 image += GetCustomStationGroundRelocation(statspec, st, ti->tile); | |
2123 image += rti->custom_ground_offset; | |
2124 } else { | |
2125 image += rti->total_offset; | |
2126 } | |
2127 if (image & PALETTE_MODIFIER_COLOR) image |= palette; | |
2128 | |
2129 // station_land array has been increased from 82 elements to 114 | |
2130 // but this is something else. If AI builds station with 114 it looks all weird | |
2131 DrawGroundSprite(image); | |
2132 | |
2133 if (GetRailType(ti->tile) == RAILTYPE_ELECTRIC && IsStationTileElectrifiable(ti->tile)) DrawCatenary(ti); | |
2134 | |
2135 foreach_draw_tile_seq(dtss, t->seq) { | |
2136 image = dtss->image; | |
2137 if (HASBIT(image, 30)) { | |
2138 CLRBIT(image, 30); | |
2139 image += rti->total_offset; | |
2140 } else { | |
2141 image += relocation; | |
2142 } | |
2143 | |
2144 if (_display_opt & DO_TRANS_BUILDINGS) { | |
2145 MAKE_TRANSPARENT(image); | |
2146 } else if (image & PALETTE_MODIFIER_COLOR) { | |
2147 image |= palette; | |
2148 } | |
2149 | |
2150 if ((byte)dtss->delta_z != 0x80) { | |
2151 AddSortableSpriteToDraw( | |
2152 image, | |
2153 ti->x + dtss->delta_x, ti->y + dtss->delta_y, | |
2154 dtss->size_x, dtss->size_y, | |
2155 dtss->size_z, ti->z + dtss->delta_z | |
2156 ); | |
2157 } else { | |
2158 AddChildSpriteScreen(image, dtss->delta_x, dtss->delta_y); | |
2159 } | |
2160 } | |
2161 } | |
2162 | |
2163 void StationPickerDrawSprite(int x, int y, RailType railtype, int image) | |
2164 { | |
2165 uint32 ormod, img; | |
2166 const DrawTileSeqStruct *dtss; | |
2167 const DrawTileSprites *t; | |
2168 const RailtypeInfo *rti = GetRailTypeInfo(railtype); | |
2169 | |
2170 ormod = PLAYER_SPRITE_COLOR(_local_player); | |
2171 | |
2172 t = &_station_display_datas[image]; | |
2173 | |
2174 img = t->ground_sprite; | |
2175 if (img & PALETTE_MODIFIER_COLOR) img |= ormod; | |
2176 DrawSprite(img + rti->total_offset, x, y); | |
2177 | |
2178 foreach_draw_tile_seq(dtss, t->seq) { | |
2179 Point pt = RemapCoords(dtss->delta_x, dtss->delta_y, dtss->delta_z); | |
2180 DrawSprite((dtss->image | ormod) + rti->total_offset, x + pt.x, y + pt.y); | |
2181 } | |
2182 } | |
2183 | |
2184 static uint GetSlopeZ_Station(TileIndex tile, uint x, uint y) | |
2185 { | |
2186 return GetTileMaxZ(tile); | |
2187 } | |
2188 | |
2189 static Slope GetSlopeTileh_Station(TileIndex tile, Slope tileh) | |
2190 { | |
2191 return SLOPE_FLAT; | |
2192 } | |
2193 | |
2194 static void GetAcceptedCargo_Station(TileIndex tile, AcceptedCargo ac) | |
2195 { | |
2196 /* not used */ | |
2197 } | |
2198 | |
2199 static void GetTileDesc_Station(TileIndex tile, TileDesc *td) | |
2200 { | |
2201 StringID str; | |
2202 | |
2203 td->owner = GetTileOwner(tile); | |
2204 td->build_date = GetStationByTile(tile)->build_date; | |
2205 | |
2206 switch (GetStationType(tile)) { | |
2207 default: NOT_REACHED(); | |
2208 case STATION_RAIL: str = STR_305E_RAILROAD_STATION; break; | |
2209 case STATION_AIRPORT: | |
2210 str = (IsHangar(tile) ? STR_305F_AIRCRAFT_HANGAR : STR_3060_AIRPORT); | |
2211 break; | |
2212 case STATION_TRUCK: str = STR_3061_TRUCK_LOADING_AREA; break; | |
2213 case STATION_BUS: str = STR_3062_BUS_STATION; break; | |
2214 case STATION_OILRIG: str = STR_4807_OIL_RIG; break; | |
2215 case STATION_DOCK: str = STR_3063_SHIP_DOCK; break; | |
2216 case STATION_BUOY: str = STR_3069_BUOY; break; | |
2217 } | |
2218 td->str = str; | |
2219 } | |
2220 | |
2221 | |
2222 static uint32 GetTileTrackStatus_Station(TileIndex tile, TransportType mode) | |
2223 { | |
2224 switch (mode) { | |
2225 case TRANSPORT_RAIL: | |
2226 if (IsRailwayStation(tile) && !IsStationTileBlocked(tile)) { | |
2227 return TrackToTrackBits(GetRailStationTrack(tile)) * 0x101; | |
2228 } | |
2229 break; | |
2230 | |
2231 case TRANSPORT_WATER: | |
2232 // buoy is coded as a station, it is always on open water | |
2233 if (IsBuoy_(tile)) return TRACK_BIT_ALL * 0x101; | |
2234 break; | |
2235 | |
2236 case TRANSPORT_ROAD: | |
2237 if (IsRoadStopTile(tile)) { | |
2238 return AxisToTrackBits(DiagDirToAxis(GetRoadStopDir(tile))) * 0x101; | |
2239 } | |
2240 break; | |
2241 | |
2242 default: | |
2243 break; | |
2244 } | |
2245 | |
2246 return 0; | |
2247 } | |
2248 | |
2249 | |
2250 static void TileLoop_Station(TileIndex tile) | |
2251 { | |
2252 // FIXME -- GetTileTrackStatus_Station -> animated stationtiles | |
2253 // hardcoded.....not good | |
2254 switch (GetStationGfx(tile)) { | |
2255 case GFX_RADAR_LARGE_FIRST: | |
2256 case GFX_WINDSACK_FIRST : // for small airport | |
2257 case GFX_RADAR_INTERNATIONAL_FIRST: | |
2258 case GFX_RADAR_METROPOLITAN_FIRST: | |
2259 case GFX_RADAR_DISTRICTWE_FIRST: // radar district W-E airport | |
2260 case GFX_WINDSACK_INTERCON_FIRST : // for intercontinental airport | |
2261 AddAnimatedTile(tile); | |
2262 break; | |
2263 | |
2264 case GFX_OILRIG_BASE: //(station part) | |
2265 case GFX_BUOY_BASE: | |
2266 TileLoop_Water(tile); | |
2267 break; | |
2268 | |
2269 default: break; | |
2270 } | |
2271 } | |
2272 | |
2273 | |
2274 static void AnimateTile_Station(TileIndex tile) | |
2275 { | |
2276 typedef struct AnimData { | |
2277 StationGfx from; // first sprite | |
2278 StationGfx to; // last sprite | |
2279 byte delay; | |
2280 } AnimData; | |
2281 | |
2282 static const AnimData data[] = { | |
2283 { GFX_RADAR_LARGE_FIRST, GFX_RADAR_LARGE_LAST, 3 }, | |
2284 { GFX_WINDSACK_FIRST, GFX_WINDSACK_LAST, 1 }, | |
2285 { GFX_RADAR_INTERNATIONAL_FIRST, GFX_RADAR_INTERNATIONAL_LAST, 3 }, | |
2286 { GFX_RADAR_METROPOLITAN_FIRST, GFX_RADAR_METROPOLITAN_LAST, 3 }, | |
2287 { GFX_RADAR_DISTRICTWE_FIRST, GFX_RADAR_DISTRICTWE_LAST, 3 }, | |
2288 { GFX_WINDSACK_INTERCON_FIRST, GFX_WINDSACK_INTERCON_LAST, 1 } | |
2289 }; | |
2290 | |
2291 StationGfx gfx = GetStationGfx(tile); | |
2292 const AnimData* i; | |
2293 | |
2294 for (i = data; i != endof(data); i++) { | |
2295 if (i->from <= gfx && gfx <= i->to) { | |
2296 if ((_tick_counter & i->delay) == 0) { | |
2297 SetStationGfx(tile, gfx < i->to ? gfx + 1 : i->from); | |
2298 MarkTileDirtyByTile(tile); | |
2299 } | |
2300 break; | |
2301 } | |
2302 } | |
2303 } | |
2304 | |
2305 | |
2306 static void ClickTile_Station(TileIndex tile) | |
2307 { | |
2308 if (IsHangar(tile)) { | |
2309 ShowDepotWindow(tile, VEH_Aircraft); | |
2310 } else { | |
2311 ShowStationViewWindow(GetStationIndex(tile)); | |
2312 } | |
2313 } | |
2314 | |
2315 static const byte _enter_station_speedtable[12] = { | |
2316 215, 195, 175, 155, 135, 115, 95, 75, 55, 35, 15, 0 | |
2317 }; | |
2318 | |
2319 static uint32 VehicleEnter_Station(Vehicle *v, TileIndex tile, int x, int y) | |
2320 { | |
2321 if (v->type == VEH_Train) { | |
2322 if (IsRailwayStation(tile) && IsFrontEngine(v) && | |
2323 !IsCompatibleTrainStationTile(tile + TileOffsByDiagDir(DirToDiagDir(v->direction)), tile)) { | |
2324 StationID station_id = GetStationIndex(tile); | |
2325 | |
2326 if ((!(v->current_order.flags & OF_NON_STOP) && !_patches.new_nonstop) || | |
2327 (v->current_order.type == OT_GOTO_STATION && v->current_order.dest == station_id)) { | |
2328 if (!(_patches.new_nonstop && v->current_order.flags & OF_NON_STOP) && | |
2329 v->current_order.type != OT_LEAVESTATION && | |
2330 v->last_station_visited != station_id) { | |
2331 DiagDirection dir = DirToDiagDir(v->direction); | |
2332 | |
2333 x &= 0xF; | |
2334 y &= 0xF; | |
2335 | |
2336 if (DiagDirToAxis(dir) != AXIS_X) intswap(x, y); | |
2337 if (y == TILE_SIZE / 2) { | |
2338 if (dir != DIAGDIR_SE && dir != DIAGDIR_SW) x = TILE_SIZE - 1 - x; | |
2339 if (x == 12) return 2 | (station_id << 8); /* enter station */ | |
2340 if (x < 12) { | |
2341 uint16 spd; | |
2342 | |
2343 v->vehstatus |= VS_TRAIN_SLOWING; | |
2344 spd = _enter_station_speedtable[x]; | |
2345 if (spd < v->cur_speed) v->cur_speed = spd; | |
2346 } | |
2347 } | |
2348 } | |
2349 } | |
2350 } | |
2351 } else if (v->type == VEH_Road) { | |
2352 if (v->u.road.state < 16 && !HASBIT(v->u.road.state, 2) && v->u.road.frame == 0) { | |
2353 if (IsRoadStop(tile)) { | |
2354 /* Attempt to allocate a parking bay in a road stop */ | |
2355 RoadStop *rs = GetRoadStopByTile(tile, GetRoadStopType(tile)); | |
2356 | |
2357 /* rs->status bits 0 and 1 describe current the two parking spots. | |
2358 * 0 means occupied, 1 means free. */ | |
2359 | |
2360 // Check if station is busy or if there are no free bays. | |
2361 if (HASBIT(rs->status, 7) || GB(rs->status, 0, 2) == 0) | |
2362 return 8; | |
2363 | |
2364 v->u.road.state += 32; | |
2365 | |
2366 // if the first bay is free, allocate that, else the second bay must be free. | |
2367 if (HASBIT(rs->status, 0)) { | |
2368 CLRBIT(rs->status, 0); | |
2369 } else { | |
2370 CLRBIT(rs->status, 1); | |
2371 v->u.road.state += 2; | |
2372 } | |
2373 | |
2374 // mark the station as busy | |
2375 SETBIT(rs->status, 7); | |
2376 } | |
2377 } | |
2378 } | |
2379 | |
2380 return 0; | |
2381 } | |
2382 | |
2383 /** | |
2384 * Cleanup a RoadStop. Make sure no vehicles try to go to this roadstop. | |
2385 */ | |
2386 void DestroyRoadStop(RoadStop* rs) | |
2387 { | |
2388 Vehicle *v; | |
2389 | |
2390 /* Clear the slot assignment of all vehicles heading for this road stop */ | |
2391 if (rs->num_vehicles != 0) { | |
2392 FOR_ALL_VEHICLES(v) { | |
2393 if (v->type == VEH_Road && v->u.road.slot == rs) { | |
2394 ClearSlot(v); | |
2395 } | |
2396 } | |
2397 } | |
2398 assert(rs->num_vehicles == 0); | |
2399 | |
2400 if (rs->prev != NULL) rs->prev->next = rs->next; | |
2401 if (rs->next != NULL) rs->next->prev = rs->prev; | |
2402 } | |
2403 | |
2404 /** | |
2405 * Clean up a station by clearing vehicle orders and invalidating windows. | |
2406 * Aircraft-Hangar orders need special treatment here, as the hangars are | |
2407 * actually part of a station (tiletype is STATION), but the order type | |
2408 * is OT_GOTO_DEPOT. | |
2409 * @param st Station to be deleted | |
2410 */ | |
2411 void DestroyStation(Station *st) | |
2412 { | |
2413 StationID index; | |
2414 | |
2415 index = st->index; | |
2416 | |
2417 DeleteName(st->string_id); | |
2418 MarkStationDirty(st); | |
2419 RebuildStationLists(); | |
2420 InvalidateWindowClasses(WC_STATION_LIST); | |
2421 | |
2422 DeleteWindowById(WC_STATION_VIEW, index); | |
2423 | |
2424 /* Now delete all orders that go to the station */ | |
2425 RemoveOrderFromAllVehicles(OT_GOTO_STATION, index); | |
2426 | |
2427 //Subsidies need removal as well | |
2428 DeleteSubsidyWithStation(index); | |
2429 | |
2430 free(st->speclist); | |
2431 } | |
2432 | |
2433 void DeleteAllPlayerStations(void) | |
2434 { | |
2435 Station *st; | |
2436 | |
2437 FOR_ALL_STATIONS(st) { | |
2438 if (IsValidPlayer(st->owner)) DeleteStation(st); | |
2439 } | |
2440 } | |
2441 | |
2442 /* this function is called for one station each tick */ | |
2443 static void StationHandleBigTick(Station *st) | |
2444 { | |
2445 UpdateStationAcceptance(st, true); | |
2446 | |
2447 if (st->facilities == 0 && ++st->delete_ctr >= 8) DeleteStation(st); | |
2448 | |
2449 } | |
2450 | |
2451 static inline void byte_inc_sat(byte *p) { byte b = *p + 1; if (b != 0) *p = b; } | |
2452 | |
2453 static void UpdateStationRating(Station *st) | |
2454 { | |
2455 GoodsEntry *ge; | |
2456 int rating; | |
2457 StationID index; | |
2458 int waiting; | |
2459 bool waiting_changed = false; | |
2460 | |
2461 byte_inc_sat(&st->time_since_load); | |
2462 byte_inc_sat(&st->time_since_unload); | |
2463 | |
2464 ge = st->goods; | |
2465 do { | |
2466 if (ge->enroute_from != INVALID_STATION) { | |
2467 byte_inc_sat(&ge->enroute_time); | |
2468 byte_inc_sat(&ge->days_since_pickup); | |
2469 | |
2470 rating = 0; | |
2471 | |
2472 { | |
2473 int b = ge->last_speed; | |
2474 if ((b-=85) >= 0) | |
2475 rating += b >> 2; | |
2476 } | |
2477 | |
2478 { | |
2479 byte age = ge->last_age; | |
2480 (age >= 3) || | |
2481 (rating += 10, age >= 2) || | |
2482 (rating += 10, age >= 1) || | |
2483 (rating += 13, true); | |
2484 } | |
2485 | |
2486 if (IsValidPlayer(st->owner) && HASBIT(st->town->statues, st->owner)) rating += 26; | |
2487 | |
2488 { | |
2489 byte days = ge->days_since_pickup; | |
2490 if (st->last_vehicle_type == VEH_Ship) | |
2491 days >>= 2; | |
2492 (days > 21) || | |
2493 (rating += 25, days > 12) || | |
2494 (rating += 25, days > 6) || | |
2495 (rating += 45, days > 3) || | |
2496 (rating += 35, true); | |
2497 } | |
2498 | |
2499 { | |
2500 waiting = GB(ge->waiting_acceptance, 0, 12); | |
2501 (rating -= 90, waiting > 1500) || | |
2502 (rating += 55, waiting > 1000) || | |
2503 (rating += 35, waiting > 600) || | |
2504 (rating += 10, waiting > 300) || | |
2505 (rating += 20, waiting > 100) || | |
2506 (rating += 10, true); | |
2507 } | |
2508 | |
2509 { | |
2510 int or = ge->rating; // old rating | |
2511 | |
2512 // only modify rating in steps of -2, -1, 0, 1 or 2 | |
2513 ge->rating = rating = or + clamp(clamp(rating, 0, 255) - or, -2, 2); | |
2514 | |
2515 // if rating is <= 64 and more than 200 items waiting, remove some random amount of goods from the station | |
2516 if (rating <= 64 && waiting >= 200) { | |
2517 int dec = Random() & 0x1F; | |
2518 if (waiting < 400) dec &= 7; | |
2519 waiting -= dec + 1; | |
2520 waiting_changed = true; | |
2521 } | |
2522 | |
2523 // if rating is <= 127 and there are any items waiting, maybe remove some goods. | |
2524 if (rating <= 127 && waiting != 0) { | |
2525 uint32 r = Random(); | |
2526 if ( (uint)rating <= (r & 0x7F) ) { | |
2527 waiting = max(waiting - ((r >> 8)&3) - 1, 0); | |
2528 waiting_changed = true; | |
2529 } | |
2530 } | |
2531 | |
2532 if (waiting_changed) SB(ge->waiting_acceptance, 0, 12, waiting); | |
2533 } | |
2534 } | |
2535 } while (++ge != endof(st->goods)); | |
2536 | |
2537 index = st->index; | |
2538 | |
2539 if (waiting_changed) { | |
2540 InvalidateWindow(WC_STATION_VIEW, index); | |
2541 } else { | |
2542 InvalidateWindowWidget(WC_STATION_VIEW, index, 5); | |
2543 } | |
2544 } | |
2545 | |
2546 /* called for every station each tick */ | |
2547 static void StationHandleSmallTick(Station *st) | |
2548 { | |
2549 byte b; | |
2550 | |
2551 if (st->facilities == 0) return; | |
2552 | |
2553 b = st->delete_ctr + 1; | |
2554 if (b >= 185) b = 0; | |
2555 st->delete_ctr = b; | |
2556 | |
2557 if (b == 0) UpdateStationRating(st); | |
2558 } | |
2559 | |
2560 void OnTick_Station(void) | |
2561 { | |
2562 uint i; | |
2563 Station *st; | |
2564 | |
2565 if (_game_mode == GM_EDITOR) return; | |
2566 | |
2567 i = _station_tick_ctr; | |
2568 if (++_station_tick_ctr > GetMaxStationIndex()) _station_tick_ctr = 0; | |
2569 | |
2570 if (IsValidStationID(i)) StationHandleBigTick(GetStation(i)); | |
2571 | |
2572 FOR_ALL_STATIONS(st) { | |
2573 StationHandleSmallTick(st); | |
2574 } | |
2575 } | |
2576 | |
2577 void StationMonthlyLoop(void) | |
2578 { | |
2579 } | |
2580 | |
2581 | |
2582 void ModifyStationRatingAround(TileIndex tile, PlayerID owner, int amount, uint radius) | |
2583 { | |
2584 Station *st; | |
2585 | |
2586 FOR_ALL_STATIONS(st) { | |
2587 if (st->owner == owner && | |
2588 DistanceManhattan(tile, st->xy) <= radius) { | |
2589 uint i; | |
2590 | |
2591 for (i = 0; i != NUM_CARGO; i++) { | |
2592 GoodsEntry* ge = &st->goods[i]; | |
2593 | |
2594 if (ge->enroute_from != INVALID_STATION) { | |
2595 ge->rating = clamp(ge->rating + amount, 0, 255); | |
2596 } | |
2597 } | |
2598 } | |
2599 } | |
2600 } | |
2601 | |
2602 static void UpdateStationWaiting(Station *st, int type, uint amount) | |
2603 { | |
2604 SB(st->goods[type].waiting_acceptance, 0, 12, | |
2605 min(0xFFF, GB(st->goods[type].waiting_acceptance, 0, 12) + amount) | |
2606 ); | |
2607 | |
2608 st->goods[type].enroute_time = 0; | |
2609 st->goods[type].enroute_from = st->index; | |
2610 InvalidateWindow(WC_STATION_VIEW, st->index); | |
2611 MarkStationTilesDirty(st); | |
2612 } | |
2613 | |
2614 /** Rename a station | |
2615 * @param tile unused | |
2616 * @param p1 station ID that is to be renamed | |
2617 * @param p2 unused | |
2618 */ | |
2619 int32 CmdRenameStation(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) | |
2620 { | |
2621 StringID str; | |
2622 Station *st; | |
2623 | |
2624 if (!IsValidStationID(p1) || _cmd_text[0] == '\0') return CMD_ERROR; | |
2625 st = GetStation(p1); | |
2626 | |
2627 if (!CheckOwnership(st->owner)) return CMD_ERROR; | |
2628 | |
2629 str = AllocateNameUnique(_cmd_text, 6); | |
2630 if (str == 0) return CMD_ERROR; | |
2631 | |
2632 if (flags & DC_EXEC) { | |
2633 StringID old_str = st->string_id; | |
2634 | |
2635 st->string_id = str; | |
2636 UpdateStationVirtCoord(st); | |
2637 DeleteName(old_str); | |
2638 ResortStationLists(); | |
2639 MarkWholeScreenDirty(); | |
2640 } else { | |
2641 DeleteName(str); | |
2642 } | |
2643 | |
2644 return 0; | |
2645 } | |
2646 | |
2647 | |
2648 uint MoveGoodsToStation(TileIndex tile, int w, int h, int type, uint amount) | |
2649 { | |
2650 Station* around[8]; | |
2651 uint i; | |
2652 uint moved; | |
2653 uint best_rating, best_rating2; | |
2654 Station *st1, *st2; | |
2655 uint t; | |
2656 int rad = 0; | |
2657 int w_prod; //width and height of the "producer" of the cargo | |
2658 int h_prod; | |
2659 int max_rad; | |
2660 | |
2661 for (i = 0; i < lengthof(around); i++) around[i] = NULL; | |
2662 | |
2663 if (_patches.modified_catchment) { | |
2664 w_prod = w; | |
2665 h_prod = h; | |
2666 w += 16; | |
2667 h += 16; | |
2668 max_rad = 8; | |
2669 } else { | |
2670 w_prod = 0; | |
2671 h_prod = 0; | |
2672 w += 8; | |
2673 h += 8; | |
2674 max_rad = 4; | |
2675 } | |
2676 | |
2677 BEGIN_TILE_LOOP(cur_tile, w, h, tile - TileDiffXY(max_rad, max_rad)) | |
2678 Station* st; | |
2679 | |
2680 cur_tile = TILE_MASK(cur_tile); | |
2681 if (!IsTileType(cur_tile, MP_STATION)) continue; | |
2682 | |
2683 st = GetStationByTile(cur_tile); | |
2684 | |
2685 for (i = 0; i != lengthof(around); i++) { | |
2686 if (around[i] == NULL) { | |
2687 if (!IsBuoy(st) && | |
2688 (st->town->exclusive_counter == 0 || st->town->exclusivity == st->owner) && // check exclusive transport rights | |
2689 st->goods[type].rating != 0 && | |
2690 (!_patches.selectgoods || st->goods[type].last_speed > 0) && // if last_speed is 0, no vehicle has been there. | |
2691 ((st->facilities & ~FACIL_BUS_STOP) != 0 || type == CT_PASSENGERS) && // if we have other fac. than a bus stop, or the cargo is passengers | |
2692 ((st->facilities & ~FACIL_TRUCK_STOP) != 0 || type != CT_PASSENGERS)) { // if we have other fac. than a cargo bay or the cargo is not passengers | |
2693 int x_dist; | |
2694 int y_dist; | |
2695 | |
2696 if (_patches.modified_catchment) { | |
2697 // min and max coordinates of the producer relative | |
2698 const int x_min_prod = 9; | |
2699 const int x_max_prod = 8 + w_prod; | |
2700 const int y_min_prod = 9; | |
2701 const int y_max_prod = 8 + h_prod; | |
2702 | |
2703 rad = FindCatchmentRadius(st); | |
2704 | |
2705 x_dist = min(w_cur - x_min_prod, x_max_prod - w_cur); | |
2706 if (w_cur < x_min_prod) { | |
2707 x_dist = x_min_prod - w_cur; | |
2708 } else if (w_cur > x_max_prod) { | |
2709 x_dist = w_cur - x_max_prod; | |
2710 } | |
2711 | |
2712 y_dist = min(h_cur - y_min_prod, y_max_prod - h_cur); | |
2713 if (h_cur < y_min_prod) { | |
2714 y_dist = y_min_prod - h_cur; | |
2715 } else if (h_cur > y_max_prod) { | |
2716 y_dist = h_cur - y_max_prod; | |
2717 } | |
2718 } else { | |
2719 x_dist = 0; | |
2720 y_dist = 0; | |
2721 } | |
2722 | |
2723 if (x_dist <= rad && y_dist <= rad) around[i] = st; | |
2724 } | |
2725 break; | |
2726 } else if (around[i] == st) { | |
2727 break; | |
2728 } | |
2729 } | |
2730 END_TILE_LOOP(cur_tile, w, h, tile - TileDiffXY(max_rad, max_rad)) | |
2731 | |
2732 /* no stations around at all? */ | |
2733 if (around[0] == NULL) return 0; | |
2734 | |
2735 if (around[1] == NULL) { | |
2736 /* only one station around */ | |
2737 moved = (amount * around[0]->goods[type].rating >> 8) + 1; | |
2738 UpdateStationWaiting(around[0], type, moved); | |
2739 return moved; | |
2740 } | |
2741 | |
2742 /* several stations around, find the two with the highest rating */ | |
2743 st2 = st1 = NULL; | |
2744 best_rating = best_rating2 = 0; | |
2745 | |
2746 for (i = 0; i != lengthof(around) && around[i] != NULL; i++) { | |
2747 if (around[i]->goods[type].rating >= best_rating) { | |
2748 best_rating2 = best_rating; | |
2749 st2 = st1; | |
2750 | |
2751 best_rating = around[i]->goods[type].rating; | |
2752 st1 = around[i]; | |
2753 } else if (around[i]->goods[type].rating >= best_rating2) { | |
2754 best_rating2 = around[i]->goods[type].rating; | |
2755 st2 = around[i]; | |
2756 } | |
2757 } | |
2758 | |
2759 assert(st1 != NULL); | |
2760 assert(st2 != NULL); | |
2761 assert(best_rating != 0 || best_rating2 != 0); | |
2762 | |
2763 /* the 2nd highest one gets a penalty */ | |
2764 best_rating2 >>= 1; | |
2765 | |
2766 /* amount given to station 1 */ | |
2767 t = (best_rating * (amount + 1)) / (best_rating + best_rating2); | |
2768 | |
2769 moved = 0; | |
2770 if (t != 0) { | |
2771 moved = t * best_rating / 256 + 1; | |
2772 amount -= t; | |
2773 UpdateStationWaiting(st1, type, moved); | |
2774 } | |
2775 | |
2776 if (amount != 0) { | |
2777 amount = amount * best_rating2 / 256 + 1; | |
2778 moved += amount; | |
2779 UpdateStationWaiting(st2, type, amount); | |
2780 } | |
2781 | |
2782 return moved; | |
2783 } | |
2784 | |
2785 void BuildOilRig(TileIndex tile) | |
2786 { | |
2787 uint j; | |
2788 Station *st = AllocateStation(); | |
2789 | |
2790 if (st == NULL) { | |
2791 DEBUG(misc, 0, "Can't allocate station for oilrig at 0x%X, reverting to oilrig only", tile); | |
2792 return; | |
2793 } | |
2794 if (!GenerateStationName(st, tile, 2)) { | |
2795 DEBUG(misc, 0, "Can't allocate station-name for oilrig at 0x%X, reverting to oilrig only", tile); | |
2796 return; | |
2797 } | |
2798 | |
2799 st->town = ClosestTownFromTile(tile, (uint)-1); | |
2800 st->sign.width_1 = 0; | |
2801 | |
2802 MakeOilrig(tile, st->index); | |
2803 | |
2804 st->owner = OWNER_NONE; | |
2805 st->airport_flags = 0; | |
2806 st->airport_type = AT_OILRIG; | |
2807 st->xy = tile; | |
2808 st->bus_stops = NULL; | |
2809 st->truck_stops = NULL; | |
2810 st->airport_tile = tile; | |
2811 st->dock_tile = tile; | |
2812 st->train_tile = 0; | |
2813 st->had_vehicle_of_type = 0; | |
2814 st->time_since_load = 255; | |
2815 st->time_since_unload = 255; | |
2816 st->delete_ctr = 0; | |
2817 st->last_vehicle_type = VEH_Invalid; | |
2818 st->facilities = FACIL_AIRPORT | FACIL_DOCK; | |
2819 st->build_date = _date; | |
2820 | |
2821 for (j = 0; j != NUM_CARGO; j++) { | |
2822 st->goods[j].waiting_acceptance = 0; | |
2823 st->goods[j].days_since_pickup = 0; | |
2824 st->goods[j].enroute_from = INVALID_STATION; | |
2825 st->goods[j].rating = 175; | |
2826 st->goods[j].last_speed = 0; | |
2827 st->goods[j].last_age = 255; | |
2828 } | |
2829 | |
2830 UpdateStationVirtCoordDirty(st); | |
2831 UpdateStationAcceptance(st, false); | |
2832 } | |
2833 | |
2834 void DeleteOilRig(TileIndex tile) | |
2835 { | |
2836 Station* st = GetStationByTile(tile); | |
2837 | |
2838 DoClearSquare(tile); | |
2839 | |
2840 st->dock_tile = 0; | |
2841 st->airport_tile = 0; | |
2842 st->facilities &= ~(FACIL_AIRPORT | FACIL_DOCK); | |
2843 st->airport_flags = 0; | |
2844 UpdateStationVirtCoordDirty(st); | |
2845 DeleteStation(st); | |
2846 } | |
2847 | |
2848 static void ChangeTileOwner_Station(TileIndex tile, PlayerID old_player, PlayerID new_player) | |
2849 { | |
2850 if (!IsTileOwner(tile, old_player)) return; | |
2851 | |
2852 if (new_player != PLAYER_SPECTATOR) { | |
2853 Station* st = GetStationByTile(tile); | |
2854 | |
2855 SetTileOwner(tile, new_player); | |
2856 st->owner = new_player; | |
2857 RebuildStationLists(); | |
2858 InvalidateWindowClasses(WC_STATION_LIST); | |
2859 } else { | |
2860 DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); | |
2861 } | |
2862 } | |
2863 | |
2864 static int32 ClearTile_Station(TileIndex tile, byte flags) | |
2865 { | |
2866 Station *st; | |
2867 | |
2868 if (flags & DC_AUTO) { | |
2869 switch (GetStationType(tile)) { | |
2870 case STATION_RAIL: return_cmd_error(STR_300B_MUST_DEMOLISH_RAILROAD); | |
2871 case STATION_AIRPORT: return_cmd_error(STR_300E_MUST_DEMOLISH_AIRPORT_FIRST); | |
2872 case STATION_TRUCK: return_cmd_error(STR_3047_MUST_DEMOLISH_TRUCK_STATION); | |
2873 case STATION_BUS: return_cmd_error(STR_3046_MUST_DEMOLISH_BUS_STATION); | |
2874 case STATION_BUOY: return_cmd_error(STR_306A_BUOY_IN_THE_WAY); | |
2875 case STATION_DOCK: return_cmd_error(STR_304D_MUST_DEMOLISH_DOCK_FIRST); | |
2876 case STATION_OILRIG: | |
2877 SetDParam(0, STR_4807_OIL_RIG); | |
2878 return_cmd_error(STR_4800_IN_THE_WAY); | |
2879 } | |
2880 } | |
2881 | |
2882 st = GetStationByTile(tile); | |
2883 | |
2884 switch (GetStationType(tile)) { | |
2885 case STATION_RAIL: return RemoveRailroadStation(st, tile, flags); | |
2886 case STATION_AIRPORT: return RemoveAirport(st, flags); | |
2887 case STATION_TRUCK: | |
2888 case STATION_BUS: return RemoveRoadStop(st, flags, tile); | |
2889 case STATION_BUOY: return RemoveBuoy(st, flags); | |
2890 case STATION_DOCK: return RemoveDock(st, flags); | |
2891 default: break; | |
2892 } | |
2893 | |
2894 return CMD_ERROR; | |
2895 } | |
2896 | |
2897 void InitializeStations(void) | |
2898 { | |
2899 /* Clean the station pool and create 1 block in it */ | |
2900 CleanPool(&_Station_pool); | |
2901 AddBlockToPool(&_Station_pool); | |
2902 | |
2903 /* Clean the roadstop pool and create 1 block in it */ | |
2904 CleanPool(&_RoadStop_pool); | |
2905 AddBlockToPool(&_RoadStop_pool); | |
2906 | |
2907 _station_tick_ctr = 0; | |
2908 | |
2909 } | |
2910 | |
2911 | |
2912 void AfterLoadStations(void) | |
2913 { | |
2914 Station *st; | |
2915 uint i; | |
2916 TileIndex tile; | |
2917 | |
2918 /* Update the speclists of all stations to point to the currently loaded custom stations. */ | |
2919 FOR_ALL_STATIONS(st) { | |
2920 for (i = 0; i < st->num_specs; i++) { | |
2921 if (st->speclist[i].grfid == 0) continue; | |
2922 | |
2923 st->speclist[i].spec = GetCustomStationSpecByGrf(st->speclist[i].grfid, st->speclist[i].localidx); | |
2924 } | |
2925 } | |
2926 | |
2927 for (tile = 0; tile < MapSize(); tile++) { | |
2928 if (GetTileType(tile) != MP_STATION) continue; | |
2929 st = GetStationByTile(tile); | |
2930 StationRect_BeforeAddTile(st, tile, RECT_MODE_FORCE); | |
2931 } | |
2932 } | |
2933 | |
2934 | |
2935 const TileTypeProcs _tile_type_station_procs = { | |
2936 DrawTile_Station, /* draw_tile_proc */ | |
2937 GetSlopeZ_Station, /* get_slope_z_proc */ | |
2938 ClearTile_Station, /* clear_tile_proc */ | |
2939 GetAcceptedCargo_Station, /* get_accepted_cargo_proc */ | |
2940 GetTileDesc_Station, /* get_tile_desc_proc */ | |
2941 GetTileTrackStatus_Station, /* get_tile_track_status_proc */ | |
2942 ClickTile_Station, /* click_tile_proc */ | |
2943 AnimateTile_Station, /* animate_tile_proc */ | |
2944 TileLoop_Station, /* tile_loop_clear */ | |
2945 ChangeTileOwner_Station, /* change_tile_owner_clear */ | |
2946 NULL, /* get_produced_cargo_proc */ | |
2947 VehicleEnter_Station, /* vehicle_enter_tile_proc */ | |
2948 GetSlopeTileh_Station, /* get_slope_tileh_proc */ | |
2949 }; | |
2950 | |
2951 static const SaveLoad _roadstop_desc[] = { | |
2952 SLE_VAR(RoadStop,xy, SLE_UINT32), | |
2953 SLE_VAR(RoadStop,used, SLE_BOOL), | |
2954 SLE_VAR(RoadStop,status, SLE_UINT8), | |
2955 /* Index was saved in some versions, but this is not needed */ | |
2956 SLE_CONDNULL(4, 0, 8), | |
2957 SLE_VAR(RoadStop,station, SLE_UINT16), | |
2958 SLE_CONDNULL(1, 0, 25), | |
2959 | |
2960 SLE_REF(RoadStop,next, REF_ROADSTOPS), | |
2961 SLE_REF(RoadStop,prev, REF_ROADSTOPS), | |
2962 | |
2963 SLE_CONDNULL(4, 0, 24), | |
2964 SLE_CONDNULL(1, 25, 25), | |
2965 | |
2966 SLE_END() | |
2967 }; | |
2968 | |
2969 static const SaveLoad _station_desc[] = { | |
2970 SLE_CONDVAR(Station, xy, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), | |
2971 SLE_CONDVAR(Station, xy, SLE_UINT32, 6, SL_MAX_VERSION), | |
2972 SLE_CONDVAR(Station, bus_tile_obsolete, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), | |
2973 SLE_CONDVAR(Station, lorry_tile_obsolete, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), | |
2974 SLE_CONDVAR(Station, train_tile, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), | |
2975 SLE_CONDVAR(Station, train_tile, SLE_UINT32, 6, SL_MAX_VERSION), | |
2976 SLE_CONDVAR(Station, airport_tile, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), | |
2977 SLE_CONDVAR(Station, airport_tile, SLE_UINT32, 6, SL_MAX_VERSION), | |
2978 SLE_CONDVAR(Station, dock_tile, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), | |
2979 SLE_CONDVAR(Station, dock_tile, SLE_UINT32, 6, SL_MAX_VERSION), | |
2980 SLE_REF(Station, town, REF_TOWN), | |
2981 SLE_VAR(Station, trainst_w, SLE_UINT8), | |
2982 SLE_CONDVAR(Station, trainst_h, SLE_UINT8, 2, SL_MAX_VERSION), | |
2983 | |
2984 // alpha_order was stored here in savegame format 0 - 3 | |
2985 SLE_CONDNULL(1, 0, 3), | |
2986 | |
2987 SLE_VAR(Station, string_id, SLE_STRINGID), | |
2988 SLE_VAR(Station, had_vehicle_of_type, SLE_UINT16), | |
2989 | |
2990 SLE_VAR(Station, time_since_load, SLE_UINT8), | |
2991 SLE_VAR(Station, time_since_unload, SLE_UINT8), | |
2992 SLE_VAR(Station, delete_ctr, SLE_UINT8), | |
2993 SLE_VAR(Station, owner, SLE_UINT8), | |
2994 SLE_VAR(Station, facilities, SLE_UINT8), | |
2995 SLE_VAR(Station, airport_type, SLE_UINT8), | |
2996 | |
2997 // truck/bus_stop_status was stored here in savegame format 0 - 6 | |
2998 SLE_CONDVAR(Station, truck_stop_status_obsolete, SLE_UINT8, 0, 5), | |
2999 SLE_CONDVAR(Station, bus_stop_status_obsolete, SLE_UINT8, 0, 5), | |
3000 | |
3001 // blocked_months was stored here in savegame format 0 - 4.0 | |
3002 SLE_CONDVAR(Station, blocked_months_obsolete, SLE_UINT8, 0, 4), | |
3003 | |
3004 SLE_CONDVAR(Station, airport_flags, SLE_VAR_U32 | SLE_FILE_U16, 0, 2), | |
3005 SLE_CONDVAR(Station, airport_flags, SLE_UINT32, 3, SL_MAX_VERSION), | |
3006 | |
3007 SLE_CONDNULL(2, 0, 25), /* Ex last-vehicle */ | |
3008 SLE_CONDVAR(Station, last_vehicle_type, SLE_UINT8, 26, SL_MAX_VERSION), | |
3009 | |
3010 // Was custom station class and id | |
3011 SLE_CONDNULL(2, 3, 25), | |
3012 SLE_CONDVAR(Station, build_date, SLE_FILE_U16 | SLE_VAR_I32, 3, 30), | |
3013 SLE_CONDVAR(Station, build_date, SLE_INT32, 31, SL_MAX_VERSION), | |
3014 | |
3015 SLE_CONDREF(Station, bus_stops, REF_ROADSTOPS, 6, SL_MAX_VERSION), | |
3016 SLE_CONDREF(Station, truck_stops, REF_ROADSTOPS, 6, SL_MAX_VERSION), | |
3017 | |
3018 /* Used by newstations for graphic variations */ | |
3019 SLE_CONDVAR(Station, random_bits, SLE_UINT16, 27, SL_MAX_VERSION), | |
3020 SLE_CONDVAR(Station, waiting_triggers, SLE_UINT8, 27, SL_MAX_VERSION), | |
3021 SLE_CONDVAR(Station, num_specs, SLE_UINT8, 27, SL_MAX_VERSION), | |
3022 | |
3023 // reserve extra space in savegame here. (currently 32 bytes) | |
3024 SLE_CONDNULL(32, 2, SL_MAX_VERSION), | |
3025 | |
3026 SLE_END() | |
3027 }; | |
3028 | |
3029 static const SaveLoad _goods_desc[] = { | |
3030 SLE_VAR(GoodsEntry, waiting_acceptance, SLE_UINT16), | |
3031 SLE_VAR(GoodsEntry, days_since_pickup, SLE_UINT8), | |
3032 SLE_VAR(GoodsEntry, rating, SLE_UINT8), | |
3033 SLE_CONDVAR(GoodsEntry, enroute_from, SLE_FILE_U8 | SLE_VAR_U16, 0, 6), | |
3034 SLE_CONDVAR(GoodsEntry, enroute_from, SLE_UINT16, 7, SL_MAX_VERSION), | |
3035 SLE_VAR(GoodsEntry, enroute_time, SLE_UINT8), | |
3036 SLE_VAR(GoodsEntry, last_speed, SLE_UINT8), | |
3037 SLE_VAR(GoodsEntry, last_age, SLE_UINT8), | |
3038 SLE_CONDVAR(GoodsEntry, feeder_profit, SLE_INT32, 14, SL_MAX_VERSION), | |
3039 | |
3040 SLE_END() | |
3041 }; | |
3042 | |
3043 static const SaveLoad _station_speclist_desc[] = { | |
3044 SLE_CONDVAR(StationSpecList, grfid, SLE_UINT32, 27, SL_MAX_VERSION), | |
3045 SLE_CONDVAR(StationSpecList, localidx, SLE_UINT8, 27, SL_MAX_VERSION), | |
3046 | |
3047 SLE_END() | |
3048 }; | |
3049 | |
3050 | |
3051 static void SaveLoad_STNS(Station *st) | |
3052 { | |
3053 uint i; | |
3054 | |
3055 SlObject(st, _station_desc); | |
3056 for (i = 0; i != NUM_CARGO; i++) { | |
3057 SlObject(&st->goods[i], _goods_desc); | |
3058 | |
3059 /* In older versions, enroute_from had 0xFF as INVALID_STATION, is now 0xFFFF */ | |
3060 if (CheckSavegameVersion(7) && st->goods[i].enroute_from == 0xFF) { | |
3061 st->goods[i].enroute_from = INVALID_STATION; | |
3062 } | |
3063 } | |
3064 | |
3065 if (st->num_specs != 0) { | |
3066 /* Allocate speclist memory when loading a game */ | |
3067 if (st->speclist == NULL) st->speclist = calloc(st->num_specs, sizeof(*st->speclist)); | |
3068 for (i = 0; i < st->num_specs; i++) SlObject(&st->speclist[i], _station_speclist_desc); | |
3069 } | |
3070 } | |
3071 | |
3072 static void Save_STNS(void) | |
3073 { | |
3074 Station *st; | |
3075 // Write the stations | |
3076 FOR_ALL_STATIONS(st) { | |
3077 SlSetArrayIndex(st->index); | |
3078 SlAutolength((AutolengthProc*)SaveLoad_STNS, st); | |
3079 } | |
3080 } | |
3081 | |
3082 static void Load_STNS(void) | |
3083 { | |
3084 int index; | |
3085 while ((index = SlIterateArray()) != -1) { | |
3086 Station *st; | |
3087 | |
3088 if (!AddBlockIfNeeded(&_Station_pool, index)) | |
3089 error("Stations: failed loading savegame: too many stations"); | |
3090 | |
3091 st = GetStation(index); | |
3092 SaveLoad_STNS(st); | |
3093 | |
3094 // this means it's an oldstyle savegame without support for nonuniform stations | |
3095 if (st->train_tile != 0 && st->trainst_h == 0) { | |
3096 uint w = GB(st->trainst_w, 4, 4); | |
3097 uint h = GB(st->trainst_w, 0, 4); | |
3098 | |
3099 if (GetRailStationAxis(st->train_tile) == AXIS_Y) uintswap(w, h); | |
3100 st->trainst_w = w; | |
3101 st->trainst_h = h; | |
3102 } | |
3103 | |
3104 /* In older versions, we had just 1 tile for a bus/lorry, now we have more.. | |
3105 * convert, if needed */ | |
3106 if (CheckSavegameVersion(6)) { | |
3107 if (st->bus_tile_obsolete != 0) { | |
3108 st->bus_stops = AllocateRoadStop(); | |
3109 if (st->bus_stops == NULL) | |
3110 error("Station: too many busstations in savegame"); | |
3111 | |
3112 InitializeRoadStop(st->bus_stops, NULL, st->bus_tile_obsolete, st->index); | |
3113 } | |
3114 if (st->lorry_tile_obsolete != 0) { | |
3115 st->truck_stops = AllocateRoadStop(); | |
3116 if (st->truck_stops == NULL) | |
3117 error("Station: too many truckstations in savegame"); | |
3118 | |
3119 InitializeRoadStop(st->truck_stops, NULL, st->lorry_tile_obsolete, st->index); | |
3120 } | |
3121 } | |
3122 } | |
3123 | |
3124 /* This is to ensure all pointers are within the limits of _stations_size */ | |
3125 if (_station_tick_ctr > GetMaxStationIndex()) _station_tick_ctr = 0; | |
3126 } | |
3127 | |
3128 static void Save_ROADSTOP(void) | |
3129 { | |
3130 RoadStop *rs; | |
3131 | |
3132 FOR_ALL_ROADSTOPS(rs) { | |
3133 SlSetArrayIndex(rs->index); | |
3134 SlObject(rs, _roadstop_desc); | |
3135 } | |
3136 } | |
3137 | |
3138 static void Load_ROADSTOP(void) | |
3139 { | |
3140 int index; | |
3141 Vehicle *v; | |
3142 | |
3143 while ((index = SlIterateArray()) != -1) { | |
3144 RoadStop *rs; | |
3145 | |
3146 if (!AddBlockIfNeeded(&_RoadStop_pool, index)) | |
3147 error("RoadStops: failed loading savegame: too many RoadStops"); | |
3148 | |
3149 rs = GetRoadStop(index); | |
3150 SlObject(rs, _roadstop_desc); | |
3151 } | |
3152 | |
3153 FOR_ALL_VEHICLES(v) { | |
3154 if (v->type == VEH_Road && v->u.road.slot != NULL) v->u.road.slot->num_vehicles++; | |
3155 } | |
3156 } | |
3157 | |
3158 const ChunkHandler _station_chunk_handlers[] = { | |
3159 { 'STNS', Save_STNS, Load_STNS, CH_ARRAY }, | |
3160 { 'ROAD', Save_ROADSTOP, Load_ROADSTOP, CH_ARRAY | CH_LAST}, | |
3161 }; | |
3162 | |
3163 | |
3164 static inline bool PtInRectXY(Rect *r, int x, int y) | |
3165 { | |
3166 return (r->left <= x && x <= r->right && r->top <= y && y <= r->bottom); | |
3167 } | |
3168 | |
3169 static void StationRect_Init(Station *st) | |
3170 { | |
3171 Rect *r = &st->rect; | |
3172 r->left = r->top = r->right = r->bottom = 0; | |
3173 } | |
3174 | |
3175 static bool StationRect_IsEmpty(Station *st) | |
3176 { | |
3177 return (st->rect.left == 0 || st->rect.left > st->rect.right || st->rect.top > st->rect.bottom); | |
3178 } | |
3179 | |
3180 static bool StationRect_BeforeAddTile(Station *st, TileIndex tile, StationRectMode mode) | |
3181 { | |
3182 Rect *r = &st->rect; | |
3183 int x = TileX(tile); | |
3184 int y = TileY(tile); | |
3185 if (StationRect_IsEmpty(st)) { | |
3186 // we are adding the first station tile | |
3187 r->left = r->right = x; | |
3188 r->top = r->bottom = y; | |
3189 } else if (!PtInRectXY(r, x, y)) { | |
3190 // current rect is not empty and new point is outside this rect | |
3191 // make new spread-out rectangle | |
3192 Rect new_rect = {min(x, r->left), min(y, r->top), max(x, r->right), max(y, r->bottom)}; | |
3193 // check new rect dimensions against preset max | |
3194 int w = new_rect.right - new_rect.left + 1; | |
3195 int h = new_rect.bottom - new_rect.top + 1; | |
3196 if (mode != RECT_MODE_FORCE && (w > _patches.station_spread || h > _patches.station_spread)) { | |
3197 assert(mode != RECT_MODE_TRY); | |
3198 _error_message = STR_306C_STATION_TOO_SPREAD_OUT; | |
3199 return false; | |
3200 } | |
3201 // spread-out ok, return true | |
3202 if (mode != RECT_MODE_TEST) { | |
3203 // we should update the station rect | |
3204 *r = new_rect; | |
3205 } | |
3206 } else { | |
3207 ; // new point is inside the rect, we don't need to do anything | |
3208 } | |
3209 return true; | |
3210 } | |
3211 | |
3212 static bool StationRect_BeforeAddRect(Station *st, TileIndex tile, int w, int h, StationRectMode mode) | |
3213 { | |
3214 return StationRect_BeforeAddTile(st, tile, mode) && StationRect_BeforeAddTile(st, TILE_ADDXY(tile, w - 1, h - 1), mode); | |
3215 } | |
3216 | |
3217 static inline bool ScanRectForStationTiles(StationID st_id, int left, int top, int right, int bottom) | |
3218 { | |
3219 TileIndex top_left = TileXY(left, top); | |
3220 int width = right - left + 1; | |
3221 int height = bottom - top + 1; | |
3222 BEGIN_TILE_LOOP(tile, width, height, top_left) | |
3223 if (IsTileType(tile, MP_STATION) && GetStationIndex(tile) == st_id) return true; | |
3224 END_TILE_LOOP(tile, width, height, top_left); | |
3225 return false; | |
3226 } | |
3227 | |
3228 static bool StationRect_AfterRemoveTile(Station *st, TileIndex tile) | |
3229 { | |
3230 Rect *r = &st->rect; | |
3231 int x = TileX(tile); | |
3232 int y = TileY(tile); | |
3233 bool reduce_x, reduce_y; | |
3234 | |
3235 // look if removed tile was on the bounding rect edge | |
3236 // and try to reduce the rect by this edge | |
3237 // do it until we have empty rect or nothing to do | |
3238 for (;;) { | |
3239 // check if removed tile is on rect edge | |
3240 bool left_edge = (x == r->left); | |
3241 bool right_edge = (x == r->right); | |
3242 bool top_edge = (y == r->top); | |
3243 bool bottom_edge = (y == r->bottom); | |
3244 // can we reduce the rect in either direction? | |
3245 reduce_x = ((left_edge || right_edge) && !ScanRectForStationTiles(st->index, x, r->top, x, r->bottom)); | |
3246 reduce_y = ((top_edge || bottom_edge) && !ScanRectForStationTiles(st->index, r->left, y, r->right, y)); | |
3247 if (!(reduce_x || reduce_y)) break; // nothing to do (can't reduce) | |
3248 if (reduce_x) { | |
3249 // reduce horizontally | |
3250 if (left_edge) { | |
3251 // move left edge right | |
3252 r->left = x = x + 1; | |
3253 } else { | |
3254 // move right edge left | |
3255 r->right = x = x - 1; | |
3256 } | |
3257 } | |
3258 if (reduce_y) { | |
3259 // reduce vertically | |
3260 if (top_edge) { | |
3261 // move top edge down | |
3262 r->top = y = y + 1; | |
3263 } else { | |
3264 // move bottom edge up | |
3265 r->bottom = y = y - 1; | |
3266 } | |
3267 } | |
3268 if (r->left > r->right || r->top > r->bottom) { | |
3269 // can't continue, if the remaining rectangle is empty | |
3270 StationRect_Init(st); | |
3271 return true; // empty remaining rect | |
3272 } | |
3273 } | |
3274 return false; // non-empty remaining rect | |
3275 } | |
3276 | |
3277 static bool StationRect_AfterRemoveRect(Station *st, TileIndex tile, int w, int h) | |
3278 { | |
3279 bool empty; | |
3280 assert(PtInRectXY(&st->rect, TileX(tile), TileY(tile))); | |
3281 assert(PtInRectXY(&st->rect, TileX(tile) + w - 1, TileY(tile) + h - 1)); | |
3282 empty = StationRect_AfterRemoveTile(st, tile); | |
3283 if (w != 1 || h != 1) empty = empty || StationRect_AfterRemoveTile(st, TILE_ADDXY(tile, w - 1, h - 1)); | |
3284 return empty; | |
3285 } |