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 */