Merge branch 'road-stops' into jgrpp

pull/374/head
Jonathan G Rennison 2 years ago
commit df92b7e449

@ -1057,6 +1057,7 @@
<li style="color: blue">m8 bits 14..12: <a href="#RoadCachedOneWayState">Road cached one way state</a></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 style="color: blue">m8 bits 5..0: custom road stop id; 0 means standard graphics</li>
</ul>
</td>
</tr>

@ -207,12 +207,12 @@ the array so you can quickly see what is used and what is not.
<td class="bits" rowspan=2><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=6><span class="free">O<span class="patch" title="Station type (extra bit)">P</span></span><span class="used" title="Station type">XXX</span> <span class="free">OOO</span></td>
<td class="bits" rowspan=2><span class="free">OOO</span><span class="used" title="Owner of road">X XXXX</span></td>
<td class="bits"><span class="free">O</span><span class="patch" title="Road cached one way state">PPP</span> <span class="used" title="Tram type">XXXX XX<span class="free">OO OOOO</span></td>
<td class="bits"><span class="free">O</span><span class="patch" title="Road cached one way state">PPP</span> <span class="used" title="Tram type">XXXX XX</span><span class="patch" title="Custom road stops specifications ID">PP PPPP</span></td>
</tr>
<tr>
<td class="caption">road waypoint</td>
<td class="bits"><span class="used" title="Owner of tram">XXXX</span> <span class="patch" title="Pavement type">PP</span> <span class="patch" title="Disallow vehicles to go a specific direction (drive-through road stop)">PP</span></td>
<td class="bits"><span class="patch" title="Snow/desert present">P</span> <span class="patch" title="Road cached one way state">PPP</span> <span class="used" title="Tram type">XXXX XX<span class="free">OO OOOO</span></td>
<td class="bits"><span class="patch" title="Snow/desert present">P</span> <span class="patch" title="Road cached one way state">PPP</span> <span class="used" title="Tram type">XXXX XX</span><span class="patch" title="Custom road stops specifications ID">PP PPPP</span></td>
</tr>
<tr>
<td class="caption">airport</td>

@ -670,5 +670,10 @@
-1 sprites/sample.png 546 8 09 23 33 -26 0
-1 sprites/sample.png 594 8 09 23 33 -5 0
</pre>
<h4 id="FIDM-features">Available features:</h4>
<table>
<tr><th>Feature name</th><th>Description</th></tr>
<tr><td><font face="monospace">road_stops</font></td><td><a href="newgrf-roadstops.html">Custom road stops (bus stops, lorry stops and road waypoints)</a></d></tr>
</table>
</body>
</html>

@ -0,0 +1,317 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>JGR's Patchpack - NewGRF Road Stops Addition to NewGRF Specifications</title>
<style type="text/css">
td li { white-space: nowrap; text-align: left; }
th { white-space: nowrap; text-align: center; }
td, th { border: 1px solid #CCCCCC; padding: 0px 5px; }
table { border-collapse: collapse; empty-cells: show; }
</style>
</head>
<body>
<h2>NewGRF Road Stops Addition to NewGRF Specifications in JGR's Patchpack</h2>
<p>This document describes the non-standard addition of the NewGRF road stops feature to the <a href="https://newgrf-specs.tt-wiki.net/wiki/Main_Page">Official OpenTTD NewGRF Specifications</a>, as implemented in this patchpack.</br>
This feature does not match OpenTTD PR #7955 in a number of key areas, this feature may not necessarily match implementations of NewGRF road stops in other patches, branches, etc.</br>
This feature as implemented here MAY also be present in other patchpacks.</p>
<p>See the <a href="newgrf-additions.html#feature-test">NewGRF additions</a> document for background information on additions to the NewGRF specifications.</p>
<p>A subset of the functionality listed below is (will be) also supported in a fork of NML, see the associated <a href="newgrf-roadstops-nml.html">NML road stops</a> and <a href="newgrf-additions-nml.html">NML additions</a> documents for more details.</p>
<p>NewGRFs which use this feature SHOULD use the <a href="newgrf-additions.html#feature-test">feature testing</a> mechanism to check whether the road stop feature and/or feature ID mapping is supported.</p>
<p>NewGRFs which use this feature MUST use the <a href="newgrf-additions.html#feature-id-mapping">feature ID mapping</a> mechanism to map the non-standard NewGRF road stop feature to a local feature ID.</p>
<p>This feature is indicated by the feature name: <font face="monospace">road_stops</font>, version 1.</br>
The feature name to use for feature ID mapping is <font face="monospace">road_stops</font>.</p>
<p><b>Actions:</b>
<ul>
<li><a href="#a0roadstops">Action 0</a></li>
<li><a href="#a2roadstops">Action 2</a></li>
<li><a href="#varaction2roadstops">Variational Action 2</a></li>
<li><a href="#randomaction2roadstops">Random Action 2</a></li>
<li><a href="#a3roadstops">Action 3</a></li>
</ul></p>
<h3 id="a0roadstops">Action 0 - Road Stops</h3>
<p>See the <a href="https://newgrf-specs.tt-wiki.net/wiki/Action0">Action 0 Specification</a> for background information.</p>
<b>Properties:</b>
<table>
<tr><th>Number</th><th>Mappable name</th><th>Size in bytes</th><th>Description</th></tr>
<tr><td><a href="#roadstop_class_id">08</a></td><td><a href="#roadstop_class_id">roadstop_class_id</a></td><td>4</td><td>Class ID</td></tr>
<tr><td><a href="#roadstop_stop_type">09</a></td><td><a href="#roadstop_stop_type">roadstop_stop_type</a></td><td>1</td><td>Stop type availability</td></tr>
<tr><td><a href="#roadstop_stop_name">0A</a></td><td><a href="#roadstop_stop_name">roadstop_stop_name</a></td><td>2</td><td>Name</td></tr>
<tr><td><a href="#roadstop_class_name">0B</a></td><td><a href="#roadstop_class_name">roadstop_class_name</a></td><td>2</td><td>Class name</td></tr>
<tr><td><a href="#roadstop_draw_mode">0C</a></td><td><a href="#roadstop_draw_mode">roadstop_draw_mode</a></td><td>1</td><td>Draw mode</td></tr>
<tr><td><a href="#roadstop_random_trigger_cargoes">0D</a></td><td><a href="#roadstop_random_trigger_cargoes">roadstop_random_trigger_cargoes</a></td><td>4</td><td>Random trigger cargoes</td></tr>
<tr><td><a href="#roadstop_animation_info">0E</a></td><td><a href="#roadstop_animation_info">roadstop_animation_info</a></td><td>2</td><td>Animation info</td></tr>
<tr><td><a href="#roadstop_animation_speed">0F</a></td><td><a href="#roadstop_animation_speed">roadstop_animation_speed</a></td><td>1</td><td>Animation speed</td></tr>
<tr><td><a href="#roadstop_animation_triggers">10</a></td><td><a href="#roadstop_animation_triggers">roadstop_animation_triggers</a></td><td>2</td><td>Animation triggers</td></tr>
<tr><td><a href="#roadstop_callback_mask">11</a></td><td><a href="#roadstop_callback_mask">roadstop_callback_mask</a></td><td>1</td><td>Callback flags</td></tr>
<tr><td><a href="#roadstop_general_flags">12</a></td><td><a href="#roadstop_general_flags">roadstop_general_flags</a></td><td>4</td><td>General flags</td></tr>
<tr><td><a href="#roadstop_min_bridge_height">13</a></td><td><a href="#roadstop_min_bridge_height">roadstop_min_bridge_height</a></td><td>6</td><td>Minimum bridge heights</td></tr>
<tr><td><a href="#roadstop_disallowed_bridge_pillars">14</a></td><td><a href="#roadstop_disallowed_bridge_pillars">roadstop_disallowed_bridge_pillars</a></td><td>6</td><td>Disallowed bridge pillars</td></tr>
</table>
<h4 id="roadstop_class_id">Road stop class ID (08, or mappable property: roadstop_class_id)</h4>
<p>This property sets the road stop class ID for this road stop ID.<br />
This property must be used first before any other property for this road stop ID.<br />
Two class names are pre-defined:
<table>
<tr><th>Name</th><th>Class ID</th><th>Meaning</th></tr>
<tr><td>DFLT</td><td>44 46 4C 54</td><td>Default bus and lorry stops</td></tr>
<tr><td>WAYP</td><td>57 41 59 50</td><td>This class is used for road waypoints</td></tr>
</table>
All classes except WAYP are used for bus and/or lorry stops.<br />
This functions the same way as <a href="https://newgrf-specs.tt-wiki.net/wiki/Action0/Stations#Station_class_.2808.29">station (feature 4) property 08</a>.<br />
The property length is 4 bytes.
</p>
<h4 id="roadstop_stop_type">Road stop type availability (09, or mappable property: roadstop_stop_type)</h4>
<p>This property sets the road stop type availability, this is ignored for road stops in the WAYP class.<br />
The property length is 1 byte. The format is:
<table>
<tr><th>Value</th><th>Meaning</th></tr>
<tr><td>0</td><td>Passenger/bus stop</td></tr>
<tr><td>1</td><td>Freight/lorry stop</td></tr>
<tr><td>2</td><td>Both passenger/bus and freight/lorry stops</td></tr>
</table>
The default value is 2 (both bus and lorry stops).
</p>
<h4 id="roadstop_stop_name">Road stop name (0A, or mappable property: roadstop_stop_name)</h4>
<p>This property sets the road stop name string ID.<br />
The property length is 2 bytes. This should be a D8xx or DCxx string ID.</p>
<h4 id="roadstop_class_name">Road stop class name (0B, or mappable property: roadstop_class_name)</h4>
<p>This property sets the road stop class name string ID.<br />
The property length is 2 bytes. This should be a D8xx or DCxx string ID.</p>
<h4 id="roadstop_draw_mode">Road stop draw mode (0C, or mappable property: roadstop_draw_mode)</h4>
<p>This property sets the road stop draw mode.<br />
The property length is 1 byte. The format is:
<table>
<tr><th>Bit</th><th>Value</th><th>Meaning</th></tr>
<tr><td>0</td><td>1</td><td>Bay stops: Draw road type ground sprite</td></tr>
<tr><td>1</td><td>2</td><td>Drive through stops: Draw road/tram type overlays</td></tr>
</table>
The default value is 3 (bits 0 and 1 both set).
</p>
<h4 id="roadstop_random_trigger_cargoes">Road stop random trigger cargoes (0D, or mappable property: roadstop_random_trigger_cargoes)</h4>
<p>This property sets the cargo types for random triggers.<br />
This functions the same way as <a href="https://newgrf-specs.tt-wiki.net/wiki/Action0/Stations#Cargo_types_for_random_triggers_.2812.29">station (feature 4) property 12</a>.<br />
This sets which cargo types should trigger re-randomizing. The cargo types are given as a bitmask of the bits from column 3 (type B) in CargoTypes. If nothing is set (the default), the no random triggers will happen, to conserve CPU time.<br />
With GRF version 7 and above, the interpretation of bits changes. Instead of climate-dependent cargo slot numbers (type B), you have to set the bits of climate-independent cargo ID (type A).<br />
The property length is 4 bytes.</p>
<h4 id="roadstop_animation_info">Road stop animation info (0E, or mappable property: roadstop_animation_info)</h4>
<p>This property sets the cargo types for random triggers.<br />
This functions the same way as <a href="https://newgrf-specs.tt-wiki.net/wiki/Action0/Stations#Animation_information_.2816.29">station (feature 4) property 16</a>.<br />
The low byte specifies the number of animation frames minus one, so 00 means 1 frame, 01 means 2 frames etc. The maximum number of frames is 256, although you can have some problems if your animation exceeds FD (253) frames.
The high byte must be 0 for non-looping animations and 01 for looping animations. Every other value is reserved for future use.
In addition, if the whole word contains FFFF, animation is turned off for this station (this is the default value). Since you can't have properties for individual station tiles, this property applies for every tile of the station.
If you don't want to animate some tiles, you should check the current position during callback 140 and return FD if the current tile doesn't need to be animated. If you also need animations of different length per tile, you'll have to use callback 141 for that.<br />
The property length is 2 bytes.</p>
<h4 id="roadstop_animation_speed">Road stop animation speed (0F, or mappable property: roadstop_animation_speed)</h4>
<p>This property sets the cargo types for random triggers.<br />
This functions the same way as <a href="https://newgrf-specs.tt-wiki.net/wiki/Action0/Stations#Animation_speed_.2817.29">station (feature 4) property 17</a>.<br />
This is the amount of time between switching frames. The default value is 2, which means the switch occurs every 108 milliseconds. Increasing this value by one doubles the wait, i.e. 3 will cause 216 ms delay, while 4 will pause 432 ms, and so on.
The minimum is 0, which means the fastest possible animation, changing frames every game tick (27ms). The maximum is 16, which means 1769 seconds (approx. half an hour) delay. Settings above this value may cause strange behaviour.<br />
The property length is 1 byte.</p>
<h4 id="roadstop_animation_triggers">Road stop animation triggers (10, or mappable property: roadstop_animation_triggers)</h4>
<p>This property sets the cargo types for random triggers.<br />
This functions the same way as <a href="https://newgrf-specs.tt-wiki.net/wiki/Action0/Stations#Animation_triggers_.2818.29">station (feature 4) property 18</a>.<br />
This is a bit mask of events that should trigger callback 140, allowing to change the animation state.<br />
<table>
<tr><th>Bit</th><th>Value</th><th>Meaning</th><th>Happens on</th><th>Var 18</th></tr>
<tr><td>0</td><td>1</td><td>Road stop is built</td><td>newly built tile</td><td></td></tr>
<tr><td>1</td><td>2</td><td>New cargo arrives at station</td><td>all tiles</td><td>Bits 8..15 contain the triggering cargo type</td></tr>
<tr><td>2</td><td>4</td><td>Cargo removed from station</td><td>all tiles</td><td>Bits 8..15 contain the triggering cargo type</td></tr>
<tr><td>3</td><td>8</td><td>Road vehicle enters stop (starts loading/unloading)</td><td>tile where the vehicle is</td><td></td></tr>
<tr><td>4</td><td>10</td><td>Road vehicle leaves stop (done loading/unloading)</td><td>tile where the vehicle is</td><td></td></tr>
<tr><td>5</td><td>20</td><td>Road vehicle loads/unloads cargo</td><td>tile where the vehicle is</td><td></td></tr>
<tr><td>6</td><td>40</td><td>Every 250 ticks</td><td>all tiles</td><td></td></tr>
</table>
For bits 1 and 2: bits 8..15 of var 18 contain the triggering cargo type. If your GRF has a cargo translation table, the cargo type will be an index in that table, or FFh if the cargo isn't in the table. If you don't have a cargo translation table, the cargo type will simply be the climate-dependent cargo type number.</p>
<h4 id="roadstop_callback_mask">Road stop callback flags (11, or mappable property: roadstop_callback_mask)</h4>
<p>This property enables callbacks for this road stop type.<br />
The property length is 1 byte. The format is:
<table>
<tr><th>Bit</th><th>Value</th><th>Variable 0C value</th><th>Callback</th></tr>
<tr><td>0</td><td>1</td><td>13</td><td>Whether to make road stop available in construction window (non-zero callback return) or not (callback returns zero)</td></tr>
<tr><td>1</td><td>2</td><td>141</td><td>Decide next animation frame</td></tr>
<tr><td>2</td><td>4</td><td>142</td><td>Decide animation speed</td></tr>
</table>
Variable 0C value is what variable 0C will be set to, for checking it in the VarAction2 for callbacks.<br />
The default value is 0 (no callbacks enabled).
</p>
<h4 id="roadstop_general_flags">Road stop general flags (12, or mappable property: roadstop_general_flags)</h4>
<p>This property sets general flags for this road stop type.<br />
The property length is 4 bytes. The format is:
<table>
<tr><th>Bit</th><th>Value</th><th>Meaning</th></tr>
<tr><td>0</td><td>1</td><td>Callback 141 needs random bits in variable 10</td></tr>
</table>
The default value is 0 (no flags enabled).
</p>
<h4 id="roadstop_min_bridge_height">Road stop minimum bridge heights (13, or mappable property: roadstop_min_bridge_height)</h4>
<p>This property allows or disallows building bridges over road stops.<br />
The bridge height property defines minimum clearances required for a bridge for each of the 6 views/rotations (or 0 to not allow any bridge). Values are given in height level units (1 level == 8px).<br />
Each height value is 1 byte, the total property length is 6 bytes.
</p>
<h4 id="roadstop_disallowed_bridge_pillars">Road stop disallowed bridge pillars (14, or mappable property: roadstop_disallowed_bridge_pillars)</h4>
<p>This property describes which bridge pillars are not allowed on the road stop tile.<br />
It consists of a set of pillar flags, for each of the 6 road stop views/rotations.<br />
Each set of flags is 1 byte, the total property length is 6 bytes.<br />
Each set of flags has the format described in the <a href="newgrf-additions.html#bridge_pillar_flags">bridge_pillar_flags property section</a>.
</p>
<p style="padding-top: 0.25em;">
<div id="roadstop_views">The 6 road stop views/rotations are described below.</div>
<table>
<tr><th>Views/rotation</th><th>Type</th><th>Description</tr>
<tr><td>0</td><td>Bay</td><td>Facing north-east</td></tr>
<tr><td>1</td><td>Bay</td><td>Facing south-east</td></tr>
<tr><td>2</td><td>Bay</td><td>Facing south-west</td></tr>
<tr><td>3</td><td>Bay</td><td>Facing north-west</td></tr>
<tr><td>4</td><td>Drive-through</td><td>X-axis: north-east to south-west</td></tr>
<tr><td>5</td><td>Drive-through</td><td>Y-axis: north-west to south-east</td></tr>
</table>
</p>
<br />
<h3 id="a2roadstops">Action 2 - Road Stops</h3>
<p>See the <a href="https://newgrf-specs.tt-wiki.net/wiki/Action2">Action 2 Specification</a> for background information.</p>
<p>Road stops use the <a href="https://newgrf-specs.tt-wiki.net/wiki/Action2/Sprite_Layout">special sprite layout format</a>, the same as Action 2 features: 7 (house), 9 (industry tile), F (object), 11 (airport tile).</p>
<br />
<h3 id="varaction2roadstops">Variational Action 2 - Road Stops</h3>
<p>See the <a href="https://newgrf-specs.tt-wiki.net/wiki/VariationalAction2">Variational Action 2 Specification</a> for background information.</p>
<b>Variables:</b>
<table>
<tr><th>Number</th><th>Mappable name</th><th>Description</th></tr>
<tr><td><a href="#roadstop_view">40</a></td><td><a href="#roadstop_view">roadstop_view</a></td><td>View/rotation</td></tr>
<tr><td><a href="#roadstop_type">41</a></td><td><a href="#roadstop_type">roadstop_type</a></td><td>Stop type</td></tr>
<tr><td><a href="#roadstop_terrain_type">42</a></td><td><a href="#roadstop_terrain_type">roadstop_terrain_type</a></td><td>Terrain type</td></tr>
<tr><td><a href="#roadstop_road_type">43</a></td><td><a href="#roadstop_road_type">roadstop_road_type</a></td><td>Road type</td></tr>
<tr><td><a href="#roadstop_tram_type">44</a></td><td><a href="#roadstop_tram_type">roadstop_tram_type</a></td><td>Tram type</td></tr>
<tr><td><a href="#roadstop_town_zone">45</a></td><td><a href="#roadstop_town_zone">roadstop_town_zone</a></td><td>Town zone and Manhattan distance of town</td></tr>
<tr><td><a href="#roadstop_town_distance_squared">46</a></td><td><a href="#roadstop_town_distance_squared">roadstop_town_distance_squared</a></td><td>Square of Euclidean distance of town</td></tr>
<tr><td><a href="#roadstop_company_info">47</a></td><td><a href="#roadstop_company_info">roadstop_company_info</a></td><td>Player/company info</td></tr>
<tr><td><a href="https://newgrf-specs.tt-wiki.net/wiki/VariationalAction2/BaseStation">48</a></td><td></td><td>Bitmask of accepted cargoes (BaseStation)</td></tr>
<tr><td><a href="#roadstop_animation_frame">49</a></td><td><a href="#roadstop_animation_frame">roadstop_animation_frame</a></td><td>Current animation frame</td></tr>
<tr><td><a href="https://newgrf-specs.tt-wiki.net/wiki/VariationalAction2/BaseStation">60</a></td><td></td><td>Amount of cargo waiting (BaseStation)</td></tr>
<tr><td><a href="https://newgrf-specs.tt-wiki.net/wiki/VariationalAction2/BaseStation">61</a></td><td></td><td>Time since last cargo pickup (BaseStation)</td></tr>
<tr><td><a href="https://newgrf-specs.tt-wiki.net/wiki/VariationalAction2/BaseStation">62</a></td><td></td><td>Rating of cargo (BaseStation)</td></tr>
<tr><td><a href="https://newgrf-specs.tt-wiki.net/wiki/VariationalAction2/BaseStation">63</a></td><td></td><td>Time spent on route (BaseStation)</td></tr>
<tr><td><a href="https://newgrf-specs.tt-wiki.net/wiki/VariationalAction2/BaseStation">64</a></td><td></td><td>Information about last vehicle picking cargo up (BaseStation)</td></tr>
<tr><td><a href="https://newgrf-specs.tt-wiki.net/wiki/VariationalAction2/BaseStation">65</a></td><td></td><td>Amount of cargo acceptance (BaseStation)</td></tr>
<tr><td><a href="#roadstop_animation_frame_nearby_tiles">66</a></td><td><a href="#roadstop_animation_frame_nearby_tiles">roadstop_animation_frame_nearby_tiles</a></td><td>Animation frame of nearby tile</td></tr>
<tr><td><a href="#roadstop_land_info_nearby_tiles">67</a></td><td><a href="#roadstop_land_info_nearby_tiles">roadstop_land_info_nearby_tiles</a></td><td>Land info of nearby tiles</td></tr>
<tr><td><a href="#roadstop_road_stop_info_nearby_tiles">68</a></td><td><a href="#roadstop_road_stop_info_nearby_tiles">roadstop_road_stop_info_nearby_tiles</a></td><td>Road stop info of nearby tiles</td></tr>
<tr><td><a href="https://newgrf-specs.tt-wiki.net/wiki/VariationalAction2/BaseStation">69</a></td><td></td><td>Information about cargo accepted in the past (BaseStation)</td></tr>
<tr><td><a href="#roadstop_road_stop_grfid_nearby_tiles">6A</a></td><td><a href="#roadstop_road_stop_grfid_nearby_tiles">roadstop_road_stop_grfid_nearby_tiles</a></td><td>GRFID of nearby road stop tiles</td></tr>
</table>
<h4 id="roadstop_view">Road stop view/rotation (40, or mappable variable: roadstop_view)</h4>
<p>This has the range 0 - 5, see <a href="#roadstop_views">the 6 road stop views/rotations</a></p>
<h4 id="roadstop_type">Road stop type (41, or mappable variable: roadstop_type)</h4>
<p>
<table>
<tr><th>Value</th><th>Meaning</th></tr>
<tr><td>0</td><td>Passenger/bus stop</td></tr>
<tr><td>1</td><td>Freight/lorry stop</td></tr>
<tr><td>2</td><td>Road waypoint</td></tr>
</table>
</p>
<h4 id="roadstop_terrain_type">Road stop tile/terrain type (42, or mappable variable: roadstop_terrain_type)</h4>
<p>This has the same value as <a href="https://newgrf-specs.tt-wiki.net/wiki/VariationalAction2/Objects#Tile_information_.2841.29">object (feature F) variable 41</a>.</p>
<h4 id="roadstop_road_type">Road stop road type (43, or mappable variable: roadstop_road_type)</h4>
<p>This has the value of the (translated) roadtype the road stop is built on, or the current roadtype in the build GUI. If the roadtype has no entry in the roadtype translation table of the GRF, this value will be 0xFF. If no translation table is present, the raw value will be returned. Note: only exact matches of roadtypes are reported. If the translation table of the GRF does not contain the exact roadtype 0xFF is returned.<br />
If the tile has no road at all (tram only) 0xFFFFFFFF is returned.</p>
<h4 id="roadstop_tram_type">Road stop tram type (44, or mappable variable: roadstop_road_type)</h4>
<p>This has the value of the (translated) tramtype the road stop is built on, or the current tramtype in the build GUI. If the tramtype has no entry in the tramtype translation table of the GRF, this value will be 0xFF. If no translation table is present, the raw value will be returned. Note: only exact matches of tramtypes are reported. If the translation table of the GRF does not contain the exact tramtype 0xFF is returned.<br />
If the tile has no tram at all (road only) 0xFFFFFFFF is returned.</p>
<h4 id="roadstop_town_zone">Town zone and Manhattan distance of town (45, or mappable variable: roadstop_town_zone)</h4>
<p>The town used is the one associated with the station/waypoint (this is in the station/waypoint name by default).
<table>
<tr><th>Bits</th><th>Meaning</th></tr>
<tr><td>0 - 15</td><td>Manhattan distance from town</td></tr>
<tr><td>16 - 24</td><td>Town zone</td></tr>
</table>
</p>
<h4 id="roadstop_town_distance_squared">Square of Euclidean distance of town (46, or mappable variable: roadstop_town_distance_squared)</h4>
<p>The town used is the one associated with the station/waypoint (this is in the station/waypoint name by default).</p>
<h4 id="roadstop_company_info">Player/company info (47, or mappable variable: roadstop_company_info)</h4>
<p>This has the same value as <a href="https://newgrf-specs.tt-wiki.net/wiki/VariationalAction2/Vehicles#Player_info_.2843.29">vehicle (features 0 - 3) variable 43</a>.<br />
The company used is the owner of the station/waypoint, not the owner of the underlying road and/or tram.</p>
<h4 id="roadstop_animation_frame">Current animation frame (49, or mappable variable: roadstop_animation_frame)</h4>
<p>This has the same value as <a href="https://newgrf-specs.tt-wiki.net/wiki/VariationalAction2/Stations">station (feature 4) variable 4A</a>.</p>
<h4 id="roadstop_animation_frame_nearby_tiles">Animation frame of nearby tile (66, or mappable variable: roadstop_animation_frame_nearby_tiles)</h4>
<p>This has the same value as <a href="https://newgrf-specs.tt-wiki.net/wiki/VariationalAction2/Stations#Animation_frame_of_nearby_tile_.2866.29">station (feature 4) variable 66</a>.</p>
<h4 id="roadstop_land_info_nearby_tiles">Land info of nearby tile (67, or mappable variable: roadstop_land_info_nearby_tiles)</h4>
<p>This has the same value as <a href="https://newgrf-specs.tt-wiki.net/wiki/VariationalAction2/Industry_Tiles#Land_info_of_nearby_tiles_.2860.29">industry tile (feature 9) variable 60</a>.</p>
<h4 id="roadstop_road_stop_info_nearby_tiles">Road stop of mearby tile (68, or mappable variable: roadstop_road_stop_info_nearby_tiles)</h4>
<p>This has a similar value to <a href="https://newgrf-specs.tt-wiki.net/wiki/VariationalAction2/Stations#Station_info_of_nearby_tiles_.2868.29">station (feature 4) variable 68</a>.<br />
<table>
<tr><th>Bits</th><th>Meaning</th></tr>
<tr><td>0 - 7</td><td>If the tile is defined in the current GRF, this is the setID used in the definition. Otherwise, the content is undefined. </td></tr>
<tr><td>8 - 9</td><td>
0 - The tile uses original TTD graphics<br />
1 - The tile is defined in the current GRF<br />
2 - The tile is defined in another GRF
</td></tr>
<tr><td>10</td><td>Set if the selected tile belongs to the current station, clear otherwise</td></tr>
<tr><td>11</td><td>Clear if the selected tile has the same view/rotation, set if a different view/rotation</td></tr>
<tr><td>14 - 12</td><td>View/rotation of the selected tile</td></tr>
</table>
</p>
<h4 id="roadstop_road_stop_grfid_nearby_tiles">GRFID of nearby road stop tile (6A, or mappable variable: roadstop_road_stop_grfid_nearby_tiles)</h4>
<p>This has the same value as <a href="https://newgrf-specs.tt-wiki.net/wiki/VariationalAction2/Stations#GRFID_of_nearby_station_tile_.286A.29">station (feature 4) variable 6A</a>.</p>
<br />
<h3 id="randomaction2roadstops">Random Action 2 - Road Stops</h3>
<p>See the <a href="https://newgrf-specs.tt-wiki.net/wiki/RandomAction2">Random Action 2 Specification</a> for background information.</p>
<p>Road stops have 16+8 random bits. Bits 0 to 15 are a property of the station as a whole, and bits 16 to 23 are different for each tile.
<table>
<tr><th>Bit</th><th>Value</th><th>Trigger</th></tr>
<tr><td>0</td><td>01</td><td>New cargo waiting</td></tr>
<tr><td>1</td><td>02</td><td>No more cargo</td></tr>
<tr><td>2</td><td>04</td><td>Road vehicle arrives (starts unloading/loading)</td></tr>
<tr><td>3</td><td>08</td><td>Road vehicle leaves (done unloading &amp; loading)</td></tr>
<tr><td>4</td><td>10</td><td>Road vehicle loads or unloads cargo</td></tr>
</table>
Note that a road vehicle arrives trigger does not imply that the stop was previously empty, and a road vehicle leaves trigger does not imply that the stop is now empty.</p>
<p>
Also note that none of the above triggers will actually trigger unless <a href="#roadstop_random_trigger_cargoes">property 0D/roadstop_random_trigger_cargoes</a> has at least one bit set.<br />
Triggers 01 will be triggered for any of the cargo types set in <a href="#roadstop_random_trigger_cargoes">property 0D/roadstop_random_trigger_cargoes</a>, but trigger 02 will only be triggered if all of those cargo types have no more cargo waiting.<br />
Triggers 04 and 08 are triggered no matter what cargo types the train transports, as long as <a href="#roadstop_random_trigger_cargoes">property 0D/roadstop_random_trigger_cargoes</a> is not zero.<br />
Triggers 04, 08, and 10 only affect the tile on which they occur, as well as the random bits of the station, but not other tiles.
</p>
<br />
<h3 id="a3roadstops">Action 3 - Road Stops</h3>
<p>See the <a href="https://newgrf-specs.tt-wiki.net/wiki/Action3">Action 3 Specification</a> for background information.</p>
<p>Road stops have the same Action 3 cargo-type values as feature 4 (station).</p>
</body>
</html>

@ -280,6 +280,8 @@ add_files(
newgrf_properties.h
newgrf_railtype.cpp
newgrf_railtype.h
newgrf_roadstop.cpp
newgrf_roadstop.h
newgrf_roadtype.cpp
newgrf_roadtype.h
newgrf_sound.cpp

@ -27,6 +27,12 @@ struct StationSpecList {
uint8 localidx; ///< Station ID within GRF of station
};
struct RoadStopSpecList {
const RoadStopSpec *spec;
uint32 grfid; ///< GRF ID of this custom road stop
uint8 localidx; ///< Station ID within GRF of road stop
};
/** StationRect - used to track station spread out rectangle - cheaper than scanning whole map */
struct StationRect : public Rect {
@ -66,18 +72,25 @@ struct BaseStation : StationPool::PoolItem<&_station_pool> {
StationFacility facilities; ///< The facilities that this station has
uint8 num_specs; ///< Number of specs in the speclist
uint8 num_roadstop_specs; ///< Number of road stop specs in the roadstop_speclist
StationSpecList *speclist; ///< List of station specs of this station
RoadStopSpecList *roadstop_speclist; ///< List of road stop specs of this station
Date build_date; ///< Date of construction
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<TileIndex> custom_road_stop_tiles; ///< List of custom road stop tiles
std::vector<uint16> custom_road_stop_data; ///< Custom road stop random bits (low) and animation byte (high) in same order as custom_road_stop_tiles
/**
* Initialize the base station.
* @param tile The location of the station sign
@ -181,6 +194,32 @@ struct BaseStation : StationPool::PoolItem<&_station_pool> {
return (this->facilities & facilities) != 0;
}
inline uint GetRoadStopData(TileIndex tile) const
{
for (size_t i = 0; i < this->custom_road_stop_tiles.size(); i++) {
if (this->custom_road_stop_tiles[i] == tile) return this->custom_road_stop_data[i];
}
return 0;
}
inline byte GetRoadStopRandomBits(TileIndex tile) const
{
return GB(this->GetRoadStopData(tile), 0, 8);
}
inline byte GetRoadStopAnimationFrame(TileIndex tile) const
{
return GB(this->GetRoadStopData(tile), 8, 8);
}
private:
void SetRoadStopTileData(TileIndex tile, byte data, byte offset);
public:
inline void SetRoadStopRandomBits(TileIndex tile, byte random_bits) { this->SetRoadStopTileData(tile, random_bits, 0); }
inline void SetRoadStopAnimationFrame(TileIndex tile, byte frame) { this->SetRoadStopTileData(tile, random_bits, 8); }
void RemoveRoadStopTileData(TileIndex tile);
static void PostDestructor(size_t index);
private:

@ -116,6 +116,7 @@ static int32 ClickChangeDateCheat(int32 p1, int32 p2)
SetDate(new_date, _date_fract);
EnginesMonthlyLoop();
InvalidateWindowClassesData(WC_BUILD_STATION, 0);
InvalidateWindowClassesData(WC_BUS_STATION, 0);
InvalidateWindowClassesData(WC_BUILD_OBJECT, 0);
ResetSignalVariant();
MarkWholeScreenDirty();

@ -67,12 +67,12 @@ CommandProc CmdBuildTunnel;
CommandProc CmdBuildTrainDepot;
CommandProcEx CmdBuildRailWaypoint;
CommandProc CmdBuildRoadWaypoint;
CommandProcEx CmdBuildRoadWaypoint;
CommandProc CmdRenameWaypoint;
CommandProc CmdSetWaypointLabelHidden;
CommandProc CmdRemoveFromRailWaypoint;
CommandProc CmdBuildRoadStop;
CommandProcEx CmdBuildRoadStop;
CommandProc CmdRemoveRoadStop;
CommandProc CmdBuildLongRoad;

@ -215,6 +215,8 @@ static void OnNewYear()
VehiclesYearlyLoop();
TownsYearlyLoop();
InvalidateWindowClassesData(WC_BUILD_STATION);
InvalidateWindowClassesData(WC_BUS_STATION);
InvalidateWindowClassesData(WC_TRUCK_STATION);
if (_network_server) NetworkServerYearlyLoop();
if (_cur_date_ymd.year == _settings_client.gui.semaphore_build_before) ResetSignalVariant();

@ -26,6 +26,7 @@
#include "newgrf_industrytiles.h"
#include "newgrf_station.h"
#include "newgrf_airporttiles.h"
#include "newgrf_roadstop.h"
#include "object.h"
#include "strings_func.h"
#include "date_func.h"
@ -2146,6 +2147,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);
TriggerRoadStopAnimation(st, st->xy, SAT_NEW_CARGO, v->cargo_type);
TriggerRoadStopRandomisation(st, st->xy, RSRT_CARGO_TAKEN, v->cargo_type);
}
new_load_unload_ticks += loaded;
@ -2166,6 +2169,9 @@ static void LoadUnloadVehicle(Vehicle *front)
if (front->type == VEH_TRAIN) {
TriggerStationRandomisation(st, station_tile, SRT_TRAIN_LOADS);
TriggerStationAnimation(st, station_tile, SAT_TRAIN_LOADS);
} else if (front->type == VEH_ROAD) {
TriggerRoadStopRandomisation(st, station_tile, RSRT_VEH_LOADS);
TriggerRoadStopAnimation(st, station_tile, SAT_TRAIN_LOADS);
}
}

@ -2450,6 +2450,7 @@ STR_CONFIG_SETTING_ALLOW_GRF_STATIONS_UNDER_BRIDGES_HELPTEXT :If enabled, New
STR_CONFIG_SETTING_ALLOW_ROAD_STATIONS_UNDER_BRIDGES :Allow road/tram stops under bridges: {STRING2}
STR_CONFIG_SETTING_ALLOW_ROAD_STATIONS_UNDER_BRIDGES_HELPTEXT :Allow road/tram stops under bridges. Drive through stops require 1 height level of clearance, drive in bays require 2 height levels of clearance.
STR_CONFIG_SETTING_ALLOW_ROAD_STATIONS_UNDER_BRIDGES_HELPTEXT_EXTRA :{STRING}{}{}NewGRF road/tram stops may override this setting and allow or disallow custom stops under bridges, and set custom height clearance requirements.
STR_CONFIG_SETTING_ALLOW_DOCKS_UNDER_BRIDGES :Allow docks under bridges: {STRING2}
STR_CONFIG_SETTING_ALLOW_DOCKS_UNDER_BRIDGES_HELPTEXT :Allow docks under bridges. Docks require 2 height levels of clearance.{}This may result in graphical issues.

@ -48,6 +48,7 @@
#include "language.h"
#include "vehicle_base.h"
#include "road.h"
#include "newgrf_roadstop.h"
#include "table/strings.h"
#include "table/build_industry.h"
@ -4923,6 +4924,176 @@ static ChangeInfoResult AirportTilesChangeInfo(uint airtid, int numinfo, int pro
return ret;
}
/**
* Ignore properties for roadstops
* @param prop The property to ignore.
* @param buf The property value.
* @return ChangeInfoResult.
*/
static ChangeInfoResult IgnoreRoadStopProperty(uint prop, ByteReader *buf)
{
ChangeInfoResult ret = CIR_SUCCESS;
switch (prop) {
case 0x09:
case 0x0C:
buf->ReadByte();
break;
case 0x0A:
case 0x0B:
buf->ReadWord();
break;
case 0x08:
case 0x0D:
buf->ReadDWord();
break;
default:
ret = HandleAction0PropertyDefault(buf, prop);
break;
}
return ret;
}
static ChangeInfoResult RoadStopChangeInfo(uint id, int numinfo, int prop, const GRFFilePropertyRemapEntry *mapping_entry, ByteReader *buf)
{
ChangeInfoResult ret = CIR_SUCCESS;
if (id + numinfo > 255) {
grfmsg(1, "RoadStopChangeInfo: RoadStop %u is invalid, max %u, ignoring", id + numinfo, 255);
return CIR_INVALID_ID;
}
if (_cur.grffile->roadstops == nullptr) _cur.grffile->roadstops = CallocT<RoadStopSpec*>(255);
for (int i = 0; i < numinfo; i++) {
RoadStopSpec *rs = _cur.grffile->roadstops[id + i];
if (rs == nullptr && prop != 0x08 && prop != A0RPI_ROADSTOP_CLASS_ID) {
grfmsg(1, "RoadStopChangeInfo: Attempt to modify undefined road stop %u, ignoring", id + i);
ChangeInfoResult cir = IgnoreRoadStopProperty(prop, buf);
if (cir > ret) ret = cir;
continue;
}
switch (prop) {
case A0RPI_ROADSTOP_CLASS_ID:
if (MappedPropertyLengthMismatch(buf, 4, mapping_entry)) break;
FALLTHROUGH;
case 0x08: { // Road Stop Class ID
RoadStopSpec **spec = &_cur.grffile->roadstops[id + i];
if (*spec == nullptr) *spec = CallocT<RoadStopSpec>(1);
uint32 classid = buf->ReadDWord();
(*spec)->cls_id = RoadStopClass::Allocate(BSWAP32(classid));
(*spec)->spec_id = id + i;
break;
}
case A0RPI_ROADSTOP_STOP_TYPE:
if (MappedPropertyLengthMismatch(buf, 1, mapping_entry)) break;
FALLTHROUGH;
case 0x09: // Road stop type
rs->stop_type = (RoadStopAvailabilityType)buf->ReadByte();
break;
case A0RPI_ROADSTOP_STOP_NAME:
if (MappedPropertyLengthMismatch(buf, 2, mapping_entry)) break;
FALLTHROUGH;
case 0x0A: // Road Stop Name
AddStringForMapping(buf->ReadWord(), &rs->name);
break;
case A0RPI_ROADSTOP_CLASS_NAME:
if (MappedPropertyLengthMismatch(buf, 2, mapping_entry)) break;
FALLTHROUGH;
case 0x0B: // Road Stop Class name
AddStringForMapping(buf->ReadWord(), &RoadStopClass::Get(rs->cls_id)->name);
break;
case A0RPI_ROADSTOP_DRAW_MODE:
if (MappedPropertyLengthMismatch(buf, 1, mapping_entry)) break;
FALLTHROUGH;
case 0x0C: // The draw mode
rs->draw_mode = (RoadStopDrawMode)buf->ReadByte();
break;
case A0RPI_ROADSTOP_TRIGGER_CARGOES:
if (MappedPropertyLengthMismatch(buf, 4, mapping_entry)) break;
FALLTHROUGH;
case 0x0D: // Cargo types for random triggers
rs->cargo_triggers = TranslateRefitMask(buf->ReadDWord());
break;
case A0RPI_ROADSTOP_ANIMATION_INFO:
if (MappedPropertyLengthMismatch(buf, 2, mapping_entry)) break;
FALLTHROUGH;
case 0x0E: // Animation info
rs->animation.frames = buf->ReadByte();
rs->animation.status = buf->ReadByte();
break;
case A0RPI_ROADSTOP_ANIMATION_SPEED:
if (MappedPropertyLengthMismatch(buf, 1, mapping_entry)) break;
FALLTHROUGH;
case 0x0F: // Animation speed
rs->animation.speed = buf->ReadByte();
break;
case A0RPI_ROADSTOP_ANIMATION_TRIGGERS:
if (MappedPropertyLengthMismatch(buf, 2, mapping_entry)) break;
FALLTHROUGH;
case 0x10: // Animation triggers
rs->animation.triggers = buf->ReadWord();
break;
case A0RPI_ROADSTOP_CALLBACK_MASK:
if (MappedPropertyLengthMismatch(buf, 1, mapping_entry)) break;
FALLTHROUGH;
case 0x11: // Callback mask
rs->callback_mask = buf->ReadByte();
break;
case A0RPI_ROADSTOP_GENERAL_FLAGS:
if (MappedPropertyLengthMismatch(buf, 4, mapping_entry)) break;
FALLTHROUGH;
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 A0RPI_ROADSTOP_MIN_BRIDGE_HEIGHT:
if (MappedPropertyLengthMismatch(buf, 6, mapping_entry)) break;
FALLTHROUGH;
case 0x13: // Minimum height for a bridge above
SetBit(rs->internal_flags, RSIF_BRIDGE_HEIGHTS_SET);
for (uint i = 0; i < 6; i++) {
rs->bridge_height[i] = buf->ReadByte();
}
break;
case A0RPI_ROADSTOP_DISALLOWED_BRIDGE_PILLARS:
if (MappedPropertyLengthMismatch(buf, 6, mapping_entry)) break;
FALLTHROUGH;
case 0x14: // Disallowed bridge pillars
SetBit(rs->internal_flags, RSIF_BRIDGE_DISALLOWED_PILLARS_SET);
for (uint i = 0; i < 6; i++) {
rs->bridge_disallowed_pillars[i] = buf->ReadByte();
}
break;
default:
ret = CIR_UNKNOWN;
break;
}
}
return ret;
}
static bool HandleChangeInfoResult(const char *caller, ChangeInfoResult cir, GrfSpecFeature feature, int property)
{
switch (cir) {
@ -5002,6 +5173,7 @@ static const char *_feature_names[] = {
"AIRPORTTILES",
"ROADTYPES",
"TRAMTYPES",
"ROADSTOPS",
};
static_assert(lengthof(_feature_names) == GSF_END);
@ -5106,6 +5278,7 @@ static void FeatureChangeInfo(ByteReader *buf)
/* GSF_AIRPORTTILES */ AirportTilesChangeInfo,
/* GSF_ROADTYPES */ RoadTypeChangeInfo,
/* GSF_TRAMTYPES */ TramTypeChangeInfo,
/* GSF_ROADSTOPS */ RoadStopChangeInfo,
};
static_assert(GSF_END == lengthof(handler));
static_assert(lengthof(handler) == lengthof(_cur.grffile->action0_property_remaps), "Action 0 feature list length mismatch");
@ -5616,7 +5789,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());
@ -5731,7 +5905,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) {
@ -6283,6 +6457,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)
@ -6388,6 +6617,10 @@ static void FeatureMapSpriteGroup(ByteReader *buf)
AirportTileMapSpriteGroup(buf, idcount);
return;
case GSF_ROADSTOPS:
RoadStopMapSpriteGroup(buf, idcount);
return;
default:
grfmsg(1, "FeatureMapSpriteGroup: Unsupported feature %s, skipping", GetFeatureString(feature_ref));
return;
@ -9620,6 +9853,20 @@ static void ResetCustomObjects()
}
}
static void ResetCustomRoadStops()
{
for (auto file : _grf_files) {
RoadStopSpec **&roadstopspec = file->roadstops;
if (roadstopspec == nullptr) continue;
for (uint i = 0; i < NUM_ROADSTOPS_PER_GRF; i++) {
free(roadstopspec[i]);
}
free(roadstopspec);
roadstopspec = nullptr;
}
}
/** Reset and clear all NewGRFs */
static void ResetNewGRF()
{
@ -9707,6 +9954,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));

@ -87,9 +87,11 @@ enum GrfSpecFeature {
GSF_AIRPORTTILES,
GSF_ROADTYPES,
GSF_TRAMTYPES,
GSF_ROADSTOPS,
GSF_END,
GSF_REAL_FEATURE_END = GSF_END,
GSF_REAL_FEATURE_END = GSF_ROADSTOPS,
GSF_FAKE_TOWNS = GSF_END, ///< Fake town GrfSpecFeature for NewGRF debugging (parent scope)
GSF_FAKE_STATION_STRUCT, ///< Fake station struct GrfSpecFeature for NewGRF debugging
@ -302,6 +304,7 @@ struct GRFFile : ZeroedMemoryAllocator {
struct ObjectSpec **objectspec;
struct AirportSpec **airportspec;
struct AirportTileSpec **airtspec;
struct RoadStopSpec **roadstops;
GRFFeatureMapRemapSet feature_id_remaps;
GRFFilePropertyRemapSet action0_property_remaps[GSF_END];

@ -279,7 +279,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;

@ -17,6 +17,12 @@
#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.
@ -24,8 +30,9 @@
* @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 (_scaled_tick_counter % (1 << 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, VMDF_NOT_MAP_MODE);
}
@ -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;
}

@ -307,6 +307,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.
*/

@ -27,6 +27,7 @@
#include "company_base.h"
#include "error.h"
#include "strings_func.h"
#include "newgrf_roadstop.h"
#include "table/strings.h"

@ -965,7 +965,14 @@ GrfSpecFeature GetGrfSpecFeature(TileIndex tile)
switch (GetStationType(tile)) {
case STATION_RAIL: return GSF_STATIONS;
case STATION_AIRPORT: return GSF_AIRPORTTILES;
default: return GSF_INVALID;
case STATION_BUS:
case STATION_TRUCK:
case STATION_ROADWAYPOINT:
return GSF_ROADSTOPS;
default:
return GSF_INVALID;
}
}
}

@ -51,11 +51,13 @@ extern const GRFFeatureInfo _grf_feature_list[] = {
GRFFeatureInfo("action0_object_edge_foundation_mode", 2),
GRFFeatureInfo("action0_object_flood_resistant", 1),
GRFFeatureInfo("action0_object_viewport_map_tile_type", 1),
GRFFeatureInfo("road_stops", 1),
GRFFeatureInfo(),
};
/** Action14 remappable feature list */
extern const GRFFeatureMapDefinition _grf_remappable_features[] = {
GRFFeatureMapDefinition(GSF_ROADSTOPS, "road_stops"),
GRFFeatureMapDefinition(),
};
@ -88,6 +90,19 @@ extern const GRFPropertyMapDefinition _grf_action0_remappable_properties[] = {
GRFPropertyMapDefinition(GSF_OBJECTS, A0RPI_OBJECT_FLOOD_RESISTANT, "object_flood_resistant"),
GRFPropertyMapDefinition(GSF_OBJECTS, A0RPI_OBJECT_VIEWPORT_MAP_TYPE, "object_viewport_map_tile_type"),
GRFPropertyMapDefinition(GSF_OBJECTS, A0RPI_OBJECT_VIEWPORT_MAP_SUBTYPE, "object_viewport_map_tile_subtype"),
GRFPropertyMapDefinition(GSF_ROADSTOPS, A0RPI_ROADSTOP_CLASS_ID, "roadstop_class_id"),
GRFPropertyMapDefinition(GSF_ROADSTOPS, A0RPI_ROADSTOP_STOP_TYPE, "roadstop_stop_type"),
GRFPropertyMapDefinition(GSF_ROADSTOPS, A0RPI_ROADSTOP_STOP_NAME, "roadstop_stop_name"),
GRFPropertyMapDefinition(GSF_ROADSTOPS, A0RPI_ROADSTOP_CLASS_NAME, "roadstop_class_name"),
GRFPropertyMapDefinition(GSF_ROADSTOPS, A0RPI_ROADSTOP_DRAW_MODE, "roadstop_draw_mode"),
GRFPropertyMapDefinition(GSF_ROADSTOPS, A0RPI_ROADSTOP_TRIGGER_CARGOES, "roadstop_random_trigger_cargoes"),
GRFPropertyMapDefinition(GSF_ROADSTOPS, A0RPI_ROADSTOP_ANIMATION_INFO, "roadstop_animation_info"),
GRFPropertyMapDefinition(GSF_ROADSTOPS, A0RPI_ROADSTOP_ANIMATION_SPEED, "roadstop_animation_speed"),
GRFPropertyMapDefinition(GSF_ROADSTOPS, A0RPI_ROADSTOP_ANIMATION_TRIGGERS, "roadstop_animation_triggers"),
GRFPropertyMapDefinition(GSF_ROADSTOPS, A0RPI_ROADSTOP_CALLBACK_MASK, "roadstop_callback_mask"),
GRFPropertyMapDefinition(GSF_ROADSTOPS, A0RPI_ROADSTOP_GENERAL_FLAGS, "roadstop_general_flags"),
GRFPropertyMapDefinition(GSF_ROADSTOPS, A0RPI_ROADSTOP_MIN_BRIDGE_HEIGHT, "roadstop_min_bridge_height"),
GRFPropertyMapDefinition(GSF_ROADSTOPS, A0RPI_ROADSTOP_DISALLOWED_BRIDGE_PILLARS, "roadstop_disallowed_bridge_pillars"),
GRFPropertyMapDefinition(),
};
@ -95,6 +110,19 @@ extern const GRFPropertyMapDefinition _grf_action0_remappable_properties[] = {
extern const GRFVariableMapDefinition _grf_action2_remappable_variables[] = {
GRFVariableMapDefinition(GSF_OBJECTS, A2VRI_OBJECT_FOUNDATION_SLOPE, "object_foundation_tile_slope"),
GRFVariableMapDefinition(GSF_OBJECTS, A2VRI_OBJECT_FOUNDATION_SLOPE_CHANGE, "object_foundation_change_tile_slope"),
GRFVariableMapDefinition(GSF_ROADSTOPS, 0x40, "roadstop_view"),
GRFVariableMapDefinition(GSF_ROADSTOPS, 0x41, "roadstop_type"),
GRFVariableMapDefinition(GSF_ROADSTOPS, 0x42, "roadstop_terrain_type"),
GRFVariableMapDefinition(GSF_ROADSTOPS, 0x43, "roadstop_road_type"),
GRFVariableMapDefinition(GSF_ROADSTOPS, 0x44, "roadstop_tram_type"),
GRFVariableMapDefinition(GSF_ROADSTOPS, 0x45, "roadstop_town_zone"),
GRFVariableMapDefinition(GSF_ROADSTOPS, 0x46, "roadstop_town_distance_squared"),
GRFVariableMapDefinition(GSF_ROADSTOPS, 0x47, "roadstop_company_info"),
GRFVariableMapDefinition(GSF_ROADSTOPS, 0x49, "roadstop_animation_frame"),
GRFVariableMapDefinition(GSF_ROADSTOPS, 0x66, "roadstop_animation_frame_nearby_tiles"),
GRFVariableMapDefinition(GSF_ROADSTOPS, 0x67, "roadstop_land_info_nearby_tiles"),
GRFVariableMapDefinition(GSF_ROADSTOPS, 0x68, "roadstop_road_stop_info_nearby_tiles"),
GRFVariableMapDefinition(GSF_ROADSTOPS, 0x6A, "roadstop_road_stop_grfid_nearby_tiles"),
GRFVariableMapDefinition(),
};

@ -39,6 +39,19 @@ enum Action0RemapPropertyIds {
A0RPI_OBJECT_FLOOD_RESISTANT,
A0RPI_OBJECT_VIEWPORT_MAP_TYPE,
A0RPI_OBJECT_VIEWPORT_MAP_SUBTYPE,
A0RPI_ROADSTOP_CLASS_ID,
A0RPI_ROADSTOP_STOP_TYPE,
A0RPI_ROADSTOP_STOP_NAME,
A0RPI_ROADSTOP_CLASS_NAME,
A0RPI_ROADSTOP_DRAW_MODE,
A0RPI_ROADSTOP_TRIGGER_CARGOES,
A0RPI_ROADSTOP_ANIMATION_INFO,
A0RPI_ROADSTOP_ANIMATION_SPEED,
A0RPI_ROADSTOP_ANIMATION_TRIGGERS,
A0RPI_ROADSTOP_CALLBACK_MASK,
A0RPI_ROADSTOP_GENERAL_FLAGS,
A0RPI_ROADSTOP_MIN_BRIDGE_HEIGHT,
A0RPI_ROADSTOP_DISALLOWED_BRIDGE_PILLARS,
};

@ -637,7 +637,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;

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

@ -558,7 +558,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;

@ -0,0 +1,644 @@
/*
* 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(uint16 variable, uint32 parameter, GetVariableExtra *extra) 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 (IsAnyRoadStopTile(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 (!IsAnyRoadStopTile(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;
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 (!IsAnyRoadStopTile(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, &(extra->available));
extra->available = false;
return UINT_MAX;
}
const SpriteGroup *RoadStopResolverObject::ResolveReal(const RealSpriteGroup *group) const
{
if (group == nullptr) return nullptr;
return group->loading[0];
}
RoadStopResolverObject::RoadStopResolverObject(const RoadStopSpec *roadstopspec, BaseStation *st, TileIndex tile, 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) {
TileInfo ti {};
ti.tile = INVALID_TILE;
DrawRoadOverlays(&ti, PAL_NONE, RoadTypeIsRoad(roadtype) ? rti : nullptr, RoadTypeIsTram(roadtype) ? rti : nullptr, sprite_offset, sprite_offset);
}
if (rti->UsesOverlay()) {
SpriteID ground = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_GROUND);
DrawSprite(ground + sprite_offset, PAL_NONE, x, y);
SpriteID overlay = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_OVERLAY);
if (overlay) DrawSprite(overlay + sprite_offset, PAL_NONE, x, y);
} else if (RoadTypeIsTram(roadtype)) {
DrawSprite(SPR_TRAMWAY_TRAM + sprite_offset, PAL_NONE, x, y);
}
} else {
/* Drive-in stop */
if (rti->UsesOverlay()) {
SpriteID ground = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_ROADSTOP);
DrawSprite(ground + view, PAL_NONE, x, y);
}
}
DrawCommonTileSeqInGUI(x, y, dts, 0, 0, palette, true);
}
/** 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));
}
uint8 GetRoadStopTileAnimationSpeed(TileIndex tile)
{
const RoadStopSpec *ss = GetRoadStopSpec(tile);
if (ss == nullptr) return 0;
return RoadStopAnimationBase::GetAnimationSpeed(ss);
}
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 (TileIndex cur_tile : st->custom_road_stop_tiles) {
process_tile(cur_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, VMDF_NOT_MAP_MODE);
}
}
};
if (trigger == RSRT_NEW_CARGO || trigger == RSRT_CARGO_TAKEN) {
for (TileIndex cur_tile : st->custom_road_stop_tiles) {
process_tile(cur_tile);
}
} else {
process_tile(tile);
}
/* Update whole station random bits */
st->waiting_triggers &= ~used_triggers;
if ((whole_reseed & 0xFFFF) != 0) {
st->random_bits &= ~whole_reseed;
st->random_bits |= Random() & whole_reseed;
}
}
/**
* Checks if there's any new stations by a specific RoadStopType
* @param rs the RoadStopType to check for.
* @return true if there was any new RoadStopSpec's found for the given RoadStopType, else false.
*/
bool GetIfNewStopsByType(RoadStopType rs)
{
if (!(RoadStopClass::GetClassCount() > 1 || RoadStopClass::Get(ROADSTOP_CLASS_DFLT)->GetSpecCount() > 1)) return false;
for (uint i = 0; i < RoadStopClass::GetClassCount(); i++) {
// We don't want to check the default or waypoint classes. These classes are always available.
if (i == ROADSTOP_CLASS_DFLT || i == ROADSTOP_CLASS_WAYP) continue;
RoadStopClass *roadstopclass = RoadStopClass::Get((RoadStopClassID)i);
if (GetIfClassHasNewStopsByType(roadstopclass, rs)) return true;
}
return false;
}
/**
* Checks if the given RoadStopClass has any specs assigned to it, compatible with the given RoadStopType.
* @param roadstopclass the RoadStopClass to check.
* @param rs the RoadStopType to check by.
* @return true if the roadstopclass has any specs compatible with the given RoadStopType.
*/
bool GetIfClassHasNewStopsByType(RoadStopClass *roadstopclass, RoadStopType rs)
{
for (uint j = 0; j < roadstopclass->GetSpecCount(); j++) {
if (GetIfStopIsForType(roadstopclass->GetSpec(j), rs)) return true;
}
return false;
}
/**
* Checks if the given RoadStopSpec is compatible with the given RoadStopType.
* @param roadstopspec the RoadStopSpec to check.
* @param rs the RoadStopType to check by.
* @return true if the roadstopspec is compatible with the given RoadStopType.
*/
bool GetIfStopIsForType(const RoadStopSpec *roadstopspec, RoadStopType rs)
{
// The roadstopspec is nullptr, must be the default station, always return true.
if (roadstopspec == nullptr) return true;
if (roadstopspec->stop_type == ROADSTOPTYPE_ALL) return true;
switch (rs) {
case ROADSTOP_BUS: if (roadstopspec->stop_type == ROADSTOPTYPE_PASSENGER) return true; break;
case ROADSTOP_TRUCK: if (roadstopspec->stop_type == ROADSTOPTYPE_FREIGHT) return true; break;
}
return false;
}
const RoadStopSpec *GetRoadStopSpec(TileIndex t)
{
if (!IsCustomRoadStopSpecIndex(t)) return nullptr;
const BaseStation *st = BaseStation::GetByTile(t);
uint specindex = GetCustomRoadStopSpecIndex(t);
return specindex < st->num_roadstop_specs ? st->roadstop_speclist[specindex].spec : nullptr;
}
int AllocateRoadStopSpecToStation(const RoadStopSpec *statspec, BaseStation *st, bool exec)
{
uint i;
if (statspec == nullptr || st == nullptr) return 0;
/* Try to find the same spec and return that one */
for (i = 1; i < st->num_roadstop_specs && i < NUM_ROADSTOPSPECS_PER_STATION; i++) {
if (st->roadstop_speclist[i].spec == statspec) return i;
}
/* Try to find an unused spec slot */
for (i = 1; i < st->num_roadstop_specs && i < NUM_ROADSTOPSPECS_PER_STATION; i++) {
if (st->roadstop_speclist[i].spec == nullptr && st->roadstop_speclist[i].grfid == 0) break;
}
if (i == NUM_ROADSTOPSPECS_PER_STATION) {
/* Full, give up */
return -1;
}
if (exec) {
if (i >= st->num_roadstop_specs) {
st->num_roadstop_specs = i + 1;
st->roadstop_speclist = ReallocT(st->roadstop_speclist, st->num_roadstop_specs);
if (st->num_roadstop_specs == 2) {
/* Initial allocation */
st->roadstop_speclist[0].spec = nullptr;
st->roadstop_speclist[0].grfid = 0;
st->roadstop_speclist[0].localidx = 0;
}
}
st->roadstop_speclist[i].spec = statspec;
st->roadstop_speclist[i].grfid = statspec->grf_prop.grffile->grfid;
st->roadstop_speclist[i].localidx = statspec->grf_prop.local_id;
StationUpdateRoadStopCachedTriggers(st);
}
return i;
}
void DeallocateRoadStopSpecFromStation(BaseStation *st, byte specindex)
{
/* specindex of 0 (default) is never freeable */
if (specindex == 0) return;
/* Check custom road stop tiles if the specindex is still in use */
for (TileIndex tile : st->custom_road_stop_tiles) {
if (GetCustomRoadStopSpecIndex(tile) == specindex) {
return;
}
}
/* This specindex is no longer in use, so deallocate it */
st->roadstop_speclist[specindex].spec = nullptr;
st->roadstop_speclist[specindex].grfid = 0;
st->roadstop_speclist[specindex].localidx = 0;
/* If this was the highest spec index, reallocate */
if (specindex == st->num_roadstop_specs - 1) {
for (; st->roadstop_speclist[st->num_roadstop_specs - 1].grfid == 0 && st->num_roadstop_specs > 1; st->num_roadstop_specs--) {}
if (st->num_roadstop_specs > 1) {
st->roadstop_speclist = ReallocT(st->roadstop_speclist, st->num_roadstop_specs);
} else {
free(st->roadstop_speclist);
st->num_roadstop_specs = 0;
st->roadstop_speclist = nullptr;
st->cached_roadstop_anim_triggers = 0;
st->cached_roadstop_cargo_triggers = 0;
return;
}
}
StationUpdateRoadStopCachedTriggers(st);
}
/**
* Update the cached animation trigger bitmask for a station.
* @param st Station to update.
*/
void StationUpdateRoadStopCachedTriggers(BaseStation *st)
{
st->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->num_roadstop_specs; 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;
}
}
}
void DumpRoadStopSpriteGroup(const BaseStation *st, const RoadStopSpec *spec, std::function<void(const char *)> print)
{
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 (spec->grf_prop.spritegroup[cs->Index()] != nullptr &&
station->goods[cs->Index()].cargo.TotalCount() > 0) {
ctype = cs->Index();
break;
}
}
}
if (spec->grf_prop.spritegroup[ctype] == nullptr) {
ctype = CT_DEFAULT;
}
DumpSpriteGroup(spec->grf_prop.spritegroup[ctype], std::move(print));
}

@ -0,0 +1,175 @@
/*
* 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.
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, ///< 0b01, Draw the road itself
ROADSTOP_DRAW_MODE_OVERLAY = 1 << 1, ///< 0b10, Draw the road overlay for roadstops, e.g. pavement
};
DECLARE_ENUM_AS_BIT_SET(RoadStopDrawMode)
enum RoadStopSpecFlags {
RSF_CB141_RANDOM_BITS, ///< Callback 141 needs random bits.
};
enum RoadStopSpecIntlFlags {
RSIF_BRIDGE_HEIGHTS_SET, ///< byte bridge_height[6] is set.
RSIF_BRIDGE_DISALLOWED_PILLARS_SET, ///< byte bridge_disallowed_pillars[6] is set.
};
/** 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(uint16 variable, uint32 parameter, GetVariableExtra *extra) const override;
};
/** Road stop resolver. */
struct RoadStopResolverObject : public ResolverObject {
RoadStopScopeResolver roadstop_scope; ///< The stop scope resolver.
TownScopeResolver *town_scope; ///< The town scope resolver (created on the first call).
RoadStopResolverObject(const RoadStopSpec* roadstopspec, BaseStation* st, TileIndex tile, 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;
uint8 internal_flags = 0; ///< Bitmask of internal spec flags (RoadStopSpecIntlFlags)
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
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);
bool GetIfClassHasNewStopsByType(RoadStopClass *roadstopclass, RoadStopType rs);
bool GetIfStopIsForType(const RoadStopSpec *roadstopspec, RoadStopType rs);
uint GetCountOfCompatibleStopsByType(RoadStopClass *roadstopclass, RoadStopType rs);
const RoadStopSpec *GetRoadStopSpec(TileIndex t);
int AllocateRoadStopSpecToStation(const RoadStopSpec *statspec, BaseStation *st, bool exec);
void DeallocateRoadStopSpecFromStation(BaseStation *st, byte specindex);
void StationUpdateRoadStopCachedTriggers(BaseStation *st);
#endif /* NEWGRF_ROADSTATION_H */

@ -906,7 +906,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;

@ -33,6 +33,14 @@
#include "date_func.h"
#include "station_map.h"
#include "waypoint_func.h"
#include "newgrf_roadstop.h"
#include "debug.h"
#include "newgrf_station.h"
#include "querystring_gui.h"
#include "sortlist_type.h"
#include "stringfilter_type.h"
#include "string_func.h"
#include "widgets/road_widget.h"
#include "table/strings.h"
@ -42,10 +50,25 @@
static void ShowRVStationPicker(Window *parent, RoadStopType rs);
static void ShowRoadDepotPicker(Window *parent);
static void ShowBuildWaypointPicker(Window *parent);
static bool _remove_button_clicked;
static bool _one_way_button_clicked;
static DiagDirection _build_depot_direction;
struct RoadStopGUISettings {
DiagDirection orientation; // This replaces _road_station_picker_orientation
RoadStopClassID roadstop_class;
byte roadstop_type;
byte roadstop_count;
};
static RoadStopGUISettings _roadstop_gui_settings;
static uint _waypoint_count = 1; ///< Number of waypoint types
static uint _cur_waypoint_type; ///< Currently selected waypoint type
/**
* Define the values of the RoadFlags
* @see CmdBuildLongRoad
@ -65,8 +88,20 @@ static RoadFlags _place_road_flag;
static RoadType _cur_roadtype;
static DiagDirection _road_depot_orientation;
static DiagDirection _road_station_picker_orientation;
/**
* 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 || !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(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2, uint64 p3, uint32 cmd)
{
@ -184,17 +219,18 @@ void CcRoadStop(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2,
*/
static void PlaceRoadStop(TileIndex start_tile, TileIndex end_tile, uint32 p2, uint32 cmd)
{
uint8 ddir = _road_station_picker_orientation;
SB(p2, 16, 16, INVALID_STATION); // no station to join
uint8 ddir = _roadstop_gui_settings.orientation;
if (ddir >= DIAGDIR_END) {
SetBit(p2, 1); // It's a drive-through stop.
ddir -= DIAGDIR_END; // Adjust picker result to actual direction.
}
p2 |= ddir << 3; // Set the DiagDirecion into p2 bits 3 and 4.
p2 |= INVALID_STATION << 16; // no station to join
TileArea ta(start_tile, end_tile);
CommandContainer cmdcont = NewCommandContainerBasic(ta.tile, (uint32)(ta.w | ta.h << 8), p2, cmd, CcRoadStop);
cmdcont.p3 = (_roadstop_gui_settings.roadstop_type << 8) | _roadstop_gui_settings.roadstop_class;
ShowSelectStationIfNeeded(cmdcont, ta);
}
@ -217,7 +253,7 @@ static void PlaceRoad_Waypoint(TileIndex tile)
} else {
/* Tile where we can't build rail waypoints. This is always going to fail,
* but provides the user with a proper error message. */
DoCommandP(tile, 1 | 1 << 8, INVALID_STATION << 16, CMD_BUILD_ROAD_WAYPOINT | CMD_MSG(STR_ERROR_CAN_T_BUILD_ROAD_WAYPOINT));
DoCommandP(tile, 1 | 1 << 8, ROADSTOP_CLASS_WAYP | INVALID_STATION << 16, CMD_BUILD_ROAD_WAYPOINT | CMD_MSG(STR_ERROR_CAN_T_BUILD_ROAD_WAYPOINT));
}
}
@ -230,8 +266,8 @@ static void PlaceRoad_BusStation(TileIndex tile)
if (_remove_button_clicked) {
VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_REMOVE_BUSSTOP);
} else {
if (_road_station_picker_orientation < DIAGDIR_END) { // Not a drive-through stop.
VpStartPlaceSizing(tile, (DiagDirToAxis(_road_station_picker_orientation) == AXIS_X) ? VPM_X_LIMITED : VPM_Y_LIMITED, DDSP_BUILD_BUSSTOP);
if (_roadstop_gui_settings.orientation < DIAGDIR_END) { // Not a drive-through stop.
VpStartPlaceSizing(tile, (DiagDirToAxis(_roadstop_gui_settings.orientation) == AXIS_X) ? VPM_X_LIMITED : VPM_Y_LIMITED, DDSP_BUILD_BUSSTOP);
} else {
VpStartPlaceSizing(tile, VPM_X_AND_Y_LIMITED, DDSP_BUILD_BUSSTOP);
}
@ -248,8 +284,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);
}
@ -496,6 +532,8 @@ struct BuildRoadToolbarWindow : Window {
case WID_ROT_BUILD_WAYPOINT:
if (HandlePlacePushButton(this, WID_ROT_BUILD_WAYPOINT, SPR_CURSOR_WAYPOINT, HT_RECT)) {
this->last_started_action = widget;
_waypoint_count = RoadStopClass::Get(ROADSTOP_CLASS_WAYP)->GetSpecCount();
if (_waypoint_count > 1) ShowBuildWaypointPicker(this);
}
break;
@ -584,7 +622,7 @@ struct BuildRoadToolbarWindow : Window {
break;
case WID_ROT_DEPOT:
DoCommandP(tile, _cur_roadtype << 2 | _road_depot_orientation, 0,
DoCommandP(tile, _cur_roadtype << 2 | _build_depot_direction, 0,
CMD_BUILD_ROAD_DEPOT | CMD_MSG(this->rti->strings.err_depot), CcRoadDepot);
break;
@ -721,9 +759,10 @@ struct BuildRoadToolbarWindow : Window {
DoCommandP(ta.tile, ta.w | ta.h << 8, (1 << 2), CMD_REMOVE_ROAD_STOP | CMD_MSG(STR_ERROR_CAN_T_REMOVE_ROAD_WAYPOINT), CcPlaySound_CONSTRUCTION_OTHER);
} else {
uint32 p1 = ta.w | ta.h << 8 | _ctrl_pressed << 16 | (select_method == VPM_X_LIMITED ? AXIS_X : AXIS_Y) << 17;
uint32 p2 = INVALID_STATION << 16;
uint32 p2 = ROADSTOP_CLASS_WAYP | INVALID_STATION << 16;
CommandContainer cmdcont = NewCommandContainerBasic(ta.tile, p1, p2, CMD_BUILD_ROAD_WAYPOINT | CMD_MSG(STR_ERROR_CAN_T_BUILD_ROAD_WAYPOINT), CcPlaySound_CONSTRUCTION_OTHER);
cmdcont.p3 = _cur_waypoint_type;
ShowSelectWaypointIfNeeded(cmdcont, ta);
}
}
@ -731,24 +770,24 @@ struct BuildRoadToolbarWindow : Window {
case DDSP_BUILD_BUSSTOP:
case DDSP_REMOVE_BUSSTOP:
if (this->IsWidgetLowered(WID_ROT_BUS_STATION)) {
if (this->IsWidgetLowered(WID_ROT_BUS_STATION) && GetIfClassHasNewStopsByType(RoadStopClass::Get(_roadstop_gui_settings.roadstop_class), ROADSTOP_BUS)) {
if (_remove_button_clicked) {
TileArea ta(start_tile, end_tile);
DoCommandP(ta.tile, ta.w | ta.h << 8, (_ctrl_pressed << 1) | ROADSTOP_BUS, CMD_REMOVE_ROAD_STOP | CMD_MSG(this->rti->strings.err_remove_station[ROADSTOP_BUS]), CcPlaySound_CONSTRUCTION_OTHER);
} else {
PlaceRoadStop(start_tile, end_tile, _cur_roadtype << 5 | (_ctrl_pressed << 2) | ROADSTOP_BUS, CMD_BUILD_ROAD_STOP | CMD_MSG(this->rti->strings.err_build_station[ROADSTOP_BUS]));
PlaceRoadStop(start_tile, end_tile, (_cur_roadtype << 5) | (_ctrl_pressed << 2) | ROADSTOP_BUS, CMD_BUILD_ROAD_STOP | CMD_MSG(this->rti->strings.err_build_station[ROADSTOP_BUS]));
}
}
break;
case DDSP_BUILD_TRUCKSTOP:
case DDSP_REMOVE_TRUCKSTOP:
if (this->IsWidgetLowered(WID_ROT_TRUCK_STATION)) {
if (this->IsWidgetLowered(WID_ROT_TRUCK_STATION) && GetIfClassHasNewStopsByType(RoadStopClass::Get(_roadstop_gui_settings.roadstop_class), ROADSTOP_TRUCK)) {
if (_remove_button_clicked) {
TileArea ta(start_tile, end_tile);
DoCommandP(ta.tile, ta.w | ta.h << 8, (_ctrl_pressed << 1) | ROADSTOP_TRUCK, CMD_REMOVE_ROAD_STOP | CMD_MSG(this->rti->strings.err_remove_station[ROADSTOP_TRUCK]), CcPlaySound_CONSTRUCTION_OTHER);
} else {
PlaceRoadStop(start_tile, end_tile, _cur_roadtype << 5 | (_ctrl_pressed << 2) | ROADSTOP_TRUCK, CMD_BUILD_ROAD_STOP | CMD_MSG(this->rti->strings.err_build_station[ROADSTOP_TRUCK]));
PlaceRoadStop(start_tile, end_tile, (_cur_roadtype << 5) | (_ctrl_pressed << 2) | ROADSTOP_TRUCK, CMD_BUILD_ROAD_STOP | CMD_MSG(this->rti->strings.err_build_station[ROADSTOP_TRUCK]));
}
}
break;
@ -1046,7 +1085,7 @@ struct BuildRoadDepotWindow : public PickerWindowBase {
{
this->CreateNestedTree();
this->LowerWidget(_road_depot_orientation + WID_BROD_DEPOT_NE);
this->LowerWidget(_build_depot_direction + WID_BROD_DEPOT_NE);
if (RoadTypeIsTram(_cur_roadtype)) {
this->GetWidget<NWidgetCore>(WID_BROD_CAPTION)->widget_data = STR_BUILD_DEPOT_TRAM_ORIENTATION_CAPTION;
for (int i = WID_BROD_DEPOT_NE; i <= WID_BROD_DEPOT_NW; i++) this->GetWidget<NWidgetCore>(i)->tool_tip = STR_BUILD_DEPOT_TRAM_ORIENTATION_SELECT_TOOLTIP;
@ -1077,9 +1116,9 @@ struct BuildRoadDepotWindow : public PickerWindowBase {
case WID_BROD_DEPOT_NE:
case WID_BROD_DEPOT_SW:
case WID_BROD_DEPOT_SE:
this->RaiseWidget(_road_depot_orientation + WID_BROD_DEPOT_NE);
_road_depot_orientation = (DiagDirection)(widget - WID_BROD_DEPOT_NE);
this->LowerWidget(_road_depot_orientation + WID_BROD_DEPOT_NE);
this->RaiseWidget(_build_depot_direction + WID_BROD_DEPOT_NE);
_build_depot_direction = (DiagDirection)(widget - WID_BROD_DEPOT_NE);
this->LowerWidget(_build_depot_direction + WID_BROD_DEPOT_NE);
if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
this->SetDirty();
break;
@ -1132,14 +1171,84 @@ static void ShowRoadDepotPicker(Window *parent)
new BuildRoadDepotWindow(&_build_road_depot_desc, parent);
}
/** Enum referring to the Hotkeys in the build road stop window */
enum BuildRoadStopHotkeys {
BROSHK_FOCUS_FILTER_BOX, ///< Focus the edit box for editing the filter string
};
struct BuildRoadStationWindow : public PickerWindowBase {
BuildRoadStationWindow(WindowDesc *desc, Window *parent, RoadStopType rs) : PickerWindowBase(desc, parent)
private:
RoadStopType roadStopType; ///< The RoadStopType for this Window.
uint line_height; ///< Height of a single line in the newstation selection matrix.
uint coverage_height; ///< Height of the coverage texts.
Scrollbar *vscrollList; ///< Vertical scrollbar of the new station list.
Scrollbar *vscrollMatrix; ///< Vertical scrollbar of the station picker matrix.
typedef GUIList<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);
}
public:
BuildRoadStationWindow(WindowDesc *desc, Window *parent, RoadStopType rs) : PickerWindowBase(desc, parent), filter_editbox(EDITBOX_MAX_SIZE * MAX_CHAR_LENGTH, EDITBOX_MAX_SIZE)
{
this->coverage_height = 2 * FONT_HEIGHT_NORMAL + 3 * WD_PAR_VSEP_NORMAL;
this->vscrollList = nullptr;
this->vscrollMatrix = nullptr;
this->roadStopType = rs;
bool newstops = GetIfNewStopsByType(rs);
this->CreateNestedTree();
/* Trams don't have non-drivethrough stations */
if (RoadTypeIsTram(_cur_roadtype) && _road_station_picker_orientation < DIAGDIR_END) {
_road_station_picker_orientation = DIAGDIR_END;
NWidgetStacked *newst_additions = this->GetWidget<NWidgetStacked>(WID_BROS_SHOW_NEWST_ADDITIONS);
newst_additions->SetDisplayedPlane(newstops ? 0 : SZSP_NONE);
newst_additions = this->GetWidget<NWidgetStacked>(WID_BROS_SHOW_NEWST_MATRIX);
newst_additions->SetDisplayedPlane(newstops ? 0 : SZSP_NONE);
newst_additions = this->GetWidget<NWidgetStacked>(WID_BROS_SHOW_NEWST_DEFSIZE);
newst_additions->SetDisplayedPlane(newstops ? 0 : SZSP_NONE);
newst_additions = this->GetWidget<NWidgetStacked>(WID_BROS_SHOW_NEWST_RESIZE);
newst_additions->SetDisplayedPlane(newstops ? 0 : SZSP_NONE);
newst_additions = this->GetWidget<NWidgetStacked>(WID_BROS_SHOW_NEWST_ORIENTATION);
newst_additions->SetDisplayedPlane(newstops ? 0 : SZSP_NONE);
newst_additions = this->GetWidget<NWidgetStacked>(WID_BROS_SHOW_NEWST_TYPE_SEL);
newst_additions->SetDisplayedPlane(newstops ? 0 : SZSP_NONE);
/* Hide the station class filter if no stations other than the default one are available. */
this->GetWidget<NWidgetStacked>(WID_BROS_FILTER_CONTAINER)->SetDisplayedPlane(newstops ? 0 : SZSP_NONE);
if (newstops) {
this->vscrollList = this->GetScrollbar(WID_BROS_NEWST_SCROLL);
this->vscrollMatrix = this->GetScrollbar(WID_BROS_MATRIX_SCROLL);
this->querystrings[WID_BROS_FILTER_EDITBOX] = &this->filter_editbox;
this->roadstop_classes.SetListing(this->last_sorting);
this->roadstop_classes.SetFiltering(this->last_filtering);
this->roadstop_classes.SetSortFuncs(this->sorter_funcs);
this->roadstop_classes.SetFilterFuncs(this->filter_funcs);
}
this->roadstop_classes.ForceRebuild();
BuildRoadStopClassesAvailable();
// Trams don't have non-drivethrough stations
if (RoadTypeIsTram(_cur_roadtype) && _roadstop_gui_settings.orientation < DIAGDIR_END) {
_roadstop_gui_settings.orientation = DIAGDIR_END;
}
const RoadTypeInfo *rti = GetRoadTypeInfo(_cur_roadtype);
this->GetWidget<NWidgetCore>(WID_BROS_CAPTION)->widget_data = rti->strings.picker_title[rs];
@ -1148,12 +1257,39 @@ 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)) {
_roadstop_gui_settings.roadstop_class = ROADSTOP_CLASS_DFLT;
_roadstop_gui_settings.roadstop_type = 0;
}
_roadstop_gui_settings.roadstop_count = RoadStopClass::Get(_roadstop_gui_settings.roadstop_class)->GetSpecCount();
_roadstop_gui_settings.roadstop_type = std::min((int)_roadstop_gui_settings.roadstop_type, _roadstop_gui_settings.roadstop_count - 1);
/* Reset back to default class if the previously selected class is not available for this road stop type. */
if (!GetIfClassHasNewStopsByType(RoadStopClass::Get(_roadstop_gui_settings.roadstop_class), roadStopType)) {
_roadstop_gui_settings.roadstop_class = ROADSTOP_CLASS_DFLT;
}
NWidgetMatrix *matrix = this->GetWidget<NWidgetMatrix>(WID_BROS_MATRIX);
matrix->SetScrollbar(this->vscrollMatrix);
matrix->SetCount(_roadstop_gui_settings.roadstop_count);
matrix->SetClicked(_roadstop_gui_settings.roadstop_type);
this->EnsureSelectedClassIsVisible();
}
}
virtual ~BuildRoadStationWindow()
@ -1161,10 +1297,86 @@ struct BuildRoadStationWindow : public PickerWindowBase {
DeleteWindowById(WC_SELECT_STATION, 0);
}
void OnPaint() override
/** Sort classes by RoadStopClassID. */
static bool RoadStopClassIDSorter(RoadStopClassID const &a, RoadStopClassID const &b)
{
this->DrawWidgets();
return a < b;
}
/** Filter classes by class name. */
static bool CDECL TagNameFilter(RoadStopClassID const *sc, StringFilter &filter)
{
char buffer[DRAW_STRING_BUFFER];
GetString(buffer, RoadStopClass::Get(*sc)->name, lastof(buffer));
filter.ResetState();
filter.AddLine(buffer);
return filter.GetState();
}
inline bool ShowNewStops() const
{
return this->vscrollList != nullptr;
}
void BuildRoadStopClassesAvailable()
{
if (!this->roadstop_classes.NeedRebuild()) return;
this->roadstop_classes.clear();
for (uint i = 0; i < RoadStopClass::GetClassCount(); i++) {
RoadStopClassID rs_id = (RoadStopClassID)i;
if (rs_id == ROADSTOP_CLASS_WAYP) {
// Skip waypoints.
continue;
}
RoadStopClass *rs_class = RoadStopClass::Get(rs_id);
if (GetIfClassHasNewStopsByType(rs_class, this->roadStopType)) this->roadstop_classes.push_back(rs_id);
}
if (this->ShowNewStops()) {
this->roadstop_classes.Filter(this->string_filter);
this->roadstop_classes.shrink_to_fit();
this->roadstop_classes.RebuildDone();
this->roadstop_classes.Sort();
this->vscrollList->SetCount((uint)this->roadstop_classes.size());
}
}
void OnInvalidateData(int data = 0, bool gui_scope = true) override
{
if (!gui_scope) return;
this->BuildRoadStopClassesAvailable();
}
EventState OnHotkey(int hotkey) override
{
switch (hotkey) {
case BROSHK_FOCUS_FILTER_BOX:
this->SetFocusedWidget(WID_BROS_FILTER_EDITBOX);
SetFocusedWindow(this); // The user has asked to give focus to the text box, so make sure this window is focused.
break;
default:
return ES_NOT_HANDLED;
}
return ES_HANDLED;
}
void OnEditboxChanged(int wid) override
{
string_filter.SetFilterTerm(this->filter_editbox.text.buf);
this->roadstop_classes.SetFilterState(!string_filter.IsEmpty());
this->roadstop_classes.ForceRebuild();
this->InvalidateData();
}
void OnPaint() override
{
int rad = _settings_game.station.modified_catchment ? ((this->window_class == WC_BUS_STATION) ? CA_BUS : CA_TRUCK) : CA_UNMODIFIED;
rad += _settings_game.station.catchment_increase;
if (_settings_client.gui.station_show_coverage) {
@ -1173,50 +1385,176 @@ struct BuildRoadStationWindow : public PickerWindowBase {
SetTileSelectSize(1, 1);
}
this->DrawWidgets();
if (this->IsShaded()) return;
/* 'Accepts' and 'Supplies' texts. */
StationCoverageType sct = (this->window_class == WC_BUS_STATION) ? SCT_PASSENGERS_ONLY : SCT_NON_PASSENGERS_ONLY;
NWidgetBase *cov = this->GetWidget<NWidgetBase>(WID_BROS_INFO);
int top = cov->pos_y + WD_PAR_VSEP_NORMAL;
int left = cov->pos_x + WD_FRAMERECT_LEFT;
int right = cov->pos_x + cov->current_x - WD_FRAMERECT_RIGHT;
int bottom = cov->pos_y + cov->current_y;
top = DrawStationCoverageAreaText(left, right, top, sct, rad, false) + WD_PAR_VSEP_NORMAL;
top = DrawStationCoverageAreaText(left, right, top, sct, rad, true) + WD_PAR_VSEP_NORMAL;
/*
int top = this->GetWidget<NWidgetBase>(WID_BROS_LT_ON)->pos_y + this->GetWidget<NWidgetBase>(WID_BROS_LT_ON)->current_y + WD_PAR_VSEP_NORMAL;
NWidgetBase *back_nwi = this->GetWidget<NWidgetBase>(WID_BROS_BACKGROUND);
int right = back_nwi->pos_x + back_nwi->current_x;
int bottom = back_nwi->pos_y + back_nwi->current_y;
int right = back_nwi->pos_x + back_nwi->current_x;
int bottom = back_nwi->pos_y + back_nwi->current_y;
top = DrawStationCoverageAreaText(back_nwi->pos_x + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, sct, rad, false) + WD_PAR_VSEP_NORMAL;
top = DrawStationCoverageAreaText(back_nwi->pos_x + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, sct, rad, true) + WD_PAR_VSEP_NORMAL;
*/
/* Resize background if the window is too small.
* Never make the window smaller to avoid oscillating if the size change affects the acceptance.
* (This is the case, if making the window bigger moves the mouse into the window.) */
if (top > bottom) {
ResizeWindow(this, 0, top - bottom, false);
this->coverage_height += top - bottom;
this->ReInit();
}
}
void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
{
if (!IsInsideMM(widget, WID_BROS_STATION_NE, WID_BROS_STATION_Y + 1)) return;
switch (widget) {
case WID_BROS_NEWST_LIST: {
Dimension d = { 0, 0 };
for (auto rs_class : this->roadstop_classes) {
d = maxdim(d, GetStringBoundingBox(RoadStopClass::Get(rs_class)->name));
}
size->width = std::max(size->width, d.width + padding.width);
this->line_height = FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM;
size->height = 5 * this->line_height;
resize->height = this->line_height;
break;
}
size->width = ScaleGUITrad(64) + 2;
size->height = ScaleGUITrad(48) + 2;
case WID_BROS_IMAGE:
size->width = ScaleGUITrad(64) + 2;
size->height = ScaleGUITrad(58) + 2;
break;
case WID_BROS_STATION_NE:
case WID_BROS_STATION_SE:
case WID_BROS_STATION_SW:
case WID_BROS_STATION_NW:
case WID_BROS_STATION_X:
case WID_BROS_STATION_Y:
case WID_BROS_MATRIX:
fill->height = 1;
resize->height = 1;
break;
case WID_BROS_INFO:
size->height = this->coverage_height;
break;
}
}
/**
* Simply to have a easier way to get the StationType for bus, truck and trams from the WindowClass.
*/
StationType GetRoadStationTypeByWindowClass(WindowClass window_class) const {
switch (window_class) {
case WC_BUS_STATION: return STATION_BUS;
case WC_TRUCK_STATION: return STATION_TRUCK;
default: NOT_REACHED();
}
}
void DrawWidget(const Rect &r, int widget) const override
{
if (!IsInsideMM(widget, WID_BROS_STATION_NE, WID_BROS_STATION_Y + 1)) return;
DrawPixelInfo tmp_dpi;
switch (GB(widget, 0, 16)) {
case WID_BROS_STATION_NE:
case WID_BROS_STATION_SE:
case WID_BROS_STATION_SW:
case WID_BROS_STATION_NW:
case WID_BROS_STATION_X:
case WID_BROS_STATION_Y: {
StationType st = GetRoadStationTypeByWindowClass(this->window_class);
const RoadStopSpec *spec = RoadStopClass::Get(_roadstop_gui_settings.roadstop_class)->GetSpec(_roadstop_gui_settings.roadstop_type);
if (spec == nullptr) {
StationPickerDrawSprite(r.left + WD_MATRIX_LEFT + ScaleGUITrad(31), r.bottom - ScaleGUITrad(31), st, INVALID_RAILTYPE, _cur_roadtype, widget - WID_BROS_STATION_NE);
} else {
DrawRoadStopTile(r.left + WD_MATRIX_LEFT + ScaleGUITrad(31), r.bottom - ScaleGUITrad(31), _cur_roadtype, spec, st, (int)widget - WID_BROS_STATION_NE);
}
break;
}
case WID_BROS_NEWST_LIST: {
uint statclass = 0;
uint row = 0;
for (auto rs_class : this->roadstop_classes) {
if (this->vscrollList->IsVisible(statclass)) {
DrawString(r.left + WD_MATRIX_LEFT, r.right, row * this->line_height + r.top + WD_MATRIX_TOP,
RoadStopClass::Get(rs_class)->name,
rs_class == _roadstop_gui_settings.roadstop_class ? TC_WHITE : TC_BLACK);
row++;
}
statclass++;
}
break;
}
case WID_BROS_IMAGE: {
byte type = GB(widget, 16, 16);
assert(type < _roadstop_gui_settings.roadstop_count);
StationType st = (this->window_class == WC_BUS_STATION) ? STATION_BUS : STATION_TRUCK;
StationPickerDrawSprite(r.left + 1 + ScaleGUITrad(31), r.bottom - ScaleGUITrad(31), st, INVALID_RAILTYPE, _cur_roadtype, widget - WID_BROS_STATION_NE);
const RoadStopSpec *spec = RoadStopClass::Get(_roadstop_gui_settings.roadstop_class)->GetSpec(type);
StationType st = GetRoadStationTypeByWindowClass(this->window_class);
if (!IsRoadStopAvailable(spec, st)) {
GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, PC_BLACK, FILLRECT_CHECKER);
}
// Set up a clipping area for the sprite preview.
if (FillDrawPixelInfo(&tmp_dpi, r.left, r.top, r.right - r.left + 1, r.bottom - r.top + 1)) {
DrawPixelInfo *old_dpi = _cur_dpi;
_cur_dpi = &tmp_dpi;
int x = ScaleGUITrad(31) + 1;
int y = r.bottom - r.top - ScaleGUITrad(31);
// Instead of "5" (5th view), pass the orientation clicked in the selection.
if (spec == nullptr) {
StationPickerDrawSprite(r.left + 1 + ScaleGUITrad(31), r.bottom - ScaleGUITrad(31), st, INVALID_RAILTYPE, _cur_roadtype, _roadstop_gui_settings.orientation);
} else {
DrawRoadStopTile(x, y, _cur_roadtype, spec, st, (uint8)_roadstop_gui_settings.orientation);
}
_cur_dpi = old_dpi;
}
break;
}
}
}
void OnResize() override {
if (this->vscrollList != nullptr) {
this->vscrollList->SetCapacityFromWidget(this, WID_BROS_NEWST_LIST);
}
}
void SetStringParameters(int widget) const override {
if (widget == WID_BROS_SHOW_NEWST_TYPE) {
const RoadStopSpec *roadstopspec = RoadStopClass::Get(_roadstop_gui_settings.roadstop_class)->GetSpec(_roadstop_gui_settings.roadstop_type);
SetDParam(0, (roadstopspec != nullptr && roadstopspec->name != 0) ? roadstopspec->name : STR_STATION_CLASS_DFLT);
}
}
void OnClick(Point pt, int widget, int click_count) override
{
switch (widget) {
switch (GB(widget, 0, 16)) {
case WID_BROS_STATION_NE:
case WID_BROS_STATION_SE:
case WID_BROS_STATION_SW:
case WID_BROS_STATION_NW:
case WID_BROS_STATION_X:
case WID_BROS_STATION_Y:
this->RaiseWidget(_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);
this->RaiseWidget(_roadstop_gui_settings.orientation + WID_BROS_STATION_NE);
_roadstop_gui_settings.orientation = (DiagDirection)(widget - WID_BROS_STATION_NE);
this->LowerWidget(_roadstop_gui_settings.orientation + WID_BROS_STATION_NE);
if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
this->SetDirty();
DeleteWindowById(WC_SELECT_STATION, 0);
@ -1232,6 +1570,46 @@ struct BuildRoadStationWindow : public PickerWindowBase {
SetViewportCatchmentStation(nullptr, true);
break;
case WID_BROS_NEWST_LIST: {
int y = this->vscrollList->GetScrolledRowFromWidget(pt.y, this, WID_BROS_NEWST_LIST);
if (y >= (int)this->roadstop_classes.size()) return;
RoadStopClassID class_id = this->roadstop_classes[y];
if (_roadstop_gui_settings.roadstop_class != class_id && GetIfClassHasNewStopsByType(RoadStopClass::Get(class_id), roadStopType)) {
_roadstop_gui_settings.roadstop_class = class_id;
RoadStopClass *rsclass = RoadStopClass::Get(_roadstop_gui_settings.roadstop_class);
_roadstop_gui_settings.roadstop_count = rsclass->GetSpecCount();
_roadstop_gui_settings.roadstop_type = std::min((int)_roadstop_gui_settings.roadstop_type, std::max(0, (int)_roadstop_gui_settings.roadstop_count - 1));
NWidgetMatrix *matrix = this->GetWidget<NWidgetMatrix>(WID_BROS_MATRIX);
matrix->SetCount(_roadstop_gui_settings.roadstop_count);
matrix->SetClicked(_roadstop_gui_settings.roadstop_type);
}
if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
this->SetDirty();
DeleteWindowById(WC_SELECT_STATION, 0);
break;
}
case WID_BROS_IMAGE: {
int y = GB(widget, 16, 16);
if (y >= _roadstop_gui_settings.roadstop_count) return;
const RoadStopSpec *spec = RoadStopClass::Get(_roadstop_gui_settings.roadstop_class)->GetSpec(y);
StationType st = GetRoadStationTypeByWindowClass(this->window_class);
if (!IsRoadStopAvailable(spec, st)) return;
/* Check station availability callback */
_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();
DeleteWindowById(WC_SELECT_STATION, 0);
break;
}
default:
break;
}
@ -1241,6 +1619,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 */
@ -1248,38 +1645,82 @@ static const NWidgetPart _nested_road_station_picker_widgets[] = {
NWidget(NWID_HORIZONTAL),
NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_BROS_CAPTION),
NWidget(WWT_SHADEBOX, COLOUR_DARK_GREEN),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_DEFSIZE),
NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN),
EndContainer(),
EndContainer(),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BROS_BACKGROUND),
NWidget(NWID_SPACER), SetMinimalSize(0, 3),
NWidget(NWID_HORIZONTAL), SetPIP(0, 2, 0),
NWidget(NWID_SPACER), SetFill(1, 0),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_NW), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_NE), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_X), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(),
NWidget(NWID_SPACER), SetFill(1, 0),
EndContainer(),
NWidget(NWID_SPACER), SetMinimalSize(0, 2),
NWidget(NWID_HORIZONTAL), SetPIP(0, 2, 0),
NWidget(NWID_SPACER), SetFill(1, 0),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_SW), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_SE), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_Y), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(),
NWidget(NWID_SPACER), SetFill(1, 0),
EndContainer(),
NWidget(NWID_SPACER), SetMinimalSize(0, 1),
NWidget(NWID_HORIZONTAL), SetPIP(2, 0, 2),
NWidget(WWT_LABEL, COLOUR_DARK_GREEN, WID_BROS_INFO), SetMinimalSize(140, 14), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL),
NWidget(NWID_SPACER), SetFill(1, 0),
NWidget(NWID_HORIZONTAL),
NWidget(NWID_VERTICAL),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_FILTER_CONTAINER),
NWidget(NWID_HORIZONTAL), SetPadding(0, 5, 2, 0),
NWidget(WWT_TEXT, COLOUR_DARK_GREEN), SetFill(0, 1), SetDataTip(STR_LIST_FILTER_TITLE, STR_NULL),
NWidget(WWT_EDITBOX, COLOUR_GREY, WID_BROS_FILTER_EDITBOX), SetFill(1, 0), SetResize(1, 0),
SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
EndContainer(),
EndContainer(),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_ADDITIONS),
NWidget(NWID_HORIZONTAL), SetPIP(7, 0, 7), SetPadding(2, 0, 1, 0),
NWidget(WWT_MATRIX, COLOUR_GREY, WID_BROS_NEWST_LIST), SetMinimalSize(122, 71), SetFill(1, 0),
SetMatrixDataTip(1, 0, STR_STATION_BUILD_STATION_CLASS_TOOLTIP), SetScrollbar(WID_BROS_NEWST_SCROLL),
NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_BROS_NEWST_SCROLL),
EndContainer(),
EndContainer(),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_ORIENTATION),
NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetMinimalSize(144, 11), SetDataTip(STR_STATION_BUILD_ORIENTATION, STR_NULL), SetPadding(4, 2, 1, 2),
EndContainer(),
NWidget(NWID_HORIZONTAL), SetPIP(5, 2, 5), SetPadding(0, 0, 1, 0),
NWidget(NWID_SPACER), SetFill(1, 0),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_NW), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_NE), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_X), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(),
NWidget(NWID_SPACER), SetFill(1, 0),
EndContainer(),
NWidget(NWID_SPACER), SetMinimalSize(0, 2),
NWidget(NWID_HORIZONTAL), SetPIP(5, 2, 5), SetPadding(0, 0, 1, 0), // For PIP, 5 because the 2 is applied before and after aswell. We want to use 7 to be the same as the class list
NWidget(NWID_SPACER), SetFill(1, 0),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_SW), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_SE), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_Y), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(),
NWidget(NWID_SPACER), SetFill(1, 0),
EndContainer(),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_TYPE_SEL),
NWidget(WWT_LABEL, COLOUR_DARK_GREEN, WID_BROS_SHOW_NEWST_TYPE), SetMinimalSize(144, 8), SetDataTip(STR_ORANGE_STRING, STR_NULL), SetPadding(4, 2, 4, 2),
EndContainer(),
NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetMinimalSize(140, 14), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL), SetPadding(3, 2, 0, 2),
NWidget(NWID_HORIZONTAL), SetPIP(2, 0, 2),
NWidget(NWID_SPACER), SetFill(1, 0),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROS_LT_OFF), SetMinimalSize(60, 12),
SetDataTip(STR_STATION_BUILD_COVERAGE_OFF, STR_STATION_BUILD_COVERAGE_AREA_OFF_TOOLTIP),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROS_LT_ON), SetMinimalSize(60, 12),
SetDataTip(STR_STATION_BUILD_COVERAGE_ON, STR_STATION_BUILD_COVERAGE_AREA_ON_TOOLTIP),
NWidget(NWID_SPACER), SetFill(1, 0),
EndContainer(),
EndContainer(),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_MATRIX),
/* We need an additional background for the matrix, as the matrix cannot handle the scrollbar due to not being an NWidgetCore. */
NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetScrollbar(WID_BROS_MATRIX_SCROLL),
NWidget(NWID_HORIZONTAL),
NWidget(NWID_MATRIX, COLOUR_DARK_GREEN, WID_BROS_MATRIX), SetScrollbar(WID_BROS_MATRIX_SCROLL), SetPIP(0, 2, 0), SetPadding(2, 0, 0, 0),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BROS_IMAGE), SetMinimalSize(66, 60),
SetFill(0, 0), SetResize(0, 0), SetDataTip(0x0, STR_STATION_BUILD_STATION_TYPE_TOOLTIP), SetScrollbar(WID_BROS_MATRIX_SCROLL),
EndContainer(),
EndContainer(),
NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_BROS_MATRIX_SCROLL),
EndContainer(),
EndContainer(),
EndContainer(),
EndContainer(),
NWidget(NWID_HORIZONTAL), SetPIP(2, 0, 2),
NWidget(NWID_SPACER), SetFill(1, 0),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROS_LT_OFF), SetMinimalSize(60, 12),
SetDataTip(STR_STATION_BUILD_COVERAGE_OFF, STR_STATION_BUILD_COVERAGE_AREA_OFF_TOOLTIP),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROS_LT_ON), SetMinimalSize(60, 12),
SetDataTip(STR_STATION_BUILD_COVERAGE_ON, STR_STATION_BUILD_COVERAGE_AREA_ON_TOOLTIP),
NWidget(NWID_SPACER), SetFill(1, 0),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_EMPTY, INVALID_COLOUR, WID_BROS_INFO), SetPadding(2, 5, 0, 1), SetFill(1, 1), SetResize(1, 0),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_RESIZE),
NWidget(NWID_VERTICAL),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetFill(0, 1), EndContainer(),
NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN),
EndContainer(),
EndContainer(),
EndContainer(),
NWidget(NWID_SPACER), SetMinimalSize(0, 10), SetResize(0, 1),
EndContainer(),
};
@ -1295,29 +1736,66 @@ static const NWidgetPart _nested_tram_station_picker_widgets[] = {
NWidget(NWID_HORIZONTAL),
NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_BROS_CAPTION),
NWidget(WWT_SHADEBOX, COLOUR_DARK_GREEN),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_DEFSIZE),
NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN),
EndContainer(),
EndContainer(),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BROS_BACKGROUND),
NWidget(NWID_SPACER), SetMinimalSize(0, 3),
NWidget(NWID_HORIZONTAL), SetPIP(0, 2, 0),
NWidget(NWID_SPACER), SetFill(1, 0),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_X), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_Y), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(),
NWidget(NWID_SPACER), SetFill(1, 0),
EndContainer(),
NWidget(NWID_SPACER), SetMinimalSize(0, 1),
NWidget(NWID_HORIZONTAL), SetPIP(2, 0, 2),
NWidget(WWT_LABEL, COLOUR_DARK_GREEN, WID_BROS_INFO), SetMinimalSize(140, 14), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL),
NWidget(NWID_SPACER), SetFill(1, 0),
NWidget(NWID_HORIZONTAL),
NWidget(NWID_VERTICAL),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_ADDITIONS),
NWidget(NWID_HORIZONTAL), SetPIP(7, 0, 7), SetPadding(2, 0, 1, 0),
NWidget(WWT_MATRIX, COLOUR_GREY, WID_BROS_NEWST_LIST), SetMinimalSize(122, 71), SetFill(1, 0),
SetMatrixDataTip(1, 0, STR_STATION_BUILD_STATION_CLASS_TOOLTIP), SetScrollbar(WID_BROS_NEWST_SCROLL),
NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_BROS_NEWST_SCROLL),
EndContainer(),
EndContainer(),
NWidget(NWID_SPACER), SetMinimalSize(0, 3),
NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetMinimalSize(144, 11), SetDataTip(STR_STATION_BUILD_ORIENTATION, STR_NULL), SetPadding(1, 2, 0, 2),
NWidget(NWID_SPACER), SetMinimalSize(0, 1),
NWidget(NWID_HORIZONTAL), SetPIP(0, 2, 0),
NWidget(NWID_SPACER), SetFill(1, 0),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_X), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_Y), SetMinimalSize(66, 50), SetFill(0, 0), EndContainer(),
NWidget(NWID_SPACER), SetFill(1, 0),
EndContainer(),
NWidget(NWID_SPACER), SetMinimalSize(0, 3),
NWidget(WWT_LABEL, COLOUR_DARK_GREEN, WID_BROS_SHOW_NEWST_TYPE), SetMinimalSize(144, 11), SetDataTip(STR_ORANGE_STRING, STR_NULL), SetPadding(1, 2, 4, 2),
NWidget(NWID_SPACER), SetMinimalSize(0, 1),
NWidget(NWID_HORIZONTAL), SetPIP(2, 0, 2),
NWidget(NWID_SPACER), SetFill(1, 0),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROS_LT_OFF), SetMinimalSize(60, 12),
SetDataTip(STR_STATION_BUILD_COVERAGE_OFF, STR_STATION_BUILD_COVERAGE_AREA_OFF_TOOLTIP),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROS_LT_ON), SetMinimalSize(60, 12),
SetDataTip(STR_STATION_BUILD_COVERAGE_ON, STR_STATION_BUILD_COVERAGE_AREA_ON_TOOLTIP),
NWidget(NWID_SPACER), SetFill(1, 0),
EndContainer(),
NWidget(NWID_SPACER), SetMinimalSize(0, 10), SetResize(0, 1),
EndContainer(),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_MATRIX),
/* We need an additional background for the matrix, as the matrix cannot handle the scrollbar due to not being an NWidgetCore. */
NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetScrollbar(WID_BROS_MATRIX_SCROLL),
NWidget(NWID_HORIZONTAL),
NWidget(NWID_MATRIX, COLOUR_DARK_GREEN, WID_BROS_MATRIX), SetScrollbar(WID_BROS_MATRIX_SCROLL), SetPIP(0, 2, 0), SetPadding(2, 0, 0, 0),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BROS_IMAGE), SetMinimalSize(66, 60),
SetFill(0, 0), SetResize(0, 0), SetDataTip(0x0, STR_STATION_BUILD_STATION_TYPE_TOOLTIP), SetScrollbar(WID_BROS_MATRIX_SCROLL),
EndContainer(),
EndContainer(),
NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_BROS_MATRIX_SCROLL),
EndContainer(),
EndContainer(),
EndContainer(),
EndContainer(),
NWidget(NWID_HORIZONTAL), SetPIP(2, 0, 2),
NWidget(NWID_SPACER), SetFill(1, 0),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROS_LT_OFF), SetMinimalSize(60, 12),
SetDataTip(STR_STATION_BUILD_COVERAGE_OFF, STR_STATION_BUILD_COVERAGE_AREA_OFF_TOOLTIP),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROS_LT_ON), SetMinimalSize(60, 12),
SetDataTip(STR_STATION_BUILD_COVERAGE_ON, STR_STATION_BUILD_COVERAGE_AREA_ON_TOOLTIP),
NWidget(NWID_SPACER), SetFill(1, 0),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_EMPTY, INVALID_COLOUR, WID_BROS_INFO), SetFill(1, 1), SetResize(1, 0),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_RESIZE),
NWidget(NWID_VERTICAL),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetFill(0, 1), EndContainer(),
NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN),
EndContainer(),
EndContainer(),
EndContainer(),
NWidget(NWID_SPACER), SetMinimalSize(0, 10), SetResize(0, 1),
EndContainer(),
};
@ -1333,10 +1811,113 @@ static void ShowRVStationPicker(Window *parent, RoadStopType rs)
new BuildRoadStationWindow(RoadTypeIsRoad(_cur_roadtype) ? &_road_station_picker_desc : &_tram_station_picker_desc, parent, rs);
}
struct BuildRoadWaypointWindow : PickerWindowBase {
BuildRoadWaypointWindow(WindowDesc *desc, Window *parent) : PickerWindowBase(desc, parent)
{
this->CreateNestedTree();
NWidgetMatrix *matrix = this->GetWidget<NWidgetMatrix>(WID_BROW_WAYPOINT_MATRIX);
matrix->SetScrollbar(this->GetScrollbar(WID_BROW_SCROLL));
this->FinishInitNested(TRANSPORT_ROAD);
matrix->SetCount(_waypoint_count);
if (_cur_waypoint_type >= _waypoint_count) _cur_waypoint_type = 0;
matrix->SetClicked(_cur_waypoint_type);
}
void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
{
switch (widget) {
case WID_BROW_WAYPOINT_MATRIX:
/* Three blobs high and wide. */
size->width += resize->width * 2;
size->height += resize->height * 2;
/* Resizing in X direction only at blob size, but at pixel level in Y. */
resize->height = 1;
break;
case WID_BROW_WAYPOINT:
size->width = ScaleGUITrad(64) + 2;
size->height = ScaleGUITrad(58) + 2;
break;
}
}
void DrawWidget(const Rect &r, int widget) const override
{
switch (GB(widget, 0, 16)) {
case WID_BROW_WAYPOINT: {
uint type = GB(widget, 16, 16);
const RoadStopSpec *spec = RoadStopClass::Get(ROADSTOP_CLASS_WAYP)->GetSpec(type);
if (spec == nullptr) {
StationPickerDrawSprite(r.left + 1 + ScaleGUITrad(31), r.bottom - ScaleGUITrad(31), STATION_ROADWAYPOINT, INVALID_RAILTYPE, _cur_roadtype, 4);
} else {
DrawRoadStopTile(r.left + 1 + ScaleGUITrad(31), r.bottom - ScaleGUITrad(31), _cur_roadtype, spec, STATION_ROADWAYPOINT, 4);
}
if (!IsRoadStopAvailable(spec, STATION_ROADWAYPOINT)) {
GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, PC_BLACK, FILLRECT_CHECKER);
}
}
}
}
void OnClick(Point pt, int widget, int click_count) override
{
switch (GB(widget, 0, 16)) {
case WID_BROW_WAYPOINT: {
uint type = GB(widget, 16, 16);
const RoadStopSpec *spec = RoadStopClass::Get(ROADSTOP_CLASS_WAYP)->GetSpec(type);
if (!IsRoadStopAvailable(spec, STATION_ROADWAYPOINT)) return;
this->GetWidget<NWidgetMatrix>(WID_BROW_WAYPOINT_MATRIX)->SetClicked(_cur_waypoint_type);
_cur_waypoint_type = type;
this->GetWidget<NWidgetMatrix>(WID_BROW_WAYPOINT_MATRIX)->SetClicked(_cur_waypoint_type);
if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
this->SetDirty();
break;
}
}
}
};
/** Nested widget definition for the build NewGRF road waypoint window */
static const NWidgetPart _nested_build_waypoint_widgets[] = {
NWidget(NWID_HORIZONTAL),
NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetDataTip(STR_WAYPOINT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN),
EndContainer(),
NWidget(NWID_HORIZONTAL),
NWidget(NWID_MATRIX, COLOUR_DARK_GREEN, WID_BROW_WAYPOINT_MATRIX), SetPIP(3, 2, 3), SetScrollbar(WID_BROW_SCROLL),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BROW_WAYPOINT), SetMinimalSize(66, 60), SetDataTip(0x0, STR_WAYPOINT_GRAPHICS_TOOLTIP), SetScrollbar(WID_BROW_SCROLL), EndContainer(),
EndContainer(),
NWidget(NWID_VERTICAL),
NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_BROW_SCROLL),
NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN),
EndContainer(),
EndContainer(),
};
static WindowDesc _build_waypoint_desc(
WDP_AUTO, "build_waypoint", 0, 0,
WC_BUILD_WAYPOINT, WC_BUILD_TOOLBAR,
WDF_CONSTRUCTION,
_nested_build_waypoint_widgets, lengthof(_nested_build_waypoint_widgets)
);
static void ShowBuildWaypointPicker(Window *parent)
{
new BuildRoadWaypointWindow(&_build_waypoint_desc, parent);
}
void InitializeRoadGui()
{
_road_depot_orientation = DIAGDIR_NW;
_road_station_picker_orientation = DIAGDIR_NW;
_build_depot_direction = DIAGDIR_NW;
_roadstop_gui_settings.orientation = DIAGDIR_NW;
}

@ -38,6 +38,7 @@
#include "scope_info.h"
#include "string_func.h"
#include "core/checksum_func.hpp"
#include "newgrf_roadstop.h"
#include "table/strings.h"
@ -1979,6 +1980,8 @@ again:
v->last_station_visited = st->index;
RoadVehArrivesAt(v, st);
v->BeginLoading();
TriggerRoadStopRandomisation(st, v->tile, RSRT_VEH_ARRIVES);
TriggerRoadStopAnimation(st, v->tile, SAT_TRAIN_ARRIVES);
}
return false;
}
@ -2041,6 +2044,8 @@ again:
if (IsDriveThroughStopTile(v->tile) || (v->current_order.IsType(OT_GOTO_STATION) && v->current_order.GetDestination() == st->index)) {
RoadVehArrivesAt(v, st);
v->BeginLoading();
TriggerRoadStopRandomisation(st, v->tile, RSRT_VEH_ARRIVES);
TriggerRoadStopAnimation(st, v->tile, SAT_TRAIN_ARRIVES);
return false;
}
} else {

@ -170,6 +170,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = {
{ XSLFI_ROAD_WAYPOINTS, XSCF_NULL, 1, 1, "road_waypoints", nullptr, nullptr, nullptr },
{ XSLFI_MORE_STATION_TYPES, XSCF_NULL, 1, 1, "more_station_types", nullptr, nullptr, nullptr },
{ XSLFI_RV_ORDER_EXTRA_FLAGS, XSCF_IGNORABLE_UNKNOWN, 1, 1, "rv_order_extra_flags", nullptr, nullptr, nullptr },
{ XSLFI_GRF_ROADSTOPS, XSCF_NULL, 1, 1, "grf_road_stops", nullptr, nullptr, nullptr },
{ XSLFI_SCRIPT_INT64, XSCF_NULL, 1, 1, "script_int64", nullptr, nullptr, nullptr },
{ XSLFI_NULL, XSCF_NULL, 0, 0, nullptr, nullptr, nullptr, nullptr },// This is the end marker
};

@ -123,6 +123,7 @@ enum SlXvFeatureIndex {
XSLFI_ROAD_WAYPOINTS, ///< Road waypoints
XSLFI_MORE_STATION_TYPES, ///< More station types (field widening)
XSLFI_RV_ORDER_EXTRA_FLAGS, ///< Road vehicle order extra flags
XSLFI_GRF_ROADSTOPS, ///< NewGRF road stops
XSLFI_SCRIPT_INT64, ///< See: SLV_SCRIPT_INT64

@ -13,6 +13,7 @@
#include "../roadstop_base.h"
#include "../vehicle_base.h"
#include "../newgrf_station.h"
#include "../newgrf_roadstop.h"
#include "saveload.h"
#include "saveload_buffer.h"
@ -114,6 +115,11 @@ void AfterLoadStations()
st->speclist[i].spec = StationClass::GetByGrf(st->speclist[i].grfid, st->speclist[i].localidx, nullptr);
}
for (uint i = 0; i < st->num_roadstop_specs; i++) {
if (st->roadstop_speclist[i].grfid == 0) continue;
st->roadstop_speclist[i].spec = RoadStopClass::GetByGrf(st->roadstop_speclist[i].grfid, st->roadstop_speclist[i].localidx, nullptr);
}
if (Station::IsExpected(st)) {
Station *sta = Station::From(st);
@ -122,6 +128,7 @@ void AfterLoadStations()
}
StationUpdateCachedTriggers(st);
StationUpdateRoadStopCachedTriggers(st);
}
}
@ -402,6 +409,9 @@ static const SaveLoad _base_station_desc[] = {
SLE_VAR(BaseStation, random_bits, SLE_UINT16),
SLE_VAR(BaseStation, waiting_triggers, SLE_UINT8),
SLE_VAR(BaseStation, num_specs, SLE_UINT8),
SLE_CONDVAR_X(BaseStation, num_roadstop_specs, SLE_UINT8, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_GRF_ROADSTOPS)),
SLE_CONDVARVEC_X(BaseStation, custom_road_stop_tiles, SLE_UINT32, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_GRF_ROADSTOPS)),
SLE_CONDVARVEC_X(BaseStation, custom_road_stop_data, SLE_UINT16, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_GRF_ROADSTOPS)),
};
static OldPersistentStorage _old_st_persistent_storage;
@ -554,6 +564,10 @@ static void RealSave_STNN(BaseStation *bst)
for (uint i = 0; i < bst->num_specs; i++) {
SlObjectSaveFiltered(&bst->speclist[i], _filtered_station_speclist_desc);
}
for (uint i = 0; i < bst->num_roadstop_specs; i++) {
SlObjectSaveFiltered(&bst->roadstop_speclist[i], _filtered_station_speclist_desc);
}
}
static void Save_STNN()
@ -676,6 +690,14 @@ static void Load_STNN()
SlObjectLoadFiltered(&bst->speclist[i], _filtered_station_speclist_desc);
}
}
if (bst->num_roadstop_specs != 0) {
/* Allocate speclist memory when loading a game */
bst->roadstop_speclist = CallocT<RoadStopSpecList>(bst->num_roadstop_specs);
for (uint i = 0; i < bst->num_roadstop_specs; i++) {
SlObjectLoadFiltered(&bst->roadstop_speclist[i], _filtered_station_speclist_desc);
}
}
}
}

@ -933,6 +933,8 @@ static void StationSpreadChanged(int32 new_value)
{
InvalidateWindowData(WC_SELECT_STATION, 0);
InvalidateWindowData(WC_BUILD_STATION, 0);
InvalidateWindowData(WC_BUS_STATION, 0);
InvalidateWindowData(WC_TRUCK_STATION, 0);
}
static void UpdateConsists(int32 new_value)
@ -1786,6 +1788,19 @@ static bool SpriteZoomMinSettingGUI(SettingOnGuiCtrlData &data)
}
}
static bool AllowRoadStopsUnderBridgesSettingGUI(SettingOnGuiCtrlData &data)
{
switch (data.type) {
case SOGCT_DESCRIPTION_TEXT:
SetDParam(0, data.text);
data.text = STR_CONFIG_SETTING_ALLOW_ROAD_STATIONS_UNDER_BRIDGES_HELPTEXT_EXTRA;
return true;
default:
return false;
}
}
/* End - GUI callbacks */
/**

@ -57,6 +57,7 @@ void RebuildStationKdtree()
BaseStation::~BaseStation()
{
free(this->speclist);
free(this->roadstop_speclist);
if (CleaningPool()) return;
@ -184,6 +185,31 @@ void BaseStation::PostDestructor(size_t index)
InvalidateWindowData(WC_SELECT_STATION, 0, 0);
}
void BaseStation::SetRoadStopTileData(TileIndex tile, byte data, byte offset)
{
for (size_t i = 0; i < this->custom_road_stop_tiles.size(); i++) {
if (this->custom_road_stop_tiles[i] == tile) {
SB(this->custom_road_stop_data[i], offset, 8, data);
return;
}
}
this->custom_road_stop_tiles.push_back(tile);
this->custom_road_stop_data.push_back(((uint)data) << offset);
}
void BaseStation::RemoveRoadStopTileData(TileIndex tile)
{
for (size_t i = 0; i < this->custom_road_stop_tiles.size(); i++) {
if (this->custom_road_stop_tiles[i] == tile) {
this->custom_road_stop_tiles[i] = this->custom_road_stop_tiles.back();
this->custom_road_stop_data[i] = this->custom_road_stop_data.back();
this->custom_road_stop_tiles.pop_back();
this->custom_road_stop_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

@ -873,6 +873,11 @@ public:
return IsRailStationTile(tile) && GetStationIndex(tile) == this->index;
}
inline bool TileBelongsToRoadStop(TileIndex tile) const
{
return IsAnyRoadStopTile(tile) && GetStationIndex(tile) == this->index;
}
inline bool TileBelongsToAirport(TileIndex tile) const
{
return IsAirportTile(tile) && GetStationIndex(tile) == this->index;

@ -58,6 +58,7 @@
#include "zoning.h"
#include "tunnelbridge_map.h"
#include "cheat_type.h"
#include "newgrf_roadstop.h"
#include "table/strings.h"
@ -945,18 +946,28 @@ CommandCost IsRailStationBridgeAboveOk(TileIndex tile, const StationSpec *statsp
GetBridgeType(southern_bridge_end), GetTunnelBridgeTransportType(southern_bridge_end));
}
CommandCost IsRoadStopBridgeAboveOK(TileIndex tile, bool drive_through, DiagDirection entrance,
CommandCost IsRoadStopBridgeAboveOK(TileIndex tile, const RoadStopSpec *spec, bool drive_through, DiagDirection entrance,
TileIndex northern_bridge_end, TileIndex southern_bridge_end, int bridge_height,
BridgeType bridge_type, TransportType bridge_transport_type)
{
if (!_settings_game.construction.allow_road_stops_under_bridges) return CommandCost(INVALID_STRING_ID);
if (spec && HasBit(spec->internal_flags, RSIF_BRIDGE_HEIGHTS_SET)) {
int height = spec->bridge_height[drive_through ? (GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET + DiagDirToAxis(entrance)) : entrance];
if (height == 0) return CommandCost(INVALID_STRING_ID);
if (GetTileMaxZ(tile) + height > bridge_height) {
return CommandCost(STR_ERROR_BRIDGE_TOO_LOW_FOR_STATION);
}
} else {
if (!_settings_game.construction.allow_road_stops_under_bridges) return CommandCost(INVALID_STRING_ID);
if (GetTileMaxZ(tile) + (drive_through ? 1 : 2) > bridge_height) {
return CommandCost(STR_ERROR_BRIDGE_TOO_LOW_FOR_STATION);
if (GetTileMaxZ(tile) + (drive_through ? 1 : 2) > bridge_height) {
return CommandCost(STR_ERROR_BRIDGE_TOO_LOW_FOR_STATION);
}
}
BridgePiecePillarFlags disallowed_pillar_flags = (BridgePiecePillarFlags) 0;
if (drive_through) {
if (spec && HasBit(spec->internal_flags, RSIF_BRIDGE_DISALLOWED_PILLARS_SET)) {
disallowed_pillar_flags = (BridgePiecePillarFlags) spec->bridge_disallowed_pillars[drive_through ? (GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET + DiagDirToAxis(entrance)) : entrance];
} else if (drive_through) {
disallowed_pillar_flags = (BridgePiecePillarFlags) (DiagDirToAxis(entrance) == AXIS_X ? 0x50 : 0xA0);
} else {
SetBit(disallowed_pillar_flags, 4 + entrance);
@ -1065,6 +1076,7 @@ static CommandCost CheckFlatLandRailStation(TileArea tile_area, DoCommandFlag fl
/**
* Checks if a road stop can be built at the given tile.
* @param tile_area Area to check.
* @param spec Road stop spec.
* @param flags Operation to perform.
* @param invalid_dirs Prohibited directions (set of DiagDirections).
* @param is_drive_through True if trying to build a drive-through station.
@ -1075,20 +1087,21 @@ static CommandCost CheckFlatLandRailStation(TileArea tile_area, DoCommandFlag fl
* @param require_road Is existing road required.
* @return The cost in case of success, or an error code if it failed.
*/
CommandCost CheckFlatLandRoadStop(TileArea tile_area, DoCommandFlag flags, uint invalid_dirs, bool is_drive_through, StationType station_type, Axis axis, StationID *station, RoadType rt, bool require_road)
CommandCost CheckFlatLandRoadStop(TileArea tile_area, const RoadStopSpec *spec, DoCommandFlag flags, uint invalid_dirs, bool is_drive_through, StationType station_type, Axis axis, StationID *station, RoadType rt, bool require_road)
{
CommandCost cost(EXPENSES_CONSTRUCTION);
int allowed_z = -1;
for (TileIndex cur_tile : tile_area) {
CommandCost ret = CheckBuildableTile(cur_tile, invalid_dirs, allowed_z, !is_drive_through, !_settings_game.construction.allow_road_stops_under_bridges);
bool allow_under_bridge = _settings_game.construction.allow_road_stops_under_bridges || (spec != nullptr && HasBit(spec->internal_flags, RSIF_BRIDGE_HEIGHTS_SET));
CommandCost ret = CheckBuildableTile(cur_tile, invalid_dirs, allowed_z, !is_drive_through, !allow_under_bridge);
if (ret.Failed()) return ret;
cost.AddCost(ret);
if (_settings_game.construction.allow_road_stops_under_bridges && IsBridgeAbove(cur_tile)) {
if (allow_under_bridge && IsBridgeAbove(cur_tile)) {
TileIndex southern_bridge_end = GetSouthernBridgeEnd(cur_tile);
TileIndex northern_bridge_end = GetNorthernBridgeEnd(cur_tile);
CommandCost bridge_ret = IsRoadStopBridgeAboveOK(cur_tile, is_drive_through, (DiagDirection) FindFirstBit(invalid_dirs),
CommandCost bridge_ret = IsRoadStopBridgeAboveOK(cur_tile, spec, is_drive_through, (DiagDirection) FindFirstBit(invalid_dirs),
northern_bridge_end, southern_bridge_end, GetBridgeHeight(southern_bridge_end),
GetBridgeType(southern_bridge_end), GetTunnelBridgeTransportType(southern_bridge_end));
if (bridge_ret.Failed()) return bridge_ret;
@ -2049,10 +2062,12 @@ static CommandCost FindJoiningRoadStop(StationID existing_stop, StationID statio
* bit 3: #Axis of the road for drive-through stops.
* bit 5..10: The roadtype.
* bit 16..31: Station ID to join (NEW_STATION if build new one).
* @param p3 bit 0..7: Roadstop class.
* bit 8..15: Roadstopspec index.
* @param text Unused.
* @return The cost of this operation or an error.
*/
CommandCost CmdBuildRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
CommandCost CmdBuildRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, uint64 p3, const char *text, uint32 binary_length)
{
bool type = HasBit(p2, 0);
bool is_drive_through = HasBit(p2, 1);
@ -2066,6 +2081,19 @@ CommandCost CmdBuildRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, uin
uint8 width = (uint8)GB(p1, 0, 8);
uint8 length = (uint8)GB(p1, 8, 8);
RoadStopClassID spec_class = Extract<RoadStopClassID, 0, 8>(p3);
byte spec_index = GB(p3, 8, 8);
/* Check if the given station class is valid */
if ((uint)spec_class >= RoadStopClass::GetClassCount() || spec_class == ROADSTOP_CLASS_WAYP) return CMD_ERROR;
if (spec_index >= RoadStopClass::Get(spec_class)->GetSpecCount()) return CMD_ERROR;
const RoadStopSpec *roadstopspec = RoadStopClass::Get(spec_class)->GetSpec(spec_index);
if (roadstopspec != nullptr) {
if (type && roadstopspec->stop_type != ROADSTOPTYPE_FREIGHT && roadstopspec->stop_type != ROADSTOPTYPE_ALL) return CMD_ERROR;
if (!type && roadstopspec->stop_type != ROADSTOPTYPE_PASSENGER && roadstopspec->stop_type != ROADSTOPTYPE_ALL) return CMD_ERROR;
}
/* Check if the requested road stop is too big */
if (width > _settings_game.station.station_spread || length > _settings_game.station.station_spread) return_cmd_error(STR_ERROR_STATION_TOO_SPREAD_OUT);
/* Check for incorrect width / length. */
@ -2098,7 +2126,7 @@ CommandCost CmdBuildRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, uin
/* Total road stop cost. */
CommandCost cost(EXPENSES_CONSTRUCTION, roadstop_area.w * roadstop_area.h * _price[type ? PR_BUILD_STATION_TRUCK : PR_BUILD_STATION_BUS]);
StationID est = INVALID_STATION;
ret = CheckFlatLandRoadStop(roadstop_area, flags, is_drive_through ? 5 << axis : 1 << ddir, is_drive_through, type ? STATION_TRUCK : STATION_BUS, axis, &est, rt, false);
ret = CheckFlatLandRoadStop(roadstop_area, roadstopspec, flags, is_drive_through ? 5 << axis : 1 << ddir, is_drive_through, type ? STATION_TRUCK : STATION_BUS, axis, &est, rt, false);
if (ret.Failed()) return ret;
cost.AddCost(ret);
@ -2112,6 +2140,20 @@ CommandCost CmdBuildRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, uin
ret = BuildStationPart(&st, flags, reuse, roadstop_area, STATIONNAMING_ROAD);
if (ret.Failed()) return ret;
/* Check if we can allocate a custom stationspec to this station */
int specindex = AllocateRoadStopSpecToStation(roadstopspec, st, (flags & DC_EXEC) != 0);
if (specindex == -1) return_cmd_error(STR_ERROR_TOO_MANY_STATION_SPECS);
if (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, type ? 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) {
@ -2126,6 +2168,12 @@ CommandCost CmdBuildRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, uin
RemoveRoadStop(cur_tile, flags);
}
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);
/* Insert into linked list of RoadStops. */
RoadStop **currstop = FindRoadStopSpot(type, st);
@ -2169,6 +2217,12 @@ CommandCost CmdBuildRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, uin
}
Company::Get(st->owner)->infrastructure.station++;
SetCustomRoadStopSpecIndex(cur_tile, specindex);
if (roadstopspec != nullptr) {
st->SetRoadStopRandomBits(cur_tile, GB(Random(), 0, 8));
TriggerRoadStopAnimation(st, cur_tile, SAT_BUILT);
}
MarkTileDirtyByTile(cur_tile);
UpdateRoadCachedOneWayStatesAroundTile(cur_tile);
}
@ -2222,10 +2276,19 @@ CommandCost RemoveRoadWaypointStop(TileIndex tile, DoCommandFlag flags)
Company::Get(wp->owner)->infrastructure.station--;
DirtyCompanyInfrastructureWindows(wp->owner);
DeleteAnimatedTile(tile);
uint specindex = GetCustomRoadStopSpecIndex(tile);
DeleteNewGRFInspectWindow(GSF_ROADSTOPS, tile);
DoClearSquare(tile);
wp->rect.AfterRemoveTile(wp, tile);
wp->RemoveRoadStopTileData(tile);
DeallocateRoadStopSpecFromStation(wp, specindex);
MakeRoadWaypointStationAreaSmaller(wp, wp->road_waypoint_area);
UpdateStationSignCoord(wp);
@ -2311,6 +2374,12 @@ 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();
@ -2332,6 +2401,9 @@ CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags)
st->AfterStationTileSetChange(false, is_truck ? STATION_TRUCK: STATION_BUS);
st->RemoveRoadStopTileData(tile);
DeallocateRoadStopSpecFromStation(st, specindex);
/* Update the tile area of the truck/bus stop */
if (is_truck) {
st->truck_station.Clear();
@ -3417,25 +3489,38 @@ draw_default_foundation:
if (IsAnyRoadStop(ti->tile)) {
RoadType road_rt = GetRoadTypeRoad(ti->tile);
RoadType tram_rt = GetRoadTypeTram(ti->tile);
const RoadTypeInfo* road_rti = road_rt == INVALID_ROADTYPE ? nullptr : GetRoadTypeInfo(road_rt);
const RoadTypeInfo* tram_rti = tram_rt == INVALID_ROADTYPE ? nullptr : GetRoadTypeInfo(tram_rt);
const RoadTypeInfo *road_rti = road_rt == INVALID_ROADTYPE ? nullptr : GetRoadTypeInfo(road_rt);
const RoadTypeInfo *tram_rti = tram_rt == INVALID_ROADTYPE ? nullptr : GetRoadTypeInfo(tram_rt);
if (IsDriveThroughStopTile(ti->tile)) {
Axis axis = GetRoadStopDir(ti->tile) == DIAGDIR_NE ? AXIS_X : AXIS_Y;
uint sprite_offset = axis == AXIS_X ? 1 : 0;
Axis axis = GetRoadStopDir(ti->tile) == DIAGDIR_NE ? AXIS_X : AXIS_Y;
DiagDirection dir = GetRoadStopDir(ti->tile);
const RoadStopSpec *stopspec = GetRoadStopSpec(ti->tile);
if (stopspec != nullptr) {
int view = dir;
if (IsDriveThroughStopTile(ti->tile)) view += 4;
st = BaseStation::GetByTile(ti->tile);
RoadStopResolverObject object(stopspec, st, ti->tile, INVALID_ROADTYPE, GetStationType(ti->tile), view);
const SpriteGroup *group = object.Resolve();
const DrawTileSprites *dts = ((const TileLayoutSpriteGroup *)group)->ProcessRegisters(nullptr);
t = dts;
}
DrawRoadOverlays(ti, PAL_NONE, road_rti, tram_rti, sprite_offset, sprite_offset);
if (IsDriveThroughStopTile(ti->tile)) {
if (stopspec == nullptr || (stopspec->draw_mode & ROADSTOP_DRAW_MODE_OVERLAY) != 0) {
uint sprite_offset = axis == AXIS_X ? 1 : 0;
DrawRoadOverlays(ti, PAL_NONE, road_rti, tram_rti, sprite_offset, sprite_offset);
DisallowedRoadDirections drd = GetDriveThroughStopDisallowedRoadDirections(ti->tile);
if (drd != DRD_NONE) {
DrawGroundSpriteAt(SPR_ONEWAY_BASE + drd - 1 + ((axis == AXIS_X) ? 0 : 3), PAL_NONE, 8, 8, 0);
DisallowedRoadDirections drd = GetDriveThroughStopDisallowedRoadDirections(ti->tile);
if (drd != DRD_NONE) {
DrawGroundSpriteAt(SPR_ONEWAY_BASE + drd - 1 + ((axis == AXIS_X) ? 0 : 3), PAL_NONE, 8, 8, 0);
}
}
} else {
/* Non-drivethrough road stops are only valid for roads. */
assert_tile(road_rt != INVALID_ROADTYPE && tram_rt == INVALID_ROADTYPE, ti->tile);
if (road_rti->UsesOverlay()) {
DiagDirection dir = GetRoadStopDir(ti->tile);
if ((stopspec != nullptr && (stopspec->draw_mode & ROADSTOP_DRAW_MODE_ROAD) != 0) && road_rti->UsesOverlay()) {
SpriteID ground = GetCustomRoadSprite(road_rti, ti->tile, ROTSG_ROADSTOP);
DrawGroundSprite(ground + dir, PAL_NONE);
}
@ -3502,7 +3587,7 @@ void StationPickerDrawSprite(int x, int y, StationType st, RailType railtype, Ro
}
/* Default waypoint has no railtype specific sprites */
DrawRailTileSeqInGUI(x, y, t, st == STATION_WAYPOINT ? 0 : total_offset, 0, pal);
DrawRailTileSeqInGUI(x, y, t, (st == STATION_WAYPOINT || st == STATION_ROADWAYPOINT) ? 0 : total_offset, 0, pal);
}
static int GetSlopePixelZ_Station(TileIndex tile, uint x, uint y)
@ -3738,6 +3823,12 @@ void AnimateTile_Station(TileIndex tile)
if (IsAirport(tile)) {
AnimateAirportTile(tile);
return;
}
if (IsAnyRoadStopTile(tile)) {
AnimateRoadStopTile(tile);
return;
}
}
@ -3748,7 +3839,11 @@ uint8 GetAnimatedTileSpeed_Station(TileIndex tile)
}
if (IsAirport(tile)) {
AnimateAirportTile(tile);
return GetAirportTileAnimationSpeed(tile);
}
if (IsAnyRoadStopTile(tile)) {
return GetRoadStopTileAnimationSpeed(tile);
}
return 0;
}
@ -4435,6 +4530,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);
}
}
@ -4519,6 +4615,8 @@ 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);
TriggerRoadStopAnimation(st, st->xy, SAT_NEW_CARGO, type);
TriggerRoadStopRandomisation(st, st->xy, RSRT_NEW_CARGO, type);
SetWindowDirty(WC_STATION_VIEW, st->index);
st->MarkTilesDirty(true);

@ -605,6 +605,42 @@ static inline uint GetCustomStationSpecIndex(TileIndex t)
return _m[t].m4;
}
/**
* Is there a custom road stop spec on this tile?
* @param t Tile to query
* @pre IsAnyRoadStopTile(t)
* @return True if this station is part of a newgrf station.
*/
static inline bool IsCustomRoadStopSpecIndex(TileIndex t)
{
assert_tile(IsAnyRoadStopTile(t), t);
return GB(_me[t].m8, 0, 6) != 0;
}
/**
* Set the custom road stop spec for this tile.
* @param t Tile to set the stationspec of.
* @param specindex The new spec.
* @pre IsAnyRoadStopTile(t)
*/
static inline void SetCustomRoadStopSpecIndex(TileIndex t, byte specindex)
{
assert_tile(IsAnyRoadStopTile(t), t);
SB(_me[t].m8, 0, 6, specindex);
}
/**
* Get the custom road stop spec for this tile.
* @param t Tile to query
* @pre IsAnyRoadStopTile(t)
* @return The custom station spec of this tile.
*/
static inline uint GetCustomRoadStopSpecIndex(TileIndex t)
{
assert_tile(IsAnyRoadStopTile(t), t);
return GB(_me[t].m8, 0, 6);
}
/**
* Set the random bits for a station tile.
* @param t Tile to set random bits for.

@ -10,6 +10,7 @@
#include "../newgrf_house.h"
#include "../newgrf_engine.h"
#include "../newgrf_roadtype.h"
#include "../newgrf_roadstop.h"
#include "../newgrf_cargo.h"
#include "../date_func.h"
#include "../timetable.h"
@ -1370,6 +1371,93 @@ static const NIFeature _nif_roadtype = {
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; }
bool ShowSpriteDumpButton(uint index) const override { return true; }
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, uint param, GetVariableExtra *extra) 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, extra);
}
void ExtraInfo(uint index, NIExtraInfoOutput &output) const override
{
char buffer[1024];
output.print("Debug Info:");
const RoadStopSpec *spec = GetRoadStopSpec(index);
if (spec) {
uint class_id = RoadStopClass::Get(spec->cls_id)->global_id;
seprintf(buffer, lastof(buffer), " class ID: %c%c%c%c, spec ID: %u", class_id >> 24, class_id >> 16, class_id >> 8, class_id, spec->spec_id);
output.print(buffer);
seprintf(buffer, lastof(buffer), " spec: stop type: %X, draw mode: %X, cargo triggers: " OTTD_PRINTFHEX64, spec->stop_type, spec->draw_mode, spec->cargo_triggers);
output.print(buffer);
seprintf(buffer, lastof(buffer), " spec: callback mask: %X, flags: %X, intl flags: %X", spec->callback_mask, spec->flags, spec->internal_flags);
output.print(buffer);
seprintf(buffer, lastof(buffer), " animation: frames: %u, status: %u, speed: %u, triggers: 0x%X", spec->animation.frames, spec->animation.status, spec->animation.speed, spec->animation.triggers);
output.print(buffer);
const BaseStation *st = BaseStation::GetByTile(index);
seprintf(buffer, lastof(buffer), " road stop: random bits: %02X, animation frame: %02X", st->GetRoadStopRandomBits(index), st->GetRoadStopAnimationFrame(index));
output.print(buffer);
}
}
/* virtual */ void SpriteDump(uint index, std::function<void(const char *)> print) const override
{
extern void DumpRoadStopSpriteGroup(const BaseStation *st, const RoadStopSpec *spec, std::function<void(const char *)> print);
DumpRoadStopSpriteGroup(BaseStation::GetByTile(index), GetRoadStopSpec(index), std::move(print));
}
};
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
@ -1392,6 +1480,7 @@ static const NIFeature * const _nifeatures[] = {
&_nif_airporttile, // GSF_AIRPORTTILES
&_nif_roadtype, // GSF_ROADTYPES
&_nif_roadtype, // GSF_TRAMTYPES
&_nif_roadstop, // GSF_ROADSTOPS
&_nif_town, // GSF_FAKE_TOWNS
&_nif_station_struct, // GSF_FAKE_STATION_STRUCT
};

@ -82,6 +82,7 @@ static int64 LinkGraphDistModeXrefChillPP(int64 val);
static bool LinkGraphDistributionSettingGUI(SettingOnGuiCtrlData &data);
static bool OrderTownGrowthRate(SettingOnGuiCtrlData &data);
static bool SpriteZoomMinSettingGUI(SettingOnGuiCtrlData &data);
static bool AllowRoadStopsUnderBridgesSettingGUI(SettingOnGuiCtrlData &data);
/* End - GUI callbacks */
@ -2044,6 +2045,7 @@ cat = SC_ADVANCED
str = STR_CONFIG_SETTING_ALLOW_ROAD_STATIONS_UNDER_BRIDGES
strhelp = STR_CONFIG_SETTING_ALLOW_ROAD_STATIONS_UNDER_BRIDGES_HELPTEXT
patxname = ""allow_stations_under_bridges.construction.allow_road_stops_under_bridges""
guiproc = AllowRoadStopsUnderBridgesSettingGUI
[SDT_BOOL]
var = construction.allow_docks_under_bridges

@ -49,6 +49,7 @@
#include "newgrf_station.h"
#include "station_func.h"
#include "tracerestrict.h"
#include "newgrf_roadstop.h"
#include "table/strings.h"
#include "table/bridge_land.h"
@ -69,7 +70,7 @@ extern const RoadBits _invalid_tileh_slopes_road[2][15];
extern CommandCost IsRailStationBridgeAboveOk(TileIndex tile, const StationSpec *statspec, byte layout, TileIndex northern_bridge_end, TileIndex southern_bridge_end, int bridge_height,
BridgeType bridge_type, TransportType bridge_transport_type);
extern CommandCost IsRoadStopBridgeAboveOK(TileIndex tile, bool drive_through, DiagDirection entrance,
extern CommandCost IsRoadStopBridgeAboveOK(TileIndex tile, const RoadStopSpec *spec, bool drive_through, DiagDirection entrance,
TileIndex northern_bridge_end, TileIndex southern_bridge_end, int bridge_height,
BridgeType bridge_type, TransportType bridge_transport_type);
@ -551,7 +552,7 @@ CommandCost CmdBuildBridge(TileIndex end_tile, DoCommandFlag flags, uint32 p1, u
case STATION_BUS:
case STATION_TRUCK:
case STATION_ROADWAYPOINT: {
CommandCost ret = IsRoadStopBridgeAboveOK(tile, IsDriveThroughStopTile(tile), GetRoadStopDir(tile),
CommandCost ret = IsRoadStopBridgeAboveOK(tile, GetRoadStopSpec(tile), IsDriveThroughStopTile(tile), GetRoadStopDir(tile),
tile_start, tile_end, z_start + 1, bridge_type, transport_type);
if (ret.Failed()) {
if (ret.GetErrorMessage() != INVALID_STRING_ID) return ret;
@ -675,7 +676,7 @@ CommandCost CmdBuildBridge(TileIndex end_tile, DoCommandFlag flags, uint32 p1, u
case STATION_BUS:
case STATION_TRUCK:
case STATION_ROADWAYPOINT: {
CommandCost ret = IsRoadStopBridgeAboveOK(tile, IsDriveThroughStopTile(tile), GetRoadStopDir(tile),
CommandCost ret = IsRoadStopBridgeAboveOK(tile, GetRoadStopSpec(tile), IsDriveThroughStopTile(tile), GetRoadStopDir(tile),
tile_start, tile_end, z_start + 1, bridge_type, transport_type);
if (ret.Failed()) {
if (ret.GetErrorMessage() != INVALID_STRING_ID) return ret;

@ -22,6 +22,7 @@
#include "newgrf_debug.h"
#include "newgrf_sound.h"
#include "newgrf_station.h"
#include "newgrf_roadstop.h"
#include "group_gui.h"
#include "strings_func.h"
#include "zoom_func.h"
@ -3418,6 +3419,13 @@ 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 (IsAnyRoadStopTile(this->tile)) {
TriggerRoadStopRandomisation(st, this->tile, RSRT_VEH_DEPARTS);
TriggerRoadStopAnimation(st, this->tile, SAT_TRAIN_DEPARTS);
}
}
if (this->cur_real_order_index < this->GetNumOrders()) {
Order *real_current_order = this->GetOrder(this->cur_real_order_index);

@ -25,6 +25,7 @@
#include "string_func.h"
#include "company_func.h"
#include "newgrf_station.h"
#include "newgrf_roadstop.h"
#include "company_base.h"
#include "water.h"
#include "company_gui.h"
@ -192,7 +193,6 @@ extern CommandCost IsRailStationBridgeAboveOk(TileIndex tile, const StationSpec
* - p1 = (bit 24) - allow waypoints directly adjacent to other waypoints.
* @param p2 various bitstuffed elements
* - p2 = (bit 0- 7) - custom station class
* - p2 = (bit 8-15) - custom station id
* - p2 = (bit 31-16) - station ID to join
* @param p3 various bitstuffed elements
* - p3 = (bit 0-31) - custom station id
@ -337,11 +337,14 @@ CommandCost CmdBuildRailWaypoint(TileIndex start_tile, DoCommandFlag flags, uint
* bit 8..15: Length of the road stop.
* bit 16: Allow stations directly adjacent to other stations.
* bit 17: #Axis of the road.
* @param p2 bit 16..31: Station ID to join (NEW_STATION if build new one).
* @param p2 bit 0..7: Custom road stop class
* bit 16..31: Station ID to join (NEW_STATION if build new one).
* @param p3 various bitstuffed elements
* - p3 = (bit 0-31) - custom road stop id
* @param text Unused.
* @return The cost of this operation or an error.
*/
CommandCost CmdBuildRoadWaypoint(TileIndex start_tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
CommandCost CmdBuildRoadWaypoint(TileIndex start_tile, DoCommandFlag flags, uint32 p1, uint32 p2, uint64 p3, const char *text, uint32 binary_length)
{
StationID station_to_join = GB(p2, 16, 16);
byte width = GB(p1, 0, 8);
@ -349,6 +352,15 @@ CommandCost CmdBuildRoadWaypoint(TileIndex start_tile, DoCommandFlag flags, uint
bool adjacent = HasBit(p1, 16);
Axis axis = Extract<Axis, 17, 1>(p1);
RoadStopClassID spec_class = Extract<RoadStopClassID, 0, 8>(p2);
uint spec_index = GB(p3, 0, 32);
/* Check if the given road stop class is valid */
if (spec_class != ROADSTOP_CLASS_WAYP) return CMD_ERROR;
if (spec_index >= RoadStopClass::Get(spec_class)->GetSpecCount()) return CMD_ERROR;
const RoadStopSpec *spec = RoadStopClass::Get(spec_class)->GetSpec(spec_index);
/* The number of parts to build */
byte count = axis == AXIS_X ? height : width;
@ -368,8 +380,8 @@ CommandCost CmdBuildRoadWaypoint(TileIndex start_tile, DoCommandFlag flags, uint
/* Total road stop cost. */
CommandCost cost(EXPENSES_CONSTRUCTION, roadstop_area.w * roadstop_area.h * _price[PR_BUILD_STATION_TRUCK]);
StationID est = INVALID_STATION;
extern CommandCost CheckFlatLandRoadStop(TileArea tile_area, DoCommandFlag flags, uint invalid_dirs, bool is_drive_through, StationType station_type, Axis axis, StationID *station, RoadType rt, bool require_road);
CommandCost ret = CheckFlatLandRoadStop(roadstop_area, flags, 5 << axis, true, STATION_ROADWAYPOINT, axis, &est, INVALID_ROADTYPE, true);
extern CommandCost CheckFlatLandRoadStop(TileArea tile_area, const RoadStopSpec *spec, DoCommandFlag flags, uint invalid_dirs, bool is_drive_through, StationType station_type, Axis axis, StationID *station, RoadType rt, bool require_road);
CommandCost ret = CheckFlatLandRoadStop(roadstop_area, spec, flags, 5 << axis, true, STATION_ROADWAYPOINT, axis, &est, INVALID_ROADTYPE, true);
if (ret.Failed()) return ret;
cost.AddCost(ret);
@ -393,6 +405,9 @@ CommandCost CmdBuildRoadWaypoint(TileIndex start_tile, DoCommandFlag flags, uint
if (!Waypoint::CanAllocateItem()) return_cmd_error(STR_ERROR_TOO_MANY_STATIONS_LOADING);
}
/* Check if we can allocate a custom stationspec to this station */
if (AllocateRoadStopSpecToStation(spec, wp, false) == -1) return_cmd_error(STR_ERROR_TOO_MANY_STATION_SPECS);
if (flags & DC_EXEC) {
if (wp == nullptr) {
wp = new Waypoint(start_tile);
@ -405,6 +420,12 @@ CommandCost CmdBuildRoadWaypoint(TileIndex start_tile, DoCommandFlag flags, uint
wp->rect.BeforeAddRect(start_tile, width, height, StationRect::ADD_TRY);
if (spec != nullptr) {
/* Include this road stop spec's animation trigger bitmask
* in the station's cached copy. */
wp->cached_roadstop_anim_triggers |= spec->animation.triggers;
}
wp->delete_ctr = 0;
wp->facilities |= FACIL_BUS_STOP | FACIL_TRUCK_STOP;
wp->build_date = _date;
@ -414,6 +435,8 @@ CommandCost CmdBuildRoadWaypoint(TileIndex start_tile, DoCommandFlag flags, uint
wp->UpdateVirtCoord();
byte map_spec_index = AllocateRoadStopSpecToStation(spec, wp, true);
/* Check every tile in the area. */
for (TileIndex cur_tile : roadstop_area) {
/* Get existing road types and owners before any tile clearing */
@ -444,6 +467,8 @@ CommandCost CmdBuildRoadWaypoint(TileIndex start_tile, DoCommandFlag flags, uint
MakeDriveThroughRoadStop(cur_tile, wp->owner, road_owner, tram_owner, wp->index, STATION_ROADWAYPOINT, road_rt, tram_rt, axis);
SetDriveThroughStopDisallowedRoadDirections(cur_tile, drd);
SetCustomRoadStopSpecIndex(cur_tile, map_spec_index);
if (spec != nullptr) wp->SetRoadStopRandomBits(cur_tile, 0);
Company::Get(wp->owner)->infrastructure.station++;

@ -53,6 +53,29 @@ enum BuildRoadStationWidgets {
WID_BROS_LT_OFF, ///< Turn off area highlight.
WID_BROS_LT_ON, ///< Turn on area highlight.
WID_BROS_INFO, ///< Station acceptance info.
WID_BROS_MATRIX, ///< Matrix widget displaying all available road stops.
WID_BROS_IMAGE, ///< Panel used for each image of the matrix.
WID_BROS_MATRIX_SCROLL, ///< Scrollbar of the #WID_BROS_SHOW_NEWST_ADDITIONS.
WID_BROS_FILTER_CONTAINER, ///< Container for the filter text box for the road stop class list.
WID_BROS_FILTER_EDITBOX, ///< Filter text box for the road stop class list.
WID_BROS_SHOW_NEWST_DEFSIZE, ///< Selection for default-size button for new road stops.
WID_BROS_SHOW_NEWST_ADDITIONS, ///< Selection for new class selection list.
WID_BROS_SHOW_NEWST_MATRIX, ///< Selection for new stop image matrix.
WID_BROS_SHOW_NEWST_RESIZE, ///< Selection for panel and resize at bottom right for new stops.
WID_BROS_SHOW_NEWST_ORIENTATION, ///< Selection for the orientation string for new stops.
WID_BROS_SHOW_NEWST_TYPE_SEL, ///< Selection for the type name.
WID_BROS_SHOW_NEWST_TYPE, ///< Display of selected stop type.
WID_BROS_NEWST_LIST, ///< List with new road stops.
WID_BROS_NEWST_SCROLL, ///< Scrollbar of the #WID_BROS_NEWST_LIST.
};
/** Widgets of the #BuildRoadWaypointWindow class. */
enum BuildRoadWaypointWidgets {
WID_BROW_WAYPOINT_MATRIX, ///< Matrix with waypoints.
WID_BROW_WAYPOINT, ///< A single waypoint.
WID_BROW_SCROLL, ///< Scrollbar for the matrix.
};
#endif /* WIDGETS_ROAD_WIDGET_H */

@ -106,7 +106,7 @@ enum WindowClass {
* - 0 = #ToolTipsWidgets
*/
WC_TOOLTIPS,
/**
* Station rating tooltip window; %Window numbers:
* - 0 = #ToolTipsWidgets

Loading…
Cancel
Save