diff --git a/docs/landscape.html b/docs/landscape.html index ab4ad989e3..0f9a18bbf5 100644 --- a/docs/landscape.html +++ b/docs/landscape.html @@ -998,6 +998,7 @@
  • m7: animation frame (railway stations/waypoints, airports)
  • 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 fc0c6e0cfc..e7e55aabf3 100644 --- a/docs/landscape_grid.html +++ b/docs/landscape_grid.html @@ -203,7 +203,7 @@ the array so you can quickly see what is used and what is not. OOOO OXXX OOXX XOOO OOOX XXXX - OOOO XXXX XXOO OOOO + OOOO XXXX XX XXXXXX airport diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b27a4ca080..5b49deba25 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -274,6 +274,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 2ad09ca21c..8550d3ec49 100644 --- a/src/base_station_base.h +++ b/src/base_station_base.h @@ -24,6 +24,17 @@ 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 +}; + +struct RoadStopTileData { + TileIndex tile; + uint8 random_bits; + uint8 animation_frame; +}; /** StationRect - used to track station spread out rectangle - cheaper than scanning whole map */ struct StationRect : public Rect { @@ -62,18 +73,23 @@ struct BaseStation : StationPool::PoolItem<&_station_pool> { Owner owner; ///< The owner of this station StationFacility facilities; ///< The facilities that this station has - std::vector speclist; ///< List of rail station specs of this station. + std::vector speclist; ///< List of rail station specs of this station. + std::vector roadstop_speclist; ///< List of road stop specs of this station Date build_date; ///< Date of construction uint16 random_bits; ///< Random bits assigned to this station 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 + uint8 cached_anim_triggers; ///< NOSAVE: Combined animation trigger bitmask, used to determine if trigger processing should happen. + uint8 cached_roadstop_anim_triggers; ///< NOSAVE: Combined animation trigger bitmask for road stops, used to determine if trigger processing should happen. + CargoTypes cached_cargo_triggers; ///< NOSAVE: Combined cargo trigger bitmask + CargoTypes cached_roadstop_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_roadstop_tile_data; ///< List of custom road stop tile data + /** * Initialize the base station. * @param tile The location of the station sign @@ -167,6 +183,30 @@ struct BaseStation : StationPool::PoolItem<&_station_pool> { return (this->facilities & ~FACIL_WAYPOINT) != 0; } + inline byte GetRoadStopRandomBits(TileIndex tile) const + { + for (const RoadStopTileData &tile_data : this->custom_roadstop_tile_data) { + if (tile_data.tile == tile) return tile_data.random_bits; + } + return 0; + } + + inline byte GetRoadStopAnimationFrame(TileIndex tile) const + { + for (const RoadStopTileData &tile_data : this->custom_roadstop_tile_data) { + if (tile_data.tile == tile) return tile_data.animation_frame; + } + return 0; + } + +private: + void SetRoadStopTileData(TileIndex tile, byte data, bool animation); + +public: + inline void SetRoadStopRandomBits(TileIndex tile, byte random_bits) { this->SetRoadStopTileData(tile, random_bits, false); } + inline void SetRoadStopAnimationFrame(TileIndex tile, byte frame) { this->SetRoadStopTileData(tile, frame, true); } + void RemoveRoadStopTileData(TileIndex tile); + static void PostDestructor(size_t index); private: diff --git a/src/cheat_gui.cpp b/src/cheat_gui.cpp index ba70bdb8e1..34418c9840 100644 --- a/src/cheat_gui.cpp +++ b/src/cheat_gui.cpp @@ -115,6 +115,8 @@ static int32 ClickChangeDateCheat(int32 p1, int32 p2) EnginesMonthlyLoop(); SetWindowDirty(WC_STATUS_BAR, 0); InvalidateWindowClassesData(WC_BUILD_STATION, 0); + InvalidateWindowClassesData(WC_BUS_STATION, 0); + InvalidateWindowClassesData(WC_TRUCK_STATION, 0); InvalidateWindowClassesData(WC_BUILD_OBJECT, 0); ResetSignalVariant(); return _cur_year; diff --git a/src/date.cpp b/src/date.cpp index 66ccc1444c..198dd89585 100644 --- a/src/date.cpp +++ b/src/date.cpp @@ -195,6 +195,8 @@ static void OnNewYear() VehiclesYearlyLoop(); TownsYearlyLoop(); InvalidateWindowClassesData(WC_BUILD_STATION); + InvalidateWindowClassesData(WC_BUS_STATION); + InvalidateWindowClassesData(WC_TRUCK_STATION); if (_network_server) NetworkServerYearlyLoop(); if (_cur_year == _settings_client.gui.semaphore_build_before) ResetSignalVariant(); diff --git a/src/economy.cpp b/src/economy.cpp index ab23288c5f..6d22e69ff3 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" @@ -1813,6 +1814,8 @@ 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); + TriggerRoadStopAnimation(st, st->xy, SAT_CARGO_TAKEN, v->cargo_type); } new_load_unload_ticks += loaded; @@ -1833,6 +1836,9 @@ static void LoadUnloadVehicle(Vehicle *front) if (front->type == VEH_TRAIN) { TriggerStationRandomisation(st, front->tile, SRT_TRAIN_LOADS); TriggerStationAnimation(st, front->tile, SAT_TRAIN_LOADS); + } else if (front->type == VEH_ROAD) { + TriggerRoadStopRandomisation(st, front->tile, RSRT_VEH_LOADS); + TriggerRoadStopAnimation(st, front->tile, SAT_TRAIN_LOADS); } } diff --git a/src/newgrf.cpp b/src/newgrf.cpp index 84c8cab467..611ce367c2 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" @@ -4747,6 +4748,131 @@ 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 = CIR_UNKNOWN; + break; + } + + return ret; +} + +static ChangeInfoResult RoadStopChangeInfo(uint id, int numinfo, int prop, 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) { + 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 0x08: { // Road Stop Class ID + RoadStopSpec **spec = &_cur.grffile->roadstops[id + i]; + + if (*spec == nullptr) { + *spec = CallocT(1); + new (*spec) RoadStopSpec(); + } + + uint32 classid = buf->ReadDWord(); + (*spec)->cls_id = RoadStopClass::Allocate(BSWAP32(classid)); + (*spec)->spec_id = id + i; + break; + } + + case 0x09: // Road stop type + rs->stop_type = (RoadStopAvailabilityType)buf->ReadByte(); + break; + + case 0x0A: // Road Stop Name + AddStringForMapping(buf->ReadWord(), &rs->name); + break; + + case 0x0B: // Road Stop Class name + AddStringForMapping(buf->ReadWord(), &RoadStopClass::Get(rs->cls_id)->name); + break; + + case 0x0C: // The draw mode + rs->draw_mode = (RoadStopDrawMode)buf->ReadByte(); + break; + + case 0x0D: // Cargo types for random triggers + rs->cargo_triggers = TranslateRefitMask(buf->ReadDWord()); + break; + + case 0x0E: // Animation info + rs->animation.frames = buf->ReadByte(); + rs->animation.status = buf->ReadByte(); + break; + + case 0x0F: // Animation speed + rs->animation.speed = buf->ReadByte(); + break; + + case 0x10: // Animation triggers + rs->animation.triggers = buf->ReadWord(); + break; + + case 0x11: // Callback mask + rs->callback_mask = buf->ReadByte(); + break; + + case 0x12: // General flags + rs->flags = (uint8)buf->ReadDWord(); // Future-proofing, size this as 4 bytes, but we only need one byte's worth of flags at present + break; + + case 0x15: // Cost multipliers + rs->build_cost_multiplier = buf->ReadByte(); + rs->clear_cost_multiplier = buf->ReadByte(); + break; + + default: + ret = CIR_UNKNOWN; + break; + } + } + + return ret; +} + static bool HandleChangeInfoResult(const char *caller, ChangeInfoResult cir, uint8 feature, uint8 property) { switch (cir) { @@ -4811,6 +4937,7 @@ static void FeatureChangeInfo(ByteReader *buf) /* GSF_AIRPORTTILES */ AirportTilesChangeInfo, /* GSF_ROADTYPES */ RoadTypeChangeInfo, /* GSF_TRAMTYPES */ TramTypeChangeInfo, + /* GSF_ROADSTOPS */ RoadStopChangeInfo, }; static_assert(GSF_END == lengthof(handler)); @@ -5289,7 +5416,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()); @@ -5404,7 +5532,7 @@ static CargoID TranslateCargo(uint8 feature, uint8 ctype) } } /* Special cargo types for purchase list and stations */ - if (feature == GSF_STATIONS && ctype == 0xFE) return CT_DEFAULT_NA; + if ((feature == GSF_STATIONS || feature == GSF_ROADSTOPS) && ctype == 0xFE) return CT_DEFAULT_NA; if (ctype == 0xFF) return CT_PURCHASE; if (_cur.grffile->cargo_list.size() == 0) { @@ -5923,6 +6051,61 @@ 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(); + } + + uint8 cidcount = buf->ReadByte(); + for (uint c = 0; c < cidcount; c++) { + uint8 ctype = buf->ReadByte(); + uint16 groupid = buf->ReadWord(); + if (!IsValidGroupID(groupid, "RoadStopMapSpriteGroup")) continue; + + ctype = TranslateCargo(GSF_ROADSTOPS, ctype); + if (ctype == CT_INVALID) continue; + + 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; + } + + roadstopspec->grf_prop.spritegroup[ctype] = _cur.spritegroups[groupid]; + } + } + + 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) @@ -6023,6 +6206,10 @@ static void FeatureMapSpriteGroup(ByteReader *buf) AirportTileMapSpriteGroup(buf, idcount); return; + case GSF_ROADSTOPS: + RoadStopMapSpriteGroup(buf, idcount); + return; + default: grfmsg(1, "FeatureMapSpriteGroup: Unsupported feature 0x%02X, skipping", feature); return; @@ -8601,6 +8788,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() { @@ -8687,6 +8888,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 9e6a4f1c25..7561fb145b 100644 --- a/src/newgrf.h +++ b/src/newgrf.h @@ -84,6 +84,7 @@ enum GrfSpecFeature { GSF_AIRPORTTILES, GSF_ROADTYPES, GSF_TRAMTYPES, + GSF_ROADSTOPS, GSF_END, GSF_FAKE_TOWNS = GSF_END, ///< Fake town GrfSpecFeature for NewGRF debugging (parent scope) @@ -118,6 +119,7 @@ struct GRFFile : ZeroedMemoryAllocator { struct ObjectSpec **objectspec; struct AirportSpec **airportspec; struct AirportTileSpec **airtspec; + struct RoadStopSpec **roadstops; uint32 param[0x80]; uint param_end; ///< one more than the highest set parameter diff --git a/src/newgrf_airporttiles.cpp b/src/newgrf_airporttiles.cpp index b34992fcf0..ebdf226dd0 100644 --- a/src/newgrf_airporttiles.cpp +++ b/src/newgrf_airporttiles.cpp @@ -278,7 +278,7 @@ bool DrawNewAirportTile(TileInfo *ti, Station *st, StationGfx gfx, const Airport } /** Helper class for animation control. */ -struct AirportTileAnimationBase : public AnimationBase { +struct AirportTileAnimationBase : public AnimationBase > { static const CallbackID cb_animation_speed = CBID_AIRPTILE_ANIMATION_SPEED; static const CallbackID cb_animation_next_frame = CBID_AIRPTILE_ANIM_NEXT_FRAME; diff --git a/src/newgrf_animation_base.h b/src/newgrf_animation_base.h index faffe95e4f..8df0228408 100644 --- a/src/newgrf_animation_base.h +++ b/src/newgrf_animation_base.h @@ -17,15 +17,22 @@ #include "newgrf_callbacks.h" #include "tile_map.h" +template +struct TileAnimationFrameAnimationHelper { + static byte Get(Tobj *obj, TileIndex tile) { return GetAnimationFrame(tile); } + static void Set(Tobj *obj, TileIndex tile, byte frame) { SetAnimationFrame(tile, frame); } +}; + /** * Helper class for a unified approach to NewGRF animation. - * @tparam Tbase Instantiation of this class. - * @tparam Tspec NewGRF specification related to the animated tile. - * @tparam Tobj Object related to the animated tile. - * @tparam Textra Custom extra callback data. - * @tparam GetCallback The callback function pointer. + * @tparam Tbase Instantiation of this class. + * @tparam Tspec NewGRF specification related to the animated tile. + * @tparam Tobj Object related to the animated tile. + * @tparam Textra Custom extra callback data. + * @tparam GetCallback The callback function pointer. + * @tparam Tframehelper The animation frame get/set helper. */ -template +template struct AnimationBase { /** * Animate a single tile. @@ -55,7 +62,7 @@ struct AnimationBase { * maximum, corresponding to around 33 minutes. */ if (_tick_counter % (1ULL << animation_speed) != 0) return; - uint8 frame = GetAnimationFrame(tile); + uint8 frame = Tframehelper::Get(obj, tile); uint8 num_frames = spec->animation.frames; bool frame_set_by_callback = false; @@ -98,7 +105,7 @@ struct AnimationBase { } } - SetAnimationFrame(tile, frame); + Tframehelper::Set(obj, tile, frame); MarkTileDirtyByTile(tile); } @@ -124,7 +131,7 @@ struct AnimationBase { case 0xFE: AddAnimatedTile(tile); break; case 0xFF: DeleteAnimatedTile(tile); break; default: - SetAnimationFrame(tile, callback); + Tframehelper::Set(obj, tile, callback); AddAnimatedTile(tile); break; } diff --git a/src/newgrf_callbacks.h b/src/newgrf_callbacks.h index ea20105cb8..482031cec0 100644 --- a/src/newgrf_callbacks.h +++ b/src/newgrf_callbacks.h @@ -311,6 +311,15 @@ enum StationCallbackMask { CBM_STATION_SLOPE_CHECK = 4, ///< Check slope of new station tiles }; +/** + * Callback masks for road stops. + */ +enum RoadStopCallbackMask { + CBM_ROAD_STOP_AVAIL = 0, ///< Availability of road stop in construction window + CBM_ROAD_STOP_ANIMATION_NEXT_FRAME = 1, ///< Use a custom next frame callback + CBM_ROAD_STOP_ANIMATION_SPEED = 2, ///< Customize the animation speed of the road stop +}; + /** * Callback masks for houses. */ diff --git a/src/newgrf_debug_gui.cpp b/src/newgrf_debug_gui.cpp index 7c619e78c8..764fd995cf 100644 --- a/src/newgrf_debug_gui.cpp +++ b/src/newgrf_debug_gui.cpp @@ -776,6 +776,8 @@ GrfSpecFeature GetGrfSpecFeature(TileIndex tile) switch (GetStationType(tile)) { case STATION_RAIL: return GSF_STATIONS; case STATION_AIRPORT: return GSF_AIRPORTTILES; + case STATION_BUS: return GSF_ROADSTOPS; + case STATION_TRUCK: return GSF_ROADSTOPS; default: return GSF_INVALID; } } diff --git a/src/newgrf_house.cpp b/src/newgrf_house.cpp index 21a4413fa2..53ff4b8f72 100644 --- a/src/newgrf_house.cpp +++ b/src/newgrf_house.cpp @@ -480,7 +480,7 @@ uint16 GetSimpleHouseCallback(CallbackID callback, uint32 param1, uint32 param2, } /** Helper class for animation control. */ -struct HouseAnimationBase : public AnimationBase { +struct HouseAnimationBase : public AnimationBase > { static const CallbackID cb_animation_speed = CBID_HOUSE_ANIMATION_SPEED; static const CallbackID cb_animation_next_frame = CBID_HOUSE_ANIMATION_NEXT_FRAME; diff --git a/src/newgrf_industrytiles.cpp b/src/newgrf_industrytiles.cpp index 6d8601192f..72241fb120 100644 --- a/src/newgrf_industrytiles.cpp +++ b/src/newgrf_industrytiles.cpp @@ -257,7 +257,7 @@ uint16 GetSimpleIndustryCallback(CallbackID callback, uint32 param1, uint32 para } /** Helper class for animation control. */ -struct IndustryAnimationBase : public AnimationBase { +struct IndustryAnimationBase : public AnimationBase > { static const CallbackID cb_animation_speed = CBID_INDTILE_ANIMATION_SPEED; static const CallbackID cb_animation_next_frame = CBID_INDTILE_ANIM_NEXT_FRAME; diff --git a/src/newgrf_object.cpp b/src/newgrf_object.cpp index 730fa7afd0..ccd46e2e92 100644 --- a/src/newgrf_object.cpp +++ b/src/newgrf_object.cpp @@ -510,7 +510,7 @@ uint16 StubGetObjectCallback(CallbackID callback, uint32 param1, uint32 param2, } /** Helper class for animation control. */ -struct ObjectAnimationBase : public AnimationBase { +struct ObjectAnimationBase : public AnimationBase > { static const CallbackID cb_animation_speed = CBID_OBJECT_ANIMATION_SPEED; static const CallbackID cb_animation_next_frame = CBID_OBJECT_ANIMATION_NEXT_FRAME; diff --git a/src/newgrf_roadstop.cpp b/src/newgrf_roadstop.cpp new file mode 100644 index 0000000000..76b901d8f7 --- /dev/null +++ b/src/newgrf_roadstop.cpp @@ -0,0 +1,614 @@ +/* + * 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 "newgrf_animation_base.h" +#include "newgrf_sound.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(byte variable, uint32 parameter, bool *available) const +{ + auto get_road_type_variable = [&](RoadTramType rtt) -> uint32 { + RoadType rt; + if (this->tile == INVALID_TILE) { + rt = (GetRoadTramType(this->roadtype) == rtt) ? this->roadtype : INVALID_ROADTYPE; + } else { + rt = GetRoadType(this->tile, rtt); + } + if (rt == INVALID_ROADTYPE) { + return 0xFFFFFFFF; + } else { + return GetReverseRoadTypeTranslation(rt, this->roadstopspec->grf_prop.grffile); + } + }; + + switch (variable) { + /* View/rotation */ + case 0x40: return this->view; + + /* Stop type: 0: bus, 1: truck, 2: waypoint */ + case 0x41: + if (this->type == STATION_BUS) return 0; + if (this->type == STATION_TRUCK) return 1; + return 2; + + /* Terrain type */ + case 0x42: return this->tile == INVALID_TILE ? 0 : GetTerrainType(this->tile, TCX_NORMAL); // terrain_type + + /* Road type */ + case 0x43: return get_road_type_variable(RTT_ROAD); + + /* Tram type */ + case 0x44: return get_road_type_variable(RTT_TRAM); + + /* Town zone and Manhattan distance of closest town */ + case 0x45: { + if (this->tile == INVALID_TILE) return HZB_TOWN_EDGE << 16; + const Town *t = (this->st == nullptr) ? ClosestTownFromTile(this->tile, UINT_MAX) : this->st->town; + return t != nullptr ? (GetTownRadiusGroup(t, this->tile) << 16 | std::min(DistanceManhattan(this->tile, t->xy), 0xFFFFu)) : HZB_TOWN_EDGE << 16; + } + + /* Get square of Euclidian distance of closest town */ + case 0x46: { + if (this->tile == INVALID_TILE) return 0; + const Town *t = (this->st == nullptr) ? ClosestTownFromTile(this->tile, UINT_MAX) : this->st->town; + return t != nullptr ? DistanceSquare(this->tile, t->xy) : 0; + } + + /* Company information */ + case 0x47: return GetCompanyInfo(this->st == nullptr ? _current_company : this->st->owner); + + /* Animation frame */ + case 0x49: return this->tile == INVALID_TILE ? 0 : this->st->GetRoadStopAnimationFrame(this->tile); + + /* Variables which use the parameter */ + /* Variables 0x60 to 0x65 and 0x69 are handled separately below */ + + /* Animation frame of nearby tile */ + case 0x66: { + if (this->tile == INVALID_TILE) return UINT_MAX; + TileIndex tile = this->tile; + if (parameter != 0) tile = GetNearbyTile(parameter, tile); + return (IsRoadStopTile(tile) && GetStationIndex(tile) == this->st->index) ? this->st->GetRoadStopAnimationFrame(tile) : UINT_MAX; + } + + /* Land info of nearby tile */ + case 0x67: { + if (this->tile == INVALID_TILE) return 0; + TileIndex tile = this->tile; + if (parameter != 0) tile = GetNearbyTile(parameter, tile); // only perform if it is required + return GetNearbyTileInformation(tile, this->ro.grffile->grf_version >= 8); + } + + /* Road stop info of nearby tiles */ + case 0x68: { + if (this->tile == INVALID_TILE) return 0xFFFFFFFF; + TileIndex nearby_tile = GetNearbyTile(parameter, this->tile); + + if (!IsRoadStopTile(nearby_tile)) return 0xFFFFFFFF; + + uint32 grfid = this->st->roadstop_speclist[GetCustomRoadStopSpecIndex(this->tile)].grfid; + bool same_orientation = GetStationGfx(this->tile) == GetStationGfx(nearby_tile); + bool same_station = GetStationIndex(nearby_tile) == this->st->index; + uint32 res = GetStationGfx(nearby_tile) << 12 | !same_orientation << 11 | !!same_station << 10; + StationType type = GetStationType(nearby_tile); + if (type == STATION_TRUCK) res |= (1 << 16); + if (type == this->type) SetBit(res, 20); + + if (IsCustomRoadStopSpecIndex(nearby_tile)) { + const RoadStopSpecList ssl = BaseStation::GetByTile(nearby_tile)->roadstop_speclist[GetCustomRoadStopSpecIndex(nearby_tile)]; + res |= 1 << (ssl.grfid != grfid ? 9 : 8) | ssl.localidx; + } + return res; + } + + /* GRFID of nearby road stop tiles */ + case 0x6A: { + if (this->tile == INVALID_TILE) return 0xFFFFFFFF; + TileIndex nearby_tile = GetNearbyTile(parameter, this->tile); + + if (!IsRoadStopTile(nearby_tile)) return 0xFFFFFFFF; + if (!IsCustomRoadStopSpecIndex(nearby_tile)) return 0; + + const RoadStopSpecList ssl = BaseStation::GetByTile(nearby_tile)->roadstop_speclist[GetCustomRoadStopSpecIndex(nearby_tile)]; + return ssl.grfid; + } + + 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, available); + + *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, RoadType roadtype, StationType type, uint8 view, + CallbackID callback, uint32 param1, uint32 param2) + : ResolverObject(roadstopspec->grf_prop.grffile, callback, param1, param2), roadstop_scope(*this, st, roadstopspec, tile, roadtype, type, view) + { + + this->town_scope = nullptr; + + CargoID ctype = CT_DEFAULT_NA; + + if (st == nullptr) { + /* No station, so we are in a purchase list */ + ctype = CT_PURCHASE; + } else if (Station::IsExpected(st)) { + const Station *station = Station::From(st); + /* Pick the first cargo that we have waiting */ + for (const CargoSpec *cs : CargoSpec::Iterate()) { + if (roadstopspec->grf_prop.spritegroup[cs->Index()] != nullptr && + station->goods[cs->Index()].cargo.TotalCount() > 0) { + ctype = cs->Index(); + break; + } + } + } + + if (roadstopspec->grf_prop.spritegroup[ctype] == nullptr) { + ctype = CT_DEFAULT; + } + + /* Remember the cargo type we've picked */ + this->roadstop_scope.cargo_type = ctype; + this->root_spritegroup = roadstopspec->grf_prop.spritegroup[ctype]; +} + +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; +} + +uint16 GetRoadStopCallback(CallbackID callback, uint32 param1, uint32 param2, const RoadStopSpec *roadstopspec, BaseStation *st, TileIndex tile, RoadType roadtype, StationType type, uint8 view) +{ + RoadStopResolverObject object(roadstopspec, st, tile, roadtype, type, view, callback, param1, param2); + return object.ResolveCallback(); +} + +/** + * 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, roadtype, 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 (spec->draw_mode & ROADSTOP_DRAW_MODE_OVERLAY) { + 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 ((spec->draw_mode & ROADSTOP_DRAW_MODE_ROAD) && 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); +} + +/** Wrapper for animation control, see GetRoadStopCallback. */ +uint16 GetAnimRoadStopCallback(CallbackID callback, uint32 param1, uint32 param2, const RoadStopSpec *roadstopspec, BaseStation *st, TileIndex tile, int extra_data) +{ + return GetRoadStopCallback(callback, param1, param2, roadstopspec, st, tile, INVALID_ROADTYPE, GetStationType(tile), GetStationGfx(tile)); +} + +struct RoadStopAnimationFrameAnimationHelper { + static byte Get(BaseStation *st, TileIndex tile) { return st->GetRoadStopAnimationFrame(tile); } + static void Set(BaseStation *st, TileIndex tile, byte frame) { st->SetRoadStopAnimationFrame(tile, frame); } +}; + +/** Helper class for animation control. */ +struct RoadStopAnimationBase : public AnimationBase { + static const CallbackID cb_animation_speed = CBID_STATION_ANIMATION_SPEED; + static const CallbackID cb_animation_next_frame = CBID_STATION_ANIM_NEXT_FRAME; + + static const RoadStopCallbackMask cbm_animation_speed = CBM_ROAD_STOP_ANIMATION_SPEED; + static const RoadStopCallbackMask cbm_animation_next_frame = CBM_ROAD_STOP_ANIMATION_NEXT_FRAME; +}; + +void AnimateRoadStopTile(TileIndex tile) +{ + const RoadStopSpec *ss = GetRoadStopSpec(tile); + if (ss == nullptr) return; + + RoadStopAnimationBase::AnimateTile(ss, BaseStation::GetByTile(tile), tile, HasBit(ss->flags, RSF_CB141_RANDOM_BITS)); +} + +void TriggerRoadStopAnimation(BaseStation *st, TileIndex trigger_tile, StationAnimationTrigger trigger, CargoID cargo_type) +{ + /* Get Station if it wasn't supplied */ + if (st == nullptr) st = BaseStation::GetByTile(trigger_tile); + + /* Check the cached animation trigger bitmask to see if we need + * to bother with any further processing. */ + if (!HasBit(st->cached_roadstop_anim_triggers, trigger)) return; + + uint16 random_bits = Random(); + auto process_tile = [&](TileIndex cur_tile) { + const RoadStopSpec *ss = GetRoadStopSpec(cur_tile); + if (ss != nullptr && HasBit(ss->animation.triggers, trigger)) { + CargoID cargo; + if (cargo_type == CT_INVALID) { + cargo = CT_INVALID; + } else { + cargo = ss->grf_prop.grffile->cargo_map[cargo_type]; + } + RoadStopAnimationBase::ChangeAnimationFrame(CBID_STATION_ANIM_START_STOP, ss, st, cur_tile, (random_bits << 16) | Random(), (uint8)trigger | (cargo << 8)); + } + }; + + if (trigger == SAT_NEW_CARGO || trigger == SAT_CARGO_TAKEN || trigger == SAT_250_TICKS) { + for (const RoadStopTileData &tile_data : st->custom_roadstop_tile_data) { + process_tile(tile_data.tile); + } + } else { + process_tile(trigger_tile); + } +} + +/** + * 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->cached_roadstop_cargo_triggers == 0) return; + if (cargo_type != CT_INVALID && !HasBit(st->cached_roadstop_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(cur_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)) { + RoadStopResolverObject object(ss, st, cur_tile, INVALID_ROADTYPE, GetStationType(cur_tile), GetStationGfx(cur_tile)); + 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); + } + } + }; + if (trigger == RSRT_NEW_CARGO || trigger == RSRT_CARGO_TAKEN) { + for (const RoadStopTileData &tile_data : st->custom_roadstop_tile_data) { + process_tile(tile_data.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. + * @param roadtype the RoadType to check. + * @return true if there was any new RoadStopSpec's found for the given RoadStopType and RoadType, else false. + */ +bool GetIfNewStopsByType(RoadStopType rs, RoadType roadtype) +{ + 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, roadtype)) 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. + * @param roadtype the RoadType to check. + * @return true if the RoadStopSpec has any specs compatible with the given RoadStopType and RoadType. + */ +bool GetIfClassHasNewStopsByType(RoadStopClass *roadstopclass, RoadStopType rs, RoadType roadtype) +{ + for (uint j = 0; j < roadstopclass->GetSpecCount(); j++) { + if (GetIfStopIsForType(roadstopclass->GetSpec(j), rs, roadtype)) 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. + * @param roadtype the RoadType to check. + * @return true if the RoadStopSpec is compatible with the given RoadStopType and RoadType. + */ +bool GetIfStopIsForType(const RoadStopSpec *roadstopspec, RoadStopType rs, RoadType roadtype) +{ + // The roadstopspec is nullptr, must be the default station, always return true. + if (roadstopspec == nullptr) return true; + + if (HasBit(roadstopspec->flags, RSF_BUILD_MENU_ROAD_ONLY) && !RoadTypeIsRoad(roadtype)) return false; + if (HasBit(roadstopspec->flags, RSF_BUILD_MENU_TRAM_ONLY) && !RoadTypeIsTram(roadtype)) return false; + + 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; + + default: + NOT_REACHED(); + } + 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->roadstop_speclist.size() ? st->roadstop_speclist[specindex].spec : nullptr; +} + +int AllocateSpecToRoadStop(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->roadstop_speclist.size() && 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->roadstop_speclist.size() && 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->roadstop_speclist.size()) st->roadstop_speclist.resize(i + 1); + 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; + + RoadStopUpdateCachedTriggers(st); + } + + return i; +} + +void DeallocateSpecFromRoadStop(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 (const RoadStopTileData &tile_data : st->custom_roadstop_tile_data) { + if (GetCustomRoadStopSpecIndex(tile_data.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->roadstop_speclist.size() - 1) { + size_t num_specs; + for (num_specs = st->roadstop_speclist.size() - 1; num_specs > 0; num_specs--) { + if (st->roadstop_speclist[num_specs].grfid != 0) break; + } + + if (num_specs > 0) { + st->roadstop_speclist.resize(num_specs + 1); + } else { + st->roadstop_speclist.clear(); + st->cached_roadstop_anim_triggers = 0; + st->cached_roadstop_cargo_triggers = 0; + return; + } + } + + RoadStopUpdateCachedTriggers(st); +} + +/** + * Update the cached animation trigger bitmask for a station. + * @param st Station to update. + */ +void RoadStopUpdateCachedTriggers(BaseStation *st) +{ + st->cached_roadstop_anim_triggers = 0; + st->cached_roadstop_cargo_triggers = 0; + + /* Combine animation trigger bitmask for all road stop specs + * of this station. */ + for (uint i = 0; i < st->roadstop_speclist.size(); i++) { + const RoadStopSpec *ss = st->roadstop_speclist[i].spec; + if (ss != nullptr) { + st->cached_roadstop_anim_triggers |= ss->animation.triggers; + st->cached_roadstop_cargo_triggers |= ss->cargo_triggers; + } + } +} diff --git a/src/newgrf_roadstop.h b/src/newgrf_roadstop.h new file mode 100644 index 0000000000..fa05c30ba1 --- /dev/null +++ b/src/newgrf_roadstop.h @@ -0,0 +1,189 @@ +/* + * 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_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 (unimplemented: this is reserved for future use with road waypoints). + 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_ARRIVES, ///< Trigger roadstop when road vehicle arrives. + RSRT_VEH_DEPARTS, ///< Trigger roadstop when road vehicle leaves. + 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, ///< Bay stops: Draw the road itself + ROADSTOP_DRAW_MODE_OVERLAY = 1 << 1, ///< Drive-through stops: Draw the road overlay, e.g. pavement +}; +DECLARE_ENUM_AS_BIT_SET(RoadStopDrawMode) + +enum RoadStopSpecFlags { + RSF_CB141_RANDOM_BITS = 0, ///< Callback 141 needs random bits. + RSF_NO_CATENARY = 2, ///< Do not show catenary. + RSF_DRIVE_THROUGH_ONLY = 3, ///< Stop is drive-through only. + RSF_NO_AUTO_ROAD_CONNECTION = 4, ///< No auto road connection. + RSF_BUILD_MENU_ROAD_ONLY = 5, ///< Only show in the road build menu (not tram). + RSF_BUILD_MENU_TRAM_ONLY = 6, ///< Only show in the tram build menu (not road). +}; + +/** 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. + RoadType roadtype; ///< Road type (used when no tile) + + RoadStopScopeResolver(ResolverObject& ro, BaseStation* st, const RoadStopSpec *roadstopspec, TileIndex tile, RoadType roadtype, StationType type, uint8 view = 0) + : ScopeResolver(ro), tile(tile), st(st), roadstopspec(roadstopspec), type(type), view(view), roadtype(roadtype) + { + } + + uint32 GetRandomBits() const override; + uint32 GetTriggers() const override; + + uint32 GetVariable(byte variable, uint32 parameter, bool *available) 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, RoadType roadtype, 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 { + /** + * Properties related the the grf file. + * NUM_CARGO real cargo plus three pseudo cargo sprite groups. + * Used for obtaining the sprite offset of custom sprites, and for + * evaluating callbacks. + */ + 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; + uint8 callback_mask = 0; + uint8 flags = 0; + + CargoTypes cargo_triggers = 0; ///< Bitmask of cargo types which cause trigger re-randomizing + + AnimationInfo animation; + + byte bridge_height[6]; ///< Minimum height for a bridge above, 0 for none + byte bridge_disallowed_pillars[6]; ///< Disallowed pillar flags for a bridge above + + uint8 build_cost_multiplier = 16; ///< Build cost multiplier per tile. + uint8 clear_cost_multiplier = 16; ///< Clear cost multiplier per tile. + + /** + * Get the cost for building a road stop of this type. + * @return The cost for building. + */ + Money GetBuildCost(Price category) const { return GetPrice(category, this->build_cost_multiplier, this->grf_prop.grffile, -4); } + + /** + * Get the cost for clearing a road stop of this type. + * @return The cost for clearing. + */ + Money GetClearCost(Price category) const { return GetPrice(category, this->clear_cost_multiplier, this->grf_prop.grffile, -4); } + + 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); + +uint16 GetRoadStopCallback(CallbackID callback, uint32 param1, uint32 param2, const RoadStopSpec *roadstopspec, BaseStation *st, TileIndex tile, RoadType roadtype, StationType type, uint8 view); + +void AnimateRoadStopTile(TileIndex tile); +uint8 GetRoadStopTileAnimationSpeed(TileIndex tile); +void TriggerRoadStopAnimation(BaseStation *st, TileIndex tile, StationAnimationTrigger trigger, CargoID cargo_type = CT_INVALID); +void TriggerRoadStopRandomisation(Station *st, TileIndex tile, RoadStopRandomTrigger trigger, CargoID cargo_type = CT_INVALID); + +bool GetIfNewStopsByType(RoadStopType rs, RoadType roadtype); +bool GetIfClassHasNewStopsByType(RoadStopClass *roadstopclass, RoadStopType rs, RoadType roadtype); +bool GetIfStopIsForType(const RoadStopSpec *roadstopspec, RoadStopType rs, RoadType roadtype); + +uint GetCountOfCompatibleStopsByType(RoadStopClass *roadstopclass, RoadStopType rs); + +const RoadStopSpec *GetRoadStopSpec(TileIndex t); +int AllocateSpecToRoadStop(const RoadStopSpec *statspec, BaseStation *st, bool exec); +void DeallocateSpecFromRoadStop(BaseStation *st, byte specindex); +void RoadStopUpdateCachedTriggers(BaseStation *st); + +#endif /* NEWGRF_ROADSTATION_H */ diff --git a/src/newgrf_station.cpp b/src/newgrf_station.cpp index bf938eab0f..671104fe23 100644 --- a/src/newgrf_station.cpp +++ b/src/newgrf_station.cpp @@ -894,7 +894,7 @@ uint16 GetAnimStationCallback(CallbackID callback, uint32 param1, uint32 param2, } /** Helper class for animation control. */ -struct StationAnimationBase : public AnimationBase { +struct StationAnimationBase : public AnimationBase > { static const CallbackID cb_animation_speed = CBID_STATION_ANIMATION_SPEED; static const CallbackID cb_animation_next_frame = CBID_STATION_ANIM_NEXT_FRAME; diff --git a/src/road_cmd.h b/src/road_cmd.h index 21539e6cb8..fbe05ef33d 100644 --- a/src/road_cmd.h +++ b/src/road_cmd.h @@ -14,6 +14,8 @@ #include "road_type.h" #include "command_type.h" +enum RoadStopClassID : byte; + void DrawRoadDepotSprite(int x, int y, DiagDirection dir, RoadType rt); void UpdateNearestTownForRoadTiles(bool invalidate); @@ -32,6 +34,6 @@ DEF_CMD_TRAIT(CMD_CONVERT_ROAD, CmdConvertRoad, 0, CommandCallback CcPlaySound_CONSTRUCTION_OTHER; CommandCallback CcBuildRoadTunnel; void CcRoadDepot(Commands cmd, const CommandCost &result, TileIndex tile, RoadType rt, DiagDirection dir); -void CcRoadStop(Commands cmd, const CommandCost &result, TileIndex tile, uint8 width, uint8 length, RoadStopType, bool is_drive_through, DiagDirection dir, RoadType, StationID, bool); +void CcRoadStop(Commands cmd, const CommandCost &result, TileIndex tile, uint8 width, uint8 length, RoadStopType, bool is_drive_through, DiagDirection dir, RoadType, RoadStopClassID spec_class, byte spec_index, StationID, bool); #endif /* ROAD_CMD_H */ diff --git a/src/road_gui.cpp b/src/road_gui.cpp index b933db981d..0c9ee9d298 100644 --- a/src/road_gui.cpp +++ b/src/road_gui.cpp @@ -34,6 +34,11 @@ #include "station_cmd.h" #include "road_cmd.h" #include "tunnelbridge_cmd.h" +#include "newgrf_roadstop.h" +#include "querystring_gui.h" +#include "sortlist_type.h" +#include "stringfilter_type.h" +#include "string_func.h" #include "widgets/road_widget.h" @@ -55,7 +60,42 @@ static bool _place_road_end_half; static RoadType _cur_roadtype; static DiagDirection _road_depot_orientation; -static DiagDirection _road_station_picker_orientation; + +struct RoadStopGUISettings { + DiagDirection orientation; + + RoadStopClassID roadstop_class; + byte roadstop_type; + byte roadstop_count; +}; +static RoadStopGUISettings _roadstop_gui_settings; + +/** + * Check whether a road stop type can be built. + * @return true if building is allowed. + */ +static bool IsRoadStopAvailable(const RoadStopSpec *roadstopspec, StationType type) +{ + if (roadstopspec == nullptr) return true; + + if (HasBit(roadstopspec->flags, RSF_BUILD_MENU_ROAD_ONLY) && !RoadTypeIsRoad(_cur_roadtype)) return false; + if (HasBit(roadstopspec->flags, RSF_BUILD_MENU_TRAM_ONLY) && !RoadTypeIsTram(_cur_roadtype)) return false; + + if (roadstopspec->stop_type != ROADSTOPTYPE_ALL) { + switch (type) { + case STATION_BUS: if (roadstopspec->stop_type != ROADSTOPTYPE_PASSENGER) return false; break; + case STATION_TRUCK: if (roadstopspec->stop_type != ROADSTOPTYPE_FREIGHT) return false; break; + default: break; + } + } + + if (!HasBit(roadstopspec->callback_mask, CBM_ROAD_STOP_AVAIL)) return true; + + uint16 cb_res = GetRoadStopCallback(CBID_STATION_AVAILABILITY, 0, 0, roadstopspec, nullptr, INVALID_TILE, _cur_roadtype, type, 0); + if (cb_res == CALLBACK_FAILED) return true; + + return Convert8bitBooleanCallback(roadstopspec->grf_prop.grffile, CBID_STATION_AVAILABILITY, cb_res); +} void CcPlaySound_CONSTRUCTION_OTHER(Commands cmd, const CommandCost &result, TileIndex tile) { @@ -135,19 +175,31 @@ void CcRoadDepot(Commands cmd, const CommandCost &result, TileIndex tile, RoadTy * @param length Length of the road stop. * @param is_drive_through False for normal stops, true for drive-through. * @param dir Entrance direction (#DiagDirection) for normal stops. Converted to the axis for drive-through stops. + * @param spec_class Road stop spec class. + * @param spec_index Road stop spec index. * @see CmdBuildRoadStop */ -void CcRoadStop(Commands cmd, const CommandCost &result, TileIndex tile, uint8 width, uint8 length, RoadStopType, bool is_drive_through, DiagDirection dir, RoadType, StationID, bool) +void CcRoadStop(Commands cmd, const CommandCost &result, TileIndex tile, uint8 width, uint8 length, RoadStopType, bool is_drive_through, + DiagDirection dir, RoadType, RoadStopClassID spec_class, byte spec_index, StationID, bool) { if (result.Failed()) return; if (_settings_client.sound.confirm) SndPlayTileFx(SND_1F_CONSTRUCTION_OTHER, tile); if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace(); - TileArea roadstop_area(tile, width, length); - for (TileIndex cur_tile : roadstop_area) { - ConnectRoadToStructure(cur_tile, dir); - /* For a drive-through road stop build connecting road for other entrance. */ - if (is_drive_through) ConnectRoadToStructure(cur_tile, ReverseDiagDir(dir)); + + bool connect_to_road = true; + if ((uint)spec_class < RoadStopClass::GetClassCount() && spec_index < RoadStopClass::Get(spec_class)->GetSpecCount()) { + const RoadStopSpec *roadstopspec = RoadStopClass::Get(spec_class)->GetSpec(spec_index); + if (roadstopspec != nullptr && HasBit(roadstopspec->flags, RSF_NO_AUTO_ROAD_CONNECTION)) connect_to_road = false; + } + + if (connect_to_road) { + TileArea roadstop_area(tile, width, length); + for (TileIndex cur_tile : roadstop_area) { + ConnectRoadToStructure(cur_tile, dir); + /* For a drive-through road stop build connecting road for other entrance. */ + if (is_drive_through) ConnectRoadToStructure(cur_tile, ReverseDiagDir(dir)); + } } } @@ -164,15 +216,19 @@ void CcRoadStop(Commands cmd, const CommandCost &result, TileIndex tile, uint8 w static void PlaceRoadStop(TileIndex start_tile, TileIndex end_tile, RoadStopType stop_type, bool adjacent, RoadType rt, StringID err_msg) { TileArea ta(start_tile, end_tile); - DiagDirection ddir = _road_station_picker_orientation; + DiagDirection ddir = _roadstop_gui_settings.orientation; bool drive_through = ddir >= DIAGDIR_END; if (drive_through) ddir = static_cast(ddir - DIAGDIR_END); // Adjust picker result to actual direction. + RoadStopClassID spec_class = _roadstop_gui_settings.roadstop_class; + byte spec_index = _roadstop_gui_settings.roadstop_type; auto proc = [=](bool test, StationID to_join) -> bool { if (test) { - return Command::Do(CommandFlagsToDCFlags(GetCommandFlags()), ta.tile, ta.w, ta.h, stop_type, drive_through, ddir, rt, INVALID_STATION, adjacent).Succeeded(); + return Command::Do(CommandFlagsToDCFlags(GetCommandFlags()), ta.tile, ta.w, ta.h, stop_type, drive_through, + ddir, rt, spec_class, spec_index, INVALID_STATION, adjacent).Succeeded(); } else { - return Command::Post(err_msg, CcRoadStop, ta.tile, ta.w, ta.h, stop_type, drive_through, ddir, rt, to_join, adjacent); + return Command::Post(err_msg, CcRoadStop, ta.tile, ta.w, ta.h, stop_type, drive_through, + ddir, rt, spec_class, spec_index, to_join, adjacent); } }; @@ -188,8 +244,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); } @@ -206,8 +262,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); } @@ -652,7 +708,7 @@ 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, _cur_roadtype)) { if (_remove_button_clicked) { TileArea ta(start_tile, end_tile); Command::Post(this->rti->strings.err_remove_station[ROADSTOP_BUS], CcPlaySound_CONSTRUCTION_OTHER, ta.tile, ta.w, ta.h, ROADSTOP_BUS, _ctrl_pressed); @@ -664,7 +720,7 @@ struct BuildRoadToolbarWindow : Window { 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, _cur_roadtype)) { if (_remove_button_clicked) { TileArea ta(start_tile, end_tile); Command::Post(this->rti->strings.err_remove_station[ROADSTOP_TRUCK], CcPlaySound_CONSTRUCTION_OTHER, ta.tile, ta.w, ta.h, ROADSTOP_TRUCK, _ctrl_pressed); @@ -1046,14 +1102,91 @@ 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); + } + + void CheckOrientationValid() + { + if (_roadstop_gui_settings.orientation >= DIAGDIR_END) return; + const RoadStopSpec *spec = RoadStopClass::Get(_roadstop_gui_settings.roadstop_class)->GetSpec(_roadstop_gui_settings.roadstop_type); + if (spec != nullptr && HasBit(spec->flags, RSF_DRIVE_THROUGH_ONLY)) { + this->RaiseWidget(_roadstop_gui_settings.orientation + WID_BROS_STATION_NE); + _roadstop_gui_settings.orientation = DIAGDIR_END; + this->LowerWidget(_roadstop_gui_settings.orientation + WID_BROS_STATION_NE); + this->SetDirty(); + CloseWindowById(WC_SELECT_STATION, 0); + } + } + +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 * WidgetDimensions::scaled.vsep_normal; + this->vscrollList = nullptr; + this->vscrollMatrix = nullptr; + this->roadStopType = rs; + bool newstops = GetIfNewStopsByType(rs, _cur_roadtype); + this->CreateNestedTree(); + /* Hide the station class filter if no stations other than the default one are available. */ + this->GetWidget(WID_BROS_SHOW_NEWST_DEFSIZE)->SetDisplayedPlane(newstops ? 0 : SZSP_NONE); + this->GetWidget(WID_BROS_FILTER_CONTAINER)->SetDisplayedPlane(newstops ? 0 : SZSP_HORIZONTAL); + this->GetWidget(WID_BROS_SHOW_NEWST_ADDITIONS)->SetDisplayedPlane(newstops ? 0 : SZSP_HORIZONTAL); + this->GetWidget(WID_BROS_SHOW_NEWST_ORIENTATION)->SetDisplayedPlane(newstops ? 0 : SZSP_HORIZONTAL); + this->GetWidget(WID_BROS_SHOW_NEWST_TYPE_SEL)->SetDisplayedPlane(newstops ? 0 : SZSP_HORIZONTAL); + this->GetWidget(WID_BROS_SHOW_NEWST_MATRIX)->SetDisplayedPlane(newstops ? 0 : SZSP_NONE); + this->GetWidget(WID_BROS_SHOW_NEWST_RESIZE)->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) && _road_station_picker_orientation < DIAGDIR_END) { - _road_station_picker_orientation = DIAGDIR_END; + 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]; @@ -1062,12 +1195,42 @@ 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, _cur_roadtype)) { + _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, _cur_roadtype)) { + _roadstop_gui_settings.roadstop_class = ROADSTOP_CLASS_DFLT; + } + + this->SelectFirstAvailableTypeIfUnavailable(); + + 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(); + this->CheckOrientationValid(); + } } void Close() override @@ -1076,6 +1239,94 @@ struct BuildRoadStationWindow : public PickerWindowBase { this->PickerWindowBase::Close(); } + /** 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, _cur_roadtype)) 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 SelectFirstAvailableTypeIfUnavailable() + { + const RoadStopClass *rs_class = RoadStopClass::Get(_roadstop_gui_settings.roadstop_class); + StationType st = GetRoadStationTypeByWindowClass(this->window_class); + + if (IsRoadStopAvailable(rs_class->GetSpec(_roadstop_gui_settings.roadstop_type), st)) return; + for (uint i = 0; i < _roadstop_gui_settings.roadstop_count; i++) { + if (IsRoadStopAvailable(rs_class->GetSpec(i), st)) { + _roadstop_gui_settings.roadstop_type = i; + break; + } + } + } + + void OnInvalidateData(int data = 0, bool gui_scope = true) override + { + if (!gui_scope) return; + + this->BuildRoadStopClassesAvailable(); + } + + EventState OnHotkey(int hotkey) override + { + if (hotkey == 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. + return ES_HANDLED; + } + + return ES_NOT_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(); @@ -1087,6 +1338,8 @@ struct BuildRoadStationWindow : public PickerWindowBase { SetTileSelectSize(1, 1); } + if (this->IsShaded()) return; + /* 'Accepts' and 'Supplies' texts. */ StationCoverageType sct = (this->window_class == WC_BUS_STATION) ? SCT_PASSENGERS_ONLY : SCT_NON_PASSENGERS_ONLY; Rect r = this->GetWidget(WID_BROS_ACCEPTANCE)->GetCurrentRect(); @@ -1097,45 +1350,175 @@ struct BuildRoadStationWindow : public PickerWindowBase { * 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 > r.bottom) { - ResizeWindow(this, 0, top - r.bottom, false); + this->coverage_height += top - r.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) + WidgetDimensions::scaled.fullbevel.Horizontal(); - size->height = ScaleGUITrad(48) + WidgetDimensions::scaled.fullbevel.Vertical(); - } - - 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; - - DrawPixelInfo tmp_dpi; - if (FillDrawPixelInfo(&tmp_dpi, r.left, r.top, r.Width(), r.Height())) { - AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi); - int x = (r.Width() - ScaleSpriteTrad(64)) / 2 + ScaleSpriteTrad(31); - int y = (r.Height() + ScaleSpriteTrad(48)) / 2 - ScaleSpriteTrad(31); - StationPickerDrawSprite(x, y, 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 + WidgetDimensions::scaled.matrix.Vertical(); + size->height = 5 * this->line_height; + resize->height = this->line_height; + break; + } + + case WID_BROS_SHOW_NEWST_TYPE: { + Dimension d = {0, 0}; + StringID str = this->GetWidget(widget)->widget_data; + for (auto roadstop_class : this->roadstop_classes) { + RoadStopClass *rs_class = RoadStopClass::Get(roadstop_class); + for (uint j = 0; j < rs_class->GetSpecCount(); j++) { + const RoadStopSpec *roadstopspec = rs_class->GetSpec(j); + SetDParam(0, (roadstopspec != nullptr && roadstopspec->name != 0) ? roadstopspec->name : STR_STATION_CLASS_DFLT); + d = maxdim(d, GetStringBoundingBox(str)); + } + } + size->width = std::max(size->width, d.width + padding.width); + 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_IMAGE: + size->width = ScaleGUITrad(64) + WidgetDimensions::scaled.fullbevel.Horizontal(); + size->height = ScaleGUITrad(48) + WidgetDimensions::scaled.fullbevel.Vertical(); + break; + + case WID_BROS_MATRIX: + fill->height = 1; + resize->height = 1; + break; + + case WID_BROS_ACCEPTANCE: + 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 + { + 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); + bool disabled = (spec != nullptr && widget < WID_BROS_STATION_X && HasBit(spec->flags, RSF_DRIVE_THROUGH_ONLY)); + DrawPixelInfo tmp_dpi; + if (FillDrawPixelInfo(&tmp_dpi, r.left, r.top, r.Width(), r.Height())) { + AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi); + int x = (r.Width() - ScaleSpriteTrad(64)) / 2 + ScaleSpriteTrad(31); + int y = (r.Height() + ScaleSpriteTrad(48)) / 2 - ScaleSpriteTrad(31); + if (spec == nullptr || disabled) { + StationPickerDrawSprite(x, y, st, INVALID_RAILTYPE, _cur_roadtype, widget - WID_BROS_STATION_NE); + if (disabled) GfxFillRect(1, 1, r.Width() - 1, r.Height() - 1, PC_BLACK, FILLRECT_CHECKER); + } else { + DrawRoadStopTile(x, y, _cur_roadtype, spec, st, 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 + WidgetDimensions::scaled.matrix.left, r.right, row * this->line_height + r.top + WidgetDimensions::scaled.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); + + const RoadStopSpec *spec = RoadStopClass::Get(_roadstop_gui_settings.roadstop_class)->GetSpec(type); + StationType st = GetRoadStationTypeByWindowClass(this->window_class); + + if (!IsRoadStopAvailable(spec, st)) { + GfxFillRect(r.Shrink(WidgetDimensions::scaled.bevel), PC_BLACK, FILLRECT_CHECKER); + } + + /* Set up a clipping area for the sprite preview. */ + DrawPixelInfo tmp_dpi; + if (FillDrawPixelInfo(&tmp_dpi, r.left, r.top, r.Width(), r.Height())) { + AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi); + int x = (r.Width() - ScaleSpriteTrad(64)) / 2 + ScaleSpriteTrad(31); + int y = (r.Height() + ScaleSpriteTrad(48)) / 2 - ScaleSpriteTrad(31); + if (spec == nullptr) { + StationPickerDrawSprite(x, y, st, INVALID_RAILTYPE, _cur_roadtype, _roadstop_gui_settings.orientation); + } else { + DiagDirection orientation = _roadstop_gui_settings.orientation; + if (orientation < DIAGDIR_END && HasBit(spec->flags, RSF_DRIVE_THROUGH_ONLY)) orientation = DIAGDIR_END; + DrawRoadStopTile(x, y, _cur_roadtype, spec, st, (uint8)orientation); + } + } + 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: + if (widget < WID_BROS_STATION_X) { + const RoadStopSpec *spec = RoadStopClass::Get(_roadstop_gui_settings.roadstop_class)->GetSpec(_roadstop_gui_settings.roadstop_type); + if (spec != nullptr && HasBit(spec->flags, RSF_DRIVE_THROUGH_ONLY)) return; + } + 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(); CloseWindowById(WC_SELECT_STATION, 0); @@ -1151,6 +1534,48 @@ 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, _cur_roadtype)) { + _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)); + this->SelectFirstAvailableTypeIfUnavailable(); + + NWidgetMatrix *matrix = this->GetWidget(WID_BROS_MATRIX); + matrix->SetCount(_roadstop_gui_settings.roadstop_count); + matrix->SetClicked(_roadstop_gui_settings.roadstop_type); + this->CheckOrientationValid(); + } + if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP); + this->SetDirty(); + CloseWindowById(WC_SELECT_STATION, 0); + break; + } + + case WID_BROS_IMAGE: { + int y = GB(widget, 16, 16); + if (y >= _roadstop_gui_settings.roadstop_count) return; + + const RoadStopSpec *spec = RoadStopClass::Get(_roadstop_gui_settings.roadstop_class)->GetSpec(y); + StationType st = GetRoadStationTypeByWindowClass(this->window_class); + + if (!IsRoadStopAvailable(spec, st)) return; + + _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(); + CloseWindowById(WC_SELECT_STATION, 0); + this->CheckOrientationValid(); + break; + } + default: break; } @@ -1160,6 +1585,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 */ @@ -1167,34 +1611,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_HORIZONTAL), SetPadding(3), - NWidget(NWID_SPACER), SetFill(1, 0), - NWidget(NWID_VERTICAL), SetPIP(0, 2, 0), - NWidget(NWID_HORIZONTAL), SetPIP(0, 2, 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_HORIZONTAL), SetPadding(2, 0, 0, 2), + 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_HORIZONTAL), SetPIP(0, 2, 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_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_ADDITIONS), + NWidget(NWID_HORIZONTAL), SetPadding(0, 5, 2, 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), SetFill(1, 0), + EndContainer(), + NWidget(NWID_HORIZONTAL), SetPadding(3), + NWidget(NWID_SPACER), SetFill(1, 0), + NWidget(NWID_VERTICAL), SetPIP(0, 2, 0), + NWidget(NWID_HORIZONTAL), SetPIP(0, 2, 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(), + EndContainer(), + NWidget(NWID_HORIZONTAL), SetPIP(0, 2, 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(), + EndContainer(), + 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), SetFill(1, 0), + EndContainer(), + NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL), SetPadding(WidgetDimensions::unscaled.framerect), SetFill(1, 0), + NWidget(NWID_HORIZONTAL), + NWidget(NWID_SPACER), SetMinimalSize(2, 0), 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), SetMinimalSize(2, 0), SetFill(1, 0), + EndContainer(), + EndContainer(), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_MATRIX), + 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), + 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(), - NWidget(NWID_SPACER), SetFill(1, 0), EndContainer(), - NWidget(WWT_LABEL, COLOUR_DARK_GREEN, WID_BROS_INFO), SetPadding(WidgetDimensions::unscaled.framerect), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL), SetFill(1, 0), - NWidget(NWID_HORIZONTAL), SetPadding(3), - 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), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_EMPTY, INVALID_COLOUR, WID_BROS_ACCEPTANCE), SetPadding(WidgetDimensions::unscaled.framerect), 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(WWT_EMPTY, COLOUR_DARK_GREEN, WID_BROS_ACCEPTANCE), SetPadding(WidgetDimensions::unscaled.framerect), SetResize(0, 1), EndContainer(), }; @@ -1210,28 +1702,76 @@ 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_HORIZONTAL), SetPadding(3), - NWidget(NWID_SPACER), SetFill(1, 0), - NWidget(NWID_VERTICAL), SetPIP(0, 2, 0), - NWidget(NWID_HORIZONTAL), SetPIP(0, 2, 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_HORIZONTAL), SetPadding(2, 0, 0, 2), + 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), SetPadding(0, 5, 2, 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), SetFill(1, 0), + EndContainer(), + NWidget(NWID_HORIZONTAL), SetPadding(3), + NWidget(NWID_SPACER), SetFill(1, 0), + NWidget(NWID_VERTICAL), SetPIP(0, 2, 0), + NWidget(NWID_HORIZONTAL), SetPIP(0, 2, 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(), + EndContainer(), + 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), SetFill(1, 0), + EndContainer(), + NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL), SetPadding(WidgetDimensions::unscaled.framerect), SetFill(1, 0), + NWidget(NWID_HORIZONTAL), + NWidget(NWID_SPACER), SetMinimalSize(2, 0), 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), SetMinimalSize(2, 0), SetFill(1, 0), + EndContainer(), + EndContainer(), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_MATRIX), + 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(), - NWidget(NWID_SPACER), SetFill(1, 0), EndContainer(), - NWidget(WWT_LABEL, COLOUR_DARK_GREEN, WID_BROS_INFO), SetPadding(WidgetDimensions::unscaled.framerect), SetMinimalSize(140, 14), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL), SetFill(1, 0), - NWidget(NWID_HORIZONTAL), SetPadding(3), - 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), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_EMPTY, INVALID_COLOUR, WID_BROS_ACCEPTANCE), SetPadding(WidgetDimensions::unscaled.framerect), 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(WWT_EMPTY, COLOUR_DARK_GREEN, WID_BROS_ACCEPTANCE), SetPadding(WidgetDimensions::unscaled.framerect), SetResize(0, 1), EndContainer(), }; @@ -1250,7 +1790,7 @@ static void ShowRVStationPicker(Window *parent, RoadStopType rs) void InitializeRoadGui() { _road_depot_orientation = DIAGDIR_NW; - _road_station_picker_orientation = DIAGDIR_NW; + _roadstop_gui_settings.orientation = DIAGDIR_NW; } /** diff --git a/src/saveload/compat/station_sl_compat.h b/src/saveload/compat/station_sl_compat.h index 1c24a8d5d9..d94761506e 100644 --- a/src/saveload/compat/station_sl_compat.h +++ b/src/saveload/compat/station_sl_compat.h @@ -32,6 +32,12 @@ const SaveLoadCompat _station_spec_list_sl_compat[] = { SLC_VAR("localidx"), }; +/** Nominal field order for SlRoadStopSpecList. */ +const SaveLoadCompat _station_road_stop_spec_list_sl_compat[] = { + SLC_VAR("grfid"), + SLC_VAR("localidx"), +}; + /** Original field order for SlStationCargo. */ const SaveLoadCompat _station_cargo_sl_compat[] = { SLC_VAR("first"), diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index ad082ef590..de27caa8de 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -343,6 +343,7 @@ enum SaveLoadVersion : uint16 { SLV_U64_TICK_COUNTER, ///< 300 PR#10035 Make _tick_counter 64bit to avoid wrapping. SLV_LAST_LOADING_TICK, ///< 301 PR#9693 Store tick of last loading for vehicles. SLV_MULTITRACK_LEVEL_CROSSINGS, ///< 302 PR#9931 v13.0 Multi-track level crossings. + SLV_NEWGRF_ROAD_STOPS, ///< 303 PR#10144 NewGRF road stops. SL_MAX_VERSION, ///< Highest possible saveload version }; diff --git a/src/saveload/station_sl.cpp b/src/saveload/station_sl.cpp index 7a0b57d67d..f964a8b8cd 100644 --- a/src/saveload/station_sl.cpp +++ b/src/saveload/station_sl.cpp @@ -17,6 +17,7 @@ #include "../roadstop_base.h" #include "../vehicle_base.h" #include "../newgrf_station.h" +#include "../newgrf_roadstop.h" #include "table/strings.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->roadstop_speclist.size(); 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); + RoadStopUpdateCachedTriggers(st); } } @@ -224,6 +231,33 @@ public: uint8 SlStationSpecList::last_num_specs; +class SlRoadStopSpecList : public DefaultSaveLoadHandler { +public: + inline static const SaveLoad description[] = { + SLE_VAR(RoadStopSpecList, grfid, SLE_UINT32), + SLE_VAR(RoadStopSpecList, localidx, SLE_UINT8), + }; + inline const static SaveLoadCompatTable compat_description = _station_road_stop_spec_list_sl_compat; + + void Save(BaseStation *bst) const override + { + SlSetStructListLength(bst->roadstop_speclist.size()); + for (uint i = 0; i < bst->roadstop_speclist.size(); i++) { + SlObject(&bst->roadstop_speclist[i], this->GetDescription()); + } + } + + void Load(BaseStation *bst) const override + { + uint8 num_specs = (uint8)SlGetStructListLength(UINT8_MAX); + + bst->roadstop_speclist.resize(num_specs); + for (uint i = 0; i < num_specs; i++) { + SlObject(&bst->roadstop_speclist[i], this->GetLoadDescription()); + } + } +}; + class SlStationCargo : public DefaultSaveLoadHandler { public: inline static const SaveLoad description[] = { @@ -516,6 +550,35 @@ struct STNSChunkHandler : ChunkHandler { } }; +class SlRoadStopTileData : public DefaultSaveLoadHandler { +public: + inline static const SaveLoad description[] = { + SLE_VAR(RoadStopTileData, tile, SLE_UINT32), + SLE_VAR(RoadStopTileData, random_bits, SLE_UINT8), + SLE_VAR(RoadStopTileData, animation_frame, SLE_UINT8), + }; + inline const static SaveLoadCompatTable compat_description = {}; + + static uint8 last_num_specs; ///< Number of specs of the last loaded station. + + void Save(BaseStation *bst) const override + { + SlSetStructListLength(bst->custom_roadstop_tile_data.size()); + for (uint i = 0; i < bst->custom_roadstop_tile_data.size(); i++) { + SlObject(&bst->custom_roadstop_tile_data[i], this->GetDescription()); + } + } + + void Load(BaseStation *bst) const override + { + uint32 num_tiles = (uint32)SlGetStructListLength(UINT32_MAX); + bst->custom_roadstop_tile_data.resize(num_tiles); + for (uint i = 0; i < num_tiles; i++) { + SlObject(&bst->custom_roadstop_tile_data[i], this->GetLoadDescription()); + } + } +}; + /** * SaveLoad handler for the BaseStation, which all other stations / waypoints * make use of. @@ -593,6 +656,7 @@ public: SLE_REFLIST(Station, loading_vehicles, REF_VEHICLE), SLE_CONDVAR(Station, always_accepted, SLE_FILE_U32 | SLE_VAR_U64, SLV_127, SLV_EXTEND_CARGOTYPES), SLE_CONDVAR(Station, always_accepted, SLE_UINT64, SLV_EXTEND_CARGOTYPES, SL_MAX_VERSION), + SLEG_CONDSTRUCTLIST("speclist", SlRoadStopTileData, SLV_NEWGRF_ROAD_STOPS, SL_MAX_VERSION), SLEG_STRUCTLIST("goods", SlStationGoods), }; inline const static SaveLoadCompatTable compat_description = _station_normal_sl_compat; @@ -652,6 +716,7 @@ static const SaveLoad _station_desc[] = { SLEG_STRUCT("normal", SlStationNormal), SLEG_STRUCT("waypoint", SlStationWaypoint), SLEG_CONDSTRUCTLIST("speclist", SlStationSpecList, SLV_27, SL_MAX_VERSION), + SLEG_CONDSTRUCTLIST("roadstopspeclist", SlRoadStopSpecList, SLV_NEWGRF_ROAD_STOPS, SL_MAX_VERSION), }; struct STNNChunkHandler : ChunkHandler { diff --git a/src/script/api/script_road.cpp b/src/script/api/script_road.cpp index 1cb01327c4..6ee2a41158 100644 --- a/src/script/api/script_road.cpp +++ b/src/script/api/script_road.cpp @@ -15,6 +15,7 @@ #include "../../landscape_cmd.h" #include "../../road_cmd.h" #include "../../station_cmd.h" +#include "../../newgrf_roadstop.h" #include "../../script/squirrel_helper_type.hpp" #include "../../safeguards.h" @@ -549,7 +550,7 @@ static bool NeighbourHasReachableRoad(::RoadType rt, TileIndex start_tile, DiagD DiagDirection entrance_dir = DiagdirBetweenTiles(tile, front); RoadStopType stop_type = road_veh_type == ROADVEHTYPE_TRUCK ? ROADSTOP_TRUCK : ROADSTOP_BUS; StationID to_join = ScriptStation::IsValidStation(station_id) ? station_id : INVALID_STATION; - return ScriptObject::Command::Do(tile, 1, 1, stop_type, drive_through, entrance_dir, ScriptObject::GetRoadType(), to_join, station_id != ScriptStation::STATION_JOIN_ADJACENT); + return ScriptObject::Command::Do(tile, 1, 1, stop_type, drive_through, entrance_dir, ScriptObject::GetRoadType(), ROADSTOP_CLASS_DFLT, 0, to_join, station_id != ScriptStation::STATION_JOIN_ADJACENT); } /* static */ bool ScriptRoad::BuildRoadStation(TileIndex tile, TileIndex front, RoadVehicleType road_veh_type, StationID station_id) diff --git a/src/station.cpp b/src/station.cpp index 5230eeeaf5..aecd019901 100644 --- a/src/station.cpp +++ b/src/station.cpp @@ -170,6 +170,36 @@ void BaseStation::PostDestructor(size_t index) InvalidateWindowData(WC_SELECT_STATION, 0, 0); } +void BaseStation::SetRoadStopTileData(TileIndex tile, byte data, bool animation) +{ + for (RoadStopTileData &tile_data : this->custom_roadstop_tile_data) { + if (tile_data.tile == tile) { + if (animation) { + tile_data.animation_frame = data; + } else { + tile_data.random_bits = data; + } + return; + } + } + RoadStopTileData tile_data; + tile_data.tile = tile; + tile_data.animation_frame = animation ? data : 0; + tile_data.random_bits = animation ? 0 : data; + this->custom_roadstop_tile_data.push_back(tile_data); +} + +void BaseStation::RemoveRoadStopTileData(TileIndex tile) +{ + for (RoadStopTileData &tile_data : this->custom_roadstop_tile_data) { + if (tile_data.tile == tile) { + tile_data = this->custom_roadstop_tile_data.back(); + this->custom_roadstop_tile_data.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 e8eb40d4b8..b2e31a29de 100644 --- a/src/station_base.h +++ b/src/station_base.h @@ -521,6 +521,11 @@ public: return IsRailStationTile(tile) && GetStationIndex(tile) == this->index; } + inline bool TileBelongsToRoadStop(TileIndex tile) const + { + return IsRoadStopTile(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 a4479deca4..414c09812d 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -59,6 +59,7 @@ #include "waypoint_cmd.h" #include "landscape_cmd.h" #include "rail_cmd.h" +#include "newgrf_roadstop.h" #include "table/strings.h" @@ -1794,7 +1795,7 @@ static RoadStop **FindRoadStopSpot(bool truck_station, Station *st) } } -static CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags); +static CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags, int replacement_spec_index = -1); /** * Find a nearby station that joins this road stop. @@ -1820,17 +1821,31 @@ static CommandCost FindJoiningRoadStop(StationID existing_stop, StationID statio * @param is_drive_through False for normal stops, true for drive-through. * @param ddir Entrance direction (#DiagDirection) for normal stops. Converted to the axis for drive-through stops. * @param rt The roadtype. + * @param spec_class Road stop spec class. + * @param spec_index Road stop spec index. * @param station_to_join Station ID to join (NEW_STATION if build new one). * @param adjacent Allow stations directly adjacent to other stations. * @return The cost of this operation or an error. */ -CommandCost CmdBuildRoadStop(DoCommandFlag flags, TileIndex tile, uint8 width, uint8 length, RoadStopType stop_type, bool is_drive_through, DiagDirection ddir, RoadType rt, StationID station_to_join, bool adjacent) +CommandCost CmdBuildRoadStop(DoCommandFlag flags, TileIndex tile, uint8 width, uint8 length, RoadStopType stop_type, bool is_drive_through, + DiagDirection ddir, RoadType rt, RoadStopClassID spec_class, byte spec_index, StationID station_to_join, bool adjacent) { if (!ValParamRoadType(rt) || !IsValidDiagDirection(ddir) || stop_type >= ROADSTOP_END) return CMD_ERROR; bool reuse = (station_to_join != NEW_STATION); if (!reuse) station_to_join = INVALID_STATION; bool distant_join = (station_to_join != INVALID_STATION); + /* 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 (stop_type == ROADSTOP_TRUCK && roadstopspec->stop_type != ROADSTOPTYPE_FREIGHT && roadstopspec->stop_type != ROADSTOPTYPE_ALL) return CMD_ERROR; + if (stop_type == ROADSTOP_BUS && roadstopspec->stop_type != ROADSTOPTYPE_PASSENGER && roadstopspec->stop_type != ROADSTOPTYPE_ALL) return CMD_ERROR; + if (!is_drive_through && HasBit(roadstopspec->flags, RSF_DRIVE_THROUGH_ONLY)) 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. */ @@ -1853,7 +1868,13 @@ CommandCost CmdBuildRoadStop(DoCommandFlag flags, TileIndex tile, uint8 width, u bool is_truck_stop = stop_type != ROADSTOP_BUS; /* Total road stop cost. */ - CommandCost cost(EXPENSES_CONSTRUCTION, roadstop_area.w * roadstop_area.h * _price[is_truck_stop ? PR_BUILD_STATION_TRUCK : PR_BUILD_STATION_BUS]); + Money unit_cost; + if (roadstopspec != nullptr) { + unit_cost = roadstopspec->GetBuildCost(is_truck_stop ? PR_BUILD_STATION_TRUCK : PR_BUILD_STATION_BUS); + } else { + unit_cost = _price[is_truck_stop ? PR_BUILD_STATION_TRUCK : PR_BUILD_STATION_BUS]; + } + CommandCost cost(EXPENSES_CONSTRUCTION, roadstop_area.w * roadstop_area.h * unit_cost); StationID est = INVALID_STATION; ret = CheckFlatLandRoadStop(roadstop_area, flags, is_drive_through ? 5 << axis : 1 << ddir, is_drive_through, is_truck_stop, axis, &est, rt); if (ret.Failed()) return ret; @@ -1869,6 +1890,20 @@ CommandCost CmdBuildRoadStop(DoCommandFlag flags, TileIndex tile, uint8 width, u 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 = AllocateSpecToRoadStop(roadstopspec, st, (flags & DC_EXEC) != 0); + if (specindex == -1) return_cmd_error(STR_ERROR_TOO_MANY_STATION_SPECS); + + if (roadstopspec != nullptr) { + /* Perform NewGRF checks */ + + /* Check if the road stop is buildable */ + if (HasBit(roadstopspec->callback_mask, CBM_ROAD_STOP_AVAIL)) { + uint16 cb_res = GetRoadStopCallback(CBID_STATION_AVAILABILITY, 0, 0, roadstopspec, nullptr, INVALID_TILE, rt, is_truck_stop ? STATION_TRUCK : STATION_BUS, 0); + if (cb_res != CALLBACK_FAILED && !Convert8bitBooleanCallback(roadstopspec->grf_prop.grffile, CBID_STATION_AVAILABILITY, cb_res)) return CMD_ERROR; + } + } + if (flags & DC_EXEC) { /* Check every tile in the area. */ for (TileIndex cur_tile : roadstop_area) { @@ -1879,7 +1914,13 @@ CommandCost CmdBuildRoadStop(DoCommandFlag flags, TileIndex tile, uint8 width, u Owner tram_owner = tram_rt != INVALID_ROADTYPE ? GetRoadOwner(cur_tile, RTT_TRAM) : _current_company; if (IsTileType(cur_tile, MP_STATION) && IsRoadStop(cur_tile)) { - RemoveRoadStop(cur_tile, flags); + RemoveRoadStop(cur_tile, flags, specindex); + } + + if (roadstopspec != nullptr) { + /* Include this road stop spec's animation trigger bitmask + * in the station's cached copy. */ + st->cached_roadstop_anim_triggers |= roadstopspec->animation.triggers; } RoadStop *road_stop = new RoadStop(cur_tile); @@ -1921,6 +1962,15 @@ CommandCost CmdBuildRoadStop(DoCommandFlag flags, TileIndex tile, uint8 width, u UpdateCompanyRoadInfrastructure(tram_rt, tram_owner, ROAD_STOP_TRACKBIT_FACTOR); Company::Get(st->owner)->infrastructure.station++; + UpdateCompanyRoadInfrastructure(road_rt, road_owner, ROAD_STOP_TRACKBIT_FACTOR); + UpdateCompanyRoadInfrastructure(tram_rt, tram_owner, ROAD_STOP_TRACKBIT_FACTOR); + + SetCustomRoadStopSpecIndex(cur_tile, specindex); + if (roadstopspec != nullptr) { + st->SetRoadStopRandomBits(cur_tile, GB(Random(), 0, 8)); + TriggerRoadStopAnimation(st, cur_tile, SAT_BUILT); + } + MarkTileDirtyByTile(cur_tile); } @@ -1953,9 +2003,10 @@ static Vehicle *ClearRoadStopStatusEnum(Vehicle *v, void *) * Remove a bus station/truck stop * @param tile TileIndex been queried * @param flags operation to perform + * @param replacement_spec_index replacement spec index to avoid deallocating, if < 0, tile is not being replaced * @return cost or failure of operation */ -static CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags) +static CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags, int replacement_spec_index) { Station *st = Station::GetByTile(tile); @@ -1987,6 +2038,8 @@ static CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags) if (ret.Failed()) return ret; } + const RoadStopSpec *spec = GetRoadStopSpec(tile); + if (flags & DC_EXEC) { if (*primary_stop == cur_stop) { /* removed the first stop in the list */ @@ -2011,6 +2064,12 @@ static CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags) Company::Get(st->owner)->infrastructure.station--; DirtyCompanyInfrastructureWindows(st->owner); + DeleteAnimatedTile(tile); + + uint specindex = GetCustomRoadStopSpecIndex(tile); + + DeleteNewGRFInspectWindow(GSF_ROADSTOPS, tile); + if (IsDriveThroughStopTile(tile)) { /* Clears the tile for us */ cur_stop->ClearDriveThrough(); @@ -2030,7 +2089,10 @@ static CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags) st->rect.AfterRemoveTile(st, tile); - st->AfterStationTileSetChange(false, is_truck ? STATION_TRUCK: STATION_BUS); + if (replacement_spec_index < 0) st->AfterStationTileSetChange(false, is_truck ? STATION_TRUCK: STATION_BUS); + + st->RemoveRoadStopTileData(tile); + if ((int)specindex != replacement_spec_index) DeallocateSpecFromRoadStop(st, specindex); /* Update the tile area of the truck/bus stop */ if (is_truck) { @@ -2042,7 +2104,8 @@ static CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags) } } - return CommandCost(EXPENSES_CONSTRUCTION, _price[is_truck ? PR_CLEAR_STATION_TRUCK : PR_CLEAR_STATION_BUS]); + Price category = is_truck ? PR_CLEAR_STATION_TRUCK : PR_CLEAR_STATION_BUS; + return CommandCost(EXPENSES_CONSTRUCTION, spec != nullptr ? spec->GetClearCost(category) : _price[category]); } /** @@ -2932,6 +2995,8 @@ draw_default_foundation: } } + bool draw_ground = false; + if (IsBuoy(ti->tile)) { DrawWaterClassGround(ti); SpriteID sprite = GetCanalSprite(CF_BUOY, ti->tile); @@ -2970,6 +3035,10 @@ draw_default_foundation: ground_relocation += rti->fallback_railtype; } + draw_ground = true; + } + + if (draw_ground && !IsRoadStop(ti->tile)) { SpriteID image = t->ground.sprite; PaletteID pal = t->ground.pal; RailTrackOffset overlay_offset; @@ -3002,8 +3071,32 @@ draw_default_foundation: 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); + StationType type = GetStationType(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, INVALID_ROADTYPE, type, view); + const SpriteGroup *group = object.Resolve(); + if (group != nullptr && group->type == SGT_TILELAYOUT) { + t = ((const TileLayoutSpriteGroup *)group)->ProcessRegisters(nullptr); + } + } + + /* Draw ground sprite */ + if (draw_ground) { + SpriteID image = t->ground.sprite; + PaletteID pal = t->ground.pal; + image += HasBit(image, SPRITE_MODIFIER_CUSTOM_SPRITE) ? ground_relocation : total_offset; + if (HasBit(pal, SPRITE_MODIFIER_CUSTOM_SPRITE)) pal += ground_relocation; + DrawGroundSprite(image, GroundSpritePaletteTransform(image, pal, palette)); + } + if (IsDriveThroughStopTile(ti->tile)) { - Axis axis = GetRoadStopDir(ti->tile) == DIAGDIR_NE ? AXIS_X : AXIS_Y; uint sprite_offset = axis == AXIS_X ? 1 : 0; DrawRoadOverlays(ti, PAL_NONE, road_rti, tram_rti, sprite_offset, sprite_offset); @@ -3011,15 +3104,16 @@ draw_default_foundation: /* Non-drivethrough road stops are only valid for roads. */ assert(road_rt != INVALID_ROADTYPE && tram_rt == INVALID_ROADTYPE); - 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); } } - /* Draw road, tram catenary */ - DrawRoadCatenary(ti); + if (stopspec == nullptr || !HasBit(stopspec->flags, RSF_NO_CATENARY)) { + /* Draw road, tram catenary */ + DrawRoadCatenary(ti); + } } if (IsRailWaypoint(ti->tile)) { @@ -3278,6 +3372,12 @@ static void AnimateTile_Station(TileIndex tile) if (IsAirport(tile)) { AnimateAirportTile(tile); + return; + } + + if (IsRoadStopTile(tile)) { + AnimateRoadStopTile(tile); + return; } } @@ -3805,6 +3905,7 @@ void OnTick_Station() /* Stop processing this station if it was deleted */ if (!StationHandleBigTick(st)) continue; TriggerStationAnimation(st, st->xy, SAT_250_TICKS); + TriggerRoadStopAnimation(st, st->xy, SAT_250_TICKS); if (Station::IsExpected(st)) AirportAnimationTrigger(Station::From(st), AAT_STATION_250_TICKS); } } @@ -3877,6 +3978,9 @@ 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); + TriggerRoadStopAnimation(st, st->xy, SAT_NEW_CARGO, type); + SetWindowDirty(WC_STATION_VIEW, st->index); st->MarkTilesDirty(true); diff --git a/src/station_cmd.h b/src/station_cmd.h index 84e17ed59f..50a08bbd24 100644 --- a/src/station_cmd.h +++ b/src/station_cmd.h @@ -14,6 +14,7 @@ #include "station_type.h" enum StationClassID : byte; +enum RoadStopClassID : byte; extern Town *AirportGetNearestTown(const struct AirportSpec *as, const TileIterator &it, uint &mindist); extern uint8 GetAirportNoiseLevelForDistance(const struct AirportSpec *as, uint distance); @@ -22,7 +23,7 @@ CommandCost CmdBuildAirport(DoCommandFlag flags, TileIndex tile, byte airport_ty CommandCost CmdBuildDock(DoCommandFlag flags, TileIndex tile, StationID station_to_join, bool adjacent); CommandCost CmdBuildRailStation(DoCommandFlag flags, TileIndex tile_org, RailType rt, Axis axis, byte numtracks, byte plat_len, StationClassID spec_class, byte spec_index, StationID station_to_join, bool adjacent); CommandCost CmdRemoveFromRailStation(DoCommandFlag flags, TileIndex start, TileIndex end, bool keep_rail); -CommandCost CmdBuildRoadStop(DoCommandFlag flags, TileIndex tile, uint8 width, uint8 length, RoadStopType stop_type, bool is_drive_through, DiagDirection ddir, RoadType rt, StationID station_to_join, bool adjacent); +CommandCost CmdBuildRoadStop(DoCommandFlag flags, TileIndex tile, uint8 width, uint8 length, RoadStopType stop_type, bool is_drive_through, DiagDirection ddir, RoadType rt, RoadStopClassID spec_class, byte spec_index, StationID station_to_join, bool adjacent); CommandCost CmdRemoveRoadStop(DoCommandFlag flags, TileIndex tile, uint8 width, uint8 height, RoadStopType stop_type, bool remove_road); CommandCost CmdRenameStation(DoCommandFlag flags, StationID station_id, const std::string &text); CommandCost CmdOpenCloseAirport(DoCommandFlag flags, StationID station_id); diff --git a/src/station_map.h b/src/station_map.h index d38ffe337f..201d3554aa 100644 --- a/src/station_map.h +++ b/src/station_map.h @@ -497,6 +497,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 IsRoadStopTile(t) + * @return True if this station is part of a newgrf station. + */ +static inline bool IsCustomRoadStopSpecIndex(TileIndex t) +{ + assert(IsRoadStopTile(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 IsRoadStopTile(t) + */ +static inline void SetCustomRoadStopSpecIndex(TileIndex t, byte specindex) +{ + assert(IsRoadStopTile(t)); + SB(_me[t].m8, 0, 6, specindex); +} + +/** + * Get the custom road stop spec for this tile. + * @param t Tile to query + * @pre IsRoadStopTile(t) + * @return The custom station spec of this tile. + */ +static inline uint GetCustomRoadStopSpecIndex(TileIndex t) +{ + assert(IsRoadStopTile(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 f21d2df4d4..b99a11c4df 100644 --- a/src/table/newgrf_debug_data.h +++ b/src/table/newgrf_debug_data.h @@ -10,6 +10,7 @@ #include "../newgrf_house.h" #include "../newgrf_engine.h" #include "../newgrf_roadtype.h" +#include "../newgrf_roadstop.h" /* Helper for filling property tables */ #define NIP(prop, base, variable, type, name) { name, [] (const void *b) -> const void * { return std::addressof(static_cast(b)->variable); }, cpp_sizeof(base, variable), prop, type } @@ -607,6 +608,64 @@ static const NIFeature _nif_tramtype = { new NIHRoadType(), }; +#define NICRS(cb_id, bit) NIC(cb_id, RoadStopSpec, callback_mask, bit) +static const NICallback _nic_roadstops[] = { + NICRS(CBID_STATION_AVAILABILITY, CBM_ROAD_STOP_AVAIL), + NICRS(CBID_STATION_ANIM_START_STOP, CBM_NO_BIT), + NICRS(CBID_STATION_ANIM_NEXT_FRAME, CBM_ROAD_STOP_ANIMATION_NEXT_FRAME), + NICRS(CBID_STATION_ANIMATION_SPEED, CBM_ROAD_STOP_ANIMATION_SPEED), + NIC_END() +}; + +static const NIVariable _nif_roadstops[] = { + NIV(0x40, "view/rotation"), + NIV(0x41, "stop type"), + NIV(0x42, "terrain type"), + NIV(0x43, "road type"), + NIV(0x44, "tram type"), + NIV(0x45, "town zone and Manhattan distance of town"), + NIV(0x46, "square of Euclidean distance of town"), + NIV(0x47, "player info"), + NIV(0x48, "bitmask of accepted cargoes"), + NIV(0x49, "current animation frame"), + NIV(0x60, "amount of cargo waiting"), + NIV(0x61, "time since last cargo pickup"), + NIV(0x62, "rating of cargo"), + NIV(0x63, "time spent on route"), + NIV(0x64, "information about last vehicle picking cargo up"), + NIV(0x65, "amount of cargo acceptance"), + NIV(0x66, "animation frame of nearby tile"), + NIV(0x67, "land info of nearby tiles"), + NIV(0x68, "road stop info of nearby tiles"), + NIV(0x69, "information about cargo accepted in the past"), + NIV(0x6A, "GRFID of nearby road stop tiles"), + NIV_END(), +}; + +class NIHRoadStop : public NIHelper { + bool IsInspectable(uint index) const override { return GetRoadStopSpec(index) != nullptr; } + uint GetParent(uint index) const override { return GetInspectWindowNumber(GSF_FAKE_TOWNS, BaseStation::GetByTile(index)->town->index); } + const void *GetInstance(uint index)const override { return nullptr; } + const void *GetSpec(uint index) const override { return GetRoadStopSpec(index); } + void SetStringParameters(uint index) const override { this->SetObjectAtStringParameters(STR_STATION_NAME, GetStationIndex(index), index); } + uint32 GetGRFID(uint index) const override { return (this->IsInspectable(index)) ? GetRoadStopSpec(index)->grf_prop.grffile->grfid : 0; } + + uint Resolve(uint index, uint var, uint32 param, bool *avail) const override + { + int view = GetRoadStopDir(index); + if (IsDriveThroughStopTile(index)) view += 4; + RoadStopResolverObject ro(GetRoadStopSpec(index), BaseStation::GetByTile(index), index, INVALID_ROADTYPE, GetStationType(index), view); + return ro.GetScope(VSG_SCOPE_SELF)->GetVariable(var, param, avail); + } +}; + +static const NIFeature _nif_roadstop = { + nullptr, + _nic_roadstops, + _nif_roadstops, + new NIHRoadStop(), +}; + /** Table with all NIFeatures. */ static const NIFeature * const _nifeatures[] = { &_nif_vehicle, // GSF_TRAINS @@ -629,6 +688,7 @@ static const NIFeature * const _nifeatures[] = { &_nif_airporttile, // GSF_AIRPORTTILES &_nif_roadtype, // GSF_ROADTYPES &_nif_tramtype, // GSF_TRAMTYPES + &_nif_roadstop, // GSF_ROADSTOPS &_nif_town, // GSF_FAKE_TOWNS }; static_assert(lengthof(_nifeatures) == GSF_FAKE_END); diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 937625e884..678b7db0ac 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -55,6 +55,7 @@ #include "misc_cmd.h" #include "train_cmd.h" #include "vehicle_cmd.h" +#include "newgrf_roadstop.h" #include "table/strings.h" @@ -2303,6 +2304,14 @@ void Vehicle::LeaveStation() SetBit(Train::From(this)->flags, VRF_LEAVING_STATION); } + if (this->type == VEH_ROAD && !(this->vehstatus & VS_CRASHED)) { + /* Trigger road stop animation */ + if (IsRoadStopTile(this->tile)) { + TriggerRoadStopRandomisation(st, this->tile, RSRT_VEH_DEPARTS); + TriggerRoadStopAnimation(st, this->tile, SAT_TRAIN_DEPARTS); + } + } + this->MarkDirty(); } diff --git a/src/widgets/road_widget.h b/src/widgets/road_widget.h index 425031e05b..fa23631ba1 100644 --- a/src/widgets/road_widget.h +++ b/src/widgets/road_widget.h @@ -41,18 +41,31 @@ enum BuildRoadDepotWidgets { /** Widgets of the #BuildRoadStationWindow class. */ enum BuildRoadStationWidgets { /* Name starts with BRO instead of BR, because of collision with BuildRailStationWidgets */ - WID_BROS_CAPTION, ///< Caption of the window. - WID_BROS_BACKGROUND, ///< Background of the window. - WID_BROS_STATION_NE, ///< Terminal station with NE entry. - WID_BROS_STATION_SE, ///< Terminal station with SE entry. - WID_BROS_STATION_SW, ///< Terminal station with SW entry. - WID_BROS_STATION_NW, ///< Terminal station with NW entry. - WID_BROS_STATION_X, ///< Drive-through station in x-direction. - WID_BROS_STATION_Y, ///< Drive-through station in y-direction. - WID_BROS_LT_OFF, ///< Turn off area highlight. - WID_BROS_LT_ON, ///< Turn on area highlight. - WID_BROS_INFO, ///< Station acceptance toggle. - WID_BROS_ACCEPTANCE, ///< Station acceptance. + WID_BROS_CAPTION, ///< Caption of the window. + WID_BROS_BACKGROUND, ///< Background of the window. + WID_BROS_STATION_NE, ///< Terminal station with NE entry. + WID_BROS_STATION_SE, ///< Terminal station with SE entry. + WID_BROS_STATION_SW, ///< Terminal station with SW entry. + WID_BROS_STATION_NW, ///< Terminal station with NW entry. + WID_BROS_STATION_X, ///< Drive-through station in x-direction. + WID_BROS_STATION_Y, ///< Drive-through station in y-direction. + WID_BROS_LT_OFF, ///< Turn off area highlight. + WID_BROS_LT_ON, ///< Turn on area highlight. + WID_BROS_ACCEPTANCE, ///< 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 */