diff --git a/docs/landscape.html b/docs/landscape.html index 8fc837d3a2..47a8b52aad 100644 --- a/docs/landscape.html +++ b/docs/landscape.html @@ -1057,6 +1057,7 @@
  • m8 bits 14..12: Road cached one way state
  • m8 bits 11..6: Tramtype
  • m8 bits 5..0: track type for railway stations/waypoints
  • +
  • m8 bits 5..0: custom road stop id; 0 means standard graphics
  • diff --git a/docs/landscape_grid.html b/docs/landscape_grid.html index b856c1b61e..529f4c29e6 100644 --- a/docs/landscape_grid.html +++ b/docs/landscape_grid.html @@ -207,12 +207,12 @@ the array so you can quickly see what is used and what is not. OOOO OXXX OPXXX OOO OOOX XXXX - OPPP XXXX XXOO OOOO + OPPP XXXX XXPP PPPP road waypoint XXXX PP PP - P PPP XXXX XXOO OOOO + P PPP XXXX XXPP PPPP airport diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 69cef607b2..5acaa50067 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -280,6 +280,8 @@ add_files( newgrf_properties.h newgrf_railtype.cpp newgrf_railtype.h + newgrf_roadstop.cpp + newgrf_roadstop.h newgrf_roadtype.cpp newgrf_roadtype.h newgrf_sound.cpp diff --git a/src/base_station_base.h b/src/base_station_base.h index 1666e21f2f..c9385e24c1 100644 --- a/src/base_station_base.h +++ b/src/base_station_base.h @@ -27,6 +27,12 @@ struct StationSpecList { uint8 localidx; ///< Station ID within GRF of station }; +struct RoadStopSpecList { + const RoadStopSpec *spec; + uint32 grfid; ///< GRF ID of this custom road stop + uint8 localidx; ///< Station ID within GRF of road stop +}; + /** StationRect - used to track station spread out rectangle - cheaper than scanning whole map */ struct StationRect : public Rect { @@ -66,7 +72,9 @@ struct BaseStation : StationPool::PoolItem<&_station_pool> { StationFacility facilities; ///< The facilities that this station has uint8 num_specs; ///< Number of specs in the speclist + uint8 num_roadstop_specs; ///< Number of road stop specs in the roadstop_speclist StationSpecList *speclist; ///< List of station specs of this station + RoadStopSpecList *roadstop_speclist; ///< List of road stop specs of this station Date build_date; ///< Date of construction @@ -74,10 +82,14 @@ struct BaseStation : StationPool::PoolItem<&_station_pool> { byte waiting_triggers; ///< Waiting triggers (NewGRF) for this station uint8 cached_anim_triggers; ///< NOSAVE: Combined animation trigger bitmask, used to determine if trigger processing should happen. CargoTypes cached_cargo_triggers; ///< NOSAVE: Combined cargo trigger bitmask + CargoTypes roadstop_cached_cargo_triggers; ///< NOSAVE: Combined cargo trigger bitmask for road stops TileArea train_station; ///< Tile area the train 'station' part covers StationRect rect; ///< NOSAVE: Station spread out rectangle maintained by StationRect::xxx() functions + std::vector custom_road_stop_tiles; ///< List of custom road stop tiles + std::vector custom_road_stop_random_bits; ///< Custom road stop random bits in same order as custom_road_stop_tiles + /** * Initialize the base station. * @param tile The location of the station sign @@ -181,6 +193,17 @@ struct BaseStation : StationPool::PoolItem<&_station_pool> { return (this->facilities & facilities) != 0; } + inline byte GetRoadStopRandomBits(TileIndex tile) const + { + for (size_t i = 0; i < this->custom_road_stop_tiles.size(); i++) { + if (this->custom_road_stop_tiles[i] == tile) return this->custom_road_stop_random_bits[i]; + } + return 0; + } + + void SetRoadStopRandomBits(TileIndex tile, byte random_bits); + void RemoveRoadStopRandomBits(TileIndex tile); + static void PostDestructor(size_t index); private: diff --git a/src/cheat_gui.cpp b/src/cheat_gui.cpp index 072c18b5c3..bcb309633e 100644 --- a/src/cheat_gui.cpp +++ b/src/cheat_gui.cpp @@ -116,6 +116,7 @@ static int32 ClickChangeDateCheat(int32 p1, int32 p2) SetDate(new_date, _date_fract); EnginesMonthlyLoop(); InvalidateWindowClassesData(WC_BUILD_STATION, 0); + InvalidateWindowClassesData(WC_BUS_STATION, 0); InvalidateWindowClassesData(WC_BUILD_OBJECT, 0); ResetSignalVariant(); MarkWholeScreenDirty(); diff --git a/src/command.cpp b/src/command.cpp index f3274b4b4c..d406967b39 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -67,12 +67,12 @@ CommandProc CmdBuildTunnel; CommandProc CmdBuildTrainDepot; CommandProcEx CmdBuildRailWaypoint; -CommandProc CmdBuildRoadWaypoint; +CommandProcEx CmdBuildRoadWaypoint; CommandProc CmdRenameWaypoint; CommandProc CmdSetWaypointLabelHidden; CommandProc CmdRemoveFromRailWaypoint; -CommandProc CmdBuildRoadStop; +CommandProcEx CmdBuildRoadStop; CommandProc CmdRemoveRoadStop; CommandProc CmdBuildLongRoad; diff --git a/src/date.cpp b/src/date.cpp index 7a5d99bd19..a330f1ce94 100644 --- a/src/date.cpp +++ b/src/date.cpp @@ -215,6 +215,8 @@ static void OnNewYear() VehiclesYearlyLoop(); TownsYearlyLoop(); InvalidateWindowClassesData(WC_BUILD_STATION); + InvalidateWindowClassesData(WC_BUS_STATION); + InvalidateWindowClassesData(WC_TRUCK_STATION); if (_network_server) NetworkServerYearlyLoop(); if (_cur_date_ymd.year == _settings_client.gui.semaphore_build_before) ResetSignalVariant(); diff --git a/src/economy.cpp b/src/economy.cpp index 2a70ffd20e..3b18aec3e0 100644 --- a/src/economy.cpp +++ b/src/economy.cpp @@ -26,6 +26,7 @@ #include "newgrf_industrytiles.h" #include "newgrf_station.h" #include "newgrf_airporttiles.h" +#include "newgrf_roadstop.h" #include "object.h" #include "strings_func.h" #include "date_func.h" @@ -2146,6 +2147,7 @@ static void LoadUnloadVehicle(Vehicle *front) TriggerStationRandomisation(st, st->xy, SRT_CARGO_TAKEN, v->cargo_type); TriggerStationAnimation(st, st->xy, SAT_CARGO_TAKEN, v->cargo_type); AirportAnimationTrigger(st, AAT_STATION_CARGO_TAKEN, v->cargo_type); + TriggerRoadStopRandomisation(st, st->xy, RSRT_CARGO_TAKEN, v->cargo_type); } new_load_unload_ticks += loaded; @@ -2166,6 +2168,8 @@ static void LoadUnloadVehicle(Vehicle *front) if (front->type == VEH_TRAIN) { TriggerStationRandomisation(st, station_tile, SRT_TRAIN_LOADS); TriggerStationAnimation(st, station_tile, SAT_TRAIN_LOADS); + } else if (front->type == VEH_ROAD) { + TriggerRoadStopRandomisation(st, station_tile, RSRT_VEH_LOADS); } } diff --git a/src/newgrf.cpp b/src/newgrf.cpp index 05b22ecd27..3c54e88fca 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -48,6 +48,7 @@ #include "language.h" #include "vehicle_base.h" #include "road.h" +#include "newgrf_roadstop.h" #include "table/strings.h" #include "table/build_industry.h" @@ -4923,6 +4924,120 @@ static ChangeInfoResult AirportTilesChangeInfo(uint airtid, int numinfo, int pro return ret; } +/** + * Ignore properties for roadstops + * @param prop The property to ignore. + * @param buf The property value. + * @return ChangeInfoResult. + */ +static ChangeInfoResult IgnoreRoadStopProperty(uint prop, ByteReader *buf) +{ + ChangeInfoResult ret = CIR_SUCCESS; + + switch (prop) { + case 0x09: + case 0x0C: + buf->ReadByte(); + break; + + case 0x0A: + case 0x0B: + buf->ReadWord(); + break; + + case 0x08: + case 0x0D: + buf->ReadDWord(); + break; + + default: + ret = HandleAction0PropertyDefault(buf, prop); + break; + } + + return ret; +} + +static ChangeInfoResult RoadStopChangeInfo(uint id, int numinfo, int prop, const GRFFilePropertyRemapEntry *mapping_entry, ByteReader *buf) +{ + ChangeInfoResult ret = CIR_SUCCESS; + + if (id + numinfo > 255) { + grfmsg(1, "RoadStopChangeInfo: RoadStop %u is invalid, max %u, ignoring", id + numinfo, 255); + return CIR_INVALID_ID; + } + + if (_cur.grffile->roadstops == nullptr) _cur.grffile->roadstops = CallocT(255); + + for (int i = 0; i < numinfo; i++) { + RoadStopSpec *rs = _cur.grffile->roadstops[id + i]; + + if (rs == nullptr && prop != 0x08 && prop != A0RPI_ROADSTOP_CLASS_ID) { + grfmsg(1, "RoadStopChangeInfo: Attempt to modify undefined road stop %u, ignoring", id + i); + ChangeInfoResult cir = IgnoreRoadStopProperty(prop, buf); + if (cir > ret) ret = cir; + continue; + } + + switch (prop) { + case A0RPI_ROADSTOP_CLASS_ID: + if (MappedPropertyLengthMismatch(buf, 4, mapping_entry)) break; + FALLTHROUGH; + case 0x08: { // Road Stop Class ID + RoadStopSpec **spec = &_cur.grffile->roadstops[id + i]; + + if (*spec == nullptr) *spec = CallocT(1); + + uint32 classid = buf->ReadDWord(); + (*spec)->cls_id = RoadStopClass::Allocate(BSWAP32(classid)); + (*spec)->spec_id = id + i; + break; + } + + case A0RPI_ROADSTOP_STOP_TYPE: + if (MappedPropertyLengthMismatch(buf, 4, mapping_entry)) break; + FALLTHROUGH; + case 0x09: // Road stop type + rs->stop_type = (RoadStopAvailabilityType)buf->ReadByte(); + break; + + case A0RPI_ROADSTOP_STOP_NAME: + if (MappedPropertyLengthMismatch(buf, 2, mapping_entry)) break; + FALLTHROUGH; + case 0x0A: // Road Stop Name + AddStringForMapping(buf->ReadWord(), &rs->name); + break; + + case A0RPI_ROADSTOP_CLASS_NAME: + if (MappedPropertyLengthMismatch(buf, 2, mapping_entry)) break; + FALLTHROUGH; + case 0x0B: // Road Stop Class name + AddStringForMapping(buf->ReadWord(), &RoadStopClass::Get(rs->cls_id)->name); + break; + + case A0RPI_ROADSTOP_DRAW_MODE: + if (MappedPropertyLengthMismatch(buf, 1, mapping_entry)) break; + FALLTHROUGH; + case 0x0C: // The draw mode + rs->draw_mode = (RoadStopDrawMode)buf->ReadByte(); + break; + + case A0RPI_ROADSTOP_TRIGGER_CARGOES: + if (MappedPropertyLengthMismatch(buf, 4, mapping_entry)) break; + FALLTHROUGH; + case 0x0D: // Cargo types for random triggers + rs->cargo_triggers = TranslateRefitMask(buf->ReadDWord()); + break; + + default: + ret = CIR_UNKNOWN; + break; + } + } + + return ret; +} + static bool HandleChangeInfoResult(const char *caller, ChangeInfoResult cir, GrfSpecFeature feature, int property) { switch (cir) { @@ -5002,6 +5117,7 @@ static const char *_feature_names[] = { "AIRPORTTILES", "ROADTYPES", "TRAMTYPES", + "ROADSTOPS", }; static_assert(lengthof(_feature_names) == GSF_END); @@ -5106,6 +5222,7 @@ static void FeatureChangeInfo(ByteReader *buf) /* GSF_AIRPORTTILES */ AirportTilesChangeInfo, /* GSF_ROADTYPES */ RoadTypeChangeInfo, /* GSF_TRAMTYPES */ TramTypeChangeInfo, + /* GSF_ROADSTOPS */ RoadStopChangeInfo, }; static_assert(GSF_END == lengthof(handler)); static_assert(lengthof(handler) == lengthof(_cur.grffile->action0_property_remaps), "Action 0 feature list length mismatch"); @@ -5616,7 +5733,8 @@ static void NewSpriteGroup(ByteReader *buf) case GSF_HOUSES: case GSF_AIRPORTTILES: case GSF_OBJECTS: - case GSF_INDUSTRYTILES: { + case GSF_INDUSTRYTILES: + case GSF_ROADSTOPS: { byte num_building_sprites = std::max((uint8)1, type); assert(TileLayoutSpriteGroup::CanAllocateItem()); @@ -6283,6 +6401,44 @@ static void AirportTileMapSpriteGroup(ByteReader *buf, uint8 idcount) } } +static void RoadStopMapSpriteGroup(ByteReader *buf, uint8 idcount) +{ + uint8 *roadstops = AllocaM(uint8, idcount); + for (uint i = 0; i < idcount; i++) { + roadstops[i] = buf->ReadByte(); + } + + /* Skip the cargo type section, we only care about the default group */ + uint8 cidcount = buf->ReadByte(); + buf->Skip(cidcount * 3); + + uint16 groupid = buf->ReadWord(); + if (!IsValidGroupID(groupid, "RoadStopMapSpriteGroup")) return; + + if (_cur.grffile->roadstops == nullptr) { + grfmsg(0, "RoadStopMapSpriteGroup: No roadstops defined, skipping."); + return; + } + + for (uint i = 0; i < idcount; i++) { + RoadStopSpec *roadstopspec = _cur.grffile->roadstops == nullptr ? nullptr : _cur.grffile->roadstops[roadstops[i]]; + + if (roadstopspec == nullptr) { + grfmsg(1, "RoadStopMapSpriteGroup: Road stop with ID 0x%02X does not exist, skipping.", roadstops[i]); + continue; + } + + if (roadstopspec->grf_prop.grffile != nullptr) { + grfmsg(1, "RoadStopMapSpriteGroup: Road stop with ID 0x%02X mapped multiple times, skipping", roadstops[i]); + continue; + } + + roadstopspec->grf_prop.spritegroup[CT_DEFAULT] = _cur.spritegroups[groupid]; + roadstopspec->grf_prop.grffile = _cur.grffile; + roadstopspec->grf_prop.local_id = roadstops[i]; + RoadStopClass::Assign(roadstopspec); + } +} /* Action 0x03 */ static void FeatureMapSpriteGroup(ByteReader *buf) @@ -6388,6 +6544,10 @@ static void FeatureMapSpriteGroup(ByteReader *buf) AirportTileMapSpriteGroup(buf, idcount); return; + case GSF_ROADSTOPS: + RoadStopMapSpriteGroup(buf, idcount); + return; + default: grfmsg(1, "FeatureMapSpriteGroup: Unsupported feature %s, skipping", GetFeatureString(feature_ref)); return; @@ -9620,6 +9780,20 @@ static void ResetCustomObjects() } } +static void ResetCustomRoadStops() +{ + for (auto file : _grf_files) { + RoadStopSpec **&roadstopspec = file->roadstops; + if (roadstopspec == nullptr) continue; + for (uint i = 0; i < NUM_ROADSTOPS_PER_GRF; i++) { + free(roadstopspec[i]); + } + + free(roadstopspec); + roadstopspec = nullptr; + } +} + /** Reset and clear all NewGRFs */ static void ResetNewGRF() { @@ -9707,6 +9881,10 @@ void ResetNewGRFData() AirportSpec::ResetAirports(); AirportTileSpec::ResetAirportTiles(); + /* Reset road stop classes */ + RoadStopClass::Reset(); + ResetCustomRoadStops(); + /* Reset canal sprite groups and flags */ memset(_water_feature, 0, sizeof(_water_feature)); diff --git a/src/newgrf.h b/src/newgrf.h index fb1a70b8f5..1ee20a8dd5 100644 --- a/src/newgrf.h +++ b/src/newgrf.h @@ -87,9 +87,11 @@ enum GrfSpecFeature { GSF_AIRPORTTILES, GSF_ROADTYPES, GSF_TRAMTYPES, + + GSF_ROADSTOPS, GSF_END, - GSF_REAL_FEATURE_END = GSF_END, + GSF_REAL_FEATURE_END = GSF_ROADSTOPS, GSF_FAKE_TOWNS = GSF_END, ///< Fake town GrfSpecFeature for NewGRF debugging (parent scope) GSF_FAKE_STATION_STRUCT, ///< Fake station struct GrfSpecFeature for NewGRF debugging @@ -302,6 +304,7 @@ struct GRFFile : ZeroedMemoryAllocator { struct ObjectSpec **objectspec; struct AirportSpec **airportspec; struct AirportTileSpec **airtspec; + struct RoadStopSpec **roadstops; GRFFeatureMapRemapSet feature_id_remaps; GRFFilePropertyRemapSet action0_property_remaps[GSF_END]; diff --git a/src/newgrf_commons.cpp b/src/newgrf_commons.cpp index 8717cb798b..ee6964dab9 100644 --- a/src/newgrf_commons.cpp +++ b/src/newgrf_commons.cpp @@ -27,6 +27,7 @@ #include "company_base.h" #include "error.h" #include "strings_func.h" +#include "newgrf_roadstop.h" #include "table/strings.h" diff --git a/src/newgrf_extension.cpp b/src/newgrf_extension.cpp index 9112ad4078..8b731d6299 100644 --- a/src/newgrf_extension.cpp +++ b/src/newgrf_extension.cpp @@ -51,11 +51,13 @@ extern const GRFFeatureInfo _grf_feature_list[] = { GRFFeatureInfo("action0_object_edge_foundation_mode", 2), GRFFeatureInfo("action0_object_flood_resistant", 1), GRFFeatureInfo("action0_object_viewport_map_tile_type", 1), + GRFFeatureInfo("road_stops", 1), GRFFeatureInfo(), }; /** Action14 remappable feature list */ extern const GRFFeatureMapDefinition _grf_remappable_features[] = { + GRFFeatureMapDefinition(GSF_ROADSTOPS, "road_stops"), GRFFeatureMapDefinition(), }; @@ -88,6 +90,12 @@ extern const GRFPropertyMapDefinition _grf_action0_remappable_properties[] = { GRFPropertyMapDefinition(GSF_OBJECTS, A0RPI_OBJECT_FLOOD_RESISTANT, "object_flood_resistant"), GRFPropertyMapDefinition(GSF_OBJECTS, A0RPI_OBJECT_VIEWPORT_MAP_TYPE, "object_viewport_map_tile_type"), GRFPropertyMapDefinition(GSF_OBJECTS, A0RPI_OBJECT_VIEWPORT_MAP_SUBTYPE, "object_viewport_map_tile_subtype"), + GRFPropertyMapDefinition(GSF_ROADSTOPS, A0RPI_ROADSTOP_CLASS_ID, "roadstop_class_id"), + GRFPropertyMapDefinition(GSF_ROADSTOPS, A0RPI_ROADSTOP_STOP_TYPE, "roadstop_stop_type"), + GRFPropertyMapDefinition(GSF_ROADSTOPS, A0RPI_ROADSTOP_STOP_NAME, "roadstop_stop_name"), + GRFPropertyMapDefinition(GSF_ROADSTOPS, A0RPI_ROADSTOP_CLASS_NAME, "roadstop_class_name"), + GRFPropertyMapDefinition(GSF_ROADSTOPS, A0RPI_ROADSTOP_DRAW_MODE, "roadstop_draw_mode"), + GRFPropertyMapDefinition(GSF_ROADSTOPS, A0RPI_ROADSTOP_TRIGGER_CARGOES, "roadstop_trigger_cargoes"), GRFPropertyMapDefinition(), }; @@ -95,6 +103,13 @@ extern const GRFPropertyMapDefinition _grf_action0_remappable_properties[] = { extern const GRFVariableMapDefinition _grf_action2_remappable_variables[] = { GRFVariableMapDefinition(GSF_OBJECTS, A2VRI_OBJECT_FOUNDATION_SLOPE, "object_foundation_tile_slope"), GRFVariableMapDefinition(GSF_OBJECTS, A2VRI_OBJECT_FOUNDATION_SLOPE_CHANGE, "object_foundation_change_tile_slope"), + GRFVariableMapDefinition(GSF_ROADSTOPS, 0x40, "roadstop_view"), + GRFVariableMapDefinition(GSF_ROADSTOPS, 0x41, "roadstop_type"), + GRFVariableMapDefinition(GSF_ROADSTOPS, 0x42, "roadstop_terrain_type"), + GRFVariableMapDefinition(GSF_ROADSTOPS, 0x43, "roadstop_road_type"), + GRFVariableMapDefinition(GSF_ROADSTOPS, 0x44, "roadstop_tram_type"), + GRFVariableMapDefinition(GSF_ROADSTOPS, 0x45, "roadstop_town_zone"), + GRFVariableMapDefinition(GSF_ROADSTOPS, 0x46, "roadstop_company_info"), GRFVariableMapDefinition(), }; diff --git a/src/newgrf_extension.h b/src/newgrf_extension.h index ca21497efc..4c243556ad 100644 --- a/src/newgrf_extension.h +++ b/src/newgrf_extension.h @@ -39,6 +39,12 @@ enum Action0RemapPropertyIds { A0RPI_OBJECT_FLOOD_RESISTANT, A0RPI_OBJECT_VIEWPORT_MAP_TYPE, A0RPI_OBJECT_VIEWPORT_MAP_SUBTYPE, + A0RPI_ROADSTOP_CLASS_ID, + A0RPI_ROADSTOP_STOP_TYPE, + A0RPI_ROADSTOP_STOP_NAME, + A0RPI_ROADSTOP_CLASS_NAME, + A0RPI_ROADSTOP_DRAW_MODE, + A0RPI_ROADSTOP_TRIGGER_CARGOES, }; diff --git a/src/newgrf_roadstop.cpp b/src/newgrf_roadstop.cpp new file mode 100644 index 0000000000..86684e4d8b --- /dev/null +++ b/src/newgrf_roadstop.cpp @@ -0,0 +1,438 @@ +/* + * This file is part of OpenTTD. + * 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. + * 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. + * 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 . + */ + +/** @file command.cpp Handling of NewGRF road stops. */ + +#include "stdafx.h" +#include "debug.h" +#include "station_base.h" +#include "roadstop_base.h" +#include "newgrf_roadstop.h" +#include "newgrf_class_func.h" +#include "newgrf_cargo.h" +#include "newgrf_roadtype.h" +#include "gfx_type.h" +#include "company_func.h" +#include "road.h" +#include "window_type.h" +#include "date_func.h" +#include "town.h" +#include "viewport_func.h" + +#include "safeguards.h" + +template +void NewGRFClass::InsertDefaults() +{ + /* Set up initial data */ + classes[0].global_id = 'DFLT'; + classes[0].name = STR_STATION_CLASS_DFLT; + classes[0].Insert(nullptr); + + classes[1].global_id = 'WAYP'; + classes[1].name = STR_STATION_CLASS_WAYP; + classes[1].Insert(nullptr); +} + +template +bool NewGRFClass::IsUIAvailable(uint index) const +{ + return true; +} + +INSTANTIATE_NEWGRF_CLASS_METHODS(RoadStopClass, RoadStopSpec, RoadStopClassID, ROADSTOP_CLASS_MAX) + +static const uint NUM_ROADSTOPSPECS_PER_STATION = 63; ///< Maximum number of parts per station. + +uint32 RoadStopScopeResolver::GetRandomBits() const +{ + if (this->st == nullptr) return 0; + + uint32 bits = this->st->random_bits; + if (this->tile != INVALID_TILE && Station::IsExpected(this->st)) { + bits |= Station::From(this->st)->GetRoadStopRandomBits(this->tile) << 16; + } + return bits; +} + +uint32 RoadStopScopeResolver::GetTriggers() const +{ + return this->st == nullptr ? 0 : this->st->waiting_triggers; +} + +uint32 RoadStopScopeResolver::GetVariable(uint16 variable, uint32 parameter, GetVariableExtra *extra) const +{ + switch (variable) { + case 0x40: return this->view; // view + + case 0x41: // stop type + if (this->type == STATION_BUS) return 0; + if (this->type == STATION_TRUCK) return 1; + return 2; + + case 0x42: return this->tile == INVALID_TILE ? 0 : GetTerrainType(this->tile, TCX_NORMAL); // terrain_type + + case 0x43: return this->tile == INVALID_TILE ? 0 : GetReverseRoadTypeTranslation(GetRoadTypeRoad(this->tile), this->roadstopspec->grf_prop.grffile); // road_type + + case 0x44: return this->tile == INVALID_TILE ? 0 : GetReverseRoadTypeTranslation(GetRoadTypeTram(this->tile), this->roadstopspec->grf_prop.grffile); // tram_type + + case 0x45: { // town_zone + if (this->tile == INVALID_TILE) return HZB_TOWN_EDGE; + const Town *t = ClosestTownFromTile(this->tile, UINT_MAX); + return t != nullptr ? GetTownRadiusGroup(t, this->tile) : HZB_TOWN_EDGE; + } + + case 0x46: return GetCompanyInfo(this->st == nullptr ? _current_company : this->st->owner); // company_type + + case 0xF0: return this->st == nullptr ? 0 : this->st->facilities; // facilities + + case 0xFA: return Clamp((this->st == nullptr ? _date : this->st->build_date) - DAYS_TILL_ORIGINAL_BASE_YEAR, 0, 65535); // build date + } + + if (this->st != nullptr) return this->st->GetNewGRFVariable(this->ro, variable, parameter, &(extra->available)); + + extra->available = false; + return UINT_MAX; +} + +const SpriteGroup *RoadStopResolverObject::ResolveReal(const RealSpriteGroup *group) const +{ + if (group == nullptr) return nullptr; + + return group->loading[0]; +} + +RoadStopResolverObject::RoadStopResolverObject(const RoadStopSpec *roadstopspec, BaseStation *st, TileIndex tile, const RoadTypeInfo *rti, StationType type, uint8 view, + CallbackID callback, uint32 param1, uint32 param2) + : ResolverObject(roadstopspec->grf_prop.grffile, callback, param1, param2), roadstop_scope(*this, st, roadstopspec, tile, rti, type, view) + { + + this->town_scope = nullptr; + this->root_spritegroup = (st == nullptr && roadstopspec->grf_prop.spritegroup[CT_DEFAULT] != nullptr) + ? roadstopspec->grf_prop.spritegroup[CT_DEFAULT] : roadstopspec->grf_prop.spritegroup[CT_DEFAULT]; +} + +RoadStopResolverObject::~RoadStopResolverObject() +{ + delete this->town_scope; +} + +TownScopeResolver* RoadStopResolverObject::GetTown() +{ + if (this->town_scope == nullptr) { + Town *t; + if (this->roadstop_scope.st != nullptr) { + t = this->roadstop_scope.st->town; + } else { + t = ClosestTownFromTile(this->roadstop_scope.tile, UINT_MAX); + } + if (t == nullptr) return nullptr; + this->town_scope = new TownScopeResolver(*this, t, this->roadstop_scope.st == nullptr); + } + return this->town_scope; +} + +/** + * Draw representation of a road stop tile for GUI purposes. + * @param x position x of image. + * @param y position y of image. + * @param image an int offset for the sprite. + * @param roadtype the RoadType of the underlying road. + * @param spec the RoadStop's spec. + * @return true of the tile was drawn (allows for fallback to default graphics) + */ +void DrawRoadStopTile(int x, int y, RoadType roadtype, const RoadStopSpec *spec, StationType type, int view) +{ + assert(roadtype != INVALID_ROADTYPE); + assert(spec != nullptr); + + const RoadTypeInfo *rti = GetRoadTypeInfo(roadtype); + RoadStopResolverObject object(spec, nullptr, INVALID_TILE, rti, type, view); + const SpriteGroup *group = object.Resolve(); + if (group == nullptr || group->type != SGT_TILELAYOUT) return; + const DrawTileSprites *dts = ((const TileLayoutSpriteGroup *)group)->ProcessRegisters(nullptr); + + PaletteID palette = COMPANY_SPRITE_COLOUR(_local_company); + + SpriteID image = dts->ground.sprite; + PaletteID pal = dts->ground.pal; + + if (GB(image, 0, SPRITE_WIDTH) != 0) { + DrawSprite(image, GroundSpritePaletteTransform(image, pal, palette), x, y); + } + + if (view >= 4) { + /* Drive-through stop */ + uint sprite_offset = 5 - view; + + /* Road underlay takes precedence over tram */ + if (HasBit(spec->draw_mode, ROADSTOP_DRAW_MODE_OVERLAY)) { + TileInfo ti {}; + ti.tile = INVALID_TILE; + DrawRoadOverlays(&ti, PAL_NONE, rti, rti, sprite_offset, sprite_offset); + } + + if (rti->UsesOverlay()) { + SpriteID ground = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_GROUND); + DrawSprite(ground + sprite_offset, PAL_NONE, x, y); + + SpriteID overlay = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_OVERLAY); + if (overlay) DrawSprite(overlay + sprite_offset, PAL_NONE, x, y); + } else if (RoadTypeIsTram(roadtype)) { + DrawSprite(SPR_TRAMWAY_TRAM + sprite_offset, PAL_NONE, x, y); + } + } else { + /* Drive-in stop */ + if (rti->UsesOverlay()) { + SpriteID ground = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_ROADSTOP); + DrawSprite(ground + view, PAL_NONE, x, y); + } + } + + DrawCommonTileSeqInGUI(x, y, dts, 0, 0, palette, true); +} + +/** + * Trigger road stop randomisation + * + * @param st the station being triggered + * @param tile the exact tile of the station that should be triggered + * @param trigger trigger type + * @param cargo_type cargo type causing the trigger + */ +void TriggerRoadStopRandomisation(Station *st, TileIndex tile, RoadStopRandomTrigger trigger, CargoID cargo_type) +{ + if (st == nullptr) st = Station::GetByTile(tile); + + /* Check the cached cargo trigger bitmask to see if we need + * to bother with any further processing. */ + if (st->roadstop_cached_cargo_triggers == 0) return; + if (cargo_type != CT_INVALID && !HasBit(st->roadstop_cached_cargo_triggers, cargo_type)) return; + + SetBit(st->waiting_triggers, trigger); + + uint32 whole_reseed = 0; + + CargoTypes empty_mask = 0; + if (trigger == RSRT_CARGO_TAKEN) { + /* Create a bitmask of completely empty cargo types to be matched */ + for (CargoID i = 0; i < NUM_CARGO; i++) { + if (st->goods[i].cargo.TotalCount() == 0) { + SetBit(empty_mask, i); + } + } + } + + uint32 used_triggers = 0; + auto process_tile = [&](TileIndex cur_tile) { + const RoadStopSpec *ss = GetRoadStopSpec(tile); + if (ss == nullptr) return; + + /* Cargo taken "will only be triggered if all of those + * cargo types have no more cargo waiting." */ + if (trigger == RSRT_CARGO_TAKEN) { + if ((ss->cargo_triggers & ~empty_mask) != 0) return; + } + + if (cargo_type == CT_INVALID || HasBit(ss->cargo_triggers, cargo_type)) { + int dir = GetRoadStopDir(cur_tile); + if (IsDriveThroughStopTile(cur_tile)) dir += 4; + + RoadStopResolverObject object(ss, st, cur_tile, nullptr, GetStationType(cur_tile), dir); + object.waiting_triggers = st->waiting_triggers; + + const SpriteGroup *group = object.Resolve(); + if (group == nullptr) return; + + used_triggers |= object.used_triggers; + + uint32 reseed = object.GetReseedSum(); + if (reseed != 0) { + whole_reseed |= reseed; + reseed >>= 16; + + /* Set individual tile random bits */ + uint8 random_bits = st->GetRoadStopRandomBits(cur_tile); + random_bits &= ~reseed; + random_bits |= Random() & reseed; + st->SetRoadStopRandomBits(cur_tile, random_bits); + + MarkTileDirtyByTile(cur_tile, VMDF_NOT_MAP_MODE); + } + } + }; + if (trigger == RSRT_NEW_CARGO || trigger == RSRT_CARGO_TAKEN) { + for (TileIndex cur_tile : st->custom_road_stop_tiles) { + process_tile(cur_tile); + } + } else { + process_tile(tile); + } + + /* Update whole station random bits */ + st->waiting_triggers &= ~used_triggers; + if ((whole_reseed & 0xFFFF) != 0) { + st->random_bits &= ~whole_reseed; + st->random_bits |= Random() & whole_reseed; + } +} + +/** + * Checks if there's any new stations by a specific RoadStopType + * @param rs the RoadStopType to check for. + * @return true if there was any new RoadStopSpec's found for the given RoadStopType, else false. + */ +bool GetIfNewStopsByType(RoadStopType rs) +{ + if (!(RoadStopClass::GetClassCount() > 1 || RoadStopClass::Get(ROADSTOP_CLASS_DFLT)->GetSpecCount() > 1)) return false; + for (uint i = 0; i < RoadStopClass::GetClassCount(); i++) { + // We don't want to check the default or waypoint classes. These classes are always available. + if (i == ROADSTOP_CLASS_DFLT || i == ROADSTOP_CLASS_WAYP) continue; + RoadStopClass *roadstopclass = RoadStopClass::Get((RoadStopClassID)i); + if (GetIfClassHasNewStopsByType(roadstopclass, rs)) return true; + } + return false; +} + +/** + * Checks if the given RoadStopClass has any specs assigned to it, compatible with the given RoadStopType. + * @param roadstopclass the RoadStopClass to check. + * @param rs the RoadStopType to check by. + * @return true if the roadstopclass has any specs compatible with the given RoadStopType. + */ +bool GetIfClassHasNewStopsByType(RoadStopClass *roadstopclass, RoadStopType rs) +{ + for (uint j = 0; j < roadstopclass->GetSpecCount(); j++) { + if (GetIfStopIsForType(roadstopclass->GetSpec(j), rs)) return true; + } + return false; +} + +/** + * Checks if the given RoadStopSpec is compatible with the given RoadStopType. + * @param roadstopspec the RoadStopSpec to check. + * @param rs the RoadStopType to check by. + * @return true if the roadstopspec is compatible with the given RoadStopType. + */ +bool GetIfStopIsForType(const RoadStopSpec *roadstopspec, RoadStopType rs) +{ + // The roadstopspec is nullptr, must be the default station, always return true. + if (roadstopspec == nullptr) return true; + if (roadstopspec->stop_type == ROADSTOPTYPE_ALL) return true; + + switch (rs) { + case ROADSTOP_BUS: if (roadstopspec->stop_type == ROADSTOPTYPE_PASSENGER) return true; break; + case ROADSTOP_TRUCK: if (roadstopspec->stop_type == ROADSTOPTYPE_FREIGHT) return true; break; + } + return false; +} + +const RoadStopSpec *GetRoadStopSpec(TileIndex t) +{ + if (!IsCustomRoadStopSpecIndex(t)) return nullptr; + + const BaseStation *st = BaseStation::GetByTile(t); + uint specindex = GetCustomRoadStopSpecIndex(t); + return specindex < st->num_roadstop_specs ? st->roadstop_speclist[specindex].spec : nullptr; +} + +int AllocateRoadStopSpecToStation(const RoadStopSpec *statspec, BaseStation *st, bool exec) +{ + uint i; + + if (statspec == nullptr || st == nullptr) return 0; + + /* Try to find the same spec and return that one */ + for (i = 1; i < st->num_roadstop_specs && i < NUM_ROADSTOPSPECS_PER_STATION; i++) { + if (st->roadstop_speclist[i].spec == statspec) return i; + } + + /* Try to find an unused spec slot */ + for (i = 1; i < st->num_roadstop_specs && i < NUM_ROADSTOPSPECS_PER_STATION; i++) { + if (st->roadstop_speclist[i].spec == nullptr && st->roadstop_speclist[i].grfid == 0) break; + } + + if (i == NUM_ROADSTOPSPECS_PER_STATION) { + /* Full, give up */ + return -1; + } + + if (exec) { + if (i >= st->num_roadstop_specs) { + st->num_roadstop_specs = i + 1; + st->roadstop_speclist = ReallocT(st->roadstop_speclist, st->num_roadstop_specs); + + if (st->num_roadstop_specs == 2) { + /* Initial allocation */ + st->roadstop_speclist[0].spec = nullptr; + st->roadstop_speclist[0].grfid = 0; + st->roadstop_speclist[0].localidx = 0; + } + } + + st->roadstop_speclist[i].spec = statspec; + st->roadstop_speclist[i].grfid = statspec->grf_prop.grffile->grfid; + st->roadstop_speclist[i].localidx = statspec->grf_prop.local_id; + + StationUpdateRoadStopCachedTriggers(st); + } + + return i; +} + +void DeallocateRoadStopSpecFromStation(BaseStation *st, byte specindex) +{ + /* specindex of 0 (default) is never freeable */ + if (specindex == 0) return; + + /* Check custom road stop tiles if the specindex is still in use */ + for (TileIndex tile : st->custom_road_stop_tiles) { + if (GetCustomRoadStopSpecIndex(tile) == specindex) { + return; + } + } + + /* This specindex is no longer in use, so deallocate it */ + st->roadstop_speclist[specindex].spec = nullptr; + st->roadstop_speclist[specindex].grfid = 0; + st->roadstop_speclist[specindex].localidx = 0; + + /* If this was the highest spec index, reallocate */ + if (specindex == st->num_roadstop_specs - 1) { + for (; st->roadstop_speclist[st->num_roadstop_specs - 1].grfid == 0 && st->num_roadstop_specs > 1; st->num_roadstop_specs--) {} + + if (st->num_roadstop_specs > 1) { + st->roadstop_speclist = ReallocT(st->roadstop_speclist, st->num_roadstop_specs); + } else { + free(st->roadstop_speclist); + st->num_roadstop_specs = 0; + st->roadstop_speclist = nullptr; + st->roadstop_cached_cargo_triggers = 0; + return; + } + } + + StationUpdateRoadStopCachedTriggers(st); +} + +/** + * Update the cached animation trigger bitmask for a station. + * @param st Station to update. + */ +void StationUpdateRoadStopCachedTriggers(BaseStation *st) +{ + st->roadstop_cached_cargo_triggers = 0; + + /* Combine animation trigger bitmask for all road stop specs + * of this station. */ + for (uint i = 0; i < st->num_roadstop_specs; i++) { + const RoadStopSpec *ss = st->roadstop_speclist[i].spec; + if (ss != nullptr) { + st->roadstop_cached_cargo_triggers |= ss->cargo_triggers; + } + } +} diff --git a/src/newgrf_roadstop.h b/src/newgrf_roadstop.h new file mode 100644 index 0000000000..e4e8a1d035 --- /dev/null +++ b/src/newgrf_roadstop.h @@ -0,0 +1,147 @@ +/* + * This file is part of OpenTTD. + * 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. + * 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. + * 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 . + */ + +/** + * @file newgrf_roadstop.h NewGRF definitions and structures for road stops. + */ + +#ifndef NEWGRF_ROADSTATION_H +#define NEWGRF_ROADSTATION_H + +#include "newgrf_animation_type.h" +#include "newgrf_spritegroup.h" +#include "newgrf_class.h" +#include "newgrf_commons.h" +#include "newgrf_town.h" +#include "road.h" + +/** The maximum amount of roadstops a single GRF is allowed to add */ +static const int NUM_ROADSTOPS = 64000U; +static const int NUM_ROADSTOPS_PER_GRF = 255; + +enum RoadStopClassID : byte { + ROADSTOP_CLASS_BEGIN = 0, ///< The lowest valid value + ROADSTOP_CLASS_DFLT = 0, ///< Default road stop class. + ROADSTOP_CLASS_WAYP, ///< Waypoint class. + ROADSTOP_CLASS_MAX = 255, ///< Maximum number of classes. +}; +DECLARE_POSTFIX_INCREMENT(RoadStopClassID) + +/* Some Triggers etc. */ +enum RoadStopRandomTrigger { + RSRT_NEW_CARGO, ///< Trigger roadstop on arrival of new cargo. + RSRT_CARGO_TAKEN, ///< Trigger roadstop when cargo is completely taken. + RSRT_VEH_LOADS, ///< Trigger roadstop when road vehicle loads. +}; + +/** + * Various different options for availability, restricting + * the roadstop to be only for busses or for trucks. + */ +enum RoadStopAvailabilityType : byte { + ROADSTOPTYPE_PASSENGER, ///< This RoadStop is for passenger (bus) stops. + ROADSTOPTYPE_FREIGHT, ///< This RoadStop is for freight (truck) stops. + ROADSTOPTYPE_ALL, ///< This RoadStop is for both types of station road stops. + + ROADSTOPTYPE_END, +}; + +/** + * Different draw modes to disallow rendering of some parts of the stop + * or road. + */ +enum RoadStopDrawMode : byte { + ROADSTOP_DRAW_MODE_NONE = 0, + ROADSTOP_DRAW_MODE_ROAD = 1 << 0, ///< 0b01, Draw the road itself + ROADSTOP_DRAW_MODE_OVERLAY = 1 << 1, ///< 0b10, Draw the road overlay for roadstops, e.g. pavement +}; +DECLARE_ENUM_AS_BIT_SET(RoadStopDrawMode) + +/** Scope resolver for road stops. */ +struct RoadStopScopeResolver : public ScopeResolver { + TileIndex tile; ///< %Tile of the station. + struct BaseStation *st; ///< Instance of the station. + const struct RoadStopSpec *roadstopspec; ///< Station (type) specification. + CargoID cargo_type; ///< Type of cargo of the station. + StationType type; ///< Station type. + uint8 view; ///< Station axis. + const RoadTypeInfo *rti; ///< Road type info + + RoadStopScopeResolver(ResolverObject& ro, BaseStation* st, const RoadStopSpec *roadstopspec, TileIndex tile, const RoadTypeInfo *rti, StationType type, uint8 view = 0) + : ScopeResolver(ro), tile(tile), st(st), roadstopspec(roadstopspec), type(type), view(view), rti(rti) + { + + } + + uint32 GetRandomBits() const override; + uint32 GetTriggers() const override; + + uint32 GetVariable(uint16 variable, uint32 parameter, GetVariableExtra *extra) const override; +}; + +/** Road stop resolver. */ +struct RoadStopResolverObject : public ResolverObject { + RoadStopScopeResolver roadstop_scope; ///< The stop scope resolver. + TownScopeResolver *town_scope; ///< The town scope resolver (created on the first call). + + RoadStopResolverObject(const RoadStopSpec* roadstopspec, BaseStation* st, TileIndex tile, const RoadTypeInfo *rti, StationType type, uint8 view, CallbackID callback = CBID_NO_CALLBACK, uint32 param1 = 0, uint32 param2 = 0); + ~RoadStopResolverObject(); + + ScopeResolver* GetScope(VarSpriteGroupScope scope = VSG_SCOPE_SELF, byte relative = 0) override { + switch (scope) { + case VSG_SCOPE_SELF: return &this->roadstop_scope; + case VSG_SCOPE_PARENT: { + TownScopeResolver *tsr = this->GetTown(); + if (tsr != nullptr) return tsr; + FALLTHROUGH; + } + default: return ResolverObject::GetScope(scope, relative); + } + } + + TownScopeResolver *GetTown(); + + const SpriteGroup *ResolveReal(const RealSpriteGroup *group) const override; +}; + +/** Road stop specification. */ +struct RoadStopSpec { + // We'll have a default and a fence "cargo". Or maybe just a default one? + GRFFilePropsBase grf_prop; + RoadStopClassID cls_id; ///< The class to which this spec belongs. + int spec_id; ///< The ID of this spec inside the class. + StringID name; ///< Name of this stop + + RoadStopAvailabilityType stop_type = ROADSTOPTYPE_ALL; + RoadStopDrawMode draw_mode = ROADSTOP_DRAW_MODE_ROAD | ROADSTOP_DRAW_MODE_OVERLAY; + + CargoTypes cargo_triggers = 0; ///< Bitmask of cargo types which cause trigger re-randomizing + + static const RoadStopSpec *Get(uint16 index); +}; + +template <> +struct EnumPropsT : MakeEnumPropsT {}; + +typedef NewGRFClass RoadStopClass; + +void DrawRoadStopTile(int x, int y, RoadType roadtype, const RoadStopSpec *spec, StationType type, int view); + +void TriggerRoadStopRandomisation(Station *st, TileIndex tile, RoadStopRandomTrigger trigger, CargoID cargo_type = CT_INVALID); + +bool GetIfNewStopsByType(RoadStopType rs); +bool GetIfClassHasNewStopsByType(RoadStopClass *roadstopclass, RoadStopType rs); +bool GetIfStopIsForType(const RoadStopSpec *roadstopspec, RoadStopType rs); + +uint GetCountOfCompatibleStopsByType(RoadStopClass *roadstopclass, RoadStopType rs); + +const RoadStopSpec *GetRoadStopSpec(TileIndex t); +int AllocateRoadStopSpecToStation(const RoadStopSpec *statspec, BaseStation *st, bool exec); +void DeallocateRoadStopSpecFromStation(BaseStation *st, byte specindex); +void StationUpdateRoadStopCachedTriggers(BaseStation *st); + +#endif /* NEWGRF_ROADSTATION_H */ diff --git a/src/road_gui.cpp b/src/road_gui.cpp index 242ddcb975..50ef06f8da 100644 --- a/src/road_gui.cpp +++ b/src/road_gui.cpp @@ -33,6 +33,14 @@ #include "date_func.h" #include "station_map.h" #include "waypoint_func.h" +#include "newgrf_roadstop.h" +#include "debug.h" +#include "newgrf_station.h" +#include "querystring_gui.h" +#include "sortlist_type.h" +#include "stringfilter_type.h" +#include "string_func.h" + #include "widgets/road_widget.h" #include "table/strings.h" @@ -46,6 +54,17 @@ static void ShowRoadDepotPicker(Window *parent); static bool _remove_button_clicked; static bool _one_way_button_clicked; +static DiagDirection _build_depot_direction; + +struct RoadStopGUISettings { + DiagDirection orientation; // This replaces _road_station_picker_orientation + + RoadStopClassID roadstop_class; + byte roadstop_type; + byte roadstop_count; +}; +static RoadStopGUISettings _roadstop_gui_settings; + /** * Define the values of the RoadFlags * @see CmdBuildLongRoad @@ -65,8 +84,6 @@ static RoadFlags _place_road_flag; static RoadType _cur_roadtype; -static DiagDirection _road_depot_orientation; -static DiagDirection _road_station_picker_orientation; void CcPlaySound_CONSTRUCTION_OTHER(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2, uint64 p3, uint32 cmd) { @@ -184,17 +201,18 @@ void CcRoadStop(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2, */ static void PlaceRoadStop(TileIndex start_tile, TileIndex end_tile, uint32 p2, uint32 cmd) { - uint8 ddir = _road_station_picker_orientation; - SB(p2, 16, 16, INVALID_STATION); // no station to join + uint8 ddir = _roadstop_gui_settings.orientation; if (ddir >= DIAGDIR_END) { SetBit(p2, 1); // It's a drive-through stop. ddir -= DIAGDIR_END; // Adjust picker result to actual direction. } p2 |= ddir << 3; // Set the DiagDirecion into p2 bits 3 and 4. + p2 |= INVALID_STATION << 16; // no station to join TileArea ta(start_tile, end_tile); CommandContainer cmdcont = NewCommandContainerBasic(ta.tile, (uint32)(ta.w | ta.h << 8), p2, cmd, CcRoadStop); + cmdcont.p3 = (_roadstop_gui_settings.roadstop_type << 8) | _roadstop_gui_settings.roadstop_class; ShowSelectStationIfNeeded(cmdcont, ta); } @@ -217,7 +235,7 @@ static void PlaceRoad_Waypoint(TileIndex tile) } else { /* Tile where we can't build rail waypoints. This is always going to fail, * but provides the user with a proper error message. */ - DoCommandP(tile, 1 | 1 << 8, INVALID_STATION << 16, CMD_BUILD_ROAD_WAYPOINT | CMD_MSG(STR_ERROR_CAN_T_BUILD_ROAD_WAYPOINT)); + DoCommandP(tile, 1 | 1 << 8, ROADSTOP_CLASS_WAYP | INVALID_STATION << 16, CMD_BUILD_ROAD_WAYPOINT | CMD_MSG(STR_ERROR_CAN_T_BUILD_ROAD_WAYPOINT)); } } @@ -230,8 +248,8 @@ static void PlaceRoad_BusStation(TileIndex tile) if (_remove_button_clicked) { VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_REMOVE_BUSSTOP); } else { - if (_road_station_picker_orientation < DIAGDIR_END) { // Not a drive-through stop. - VpStartPlaceSizing(tile, (DiagDirToAxis(_road_station_picker_orientation) == AXIS_X) ? VPM_X_LIMITED : VPM_Y_LIMITED, DDSP_BUILD_BUSSTOP); + if (_roadstop_gui_settings.orientation < DIAGDIR_END) { // Not a drive-through stop. + VpStartPlaceSizing(tile, (DiagDirToAxis(_roadstop_gui_settings.orientation) == AXIS_X) ? VPM_X_LIMITED : VPM_Y_LIMITED, DDSP_BUILD_BUSSTOP); } else { VpStartPlaceSizing(tile, VPM_X_AND_Y_LIMITED, DDSP_BUILD_BUSSTOP); } @@ -248,8 +266,8 @@ static void PlaceRoad_TruckStation(TileIndex tile) if (_remove_button_clicked) { VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_REMOVE_TRUCKSTOP); } else { - if (_road_station_picker_orientation < DIAGDIR_END) { // Not a drive-through stop. - VpStartPlaceSizing(tile, (DiagDirToAxis(_road_station_picker_orientation) == AXIS_X) ? VPM_X_LIMITED : VPM_Y_LIMITED, DDSP_BUILD_TRUCKSTOP); + if (_roadstop_gui_settings.orientation < DIAGDIR_END) { // Not a drive-through stop. + VpStartPlaceSizing(tile, (DiagDirToAxis(_roadstop_gui_settings.orientation) == AXIS_X) ? VPM_X_LIMITED : VPM_Y_LIMITED, DDSP_BUILD_TRUCKSTOP); } else { VpStartPlaceSizing(tile, VPM_X_AND_Y_LIMITED, DDSP_BUILD_TRUCKSTOP); } @@ -584,7 +602,7 @@ struct BuildRoadToolbarWindow : Window { break; case WID_ROT_DEPOT: - DoCommandP(tile, _cur_roadtype << 2 | _road_depot_orientation, 0, + DoCommandP(tile, _cur_roadtype << 2 | _build_depot_direction, 0, CMD_BUILD_ROAD_DEPOT | CMD_MSG(this->rti->strings.err_depot), CcRoadDepot); break; @@ -721,7 +739,7 @@ struct BuildRoadToolbarWindow : Window { DoCommandP(ta.tile, ta.w | ta.h << 8, (1 << 2), CMD_REMOVE_ROAD_STOP | CMD_MSG(STR_ERROR_CAN_T_REMOVE_ROAD_WAYPOINT), CcPlaySound_CONSTRUCTION_OTHER); } else { uint32 p1 = ta.w | ta.h << 8 | _ctrl_pressed << 16 | (select_method == VPM_X_LIMITED ? AXIS_X : AXIS_Y) << 17; - uint32 p2 = INVALID_STATION << 16; + uint32 p2 = ROADSTOP_CLASS_WAYP | INVALID_STATION << 16; CommandContainer cmdcont = NewCommandContainerBasic(ta.tile, p1, p2, CMD_BUILD_ROAD_WAYPOINT | CMD_MSG(STR_ERROR_CAN_T_BUILD_ROAD_WAYPOINT), CcPlaySound_CONSTRUCTION_OTHER); ShowSelectWaypointIfNeeded(cmdcont, ta); @@ -731,24 +749,24 @@ struct BuildRoadToolbarWindow : Window { case DDSP_BUILD_BUSSTOP: case DDSP_REMOVE_BUSSTOP: - if (this->IsWidgetLowered(WID_ROT_BUS_STATION)) { + if (this->IsWidgetLowered(WID_ROT_BUS_STATION) && GetIfClassHasNewStopsByType(RoadStopClass::Get(_roadstop_gui_settings.roadstop_class), ROADSTOP_BUS)) { if (_remove_button_clicked) { TileArea ta(start_tile, end_tile); DoCommandP(ta.tile, ta.w | ta.h << 8, (_ctrl_pressed << 1) | ROADSTOP_BUS, CMD_REMOVE_ROAD_STOP | CMD_MSG(this->rti->strings.err_remove_station[ROADSTOP_BUS]), CcPlaySound_CONSTRUCTION_OTHER); } else { - PlaceRoadStop(start_tile, end_tile, _cur_roadtype << 5 | (_ctrl_pressed << 2) | ROADSTOP_BUS, CMD_BUILD_ROAD_STOP | CMD_MSG(this->rti->strings.err_build_station[ROADSTOP_BUS])); + PlaceRoadStop(start_tile, end_tile, (_cur_roadtype << 5) | (_ctrl_pressed << 2) | ROADSTOP_BUS, CMD_BUILD_ROAD_STOP | CMD_MSG(this->rti->strings.err_build_station[ROADSTOP_BUS])); } } break; case DDSP_BUILD_TRUCKSTOP: case DDSP_REMOVE_TRUCKSTOP: - if (this->IsWidgetLowered(WID_ROT_TRUCK_STATION)) { + if (this->IsWidgetLowered(WID_ROT_TRUCK_STATION) && GetIfClassHasNewStopsByType(RoadStopClass::Get(_roadstop_gui_settings.roadstop_class), ROADSTOP_TRUCK)) { if (_remove_button_clicked) { TileArea ta(start_tile, end_tile); DoCommandP(ta.tile, ta.w | ta.h << 8, (_ctrl_pressed << 1) | ROADSTOP_TRUCK, CMD_REMOVE_ROAD_STOP | CMD_MSG(this->rti->strings.err_remove_station[ROADSTOP_TRUCK]), CcPlaySound_CONSTRUCTION_OTHER); } else { - PlaceRoadStop(start_tile, end_tile, _cur_roadtype << 5 | (_ctrl_pressed << 2) | ROADSTOP_TRUCK, CMD_BUILD_ROAD_STOP | CMD_MSG(this->rti->strings.err_build_station[ROADSTOP_TRUCK])); + PlaceRoadStop(start_tile, end_tile, (_cur_roadtype << 5) | (_ctrl_pressed << 2) | ROADSTOP_TRUCK, CMD_BUILD_ROAD_STOP | CMD_MSG(this->rti->strings.err_build_station[ROADSTOP_TRUCK])); } } break; @@ -1044,7 +1062,7 @@ struct BuildRoadDepotWindow : public PickerWindowBase { { this->CreateNestedTree(); - this->LowerWidget(_road_depot_orientation + WID_BROD_DEPOT_NE); + this->LowerWidget(_build_depot_direction + WID_BROD_DEPOT_NE); if (RoadTypeIsTram(_cur_roadtype)) { this->GetWidget(WID_BROD_CAPTION)->widget_data = STR_BUILD_DEPOT_TRAM_ORIENTATION_CAPTION; for (int i = WID_BROD_DEPOT_NE; i <= WID_BROD_DEPOT_NW; i++) this->GetWidget(i)->tool_tip = STR_BUILD_DEPOT_TRAM_ORIENTATION_SELECT_TOOLTIP; @@ -1075,9 +1093,9 @@ struct BuildRoadDepotWindow : public PickerWindowBase { case WID_BROD_DEPOT_NE: case WID_BROD_DEPOT_SW: case WID_BROD_DEPOT_SE: - this->RaiseWidget(_road_depot_orientation + WID_BROD_DEPOT_NE); - _road_depot_orientation = (DiagDirection)(widget - WID_BROD_DEPOT_NE); - this->LowerWidget(_road_depot_orientation + WID_BROD_DEPOT_NE); + this->RaiseWidget(_build_depot_direction + WID_BROD_DEPOT_NE); + _build_depot_direction = (DiagDirection)(widget - WID_BROD_DEPOT_NE); + this->LowerWidget(_build_depot_direction + WID_BROD_DEPOT_NE); if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP); this->SetDirty(); break; @@ -1130,14 +1148,84 @@ static void ShowRoadDepotPicker(Window *parent) new BuildRoadDepotWindow(&_build_road_depot_desc, parent); } +/** Enum referring to the Hotkeys in the build road stop window */ +enum BuildRoadStopHotkeys { + BROSHK_FOCUS_FILTER_BOX, ///< Focus the edit box for editing the filter string +}; + struct BuildRoadStationWindow : public PickerWindowBase { - BuildRoadStationWindow(WindowDesc *desc, Window *parent, RoadStopType rs) : PickerWindowBase(desc, parent) +private: + RoadStopType roadStopType; ///< The RoadStopType for this Window. + uint line_height; ///< Height of a single line in the newstation selection matrix. + uint coverage_height; ///< Height of the coverage texts. + Scrollbar *vscrollList; ///< Vertical scrollbar of the new station list. + Scrollbar *vscrollMatrix; ///< Vertical scrollbar of the station picker matrix. + + typedef GUIList GUIRoadStopClassList; ///< Type definition for the list to hold available road stop classes. + + static const uint EDITBOX_MAX_SIZE = 16; ///< The maximum number of characters for the filter edit box. + + static Listing last_sorting; ///< Default sorting of #GUIRoadStopClassList. + static Filtering last_filtering; ///< Default filtering of #GUIRoadStopClassList. + static GUIRoadStopClassList::SortFunction * const sorter_funcs[]; ///< Sort functions of the #GUIRoadStopClassList. + static GUIRoadStopClassList::FilterFunction * const filter_funcs[]; ///< Filter functions of the #GUIRoadStopClassList. + GUIRoadStopClassList roadstop_classes; ///< Available road stop classes. + StringFilter string_filter; ///< Filter for available road stop classes. + QueryString filter_editbox; ///< Filter editbox. + + void EnsureSelectedClassIsVisible() { + uint pos = 0; + for (auto rs_class : this->roadstop_classes) { + if (rs_class == _roadstop_gui_settings.roadstop_class) break; + pos++; + } + this->vscrollList->SetCount((int)this->roadstop_classes.size()); + this->vscrollList->ScrollTowards(pos); + } + +public: + BuildRoadStationWindow(WindowDesc *desc, Window *parent, RoadStopType rs) : PickerWindowBase(desc, parent), filter_editbox(EDITBOX_MAX_SIZE * MAX_CHAR_LENGTH, EDITBOX_MAX_SIZE) + { + this->coverage_height = 2 * FONT_HEIGHT_NORMAL + 3 * WD_PAR_VSEP_NORMAL; + this->vscrollList = nullptr; + this->vscrollMatrix = nullptr; + this->roadStopType = rs; + bool newstops = GetIfNewStopsByType(rs); + this->CreateNestedTree(); - /* Trams don't have non-drivethrough stations */ - if (RoadTypeIsTram(_cur_roadtype) && _road_station_picker_orientation < DIAGDIR_END) { - _road_station_picker_orientation = DIAGDIR_END; + NWidgetStacked *newst_additions = this->GetWidget(WID_BROS_SHOW_NEWST_ADDITIONS); + newst_additions->SetDisplayedPlane(newstops ? 0 : SZSP_NONE); + newst_additions = this->GetWidget(WID_BROS_SHOW_NEWST_MATRIX); + newst_additions->SetDisplayedPlane(newstops ? 0 : SZSP_NONE); + newst_additions = this->GetWidget(WID_BROS_SHOW_NEWST_DEFSIZE); + newst_additions->SetDisplayedPlane(newstops ? 0 : SZSP_NONE); + newst_additions = this->GetWidget(WID_BROS_SHOW_NEWST_RESIZE); + newst_additions->SetDisplayedPlane(newstops ? 0 : SZSP_NONE); + newst_additions = this->GetWidget(WID_BROS_SHOW_NEWST_ORIENTATION); + newst_additions->SetDisplayedPlane(newstops ? 0 : SZSP_NONE); + newst_additions = this->GetWidget(WID_BROS_SHOW_NEWST_TYPE_SEL); + newst_additions->SetDisplayedPlane(newstops ? 0 : SZSP_NONE); + /* Hide the station class filter if no stations other than the default one are available. */ + this->GetWidget(WID_BROS_FILTER_CONTAINER)->SetDisplayedPlane(newstops ? 0 : SZSP_NONE); + if (newstops) { + this->vscrollList = this->GetScrollbar(WID_BROS_NEWST_SCROLL); + this->vscrollMatrix = this->GetScrollbar(WID_BROS_MATRIX_SCROLL); + + this->querystrings[WID_BROS_FILTER_EDITBOX] = &this->filter_editbox; + this->roadstop_classes.SetListing(this->last_sorting); + this->roadstop_classes.SetFiltering(this->last_filtering); + this->roadstop_classes.SetSortFuncs(this->sorter_funcs); + this->roadstop_classes.SetFilterFuncs(this->filter_funcs); + } + + this->roadstop_classes.ForceRebuild(); + BuildRoadStopClassesAvailable(); + + // Trams don't have non-drivethrough stations + if (RoadTypeIsTram(_cur_roadtype) && _roadstop_gui_settings.orientation < DIAGDIR_END) { + _roadstop_gui_settings.orientation = DIAGDIR_END; } const RoadTypeInfo *rti = GetRoadTypeInfo(_cur_roadtype); this->GetWidget(WID_BROS_CAPTION)->widget_data = rti->strings.picker_title[rs]; @@ -1146,12 +1234,39 @@ struct BuildRoadStationWindow : public PickerWindowBase { this->GetWidget(i)->tool_tip = rti->strings.picker_tooltip[rs]; } - this->LowerWidget(_road_station_picker_orientation + WID_BROS_STATION_NE); + this->LowerWidget(_roadstop_gui_settings.orientation + WID_BROS_STATION_NE); this->LowerWidget(_settings_client.gui.station_show_coverage + WID_BROS_LT_OFF); this->FinishInitNested(TRANSPORT_ROAD); this->window_class = (rs == ROADSTOP_BUS) ? WC_BUS_STATION : WC_TRUCK_STATION; + if (!newstops || _roadstop_gui_settings.roadstop_class >= (int)RoadStopClass::GetClassCount()) { + /* There's no new stops available or the list has reduced in size. + * Now, set the default road stops as selected. */ + _roadstop_gui_settings.roadstop_class = ROADSTOP_CLASS_DFLT; + _roadstop_gui_settings.roadstop_type = 0; + } + if (newstops) { + /* The currently selected class doesn't have any stops for this RoadStopType, reset the selection. */ + if (!GetIfClassHasNewStopsByType(RoadStopClass::Get(_roadstop_gui_settings.roadstop_class), rs)) { + _roadstop_gui_settings.roadstop_class = ROADSTOP_CLASS_DFLT; + _roadstop_gui_settings.roadstop_type = 0; + } + _roadstop_gui_settings.roadstop_count = RoadStopClass::Get(_roadstop_gui_settings.roadstop_class)->GetSpecCount(); + _roadstop_gui_settings.roadstop_type = std::min((int)_roadstop_gui_settings.roadstop_type, _roadstop_gui_settings.roadstop_count - 1); + + /* Reset back to default class if the previously selected class is not available for this road stop type. */ + if (!GetIfClassHasNewStopsByType(RoadStopClass::Get(_roadstop_gui_settings.roadstop_class), roadStopType)) { + _roadstop_gui_settings.roadstop_class = ROADSTOP_CLASS_DFLT; + } + + NWidgetMatrix *matrix = this->GetWidget(WID_BROS_MATRIX); + matrix->SetScrollbar(this->vscrollMatrix); + matrix->SetCount(_roadstop_gui_settings.roadstop_count); + matrix->SetClicked(_roadstop_gui_settings.roadstop_type); + + this->EnsureSelectedClassIsVisible(); + } } virtual ~BuildRoadStationWindow() @@ -1159,10 +1274,86 @@ struct BuildRoadStationWindow : public PickerWindowBase { DeleteWindowById(WC_SELECT_STATION, 0); } + /** Sort classes by RoadStopClassID. */ + static bool RoadStopClassIDSorter(RoadStopClassID const &a, RoadStopClassID const &b) + { + return a < b; + } + + /** Filter classes by class name. */ + static bool CDECL TagNameFilter(RoadStopClassID const *sc, StringFilter &filter) + { + char buffer[DRAW_STRING_BUFFER]; + GetString(buffer, RoadStopClass::Get(*sc)->name, lastof(buffer)); + + filter.ResetState(); + filter.AddLine(buffer); + return filter.GetState(); + } + + inline bool ShowNewStops() const + { + return this->vscrollList != nullptr; + } + + void BuildRoadStopClassesAvailable() + { + if (!this->roadstop_classes.NeedRebuild()) return; + + this->roadstop_classes.clear(); + + for (uint i = 0; i < RoadStopClass::GetClassCount(); i++) { + RoadStopClassID rs_id = (RoadStopClassID)i; + if (rs_id == ROADSTOP_CLASS_WAYP) { + // Skip waypoints. + continue; + } + RoadStopClass *rs_class = RoadStopClass::Get(rs_id); + if (GetIfClassHasNewStopsByType(rs_class, this->roadStopType)) this->roadstop_classes.push_back(rs_id); + } + + if (this->ShowNewStops()) { + this->roadstop_classes.Filter(this->string_filter); + this->roadstop_classes.shrink_to_fit(); + this->roadstop_classes.RebuildDone(); + this->roadstop_classes.Sort(); + + this->vscrollList->SetCount((uint)this->roadstop_classes.size()); + } + } + + void OnInvalidateData(int data = 0, bool gui_scope = true) override + { + if (!gui_scope) return; + + this->BuildRoadStopClassesAvailable(); + } + + EventState OnHotkey(int hotkey) override + { + switch (hotkey) { + case BROSHK_FOCUS_FILTER_BOX: + this->SetFocusedWidget(WID_BROS_FILTER_EDITBOX); + SetFocusedWindow(this); // The user has asked to give focus to the text box, so make sure this window is focused. + break; + + default: + return ES_NOT_HANDLED; + } + + return ES_HANDLED; + } + + void OnEditboxChanged(int wid) override + { + string_filter.SetFilterTerm(this->filter_editbox.text.buf); + this->roadstop_classes.SetFilterState(!string_filter.IsEmpty()); + this->roadstop_classes.ForceRebuild(); + this->InvalidateData(); + } + void OnPaint() override { - this->DrawWidgets(); - int rad = _settings_game.station.modified_catchment ? ((this->window_class == WC_BUS_STATION) ? CA_BUS : CA_TRUCK) : CA_UNMODIFIED; rad += _settings_game.station.catchment_increase; if (_settings_client.gui.station_show_coverage) { @@ -1171,50 +1362,172 @@ struct BuildRoadStationWindow : public PickerWindowBase { SetTileSelectSize(1, 1); } + this->DrawWidgets(); + + if (this->IsShaded()) return; /* 'Accepts' and 'Supplies' texts. */ StationCoverageType sct = (this->window_class == WC_BUS_STATION) ? SCT_PASSENGERS_ONLY : SCT_NON_PASSENGERS_ONLY; + + NWidgetBase *cov = this->GetWidget(WID_BROS_INFO); + int top = cov->pos_y + WD_PAR_VSEP_NORMAL; + int left = cov->pos_x + WD_FRAMERECT_LEFT; + int right = cov->pos_x + cov->current_x - WD_FRAMERECT_RIGHT; + int bottom = cov->pos_y + cov->current_y; + top = DrawStationCoverageAreaText(left, right, top, sct, rad, false) + WD_PAR_VSEP_NORMAL; + top = DrawStationCoverageAreaText(left, right, top, sct, rad, true) + WD_PAR_VSEP_NORMAL; + + /* int top = this->GetWidget(WID_BROS_LT_ON)->pos_y + this->GetWidget(WID_BROS_LT_ON)->current_y + WD_PAR_VSEP_NORMAL; NWidgetBase *back_nwi = this->GetWidget(WID_BROS_BACKGROUND); - int right = back_nwi->pos_x + back_nwi->current_x; - int bottom = back_nwi->pos_y + back_nwi->current_y; + int right = back_nwi->pos_x + back_nwi->current_x; + int bottom = back_nwi->pos_y + back_nwi->current_y; top = DrawStationCoverageAreaText(back_nwi->pos_x + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, sct, rad, false) + WD_PAR_VSEP_NORMAL; top = DrawStationCoverageAreaText(back_nwi->pos_x + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, sct, rad, true) + WD_PAR_VSEP_NORMAL; + */ /* Resize background if the window is too small. * Never make the window smaller to avoid oscillating if the size change affects the acceptance. * (This is the case, if making the window bigger moves the mouse into the window.) */ if (top > bottom) { - ResizeWindow(this, 0, top - bottom, false); + this->coverage_height += top - bottom; + this->ReInit(); } } void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override - { - if (!IsInsideMM(widget, WID_BROS_STATION_NE, WID_BROS_STATION_Y + 1)) return; - - size->width = ScaleGUITrad(64) + 2; - size->height = ScaleGUITrad(48) + 2; - } - - void DrawWidget(const Rect &r, int widget) const override - { - if (!IsInsideMM(widget, WID_BROS_STATION_NE, WID_BROS_STATION_Y + 1)) return; - - StationType st = (this->window_class == WC_BUS_STATION) ? STATION_BUS : STATION_TRUCK; - StationPickerDrawSprite(r.left + 1 + ScaleGUITrad(31), r.bottom - ScaleGUITrad(31), st, INVALID_RAILTYPE, _cur_roadtype, widget - WID_BROS_STATION_NE); - } - - void OnClick(Point pt, int widget, int click_count) override { switch (widget) { + case WID_BROS_NEWST_LIST: { + Dimension d = { 0, 0 }; + for (auto rs_class : this->roadstop_classes) { + d = maxdim(d, GetStringBoundingBox(RoadStopClass::Get(rs_class)->name)); + } + size->width = std::max(size->width, d.width + padding.width); + this->line_height = FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM; + size->height = 5 * this->line_height; + resize->height = this->line_height; + break; + } + + case WID_BROS_IMAGE: + size->width = ScaleGUITrad(64) + 2; + size->height = ScaleGUITrad(58) + 2; + break; + case WID_BROS_STATION_NE: case WID_BROS_STATION_SE: case WID_BROS_STATION_SW: case WID_BROS_STATION_NW: case WID_BROS_STATION_X: case WID_BROS_STATION_Y: - this->RaiseWidget(_road_station_picker_orientation + WID_BROS_STATION_NE); - _road_station_picker_orientation = (DiagDirection)(widget - WID_BROS_STATION_NE); - this->LowerWidget(_road_station_picker_orientation + WID_BROS_STATION_NE); + case WID_BROS_MATRIX: + fill->height = 1; + resize->height = 1; + break; + + case WID_BROS_INFO: + size->height = this->coverage_height; + break; + } + } + + /** + * Simply to have a easier way to get the StationType for bus, truck and trams from the WindowClass. + */ + StationType GetRoadStationTypeByWindowClass(WindowClass window_class) const { + switch (window_class) { + case WC_BUS_STATION: return STATION_BUS; + case WC_TRUCK_STATION: return STATION_TRUCK; + default: NOT_REACHED(); + } + } + + void DrawWidget(const Rect &r, int widget) const override + { + DrawPixelInfo tmp_dpi; + + switch (GB(widget, 0, 16)) { + case WID_BROS_STATION_NE: + case WID_BROS_STATION_SE: + case WID_BROS_STATION_SW: + case WID_BROS_STATION_NW: + case WID_BROS_STATION_X: + case WID_BROS_STATION_Y: { + StationType st = GetRoadStationTypeByWindowClass(this->window_class); + const RoadStopSpec *spec = RoadStopClass::Get(_roadstop_gui_settings.roadstop_class)->GetSpec(_roadstop_gui_settings.roadstop_type); + if (spec == nullptr) { + StationPickerDrawSprite(r.left + WD_MATRIX_LEFT + ScaleGUITrad(31), r.bottom - ScaleGUITrad(31), st, INVALID_RAILTYPE, _cur_roadtype, widget - WID_BROS_STATION_NE); + } else { + DrawRoadStopTile(r.left + WD_MATRIX_LEFT + ScaleGUITrad(31), r.bottom - ScaleGUITrad(31), _cur_roadtype, + RoadStopClass::Get(_roadstop_gui_settings.roadstop_class)->GetSpec(_roadstop_gui_settings.roadstop_type), st, (int)widget - WID_BROS_STATION_NE); + } + break; + } + + case WID_BROS_NEWST_LIST: { + uint statclass = 0; + uint row = 0; + for (auto rs_class : this->roadstop_classes) { + if (this->vscrollList->IsVisible(statclass)) { + DrawString(r.left + WD_MATRIX_LEFT, r.right, row * this->line_height + r.top + WD_MATRIX_TOP, + RoadStopClass::Get(rs_class)->name, + rs_class == _roadstop_gui_settings.roadstop_class ? TC_WHITE : TC_BLACK); + row++; + } + statclass++; + } + break; + } + + case WID_BROS_IMAGE: { + byte type = GB(widget, 16, 16); + assert(type < _roadstop_gui_settings.roadstop_count); + + // Set up a clipping area for the sprite preview. + if (FillDrawPixelInfo(&tmp_dpi, r.left, r.top, r.right - r.left + 1, r.bottom - r.top + 1)) { + DrawPixelInfo *old_dpi = _cur_dpi; + _cur_dpi = &tmp_dpi; + int x = ScaleGUITrad(31) + 1; + int y = r.bottom - r.top - ScaleGUITrad(31); + // Instead of "5" (5th view), pass the orientation clicked in the selection. + const RoadStopSpec *spec = RoadStopClass::Get(_roadstop_gui_settings.roadstop_class)->GetSpec(_roadstop_gui_settings.roadstop_type); + StationType st = GetRoadStationTypeByWindowClass(this->window_class); + if (spec == nullptr) { + StationPickerDrawSprite(r.left + 1 + ScaleGUITrad(31), r.bottom - ScaleGUITrad(31), st, INVALID_RAILTYPE, _cur_roadtype, _roadstop_gui_settings.orientation); + } else { + DrawRoadStopTile(x, y, _cur_roadtype, RoadStopClass::Get(_roadstop_gui_settings.roadstop_class)->GetSpec(type), st, (uint8)_roadstop_gui_settings.orientation); + } + _cur_dpi = old_dpi; + } + break; + } + } + } + + void OnResize() override { + if (this->vscrollList != nullptr) { + this->vscrollList->SetCapacityFromWidget(this, WID_BROS_NEWST_LIST); + } + } + + void SetStringParameters(int widget) const override { + if (widget == WID_BROS_SHOW_NEWST_TYPE) { + const RoadStopSpec *roadstopspec = RoadStopClass::Get(_roadstop_gui_settings.roadstop_class)->GetSpec(_roadstop_gui_settings.roadstop_type); + SetDParam(0, (roadstopspec != nullptr && roadstopspec->name != 0) ? roadstopspec->name : STR_STATION_CLASS_DFLT); + } + } + + void OnClick(Point pt, int widget, int click_count) override + { + switch (GB(widget, 0, 16)) { + case WID_BROS_STATION_NE: + case WID_BROS_STATION_SE: + case WID_BROS_STATION_SW: + case WID_BROS_STATION_NW: + case WID_BROS_STATION_X: + case WID_BROS_STATION_Y: + this->RaiseWidget(_roadstop_gui_settings.orientation + WID_BROS_STATION_NE); + _roadstop_gui_settings.orientation = (DiagDirection)(widget - WID_BROS_STATION_NE); + this->LowerWidget(_roadstop_gui_settings.orientation + WID_BROS_STATION_NE); if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP); this->SetDirty(); DeleteWindowById(WC_SELECT_STATION, 0); @@ -1230,6 +1543,41 @@ struct BuildRoadStationWindow : public PickerWindowBase { SetViewportCatchmentStation(nullptr, true); break; + case WID_BROS_NEWST_LIST: { + int y = this->vscrollList->GetScrolledRowFromWidget(pt.y, this, WID_BROS_NEWST_LIST); + if (y >= (int)this->roadstop_classes.size()) return; + RoadStopClassID class_id = this->roadstop_classes[y]; + if (_roadstop_gui_settings.roadstop_class != class_id && GetIfClassHasNewStopsByType(RoadStopClass::Get(class_id), roadStopType)) { + _roadstop_gui_settings.roadstop_class = class_id; + RoadStopClass *rsclass = RoadStopClass::Get(_roadstop_gui_settings.roadstop_class); + _roadstop_gui_settings.roadstop_count = rsclass->GetSpecCount(); + _roadstop_gui_settings.roadstop_type = std::min((int)_roadstop_gui_settings.roadstop_type, std::max(0, (int)_roadstop_gui_settings.roadstop_count - 1)); + + NWidgetMatrix *matrix = this->GetWidget(WID_BROS_MATRIX); + matrix->SetCount(_roadstop_gui_settings.roadstop_count); + matrix->SetClicked(_roadstop_gui_settings.roadstop_type); + } + if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP); + this->SetDirty(); + DeleteWindowById(WC_SELECT_STATION, 0); + break; + } + + case WID_BROS_IMAGE: { + int y = GB(widget, 16, 16); + if (y >= _roadstop_gui_settings.roadstop_count) return; + + /* Check station availability callback */ + _roadstop_gui_settings.roadstop_type = y; + + this->GetWidget(WID_BROS_MATRIX)->SetClicked(_roadstop_gui_settings.roadstop_type); + + if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP); + this->SetDirty(); + DeleteWindowById(WC_SELECT_STATION, 0); + break; + } + default: break; } @@ -1239,6 +1587,25 @@ struct BuildRoadStationWindow : public PickerWindowBase { { CheckRedrawStationCoverage(this); } + + static HotkeyList hotkeys; +}; + +static Hotkey buildroadstop_hotkeys[] = { + Hotkey('F', "focus_filter_box", BROSHK_FOCUS_FILTER_BOX), + HOTKEY_LIST_END +}; +HotkeyList BuildRoadStationWindow::hotkeys("buildroadstop", buildroadstop_hotkeys); + +Listing BuildRoadStationWindow::last_sorting = { false, 0 }; +Filtering BuildRoadStationWindow::last_filtering = { false, 0 }; + +BuildRoadStationWindow::GUIRoadStopClassList::SortFunction * const BuildRoadStationWindow::sorter_funcs[] = { + &RoadStopClassIDSorter, +}; + +BuildRoadStationWindow::GUIRoadStopClassList::FilterFunction * const BuildRoadStationWindow::filter_funcs[] = { + &TagNameFilter, }; /** Widget definition of the build road station window */ @@ -1246,38 +1613,82 @@ static const NWidgetPart _nested_road_station_picker_widgets[] = { NWidget(NWID_HORIZONTAL), NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN), NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_BROS_CAPTION), + NWidget(WWT_SHADEBOX, COLOUR_DARK_GREEN), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_DEFSIZE), + NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN), + EndContainer(), EndContainer(), NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BROS_BACKGROUND), - NWidget(NWID_SPACER), SetMinimalSize(0, 3), - NWidget(NWID_HORIZONTAL), SetPIP(0, 2, 0), - NWidget(NWID_SPACER), SetFill(1, 0), - NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_NW), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(), - NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_NE), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(), - NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_X), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(), - NWidget(NWID_SPACER), SetFill(1, 0), + NWidget(NWID_HORIZONTAL), + NWidget(NWID_VERTICAL), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_FILTER_CONTAINER), + NWidget(NWID_HORIZONTAL), SetPadding(0, 5, 2, 0), + NWidget(WWT_TEXT, COLOUR_DARK_GREEN), SetFill(0, 1), SetDataTip(STR_LIST_FILTER_TITLE, STR_NULL), + NWidget(WWT_EDITBOX, COLOUR_GREY, WID_BROS_FILTER_EDITBOX), SetFill(1, 0), SetResize(1, 0), + SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP), + EndContainer(), + EndContainer(), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_ADDITIONS), + NWidget(NWID_HORIZONTAL), SetPIP(7, 0, 7), SetPadding(2, 0, 1, 0), + NWidget(WWT_MATRIX, COLOUR_GREY, WID_BROS_NEWST_LIST), SetMinimalSize(122, 71), SetFill(1, 0), + SetMatrixDataTip(1, 0, STR_STATION_BUILD_STATION_CLASS_TOOLTIP), SetScrollbar(WID_BROS_NEWST_SCROLL), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_BROS_NEWST_SCROLL), + EndContainer(), + EndContainer(), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_ORIENTATION), + NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetMinimalSize(144, 11), SetDataTip(STR_STATION_BUILD_ORIENTATION, STR_NULL), SetPadding(4, 2, 1, 2), + EndContainer(), + NWidget(NWID_HORIZONTAL), SetPIP(5, 2, 5), SetPadding(0, 0, 1, 0), + NWidget(NWID_SPACER), SetFill(1, 0), + NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_NW), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(), + NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_NE), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(), + NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_X), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(), + NWidget(NWID_SPACER), SetFill(1, 0), + EndContainer(), + NWidget(NWID_SPACER), SetMinimalSize(0, 2), + NWidget(NWID_HORIZONTAL), SetPIP(5, 2, 5), SetPadding(0, 0, 1, 0), // For PIP, 5 because the 2 is applied before and after aswell. We want to use 7 to be the same as the class list + NWidget(NWID_SPACER), SetFill(1, 0), + NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_SW), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(), + NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_SE), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(), + NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_Y), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(), + NWidget(NWID_SPACER), SetFill(1, 0), + EndContainer(), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_TYPE_SEL), + NWidget(WWT_LABEL, COLOUR_DARK_GREEN, WID_BROS_SHOW_NEWST_TYPE), SetMinimalSize(144, 8), SetDataTip(STR_ORANGE_STRING, STR_NULL), SetPadding(4, 2, 4, 2), + EndContainer(), + NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetMinimalSize(140, 14), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL), SetPadding(3, 2, 0, 2), + NWidget(NWID_HORIZONTAL), SetPIP(2, 0, 2), + NWidget(NWID_SPACER), SetFill(1, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROS_LT_OFF), SetMinimalSize(60, 12), + SetDataTip(STR_STATION_BUILD_COVERAGE_OFF, STR_STATION_BUILD_COVERAGE_AREA_OFF_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROS_LT_ON), SetMinimalSize(60, 12), + SetDataTip(STR_STATION_BUILD_COVERAGE_ON, STR_STATION_BUILD_COVERAGE_AREA_ON_TOOLTIP), + NWidget(NWID_SPACER), SetFill(1, 0), + EndContainer(), + EndContainer(), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_MATRIX), + /* We need an additional background for the matrix, as the matrix cannot handle the scrollbar due to not being an NWidgetCore. */ + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetScrollbar(WID_BROS_MATRIX_SCROLL), + NWidget(NWID_HORIZONTAL), + NWidget(NWID_MATRIX, COLOUR_DARK_GREEN, WID_BROS_MATRIX), SetScrollbar(WID_BROS_MATRIX_SCROLL), SetPIP(0, 2, 0), SetPadding(2, 0, 0, 0), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BROS_IMAGE), SetMinimalSize(66, 60), + SetFill(0, 0), SetResize(0, 0), SetDataTip(0x0, STR_STATION_BUILD_STATION_TYPE_TOOLTIP), SetScrollbar(WID_BROS_MATRIX_SCROLL), + EndContainer(), + EndContainer(), + NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_BROS_MATRIX_SCROLL), + EndContainer(), + EndContainer(), + EndContainer(), EndContainer(), - NWidget(NWID_SPACER), SetMinimalSize(0, 2), - NWidget(NWID_HORIZONTAL), SetPIP(0, 2, 0), - NWidget(NWID_SPACER), SetFill(1, 0), - NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_SW), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(), - NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_SE), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(), - NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_Y), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(), - NWidget(NWID_SPACER), SetFill(1, 0), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_EMPTY, INVALID_COLOUR, WID_BROS_INFO), SetPadding(2, 5, 0, 1), SetFill(1, 1), SetResize(1, 0), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_RESIZE), + NWidget(NWID_VERTICAL), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetFill(0, 1), EndContainer(), + NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN), + EndContainer(), + EndContainer(), EndContainer(), - NWidget(NWID_SPACER), SetMinimalSize(0, 1), - NWidget(NWID_HORIZONTAL), SetPIP(2, 0, 2), - NWidget(WWT_LABEL, COLOUR_DARK_GREEN, WID_BROS_INFO), SetMinimalSize(140, 14), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL), - NWidget(NWID_SPACER), SetFill(1, 0), - EndContainer(), - NWidget(NWID_HORIZONTAL), SetPIP(2, 0, 2), - NWidget(NWID_SPACER), SetFill(1, 0), - NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROS_LT_OFF), SetMinimalSize(60, 12), - SetDataTip(STR_STATION_BUILD_COVERAGE_OFF, STR_STATION_BUILD_COVERAGE_AREA_OFF_TOOLTIP), - NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROS_LT_ON), SetMinimalSize(60, 12), - SetDataTip(STR_STATION_BUILD_COVERAGE_ON, STR_STATION_BUILD_COVERAGE_AREA_ON_TOOLTIP), - NWidget(NWID_SPACER), SetFill(1, 0), - EndContainer(), - NWidget(NWID_SPACER), SetMinimalSize(0, 10), SetResize(0, 1), EndContainer(), }; @@ -1293,29 +1704,66 @@ static const NWidgetPart _nested_tram_station_picker_widgets[] = { NWidget(NWID_HORIZONTAL), NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN), NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_BROS_CAPTION), + NWidget(WWT_SHADEBOX, COLOUR_DARK_GREEN), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_DEFSIZE), + NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN), + EndContainer(), EndContainer(), NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BROS_BACKGROUND), - NWidget(NWID_SPACER), SetMinimalSize(0, 3), - NWidget(NWID_HORIZONTAL), SetPIP(0, 2, 0), - NWidget(NWID_SPACER), SetFill(1, 0), - NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_X), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(), - NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_Y), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(), - NWidget(NWID_SPACER), SetFill(1, 0), + NWidget(NWID_HORIZONTAL), + NWidget(NWID_VERTICAL), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_ADDITIONS), + NWidget(NWID_HORIZONTAL), SetPIP(7, 0, 7), SetPadding(2, 0, 1, 0), + NWidget(WWT_MATRIX, COLOUR_GREY, WID_BROS_NEWST_LIST), SetMinimalSize(122, 71), SetFill(1, 0), + SetMatrixDataTip(1, 0, STR_STATION_BUILD_STATION_CLASS_TOOLTIP), SetScrollbar(WID_BROS_NEWST_SCROLL), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_BROS_NEWST_SCROLL), + EndContainer(), + EndContainer(), + NWidget(NWID_SPACER), SetMinimalSize(0, 3), + NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetMinimalSize(144, 11), SetDataTip(STR_STATION_BUILD_ORIENTATION, STR_NULL), SetPadding(1, 2, 0, 2), + NWidget(NWID_SPACER), SetMinimalSize(0, 1), + NWidget(NWID_HORIZONTAL), SetPIP(0, 2, 0), + NWidget(NWID_SPACER), SetFill(1, 0), + NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_X), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(), + NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_Y), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(), + NWidget(NWID_SPACER), SetFill(1, 0), + EndContainer(), + NWidget(NWID_SPACER), SetMinimalSize(0, 3), + NWidget(WWT_LABEL, COLOUR_DARK_GREEN, WID_BROS_SHOW_NEWST_TYPE), SetMinimalSize(144, 11), SetDataTip(STR_ORANGE_STRING, STR_NULL), SetPadding(1, 2, 4, 2), + NWidget(NWID_SPACER), SetMinimalSize(0, 1), + NWidget(NWID_HORIZONTAL), SetPIP(2, 0, 2), + NWidget(NWID_SPACER), SetFill(1, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROS_LT_OFF), SetMinimalSize(60, 12), + SetDataTip(STR_STATION_BUILD_COVERAGE_OFF, STR_STATION_BUILD_COVERAGE_AREA_OFF_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROS_LT_ON), SetMinimalSize(60, 12), + SetDataTip(STR_STATION_BUILD_COVERAGE_ON, STR_STATION_BUILD_COVERAGE_AREA_ON_TOOLTIP), + NWidget(NWID_SPACER), SetFill(1, 0), + EndContainer(), + NWidget(NWID_SPACER), SetMinimalSize(0, 10), SetResize(0, 1), + EndContainer(), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_MATRIX), + /* We need an additional background for the matrix, as the matrix cannot handle the scrollbar due to not being an NWidgetCore. */ + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetScrollbar(WID_BROS_MATRIX_SCROLL), + NWidget(NWID_HORIZONTAL), + NWidget(NWID_MATRIX, COLOUR_DARK_GREEN, WID_BROS_MATRIX), SetScrollbar(WID_BROS_MATRIX_SCROLL), SetPIP(0, 2, 0), SetPadding(2, 0, 0, 0), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BROS_IMAGE), SetMinimalSize(66, 60), + SetFill(0, 0), SetResize(0, 0), SetDataTip(0x0, STR_STATION_BUILD_STATION_TYPE_TOOLTIP), SetScrollbar(WID_BROS_MATRIX_SCROLL), + EndContainer(), + EndContainer(), + NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_BROS_MATRIX_SCROLL), + EndContainer(), + EndContainer(), + EndContainer(), EndContainer(), - NWidget(NWID_SPACER), SetMinimalSize(0, 1), - NWidget(NWID_HORIZONTAL), SetPIP(2, 0, 2), - NWidget(WWT_LABEL, COLOUR_DARK_GREEN, WID_BROS_INFO), SetMinimalSize(140, 14), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL), - NWidget(NWID_SPACER), SetFill(1, 0), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_EMPTY, INVALID_COLOUR, WID_BROS_INFO), SetFill(1, 1), SetResize(1, 0), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_RESIZE), + NWidget(NWID_VERTICAL), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetFill(0, 1), EndContainer(), + NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN), + EndContainer(), + EndContainer(), EndContainer(), - NWidget(NWID_HORIZONTAL), SetPIP(2, 0, 2), - NWidget(NWID_SPACER), SetFill(1, 0), - NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROS_LT_OFF), SetMinimalSize(60, 12), - SetDataTip(STR_STATION_BUILD_COVERAGE_OFF, STR_STATION_BUILD_COVERAGE_AREA_OFF_TOOLTIP), - NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROS_LT_ON), SetMinimalSize(60, 12), - SetDataTip(STR_STATION_BUILD_COVERAGE_ON, STR_STATION_BUILD_COVERAGE_AREA_ON_TOOLTIP), - NWidget(NWID_SPACER), SetFill(1, 0), - EndContainer(), - NWidget(NWID_SPACER), SetMinimalSize(0, 10), SetResize(0, 1), EndContainer(), }; @@ -1333,8 +1781,8 @@ static void ShowRVStationPicker(Window *parent, RoadStopType rs) void InitializeRoadGui() { - _road_depot_orientation = DIAGDIR_NW; - _road_station_picker_orientation = DIAGDIR_NW; + _build_depot_direction = DIAGDIR_NW; + _roadstop_gui_settings.orientation = DIAGDIR_NW; } diff --git a/src/saveload/extended_ver_sl.cpp b/src/saveload/extended_ver_sl.cpp index 85a945082d..6df9dc469b 100644 --- a/src/saveload/extended_ver_sl.cpp +++ b/src/saveload/extended_ver_sl.cpp @@ -170,6 +170,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = { { XSLFI_ROAD_WAYPOINTS, XSCF_NULL, 1, 1, "road_waypoints", nullptr, nullptr, nullptr }, { XSLFI_MORE_STATION_TYPES, XSCF_NULL, 1, 1, "more_station_types", nullptr, nullptr, nullptr }, { XSLFI_RV_ORDER_EXTRA_FLAGS, XSCF_IGNORABLE_UNKNOWN, 1, 1, "rv_order_extra_flags", nullptr, nullptr, nullptr }, + { XSLFI_GRF_ROADSTOPS, XSCF_NULL, 1, 1, "grf_road_stops", nullptr, nullptr, nullptr }, { XSLFI_SCRIPT_INT64, XSCF_NULL, 1, 1, "script_int64", nullptr, nullptr, nullptr }, { XSLFI_NULL, XSCF_NULL, 0, 0, nullptr, nullptr, nullptr, nullptr },// This is the end marker }; diff --git a/src/saveload/extended_ver_sl.h b/src/saveload/extended_ver_sl.h index 189a4b2431..15d5a8b60b 100644 --- a/src/saveload/extended_ver_sl.h +++ b/src/saveload/extended_ver_sl.h @@ -123,6 +123,7 @@ enum SlXvFeatureIndex { XSLFI_ROAD_WAYPOINTS, ///< Road waypoints XSLFI_MORE_STATION_TYPES, ///< More station types (field widening) XSLFI_RV_ORDER_EXTRA_FLAGS, ///< Road vehicle order extra flags + XSLFI_GRF_ROADSTOPS, ///< NewGRF road stops XSLFI_SCRIPT_INT64, ///< See: SLV_SCRIPT_INT64 diff --git a/src/saveload/station_sl.cpp b/src/saveload/station_sl.cpp index 7c1ed34f89..62439eb110 100644 --- a/src/saveload/station_sl.cpp +++ b/src/saveload/station_sl.cpp @@ -13,6 +13,7 @@ #include "../roadstop_base.h" #include "../vehicle_base.h" #include "../newgrf_station.h" +#include "../newgrf_roadstop.h" #include "saveload.h" #include "saveload_buffer.h" @@ -114,6 +115,11 @@ void AfterLoadStations() st->speclist[i].spec = StationClass::GetByGrf(st->speclist[i].grfid, st->speclist[i].localidx, nullptr); } + for (uint i = 0; i < st->num_roadstop_specs; i++) { + if (st->roadstop_speclist[i].grfid == 0) continue; + + st->roadstop_speclist[i].spec = RoadStopClass::GetByGrf(st->roadstop_speclist[i].grfid, st->roadstop_speclist[i].localidx, nullptr); + } if (Station::IsExpected(st)) { Station *sta = Station::From(st); @@ -122,6 +128,7 @@ void AfterLoadStations() } StationUpdateCachedTriggers(st); + StationUpdateRoadStopCachedTriggers(st); } } @@ -402,6 +409,9 @@ static const SaveLoad _base_station_desc[] = { SLE_VAR(BaseStation, random_bits, SLE_UINT16), SLE_VAR(BaseStation, waiting_triggers, SLE_UINT8), SLE_VAR(BaseStation, num_specs, SLE_UINT8), + SLE_CONDVAR_X(BaseStation, num_roadstop_specs, SLE_UINT8, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_GRF_ROADSTOPS)), + SLE_CONDVARVEC_X(BaseStation, custom_road_stop_tiles, SLE_UINT32, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_GRF_ROADSTOPS)), + SLE_CONDVARVEC_X(BaseStation, custom_road_stop_random_bits, SLE_UINT8, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_GRF_ROADSTOPS)), }; static OldPersistentStorage _old_st_persistent_storage; @@ -554,6 +564,10 @@ static void RealSave_STNN(BaseStation *bst) for (uint i = 0; i < bst->num_specs; i++) { SlObjectSaveFiltered(&bst->speclist[i], _filtered_station_speclist_desc); } + + for (uint i = 0; i < bst->num_roadstop_specs; i++) { + SlObjectSaveFiltered(&bst->roadstop_speclist[i], _filtered_station_speclist_desc); + } } static void Save_STNN() @@ -676,6 +690,14 @@ static void Load_STNN() SlObjectLoadFiltered(&bst->speclist[i], _filtered_station_speclist_desc); } } + + if (bst->num_roadstop_specs != 0) { + /* Allocate speclist memory when loading a game */ + bst->roadstop_speclist = CallocT(bst->num_roadstop_specs); + for (uint i = 0; i < bst->num_roadstop_specs; i++) { + SlObjectLoadFiltered(&bst->roadstop_speclist[i], _filtered_station_speclist_desc); + } + } } } diff --git a/src/settings.cpp b/src/settings.cpp index 5cee4f1907..204fbe5084 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -933,6 +933,8 @@ static void StationSpreadChanged(int32 new_value) { InvalidateWindowData(WC_SELECT_STATION, 0); InvalidateWindowData(WC_BUILD_STATION, 0); + InvalidateWindowData(WC_BUS_STATION, 0); + InvalidateWindowData(WC_TRUCK_STATION, 0); } static void UpdateConsists(int32 new_value) diff --git a/src/station.cpp b/src/station.cpp index d996e18db7..ee6d9c739e 100644 --- a/src/station.cpp +++ b/src/station.cpp @@ -57,6 +57,7 @@ void RebuildStationKdtree() BaseStation::~BaseStation() { free(this->speclist); + free(this->roadstop_speclist); if (CleaningPool()) return; @@ -184,6 +185,31 @@ void BaseStation::PostDestructor(size_t index) InvalidateWindowData(WC_SELECT_STATION, 0, 0); } +void BaseStation::SetRoadStopRandomBits(TileIndex tile, byte random_bits) +{ + for (size_t i = 0; i < this->custom_road_stop_tiles.size(); i++) { + if (this->custom_road_stop_tiles[i] == tile) { + this->custom_road_stop_random_bits[i] = random_bits; + return; + } + } + this->custom_road_stop_tiles.push_back(tile); + this->custom_road_stop_random_bits.push_back(random_bits); +} + +void BaseStation::RemoveRoadStopRandomBits(TileIndex tile) +{ + for (size_t i = 0; i < this->custom_road_stop_tiles.size(); i++) { + if (this->custom_road_stop_tiles[i] == tile) { + this->custom_road_stop_tiles[i] = this->custom_road_stop_tiles.back(); + this->custom_road_stop_random_bits[i] = this->custom_road_stop_random_bits.back(); + this->custom_road_stop_tiles.pop_back(); + this->custom_road_stop_random_bits.pop_back(); + return; + } + } +} + /** * Get the primary road stop (the first road stop) that the given vehicle can load/unload. * @param v the vehicle to get the first road stop for diff --git a/src/station_base.h b/src/station_base.h index c6157e2dbd..8849418588 100644 --- a/src/station_base.h +++ b/src/station_base.h @@ -873,6 +873,11 @@ public: return IsRailStationTile(tile) && GetStationIndex(tile) == this->index; } + inline bool TileBelongsToRoadStop(TileIndex tile) const + { + return IsAnyRoadStopTile(tile) && GetStationIndex(tile) == this->index; + } + inline bool TileBelongsToAirport(TileIndex tile) const { return IsAirportTile(tile) && GetStationIndex(tile) == this->index; diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 1d286971d4..f680719424 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -58,6 +58,7 @@ #include "zoning.h" #include "tunnelbridge_map.h" #include "cheat_type.h" +#include "newgrf_roadstop.h" #include "table/strings.h" @@ -2049,10 +2050,12 @@ static CommandCost FindJoiningRoadStop(StationID existing_stop, StationID statio * bit 3: #Axis of the road for drive-through stops. * bit 5..10: The roadtype. * bit 16..31: Station ID to join (NEW_STATION if build new one). + * @param p3 bit 0..7: Roadstop class. + * bit 8..15: Roadstopspec index. * @param text Unused. * @return The cost of this operation or an error. */ -CommandCost CmdBuildRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +CommandCost CmdBuildRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, uint64 p3, const char *text, uint32 binary_length) { bool type = HasBit(p2, 0); bool is_drive_through = HasBit(p2, 1); @@ -2066,6 +2069,19 @@ CommandCost CmdBuildRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, uin uint8 width = (uint8)GB(p1, 0, 8); uint8 length = (uint8)GB(p1, 8, 8); + RoadStopClassID spec_class = Extract(p3); + byte spec_index = GB(p3, 8, 8); + + /* Check if the given station class is valid */ + if ((uint)spec_class >= RoadStopClass::GetClassCount() || spec_class == ROADSTOP_CLASS_WAYP) return CMD_ERROR; + if (spec_index >= RoadStopClass::Get(spec_class)->GetSpecCount()) return CMD_ERROR; + + const RoadStopSpec *roadstopspec = RoadStopClass::Get(spec_class)->GetSpec(spec_index); + if (roadstopspec != nullptr) { + if (type && roadstopspec->stop_type != ROADSTOPTYPE_FREIGHT && roadstopspec->stop_type != ROADSTOPTYPE_ALL) return CMD_ERROR; + if (!type && roadstopspec->stop_type != ROADSTOPTYPE_PASSENGER && roadstopspec->stop_type != ROADSTOPTYPE_ALL) return CMD_ERROR; + } + /* Check if the requested road stop is too big */ if (width > _settings_game.station.station_spread || length > _settings_game.station.station_spread) return_cmd_error(STR_ERROR_STATION_TOO_SPREAD_OUT); /* Check for incorrect width / length. */ @@ -2112,6 +2128,10 @@ CommandCost CmdBuildRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, uin ret = BuildStationPart(&st, flags, reuse, roadstop_area, STATIONNAMING_ROAD); if (ret.Failed()) return ret; + /* Check if we can allocate a custom stationspec to this station */ + int specindex = AllocateRoadStopSpecToStation(roadstopspec, st, (flags & DC_EXEC) != 0); + if (specindex == -1) return_cmd_error(STR_ERROR_TOO_MANY_STATION_SPECS); + if (flags & DC_EXEC) { /* Check every tile in the area. */ for (TileIndex cur_tile : roadstop_area) { @@ -2169,6 +2189,9 @@ CommandCost CmdBuildRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, uin } Company::Get(st->owner)->infrastructure.station++; + SetCustomRoadStopSpecIndex(cur_tile, specindex); + if (roadstopspec != nullptr) st->SetRoadStopRandomBits(cur_tile, GB(Random(), 0, 4)); + MarkTileDirtyByTile(cur_tile); UpdateRoadCachedOneWayStatesAroundTile(cur_tile); } @@ -2222,10 +2245,15 @@ CommandCost RemoveRoadWaypointStop(TileIndex tile, DoCommandFlag flags) Company::Get(wp->owner)->infrastructure.station--; DirtyCompanyInfrastructureWindows(wp->owner); + uint specindex = GetCustomRoadStopSpecIndex(tile); + DoClearSquare(tile); wp->rect.AfterRemoveTile(wp, tile); + wp->RemoveRoadStopRandomBits(tile); + DeallocateRoadStopSpecFromStation(wp, specindex); + MakeRoadWaypointStationAreaSmaller(wp, wp->road_waypoint_area); UpdateStationSignCoord(wp); @@ -2311,6 +2339,8 @@ CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags) Company::Get(st->owner)->infrastructure.station--; DirtyCompanyInfrastructureWindows(st->owner); + uint specindex = GetCustomRoadStopSpecIndex(tile); + if (IsDriveThroughStopTile(tile)) { /* Clears the tile for us */ cur_stop->ClearDriveThrough(); @@ -2332,6 +2362,9 @@ CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags) st->AfterStationTileSetChange(false, is_truck ? STATION_TRUCK: STATION_BUS); + st->RemoveRoadStopRandomBits(tile); + DeallocateRoadStopSpecFromStation(st, specindex); + /* Update the tile area of the truck/bus stop */ if (is_truck) { st->truck_station.Clear(); @@ -3417,25 +3450,38 @@ draw_default_foundation: if (IsAnyRoadStop(ti->tile)) { RoadType road_rt = GetRoadTypeRoad(ti->tile); RoadType tram_rt = GetRoadTypeTram(ti->tile); - const RoadTypeInfo* road_rti = road_rt == INVALID_ROADTYPE ? nullptr : GetRoadTypeInfo(road_rt); - const RoadTypeInfo* tram_rti = tram_rt == INVALID_ROADTYPE ? nullptr : GetRoadTypeInfo(tram_rt); + const RoadTypeInfo *road_rti = road_rt == INVALID_ROADTYPE ? nullptr : GetRoadTypeInfo(road_rt); + const RoadTypeInfo *tram_rti = tram_rt == INVALID_ROADTYPE ? nullptr : GetRoadTypeInfo(tram_rt); + + Axis axis = GetRoadStopDir(ti->tile) == DIAGDIR_NE ? AXIS_X : AXIS_Y; + DiagDirection dir = GetRoadStopDir(ti->tile); + + const RoadStopSpec *stopspec = GetRoadStopSpec(ti->tile); + if (stopspec != nullptr) { + int view = dir; + if (IsDriveThroughStopTile(ti->tile)) view += 4; + st = BaseStation::GetByTile(ti->tile); + RoadStopResolverObject object(stopspec, st, ti->tile, nullptr, GetStationType(ti->tile), view); + const SpriteGroup *group = object.Resolve(); + const DrawTileSprites *dts = ((const TileLayoutSpriteGroup *)group)->ProcessRegisters(nullptr); + t = dts; + } if (IsDriveThroughStopTile(ti->tile)) { - Axis axis = GetRoadStopDir(ti->tile) == DIAGDIR_NE ? AXIS_X : AXIS_Y; - uint sprite_offset = axis == AXIS_X ? 1 : 0; + if (stopspec == nullptr || (stopspec->draw_mode & ROADSTOP_DRAW_MODE_OVERLAY) != 0) { + uint sprite_offset = axis == AXIS_X ? 1 : 0; + DrawRoadOverlays(ti, PAL_NONE, road_rti, tram_rti, sprite_offset, sprite_offset); - DrawRoadOverlays(ti, PAL_NONE, road_rti, tram_rti, sprite_offset, sprite_offset); - - DisallowedRoadDirections drd = GetDriveThroughStopDisallowedRoadDirections(ti->tile); - if (drd != DRD_NONE) { - DrawGroundSpriteAt(SPR_ONEWAY_BASE + drd - 1 + ((axis == AXIS_X) ? 0 : 3), PAL_NONE, 8, 8, 0); + DisallowedRoadDirections drd = GetDriveThroughStopDisallowedRoadDirections(ti->tile); + if (drd != DRD_NONE) { + DrawGroundSpriteAt(SPR_ONEWAY_BASE + drd - 1 + ((axis == AXIS_X) ? 0 : 3), PAL_NONE, 8, 8, 0); + } } } else { /* Non-drivethrough road stops are only valid for roads. */ assert_tile(road_rt != INVALID_ROADTYPE && tram_rt == INVALID_ROADTYPE, ti->tile); - if (road_rti->UsesOverlay()) { - DiagDirection dir = GetRoadStopDir(ti->tile); + if ((stopspec != nullptr && (stopspec->draw_mode & ROADSTOP_DRAW_MODE_ROAD) != 0) && road_rti->UsesOverlay()) { SpriteID ground = GetCustomRoadSprite(road_rti, ti->tile, ROTSG_ROADSTOP); DrawGroundSprite(ground + dir, PAL_NONE); } @@ -4519,6 +4565,7 @@ static uint UpdateStationWaiting(Station *st, CargoID type, uint amount, SourceT TriggerStationRandomisation(st, st->xy, SRT_NEW_CARGO, type); TriggerStationAnimation(st, st->xy, SAT_NEW_CARGO, type); AirportAnimationTrigger(st, AAT_STATION_NEW_CARGO, type); + TriggerRoadStopRandomisation(st, st->xy, RSRT_NEW_CARGO, type); SetWindowDirty(WC_STATION_VIEW, st->index); st->MarkTilesDirty(true); diff --git a/src/station_map.h b/src/station_map.h index 2b8978c3c8..440b9e2007 100644 --- a/src/station_map.h +++ b/src/station_map.h @@ -605,6 +605,42 @@ static inline uint GetCustomStationSpecIndex(TileIndex t) return _m[t].m4; } +/** + * Is there a custom road stop spec on this tile? + * @param t Tile to query + * @pre IsAnyRoadStopTile(t) + * @return True if this station is part of a newgrf station. + */ +static inline bool IsCustomRoadStopSpecIndex(TileIndex t) +{ + assert_tile(IsAnyRoadStopTile(t), t); + return GB(_me[t].m8, 0, 6) != 0; +} + +/** + * Set the custom road stop spec for this tile. + * @param t Tile to set the stationspec of. + * @param specindex The new spec. + * @pre IsAnyRoadStopTile(t) + */ +static inline void SetCustomRoadStopSpecIndex(TileIndex t, byte specindex) +{ + assert_tile(IsAnyRoadStopTile(t), t); + SB(_me[t].m8, 0, 6, specindex); +} + +/** + * Get the custom road stop spec for this tile. + * @param t Tile to query + * @pre IsAnyRoadStopTile(t) + * @return The custom station spec of this tile. + */ +static inline uint GetCustomRoadStopSpecIndex(TileIndex t) +{ + assert_tile(IsAnyRoadStopTile(t), t); + return GB(_me[t].m8, 0, 6); +} + /** * Set the random bits for a station tile. * @param t Tile to set random bits for. diff --git a/src/table/newgrf_debug_data.h b/src/table/newgrf_debug_data.h index f69d3f0ed3..76ed5ec5b5 100644 --- a/src/table/newgrf_debug_data.h +++ b/src/table/newgrf_debug_data.h @@ -1370,6 +1370,34 @@ static const NIFeature _nif_roadtype = { new NIHRoadType(), }; +/*** ***/ + +static const NIVariable _nif_roadstops[] = { + NIV(0x40, "test"), + NIV_END(), +}; + +class NIHRoadStop : public NIHelper { + bool IsInspectable(uint index) const override { return false; } + uint GetParent(uint index) const override { return UINT32_MAX; } + const void *GetInstance(uint index)const override { return nullptr; } + const void *GetSpec(uint index) const override { return nullptr; } + void SetStringParameters(uint index) const override { } + uint32 GetGRFID(uint index) const override { return 0; } + + uint Resolve(uint index, uint var, uint param, GetVariableExtra *extra) const override + { + return UINT32_MAX; + } +}; + +static const NIFeature _nif_roadstop = { + nullptr, + nullptr, + _nif_roadstops, + new NIHRoadStop(), +}; + /** Table with all NIFeatures. */ static const NIFeature * const _nifeatures[] = { &_nif_vehicle, // GSF_TRAINS @@ -1392,6 +1420,7 @@ static const NIFeature * const _nifeatures[] = { &_nif_airporttile, // GSF_AIRPORTTILES &_nif_roadtype, // GSF_ROADTYPES &_nif_roadtype, // GSF_TRAMTYPES + &_nif_roadstop, // GSF_ROADSTATIONS &_nif_town, // GSF_FAKE_TOWNS &_nif_station_struct, // GSF_FAKE_STATION_STRUCT }; diff --git a/src/waypoint_cmd.cpp b/src/waypoint_cmd.cpp index 0d4a001c6e..d5a0f64e02 100644 --- a/src/waypoint_cmd.cpp +++ b/src/waypoint_cmd.cpp @@ -25,6 +25,7 @@ #include "string_func.h" #include "company_func.h" #include "newgrf_station.h" +#include "newgrf_roadstop.h" #include "company_base.h" #include "water.h" #include "company_gui.h" @@ -192,7 +193,6 @@ extern CommandCost IsRailStationBridgeAboveOk(TileIndex tile, const StationSpec * - p1 = (bit 24) - allow waypoints directly adjacent to other waypoints. * @param p2 various bitstuffed elements * - p2 = (bit 0- 7) - custom station class - * - p2 = (bit 8-15) - custom station id * - p2 = (bit 31-16) - station ID to join * @param p3 various bitstuffed elements * - p3 = (bit 0-31) - custom station id @@ -337,11 +337,14 @@ CommandCost CmdBuildRailWaypoint(TileIndex start_tile, DoCommandFlag flags, uint * bit 8..15: Length of the road stop. * bit 16: Allow stations directly adjacent to other stations. * bit 17: #Axis of the road. - * @param p2 bit 16..31: Station ID to join (NEW_STATION if build new one). + * @param p2 bit 0..7: Custom road stop class + * bit 16..31: Station ID to join (NEW_STATION if build new one). + * @param p3 various bitstuffed elements + * - p3 = (bit 0-31) - custom road stop id * @param text Unused. * @return The cost of this operation or an error. */ -CommandCost CmdBuildRoadWaypoint(TileIndex start_tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +CommandCost CmdBuildRoadWaypoint(TileIndex start_tile, DoCommandFlag flags, uint32 p1, uint32 p2, uint64 p3, const char *text, uint32 binary_length) { StationID station_to_join = GB(p2, 16, 16); byte width = GB(p1, 0, 8); @@ -349,6 +352,15 @@ CommandCost CmdBuildRoadWaypoint(TileIndex start_tile, DoCommandFlag flags, uint bool adjacent = HasBit(p1, 16); Axis axis = Extract(p1); + RoadStopClassID spec_class = Extract(p2); + uint spec_index = GB(p3, 0, 32); + + /* Check if the given road stop class is valid */ + if (spec_class != ROADSTOP_CLASS_WAYP) return CMD_ERROR; + if (spec_index >= RoadStopClass::Get(spec_class)->GetSpecCount()) return CMD_ERROR; + + const RoadStopSpec *spec = RoadStopClass::Get(spec_class)->GetSpec(spec_index); + /* The number of parts to build */ byte count = axis == AXIS_X ? height : width; @@ -393,6 +405,9 @@ CommandCost CmdBuildRoadWaypoint(TileIndex start_tile, DoCommandFlag flags, uint if (!Waypoint::CanAllocateItem()) return_cmd_error(STR_ERROR_TOO_MANY_STATIONS_LOADING); } + /* Check if we can allocate a custom stationspec to this station */ + if (AllocateRoadStopSpecToStation(spec, wp, false) == -1) return_cmd_error(STR_ERROR_TOO_MANY_STATION_SPECS); + if (flags & DC_EXEC) { if (wp == nullptr) { wp = new Waypoint(start_tile); @@ -414,6 +429,8 @@ CommandCost CmdBuildRoadWaypoint(TileIndex start_tile, DoCommandFlag flags, uint wp->UpdateVirtCoord(); + byte map_spec_index = AllocateRoadStopSpecToStation(spec, wp, true); + /* Check every tile in the area. */ for (TileIndex cur_tile : roadstop_area) { /* Get existing road types and owners before any tile clearing */ @@ -444,6 +461,8 @@ CommandCost CmdBuildRoadWaypoint(TileIndex start_tile, DoCommandFlag flags, uint MakeDriveThroughRoadStop(cur_tile, wp->owner, road_owner, tram_owner, wp->index, STATION_ROADWAYPOINT, road_rt, tram_rt, axis); SetDriveThroughStopDisallowedRoadDirections(cur_tile, drd); + SetCustomRoadStopSpecIndex(cur_tile, map_spec_index); + if (spec != nullptr) wp->SetRoadStopRandomBits(cur_tile, 0); Company::Get(wp->owner)->infrastructure.station++; diff --git a/src/widgets/road_widget.h b/src/widgets/road_widget.h index 5d9ea034af..acca1e5731 100644 --- a/src/widgets/road_widget.h +++ b/src/widgets/road_widget.h @@ -53,6 +53,22 @@ enum BuildRoadStationWidgets { WID_BROS_LT_OFF, ///< Turn off area highlight. WID_BROS_LT_ON, ///< Turn on area highlight. WID_BROS_INFO, ///< Station acceptance info. + + WID_BROS_MATRIX, ///< Matrix widget displaying all available road stops. + WID_BROS_IMAGE, ///< Panel used for each image of the matrix. + WID_BROS_MATRIX_SCROLL, ///< Scrollbar of the #WID_BROS_SHOW_NEWST_ADDITIONS. + + WID_BROS_FILTER_CONTAINER, ///< Container for the filter text box for the road stop class list. + WID_BROS_FILTER_EDITBOX, ///< Filter text box for the road stop class list. + WID_BROS_SHOW_NEWST_DEFSIZE, ///< Selection for default-size button for new road stops. + WID_BROS_SHOW_NEWST_ADDITIONS, ///< Selection for new class selection list. + WID_BROS_SHOW_NEWST_MATRIX, ///< Selection for new stop image matrix. + WID_BROS_SHOW_NEWST_RESIZE, ///< Selection for panel and resize at bottom right for new stops. + WID_BROS_SHOW_NEWST_ORIENTATION, ///< Selection for the orientation string for new stops. + WID_BROS_SHOW_NEWST_TYPE_SEL, ///< Selection for the type name. + WID_BROS_SHOW_NEWST_TYPE, ///< Display of selected stop type. + WID_BROS_NEWST_LIST, ///< List with new road stops. + WID_BROS_NEWST_SCROLL, ///< Scrollbar of the #WID_BROS_NEWST_LIST. }; #endif /* WIDGETS_ROAD_WIDGET_H */ diff --git a/src/window_type.h b/src/window_type.h index ba9c22f23c..73d2422c25 100644 --- a/src/window_type.h +++ b/src/window_type.h @@ -106,7 +106,7 @@ enum WindowClass { * - 0 = #ToolTipsWidgets */ WC_TOOLTIPS, - + /** * Station rating tooltip window; %Window numbers: * - 0 = #ToolTipsWidgets