Add: NewGRF Badges.

This commit is contained in:
Peter Nelson
2024-10-10 22:18:01 +01:00
committed by Peter Nelson
parent ca3b5ed6c1
commit 8f14894024
46 changed files with 1224 additions and 54 deletions

View File

@@ -22,6 +22,8 @@
#include "fontcache.h"
#include "currency.h"
#include "landscape.h"
#include "newgrf_badge.h"
#include "newgrf_badge_type.h"
#include "newgrf_cargo.h"
#include "newgrf_house.h"
#include "newgrf_sound.h"
@@ -1049,6 +1051,50 @@ static ChangeInfoResult CommonVehicleChangeInfo(EngineInfo *ei, int prop, ByteRe
return CIR_SUCCESS;
}
/**
* Skip a list of badges.
* @param buf Buffer reader containing list of badges to skip.
*/
static void SkipBadgeList(ByteReader &buf)
{
uint16_t count = buf.ReadWord();
while (count-- > 0) {
buf.ReadWord();
}
}
/**
* Read a list of badges.
* @param buf Buffer reader containing list of badges to read.
* @param feature The feature of the badge list.
* @returns list of badges.
*/
static std::vector<BadgeID> ReadBadgeList(ByteReader &buf, GrfSpecFeature feature)
{
uint16_t count = buf.ReadWord();
std::vector<BadgeID> badges;
badges.reserve(count);
while (count-- > 0) {
uint16_t local_index = buf.ReadWord();
if (local_index >= std::size(_cur.grffile->badge_list)) {
GrfMsg(1, "ReadBadgeList: Badge label {} out of range (max {}), skipping.", local_index, std::size(_cur.grffile->badge_list) - 1);
continue;
}
BadgeID index = _cur.grffile->badge_list[local_index];
/* Is badge already present? */
if (std::ranges::find(badges, index) != std::end(badges)) continue;
badges.push_back(index);
MarkBadgeSeen(index, feature);
}
return badges;
}
/**
* Define properties for rail vehicles
* @param first Local ID of the first vehicle.
@@ -1354,6 +1400,10 @@ static ChangeInfoResult RailVehicleChangeInfo(uint first, uint last, int prop, B
_gted[e->index].cargo_allowed_required = CargoClasses{buf.ReadWord()};
break;
case 0x33: // Badge list
e->badges = ReadBadgeList(buf, GSF_TRAINS);
break;
default:
ret = CommonVehicleChangeInfo(ei, prop, buf);
break;
@@ -1565,6 +1615,10 @@ static ChangeInfoResult RoadVehicleChangeInfo(uint first, uint last, int prop, B
_gted[e->index].cargo_allowed_required = CargoClasses{buf.ReadWord()};
break;
case 0x2A: // Badge list
e->badges = ReadBadgeList(buf, GSF_ROADVEHICLES);
break;
default:
ret = CommonVehicleChangeInfo(ei, prop, buf);
break;
@@ -1762,6 +1816,10 @@ static ChangeInfoResult ShipVehicleChangeInfo(uint first, uint last, int prop, B
_gted[e->index].cargo_allowed_required = CargoClasses{buf.ReadWord()};
break;
case 0x26: // Badge list
e->badges = ReadBadgeList(buf, GSF_SHIPS);
break;
default:
ret = CommonVehicleChangeInfo(ei, prop, buf);
break;
@@ -1937,6 +1995,10 @@ static ChangeInfoResult AircraftVehicleChangeInfo(uint first, uint last, int pro
_gted[e->index].cargo_allowed_required = CargoClasses{buf.ReadWord()};
break;
case 0x24: // Badge list
e->badges = ReadBadgeList(buf, GSF_AIRCRAFT);
break;
default:
ret = CommonVehicleChangeInfo(ei, prop, buf);
break;
@@ -2217,6 +2279,10 @@ static ChangeInfoResult StationChangeInfo(uint first, uint last, int prop, ByteR
break;
}
case 0x1F: // Badge list
statspec->badges = ReadBadgeList(buf, GSF_STATIONS);
break;
default:
ret = CIR_UNKNOWN;
break;
@@ -2698,6 +2764,10 @@ static ChangeInfoResult TownHouseChangeInfo(uint first, uint last, int prop, Byt
break;
}
case 0x24: // Badge list
housespec->badges = ReadBadgeList(buf, GSF_HOUSES);
break;
default:
ret = CIR_UNKNOWN;
break;
@@ -2760,6 +2830,23 @@ static ChangeInfoResult LoadTranslationTable(uint first, uint last, ByteReader &
return CIR_SUCCESS;
}
static ChangeInfoResult LoadBadgeTranslationTable(uint first, uint last, ByteReader &buf, std::vector<BadgeID> &translation_table, const char *name)
{
if (first != 0 && first != std::size(translation_table)) {
GrfMsg(1, "LoadBadgeTranslationTable: {} translation table must start at zero or {}", name, std::size(translation_table));
return CIR_INVALID_ID;
}
if (first == 0) translation_table.clear();
translation_table.reserve(last);
for (uint id = first; id < last; ++id) {
std::string_view label = buf.ReadString();
translation_table.push_back(GetOrCreateBadge(label).index);
}
return CIR_SUCCESS;
}
/**
* Helper to read a DWord worth of bytes from the reader
* and to return it as a valid string.
@@ -2797,6 +2884,9 @@ static ChangeInfoResult GlobalVarChangeInfo(uint first, uint last, int prop, Byt
case 0x17: // Tram type translation table; loading during both reservation and activation stage (in case it is selected depending on defined tramtypes)
return LoadTranslationTable<RoadTypeLabel>(first, last, buf, [](GRFFile &grf) -> std::vector<RoadTypeLabel> & { return grf.tramtype_list; }, "Tram type");
case 0x18: // Badge translation table
return LoadBadgeTranslationTable(first, last, buf, _cur.grffile->badge_list, "Badge");
default:
break;
}
@@ -3020,6 +3110,9 @@ static ChangeInfoResult GlobalVarReserveInfo(uint first, uint last, int prop, By
case 0x17: // Tram type translation table; loading during both reservation and activation stage (in case it is selected depending on defined tramtypes)
return LoadTranslationTable<RoadTypeLabel>(first, last, buf, [](GRFFile &grf) -> std::vector<RoadTypeLabel> & { return grf.tramtype_list; }, "Tram type");
case 0x18: // Badge translation table
return LoadBadgeTranslationTable(first, last, buf, _cur.grffile->badge_list, "Badge");
default:
break;
}
@@ -3455,6 +3548,10 @@ static ChangeInfoResult IndustrytilesChangeInfo(uint first, uint last, int prop,
break;
}
case 0x14: // Badge list
tsp->badges = ReadBadgeList(buf, GSF_INDUSTRYTILES);
break;
default:
ret = CIR_UNKNOWN;
break;
@@ -3548,6 +3645,10 @@ static ChangeInfoResult IgnoreIndustryProperty(int prop, ByteReader &buf)
break;
}
case 0x29: // Badge list
SkipBadgeList(buf);
break;
default:
ret = CIR_UNKNOWN;
break;
@@ -3951,6 +4052,10 @@ static ChangeInfoResult IndustriesChangeInfo(uint first, uint last, int prop, By
break;
}
case 0x29: // Badge list
indsp->badges = ReadBadgeList(buf, GSF_INDUSTRIES);
break;
default:
ret = CIR_UNKNOWN;
break;
@@ -4106,6 +4211,10 @@ static ChangeInfoResult AirportChangeInfo(uint first, uint last, int prop, ByteR
as->maintenance_cost = buf.ReadWord();
break;
case 0x12: // Badge list
as->badges = ReadBadgeList(buf, GSF_AIRPORTS);
break;
default:
ret = CIR_UNKNOWN;
break;
@@ -4152,6 +4261,10 @@ static ChangeInfoResult IgnoreObjectProperty(uint prop, ByteReader &buf)
buf.ReadDWord();
break;
case 0x19: // Badge list
SkipBadgeList(buf);
break;
default:
ret = CIR_UNKNOWN;
break;
@@ -4281,6 +4394,10 @@ static ChangeInfoResult ObjectChangeInfo(uint first, uint last, int prop, ByteRe
spec->generate_amount = buf.ReadByte();
break;
case 0x19: // Badge list
spec->badges = ReadBadgeList(buf, GSF_OBJECTS);
break;
default:
ret = CIR_UNKNOWN;
break;
@@ -4419,6 +4536,10 @@ static ChangeInfoResult RailTypeChangeInfo(uint first, uint last, int prop, Byte
for (int j = buf.ReadByte(); j != 0; j--) buf.ReadDWord();
break;
case 0x1E: // Badge list
rti->badges = ReadBadgeList(buf, GSF_RAILTYPES);
break;
default:
ret = CIR_UNKNOWN;
break;
@@ -4499,6 +4620,10 @@ static ChangeInfoResult RailTypeReserveInfo(uint first, uint last, int prop, Byt
buf.ReadDWord();
break;
case 0x1E: // Badge list
SkipBadgeList(buf);
break;
default:
ret = CIR_UNKNOWN;
break;
@@ -4624,6 +4749,10 @@ static ChangeInfoResult RoadTypeChangeInfo(uint first, uint last, int prop, Byte
for (int j = buf.ReadByte(); j != 0; j--) buf.ReadDWord();
break;
case 0x1E: // Badge list
rti->badges = ReadBadgeList(buf, GSF_ROADTYPES);
break;
default:
ret = CIR_UNKNOWN;
break;
@@ -4713,6 +4842,10 @@ static ChangeInfoResult RoadTypeReserveInfo(uint first, uint last, int prop, Byt
buf.ReadDWord();
break;
case 0x1E: // Badge list
SkipBadgeList(buf);
break;
default:
ret = CIR_UNKNOWN;
break;
@@ -4808,6 +4941,10 @@ static ChangeInfoResult AirportTilesChangeInfo(uint first, uint last, int prop,
tsp->animation.triggers = buf.ReadByte();
break;
case 0x12: // Badge list
tsp->badges = ReadBadgeList(buf, GSF_TRAMTYPES);
break;
default:
ret = CIR_UNKNOWN;
break;
@@ -4849,6 +4986,10 @@ static ChangeInfoResult IgnoreRoadStopProperty(uint prop, ByteReader &buf)
buf.ReadDWord();
break;
case 0x16: // Badge list
SkipBadgeList(buf);
break;
default:
ret = CIR_UNKNOWN;
break;
@@ -4857,6 +4998,45 @@ static ChangeInfoResult IgnoreRoadStopProperty(uint prop, ByteReader &buf)
return ret;
}
static ChangeInfoResult BadgeChangeInfo(uint first, uint last, int prop, ByteReader &buf)
{
ChangeInfoResult ret = CIR_SUCCESS;
if (last >= UINT16_MAX) {
GrfMsg(1, "BadgeChangeInfo: Tag {} is invalid, max {}, ignoring", last, UINT16_MAX - 1);
return CIR_INVALID_ID;
}
for (uint id = first; id < last; ++id) {
auto it = _cur.grffile->badge_map.find(id);
if (prop != 0x08 && it == std::end(_cur.grffile->badge_map)) {
GrfMsg(1, "BadgeChangeInfo: Attempt to modify undefined tag {}, ignoring", id);
return CIR_INVALID_ID;
}
Badge *badge = nullptr;
if (prop != 0x08) badge = GetBadge(it->second);
switch (prop) {
case 0x08: { // Label
std::string_view label = buf.ReadString();
_cur.grffile->badge_map[id] = GetOrCreateBadge(label).index;
break;
}
case 0x09: // Flags
badge->flags = static_cast<BadgeFlags>(buf.ReadDWord());
break;
default:
ret = CIR_UNKNOWN;
break;
}
}
return ret;
}
static ChangeInfoResult RoadStopChangeInfo(uint first, uint last, int prop, ByteReader &buf)
{
ChangeInfoResult ret = CIR_SUCCESS;
@@ -4935,6 +5115,10 @@ static ChangeInfoResult RoadStopChangeInfo(uint first, uint last, int prop, Byte
rs->clear_cost_multiplier = buf.ReadByte();
break;
case 0x16: // Badge list
rs->badges = ReadBadgeList(buf, GSF_ROADSTOPS);
break;
default:
ret = CIR_UNKNOWN;
break;
@@ -5009,6 +5193,7 @@ static void FeatureChangeInfo(ByteReader &buf)
/* GSF_ROADTYPES */ RoadTypeChangeInfo,
/* GSF_TRAMTYPES */ TramTypeChangeInfo,
/* GSF_ROADSTOPS */ RoadStopChangeInfo,
/* GSF_BADGES */ BadgeChangeInfo,
};
static_assert(GSF_END == std::size(handler));
@@ -5439,6 +5624,7 @@ static void NewSpriteGroup(ByteReader &buf)
case GSF_RAILTYPES:
case GSF_ROADTYPES:
case GSF_TRAMTYPES:
case GSF_BADGES:
{
uint8_t num_loaded = type;
uint8_t num_loading = buf.ReadByte();
@@ -6211,6 +6397,56 @@ static void RoadStopMapSpriteGroup(ByteReader &buf, uint8_t idcount)
}
}
static void BadgeMapSpriteGroup(ByteReader &buf, uint8_t idcount)
{
if (_cur.grffile->badge_map.empty()) {
GrfMsg(1, "BadgeMapSpriteGroup: No badges defined, skipping");
return;
}
std::vector<uint16_t> local_ids;
local_ids.reserve(idcount);
for (uint i = 0; i < idcount; i++) {
local_ids.push_back(buf.ReadExtendedByte());
}
uint8_t cidcount = buf.ReadByte();
for (uint c = 0; c < cidcount; c++) {
uint8_t ctype = buf.ReadByte();
uint16_t groupid = buf.ReadWord();
if (!IsValidGroupID(groupid, "BadgeMapSpriteGroup")) continue;
if (ctype >= GSF_END) continue;
for (const auto &local_id : local_ids) {
auto found = _cur.grffile->badge_map.find(local_id);
if (found == std::end(_cur.grffile->badge_map)) {
GrfMsg(1, "BadgeMapSpriteGroup: Badge {} undefined, skipping", local_id);
continue;
}
auto &badge = *GetBadge(found->second);
badge.grf_prop.SetSpriteGroup(ctype, _cur.spritegroups[groupid]);
}
}
uint16_t groupid = buf.ReadWord();
if (!IsValidGroupID(groupid, "BadgeMapSpriteGroup")) return;
for (auto &local_id : local_ids) {
auto found = _cur.grffile->badge_map.find(local_id);
if (found == std::end(_cur.grffile->badge_map)) {
GrfMsg(1, "BadgeMapSpriteGroup: Badge {} undefined, skipping", local_id);
continue;
}
auto &badge = *GetBadge(found->second);
badge.grf_prop.SetSpriteGroup(GSF_END, _cur.spritegroups[groupid]);
badge.grf_prop.grffile = _cur.grffile;
badge.grf_prop.local_id = local_id;
}
}
/* Action 0x03 */
static void FeatureMapSpriteGroup(ByteReader &buf)
{
@@ -6314,6 +6550,10 @@ static void FeatureMapSpriteGroup(ByteReader &buf)
RoadStopMapSpriteGroup(buf, idcount);
return;
case GSF_BADGES:
BadgeMapSpriteGroup(buf, idcount);
break;
default:
GrfMsg(1, "FeatureMapSpriteGroup: Unsupported feature 0x{:02X}, skipping", feature);
return;
@@ -6353,7 +6593,7 @@ static void FeatureNewName(ByteReader &buf)
uint16_t id;
if (generic) {
id = buf.ReadWord();
} else if (feature <= GSF_AIRCRAFT) {
} else if (feature <= GSF_AIRCRAFT || feature == GSF_BADGES) {
id = buf.ReadExtendedByte();
} else {
id = buf.ReadByte();
@@ -6388,6 +6628,21 @@ static void FeatureNewName(ByteReader &buf)
}
break;
case GSF_BADGES: {
if (!generic) {
auto found = _cur.grffile->badge_map.find(id);
if (found == std::end(_cur.grffile->badge_map)) {
GrfMsg(1, "FeatureNewName: Attempt to name undefined badge 0x{:X}, ignoring", id);
} else {
Badge &badge = *GetBadge(found->second);
badge.name = AddGRFString(_cur.grffile->grfid, GRFStringID{feature_overlay | id}, lang, true, false, name, STR_UNDEFINED);
}
} else {
AddGRFString(_cur.grffile->grfid, GRFStringID{id}, lang, new_scheme, true, name, STR_UNDEFINED);
}
break;
}
default:
if (IsInsideMM(id, 0xD000, 0xD400) || IsInsideMM(id, 0xD800, 0x10000)) {
AddGRFString(_cur.grffile->grfid, GRFStringID{id}, lang, new_scheme, true, name, STR_UNDEFINED);
@@ -8811,6 +9066,8 @@ void ResetNewGRFData()
CleanUpStrings();
CleanUpGRFTownNames();
ResetBadges();
/* Copy/reset original engine info data */
SetupEngines();
@@ -9253,6 +9510,12 @@ static void FinaliseEngineArray()
if (!e->info.climates.Test(_settings_game.game_creation.landscape)) continue;
switch (e->type) {
case VEH_TRAIN: AppendCopyableBadgeList(e->badges, GetRailTypeInfo(e->u.rail.railtype)->badges, GSF_TRAINS); break;
case VEH_ROAD: AppendCopyableBadgeList(e->badges, GetRoadTypeInfo(e->u.road.roadtype)->badges, GSF_ROADVEHICLES); break;
default: break;
}
/* Skip wagons, there livery is defined via the engine */
if (e->type != VEH_TRAIN || e->u.rail.railveh_type != RAILVEH_WAGON) {
LiveryScheme ls = GetEngineLiveryScheme(e->index, EngineID::Invalid(), nullptr);
@@ -9944,6 +10207,42 @@ static void FinalisePriceBaseMultipliers()
}
}
template <typename T>
void AddBadgeToSpecs(T &specs, GrfSpecFeature feature, Badge &badge)
{
for (auto &spec : specs) {
if (spec == nullptr) continue;
spec->badges.push_back(badge.index);
badge.features.Set(feature);
}
}
/** Finish up applying badges to things */
static void FinaliseBadges()
{
for (GRFFile * const file : _grf_files) {
Badge *badge = GetBadgeByLabel(fmt::format("newgrf/{:08x}", std::byteswap(file->grfid)));
if (badge == nullptr) continue;
for (Engine *e : Engine::Iterate()) {
if (e->grf_prop.grffile != file) continue;
e->badges.push_back(badge->index);
badge->features.Set(static_cast<GrfSpecFeature>(GSF_TRAINS + e->type));
}
AddBadgeToSpecs(file->stations, GSF_STATIONS, *badge);
AddBadgeToSpecs(file->housespec, GSF_HOUSES, *badge);
AddBadgeToSpecs(file->industryspec, GSF_INDUSTRIES, *badge);
AddBadgeToSpecs(file->indtspec, GSF_INDUSTRYTILES, *badge);
AddBadgeToSpecs(file->objectspec, GSF_OBJECTS, *badge);
AddBadgeToSpecs(file->airportspec, GSF_AIRPORTS, *badge);
AddBadgeToSpecs(file->airtspec, GSF_AIRPORTTILES, *badge);
AddBadgeToSpecs(file->roadstops, GSF_ROADSTOPS, *badge);
}
ApplyBadgeFeaturesToClassBadges();
}
extern void InitGRFTownGeneratorNames();
/** Finish loading NewGRFs and execute needed post-processing */
@@ -9961,6 +10260,8 @@ static void AfterLoadGRFs()
/* Clear the action 6 override sprites. */
_grf_line_to_action6_sprite_override.clear();
FinaliseBadges();
/* Polish cargoes */
FinaliseCargoArray();