summaryrefslogtreecommitdiff
path: root/src/station.cpp
blob: 7a6d1a7e0e4b9232d1477f9e6e99510e7434bf30 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
/* $Id$ */

/** @file station_cmd.c */

#include "stdafx.h"
#include "openttd.h"
#include "bridge_map.h"
#include "debug.h"
#include "functions.h"
#include "station_map.h"
#include "table/sprites.h"
#include "table/strings.h"
#include "map.h"
#include "tile.h"
#include "station.h"
#include "gfx.h"
#include "window.h"
#include "viewport.h"
#include "command.h"
#include "town.h"
#include "vehicle.h"
#include "news.h"
#include "saveload.h"
#include "economy.h"
#include "player.h"
#include "airport.h"
#include "sprite.h"
#include "depot.h"
#include "train.h"
#include "water_map.h"
#include "industry_map.h"
#include "newgrf_callbacks.h"
#include "newgrf_station.h"
#include "yapf/yapf.h"
#include "date.h"
#include "helpers.hpp"

Station::Station(TileIndex tile)
{
	DEBUG(station, cDebugCtorLevel, "I+%3d", index);

	xy = tile;
	airport_tile = dock_tile = train_tile = 0;
	bus_stops = truck_stops = NULL;
	had_vehicle_of_type = 0;
	time_since_load = 255;
	time_since_unload = 255;
	delete_ctr = 0;
	facilities = 0;

	last_vehicle_type = VEH_Invalid;

	random_bits = Random();
	waiting_triggers = 0;
}

/**
* Clean up a station by clearing vehicle orders and invalidating windows.
* Aircraft-Hangar orders need special treatment here, as the hangars are
* actually part of a station (tiletype is STATION), but the order type
* is OT_GOTO_DEPOT.
* @param st Station to be deleted
*/
Station::~Station()
{
	DEBUG(station, cDebugCtorLevel, "I-%3d", index);

	DeleteName(string_id);
	MarkDirty();
	RebuildStationLists();
	InvalidateWindowClasses(WC_STATION_LIST);

	DeleteWindowById(WC_STATION_VIEW, index);

	/* Now delete all orders that go to the station */
	RemoveOrderFromAllVehicles(OT_GOTO_STATION, index);

	//Subsidies need removal as well
	DeleteSubsidyWithStation(index);

	free(speclist);
	xy = 0;
}

void* Station::operator new(size_t size)
{
	Station *st = AllocateRaw();
	return st;
}

void* Station::operator new(size_t size, int st_idx)
{
	if (!AddBlockIfNeeded(&_Station_pool, st_idx))
		error("Stations: failed loading savegame: too many stations");

	Station *st = GetStation(st_idx);
	return st;
}

void Station::operator delete(void *p)
{
}

void Station::operator delete(void *p, int st_idx)
{
}

void Station::MarkDirty() const
{
	if (sign.width_1 != 0) {
		InvalidateWindowWidget(WC_STATION_VIEW, index, 1);

		MarkAllViewportsDirty(
			sign.left - 6,
			sign.top,
			sign.left + (sign.width_1 << 2) + 12,
			sign.top + 48);
	}
}

void Station::MarkTilesDirty() const
{
	TileIndex tile = train_tile;
	int w, h;

	// XXX No station is recorded as 0, not INVALID_TILE...
	if (tile == 0) return;

	for (h = 0; h < trainst_h; h++) {
		for (w = 0; w < trainst_w; w++) {
			if (TileBelongsToRailStation(tile)) {
				MarkTileDirtyByTile(tile);
			}
			tile += TileDiffXY(1, 0);
		}
		tile += TileDiffXY(-w, 1);
	}
}

bool Station::TileBelongsToRailStation(TileIndex tile) const
{
	return IsTileType(tile, MP_STATION) && GetStationIndex(tile) == index && IsRailwayStation(tile);
}

/*static*/ Station *Station::AllocateRaw(void)
{
	Station *st = NULL;

	/* We don't use FOR_ALL here, because FOR_ALL skips invalid items.
	 * TODO - This is just a temporary stage, this will be removed. */
	for (st = GetStation(0); st != NULL; st = (st->index + 1U < GetStationPoolSize()) ? GetStation(st->index + 1U) : NULL) {
		if (!IsValidStation(st)) {
			StationID index = st->index;

			memset(st, 0, sizeof(Station));
			st->index = index;
			return st;
		}
	}

	/* Check if we can add a block to the pool */
	if (AddBlockToPool(&_Station_pool)) return AllocateRaw();

	_error_message = STR_3008_TOO_MANY_STATIONS_LOADING;
	return NULL;
}



/************************************************************************/
/*                     StationRect implementation                       */
/************************************************************************/

StationRect::StationRect()
{
	MakeEmpty();
}

void StationRect::MakeEmpty()
{
	left = top = right = bottom = 0;
}

bool StationRect::PtInRectXY(int x, int y) const
{
	return (left <= x && x <= right && top <= y && y <= bottom);
}

bool StationRect::IsEmpty() const
{
	return (left == 0 || left > right || top > bottom);
}

bool StationRect::BeforeAddTile(TileIndex tile, StationRectMode mode)
{
	int x = TileX(tile);
	int y = TileY(tile);
	if (IsEmpty()) {
		// we are adding the first station tile
		left = right = x;
		top = bottom = y;
	} else if (!PtInRectXY(x, y)) {
		// current rect is not empty and new point is outside this rect
		// make new spread-out rectangle
		Rect new_rect = {min(x, left), min(y, top), max(x, right), max(y, bottom)};
		// check new rect dimensions against preset max
		int w = new_rect.right - new_rect.left + 1;
		int h = new_rect.bottom - new_rect.top + 1;
		if (mode != ADD_FORCE && (w > _patches.station_spread || h > _patches.station_spread)) {
			assert(mode != ADD_TRY);
			_error_message = STR_306C_STATION_TOO_SPREAD_OUT;
			return false;
		}
		// spread-out ok, return true
		if (mode != ADD_TEST) {
			// we should update the station rect
			*this = new_rect;
		}
	} else {
		; // new point is inside the rect, we don't need to do anything
	}
	return true;
}

bool StationRect::BeforeAddRect(TileIndex tile, int w, int h, StationRectMode mode)
{
	return BeforeAddTile(tile, mode) && BeforeAddTile(TILE_ADDXY(tile, w - 1, h - 1), mode);
}

/*static*/ bool StationRect::ScanForStationTiles(StationID st_id, int left_a, int top_a, int right_a, int bottom_a)
{
	TileIndex top_left = TileXY(left_a, top_a);
	int width = right_a - left_a + 1;
	int height = bottom_a - top_a + 1;
	BEGIN_TILE_LOOP(tile, width, height, top_left)
		if (IsTileType(tile, MP_STATION) && GetStationIndex(tile) == st_id) return true;
	END_TILE_LOOP(tile, width, height, top_left);
	return false;
}

bool StationRect::AfterRemoveTile(Station *st, TileIndex tile)
{
	int x = TileX(tile);
	int y = TileY(tile);
	bool reduce_x, reduce_y;

	// look if removed tile was on the bounding rect edge
	// and try to reduce the rect by this edge
	// do it until we have empty rect or nothing to do
	for (;;) {
		// check if removed tile is on rect edge
		bool left_edge = (x == left);
		bool right_edge = (x == right);
		bool top_edge = (y == top);
		bool bottom_edge = (y == bottom);
		// can we reduce the rect in either direction?
		reduce_x = ((left_edge || right_edge) && !ScanForStationTiles(st->index, x, top, x, bottom));
		reduce_y = ((top_edge || bottom_edge) && !ScanForStationTiles(st->index, left, y, right, y));
		if (!(reduce_x || reduce_y)) break; // nothing to do (can't reduce)
		if (reduce_x) {
			// reduce horizontally
			if (left_edge) {
				// move left edge right
				left = x = x + 1;
			} else {
				// move right edge left
				right = x = x - 1;
			}
		}
		if (reduce_y) {
			// reduce vertically
			if (top_edge) {
				// move top edge down
				top = y = y + 1;
			} else {
				// move bottom edge up
				bottom = y = y - 1;
			}
		}
		if (left > right || top > bottom) {
			// can't continue, if the remaining rectangle is empty
			MakeEmpty();
			return true; // empty remaining rect
		}
	}
	return false; // non-empty remaining rect
}

bool StationRect::AfterRemoveRect(Station *st, TileIndex tile, int w, int h)
{
	bool empty;
	assert(PtInRectXY(TileX(tile), TileY(tile)));
	assert(PtInRectXY(TileX(tile) + w - 1, TileY(tile) + h - 1));
	empty = AfterRemoveTile(st, tile);
	if (w != 1 || h != 1) empty = empty || AfterRemoveTile(st, TILE_ADDXY(tile, w - 1, h - 1));
	return empty;
}

StationRect& StationRect::operator = (Rect src)
{
	left = src.left;
	top = src.top;
	right = src.right;
	bottom = src.bottom;
	return *this;
}