Add: NewGRF road stops

This commit is contained in:
Jonathan G Rennison 2022-11-06 15:01:27 +00:00 committed by rubidium42
parent a18182e24b
commit 4c1406a4b5
33 changed files with 2086 additions and 132 deletions

View File

@ -998,6 +998,7 @@
<li>m7: animation frame (railway stations/waypoints, airports)</li>
<li>m8 bits 11..6: <a href="#TramType">Tramtype</a></li>
<li>m8 bits 5..0: <a href="#TrackType">track type</a> for railway stations/waypoints</li>
<li>m8 bits 5..0: custom road stop id; 0 means standard graphics</li>
</ul>
</td>
</tr>

View File

@ -203,7 +203,7 @@ the array so you can quickly see what is used and what is not.
<td class="bits"><span class="usable" title="Graphics index">OOOO O</span><span class="used" title="Graphics index: 00 (exit towards NE), 01 (exit towards SE), 02 (exit towards SW), 03 (exit towards NW), 04 (drive through X), 05 (drive through Y)">XXX</span></td>
<td class="bits" rowspan=5><span class="free">OO</span><span class="used" title="Station type">XX X</span><span class="free">OOO</span></td>
<td class="bits"><span class="free">OOO</span><span class="used" title="Owner of road">X XXXX</span></td>
<td class="bits"><span class="free">OOOO</span> <span class="used" title="Tram type">XXXX XX<span class="free">OO OOOO</span></td>
<td class="bits"><span class="free">OOOO</span> <span class="used" title="Tram type">XXXX XX</span> <span class="used" title="Custom road stops specifications ID">XXXXXX</span></td>
</tr>
<tr>
<td class="caption">airport</td>

View File

@ -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

View File

@ -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<StationSpecList> speclist; ///< List of rail station specs of this station.
std::vector<StationSpecList> speclist; ///< List of rail station specs of this station.
std::vector<RoadStopSpecList> 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<RoadStopTileData> 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:

View File

@ -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;

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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<RoadStopSpec*>(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<RoadStopSpec>(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));

View File

@ -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

View File

@ -278,7 +278,7 @@ bool DrawNewAirportTile(TileInfo *ti, Station *st, StationGfx gfx, const Airport
}
/** Helper class for animation control. */
struct AirportTileAnimationBase : public AnimationBase<AirportTileAnimationBase, AirportTileSpec, Station, int, GetAirportTileCallback> {
struct AirportTileAnimationBase : public AnimationBase<AirportTileAnimationBase, AirportTileSpec, Station, int, GetAirportTileCallback, TileAnimationFrameAnimationHelper<Station> > {
static const CallbackID cb_animation_speed = CBID_AIRPTILE_ANIMATION_SPEED;
static const CallbackID cb_animation_next_frame = CBID_AIRPTILE_ANIM_NEXT_FRAME;

View File

@ -17,15 +17,22 @@
#include "newgrf_callbacks.h"
#include "tile_map.h"
template <typename Tobj>
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 <typename Tbase, typename Tspec, typename Tobj, typename Textra, uint16 (*GetCallback)(CallbackID callback, uint32 param1, uint32 param2, const Tspec *statspec, Tobj *st, TileIndex tile, Textra extra_data)>
template <typename Tbase, typename Tspec, typename Tobj, typename Textra, uint16 (*GetCallback)(CallbackID callback, uint32 param1, uint32 param2, const Tspec *statspec, Tobj *st, TileIndex tile, Textra extra_data), typename Tframehelper>
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;
}

View File

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

View File

@ -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;
}
}

View File

@ -480,7 +480,7 @@ uint16 GetSimpleHouseCallback(CallbackID callback, uint32 param1, uint32 param2,
}
/** Helper class for animation control. */
struct HouseAnimationBase : public AnimationBase<HouseAnimationBase, HouseSpec, Town, CargoTypes, GetSimpleHouseCallback> {
struct HouseAnimationBase : public AnimationBase<HouseAnimationBase, HouseSpec, Town, CargoTypes, GetSimpleHouseCallback, TileAnimationFrameAnimationHelper<Town> > {
static const CallbackID cb_animation_speed = CBID_HOUSE_ANIMATION_SPEED;
static const CallbackID cb_animation_next_frame = CBID_HOUSE_ANIMATION_NEXT_FRAME;

View File

@ -257,7 +257,7 @@ uint16 GetSimpleIndustryCallback(CallbackID callback, uint32 param1, uint32 para
}
/** Helper class for animation control. */
struct IndustryAnimationBase : public AnimationBase<IndustryAnimationBase, IndustryTileSpec, Industry, int, GetSimpleIndustryCallback> {
struct IndustryAnimationBase : public AnimationBase<IndustryAnimationBase, IndustryTileSpec, Industry, int, GetSimpleIndustryCallback, TileAnimationFrameAnimationHelper<Industry> > {
static const CallbackID cb_animation_speed = CBID_INDTILE_ANIMATION_SPEED;
static const CallbackID cb_animation_next_frame = CBID_INDTILE_ANIM_NEXT_FRAME;

View File

@ -510,7 +510,7 @@ uint16 StubGetObjectCallback(CallbackID callback, uint32 param1, uint32 param2,
}
/** Helper class for animation control. */
struct ObjectAnimationBase : public AnimationBase<ObjectAnimationBase, ObjectSpec, Object, int, StubGetObjectCallback> {
struct ObjectAnimationBase : public AnimationBase<ObjectAnimationBase, ObjectSpec, Object, int, StubGetObjectCallback, TileAnimationFrameAnimationHelper<Object> > {
static const CallbackID cb_animation_speed = CBID_OBJECT_ANIMATION_SPEED;
static const CallbackID cb_animation_next_frame = CBID_OBJECT_ANIMATION_NEXT_FRAME;

614
src/newgrf_roadstop.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
/** @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 <typename Tspec, typename Tid, Tid Tmax>
void NewGRFClass<Tspec, Tid, Tmax>::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 <typename Tspec, typename Tid, Tid Tmax>
bool NewGRFClass<Tspec, Tid, Tmax>::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<RoadStopAnimationBase, RoadStopSpec, BaseStation, int, GetAnimRoadStopCallback, RoadStopAnimationFrameAnimationHelper> {
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;
}
}
}

189
src/newgrf_roadstop.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* @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<NUM_CARGO + 3> 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<RoadStopClassID> : MakeEnumPropsT<RoadStopClassID, byte, ROADSTOP_CLASS_BEGIN, ROADSTOP_CLASS_MAX, ROADSTOP_CLASS_MAX, 8> {};
typedef NewGRFClass<RoadStopSpec, RoadStopClassID, ROADSTOP_CLASS_MAX> 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 */

View File

@ -894,7 +894,7 @@ uint16 GetAnimStationCallback(CallbackID callback, uint32 param1, uint32 param2,
}
/** Helper class for animation control. */
struct StationAnimationBase : public AnimationBase<StationAnimationBase, StationSpec, BaseStation, int, GetAnimStationCallback> {
struct StationAnimationBase : public AnimationBase<StationAnimationBase, StationSpec, BaseStation, int, GetAnimStationCallback, TileAnimationFrameAnimationHelper<BaseStation> > {
static const CallbackID cb_animation_speed = CBID_STATION_ANIMATION_SPEED;
static const CallbackID cb_animation_next_frame = CBID_STATION_ANIM_NEXT_FRAME;

View File

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

View File

@ -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<DiagDirection>(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<CMD_BUILD_ROAD_STOP>::Do(CommandFlagsToDCFlags(GetCommandFlags<CMD_BUILD_ROAD_STOP>()), ta.tile, ta.w, ta.h, stop_type, drive_through, ddir, rt, INVALID_STATION, adjacent).Succeeded();
return Command<CMD_BUILD_ROAD_STOP>::Do(CommandFlagsToDCFlags(GetCommandFlags<CMD_BUILD_ROAD_STOP>()), ta.tile, ta.w, ta.h, stop_type, drive_through,
ddir, rt, spec_class, spec_index, INVALID_STATION, adjacent).Succeeded();
} else {
return Command<CMD_BUILD_ROAD_STOP>::Post(err_msg, CcRoadStop, ta.tile, ta.w, ta.h, stop_type, drive_through, ddir, rt, to_join, adjacent);
return Command<CMD_BUILD_ROAD_STOP>::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<CMD_REMOVE_ROAD_STOP>::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<CMD_REMOVE_ROAD_STOP>::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<RoadStopClassID, StringFilter &> 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<NWidgetStacked>(WID_BROS_SHOW_NEWST_DEFSIZE)->SetDisplayedPlane(newstops ? 0 : SZSP_NONE);
this->GetWidget<NWidgetStacked>(WID_BROS_FILTER_CONTAINER)->SetDisplayedPlane(newstops ? 0 : SZSP_HORIZONTAL);
this->GetWidget<NWidgetStacked>(WID_BROS_SHOW_NEWST_ADDITIONS)->SetDisplayedPlane(newstops ? 0 : SZSP_HORIZONTAL);
this->GetWidget<NWidgetStacked>(WID_BROS_SHOW_NEWST_ORIENTATION)->SetDisplayedPlane(newstops ? 0 : SZSP_HORIZONTAL);
this->GetWidget<NWidgetStacked>(WID_BROS_SHOW_NEWST_TYPE_SEL)->SetDisplayedPlane(newstops ? 0 : SZSP_HORIZONTAL);
this->GetWidget<NWidgetStacked>(WID_BROS_SHOW_NEWST_MATRIX)->SetDisplayedPlane(newstops ? 0 : SZSP_NONE);
this->GetWidget<NWidgetStacked>(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<NWidgetCore>(WID_BROS_CAPTION)->widget_data = rti->strings.picker_title[rs];
@ -1062,12 +1195,42 @@ struct BuildRoadStationWindow : public PickerWindowBase {
this->GetWidget<NWidgetCore>(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<NWidgetMatrix>(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<NWidgetBase>(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<NWidgetCore>(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<NWidgetMatrix>(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<NWidgetMatrix>(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;
}
/**

View File

@ -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"),

View File

@ -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
};

View File

@ -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<SlRoadStopSpecList, BaseStation> {
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<SlStationCargo, GoodsEntry> {
public:
inline static const SaveLoad description[] = {
@ -516,6 +550,35 @@ struct STNSChunkHandler : ChunkHandler {
}
};
class SlRoadStopTileData : public DefaultSaveLoadHandler<SlRoadStopTileData, BaseStation> {
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 {

View File

@ -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<CMD_BUILD_ROAD_STOP>::Do(tile, 1, 1, stop_type, drive_through, entrance_dir, ScriptObject::GetRoadType(), to_join, station_id != ScriptStation::STATION_JOIN_ADJACENT);
return ScriptObject::Command<CMD_BUILD_ROAD_STOP>::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)

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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.

View File

@ -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<const base *>(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);

View File

@ -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();
}

View File

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