OpenTTD Source 20250328-master-gc3457cd4c0
newgrf_act0_houses.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
7
10#include "../stdafx.h"
11#include "../debug.h"
12#include "../town.h"
13#include "../newgrf_cargo.h"
14#include "../newgrf_house.h"
15#include "newgrf_bytereader.h"
16#include "newgrf_internal.h"
18
19#include "table/strings.h"
20
21#include "../safeguards.h"
22
30{
32
33 switch (prop) {
34 case 0x09:
35 case 0x0B:
36 case 0x0C:
37 case 0x0D:
38 case 0x0E:
39 case 0x0F:
40 case 0x11:
41 case 0x14:
42 case 0x15:
43 case 0x16:
44 case 0x18:
45 case 0x19:
46 case 0x1A:
47 case 0x1B:
48 case 0x1C:
49 case 0x1D:
50 case 0x1F:
51 buf.ReadByte();
52 break;
53
54 case 0x0A:
55 case 0x10:
56 case 0x12:
57 case 0x13:
58 case 0x21:
59 case 0x22:
60 buf.ReadWord();
61 break;
62
63 case 0x1E:
64 buf.ReadDWord();
65 break;
66
67 case 0x17:
68 for (uint j = 0; j < 4; j++) buf.ReadByte();
69 break;
70
71 case 0x20: {
72 uint8_t count = buf.ReadByte();
73 for (uint8_t j = 0; j < count; j++) buf.ReadByte();
74 break;
75 }
76
77 case 0x23:
78 buf.Skip(buf.ReadByte() * 2);
79 break;
80
81 default:
82 ret = CIR_UNKNOWN;
83 break;
84 }
85 return ret;
86}
87
96static ChangeInfoResult TownHouseChangeInfo(uint first, uint last, int prop, ByteReader &buf)
97{
99
100 if (last > NUM_HOUSES_PER_GRF) {
101 GrfMsg(1, "TownHouseChangeInfo: Too many houses loaded ({}), max ({}). Ignoring.", last, NUM_HOUSES_PER_GRF);
102 return CIR_INVALID_ID;
103 }
104
105 /* Allocate house specs if they haven't been allocated already. */
106 if (_cur.grffile->housespec.size() < last) _cur.grffile->housespec.resize(last);
107
108 for (uint id = first; id < last; ++id) {
109 auto &housespec = _cur.grffile->housespec[id];
110
111 if (prop != 0x08 && housespec == nullptr) {
112 /* If the house property 08 is not yet set, ignore this property */
114 if (cir > ret) ret = cir;
115 continue;
116 }
117
118 switch (prop) {
119 case 0x08: { // Substitute building type, and definition of a new house
120 uint8_t subs_id = buf.ReadByte();
121 if (subs_id == 0xFF) {
122 /* Instead of defining a new house, a substitute house id
123 * of 0xFF disables the old house with the current id. */
124 if (id < NEW_HOUSE_OFFSET) HouseSpec::Get(id)->enabled = false;
125 continue;
126 } else if (subs_id >= NEW_HOUSE_OFFSET) {
127 /* The substitute id must be one of the original houses. */
128 GrfMsg(2, "TownHouseChangeInfo: Attempt to use new house {} as substitute house for {}. Ignoring.", subs_id, id);
129 continue;
130 }
131
132 /* Allocate space for this house. */
133 if (housespec == nullptr) {
134 /* Only the first property 08 setting copies properties; if you later change it, properties will stay. */
135 housespec = std::make_unique<HouseSpec>(*HouseSpec::Get(subs_id));
136
137 housespec->enabled = true;
138 housespec->grf_prop.local_id = id;
139 housespec->grf_prop.subst_id = subs_id;
140 housespec->grf_prop.SetGRFFile(_cur.grffile);
141 /* Set default colours for randomization, used if not overridden. */
142 housespec->random_colour[0] = COLOUR_RED;
143 housespec->random_colour[1] = COLOUR_BLUE;
144 housespec->random_colour[2] = COLOUR_ORANGE;
145 housespec->random_colour[3] = COLOUR_GREEN;
146
147 /* House flags 40 and 80 are exceptions; these flags are never set automatically. */
148 housespec->building_flags.Reset(BuildingFlag::IsChurch).Reset(BuildingFlag::IsStadium);
149
150 /* Make sure that the third cargo type is valid in this
151 * climate. This can cause problems when copying the properties
152 * of a house that accepts food, where the new house is valid
153 * in the temperate climate. */
154 CargoType cargo_type = housespec->accepts_cargo[2];
155 if (!IsValidCargoType(cargo_type)) cargo_type = GetCargoTypeByLabel(housespec->accepts_cargo_label[2]);
156 if (!IsValidCargoType(cargo_type)) {
157 housespec->cargo_acceptance[2] = 0;
158 }
159 }
160 break;
161 }
162
163 case 0x09: // Building flags
164 housespec->building_flags = (BuildingFlags)buf.ReadByte();
165 break;
166
167 case 0x0A: { // Availability years
168 uint16_t years = buf.ReadWord();
169 housespec->min_year = GB(years, 0, 8) > 150 ? CalendarTime::MAX_YEAR : CalendarTime::ORIGINAL_BASE_YEAR + GB(years, 0, 8);
170 housespec->max_year = GB(years, 8, 8) > 150 ? CalendarTime::MAX_YEAR : CalendarTime::ORIGINAL_BASE_YEAR + GB(years, 8, 8);
171 break;
172 }
173
174 case 0x0B: // Population
175 housespec->population = buf.ReadByte();
176 break;
177
178 case 0x0C: // Mail generation multiplier
179 housespec->mail_generation = buf.ReadByte();
180 break;
181
182 case 0x0D: // Passenger acceptance
183 case 0x0E: // Mail acceptance
184 housespec->cargo_acceptance[prop - 0x0D] = buf.ReadByte();
185 break;
186
187 case 0x0F: { // Goods/candy, food/fizzy drinks acceptance
188 int8_t goods = buf.ReadByte();
189
190 /* If value of goods is negative, it means in fact food or, if in toyland, fizzy_drink acceptance.
191 * Else, we have "standard" 3rd cargo type, goods or candy, for toyland once more */
192 CargoType cargo_type = (goods >= 0) ? ((_settings_game.game_creation.landscape == LandscapeType::Toyland) ? GetCargoTypeByLabel(CT_CANDY) : GetCargoTypeByLabel(CT_GOODS)) :
193 ((_settings_game.game_creation.landscape == LandscapeType::Toyland) ? GetCargoTypeByLabel(CT_FIZZY_DRINKS) : GetCargoTypeByLabel(CT_FOOD));
194
195 /* Make sure the cargo type is valid in this climate. */
196 if (!IsValidCargoType(cargo_type)) goods = 0;
197
198 housespec->accepts_cargo[2] = cargo_type;
199 housespec->accepts_cargo_label[2] = CT_INVALID;
200 housespec->cargo_acceptance[2] = abs(goods); // but we do need positive value here
201 break;
202 }
203
204 case 0x10: // Local authority rating decrease on removal
205 housespec->remove_rating_decrease = buf.ReadWord();
206 break;
207
208 case 0x11: // Removal cost multiplier
209 housespec->removal_cost = buf.ReadByte();
210 break;
211
212 case 0x12: // Building name ID
213 AddStringForMapping(GRFStringID{buf.ReadWord()}, &housespec->building_name);
214 break;
215
216 case 0x13: // Building availability mask
217 housespec->building_availability = (HouseZones)buf.ReadWord();
218 break;
219
220 case 0x14: { // House callback mask
221 auto mask = housespec->callback_mask.base();
222 SB(mask, 0, 8, buf.ReadByte());
223 housespec->callback_mask = HouseCallbackMasks{mask};
224 break;
225 }
226
227 case 0x15: { // House override byte
228 uint8_t override = buf.ReadByte();
229
230 /* The house being overridden must be an original house. */
231 if (override >= NEW_HOUSE_OFFSET) {
232 GrfMsg(2, "TownHouseChangeInfo: Attempt to override new house {} with house id {}. Ignoring.", override, id);
233 continue;
234 }
235
236 _house_mngr.Add(id, _cur.grffile->grfid, override);
237 break;
238 }
239
240 case 0x16: // Periodic refresh multiplier
241 housespec->processing_time = std::min<uint8_t>(buf.ReadByte(), 63u);
242 break;
243
244 case 0x17: // Four random colours to use
245 for (uint j = 0; j < 4; j++) housespec->random_colour[j] = static_cast<Colours>(GB(buf.ReadByte(), 0, 4));
246 break;
247
248 case 0x18: // Relative probability of appearing
249 housespec->probability = buf.ReadByte();
250 break;
251
252 case 0x19: // Extra flags
253 housespec->extra_flags = static_cast<HouseExtraFlags>(buf.ReadByte());
254 break;
255
256 case 0x1A: // Animation frames
257 housespec->animation.frames = buf.ReadByte();
258 housespec->animation.status = GB(housespec->animation.frames, 7, 1);
259 SB(housespec->animation.frames, 7, 1, 0);
260 break;
261
262 case 0x1B: // Animation speed
263 housespec->animation.speed = Clamp(buf.ReadByte(), 2, 16);
264 break;
265
266 case 0x1C: // Class of the building type
267 housespec->class_id = AllocateHouseClassID(buf.ReadByte(), _cur.grffile->grfid);
268 break;
269
270 case 0x1D: { // Callback mask part 2
271 auto mask = housespec->callback_mask.base();
272 SB(mask, 8, 8, buf.ReadByte());
273 housespec->callback_mask = HouseCallbackMasks{mask};
274 break;
275 }
276
277 case 0x1E: { // Accepted cargo types
278 uint32_t cargotypes = buf.ReadDWord();
279
280 /* Check if the cargo types should not be changed */
281 if (cargotypes == 0xFFFFFFFF) break;
282
283 for (uint j = 0; j < HOUSE_ORIGINAL_NUM_ACCEPTS; j++) {
284 /* Get the cargo number from the 'list' */
285 uint8_t cargo_part = GB(cargotypes, 8 * j, 8);
286 CargoType cargo = GetCargoTranslation(cargo_part, _cur.grffile);
287
288 if (!IsValidCargoType(cargo)) {
289 /* Disable acceptance of invalid cargo type */
290 housespec->cargo_acceptance[j] = 0;
291 } else {
292 housespec->accepts_cargo[j] = cargo;
293 }
294 housespec->accepts_cargo_label[j] = CT_INVALID;
295 }
296 break;
297 }
298
299 case 0x1F: // Minimum life span
300 housespec->minimum_life = buf.ReadByte();
301 break;
302
303 case 0x20: { // Cargo acceptance watch list
304 uint8_t count = buf.ReadByte();
305 for (uint8_t j = 0; j < count; j++) {
306 CargoType cargo = GetCargoTranslation(buf.ReadByte(), _cur.grffile);
307 if (IsValidCargoType(cargo)) SetBit(housespec->watched_cargoes, cargo);
308 }
309 break;
310 }
311
312 case 0x21: // long introduction year
313 housespec->min_year = TimerGameCalendar::Year{buf.ReadWord()};
314 break;
315
316 case 0x22: // long maximum year
317 housespec->max_year = TimerGameCalendar::Year{buf.ReadWord()};
318 if (housespec->max_year == UINT16_MAX) housespec->max_year = CalendarTime::MAX_YEAR;
319 break;
320
321 case 0x23: { // variable length cargo types accepted
322 uint count = buf.ReadByte();
323 if (count > lengthof(housespec->accepts_cargo)) {
324 GRFError *error = DisableGrf(STR_NEWGRF_ERROR_LIST_PROPERTY_TOO_LONG);
325 error->param_value[1] = prop;
326 return CIR_DISABLED;
327 }
328 /* Always write the full accepts_cargo array, and check each index for being inside the
329 * provided data. This ensures all values are properly initialized, and also avoids
330 * any risks of array overrun. */
331 for (uint i = 0; i < lengthof(housespec->accepts_cargo); i++) {
332 if (i < count) {
333 housespec->accepts_cargo[i] = GetCargoTranslation(buf.ReadByte(), _cur.grffile);
334 housespec->cargo_acceptance[i] = buf.ReadByte();
335 } else {
336 housespec->accepts_cargo[i] = INVALID_CARGO;
337 housespec->cargo_acceptance[i] = 0;
338 }
339 if (i < std::size(housespec->accepts_cargo_label)) housespec->accepts_cargo_label[i] = CT_INVALID;
340 }
341 break;
342 }
343
344 case 0x24: // Badge list
345 housespec->badges = ReadBadgeList(buf, GSF_HOUSES);
346 break;
347
348 default:
349 ret = CIR_UNKNOWN;
350 break;
351 }
352 }
353
354 return ret;
355}
356
358template <> ChangeInfoResult GrfChangeInfoHandler<GSF_HOUSES>::Activation(uint first, uint last, int prop, ByteReader &buf) { return TownHouseChangeInfo(first, last, prop, buf); }
constexpr T SB(T &x, const uint8_t s, const uint8_t n, const U d)
Set n bits in x starting at bit s to d.
constexpr T SetBit(T &x, const uint8_t y)
Set a bit in a variable.
debug_inline static constexpr uint GB(const T x, const uint8_t s, const uint8_t n)
Fetch n bits from x, started at bit s.
static constexpr CargoLabel CT_INVALID
Invalid cargo type.
Definition cargo_type.h:72
uint8_t CargoType
Cargo slots to indicate a cargo type within a game.
Definition cargo_type.h:23
bool IsValidCargoType(CargoType cargo)
Test whether cargo type is not INVALID_CARGO.
Definition cargo_type.h:106
Class to read from a NewGRF file.
uint32_t ReadDWord()
Read a single DWord (32 bits).
uint16_t ReadWord()
Read a single Word (16 bits).
uint8_t ReadByte()
Read a single byte (8 bits).
void Add(uint16_t local_id, uint32_t grfid, uint entity_type)
Since the entity IDs defined by the GRF file does not necessarily correlate to those used by the game...
static constexpr TimerGame< struct Calendar >::Year ORIGINAL_BASE_YEAR
The minimum starting year/base year of the original TTD.
static constexpr TimerGame< struct Calendar >::Year MAX_YEAR
MAX_YEAR, nicely rounded value of the number of years that can be encoded in a single 32 bits date,...
static const HouseID NEW_HOUSE_OFFSET
Offset for new houses.
Definition house.h:28
static const HouseID NUM_HOUSES_PER_GRF
Number of supported houses per NewGRF.
Definition house.h:32
static const uint HOUSE_ORIGINAL_NUM_ACCEPTS
Original number of accepted cargo types.
Definition house.h:35
HouseZones
Definition house.h:68
constexpr T abs(const T a)
Returns the absolute value of (scalar) variable.
Definition math_func.hpp:23
constexpr T Clamp(const T a, const T min, const T max)
Clamp a value between an interval.
Definition math_func.hpp:79
GRFError * DisableGrf(StringID message, GRFConfig *config)
Disable a GRF.
Definition newgrf.cpp:131
std::vector< BadgeID > ReadBadgeList(ByteReader &buf, GrfSpecFeature feature)
Read a list of badges.
static ChangeInfoResult IgnoreTownHouseProperty(int prop, ByteReader &buf)
Ignore a house property.
static ChangeInfoResult TownHouseChangeInfo(uint first, uint last, int prop, ByteReader &buf)
Define properties for houses.
NewGRF buffer reader definition.
CargoType GetCargoTranslation(uint8_t cargo, const GRFFile *grffile, bool usebit)
Translate a GRF-local cargo slot/bitnum into a CargoType.
NewGRF internal processing state.
ChangeInfoResult
Possible return values for the GrfChangeInfoHandler functions.
@ CIR_INVALID_ID
Attempt to modify an invalid ID.
@ CIR_DISABLED
GRF was disabled due to error.
@ CIR_UNKNOWN
Variable is unknown.
@ CIR_UNHANDLED
Variable was parsed but unread.
@ CIR_SUCCESS
Variable was parsed and read.
void AddStringForMapping(GRFStringID source, std::function< void(StringID)> &&func)
Record a static StringID for getting translated later.
NewGRF string mapping definition.
GameSettings _settings_game
Game settings of a running game or the scenario editor.
Definition settings.cpp:59
#define lengthof(array)
Return the length of an fixed size array.
Definition stdafx.h:277
Information about why GRF had problems during initialisation.
std::array< uint32_t, 2 > param_value
Values of GRF parameters to show for message and custom_message.
LandscapeType landscape
the landscape we're currently in
GameCreationSettings game_creation
settings used during the creation of a game (map)
GRF feature handler.
GRFFile * grffile
Currently processed GRF file.
bool enabled
the house is available to build (true by default, but can be disabled by newgrf)
Definition house.h:106
static HouseSpec * Get(size_t house_id)
Get the spec for a house ID.