diff --git a/.gitignore b/.gitignore index 3d0376aa94f08..58223d469800e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ docs/gamedocs/* docs/source/* /out /vcpkg_installed +/.cache diff --git a/docs/Airport noise.html b/docs/Airport noise.html new file mode 100644 index 0000000000000..4717b016dc179 --- /dev/null +++ b/docs/Airport noise.html @@ -0,0 +1,35 @@ + +- no title specified
           
  

Base noise

Runway noise

       
 

Gravel

0

1

       
 

Asphalt

1

3

       
 

Water

1

0

       
           
 

Airport name

Elements

Total noise of the multi-tile airport

Original airport stats

Diference of noise

Multi-tiled – original

 

Runways

Normal aprons

Heliports/Helipads

Gravel

Asphalt

Water

Surface type

Original noise

 

Small

1

2

0

3

6

3

Gravel

3

0

 

Commuter

1

3

2

6

9

6

Asphalt

4

5

 

City

1

3

0

4

7

4

Asphalt

5

2

 

Metropolitan

2

3

0

5

10

4

Asphalt

8

2

 

Internacional

2

6

2

10

15

9

Asphalt

17

2

 

Intercontinental

4

8

2

14

23

11

Asphalt

25

2

 

Heliport

0

0

1

1

2

2

Asphalt

1

1

 

Helidepot

0

0

1

1

2

2

Asphalt

2

0

 

Helistation

0

0

3

3

4

4

Asphalt

3

1

           
           
           
           
           
           
\ No newline at end of file diff --git a/docs/Airport noise.ods b/docs/Airport noise.ods new file mode 100644 index 0000000000000..51cb3bd274f9b Binary files /dev/null and b/docs/Airport noise.ods differ diff --git a/docs/landscape.html b/docs/landscape.html index c48aedcebc2a3..331678f8091ab 100644 --- a/docs/landscape.html +++ b/docs/landscape.html @@ -520,7 +520,7 @@

Landscape

  • m2 bit 11: opposite track is reserved, too
  • -
  • m5 bit 7 set, bit 6 set: railway depot +
  • m5 bit 7 set, bit 6 clear: railway depot
  • @@ -666,7 +671,7 @@

    Landscape

    @@ -1047,55 +1061,55 @@

    Landscape

    - 10..1B  + 40..4B  canal locks - + - + - + - + - + - + - + - + - + - + - + - +
    10  40  middle part, (SW-NE direction)
    11  41  middle part, (NW-SE direction)
    12  42  middle part, (NE-SW direction)
    13  43  middle part, (SE-NW direction)
    14  44  lower part, (SW-NE direction)
    15  45  lower part, (NW-SE direction)
    16  46  lower part, (NE-SW direction)
    17  47  lower part, (SE-NW direction)
    18  48  upper part, (SW-NE direction)
    19  49  upper part, (NW-SE direction)
    1A  4A  upper part, (NE-SW direction)
    1B  4B  upper part, (SE-NW direction)
    @@ -1127,6 +1141,11 @@

    Landscape

    +
  • m5 bit 5 clear: standard depot (for depots only)
  • +
  • m5 bit 5 set: extended depot + diff --git a/docs/landscape_grid.html b/docs/landscape_grid.html index 603d78025b0d2..57b76bc42d8e3 100644 --- a/docs/landscape_grid.html +++ b/docs/landscape_grid.html @@ -79,8 +79,8 @@

    Landscape

    0 ground - XXXX XX XX - XXXX XXXX + XXXX XX XX + XXXX XXXX OOO1 OOOO OOOO OOOO OOOO OOOO XXX XOOOO @@ -95,16 +95,16 @@

    Landscape

    XXX XXXXX - 1 + 1 rail - XOOX XXXX + XOOX XXXX OOOO XXXX OOOO OOOO OOOO OOOO OOOO XXXX OO XXXXXX - OOOO OOOO - OOOO OOOO - OOOO OOOO OOXX XXXX + OOOO OOOO + OOOO OOOO + OOOO OOOO OOXX XXXX rail with signals @@ -114,22 +114,27 @@

    Landscape

    O1 XXXXXX - depot - XXXX XXXX XXXX XXXX - OOOO OOOO + standard rail depot + XXXX XXXX XXXX XXXX + OOOO OOOO OOOO XXXX - 11OX OOXX + 1OOX OOXX + + + extended rail depot + XXOO XXXX + 1O1X OOXX - 2 + 2 road OOOX XXXX XXXX XXXX XXXX XXXX XXXX XXXX - OOXX XXXX + OO XXXXXX OO XX XXXX - OO XXXOOO - OOX OXXXX + OOXX XOOO + OOXO XXXX OOOO XXXX XXOO OOOO @@ -137,20 +142,27 @@

    Landscape

    OOOX XXXX XXXX OOOO O1 X XOOOX - OO XXXOOO + OOXX XOOO OOX XXXXX OOOO XXXX XX XXXXXX - road depot - OOOX XXXX - XXXX XXXX XXXX XXXX - XXXX OOOO - 1OOO OOXX - OOOO OOOO - OOX XXXXX - OOOO XXXX XXOO OOOO + standard road depot + OOOX XXXX + XXXX XXXX XXXX XXXX + XXXX XXXX + 1OOO XXXX + XXOO OOOO + OOX XXXXX + OOOO XXXX XXOO OOOO + + extended road depot + XX XXXXXX + 1O1O XXXX + XX XXOOOO + + 3 finished house @@ -181,27 +193,60 @@

    Landscape

    OOOO OOOO OOOO OOOO - 5 + 5 rail station - OXX XXXXX - XXXX XXXX XXXX XXXX + OXX XXXXX + XXXX XXXX XXXX XXXX XXXX OOOO XXXX XXXX XXXX XXXX XXXXX XXX - XXXX XXXX + XXXX XXXX OOOO OOOO OOXX XXXX rail waypoint OOOO OOOX + + airport infrastructure + XXXX XXXX + XXXX XXXX + OOOX OOOO + XXXX XOOO + XXOO OOOO XXXX XXXX + + + airport tracks + OO1O OOOO + OO OO XXXXXX XXXXXX + + + airport apron + O1XX OOOO + XX OO XXXXXX XXXXXX + + + airport hangar + 1OXO OOOO + XX OO XXXXXX XXXXXX + + + airport runway (plain) + 11OO OOOO + X XXX XXXXXX XXXXXX + + + airport runway (start/end) + 11XX OOOO + X OXX XXXXXX XXXXXX + road stop XXXX OOOO OOXX XXXX OOOO OXXX - OXXX XOOO + OXXX XOOO OOOX XXXX OOOO XXXX XX XXXXXX @@ -210,20 +255,13 @@

    Landscape

    XXXX XXOO XOOO XXXX XXOO OOOO - - airport - XXXX OOOO - OOOO OOOO - XXXX XXXX - XXXX XXXX - OOOO OOOO OOOO OOOO - dock OOOO OOOO OOOO OOOO OOOO OXXX OOOO OOOO + OOOO OOOO OOOO OOOO buoy @@ -240,33 +278,38 @@

    Landscape

    OOOO OOOO - 6 + 6 sea, shore - X XX XXXXX + X XX XXXXX OOOO OOOO OOOO OOOO - OOOO OOOO + OOOO OOOO OOOO OOOO - OOOO OOOX - OOOO OOOO - OOOO OOOO - OOOO OOOO OOOO OOOO + OOOO OOOX + OOOO OOOO + OOOO OOOO + OOOO OOOO OOOO OOOO canal, river XXXX XXXX - OOOO OOOO + OOOO OOOO lock OOOO OOOO - OOO1 XX XX + O1OOXX XX - shipdepot - XXXX XXXX XXXX XXXX + standard ship depot + XXXX XXXX XXXX XXXX OOOO OOOO - 1OOO OOX X + 1OOOOOX X + + extended ship depot + XXOO OOOO + 1O1OOOX X + 8 finished industry diff --git a/media/baseset/openttd.grf b/media/baseset/openttd.grf index 6cee79959ece1..bfdc9639db2a3 100644 Binary files a/media/baseset/openttd.grf and b/media/baseset/openttd.grf differ diff --git a/media/baseset/openttd.grf.hash b/media/baseset/openttd.grf.hash index f3b216de533da..445bb04a808e5 100644 --- a/media/baseset/openttd.grf.hash +++ b/media/baseset/openttd.grf.hash @@ -1 +1 @@ -8bc3926cb50e19747de498357417d973 +8bf3527c85bb373fa65b8e1d6b68f388 diff --git a/media/baseset/openttd/CMakeLists.txt b/media/baseset/openttd/CMakeLists.txt index ee93ba80b19e6..5f3f684681be5 100644 --- a/media/baseset/openttd/CMakeLists.txt +++ b/media/baseset/openttd/CMakeLists.txt @@ -27,7 +27,22 @@ if(GRFCODEC_FOUND) ${CMAKE_CURRENT_SOURCE_DIR}/tunnel_portals.nfo ${CMAKE_CURRENT_SOURCE_DIR}/2ccmap.nfo PNG_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/airports.png + ${CMAKE_CURRENT_SOURCE_DIR}/airport_asphalt.png + ${CMAKE_CURRENT_SOURCE_DIR}/airport_asphalt_dark.png + ${CMAKE_CURRENT_SOURCE_DIR}/airport_asphalt_yellow.png + ${CMAKE_CURRENT_SOURCE_DIR}/airport_gravel.png ${CMAKE_CURRENT_SOURCE_DIR}/airport_preview.png + ${CMAKE_CURRENT_SOURCE_DIR}/airport_water.png + ${CMAKE_CURRENT_SOURCE_DIR}/preview_city.png + ${CMAKE_CURRENT_SOURCE_DIR}/preview_commuter.png + ${CMAKE_CURRENT_SOURCE_DIR}/preview_helidepot.png + ${CMAKE_CURRENT_SOURCE_DIR}/preview_heliport.png + ${CMAKE_CURRENT_SOURCE_DIR}/preview_helistation.png + ${CMAKE_CURRENT_SOURCE_DIR}/preview_intercontinental.png + ${CMAKE_CURRENT_SOURCE_DIR}/preview_international.png + ${CMAKE_CURRENT_SOURCE_DIR}/preview_metropolitan.png + ${CMAKE_CURRENT_SOURCE_DIR}/preview_small.png + ${CMAKE_CURRENT_SOURCE_DIR}/preview_small_temperate.png ${CMAKE_CURRENT_SOURCE_DIR}/aqueduct.png ${CMAKE_CURRENT_SOURCE_DIR}/autorail.png ${CMAKE_CURRENT_SOURCE_DIR}/canals.png diff --git a/media/baseset/openttd/airport_asphalt.png b/media/baseset/openttd/airport_asphalt.png new file mode 100755 index 0000000000000..279ed7763185a Binary files /dev/null and b/media/baseset/openttd/airport_asphalt.png differ diff --git a/media/baseset/openttd/airport_asphalt_dark.png b/media/baseset/openttd/airport_asphalt_dark.png new file mode 100755 index 0000000000000..a1d4a07d84b88 Binary files /dev/null and b/media/baseset/openttd/airport_asphalt_dark.png differ diff --git a/media/baseset/openttd/airport_asphalt_yellow.png b/media/baseset/openttd/airport_asphalt_yellow.png new file mode 100755 index 0000000000000..9f1f9ca72e4cd Binary files /dev/null and b/media/baseset/openttd/airport_asphalt_yellow.png differ diff --git a/media/baseset/openttd/airport_gravel.png b/media/baseset/openttd/airport_gravel.png new file mode 100755 index 0000000000000..9a05711ebbd51 Binary files /dev/null and b/media/baseset/openttd/airport_gravel.png differ diff --git a/media/baseset/openttd/airport_water.png b/media/baseset/openttd/airport_water.png new file mode 100755 index 0000000000000..1be69cc7e0bc4 Binary files /dev/null and b/media/baseset/openttd/airport_water.png differ diff --git a/media/baseset/openttd/airtypes.nfo b/media/baseset/openttd/airtypes.nfo new file mode 100644 index 0000000000000..1e69a71074d6d --- /dev/null +++ b/media/baseset/openttd/airtypes.nfo @@ -0,0 +1,778 @@ +// 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 . +// 129 per airtype, 5 airytpes + 36 previews + -1 * 3 05 1A FF BC 02 +// gravel icons 11 + -1 sprites/airport_gravel.png 8bpp 10 10 20 20 0 0 normal + -1 sprites/airport_gravel.png 8bpp 35 10 20 20 0 0 normal + -1 sprites/airport_gravel.png 8bpp 60 10 20 20 0 0 normal + -1 sprites/airport_gravel.png 8bpp 85 10 20 20 0 0 normal + -1 sprites/airport_gravel.png 8bpp 110 10 20 20 0 0 normal + -1 sprites/airport_gravel.png 8bpp 135 10 20 20 0 0 normal + -1 sprites/airport_gravel.png 8bpp 160 10 20 20 0 0 normal + -1 sprites/airport_gravel.png 8bpp 185 10 20 20 0 0 normal + -1 sprites/airport_gravel.png 8bpp 210 10 20 20 0 0 normal + -1 sprites/airport_gravel.png 8bpp 235 10 20 20 0 0 normal + -1 sprites/airport_gravel.png 8bpp 260 10 20 20 0 0 normal +// gravel cursors 11 + -1 sprites/airport_gravel.png 8bpp 10 35 32 32 0 0 normal + -1 sprites/airport_gravel.png 8bpp 45 35 32 32 0 0 normal + -1 sprites/airport_gravel.png 8bpp 80 35 32 32 0 0 normal + -1 sprites/airport_gravel.png 8bpp 115 35 30 32 0 0 normal + -1 sprites/airport_gravel.png 8bpp 148 35 32 32 0 0 normal + -1 sprites/airport_gravel.png 8bpp 183 35 32 32 0 0 normal + -1 sprites/airport_gravel.png 8bpp 218 35 32 32 0 0 normal + -1 sprites/airport_gravel.png 8bpp 253 35 32 32 0 0 normal + -1 sprites/airport_gravel.png 8bpp 288 35 32 32 0 0 normal + -1 sprites/airport_gravel.png 8bpp 323 35 32 32 0 0 normal + -1 sprites/airport_gravel.png 8bpp 358 35 32 32 0 0 normal +// Gravel ground 5 + -1 sprites/airport_gravel.png 8bpp 500 10 64 31 -31 0 normal // +22 + -1 sprites/airport_gravel.png 8bpp 575 10 64 31 -31 0 normal + -1 sprites/airport_gravel.png 8bpp 650 10 64 31 -31 0 normal + -1 sprites/airport_gravel.png 8bpp 725 10 64 31 -31 0 normal + -1 sprites/airport_gravel.png 8bpp 800 10 64 31 -31 0 normal +// Gravel middle runways 8 + -1 sprites/airport_gravel.png 8bpp 950 10 64 31 -31 0 normal // +27 + -1 sprites/airport_gravel.png 8bpp 875 10 64 31 -31 0 normal + -1 sprites/airport_gravel.png 8bpp 950 92 64 31 -31 0 normal + -1 sprites/airport_gravel.png 8bpp 875 133 64 31 -31 0 normal + -1 sprites/airport_gravel.png 8bpp 950 133 64 31 -31 0 normal + -1 sprites/airport_gravel.png 8bpp 875 51 64 31 -31 0 normal + -1 sprites/airport_gravel.png 8bpp 950 51 64 31 -31 0 normal + -1 sprites/airport_gravel.png 8bpp 875 92 64 31 -31 0 normal +// Gravel starting runways allow, not allow 8 + -1 sprites/airport_gravel.png 8bpp 1025 10 64 31 -31 0 normal // +35 + -1 sprites/airport_gravel.png 8bpp 1025 133 64 31 -31 0 normal + -1 sprites/airport_gravel.png 8bpp 1025 51 64 31 -31 0 normal + -1 sprites/airport_gravel.png 8bpp 1025 92 64 31 -31 0 normal + -1 sprites/airport_gravel.png 8bpp 1025 175 64 31 -31 0 normal + -1 sprites/airport_gravel.png 8bpp 1025 298 64 31 -31 0 normal + -1 sprites/airport_gravel.png 8bpp 1025 216 64 31 -31 0 normal + -1 sprites/airport_gravel.png 8bpp 1025 257 64 31 -31 0 normal +// Gravel ending runways allow, not allow 8 + -1 sprites/airport_gravel.png 8bpp 1100 10 64 31 -31 0 normal // +43 + -1 sprites/airport_gravel.png 8bpp 1100 133 64 31 -31 0 normal + -1 sprites/airport_gravel.png 8bpp 1100 51 64 31 -31 0 normal + -1 sprites/airport_gravel.png 8bpp 1100 92 64 31 -31 0 normal + -1 sprites/airport_gravel.png 8bpp 1100 175 64 31 -31 0 normal + -1 sprites/airport_gravel.png 8bpp 1100 298 64 31 -31 0 normal + -1 sprites/airport_gravel.png 8bpp 1100 216 64 31 -31 0 normal + -1 sprites/airport_gravel.png 8bpp 1100 257 64 31 -31 0 normal +// Hangars normal 4 + -1 sprites/airport_gravel.png 8bpp 500 90 64 50 -3 -33 normal // +51 + -1 sprites/airport_gravel.png 8bpp 580 90 64 50 -59 -33 normal + -1 sprites/airport_gravel.png 8bpp 660 90 64 50 -31 -19 normal + -1 sprites/airport_gravel.png 8bpp 740 90 64 50 -31 -19 normal +// Hangars snow 4 + -1 sprites/airport_gravel.png 8bpp 500 156 64 50 -3 -33 normal // +55 + -1 sprites/airport_gravel.png 8bpp 580 156 64 50 -59 -33 normal + -1 sprites/airport_gravel.png 8bpp 660 156 64 50 -31 -19 normal + -1 sprites/airport_gravel.png 8bpp 740 156 64 50 -31 -19 normal +// Hangars ground 2 + -1 sprites/airport_gravel.png 8bpp 500 240 64 31 -31 0 normal // +59 + -1 sprites/airport_gravel.png 8bpp 575 240 64 31 -31 0 normal +// Hangars back 2 + -1 sprites/airport_gravel.png 8bpp 500 290 64 30 -31 -1 normal // +61 + -1 sprites/airport_gravel.png 8bpp 575 290 64 30 -31 -1 normal +// Aprons 10 + -1 sprites/airport_gravel.png 8bpp 500 50 64 31 -31 0 normal // +63 + -1 sprites/airport_gravel.png 8bpp 575 50 64 31 -31 0 normal + -1 sprites/airport_gravel.png 8bpp 1175 10 64 87 -31 -56 normal // heliport + -1 sprites/airport_gravel.png 8bpp 1255 10 64 87 -31 -56 normal + -1 sprites/airport_gravel.png 8bpp 1335 10 64 87 -31 -56 normal + -1 sprites/airport_gravel.png 8bpp 1415 10 64 87 -31 -56 normal + -1 sprites/airport_gravel.png 8bpp 1175 105 64 87 -31 -56 normal // heliport snow + -1 sprites/airport_gravel.png 8bpp 1255 105 64 87 -31 -56 normal + -1 sprites/airport_gravel.png 8bpp 1335 105 64 87 -31 -56 normal + -1 sprites/airport_gravel.png 8bpp 1415 105 64 87 -31 -56 normal +// infrastructure: transmitter 16 + -1 sprites/airport_gravel.png 8bpp 1430 400 64 96 -31 -80 normal // transmitter + -1 sprites/airport_gravel.png 8bpp 1430 400 64 96 -31 -80 normal + -1 sprites/airport_gravel.png 8bpp 1430 400 64 96 -31 -80 normal + -1 sprites/airport_gravel.png 8bpp 1430 400 64 96 -31 -80 normal + -1 sprites/airport_gravel.png 8bpp 1430 400 64 96 -31 -80 normal // snowed transmitter (to do) + -1 sprites/airport_gravel.png 8bpp 1430 400 64 96 -31 -80 normal + -1 sprites/airport_gravel.png 8bpp 1430 400 64 96 -31 -80 normal + -1 sprites/airport_gravel.png 8bpp 1430 400 64 96 -31 -80 normal + -1 sprites/airport_gravel.png 8bpp 1175 200 50 79 -25 -68 normal // tower + -1 sprites/airport_gravel.png 8bpp 1235 200 50 79 -25 -68 normal + -1 sprites/airport_gravel.png 8bpp 1295 200 50 79 -25 -68 normal + -1 sprites/airport_gravel.png 8bpp 1355 200 50 79 -25 -68 normal + -1 sprites/airport_gravel.png 8bpp 1175 289 50 79 -25 -68 normal // snowed tower + -1 sprites/airport_gravel.png 8bpp 1235 289 50 79 -25 -68 normal + -1 sprites/airport_gravel.png 8bpp 1295 289 50 79 -25 -68 normal + -1 sprites/airport_gravel.png 8bpp 1355 289 50 79 -25 -68 normal +// Catchment buildings: 5 buildings x 4 = 20 + -1 sprites/airport_gravel.png 8bpp 430 360 64 30 -32 1 normal // 1 + -1 sprites/airport_gravel.png 8bpp 500 360 64 31 -32 0 normal + -1 sprites/airport_gravel.png 8bpp 290 360 64 36 -32 -5 normal + -1 sprites/airport_gravel.png 8bpp 360 360 64 32 -32 -1 normal + -1 sprites/airport_gravel.png 8bpp 150 360 64 47 -32 -16 normal // 2 + -1 sprites/airport_gravel.png 8bpp 220 360 64 50 -32 -19 normal + -1 sprites/airport_gravel.png 8bpp 10 360 64 36 -32 -5 normal + -1 sprites/airport_gravel.png 8bpp 80 360 64 32 -32 -1 normal + -1 sprites/airport_gravel.png 8bpp 1705 360 64 32 -32 -1 normal // 3 + -1 sprites/airport_gravel.png 8bpp 1500 360 64 57 -32 -26 normal + -1 sprites/airport_gravel.png 8bpp 1570 360 64 53 -32 -22 normal + -1 sprites/airport_gravel.png 8bpp 1640 360 64 46 -32 -15 normal + -1 sprites/airport_gravel.png 8bpp 835 400 64 36 -32 -5 normal // 4 + -1 sprites/airport_gravel.png 8bpp 910 400 64 36 -32 -5 normal + -1 sprites/airport_gravel.png 8bpp 985 400 64 36 -32 -5 normal + -1 sprites/airport_gravel.png 8bpp 1060 400 64 36 -32 -5 normal + -1 sprites/airport_gravel.png 8bpp 1140 400 64 41 -31 -16 normal // 5 + -1 sprites/airport_gravel.png 8bpp 1215 400 64 41 -31 -16 normal + -1 sprites/airport_gravel.png 8bpp 1140 400 64 41 -31 -16 normal + -1 sprites/airport_gravel.png 8bpp 1215 400 64 41 -31 -16 normal +// Snowed catchment buildings: 5 buildings x 4 = 20 + -1 sprites/airport_gravel.png 8bpp 430 427 64 30 -32 1 normal // 1 + -1 sprites/airport_gravel.png 8bpp 500 427 64 31 -32 0 normal + -1 sprites/airport_gravel.png 8bpp 290 427 64 36 -32 -5 normal + -1 sprites/airport_gravel.png 8bpp 360 427 64 32 -32 -1 normal + -1 sprites/airport_gravel.png 8bpp 150 427 64 47 -32 -16 normal // 2 + -1 sprites/airport_gravel.png 8bpp 220 427 64 50 -32 -19 normal + -1 sprites/airport_gravel.png 8bpp 10 427 64 36 -32 -5 normal + -1 sprites/airport_gravel.png 8bpp 80 427 64 32 -32 -1 normal + -1 sprites/airport_gravel.png 8bpp 1705 427 64 32 -32 -1 normal // 3 + -1 sprites/airport_gravel.png 8bpp 1500 427 64 57 -32 -26 normal + -1 sprites/airport_gravel.png 8bpp 1570 427 64 53 -32 -22 normal + -1 sprites/airport_gravel.png 8bpp 1640 427 64 46 -32 -15 normal + -1 sprites/airport_gravel.png 8bpp 835 400 64 36 -32 -5 normal // 4 + -1 sprites/airport_gravel.png 8bpp 910 400 64 36 -32 -5 normal + -1 sprites/airport_gravel.png 8bpp 985 400 64 36 -32 -5 normal + -1 sprites/airport_gravel.png 8bpp 1060 400 64 36 -32 -5 normal + -1 sprites/airport_gravel.png 8bpp 1140 400 64 41 -31 -16 normal // 5 + -1 sprites/airport_gravel.png 8bpp 1215 400 64 41 -31 -16 normal + -1 sprites/airport_gravel.png 8bpp 1140 400 64 41 -31 -16 normal + -1 sprites/airport_gravel.png 8bpp 1215 400 64 41 -31 -16 normal +// icons asphalt + -1 sprites/airport_asphalt.png 8bpp 10 10 20 20 0 0 normal + -1 sprites/airport_asphalt.png 8bpp 35 10 20 20 0 0 normal + -1 sprites/airport_asphalt.png 8bpp 60 10 20 20 0 0 normal + -1 sprites/airport_asphalt.png 8bpp 85 10 20 20 0 0 normal + -1 sprites/airport_asphalt.png 8bpp 110 10 20 20 0 0 normal + -1 sprites/airport_asphalt.png 8bpp 135 10 20 20 0 0 normal + -1 sprites/airport_asphalt.png 8bpp 160 10 20 20 0 0 normal + -1 sprites/airport_asphalt.png 8bpp 185 10 20 20 0 0 normal + -1 sprites/airport_asphalt.png 8bpp 210 10 20 20 0 0 normal + -1 sprites/airport_asphalt.png 8bpp 235 10 20 20 0 0 normal + -1 sprites/airport_asphalt.png 8bpp 260 10 20 20 0 0 normal +// cursors asphalt + -1 sprites/airport_asphalt.png 8bpp 10 35 32 32 0 0 normal + -1 sprites/airport_asphalt.png 8bpp 45 35 32 32 0 0 normal + -1 sprites/airport_asphalt.png 8bpp 80 35 32 32 0 0 normal + -1 sprites/airport_asphalt.png 8bpp 115 35 30 32 0 0 normal + -1 sprites/airport_asphalt.png 8bpp 148 35 32 32 0 0 normal + -1 sprites/airport_asphalt.png 8bpp 183 35 32 32 0 0 normal + -1 sprites/airport_asphalt.png 8bpp 218 35 32 32 0 0 normal + -1 sprites/airport_asphalt.png 8bpp 253 35 32 32 0 0 normal + -1 sprites/airport_asphalt.png 8bpp 288 35 32 32 0 0 normal + -1 sprites/airport_asphalt.png 8bpp 323 35 32 32 0 0 normal + -1 sprites/airport_asphalt.png 8bpp 358 35 32 32 0 0 normal +// Asphalt ground + -1 sprites/airport_asphalt.png 8bpp 500 10 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 575 10 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 650 10 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 725 10 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 800 10 64 31 -31 0 normal +// Asphalt middle runways + -1 sprites/airport_asphalt.png 8bpp 950 10 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 875 10 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 950 92 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 875 133 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 950 133 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 875 51 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 950 51 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 875 92 64 31 -31 0 normal +// Asphalt starting runways allow, not allow + -1 sprites/airport_asphalt.png 8bpp 1025 10 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 1025 133 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 1025 51 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 1025 92 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 1025 175 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 1025 298 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 1025 216 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 1025 257 64 31 -31 0 normal +// Asphalt ending runways allow, not allow + -1 sprites/airport_asphalt.png 8bpp 1100 10 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 1100 133 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 1100 51 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 1100 92 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 1100 175 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 1100 298 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 1100 216 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 1100 257 64 31 -31 0 normal +// Hangars normal + -1 sprites/airport_asphalt.png 8bpp 500 90 64 53 -3 -36 normal + -1 sprites/airport_asphalt.png 8bpp 575 90 64 53 -59 -36 normal + -1 sprites/airport_asphalt.png 8bpp 650 90 64 53 -33 -21 normal + -1 sprites/airport_asphalt.png 8bpp 725 90 64 53 -33 -21 normal +// Hangars snow + -1 sprites/airport_asphalt.png 8bpp 500 165 64 53 -4 -34 normal + -1 sprites/airport_asphalt.png 8bpp 575 165 64 53 -30 -35 normal + -1 sprites/airport_asphalt.png 8bpp 650 165 64 53 -32 -20 normal + -1 sprites/airport_asphalt.png 8bpp 725 165 64 53 -32 -20 normal +// Hangars ground + -1 sprites/airport_asphalt.png 8bpp 500 240 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 575 240 64 31 -31 0 normal +// Hangars back + -1 sprites/airport_asphalt.png 8bpp 500 290 64 30 -31 -1 normal + -1 sprites/airport_asphalt.png 8bpp 575 290 64 30 -31 -1 normal +// Aprons + -1 sprites/airport_asphalt.png 8bpp 500 50 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 575 50 64 31 -31 0 normal + -1 sprites/airport_asphalt.png 8bpp 1175 10 64 87 -31 -56 normal // heliport + -1 sprites/airport_asphalt.png 8bpp 1255 10 64 87 -31 -56 normal + -1 sprites/airport_asphalt.png 8bpp 1335 10 64 87 -31 -56 normal + -1 sprites/airport_asphalt.png 8bpp 1415 10 64 87 -31 -56 normal + -1 sprites/airport_asphalt.png 8bpp 1175 105 64 87 -31 -56 normal // heliport snow + -1 sprites/airport_asphalt.png 8bpp 1255 105 64 87 -31 -56 normal + -1 sprites/airport_asphalt.png 8bpp 1335 105 64 87 -31 -56 normal + -1 sprites/airport_asphalt.png 8bpp 1415 105 64 87 -31 -56 normal +// Infrastructure without catchment (transmitter) + -1 sprites/airport_asphalt.png 8bpp 1430 200 44 91 -22 -80 normal //advanced transmitter + -1 sprites/airport_asphalt.png 8bpp 1430 200 44 91 -22 -80 normal + -1 sprites/airport_asphalt.png 8bpp 1430 200 44 91 -22 -80 normal + -1 sprites/airport_asphalt.png 8bpp 1430 200 44 91 -22 -80 normal + -1 sprites/airport_asphalt.png 8bpp 1430 300 44 91 -22 -80 normal // advanced snoed transmitter + -1 sprites/airport_asphalt.png 8bpp 1430 300 44 91 -22 -80 normal + -1 sprites/airport_asphalt.png 8bpp 1430 300 44 91 -22 -80 normal + -1 sprites/airport_asphalt.png 8bpp 1430 300 44 91 -22 -80 normal + -1 sprites/airport_asphalt.png 8bpp 1175 200 50 79 -25 -68 normal // tower + -1 sprites/airport_asphalt.png 8bpp 1235 200 50 79 -25 -68 normal + -1 sprites/airport_asphalt.png 8bpp 1295 200 50 79 -25 -68 normal + -1 sprites/airport_asphalt.png 8bpp 1355 200 50 79 -25 -68 normal + -1 sprites/airport_asphalt.png 8bpp 1175 289 50 79 -25 -68 normal // snowed tower + -1 sprites/airport_asphalt.png 8bpp 1235 289 50 79 -25 -68 normal + -1 sprites/airport_asphalt.png 8bpp 1295 289 50 79 -25 -68 normal + -1 sprites/airport_asphalt.png 8bpp 1355 289 50 79 -25 -68 normal +// Catchment buildings: 5 buildings x 4 = 20 + -1 sprites/airport_asphalt.png 8bpp 10 335 64 55 -32 -24 normal // 1 + -1 sprites/airport_asphalt.png 8bpp 85 335 64 55 -32 -24 normal + -1 sprites/airport_asphalt.png 8bpp 160 335 64 55 -32 -24 normal + -1 sprites/airport_asphalt.png 8bpp 234 335 64 55 -32 -24 normal + -1 sprites/airport_asphalt.png 8bpp 310 335 64 74 -32 -43 normal // 2 + -1 sprites/airport_asphalt.png 8bpp 385 335 64 74 -32 -43 normal + -1 sprites/airport_asphalt.png 8bpp 460 335 64 74 -32 -43 normal + -1 sprites/airport_asphalt.png 8bpp 535 335 64 74 -32 -43 normal + -1 sprites/airport_asphalt.png 8bpp 615 335 42 53 -21 -28 normal // 3 + -1 sprites/airport_asphalt.png 8bpp 670 335 42 53 -21 -28 normal + -1 sprites/airport_asphalt.png 8bpp 725 335 42 53 -21 -28 normal + -1 sprites/airport_asphalt.png 8bpp 780 335 42 53 -21 -28 normal + -1 sprites/airport_asphalt.png 8bpp 835 400 64 36 -32 -5 normal // 4 + -1 sprites/airport_asphalt.png 8bpp 910 400 64 36 -32 -5 normal + -1 sprites/airport_asphalt.png 8bpp 985 400 64 36 -32 -5 normal + -1 sprites/airport_asphalt.png 8bpp 1060 400 64 36 -32 -5 normal + -1 sprites/airport_asphalt.png 8bpp 1140 400 64 41 -31 -16 normal // 5 + -1 sprites/airport_asphalt.png 8bpp 1215 400 64 41 -31 -16 normal + -1 sprites/airport_asphalt.png 8bpp 1140 400 64 41 -31 -16 normal + -1 sprites/airport_asphalt.png 8bpp 1215 400 64 41 -31 -16 normal +// Snowed catchment buildings: 5 buildings x 4 = 20 + -1 sprites/airport_asphalt.png 8bpp 10 410 64 55 -32 -24 normal // 1 + -1 sprites/airport_asphalt.png 8bpp 85 410 64 55 -32 -24 normal + -1 sprites/airport_asphalt.png 8bpp 160 410 64 55 -32 -24 normal + -1 sprites/airport_asphalt.png 8bpp 234 410 64 55 -32 -24 normal + -1 sprites/airport_asphalt.png 8bpp 310 415 64 74 -32 -43 normal // 2 + -1 sprites/airport_asphalt.png 8bpp 385 415 64 74 -32 -43 normal + -1 sprites/airport_asphalt.png 8bpp 460 415 64 74 -32 -43 normal + -1 sprites/airport_asphalt.png 8bpp 535 415 64 74 -32 -43 normal + -1 sprites/airport_asphalt.png 8bpp 615 400 42 53 -21 -28 normal // 3 + -1 sprites/airport_asphalt.png 8bpp 670 400 42 53 -21 -28 normal + -1 sprites/airport_asphalt.png 8bpp 725 400 42 53 -21 -28 normal + -1 sprites/airport_asphalt.png 8bpp 780 400 42 53 -21 -28 normal + -1 sprites/airport_asphalt.png 8bpp 835 445 64 36 -32 -5 normal // 4 + -1 sprites/airport_asphalt.png 8bpp 910 445 64 36 -32 -5 normal + -1 sprites/airport_asphalt.png 8bpp 985 445 64 36 -32 -5 normal + -1 sprites/airport_asphalt.png 8bpp 1060 445 64 36 -32 -5 normal + -1 sprites/airport_asphalt.png 8bpp 1140 450 64 41 -31 -16 normal // 5 + -1 sprites/airport_asphalt.png 8bpp 1215 450 64 41 -31 -16 normal + -1 sprites/airport_asphalt.png 8bpp 1140 450 64 41 -31 -16 normal + -1 sprites/airport_asphalt.png 8bpp 1215 450 64 41 -31 -16 normal +// water icons + -1 sprites/airport_water.png 8bpp 10 10 20 20 0 0 normal + -1 sprites/airport_water.png 8bpp 35 10 20 20 0 0 normal + -1 sprites/airport_water.png 8bpp 60 10 20 20 0 0 normal + -1 sprites/airport_water.png 8bpp 85 10 20 20 0 0 normal + -1 sprites/airport_water.png 8bpp 110 10 20 20 0 0 normal + -1 sprites/airport_water.png 8bpp 135 10 20 20 0 0 normal + -1 sprites/airport_water.png 8bpp 160 10 20 20 0 0 normal + -1 sprites/airport_water.png 8bpp 185 10 20 20 0 0 normal + -1 sprites/airport_water.png 8bpp 210 10 20 20 0 0 normal + -1 sprites/airport_water.png 8bpp 235 10 20 20 0 0 normal + -1 sprites/airport_water.png 8bpp 260 10 20 20 0 0 normal +// water cursors + -1 sprites/airport_water.png 8bpp 10 35 32 32 0 0 normal + -1 sprites/airport_water.png 8bpp 45 35 32 32 0 0 normal + -1 sprites/airport_water.png 8bpp 80 35 32 32 0 0 normal + -1 sprites/airport_water.png 8bpp 115 35 30 32 0 0 normal + -1 sprites/airport_water.png 8bpp 148 35 32 32 0 0 normal + -1 sprites/airport_water.png 8bpp 183 35 32 32 0 0 normal + -1 sprites/airport_water.png 8bpp 218 35 32 32 0 0 normal + -1 sprites/airport_water.png 8bpp 253 35 32 32 0 0 normal + -1 sprites/airport_water.png 8bpp 288 35 32 32 0 0 normal + -1 sprites/airport_water.png 8bpp 323 35 32 32 0 0 normal + -1 sprites/airport_water.png 8bpp 358 35 32 32 0 0 normal +// Water ground 5 + -1 sprites/airport_water.png 8bpp 500 10 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 575 10 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 650 10 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 725 10 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 800 10 64 31 -31 0 normal +// Water middle runways 8 + -1 sprites/airport_water.png 8bpp 950 10 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 875 10 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 950 92 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 875 133 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 950 133 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 875 51 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 950 51 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 875 92 64 31 -31 0 normal +// Water starting runways allow, not allow 8 + -1 sprites/airport_water.png 8bpp 1025 10 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 1025 133 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 1025 51 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 1025 92 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 1025 175 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 1025 298 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 1025 216 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 1025 257 64 31 -31 0 normal +// Water ending runways allow, not allow 8 + -1 sprites/airport_water.png 8bpp 1100 10 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 1100 133 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 1100 51 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 1100 92 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 1100 175 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 1100 298 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 1100 216 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 1100 257 64 31 -31 0 normal +// Hangars normal 4 + -1 sprites/airport_water.png 8bpp 500 90 64 50 -3 -33 normal + -1 sprites/airport_water.png 8bpp 580 90 64 50 -59 -33 normal + -1 sprites/airport_water.png 8bpp 660 90 64 50 -31 -19 normal + -1 sprites/airport_water.png 8bpp 740 90 64 50 -31 -19 normal +// Hangars snow 4 + -1 sprites/airport_water.png 8bpp 500 165 64 50 -3 -33 normal + -1 sprites/airport_water.png 8bpp 580 165 64 50 -59 -33 normal + -1 sprites/airport_water.png 8bpp 660 165 64 50 -31 -19 normal + -1 sprites/airport_water.png 8bpp 740 165 64 50 -31 -19 normal +// Hangars ground 2 + -1 sprites/airport_water.png 8bpp 500 240 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 575 240 64 31 -31 0 normal +// Hangars back 2 + -1 sprites/airport_water.png 8bpp 500 290 64 30 -31 -1 normal + -1 sprites/airport_water.png 8bpp 575 290 64 30 -31 -1 normal +// Aprons 10 + -1 sprites/airport_water.png 8bpp 500 50 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 575 50 64 31 -31 0 normal + -1 sprites/airport_water.png 8bpp 1175 10 64 87 -31 -56 normal // heliport + -1 sprites/airport_water.png 8bpp 1255 10 64 87 -31 -56 normal + -1 sprites/airport_water.png 8bpp 1335 10 64 87 -31 -56 normal + -1 sprites/airport_water.png 8bpp 1415 10 64 87 -31 -56 normal + -1 sprites/airport_water.png 8bpp 1175 105 64 87 -31 -56 normal // heliport snow + -1 sprites/airport_water.png 8bpp 1255 105 64 87 -31 -56 normal + -1 sprites/airport_water.png 8bpp 1335 105 64 87 -31 -56 normal + -1 sprites/airport_water.png 8bpp 1415 105 64 87 -31 -56 normal +// Infrastructure without catchment (tower) 16 + -1 sprites/airport_water.png 8bpp 1430 400 64 96 -31 -80 normal // transmitter + -1 sprites/airport_water.png 8bpp 1430 400 64 96 -31 -80 normal + -1 sprites/airport_water.png 8bpp 1430 400 64 96 -31 -80 normal + -1 sprites/airport_water.png 8bpp 1430 400 64 96 -31 -80 normal + -1 sprites/airport_water.png 8bpp 1430 400 64 96 -31 -80 normal // snowed transmitter (to do) + -1 sprites/airport_water.png 8bpp 1430 400 64 96 -31 -80 normal + -1 sprites/airport_water.png 8bpp 1430 400 64 96 -31 -80 normal + -1 sprites/airport_water.png 8bpp 1430 400 64 96 -31 -80 normal + -1 sprites/airport_water.png 8bpp 1175 200 50 79 -25 -68 normal // tower + -1 sprites/airport_water.png 8bpp 1235 200 50 79 -25 -68 normal + -1 sprites/airport_water.png 8bpp 1295 200 50 79 -25 -68 normal + -1 sprites/airport_water.png 8bpp 1355 200 50 79 -25 -68 normal + -1 sprites/airport_water.png 8bpp 1175 289 50 79 -25 -68 normal // snowed tower + -1 sprites/airport_water.png 8bpp 1235 289 50 79 -25 -68 normal + -1 sprites/airport_water.png 8bpp 1295 289 50 79 -25 -68 normal + -1 sprites/airport_water.png 8bpp 1355 289 50 79 -25 -68 normal +// Catchment buildings: 5 buildings x 4 = 20 + -1 sprites/airport_water.png 8bpp 10 335 64 55 -32 -24 normal // 1 + -1 sprites/airport_water.png 8bpp 85 335 64 55 -32 -24 normal + -1 sprites/airport_water.png 8bpp 160 335 64 55 -32 -24 normal + -1 sprites/airport_water.png 8bpp 234 335 64 55 -32 -24 normal + -1 sprites/airport_water.png 8bpp 310 335 64 74 -32 -43 normal // 2 + -1 sprites/airport_water.png 8bpp 385 335 64 74 -32 -43 normal + -1 sprites/airport_water.png 8bpp 460 335 64 74 -32 -43 normal + -1 sprites/airport_water.png 8bpp 535 335 64 74 -32 -43 normal + -1 sprites/airport_water.png 8bpp 615 335 42 53 -21 -28 normal // 3 + -1 sprites/airport_water.png 8bpp 670 335 42 53 -21 -28 normal + -1 sprites/airport_water.png 8bpp 725 335 42 53 -21 -28 normal + -1 sprites/airport_water.png 8bpp 780 335 42 53 -21 -28 normal + -1 sprites/airport_water.png 8bpp 835 400 64 36 -32 -5 normal // 4 + -1 sprites/airport_water.png 8bpp 910 400 64 36 -32 -5 normal + -1 sprites/airport_water.png 8bpp 985 400 64 36 -32 -5 normal + -1 sprites/airport_water.png 8bpp 1060 400 64 36 -32 -5 normal + -1 sprites/airport_water.png 8bpp 1140 400 64 41 -31 -16 normal // 5 + -1 sprites/airport_water.png 8bpp 1215 400 64 41 -31 -16 normal + -1 sprites/airport_water.png 8bpp 1140 400 64 41 -31 -16 normal + -1 sprites/airport_water.png 8bpp 1215 400 64 41 -31 -16 normal +// Snowed catchment buildings: 5 buildings x 4 = 20 + -1 sprites/airport_water.png 8bpp 10 410 64 55 -32 -24 normal // 1 + -1 sprites/airport_water.png 8bpp 85 410 64 55 -32 -24 normal + -1 sprites/airport_water.png 8bpp 160 410 64 55 -32 -24 normal + -1 sprites/airport_water.png 8bpp 234 410 64 55 -32 -24 normal + -1 sprites/airport_water.png 8bpp 310 415 64 74 -32 -43 normal // 2 + -1 sprites/airport_water.png 8bpp 385 415 64 74 -32 -43 normal + -1 sprites/airport_water.png 8bpp 460 415 64 74 -32 -43 normal + -1 sprites/airport_water.png 8bpp 535 415 64 74 -32 -43 normal + -1 sprites/airport_water.png 8bpp 615 400 42 53 -21 -28 normal // 3 + -1 sprites/airport_water.png 8bpp 670 400 42 53 -21 -28 normal + -1 sprites/airport_water.png 8bpp 725 400 42 53 -21 -28 normal + -1 sprites/airport_water.png 8bpp 780 400 42 53 -21 -28 normal + -1 sprites/airport_water.png 8bpp 835 445 64 36 -32 -5 normal // 4 + -1 sprites/airport_water.png 8bpp 910 445 64 36 -32 -5 normal + -1 sprites/airport_water.png 8bpp 985 445 64 36 -32 -5 normal + -1 sprites/airport_water.png 8bpp 1060 445 64 36 -32 -5 normal + -1 sprites/airport_water.png 8bpp 1140 450 64 41 -31 -16 normal // 5 + -1 sprites/airport_water.png 8bpp 1215 450 64 41 -31 -16 normal + -1 sprites/airport_water.png 8bpp 1140 450 64 41 -31 -16 normal + -1 sprites/airport_water.png 8bpp 1215 450 64 41 -31 -16 normal +// icons asphalt + -1 sprites/airport_asphalt_dark.png 8bpp 10 10 20 20 0 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 35 10 20 20 0 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 60 10 20 20 0 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 85 10 20 20 0 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 110 10 20 20 0 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 135 10 20 20 0 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 160 10 20 20 0 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 185 10 20 20 0 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 210 10 20 20 0 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 235 10 20 20 0 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 260 10 20 20 0 0 normal +// cursors asphalt + -1 sprites/airport_asphalt_dark.png 8bpp 10 35 32 32 0 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 45 35 32 32 0 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 80 35 32 32 0 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 115 35 30 32 0 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 148 35 32 32 0 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 183 35 32 32 0 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 218 35 32 32 0 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 253 35 32 32 0 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 288 35 32 32 0 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 323 35 32 32 0 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 358 35 32 32 0 0 normal +// Asphalt ground + -1 sprites/airport_asphalt_dark.png 8bpp 500 10 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 575 10 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 650 10 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 725 10 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 800 10 64 31 -31 0 normal +// Asphalt middle runways + -1 sprites/airport_asphalt_dark.png 8bpp 950 10 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 875 10 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 950 92 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 875 133 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 950 133 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 875 51 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 950 51 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 875 92 64 31 -31 0 normal +// Asphalt starting runways allow, not allow + -1 sprites/airport_asphalt_dark.png 8bpp 1025 10 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1025 133 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1025 51 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1025 92 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1025 175 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1025 298 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1025 216 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1025 257 64 31 -31 0 normal +// Asphalt ending runways allow, not allow + -1 sprites/airport_asphalt_dark.png 8bpp 1100 10 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1100 133 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1100 51 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1100 92 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1100 175 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1100 298 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1100 216 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1100 257 64 31 -31 0 normal +// Hangars normal + -1 sprites/airport_asphalt_dark.png 8bpp 500 90 64 53 -3 -36 normal + -1 sprites/airport_asphalt_dark.png 8bpp 575 90 64 53 -59 -36 normal + -1 sprites/airport_asphalt_dark.png 8bpp 650 90 64 53 -33 -21 normal + -1 sprites/airport_asphalt_dark.png 8bpp 725 90 64 53 -33 -21 normal +// Hangars snow + -1 sprites/airport_asphalt_dark.png 8bpp 500 165 64 53 -4 -34 normal + -1 sprites/airport_asphalt_dark.png 8bpp 575 165 64 53 -30 -35 normal + -1 sprites/airport_asphalt_dark.png 8bpp 650 165 64 53 -32 -20 normal + -1 sprites/airport_asphalt_dark.png 8bpp 725 165 64 53 -32 -20 normal +// Hangars ground + -1 sprites/airport_asphalt_dark.png 8bpp 500 240 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 575 240 64 31 -31 0 normal +// Hangars back + -1 sprites/airport_asphalt_dark.png 8bpp 500 290 64 30 -31 -1 normal + -1 sprites/airport_asphalt_dark.png 8bpp 575 290 64 30 -31 -1 normal +// Aprons + -1 sprites/airport_asphalt_dark.png 8bpp 500 50 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 575 50 64 31 -31 0 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1175 10 64 87 -31 -56 normal // heliport + -1 sprites/airport_asphalt_dark.png 8bpp 1255 10 64 87 -31 -56 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1335 10 64 87 -31 -56 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1415 10 64 87 -31 -56 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1175 105 64 87 -31 -56 normal // heliport snow + -1 sprites/airport_asphalt_dark.png 8bpp 1255 105 64 87 -31 -56 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1335 105 64 87 -31 -56 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1415 105 64 87 -31 -56 normal +// Infrastructure without catchment (transmitter) + -1 sprites/airport_asphalt_dark.png 8bpp 1430 200 44 91 -22 -80 normal //advanced transmitter + -1 sprites/airport_asphalt_dark.png 8bpp 1430 200 44 91 -22 -80 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1430 200 44 91 -22 -80 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1430 200 44 91 -22 -80 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1430 300 44 91 -22 -80 normal // advanced snoed transmitter + -1 sprites/airport_asphalt_dark.png 8bpp 1430 300 44 91 -22 -80 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1430 300 44 91 -22 -80 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1430 300 44 91 -22 -80 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1175 200 50 79 -25 -68 normal // tower + -1 sprites/airport_asphalt_dark.png 8bpp 1235 200 50 79 -25 -68 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1295 200 50 79 -25 -68 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1355 200 50 79 -25 -68 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1175 289 50 79 -25 -68 normal // snowed tower + -1 sprites/airport_asphalt_dark.png 8bpp 1235 289 50 79 -25 -68 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1295 289 50 79 -25 -68 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1355 289 50 79 -25 -68 normal +// Catchment buildings: 5 buildings x 4 = 20 + -1 sprites/airport_asphalt_dark.png 8bpp 10 335 64 55 -32 -24 normal // 1 + -1 sprites/airport_asphalt_dark.png 8bpp 85 335 64 55 -32 -24 normal + -1 sprites/airport_asphalt_dark.png 8bpp 160 335 64 55 -32 -24 normal + -1 sprites/airport_asphalt_dark.png 8bpp 234 335 64 55 -32 -24 normal + -1 sprites/airport_asphalt_dark.png 8bpp 310 335 64 74 -32 -43 normal // 2 + -1 sprites/airport_asphalt_dark.png 8bpp 385 335 64 74 -32 -43 normal + -1 sprites/airport_asphalt_dark.png 8bpp 460 335 64 74 -32 -43 normal + -1 sprites/airport_asphalt_dark.png 8bpp 535 335 64 74 -32 -43 normal + -1 sprites/airport_asphalt_dark.png 8bpp 615 335 42 53 -21 -28 normal // 3 + -1 sprites/airport_asphalt_dark.png 8bpp 670 335 42 53 -21 -28 normal + -1 sprites/airport_asphalt_dark.png 8bpp 725 335 42 53 -21 -28 normal + -1 sprites/airport_asphalt_dark.png 8bpp 780 335 42 53 -21 -28 normal + -1 sprites/airport_asphalt_dark.png 8bpp 835 400 64 36 -32 -5 normal // 4 + -1 sprites/airport_asphalt_dark.png 8bpp 910 400 64 36 -32 -5 normal + -1 sprites/airport_asphalt_dark.png 8bpp 985 400 64 36 -32 -5 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1060 400 64 36 -32 -5 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1140 400 64 41 -31 -16 normal // 5 + -1 sprites/airport_asphalt_dark.png 8bpp 1215 400 64 41 -31 -16 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1140 400 64 41 -31 -16 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1215 400 64 41 -31 -16 normal +// Snowed catchment buildings: 5 buildings x 4 = 20 + -1 sprites/airport_asphalt_dark.png 8bpp 10 410 64 55 -32 -24 normal // 1 + -1 sprites/airport_asphalt_dark.png 8bpp 85 410 64 55 -32 -24 normal + -1 sprites/airport_asphalt_dark.png 8bpp 160 410 64 55 -32 -24 normal + -1 sprites/airport_asphalt_dark.png 8bpp 234 410 64 55 -32 -24 normal + -1 sprites/airport_asphalt_dark.png 8bpp 310 415 64 74 -32 -43 normal // 2 + -1 sprites/airport_asphalt_dark.png 8bpp 385 415 64 74 -32 -43 normal + -1 sprites/airport_asphalt_dark.png 8bpp 460 415 64 74 -32 -43 normal + -1 sprites/airport_asphalt_dark.png 8bpp 535 415 64 74 -32 -43 normal + -1 sprites/airport_asphalt_dark.png 8bpp 615 400 42 53 -21 -28 normal // 3 + -1 sprites/airport_asphalt_dark.png 8bpp 670 400 42 53 -21 -28 normal + -1 sprites/airport_asphalt_dark.png 8bpp 725 400 42 53 -21 -28 normal + -1 sprites/airport_asphalt_dark.png 8bpp 780 400 42 53 -21 -28 normal + -1 sprites/airport_asphalt_dark.png 8bpp 835 445 64 36 -32 -5 normal // 4 + -1 sprites/airport_asphalt_dark.png 8bpp 910 445 64 36 -32 -5 normal + -1 sprites/airport_asphalt_dark.png 8bpp 985 445 64 36 -32 -5 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1060 445 64 36 -32 -5 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1140 450 64 41 -31 -16 normal // 5 + -1 sprites/airport_asphalt_dark.png 8bpp 1215 450 64 41 -31 -16 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1140 450 64 41 -31 -16 normal + -1 sprites/airport_asphalt_dark.png 8bpp 1215 450 64 41 -31 -16 normal +// icons asphalt + -1 sprites/airport_asphalt_yellow.png 8bpp 10 10 20 20 0 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 35 10 20 20 0 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 60 10 20 20 0 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 85 10 20 20 0 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 110 10 20 20 0 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 135 10 20 20 0 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 160 10 20 20 0 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 185 10 20 20 0 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 210 10 20 20 0 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 235 10 20 20 0 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 260 10 20 20 0 0 normal +// cursors asphalt + -1 sprites/airport_asphalt_yellow.png 8bpp 10 35 32 32 0 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 45 35 32 32 0 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 80 35 32 32 0 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 115 35 30 32 0 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 148 35 32 32 0 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 183 35 32 32 0 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 218 35 32 32 0 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 253 35 32 32 0 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 288 35 32 32 0 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 323 35 32 32 0 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 358 35 32 32 0 0 normal +// Asphalt ground + -1 sprites/airport_asphalt_yellow.png 8bpp 500 10 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 575 10 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 650 10 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 725 10 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 800 10 64 31 -31 0 normal +// Asphalt middle runways + -1 sprites/airport_asphalt_yellow.png 8bpp 950 10 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 875 10 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 950 92 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 875 133 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 950 133 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 875 51 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 950 51 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 875 92 64 31 -31 0 normal +// Asphalt starting runways allow, not allow + -1 sprites/airport_asphalt_yellow.png 8bpp 1025 10 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1025 133 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1025 51 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1025 92 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1025 175 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1025 298 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1025 216 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1025 257 64 31 -31 0 normal +// Asphalt ending runways allow, not allow + -1 sprites/airport_asphalt_yellow.png 8bpp 1100 10 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1100 133 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1100 51 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1100 92 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1100 175 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1100 298 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1100 216 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1100 257 64 31 -31 0 normal +// Hangars normal + -1 sprites/airport_asphalt_yellow.png 8bpp 500 90 64 53 -3 -36 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 575 90 64 53 -59 -36 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 650 90 64 53 -33 -21 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 725 90 64 53 -33 -21 normal +// Hangars snow + -1 sprites/airport_asphalt_yellow.png 8bpp 500 165 64 53 -4 -34 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 575 165 64 53 -30 -35 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 650 165 64 53 -32 -20 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 725 165 64 53 -32 -20 normal +// Hangars ground + -1 sprites/airport_asphalt_yellow.png 8bpp 500 240 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 575 240 64 31 -31 0 normal +// Hangars back + -1 sprites/airport_asphalt_yellow.png 8bpp 500 290 64 30 -31 -1 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 575 290 64 30 -31 -1 normal +// Aprons + -1 sprites/airport_asphalt_yellow.png 8bpp 500 50 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 575 50 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1175 10 64 87 -31 -56 normal // heliport + -1 sprites/airport_asphalt_yellow.png 8bpp 1255 10 64 87 -31 -56 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1335 10 64 87 -31 -56 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1415 10 64 87 -31 -56 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1175 105 64 87 -31 -56 normal // heliport snow + -1 sprites/airport_asphalt_yellow.png 8bpp 1255 105 64 87 -31 -56 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1335 105 64 87 -31 -56 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1415 105 64 87 -31 -56 normal +// Infrastructure without catchment (transmitter) + -1 sprites/airport_asphalt_yellow.png 8bpp 1430 200 44 91 -22 -80 normal //advanced transmitter + -1 sprites/airport_asphalt_yellow.png 8bpp 1430 200 44 91 -22 -80 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1430 200 44 91 -22 -80 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1430 200 44 91 -22 -80 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1430 300 44 91 -22 -80 normal // advanced snoed transmitter + -1 sprites/airport_asphalt_yellow.png 8bpp 1430 300 44 91 -22 -80 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1430 300 44 91 -22 -80 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1430 300 44 91 -22 -80 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1175 200 50 79 -25 -68 normal // tower + -1 sprites/airport_asphalt_yellow.png 8bpp 1235 200 50 79 -25 -68 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1295 200 50 79 -25 -68 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1355 200 50 79 -25 -68 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1175 289 50 79 -25 -68 normal // snowed tower + -1 sprites/airport_asphalt_yellow.png 8bpp 1235 289 50 79 -25 -68 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1295 289 50 79 -25 -68 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1355 289 50 79 -25 -68 normal +// Catchment buildings: 5 buildings x 4 = 20 + -1 sprites/airport_asphalt_yellow.png 8bpp 10 335 64 55 -32 -24 normal // 1 + -1 sprites/airport_asphalt_yellow.png 8bpp 85 335 64 55 -32 -24 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 160 335 64 55 -32 -24 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 234 335 64 55 -32 -24 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 310 335 64 74 -32 -43 normal // 2 + -1 sprites/airport_asphalt_yellow.png 8bpp 385 335 64 74 -32 -43 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 460 335 64 74 -32 -43 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 535 335 64 74 -32 -43 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 615 335 42 53 -21 -28 normal // 3 + -1 sprites/airport_asphalt_yellow.png 8bpp 670 335 42 53 -21 -28 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 725 335 42 53 -21 -28 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 780 335 42 53 -21 -28 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 835 400 64 36 -32 -5 normal // 4 + -1 sprites/airport_asphalt_yellow.png 8bpp 910 400 64 36 -32 -5 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 985 400 64 36 -32 -5 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1060 400 64 36 -32 -5 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1140 400 64 41 -31 -16 normal // 5 + -1 sprites/airport_asphalt_yellow.png 8bpp 1215 400 64 41 -31 -16 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1140 400 64 41 -31 -16 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1215 400 64 41 -31 -16 normal +// Snowed catchment buildings: 5 buildings x 4 = 20 + -1 sprites/airport_asphalt_yellow.png 8bpp 10 410 64 55 -32 -24 normal // 1 + -1 sprites/airport_asphalt_yellow.png 8bpp 85 410 64 55 -32 -24 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 160 410 64 55 -32 -24 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 234 410 64 55 -32 -24 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 310 415 64 74 -32 -43 normal // 2 + -1 sprites/airport_asphalt_yellow.png 8bpp 385 415 64 74 -32 -43 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 460 415 64 74 -32 -43 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 535 415 64 74 -32 -43 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 615 400 42 53 -21 -28 normal // 3 + -1 sprites/airport_asphalt_yellow.png 8bpp 670 400 42 53 -21 -28 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 725 400 42 53 -21 -28 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 780 400 42 53 -21 -28 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 835 445 64 36 -32 -5 normal // 4 + -1 sprites/airport_asphalt_yellow.png 8bpp 910 445 64 36 -32 -5 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 985 445 64 36 -32 -5 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1060 445 64 36 -32 -5 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1140 450 64 41 -31 -16 normal // 5 + -1 sprites/airport_asphalt_yellow.png 8bpp 1215 450 64 41 -31 -16 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1140 450 64 41 -31 -16 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 1215 450 64 41 -31 -16 normal +// 20 extra track sprites + -1 sprites/airport_asphalt_yellow.png 8bpp 10 500 64 31 -31 0 normal // x + -1 sprites/airport_asphalt_yellow.png 8bpp 80 500 64 31 -31 0 normal // y + -1 sprites/airport_asphalt_yellow.png 8bpp 150 500 64 31 -31 0 normal // cross + -1 sprites/airport_asphalt_yellow.png 8bpp 10 540 64 31 -31 0 normal // curve left + -1 sprites/airport_asphalt_yellow.png 8bpp 80 540 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 150 540 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 220 540 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 10 580 64 31 -31 0 normal // t + -1 sprites/airport_asphalt_yellow.png 8bpp 80 580 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 150 580 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 220 580 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 10 780 64 31 -31 0 normal // diag left + -1 sprites/airport_asphalt_yellow.png 8bpp 80 780 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 150 780 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 220 780 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 10 740 64 31 -31 0 normal // end + -1 sprites/airport_asphalt_yellow.png 8bpp 80 740 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 150 740 64 31 -31 0 normal + -1 sprites/airport_asphalt_yellow.png 8bpp 220 740 64 31 -31 0 normal +// previews + -1 sprites/preview_small_temperate.png 8bpp 20 20 224 130 0 0 normal + -1 sprites/preview_small_temperate.png 8bpp 20 166 224 130 0 0 normal + -1 sprites/preview_small_temperate.png 8bpp 20 312 224 130 0 0 normal + -1 sprites/preview_small_temperate.png 8bpp 20 458 224 130 0 0 normal + -1 sprites/preview_city.png 8bpp 20 20 192 114 0 0 normal + -1 sprites/preview_city.png 8bpp 20 150 192 114 0 0 normal + -1 sprites/preview_city.png 8bpp 20 280 192 114 0 0 normal + -1 sprites/preview_city.png 8bpp 20 410 192 114 0 0 normal + -1 sprites/preview_heliport.png 8bpp 20 20 64 87 0 0 normal + -1 sprites/preview_heliport.png 8bpp 100 20 64 87 0 0 normal + -1 sprites/preview_heliport.png 8bpp 180 20 64 87 0 0 normal + -1 sprites/preview_heliport.png 8bpp 260 20 64 87 0 0 normal + -1 sprites/preview_metropolitan.png 8bpp 20 20 192 114 0 0 normal + -1 sprites/preview_metropolitan.png 8bpp 20 150 192 114 0 0 normal + -1 sprites/preview_metropolitan.png 8bpp 20 280 192 114 0 0 normal + -1 sprites/preview_metropolitan.png 8bpp 20 410 192 114 0 0 normal + -1 sprites/preview_international.png 8bpp 20 20 225 138 0 0 normal + -1 sprites/preview_international.png 8bpp 20 174 225 138 0 0 normal + -1 sprites/preview_international.png 8bpp 20 328 225 138 0 0 normal + -1 sprites/preview_international.png 8bpp 20 482 225 138 0 0 normal + -1 sprites/preview_commuter.png 8bpp 20 20 144 99 0 0 normal + -1 sprites/preview_commuter.png 8bpp 20 129 144 99 0 0 normal + -1 sprites/preview_commuter.png 8bpp 20 238 144 99 0 0 normal + -1 sprites/preview_commuter.png 8bpp 20 347 144 99 0 0 normal + -1 sprites/preview_helidepot.png 8bpp 20 20 128 69 0 0 normal + -1 sprites/preview_helidepot.png 8bpp 150 20 128 85 0 0 normal + -1 sprites/preview_helidepot.png 8bpp 20 109 128 67 0 0 normal + -1 sprites/preview_helidepot.png 8bpp 150 107 128 69 0 0 normal + -1 sprites/preview_intercontinental.png 8bpp 20 20 160 85 0 0 normal + -1 sprites/preview_intercontinental.png 8bpp 20 121 160 85 0 0 normal + -1 sprites/preview_intercontinental.png 8bpp 20 222 160 85 0 0 normal + -1 sprites/preview_intercontinental.png 8bpp 20 323 160 85 0 0 normal + -1 sprites/preview_helistation.png 8bpp 20 20 192 117 0 0 normal + -1 sprites/preview_helistation.png 8bpp 214 38 192 99 0 0 normal + -1 sprites/preview_helistation.png 8bpp 20 139 192 99 0 0 normal + -1 sprites/preview_helistation.png 8bpp 214 139 192 101 0 0 normal diff --git a/media/baseset/openttd/openttd.nfo b/media/baseset/openttd/openttd.nfo index 3d59f08543285..e15401096348f 100644 --- a/media/baseset/openttd/openttd.nfo +++ b/media/baseset/openttd/openttd.nfo @@ -99,3 +99,4 @@ #include "tunnel_portals.nfo" #include "palette.nfo" #include "road_waypoints.nfo" +#include "airtypes.nfo" diff --git a/media/baseset/openttd/preview_city.png b/media/baseset/openttd/preview_city.png new file mode 100755 index 0000000000000..c0475df89dfd3 Binary files /dev/null and b/media/baseset/openttd/preview_city.png differ diff --git a/media/baseset/openttd/preview_commuter.png b/media/baseset/openttd/preview_commuter.png new file mode 100644 index 0000000000000..ba5b4b750b800 Binary files /dev/null and b/media/baseset/openttd/preview_commuter.png differ diff --git a/media/baseset/openttd/preview_helidepot.png b/media/baseset/openttd/preview_helidepot.png new file mode 100755 index 0000000000000..b37529ef51072 Binary files /dev/null and b/media/baseset/openttd/preview_helidepot.png differ diff --git a/media/baseset/openttd/preview_heliport.png b/media/baseset/openttd/preview_heliport.png new file mode 100755 index 0000000000000..ff2eb52ef1e14 Binary files /dev/null and b/media/baseset/openttd/preview_heliport.png differ diff --git a/media/baseset/openttd/preview_helistation.png b/media/baseset/openttd/preview_helistation.png new file mode 100755 index 0000000000000..e1c34c8d7eb1d Binary files /dev/null and b/media/baseset/openttd/preview_helistation.png differ diff --git a/media/baseset/openttd/preview_intercontinental.png b/media/baseset/openttd/preview_intercontinental.png new file mode 100755 index 0000000000000..8a70b3f0855ca Binary files /dev/null and b/media/baseset/openttd/preview_intercontinental.png differ diff --git a/media/baseset/openttd/preview_international.png b/media/baseset/openttd/preview_international.png new file mode 100755 index 0000000000000..4a1545efce541 Binary files /dev/null and b/media/baseset/openttd/preview_international.png differ diff --git a/media/baseset/openttd/preview_metropolitan.png b/media/baseset/openttd/preview_metropolitan.png new file mode 100755 index 0000000000000..a9ef0cc7aae03 Binary files /dev/null and b/media/baseset/openttd/preview_metropolitan.png differ diff --git a/media/baseset/openttd/preview_small.png b/media/baseset/openttd/preview_small.png new file mode 100755 index 0000000000000..f43cd0510075b Binary files /dev/null and b/media/baseset/openttd/preview_small.png differ diff --git a/media/baseset/openttd/preview_small_temperate.png b/media/baseset/openttd/preview_small_temperate.png new file mode 100755 index 0000000000000..e569b318d08f8 Binary files /dev/null and b/media/baseset/openttd/preview_small_temperate.png differ diff --git a/regression/regression/result.txt b/regression/regression/result.txt index 27a81675a9d9d..de2c1e30a5a24 100644 --- a/regression/regression/result.txt +++ b/regression/regression/result.txt @@ -810,25 +810,25 @@ ERROR: IsEnd() is invalid as Begin() is never called IsValidAirportType(0): true GetAirportWidth(0): 4 GetAirportHeight(0): 3 - GetAirportCoverageRadius(0): 4 + GetAirportCoverageRadius(0): 3 GetAirportNumHelipads(0): 0 IsAirportInformationAvailable(1): true IsValidAirportType(1): false GetAirportWidth(1): 6 GetAirportHeight(1): 6 - GetAirportCoverageRadius(1): 5 + GetAirportCoverageRadius(1): 8 GetAirportNumHelipads(1): 0 IsAirportInformationAvailable(2): true IsValidAirportType(2): false GetAirportWidth(2): 1 GetAirportHeight(2): 1 - GetAirportCoverageRadius(2): 4 - GetAirportNumHelipads(2): 1 + GetAirportCoverageRadius(2): 8 + GetAirportNumHelipads(2): 0 IsAirportInformationAvailable(3): true IsValidAirportType(3): false GetAirportWidth(3): 6 GetAirportHeight(3): 6 - GetAirportCoverageRadius(3): 6 + GetAirportCoverageRadius(3): 8 GetAirportNumHelipads(3): 0 IsAirportInformationAvailable(4): true IsValidAirportType(4): false @@ -840,25 +840,25 @@ ERROR: IsEnd() is invalid as Begin() is never called IsValidAirportType(5): false GetAirportWidth(5): 5 GetAirportHeight(5): 4 - GetAirportCoverageRadius(5): 4 + GetAirportCoverageRadius(5): 8 GetAirportNumHelipads(5): 2 IsAirportInformationAvailable(6): true IsValidAirportType(6): false GetAirportWidth(6): 2 GetAirportHeight(6): 2 - GetAirportCoverageRadius(6): 4 + GetAirportCoverageRadius(6): 8 GetAirportNumHelipads(6): 1 IsAirportInformationAvailable(7): true IsValidAirportType(7): false GetAirportWidth(7): 9 GetAirportHeight(7): 11 - GetAirportCoverageRadius(7): 10 + GetAirportCoverageRadius(7): 8 GetAirportNumHelipads(7): 2 IsAirportInformationAvailable(8): true IsValidAirportType(8): false GetAirportWidth(8): 4 GetAirportHeight(8): 2 - GetAirportCoverageRadius(8): 4 + GetAirportCoverageRadius(8): 8 GetAirportNumHelipads(8): 3 IsAirportInformationAvailable(9): false IsValidAirportType(9): false @@ -7364,7 +7364,7 @@ ERROR: IsEnd() is invalid as Begin() is never called IsBuoyTile(): false IsLockTile(): false IsCanalTile(): false - GetBankBalance(): 1999979304 + GetBankBalance(): 1999979244 BuildWaterDepot(): true BuildDock(): true BuildBuoy(): true @@ -7377,7 +7377,7 @@ ERROR: IsEnd() is invalid as Begin() is never called IsBuoyTile(): true IsLockTile(): true IsCanalTile(): true - GetBankBalance(): 1999964680 + GetBankBalance(): 1999964620 --AIWaypointList(BUOY)-- Count(): 1 @@ -7396,7 +7396,7 @@ ERROR: IsEnd() is invalid as Begin() is never called IsBuoyTile(): false IsLockTile(): false IsCanalTile(): false - GetBankBalance(): 1999959285 + GetBankBalance(): 1999959225 BuildWaterDepot(): true BuildDock(): true BuildBuoy(): true diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5f7847ff8a7bc..12e8a567af9e3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -40,14 +40,20 @@ add_files( ) add_files( + air.h + air.cpp + air_type.h aircraft.h aircraft_cmd.cpp aircraft_cmd.h aircraft_gui.cpp + air_map.h airport.cpp airport.h + airport_cmd.cpp airport_cmd.h airport_gui.cpp + airport_gui.h animated_tile.cpp animated_tile_func.h articulated_vehicles.cpp @@ -260,6 +266,8 @@ add_files( newgrf_airport.h newgrf_airporttiles.cpp newgrf_airporttiles.h + newgrf_airtype.cpp + newgrf_airtype.h newgrf_animation_base.h newgrf_animation_type.h newgrf_callbacks.h @@ -339,9 +347,14 @@ add_files( palette_func.h pbs.cpp pbs.h + pbs_air.cpp + pbs_air.h picker_func.h picker_gui.cpp picker_gui.h + platform_func.h + platform_type.h + platform.cpp progress.cpp progress.h querystring_gui.h @@ -501,6 +514,8 @@ add_files( train.h train_cmd.cpp train_cmd.h + train_placement.cpp + train_placement.h train_gui.cpp transparency.h transparency_gui.cpp diff --git a/src/air.cpp b/src/air.cpp new file mode 100644 index 0000000000000..2dd53405cedb8 --- /dev/null +++ b/src/air.cpp @@ -0,0 +1,165 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file air.cpp Implementation of air specific functions. */ + +#include "stdafx.h" +#include "station_map.h" +#include "company_func.h" +#include "company_base.h" +#include "engine_base.h" +#include "aircraft.h" + +#include "table/airtypes.h" + +/** + * Finds out if a company has a certain buildable airtype available. + * @param company the company in question + * @param airtype requested AirType + * @return true if company has requested AirType available + */ +bool HasAirTypeAvail(const CompanyID company, const AirType airtype) +{ + return !HasBit(_airtypes_hidden_mask, airtype) && HasBit(Company::Get(company)->avail_airtypes, airtype); +} + +/** + * Test if any buildable airtype is available for a company. + * @param company the company in question + * @return true if company has any AirTypes available + */ +bool HasAnyAirTypesAvail(const CompanyID company) +{ + return (Company::Get(company)->avail_airtypes & ~_airtypes_hidden_mask) != 0; +} + +/** + * Validate functions for air building. + * @param air the airtype to check. + * @return true if the current company may build the air. + */ +bool ValParamAirType(const AirType air) +{ + return air < AIRTYPE_END && HasAirTypeAvail(_current_company, air); +} + +/** + * Add the air types that are to be introduced at the given date. + * @param current The currently available airtypes. + * @param date The date for the introduction comparisons. + * @return The air types that should be available when date + * introduced air types are taken into account as well. + */ +AirTypes AddDateIntroducedAirTypes(AirTypes current, TimerGameCalendar::Date date) +{ + AirTypes rts = current; + + for (AirType rt = AIRTYPE_BEGIN; rt != AIRTYPE_END; rt++) { + const AirTypeInfo *rti = GetAirTypeInfo(rt); + /* Unused air type. */ + if (rti->label == 0) continue; + + /* Not date introduced. */ + if (!IsInsideMM(rti->introduction_date, 0, CalendarTime::MAX_DATE.base())) continue; + + /* Not yet introduced at this date. */ + if (rti->introduction_date > date) continue; + + /* Have we introduced all required airtypes? */ + AirTypes required = rti->introduction_required_airtypes; + if ((rts & required) != required) continue; + + rts |= rti->introduces_airtypes; + } + + /* When we added airtypes we need to run this method again; the added + * airtypes might enable more air types to become introduced. */ + return rts == current ? rts : AddDateIntroducedAirTypes(rts, date); +} + +/** + * Get the air types the given company can build. + * @param company the company to get the air types for. + * @param introduces If true, include air types introduced by other air types + * @return the air types. + */ +AirTypes GetCompanyAirTypes(CompanyID company, bool introduces) +{ + AirTypes rts = AIRTYPES_NONE; + + for (const Engine *e : Engine::IterateType(VEH_AIRCRAFT)) { + const EngineInfo *ei = &e->info; + + if (HasBit(ei->climates, _settings_game.game_creation.landscape) && + (HasBit(e->company_avail, company) || TimerGameCalendar::date >= e->intro_date + CalendarTime::DAYS_IN_YEAR)) { + const AircraftVehicleInfo *rvi = &e->u.air; + + assert(rvi->airtype < AIRTYPE_END); + if (introduces) { + rts |= GetAirTypeInfo(rvi->airtype)->introduces_airtypes; + } else { + SetBit(rts, rvi->airtype); + } + } + } + + if (introduces) return AddDateIntroducedAirTypes(rts, TimerGameCalendar::date); + return rts; +} + +/** + * Get list of air types, regardless of company availability. + * @param introduces If true, include air types introduced by other air types + * @return the air types. + */ +AirTypes GetAirTypes(bool introduces) +{ + AirTypes rts = AIRTYPES_NONE; + + for (const Engine *e : Engine::IterateType(VEH_AIRCRAFT)) { + const EngineInfo *ei = &e->info; + if (!HasBit(ei->climates, _settings_game.game_creation.landscape)) continue; + + const AircraftVehicleInfo *rvi = &e->u.air; + assert(rvi->airtype < AIRTYPE_END); + if (introduces) { + rts |= GetAirTypeInfo(rvi->airtype)->introduces_airtypes; + } else { + SetBit(rts, rvi->airtype); + } + } + + if (introduces) return AddDateIntroducedAirTypes(rts, CalendarTime::MAX_DATE); + return rts; +} + +/** + * Get the air type for a given label. + * @param label the airtype label. + * @param allow_alternate_labels Search in the alternate label lists as well. + * @return the airtype. + */ +AirType GetAirTypeByLabel(AirTypeLabel label, bool allow_alternate_labels) +{ + /* Loop through each air type until the label is found */ + for (AirType r = AIRTYPE_BEGIN; r != AIRTYPE_END; r++) { + const AirTypeInfo *rti = GetAirTypeInfo(r); + if (rti->label == label) return r; + } + + if (allow_alternate_labels) { + /* Test if any air type defines the label as an alternate. */ + for (AirType r = AIRTYPE_BEGIN; r != AIRTYPE_END; r++) { + const AirTypeInfo *rti = GetAirTypeInfo(r); + if (std::find(rti->alternate_labels.begin(), rti->alternate_labels.end(), label) != rti->alternate_labels.end()) return r; + } + } + + /* No matching label was found, so it is invalid */ + return INVALID_AIRTYPE; +} + diff --git a/src/air.h b/src/air.h new file mode 100644 index 0000000000000..6adc401c8b119 --- /dev/null +++ b/src/air.h @@ -0,0 +1,379 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file air.h Air specific functions. */ + +#ifndef AIR_H +#define AIR_H + +#include "track_type.h" +#include "gfx_type.h" +#include "core/bitmath_func.hpp" +#include "economy_func.h" +#include "slope_type.h" +#include "strings_type.h" +#include "air_type.h" + +#include "timer/timer_game_calendar.h" + +struct SpriteGroup; + +/** Sprite groups for a airtype. */ +enum AirTypeSpriteGroup { + ATSG_CURSORS, ///< Cursor and toolbar icon images + ATSG_OVERLAY, ///< Images for overlaying track + ATSG_GROUND, ///< Main group of ground images + ATSG_TUNNEL, ///< Main group of ground images for snow or desert + ATSG_HANGARS, ///< Depot images + ATSG_FENCES, ///< Fence images + ATSG_END, +}; + +/** + * Offsets for sprites within an overlay/underlay set. + * These are the same for overlay and underlay sprites. + */ +enum AirTrackOffset { + ATO_X, ///< Piece of air track in X direction + ATO_Y, ///< Piece of air track in Y direction + ATO_N, ///< Piece of air track in northern corner + ATO_S, ///< Piece of air track in southern corner + ATO_E, ///< Piece of air track in eastern corner + ATO_W, ///< Piece of air track in western corner +}; + +/** + * Offsets from base sprite for fence sprites. These are in the order of + * the sprites in the original data files. + */ +enum AirFenceOffset { + AFO_FLAT_X, + AFO_FLAT_Y, + AFO_FLAT_VERT, + AFO_FLAT_HORZ, +}; + +/** List of airport type labels. */ +typedef std::vector AirTypeLabelList; + +/** + * This struct contains all the info that is needed to draw and construct tracks. + */ +class AirTypeInfo { +public: + /** + * Struct containing the main sprites. @note not all sprites are listed, but only + * the ones used directly in the code + */ + struct { + SpriteID ground[20]; ///< ground sprite + SpriteID infra_catch[2][5][4]; ///< non-snowed/snowed + building number + rotation + SpriteID wind[4][4]; + SpriteID radar[12]; + SpriteID infra_no_catch[4][4]; // transmitter, snowed transmitter, tower, snowed tower + SpriteID runways[24]; // 2 normal + 1 cross + 4 ends + SpriteID aprons[10]; + SpriteID hangars[12]; + } base_sprites; + + /** + * struct containing the sprites for the airport GUI. @note only sprites referred to + * directly in the code are listed + */ + struct { + SpriteID add_airport_tiles; + SpriteID build_track_tile; + SpriteID change_airtype; + SpriteID build_catchment_infra; + SpriteID build_noncatchment_infra; + SpriteID define_landing_runway; + SpriteID define_nonlanding_runway; + SpriteID build_apron; + SpriteID build_helipad; + SpriteID build_heliport; + SpriteID build_hangar; + } gui_sprites; + + /** + * struct containing the sprites for the airport GUI. @note only sprites referred to + * directly in the code are listed + */ + struct { + SpriteID add_airport_tiles; + SpriteID build_track_tile; + SpriteID change_airtype; + SpriteID build_catchment_infra; + SpriteID build_noncatchment_infra; + SpriteID define_landing_runway; + SpriteID define_nonlanding_runway; + SpriteID build_apron; + SpriteID build_helipad; + SpriteID build_heliport; + SpriteID build_hangar; + } cursor; + + struct { + StringID name; + StringID toolbar_caption; + StringID menu_text; + StringID replace_text; + } strings; + + /** sprite number difference between a piece of track on a snowy ground and the corresponding one on normal ground */ + SpriteID snow_offset; + + /** bitmask to the OTHER airtypes on which an engine of THIS airtype can physically travel */ + AirTypes compatible_airtypes; + + /** + * Original airtype number to use when drawing non-newgrf airtypes, or when drawing stations. + */ + uint8_t fallback_airtype; + + /** + * Cost multiplier for building this air type + */ + uint16_t cost_multiplier; + + /** + * Cost multiplier for maintenance of this air type + */ + uint16_t maintenance_multiplier; + + /** + * Maximum speed for vehicles travelling on this air type + */ + uint16_t max_speed; + + /** + * Unique 32 bit air type identifier + */ + AirTypeLabel label; + + /** + * Air type labels this type provides in addition to the main label. + */ + AirTypeLabelList alternate_labels; + + /** + * Colour on mini-map + */ + uint8_t map_colour; + + /** + * Introduction date. + * When #INVALID_DATE or a vehicle using this airtype gets introduced earlier, + * the vehicle's introduction date will be used instead for this airtype. + * The introduction at this date is furthermore limited by the + * #introduction_required_types. + */ + TimerGameCalendar::Date introduction_date; + + /** + * Bitmask of airtypes that are required for this airtype to be introduced + * at a given #introduction_date. + */ + AirTypes introduction_required_airtypes; + + /** + * Bitmask of which other airtypes are introduced when this airtype is introduced. + */ + AirTypes introduces_airtypes; + + /** + * The sorting order of this airtype for the toolbar dropdown. + */ + uint8_t sorting_order; + + /** + * NewGRF providing the Action3 for the airtype. nullptr if not available. + */ + const GRFFile *grffile[ATSG_END]; + + /** + * Sprite groups for resolving sprites + */ + const SpriteGroup *group[ATSG_END]; + + /** + * Catchment area radius. + */ + uint8_t catchment_radius; + + /** + * Max number of runways. + */ + uint8_t max_num_runways; + + /** + * Minimum runway length in tiles. + */ + uint8_t min_runway_length; + + /** + * Base noise level. Each station has this noise level plus the noise created by each runway. + * Example: if base noise is 5 and there are 4 runways and runway level is 6, + * total noise level of the airport is 5 + 4 * 6 = 29 + */ + uint8_t base_noise_level; + + /** + * Runway noise level. + */ + uint8_t runway_noise_level; + + /** + * Heliport availability. + */ + bool heliport_availability; + + /** + * Build airports on water. + */ + bool build_on_water; + + + inline bool UsesOverlay() const + { + return this->group[ATSG_GROUND] != nullptr; + } + + /** + * Offset between the current airtype and normal air. This means that:

    + * 1) All the sprites in an airset MUST be in the same order. This order + * is determined by normal air. Check sprites xxxx and following for this order

    + * 2) The position where the airtype is loaded must always be the same, otherwise + * the offset will fail. + */ + inline uint GetAirTypeSpriteOffset() const + { + return 82 * this->fallback_airtype; + } +}; + + +/** + * Returns a pointer to the AirType information for a given airtype + * @param airtype the air type which the information is requested for + * @return The pointer to the AirTypeInfo + */ +static inline const AirTypeInfo *GetAirTypeInfo(const AirType airtype) +{ + extern AirTypeInfo _airtypes[AIRTYPE_END]; + assert(airtype < AIRTYPE_END); + return &_airtypes[airtype]; +} + +/** + * Checks if an engine of the given \a enginetype can drive + * on a tile with a given AirType \a tiletype. + * @return Whether the engine can drive on this tile. + * @param enginetype The AirType of the engine we are considering. + * @param tiletype The AirType of the tile we are considering. + */ +static inline bool IsCompatibleAirType(const AirType enginetype, const AirType tiletype) +{ + return HasBit(GetAirTypeInfo(enginetype)->compatible_airtypes, tiletype); +} + +/** + * Checks if an engine of the given AirType can drive + * on a tile with a given AirType. + * @return Whether the engine can drive on this tile. + * @param enginetype The AirType of the engine we are considering. + * @param tiletype The AirType of the tile we are considering. + */ +static inline AirTypes GetCompatibleAirTypes(const AirType airtype) +{ + return GetAirTypeInfo(airtype)->compatible_airtypes; +} + +/** + * Returns the cost of building the specified airtype. + * @param airtype The airtype being built. + * @return The cost multiplier. + */ +static inline Money AirBuildCost(AirType airtype) +{ + assert(airtype < AIRTYPE_END); + return (_price[PR_BUILD_STATION_AIRPORT] * GetAirTypeInfo(airtype)->cost_multiplier) >> 3; +} + +/** + * Returns the 'cost' of clearing the specified airtype. + * @param airtype The airtype being removed. + * @return The cost. + */ +static inline Money AirClearCost(AirType airtype) +{ + /* Clearing airport tiles in fact earns money, but if the build cost is set + * very low then a loophole exists where money can be made. + * In this case we limit the removal earnings to 3/4s of the build + * cost. + */ + assert(airtype < AIRTYPE_END); + return std::max(_price[PR_CLEAR_STATION_AIRPORT], -AirBuildCost(airtype) * 3 / 4); +} + +/** + * Calculates the cost of air conversion + * @param from The airtype we are converting from + * @param to The airtype we are converting to + * @return Cost per TrackBit + */ +static inline Money AirConvertCost(AirType from, AirType to) +{ + return AirBuildCost(to) + AirClearCost(from); +} + +/** + * Calculates the maintenance cost of a number of track bits. + * @param airtype The airtype to get the cost of. + * @param num Number of track bits of this airtype. + * @param total_num Total number of track bits of all airtypes. + * @return Total cost. + */ +static inline Money AirMaintenanceCost(AirType airtype, uint32_t num, uint32_t total_num) +{ + assert(airtype < AIRTYPE_END); + return (_price[PR_INFRASTRUCTURE_AIRPORT] * GetAirTypeInfo(airtype)->maintenance_multiplier * num * (1 + IntSqrt(total_num))) >> 11; // 4 bits fraction for the multiplier and 7 bits scaling. +} + +static inline bool DoesHaveWaterCompatibleAirTypes(AirTypes airtypes) +{ + return (airtypes & AIRTYPES_WATER) != 0; +} + +static inline bool AreHeliportsAvailable(AirType airtype) +{ + return GetAirTypeInfo(airtype)->heliport_availability; +} + +Foundation GetAirFoundation(Slope tileh, TrackBits bits); + + +bool HasAirTypeAvail(const CompanyID company, const AirType AirType); +bool HasAnyAirTypesAvail(const CompanyID company); +bool ValParamAirType(const AirType Air); + +AirTypes AddDateIntroducedAirTypes(AirTypes current, TimerGameCalendar::Date date); + +AirTypes GetCompanyAirTypes(CompanyID company, bool introduces = true); +AirTypes GetAirTypes(bool introduces); + +AirType GetAirTypeByLabel(AirTypeLabel label, bool allow_alternate_labels = true); + +void ResetAirTypes(); +void InitAirTypes(); +AirType AllocateAirType(AirTypeLabel label); + +extern std::vector _sorted_airtypes; +extern AirTypes _airtypes_hidden_mask; + +void AfterLoadSetAirportTileTypes(); + +#endif /* AIR_H */ diff --git a/src/air_map.h b/src/air_map.h new file mode 100644 index 0000000000000..139aaa966f429 --- /dev/null +++ b/src/air_map.h @@ -0,0 +1,1109 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file air_map.h Hides the direct accesses to the map array with map accessors. */ + +#ifndef AIR_MAP_H +#define AIR_MAP_H + +#include "air_type.h" +#include "depot_type.h" +#include "track_func.h" +#include "tile_map.h" +#include "station_map.h" +#include "viewport_func.h" +#include "table/airporttile_ids.h" + +extern bool _show_airport_tracks; + +/** + * Set the airport type of an airport tile. + * @param t Tile to modify. + * @param type New type for the tile: gravel, asphalt, ... + * @pre IsAirportTile + */ +static inline void SetAirType(Tile t, AirType type) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(type < AIRTYPE_END); + SB(t.m3(), 0, 4, type); +} + +/** + * Get the airport type of an airport tile. + * @param t Tile to get the type of. + * @return The type of the tile: gravel, asphalt, ... + * @pre IsAirportTile + */ +static inline AirType GetAirType(Tile t) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + AirType type = (AirType)GB(t.m3(), 0, 4); + assert(type < AIRTYPE_END); + return type; +} + +/** + * Set the airport tile type of an airport tile. + * @param t Tile to modify. + * @param type Type for the tile: hangar, runway, ... + * @pre IsAirportTile + */ +static inline void SetAirportTileType(Tile t, AirportTileType type) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(type < ATT_END); + SB(t.m5(), 4, ATT_NUM_BITS, type); +} + +/** + * Get the airport tile type of an airport tile. + * @param t Tile to get the type of. + * @return The type of the tile. + * @pre IsAirportTile + */ +static inline AirportTileType GetAirportTileType(Tile t) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + AirportTileType type = (AirportTileType)(GB(t.m5(), 4, ATT_NUM_BITS)); + assert(type < ATT_END); + return type; +} + +/** + * Check if a tile is a plain airport tile. + * @param t Tile to check. + * @return The type of the tile. + * @pre IsAirportTile + */ +static inline bool IsSimpleTrack(TileIndex t) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + + return GetAirportTileType(t) == ATT_SIMPLE_TRACK; +} + +/** + * Check if a tile is infrastructure of an airport. + * @param t Tile to check. + * @return The type of the tile. + * @pre IsAirportTile + */ +static inline bool IsInfrastructure(Tile t) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + + return GB(t.m5(), 8 - ATT_INFRA_LAYOUT_NUM_BITS, ATT_INFRA_LAYOUT_NUM_BITS) == ATT_INFRA_LAYOUT_BITS; +} + +/** + * Check if a tile can contain tracks for aircraft. + * @param t Tile to check. + * @return Whether the tile may contain airport tracks. + * @pre IsAirportTile + */ +static inline bool MayHaveAirTracks(TileIndex t) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + + return !IsInfrastructure(t); +} + +/** + * Infrastructure may be part of the catchment tiles of the station or not (buildings/radars). + * @param t Tile to modify. + * @param catchment Whether the tile should be marked as getting/delivering cargo. + * @pre IsInfrastructure + */ +static inline void SetCatchmentAirportType(Tile t, bool catchment) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(IsInfrastructure(t)); + + SB(t.m5(), 4, 1, catchment); +} + +/** + * Get whether the tile has catchment or not. + * @param t Tile to get the accessibility of. + * @return Whether the tile is marked as getting/delivering cargo. + * @pre IsInfrastructure + */ +static inline bool GetCatchmentAirportType(Tile t) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(IsInfrastructure(t)); + + return GB(t.m5(), 4, 1); +} + +/** + * Set the apron type of an airport tile. + * @param t Tile to modify. + * @param type Type of apron. + * @pre IsApron + */ +static inline void SetApronType(Tile t, ApronType type) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + + assert(IsApron(t)); + + assert(type < APRON_END); + + SB(t.m5(), 4, 2, type); +} + +/** + * Is a given tile a plane apron? + * @param t Tile to get the type of. + * @return True if it is a plane apron. + * @pre IsApron + */ +static inline bool IsPlaneApron(TileIndex t) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(IsApron(t)); + + return GetApronType(t) == APRON_APRON; +} + +/** + * Is this tile a basic plane apron? + * @param t the tile to get the information from. + * @return true if and only if the tile is a plane apron. + */ +static inline bool IsPlaneApronTile(TileIndex t) +{ + return IsTileType(t, MP_STATION) && + IsAirport(t) && + IsApron(t) && + IsPlaneApron(t); +} + +/** + * Is a given tile a heliport or a built-in heliport? + * @param t Tile to get the type of. + * @return True if it is a heliport. + * @pre IsApron + */ +static inline bool IsHeliport(TileIndex t) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(IsApron(t)); + + ApronType type = GetApronType(t); + + return type == APRON_HELIPORT || type == APRON_BUILTIN_HELIPORT; +} + +/** + * Is a given tile a heliport or a built-in heliport? + * @param t Tile to get the type of. + * @return True if it is a heliport. + */ +static inline bool IsHeliportTile(TileIndex t) +{ + assert(IsValidTile(t)); + + return IsTileType(t, MP_STATION) && + IsAirport(t) && + IsApron(t) && + IsHeliport(t); +} + +/** + * Is a given tile a helipad? + * @param t Tile to get the type of. + * @return True if it is a helipad. + * @pre IsApron + */ +static inline bool IsHelipad(TileIndex t) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(IsApron(t)); + + return GetApronType(t) == APRON_HELIPAD; +} + +/** + * Is this tile a helipad? + * @param t the tile to get the information from. + * @return true if and only if the tile is a helipad. + */ +static inline bool IsHelipadTile(TileIndex t) +{ + return IsTileType(t, MP_STATION) && + IsAirport(t) && + IsApron(t) && + IsHelipad(t); +} + +/** + * Get landing height for aircraft. + * @param t the tile to get the information from. + * @return landing height for aircraft. + */ +static inline int GetLandingHeight(TileIndex t) +{ + assert(IsTileType(t, MP_STATION) && IsAirport(t)); + + if (!IsApron(t)) return 0; + + switch (GetApronType(t)) { + case APRON_HELIPORT: + return 60; + case APRON_BUILTIN_HELIPORT: + return 54; + default: + return 0; + } +} + +/** + * Has this tile airport catchment? + * @param t the tile to get the information from. + * @return true if and only if the tile adds for airport catchment. + * @pre IsAirportTile + */ +static inline bool HasAirportCatchment(TileIndex t) +{ + assert(IsAirportTile(t)); + + return ((IsInfrastructure(t) && GetCatchmentAirportType(t)) || IsHeliportTile(t)); +} + +/** + * Set the rotation of an airport tile (see SetHangarDirection). + * @param t Tile to modify. + * @param dir Rotation. + * @pre IsAirportTile && (IsInfrastructure || IsApron) + */ +static inline void SetAirportTileRotation(Tile t, DiagDirection dir) +{ + assert(IsAirportTile(t)); + assert(IsApron(t) || IsInfrastructure(t)); + SB(t.m8(), 14, 2, dir); +} + +/** + * Get the hangar direction. + * @param t Tile to check. + * @return The exit direction of the hangar. + * @pre IsAirportTile && (IsInfrastructure || IsApron) + */ +static inline DiagDirection GetAirportTileRotation(Tile t) +{ + assert(IsAirportTile(t)); + assert(IsApron(t) || IsInfrastructure(t)); + return (DiagDirection)GB(t.m8(), 14, 2); + +} + +/** + * Is a given tile a runway? + * @param t Tile to check. + * @return True if it is a runway. + * @pre IsAirportTile + */ +static inline bool IsRunway(Tile t) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + + return GB(t.m5(), 8 - ATT_RUNWAY_LAYOUT_NUM_BITS, ATT_RUNWAY_LAYOUT_NUM_BITS) == ATT_RUNWAY_LAYOUT_BITS; +} + +/** + * Is a given tile a runway extreme? + * @param t Tile to check. + * @return True if it is a runway extreme. + * @pre IsAirportTile + */ +static inline bool IsRunwayExtreme(TileIndex t) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + + return IsRunway(t) && GetAirportTileType(t) != ATT_RUNWAY_MIDDLE; +} + +/** + * Is a given tile a starting runway? + * @param t Tile to check. + * @return True if it is the start of a runway. + * @pre IsAirportTile + */ +static inline bool IsRunwayStart(Tile t) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + + return GB(t.m5(), 8 - ATT_RUNWAY_START_LAYOUT_NUM_BITS, ATT_RUNWAY_START_LAYOUT_NUM_BITS) == ATT_RUNWAY_START_LAYOUT_BITS; +} + +/** + * Is a given tile an ending runway? + * @param t Tile to check. + * @return True if it is the ending of a runway. + * @pre IsAirportTile + */ +static inline bool IsRunwayEnd(TileIndex t) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + + return GetAirportTileType(t) == ATT_RUNWAY_END; +} + +/** + * Is a given tile the middle section of a runway? + * @param t Tile to check. + * @return True if it is a runway middle tile. + * @pre IsAirportTile + */ +static inline bool IsPlainRunway(TileIndex t) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + + return IsRunway(t) && GetAirportTileType(t) == ATT_RUNWAY_MIDDLE; +} + +/** + * Set the runway reservation bit. + * @param t Tile to set. + * @param reserve new state for the runway reservation. + * @pre IsRunway + */ +static inline void SetReservationAsRunway(Tile t, bool reserve) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(IsRunway(t)); + + SB(t.m8(), 15, 1, reserve); +} + +/** + * Check if a runway is reserved (as a runway). + * @param t Tile to check. + * @return True iff it is reserved. + * @pre IsRunway + */ +static inline bool GetReservationAsRunway(Tile t) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(IsRunway(t)); + + return HasBit(t.m8(), 15); +} + +/** + * Set the allow landing bit on a runway start/end. + * @param t Tile to check. + * @param landing True iff runway should allow landing planes. + * @pre IsRunwayExtreme + */ +static inline void SetLandingType(Tile t, bool landing) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(IsRunwayExtreme(t)); + + SB(t.m5(), 4, 1, landing); +} + +/** + * Is a given tile a starting runway where landing is allowed? + * @param t Tile to check. + * @return True if landing is allowed. + * @pre IsRunwayExtreme + */ +static inline bool IsLandingTypeTile(Tile t) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(IsRunwayExtreme(t)); + + return HasBit(t.m5(), 4); +} + + +/** + * Get the direction of a runway. + * @param t Tile to check. + * @return Direction of the runway. + * @pre IsRunwayExtreme + */ +static inline DiagDirection GetRunwayExtremeDirection(Tile t) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(IsRunwayExtreme(t)); + + return (DiagDirection)GB(t.m8(), 12, 2); +} + +/** + * Get the two bits for a runway middle section. + * @param t Tile to inspect. + * @return the directions of the runway. + * @pre IsPlainRunway + */ +static inline Direction GetPlainRunwayDirections(Tile t) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(IsPlainRunway(t)); + + return (Direction)GB(t.m8(), 12, 3); +} + +/** + * Get the runway tracks of a tile. + * @param t Tile to get the type of. + * @return Runway tracks of this tile. + * @pre IsRunway + */ +static inline TrackdirBits GetRunwayTrackdirs(TileIndex t) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(IsRunway(t)); + + Direction dir; + if (IsPlainRunway(t)) { + dir = GetPlainRunwayDirections(t); + } else { + dir = DiagDirToDir(GetRunwayExtremeDirection(t)); + } + + static TrackdirBits dir_to_trackdirbits[] = { + TRACKDIR_BIT_Y_NW | TRACKDIR_BIT_X_NE, + TRACKDIR_BIT_X_NE, + TRACKDIR_BIT_X_NE | TRACKDIR_BIT_Y_SE, + TRACKDIR_BIT_Y_SE, + TRACKDIR_BIT_Y_SE | TRACKDIR_BIT_X_SW, + TRACKDIR_BIT_X_SW, + TRACKDIR_BIT_X_SW | TRACKDIR_BIT_Y_NW, + TRACKDIR_BIT_Y_NW, + }; + return dir_to_trackdirbits[dir]; +} + +static inline TrackBits GetRunwayTracks(TileIndex t) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(IsRunway(t)); + + TrackdirBits trackdirs = GetRunwayTrackdirs(t); + + return TrackdirBitsToTrackBits(trackdirs); +} + +/** + * Set the direction of a runway. + * @param t Tile to check. + * @param dir Direction of the runway. + * @pre IsRunwayExtreme + */ +static inline void SetRunwayExtremeDirection(Tile t, DiagDirection dir) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(IsRunwayExtreme(t)); + + SB(t.m8(), 12, 2, dir); +} + +/** + * Set the directions for a runway middle section. + * @param t Tile to set. + * @param dir the directions for the runway tile. + * @pre IsPlainRunway + */ +static inline void AddPlainRunwayDirections(Tile t, DiagDirection dir, bool first) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(IsPlainRunway(t)); + + if (first) { + SB(t.m8(), 12, 3, DiagDirToDir(dir)); + } else { + Direction pre_dir = GetPlainRunwayDirections(t); + Direction add_dir = DiagDirToDir(dir); + assert(IsDiagonalDirection(pre_dir)); + if (pre_dir < add_dir) Swap(add_dir, pre_dir); + assert(((uint)DirToDiagDir(pre_dir) + (uint)DirToDiagDir(add_dir)) % 2 == 1); + if (add_dir + 2 == pre_dir) { + SB(t.m8(), 12, 3, add_dir + 1); + } else if (pre_dir == DIR_NW && add_dir == DIR_NE) { + SB(t.m8(), 12, 3, DIR_N); + } else { + NOT_REACHED(); + } + } +} + +/** + * Set the directions for a runway middle section. + * @param t Tile to set. + * @param dir the directions for the runway tile. + * @return true if tile is no more a runway. + * @pre IsPlainRunway + */ +static inline bool RemovePlainRunwayDirections(Tile t, DiagDirection dir) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(IsPlainRunway(t)); + + Direction cur_dir = GetPlainRunwayDirections(t); + Direction remove_dir = DiagDirToDir(dir); + + if (remove_dir == cur_dir) { + SB(t.m8(), 12, 4, 0); + SetAirportTileType(t, ATT_SIMPLE_TRACK); + return true; + } else if ((cur_dir + 1) % DIR_END == remove_dir) { + SB(t.m8(), 12, 3, (cur_dir - 1) & (DIR_END - 1)); + return false; + } else if (cur_dir == (remove_dir + 1) % DIR_END) { + SB(t.m8(), 12, 3, (cur_dir + 1) % DIR_END); + return false; + } + + NOT_REACHED(); +} + +/** + * Set the airport tracks a given tile has + * (runway tracks are stored in another place). + * @param t Tile to modify. + * @param tracks Tracks this tile has. + * @pre MayHaveAirTracks and !IsHangar + */ +static inline void SetAirportTileTracks(Tile t, TrackBits tracks) +{ + assert(IsAirportTile(t)); + assert(MayHaveAirTracks(t)); + + SB(t.m8(), 0, 6, tracks); +} + +/** + * Set the hangar direction. + * @param t Tile to modify. + * @param dir Exit direction of the hangar. + * @pre IsHangar + */ +static inline void SetHangarDirection(Tile t, DiagDirection dir) +{ + assert(IsHangar(t)); + SB(t.m8(), 14, 2, dir); +} + +/** + * Get the hangar direction. + * @param t Tile to check. + * @return the exit direction of the hangar. + * @pre IsHangar + */ +static inline DiagDirection GetHangarDirection(Tile t) +{ + assert(IsHangar(t)); + return (DiagDirection)GB(t.m8(), 14, 2); + +} + +/** + * Set whether the hangar is an extended one. + * @param t Tile to modify. + * @param is_extended Whether the hangar is an extended hangar. + * @pre IsHangar + */ +static inline void SetExtendedHangar(Tile t, bool is_extended) +{ + assert(IsHangar(t)); + SB(t.m5(), 5, 1, is_extended); +} + +/** + * Check if it a tile is an extended hangar. + * @param t Tile. + * @return true if it is an extended hangar. + * @pre IsHangar + */ +static inline bool IsExtendedHangar(Tile t) +{ + assert(IsHangar(t)); + return GB(t.m5(), 5, 1); +} + +/** + * Check if it a tile is a standard hangar. + * @param t Tile. + * @return true if extended hangar. + * @pre IsHangar + */ +static inline bool IsStandardHangar(TileIndex t) +{ + assert(IsHangar(t)); + return !IsExtendedHangar(t); +} + +/** + * Return true if tile is an extended hangar. + * @param t Tile. + * @return true if extended hangar. + * @pre IsHangar + */ +static inline bool IsExtendedHangarTile(TileIndex t) +{ + assert(IsAirport(t)); + return IsHangar(t) && IsExtendedHangar(t); +} + +/** + * Return true if tile is a standard hangar. + * @param t Tile. + * @return true if standard hangar. + * @pre IsHangar + */ +static inline bool IsStandardHangarTile(TileIndex t) +{ + assert(IsAirport(t)); + return IsHangar(t) && !IsExtendedHangar(t); +} + +/** + * Get the tracks a given tile has. + * @param t Tile to get the tracks of. + * @pre MayHaveAirTracks + */ +static inline TrackBits GetAirportTileTracks(Tile t) +{ + assert(MayHaveAirTracks(t)); + + return (TrackBits)GB(t.m8(), 0, 6); +} + +/** + * Get the tracks a given tile has. + * @param t Tile to get the tracks of. + * @pre MayHaveAirTracks + */ +static inline bool HasAirportTileSomeTrack(TileIndex t) +{ + assert(MayHaveAirTracks(t)); + + return GetAirportTileTracks(t) != TRACK_BIT_NONE; +} + +/** + * Check if a tile has a given airport track. + * @param t Tile to check. + * @param track Track to check. + * @return True iff tile has given track. + * @pre MayHaveAirTracks + */ +static inline bool HasAirportTileTrack(TileIndex t, Track track) +{ + assert(MayHaveAirTracks(t)); + return HasTrack(GetAirportTileTracks(t), track); +} + +/** + * Return the reserved airport track bits of the tile. + * @param t Tile to query. + * @return Reserved trackbits. + * @pre MayHaveAirTracks + */ +static inline TrackBits GetReservedAirportTracks(Tile t) +{ + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(MayHaveAirTracks(t)); + + return (TrackBits)GB(t.m8(), 6, 6); +} + +/** + * Check if a given track is reserved. + * @param t Tile to query. + * @param track Track to check. + * @return True iff the track is reserved on tile t. + * @pre MayHaveAirTracks + */ +static inline bool HasAirportTrackReserved(TileIndex t, Track track) +{ + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(MayHaveAirTracks(t)); + + return HasTrack(GetReservedAirportTracks(t), track); +} + +/** + * Check if an airport tile has any reserved track. + * @param t Tile to query. + * @return True iff the track is reserved on tile t. + * @pre MayHaveAirTracks + */ +static inline bool HasAirportTrackReserved(TileIndex t) +{ + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(MayHaveAirTracks(t)); + + return GetReservedAirportTracks(t) != 0; +} + +/** + * Are some of this tracks reserved? + * @param t Tile to check. + * @param tracks Tracks to check. + * @return True if any of the given tracks \a tracks is reserved on tile \a t. + */ +static inline bool HasAirportTracksReserved(TileIndex t, TrackBits tracks) +{ + assert(IsAirportTile(t)); + assert(MayHaveAirTracks(t)); + return (GetReservedAirportTracks(t) & tracks) != TRACK_BIT_NONE; +} + +/** + * Set the reserved tracks of an airport tile. + * @param t Tile where to reserve. + * @param trackbits The tracks that will be reserved on tile \a tile + * @pre MayHaveAirTracks + */ +static inline bool SetAirportTracksReservation(Tile t, TrackBits tracks) +{ + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(MayHaveAirTracks(t)); + + TrackBits already_set = GetReservedAirportTracks(t); + if ((tracks & ~already_set) == 0) return false; + + tracks |= already_set; + + SB(t.m8(), 6, 6, tracks); + + if (_show_airport_tracks) MarkTileDirtyByTile(t); + return true; +} + +/** + * Reserve an airport track on a tile. + * @param t Tile where to reserve. + * @param track The track that will be reserved on tile \a tile + * @pre MayHaveAirTracks + */ +static inline void SetAirportTrackReservation(TileIndex t, Track track) +{ + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(MayHaveAirTracks(t)); + + SetAirportTracksReservation(t, TrackToTrackBits(track)); +} + +/** + * Remove an airport track on a tile. + * @param t Tile where to free the reserved track. + * @param track The track that will be freed on tile \a tile + * return whether the track has been removed + * @pre MayHaveAirTracks + */ +static inline bool RemoveAirportTrackReservation(Tile t, Track track) +{ + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(MayHaveAirTracks(t)); + + TrackBits reserved = GetReservedAirportTracks(t); + TrackBits tracks = TrackToTrackBits(track); + if ((tracks & reserved) == TRACK_BIT_NONE) return false; + reserved &= ~tracks; + + SB(t.m8(), 6, 6, reserved); + if (_show_airport_tracks) MarkTileDirtyByTile(t); + + return true; +} + +/** + * Are some of this tracks reserved? + * @param t Tile to check. + * @param tracks Tracks to check. + * @return True if any of the given tracks \a tracks is reserved on tile \a t. + */ +static inline bool HasAirportTileAnyReservation(TileIndex t) +{ + assert(IsAirportTile(t)); + assert(MayHaveAirTracks(t)); + return (GetReservedAirportTracks(t) != TRACK_BIT_NONE) || + (IsRunway(t) && GetReservationAsRunway(t)); +} + +/** + * Whether the gfx of the tile are controlled through its airtype. + * @param t Tile. + * @return whether it is an airtype controlled airtype. + */ +static inline bool HasAirtypeGfx(Tile t) +{ + assert(IsAirportTile(t)); + return (bool)GB(t.m6(), 7, 1); +} + +/** + * Set the gfx type of the tile. + * @param t Tile to set the gfx type of. + * @param airtype_controlled whether the gfx of the tile are handled via its airtype. + */ +static inline void SetAirGfxType(Tile t, bool airtype_controlled) +{ + assert(IsAirportTile(t)); + SB(t.m6(), 7, 1, airtype_controlled); +} + +/** + * Get the gfx_id a given tile has. + * @param t Tile to get the gfx of. + */ +static inline AirportTiles GetTileAirportGfx(Tile t) +{ + assert(IsAirportTile(t)); + return (AirportTiles)GB(t.m4(), 0, 8); +} + +/** + * Set the gfx_id a given tile has. + * @param t Tile to set the gfx of. + */ +static inline void SetTileAirportGfx(Tile t, AirportTiles at) +{ + assert(IsAirportTile(t)); + SB(t.m4(), 0, 8, at); +} + +/** + * Get the sprite for an airport tile. + * @param t Tile to get the sprite of. + * @return AirportTile ID. + */ +StationGfx GetAirportGfx(TileIndex t); + +StationGfx GetTranslatedAirportTileID(StationGfx gfx); + +/** + * Set the gfx_id a given tile has for an airtype. + * @param t Tile to set the gfx of. + * @pre IsInfrastructure + */ +static inline void SetAirportGfxForAirtype(Tile t, AirportTiles at) +{ + assert(IsAirportTile(t)); + assert(IsInfrastructure(t)); + SB(t.m8(), 0, 8, at); +} + +/** + * Get the gfx_id a given tile has for an airtype. + * @param t Tile to get the gfx of. + */ +static inline AirportTiles GetAirportGfxForAirtype(Tile t) +{ + assert(IsAirportTile(t)); + return (AirportTiles)GB(t.m8(), 0, 8); +} + +/** + * Return whether is an airport tile of a given station. + * @param t tile. + * @param st_id ID of the station. + */ +static inline bool IsAirportTileOfStation(TileIndex t, StationID st_id) +{ + assert(IsValidTile(t)); + + return IsAirportTile(t) && st_id == GetStationIndex(t); +} + +/** + * Airport ground types. Valid densities in comments after the enum. + */ +enum AirportGround { + AG_GRASS = 0, ///< 0-3 + AG_SNOW = 1, ///< 0-3 + AG_DESERT = 2, ///< 1,3 + AG_AIRTYPE = 3, ///< 0-3 snow density +}; + +/** + * Get the type of airport ground to draw on a tile. + * @param t the tile to get the airport ground type of + * @pre IsAirportTile(t) + * @return the ground type + */ +inline AirportGround GetAirportGround(Tile t) +{ + assert(IsAirportTile(t)); + return (AirportGround)GB(t.m5(), 0, 2); +} + +/** + * Get the ground density of an airport tile. + * @param t the tile to get the density of + * @pre IsAirportTile(t) + * @return the density + */ +inline uint GetAirportGroundDensity(Tile t) +{ + assert(IsAirportTile(t)); + return GB(t.m5(), 2, 2); +} + +/** + * Get whether an airport tile has snow. + * @param t the tile to check + * @pre IsAirportTile(t) + * @return whether the tile has snow. + */ +inline bool HasAirportGroundSnow(Tile t) +{ + assert(IsAirportTile(t)); + AirportGround airport_ground = GetAirportGround(t); + return (airport_ground == AG_SNOW || airport_ground == AG_AIRTYPE) && GetAirportGroundDensity(t) != 0; +} + +/** + * Increment the ground density of an airport tile. + * @param t the tile to increment the density of + * @param d the amount to increment the density with + * @pre IsAirportTile(t) + */ +inline void AddAirportGroundDensity(Tile t, int d) +{ + assert(IsAirportTile(t)); + SB(t.m5(), 2, 2, GetAirportGroundDensity(t) + d); +} + +/** + * Set the ground density of an airport tile. + * @param t the tile to set the density of + * @param d the new density + * @pre IsAirportTile(t) + */ +inline void SetAirportGroundDensity(Tile t, uint d) +{ + assert(IsAirportTile(t)); + SB(t.m5(), 2, 2, d); +} + +/** + * Get the counter used to advance to the next ground density. + * @param t the tile to get the counter of + * @return the value of the counter + * @pre IsAirportTile(t) + */ +inline uint GetAirportGroundCounter(Tile t) +{ + assert(IsAirportTile(t)); + return GB(t.m6(), 0, 3); +} + +/** + * Increments the counter used to advance to the next ground density. + * @param t the tile to increment the counter of + * @param c the amount to increment the counter with + * @pre IsAirportTile(t) + */ +inline void AddAirportGroundCounter(Tile t, int c) +{ + assert(IsAirportTile(t)); + SB(t.m6(), 0, 3, GetAirportGroundCounter(t) + c); +} + +/** + * Sets the counter used to advance to the next ground density. + * @param t the tile to set the counter of + * @param c the amount to set the counter to + * @pre IsAirportTile(t) + */ +inline void SetAirportGroundCounter(Tile t, uint c) +{ + assert(IsAirportTile(t)); + SB(t.m6(), 0, 3, c); +} + + +/** + * Sets ground type and density in one go. Also sets the counter to 0. + * @param t the tile to set the ground type and density for + * @param type the new ground type of the tile + * @param density the density of the ground tile + * @pre IsAirportTile(t) + */ +inline void SetAirportGroundAndDensity(Tile t, AirportGround type, uint density) +{ + assert(IsAirportTile(t)); + SB(t.m5(), 0, 2, type); + SB(t.m5(), 2, 2, density); + SetAirportGroundCounter(t, 0); +} + +#endif /* AIR_MAP_H */ diff --git a/src/air_type.h b/src/air_type.h new file mode 100644 index 0000000000000..a16a53a6ef0ce --- /dev/null +++ b/src/air_type.h @@ -0,0 +1,100 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file air_type.h The different types of air tracks. */ + +#ifndef AIR_TYPE_H +#define AIR_TYPE_H + +#include "core/enum_type.hpp" + +typedef uint32_t AirTypeLabel; + +static const AirTypeLabel AIRTYPE_LABEL_GRAVEL = 'GRVL'; +static const AirTypeLabel AIRTYPE_LABEL_ASPHALT = 'ASPH'; +static const AirTypeLabel AIRTYPE_LABEL_WATER = 'WATR'; +static const AirTypeLabel AIRTYPE_LABEL_ASPHALT_DARK = 'ASPD'; +static const AirTypeLabel AIRTYPE_LABEL_ASPHALT_YELLOW = 'ASPY'; + + +/** Enumeration for all possible airtypes. */ +enum AirType : uint8_t { + AIRTYPE_BEGIN = 0, ///< Used for iterations + AIRTYPE_GRAVEL = 0, ///< Gravel surface + AIRTYPE_ASPHALT = 1, ///< Asphalt surface + AIRTYPE_WATER = 2, ///< Water surface + AIRTYPE_DARK = 3, + AIRTYPE_YELLOW = 4, + AIRTYPE_END = 16, ///< Used for iterations + INVALID_AIRTYPE = 0xFF, ///< Flag for invalid airtype + + DEF_AIRTYPE_FIRST = AIRTYPE_END, ///< Default airtype: first available + DEF_AIRTYPE_LAST, ///< Default airtype: last available + DEF_AIRTYPE_MOST_USED, ///< Default airtype: most used +}; + +/** Allow incrementing of airtype variables */ +DECLARE_POSTFIX_INCREMENT(AirType) + +/** The different airtypes we support, but then a bitmask of them. */ +enum AirTypes : uint64_t { + AIRTYPES_NONE = 0, ///< No rail types + AIRTYPES_GRAVEL = 1 << AIRTYPE_GRAVEL, ///< Gravel surface + AIRTYPES_ASPHALT = 1 << AIRTYPE_ASPHALT, ///< Asphalt surface + AIRTYPES_WATER = 1 << AIRTYPE_WATER, ///< Water surface + AIRTYPES_DARK = 1 << AIRTYPE_DARK, ///< Dark surface + AIRTYPES_YELLOW = 1 << AIRTYPE_YELLOW, ///< Yellow surface + AIRTYPES_ALL = AIRTYPES_GRAVEL | AIRTYPES_ASPHALT | AIRTYPES_WATER | AIRTYPES_DARK | AIRTYPES_YELLOW, + INVALID_AIRTYPES = UINT16_MAX, ///< Invalid airtypes +}; +DECLARE_ENUM_AS_BIT_SET(AirTypes) + +/** Types of tiles an airport can have. */ +enum AirportTileType : uint8_t { + ATT_BEGIN = 0, + ATT_INFRASTRUCTURE_NO_CATCH = ATT_BEGIN, // 0000 + ATT_INFRASTRUCTURE_WITH_CATCH = 1, // 0001 + ATT_SIMPLE_TRACK = 2, // 0010 + ATT_WAITING_POINT = 3, // 0011 + ATT_APRON_NORMAL = 4, // 0100 + ATT_APRON_HELIPAD = 5, // 0101 + ATT_APRON_HELIPORT = 6, // 0110 + ATT_APRON_BUILTIN_HELIPORT = 7, // 0111 + ATT_HANGAR_STANDARD = 8, // 1000 + ATT_HANGAR_EXTENDED = 10, // 1010 + ATT_RUNWAY_MIDDLE = 12, // 1100 + ATT_RUNWAY_END = 13, // 1101 + ATT_RUNWAY_START_NO_LANDING = 14, // 1110 + ATT_RUNWAY_START_ALLOW_LANDING = 15, // 1111 + ATT_END, + + ATT_INVALID, + + ATT_NUM_BITS = 4, + ATT_INFRA_LAYOUT_NUM_BITS = 3, + ATT_INFRA_LAYOUT_BITS = 0, + ATT_APRON_LAYOUT_NUM_BITS = 2, + ATT_APRON_LAYOUT_BITS = 1, + ATT_HANGAR_LAYOUT_NUM_BITS = 2, + ATT_HANGAR_LAYOUT_BITS = 2, + ATT_RUNWAY_LAYOUT_NUM_BITS = 2, + ATT_RUNWAY_LAYOUT_BITS = 3, + ATT_RUNWAY_START_LAYOUT_NUM_BITS = 3, + ATT_RUNWAY_START_LAYOUT_BITS = 7, +}; + +enum ApronType : uint8_t { + APRON_BEGIN = 0, + APRON_APRON = APRON_BEGIN, + APRON_HELIPAD, + APRON_HELIPORT, + APRON_BUILTIN_HELIPORT, + APRON_END, + APRON_INVALID = APRON_END, +}; + +#endif /* AIR_TYPE_H */ diff --git a/src/aircraft.h b/src/aircraft.h index 6c47d6df56037..a0044c1dd1978 100644 --- a/src/aircraft.h +++ b/src/aircraft.h @@ -31,11 +31,11 @@ enum AircraftSubType { AIR_HELICOPTER = 0, ///< an helicopter AIR_AIRCRAFT = 2, ///< an airplane AIR_SHADOW = 4, ///< shadow of the aircraft - AIR_ROTOR = 6, ///< rotor of an helicopter + AIR_ROTOR = 6, ///< rotor of a helicopter }; /** Flags for air vehicles; shared with disaster vehicles. */ -enum AirVehicleFlags { +enum AirVehicleFlags : uint8_t { VAF_DEST_TOO_FAR = 0, ///< Next destination is too far away. /* The next two flags are to prevent stair climbing of the aircraft. The idea is that the aircraft @@ -44,20 +44,20 @@ enum AirVehicleFlags { VAF_IN_MAX_HEIGHT_CORRECTION = 1, ///< The vehicle is currently lowering its altitude because it hit the upper bound. VAF_IN_MIN_HEIGHT_CORRECTION = 2, ///< The vehicle is currently raising its altitude because it hit the lower bound. - VAF_HELI_DIRECT_DESCENT = 3, ///< The helicopter is descending directly at its destination (helipad or in front of hangar) + VAF_CAN_T_LAND = 3, ///< The vehicle cannot land on destination airport. }; static const int ROTOR_Z_OFFSET = 5; ///< Z Offset between helicopter- and rotorsprite. -void HandleAircraftEnterHangar(Aircraft *v); void GetAircraftSpriteSize(EngineID engine, uint &width, uint &height, int &xoffs, int &yoffs, EngineImageType image_type); -void UpdateAirplanesOnNewStation(const Station *st); void UpdateAircraftCache(Aircraft *v, bool update_range = false); -void AircraftLeaveHangar(Aircraft *v, Direction exit_dir); -void AircraftNextAirportPos_and_Order(Aircraft *v); +void AircraftUpdateNextPos(Aircraft *v); void SetAircraftPosition(Aircraft *v, int x, int y, int z); +void AssignLandingTile(Aircraft *v, TileIndex tile); +TileIndex FindClosestLandingTile(Aircraft *v); + void GetAircraftFlightLevelBounds(const Vehicle *v, int *min, int *max); template int GetAircraftFlightLevel(T *v, bool takeoff = false); @@ -68,15 +68,153 @@ struct AircraftCache { uint16_t cached_max_range; ///< Cached maximum range. }; +enum AircraftStateBits : uint8_t { + ASB_FLYING_CRASHING = 3, + ASB_FLYING_ON_AIRPORT = 4, + ASB_FREE_FLIGHT = 5, + ASB_ON_HOLD = 6, + ASB_NO_HARD_LIMIT_SPEED = 7, +}; + +/** States of aircraft. */ +enum AircraftState : uint8_t { + AS_FLYING_CRASHING = 1 << ASB_FLYING_CRASHING, + AS_FLYING_FREE_FLIGHT = 1 << ASB_FREE_FLIGHT, + AS_FLYING_ON_AIRPORT = 1 << ASB_FLYING_ON_AIRPORT, + AS_ON_HOLD = 1 << ASB_ON_HOLD, + AS_NO_HARD_LIMIT_SPEED = 1 << ASB_NO_HARD_LIMIT_SPEED, + + AS_BEGIN = 0, + AS_HANGAR = AS_BEGIN, + AS_IDLE = 1, + AS_TERMINAL_BEGIN, + AS_APRON = AS_TERMINAL_BEGIN, + AS_HELIPAD = 3, + AS_HELIPORT = 4, + AS_BUILTIN_HELIPORT = 5, + AS_TERMINAL_END = AS_BUILTIN_HELIPORT, + AS_MOVING = 6, + AS_RUNNING = AS_MOVING, + + AS_START_TAKEOFF = 7, + AS_TAKEOFF_BEFORE_FLYING = 8, + AS_LANDED = 9 | AS_NO_HARD_LIMIT_SPEED, + + /* Flying while keeping some reserved track on the airport. */ + AS_FLYING_TAKEOFF = AS_FLYING_ON_AIRPORT, + AS_FLYING_HELICOPTER_TAKEOFF, + AS_DESCENDING = AS_FLYING_ON_AIRPORT | AS_NO_HARD_LIMIT_SPEED, + AS_FLYING_LANDING, + AS_FLYING_HELICOPTER_LANDING, + AS_ON_HOLD_APPROACHING = AS_FLYING_ON_AIRPORT | AS_NO_HARD_LIMIT_SPEED | AS_ON_HOLD, + + /* Flying free with no reservation on any airport tile. */ + AS_FLYING = AS_FLYING_FREE_FLIGHT | AS_NO_HARD_LIMIT_SPEED, + AS_FLYING_FALLING, + AS_FLYING_NO_DEST, + AS_FLYING_LEAVING_AIRPORT, + AS_ON_HOLD_WAITING = AS_FLYING_FREE_FLIGHT | AS_FLYING_ON_AIRPORT | AS_NO_HARD_LIMIT_SPEED | AS_ON_HOLD, + + AS_FLYING_MASK = AS_FLYING_FREE_FLIGHT | AS_FLYING_ON_AIRPORT, + + INVALID_AS = 0xFF, + + /* Helicopter rotor animation states. */ + HRS_ROTOR_STOPPED = 0, + HRS_ROTOR_MOVING_1 = 1, + HRS_ROTOR_MOVING_2 = 2, + HRS_ROTOR_MOVING_3 = 3, + HRS_ROTOR_NUM_STATES = 3, +}; +DECLARE_ENUM_AS_ADDABLE(AircraftState) + +inline bool IsTerminalState(AircraftState as) +{ + return as >= AS_TERMINAL_BEGIN && as <= AS_TERMINAL_END; +} + +enum AircraftPos : uint8_t { + AP_BEGIN = 0, + AP_DEFAULT = AP_BEGIN, + AP_HELICOPTER_HOLD_START, + AP_HELICOPTER_HOLD_2, + AP_HELICOPTER_HOLD_3, + AP_HELICOPTER_HOLD_4, + AP_HELICOPTER_HOLD_5, + AP_HELICOPTER_HOLD_6, + AP_HELICOPTER_HOLD_7, + AP_HELICOPTER_HOLD_END, + AP_HELIPORT_DEST, + AP_BUILTIN_HELIPORT_DEST, + AP_START_TAKE_OFF, + AP_PLANE_BEFORE_FLYING, + AP_PLANE_START_FLYING, + AP_PLANE_LEAVE_AIRPORT, + AP_PLANE_HOLD_START, + AP_PLANE_HOLD_2, + AP_PLANE_HOLD_3, + AP_PLANE_HOLD_4, + AP_PLANE_HOLD_5, + AP_PLANE_HOLD_6, + AP_PLANE_HOLD_7, + AP_PLANE_HOLD_8, + AP_PLANE_HOLD_END, + AP_PLANE_DESCENDING, + AP_PLANE_LANDING, + + AP_END +}; +DECLARE_ENUM_AS_ADDABLE(AircraftPos) + +/** + * Struct that contains the offsets in x and y of a position + * that an aircraft must reach calculated from its destination tile. + */ +struct AircraftPosition { + AircraftPos pos; + int x; + int y; +}; + +struct AircraftPathChoice { + std::deque td; + std::deque tile; ///< Kept for debugging purposes. Should be removed in the future. + + inline bool empty() const { return this->td.empty(); } + + inline size_t size() const + { + assert(this->td.size() == this->tile.size()); + return this->td.size(); + } + + inline void clear() + { + this->td.clear(); + this->tile.clear(); + } + + inline void pop_front() + { + assert(!this->empty()); + this->td.pop_front(); + this->tile.pop_front(); + } +}; + /** * Aircraft, helicopters, rotors and their shadows belong to this class. */ struct Aircraft final : public SpecializedVehicle { - uint16_t crashed_counter; ///< Timer for handling crash animations. - uint8_t pos; ///< Next desired position of the aircraft. - uint8_t previous_pos; ///< Previous desired position of the aircraft. + AircraftPathChoice path; ///< Cached path choices + uint16_t crashed_counter; ///< Timer for handling crash animations. + Trackdir trackdir; ///< Current trackdir while aircraft is on land. + AircraftState state; ///< Current aircraft state. @see AircraftState StationID targetairport; ///< Airport to go to next. - uint8_t state; ///< State of the airport. @see AirportMovementStates + + Trackdir next_trackdir; ///< Desired trackdir when rotating at airport, or entry trackdir to an airport while flying. + AircraftPosition next_pos; ///< next x_pos and y_pos coordinate. + Direction last_direction; uint8_t number_consecutive_turns; ///< Protection to prevent the aircraft of making a lot of turns in order to reach a specific point. uint8_t turn_counter; ///< Ticks between each turn to prevent > 45 degree turns. @@ -103,9 +241,26 @@ struct Aircraft final : public SpecializedVehicle { bool IsInDepot() const override { assert(this->IsPrimaryVehicle()); - return (this->vehstatus & VS_HIDDEN) != 0 && IsHangarTile(this->tile); + return this->state == AS_HANGAR; + } + + Trackdir GetVehicleTrackdir() const override + { + assert(this->IsPrimaryVehicle()); + return this->trackdir; + } + + TileIndex GetNextTile() const + { + assert(this->IsPrimaryVehicle()); + return this->Next()->dest_tile; } + StationID GetCurrentAirportID() const; + Station *GetCurrentAirport() const; + void UpdateNextTile(TileIndex tile); + void SetDestTile(TileIndex tile) override; + bool Tick() override; void OnNewCalendarDay() override; void OnNewEconomyDay() override; @@ -113,6 +268,7 @@ struct Aircraft final : public SpecializedVehicle { TileIndex GetOrderStationLocation(StationID station) override; TileIndex GetCargoTile() const override { return this->First()->tile; } ClosestDepot FindClosestDepot() override; + TileIndex GetOrderHangarLocation(DepotID depot); /** * Check if the aircraft type is a normal flying device; eg @@ -128,6 +284,11 @@ struct Aircraft final : public SpecializedVehicle { return this->subtype <= AIR_AIRCRAFT; } + inline bool IsHelicopter() const + { + return this->subtype == AIR_HELICOPTER; + } + /** * Get the range of this aircraft. * @return Range in tiles or 0 if unlimited range. @@ -136,6 +297,67 @@ struct Aircraft final : public SpecializedVehicle { { return this->acache.cached_max_range; } + + /** + * Check whether the vehicle is flying. + * @return True if the vehicle is currently flying: from taking off until landing. + */ + bool IsAircraftFlying() const + { + assert(this->IsNormalAircraft()); + return (this->state & AS_FLYING_MASK) != 0; + } + + /** + * Check whether the vehicle is flying and has no reserved tile on any airport. + * @return True if the vehicle is currently freely flying. + */ + bool IsAircraftFreelyFlying() const + { + assert(this->IsNormalAircraft()); + return HasBit(this->state, ASB_FREE_FLIGHT); + } + + /** + * Check whether the vehicle is flying and falling, about to crash. + * @return True if the vehicle is currently flying and falling. + */ + bool IsAircraftFalling() const + { + assert(this->IsNormalAircraft()); + return (this->state == AS_FLYING_FALLING); + } + + /** + * Check whether the vehicle is flying rotating around its destination. + * @return True if the vehicle is currently flying around its destination. + */ + bool IsAircraftOnHold() const + { + assert(this->IsNormalAircraft()); + return HasBit(this->state, ASB_ON_HOLD); + } + + void SetWaitTime(uint16_t wait_counter) + { + this->wait_counter = wait_counter; + } + + void ClearWaitTime() + { + this->SetWaitTime(0); + } + + bool IsWaiting() const + { + return this->wait_counter > 0; + } + + void AdvanceWaitTime() + { + assert (this->IsWaiting()); + this->wait_counter--; + } }; void GetRotorImage(const Aircraft *v, EngineImageType image_type, VehicleSpriteSeq *result); diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp index 44d76650a171b..d81a6afa37d3c 100644 --- a/src/aircraft_cmd.cpp +++ b/src/aircraft_cmd.cpp @@ -11,10 +11,9 @@ */ #include "stdafx.h" +#include "air.h" #include "aircraft.h" -#include "landscape.h" #include "news_func.h" -#include "newgrf_engine.h" #include "newgrf_sound.h" #include "spritecache.h" #include "error_func.h" @@ -23,24 +22,23 @@ #include "window_func.h" #include "timer/timer_game_calendar.h" #include "timer/timer_game_economy.h" +#include "strings_func.h" #include "vehicle_func.h" #include "sound_func.h" -#include "cheat_type.h" -#include "company_base.h" #include "ai/ai.hpp" #include "game/game.hpp" -#include "company_func.h" #include "effectvehicle_func.h" -#include "station_base.h" -#include "engine_base.h" -#include "core/random_func.hpp" -#include "core/backup_type.hpp" #include "zoom_func.h" #include "disaster_vehicle.h" #include "newgrf_airporttiles.h" #include "framerate_type.h" #include "aircraft_cmd.h" #include "vehicle_cmd.h" +#include "air_map.h" +#include "pbs_air.h" + +#include "pathfinder/yapf/yapf.h" +#include "pathfinder/follow_track.hpp" #include "table/strings.h" @@ -58,15 +56,9 @@ void Aircraft::UpdateDeltaXY() case AIR_AIRCRAFT: case AIR_HELICOPTER: - switch (this->state) { - default: break; - case ENDTAKEOFF: - case LANDING: - case HELILANDING: - case FLYING: - this->x_extent = 24; - this->y_extent = 24; - break; + if (this->IsAircraftFlying()) { + this->x_extent = 24; + this->y_extent = 24; } this->z_extent = 5; break; @@ -83,12 +75,41 @@ void Aircraft::UpdateDeltaXY() } } -static bool AirportMove(Aircraft *v, const AirportFTAClass *apc); -static bool AirportSetBlocks(Aircraft *v, const AirportFTA *current_pos, const AirportFTAClass *apc); -static bool AirportHasBlock(Aircraft *v, const AirportFTA *current_pos, const AirportFTAClass *apc); -static bool AirportFindFreeTerminal(Aircraft *v, const AirportFTAClass *apc); -static bool AirportFindFreeHelipad(Aircraft *v, const AirportFTAClass *apc); -static void CrashAirplane(Aircraft *v); +void Aircraft::MarkDirty() +{ + this->colourmap = PAL_NONE; + this->UpdateViewport(true, false); + if (this->subtype == AIR_HELICOPTER) { + GetRotorImage(this, EIT_ON_MAP, &this->Next()->Next()->sprite_cache.sprite_seq); + } +} + +/** + * Sets the visibility of an aircraft when it enters or leaves a hangar. + * @param v Aircraft + * @param visible Whether it should be visible or not. + */ +void SetVisibility(Aircraft *v, bool visible) +{ + assert(IsHangarTile(v->tile)); + + if (visible) { + v->vehstatus &= ~VS_HIDDEN; + v->Next()->vehstatus &= ~VS_HIDDEN; + if (v->IsHelicopter()) v->Next()->Next()->vehstatus &= ~VS_HIDDEN; + } else { + v->vehstatus |= VS_HIDDEN; + v->Next()->vehstatus |= VS_HIDDEN; + /* Hide and stop rotor for helicopters. */ + if (v->IsHelicopter()) { + v->Next()->Next()->vehstatus |= VS_HIDDEN; + v->Next()->Next()->cur_speed = 0; + } + } + + v->UpdateViewport(true, true); + v->UpdatePosition(); +} static const SpriteID _aircraft_sprite[] = { 0x0EB5, 0x0EBD, 0x0EC5, 0x0ECD, @@ -106,71 +127,6 @@ bool IsValidImageIndex(uint8_t image_index) return image_index < lengthof(_aircraft_sprite); } -/** Helicopter rotor animation states */ -enum HelicopterRotorStates { - HRS_ROTOR_STOPPED, - HRS_ROTOR_MOVING_1, - HRS_ROTOR_MOVING_2, - HRS_ROTOR_MOVING_3, -}; - -/** - * Find the nearest hangar to v - * INVALID_STATION is returned, if the company does not have any suitable - * airports (like helipads only) - * @param v vehicle looking for a hangar - * @return the StationID if one is found, otherwise, INVALID_STATION - */ -static StationID FindNearestHangar(const Aircraft *v) -{ - uint best = 0; - StationID index = INVALID_STATION; - TileIndex vtile = TileVirtXY(v->x_pos, v->y_pos); - const AircraftVehicleInfo *avi = AircraftVehInfo(v->engine_type); - uint max_range = v->acache.cached_max_range_sqr; - - /* Determine destinations where it's coming from and where it's heading to */ - const Station *last_dest = nullptr; - const Station *next_dest = nullptr; - if (max_range != 0) { - if (v->current_order.IsType(OT_GOTO_STATION) || - (v->current_order.IsType(OT_GOTO_DEPOT) && (v->current_order.GetDepotActionType() & ODATFB_NEAREST_DEPOT) == 0)) { - last_dest = Station::GetIfValid(v->last_station_visited); - next_dest = Station::GetIfValid(v->current_order.GetDestination()); - } else { - last_dest = GetTargetAirportIfValid(v); - next_dest = Station::GetIfValid(v->GetNextStoppingStation().value); - } - } - - for (const Station *st : Station::Iterate()) { - if (st->owner != v->owner || !(st->facilities & FACIL_AIRPORT) || !st->airport.HasHangar()) continue; - - const AirportFTAClass *afc = st->airport.GetFTA(); - - /* don't crash the plane if we know it can't land at the airport */ - if ((afc->flags & AirportFTAClass::SHORT_STRIP) && (avi->subtype & AIR_FAST) && !_cheats.no_jetcrash.value) continue; - - /* the plane won't land at any helicopter station */ - if (!(afc->flags & AirportFTAClass::AIRPLANES) && (avi->subtype & AIR_CTOL)) continue; - - /* Check if our last and next destinations can be reached from the depot airport. */ - if (max_range != 0) { - uint last_dist = (last_dest != nullptr && last_dest->airport.tile != INVALID_TILE) ? DistanceSquare(st->airport.tile, last_dest->airport.tile) : 0; - uint next_dist = (next_dest != nullptr && next_dest->airport.tile != INVALID_TILE) ? DistanceSquare(st->airport.tile, next_dest->airport.tile) : 0; - if (last_dist > max_range || next_dist > max_range) continue; - } - - /* v->tile can't be used here, when aircraft is flying v->tile is set to 0 */ - uint distance = DistanceSquare(vtile, st->airport.tile); - if (distance < best || index == INVALID_STATION) { - best = distance; - index = st->index; - } - } - return index; -} - void Aircraft::GetImage(Direction direction, EngineImageType image_type, VehicleSpriteSeq *result) const { uint8_t spritenum = this->spritenum; @@ -260,6 +216,145 @@ void GetAircraftSpriteSize(EngineID engine, uint &width, uint &height, int &xoff yoffs = UnScaleGUI(rect.top); } +/** + * Get the station ID of the airport where the aircraft is in. + * @return the current aiport id if the aircraft is in, or INVALID_STATION if the aircraft is flying. + */ +StationID Aircraft::GetCurrentAirportID() const +{ + assert(this->IsPrimaryVehicle()); + if (this->state > AS_MOVING) return INVALID_STATION; + + assert(IsAirportTile(this->tile)); + return GetStationIndex(this->tile); +} + +/** + * Returns aircraft's target station if its target + * is a valid station with an airport. + * @param v Aircraft to get target airport for + * @return pointer to target station, nullptr if invalid + */ +Station *GetTargetAirportIfValid(const Aircraft *v) +{ + Station *st = Station::GetIfValid(v->targetairport); + if (st == nullptr) return nullptr; + + return st->airport.tile == INVALID_TILE ? nullptr : st; +} + +TileIndex Aircraft::GetOrderStationLocation(StationID station) +{ + if (station == this->last_station_visited) this->last_station_visited = INVALID_STATION; + + assert(Station::IsValidID(station)); + Station *st = Station::Get(station); + + if (!CanVehicleUseStation(this, st)) { + this->IncrementRealOrderIndex(); + this->Next()->dest_tile = INVALID_TILE; + return 0; + } + + if (!st->airport.aprons.empty()) return st->airport.aprons[0]; + + assert(this->IsHelicopter()); + + if (!st->airport.helipads.empty()) return st->airport.helipads[0]; + + assert(!st->airport.heliports.empty()); + return st->airport.heliports[0]; +} + +TileIndex Aircraft::GetOrderHangarLocation(DepotID depot) +{ + assert(Depot::IsValidID(depot)); + Depot *dep = Depot::Get(depot); + assert(dep->veh_type == VEH_AIRCRAFT); + if (!dep->depot_tiles.empty()) { + TileIndex tile = dep->depot_tiles[0]; + assert(IsAirportTile(tile) && IsHangar(tile)); + Station *st = Station::GetByTile(tile); + if (CanVehicleUseStation(this, st)) return tile; + } + + this->IncrementRealOrderIndex(); + return 0; +} + +/** + * Find the nearest hangar for an aircraft. + * @param v vehicle looking for a hangar + * @return the StationID of the closest airport with a hangar; otherwise, INVALID_STATION. + */ +static StationID FindClosestHangar(const Aircraft *v) +{ + uint best = 0; + StationID index = INVALID_STATION; + /* revise: they are not clamped */ + TileIndex vtile = TileVirtXY(v->x_pos, v->y_pos); + uint max_range = v->acache.cached_max_range_sqr; + + /* Determine destinations where it's coming from and where it's heading to */ + const Station *last_dest = nullptr; + const Station *next_dest = nullptr; + if (max_range != 0) { + if (v->current_order.IsType(OT_GOTO_STATION) || + (v->current_order.IsType(OT_GOTO_DEPOT) && (v->current_order.GetDepotActionType() & ODATFB_NEAREST_DEPOT) == 0)) { + last_dest = Station::GetIfValid(v->last_station_visited); + next_dest = Station::GetIfValid(GetTargetDestination(v->current_order, true)); + } else { + last_dest = GetTargetAirportIfValid(v); + next_dest = Station::GetIfValid(v->GetNextStoppingStation().value); // revise getnextstoppingstation could ignore depot orders + } + } + + for (const Station *st : Station::Iterate()) { + if (st->owner != v->owner || !CanVehicleUseStation(v, st) || !st->airport.HasHangar()) continue; + + /* Check if our last and next destinations can be reached from the depot airport. */ + if (max_range != 0) { + if (last_dest != nullptr && + (last_dest->facilities & FACIL_AIRPORT) && + DistanceSquare(st->airport.tile, last_dest->airport.tile) > max_range) continue; + if (next_dest != nullptr && + (next_dest->facilities & FACIL_AIRPORT) && + DistanceSquare(st->airport.tile, next_dest->airport.tile) > max_range) continue; + } + + uint distance = DistanceSquare(vtile, st->airport.tile); + if (distance < best || index == INVALID_STATION) { + best = distance; + index = st->index; + } + } + return index; +} + +/** + * Return a tile for placing a newly bought aircraft. + * @param depot a depot. + * @return a hangar tile where the new aircraft can be placed, or INVALID_TILE if no hangar available. + */ +TileIndex GetHangarTileForNewAircraft(const Depot *depot) +{ + assert(depot->veh_type == VEH_AIRCRAFT); + + for (const auto &tile : depot->depot_tiles) { + switch (GetAirportTileType(tile)) { + case ATT_HANGAR_STANDARD: + return tile; + case ATT_HANGAR_EXTENDED: + if (!HasAirportTileAnyReservation(tile)) return tile; + break; + default: + NOT_REACHED(); + } + } + + return INVALID_TILE; +} + /** * Build an aircraft. * @param flags type of operation. @@ -276,22 +371,34 @@ CommandCost CmdBuildAircraft(DoCommandFlag flags, TileIndex tile, const Engine * /* Prevent building aircraft types at places which can't handle them */ if (!CanVehicleUseStation(e->index, st)) return CMD_ERROR; - /* Make sure all aircraft end up in the first tile of the hangar. */ - tile = st->airport.GetHangarTile(st->airport.GetHangarNum(tile)); + if (!st->airport.HasHangar()) return CMD_ERROR; + + /* Make sure all aircraft ends up in an appropriate hangar. */ + if ((flags & DC_AUTOREPLACE) == 0) { + tile = GetHangarTileForNewAircraft(st->airport.hangar); + if (tile == INVALID_TILE) return_cmd_error(STR_ERROR_NO_FREE_HANGAR); + } + + bool extended_hangar = IsExtendedHangar(tile); if (flags & DC_EXEC) { Aircraft *v = new Aircraft(); // aircraft Aircraft *u = new Aircraft(); // shadow *ret = v; - v->direction = DIR_SE; + v->tile = tile; + v->dest_tile = 0; + v->next_trackdir = INVALID_TRACKDIR; + v->direction = u->direction = DiagDirToDir(GetHangarDirection(tile)); + v->trackdir = DiagDirToDiagTrackdir(GetHangarDirection(tile)); + v->wait_counter = 0; v->owner = u->owner = _current_company; + v->SetNext(u); + v->UpdateNextTile(tile); - v->tile = tile; - - uint x = TileX(tile) * TILE_SIZE + 5; - uint y = TileY(tile) * TILE_SIZE + 3; + uint x = TileX(tile) * TILE_SIZE + 8; + uint y = TileY(tile) * TILE_SIZE + 8; v->x_pos = u->x_pos = x; v->y_pos = u->y_pos = y; @@ -299,8 +406,16 @@ CommandCost CmdBuildAircraft(DoCommandFlag flags, TileIndex tile, const Engine * u->z_pos = GetSlopePixelZ(x, y); v->z_pos = u->z_pos + 1; - v->vehstatus = VS_HIDDEN | VS_STOPPED | VS_DEFPAL; - u->vehstatus = VS_HIDDEN | VS_UNCLICKABLE | VS_SHADOW; + v->vehstatus = VS_STOPPED | VS_DEFPAL; + u->vehstatus = VS_UNCLICKABLE | VS_SHADOW; + + if (!extended_hangar) { + v->vehstatus |= VS_HIDDEN; + u->vehstatus |= VS_HIDDEN; + } else { + assert(IsValidTrackdir(v->trackdir)); + SetAirportTrackReservation(tile, TrackdirToTrack(v->trackdir)); + } v->spritenum = avi->image_index; @@ -335,13 +450,9 @@ CommandCost CmdBuildAircraft(DoCommandFlag flags, TileIndex tile, const Engine * v->reliability_spd_dec = e->reliability_spd_dec; v->max_age = e->GetLifeLengthInDays(); - v->pos = GetVehiclePosOnBuild(tile); + v->state = AS_HANGAR; - v->state = HANGAR; - v->previous_pos = v->pos; v->targetairport = GetStationIndex(tile); - v->SetNext(u); - v->SetServiceInterval(Company::Get(_current_company)->settings.vehicle.servint_aircraft); v->date_of_last_service = TimerGameEconomy::date; @@ -378,7 +489,8 @@ CommandCost CmdBuildAircraft(DoCommandFlag flags, TileIndex tile, const Engine * w->x_pos = v->x_pos; w->y_pos = v->y_pos; w->z_pos = v->z_pos + ROTOR_Z_OFFSET; - w->vehstatus = VS_HIDDEN | VS_UNCLICKABLE; + w->vehstatus = VS_UNCLICKABLE; + if (!extended_hangar) w->vehstatus |= VS_HIDDEN; w->spritenum = 0xFF; w->subtype = AIR_ROTOR; w->sprite_cache.sprite_seq.Set(SPR_ROTOR_STOPPED); @@ -390,30 +502,23 @@ CommandCost CmdBuildAircraft(DoCommandFlag flags, TileIndex tile, const Engine * u->SetNext(w); w->UpdatePosition(); } - } - - return CommandCost(); -} - -ClosestDepot Aircraft::FindClosestDepot() -{ - const Station *st = GetTargetAirportIfValid(this); - /* If the station is not a valid airport or if it has no hangars */ - if (st == nullptr || !CanVehicleUseStation(this, st) || !st->airport.HasHangar()) { - /* the aircraft has to search for a hangar on its own */ - StationID station = FindNearestHangar(this); - - if (station == INVALID_STATION) return ClosestDepot(); - - st = Station::Get(station); + if (extended_hangar) { + SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos); + v->MarkDirty(); + } } - return ClosestDepot(st->xy, st->index); + return CommandCost(); } +/** Check whether the aircrafts needs to visit a hangar. + * @param v Aircraft + */ static void CheckIfAircraftNeedsService(Aircraft *v) { + if (v->IsAircraftFlying() && !v->IsAircraftFreelyFlying()) return; + if (Company::Get(v->owner)->settings.vehicle.servint_aircraft == 0 || !v->NeedsAutomaticServicing()) return; if (v->IsChainInDepot()) { VehicleServiceInDepot(v); @@ -424,17 +529,29 @@ static void CheckIfAircraftNeedsService(Aircraft *v) * we don't want to consider going to a depot too. */ if (!v->current_order.IsType(OT_GOTO_DEPOT) && !v->current_order.IsType(OT_GOTO_STATION)) return; - const Station *st = Station::Get(v->current_order.GetDestination()); - + const Station *st; + if (v->state <= AS_RUNNING) { + st = Station::Get(v->GetCurrentAirportID()); + } else { + st = Station::Get(GetTargetDestination(v->current_order, true)); + } assert(st != nullptr); - /* only goto depot if the target airport has a depot */ if (st->airport.HasHangar() && CanVehicleUseStation(v, st)) { - v->current_order.MakeGoToDepot(st->index, ODTFB_SERVICE); + v->current_order.MakeGoToDepot(st->airport.hangar->index, ODTFB_SERVICE); + v->SetDestTile(v->GetOrderHangarLocation(st->airport.hangar->index)); SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); } else if (v->current_order.IsType(OT_GOTO_DEPOT)) { v->current_order.MakeDummy(); SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); + } else { + /* Try going to another hangar. */ + ClosestDepot closest_hangar = v->FindClosestDepot(); + if (closest_hangar.location != INVALID_TILE) { + v->current_order.MakeGoToDepot(closest_hangar.destination, ODTFB_SERVICE); + v->SetDestTile(closest_hangar.location); + SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); + } } } @@ -478,52 +595,6 @@ void Aircraft::OnNewEconomyDay() SetWindowClassesDirty(WC_AIRCRAFT_LIST); } -static void HelicopterTickHandler(Aircraft *v) -{ - Aircraft *u = v->Next()->Next(); - - if (u->vehstatus & VS_HIDDEN) return; - - /* if true, helicopter rotors do not rotate. This should only be the case if a helicopter is - * loading/unloading at a terminal or stopped */ - if (v->current_order.IsType(OT_LOADING) || (v->vehstatus & VS_STOPPED)) { - if (u->cur_speed != 0) { - u->cur_speed++; - if (u->cur_speed >= 0x80 && u->state == HRS_ROTOR_MOVING_3) { - u->cur_speed = 0; - } - } - } else { - if (u->cur_speed == 0) { - u->cur_speed = 0x70; - } - if (u->cur_speed >= 0x50) { - u->cur_speed--; - } - } - - int tick = ++u->tick_counter; - int spd = u->cur_speed >> 4; - - VehicleSpriteSeq seq; - if (spd == 0) { - u->state = HRS_ROTOR_STOPPED; - GetRotorImage(v, EIT_ON_MAP, &seq); - if (u->sprite_cache.sprite_seq == seq) return; - } else if (tick >= spd) { - u->tick_counter = 0; - u->state++; - if (u->state > HRS_ROTOR_MOVING_3) u->state = HRS_ROTOR_MOVING_1; - GetRotorImage(v, EIT_ON_MAP, &seq); - } else { - return; - } - - u->sprite_cache.sprite_seq = seq; - - u->UpdatePositionAndViewport(); -} - /** * Set aircraft position. * @param v Aircraft to position. @@ -566,34 +637,6 @@ void SetAircraftPosition(Aircraft *v, int x, int y, int z) } } -/** - * Handle Aircraft specific tasks when an Aircraft enters a hangar - * @param *v Vehicle that enters the hangar - */ -void HandleAircraftEnterHangar(Aircraft *v) -{ - v->subspeed = 0; - v->progress = 0; - - Aircraft *u = v->Next(); - u->vehstatus |= VS_HIDDEN; - u = u->Next(); - if (u != nullptr) { - u->vehstatus |= VS_HIDDEN; - u->cur_speed = 0; - } - - SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos); -} - -static void PlayAircraftSound(const Vehicle *v) -{ - if (!PlayVehicleSound(v, VSE_START)) { - SndPlayVehicleFx(AircraftVehInfo(v->engine_type)->sfx, v); - } -} - - /** * Update cached values of an aircraft. * Currently caches callback 36 max speed. @@ -626,12 +669,10 @@ void UpdateAircraftCache(Aircraft *v, bool update_range) } } - /** * Special velocities for aircraft */ enum AircraftSpeedLimits { - SPEED_LIMIT_TAXI = 50, ///< Maximum speed of an aircraft while taxiing SPEED_LIMIT_APPROACH = 230, ///< Maximum speed of an aircraft on finals SPEED_LIMIT_BROKEN = 320, ///< Maximum speed of an aircraft that is broken SPEED_LIMIT_HOLD = 425, ///< Maximum speed of an aircraft that flies the holding pattern @@ -641,12 +682,30 @@ enum AircraftSpeedLimits { /** * Sets the new speed for an aircraft * @param v The vehicle for which the speed should be obtained - * @param speed_limit The maximum speed the vehicle may have. - * @param hard_limit If true, the limit is directly enforced, otherwise the plane is slowed down gradually * @return The number of position updates needed within the tick */ -static int UpdateAircraftSpeed(Aircraft *v, uint speed_limit = SPEED_LIMIT_NONE, bool hard_limit = true) +static int UpdateAircraftSpeed(Aircraft *v) { + assert(v->state >= AS_MOVING); + + /* If true, the limit is directly enforced, otherwise the plane is slowed down gradually. */ + bool hard_limit = true; + /* The maximum speed the vehicle may have. */ + uint speed_limit = SPEED_LIMIT_NONE; + + hard_limit = !HasBit(v->state, ASB_NO_HARD_LIMIT_SPEED); + + if (!hard_limit) { + if (HasBit(v->state, ASB_FLYING_ON_AIRPORT)) { + speed_limit = v->IsAircraftOnHold() ? SPEED_LIMIT_HOLD : SPEED_LIMIT_APPROACH; + } else if (!v->IsAircraftFlying()){ + speed_limit = GetAirTypeInfo(GetAirType(v->GetNextTile()))->max_speed; + } + } else if (v->state == AS_RUNNING) { + assert(IsAirportTile(v->tile)); + speed_limit = GetAirTypeInfo(GetAirType(v->tile))->max_speed; + } + /** * 'acceleration' has the unit 3/8 mph/tick. This function is called twice per tick. * So the speed amount we need to accelerate is: @@ -714,7 +773,7 @@ int GetTileHeightBelowAircraft(const Vehicle *v) { int safe_x = Clamp(v->x_pos, 0, Map::MaxX() * TILE_SIZE); int safe_y = Clamp(v->y_pos, 0, Map::MaxY() * TILE_SIZE); - return TileHeight(TileVirtXY(safe_x, safe_y)) * TILE_HEIGHT; + return TilePixelHeight(TileVirtXY(safe_x, safe_y)); } /** @@ -730,7 +789,7 @@ int GetTileHeightBelowAircraft(const Vehicle *v) void GetAircraftFlightLevelBounds(const Vehicle *v, int *min_level, int *max_level) { int base_altitude = GetTileHeightBelowAircraft(v); - if (v->type == VEH_AIRCRAFT && Aircraft::From(v)->subtype == AIR_HELICOPTER) { + if (v->type == VEH_AIRCRAFT && Aircraft::From(v)->IsHelicopter()) { base_altitude += HELICOPTER_HOLD_MAX_FLYING_ALTITUDE - PLANE_HOLD_MAX_FLYING_ALTITUDE; } @@ -766,7 +825,7 @@ int GetAircraftHoldMaxAltitude(const Aircraft *v) { int tile_height = GetTileHeightBelowAircraft(v); - return tile_height + ((v->subtype == AIR_HELICOPTER) ? HELICOPTER_HOLD_MAX_FLYING_ALTITUDE : PLANE_HOLD_MAX_FLYING_ALTITUDE); + return tile_height + (v->IsHelicopter() ? HELICOPTER_HOLD_MAX_FLYING_ALTITUDE : PLANE_HOLD_MAX_FLYING_ALTITUDE); } template @@ -811,423 +870,51 @@ int GetAircraftFlightLevel(T *v, bool takeoff) template int GetAircraftFlightLevel(DisasterVehicle *v, bool takeoff); template int GetAircraftFlightLevel(Aircraft *v, bool takeoff); -/** - * Find the entry point to an airport depending on direction which - * the airport is being approached from. Each airport can have up to - * four entry points for its approach system so that approaching - * aircraft do not fly through each other or are forced to do 180 - * degree turns during the approach. The arrivals are grouped into - * four sectors dependent on the DiagDirection from which the airport - * is approached. - * - * @param v The vehicle that is approaching the airport - * @param apc The Airport Class being approached. - * @param rotation The rotation of the airport. - * @return The index of the entry point - */ -static uint8_t AircraftGetEntryPoint(const Aircraft *v, const AirportFTAClass *apc, Direction rotation) +static void HandleHelicopterRotor(Aircraft *v) { - assert(v != nullptr); - assert(apc != nullptr); + Aircraft *u = v->Next()->Next(); - /* In the case the station doesn't exit anymore, set target tile 0. - * It doesn't hurt much, aircraft will go to next order, nearest hangar - * or it will simply crash in next tick */ - TileIndex tile = 0; + if (u->vehstatus & VS_HIDDEN) return; - const Station *st = Station::GetIfValid(v->targetairport); - if (st != nullptr) { - /* Make sure we don't go to INVALID_TILE if the airport has been removed. */ - tile = (st->airport.tile != INVALID_TILE) ? st->airport.tile : st->xy; - } - - int delta_x = v->x_pos - TileX(tile) * TILE_SIZE; - int delta_y = v->y_pos - TileY(tile) * TILE_SIZE; - - DiagDirection dir; - if (abs(delta_y) < abs(delta_x)) { - /* We are northeast or southwest of the airport */ - dir = delta_x < 0 ? DIAGDIR_NE : DIAGDIR_SW; - } else { - /* We are northwest or southeast of the airport */ - dir = delta_y < 0 ? DIAGDIR_NW : DIAGDIR_SE; - } - dir = ChangeDiagDir(dir, DiagDirDifference(DIAGDIR_NE, DirToDiagDir(rotation))); - return apc->entry_points[dir]; -} - - -static void MaybeCrashAirplane(Aircraft *v); - -/** - * Controls the movement of an aircraft. This function actually moves the vehicle - * on the map and takes care of minor things like sound playback. - * @todo De-mystify the cur_speed values for helicopter rotors. - * @param v The vehicle that is moved. Must be the first vehicle of the chain - * @return Whether the position requested by the State Machine has been reached - */ -static bool AircraftController(Aircraft *v) -{ - /* nullptr if station is invalid */ - const Station *st = Station::GetIfValid(v->targetairport); - /* INVALID_TILE if there is no station */ - TileIndex tile = INVALID_TILE; - Direction rotation = DIR_N; - uint size_x = 1, size_y = 1; - if (st != nullptr) { - if (st->airport.tile != INVALID_TILE) { - tile = st->airport.tile; - rotation = st->airport.rotation; - size_x = st->airport.w; - size_y = st->airport.h; - } else { - tile = st->xy; - } - } - /* DUMMY if there is no station or no airport */ - const AirportFTAClass *afc = tile == INVALID_TILE ? GetAirport(AT_DUMMY) : st->airport.GetFTA(); - - /* prevent going to INVALID_TILE if airport is deleted. */ - if (st == nullptr || st->airport.tile == INVALID_TILE) { - /* Jump into our "holding pattern" state machine if possible */ - if (v->pos >= afc->nofelements) { - v->pos = v->previous_pos = AircraftGetEntryPoint(v, afc, DIR_N); - } else if (v->targetairport != v->current_order.GetDestination()) { - /* If not possible, just get out of here fast */ - v->state = FLYING; - UpdateAircraftCache(v); - AircraftNextAirportPos_and_Order(v); - /* get aircraft back on running altitude */ - SetAircraftPosition(v, v->x_pos, v->y_pos, GetAircraftFlightLevel(v)); - return false; - } - } - - /* get airport moving data */ - const AirportMovingData amd = RotateAirportMovingData(afc->MovingData(v->pos), rotation, size_x, size_y); - - int x = TileX(tile) * TILE_SIZE; - int y = TileY(tile) * TILE_SIZE; - - /* Helicopter raise */ - if (amd.flag & AMED_HELI_RAISE) { - Aircraft *u = v->Next()->Next(); - - /* Make sure the rotors don't rotate too fast */ - if (u->cur_speed > 32) { - v->cur_speed = 0; - if (--u->cur_speed == 32) { - if (!PlayVehicleSound(v, VSE_START)) { - SoundID sfx = AircraftVehInfo(v->engine_type)->sfx; - /* For compatibility with old NewGRF we ignore the sfx property, unless a NewGRF-defined sound is used. - * The baseset has only one helicopter sound, so this only limits using plane or cow sounds. */ - if (sfx < ORIGINAL_SAMPLE_COUNT) sfx = SND_18_TAKEOFF_HELICOPTER; - SndPlayVehicleFx(sfx, v); - } - } - } else { - u->cur_speed = 32; - int count = UpdateAircraftSpeed(v); - if (count > 0) { - v->tile = 0; - - int z_dest; - GetAircraftFlightLevelBounds(v, &z_dest, nullptr); - - /* Reached altitude? */ - if (v->z_pos >= z_dest) { - v->cur_speed = 0; - return true; - } - SetAircraftPosition(v, v->x_pos, v->y_pos, std::min(v->z_pos + count, z_dest)); + /* if true, helicopter rotors do not rotate. This should only be the case if a helicopter is + * loading/unloading at a terminal or stopped */ + if (v->current_order.IsType(OT_LOADING) || (v->vehstatus & VS_STOPPED)) { + if (u->cur_speed != 0) { + u->cur_speed++; + if (u->cur_speed >= 0x80 && u->state == HRS_ROTOR_MOVING_3) { + u->cur_speed = 0; } } - return false; - } - - /* Helicopter landing. */ - if (amd.flag & AMED_HELI_LOWER) { - SetBit(v->flags, VAF_HELI_DIRECT_DESCENT); - - if (st == nullptr) { - /* FIXME - AircraftController -> if station no longer exists, do not land - * helicopter will circle until sign disappears, then go to next order - * what to do when it is the only order left, right now it just stays in 1 place */ - v->state = FLYING; - UpdateAircraftCache(v); - AircraftNextAirportPos_and_Order(v); - return false; - } - - /* Vehicle is now at the airport. - * Helicopter has arrived at the target landing pad, so the current position is also where it should land. - * Except for Oilrigs which are special due to being a 1x1 station, and helicopters land outside it. */ - if (st->airport.type != AT_OILRIG) { - x = v->x_pos; - y = v->y_pos; - tile = TileVirtXY(x, y); - } - v->tile = tile; - - /* Find altitude of landing position. */ - int z = GetSlopePixelZ(x, y) + 1 + afc->delta_z; - - if (z == v->z_pos) { - Vehicle *u = v->Next()->Next(); - - /* Increase speed of rotors. When speed is 80, we've landed. */ - if (u->cur_speed >= 80) { - ClrBit(v->flags, VAF_HELI_DIRECT_DESCENT); - return true; - } - u->cur_speed += 4; - } else { - int count = UpdateAircraftSpeed(v); - if (count > 0) { - if (v->z_pos > z) { - SetAircraftPosition(v, v->x_pos, v->y_pos, std::max(v->z_pos - count, z)); - } else { - SetAircraftPosition(v, v->x_pos, v->y_pos, std::min(v->z_pos + count, z)); - } - } + } else { + if (u->cur_speed == 0) { + u->cur_speed = 0x70; } - return false; - } - - /* Get distance from destination pos to current pos. */ - uint dist = abs(x + amd.x - v->x_pos) + abs(y + amd.y - v->y_pos); - - /* Need exact position? */ - if (!(amd.flag & AMED_EXACTPOS) && dist <= (amd.flag & AMED_SLOWTURN ? 8U : 4U)) return true; - - /* At final pos? */ - if (dist == 0) { - /* Change direction smoothly to final direction. */ - DirDiff dirdiff = DirDifference(amd.direction, v->direction); - /* if distance is 0, and plane points in right direction, no point in calling - * UpdateAircraftSpeed(). So do it only afterwards */ - if (dirdiff == DIRDIFF_SAME) { - v->cur_speed = 0; - return true; + if (u->cur_speed >= 0x50) { + u->cur_speed--; } - - if (!UpdateAircraftSpeed(v, SPEED_LIMIT_TAXI)) return false; - - v->direction = ChangeDir(v->direction, dirdiff > DIRDIFF_REVERSE ? DIRDIFF_45LEFT : DIRDIFF_45RIGHT); - v->cur_speed >>= 1; - - SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos); - return false; - } - - if (amd.flag & AMED_BRAKE && v->cur_speed > SPEED_LIMIT_TAXI * _settings_game.vehicle.plane_speed) { - MaybeCrashAirplane(v); - if ((v->vehstatus & VS_CRASHED) != 0) return false; } - uint speed_limit = SPEED_LIMIT_TAXI; - bool hard_limit = true; - - if (amd.flag & AMED_NOSPDCLAMP) speed_limit = SPEED_LIMIT_NONE; - if (amd.flag & AMED_HOLD) { speed_limit = SPEED_LIMIT_HOLD; hard_limit = false; } - if (amd.flag & AMED_LAND) { speed_limit = SPEED_LIMIT_APPROACH; hard_limit = false; } - if (amd.flag & AMED_BRAKE) { speed_limit = SPEED_LIMIT_TAXI; hard_limit = false; } - - int count = UpdateAircraftSpeed(v, speed_limit, hard_limit); - if (count == 0) return false; - - /* If the plane will be a few subpixels away from the destination after - * this movement loop, start nudging it towards the exact position for - * the whole loop. Otherwise, heavily depending on the speed of the plane, - * it is possible we totally overshoot the target, causing the plane to - * make a loop, and trying again, and again, and again .. */ - bool nudge_towards_target = static_cast(count) + 3 > dist; - - if (v->turn_counter != 0) v->turn_counter--; - - do { - - GetNewVehiclePosResult gp; - - if (nudge_towards_target || (amd.flag & AMED_LAND)) { - /* move vehicle one pixel towards target */ - gp.x = (v->x_pos != (x + amd.x)) ? - v->x_pos + ((x + amd.x > v->x_pos) ? 1 : -1) : - v->x_pos; - gp.y = (v->y_pos != (y + amd.y)) ? - v->y_pos + ((y + amd.y > v->y_pos) ? 1 : -1) : - v->y_pos; - - /* Oilrigs must keep v->tile as st->airport.tile, since the landing pad is in a non-airport tile */ - gp.new_tile = (st->airport.type == AT_OILRIG) ? st->airport.tile : TileVirtXY(gp.x, gp.y); - - } else { - - /* Turn. Do it slowly if in the air. */ - Direction newdir = GetDirectionTowards(v, x + amd.x, y + amd.y); - if (newdir != v->direction) { - if (amd.flag & AMED_SLOWTURN && v->number_consecutive_turns < 8 && v->subtype == AIR_AIRCRAFT) { - if (v->turn_counter == 0 || newdir == v->last_direction) { - if (newdir == v->last_direction) { - v->number_consecutive_turns = 0; - } else { - v->number_consecutive_turns++; - } - v->turn_counter = 2 * _settings_game.vehicle.plane_speed; - v->last_direction = v->direction; - v->direction = newdir; - } - - /* Move vehicle. */ - gp = GetNewVehiclePos(v); - } else { - v->cur_speed >>= 1; - v->direction = newdir; - - /* When leaving a terminal an aircraft often goes to a position - * directly in front of it. If it would move while turning it - * would need an two extra turns to end up at the correct position. - * To make it easier just disallow all moving while turning as - * long as an aircraft is on the ground. */ - gp.x = v->x_pos; - gp.y = v->y_pos; - gp.new_tile = gp.old_tile = v->tile; - } - } else { - v->number_consecutive_turns = 0; - /* Move vehicle. */ - gp = GetNewVehiclePos(v); - } - } - - v->tile = gp.new_tile; - /* If vehicle is in the air, use tile coordinate 0. */ - if (amd.flag & (AMED_TAKEOFF | AMED_SLOWTURN | AMED_LAND)) v->tile = 0; - - /* Adjust Z for land or takeoff? */ - int z = v->z_pos; - - if (amd.flag & AMED_TAKEOFF) { - z = GetAircraftFlightLevel(v, true); - } else if (amd.flag & AMED_HOLD) { - /* Let the plane drop from normal flight altitude to holding pattern altitude */ - if (z > GetAircraftHoldMaxAltitude(v)) z--; - } else if ((amd.flag & AMED_SLOWTURN) && (amd.flag & AMED_NOSPDCLAMP)) { - z = GetAircraftFlightLevel(v); - } - - /* NewGRF airports (like a rotated intercontinental from OpenGFX+Airports) can be non-rectangular - * and their primary (north-most) tile does not have to be part of the airport. - * As such, the height of the primary tile can be different from the rest of the airport. - * Given we are landing/breaking, and as such are not a helicopter, we know that there has to be a hangar. - * We also know that the airport itself has to be completely flat (otherwise it is not a valid airport). - * Therefore, use the height of this hangar to calculate our z-value. */ - int airport_z = v->z_pos; - if ((amd.flag & (AMED_LAND | AMED_BRAKE)) && st != nullptr) { - assert(st->airport.HasHangar()); - TileIndex hangar_tile = st->airport.GetHangarTile(0); - airport_z = GetTileMaxPixelZ(hangar_tile) + 1; // To avoid clashing with the shadow - } - - if (amd.flag & AMED_LAND) { - if (st->airport.tile == INVALID_TILE) { - /* Airport has been removed, abort the landing procedure */ - v->state = FLYING; - UpdateAircraftCache(v); - AircraftNextAirportPos_and_Order(v); - /* get aircraft back on running altitude */ - SetAircraftPosition(v, gp.x, gp.y, GetAircraftFlightLevel(v)); - continue; - } - - /* We're not flying below our destination, right? */ - assert(airport_z <= z); - int t = std::max(1U, dist - 4); - int delta = z - airport_z; - - /* Only start lowering when we're sufficiently close for a 1:1 glide */ - if (delta >= t) { - z -= CeilDiv(z - airport_z, t); - } - if (z < airport_z) z = airport_z; - } - - /* We've landed. Decrease speed when we're reaching end of runway. */ - if (amd.flag & AMED_BRAKE) { - - if (z > airport_z) { - z--; - } else if (z < airport_z) { - z++; - } - - } - - SetAircraftPosition(v, gp.x, gp.y, z); - } while (--count != 0); - return false; -} - -/** - * Handle crashed aircraft \a v. - * @param v Crashed aircraft. - */ -static bool HandleCrashedAircraft(Aircraft *v) -{ - v->crashed_counter += 3; - - Station *st = GetTargetAirportIfValid(v); + int tick = ++u->tick_counter; + int spd = u->cur_speed >> 4; - /* make aircraft crash down to the ground */ - if (v->crashed_counter < 500 && st == nullptr && ((v->crashed_counter % 3) == 0) ) { - int z = GetSlopePixelZ(Clamp(v->x_pos, 0, Map::MaxX() * TILE_SIZE), Clamp(v->y_pos, 0, Map::MaxY() * TILE_SIZE)); - v->z_pos -= 1; - if (v->z_pos <= z) { - v->crashed_counter = 500; - v->z_pos = z + 1; - } else { - v->crashed_counter = 0; - } - SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos); + VehicleSpriteSeq seq; + if (spd == 0) { + u->state = HRS_ROTOR_STOPPED; + GetRotorImage(v, EIT_ON_MAP, &seq); + if (u->sprite_cache.sprite_seq == seq) return; + } else if (tick >= spd) { + u->tick_counter = 0; + u->state = (AircraftState)((u->state % HRS_ROTOR_NUM_STATES) + 1); + GetRotorImage(v, EIT_ON_MAP, &seq); + } else { + return; } - if (v->crashed_counter < 650) { - uint32_t r; - if (Chance16R(1, 32, r)) { - static const DirDiff delta[] = { - DIRDIFF_45LEFT, DIRDIFF_SAME, DIRDIFF_SAME, DIRDIFF_45RIGHT - }; - - v->direction = ChangeDir(v->direction, delta[GB(r, 16, 2)]); - SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos); - r = Random(); - CreateEffectVehicleRel(v, - GB(r, 0, 4) - 4, - GB(r, 4, 4) - 4, - GB(r, 8, 4), - EV_EXPLOSION_SMALL); - } - } else if (v->crashed_counter >= 10000) { - /* remove rubble of crashed airplane */ - - /* clear runway-in on all airports, set by crashing plane - * small airports use AIRPORT_BUSY, city airports use RUNWAY_IN_OUT_block, etc. - * but they all share the same number */ - if (st != nullptr) { - CLRBITS(st->airport.flags, RUNWAY_IN_block); - CLRBITS(st->airport.flags, RUNWAY_IN_OUT_block); // commuter airport - CLRBITS(st->airport.flags, RUNWAY_IN2_block); // intercontinental - } - - delete v; - - return false; - } + u->sprite_cache.sprite_seq = seq; - return true; + u->UpdatePositionAndViewport(); } - /** * Handle smoke of broken aircraft. * @param v Aircraft @@ -1269,61 +956,45 @@ static void HandleAircraftSmoke(Aircraft *v, bool mode) } } -void HandleMissingAircraftOrders(Aircraft *v) +// REVISE +/** + * Mark an aircraft as falling. + * @param v aircraft + */ +void AircraftStartsFalling(Aircraft *v) { - /* - * We do not have an order. This can be divided into two cases: - * 1) we are heading to an invalid station. In this case we must - * find another airport to go to. If there is nowhere to go, - * we will destroy the aircraft as it otherwise will enter - * the holding pattern for the first airport, which can cause - * the plane to go into an undefined state when building an - * airport with the same StationID. - * 2) we are (still) heading to a (still) valid airport, then we - * can continue going there. This can happen when you are - * changing the aircraft's orders while in-flight or in for - * example a depot. However, when we have a current order to - * go to a depot, we have to keep that order so the aircraft - * actually stops. - */ - const Station *st = GetTargetAirportIfValid(v); - if (st == nullptr) { - Backup cur_company(_current_company, v->owner); - CommandCost ret = Command::Do(DC_EXEC, v->index, DepotCommand::None, {}); - cur_company.Restore(); + assert(v->IsAircraftFreelyFlying()); - if (ret.Failed()) CrashAirplane(v); - } else if (!v->current_order.IsType(OT_GOTO_DEPOT)) { - v->current_order.Free(); - } + v->state = AS_FLYING_FALLING; + v->vehstatus |= VS_AIRCRAFT_BROKEN; + v->acceleration = 0; + v->dest_tile = 0; + v->current_order.MakeDummy(); + // revise: next pos? tile? desttile? + SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); } - -TileIndex Aircraft::GetOrderStationLocation(StationID) +uint Aircraft::Crash(bool flooded) { - /* Orders are changed in flight, ensure going to the right station. */ - if (this->state == FLYING) { - AircraftNextAirportPos_and_Order(this); + if (this->IsAircraftFalling() && + HasTileWaterClass(this->tile) && + IsTileOnWater(this->tile)) { + flooded = true; } - /* Aircraft do not use dest-tile */ - return 0; -} + uint victims = Vehicle::Crash(flooded) + 2; // pilots + this->crashed_counter = flooded ? 9000 : 0; // max 10000, disappear pretty fast when flooded -void Aircraft::MarkDirty() -{ - this->colourmap = PAL_NONE; - this->UpdateViewport(true, false); - if (this->subtype == AIR_HELICOPTER) { - GetRotorImage(this, EIT_ON_MAP, &this->Next()->Next()->sprite_cache.sprite_seq); - } -} + /* Remove the loading indicators (if any) */ + HideFillingPercent(&this->fill_percent_te_id); + // revise: what happens if a falling aircraft falls in an airport? + if (!this->IsAircraftFalling() && !(IsRunway(this->tile) && GetReservationAsRunway(this->tile))) { + /* Lift reserved path except the first tile. Skip reserved runways. */ + LiftAirportPathReservation(this, true); + } -uint Aircraft::Crash(bool flooded) -{ - uint victims = Vehicle::Crash(flooded) + 2; // pilots - this->crashed_counter = flooded ? 9000 : 0; // max 10000, disappear pretty fast when flooded + this->dest_tile = 0; return victims; } @@ -1332,7 +1003,7 @@ uint Aircraft::Crash(bool flooded) * Bring the aircraft in a crashed state, create the explosion animation, and create a news item about the crash. * @param v Aircraft that crashed. */ -static void CrashAirplane(Aircraft *v) +void CrashAircraft(Aircraft *v) { CreateEffectVehicleRel(v, 4, 4, 8, EV_EXPLOSION_LARGE); @@ -1342,23 +1013,18 @@ static void CrashAirplane(Aircraft *v) v->cargo.Truncate(); v->Next()->cargo.Truncate(); const Station *st = GetTargetAirportIfValid(v); - StringID newsitem; - TileIndex vt = TileVirtXY(v->x_pos, v->y_pos); - if (st == nullptr) { - newsitem = STR_NEWS_PLANE_CRASH_OUT_OF_FUEL; - } else { + + StringID newsitem = STR_NEWS_AIRCRAFT_CRASH_NO_AIRPORT; + if (st != nullptr && !v->IsAircraftFalling()) { SetDParam(1, st->index); newsitem = STR_NEWS_AIRCRAFT_CRASH; } + TileIndex vt = TileVirtXY(v->x_pos, v->y_pos); AI::NewEvent(v->owner, new ScriptEventVehicleCrashed(v->index, vt, st == nullptr ? ScriptEventVehicleCrashed::CRASH_AIRCRAFT_NO_AIRPORT : ScriptEventVehicleCrashed::CRASH_PLANE_LANDING, victims)); Game::NewEvent(new ScriptEventVehicleCrashed(v->index, vt, st == nullptr ? ScriptEventVehicleCrashed::CRASH_AIRCRAFT_NO_AIRPORT : ScriptEventVehicleCrashed::CRASH_PLANE_LANDING, victims)); - NewsType newstype = NT_ACCIDENT; - if (v->owner != _local_company) { - newstype = NT_ACCIDENT_OTHER; - } - + NewsType newstype = v->owner == _local_company ? NT_ACCIDENT : NT_ACCIDENT_OTHER; AddTileNewsItem(newsitem, newstype, vt, nullptr, st != nullptr ? st->index : INVALID_STATION); ModifyStationRatingAround(vt, v->owner, -160, 30); @@ -1368,754 +1034,1966 @@ static void CrashAirplane(Aircraft *v) /** * Decide whether aircraft \a v should crash. * @param v Aircraft to test. + * @return Whether the plane has crashed. */ -static void MaybeCrashAirplane(Aircraft *v) +static bool MaybeCrashAirplane(Aircraft *v) { + if (_settings_game.vehicle.plane_crashes == 0) return false; - Station *st = Station::Get(v->targetairport); - - uint32_t prob; - if ((st->airport.GetFTA()->flags & AirportFTAClass::SHORT_STRIP) && - (AircraftVehInfo(v->engine_type)->subtype & AIR_FAST) && - !_cheats.no_jetcrash.value) { - prob = 3276; - } else { - if (_settings_game.vehicle.plane_crashes == 0) return; - prob = (0x4000 << _settings_game.vehicle.plane_crashes) / 1500; - } - - if (GB(Random(), 0, 22) > prob) return; + uint32_t prob = (0x4000 << _settings_game.vehicle.plane_crashes) / 1500; + uint32_t rand = GB(Random(), 0, 18); + if (rand > prob) return false; /* Crash the airplane. Remove all goods stored at the station. */ + Station *st = Station::Get(v->targetairport); for (GoodsEntry &ge : st->goods) { ge.rating = 1; ge.cargo.Truncate(); } - CrashAirplane(v); + CrashAircraft(v); + return true; } /** - * Aircraft arrives at a terminal. If it is the first aircraft, throw a party. - * Start loading cargo. - * @param v Aircraft that arrived. + * Handle crashed aircraft \a v. + * @param v Crashed aircraft. */ -static void AircraftEntersTerminal(Aircraft *v) +static bool HandleCrashedAircraft(Aircraft *v) { - if (v->current_order.IsType(OT_GOTO_DEPOT)) return; + v->crashed_counter += 3; - Station *st = Station::Get(v->targetairport); - v->last_station_visited = v->targetairport; + if (v->crashed_counter < 650) { + uint32_t r; + if (Chance16R(1, 32, r)) { + static const DirDiff delta[] = { + DIRDIFF_45LEFT, DIRDIFF_SAME, DIRDIFF_SAME, DIRDIFF_45RIGHT + }; - /* Check if station was ever visited before */ - if (!(st->had_vehicle_of_type & HVOT_AIRCRAFT)) { - st->had_vehicle_of_type |= HVOT_AIRCRAFT; - SetDParam(0, st->index); - /* show newsitem of celebrating citizens */ - AddVehicleNewsItem( - STR_NEWS_FIRST_AIRCRAFT_ARRIVAL, - (v->owner == _local_company) ? NT_ARRIVAL_COMPANY : NT_ARRIVAL_OTHER, - v->index, - st->index - ); - AI::NewEvent(v->owner, new ScriptEventStationFirstVehicle(st->index, v->index)); - Game::NewEvent(new ScriptEventStationFirstVehicle(st->index, v->index)); + v->direction = v->Next()->direction = ChangeDir(v->direction, delta[GB(r, 16, 2)]); + SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos); + r = Random(); + CreateEffectVehicleRel(v, + GB(r, 0, 4) - 4, + GB(r, 4, 4) - 4, + GB(r, 8, 4), + EV_EXPLOSION_SMALL); + } + } else if (v->crashed_counter >= 10000) { + if ((v->vehstatus & VS_HIDDEN) != 0 || v->IsAircraftFalling()) { + /* Deleting a vehicle in a hangar or crashed outside the airport. */ + delete v; + return false; + } + + /* remove rubble of crashed airplane */ + if (HasAirportTrackReserved(v->tile)) { + assert(!v->IsAircraftFlying()); + assert(HasAirportTrackReserved(v->tile, TrackdirToTrack(v->trackdir))); + RemoveAirportTrackReservation(v->tile, TrackdirToTrack(v->trackdir)); + } else { + assert(IsAirportTile(v->tile)); + assert(IsRunway(v->tile)); + assert(GetReservationAsRunway(v->tile)); + assert(IsDiagonalTrackdir(v->trackdir)); + DiagDirection diagdir = TrackdirToExitdir(v->trackdir); + TileIndex start_tile = GetRunwayExtreme(v->tile, ReverseDiagDir(diagdir)); + SetRunwayReservation(start_tile, false); + } + + delete v; + return false; } - v->BeginLoading(); + return true; } /** - * Aircraft touched down at the landing strip. - * @param v Aircraft that landed. + * Aircraft \a v cannot find an airport to go to and it will fall until it crashes. + * @param v Aircraft falling to the ground. */ -static void AircraftLandAirplane(Aircraft *v) +static void HandleAircraftFalling(Aircraft *v) { - Station *st = Station::Get(v->targetairport); + assert(v->IsAircraftFalling()); + int z = GetSlopePixelZ(Clamp(v->x_pos, 0, Map::MaxX() * TILE_SIZE), Clamp(v->y_pos, 0, Map::MaxY() * TILE_SIZE)); + GetNewVehiclePosResult gp = GetNewVehiclePos(v); - TileIndex vt = TileVirtXY(v->x_pos, v->y_pos); + /* MoveAircraft() is called twice, but handling out of fuel only once. */ + uint count = UpdateAircraftSpeed(v) + UpdateAircraftSpeed(v); + v->x_pos += count * (gp.x - v->x_pos); + v->y_pos += count * (gp.y - v->y_pos); - v->UpdateDeltaXY(); + if (count > 0) v->z_pos -= 1; - AirportTileAnimationTrigger(st, vt, AAT_STATION_AIRPLANE_LAND); + if (v->z_pos == z) { + v->z_pos++; + CreateEffectVehicleRel(v, 4, 4, 8, EV_EXPLOSION_LARGE); + v->vehstatus &= ~VS_AIRCRAFT_BROKEN; + CrashAircraft(v); + } else { + HandleAircraftSmoke(v, false); + SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos); + } +} - if (!PlayVehicleSound(v, VSE_TOUCHDOWN)) { - SndPlayVehicleFx(SND_17_SKID_PLANE, v); +/** Structure for aircraft sub-coordinate data for moving into a new tile via a Diagdir onto a Track. */ +struct AircraftSubcoordData { + uint8_t x_subcoord; ///< New X sub-coordinate on the new tile + uint8_t y_subcoord; ///< New Y sub-coordinate on the new tile + Direction dir; ///< New Direction to move in on the new track +}; + +/** Aircraft sub-coordinate data for moving into a new tile via a Diagdir onto a Track. + * Array indexes are Diagdir, Track. + * There will always be three possible tracks going into an adjacent tile via a Diagdir, + * so each Diagdir sub-array will have three valid and three invalid structures per Track. + */ +static const AircraftSubcoordData _aircraft_subcoord[DIAGDIR_END][TRACK_END] = { + // DIAGDIR_NE + { + {15, 8, DIR_NE}, // TRACK_X + { 0, 0, INVALID_DIR}, // TRACK_Y + { 0, 0, INVALID_DIR}, // TRACK_UPPER + {15, 8, DIR_E}, // TRACK_LOWER + {15, 7, DIR_N}, // TRACK_LEFT + { 0, 0, INVALID_DIR}, // TRACK_RIGHT + }, + // DIAGDIR_SE + { + { 0, 0, INVALID_DIR}, // TRACK_X + { 8, 0, DIR_SE}, // TRACK_Y + { 7, 0, DIR_E}, // TRACK_UPPER + { 0, 0, INVALID_DIR}, // TRACK_LOWER + { 8, 0, DIR_S}, // TRACK_LEFT + { 0, 0, INVALID_DIR}, // TRACK_RIGHT + }, + // DIAGDIR_SW + { + { 0, 8, DIR_SW}, // TRACK_X + { 0, 0, INVALID_DIR}, // TRACK_Y + { 0, 7, DIR_W}, // TRACK_UPPER + { 0, 0, INVALID_DIR}, // TRACK_LOWER + { 0, 0, INVALID_DIR}, // TRACK_LEFT + { 0, 8, DIR_S}, // TRACK_RIGHT + }, + // DIAGDIR_NW + { + { 0, 0, INVALID_DIR}, // TRACK_X + { 8, 15, DIR_NW}, // TRACK_Y + { 0, 0, INVALID_DIR}, // TRACK_UPPER + { 8, 15, DIR_W}, // TRACK_LOWER + { 0, 0, INVALID_DIR}, // TRACK_LEFT + { 7, 15, DIR_N}, // TRACK_RIGHT } +}; + +/** + * Check whether the aircraft needs to rotate its current trackdir. + * @param v Aircraft + * @return whether the aircraft needs to rotate its current trackdir. + */ +bool DoesAircraftNeedRotation(Aircraft *v) +{ + assert(v->next_trackdir == INVALID_TRACKDIR || IsValidTrackdir(v->next_trackdir)); + return v->next_trackdir != INVALID_TRACKDIR; } +const uint16_t AIRCRAFT_ROTATION_STEP_TICKS = 30; +const uint16_t AIRCRAFT_WAIT_FREE_PATH_TICKS = 10; +const uint16_t AIRCRAFT_WAIT_LEAVE_HANGAR_TICKS = 200; +const uint16_t AIRCRAFT_CANT_LEAVE_RUNWAY = 200; -/** set the right pos when heading to other airports after takeoff */ -void AircraftNextAirportPos_and_Order(Aircraft *v) +/** + * Slightly rotate an aircraft towards its desired trackdir. + * @param v Aircraft + */ +void DoRotationStep(Aircraft *v) { - if (v->current_order.IsType(OT_GOTO_STATION) || v->current_order.IsType(OT_GOTO_DEPOT)) { - v->targetairport = v->current_order.GetDestination(); + assert(DoesAircraftNeedRotation(v)); + if (v->trackdir == v->next_trackdir) { + v->next_trackdir = INVALID_TRACKDIR; + v->ClearWaitTime(); + return; + } else { + if (v->cur_speed != 0) { + SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); + v->cur_speed = 0; + } + + Direction desired_direction = TrackdirToDir(v->next_trackdir); + assert(IsValidDirection(desired_direction)); + assert(v->direction != desired_direction); + DirDiff difference = DirDifference(v->direction, desired_direction); + assert(difference != DIRDIFF_SAME); + difference = difference <= DIRDIFF_REVERSE ? DIRDIFF_45LEFT : DIRDIFF_45RIGHT; + v->direction = v->Next()->direction = ChangeDir(v->direction, difference); + + if (v->direction == desired_direction) { + v->trackdir = v->next_trackdir; + + if (IsDiagonalTrackdir(v->trackdir)) { + /* Amend position when rotating in the middle of the tile. */ + if (DiagDirToAxis(DirToDiagDir(v->direction)) == AXIS_X) { + v->y_pos = (v->y_pos & ~0xF) | 8; + } else { + v->x_pos = (v->x_pos & ~0xF) | 8; + } + } else { + /* Amend position when rotating at the edge of a tile. */ + const AircraftSubcoordData &b = _aircraft_subcoord[TrackdirToEntrydir(v->trackdir)][TrackdirToTrack(v->trackdir)]; + v->x_pos = (v->x_pos & ~0xF) | b.x_subcoord; + v->y_pos = (v->y_pos & ~0xF) | b.y_subcoord; + } + } + + assert(!v->IsWaiting()); + v->SetWaitTime(AIRCRAFT_ROTATION_STEP_TICKS); } - const Station *st = GetTargetAirportIfValid(v); - const AirportFTAClass *apc = st == nullptr ? GetAirport(AT_DUMMY) : st->airport.GetFTA(); - Direction rotation = st == nullptr ? DIR_N : st->airport.rotation; - v->pos = v->previous_pos = AircraftGetEntryPoint(v, apc, rotation); + SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos); +} + +/** + * Check whether a runway can be reserved. + * @param tile A start or end tile of the runway. + * @param skip_first_tile whether the first tile is already occupied and should be skipped. + * @return true if none of the tiles of the runway has a runway or track reservation. + */ +bool CanRunwayBeReserved(TileIndex tile, bool skip_first_tile = false) +{ + if (tile == 0) return false; + + assert(IsTileType(tile, MP_STATION)); + assert(IsAirportTile(tile)); + assert(IsRunwayExtreme(tile)); + DiagDirection dir = GetRunwayExtremeDirection(tile); + if (IsRunwayEnd(tile)) dir = ReverseDiagDir(dir); + TileIndexDiff diff = TileOffsByDiagDir(dir); + + TileIndex t = tile; + if (skip_first_tile) t = TileAdd(t, diff); + + for (;; t = TileAdd(t, diff)) { + assert(IsAirportTile(t)); + assert(IsRunway(t)); + if (HasAirportTileAnyReservation(t)) return false; + if (t != tile && IsRunwayExtreme(t)) return true; + } + + NOT_REACHED(); +} + +/** + * Checks if an aircraft is at its next position. + * @param v aircraft + * @return whether it is at its next position. + */ +static bool IsAircraftOnNextPosition(const Aircraft *v) +{ + return v->x_pos == v->next_pos.x && v->y_pos == v->next_pos.y; +} + +/** + * Updates state for an aircraft. + * @param v aircraft. + */ +void UpdateAircraftState(Aircraft *v) +{ + // revise: check conditions + // revise: is IsAircraftOnNextPosition always true here? + if (v->state == AS_RUNNING && !IsAircraftOnNextPosition(v)) return; + if (v->IsAircraftFlying() && !v->IsAircraftFreelyFlying()) return; + + StationID cur_station = v->GetCurrentAirportID(); + StationID cur_dest_station = v->targetairport = GetTargetDestination(v->current_order, true); + AircraftState next_state = AS_IDLE; + TileIndex dest_tile = 0; + + switch (v->current_order.GetType()) { + case OT_GOTO_STATION: + next_state = AS_APRON; + dest_tile = v->GetOrderStationLocation(v->current_order.GetDestination()); + break; + + case OT_GOTO_DEPOT: + next_state = AS_HANGAR; + dest_tile = v->GetOrderHangarLocation(v->current_order.GetDestination()); + break; + + case OT_NOTHING: + if (cur_station == INVALID_STATION) { + /* If flying, find closest airport and go there. */ + ClosestDepot closestHangar = v->FindClosestDepot(); + cur_dest_station = closestHangar.st_destination; + dest_tile = v->GetOrderHangarLocation(closestHangar.destination); + } else { + /* If aircraft is in an airport, go to its hangar or aprons. */ + Station *st = Station::Get(cur_station); + if (st->airport.HasHangar()) { + next_state = AS_HANGAR; + dest_tile = v->GetOrderHangarLocation(st->airport.hangar->index); + } else { + next_state = AS_APRON; + dest_tile = v->GetOrderStationLocation(st->index); + } + } + break; + + default: + Debug(misc, 0, "Unhandled order type"); + break; + } + + v->dest_tile = dest_tile; + + if (cur_station == INVALID_STATION) { + if (cur_dest_station == INVALID_STATION && v->IsAircraftFreelyFlying()) AircraftStartsFalling(v); + return; + } + + if (cur_station != cur_dest_station) { + /* Aircraft has to leave current airport. */ + next_state = AS_START_TAKEOFF; + } + + if (v->state == next_state) return; + + switch (next_state) { + case AS_START_TAKEOFF: + if (v->IsHelicopter()) { + if (IsApron(v->tile)) { + v->state = AS_START_TAKEOFF; + } + } else { + if (IsRunwayStart(v->tile)) { + v->state = AS_START_TAKEOFF; + v->UpdateNextTile(v->tile); + } else { + v->UpdateNextTile(INVALID_TILE); + } + } + break; + case AS_APRON: + if (v->state == AS_HANGAR) break; + if (!IsApron(v->tile) || (!v->IsHelicopter() && !IsPlaneApron(v->tile))) { + // Current tile is not a valid terminal. + v->state = AS_IDLE; + v->UpdateNextTile(INVALID_TILE); + } + break; + case AS_HANGAR: + if (!IsHangarTile(v->tile)) { + if (IsHeliportTile(v->tile)) { + /* Take off, as it is not possible to reach the hangar. */ + v->state = AS_START_TAKEOFF; + v->Next()->Next()->cur_speed = 0; + break; + } + } else if (v->current_order.IsType(OT_GOTO_DEPOT) && v->current_order.GetDestination() == GetDepotIndex(v->tile)) { + v->UpdateNextTile(v->tile); + } + break; + default: + break; + } +} + +/** + * Handle Aircraft specific tasks when an Aircraft enters a hangar. + * @param v Vehicle that enters the hangar. + */ +void AircraftEntersHangar(Aircraft *v) +{ + v->subspeed = 0; + v->progress = 0; + v->cur_speed = 0; + v->state = AS_HANGAR; + + if (IsExtendedDepot(v->tile)) { + v->UpdateViewport(true, true); + SetWindowClassesDirty(WC_AIRCRAFT_LIST); + SetWindowDirty(WC_VEHICLE_VIEW, v->index); + + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); + assert(!v->IsServicing()); + v->StartService(); + } else { + assert(IsValidTrackdir(v->trackdir)); + assert(TrackdirToTrack(v->trackdir) == DiagDirToDiagTrack(GetHangarDirection(v->tile))); + if ((v->vehstatus & VS_HIDDEN) == 0) { + v->direction = v->Next()->direction = DiagDirToDir(GetHangarDirection(v->tile)); + RemoveAirportTrackReservation(v->tile, TrackdirToTrack(v->trackdir)); + + /* Hide vehicle. */ + SetVisibility(v, false); + SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos); + } + VehicleEnterDepot(v); + } } /** * Aircraft is about to leave the hangar. * @param v Aircraft leaving. - * @param exit_dir The direction the vehicle leaves the hangar. - * @note This function is called in AfterLoadGame for old savegames, so don't rely - * on any data to be valid, especially don't rely on the fact that the vehicle - * is actually on the ground inside a depot. */ -void AircraftLeaveHangar(Aircraft *v, Direction exit_dir) +void AircraftLeavesHangar(Aircraft *v) { + assert(IsHangarTile(v->tile)); v->cur_speed = 0; v->subspeed = 0; v->progress = 0; - v->direction = exit_dir; - v->vehstatus &= ~VS_HIDDEN; - { - Vehicle *u = v->Next(); - u->vehstatus &= ~VS_HIDDEN; - /* Rotor blades */ - u = u->Next(); - if (u != nullptr) { - u->vehstatus &= ~VS_HIDDEN; - u->cur_speed = 80; - } - } + Aircraft *u = v->Next(); + u->direction = v->direction; + u->trackdir = v->trackdir; + + /* Rotor blades */ + u = u->Next(); + if (u != nullptr) u->cur_speed = 80; VehicleServiceInDepot(v); v->LeaveUnbunchingDepot(); + if (!IsExtendedHangar(v->tile)) SetVisibility(v, true); + SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos); - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); SetWindowClassesDirty(WC_AIRCRAFT_LIST); } -//////////////////////////////////////////////////////////////////////////////// -/////////////////// AIRCRAFT MOVEMENT SCHEME //////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -static void AircraftEventHandler_EnterTerminal(Aircraft *v, const AirportFTAClass *apc) +/** + * Aircraft arrives at a terminal. If it is the first aircraft, throw a party. + * Start loading cargo. + * @param v Aircraft that arrived. + */ +static void AircraftEntersTerminal(Aircraft *v) { - AircraftEntersTerminal(v); - v->state = apc->layout[v->pos].heading; + assert(HasAirportTrackReserved(v->tile)); + assert(CountBits(GetReservedAirportTracks(v->tile)) == 1); + assert(IsDiagonalTrackdir(v->trackdir)); + assert(Station::IsValidID(v->targetairport)); + + Station *st = Station::Get(v->targetairport); + v->last_station_visited = st->index; + + v->state = AS_APRON + (AircraftState)GetApronType(v->tile); + + /* Check if station was ever visited before */ + if (!(st->had_vehicle_of_type & HVOT_AIRCRAFT)) { + st->had_vehicle_of_type |= HVOT_AIRCRAFT; + SetDParam(0, st->index); + /* show newsitem of celebrating citizens */ + AddVehicleNewsItem( + STR_NEWS_FIRST_AIRCRAFT_ARRIVAL, + (v->owner == _local_company) ? NT_ARRIVAL_COMPANY : NT_ARRIVAL_OTHER, + v->index, + st->index + ); + AI::NewEvent(v->owner, new ScriptEventStationFirstVehicle(st->index, v->index)); + Game::NewEvent(new ScriptEventStationFirstVehicle(st->index, v->index)); + } + + if (_settings_game.order.serviceathelipad && v->IsHelicopter() && IsHelipad(v->tile)) { + /* an excerpt of ServiceAircraft, without the invisibility stuff */ + v->date_of_last_service = TimerGameEconomy::date; + v->breakdowns_since_last_service = 0; + v->reliability = v->GetEngine()->reliability; + SetWindowDirty(WC_VEHICLE_DETAILS, v->index); + } + + v->BeginLoading(); } +void HandleAircraftLanding(Aircraft *v); + /** - * Aircraft arrived in an airport hangar. - * @param v Aircraft in the hangar. - * @param apc Airport description containing the hangar. + * Raises or lowers the helicopter. + * @param v The helicopter. + * @return Whether the helicopter is taking off or landing. + * @pre v->IsHelicopter() */ -static void AircraftEventHandler_EnterHangar(Aircraft *v, const AirportFTAClass *apc) +bool RaiseLowerHelicopter(Aircraft *v) +{ + assert(v->IsHelicopter()); + + switch (v->state) { + case AS_FLYING_HELICOPTER_TAKEOFF: + case AS_START_TAKEOFF: { + Aircraft *u = v->Next()->Next(); + + /* Make sure the rotors don't rotate too fast */ + if (u->cur_speed > 32) { + v->cur_speed = 0; + if (--u->cur_speed == 32) { + if (!PlayVehicleSound(v, VSE_START)) { + SoundID sfx = AircraftVehInfo(v->engine_type)->sfx; + /* For compatibility with old NewGRF we ignore the sfx property, unless a NewGRF-defined sound is used. + * The baseset has only one helicopter sound, so this only limits using plane or cow sounds. */ + if (sfx < ORIGINAL_SAMPLE_COUNT) sfx = SND_18_TAKEOFF_HELICOPTER; + SndPlayVehicleFx(sfx, v); + } + v->state = AS_FLYING_HELICOPTER_TAKEOFF; + } + } else { + u->cur_speed = 32; + int count = UpdateAircraftSpeed(v); + if (count > 0) { + int z_dest; + GetAircraftFlightLevelBounds(v, &z_dest, nullptr); + + /* Reached altitude? */ + if (v->z_pos + count >= z_dest) { + if (v->cur_speed != 0) SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); + v->cur_speed = 0; + if (v->NeedsAutomaticServicing()) { + Backup cur_company(_current_company, v->owner); + Command::Do(DC_EXEC, v->index, DepotCommand::Service | DepotCommand::LocateHangar, {}); + cur_company.Restore(); + } + RemoveAirportTrackReservation(v->tile, TrackdirToTrack(v->trackdir)); + v->state = AS_FLYING; + AircraftUpdateNextPos(v); + } + v->z_pos = std::min(v->z_pos + count, z_dest); + } + } + SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos); + return true; + } + + case AS_FLYING_HELICOPTER_LANDING: { + /* Find altitude of landing position. */ + int z = GetTileMaxPixelZ(v->tile) + 1; + z += GetLandingHeight(v->GetNextTile()); + + if (z == v->z_pos) { + Vehicle *u = v->Next()->Next(); + + /* Increase speed of rotors. When speed is 80, we've landed. */ + if (u->cur_speed >= 80) { + if (v->cur_speed != 0) SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); + v->cur_speed = 0; + SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos); + v->state = AS_LANDED; + HandleAircraftLanding(v); + return true; + } + u->cur_speed += 4; + } else { + int count = UpdateAircraftSpeed(v); + if (count > 0) { + SetAircraftPosition(v, v->x_pos, v->y_pos, std::max(v->z_pos - count, z)); + } + } + return true; + } + + default: + return false; + } +} + +static void PlayAircraftTakeoffSound(const Vehicle *v) { - VehicleEnterDepot(v); - v->state = apc->layout[v->pos].heading; + if (PlayVehicleSound(v, VSE_START)) return; + SndPlayVehicleFx(AircraftVehInfo(v->engine_type)->sfx, v); } /** - * Handle aircraft movement/decision making in an airport hangar. - * @param v Aircraft in the hangar. - * @param apc Airport description containing the hangar. + * Aircraft is at a position where it can start taking off. + * Check whether it should start taking off, change its mind or wait till the runway is free. + * @param v Vehicle ready to take off. + * @return whether it should stop moving this tick. */ -static void AircraftEventHandler_InHangar(Aircraft *v, const AirportFTAClass *apc) +bool HandleAircraftReadyToTakeoff(Aircraft *v) { - /* if we just arrived, execute EnterHangar first */ - if (v->previous_pos != v->pos) { - AircraftEventHandler_EnterHangar(v, apc); - return; + assert(v->state == AS_START_TAKEOFF); + + if (v->IsHelicopter()) { + assert(IsAirportTile(v->tile)); + assert(IsApron(v->tile)); + + if (v->targetairport == v->GetCurrentAirportID()) { + /* Trying to go to the same airport. */ + // revise + NOT_REACHED(); + v->state = AS_IDLE; + return true; + } + + RaiseLowerHelicopter(v); + return true; } - /* if we were sent to the depot, stay there */ - if (v->current_order.IsType(OT_GOTO_DEPOT) && (v->vehstatus & VS_STOPPED)) { - v->current_order.Free(); - return; + assert(IsRunwayStart(v->tile)); + + if (v->targetairport == v->GetCurrentAirportID()) { + /* Trying to go to the same airport. */ + v->state = AS_IDLE; + return true; } - /* Check if we should wait here for unbunching. */ - if (v->IsWaitingForUnbunching()) return; + /* Aircraft tries to take off using a runway. */ + if (!CanRunwayBeReserved(v->tile, true)) return true; - if (!v->current_order.IsType(OT_GOTO_STATION) && - !v->current_order.IsType(OT_GOTO_DEPOT)) - return; + RemoveAirportTrackReservation(v->tile, TrackdirToTrack(v->trackdir)); + SetRunwayReservation(v->tile, true); - /* We are leaving a hangar, but have to go to the exact same one; re-enter */ - if (v->current_order.IsType(OT_GOTO_DEPOT) && v->current_order.GetDestination() == v->targetairport) { - VehicleEnterDepot(v); - return; + v->state = AS_TAKEOFF_BEFORE_FLYING; + v->next_trackdir = v->Next()->next_trackdir = DiagDirToDiagTrackdir(GetRunwayExtremeDirection(v->tile)); + v->UpdateNextTile(GetRunwayExtreme(v->tile, GetRunwayExtremeDirection(v->tile))); + + if (v->trackdir != v->next_trackdir) { + /* If plane needs to rotate, rotate first and then play the take off sound. */ + v->SetWaitTime(AIRCRAFT_ROTATION_STEP_TICKS); + } else { + /* Plane doesn't need to rotate. Play the take off sound right now. */ + PlayAircraftTakeoffSound(v); } - /* if the block of the next position is busy, stay put */ - if (AirportHasBlock(v, &apc->layout[v->pos], apc)) return; + return false; +} - /* We are already at the target airport, we need to find a terminal */ - if (v->current_order.GetDestination() == v->targetairport) { - /* FindFreeTerminal: - * 1. Find a free terminal, 2. Occupy it, 3. Set the vehicle's state to that terminal */ - if (v->subtype == AIR_HELICOPTER) { - if (!AirportFindFreeHelipad(v, apc)) return; // helicopter - } else { - if (!AirportFindFreeTerminal(v, apc)) return; // airplane +/** + * Aircraft is taking off accelerating on runway, starting its flight or leaving the airport. + * @param v Vehicle that is taking off. + */ +void HandleAircraftTakingoff(Aircraft *v) +{ + switch (v->state) { + case AS_TAKEOFF_BEFORE_FLYING: + assert(!v->IsHelicopter()); + v->state = AS_FLYING_TAKEOFF; + v->UpdateNextTile(v->GetNextTile()); + break; + + case AS_FLYING_TAKEOFF: { + assert(!v->IsHelicopter()); + /* Next tile contains the runway end, so it can be unreserved. */ + TileIndex old_runway_tile = v->GetNextTile(); + SetRunwayReservation(old_runway_tile, false); + v->state = AS_FLYING_LEAVING_AIRPORT; + v->UpdateNextTile(old_runway_tile); + break; + } + + case AS_FLYING_HELICOPTER_TAKEOFF: + RaiseLowerHelicopter(v); + break; + + case AS_FLYING_LEAVING_AIRPORT: { + v->state = AS_FLYING; + v->UpdateNextTile(FindClosestLandingTile(v)); + break; + } + + default: + Debug(misc, 0, "Shouldnt be reached, state {}", v->state); + break; + } +} + +TileIndex FindClosestFreeLandingTile(Aircraft *v); + +/** + * Handle Aircraft flying outside any airport or keeping a holding pattern + * on its target airport. + * @param v Vehicle that is flying towards its next target station, if any. + */ +void HandleAircraftFlying(Aircraft *v) +{ + switch (v->state) { + case AS_ON_HOLD_WAITING: { + bool can_land = !(Station::Get(v->targetairport))->airport.IsClosed(); + if (v->IsHelicopter()) { + // revise + //if (!IsAirportTile(v->tile) || !IsApron(v->tile)) return; + assert(v->IsAircraftFlying()); + TileIndex landing_tile; + Trackdir trackdir; + if (can_land) { + landing_tile = FindClosestFreeLandingTile(v); + trackdir = GetFreeAirportTrackdir(landing_tile, DiagDirToDiagTrackdir(DirToDiagDir(v->direction))); + can_land = trackdir != INVALID_TRACKDIR; + } + + if (can_land) { + assert(IsValidTrackdir(trackdir)); + SetAirportTrackReservation(landing_tile, TrackdirToTrack(trackdir)); + v->state = AS_ON_HOLD_APPROACHING; + v->tile = landing_tile; + v->UpdateNextTile(landing_tile); + v->Next()->next_trackdir = trackdir; + } else { + v->UpdateNextTile(v->GetNextTile()); + } + } else { + assert(IsValidTrackdir(v->trackdir)); + TileIndex landing_tile = FindClosestLandingTile(v); + if (can_land && CanRunwayBeReserved(landing_tile)) { + assert(IsRunwayStart(landing_tile)); + v->trackdir = DiagDirToDiagTrackdir(GetRunwayExtremeDirection(landing_tile)); + SetRunwayReservation(landing_tile, true); + v->state = AS_ON_HOLD_APPROACHING; + v->UpdateNextTile(landing_tile); + } else { + v->UpdateNextTile(v->GetNextTile()); + } + } + break; + } + + case AS_ON_HOLD_APPROACHING: + if (v->IsHelicopter()) { + v->state = AS_DESCENDING; + assert(HasAirportTrackReserved(v->GetNextTile())); + assert(HasAirportTrackReserved(v->GetNextTile(), TrackdirToTrack(v->Next()->next_trackdir))); + v->trackdir = DiagDirToDiagTrackdir(DirToDiagDir(v->direction)); + if (v->trackdir != v->Next()->next_trackdir) { + v->next_trackdir = v->Next()->next_trackdir; + } + } else { + if (v->next_pos.pos == AP_PLANE_HOLD_3) { + v->state = AS_DESCENDING; + } + } + v->UpdateNextTile(v->GetNextTile()); + break; + + case AS_FLYING: + v->state = AS_ON_HOLD_WAITING; + v->UpdateNextTile(v->GetNextTile()); + break; + + case AS_FLYING_NO_DEST: + break; + + default: + Debug(misc, 0, "Shouldnt be reached, state {}", v->state); + break; + } +} + +/** + * Handle Aircraft landing on an airport. + * @param v Landing aircraft. + */ +void HandleAircraftLanding(Aircraft *v) +{ + switch (v->state) { + case AS_LANDED: { + if (v->IsHelicopter()) { + assert(IsAirportTile(v->tile)); + assert(IsApron(v->tile)); + v->state = (AircraftState)((uint8_t)GetApronType(v->tile) + (uint8_t)AS_APRON); + break; + } + + assert(IsAirportTile(v->tile)); + assert(IsRunwayExtreme(v->tile)); + assert(IsRunwayEnd(v->tile)); + + /* Free platform and reserve a track and set it to next trackdir. */ + Trackdir trackdir = DiagDirToDiagTrackdir(GetRunwayExtremeDirection(v->tile)); + Trackdir next_trackdir = GetFreeAirportTrackdir(v->tile, trackdir); + if (!IsValidTrackdir(next_trackdir)) { + v->SetWaitTime(AIRCRAFT_CANT_LEAVE_RUNWAY); + break; + } + + v->trackdir = trackdir; + SetRunwayReservation(v->tile, false); + if (next_trackdir != v->trackdir) { + v->next_trackdir = next_trackdir; + v->SetWaitTime(AIRCRAFT_ROTATION_STEP_TICKS); + } + SetAirportTrackReservation(v->tile, TrackdirToTrack(next_trackdir)); + v->state = AS_IDLE; + v->cur_speed = 0; + SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); + v->UpdateNextTile(INVALID_TILE); + break; + } + + case AS_FLYING_LANDING: + Debug(misc, 0, "Aircraft reached landed runway end still flying. Error of controller. Crashing aircraft unitnumber {} on air", v->unitnumber); + v->state = AS_LANDED; + CrashAircraft(v); + return; + + case AS_DESCENDING: + if (v->IsHelicopter()) { + v->state = AS_FLYING_HELICOPTER_LANDING; + } else { + v->state = AS_FLYING_LANDING; + TileIndex tile = GetRunwayExtreme(v->tile, GetRunwayExtremeDirection(v->tile)); + v->UpdateNextTile(tile); + } + break; + + default: + Debug(misc, 0, "Shouldnt be reached, state {}", v->state); + break; + } +} + +/** + * Plane touched down at the landing strip. + * @param v Aircraft that landed. + */ +static void HandlePlaneLandsOnRunway(Aircraft *v) +{ + assert(!v->IsHelicopter()); + assert(v->state == AS_FLYING_LANDING); + Station *st = Station::Get(v->targetairport); + + TileIndex vt = TileVirtXY(v->x_pos, v->y_pos); + + v->state = AS_LANDED; + v->UpdateNextTile(v->GetNextTile()); + + /* Check if the aircraft needs to be replaced or renewed and send it to a hangar if needed. */ + if (v->NeedsAutomaticServicing()) { + Backup cur_company(_current_company, v->owner); + Command::Do(DC_EXEC, v->index, DepotCommand::Service, {}); + cur_company.Restore(); + } + + v->UpdateDeltaXY(); + + AirportTileAnimationTrigger(st, vt, AAT_STATION_AIRPLANE_LAND); + + if (!PlayVehicleSound(v, VSE_TOUCHDOWN)) { + SndPlayVehicleFx(SND_17_SKID_PLANE, v); + } +} + +/** + * Given the current state of an aircraft, get which is the next + * state to reach its target. + * @param a Aircraft + * @return the next state \a a should try to reach. + */ +AircraftState GetNextAircraftState(const Aircraft &a) +{ + assert(!a.IsAircraftFlying()); + + if (GetStationIndex(a.tile) != a.targetairport) { + /* Aircraft has to leave current airport. */ + return AS_START_TAKEOFF; + } + + if (a.state != AS_RUNNING && IsRunwayEnd(a.tile)) { + Airport *airport = &Station::GetByTile(a.tile)->airport; + bool free_terminal = false; + for (TileIndex tile : airport->aprons) { + if (HasAirportTrackReserved(tile)) continue; + free_terminal = true; + break; + } + + if (!free_terminal) { + return airport->HasHangar() ? AS_HANGAR : AS_IDLE; + } + } + + switch (a.current_order.GetType()) { + case OT_GOTO_STATION: + return a.IsHelicopter() ? AS_HELIPAD : AS_APRON; + + case OT_GOTO_DEPOT: + return AS_HANGAR; + + case OT_NOTHING: + /* If aircraft is in an airport, go to its hangar or aprons. */ + if (Station::Get(a.targetairport)->airport.HasHangar()) { + return AS_HANGAR; + } else { + return a.IsHelicopter() ? AS_HELIPAD : AS_APRON; + } + return AS_IDLE; + + default: + return AS_IDLE; + } +} + +/** + * Aircraft reached a position where it may change to another state. + * Decide what to do. + * @param v aircraft. + * @return whether it should stop moving this tick. + */ +bool HandleAircraftState(Aircraft *v) +{ + if (!IsAircraftOnNextPosition(v)) return false; + + if (!v->IsAircraftFlying()) { + switch (v->current_order.GetType()) { + case OT_LEAVESTATION: + /* A leave station order only needs one tick to get processed, + * so we can always skip ahead. */ + v->current_order.Free(); + SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); + ProcessOrders(v); + v->state = AS_IDLE; + v->UpdateNextTile(INVALID_TILE); + UpdateAircraftState(v); + return true; + + case OT_GOTO_STATION: + if (IsAirportTile(v->tile) && IsApron(v->tile)) { + if (v->targetairport == GetStationIndex(v->tile)) { + AircraftEntersTerminal(v); + } else { + v->state = GetNextAircraftState(*v); + v->UpdateNextTile(INVALID_TILE); + } + return true; + } + break; + + case OT_NOTHING: + case OT_GOTO_DEPOT: + if (IsHangarTile(v->tile) && v->state != AS_HANGAR) { + AircraftEntersHangar(v); + v->UpdateNextTile(INVALID_TILE); + return true; + } else if (v->state == AS_RUNNING) { + v->state = AS_IDLE; + } + break; + + default: + break; + } + } + + UpdateAircraftState(v); + + switch (v->state) { + case AS_APRON: + case AS_HELIPAD: + case AS_HELIPORT: + case AS_BUILTIN_HELIPORT: + /* Helicopter takeoff. */ + AircraftEntersTerminal(v); + return true; + + case AS_START_TAKEOFF: + return HandleAircraftReadyToTakeoff(v); + + case AS_TAKEOFF_BEFORE_FLYING: + case AS_FLYING_TAKEOFF: + case AS_FLYING_LEAVING_AIRPORT: + case AS_FLYING_HELICOPTER_TAKEOFF: + HandleAircraftTakingoff(v); + return false; + + case AS_FLYING_NO_DEST: + case AS_FLYING: + case AS_ON_HOLD_WAITING: + case AS_ON_HOLD_APPROACHING: + HandleAircraftFlying(v); + return false; + + case AS_FLYING_LANDING: + case AS_DESCENDING: + case AS_LANDED: + HandleAircraftLanding(v); + return v->IsHelicopter() || v->state == AS_IDLE; + + case AS_FLYING_HELICOPTER_LANDING: + RaiseLowerHelicopter(v); + return true; + + case AS_IDLE: + return false; + + case AS_HANGAR: + if (IsHangarTile(v->tile)) { + AircraftEntersHangar(v); + v->UpdateNextTile(INVALID_TILE); + UpdateAircraftState(v); + return true; + } + break; + case AS_RUNNING: + Debug(misc, 0, "Moving aircraft {}, shouldn't reach this point. Probably there will be a crash soon.", v->unitnumber); + break; + default: + Debug(misc, 0, "Unhandled state {}, next {}", v->state, v->Next()->state); + NOT_REACHED(); + break; + } + + return false; +} + +/** + * Update the aircraft flight level according to aircraft state and position. + * @param v Aircraft. + * @pre v->IsAircraftFlying() + */ +void HandleAircraftFlightLevel(Aircraft *v) +{ + assert(v->IsAircraftFlying()); + + switch(v->state) { + case AS_ON_HOLD_WAITING: + case AS_ON_HOLD_APPROACHING: + if (v->z_pos > GetAircraftHoldMaxAltitude(v)) v->z_pos--; + break; + case AS_DESCENDING: { + assert(IsValidTile(v->GetNextTile())); + int z = GetTileHeightBelowAircraft(v) + 1; + /* Runway may be in a higher tile than the current one. */ + z = std::max(z, GetTileMaxPixelZ(v->GetNextTile()) + 1); + z = v->z_pos - z; + + if (z > 32) { + v->z_pos = v->z_pos - 2; + } else if (z > 16 || (v->tile == v->GetNextTile() && z > 8)) { + v->z_pos = v->z_pos - 1; + } + break; + } + + case AS_FLYING_LANDING: { + int z = GetTileHeightBelowAircraft(v) + 1; + assert(z < v->z_pos); + v->z_pos -= 1; + if (v->z_pos == z) HandlePlaneLandsOnRunway(v); + break; + } + + default: + v->z_pos = GetAircraftFlightLevel(v, v->state == AS_FLYING_TAKEOFF); + break; + } +} + +AircraftPosition _aircraft_pos_offsets[AP_END] = { + { AP_DEFAULT , 8 , 8 }, // default: middle of the tile + { AP_HELICOPTER_HOLD_2 , 8 + 1 * (int)TILE_SIZE, 0 + 0 * (int)TILE_SIZE }, // on helicopter hold start respect the apron tile + { AP_HELICOPTER_HOLD_3 , 0 + 0 * (int)TILE_SIZE, 8 + 1 * (int)TILE_SIZE }, // on helicopter hold, pos 2 + { AP_HELICOPTER_HOLD_4 , 0 - 1 * (int)TILE_SIZE, 8 + 1 * (int)TILE_SIZE }, // on helicopter hold, pos 3 + { AP_HELICOPTER_HOLD_5 , -8 - 2 * (int)TILE_SIZE, 0 + 0 * (int)TILE_SIZE }, // on helicopter hold, pos 4 + { AP_HELICOPTER_HOLD_6 , -8 - 2 * (int)TILE_SIZE, 0 - 1 * (int)TILE_SIZE }, // on helicopter hold, pos 5 + { AP_HELICOPTER_HOLD_7 , 0 - 1 * (int)TILE_SIZE, -8 - 2 * (int)TILE_SIZE }, // on helicopter hold, pos 6 + { AP_HELICOPTER_HOLD_END , 0 + 0 * (int)TILE_SIZE, -8 - 2 * (int)TILE_SIZE }, // on helicopter hold, pos 7 + { AP_HELICOPTER_HOLD_START , 8 + 1 * (int)TILE_SIZE, 0 - 1 * (int)TILE_SIZE }, // on helicopter hold, pos 8 and last + { AP_HELIPORT_DEST , 6 , 8 }, // heliport landing dest respect the apron tile + { AP_BUILTIN_HELIPORT_DEST , - 2 + 2 * (int)TILE_SIZE, 8 }, // builtin heliport dest respect the tile containing the airport + { AP_PLANE_BEFORE_FLYING , 8 , 8 }, // default: middle of the tile + { AP_PLANE_START_FLYING , 8 + 1 * (int)TILE_SIZE, 8 }, // start flying on runway respect the runway end + { AP_PLANE_LEAVE_AIRPORT , 8 + 0 * (int)TILE_SIZE, 8 }, // remove runway reservation respect the runway end + { AP_PLANE_HOLD_START , -8 - 5 * (int)TILE_SIZE, 8 }, // leaving airport respect the runway end + { AP_PLANE_HOLD_2 , 0 + 8 * (int)TILE_SIZE, 8 }, // on hold start and also descending respect the runway start tile + { AP_PLANE_HOLD_3 , 0 + 4 * (int)TILE_SIZE, 8 }, // on hold, pos 2 + { AP_PLANE_HOLD_4 , 0 - 8 * (int)TILE_SIZE, 8 }, // on hold, pos 3 + { AP_PLANE_HOLD_5 , -8 - 11 * (int)TILE_SIZE, 0 - 3 * (int)TILE_SIZE }, // on hold, pos 4 + { AP_PLANE_HOLD_6 , -8 - 11 * (int)TILE_SIZE, 0 - 7 * (int)TILE_SIZE }, // on hold, pos 5 + { AP_PLANE_HOLD_7 , 0 - 8 * (int)TILE_SIZE, 8 - 11 * (int)TILE_SIZE }, // on hold, pos 6 + { AP_PLANE_HOLD_8 , 0 + 8 * (int)TILE_SIZE, 8 - 11 * (int)TILE_SIZE }, // on hold, pos 7 + { AP_PLANE_HOLD_END , -8 + 12 * (int)TILE_SIZE, 0 - 7 * (int)TILE_SIZE }, // on hold, pos 8 + { AP_PLANE_HOLD_START , -8 + 12 * (int)TILE_SIZE, 0 - 3 * (int)TILE_SIZE }, // on hold, pos 9 and last + { AP_PLANE_LANDING , 8 , 8 }, // descending + { AP_DEFAULT , 8 , 8 }, // landing + +}; + +/** + * Get the position for a given position type and rotation. + * @param pos Position type + * @param dir DiagDirection indicating the rotation to apply. + * @return aircraft position for the position type and rotation. + */ +AircraftPosition RotatedAircraftPosition(AircraftPos pos, DiagDirection dir) +{ + AircraftPosition rotated_pos; + rotated_pos = _aircraft_pos_offsets[pos]; + switch (dir) { + case DIAGDIR_NE: + break; + case DIAGDIR_SE: + rotated_pos.x = _aircraft_pos_offsets[pos].y; + rotated_pos.y = TILE_SIZE - _aircraft_pos_offsets[pos].x; + break; + case DIAGDIR_SW: + rotated_pos.x = TILE_SIZE - _aircraft_pos_offsets[pos].x; + rotated_pos.y = TILE_SIZE - _aircraft_pos_offsets[pos].y; + break; + case DIAGDIR_NW: + rotated_pos.x = TILE_SIZE - _aircraft_pos_offsets[pos].y; + rotated_pos.y = _aircraft_pos_offsets[pos].x; + break; + default: + NOT_REACHED(); + } + + return rotated_pos; +} + +AircraftPos helicopter_entry_point[8] = { + AP_HELICOPTER_HOLD_2, + AP_HELICOPTER_HOLD_7, + AP_HELICOPTER_HOLD_3, + AP_HELICOPTER_HOLD_6, + AP_HELICOPTER_HOLD_START, + AP_HELICOPTER_HOLD_END, + AP_HELICOPTER_HOLD_4, + AP_HELICOPTER_HOLD_5, +}; + +AircraftPos plane_entry_pos[DIAGDIR_END][4] = { + {AP_PLANE_HOLD_START, AP_PLANE_HOLD_7, AP_PLANE_HOLD_5, AP_PLANE_HOLD_3}, + {AP_PLANE_HOLD_5, AP_PLANE_HOLD_3, AP_PLANE_HOLD_7, AP_PLANE_HOLD_START}, + {AP_PLANE_HOLD_7, AP_PLANE_HOLD_START, AP_PLANE_HOLD_3, AP_PLANE_HOLD_5}, + {AP_PLANE_HOLD_3, AP_PLANE_HOLD_5, AP_PLANE_HOLD_START, AP_PLANE_HOLD_7}, +}; + +/** + * Get the offset position an aircraft must get respect a tile or its next position. + * @param tile The tile the aircraft tries to reach. + * @param next_pos The next position type the aircraft is trying to reach. + * @return the destination position of the aircraft. + */ +AircraftPosition GetAircraftPositionByTile(TileIndex tile, AircraftPos next_pos) { + assert(IsAirportTile(tile)); + + switch (GetAirportTileType(tile)) { + case ATT_APRON_NORMAL: + case ATT_APRON_HELIPAD: + return _aircraft_pos_offsets[AP_DEFAULT]; + case ATT_APRON_HELIPORT: { + DiagDirection diagdir = GetAirportTileRotation(tile); + return RotatedAircraftPosition(AP_HELIPORT_DEST, diagdir); } - } else { // Else prepare for launch. - /* airplane goto state takeoff, helicopter to helitakeoff */ - v->state = (v->subtype == AIR_HELICOPTER) ? HELITAKEOFF : TAKEOFF; - } - const Station *st = Station::GetByTile(v->tile); - AircraftLeaveHangar(v, st->airport.GetHangarExitDirection(v->tile)); - AirportMove(v, apc); -} - -/** At one of the Airport's Terminals */ -static void AircraftEventHandler_AtTerminal(Aircraft *v, const AirportFTAClass *apc) -{ - /* if we just arrived, execute EnterTerminal first */ - if (v->previous_pos != v->pos) { - AircraftEventHandler_EnterTerminal(v, apc); - /* on an airport with helipads, a helicopter will always land there - * and get serviced at the same time - setting */ - if (_settings_game.order.serviceathelipad) { - if (v->subtype == AIR_HELICOPTER && apc->num_helipads > 0) { - /* an excerpt of ServiceAircraft, without the invisibility stuff */ - v->date_of_last_service = TimerGameEconomy::date; - v->date_of_last_service_newgrf = TimerGameCalendar::date; - v->breakdowns_since_last_service = 0; - v->reliability = v->GetEngine()->reliability; - SetWindowDirty(WC_VEHICLE_DETAILS, v->index); + case ATT_APRON_BUILTIN_HELIPORT: + return _aircraft_pos_offsets[AP_BUILTIN_HELIPORT_DEST]; + case ATT_RUNWAY_START_NO_LANDING: + case ATT_RUNWAY_START_ALLOW_LANDING: + return _aircraft_pos_offsets[AP_DEFAULT]; + default: + return _aircraft_pos_offsets[next_pos]; + } +} + + +/** + * @param v Aircraft + */ +void SetNextAircraftPosition(Aircraft &v) +{ + TileIndex tile = v.GetNextTile(); + AircraftPos next_pos = v.next_pos.pos; + DiagDirection diagdir = DIAGDIR_NE; + + switch (v.state) { + case AS_START_TAKEOFF: + next_pos = AP_START_TAKE_OFF; + [[fallthrough]]; + case AS_DESCENDING: + case AS_FLYING_LEAVING_AIRPORT: + case AS_TAKEOFF_BEFORE_FLYING: + case AS_FLYING_TAKEOFF: + if (!IsValidTile(tile)) break; + assert(IsAirportTile(tile)); + if (v.IsHelicopter()) { + assert(IsApron(tile)); + v.next_pos = GetAircraftPositionByTile(tile, AP_DEFAULT); + } else { + if (v.state == AS_DESCENDING) next_pos = AP_PLANE_DESCENDING; + assert(IsRunwayExtreme(tile)); + diagdir = GetRunwayExtremeDirection(tile); + v.next_pos = RotatedAircraftPosition(next_pos, diagdir); + } + break; + + case AS_FLYING_NO_DEST: + if (next_pos == AP_DEFAULT) { + diagdir = DirToDiagDir(v.direction); + next_pos = AP_PLANE_HOLD_START; + } + v.next_pos = RotatedAircraftPosition(next_pos, diagdir); + break; + + case AS_FLYING_LANDING: + assert(IsRunwayEnd(tile) && IsLandingTypeTile(tile)); + diagdir = GetRunwayExtremeDirection(tile); + diagdir = ReverseDiagDir(diagdir); + v.next_pos = RotatedAircraftPosition(AP_PLANE_LANDING, diagdir); + break; + + case AS_LANDED: + assert(IsValidTile(tile)); + assert(IsAirportTile(tile)); + assert(!v.IsHelicopter()); + assert(IsRunwayEnd(tile) && IsLandingTypeTile(tile)); + diagdir = GetRunwayExtremeDirection(tile); + v.next_pos = RotatedAircraftPosition(AP_PLANE_LANDING, diagdir); + break; + + case AS_ON_HOLD_APPROACHING: + if (v.IsHelicopter()) { + assert(IsAirportTile(tile)); + v.next_pos = GetAircraftPositionByTile(tile, AP_DEFAULT); + + break; + } + [[fallthrough]]; + + case AS_ON_HOLD_WAITING: + if (!v.IsHelicopter()) { + assert(IsAirportTile(tile)); + assert(IsRunwayExtreme(tile)); + /* If heading to the same runway, but it is occupied, try rotating. */ + diagdir = GetRunwayExtremeDirection(tile); + v.next_pos = RotatedAircraftPosition(next_pos, diagdir); + break; + } + [[fallthrough]]; + + case AS_FLYING: { + // Decide the entry point + AircraftPosition origin_offset; + assert(IsAirportTile(tile)); + if (v.IsHelicopter()) { + origin_offset = GetAircraftPositionByTile(tile, AP_DEFAULT); + } else { + assert(IsAirportTile(tile)); + assert(IsRunwayStart(tile) && IsLandingTypeTile(tile)); + diagdir = GetRunwayExtremeDirection(tile); + origin_offset = RotatedAircraftPosition(AP_PLANE_HOLD_START, diagdir); + } + + int delta_x = v.x_pos - TileX(tile) * TILE_SIZE - origin_offset.x; + int delta_y = v.y_pos - TileY(tile) * TILE_SIZE - origin_offset.y; + + uint entry_num; + AircraftPos entry_pos; + if (v.IsHelicopter()) { + entry_num = (delta_y < 0) + + ((delta_x < 0) << 1) + + ((abs(delta_y) < abs(delta_x)) << 2); + entry_pos = helicopter_entry_point[entry_num]; + } else { + if (abs(delta_y) < abs(delta_x)) { + /* We are northeast or southwest of the airport */ + entry_pos = plane_entry_pos[diagdir][delta_x < 0]; + } else { + /* We are northwest or southeast of the airport */ + entry_pos = plane_entry_pos[diagdir][(delta_y < 0) + 2]; + } + } + + v.next_pos = RotatedAircraftPosition(entry_pos, diagdir); + if (v.IsHelicopter()) { + v.next_pos.x += origin_offset.x; + v.next_pos.y += origin_offset.y; } + break; } + + default: + if (!IsValidTile(tile)) break; + v.next_pos = GetAircraftPositionByTile(tile, next_pos); + break; + } +} + +/** + * Update this->next_pos and next path tile (this->Next()->dest_tile). + * Use it after updating next_tile or when next desired position changes + * (i.e. when flying and approaching a runway). + * @param tile next tile + */ +void Aircraft::UpdateNextTile(TileIndex tile) +{ + if (tile == 0) return; + + /* Update next path tile. */ + this->Next()->dest_tile = tile; + + SetNextAircraftPosition(*this); + + this->next_pos.x += TileX(tile) * TILE_SIZE; + this->next_pos.y += TileY(tile) * TILE_SIZE; +} + +/** Set the right pos when heading to other airports after takeoff. + * @param v Aircraft. + */ +void AircraftUpdateNextPos(Aircraft *v) +{ + assert(v->IsAircraftFreelyFlying()); + + TileIndex tile = v->GetNextTile(); + if (IsValidTile(tile) && IsAirportTile(tile) && + IsRunwayStart(tile) && v->targetairport == GetStationIndex(tile)) { return; } - if (v->current_order.IsType(OT_NOTHING)) return; + AssignLandingTile(v, FindClosestLandingTile(v)); + v->UpdateNextTile(v->GetNextTile()); +} + +/** + * Get a tile where aircraft can land. For helicopters, it will check helipads, heliports + * and aprons, in this ordrer, and finally runways. For normal aircraft, it will check runways. + * @param v The aircraft trying to land. + * @return a valid tile where to land, or 0 otherwise. + */ +TileIndex FindClosestLandingTile(Aircraft *v) +{ + v->targetairport = GetTargetDestination(v->current_order, true); + assert(Station::IsValidID(v->targetairport)); + Station *st = Station::GetIfValid(v->targetairport); + + if (!CanVehicleUseStation(v, st)) return 0; + + TileIndex landing_tile = 0; + TileIndex free_landing_tile = 0; + uint32_t best_dist = UINT32_MAX; + uint32_t free_best_dist = UINT32_MAX; + + if (v->IsHelicopter()) { + for (auto &it : st->airport.helipads) { + if (DistanceSquare(it, v->tile) < best_dist) { + landing_tile = it; + best_dist = DistanceSquare(it, v->tile); + } + if (!HasAirportTrackReserved(it) && DistanceSquare(it, v->tile) < free_best_dist) { + free_landing_tile = it; + free_best_dist = DistanceSquare(it, v->tile); + } + } + + if (free_landing_tile != 0) return free_landing_tile; - /* if the block of the next position is busy, stay put */ - if (AirportHasBlock(v, &apc->layout[v->pos], apc)) return; + if (v->current_order.GetType() != OT_GOTO_DEPOT) { + for (auto &it : st->airport.heliports) { + if (DistanceSquare(it, v->tile) < best_dist) { + landing_tile = it; + best_dist = DistanceSquare(it, v->tile); + } + if (!HasAirportTrackReserved(it) && DistanceSquare(it, v->tile) < free_best_dist) { + free_landing_tile = it; + free_best_dist = DistanceSquare(it, v->tile); + } + } + } - /* airport-road is free. We either have to go to another airport, or to the hangar - * ---> start moving */ + if (free_landing_tile != 0) return free_landing_tile; + + for (auto &it : st->airport.aprons) { + if (DistanceSquare(it, v->tile) < best_dist) { + landing_tile = it; + best_dist = DistanceSquare(it, v->tile); + } + if (!HasAirportTrackReserved(it) && DistanceSquare(it, v->tile) < free_best_dist) { + free_landing_tile = it; + free_best_dist = DistanceSquare(it, v->tile); + } + } + + if (free_landing_tile != 0) return free_landing_tile; + + for (auto &it : st->airport.aprons) { + if (DistanceSquare(it, v->tile) < best_dist) { + landing_tile = it; + best_dist = DistanceSquare(it, v->tile); + } + if (!HasAirportTrackReserved(it) && DistanceSquare(it, v->tile) < free_best_dist) { + free_landing_tile = it; + free_best_dist = DistanceSquare(it, v->tile); + } + } - bool go_to_hangar = false; - switch (v->current_order.GetType()) { - case OT_GOTO_STATION: // ready to fly to another airport - break; - case OT_GOTO_DEPOT: // visit hangar for servicing, sale, etc. - go_to_hangar = v->current_order.GetDestination() == v->targetairport; - break; - case OT_CONDITIONAL: - /* In case of a conditional order we just have to wait a tick - * longer, so the conditional order can actually be processed; - * we should not clear the order as that makes us go nowhere. */ - return; - default: // orders have been deleted (no orders), goto depot and don't bother us - v->current_order.Free(); - go_to_hangar = true; + return landing_tile; } - if (go_to_hangar && Station::Get(v->targetairport)->airport.HasHangar()) { - v->state = HANGAR; - } else { - /* airplane goto state takeoff, helicopter to helitakeoff */ - v->state = (v->subtype == AIR_HELICOPTER) ? HELITAKEOFF : TAKEOFF; + for (auto &it : st->airport.runways) { + if (!IsLandingTypeTile(it)) continue; + if (DistanceSquare(it, v->tile) < best_dist) { + landing_tile = it; + best_dist = DistanceSquare(it, v->tile); + } + if (CanRunwayBeReserved(it) && + DistanceSquare(it, v->tile) < free_best_dist) { + free_landing_tile = it; + free_best_dist = DistanceSquare(it, v->tile); + } } - AirportMove(v, apc); -} -static void AircraftEventHandler_General(Aircraft *, const AirportFTAClass *) -{ - FatalError("OK, you shouldn't be here, check your Airport Scheme!"); -} + if (free_landing_tile != 0) return free_landing_tile; -static void AircraftEventHandler_TakeOff(Aircraft *v, const AirportFTAClass *) -{ - PlayAircraftSound(v); // play takeoffsound for airplanes - v->state = STARTTAKEOFF; + return landing_tile; } -static void AircraftEventHandler_StartTakeOff(Aircraft *v, const AirportFTAClass *) -{ - v->state = ENDTAKEOFF; - v->UpdateDeltaXY(); +/** + * Get a tile where aircraft can land. For helicopters, it will check helipads, heliports + * and aprons, in this ordrer, and finally runways. For normal aircraft, it will check runways. + * @param v The aircraft trying to land. + * @return a valid tile where to land, or INVALID_TILE otherwise. + */ +TileIndex FindClosestFreeLandingTile(Aircraft *v) { + TileIndex tile = FindClosestLandingTile(v); + if (tile == 0) return INVALID_TILE; + if (HasAirportTrackReserved(tile)) return INVALID_TILE; + return tile; } -static void AircraftEventHandler_EndTakeOff(Aircraft *v, const AirportFTAClass *) -{ - v->state = FLYING; - /* get the next position to go to, differs per airport */ - AircraftNextAirportPos_and_Order(v); -} -static void AircraftEventHandler_HeliTakeOff(Aircraft *v, const AirportFTAClass *) +ClosestDepot Aircraft::FindClosestDepot() { - v->state = FLYING; - v->UpdateDeltaXY(); + const Station *st = Station::GetIfValid(this->GetCurrentAirportID()); + if (st == nullptr || !st->airport.HasHangar()) st = GetTargetAirportIfValid(this); + /* If the station is not a valid airport or if it has no hangars */ + if (st == nullptr || !CanVehicleUseStation(this, st) || !st->airport.HasHangar()) { + /* the aircraft has to search for a hangar on its own */ + StationID station = FindClosestHangar(this); - /* get the next position to go to, differs per airport */ - AircraftNextAirportPos_and_Order(v); + if (station == INVALID_STATION) return ClosestDepot(); - /* Send the helicopter to a hangar if needed for replacement */ - if (v->NeedsAutomaticServicing()) { - Backup cur_company(_current_company, v->owner); - Command::Do(DC_EXEC, v->index, DepotCommand::Service | DepotCommand::LocateHangar, {}); - cur_company.Restore(); + st = Station::Get(station); } + + return ClosestDepot(st->airport.hangar->depot_tiles[0], st->airport.hangar->index, st->index); } -static void AircraftEventHandler_Flying(Aircraft *v, const AirportFTAClass *apc) +/** + * Checks whether an aircraft can land on the next targetairport. + * It checks whether it can land (helipads for helicopters, whether there is a landing runway...). + * It also checks if the destination is too far. + * @param v Aircraft + * @return whether it can reach its targetairport + */ +bool IsReachableDest(Aircraft *v) { + assert(IsAirportTile(v->tile)); + assert(!v->IsAircraftFlying()); + if (v->targetairport == GetStationIndex(v->tile)) return true; + if (v->targetairport == INVALID_STATION) return false; + + assert(Station::IsValidID(v->targetairport)); Station *st = Station::Get(v->targetairport); - /* Runway busy, not allowed to use this airstation or closed, circle. */ - if (CanVehicleUseStation(v, st) && (st->owner == OWNER_NONE || st->owner == v->owner) && !(st->airport.flags & AIRPORT_CLOSED_block)) { - /* {32,FLYING,NOTHING_block,37}, {32,LANDING,N,33}, {32,HELILANDING,N,41}, - * if it is an airplane, look for LANDING, for helicopter HELILANDING - * it is possible to choose from multiple landing runways, so loop until a free one is found */ - uint8_t landingtype = (v->subtype == AIR_HELICOPTER) ? HELILANDING : LANDING; - const AirportFTA *current = apc->layout[v->pos].next; - while (current != nullptr) { - if (current->heading == landingtype) { - /* save speed before, since if AirportHasBlock is false, it resets them to 0 - * we don't want that for plane in air - * hack for speed thingie */ - uint16_t tcur_speed = v->cur_speed; - uint16_t tsubspeed = v->subspeed; - if (!AirportHasBlock(v, current, apc)) { - v->state = landingtype; // LANDING / HELILANDING - if (v->state == HELILANDING) SetBit(v->flags, VAF_HELI_DIRECT_DESCENT); - /* it's a bit dirty, but I need to set position to next position, otherwise - * if there are multiple runways, plane won't know which one it took (because - * they all have heading LANDING). And also occupy that block! */ - v->pos = current->next_position; - SETBITS(st->airport.flags, apc->layout[v->pos].block); - return; - } - v->cur_speed = tcur_speed; - v->subspeed = tsubspeed; + TileIndex closest_landing = FindClosestLandingTile(v); + if (closest_landing == 0 || !CanVehicleUseStation(v, st)) { + if (!HasBit(v->flags, VAF_CAN_T_LAND)) { + SetBit(v->flags, VAF_CAN_T_LAND); + v->SetWaitTime(AIRCRAFT_WAIT_FREE_PATH_TICKS); + SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); + AI::NewEvent(v->owner, new ScriptEventAircraftNoLandDest(v->index)); + if (v->owner == _local_company) { + /* Post a news message. */ + SetDParam(0, v->index); + AddVehicleAdviceNewsItem(STR_NEWS_AIRCRAFT_CAN_T_LAND, v->index); } - current = current->next; } + if (v->state != AS_HANGAR) { + v->state = AS_IDLE; + v->UpdateNextTile(v->tile); + } + return false; + } else if (HasBit(v->flags, VAF_CAN_T_LAND)) { + /* Aircraft can land now. */ + ClrBit(v->flags, VAF_CAN_T_LAND); + SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); + DeleteVehicleNews(v->index, STR_NEWS_AIRCRAFT_CAN_T_LAND); } - v->state = FLYING; - v->pos = apc->layout[v->pos].next_position; -} -static void AircraftEventHandler_Landing(Aircraft *v, const AirportFTAClass *) -{ - v->state = ENDLANDING; - AircraftLandAirplane(v); // maybe crash airplane + if (v->acache.cached_max_range_sqr == 0) return true; + Station *cur_st = Station::GetIfValid(GetStationIndex(v->tile)); - /* check if the aircraft needs to be replaced or renewed and send it to a hangar if needed */ - if (v->NeedsAutomaticServicing()) { - Backup cur_company(_current_company, v->owner); - Command::Do(DC_EXEC, v->index, DepotCommand::Service, {}); - cur_company.Restore(); + if (DistanceSquare(cur_st->airport.tile, closest_landing) > v->acache.cached_max_range_sqr) { + if (!HasBit(v->flags, VAF_DEST_TOO_FAR)) { + SetBit(v->flags, VAF_DEST_TOO_FAR); + v->SetWaitTime(AIRCRAFT_WAIT_FREE_PATH_TICKS); + SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); + AI::NewEvent(v->owner, new ScriptEventAircraftDestTooFar(v->index)); + if (v->owner == _local_company) { + /* Post a news message. */ + SetDParam(0, v->index); + AddVehicleAdviceNewsItem(STR_NEWS_AIRCRAFT_DEST_TOO_FAR, v->index); + } + } + return false; } -} -static void AircraftEventHandler_HeliLanding(Aircraft *v, const AirportFTAClass *) -{ - v->state = HELIENDLANDING; - v->UpdateDeltaXY(); + if (HasBit(v->flags, VAF_DEST_TOO_FAR)) { + /* Not too far anymore, clear flag and message. */ + ClrBit(v->flags, VAF_DEST_TOO_FAR); + SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); + DeleteVehicleNews(v->index, STR_NEWS_AIRCRAFT_DEST_TOO_FAR); + } + + return true; } -static void AircraftEventHandler_EndLanding(Aircraft *v, const AirportFTAClass *apc) +void AssignLandingTile(Aircraft *v, TileIndex tile) { - /* next block busy, don't do a thing, just wait */ - if (AirportHasBlock(v, &apc->layout[v->pos], apc)) return; + assert(v->IsAircraftFreelyFlying()); - /* if going to terminal (OT_GOTO_STATION) choose one - * 1. in case all terminals are busy AirportFindFreeTerminal() returns false or - * 2. not going for terminal (but depot, no order), - * --> get out of the way to the hangar. */ - if (v->current_order.IsType(OT_GOTO_STATION)) { - if (AirportFindFreeTerminal(v, apc)) return; + if (tile != 0 && IsValidTile(tile)) { + assert(IsAirportTile(tile)); + assert((IsRunwayStart(tile) && IsLandingTypeTile(tile)) || (v->IsHelicopter() && IsApron(tile))); + v->state = AS_FLYING; + v->UpdateNextTile(tile); + } else { + v->state = AS_FLYING_NO_DEST; + v->next_pos.pos = AP_DEFAULT; + v->UpdateNextTile(v->tile); } - v->state = HANGAR; + v->next_pos.pos = v->IsHelicopter() ? AP_HELICOPTER_HOLD_START : AP_PLANE_HOLD_START; } -static void AircraftEventHandler_HeliEndLanding(Aircraft *v, const AirportFTAClass *apc) +/** + * Handle aircraft with missing orders. + * @param v An aircraft with missing orders. + */ +void HandleMissingAircraftOrders(Aircraft *v) { - /* next block busy, don't do a thing, just wait */ - if (AirportHasBlock(v, &apc->layout[v->pos], apc)) return; + /* + * We do not have an order. This can be divided into two cases: + * 1) we are heading to an invalid station. In this case we must + * find another airport to go to. If there is nowhere to go, + * we will destroy the aircraft as it otherwise will enter + * the holding pattern for the first airport, which can cause + * the plane to go into an undefined state when building an + * airport with the same StationID. + * 2) we are (still) heading to a (still) valid airport, then we + * can continue going there. This can happen when you are + * changing the aircraft's orders while in-flight or in for + * example a depot. However, when we have a current order to + * go to a depot, we have to keep that order so the aircraft + * actually stops. + */ + const Station *st = GetTargetAirportIfValid(v); + if (st == nullptr) { + Backup cur_company(_current_company, v->owner); + CommandCost ret = Command::Do(DC_EXEC, v->index, DepotCommand::None, {}); + cur_company.Restore(); - /* if going to helipad (OT_GOTO_STATION) choose one. If airport doesn't have helipads, choose terminal - * 1. in case all terminals/helipads are busy (AirportFindFreeHelipad() returns false) or - * 2. not going for terminal (but depot, no order), - * --> get out of the way to the hangar IF there are terminals on the airport. - * --> else TAKEOFF - * the reason behind this is that if an airport has a terminal, it also has a hangar. Airplanes - * must go to a hangar. */ - if (v->current_order.IsType(OT_GOTO_STATION)) { - if (AirportFindFreeHelipad(v, apc)) return; + if (ret.Failed()) HandleAircraftFalling(v); + } else if (!v->current_order.IsType(OT_GOTO_DEPOT)) { + v->current_order.Free(); } - v->state = Station::Get(v->targetairport)->airport.HasHangar() ? HANGAR : HELITAKEOFF; } /** - * Signature of the aircraft handler function. - * @param v Aircraft to handle. - * @param apc Airport state machine. + * Set a destination tile. For aircraft, it won't be assigned directly to this->dest_tile. + * @param tile hangar or apron tile of destination airport + * (hangar/apron depending on current order type being GOTO_DEPOT/GOTO_STATION). + * @pre tile == 0 || (IsAirportTile(tile) && (IsHangar(tile) || IsApron(tile))) */ -typedef void AircraftStateHandler(Aircraft *v, const AirportFTAClass *apc); -/** Array of handler functions for each target of the aircraft. */ -static AircraftStateHandler * const _aircraft_state_handlers[] = { - AircraftEventHandler_General, // TO_ALL = 0 - AircraftEventHandler_InHangar, // HANGAR = 1 - AircraftEventHandler_AtTerminal, // TERM1 = 2 - AircraftEventHandler_AtTerminal, // TERM2 = 3 - AircraftEventHandler_AtTerminal, // TERM3 = 4 - AircraftEventHandler_AtTerminal, // TERM4 = 5 - AircraftEventHandler_AtTerminal, // TERM5 = 6 - AircraftEventHandler_AtTerminal, // TERM6 = 7 - AircraftEventHandler_AtTerminal, // HELIPAD1 = 8 - AircraftEventHandler_AtTerminal, // HELIPAD2 = 9 - AircraftEventHandler_TakeOff, // TAKEOFF = 10 - AircraftEventHandler_StartTakeOff, // STARTTAKEOFF = 11 - AircraftEventHandler_EndTakeOff, // ENDTAKEOFF = 12 - AircraftEventHandler_HeliTakeOff, // HELITAKEOFF = 13 - AircraftEventHandler_Flying, // FLYING = 14 - AircraftEventHandler_Landing, // LANDING = 15 - AircraftEventHandler_EndLanding, // ENDLANDING = 16 - AircraftEventHandler_HeliLanding, // HELILANDING = 17 - AircraftEventHandler_HeliEndLanding, // HELIENDLANDING = 18 - AircraftEventHandler_AtTerminal, // TERM7 = 19 - AircraftEventHandler_AtTerminal, // TERM8 = 20 - AircraftEventHandler_AtTerminal, // HELIPAD3 = 21 -}; - -static void AirportClearBlock(const Aircraft *v, const AirportFTAClass *apc) +void Aircraft::SetDestTile(TileIndex tile) { - /* we have left the previous block, and entered the new one. Free the previous block */ - if (apc->layout[v->previous_pos].block != apc->layout[v->pos].block) { - Station *st = Station::Get(v->targetairport); + if (tile != 0) { + assert(IsValidTile(tile)); + assert(IsAirportTile(tile)); + assert(IsHangar(tile) || IsApron(tile)); + } - CLRBITS(st->airport.flags, apc->layout[v->previous_pos].block); + if (this->dest_tile == tile) return; + + this->dest_tile = tile; + this->targetairport = GetTargetDestination(this->current_order, true); + + if (this->IsAircraftFreelyFlying()) { + this->state = AS_FLYING; + AircraftUpdateNextPos(this); } + + SetWindowWidgetDirty(WC_VEHICLE_VIEW, this->index, WID_VV_START_STOP); } -static void AirportGoToNextPosition(Aircraft *v) +/** + * For moving aircraft, it lifts its current path + * and looks for the best path. It will find the same + * starting path or a best one. + * @param v A moving aircraft. + */ +void UpdatePath(Aircraft *v) { - /* if aircraft is not in position, wait until it is */ - if (!AircraftController(v)) return; + assert(v->state == AS_RUNNING); + assert(v->next_trackdir == INVALID_TRACKDIR); + LiftAirportPathReservation(v, false); + + /* Look for a path again with the same destination. */ + PBSTileInfo best_dest; + bool path_found; + Trackdir first_trackdir = YapfAircraftFindPath(v, best_dest, path_found, v->Next()->state, v->path); - const AirportFTAClass *apc = Station::Get(v->targetairport)->airport.GetFTA(); + /* If a reservable path existed, a reservable path must exist. */ + assert(path_found); + assert(first_trackdir != INVALID_TRACKDIR); + assert(best_dest.okay); + assert(IsValidTile(best_dest.tile)); + v->UpdateNextTile(best_dest.tile); - AirportClearBlock(v, apc); - AirportMove(v, apc); // move aircraft to next position + if (v->trackdir != first_trackdir) { + v->SetWaitTime(AIRCRAFT_ROTATION_STEP_TICKS); + v->next_trackdir = first_trackdir; + } } -/* gets pos from vehicle and next orders */ -static bool AirportMove(Aircraft *v, const AirportFTAClass *apc) +/** + * Checks if a path reservation can be made towards + * next target of the aircraft. + * @param v Aircraft to check. + * @return Whether a path can be reserved. + */ +bool TryReservePath(Aircraft *v) { - /* error handling */ - if (v->pos >= apc->nofelements) { - Debug(misc, 0, "[Ap] position {} is not valid for current airport. Max position is {}", v->pos, apc->nofelements-1); - assert(v->pos < apc->nofelements); - } + assert(v->state < AS_MOVING); - const AirportFTA *current = &apc->layout[v->pos]; - /* we have arrived in an important state (eg terminal, hangar, etc.) */ - if (current->heading == v->state) { - uint8_t prev_pos = v->pos; // location could be changed in state, so save it before-hand - uint8_t prev_state = v->state; - _aircraft_state_handlers[v->state](v, apc); - if (v->state != FLYING) v->previous_pos = prev_pos; - if (v->state != prev_state || v->pos != prev_pos) UpdateAircraftCache(v); - return true; - } + /* First, assert diagonal diadgir. + * We shouldn't start paths in stranger tracks. */ + assert(IsDiagonalTrackdir(v->GetVehicleTrackdir())); - v->previous_pos = v->pos; // save previous location + v->UpdateNextTile(INVALID_TILE); - /* there is only one choice to move to */ - if (current->next == nullptr) { - if (AirportSetBlocks(v, current, apc)) { - v->pos = current->next_position; - UpdateAircraftCache(v); - } // move to next position + /* Then, if inside a standard hangar, make sure it is not reserved. */ + if (v->vehstatus & VS_HIDDEN) { + assert(IsHangarTile(v->tile)); + if (IsStandardHangar(v->tile) && HasAirportTrackReserved(v->tile)) return false; + } + + if (IsApron(v->tile) && + v->targetairport == GetStationIndex(v->tile) && + IsTerminalState(v->state)) { return false; } - /* there are more choices to choose from, choose the one that - * matches our heading */ - do { - if (v->state == current->heading || current->heading == TO_ALL) { - if (AirportSetBlocks(v, current, apc)) { - v->pos = current->next_position; - UpdateAircraftCache(v); - } // move to next position - return false; - } - current = current->next; - } while (current != nullptr); + PBSTileInfo best_dest; + bool path_found; + AircraftState dest_state = GetNextAircraftState(*v); + Trackdir first_trackdir = YapfAircraftFindPath(v, best_dest, path_found, dest_state, v->path); + v->HandlePathfindingResult(path_found); - Debug(misc, 0, "[Ap] cannot move further on Airport! (pos {} state {}) for vehicle {}", v->pos, v->state, v->index); - NOT_REACHED(); -} + if (!path_found) return false; -/** returns true if the road ahead is busy, eg. you must wait before proceeding. */ -static bool AirportHasBlock(Aircraft *v, const AirportFTA *current_pos, const AirportFTAClass *apc) -{ - const AirportFTA *reference = &apc->layout[v->pos]; - const AirportFTA *next = &apc->layout[current_pos->next_position]; + assert(first_trackdir != INVALID_TRACKDIR); + assert(IsValidTile(best_dest.tile)); - /* same block, then of course we can move */ - if (apc->layout[current_pos->position].block != next->block) { - const Station *st = Station::Get(v->targetairport); - uint64_t airport_flags = next->block; + /* A path exists but right now cannot be reserved. */ + if (!best_dest.okay) return false; - /* check additional possible extra blocks */ - if (current_pos != reference && current_pos->block != NOTHING_block) { - airport_flags |= current_pos->block; - } + // revise possible unneeded servicing here + if (v->state != AS_HANGAR && dest_state == AS_HANGAR && !v->current_order.IsType(OT_GOTO_DEPOT)) { + /* Create the hangar order. */ + // revise + Depot *hangar = Station::GetByTile(v->tile)->airport.hangar; + assert(hangar != nullptr); + v->current_order.MakeGoToDepot(hangar->index, ODTFB_SERVICE); + SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); + } - if (st->airport.flags & airport_flags) { - v->cur_speed = 0; - v->subspeed = 0; - return true; + v->UpdateNextTile(best_dest.tile); + + /* If a path is found, service, reserve and return true. */ + if (IsHangarTile(v->tile)) { + assert(IsValidTrackdir(first_trackdir)); + SetAirportTracksReservation(v->tile, TrackToTrackBits(TrackdirToTrack(first_trackdir))); + + if (v->cur_speed != 0) SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); + v->cur_speed = 0; + v->subspeed = 0; + v->progress = 0; + + /* Rotor blades */ + if (v->Next()->Next() != nullptr) { + v->Next()->Next()->cur_speed = 80; } - } - return false; -} -/** - * "reserve" a block for the plane - * @param v airplane that requires the operation - * @param current_pos of the vehicle in the list of blocks - * @param apc airport on which block is requested to be set - * @returns true on success. Eg, next block was free and we have occupied it - */ -static bool AirportSetBlocks(Aircraft *v, const AirportFTA *current_pos, const AirportFTAClass *apc) -{ - const AirportFTA *next = &apc->layout[current_pos->next_position]; - const AirportFTA *reference = &apc->layout[v->pos]; - - /* if the next position is in another block, check it and wait until it is free */ - if ((apc->layout[current_pos->position].block & next->block) != next->block) { - uint64_t airport_flags = next->block; - /* search for all all elements in the list with the same state, and blocks != N - * this means more blocks should be checked/set */ - const AirportFTA *current = current_pos; - if (current == reference) current = current->next; - while (current != nullptr) { - if (current->heading == current_pos->heading && current->block != 0) { - airport_flags |= current->block; - break; - } - current = current->next; + if (!IsExtendedHangar(v->tile)) { + v->trackdir = v->Next()->trackdir = first_trackdir; + SetVisibility(v, true); } - /* if the block to be checked is in the next position, then exclude that from - * checking, because it has been set by the airplane before */ - if (current_pos->block == next->block) airport_flags ^= next->block; + AircraftLeavesHangar(v); + v->PlayLeaveStationSound(); + } - Station *st = Station::Get(v->targetairport); - if (st->airport.flags & airport_flags) { - v->cur_speed = 0; - v->subspeed = 0; - return false; + assert(IsDiagonalTrackdir(first_trackdir)); + if (first_trackdir != v->GetVehicleTrackdir()) { + v->SetWaitTime(AIRCRAFT_ROTATION_STEP_TICKS); + v->next_trackdir = first_trackdir; + if (GetReservedAirportTracks(v->tile) == TRACK_BIT_CROSS) { + assert(IsValidTrackdir(v->trackdir)); + RemoveAirportTrackReservation(v->tile, TrackdirToTrack(v->trackdir)); } + } - if (next->block != NOTHING_block) { - SETBITS(st->airport.flags, airport_flags); // occupy next block - } + if (v->tile != v->GetNextTile() && v->GetNextTile() != INVALID_TILE) { + v->state = AS_RUNNING; + v->Next()->state = dest_state; } + return true; } /** - * Combination of aircraft state for going to a certain terminal and the - * airport flag for that terminal block. + * While aircraft is on land and moving through an airport, + * check whether it is in the middle of a tile. If it is the middle of + * a tile, try updating the path and the next trackdir, if needed. + * @param v Aircraft to check. + * @param gp New position of the aircraft. + * @return Whether it needs to rotate. */ -struct MovementTerminalMapping { - AirportMovementStates state; ///< Aircraft movement state when going to this terminal. - uint64_t airport_flag; ///< Bitmask in the airport flags that need to be free for this terminal. -}; +bool TryRotateInMiddleOfTile(Aircraft *v, const GetNewVehiclePosResult &gp) { + assert(v->state == AS_RUNNING); + assert(IsAirportTile(gp.new_tile)); + assert(MayHaveAirTracks(gp.new_tile)); -/** A list of all valid terminals and their associated blocks. */ -static const MovementTerminalMapping _airport_terminal_mapping[] = { - {TERM1, TERM1_block}, - {TERM2, TERM2_block}, - {TERM3, TERM3_block}, - {TERM4, TERM4_block}, - {TERM5, TERM5_block}, - {TERM6, TERM6_block}, - {TERM7, TERM7_block}, - {TERM8, TERM8_block}, - {HELIPAD1, HELIPAD1_block}, - {HELIPAD2, HELIPAD2_block}, - {HELIPAD3, HELIPAD3_block}, -}; + if ((gp.x & 0xF) != 8 || (gp.y & 0xF) != 8) return false; -/** - * Find a free terminal or helipad, and if available, assign it. - * @param v Aircraft looking for a free terminal or helipad. - * @param i First terminal to examine. - * @param last_terminal Terminal number to stop examining. - * @return A terminal or helipad has been found, and has been assigned to the aircraft. - */ -static bool FreeTerminal(Aircraft *v, uint8_t i, uint8_t last_terminal) -{ - assert(last_terminal <= lengthof(_airport_terminal_mapping)); - Station *st = Station::Get(v->targetairport); - for (; i < last_terminal; i++) { - if ((st->airport.flags & _airport_terminal_mapping[i].airport_flag) == 0) { - /* TERMINAL# HELIPAD# */ - v->state = _airport_terminal_mapping[i].state; // start moving to that terminal/helipad - SETBITS(st->airport.flags, _airport_terminal_mapping[i].airport_flag); // occupy terminal/helipad - return true; - } - } - return false; -} + /* Check whether the aircraft must rotate in the middle of the tile. */ + if (GetReservedAirportTracks(gp.new_tile) != TRACK_BIT_CROSS) return false; -/** - * Get the number of terminals at the airport. - * @param apc Airport description. - * @return Number of terminals. - */ -static uint GetNumTerminals(const AirportFTAClass *apc) -{ - uint num = 0; + assert(IsValidTrackdir(v->trackdir)); + assert(v->next_trackdir == INVALID_TRACKDIR); + + /* A good moment to update the path. */ + //UpdatePath(v); - for (uint i = apc->terminals[0]; i > 0; i--) num += apc->terminals[i]; + if (DoesAircraftNeedRotation(v)) return true; - return num; + if (GetReservedAirportTracks(gp.new_tile) == TRACK_BIT_CROSS) { + RemoveAirportTrackReservation(gp.new_tile, TrackdirToTrack(v->trackdir)); + assert(!v->path.empty()); + assert(v->path.tile.front() == gp.new_tile); + v->SetWaitTime(AIRCRAFT_ROTATION_STEP_TICKS); + v->next_trackdir = v->path.td.front(); + v->path.pop_front(); + } + + return true; } /** - * Find a free terminal, and assign it if available. - * @param v Aircraft to handle. - * @param apc Airport state machine. - * @return Found a free terminal and assigned it. + * Moves an aircraft one time. + * @param v Aircraft to move. + * @param nudge_towards_target Indicates whether v is flying and close to its target. */ -static bool AirportFindFreeTerminal(Aircraft *v, const AirportFTAClass *apc) -{ - /* example of more terminalgroups - * {0,HANGAR,NOTHING_block,1}, {0,TERMGROUP,TERM_GROUP1_block,0}, {0,TERMGROUP,TERM_GROUP2_ENTER_block,1}, {0,0,N,1}, - * Heading TERMGROUP denotes a group. We see 2 groups here: - * 1. group 0 -- TERM_GROUP1_block (check block) - * 2. group 1 -- TERM_GROUP2_ENTER_block (check block) - * First in line is checked first, group 0. If the block (TERM_GROUP1_block) is free, it - * looks at the corresponding terminals of that group. If no free ones are found, other - * possible groups are checked (in this case group 1, since that is after group 0). If that - * fails, then attempt fails and plane waits - */ - if (apc->terminals[0] > 1) { - const Station *st = Station::Get(v->targetairport); - const AirportFTA *temp = apc->layout[v->pos].next; - - while (temp != nullptr) { - if (temp->heading == TERMGROUP) { - if (!(st->airport.flags & temp->block)) { - /* read which group do we want to go to? - * (the first free group) */ - uint target_group = temp->next_position + 1; - - /* at what terminal does the group start? - * that means, sum up all terminals of - * groups with lower number */ - uint group_start = 0; - for (uint i = 1; i < target_group; i++) { - group_start += apc->terminals[i]; - } +void MoveAircraft(Aircraft *v, const bool nudge_towards_target) +{ + GetNewVehiclePosResult gp; + + if (nudge_towards_target) { + /* Move vehicle one pixel towards target. */ + gp.x = (v->x_pos != v->next_pos.x) ? v->x_pos + ((v->next_pos.x > v->x_pos) ? 1 : -1) : v->x_pos; + gp.y = (v->y_pos != v->next_pos.y) ? v->y_pos + ((v->next_pos.y > v->y_pos) ? 1 : -1) : v->y_pos; + + /* Builtin heliports keep v->tile as the terminal tile, since the landing pad is in a non-airport tile. */ + gp.new_tile = IsBuiltInHeliportTile(v->GetNextTile()) ? v->GetNextTile() : TileVirtXY(gp.x, gp.y); + } else if (v->state > AS_RUNNING) { + /* Aircraft is flying or moving in a runway. */ + assert(!v->IsHelicopter() || ((v->state != AS_LANDED && v->state != AS_START_TAKEOFF))); + + /* Turn. Do it slowly if in the air. */ + if (v->turn_counter != 0) v->turn_counter--; + Direction newdir = GetDirectionTowards(v, v->next_pos.x, v->next_pos.y); + if (newdir == v->direction) { + v->number_consecutive_turns = 0; + } else if (v->turn_counter == 0 || newdir == v->last_direction) { + if (newdir == v->last_direction) { + v->number_consecutive_turns = 0; + } else { + v->number_consecutive_turns++; + } + v->turn_counter = v->IsHelicopter() ? 0 : (2 * _settings_game.vehicle.plane_speed); + v->last_direction = v->direction; + v->direction = v->Next()->direction = newdir; + } + + gp = GetNewVehiclePos(v); + } else { + /* Aircraft is taxiing on the airport. */ + assert(v->state == AS_RUNNING); + + gp = GetNewVehiclePos(v); - uint group_end = group_start + apc->terminals[target_group]; - if (FreeTerminal(v, group_start, group_end)) return true; + if (gp.old_tile == gp.new_tile) { + if (TryRotateInMiddleOfTile(v, gp)) return; + } else { + /* Entering a new tile. */ + assert(IsTileType(gp.new_tile, MP_STATION)); + assert(IsAirportTile(gp.new_tile)); + assert(MayHaveAirTracks(gp.new_tile)); + assert(IsValidTrackdir(v->trackdir)); + assert(v->next_trackdir == INVALID_TRACKDIR); + + //UpdatePath(v); + + if (DoesAircraftNeedRotation(v)) return; + + RemoveAirportTrackReservation(gp.old_tile, TrackdirToTrack(v->trackdir)); + TrackdirBits trackdirs = TrackdirReachesTrackdirs(v->trackdir) & + TrackBitsToTrackdirBits(GetReservedAirportTracks(gp.new_tile)); + + if (trackdirs == TRACKDIR_BIT_NONE) { + /* Rotate at the end of the tile. */ + DiagDirection exit_dir = TrackdirToExitdir(v->trackdir); + trackdirs = DiagdirReachesTrackdirs(ReverseDiagDir(exit_dir)) & + TrackBitsToTrackdirBits(GetReservedAirportTracks(gp.old_tile)); + + /* Must reverse now and rotate in the middle of the tile. */ + if (CountBits(trackdirs) == 0) { + [[maybe_unused]] TrackBits reserved_tracks = GetReservedAirportTracks(gp.old_tile); + assert(CountBits(reserved_tracks) == 1); + assert(IsDiagonalTrack(RemoveFirstTrack(&reserved_tracks))); + v->SetWaitTime(AIRCRAFT_ROTATION_STEP_TICKS); + v->next_trackdir = ReverseTrackdir(v->trackdir); + SetAirportTrackReservation(gp.old_tile, TrackdirToTrack(v->next_trackdir)); + return; } - } else { - /* once the heading isn't 255, we've exhausted the possible blocks. - * So we cannot move */ - return false; + + assert(CountBits(trackdirs) == 1); + v->next_trackdir = RemoveFirstTrackdir(&trackdirs); + assert(trackdirs == TRACKDIR_BIT_NONE); + SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos); + return; } - temp = temp->next; + + v->trackdir = v->Next()->trackdir = RemoveFirstTrackdir(&trackdirs); + assert(IsValidTrackdir(v->trackdir)); + + DiagDirection diagdir = DiagdirBetweenTiles(gp.old_tile, gp.new_tile); + const AircraftSubcoordData &b = _aircraft_subcoord[diagdir][TrackdirToTrack(v->trackdir)]; + gp.x = (gp.x & ~0xF) | b.x_subcoord; + gp.y = (gp.y & ~0xF) | b.y_subcoord; + + uint32_t r = VehicleEnterTile(v, gp.new_tile, gp.x, gp.y); + if (HasBit(r, VETS_CANNOT_ENTER)) NOT_REACHED(); + + v->direction = v->Next()->direction = b.dir; } } - /* if there is only 1 terminalgroup, all terminals are checked (starting from 0 to max) */ - return FreeTerminal(v, 0, GetNumTerminals(apc)); -} - -/** - * Find a free helipad, and assign it if available. - * @param v Aircraft to handle. - * @param apc Airport state machine. - * @return Found a free helipad and assigned it. - */ -static bool AirportFindFreeHelipad(Aircraft *v, const AirportFTAClass *apc) -{ - /* if an airport doesn't have helipads, use terminals */ - if (apc->num_helipads == 0) return AirportFindFreeTerminal(v, apc); + v->tile = gp.new_tile; + v->x_pos = gp.x; + v->y_pos = gp.y; - /* only 1 helicoptergroup, check all helipads - * The blocks for helipads start after the last terminal (MAX_TERMINALS) */ - return FreeTerminal(v, MAX_TERMINALS, apc->num_helipads + MAX_TERMINALS); + if (v->IsAircraftFlying()) HandleAircraftFlightLevel(v); } /** - * Handle the 'dest too far' flag and the corresponding news message for aircraft. - * @param v The aircraft. - * @param too_far True if the current destination is too far away. + * Moves the aircraft one time. + * @param v Aircraft to move. + * @return whether the vehicle can move more times during this tick. */ -static void AircraftHandleDestTooFar(Aircraft *v, bool too_far) +bool HandleAircraftMovement(Aircraft *v) { - if (too_far) { - if (!HasBit(v->flags, VAF_DEST_TOO_FAR)) { - SetBit(v->flags, VAF_DEST_TOO_FAR); - SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); - AI::NewEvent(v->owner, new ScriptEventAircraftDestTooFar(v->index)); - if (v->owner == _local_company) { - /* Post a news message. */ - SetDParam(0, v->index); - AddVehicleAdviceNewsItem(STR_NEWS_AIRCRAFT_DEST_TOO_FAR, v->index); - } + if (v->IsAircraftFalling()) { + HandleAircraftFalling(v); + return true; + } + + if (DoesAircraftNeedRotation(v)) { + DoRotationStep(v); + if (v->state == AS_START_TAKEOFF && !DoesAircraftNeedRotation(v)) { + /* Take off starts right now. */ + PlayAircraftTakeoffSound(v); } - return; + return true; } - if (HasBit(v->flags, VAF_DEST_TOO_FAR)) { - /* Not too far anymore, clear flag and message. */ - ClrBit(v->flags, VAF_DEST_TOO_FAR); - SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); - DeleteVehicleNews(v->index, STR_NEWS_AIRCRAFT_DEST_TOO_FAR); + if (v->IsHelicopter() && RaiseLowerHelicopter(v)) return true; + + if (HandleAircraftState(v)) return true; + + if (v->state < AS_MOVING) return false; + + /* Maybe crash the airplane if landing too fast. */ + assert(v->state != AS_LANDED || IsAirportTile(v->tile)); + if (v->state == AS_LANDED && + v->cur_speed > GetAirTypeInfo(GetAirType(v->tile))->max_speed * _settings_game.vehicle.plane_speed) { + if (MaybeCrashAirplane(v)) return true; + } + + int count = UpdateAircraftSpeed(v); + + if (v->next_trackdir != INVALID_TRACKDIR) return true; + + /* If the plane will be a few subpixels away from the destination after + * this movement loop, start nudging it towards the exact position for + * the whole loop. Otherwise, heavily depending on the speed of the plane, + * it is possible we totally overshoot the target, causing the plane to + * make a loop, and trying again, and again, and again .. */ + bool nudge_towards_target = v->IsAircraftFlying() && + count + 3 > abs(v->next_pos.x - v->x_pos) + abs(v->next_pos.y - v->y_pos); + + for (; count > 0; count--) { + MoveAircraft(v, nudge_towards_target); + if (HandleAircraftState(v)) break; } + + SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos); + return true; } -static bool AircraftEventHandler(Aircraft *v, int loop) +/** + * Aircraft controller. + * @param v Aircraft to move. + * @param mode False during the first call in each tick, true during second call. + * @return whether the vehicle is still valid. + */ +static bool AircraftController(Aircraft *v, bool mode) { + /* Aircraft crashed? */ if (v->vehstatus & VS_CRASHED) { - return HandleCrashedAircraft(v); + return mode ? true : HandleCrashedAircraft(v); // 'v' can be deleted here } - if (v->vehstatus & VS_STOPPED) return true; + if ((v->vehstatus & VS_STOPPED) && v->cur_speed == 0) return true; + + if (v->IsServicing()) { + if (mode) v->ContinueServicing(); + return true; + } v->HandleBreakdown(); - HandleAircraftSmoke(v, loop != 0); + HandleAircraftSmoke(v, mode); + + if (v->IsWaiting()) { + if (mode) v->AdvanceWaitTime(); + return true; + } + ProcessOrders(v); - v->HandleLoading(loop != 0); - - if (v->current_order.IsType(OT_LOADING) || v->current_order.IsType(OT_LEAVESTATION)) return true; - - if (v->state >= ENDTAKEOFF && v->state <= HELIENDLANDING) { - /* If we are flying, unconditionally clear the 'dest too far' state. */ - AircraftHandleDestTooFar(v, false); - } else if (v->acache.cached_max_range_sqr != 0) { - /* Check the distance to the next destination. This code works because the target - * airport is only updated after take off and not on the ground. */ - Station *cur_st = Station::GetIfValid(v->targetairport); - Station *next_st = v->current_order.IsType(OT_GOTO_STATION) || v->current_order.IsType(OT_GOTO_DEPOT) ? Station::GetIfValid(v->current_order.GetDestination()) : nullptr; - - if (cur_st != nullptr && cur_st->airport.tile != INVALID_TILE && next_st != nullptr && next_st->airport.tile != INVALID_TILE) { - uint dist = DistanceSquare(cur_st->airport.tile, next_st->airport.tile); - AircraftHandleDestTooFar(v, dist > v->acache.cached_max_range_sqr); - } + + v->HandleLoading(mode); + if (v->current_order.IsType(OT_LOADING)) return true; + + /* Check if we should wait here for unbunching. */ + if (v->state == AS_HANGAR && v->IsWaitingForUnbunching()) return true; + + if (HandleAircraftMovement(v)) return true; + + /* Check if next destination is too far. */ + if (!IsReachableDest(v)) { + if (!v->IsWaiting()) v->SetWaitTime(AIRCRAFT_WAIT_FREE_PATH_TICKS); + return true; } - if (!HasBit(v->flags, VAF_DEST_TOO_FAR)) AirportGoToNextPosition(v); + /* Check whether aircraft can reserve a path towards its next target. */ + if (!TryReservePath(v)) { + /* Aircraft cannot reserve a path now. */ + v->SetWaitTime(v->state == AS_HANGAR ? AIRCRAFT_WAIT_LEAVE_HANGAR_TICKS : AIRCRAFT_WAIT_FREE_PATH_TICKS); + } return true; } +/** + * Update aircraft vehicle data for a tick. + * @return True if the vehicle still exists, false if it has ceased to exist (normal aircraft only). + */ bool Aircraft::Tick() { if (!this->IsNormalAircraft()) return true; @@ -2126,61 +3004,14 @@ bool Aircraft::Tick() if (!(this->vehstatus & VS_STOPPED)) this->running_ticks++; - if (this->subtype == AIR_HELICOPTER) HelicopterTickHandler(this); + if (this->IsHelicopter()) HandleHelicopterRotor(this); this->current_order_time++; for (uint i = 0; i != 2; i++) { /* stop if the aircraft was deleted */ - if (!AircraftEventHandler(this, i)) return false; + if (!AircraftController(this, i)) return false; } return true; } - - -/** - * Returns aircraft's target station if v->target_airport - * is a valid station with airport. - * @param v vehicle to get target airport for - * @return pointer to target station, nullptr if invalid - */ -Station *GetTargetAirportIfValid(const Aircraft *v) -{ - assert(v->type == VEH_AIRCRAFT); - - Station *st = Station::GetIfValid(v->targetairport); - if (st == nullptr) return nullptr; - - return st->airport.tile == INVALID_TILE ? nullptr : st; -} - -/** - * Updates the status of the Aircraft heading or in the station - * @param st Station been updated - */ -void UpdateAirplanesOnNewStation(const Station *st) -{ - /* only 1 station is updated per function call, so it is enough to get entry_point once */ - const AirportFTAClass *ap = st->airport.GetFTA(); - Direction rotation = st->airport.tile == INVALID_TILE ? DIR_N : st->airport.rotation; - - for (Aircraft *v : Aircraft::Iterate()) { - if (!v->IsNormalAircraft() || v->targetairport != st->index) continue; - assert(v->state == FLYING); - - Order *o = &v->current_order; - /* The aircraft is heading to a hangar, but the new station doesn't have one, - * or the aircraft can't land on the new station. Cancel current order. */ - if (o->IsType(OT_GOTO_DEPOT) && !(o->GetDepotOrderType() & ODTFB_PART_OF_ORDERS) && o->GetDestination() == st->index && - (!st->airport.HasHangar() || !CanVehicleUseStation(v, st))) { - o->MakeDummy(); - SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); - } - v->pos = v->previous_pos = AircraftGetEntryPoint(v, ap, rotation); - UpdateAircraftCache(v); - } - - /* Heliports don't have a hangar. Invalidate all go to hangar orders from all aircraft. */ - if (!st->airport.HasHangar()) RemoveOrderFromAllVehicles(OT_GOTO_DEPOT, st->index, true); -} diff --git a/src/airport.cpp b/src/airport.cpp index a1e68b0629207..f8081ab1c513c 100644 --- a/src/airport.cpp +++ b/src/airport.cpp @@ -9,225 +9,687 @@ #include "stdafx.h" #include "station_base.h" +#include "newgrf_airtype.h" +#include "depot_base.h" +#include "air_map.h" +#include "window_func.h" + +#include "debug.h" + +#include "table/airtypes.h" #include "table/strings.h" -#include "table/airport_movement.h" #include "table/airporttile_ids.h" +#include "table/airport_defaults.h" #include "safeguards.h" +/** Helper type for lists/vectors of trains */ +typedef std::vector AircraftList; + +AirTypeInfo _airtypes[AIRTYPE_END]; +std::vector _sorted_airtypes; +AirTypes _airtypes_hidden_mask; + + +const AirportSpec AirportSpec::dummy = {{APC_BEGIN, 0}, {}, INVALID_AIRTYPE, 0, 0, 0, 0, 0, CalendarTime::MIN_YEAR, CalendarTime::MIN_YEAR, STR_NULL, ATP_TTDP_LARGE, 0, false, false, false, GRFFileProps(AT_INVALID)}; +const AirportSpec AirportSpec::custom = {{APC_CUSTOM, 0}, {}, INVALID_AIRTYPE, 0, 0, 0, 0, 0, CalendarTime::MIN_YEAR, CalendarTime::MIN_YEAR, STR_AIRPORT_CUSTOM, ATP_TTDP_LARGE, 0, false, false, false, GRFFileProps(AT_INVALID)}; + + +void ResolveAirTypeGUISprites(AirTypeInfo *ati) +{ + SpriteID cursors_base = GetCustomAirSprite(ati, INVALID_TILE, ATSG_CURSORS); + if (cursors_base != 0) { + ati->gui_sprites.add_airport_tiles = cursors_base + 0; + ati->gui_sprites.build_track_tile = cursors_base + 1; + ati->gui_sprites.change_airtype = cursors_base + 2; + ati->gui_sprites.build_catchment_infra = cursors_base + 3; + ati->gui_sprites.build_noncatchment_infra = cursors_base + 4; + ati->gui_sprites.define_landing_runway = cursors_base + 5; + ati->gui_sprites.define_nonlanding_runway = cursors_base + 6; + ati->gui_sprites.build_apron = cursors_base + 7; + ati->gui_sprites.build_helipad = cursors_base + 8; + ati->gui_sprites.build_heliport = cursors_base + 9; + ati->gui_sprites.build_hangar = cursors_base + 10; + + ati->cursor.add_airport_tiles = cursors_base + 11; + ati->cursor.build_track_tile = cursors_base + 12; + ati->cursor.change_airtype = cursors_base + 13; + ati->cursor.build_catchment_infra = cursors_base + 14; + ati->cursor.build_noncatchment_infra = cursors_base + 15; + ati->cursor.define_landing_runway = cursors_base + 16; + ati->cursor.define_nonlanding_runway = cursors_base + 17; + ati->cursor.build_apron = cursors_base + 18; + ati->cursor.build_helipad = cursors_base + 19; + ati->cursor.build_heliport = cursors_base + 20; + ati->cursor.build_hangar = cursors_base + 21; + } +} + + /** - * Define a generic airport. - * @param name Suffix of the names of the airport data. - * @param terminals The terminals. - * @param num_helipads Number of heli pads. - * @param flags Information about the class of FTA. - * @param delta_z Height of the airport above the land. + * Reset all air type information to its default values. */ -#define AIRPORT_GENERIC(name, terminals, num_helipads, flags, delta_z) \ - static const AirportFTAClass _airportfta_ ## name(_airport_moving_data_ ## name, terminals, \ - num_helipads, _airport_entries_ ## name, flags, _airport_fta_ ## name, delta_z); +void ResetAirTypes() +{ + static_assert(lengthof(_original_airtypes) <= lengthof(_airtypes)); + + uint i = 0; + for (; i < lengthof(_original_airtypes); i++) _airtypes[i] = _original_airtypes[i]; + + static const AirTypeInfo empty_airtype = { + { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Ground sprite + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + { + { // Airport buildings with infrastructure: non-snowed/snowed + 5 building + 4 rotations + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 } + }, + { + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 } + } + }, + { // Airport animated flag revise: maybe 4 sprites instead of 16 is enough + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 } + }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Radar sprites + { // Infrastructure with no catchment (with 4 rotated sprites): transmitter, snowed transmitter, tower, snowed tower + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 } + }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Runway sprites + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Sprites for normal apron, helipad, 4 rotated sprites for heliports and 4 more for snowed heliports + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } // Hangar sprites + }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Icons + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Cursors + { 0, 0, 0, 0 }, // Strings + 0, AIRTYPES_NONE, AIRTYPES_NONE, 0, 0, 0, 0, AirTypeLabelList(), 0, 0, + AIRTYPES_NONE, AIRTYPES_NONE, 0, + {}, {}, 0, 0, 0, 0, 0, false, false }; + for (; i < lengthof(_airtypes); i++) _airtypes[i] = empty_airtype; +} /** - * Define an airport. - * @param name Suffix of the names of the airport data. - * @param num_helipads Number of heli pads. - * @param short_strip Airport has a short land/take-off strip. + * Compare airtypes based on their sorting order. + * @param first The airtype to compare to. + * @param second The airtype to compare. + * @return True iff the first should be sorted before the second. */ -#define AIRPORT(name, num_helipads, short_strip) \ - AIRPORT_GENERIC(name, _airport_terminal_ ## name, num_helipads, AirportFTAClass::ALL | (short_strip ? AirportFTAClass::SHORT_STRIP : (AirportFTAClass::Flags)0), 0) +static bool CompareAirTypes(const AirType &first, const AirType &second) +{ + return GetAirTypeInfo(first)->sorting_order < GetAirTypeInfo(second)->sorting_order; +} /** - * Define a heliport. - * @param name Suffix of the names of the helipad data. - * @param num_helipads Number of heli pads. - * @param delta_z Height of the airport above the land. + * Resolve sprites of custom air types */ -#define HELIPORT(name, num_helipads, delta_z) \ - AIRPORT_GENERIC(name, nullptr, num_helipads, AirportFTAClass::HELICOPTERS, delta_z) - -AIRPORT(country, 0, true) -AIRPORT(city, 0, false) -HELIPORT(heliport, 1, 60) -AIRPORT(metropolitan, 0, false) -AIRPORT(international, 2, false) -AIRPORT(commuter, 2, true) -HELIPORT(helidepot, 1, 0) -AIRPORT(intercontinental, 2, false) -HELIPORT(helistation, 3, 0) -HELIPORT(oilrig, 1, 54) -AIRPORT_GENERIC(dummy, nullptr, 0, AirportFTAClass::ALL, 0) - -#undef HELIPORT -#undef AIRPORT -#undef AIRPORT_GENERIC +void InitAirTypes() +{ + for (AirType at = AIRTYPE_BEGIN; at != AIRTYPE_END; at++) { + AirTypeInfo *ati = &_airtypes[at]; + ResolveAirTypeGUISprites(ati); + } -#include "table/airport_defaults.h" + _sorted_airtypes.clear(); + for (AirType at = AIRTYPE_BEGIN; at != AIRTYPE_END; at++) { + if (_airtypes[at].label != 0 && !HasBit(_airtypes_hidden_mask, at)) { + _sorted_airtypes.push_back(at); + } + } + std::sort(_sorted_airtypes.begin(), _sorted_airtypes.end(), CompareAirTypes); + +} + +/** + * Allocate a new air type label + */ +AirType AllocateAirType(AirTypeLabel label) +{ + for (AirType at = AIRTYPE_BEGIN; at != AIRTYPE_END; at++) { + AirTypeInfo *ati = &_airtypes[at]; + if (ati->label == 0) { + /* Set up new air type */ + *ati = _original_airtypes[AIRTYPE_BEGIN]; + ati->label = label; + ati->alternate_labels.clear(); -static uint16_t AirportGetNofElements(const AirportFTAbuildup *apFA); -static AirportFTA *AirportBuildAutomata(uint nofelements, const AirportFTAbuildup *apFA); + /* Make us compatible with ourself. */ + ati->compatible_airtypes = (AirTypes)(1 << at); + /* We also introduce ourself. */ + ati->introduces_airtypes = (AirTypes)(1 << at); + + /* Default sort order; order of allocation, but with some + * offsets so it's easier for NewGRF to pick a spot without + * changing the order of other (original) air types. + * The << is so you can place other airtypes in between the + * other airtypes, the 7 is to be able to place something + * before the first (default) air type. */ + ati->sorting_order = at << 4 | 7; + return at; + } + } + + return INVALID_AIRTYPE; +} /** - * Rotate the airport moving data to another rotation. - * @param orig Pointer to the moving data to rotate. - * @param rotation How to rotate the moving data. - * @param num_tiles_x Number of tiles in x direction. - * @param num_tiles_y Number of tiles in y direction. - * @return The rotated moving data. + * Set the new airport layout, rotation and airtype of old airports + * (even those built with OpenGRF+Airports). + * @param st Station with the airport to update, if there is one. */ -AirportMovingData RotateAirportMovingData(const AirportMovingData *orig, Direction rotation, uint num_tiles_x, uint num_tiles_y) -{ - AirportMovingData amd; - amd.flag = orig->flag; - amd.direction = ChangeDir(orig->direction, (DirDiff)rotation); - switch (rotation) { - case DIR_N: - amd.x = orig->x; - amd.y = orig->y; - break; +void ConvertOldAirportData(Station *st) { + if (st->airport.tile == INVALID_TILE) return; - case DIR_E: - amd.x = orig->y; - amd.y = num_tiles_y * TILE_SIZE - orig->x - 1; - break; + st->airport.air_type = INVALID_AIRTYPE; - case DIR_S: - amd.x = num_tiles_x * TILE_SIZE - orig->x - 1; - amd.y = num_tiles_y * TILE_SIZE - orig->y - 1; - break; + if (st->airport.type < NEW_AIRPORT_OFFSET) return; - case DIR_W: - amd.x = num_tiles_x * TILE_SIZE - orig->y - 1; - amd.y = orig->x; - break; + if (st->airport.type == NEW_AIRPORT_OFFSET + 1) { + /* Already existing water airports built on land cannot be converted properly. + * Handle them as standard small gravel airports. */ + st->airport.type = NEW_AIRPORT_OFFSET; + } - default: NOT_REACHED(); + st->airport.layout = 0; + + /* Old rotation dir values are 0, 2, 4, 6. + * Convert them to 0, 1, 2, 3 (DiagDirection values). */ + assert(st->airport.rotation % 2 == 0); + assert(st->airport.rotation <= 6); + st->airport.rotation = (Direction)(st->airport.rotation / 2); + + if (st->airport.type > 23) { + /* OpenGRF+Airports includes airport types from NEW_AIRPORT_OFFSET to 23. + * Do not try to convert a completely unknown airport type. */ + Debug(misc, 0, "Unknown airport type: station {} airport type {}", st->index, st->airport.type); + NOT_REACHED(); } - return amd; -} - -AirportFTAClass::AirportFTAClass( - const AirportMovingData *moving_data_, - const uint8_t *terminals_, - const uint8_t num_helipads_, - const uint8_t *entry_points_, - Flags flags_, - const AirportFTAbuildup *apFA, - uint8_t delta_z_ -) : - moving_data(moving_data_), - terminals(terminals_), - num_helipads(num_helipads_), - flags(flags_), - nofelements(AirportGetNofElements(apFA)), - entry_points(entry_points_), - delta_z(delta_z_) -{ - /* Build the state machine itself */ - this->layout = AirportBuildAutomata(this->nofelements, apFA); -} - -AirportFTAClass::~AirportFTAClass() -{ - for (uint i = 0; i < nofelements; i++) { - AirportFTA *current = layout[i].next; - while (current != nullptr) { - AirportFTA *next = current->next; - free(current); - current = next; - } +} + +/** + * After loading an old savegame, update type and tracks of airport tiles. + */ +void AfterLoadSetAirportTileTypes() +{ + for (Station *st : Station::Iterate()) { + ConvertOldAirportData(st); + st->LoadAirportTilesFromSpec(st->airport, (DiagDirection)st->airport.rotation, st->airport.air_type); } - free(layout); +} + +/** Clear data about infrastructure of airport */ +void Station::ClearAirportDataInfrastructure() { + this->airport.Clear(); + this->airport.air_type = INVALID_AIRTYPE; + this->airport.aprons.clear(); + this->airport.helipads.clear(); + this->airport.runways.clear(); + if (this->airport.HasHangar()) { + this->airport.hangar->depot_tiles.clear(); + } +} + +void UpdateTracks(TileIndex tile) +{ + assert(IsAirportTile(tile)); + if (!MayHaveAirTracks(tile) || IsHangar(tile)) return; + TrackBits tracks = GetAllowedTracks(tile) & GetAirportTileTracks(tile); + if (tracks != GetAirportTileTracks(tile)) { + Debug(misc, 0, "Removing invalid track on tile {}", tile); + } + + SetAirportTileTracks(tile, tracks); } /** - * Get the number of elements of a source Airport state automata - * Since it is actually just a big array of AirportFTA types, we only - * know one element from the other by differing 'position' identifiers + * Get the start or end of a runway. + * @param tile Tile belonging a runway. + * @param dir Direction to follow. + * @return a runway extreme. */ -static uint16_t AirportGetNofElements(const AirportFTAbuildup *apFA) +TileIndex GetRunwayExtreme(TileIndex tile, DiagDirection dir) { - uint16_t nofelements = 0; - int temp = apFA[0].position; + assert(IsAirportTile(tile) && IsRunway(tile)); - for (uint i = 0; i < MAX_ELEMENTS; i++) { - if (temp != apFA[i].position) { - nofelements++; - temp = apFA[i].position; + TileIndexDiff delta = TileOffsByDiagDir(dir); + TileIndex t = tile; + + for (;;) { + assert(IsAirportTile(t)); + assert(IsRunway(t)); + if (IsRunwayExtreme(t)) { + DiagDirection last_dir = GetRunwayExtremeDirection(t); + if (IsRunwayEnd(t)) { + if (last_dir == dir) return t; + assert(last_dir == ReverseDiagDir(dir)); + } else { + assert(IsRunwayStart(t)); + if (last_dir == ReverseDiagDir(dir)) return t; + assert(last_dir == dir); + } } - if (apFA[i].position == MAX_ELEMENTS) break; + t += delta; } - return nofelements; } /** - * Construct the FTA given a description. - * @param nofelements The number of elements in the FTA. - * @param apFA The description of the FTA. - * @return The FTA describing the airport. + * Check if a tile is a valid continuation of a runway. + * The tile \a test_tile is a valid continuation to \a runway, if all of the following are true: + * \li \a test_tile is an airport tile + * \li \a test_tile and \a start_tile are in the same station + * \li the tracks on \a test_tile and \a start_tile are in the same direction + * @param test_tile Tile to test + * @param start_tile Depot tile to compare with + * @pre IsAirport && IsRunwayStart(start_tile) + * @return true if the two tiles are compatible */ -static AirportFTA *AirportBuildAutomata(uint nofelements, const AirportFTAbuildup *apFA) -{ - AirportFTA *FAutomata = MallocT(nofelements); - uint16_t internalcounter = 0; - - for (uint i = 0; i < nofelements; i++) { - AirportFTA *current = &FAutomata[i]; - current->position = apFA[internalcounter].position; - current->heading = apFA[internalcounter].heading; - current->block = apFA[internalcounter].block; - current->next_position = apFA[internalcounter].next; - - /* outgoing nodes from the same position, create linked list */ - while (current->position == apFA[internalcounter + 1].position) { - AirportFTA *newNode = MallocT(1); - - newNode->position = apFA[internalcounter + 1].position; - newNode->heading = apFA[internalcounter + 1].heading; - newNode->block = apFA[internalcounter + 1].block; - newNode->next_position = apFA[internalcounter + 1].next; - /* create link */ - current->next = newNode; - current = current->next; - internalcounter++; +inline bool IsCompatibleRunwayTile(TileIndex test_tile, TileIndex start_tile) +{ + assert(IsAirportTile(start_tile) && IsRunwayStart(start_tile)); + return IsAirportTile(test_tile) && + GetStationIndex(test_tile) == GetStationIndex(start_tile) && + (GetRunwayTrackdirs(start_tile) & GetRunwayTrackdirs(test_tile)) != TRACKDIR_BIT_NONE; +} + +/** + * Get the runway length. + * @param tile Runway start tile + * @return the length of the runway. + */ +uint GetRunwayLength(TileIndex tile) +{ + assert(IsAirport(tile) && IsRunwayStart(tile)); + DiagDirection dir = GetRunwayExtremeDirection(tile); + assert(IsValidDiagDirection(dir)); + + uint length = 1; + [[maybe_unused]] TileIndex start_tile = tile; + + do { + length++; + tile += TileOffsByDiagDir(dir); + assert(IsCompatibleRunwayTile(tile, start_tile)); + } while (!IsRunwayEnd(tile)); + + return length; +} + +/** + * Set the reservation for a complete station platform. + * @pre IsRailStationTile(start) + * @param tile starting tile of the platform + * @param b the state the reservation should be set to + */ +void SetRunwayReservation(TileIndex tile, bool b) +{ + assert(IsRunwayExtreme(tile)); + DiagDirection runway_dir = GetRunwayExtremeDirection(tile); + if (IsRunwayEnd(tile)) runway_dir = ReverseDiagDir(runway_dir); + TileIndexDiff diff = TileOffsByDiagDir(runway_dir); + + do { + assert(IsAirportTile(tile)); + assert(!HasAirportTrackReserved(tile)); + SetReservationAsRunway(tile, b); + if (_show_airport_tracks) MarkTileDirtyByTile(tile); + tile = TileAdd(tile, diff); + } while (!IsRunwayExtreme(tile)); + + SetReservationAsRunway(tile, b); + if (_show_airport_tracks) MarkTileDirtyByTile(tile); +} + +/** + * Get the position in the tile spec of a tile of a tilearea. + * @param tile one tile of the area of the airport. + * @param tile_area of the airport. + * @param rotation the rotation of the airport. + */ +uint RotatedAirportSpecPosition(const TileIndex tile, const TileArea tile_area, const DiagDirection rotation) +{ + /* Get the tile difference between current tile and northern tile of the airport. + * @revise the function TileIndexToTileIndexDiffC as it seems to return the difference + * between the first tile minus the second one (one would expect to be the opposite). + */ + TileIndexDiffC tile_diff = TileIndexToTileIndexDiffC(tile, tile_area.tile); + + switch (rotation) { + case 0: + break; + case 1: + tile_diff = {(int16_t)(tile_area.h - 1 - tile_diff.y), (int16_t)tile_diff.x}; + break; + case 2: + tile_diff = {(int16_t)(tile_area.w - 1 - tile_diff.x), (int16_t)(tile_area.h - 1 - tile_diff.y)}; + break; + case 3: + tile_diff = {(int16_t)tile_diff.y, (int16_t)(tile_area.w - 1 - tile_diff.x)}; + break; + default: NOT_REACHED(); + } + + return tile_diff.x + tile_diff.y * (rotation % 2 == 0 ? tile_area.w : tile_area.h); +} + +/** + * Rotate the trackbits as indicated by a direction + * @param track_bits to rotate. + * @param dir indicating how to rotate track_bits. + * (0 -> no rotation, + * 1 -> 90 clockwise, + * 2 -> 180 clockwise, + * 3 -> 270 clockwise). + * @return the rotated trackbits. + */ +TrackBits RotateTrackBits(TrackBits track_bits, DiagDirection dir) +{ + static const TrackBits rotation_table[DIAGDIR_END][TRACK_END] = { + { TRACK_BIT_X, TRACK_BIT_Y, TRACK_BIT_UPPER, TRACK_BIT_LOWER, TRACK_BIT_LEFT, TRACK_BIT_RIGHT }, + { TRACK_BIT_Y, TRACK_BIT_X, TRACK_BIT_RIGHT, TRACK_BIT_LEFT, TRACK_BIT_UPPER, TRACK_BIT_LOWER }, + { TRACK_BIT_X, TRACK_BIT_Y, TRACK_BIT_LOWER, TRACK_BIT_UPPER, TRACK_BIT_RIGHT, TRACK_BIT_LEFT }, + { TRACK_BIT_Y, TRACK_BIT_X, TRACK_BIT_LEFT, TRACK_BIT_RIGHT, TRACK_BIT_LOWER, TRACK_BIT_UPPER } + }; + + TrackBits rotated = TRACK_BIT_NONE; + for (Track track : SetTrackBitIterator(track_bits)) { + rotated |= rotation_table[dir][track]; + } + + return rotated; +} + +void Station::LoadAirportTilesFromSpec(TileArea ta, DiagDirection rotation, AirType airtype) +{ + if (this->airport.tile == INVALID_TILE) return; + + const AirportSpec *as = this->airport.GetSpec(); + if (airtype == INVALID_AIRTYPE) airtype = as->airtype; + this->airport.air_type = airtype; + + for (Tile t : ta) { + uint pos = RotatedAirportSpecPosition(t, ta, rotation); + const AirportTileTable *airport_tile_desc = &as->layouts[this->airport.layout].tiles[pos]; + if (airport_tile_desc->type == ATT_INVALID) continue; + assert(this->TileBelongsToAirport(t)); + + t.m5() = 0; + + SetStationType(t, STATION_AIRPORT); + SetAirType(t, airtype); + SetAirportTileType(t, airport_tile_desc->type); + + bool airtype_gfx = airtype != as->airtype || airport_tile_desc->gfx[rotation] == INVALID_AIRPORTTILE; + SetAirGfxType(t, airtype_gfx); + SetTileAirportGfx(t, airtype_gfx ? airport_tile_desc->at_gfx : airport_tile_desc->gfx[rotation]); + + switch (GetAirportTileType(t)) { + case ATT_INFRASTRUCTURE_WITH_CATCH: + case ATT_INFRASTRUCTURE_NO_CATCH: + SetAirportTileRotation(t, (DiagDirection)((rotation + airport_tile_desc->dir) % DIAGDIR_END)); + if (!airtype_gfx) SetAirportGfxForAirtype(t, airport_tile_desc->at_gfx); + break; + + case ATT_SIMPLE_TRACK: + break; + case ATT_HANGAR_STANDARD: + case ATT_HANGAR_EXTENDED: + SetHangarDirection(t, RotateDiagDir(airport_tile_desc->dir, rotation)); + break; + + case ATT_APRON_NORMAL: + case ATT_APRON_HELIPAD: + case ATT_APRON_HELIPORT: + case ATT_APRON_BUILTIN_HELIPORT: + SetAirportTileRotation(t, (DiagDirection)((rotation + airport_tile_desc->dir) % DIAGDIR_END)); + break; + + case ATT_RUNWAY_MIDDLE: + SB(t.m8(), 12, 2, RotateDirection(airport_tile_desc->runway_directions, rotation)); + break; + + case ATT_RUNWAY_START_NO_LANDING: + case ATT_RUNWAY_START_ALLOW_LANDING: + case ATT_RUNWAY_END: + SB(t.m8(), 12, 2, RotateDiagDir(airport_tile_desc->dir, rotation)); + break; + case ATT_WAITING_POINT: + NOT_REACHED(); + default: NOT_REACHED(); + } + + if (!IsInfrastructure(t)) { + SetAirportTileTracks(t, RotateTrackBits(airport_tile_desc->trackbits, rotation)); } - current->next = nullptr; - internalcounter++; } - return FAutomata; + + this->UpdateAirportDataStructure(); +} + +/** Update cached variables after loading a game or modifying an airport */ +void Station::UpdateAirportDataStructure() +{ + this->ClearAirportDataInfrastructure(); + + /* Recover the airport area tile rescanning the rect of the station */ + TileArea ta(TileXY(this->rect.left, this->rect.top), TileXY(this->rect.right, this->rect.bottom)); + /* At the same time, detect any hangar. */ + TileIndex first_hangar = INVALID_TILE; + + TileArea airport_area; + for (TileIndex t : ta) { + if (!this->TileBelongsToAirport(t)) continue; + airport_area.Add(t); + + if (first_hangar != INVALID_TILE) continue; + + if (this->airport.air_type == INVALID_AIRTYPE) this->airport.air_type = GetAirType(t); + + assert(this->airport.air_type == GetAirType(t)); + + if (IsHangar(t)) first_hangar = t; + } + + /* Set/Clear depot. */ + if (first_hangar != INVALID_TILE && this->airport.hangar == nullptr) { + if (!Depot::CanAllocateItem()) NOT_REACHED(); + this->airport.hangar = new Depot(first_hangar, VEH_AIRCRAFT, GetTileOwner(first_hangar), this); + this->airport.hangar->build_date = this->build_date; + this->airport.hangar->town = this->town; + SetBit(this->airport.flags, AFB_HANGAR); + } else if (this->airport.hangar != nullptr) { + if (first_hangar == INVALID_TILE) { + ClrBit(this->airport.flags, AFB_HANGAR); + if (this->airport.hangar->IsInUse()) { + this->airport.hangar->Disuse(); + } + } else { + SetBit(this->airport.flags, AFB_HANGAR); + if (!this->airport.hangar->IsInUse()) { + /* Reuse current hangar. */ + this->airport.hangar->Reuse(first_hangar); + } + } + } + + if (airport_area.tile == INVALID_TILE) return; + + if (this->airport.hangar != nullptr) this->airport.hangar->r_types.air_types = AIRTYPES_NONE; + + bool allow_landing = false; + for (TileIndex t : airport_area) { + if (!this->TileBelongsToAirport(t)) continue; + this->airport.Add(t); + + assert(this->airport.air_type == GetAirType(t)); + + if (!MayHaveAirTracks(t)) continue; + + UpdateTracks(t); + + switch (GetAirportTileType(t)) { + case ATT_HANGAR_STANDARD: + case ATT_HANGAR_EXTENDED: + assert(this->airport.HasHangar()); + this->airport.hangar->depot_tiles.emplace_back(t); + this->airport.hangar->xy = t; + this->airport.hangar->r_types.air_types |= (AirTypes)(1 << this->airport.air_type); + break; + + case ATT_APRON_NORMAL: + this->airport.aprons.emplace_back(t); + break; + case ATT_APRON_HELIPAD: + this->airport.helipads.emplace_back(t); + break; + case ATT_APRON_HELIPORT: + case ATT_APRON_BUILTIN_HELIPORT: + this->airport.heliports.emplace_back(t); + break; + + case ATT_RUNWAY_START_ALLOW_LANDING: + allow_landing = true; + [[fallthrough]]; + case ATT_RUNWAY_START_NO_LANDING: + this->airport.runways.emplace_back(t); + break; + + default: break; + } + } + + if (this->airport.hangar != nullptr) InvalidateWindowData(WC_BUILD_VEHICLE, this->airport.hangar->index); + + if (this->airport.HasLandingRunway() != allow_landing) { + ToggleBit(this->airport.flags, AFB_LANDING_RUNWAY); + } } /** - * Get the finite state machine of an airport type. - * @param airport_type %Airport type to query FTA from. @see AirportTypes - * @return Finite state machine of the airport. + * Return the tracks a tile could have. + * It returns the tracks the tile has plus the extra tracks that + * could also exist on the tile. + * @param tile + * @return The tracks the tile could have. */ -const AirportFTAClass *GetAirport(const uint8_t airport_type) +TrackBits GetAllowedTracks(TileIndex tile) { - if (airport_type == AT_DUMMY) return &_airportfta_dummy; - return AirportSpec::Get(airport_type)->fsm; + assert(IsAirportTile(tile)); + switch (GetAirportTileType(tile)) { + case ATT_INFRASTRUCTURE_NO_CATCH: + case ATT_INFRASTRUCTURE_WITH_CATCH: + return TRACK_BIT_NONE; + + case ATT_HANGAR_STANDARD: + case ATT_HANGAR_EXTENDED: + return HasBit(Tile(tile).m8(), 15) ? TRACK_BIT_Y: TRACK_BIT_X; + + case ATT_APRON_HELIPORT: + case ATT_APRON_BUILTIN_HELIPORT: + return TRACK_BIT_CROSS; + + case ATT_APRON_NORMAL: + case ATT_APRON_HELIPAD: + case ATT_SIMPLE_TRACK: + case ATT_RUNWAY_MIDDLE: + case ATT_RUNWAY_END: + case ATT_RUNWAY_START_NO_LANDING: + case ATT_RUNWAY_START_ALLOW_LANDING: { + TrackBits tracks = TRACK_BIT_ALL; + + const TrackBits rem_tracks[] = { + ~TRACK_BIT_UPPER, + ~(TRACK_BIT_UPPER | TRACK_BIT_RIGHT), + ~TRACK_BIT_RIGHT, + ~(TRACK_BIT_LOWER | TRACK_BIT_RIGHT), + ~TRACK_BIT_LOWER, + ~(TRACK_BIT_LOWER | TRACK_BIT_LEFT), + ~TRACK_BIT_LEFT, + ~(TRACK_BIT_UPPER | TRACK_BIT_LEFT), + }; + + for (Direction dir = DIR_BEGIN; dir < DIR_END; dir++) { + TileIndex t = TileAddByDir(tile, dir); + if (!IsValidTile(t) || !IsAirportTile(t) || + GetStationIndex(t) != GetStationIndex(tile) || !MayHaveAirTracks(t)) { + tracks &= rem_tracks[dir]; + } else if (IsHangar(t)) { + tracks &= rem_tracks[dir]; + } + } + + return tracks; + } + + default: NOT_REACHED(); + } } /** - * Get the vehicle position when an aircraft is build at the given tile - * @param hangar_tile The tile on which the vehicle is build - * @return The position (index in airport node array) where the aircraft ends up + * Get the sprite for an airport tile. + * @param t Tile to get the sprite of. + * @return AirportTile ID. */ -uint8_t GetVehiclePosOnBuild(TileIndex hangar_tile) -{ - const Station *st = Station::GetByTile(hangar_tile); - const AirportFTAClass *apc = st->airport.GetFTA(); - /* When we click on hangar we know the tile it is on. By that we know - * its position in the array of depots the airport has.....we can search - * layout for #th position of depot. Since layout must start with a listing - * of all depots, it is simple */ - for (uint i = 0;; i++) { - if (st->airport.GetHangarTile(i) == hangar_tile) { - assert(apc->layout[i].heading == HANGAR); - return apc->layout[i].position; +StationGfx GetAirportGfx(TileIndex t) +{ + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + + if (!HasAirtypeGfx(t)) return GetTranslatedAirportTileID(GetTileAirportGfx(t)); + + switch (GetAirportTileType(t)) { + case ATT_INFRASTRUCTURE_NO_CATCH: + case ATT_INFRASTRUCTURE_WITH_CATCH: + return GetTileAirportGfx(t); + + case ATT_SIMPLE_TRACK: + return (StationGfx)0; + + case ATT_HANGAR_STANDARD: + case ATT_HANGAR_EXTENDED: + return (StationGfx)0; + + case ATT_APRON_NORMAL: + case ATT_APRON_HELIPAD: + case ATT_APRON_HELIPORT: + case ATT_APRON_BUILTIN_HELIPORT: + switch (GetApronType(t)) { + case APRON_APRON: + case APRON_HELIPAD: + case APRON_HELIPORT: + return (StationGfx)0; + case APRON_BUILTIN_HELIPORT: + return (StationGfx)0; // oil rig heliport + default: NOT_REACHED(); + } + + case ATT_RUNWAY_MIDDLE: + case ATT_RUNWAY_START_NO_LANDING: + case ATT_RUNWAY_START_ALLOW_LANDING: + case ATT_RUNWAY_END: { + return (StationGfx)0; + break; } + + case ATT_WAITING_POINT: + NOT_REACHED(); + default: + NOT_REACHED(); } - NOT_REACHED(); } + diff --git a/src/airport.h b/src/airport.h index d5df10e275c9e..9ea84384838fa 100644 --- a/src/airport.h +++ b/src/airport.h @@ -11,6 +11,7 @@ #define AIRPORT_H #include "direction_type.h" +#include "track_type.h" #include "tile_type.h" /** Some airport-related constants */ @@ -21,8 +22,8 @@ static const uint MAX_ELEMENTS = 255; ///< maximum number static const uint NUM_AIRPORTTILES_PER_GRF = 255; ///< Number of airport tiles per NewGRF; limited to 255 to allow extending Action3 with an extended byte later on. static const uint NUM_AIRPORTTILES = 256; ///< Total number of airport tiles. -static const uint NEW_AIRPORTTILE_OFFSET = 74; ///< offset of first newgrf airport tile -static const uint INVALID_AIRPORTTILE = NUM_AIRPORTTILES; ///< id for an invalid airport tile +static const uint NEW_AIRPORTTILE_OFFSET = 74; ///< offset of first newgrf airport tilex +static const uint NUM_AIRTYPE_INFRATILES = 11; ///< Total number of infrastructure tiles by airtype. /** Airport types */ enum AirportTypes { @@ -39,163 +40,30 @@ enum AirportTypes { NEW_AIRPORT_OFFSET = 10, ///< Number of the first newgrf airport. NUM_AIRPORTS_PER_GRF = 128, ///< Maximal number of airports per NewGRF. NUM_AIRPORTS = 128, ///< Maximal number of airports in total. + AT_CUSTOM = 253, ///< Customized airport. AT_INVALID = 254, ///< Invalid airport. AT_DUMMY = 255, ///< Dummy airport. }; -/** Flags for airport movement data. */ -enum AirportMovingDataFlags { - AMED_NOSPDCLAMP = 1 << 0, ///< No speed restrictions. - AMED_TAKEOFF = 1 << 1, ///< Takeoff movement. - AMED_SLOWTURN = 1 << 2, ///< Turn slowly (mostly used in the air). - AMED_LAND = 1 << 3, ///< Landing onto landing strip. - AMED_EXACTPOS = 1 << 4, ///< Go exactly to the destination coordinates. - AMED_BRAKE = 1 << 5, ///< Taxiing at the airport. - AMED_HELI_RAISE = 1 << 6, ///< Helicopter take-off. - AMED_HELI_LOWER = 1 << 7, ///< Helicopter landing. - AMED_HOLD = 1 << 8, ///< Holding pattern movement (above the airport). -}; - -/** Movement States on Airports (headings target) */ -enum AirportMovementStates { - TO_ALL = 0, ///< Go in this direction for every target. - HANGAR = 1, ///< Heading for hangar. - TERM1 = 2, ///< Heading for terminal 1. - TERM2 = 3, ///< Heading for terminal 2. - TERM3 = 4, ///< Heading for terminal 3. - TERM4 = 5, ///< Heading for terminal 4. - TERM5 = 6, ///< Heading for terminal 5. - TERM6 = 7, ///< Heading for terminal 6. - HELIPAD1 = 8, ///< Heading for helipad 1. - HELIPAD2 = 9, ///< Heading for helipad 2. - TAKEOFF = 10, ///< Airplane wants to leave the airport. - STARTTAKEOFF = 11, ///< Airplane has arrived at a runway for take-off. - ENDTAKEOFF = 12, ///< Airplane has reached end-point of the take-off runway. - HELITAKEOFF = 13, ///< Helicopter wants to leave the airport. - FLYING = 14, ///< %Vehicle is flying in the air. - LANDING = 15, ///< Airplane wants to land. - ENDLANDING = 16, ///< Airplane wants to finish landing. - HELILANDING = 17, ///< Helicopter wants to land. - HELIENDLANDING = 18, ///< Helicopter wants to finish landing. - TERM7 = 19, ///< Heading for terminal 7. - TERM8 = 20, ///< Heading for terminal 8. - HELIPAD3 = 21, ///< Heading for helipad 3. - MAX_HEADINGS = 21, ///< Last valid target to head for. - TERMGROUP = 255, ///< Aircraft is looking for a free terminal in a terminalgroup. -}; - -/** Movement Blocks on Airports blocks (eg_airport_flags). */ -static const uint64_t - TERM1_block = 1ULL << 0, ///< Block belonging to terminal 1. - TERM2_block = 1ULL << 1, ///< Block belonging to terminal 2. - TERM3_block = 1ULL << 2, ///< Block belonging to terminal 3. - TERM4_block = 1ULL << 3, ///< Block belonging to terminal 4. - TERM5_block = 1ULL << 4, ///< Block belonging to terminal 5. - TERM6_block = 1ULL << 5, ///< Block belonging to terminal 6. - HELIPAD1_block = 1ULL << 6, ///< Block belonging to helipad 1. - HELIPAD2_block = 1ULL << 7, ///< Block belonging to helipad 2. - RUNWAY_IN_OUT_block = 1ULL << 8, - RUNWAY_IN_block = 1ULL << 8, - AIRPORT_BUSY_block = 1ULL << 8, - RUNWAY_OUT_block = 1ULL << 9, - TAXIWAY_BUSY_block = 1ULL << 10, - OUT_WAY_block = 1ULL << 11, - IN_WAY_block = 1ULL << 12, - AIRPORT_ENTRANCE_block = 1ULL << 13, - TERM_GROUP1_block = 1ULL << 14, - TERM_GROUP2_block = 1ULL << 15, - HANGAR2_AREA_block = 1ULL << 16, - TERM_GROUP2_ENTER1_block = 1ULL << 17, - TERM_GROUP2_ENTER2_block = 1ULL << 18, - TERM_GROUP2_EXIT1_block = 1ULL << 19, - TERM_GROUP2_EXIT2_block = 1ULL << 20, - PRE_HELIPAD_block = 1ULL << 21, - - /* blocks for new airports */ - TERM7_block = 1ULL << 22, ///< Block belonging to terminal 7. - TERM8_block = 1ULL << 23, ///< Block belonging to terminal 8. - HELIPAD3_block = 1ULL << 24, ///< Block belonging to helipad 3. - HANGAR1_AREA_block = 1ULL << 26, - OUT_WAY2_block = 1ULL << 27, - IN_WAY2_block = 1ULL << 28, - RUNWAY_IN2_block = 1ULL << 29, - RUNWAY_OUT2_block = 1ULL << 10, ///< @note re-uses #TAXIWAY_BUSY_block - HELIPAD_GROUP_block = 1ULL << 13, ///< @note re-uses #AIRPORT_ENTRANCE_block - OUT_WAY_block2 = 1ULL << 31, - /* end of new blocks */ - - NOTHING_block = 1ULL << 30, - AIRPORT_CLOSED_block = 1ULL << 63; ///< Dummy block for indicating a closed airport. - -/** A single location on an airport where aircraft can move to. */ -struct AirportMovingData { - int16_t x; ///< x-coordinate of the destination. - int16_t y; ///< y-coordinate of the destination. - uint16_t flag; ///< special flags when moving towards the destination. - Direction direction; ///< Direction to turn the aircraft after reaching the destination. -}; - -AirportMovingData RotateAirportMovingData(const AirportMovingData *orig, Direction rotation, uint num_tiles_x, uint num_tiles_y); - -struct AirportFTAbuildup; - -/** Finite sTate mAchine (FTA) of an airport. */ -struct AirportFTAClass { -public: - /** Bitmask of airport flags. */ - enum Flags { - AIRPLANES = 0x1, ///< Can planes land on this airport type? - HELICOPTERS = 0x2, ///< Can helicopters land on this airport type? - ALL = AIRPLANES | HELICOPTERS, ///< Mask to check for both planes and helicopters. - SHORT_STRIP = 0x4, ///< This airport has a short landing strip, dangerous for fast aircraft. - }; - - AirportFTAClass( - const AirportMovingData *moving_data, - const uint8_t *terminals, - const uint8_t num_helipads, - const uint8_t *entry_points, - Flags flags, - const AirportFTAbuildup *apFA, - uint8_t delta_z - ); - - ~AirportFTAClass(); +uint8_t GetVehiclePosOnBuild(TileIndex hangar_tile); - /** - * Get movement data at a position. - * @param position Element number to get movement data about. - * @return Pointer to the movement data. - */ - const AirportMovingData *MovingData(uint8_t position) const - { - assert(position < nofelements); - return &moving_data[position]; - } +TrackBits GetAllowedTracks(TileIndex tile); +void SetRunwayReservation(TileIndex tile, bool b); +TileIndex GetRunwayExtreme(TileIndex tile, DiagDirection dir); +uint GetRunwayLength(TileIndex tile); - const AirportMovingData *moving_data; ///< Movement data. - struct AirportFTA *layout; ///< state machine for airport - const uint8_t *terminals; ///< %Array with the number of terminal groups, followed by the number of terminals in each group. - const uint8_t num_helipads; ///< Number of helipads on this airport. When 0 helicopters will go to normal terminals. - Flags flags; ///< Flags for this airport type. - uint8_t nofelements; ///< number of positions the airport consists of - const uint8_t *entry_points; ///< when an airplane arrives at this airport, enter it at position entry_point, index depends on direction - uint8_t delta_z; ///< Z adjustment for helicopter pads +enum AirportFlagBits : uint8_t { + AFB_CLOSED_MANUAL = 0, ///< Airport closed: manually closed. + AFB_HANGAR = 1, ///< Airport has at least one hangar tile. + AFB_LANDING_RUNWAY = 2, ///< Airport has a landing runway. }; -DECLARE_ENUM_AS_BIT_SET(AirportFTAClass::Flags) - - -/** Internal structure used in openttd - Finite sTate mAchine --> FTA */ -struct AirportFTA { - AirportFTA *next; ///< possible extra movement choices from this position - uint64_t block; ///< 64 bit blocks (st->airport.flags), should be enough for the most complex airports - uint8_t position; ///< the position that an airplane is at - uint8_t next_position; ///< next position from this position - uint8_t heading; ///< heading (current orders), guiding an airplane to its target on an airport +enum AirportFlags : uint16_t { + AF_NONE = 0, ///< No flag. + AF_CLOSED_MANUAL = 1 << AFB_CLOSED_MANUAL, + AF_HANGAR = 1 << AFB_HANGAR, + AF_LANDING_RUNWAY = 1 << AFB_LANDING_RUNWAY, }; - -const AirportFTAClass *GetAirport(const uint8_t airport_type); -uint8_t GetVehiclePosOnBuild(TileIndex hangar_tile); +DECLARE_ENUM_AS_BIT_SET(AirportFlags) #endif /* AIRPORT_H */ diff --git a/src/airport_cmd.cpp b/src/airport_cmd.cpp new file mode 100644 index 0000000000000..d7be13dc1b4d4 --- /dev/null +++ b/src/airport_cmd.cpp @@ -0,0 +1,2035 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file airport_cmd.cpp Handling of airport commands. */ + +#include "station_map.h" +#include "stdafx.h" +#include "command_func.h" +#include "core/backup_type.hpp" +#include "air.h" +#include "air_map.h" +#include "aircraft.h" +#include "airport_cmd.h" +#include "animated_tile_func.h" +#include "autoslope.h" +#include "bitmap_type.h" +#include "company_base.h" +#include "company_gui.h" +#include "depot_base.h" +#include "industry.h" +#include "landscape.h" +#include "landscape_cmd.h" +#include "newgrf_debug.h" +#include "newgrf_airport.h" +#include "newgrf_airporttiles.h" +#include "order_backup.h" +#include "pathfinder/yapf/yapf_cache.h" +#include "sound_func.h" +#include "station_func.h" +#include "station_kdtree.h" +#include "strings_func.h" +#include "town.h" +#include "vehicle_func.h" +#include "viewport_func.h" +#include "water.h" +#include "window_func.h" + +#include "pathfinder/follow_track.hpp" + +#include "table/airporttile_ids.h" +#include "table/airport_defaults.h" +#include "table/airtypes.h" +#include "table/strings.h" +#include "table/track_land.h" + +#include "widgets/station_widget.h" + +/** + * Updates the status of the Aircraft heading or in the airport + * @param st Updated airport + */ +void UpdateAircraftOnUpdatedAirport(const Station *st) +{ + for (Aircraft *v : Aircraft::Iterate()) { + if (!v->IsPrimaryVehicle() || v->targetairport != st->index) continue; + if (v->IsAircraftFreelyFlying()) { + AircraftUpdateNextPos(v); + continue; + } + + switch (v->current_order.GetType()) { + case OT_GOTO_STATION: + if (v->current_order.GetDestination() != st->index) continue; + v->dest_tile = v->GetOrderStationLocation(st->index); + break; + + case OT_GOTO_DEPOT: + if (!st->airport.HasHangar()) continue; + if (v->current_order.GetDestination() != st->airport.hangar->index) continue; + v->dest_tile = v->GetOrderHangarLocation(st->airport.hangar->index); + break; + + default: + break; + } + } + + SetWindowClassesDirty(WC_VEHICLE_ORDERS); +} + +extern CommandCost CheckBuildableTile(TileIndex tile, uint invalid_dirs, int &allowed_z, bool allow_steep, bool check_bridge = true); + +extern CommandCost FindJoiningAirport(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, Station **st); + +extern CommandCost BuildStationPart(Station **st, DoCommandFlag flags, bool reuse, TileArea area, StationNaming name_class); + +/** + * Calculate the noise level an airport would have if it were of a given airtype. + * @param airport The airport + * @param airtype The airtype to check, or INVALID_AIRTYPE (when INVALID_AIRTYPE is + * given, it will use the current airtype of the airport). + * @return The total noise generated by the airport if it has the given airtype. + */ +uint8_t CalculateAirportNoiseLevel(const Airport airport, AirType airtype = INVALID_AIRTYPE) +{ + + if (!IsValidTile(airport.tile)) return 0; + + if (airport.heliports.size() == 1 && IsBuiltInHeliportTile(airport.heliports[0])) return 0; + + if (airtype == INVALID_AIRTYPE) airtype = airport.air_type; + + const AirTypeInfo *air_type_info = GetAirTypeInfo(airtype); + + return (uint8_t)(air_type_info->base_noise_level + + airport.runways.size() * air_type_info->runway_noise_level + + airport.helipads.size() + + airport.heliports.size() + + airport.aprons.size()); +} + +/** + * Get a possible noise reduction factor based on distance from town center. + * The further you get, the less noise you generate. + * So all those folks at city council can now happily slee... work in their offices + * @param noise_level noise level of the airport + * @param distance minimum distance between town and airport + * @return the noise that will be generated, according to distance + */ +uint8_t GetAirportNoiseLevelForDistance(uint noise_level, uint distance) +{ + /* 0 cannot be accounted, and 1 is the lowest that can be reduced from town. + * So no need to go any further*/ + if (noise_level < 2) return noise_level; + + /* The steps for measuring noise reduction are based on the "magical" (and arbitrary) 8 base distance + * adding the town_council_tolerance 4 times, as a way to graduate, depending of the tolerance. + * Basically, it says that the less tolerant a town is, the bigger the distance before + * an actual decrease can be granted */ + uint8_t town_tolerance_distance = 8 + (_settings_game.difficulty.town_council_tolerance * 4); + + /* now, we want to have the distance segmented using the distance judged bareable by town + * This will give us the coefficient of reduction the distance provides. */ + uint noise_reduction = distance / town_tolerance_distance; + + /* If the noise reduction equals the airport noise itself, don't give it for free. + * Otherwise, simply reduce the airport's level. */ + return noise_reduction >= noise_level ? 1 : noise_level - noise_reduction; +} + +extern uint RotatedAirportSpecPosition(const TileIndex tile, const TileArea tile_area, const DiagDirection rotation); + +CommandCost AddAirportTileTableToBitmapTileArea(const AirportTileLayout &atl, BitmapTileArea *bta, DiagDirection rotation, uint cost_multiplier) +{ + CommandCost cost(EXPENSES_CONSTRUCTION); + + uint tile_count = 0; + for (TileIndex t : (TileArea)*bta) { + uint pos = RotatedAirportSpecPosition(t, *bta, rotation); + if (atl.tiles[pos].type != ATT_INVALID) { + bta->SetTile(t); + ++tile_count; + } + } + cost.AddCost(_price[PR_BUILD_STATION_AIRPORT] * tile_count * cost_multiplier); + return cost; +} + +/** + * Finds the town nearest to given airport. Based on minimal manhattan distance to any airport's tile. + * If two towns have the same distance, town with lower index is returned. + * @param bta BitmapTileArea of the tiles of the airport + * @param[out] mindist Minimum distance to town + * @return nearest town to airport + */ +Town *AirportGetNearestTown(BitmapTileArea bta, uint &mindist) +{ + assert(Town::GetNumItems() > 0); + + Town *nearest = nullptr; + mindist = UINT_MAX - 1; // prevent overflow + + for (TileIndex tile : bta) { + if (!bta.HasTile(tile)) continue; + /* Iterate over all tiles of the airport, as airport may not be rectangular. */ + Town *t = CalcClosestTownFromTile(tile, mindist + 1); + if (t == nullptr) continue; + + uint dist = DistanceManhattan(t->xy, tile); + if (dist == mindist && t->index < nearest->index) nearest = t; + if (dist < mindist) { + nearest = t; + mindist = dist; + } + } + + return nearest; +} + + +/** Recalculate the noise generated by the airports of each town */ +void UpdateAirportsNoise() +{ + for (Town *t : Town::Iterate()) t->noise_reached = 0; + + for (const Station *st : Station::Iterate()) { + if (st->airport.tile != INVALID_TILE && st->airport.type != AT_OILRIG) { + uint8_t noise_level = CalculateAirportNoiseLevel(st->airport); + uint dist; + BitmapTileArea bta; + bta.Initialize(st->airport); + for (TileIndex t : st->airport) { + if (st->TileBelongsToAirport(t)) bta.SetTile(t); + } + + Town *nearest = AirportGetNearestTown(bta, dist); + nearest->noise_reached += GetAirportNoiseLevelForDistance(noise_level, dist); + } + } +} + +void GetAirportArea(const Station *st, const BitmapTileArea new_tiles, BitmapTileArea &all_tiles) +{ + TileArea complete_tilearea; + if (st != nullptr) { + for (TileIndex t : st->airport) if (st->TileBelongsToAirport(t)) complete_tilearea.Add(t); + } + for (TileIndex t : new_tiles) complete_tilearea.Add(t); + all_tiles.Initialize(complete_tilearea); + for (TileIndex t : new_tiles) all_tiles.SetTile(t); + if (st != nullptr) { + for (TileIndex t : st->airport) if (st->TileBelongsToAirport(t)) all_tiles.SetTile(t); + } +} + +/** + * Checks noise and whether there are too many airports in a town. + * @param st Station with preexisting airport + * @param added_area BitmapTileArea containing the tiles of the airport to be added. + * @param[out] pre_town The town closest to preexisting airport. + * @param[out] post_town The town closest to modified airport. + * @param[out] pre_noise The current noise that the airport of station \b st in \b pre_town + * @param[in,out] post_noise The addition of noise of the new tiles as in, the noise the airport generates for post_town as out. + * @return Whether it is possible to add the tiles due to noise restriction or limit of airports. + */ +CommandCost CheckTownAuthorityForAirports(TileIndex tile, Station *st, const BitmapTileArea added_area, Town **pre_town, Town **post_town, uint &pre_noise, uint &post_noise) +{ + /* Get the area containing previous airport tiles from station st and new added tiles. */ + BitmapTileArea modified_area, initial_area; + GetAirportArea(st, BitmapTileArea(), initial_area); + GetAirportArea(st, added_area, modified_area); + + /* Get the closest town of the preexisting airport to be joined to and the closest town for the modified airport. */ + uint pre_dist = 0; + uint post_dist = 0; + *pre_town = (st != nullptr && (st->facilities & FACIL_AIRPORT) != FACIL_NONE) ? AirportGetNearestTown(initial_area, pre_dist) : nullptr; + *post_town = AirportGetNearestTown(modified_area, post_dist); + + /* Get the current noise for preexisting airport and the noise for the modified airport. */ + pre_noise = *pre_town != nullptr ? CalculateAirportNoiseLevel(st->airport) : 0; + post_noise = GetAirportNoiseLevelForDistance(post_noise + pre_noise, post_dist); + pre_noise = GetAirportNoiseLevelForDistance(pre_noise, pre_dist); + + /* Check if local auth would allow a new airport */ + StringID authority_refuse_message = STR_NULL; + Town *authority_refuse_town = *post_town; + + if (_settings_game.economy.station_noise_level) { + /* Do not allow to build a new airport if this raises the town noise over the maximum allowed by town. */ + if (*post_town == *pre_town) { + /* Preexisting airport and the same town to get noise... */ + if ((*post_town)->noise_reached - pre_noise + post_noise > (*post_town)->MaxTownNoise()) { + authority_refuse_message = STR_ERROR_LOCAL_AUTHORITY_REFUSES_NOISE; + } + } else { + /* Check that adding the airport noise to post_town doesn't exceed the maximum allowed by town. */ + if (((*post_town)->noise_reached + post_noise) > (*post_town)->MaxTownNoise()) { + authority_refuse_message = STR_ERROR_LOCAL_AUTHORITY_REFUSES_NOISE; + } + } + } else if (_settings_game.difficulty.town_council_tolerance != TOWN_COUNCIL_PERMISSIVE) { + if (st != nullptr && (st->facilities & FACIL_AIRPORT) != FACIL_NONE) return CommandCost(); + authority_refuse_town = ClosestTownFromTile(tile, UINT_MAX); + uint num = 0; + for (const Station *s : Station::Iterate()) { + if (s->town == authority_refuse_town && (s->facilities & FACIL_AIRPORT) && s->airport.type != AT_OILRIG) num++; + } + if (num >= 2) { + authority_refuse_message = STR_ERROR_LOCAL_AUTHORITY_REFUSES_AIRPORT; + } + } + + if (authority_refuse_message != STR_NULL) { + SetDParam(0, authority_refuse_town->index); + return_cmd_error(authority_refuse_message); + } + + return CommandCost(); +} + +void UpdateNoiseForTowns(Town *pre_nearest, Town *post_nearest, const uint pre_noise, const uint post_noise) +{ + /* Subtract previous noise. */ + if (pre_noise > 0) pre_nearest->noise_reached -= pre_noise; + + /* Add noise to updated airport. */ + post_nearest->noise_reached += post_noise; + + if (_settings_game.economy.station_noise_level) { + SetWindowDirty(WC_TOWN_VIEW, post_nearest->index); + if (pre_nearest != nullptr && pre_nearest != post_nearest) SetWindowDirty(WC_TOWN_VIEW, pre_nearest->index); + } +} + +CommandCost inline CheckSettingBuildByTile() +{ + if (!_settings_game.station.allow_modify_airports) return_cmd_error(STR_ERROR_AIRPORT_DISABLED_BY_TILE); + return CommandCost(); +} + +extern CommandCost ValidateAutoDrag(Trackdir *trackdir, TileIndex start, TileIndex end); + +/** + * Checks if there is a vehicle in an airport given by one of its tiles. + * @param st Station to check. + * @return A command cost with an error if a vehicle is found on ground or in a runway. + */ +CommandCost AircraftInAirport(const Station *st) +{ + if (st == nullptr) return CommandCost(); + + for (TileIndex tile : st->airport) { + if (!st->TileBelongsToAirport(tile)) continue; + + if ((MayHaveAirTracks(tile) && HasAirportTrackReserved(tile)) || + (IsRunway(tile) && GetReservationAsRunway(tile))) + return_cmd_error(STR_ERROR_AIRPORT_PRESENT_AIRCRAFT); + + /* Aircraft can be hidden inside depots with no associated reservation. */ + if (IsStandardHangarTile(tile)) { + CommandCost ret = EnsureFreeHangar(tile); + if (ret.Failed()) return ret; + } + } + + return CommandCost(); +} + +CommandCost CheckRunwayLength(AirType air_type, uint length) +{ + if (GetAirTypeInfo(air_type)->min_runway_length > length) return_cmd_error(STR_ERROR_AIRPORT_RUNWAY_TOO_SHORT); + return CommandCost(); +} + +CommandCost AddAirportTrack(TileIndex tile, Track track, DoCommandFlag flags) +{ + assert(IsAirportTile(tile) && !IsHangar(tile)); + + if (!IsValidTile(tile)) return CMD_ERROR; + CommandCost ret = CheckTileOwnership(tile); + if (ret.Failed()) return ret; + if (!MayHaveAirTracks(tile)) return_cmd_error(STR_ERROR_AIRPORT_CAN_T_HAVE_TRACKS); + if (HasAirportTileTrack(tile, track)) return_cmd_error(STR_ERROR_ALREADY_BUILT); + AirportTileType att = GetAirportTileType(tile); + if ((att == ATT_HANGAR_STANDARD || att == ATT_HANGAR_EXTENDED) && !IsDiagonalTrack(track)) return_cmd_error(STR_ERROR_AIRPORT_CAN_T_ADD_TRACK_HANGAR); + + if (!HasTrack(GetAllowedTracks(tile), track)) return_cmd_error(STR_ERROR_AIRPORT_NO_COMPATIBLE_NEIGHBOURS); + + if (flags & DC_EXEC) { + SetAirportTileTracks(tile, GetAirportTileTracks(tile) | TrackToTrackBits(track)); + if (_show_airport_tracks) MarkTileDirtyByTile(tile); + } + + return CommandCost(); +} + +CommandCost RemoveAirportTrack(TileIndex tile, Track track, DoCommandFlag flags) +{ + assert(IsAirportTile(tile) && !IsHangar(tile)); + + if (!IsValidTile(tile)) return CMD_ERROR; + CommandCost ret = CheckTileOwnership(tile); + if (ret.Failed()) return ret; + + if (!MayHaveAirTracks(tile) || !HasAirportTileTrack(tile, track)) return CommandCost(); + + if (HasAirportTrackReserved(tile, track) || (IsRunwayExtreme(tile) && GetReservationAsRunway(tile))) return CMD_ERROR; + + if (flags & DC_EXEC) { + SetAirportTileTracks(tile, GetAirportTileTracks(tile) & ~TrackToTrackBits(track)); + if (_show_airport_tracks) MarkTileDirtyByTile(tile); + } + + return CommandCost(); +} + +static const TileIndexDiffC _trackdelta[] = { + { -1, 0 }, { 0, 1 }, { -1, 0 }, { 0, 1 }, { 1, 0 }, { 0, 1 }, + { 0, 0 }, + { 0, 0 }, + { 1, 0 }, { 0, -1 }, { 0, -1 }, { 1, 0 }, { 0, -1 }, { -1, 0 }, + { 0, 0 }, + { 0, 0 } +}; + +/** + * Add/Remove tracks for an airport. + * @param flags operation to perform + * @param start_tile start tile of drag + * @param end_tile end tile of drag + * @param air_type Air type (gravel, asphalt, water...) + * @param add whether to add or remove tracks + * @param track starting track + * @return the cost of this operation or an error + */ +CommandCost CmdAddRemoveTracksToAirport(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, AirType air_type, bool add, Track track) +{ + Trackdir trackdir = TrackToTrackdir(track); + + CommandCost ret = ValidateAutoDrag(&trackdir, start_tile, end_tile); + if (ret.Failed()) return ret; + + if ((flags & DC_EXEC) && _settings_client.sound.confirm) SndPlayTileFx(SND_1F_CONSTRUCTION_OTHER, start_tile); + + bool had_success = false; + std::vector affected_stations; + CommandCost last_error = CMD_ERROR; + CommandCost total_cost; + TileIndex tile = start_tile; + for (;;) { + if (!IsValidTile(tile) || !IsAirportTile(tile) || !MayHaveAirTracks(tile) || IsHangar(tile) || (IsApron(tile) && IsHeliport(tile))) + goto fill_next_track; + + if (air_type != GetAirType(tile)) { + ret.MakeError(STR_ERROR_AIRPORT_INVALID_AIR_TYPE); + } else { + assert(IsValidTrackdir(trackdir)); + if (add) { + ret = AddAirportTrack(tile, TrackdirToTrack(trackdir), flags); + } else { + ret = RemoveAirportTrack(tile, TrackdirToTrack(trackdir), flags); + } + } + + if (ret.Failed()) { + last_error = ret; + if (last_error.GetErrorMessage() != STR_ERROR_ALREADY_BUILT) { + return last_error; + } + + /* Ownership errors are more important. */ + if (last_error.GetErrorMessage() == STR_ERROR_OWNED_BY) break; + } else { + had_success = true; + affected_stations.emplace_back(Station::GetByTile(tile)); + total_cost.AddCost(ret); + } + + fill_next_track: + if (tile == end_tile) break; // end tile + + tile += ToTileIndexDiff(_trackdelta[trackdir]); + + /* toggle railbit for the non-diagonal tracks. */ + if (!IsDiagonalTrackdir(trackdir)) ToggleBit(trackdir, 0); + } + + if (flags & DC_EXEC) { + /* Do all station specific functions here. */ + for (Station *st : affected_stations) { + assert(st != nullptr); + st->airport.type = AT_CUSTOM; + st->UpdateAirportDataStructure(); + UpdateAircraftOnUpdatedAirport(st); + } + } + + if (had_success) return total_cost; + + return last_error; +} + +/** + * Define/undefine a runway. + * @param flags operation to perform + * @param start_tile start tile of drag + * @param end_tile end tile of drag + * @param air_type air type to deal with + * @param runway_type type of runway start (allow/don' allow landing) + * @return the cost of this operation or an error + */ +CommandCost AddRunway(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, AirType air_type, AirportTileType runway_type) +{ + assert(IsValidTile(start_tile)); + assert(IsAirportTile(start_tile)); + + if (!ValParamAirType(air_type)) return_cmd_error(STR_ERROR_AIRPORT_INCORRECT_AIRTYPE); + + if (air_type != GetAirType(start_tile)) return_cmd_error(STR_ERROR_AIRPORT_INVALID_AIR_TYPE); + + Station *st = Station::GetByTile(start_tile); + assert(st != nullptr); + + if (st->airport.runways.size() + 1 > GetAirTypeInfo(air_type)->max_num_runways) return_cmd_error(STR_ERROR_AIRPORT_TOO_MUCH_RUNWAYS); + + Town *pre_town = nullptr; + Town *post_town = nullptr; + uint pre_noise; + uint post_noise = GetAirTypeInfo(air_type)->runway_noise_level; + CommandCost ret = CheckTownAuthorityForAirports(start_tile, st, BitmapTileArea(), &pre_town, &post_town, pre_noise, post_noise); + if (ret.Failed()) return ret; + + TileArea ta(start_tile, end_tile); + assert(ta.h == 1 || ta.w == 1); // Diagonal area. // + uint8_t length = ta.h * ta.w; + ret = CheckRunwayLength(air_type, length); + if (ret.Failed()) return ret; + + DiagDirection dir = DiagdirBetweenTiles(start_tile, end_tile); + Direction adding_dir = DiagDirToDir(dir); + + for (TileIndex tile_iter : ta) { + if (!IsAirportTile(tile_iter)) return_cmd_error(STR_ERROR_AIRPORT_RUNWAY_INCOMPLETE); + if (GetStationIndex(tile_iter) != st->index) return_cmd_error(STR_ERROR_AIRPORT_RUNWAY_INCOMPLETE); + if (!IsSimpleTrack(tile_iter) && !IsPlainRunway(tile_iter)) return_cmd_error(STR_ERROR_AIRPORT_RUNWAY_CAN_T_BUILD_OVER); + if (IsPlainRunway(tile_iter)) { + /* Cannot build an extreme runway tile on an already existing runway. */ + if (tile_iter == start_tile || tile_iter == end_tile) return_cmd_error(STR_ERROR_AIRPORT_RUNWAY_OVERLAP); + + /* Some directions are not compatible. + * There is no need to do this check. + * Anyway, it will be checked. */ + Direction cur_dir = GetPlainRunwayDirections(tile_iter); + if (!IsDiagonalDirection(cur_dir) || + (cur_dir != ((adding_dir + 2) % DIR_END) && (cur_dir + 2) % DIR_END != adding_dir)) + return_cmd_error(STR_ERROR_AIRPORT_RUNWAY_CAN_T_BUILD_OVER); + } + } + + CommandCost cost(EXPENSES_CONSTRUCTION); + cost.AddCost(_price[PR_BUILD_STATION_AIRPORT] * length * GetAirTypeInfo(air_type)->cost_multiplier); + + if (flags & DC_EXEC) { + /* Always update the noise, so there will be no need to recalculate when option toggles. */ + UpdateNoiseForTowns(pre_town, post_town, pre_noise, post_noise); + + for (TileIndex tile_iter : ta) { + TrackBits prev_tracks = MayHaveAirTracks(tile_iter) ? GetAirportTileTracks(tile_iter) : TRACK_BIT_NONE; + TrackBits reserved_tracks = prev_tracks != TRACK_BIT_NONE ? GetReservedAirportTracks(tile_iter) : TRACK_BIT_NONE; + bool reserved_runway = IsRunway(tile_iter) && GetReservationAsRunway(tile_iter); + if ((tile_iter == start_tile) || (tile_iter == end_tile)) { + assert(!IsRunway(tile_iter)); + Tile(tile_iter).m8() = 0; + AirportTileType att = start_tile == tile_iter ? runway_type : ATT_RUNWAY_END; + SetAirportTileType(tile_iter, att); + SetRunwayExtremeDirection(tile_iter, dir); + } else { + if (IsPlainRunway(tile_iter)) { + [[maybe_unused]] Direction cur_dir = GetPlainRunwayDirections(tile_iter); + assert(IsDiagonalDirection(cur_dir)); + assert(cur_dir == ((adding_dir + 2) % DIR_END) || (cur_dir + 2) % DIR_END == adding_dir); + AddPlainRunwayDirections(tile_iter, dir, false); + } else { + SetAirportTileType(tile_iter, ATT_RUNWAY_MIDDLE); + Tile(tile_iter).m8() = 0; + AddPlainRunwayDirections(tile_iter, dir, true); + } + } + SetAirportTileTracks(tile_iter, prev_tracks); + SetAirportTracksReservation(tile_iter, reserved_tracks); + SetReservationAsRunway(tile_iter, reserved_runway); + } + + st->airport.type = AT_CUSTOM; + st->UpdateAirportDataStructure(); + UpdateAircraftOnUpdatedAirport(st); + } + + return cost; +} + +/** + * Get the tiles of a runway. + * @param tile An extreme tile of a runway. + * @return the tile area of the runway. + */ +TileArea GetRunwayTileArea(TileIndex tile) +{ + assert(IsRunwayExtreme(tile)); + DiagDirection dir = GetRunwayExtremeDirection(tile); + return TileArea(GetRunwayExtreme(tile, dir), GetRunwayExtreme(tile, ReverseDiagDir(dir))); +} + +/** + * Define/undefine a apron. + * @param flags operation to perform + * @param start_tile start tile of drag + * @param end_tile end tile of drag + * @return the cost of this operation or an error + */ +CommandCost RemoveRunway(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile) +{ + if (start_tile == end_tile || !IsRunwayExtreme(start_tile) || !IsRunwayExtreme(end_tile)) return_cmd_error(STR_ERROR_AIRPORT_RUNWAY_CAN_T_REMOVE); + + assert(GetRunwayTileArea(start_tile).Contains(end_tile) && IsRunwayExtreme(start_tile) && IsRunwayExtreme(end_tile)); + + TileArea ta(start_tile, end_tile); + assert(ta.w == 1 || ta.h == 1); + const AirTypeInfo *ati = GetAirTypeInfo(GetAirType(start_tile)); + CommandCost cost(EXPENSES_CONSTRUCTION); + cost.AddCost(_price[PR_CLEAR_STATION_AIRPORT] * ta.w * ta.h * ati->cost_multiplier); + + Station *st = Station::GetByTile(start_tile); + assert(st != nullptr); + + for (TileIndex tile_iter : ta) { + assert(IsRunway(tile_iter)); + if (GetReservationAsRunway(tile_iter)) return CMD_ERROR; + } + + if (flags & DC_EXEC) { + uint dist; + BitmapTileArea bta; + bta.Initialize(st->airport); + for (TileIndex t : st->airport) { + if (st->TileBelongsToAirport(t)) bta.SetTile(t); + } + Town *nearest = AirportGetNearestTown(bta, dist); + uint8_t noise_level = CalculateAirportNoiseLevel(st->airport); + nearest->noise_reached -= GetAirportNoiseLevelForDistance(noise_level, dist); + + DiagDirection diag_dir = GetRunwayExtremeDirection(start_tile); + for (Tile tile_iter : ta) { + assert(IsRunway(tile_iter)); + if (IsPlainRunway(tile_iter) && !IsDiagonalDirection(GetPlainRunwayDirections(tile_iter))) { + RemovePlainRunwayDirections(tile_iter, diag_dir); + } else { + SetAirportTileType(tile_iter, ATT_SIMPLE_TRACK); + SB(tile_iter.m8(), 12, 4, 0); + SetAirGfxType(tile_iter, true); + } + MarkTileDirtyByTile(tile_iter); + } + + st->airport.type = AT_CUSTOM; + st->UpdateAirportDataStructure(); + + noise_level = CalculateAirportNoiseLevel(st->airport); + nearest->noise_reached += GetAirportNoiseLevelForDistance(noise_level, dist); + if (_settings_game.economy.station_noise_level) { + SetWindowDirty(WC_TOWN_VIEW, nearest->index); + } + + UpdateAircraftOnUpdatedAirport(st); + } + + return cost; +} + +/** + * When modifying an airport tile, neighbour tiles may lose some tracks. + * Return true if any affected neighbour tile is reserved. + * @param tile Tile to check. + * @pre IsAirportTile + */ +bool HasReservationsOnNeighbourTiles(TileIndex tile) +{ + assert(IsAirportTile(tile)); + + StationID station_id = GetStationIndex(tile); + + for (Direction dir = DIR_BEGIN; dir < DIR_END; dir++) { + TileIndex neighbour = TileAddByDir(tile, dir); + if (!IsValidTile(neighbour)) continue; + if (!IsAirportTileOfStation(neighbour, station_id)) continue; + if (!MayHaveAirTracks(neighbour)) continue; + + TrackBits reserved = GetReservedAirportTracks(neighbour); + switch (dir) { + case DIR_N: + reserved &= TRACK_BIT_LOWER; + break; + case DIR_NE: + reserved &= (TRACK_BIT_LOWER | TRACK_BIT_LEFT); + break; + case DIR_E: + reserved &= TRACK_BIT_LEFT; + break; + case DIR_SE: + reserved &= (TRACK_BIT_LEFT | TRACK_BIT_UPPER); + break; + case DIR_S: + reserved &= TRACK_BIT_UPPER; + break; + case DIR_SW: + reserved &= (TRACK_BIT_UPPER | TRACK_BIT_RIGHT); + break; + case DIR_W: + reserved &= TRACK_BIT_RIGHT; + break; + case DIR_NW: + reserved &= (TRACK_BIT_RIGHT | TRACK_BIT_LOWER); + break; + + default: NOT_REACHED(); + } + + if (reserved != TRACK_BIT_NONE) return true; + } + + return false; +} + +/** + * Change the airtype graphics of an airport tile. + * @param tile + * @param at New airtype tile. + */ +void SetGfxByAirtypeTile(TileIndex tile, AirportTiles at) { + DeleteAnimatedTile(tile); + SetAirGfxType(tile, true); + SetTileAirportGfx(tile, at); + if (AirportTileSpec::GetByTile(tile)->animation.status != ANIM_STATUS_NO_ANIMATION) AddAnimatedTile(tile); +} + +/** + * Redefine use of airport tiles. + * @param flags operation to perform + * @param start_tile start tile of drag + * @param end_tile end tile of drag + * @param air_type air type to build or to remove + * @param air_tile_type (hangar, helipad...) + * @param infra_type infrastrucutre type (building 1, building 2...) + * @param direction heliport/infrastructure direction or hangar exit direction + * @param adding whether adding or removing tiles + * @param diagonal whether remove/add a diagonal tile area + * @return the cost of this operation or an error + */ +CommandCost CmdChangeAirportTiles(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, AirType air_type, AirportTileType air_tile_type, AirportTiles infra_type, DiagDirection direction, bool adding, bool diagonal) +{ + if (!IsAirportTile(start_tile)) return CMD_ERROR; + bool adds_noise = false; + const AirTypeInfo *ati = GetAirTypeInfo(air_type); + + switch (air_tile_type) { + case ATT_APRON_NORMAL: + case ATT_APRON_HELIPAD: + case ATT_APRON_HELIPORT: + adds_noise = adding; + break; + case ATT_HANGAR_STANDARD: + case ATT_HANGAR_EXTENDED: + if (adding && Company::IsValidHumanID(_current_company) && + !HasBit(_settings_game.depot.hangar_types, air_tile_type == ATT_HANGAR_EXTENDED)) { + return_cmd_error(STR_ERROR_DEPOT_TYPE_NOT_AVAILABLE); + } + break; + case ATT_INFRASTRUCTURE_WITH_CATCH: + case ATT_INFRASTRUCTURE_NO_CATCH: + case ATT_RUNWAY_START_ALLOW_LANDING: + case ATT_RUNWAY_START_NO_LANDING: + break; + case ATT_SIMPLE_TRACK: + case ATT_RUNWAY_MIDDLE: + case ATT_RUNWAY_END: + case ATT_APRON_BUILTIN_HELIPORT: + default: + /* Not handled in this command. */ + NOT_REACHED(); + case ATT_WAITING_POINT: + /* Not implemented. */ + NOT_REACHED(); + } + + CommandCost ret = CheckSettingBuildByTile(); + if (ret.Failed()) return ret; + + if (!ValParamAirType(air_type)) return_cmd_error(STR_ERROR_AIRPORT_INCORRECT_AIRTYPE); + + TileArea ta(start_tile, end_tile); + + if (air_tile_type == ATT_RUNWAY_START_ALLOW_LANDING || air_tile_type == ATT_RUNWAY_START_NO_LANDING) { + if (air_type != GetAirType(start_tile)) return_cmd_error(STR_ERROR_AIRPORT_INVALID_AIR_TYPE); + if (GetTileOwner(start_tile) != _current_company) return_cmd_error(STR_ERROR_AREA_IS_OWNED_BY_ANOTHER); + + if (adding) { + /* check same station and free */ + return AddRunway(flags, start_tile, end_tile, air_type, air_tile_type); + } else { + return RemoveRunway(flags, start_tile, end_tile); + } + } + + CommandCost cost(EXPENSES_CONSTRUCTION); + + std::unique_ptr iter; + if (diagonal) { + iter = std::make_unique(start_tile, end_tile); + } else { + iter = std::make_unique(start_tile, end_tile); + } + + Station *st = nullptr; + Town *nearest = nullptr; + uint dist = 0; + uint noise_increase = 0; + for (; *iter != INVALID_TILE; ++(*iter)) { + Tile tile_iter = Tile(*iter); + if (!IsAirportTile(tile_iter)) return CMD_ERROR; + if (GetTileOwner(tile_iter) != _current_company) return CMD_ERROR; + if (air_type != GetAirType(tile_iter)) continue; + + if (IsStandardHangarTile(tile_iter)) { + CommandCost ret = EnsureFreeHangar(tile_iter); + if (ret.Failed()) return ret; + } + + if (st == nullptr) { + st = Station::GetByTile(tile_iter); + + BitmapTileArea bta; + bta.Initialize(st->airport); + for (TileIndex t : st->airport) { + if (st->TileBelongsToAirport(t)) bta.SetTile(t); + } + nearest = AirportGetNearestTown(bta, dist); + if (flags & DC_EXEC) { + uint8_t noise_level = CalculateAirportNoiseLevel(st->airport); + nearest->noise_reached -= GetAirportNoiseLevelForDistance(noise_level, dist); + } + } else { + if (st->index != GetStationIndex(tile_iter)) return CMD_ERROR; + } + + TrackBits tracks = MayHaveAirTracks(tile_iter) ? GetAirportTileTracks(tile_iter) : TRACK_BIT_NONE; + TrackBits reserved_tracks = tracks != TRACK_BIT_NONE ? GetReservedAirportTracks(tile_iter) : TRACK_BIT_NONE; + bool close_tracks = adding && HasReservationsOnNeighbourTiles(tile_iter); + + if (adding) { + if (!IsSimpleTrack(tile_iter)) continue; + if (air_tile_type == ATT_APRON_HELIPORT && + !AreHeliportsAvailable(air_type)) return_cmd_error(STR_ERROR_AIRPORT_CAN_T_BUILD_HELIPORT); + if (GetAirportTileType(tile_iter) == air_tile_type) continue; + + cost.AddCost(_price[PR_BUILD_STATION_AIRPORT] * ati->cost_multiplier); + if (adds_noise && !IsApron(tile_iter)) { + /* Noise concerns... */ + noise_increase++; + } + + switch (air_tile_type) { + case ATT_INFRASTRUCTURE_WITH_CATCH: + case ATT_INFRASTRUCTURE_NO_CATCH: + case ATT_HANGAR_STANDARD: + case ATT_HANGAR_EXTENDED: + case ATT_APRON_HELIPORT: + if (close_tracks || reserved_tracks != TRACK_BIT_NONE) return_cmd_error(STR_ERROR_AIRPORT_PRESENT_AIRCRAFT); + break; + default: + break; + } + + if (flags & DC_EXEC) { + switch (air_tile_type) { + default: NOT_REACHED(); + case ATT_INFRASTRUCTURE_WITH_CATCH: + case ATT_INFRASTRUCTURE_NO_CATCH: { + SetAirportTileType(tile_iter, air_tile_type); + tile_iter.m8() = 0; + SetAirportTileRotation(tile_iter, direction); + break; + } + case ATT_APRON_NORMAL: + case ATT_APRON_HELIPAD: + case ATT_APRON_HELIPORT: + SetAirportTileType(tile_iter, air_tile_type); + SetAirportTileRotation(tile_iter, direction); + if (air_tile_type == ATT_APRON_HELIPORT) { + SetAirportTileTracks(tile_iter, TRACK_BIT_CROSS); + } else { + SetAirportTileTracks(tile_iter, tracks); + SetAirportTracksReservation(tile_iter, reserved_tracks); + } + break; + case ATT_HANGAR_STANDARD: + case ATT_HANGAR_EXTENDED: + SetAirportTileType(tile_iter, air_tile_type); + SetHangarDirection(tile_iter, direction); + SetAirportTileTracks(tile_iter, HasBit(tile_iter.m8(), 14) ? TRACK_BIT_Y : TRACK_BIT_X); + break; + } + SetGfxByAirtypeTile(tile_iter, infra_type); + MarkTileDirtyByTile(tile_iter); + } + + } else { + /* Removing. */ + + if (GetAirportTileType(tile_iter) != air_tile_type) continue; + if (air_tile_type == ATT_HANGAR_STANDARD || air_tile_type == ATT_HANGAR_EXTENDED) { + if (!IsHangar(tile_iter) || GetAirportTileType(tile_iter) != air_tile_type) continue; + } else if (air_tile_type == ATT_INFRASTRUCTURE_WITH_CATCH || air_tile_type == ATT_INFRASTRUCTURE_NO_CATCH) { + if (!IsInfrastructure(tile_iter) || GetAirportTileType(tile_iter) != air_tile_type) continue; + } + + if ((reserved_tracks & TRACK_BIT_CROSS) != TRACK_BIT_NONE) return_cmd_error(STR_ERROR_AIRPORT_PRESENT_AIRCRAFT); + + cost.AddCost(_price[PR_CLEAR_STATION_AIRPORT] * ati->cost_multiplier); + if (flags & DC_EXEC) { + switch (air_tile_type) { + default: NOT_REACHED(); + case ATT_INFRASTRUCTURE_WITH_CATCH: + case ATT_INFRASTRUCTURE_NO_CATCH: + case ATT_APRON_NORMAL: + case ATT_APRON_HELIPAD: + case ATT_APRON_HELIPORT: + case ATT_HANGAR_STANDARD: + case ATT_HANGAR_EXTENDED: + tile_iter.m8() = 0; + SetAirportTileType(tile_iter, ATT_SIMPLE_TRACK); + SetAirGfxType(tile_iter, true); + break; + } + SetAirportTileTracks(tile_iter, tracks); + SetAirportTracksReservation(tile_iter, reserved_tracks); + + MarkTileDirtyByTile(tile_iter); + } + } + } + + if (st == nullptr) return CMD_ERROR; + + if (flags & DC_EXEC) { + st->airport.type = AT_CUSTOM; + + st->UpdateAirportDataStructure(); + st->AfterStationTileSetChange(true, STATION_AIRPORT); + + uint8_t noise_level = CalculateAirportNoiseLevel(st->airport); + assert(nearest != nullptr); + nearest->noise_reached += GetAirportNoiseLevelForDistance(noise_level, dist); + if (_settings_game.economy.station_noise_level) { + SetWindowDirty(WC_TOWN_VIEW, nearest->index); + } + + UpdateAircraftOnUpdatedAirport(st); + + } else if (noise_increase != 0) { + /* Check noise limits. */ + Town *pre_town = nullptr; + Town *post_town = nullptr; + uint pre_noise; + uint post_noise = noise_increase; + ret = CheckTownAuthorityForAirports(start_tile, st, BitmapTileArea(), &pre_town, &post_town, pre_noise, post_noise); + assert(pre_town == nearest); + assert(post_town == nearest); + if (ret.Failed()) return ret; + } + + return cost; +} + + +/** + * Change the air type of an airport. + * @param flags operation to perform + * @param tile with one tile of the airport + * @param air_type new air type for the airport + */ +CommandCost CmdChangeAirType(DoCommandFlag flags, TileIndex tile, AirType air_type) +{ + if (!IsAirportTile(tile)) return_cmd_error(STR_ERROR_SITE_UNSUITABLE); + if (!IsValidTile(tile)) return CMD_ERROR; + + CommandCost ret = CheckSettingBuildByTile(); + if (ret.Failed()) return ret; + + ret = CheckTileOwnership(tile); + if (ret.Failed()) return ret; + + assert(Station::IsValidID(GetStationIndex(tile))); + Station *st = Station::GetByTile(tile); + assert(st != nullptr); + + /* Check air type. */ + if (!ValParamAirType(air_type)) return_cmd_error(STR_ERROR_AIRPORT_INCORRECT_AIRTYPE); + if (st->airport.air_type == air_type) return_cmd_error(STR_ERROR_AIRPORT_ALREADY_AIRTYPE); + + const AirTypeInfo *orig_ati = GetAirTypeInfo(st->airport.air_type); + const AirTypeInfo *new_ati = GetAirTypeInfo(air_type); + if (orig_ati->build_on_water != new_ati->build_on_water) return_cmd_error(STR_ERROR_AIRPORT_CAN_T_CONVERT_WATER); + if (st->airport.runways.size() > GetAirTypeInfo(air_type)->max_num_runways) return_cmd_error(STR_ERROR_AIRPORT_TOO_MUCH_RUNWAYS); + + if (!AreHeliportsAvailable(air_type) && !st->airport.heliports.empty()) { + return_cmd_error(STR_ERROR_AIRPORT_CAN_T_CONVERT_HELIPORT); + } + + for (TileIndex t : st->airport.runways) { + uint length = GetRunwayLength(t); + ret = CheckRunwayLength(air_type, length); + if (ret.Failed()) return ret; + } + + ret = AircraftInAirport(st); + if (ret.Failed()) return ret; + + Town *pre_town = nullptr; + Town *post_town = nullptr; + uint pre_noise; + uint post_noise = CalculateAirportNoiseLevel(st->airport, air_type) - CalculateAirportNoiseLevel(st->airport); + if (st != nullptr && (st->facilities & FACIL_AIRPORT) != FACIL_NONE) post_noise -= GetAirTypeInfo(st->airport.air_type)->base_noise_level; + + ret = CheckTownAuthorityForAirports(tile, st, BitmapTileArea(), &pre_town, &post_town, pre_noise, post_noise); + if (ret.Failed()) return ret; + assert(pre_town == post_town); + + uint tiles = 0; + for (TileIndex tile_iter : st->airport) { + if (!IsAirportTileOfStation(tile_iter, st->index)) continue; + + tiles++; + if (flags & DC_EXEC) { + SetAirType(tile_iter, air_type); + MarkTileDirtyByTile(tile_iter); + + if (!HasAirtypeGfx(tile_iter)) { + bool is_infrastructure = IsInfrastructure(tile_iter); + AirportTiles at = is_infrastructure ? GetAirportGfxForAirtype(tile_iter) : ATTG_DEFAULT_GFX; + SetGfxByAirtypeTile(tile_iter, at); + if (is_infrastructure) SetAirportGfxForAirtype(tile_iter, ATTG_DEFAULT_GFX); + } + } + } + + CommandCost cost(EXPENSES_CONSTRUCTION); + cost.AddCost((_price[PR_CLEAR_STATION_AIRPORT] * GetAirTypeInfo(st->airport.air_type)->cost_multiplier + + _price[PR_BUILD_STATION_AIRPORT] * GetAirTypeInfo(air_type)->cost_multiplier) * tiles); + + if (flags & DC_EXEC) { + Company *c = Company::Get(st->owner); + c->infrastructure.air[air_type] += tiles; + c->infrastructure.air[st->airport.air_type] -= tiles; + + st->airport.air_type = air_type; + st->UpdateAirportDataStructure(); + st->AfterStationTileSetChange(true, STATION_AIRPORT); + if (st->airport.HasHangar()) InvalidateWindowData(WC_BUILD_VEHICLE, st->airport.hangar->index); + + /* Always update the noise, so there will be no need to recalculate when option toggles. */ + UpdateNoiseForTowns(pre_town, post_town, pre_noise, post_noise); + + UpdateAircraftOnUpdatedAirport(st); + + if (_settings_game.economy.station_noise_level) { + SetWindowDirty(WC_TOWN_VIEW, st->town->index); + } + } + + return cost; +} + +/** + * Try to set the best possible graphics for an airport track tile. + * @param tile The tile to set the specific graphics. + * @pre IsAirportTile(tile) + * @pre IsSimpleTrack(tile) + * @pre CheckTileOwnerShip(tile).Succeeded() + */ +void SetAdequateGfxForAirportTrack(TileIndex tile) +{ + assert(IsAirportTile(tile)); + assert(IsSimpleTrack(tile)); + assert(HasAirtypeGfx(tile)); + assert(CheckTileOwnership(tile).Succeeded()); + + TrackBits tracks = GetAirportTileTracks(tile); + uint8_t sides = 0; // The four lower bits correspond to each diagdir. The corresponding bit is set if tracks + // from the tile and the neighbour tile are connected at the corresponding edge. + StationID st_id = GetStationIndex(tile); + CFollowTrackAirport fs(INVALID_COMPANY); + + const TrackBits three_ways[] = {TRACK_BIT_3WAY_NE, TRACK_BIT_3WAY_SE, TRACK_BIT_3WAY_SW, TRACK_BIT_3WAY_NW}; + for (DiagDirection dir = DIAGDIR_BEGIN; dir < DIAGDIR_END; dir++) { + if ((tracks & three_ways[dir]) == TRACK_BIT_NONE) continue; + Trackdir trackdir = DiagDirToDiagTrackdir(dir); + if (!fs.Follow(tile, trackdir)) continue; + SetBit(sides, dir); + } + + switch (CountBits(sides)) { + case 0: + case 1: + SetTileAirportGfx(tile, (AirportTiles)(0)); + break; + case 2: { + Track track = INVALID_TRACK; + switch (sides) { + case 3: + track = TRACK_RIGHT; + break; + case 12: + track = TRACK_LEFT; + break; + case 9: + track = TRACK_UPPER; + break; + case 6: + track = TRACK_LOWER; + break; + case 5: + track = TRACK_X; + break; + case 10: + track = TRACK_Y; + break; + default: + NOT_REACHED(); + } + switch (track) { + case TRACK_X: + case TRACK_Y: + SetTileAirportGfx(tile, (AirportTiles)(6 + track)); + break; + case TRACK_UPPER: + case TRACK_LOWER: + case TRACK_LEFT: + case TRACK_RIGHT: { + uint8_t orthogonal = 0; // Whether the tile looks a vertical or horizontal path. + for (; sides != 0;) { + DiagDirection dir = (DiagDirection)FindFirstBit(sides); + ClrBit(sides, dir); + TileIndex neighbour = TileAddByDiagDir(tile, dir); + if (!IsValidTile(neighbour)) continue; + if (!IsAirportTileOfStation(neighbour, st_id)) continue; + if (!MayHaveAirTracks(neighbour)) continue; + if (!HasBit(GetAirportTileTracks(neighbour), TrackToOppositeTrack(track))) continue; + orthogonal++; + } + orthogonal = orthogonal == 2 ? 8 : 0; + SetTileAirportGfx(tile, (AirportTiles)(track + 7 + orthogonal)); + break; + } + default: + NOT_REACHED(); + } + break; + } + case 3: + switch (sides) { + default: + NOT_REACHED(); + case 7: + SetTileAirportGfx(tile, (AirportTiles)(13)); + break; + case 11: + SetTileAirportGfx(tile, (AirportTiles)(16)); + break; + case 13: + SetTileAirportGfx(tile, (AirportTiles)(15)); + break; + case 14: + SetTileAirportGfx(tile, (AirportTiles)(14)); + break; + } + break; + case 4: + SetTileAirportGfx(tile, (AirportTiles)(8)); + break; + } + + MarkTileDirtyByTile(tile); +} + +/** + * Change the + */ +CommandCost CmdAirportChangeTrackGFX(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, AirType air_type, uint8_t gfx_index, bool diagonal) +{ + CommandCost ret = CheckSettingBuildByTile(); + if (ret.Failed()) return ret; + + /* Check air type. */ + if (!ValParamAirType(air_type)) return_cmd_error(STR_ERROR_AIRPORT_INCORRECT_AIRTYPE); + const AirTypeInfo *ati = GetAirTypeInfo(air_type); + assert(!ati->build_on_water); + + std::unique_ptr iter; + if (diagonal) { + iter = std::make_unique(start_tile, end_tile); + } else { + iter = std::make_unique(start_tile, end_tile); + } + + for (; *iter != INVALID_TILE; ++(*iter)) { + Tile tile = Tile(*iter); + if (!IsAirportTile(tile)) continue; + if (air_type != GetAirType(tile)) return_cmd_error(STR_ERROR_AIRPORT_INCORRECT_AIRTYPE); + + if (CheckTileOwnership(tile).Failed()) { + /* We don't own it!. */ + return_cmd_error(STR_ERROR_OWNED_BY); + } + + if (!HasAirtypeGfx(tile)) continue; + if (!IsSimpleTrack(tile)) continue; + if (flags & DC_EXEC) { + if (gfx_index == 0) { + SetAdequateGfxForAirportTrack(tile); + } else { + SetTileAirportGfx(tile, (AirportTiles)(gfx_index - 1)); + MarkTileDirtyByTile(tile); + } + } + } + + return CommandCost(); +} + +CommandCost CmdAirportToggleGround(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, AirType air_type, bool diagonal) +{ + CommandCost ret = CheckSettingBuildByTile(); + if (ret.Failed()) return ret; + + /* Check air type. */ + if (!ValParamAirType(air_type)) return_cmd_error(STR_ERROR_AIRPORT_INCORRECT_AIRTYPE); + const AirTypeInfo *ati = GetAirTypeInfo(air_type); + assert(!ati->build_on_water); + + std::unique_ptr iter; + if (diagonal) { + iter = std::make_unique(start_tile, end_tile); + } else { + iter = std::make_unique(start_tile, end_tile); + } + + for (; *iter != INVALID_TILE; ++(*iter)) { + Tile tile = Tile(*iter); + if (!IsAirportTile(tile)) continue; + if (air_type != GetAirType(tile)) return_cmd_error(STR_ERROR_AIRPORT_INCORRECT_AIRTYPE); + + if (CheckTileOwnership(tile).Failed()) { + /* We don't own it!. */ + return_cmd_error(STR_ERROR_OWNED_BY); + } + + if (flags & DC_EXEC) { + if (!HasAirtypeGfx(tile)) continue; + SetAirportGroundAndDensity( + tile, + GetAirportGround(tile) == AG_AIRTYPE ? AG_GRASS : AG_AIRTYPE, + 0); + MarkTileDirtyByTile(tile); + } + } + + return CommandCost(); +} + +CommandCost RemoveAirportTiles(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, AirType air_type) +{ + if (!IsValidTile(end_tile)) return CMD_ERROR; + + if (_current_company < MAX_COMPANIES && !ValParamAirType(air_type)) return_cmd_error(STR_ERROR_AIRPORT_INCORRECT_AIRTYPE); + + TileArea ta(start_tile, end_tile); + std::vector affected_stations; + + CommandCost cost(EXPENSES_CONSTRUCTION); + bool any_change = false; + + for (TileIndex tile : ta) { + if (!IsAirportTile(tile)) continue; + if (air_type != GetAirType(tile)) continue; + if (MayHaveAirTracks(tile) && (HasAirportTrackReserved(tile) || HasReservationsOnNeighbourTiles(tile))) continue; + + if (_current_company != OWNER_WATER) { + CommandCost ret = CheckTileOwnership(tile); + if (ret.Failed()) continue; + } + + if (IsStandardHangarTile(tile)) { + CommandCost ret = EnsureFreeHangar(tile); + if (ret.Failed()) continue; + } + + /* Check aircraft in airport. */ + Station *st = Station::GetByTile(tile); + if (std::find(affected_stations.begin(), affected_stations.end(), st) == affected_stations.end()) { + assert(st != nullptr); + + if (flags & DC_EXEC) { + /* Remove airport noise in closest town. */ + uint dist; + BitmapTileArea bta; + bta.Initialize(st->airport); + for (TileIndex t : st->airport) { + if (st->TileBelongsToAirport(t)) bta.SetTile(t); + } + Town *nearest = AirportGetNearestTown(bta, dist); + uint8_t noise_level = CalculateAirportNoiseLevel(st->airport); + nearest->noise_reached -= GetAirportNoiseLevelForDistance(noise_level, dist); + + affected_stations.emplace_back(st); + } + } + + if (IsRunway(tile)) { + if (_current_company < MAX_COMPANIES && (flags & DC_FORCE_CLEAR_TILE) == 0) return_cmd_error(STR_ERROR_AIRPORT_REMOVE_RUNWAYS_FIRST); + if (GetReservationAsRunway(tile)) { + // in fact, if water flooding, check for landing aircraft and crash it + return CMD_ERROR; + } + } + + cost.AddCost(AirClearCost(air_type)); + any_change = true; + + if (!IsSimpleTrack(tile)) cost.AddCost(AirClearCost(air_type)); + + if (flags & DC_EXEC) { + WaterClass wc = HasTileWaterClass(tile) ? GetWaterClass(tile) : WATER_CLASS_INVALID; + DoClearSquare(tile); + /* Maybe change to water */ + if (wc != WATER_CLASS_INVALID) { + Owner o = (wc == WATER_CLASS_CANAL) ? st->owner : OWNER_WATER; + MakeWater(tile, o, wc, Random()); + } + + Company *c = Company::Get(st->owner); + c->infrastructure.air[air_type]--; + c->infrastructure.station--; + DeleteNewGRFInspectWindow(GSF_AIRPORTTILES, tile.base()); + DeleteNewGRFInspectWindow(GSF_AIRTYPES, tile.base()); + + st->rect.AfterRemoveTile(st, tile); + MarkTileDirtyByTile(tile); + } + } + + if (flags & DC_EXEC) { + /* Do all station specific functions here. */ + for (Station *st : affected_stations) { + TileArea temp(ta.tile, ta.w, ta.h); + st->airport.Clear(); + for (TileIndex tile : temp) { + if (IsAirportTile(tile) && st->index == GetStationIndex(tile)) st->airport.Add(tile); + } + + st->airport.type = AT_CUSTOM; + st->UpdateAirportDataStructure(); + if (st->airport.tile == INVALID_TILE) { + st->facilities &= ~FACIL_AIRPORT; + DeleteNewGRFInspectWindow(GSF_AIRPORTS, st->index); + } else { + uint dist; + BitmapTileArea bta; + bta.Initialize(st->airport); + for (TileIndex t : st->airport) { + if (st->TileBelongsToAirport(t)) bta.SetTile(t); + } + Town *nearest = AirportGetNearestTown(bta, dist); + uint8_t noise_level = CalculateAirportNoiseLevel(st->airport); + nearest->noise_reached += GetAirportNoiseLevelForDistance(noise_level, dist); + } + st->AfterStationTileSetChange(true, STATION_AIRPORT); + st->MarkTilesDirty(false); + UpdateAircraftOnUpdatedAirport(st); + } + + } + + if (!any_change) return CMD_ERROR; + + return cost; +} + +/** + * Clear an airport tile, removing also its runways if any. + * @param tile The tile to be cleared. + * @param flags Command flags + * @return The cost in case of success, or an error code if it failed. + */ +CommandCost ClearAirportTile(TileIndex tile, DoCommandFlag flags) +{ + assert(IsAirportTile(tile)); + CommandCost cost(EXPENSES_CONSTRUCTION); + + if (IsRunway(tile)) { + TileIndex start_tile = tile; + TileIndex end_tile = tile; + DiagDirection dir = INVALID_DIAGDIR; + + if (IsPlainRunway(tile)) { + Direction direction = GetPlainRunwayDirections(tile); + if (!IsDiagonalDirection(direction)) { + dir = DirToDiagDir((Direction)((direction - 1) & (DIR_END - 1))); + end_tile = GetRunwayExtreme(tile, dir); + start_tile = GetRunwayExtreme(tile, ReverseDiagDir(dir)); + cost.AddCost(RemoveRunway(flags, start_tile, end_tile)); + direction++; + } + assert(IsDiagonalDirection(direction)); + dir = DirToDiagDir(direction); + } else { + assert(IsRunwayExtreme(tile)); + dir = GetRunwayExtremeDirection(tile); + } + end_tile = GetRunwayExtreme(tile, dir); + start_tile = GetRunwayExtreme(tile, ReverseDiagDir(dir)); + cost.AddCost(RemoveRunway(flags, start_tile, end_tile)); + } + + cost.AddCost(RemoveAirportTiles(flags | DC_FORCE_CLEAR_TILE, tile, tile, GetAirType(tile))); + return cost; +} + +TileIndex GetAnAirportTile(Station* st) +{ + if (st == nullptr || st->airport.tile == INVALID_TILE) return INVALID_TILE; + + for (TileIndex tile : st->airport) { + if (st->TileBelongsToAirport(tile)) return tile; + } + + NOT_REACHED(); +} + +/** + * Checks if an airport can be built at the given area. + * @param tile_area Area to check. + * @param flags Operation to perform. + * @param station StationID to be queried and returned if available. + * @param allow_extending Whether to allow already airport tiles in the area. + * @return The cost in case of success, or an error code if it failed. + */ +static CommandCost CheckFlatLandAirport(BitmapTileArea tile_area, DoCommandFlag flags, StationID &station, bool build_on_water, bool allow_extending) +{ + CommandCost cost(EXPENSES_CONSTRUCTION); + int allowed_z = -1; + + /* Distant join */ + Station *st = Station::GetIfValid(station); + + if (st != nullptr && (st->facilities & FACIL_AIRPORT) != FACIL_NONE) { + TileIndex tile = GetAnAirportTile(st); + auto [tileh, z] = GetTileSlopeZ(tile); + allowed_z = z + GetSlopeMaxZ(tileh); + } + + for (TileIndex tile : tile_area) { + if (!tile_area.HasTile(tile)) continue; + + /* if station is set, then we have special handling to allow building on top of already existing stations. + * so station points to INVALID_STATION if we can build on any station. + * Or it points to a station if we're only allowed to build on exactly that station. */ + if (allow_extending && IsAirportTile(tile)) { + StationID st_id = GetStationIndex(tile); + if (station == INVALID_STATION) { + station = st_id; + } else if (station != st_id) { + return_cmd_error(STR_ERROR_ADJOINS_MORE_THAN_ONE_EXISTING); + } + continue; + } + + CommandCost ret = CheckBuildableTile(tile, 0, allowed_z, false, true); + if (ret.Failed()) return ret; + + if (build_on_water) { + if (!IsWaterTile(tile) || !IsTileFlat(tile)) return_cmd_error(STR_ERROR_AIRPORT_PLAIN_WATER); + } else { + if (IsTileType(tile, MP_WATER)) { + Slope tileh = GetTileSlope(tile); + if (!IsSlopeWithOneCornerRaised(tileh)) return_cmd_error(STR_ERROR_CAN_T_BUILD_ON_WATER); + cost.AddCost(-_price[PR_CLEAR_WATER]); + cost.AddCost(_price[PR_CLEAR_ROUGH]); + } + } + if (!build_on_water) cost.AddCost(Command::Do(flags, tile)); + if (cost.Failed()) return cost; + } + + return cost; +} + +/** + * Add/remove tiles for an airport. + * @param flags operation to perform + * @param start_tile start tile of drag + * @param end_tile end tile of drag + * @param adding whether to add or remove tiles + * @param at air type + * @param station_to_join StationID where to apply the command (NEW_STATION for a new station). + * @param adjacent allow airports directly adjacent to other airports. + * @return the cost of this operation or an error + */ +CommandCost CmdAddRemoveAirportTiles(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, bool adding, AirType at, StationID station_to_join, bool adjacent) +{ + CommandCost ret = CheckSettingBuildByTile(); + if (ret.Failed()) return ret; + + if (!adding) return RemoveAirportTiles(flags, start_tile, end_tile, at); + + /* Does the authority allow this? */ + ret = CheckIfAuthorityAllowsNewStation(start_tile, flags); + if (ret.Failed()) return ret; + + /* Check air type. */ + if (!ValParamAirType(at)) return_cmd_error(STR_ERROR_AIRPORT_INCORRECT_AIRTYPE); + const AirTypeInfo *ati = GetAirTypeInfo(at); + + TileArea new_location(start_tile, end_tile); + BitmapTileArea bta; + bta.Initialize(new_location); + for (TileIndex t : new_location) bta.SetTile(t); + + if (new_location.w > _settings_game.station.station_spread || new_location.h > _settings_game.station.station_spread) return CMD_ERROR; + + bool reuse = (station_to_join != NEW_STATION); + if (!reuse) station_to_join = INVALID_STATION; + bool distant_join = (station_to_join != INVALID_STATION); + + if (distant_join && (!_settings_game.station.distant_join_stations || !Station::IsValidID(station_to_join))) return CMD_ERROR; + + Station *st = nullptr; + if (Station::IsValidID(station_to_join)) { + st = Station::Get(station_to_join); + if ((st->facilities & FACIL_AIRPORT) && st->airport.air_type != at) + return_cmd_error(STR_ERROR_AIRPORT_INCORRECT_AIRTYPE); + } + + /* Make sure the area below consists of clear tiles. */ + StationID est = st == nullptr ? INVALID_STATION : st->index; + /* Clear the land below the station. */ + CommandCost cost = CheckFlatLandAirport(bta, flags, est, ati->build_on_water, true); + if (cost.Failed()) return cost; + + st = nullptr; + ret = FindJoiningAirport(est, station_to_join, adjacent, new_location, &st); + if (ret.Failed()) return ret; + + if (st != nullptr && st->airport.tile != INVALID_TILE && st->airport.air_type != at) return_cmd_error(STR_ERROR_AIRPORT_INCORRECT_AIRTYPE); + + Town *pre_town = nullptr; + Town *post_town = nullptr; + uint pre_noise; + uint post_noise = (st == nullptr || (st->facilities & FACIL_AIRPORT) == FACIL_NONE) ? GetAirTypeInfo(at)->base_noise_level : 0; + ret = CheckTownAuthorityForAirports(new_location.tile, st, bta, &pre_town, &post_town, pre_noise, post_noise); + if (ret.Failed()) return ret; + + ret = BuildStationPart(&st, flags, reuse, new_location, STATIONNAMING_AIRPORT); + if (ret.Failed()) return ret; + + TileIndex airport_tile = GetAnAirportTile(st); + int z = -1; + if (airport_tile != INVALID_TILE) { + auto [tileh, z] = GetTileSlopeZ(airport_tile); + z = z + GetSlopeMaxZ(tileh); + } + + Company *c = Company::GetIfValid(_current_company); + + bool tile_changed = false; + for (TileIndex tile : new_location) { + if (IsAirportTile(tile)) { + assert(GetStationIndex(tile) == st->index); + continue; + } + ret = CheckBuildableTile(tile, 0, z, false, true); + if (ret.Failed()) return_cmd_error(STR_ERROR_AIRPORT_NOT_SAME_LEVEL); + + cost.AddCost(_price[PR_BUILD_STATION_AIRPORT] * ati->cost_multiplier); + tile_changed = true; + + if (flags & DC_EXEC) { + st->airport.Add(tile); + + /* Initialize an empty station. */ + st->AddFacility(FACIL_AIRPORT, tile); + + st->rect.BeforeAddTile(tile, StationRect::ADD_TRY); + WaterClass wc = WATER_CLASS_INVALID; + if (ati->build_on_water && HasTileWaterClass(tile)) wc = GetWaterClass(tile); + + MakeAirport(tile, st->owner, st->index, 0, wc); + SetAirType(tile, at); + SetAirportTileType(tile, ATT_SIMPLE_TRACK); + SetAirGfxType(tile, true); + SetAirportGroundAndDensity(tile, AG_AIRTYPE, 0); + + c->infrastructure.air[at]++; + c->infrastructure.station++; + DirtyCompanyInfrastructureWindows(c->index); + MarkTileDirtyByTile(tile); + } + } + + if (!tile_changed) return CMD_ERROR; + + if (flags & DC_EXEC) { + assert(st != nullptr); + st->UpdateAirportDataStructure(); + st->AfterStationTileSetChange(true, STATION_AIRPORT); + st->airport.type = AT_CUSTOM; + + UpdateNoiseForTowns(pre_town, post_town, pre_noise, post_noise); + UpdateAircraftOnUpdatedAirport(st); + } + + return cost; +} + +/** + * Place an Airport. + * @param flags operation to perform + * @param tile tile where airport will be built + * @param airport_type airport type, @see airport.h + * @param layout airport layout + * @param air_type airport airtype (if modify airports is enabled) + * @param rotation rotation for the airport to be built + * @param station_to_join station ID to join (NEW_STATION if build new one) + * @param allow_adjacent allow airports directly adjacent to other airports. + * @return the cost of this operation or an error + */ +CommandCost CmdBuildAirport(DoCommandFlag flags, TileIndex tile, uint8_t airport_type, uint8_t layout, AirType air_type, DiagDirection rotation, StationID station_to_join, bool allow_adjacent) +{ + bool reuse = (station_to_join != NEW_STATION); + if (!reuse) station_to_join = INVALID_STATION; + bool distant_join = (station_to_join != INVALID_STATION); + + if (distant_join && (!_settings_game.station.distant_join_stations || !Station::IsValidID(station_to_join))) return CMD_ERROR; + + CommandCost ret = CheckIfAuthorityAllowsNewStation(tile, flags); + if (ret.Failed()) return ret; + + /* Check if a valid, buildable airport was chosen for construction */ + const AirportSpec *as = AirportSpec::Get(airport_type); + if (air_type == INVALID_AIRTYPE) air_type = as->airtype; + + if (_settings_game.station.allow_modify_airports && + as->min_runway_length > 0 && + as->min_runway_length < GetAirTypeInfo(air_type)->min_runway_length) + return_cmd_error(STR_ERROR_AIRPORT_RUNWAY_TOO_SHORT); + + if (!ValParamAirType(air_type)) return_cmd_error(STR_ERROR_AIRPORT_INCORRECT_AIRTYPE); + const AirTypeInfo *ati = GetAirTypeInfo(air_type); + + if (as->has_hangar && !Depot::CanAllocateItem()) return CMD_ERROR; + if (!as->IsAvailable()) return CMD_ERROR; + + uint16_t w = as->layouts[layout].size_x; + uint16_t h = as->layouts[layout].size_y; + if (rotation % 2 != 0) Swap(w, h); + + TileArea airport_area = TileArea(tile, w, h); + + /* Check we are within map bounds. */ + if (TileX(tile) + w >= Map::SizeX() || TileY(tile) + h >= Map::SizeY()) return CMD_ERROR; + + if (w > _settings_game.station.station_spread || h > _settings_game.station.station_spread) { + return_cmd_error(STR_ERROR_STATION_TOO_SPREAD_OUT); + } + + BitmapTileArea new_airport_tiles; // New airport tiles to be built. + new_airport_tiles.Initialize(airport_area); + CommandCost cost = AddAirportTileTableToBitmapTileArea(as->layouts[layout], &new_airport_tiles, rotation, ati->cost_multiplier); + + Station *st = nullptr; + ret = FindJoiningAirport(INVALID_STATION, station_to_join, allow_adjacent, airport_area, &st); + if (ret.Failed()) return ret; + + /* Distant join */ + if (st == nullptr && distant_join) st = Station::GetIfValid(station_to_join); + + cost.AddCost(CheckFlatLandAirport(new_airport_tiles, flags, station_to_join, ati->build_on_water, false)); + if (cost.Failed()) return cost; + + Town *pre_town = nullptr; + Town *post_town = nullptr; + uint pre_noise; + uint post_noise = as->GetAirportNoise(air_type); + if (st != nullptr && (st->facilities & FACIL_AIRPORT) != FACIL_NONE) post_noise -= GetAirTypeInfo(st->airport.air_type)->base_noise_level; + + ret = CheckTownAuthorityForAirports(tile, st, new_airport_tiles, &pre_town, &post_town, pre_noise, post_noise); + if (ret.Failed()) return ret; + + if (st != nullptr && (st->facilities & FACIL_AIRPORT) && !_settings_game.station.allow_modify_airports) { + return_cmd_error(STR_ERROR_TOO_CLOSE_TO_ANOTHER_AIRPORT); + } + + uint total_runways = as->num_runways; + if (st != nullptr && (st->facilities & FACIL_AIRPORT)) total_runways += (uint)st->airport.runways.size(); + if (total_runways > GetAirTypeInfo(air_type)->max_num_runways) return_cmd_error(STR_ERROR_AIRPORT_TOO_MUCH_RUNWAYS); + + bool custom_airport = (st != nullptr && (st->facilities & FACIL_AIRPORT) != FACIL_NONE); + + ret = BuildStationPart(&st, flags, reuse, airport_area, + as->has_heliport ? STATIONNAMING_HELIPORT : STATIONNAMING_AIRPORT); + if (ret.Failed()) return ret; + + if (flags & DC_EXEC) { + assert(st != nullptr); + + /* Always update the noise, so there will be no need to recalculate when option toggles. */ + UpdateNoiseForTowns(pre_town, post_town, pre_noise, post_noise); + + st->AddFacility(FACIL_AIRPORT, tile); + st->airport.type = airport_type; + st->airport.layout = layout; + + st->rect.BeforeAddRect(tile, w, h, StationRect::ADD_TRY); + + uint tiles = 0; + for (TileIndex t : new_airport_tiles) { + uint pos = RotatedAirportSpecPosition(t, new_airport_tiles, rotation); + if (as->layouts[layout].tiles[pos].type == ATT_INVALID) continue; // does not belong to new airport tiles. + tiles++; + WaterClass wc = HasTileWaterClass(t) ? GetWaterClass(t) : WATER_CLASS_INVALID; + MakeAirport(t, st->owner, st->index, 0, wc); + SetStationTileRandomBits(t, GB(Random(), 0, 4)); + st->airport.Add(t); + MarkTileDirtyByTile(t); + } + + st->LoadAirportTilesFromSpec(airport_area, rotation, air_type); + + if (custom_airport) st->airport.type = AT_CUSTOM; + + /* Replace all hangars with hangars the human user can build. */ + bool allow_standard_hangars = HasBit(_settings_game.depot.hangar_types, 0); + bool allow_extended_hangars = HasBit(_settings_game.depot.hangar_types, 1); + if (Company::IsValidHumanID(_current_company) && (!allow_standard_hangars || !allow_extended_hangars)) { + for (TileIndex t : new_airport_tiles) { + uint pos = RotatedAirportSpecPosition(t, new_airport_tiles, rotation); + if (as->layouts[layout].tiles[pos].type == ATT_INVALID) continue; // does not belong to new airport tiles. + assert(IsAirportTileOfStation(t, st->index)); + + /* Should we allow standard and extended hangars? */ + if (!IsHangar(t)) continue; + if (IsExtendedHangar(t)) { + if (!allow_extended_hangars) SetExtendedHangar(t, false); + } else { + if (!allow_standard_hangars) SetExtendedHangar(t, true); + } + } + } + + /* Set animated tiles. */ + for (TileIndex t : new_airport_tiles) { + if (!st->TileBelongsToAirport(t)) continue; + if (AirportTileSpec::GetByTile(t)->animation.status != ANIM_STATUS_NO_ANIMATION) AddAnimatedTile(t); + } + + /* Only call the animation trigger after all tiles have been built */ + for (TileIndex t : new_airport_tiles) { + if (!st->TileBelongsToAirport(t)) continue; + AirportTileAnimationTrigger(st, t, AAT_BUILT); + } + + Company *c = Company::Get(st->owner); + c->infrastructure.air[air_type] += tiles; + c->infrastructure.station += tiles; + + st->AfterStationTileSetChange(true, STATION_AIRPORT); + InvalidateWindowData(WC_STATION_VIEW, st->index, -1); + + UpdateAircraftOnUpdatedAirport(st); + } + + return cost; +} + +/** + * Remove an airport + * @param tile TileIndex been queried + * @param flags operation to perform + * @return cost or failure of operation + */ +CommandCost RemoveAirport(TileIndex tile, DoCommandFlag flags) +{ + Station *st = Station::GetByTile(tile); + + CommandCost ret = AircraftInAirport(st); + if (ret.Failed()) return ret; + + if (_current_company != OWNER_WATER) { + ret = CheckOwnership(st->owner); + if (ret.Failed()) return ret; + } + + CommandCost cost(EXPENSES_CONSTRUCTION); + + Town *nearest; + if (flags & DC_EXEC) { + if (st->airport.HasHangar()) { + CloseWindowById(WC_VEHICLE_DEPOT, st->airport.hangar->index); + OrderBackup::Reset(st->airport.hangar->index, false); + } + + /* The noise level is the noise from the airport and reduce it to account for the distance to the town center. + * And as for construction, always remove it, even if the setting is not set, in order to avoid the + * need of recalculation */ + uint dist; + BitmapTileArea bta; + bta.Initialize(st->airport); + for (TileIndex t : st->airport) { + if (st->TileBelongsToAirport(t)) bta.SetTile(t); + } + nearest = AirportGetNearestTown(bta, dist); + uint8_t noise_level = CalculateAirportNoiseLevel(st->airport); + nearest->noise_reached -= GetAirportNoiseLevelForDistance(noise_level, dist); + + if (_settings_game.economy.station_noise_level) { + SetWindowDirty(WC_TOWN_VIEW, nearest->index); + } + } + + + uint tiles = 0; + for (TileIndex tile_cur : st->airport) { + if (!st->TileBelongsToAirport(tile_cur)) continue; + + tiles++; + + if (flags & DC_EXEC) { + Company *c = Company::Get(st->owner); + c->infrastructure.air[st->airport.air_type]--; + c->infrastructure.station--; + DeleteAnimatedTile(tile_cur); + DoClearSquare(tile_cur); + DeleteNewGRFInspectWindow(GSF_AIRPORTTILES, tile_cur.base()); + DeleteNewGRFInspectWindow(GSF_AIRTYPES, tile_cur.base()); + } + } + + const AirTypeInfo *ati = GetAirTypeInfo(st->airport.air_type); + cost.AddCost(_price[PR_CLEAR_STATION_AIRPORT] * ati->cost_multiplier * tiles); + + if (flags & DC_EXEC) { + /* Clear the persistent storage. */ + delete st->airport.psa; + + st->rect.AfterRemoveRect(st, st->airport); + + st->UpdateAirportDataStructure(); + st->airport.Clear(); + st->facilities &= ~FACIL_AIRPORT; + + InvalidateWindowData(WC_STATION_VIEW, st->index, -1); + + st->AfterStationTileSetChange(false, STATION_AIRPORT); + + DeleteNewGRFInspectWindow(GSF_AIRPORTS, st->index); + + UpdateAircraftOnUpdatedAirport(st); + } + + return cost; +} + +void BuildBuiltInHeliport(Tile tile) +{ + if (!Station::CanAllocateItem()) { + Debug(misc, 0, "Can't allocate built in station for industry at 0x{}. Built in station won't be built.", static_cast(tile)); + return; + } + + Station *st = new Station(tile); + _station_kdtree.Insert(st->index); + st->town = ClosestTownFromTile(tile, UINT_MAX); + + st->string_id = GenerateStationName(st, tile, STATIONNAMING_OILRIG); + + assert(IsTileType(tile, MP_INDUSTRY)); + /* Mark industry as associated both ways */ + st->industry = Industry::GetByTile(tile); + st->industry->neutral_station = st; + DeleteAnimatedTile(tile); + MakeStation(tile, OWNER_NONE, st->index, STATION_AIRPORT, 0, GetWaterClass(tile)); + + const AirportSpec *as = AirportSpec::Get(AT_OILRIG); + const AirportTileTable *tile_desc = &as->layouts[0].tiles[0]; + tile.m4() = 0; + tile.m5() = 0; + SetAirType(tile, as->airtype); + SetAirportTileType(tile, tile_desc->type); + SetAirportTileTracks(tile, tile_desc->trackbits); + SetApronType(tile, tile_desc->apron_type); + st->airport.flags = AF_NONE; + + st->owner = OWNER_NONE; + st->airport.type = AT_OILRIG; + st->airport.air_type = as->airtype; + st->airport.Add(tile); + st->ship_station.Add(tile); + st->facilities = FACIL_AIRPORT | FACIL_DOCK; + st->build_date = TimerGameCalendar::date; + UpdateStationDockingTiles(st); + + st->rect.BeforeAddTile(tile, StationRect::ADD_FORCE); + + st->UpdateAirportDataStructure(); + st->UpdateVirtCoord(); + + /* An industry tile has now been replaced with a station tile, this may change the overlap between station catchments and industry tiles. + * Recalculate the station catchment for all stations currently in the industry's nearby list. + * Clear the industry's station nearby list first because Station::RecomputeCatchment cannot remove nearby industries in this case. */ + if (_settings_game.station.serve_neutral_industries) { + StationList nearby = std::move(st->industry->stations_near); + st->industry->stations_near.clear(); + for (Station *near : nearby) { + near->RecomputeCatchment(true); + UpdateStationAcceptance(near, true); + } + } + + st->RecomputeCatchment(); + UpdateStationAcceptance(st, false); +} + +void DeleteOldBuiltInHeliport(TileIndex tile) +{ + Station *st = Station::GetByTile(tile); + + MakeWaterKeepingClass(tile, OWNER_NONE); + + /* The oil rig station is not supposed to be shared with anything else */ + assert(st->facilities == (FACIL_AIRPORT | FACIL_DOCK) && st->airport.type == AT_OILRIG); + if (st->industry != nullptr && st->industry->neutral_station == st) { + /* Don't leave dangling neutral station pointer */ + st->industry->neutral_station = nullptr; + } + + delete st; +} + +void DeleteBuiltInHeliport(TileIndex tile) +{ + assert(IsBuiltInHeliportTile(tile)); + Station *st = Station::GetByTile(tile); + + bool helicopter_present = HasAirportTrackReserved(tile); + MakeWaterKeepingClass(tile, OWNER_NONE); + + /* The oil rig station is not supposed to be shared with anything else */ + assert(st->facilities == (FACIL_AIRPORT | FACIL_DOCK) && st->airport.type == AT_OILRIG); + if (st->industry != nullptr && st->industry->neutral_station == st) { + /* Don't leave dangling neutral station pointer */ + st->industry->neutral_station = nullptr; + } + + st->airport.tile = INVALID_TILE; + st->airport.air_type = INVALID_AIRTYPE; + st->facilities = FACIL_NONE; + + if (helicopter_present) { + for (Aircraft *a : Aircraft::Iterate()) { + if (!a->IsNormalAircraft()) continue; + if (!a->IsHelicopter()) continue; + if (a->tile != tile) continue; + if (a->state == AS_BUILTIN_HELIPORT || + a->state == AS_START_TAKEOFF || + a->state == AS_LANDED || + a->state == AS_FLYING_HELICOPTER_TAKEOFF || + a->state == AS_FLYING_HELICOPTER_LANDING || + a->state == AS_ON_HOLD_APPROACHING) { + a->state = AS_FLYING; + ProcessOrders(a); + a->SetDestTile(0); + } + } + } + + UpdateAircraftOnUpdatedAirport(st); + delete st; +} + +/** + * Open/close an airport to incoming aircraft. + * @param flags Operation to perform. + * @param station_id Station ID of the airport. + * @return the cost of this operation or an error + */ +CommandCost CmdOpenCloseAirport(DoCommandFlag flags, StationID station_id) +{ + if (!Station::IsValidID(station_id)) return CMD_ERROR; + Station *st = Station::Get(station_id); + + if (!(st->facilities & FACIL_AIRPORT) || st->owner == OWNER_NONE) return CMD_ERROR; + + CommandCost ret = CheckOwnership(st->owner); + if (ret.Failed()) return ret; + + if (flags & DC_EXEC) { + st->airport.flags ^= AF_CLOSED_MANUAL; + SetWindowWidgetDirty(WC_STATION_VIEW, st->index, WID_SV_CLOSE_AIRPORT); + UpdateAircraftOnUpdatedAirport(st); + } + return CommandCost(); +} + +extern void CrashAircraft(Aircraft *a); + +/** + * When an airport is being flooded, update all aircraft in that airport + * and update all aircraft trying to land on it. + * @param st Station being flooded. + */ +void FloodAircraftOnAirport(const Station *st) +{ + for (Aircraft *a : Aircraft::Iterate()) { + if (!a->IsNormalAircraft() || + a->IsAircraftFreelyFlying() || + a->vehstatus & VS_CRASHED) { + continue; + } + + if (a->IsAircraftFlying()) { + if (!a->IsHelicopter()) { + TileIndex test_tile = a->state == AS_FLYING_TAKEOFF ? a->tile : a->GetNextTile(); + if (!IsAirportTileOfStation(test_tile, st->index)) continue; + /* Remove reservation and keep flying. */ + a->state = AS_FLYING_LEAVING_AIRPORT; + DiagDirection diagdir = TrackdirToExitdir(a->trackdir); + TileIndex start_tile = (a->state == AS_FLYING_TAKEOFF || a->state == AS_FLYING_LANDING) ? + GetRunwayExtreme(a->tile, ReverseDiagDir(diagdir)) : a->GetNextTile(); + assert(IsRunwayStart(start_tile)); + SetRunwayReservation(start_tile, false); + } else { + a->state = AS_FLYING_NO_DEST; + a->next_pos.pos = AP_DEFAULT; + assert(IsValidTrackdir(a->trackdir)); + RemoveAirportTrackReservation(a->tile, TrackdirToTrack(a->trackdir)); + } + } else { + if (!IsAirportTileOfStation(a->tile, st->index)) continue; + CrashAircraft(a); + } + } +} diff --git a/src/airport_cmd.h b/src/airport_cmd.h index 2e27057d8ca29..9c91708fcd9c5 100644 --- a/src/airport_cmd.h +++ b/src/airport_cmd.h @@ -11,6 +11,33 @@ #define AIRPORT_CMD_H #include "command_type.h" +#include "air.h" +#include "air_type.h" +#include "direction_type.h" +#include "station_type.h" +#include "track_type.h" + +enum AirportTiles : uint8_t; + +CommandCost CmdChangeAirportTiles(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, AirType air_type, AirportTileType air_tile_type, AirportTiles infra_type, DiagDirection direction, bool adding, bool diagonal); +CommandCost CmdAddRemoveAirportTiles(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, bool adding, AirType at, StationID station_to_join, bool adjacent); +CommandCost CmdAddRemoveTracksToAirport(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, AirType air_type, bool add, Track track); +CommandCost CmdChangeAirType(DoCommandFlag flags, TileIndex tile, AirType air_type); +CommandCost CmdAirportChangeTrackGFX(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, AirType air_type, uint8_t gfx_index, bool diagonal); +CommandCost CmdAirportToggleGround(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, AirType air_type, bool diagonal); + +CommandCost CmdBuildAirport(DoCommandFlag flags, TileIndex tile, uint8_t airport_type, uint8_t layout, AirType air_type, DiagDirection rotation, StationID station_to_join, bool allow_adjacent); +CommandCost CmdOpenCloseAirport(DoCommandFlag flags, StationID station_id); + +DEF_CMD_TRAIT(CMD_CHANGE_AIRPORT, CmdChangeAirportTiles, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION) +DEF_CMD_TRAIT(CMD_ADD_REM_AIRPORT, CmdAddRemoveAirportTiles, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION) +DEF_CMD_TRAIT(CMD_ADD_REM_TRACKS, CmdAddRemoveTracksToAirport, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION) +DEF_CMD_TRAIT(CMD_CONVERT_AIRPORT, CmdChangeAirType, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION) +DEF_CMD_TRAIT(CMD_AIRPORT_CHANGE_GFX, CmdAirportChangeTrackGFX, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION) +DEF_CMD_TRAIT(CMD_AIRPORT_TOGGLE_GROUND, CmdAirportToggleGround, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION) + +DEF_CMD_TRAIT(CMD_BUILD_AIRPORT, CmdBuildAirport, CMD_AUTO | CMD_NO_WATER, CMDT_LANDSCAPE_CONSTRUCTION) +DEF_CMD_TRAIT(CMD_OPEN_CLOSE_AIRPORT, CmdOpenCloseAirport, 0, CMDT_ROUTE_MANAGEMENT) CommandCallback CcBuildAirport; diff --git a/src/airport_gui.cpp b/src/airport_gui.cpp index 727a8e1f6448e..f7a004584cc56 100644 --- a/src/airport_gui.cpp +++ b/src/airport_gui.cpp @@ -9,6 +9,7 @@ #include "stdafx.h" #include "economy_func.h" +#include "widget_type.h" #include "window_gui.h" #include "station_gui.h" #include "terraform_gui.h" @@ -17,6 +18,7 @@ #include "strings_func.h" #include "viewport_func.h" #include "company_func.h" +#include "command_func.h" #include "tilehighlight_func.h" #include "company_base.h" #include "station_type.h" @@ -31,6 +33,12 @@ #include "command_func.h" #include "airport_cmd.h" #include "station_cmd.h" +#include "air_type.h" +#include "air.h" +#include "air_map.h" +#include "station_map.h" +#include "engine_base.h" +#include "window_type.h" #include "zoom_func.h" #include "timer/timer.h" #include "timer/timer_game_calendar.h" @@ -39,12 +47,28 @@ #include "safeguards.h" - -static AirportClassID _selected_airport_class; ///< the currently visible airport class -static int _selected_airport_index; ///< the index of the selected airport in the current class or -1 -static uint8_t _selected_airport_layout; ///< selected airport layout number. +static AirType _cur_airtype; ///< Air type of the current build-air toolbar. +static AirportTileType _airport_tile_type; ///< Current airport tile type (hangar, infrastructure... +static DiagDirection _rotation_dir; ///< Exit direction for new hangars, or rotation for heliports and infrastructure. +static bool _remove_button_clicked; ///< Flag whether 'remove' toggle-button is currently enabled +static AirportClassID _selected_airport_class; ///< the currently visible airport class +static int _selected_airport_index; ///< the index of the selected airport in the current class or -1 +static uint8_t _selected_airport_layout; ///< selected airport layout number. +bool _show_airport_tracks = 0; ///< whether to show the airport tracks on viewports. +static DiagDirection _selected_rotation; ///< selected rotation for airport. +static uint8_t _selected_infra_catch_rotation; ///< selected rotation for infrastructure. +static AirportTiles _selected_infra_catch; ///< selected infrastructure type. +static uint8_t _selected_infra_nocatch_rotation; ///< selected rotation for infrastructure. +static AirportTiles _selected_infra_nocatch; ///< selected infrastructure type. +static uint8_t _selected_track_gfx_index; ///< selected track gfx index. static void ShowBuildAirportPicker(Window *parent); +static void ShowHangarPicker(Window *parent); +static void ShowHeliportPicker(Window *parent); +static void ShowAirportInfraNoCatchPicker(Window *parent); +static void ShowAirportInfraWithCatchPicker(Window *parent); +static void ShowTrackGfxPicker(Window *parent); +Window *ShowBuildAirToolbar(AirType airtype); SpriteID GetCustomAirportSprite(const AirportSpec *as, uint8_t layout); @@ -70,29 +94,57 @@ static void PlaceAirport(TileIndex tile) auto proc = [=](bool test, StationID to_join) -> bool { if (test) { - return Command::Do(CommandFlagsToDCFlags(GetCommandFlags()), tile, airport_type, layout, INVALID_STATION, adjacent).Succeeded(); + return adjacent || Command::Do(CommandFlagsToDCFlags(GetCommandFlags()), tile, airport_type, layout, _cur_airtype, _selected_rotation, INVALID_STATION, adjacent).Succeeded(); } else { - return Command::Post(STR_ERROR_CAN_T_BUILD_AIRPORT_HERE, CcBuildAirport, tile, airport_type, layout, to_join, adjacent); + return Command::Post(STR_ERROR_CAN_T_BUILD_AIRPORT_HERE, CcBuildAirport, tile, airport_type, layout, _cur_airtype, _selected_rotation, to_join, adjacent); } }; ShowSelectStationIfNeeded(TileArea(tile, _thd.size.x / TILE_SIZE, _thd.size.y / TILE_SIZE), proc); } +/** + * Get the other tile of a runway. + * @param tile The tile. + * @return the other extreme of the runway if the tile checked is the start or end of a runway + * or the same tile otherwise. + */ + TileIndex GetOtherEndOfRunway(TileIndex tile) +{ + if (IsValidTile(tile) && IsAirportTile(tile) && IsRunwayExtreme(tile)) { + AirportTileType att = GetAirportTileType(tile); + DiagDirection dir = GetRunwayExtremeDirection(tile); + if (att == ATT_RUNWAY_END) dir = ReverseDiagDir(dir); + return GetRunwayExtreme(tile, dir); + } + return tile; +} + /** Airport build toolbar window handler. */ struct BuildAirToolbarWindow : Window { + const bool allow_by_tile; int last_user_action; // Last started user action. - BuildAirToolbarWindow(WindowDesc &desc, WindowNumber window_number) : Window(desc) + BuildAirToolbarWindow(bool allow_by_tile, WindowDesc &desc, AirType airtype) : Window(desc), allow_by_tile(allow_by_tile) { - this->InitNested(window_number); + this->CreateNestedTree(); + this->SetupAirToolbar(airtype); + this->FinishInitNested(TRANSPORT_AIR); + + this->DisableWidget(WID_AT_REMOVE); + this->last_user_action = INVALID_WID_AT; + this->OnInvalidateData(); if (_settings_client.gui.link_terraform_toolbar) ShowTerraformToolbar(this); - this->last_user_action = INVALID_WID_AT; + _show_airport_tracks = true; + MarkWholeScreenDirty(); } void Close([[maybe_unused]] int data = 0) override { + _show_airport_tracks = false; + MarkWholeScreenDirty(); + if (this->IsWidgetLowered(WID_AT_AIRPORT)) SetViewportCatchmentStation(nullptr, true); if (_settings_client.gui.link_terraform_toolbar) CloseWindowById(WC_SCEN_LAND_GEN, 0, false); this->Window::Close(); @@ -115,13 +167,134 @@ struct BuildAirToolbarWindow : Window { /* Show in the tooltip why this button is disabled. */ this->GetWidget(WID_AT_AIRPORT)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); } else { - this->GetWidget(WID_AT_AIRPORT)->SetToolTip(STR_TOOLBAR_AIRCRAFT_BUILD_AIRPORT_TOOLTIP); + this->GetWidget(WID_AT_AIRPORT)->SetToolTip(STR_TOOLBAR_AIRPORT_BUILD_AIRPORT_TOOLTIP); + } + } + + void SetStringParameters(int widget) const override + { + if (widget == WID_AT_CAPTION) { + if (_settings_game.station.allow_modify_airports) { + SetDParam(0, GetAirTypeInfo(_cur_airtype)->strings.toolbar_caption); + } else { + SetDParam(0, STR_TOOLBAR_AIRPORT_CAPTION); + } + } + } + + /** + * Configures the air toolbar for airtype given + * @param airtype the airtype to display + */ + void SetupAirToolbar(AirType airtype) + { + if (!this->allow_by_tile) return; + assert(airtype < AIRTYPE_END); + + _cur_airtype = airtype; + const AirTypeInfo *ati = GetAirTypeInfo(airtype); + SetWidgetDisabledState(WID_AT_TOGGLE_GROUND, ati->build_on_water); + SetWidgetDisabledState(WID_AT_CHANGE_GRAPHICS, ati->build_on_water); + + this->GetWidget(WID_AT_BUILD_TILE)->widget_data = ati->gui_sprites.add_airport_tiles; + this->GetWidget(WID_AT_TRACKS)->widget_data = ati->gui_sprites.build_track_tile; + this->GetWidget(WID_AT_CONVERT)->widget_data = ati->gui_sprites.change_airtype; + this->GetWidget(WID_AT_INFRASTRUCTURE_CATCH)->widget_data = ati->gui_sprites.build_catchment_infra; + this->GetWidget(WID_AT_INFRASTRUCTURE_NO_CATCH)->widget_data = ati->gui_sprites.build_noncatchment_infra; + this->GetWidget(WID_AT_RUNWAY_LANDING)->widget_data = ati->gui_sprites.define_landing_runway; + this->GetWidget(WID_AT_RUNWAY_NO_LANDING)->widget_data = ati->gui_sprites.define_nonlanding_runway; + this->GetWidget(WID_AT_APRON)->widget_data = ati->gui_sprites.build_apron; + this->GetWidget(WID_AT_HELIPAD)->widget_data = ati->gui_sprites.build_helipad; + this->GetWidget(WID_AT_HELIPORT)->widget_data = ati->gui_sprites.build_heliport; + if (this->HasWidget(WID_AT_HANGAR_STANDARD)) this->GetWidget(WID_AT_HANGAR_STANDARD)->widget_data = ati->gui_sprites.build_hangar; + if (this->HasWidget(WID_AT_HANGAR_EXTENDED)) this->GetWidget(WID_AT_HANGAR_EXTENDED)->widget_data = ati->gui_sprites.build_hangar; + + if (!AreHeliportsAvailable(airtype)) DisableWidget(WID_AT_HELIPORT); + } + + /** + * Switch to another air type. + * @param airtype New air type. + */ + void ModifyAirType(AirType airtype) + { + this->SetupAirToolbar(airtype); + this->ReInit(); + } + + /** + * The "remove"-button click proc of the build-air toolbar. + * @see BuildAirToolbarWindow::OnClick() + */ + void BuildAirClick_Remove() + { + if (this->IsWidgetDisabled(WID_AT_REMOVE)) return; + CloseWindowById(WC_SELECT_STATION, 0); + this->ToggleWidgetLoweredState(WID_AT_REMOVE); + this->SetWidgetDirty(WID_AT_REMOVE); + _remove_button_clicked = this->IsWidgetLowered(WID_AT_REMOVE); + + if (this->last_user_action == WID_AT_RUNWAY_LANDING || + this->last_user_action == WID_AT_RUNWAY_NO_LANDING) { + SetObjectToPlace(GetAirTypeInfo(_cur_airtype)->cursor.build_hangar, PAL_NONE, _remove_button_clicked ? HT_SPECIAL : HT_RECT, this->window_class, this->window_number); + this->LowerWidget(this->last_user_action); + this->SetWidgetLoweredState(WID_AT_REMOVE, _remove_button_clicked); + } + + SetSelectionRed(_remove_button_clicked); + if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP); + } + + void UpdateWidgetSize(int widget, Dimension &size, const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override + { + if (!IsInsideMM(widget, WID_AT_BUILD_TILE, WID_AT_REMOVE)) return; + NWidgetCore *wid = this->GetWidget(widget); + + Dimension d = GetSpriteSize(wid->widget_data); + d.width += padding.width; + d.height += padding.height; + size = d; + } + + void UpdateRemoveWidgetStatus(int clicked_widget) + { + if (!this->allow_by_tile) return; + + assert(clicked_widget != WID_AT_REMOVE); + + if (clicked_widget >= WID_AT_REMOVE_FIRST && clicked_widget <= WID_AT_REMOVE_LAST) { + bool is_button_lowered = this->IsWidgetLowered(clicked_widget); + _remove_button_clicked &= is_button_lowered; + this->SetWidgetDisabledState(WID_AT_REMOVE, !is_button_lowered); + this->SetWidgetLoweredState(WID_AT_REMOVE, _remove_button_clicked); + SetSelectionRed(_remove_button_clicked); + } else { + /* When any other buttons that do not accept "removal", + * raise and disable the removal button. */ + this->DisableWidget(WID_AT_REMOVE); + this->RaiseWidget(WID_AT_REMOVE); + _remove_button_clicked = false; } } void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override { switch (widget) { + case WID_AT_BUILD_TILE: + HandlePlacePushButton(this, widget, GetAirTypeInfo(_cur_airtype)->cursor.add_airport_tiles, HT_RECT); + this->last_user_action = widget; + break; + + case WID_AT_TRACKS: + _airport_tile_type = ATT_SIMPLE_TRACK; + HandlePlacePushButton(this, widget, GetAirTypeInfo(_cur_airtype)->cursor.build_track_tile, HT_RAIL); + this->last_user_action = widget; + break; + + case WID_AT_REMOVE: + this->BuildAirClick_Remove(); + return; + case WID_AT_AIRPORT: if (HandlePlacePushButton(this, WID_AT_AIRPORT, SPR_CURSOR_AIRPORT, HT_RECT)) { ShowBuildAirportPicker(this); @@ -134,14 +307,97 @@ struct BuildAirToolbarWindow : Window { this->last_user_action = widget; break; + case WID_AT_CONVERT: + HandlePlacePushButton(this, widget, GetAirTypeInfo(_cur_airtype)->cursor.change_airtype, HT_RECT); + this->last_user_action = widget; + break; + + case WID_AT_INFRASTRUCTURE_CATCH: + _airport_tile_type = ATT_INFRASTRUCTURE_WITH_CATCH; + if (HandlePlacePushButton(this, widget, GetAirTypeInfo(_cur_airtype)->cursor.build_catchment_infra, HT_RECT | HT_DIAGONAL)) { + ShowAirportInfraWithCatchPicker(this); + } + this->last_user_action = widget; + break; + + case WID_AT_INFRASTRUCTURE_NO_CATCH: + _airport_tile_type = ATT_INFRASTRUCTURE_NO_CATCH; + if (HandlePlacePushButton(this, widget, GetAirTypeInfo(_cur_airtype)->cursor.build_noncatchment_infra, HT_RECT | HT_DIAGONAL)) { + ShowAirportInfraNoCatchPicker(this); + } + this->last_user_action = widget; + break; + + case WID_AT_APRON: + _airport_tile_type = ATT_APRON_NORMAL; + HandlePlacePushButton(this, widget, GetAirTypeInfo(_cur_airtype)->cursor.build_apron, HT_RECT | HT_DIAGONAL); + this->last_user_action = widget; + break; + + case WID_AT_HELIPAD: + _airport_tile_type = ATT_APRON_HELIPAD; + HandlePlacePushButton(this, widget, GetAirTypeInfo(_cur_airtype)->cursor.build_helipad, HT_RECT | HT_DIAGONAL); + this->last_user_action = widget; + break; + + case WID_AT_HELIPORT: + _airport_tile_type = ATT_APRON_HELIPORT; + if (HandlePlacePushButton(this, widget, GetAirTypeInfo(_cur_airtype)->cursor.build_heliport, HT_RECT | HT_DIAGONAL)) { + ShowHeliportPicker(this); + } + this->last_user_action = widget; + break; + + case WID_AT_HANGAR_STANDARD: + case WID_AT_HANGAR_EXTENDED: + _airport_tile_type = widget == WID_AT_HANGAR_STANDARD ? ATT_HANGAR_STANDARD : ATT_HANGAR_EXTENDED; + if (HandlePlacePushButton(this, widget, GetAirTypeInfo(_cur_airtype)->cursor.build_hangar, HT_RECT)) { + ShowHangarPicker(this); + } + this->last_user_action = widget; + break; + + case WID_AT_RUNWAY_LANDING: + _airport_tile_type = ATT_RUNWAY_START_ALLOW_LANDING; + HandlePlacePushButton(this, widget, GetAirTypeInfo(_cur_airtype)->cursor.define_landing_runway, _remove_button_clicked ? HT_SPECIAL : HT_RECT); + this->last_user_action = widget; + break; + + case WID_AT_RUNWAY_NO_LANDING: + _airport_tile_type = ATT_RUNWAY_START_NO_LANDING; + HandlePlacePushButton(this, widget, GetAirTypeInfo(_cur_airtype)->cursor.define_nonlanding_runway, _remove_button_clicked ? HT_SPECIAL : HT_RECT); + this->last_user_action = widget; + break; + + case WID_AT_CHANGE_GRAPHICS: + if (HandlePlacePushButton(this, widget, SPR_CURSOR_MOUSE, HT_RECT)) { + ShowTrackGfxPicker(this); + } + this->last_user_action = widget; + break; + + case WID_AT_TOGGLE_GROUND: + HandlePlacePushButton(this, widget, SPR_CURSOR_MOUSE, HT_RECT | HT_DIAGONAL); + this->last_user_action = widget; + break; + default: break; } - } + UpdateRemoveWidgetStatus(widget); + } void OnPlaceObject([[maybe_unused]] Point pt, TileIndex tile) override { switch (this->last_user_action) { + case WID_AT_BUILD_TILE: + VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_BUILD_STATION); + break; + + case WID_AT_TRACKS: + VpStartPlaceSizing(tile, VPM_RAILDIRS, DDSP_PLACE_RAIL); + break; + case WID_AT_AIRPORT: PlaceAirport(tile); break; @@ -150,6 +406,41 @@ struct BuildAirToolbarWindow : Window { PlaceProc_DemolishArea(tile); break; + case WID_AT_CONVERT: + Command::Post(STR_ERROR_CAN_T_DO_THIS, tile, _cur_airtype); + break; + + case WID_AT_HANGAR_STANDARD: + case WID_AT_HANGAR_EXTENDED: + VpStartPlaceSizing(tile, HasBit(_rotation_dir, 0) ? VPM_FIX_Y : VPM_FIX_X, DDSP_BUILD_STATION); + break; + + case WID_AT_INFRASTRUCTURE_CATCH: + case WID_AT_INFRASTRUCTURE_NO_CATCH: + case WID_AT_APRON: + case WID_AT_HELIPAD: + case WID_AT_HELIPORT: + VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_BUILD_STATION); + break; + + case WID_AT_RUNWAY_LANDING: + case WID_AT_RUNWAY_NO_LANDING: + if (_remove_button_clicked) { + Command::Post(STR_ERROR_CAN_T_DO_THIS, tile, GetOtherEndOfRunway(tile), _cur_airtype, _airport_tile_type, ATTG_DEFAULT_GFX, (DiagDirection)0, !_remove_button_clicked, false); + } else { + VpStartPlaceSizing(tile, VPM_X_OR_Y, DDSP_BUILD_STATION); + } + break; + + case WID_AT_CHANGE_GRAPHICS: + VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_BUILD_STATION); + break; + + + case WID_AT_TOGGLE_GROUND: + VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_BUILD_STATION); + break; + default: NOT_REACHED(); } } @@ -161,19 +452,95 @@ struct BuildAirToolbarWindow : Window { void OnPlaceMouseUp([[maybe_unused]] ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, [[maybe_unused]] Point pt, TileIndex start_tile, TileIndex end_tile) override { - if (pt.x != -1 && select_proc == DDSP_DEMOLISH_AREA) { - GUIPlaceProcDragXY(select_proc, start_tile, end_tile); + if (pt.x == -1) return; + + switch (this->last_user_action) { + case WID_AT_BUILD_TILE: { + assert(_settings_game.station.allow_modify_airports); + + auto proc = [=](bool test, StationID to_join) -> bool { + if (test) { + return (_ctrl_pressed && !_remove_button_clicked) || Command::Do(CommandFlagsToDCFlags(GetCommandFlags()), start_tile, end_tile, !_remove_button_clicked, _cur_airtype, INVALID_STATION, _ctrl_pressed).Succeeded(); + } else { + return Command::Post(STR_ERROR_CAN_T_DO_THIS, start_tile, end_tile, !_remove_button_clicked, _cur_airtype, to_join, _ctrl_pressed); + } + }; + + ShowSelectStationIfNeeded(TileArea(start_tile, end_tile), proc); + break; + } + case WID_AT_TRACKS: + assert(_settings_game.station.allow_modify_airports); + Command::Post(STR_ERROR_CAN_T_DO_THIS, start_tile, end_tile, _cur_airtype, !_remove_button_clicked, (Track)(_thd.drawstyle & HT_DIR_MASK)); + break; + case WID_AT_AIRPORT: + assert(start_tile == end_tile); + PlaceAirport(end_tile); + break; + case WID_AT_DEMOLISH: + GUIPlaceProcDragXY(select_proc, start_tile, end_tile); + break; + case WID_AT_CONVERT: + NOT_REACHED(); + case WID_AT_INFRASTRUCTURE_CATCH: + case WID_AT_INFRASTRUCTURE_NO_CATCH: + case WID_AT_APRON: + case WID_AT_HELIPAD: + case WID_AT_HELIPORT: + case WID_AT_HANGAR_STANDARD: + case WID_AT_HANGAR_EXTENDED: + case WID_AT_RUNWAY_LANDING: + case WID_AT_RUNWAY_NO_LANDING: { + DiagDirection dir = _rotation_dir; + AirportTiles gfx = _selected_infra_catch; + bool diagonal = _ctrl_pressed && + (this->last_user_action != WID_AT_HANGAR_EXTENDED && this->last_user_action != WID_AT_HANGAR_STANDARD); + if (this->last_user_action == WID_AT_INFRASTRUCTURE_CATCH) { + dir = (DiagDirection)_selected_infra_catch_rotation; + } else if (this->last_user_action == WID_AT_INFRASTRUCTURE_NO_CATCH) { + dir = (DiagDirection)_selected_infra_nocatch_rotation; + gfx = (AirportTiles)(ATTG_NO_CATCH_FLAG + _selected_infra_nocatch); + } + bool ret = Command::Post(STR_ERROR_CAN_T_DO_THIS, start_tile, end_tile, _cur_airtype, _airport_tile_type, gfx, dir, !_remove_button_clicked, diagonal); + if (ret && _remove_button_clicked && + (this->last_user_action == WID_AT_RUNWAY_LANDING || this->last_user_action == WID_AT_RUNWAY_NO_LANDING)) { + VpStartPlaceSizing(start_tile, VPM_X_OR_Y, DDSP_BUILD_STATION); + } + break; + } + + case WID_AT_CHANGE_GRAPHICS: + Command::Post(STR_ERROR_CAN_T_DO_THIS, start_tile, end_tile, _cur_airtype, _selected_track_gfx_index, _ctrl_pressed); + break; + + case WID_AT_TOGGLE_GROUND: + Command::Post(STR_ERROR_CAN_T_DO_THIS, start_tile, end_tile, _cur_airtype, _ctrl_pressed); + break; + + default: NOT_REACHED(); } } + void OnPlacePresize([[maybe_unused]] Point pt, TileIndex tile) override + { + assert(this->last_user_action == WID_AT_RUNWAY_LANDING || + this->last_user_action == WID_AT_RUNWAY_NO_LANDING); + assert(_remove_button_clicked); + VpSetPresizeRange(tile, GetOtherEndOfRunway(tile)); + } + void OnPlaceObjectAbort() override { if (this->IsWidgetLowered(WID_AT_AIRPORT)) SetViewportCatchmentStation(nullptr, true); this->RaiseButtons(); + CloseWindowById(WC_BUILD_DEPOT, TRANSPORT_AIR); CloseWindowById(WC_BUILD_STATION, TRANSPORT_AIR); CloseWindowById(WC_SELECT_STATION, 0); + CloseWindowById(WC_BUILD_HELIPORT, TRANSPORT_AIR); + CloseWindowById(WC_BUILD_AIRPORT_INFRASTRUCTURE, TRANSPORT_AIR); + CloseWindowById(WC_SELECT_TRACK_GFX, TRANSPORT_AIR); } /** @@ -183,8 +550,9 @@ struct BuildAirToolbarWindow : Window { */ static EventState AirportToolbarGlobalHotkeys(int hotkey) { - if (_game_mode != GM_NORMAL) return ES_NOT_HANDLED; - Window *w = ShowBuildAirToolbar(); + if (_game_mode != GM_NORMAL || !CanBuildVehicleInfrastructure(VEH_AIRCRAFT)) return ES_NOT_HANDLED; + extern AirType _last_built_airtype; + Window *w = ShowBuildAirToolbar(_settings_game.station.allow_modify_airports ? _last_built_airtype : INVALID_AIRTYPE); if (w == nullptr) return ES_NOT_HANDLED; return w->OnHotkey(hotkey); } @@ -192,43 +560,143 @@ struct BuildAirToolbarWindow : Window { static inline HotkeyList hotkeys{"airtoolbar", { Hotkey('1', "airport", WID_AT_AIRPORT), Hotkey('2', "demolish", WID_AT_DEMOLISH), + Hotkey('3', "remove", WID_AT_REMOVE), + Hotkey('4', "tiles", WID_AT_BUILD_TILE), + Hotkey('5', "tracks", WID_AT_TRACKS), + Hotkey('6', "infra_with_catch", WID_AT_INFRASTRUCTURE_CATCH), + Hotkey('7', "infra_no_catch", WID_AT_INFRASTRUCTURE_NO_CATCH), }, AirportToolbarGlobalHotkeys}; }; -static constexpr NWidgetPart _nested_air_toolbar_widgets[] = { +/** + * Add the depot icons depending on availability of construction. + * @return Panel with hangar buttons. + */ +static std::unique_ptr MakeNWidgetHangars() +{ + auto hor = std::make_unique(); + + if (HasBit(_settings_game.depot.hangar_types, 0)) { + /* Add the widget for building standard hangar. */ + hor->Add(std::make_unique(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_HANGAR_STANDARD, 0, STR_TOOLBAR_AIRPORT_BUILD_HANGAR_STANDARD)); + } + + if (HasBit(_settings_game.depot.hangar_types, 1)) { + /* Add the widget for building extended hangar. */ + hor->Add(std::make_unique(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_HANGAR_EXTENDED, 0, STR_TOOLBAR_AIRPORT_BUILD_HANGAR_EXTENDED)); + } + + return hor; +} + +static constexpr NWidgetPart _nested_air_tile_toolbar_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN), + NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_AT_CAPTION), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), SetTextStyle(TC_WHITE), + NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN), + EndContainer(), + NWidget(NWID_VERTICAL), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_AIRPORT), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_AIRPORT, STR_TOOLBAR_AIRPORT_BUILD_PRE_AIRPORT_TOOLTIP), + + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(4, 22), SetFill(1, 1), EndContainer(), + + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_DEMOLISH), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_REMOVE), SetFill(0, 1), + SetDataTip(SPR_IMG_REMOVE, STR_TOOLBAR_AIRPORT_TOGGLE_BUILD_REMOVE), + + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(4, 22), SetFill(1, 1), EndContainer(), + + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_BUILD_TILE), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(0, STR_TOOLBAR_AIRPORT_ADD_TILES), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_TRACKS), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(0, STR_TOOLBAR_AIRPORT_SET_TRACKS), + + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(4, 22), SetFill(1, 1), EndContainer(), + + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_INFRASTRUCTURE_CATCH), SetFill(0, 1), + SetDataTip(0, STR_TOOLBAR_AIRPORT_INFRASTRUCTURE_CATCHMENT), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_INFRASTRUCTURE_NO_CATCH), SetFill(0, 1), + SetDataTip(0, STR_TOOLBAR_AIRPORT_INFRASTRUCTURE_NO_CATCHMENT), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_RUNWAY_LANDING), SetFill(0, 1), + SetDataTip(0, STR_TOOLBAR_AIRPORT_DEFINE_RUNWAY_LANDING), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_RUNWAY_NO_LANDING), SetFill(0, 1), + SetDataTip(0, STR_TOOLBAR_AIRPORT_DEFINE_RUNWAY_NO_LANDING), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(4, 22), SetFill(1, 1), EndContainer(), + + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_APRON), SetFill(0, 1), + SetDataTip(0, STR_TOOLBAR_AIRPORT_BUILD_APRON), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_HELIPAD), SetFill(0, 1), + SetDataTip(0, STR_TOOLBAR_AIRPORT_BUILD_HELIPAD), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_HELIPORT), SetFill(0, 1), + SetDataTip(0, STR_TOOLBAR_AIRPORT_BUILD_HELIPORT), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(4, 22), SetFill(1, 1), EndContainer(), + NWidgetFunction(MakeNWidgetHangars), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(4, 22), SetFill(1, 1), EndContainer(), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_CONVERT), SetFill(0, 1), + SetDataTip(0, STR_TOOLBAR_AIRPORT_CHANGE_AIRTYPE), + NWidget(WWT_TEXTBTN, COLOUR_DARK_GREEN, WID_AT_CHANGE_GRAPHICS), SetFill(0, 1), + SetDataTip(STR_TOOLBAR_AIRPORT_ROTATE_GRAPHICS, STR_TOOLBAR_AIRPORT_ROTATE_GRAPHICS_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_DARK_GREEN, WID_AT_TOGGLE_GROUND), SetFill(0, 1), + SetDataTip(STR_TOOLBAR_AIRPORT_TOGGLE_GROUND, STR_TOOLBAR_AIRPORT_TOGGLE_GROUND_TOOLBAR), + EndContainer(), + EndContainer(), +}; + +static const NWidgetPart _nested_air_nontile_toolbar_widgets[] = { NWidget(NWID_HORIZONTAL), NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN), - NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetDataTip(STR_TOOLBAR_AIRCRAFT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_AT_CAPTION), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN), EndContainer(), NWidget(NWID_HORIZONTAL), - NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_AIRPORT), SetFill(0, 1), SetMinimalSize(42, 22), SetDataTip(SPR_IMG_AIRPORT, STR_TOOLBAR_AIRCRAFT_BUILD_AIRPORT_TOOLTIP), - NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(4, 22), SetFill(1, 1), EndContainer(), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_AIRPORT), SetFill(0, 1), SetMinimalSize(42, 22), + SetDataTip(SPR_IMG_AIRPORT, STR_TOOLBAR_AIRPORT_BUILD_PRE_AIRPORT_TOOLTIP), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(4, 22), SetFill(1, 1), EndContainer(), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_DEMOLISH), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC), EndContainer(), }; -static WindowDesc _air_toolbar_desc( +static WindowDesc _air_tile_toolbar_desc( WDP_ALIGN_TOOLBAR, "toolbar_air", 0, 0, WC_BUILD_TOOLBAR, WC_NONE, WDF_CONSTRUCTION, - _nested_air_toolbar_widgets, + _nested_air_tile_toolbar_widgets, + &BuildAirToolbarWindow::hotkeys +); + +static WindowDesc _air_nontile_toolbar_desc( + WDP_ALIGN_TOOLBAR, "toolbar_air_nontile", 0, 0, + WC_BUILD_TOOLBAR, WC_NONE, + WDF_CONSTRUCTION, + _nested_air_nontile_toolbar_widgets, &BuildAirToolbarWindow::hotkeys ); + /** - * Open the build airport toolbar window - * + * Open the build airport toolbar window. * If the terraform toolbar is linked to the toolbar, that window is also opened. - * + * @param airtype air type for constructing (a valid air type or + * INVALID_AIRTYPE if the build-airports-by-tile is dissabled). * @return newly opened airport toolbar, or nullptr if the toolbar could not be opened. */ -Window *ShowBuildAirToolbar() +Window *ShowBuildAirToolbar(AirType airtype) { if (!Company::IsValidID(_local_company)) return nullptr; + if (airtype != INVALID_AIRTYPE && !ValParamAirType(airtype)) return nullptr; CloseWindowByClass(WC_BUILD_TOOLBAR); - return AllocateWindowDescFront(_air_toolbar_desc, TRANSPORT_AIR); + assert((airtype == INVALID_AIRTYPE) != (_settings_game.station.allow_modify_airports)); + + _cur_airtype = airtype; + _remove_button_clicked = false; + + if (airtype == INVALID_AIRTYPE) { + return new BuildAirToolbarWindow(false, _air_nontile_toolbar_desc, airtype); + } else { + return new BuildAirToolbarWindow(true, _air_tile_toolbar_desc, airtype); + } } class BuildAirportWindow : public PickerWindowBase { @@ -242,7 +710,14 @@ class BuildAirportWindow : public PickerWindowBase { DropDownList list; for (const auto &cls : AirportClass::Classes()) { - list.push_back(MakeDropDownListStringItem(cls.name, cls.Index())); + if (cls.Index() == APC_CUSTOM) continue; + bool unavailable = true; + for (const auto &as : cls.Specs()) { + if (!as->IsAvailable(_cur_airtype)) continue; + unavailable = false; + break; + } + list.push_back(MakeDropDownListStringItem(cls.name, cls.Index(), unavailable)); } return list; @@ -275,9 +750,10 @@ class BuildAirportWindow : public PickerWindowBase { bool selectFirstAirport = true; if (_selected_airport_index != -1) { const AirportSpec *as = ac->GetSpec(_selected_airport_index); - if (as->IsAvailable()) { + if (as->IsAvailable(_cur_airtype)) { /* Ensure the airport layout is valid. */ - _selected_airport_layout = Clamp(_selected_airport_layout, 0, static_cast(as->layouts.size() - 1)); + _selected_airport_layout = Clamp(_selected_airport_layout, 0, (uint8_t)as->layouts.size() - 1); + _selected_rotation = (DiagDirection)Clamp(_selected_rotation, 0, 3); selectFirstAirport = false; this->UpdateSelectSize(); } @@ -313,6 +789,13 @@ class BuildAirportWindow : public PickerWindowBase { } break; + case WID_AP_ROTATION: + SetDParam(0, STR_EMPTY); + if (_selected_airport_index != -1) { + SetDParam(0, STR_AIRPORT_ROTATION_0 + _selected_rotation); + } + break; + default: break; } } @@ -388,7 +871,7 @@ class BuildAirportWindow : public PickerWindowBase { auto [first, last] = this->vscroll->GetVisibleRangeIterators(specs); for (auto it = first; it != last; ++it) { const AirportSpec *as = *it; - if (!as->IsAvailable()) { + if (!as->IsAvailable(_cur_airtype)) { GfxFillRect(row, PC_BLACK, FILLRECT_CHECKER); } DrawString(text, as->name, (static_cast(as->index) == _selected_airport_index) ? TC_WHITE : TC_BLACK); @@ -410,7 +893,12 @@ class BuildAirportWindow : public PickerWindowBase { const AirportSpec *as = AirportClass::Get(_selected_airport_class)->GetSpec(_selected_airport_index); StringID string = GetAirportTextCallback(as, _selected_airport_layout, CBID_AIRPORT_ADDITIONAL_TEXT); if (string != STR_UNDEFINED) { - DrawStringMultiLine(r.left, r.right, r.top, r.bottom, string, TC_BLACK); + + SetDParam(0, string); + DrawStringMultiLine(r.left, r.right, r.top, r.bottom, TC_BLACK); + } else if (as->layouts.size() > 1) { + SetDParam(0, STR_STATION_BUILD_AIRPORT_LAYOUT_NAME); + SetDParam(1, _selected_airport_layout + 1); } } break; @@ -426,23 +914,18 @@ class BuildAirportWindow : public PickerWindowBase { if (_selected_airport_index != -1) { const AirportSpec *as = AirportClass::Get(_selected_airport_class)->GetSpec(_selected_airport_index); - int rad = _settings_game.station.modified_catchment ? as->catchment : (uint)CA_UNMODIFIED; + AirType airtype = _settings_game.station.allow_modify_airports ? _cur_airtype : as->airtype; + const AirTypeInfo *ati = GetAirTypeInfo(airtype); + int rad = _settings_game.station.modified_catchment ? ati->catchment_radius : (uint)CA_UNMODIFIED; /* only show the station (airport) noise, if the noise option is activated */ if (_settings_game.economy.station_noise_level) { /* show the noise of the selected airport */ - SetDParam(0, as->noise_level); + SetDParam(0, as->GetAirportNoise(airtype)); DrawString(r.left, r.right, top, STR_STATION_BUILD_NOISE); top += GetCharacterHeight(FS_NORMAL) + WidgetDimensions::scaled.vsep_normal; } - if (_settings_game.economy.infrastructure_maintenance) { - Money monthly = _price[PR_INFRASTRUCTURE_AIRPORT] * as->maintenance_cost >> 3; - SetDParam(0, monthly * 12); - DrawString(r.left, r.right, top, TimerGameEconomy::UsingWallclockUnits() ? STR_STATION_BUILD_INFRASTRUCTURE_COST_PERIOD : STR_STATION_BUILD_INFRASTRUCTURE_COST_YEAR); - top += GetCharacterHeight(FS_NORMAL) + WidgetDimensions::scaled.vsep_normal; - } - /* strings such as 'Size' and 'Coverage Area' */ top = DrawStationCoverageAreaText(r.left, r.right, top, SCT_ALL, rad, false) + WidgetDimensions::scaled.vsep_normal; top = DrawStationCoverageAreaText(r.left, r.right, top, SCT_ALL, rad, true); @@ -471,20 +954,22 @@ class BuildAirportWindow : public PickerWindowBase { SetTileSelectSize(1, 1); this->DisableWidget(WID_AP_LAYOUT_DECREASE); this->DisableWidget(WID_AP_LAYOUT_INCREASE); + this->DisableWidget(WID_AP_ROTATION_DECREASE); + this->DisableWidget(WID_AP_ROTATION_INCREASE); } else { const AirportSpec *as = AirportClass::Get(_selected_airport_class)->GetSpec(_selected_airport_index); - int w = as->size_x; - int h = as->size_y; - Direction rotation = as->layouts[_selected_airport_layout].rotation; - if (rotation == DIR_E || rotation == DIR_W) Swap(w, h); + int w = as->layouts[_selected_airport_layout].size_x; + int h = as->layouts[_selected_airport_layout].size_y; + if (_selected_rotation % 2 != 0) Swap(w, h); SetTileSelectSize(w, h); - this->preview_sprite = GetCustomAirportSprite(as, _selected_airport_layout); + this->preview_sprite = GetCustomAirportSprite(as, _selected_airport_layout) + _selected_rotation; this->SetWidgetDisabledState(WID_AP_LAYOUT_DECREASE, _selected_airport_layout == 0); this->SetWidgetDisabledState(WID_AP_LAYOUT_INCREASE, _selected_airport_layout + 1U >= as->layouts.size()); - int rad = _settings_game.station.modified_catchment ? as->catchment : (uint)CA_UNMODIFIED; + const AirTypeInfo *ati = GetAirTypeInfo(as->airtype); + int rad = _settings_game.station.modified_catchment ? ati->catchment_radius : (uint)CA_UNMODIFIED; if (_settings_client.gui.station_show_coverage) SetTileSelectBigSize(-rad, -rad, 2 * rad, 2 * rad); } } @@ -500,7 +985,7 @@ class BuildAirportWindow : public PickerWindowBase { int32_t num_clicked = this->vscroll->GetScrolledRowFromWidget(pt.y, this, widget, 0, this->line_height); if (num_clicked == INT32_MAX) break; const AirportSpec *as = AirportClass::Get(_selected_airport_class)->GetSpec(num_clicked); - if (as->IsAvailable()) this->SelectOtherAirport(num_clicked); + if (as->IsAvailable(_cur_airtype)) this->SelectOtherAirport(num_clicked); break; } @@ -525,6 +1010,18 @@ class BuildAirportWindow : public PickerWindowBase { this->UpdateSelectSize(); this->SetDirty(); break; + + case WID_AP_ROTATION_DECREASE: + _selected_rotation = (DiagDirection)((_selected_rotation + 3) % 4); + this->UpdateSelectSize(); + this->SetDirty(); + break; + + case WID_AP_ROTATION_INCREASE: + _selected_rotation = (DiagDirection)((_selected_rotation + 1) % 4); + this->UpdateSelectSize(); + this->SetDirty(); + break; } } @@ -538,7 +1035,7 @@ class BuildAirportWindow : public PickerWindowBase { /* First try to select an airport in the selected class. */ AirportClass *sel_apclass = AirportClass::Get(_selected_airport_class); for (const AirportSpec *as : sel_apclass->Specs()) { - if (as->IsAvailable()) { + if (as->IsAvailable(_cur_airtype)) { this->SelectOtherAirport(as->index); return; } @@ -548,7 +1045,7 @@ class BuildAirportWindow : public PickerWindowBase { * from the first class where airports are available. */ for (const auto &cls : AirportClass::Classes()) { for (const auto &as : cls.Specs()) { - if (as->IsAvailable()) { + if (as->IsAvailable(_cur_airtype)) { _selected_airport_class = cls.Index(); this->vscroll->SetCount(cls.GetSpecCount()); this->SelectOtherAirport(as->GetIndex()); @@ -595,12 +1092,16 @@ static constexpr NWidgetPart _nested_build_airport_widgets[] = { NWidget(WWT_MATRIX, COLOUR_GREY, WID_AP_AIRPORT_LIST), SetFill(1, 0), SetMatrixDataTip(1, 5, STR_STATION_BUILD_AIRPORT_TOOLTIP), SetScrollbar(WID_AP_SCROLLBAR), NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_AP_SCROLLBAR), EndContainer(), - NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetDataTip(STR_STATION_BUILD_ORIENTATION, STR_NULL), SetFill(1, 0), NWidget(NWID_HORIZONTAL), NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_AP_LAYOUT_DECREASE), SetMinimalSize(12, 0), SetDataTip(AWV_DECREASE, STR_NULL), NWidget(WWT_LABEL, COLOUR_GREY, WID_AP_LAYOUT_NUM), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING1, STR_NULL), NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_AP_LAYOUT_INCREASE), SetMinimalSize(12, 0), SetDataTip(AWV_INCREASE, STR_NULL), EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_AP_ROTATION_DECREASE), SetMinimalSize(12, 0), SetDataTip(AWV_DECREASE, STR_NULL), + NWidget(WWT_LABEL, COLOUR_GREY, WID_AP_ROTATION), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_NULL), + NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_AP_ROTATION_INCREASE), SetMinimalSize(12, 0), SetDataTip(AWV_INCREASE, STR_NULL), + EndContainer(), NWidget(WWT_EMPTY, COLOUR_DARK_GREEN, WID_AP_EXTRA_TEXT), SetFill(1, 0), SetMinimalSize(150, 0), NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL), SetFill(1, 0), NWidget(NWID_HORIZONTAL), SetPIP(14, 0, 14), SetPIPRatio(1, 0, 1), @@ -629,8 +1130,652 @@ static void ShowBuildAirportPicker(Window *parent) new BuildAirportWindow(_build_airport_desc, parent); } +struct BuildHangarWindow : public PickerWindowBase { + BuildHangarWindow(WindowDesc &desc, Window *parent) : PickerWindowBase(desc, parent) + { + this->CreateNestedTree(); + this->LowerWidget(WID_BHW_NE + _rotation_dir); + this->FinishInitNested(TRANSPORT_AIR); + } + + uint GetHangarSpriteHeight() const { return 48; } + + void UpdateWidgetSize(int widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override + { + if (!IsInsideMM(widget, WID_BHW_NE, WID_BHW_NW + 1)) return; + + size.width = ScaleGUITrad(64) + 2; + size.height = ScaleGUITrad(52) + 2; + } + + void DrawWidget(const Rect &r, int widget) const override + { + if (!IsInsideMM(widget, WID_BHW_NE, WID_BHW_NW + 1)) return; + + int x = r.left + 1 + ScaleGUITrad(TILE_PIXELS - 1); + /* Height of depot sprite in OpenGFX is TILE_PIXELS + GetHangarSpriteHeight(). */ + int y = r.bottom - ScaleGUITrad(TILE_PIXELS - 1); + + SpriteID ground = GetAirTypeInfo(_cur_airtype)->base_sprites.ground[0]; + DiagDirection dir = (DiagDirection)(widget - WID_BHW_NE + DIAGDIR_NE); + PaletteID palette = COMPANY_SPRITE_COLOUR(_local_company); + extern const DrawTileSprites _airport_hangars[4]; + const DrawTileSprites *dts = &_airport_hangars[dir]; + DrawSprite(ground, PAL_NONE, x, y); + DrawRailTileSeqInGUI(x, y, dts, ground, 0, palette); + } + + void OnClick([[maybe_unused]] Point pt, int widget, [[maybe_unused]] int click_count) override + { + if (!IsInsideMM(widget, WID_BHW_NE, WID_BHW_NW + 1)) return; + + this->RaiseWidget(WID_BHW_NE + _rotation_dir); + _rotation_dir = (DiagDirection)(widget - WID_BHW_NE); + this->LowerWidget(WID_BHW_NE + _rotation_dir); + if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP); + this->SetDirty(); + } +}; + +static const NWidgetPart _nested_build_hangar_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN), + NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_BHW_CAPTION), SetDataTip(STR_BUILD_HANGAR_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + EndContainer(), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), + NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1), SetPadding(WidgetDimensions::unscaled.picker), + NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BHW_NW), SetDataTip(0x0, STR_BUILD_HANGAR_ORIENTATION_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BHW_SW), SetDataTip(0x0, STR_BUILD_HANGAR_ORIENTATION_TOOLTIP), + EndContainer(), + NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BHW_NE), SetDataTip(0x0, STR_BUILD_HANGAR_ORIENTATION_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BHW_SE), SetDataTip(0x0, STR_BUILD_HANGAR_ORIENTATION_TOOLTIP), + EndContainer(), + EndContainer(), + EndContainer(), +}; + +static WindowDesc _build_hangar_desc( + WDP_AUTO, nullptr, 0, 0, + WC_BUILD_DEPOT, WC_BUILD_TOOLBAR, + WDF_CONSTRUCTION, + _nested_build_hangar_widgets +); + +static void ShowHangarPicker(Window *parent) +{ + new BuildHangarWindow(_build_hangar_desc, parent); +} + +struct SelectTrackGfxWindow : public PickerWindowBase { + SelectTrackGfxWindow(WindowDesc &desc, Window *parent) : PickerWindowBase(desc, parent) + { + this->CreateNestedTree(); + this->LowerWidget(_selected_track_gfx_index); + this->FinishInitNested(TRANSPORT_AIR); + } + + void UpdateWidgetSize(int widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override + { + if (!IsInsideMM(widget, WID_BASGFAT_AUTO, WID_BASGFAT_20 + 1)) return; + + size.width = ScaleGUITrad(64) + 2; + size.height = ScaleGUITrad(32) + 2; + } + + void DrawWidget(const Rect &r, int widget) const override + { + if (!IsInsideMM(widget, WID_BASGFAT_01, WID_BASGFAT_20 + 1)) return; + + int x = r.left + 1 + ScaleGUITrad(TILE_PIXELS - 1); + /* Height of depot sprite in OpenGFX is TILE_PIXELS + GetHangarSpriteHeight(). */ + int y = r.bottom - ScaleGUITrad(TILE_PIXELS - 1); + + SpriteID ground = GetAirTypeInfo(_cur_airtype)->base_sprites.ground[widget - WID_BASGFAT_01]; + DrawSprite(ground, PAL_NONE, x, y); + } + + void OnClick([[maybe_unused]] Point pt, int widget, [[maybe_unused]] int click_count) override + { + if (!IsInsideMM(widget, WID_BASGFAT_AUTO, WID_BASGFAT_20 + 1)) return; + + this->RaiseWidget(WID_BASGFAT_AUTO + _selected_track_gfx_index); + _selected_track_gfx_index = widget - WID_BASGFAT_AUTO; + this->LowerWidget(WID_BASGFAT_AUTO + _selected_track_gfx_index); + if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP); + this->SetDirty(); + } +}; + +static const NWidgetPart _nested_select_track_gfx_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN), + NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_BASGFAT_CAPTION), SetDataTip(STR_SELECT_GFX_AIRPORT_TRACKS_CAPTION, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP), + EndContainer(), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), + NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0), SetPIPRatio(1, 0, 1), SetPadding(WidgetDimensions::unscaled.picker), + NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_DEFAULT), SetFill(1, 0), + SetDataTip(STR_SELECT_GFX_AIRPORT_TRACKS_DEFAULT, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_AUTO), SetFill(1, 0), + SetDataTip(STR_SELECT_GFX_AIRPORT_TRACKS_DETECT, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP), + EndContainer(), + NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_01), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_06), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_07), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_08), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP), + EndContainer(), + NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_13), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_14), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_15), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_16), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP), + EndContainer(), + NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_09), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_10), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_11), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_12), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP), + EndContainer(), + NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_02), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_03), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_04), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_05), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP), + EndContainer(), + NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_17), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_18), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_19), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_20), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP), + EndContainer(), + EndContainer(), + EndContainer(), +}; + +static WindowDesc _select_track_gfx_widgets( + WDP_AUTO, nullptr, 0, 0, + WC_SELECT_TRACK_GFX, WC_BUILD_TOOLBAR, + WDF_CONSTRUCTION, + _nested_select_track_gfx_widgets +); + +static void ShowTrackGfxPicker(Window *parent) +{ + new SelectTrackGfxWindow(_select_track_gfx_widgets, parent); +} + +struct BuildHeliportWindow : public PickerWindowBase { + BuildHeliportWindow(WindowDesc &desc, Window *parent) : PickerWindowBase(desc, parent) + { + this->CreateNestedTree(); + this->LowerWidget(WID_BHW_NE + _rotation_dir); + this->FinishInitNested(TRANSPORT_AIR); + } + + uint GetHeliportSpriteHeight() const { return 91; } + + void UpdateWidgetSize(int widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override + { + if (!IsInsideMM(widget, WID_BHW_NE, WID_BHW_NW + 1)) return; + + size.width = ScaleGUITrad(64) + 2; + size.height = ScaleGUITrad(GetHeliportSpriteHeight()) + 2; + } + + void DrawWidget(const Rect &r, int widget) const override + { + if (!IsInsideMM(widget, WID_BHW_NE, WID_BHW_NW + 1)) return; + + int x = r.left + 1 + ScaleGUITrad(TILE_PIXELS - 1); + /* Height of depot sprite in OpenGFX is TILE_PIXELS + GetHangarSpriteHeight(). */ + int y = r.bottom - ScaleGUITrad(TILE_PIXELS - 1); + + SpriteID ground = GetAirTypeInfo(_cur_airtype)->base_sprites.ground[0]; + DiagDirection dir = (DiagDirection)(widget - WID_BHW_NE + DIAGDIR_NE); + PaletteID palette = COMPANY_SPRITE_COLOUR(_local_company); + extern const DrawTileSprites _airport_heliports[]; + const DrawTileSprites *dts = &_airport_heliports[0]; + DrawSprite(ground, PAL_NONE, x, y); + DrawRailTileSeqInGUI(x, y, dts, ground + dir, 0, palette); + } + + void OnClick([[maybe_unused]] Point pt, int widget, [[maybe_unused]] int click_count) override + { + switch (widget) { + case WID_BHW_NW: + case WID_BHW_NE: + case WID_BHW_SW: + case WID_BHW_SE: + this->RaiseWidget(WID_BHW_NE + _rotation_dir); + _rotation_dir = (DiagDirection)(widget - WID_BHW_NE); + this->LowerWidget(WID_BHW_NE + _rotation_dir); + if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP); + this->SetDirty(); + break; + + default: + break; + } + } +}; + +static const NWidgetPart _nested_build_heliport_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN), + NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_BHW_CAPTION), SetDataTip(STR_BUILD_HELIPORT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + EndContainer(), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), + NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1), SetPadding(WidgetDimensions::unscaled.picker), + NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BHW_NW), SetDataTip(0x0, STR_BUILD_HELIPORT_ORIENTATION_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BHW_SW), SetDataTip(0x0, STR_BUILD_HELIPORT_ORIENTATION_TOOLTIP), + EndContainer(), + NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BHW_NE), SetDataTip(0x0, STR_BUILD_HELIPORT_ORIENTATION_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BHW_SE), SetDataTip(0x0, STR_BUILD_HELIPORT_ORIENTATION_TOOLTIP), + EndContainer(), + EndContainer(), + EndContainer(), +}; + +static WindowDesc _build_heliport_desc( + WDP_AUTO, nullptr, 0, 0, + WC_BUILD_HELIPORT, WC_BUILD_TOOLBAR, + WDF_CONSTRUCTION, + _nested_build_heliport_widgets +); + +static void ShowHeliportPicker(Window *parent) +{ + new BuildHeliportWindow(_build_heliport_desc, parent); +} + +struct BuildAirportInfraNoCatchWindow : public PickerWindowBase { + BuildAirportInfraNoCatchWindow(WindowDesc &desc, Window *parent) : PickerWindowBase(desc, parent) + { + this->CreateNestedTree(); + this->LowerWidget(WID_BAINC_FLAG + (BuildAirportInfrastructureNoCatchmentWidgets)_selected_infra_nocatch); + this->FinishInitNested(TRANSPORT_AIR); + } + + uint GetHeliportSpriteHeight() const { return 97; } + + void UpdateWidgetSize(int widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override + { + if (!IsInsideMM(widget, WID_BAINC_FLAG, WID_BAINC_EMPTY + 1)) return; + + size.width = ScaleGUITrad(64) + 2; + size.height = ScaleGUITrad(GetHeliportSpriteHeight()) + 2; + } + + void DrawWidget(const Rect &r, int widget) const override + { + if (!IsInsideMM(widget, WID_BAINC_FLAG, WID_BAINC_EMPTY + 1)) return; + + int x = r.left + 1 + ScaleGUITrad(TILE_PIXELS - 1); + int y = r.bottom - ScaleGUITrad(TILE_PIXELS - 1); + + SpriteID ground = GetAirTypeInfo(_cur_airtype)->base_sprites.ground[0]; + PaletteID palette = COMPANY_SPRITE_COLOUR(_local_company); + extern const DrawTileSprites _airtype_display_datas[]; + extern const DrawTileSprites _airtype_display_datas_radar[]; + extern const DrawTileSprites _airtype_display_datas_tower[]; + extern const DrawTileSprites _airtype_display_datas_transmitter[]; + const DrawTileSprites *dts = nullptr; + DrawSprite(ground, PAL_NONE, x, y); + + switch (widget) { + case WID_BAINC_FLAG: { + extern const DrawTileSprites _airtype_display_datas_flag_NE[]; + extern const DrawTileSprites _airtype_display_datas_flag_SE[]; + extern const DrawTileSprites _airtype_display_datas_flag_SW[]; + extern const DrawTileSprites _airtype_display_datas_flag_NW[]; + + const DrawTileSprites *flags[4] = { + _airtype_display_datas_flag_NE, + _airtype_display_datas_flag_SE, + _airtype_display_datas_flag_SW, + _airtype_display_datas_flag_NW, + }; + + ground = 0; + dts = flags[_selected_infra_nocatch_rotation]; + break; + } + case WID_BAINC_RADAR: + ground = 0; + dts = &_airtype_display_datas_radar[2]; + break; + case WID_BAINC_TOWER: + dts = &_airtype_display_datas_tower[_selected_infra_nocatch_rotation]; + break; + case WID_BAINC_TRANSMITTER: + dts = &_airtype_display_datas_transmitter[_selected_infra_nocatch_rotation]; + break; + + case WID_BAINC_EMPTY: + case WID_BAINC_PIER: + dts = &_airtype_display_datas[(widget - WID_BAINC_FLAG + ATTG_NO_CATCH_FLAG) * 4 + _selected_infra_nocatch_rotation]; + break; + default: + NOT_REACHED(); + } + if (dts != nullptr) DrawRailTileSeqInGUI(x, y, dts, ground, 0, palette); + } + + void SetStringParameters(int widget) const override + { + if (widget != WID_BAINC_ROTATION) return; + + SetDParam(0, STR_EMPTY); + SetDParam(0, STR_AIRPORT_ROTATION_0 + _selected_infra_nocatch_rotation); + } + + void OnClick([[maybe_unused]] Point pt, int widget, [[maybe_unused]] int click_count) override + { + switch (widget) { + case WID_BAINC_FLAG: + case WID_BAINC_TRANSMITTER: + case WID_BAINC_TOWER: + case WID_BAINC_RADAR: + case WID_BAINC_PIER: + case WID_BAINC_EMPTY: + this->RaiseWidget(WID_BAINC_FLAG + (BuildAirportInfrastructureNoCatchmentWidgets)_selected_infra_nocatch); + _selected_infra_nocatch = (AirportTiles)(widget - WID_BAINC_FLAG); + this->LowerWidget(WID_BAINC_FLAG + (BuildAirportInfrastructureNoCatchmentWidgets)_selected_infra_nocatch); + if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP); + this->SetDirty(); + break; + + case WID_BAINC_ROTATION_DECREASE: + _selected_infra_nocatch_rotation = ((int)_selected_infra_nocatch_rotation + 3) % 4; + this->SetDirty(); + break; + + case WID_BAINC_ROTATION_INCREASE: + _selected_infra_nocatch_rotation = (_selected_infra_nocatch_rotation + 1) % 4; + this->SetDirty(); + break; + + default: + break; + } + } +}; + +static const NWidgetPart _nested_build_airport_infra_no_catch_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN), + NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_BAINC_CAPTION), SetDataTip(STR_BUILD_AIRPORT_INFRA_NO_CATCH_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + EndContainer(), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), + /* Graphics */ + NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1), SetPadding(WidgetDimensions::unscaled.picker), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BAINC_FLAG), SetDataTip(0x0, STR_BUILD_AIRPORT_INFRA_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BAINC_TRANSMITTER), SetDataTip(0x0, STR_BUILD_AIRPORT_INFRA_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BAINC_TOWER), SetDataTip(0x0, STR_BUILD_AIRPORT_INFRA_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BAINC_RADAR), SetDataTip(0x0, STR_BUILD_AIRPORT_INFRA_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BAINC_PIER), SetDataTip(0x0, STR_BUILD_AIRPORT_INFRA_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BAINC_EMPTY), SetDataTip(0x0, STR_BUILD_AIRPORT_INFRA_TOOLTIP), + EndContainer(), + /* Rotation */ + NWidget(NWID_HORIZONTAL), + NWidget(NWID_SPACER), SetMinimalSize(3, 0), SetFill(1, 0), + NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_BAINC_ROTATION_DECREASE), SetMinimalSize(12, 0), SetDataTip(AWV_DECREASE, STR_NULL), + NWidget(WWT_LABEL, COLOUR_GREY, WID_BAINC_ROTATION), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_NULL), + NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_BAINC_ROTATION_INCREASE), SetMinimalSize(12, 0), SetDataTip(AWV_INCREASE, STR_NULL), + NWidget(NWID_SPACER), SetMinimalSize(3, 0), SetFill(1, 0), + EndContainer(), + NWidget(NWID_SPACER), SetMinimalSize(0, 3), SetFill(1, 0), + EndContainer(), +}; + +static WindowDesc _build_airport_infra_no_catch_desc( + WDP_ALIGN_TOOLBAR, nullptr, 0, 0, + WC_BUILD_AIRPORT_INFRASTRUCTURE, WC_BUILD_TOOLBAR, + WDF_CONSTRUCTION, + _nested_build_airport_infra_no_catch_widgets +); + +static void ShowAirportInfraNoCatchPicker(Window *parent) +{ + new BuildAirportInfraNoCatchWindow(_build_airport_infra_no_catch_desc, parent); +} + +struct BuildAirportInfraWithCatchWindow : public PickerWindowBase { + BuildAirportInfraWithCatchWindow(WindowDesc &desc, Window *parent) : PickerWindowBase(desc, parent) + { + this->CreateNestedTree(); + this->LowerWidget(WID_BAIWC_BUILDING_1 + (BuildAirportInfrastructureNoCatchmentWidgets)_selected_infra_catch); + this->FinishInitNested(TRANSPORT_AIR); + } + + uint GetHeliportSpriteHeight() const { return 91; } + + void UpdateWidgetSize(int widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override + { + if (!IsInsideMM(widget, WID_BAIWC_BUILDING_1, WID_BAIWC_BUILDING_TERMINAL + 1)) return; + + size.width = ScaleGUITrad(64) + 2; + size.height = ScaleGUITrad(GetHeliportSpriteHeight()) + 2; + } + + void DrawWidget(const Rect &r, int widget) const override + { + if (!IsInsideMM(widget, WID_BAIWC_BUILDING_1, WID_BAIWC_BUILDING_TERMINAL + 1)) return; + + int x = r.left + 1 + ScaleGUITrad(TILE_PIXELS - 1); + int y = r.bottom - ScaleGUITrad(TILE_PIXELS - 1); + + SpriteID ground = GetAirTypeInfo(_cur_airtype)->base_sprites.ground[0]; + PaletteID palette = COMPANY_SPRITE_COLOUR(_local_company); + extern const DrawTileSprites _airtype_display_datas[]; + const DrawTileSprites *dts = &_airtype_display_datas[ + (widget - WID_BAIWC_BUILDING_1 + ATTG_WITH_CATCH_BUILDING_1) * 4 + _selected_infra_catch_rotation]; + DrawSprite(ground, PAL_NONE, x, y); + DrawRailTileSeqInGUI(x, y, dts, ground, 0, palette); + } + + void SetStringParameters(int widget) const override + { + if (widget != WID_BAIWC_ROTATION) return; + + SetDParam(0, STR_EMPTY); + SetDParam(0, STR_AIRPORT_ROTATION_0 + _selected_infra_catch_rotation); + } + + void OnClick([[maybe_unused]] Point pt, int widget, [[maybe_unused]] int click_count) override + { + switch (widget) { + case WID_BAIWC_BUILDING_1: + case WID_BAIWC_BUILDING_2: + case WID_BAIWC_BUILDING_3: + case WID_BAIWC_BUILDING_FLAT: + case WID_BAIWC_BUILDING_TERMINAL: + this->RaiseWidget(WID_BAIWC_BUILDING_1 + (BuildAirportInfrastructureNoCatchmentWidgets)_selected_infra_catch); + _selected_infra_catch = (AirportTiles)(widget - WID_BAIWC_BUILDING_1); + this->LowerWidget(WID_BAIWC_BUILDING_1 + (BuildAirportInfrastructureNoCatchmentWidgets)_selected_infra_catch); + if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP); + this->SetDirty(); + break; + + case WID_BAIWC_ROTATION_DECREASE: + _selected_infra_catch_rotation = (_selected_infra_catch_rotation + 3) % 4; + this->SetDirty(); + break; + + case WID_BAIWC_ROTATION_INCREASE: + _selected_infra_catch_rotation = (_selected_infra_catch_rotation + 1) % 4; + this->SetDirty(); + break; + + default: + break; + } + } +}; + +static const NWidgetPart _nested_build_airport_infra_with_catch_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN), + NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_BAIWC_CAPTION), SetDataTip(STR_BUILD_AIRPORT_INFRA_WITH_CATCH_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + EndContainer(), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), + /* Graphics */ + NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1), SetPadding(WidgetDimensions::unscaled.picker), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BAIWC_BUILDING_1), SetDataTip(0x0, STR_BUILD_AIRPORT_INFRA_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BAIWC_BUILDING_2), SetDataTip(0x0, STR_BUILD_AIRPORT_INFRA_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BAIWC_BUILDING_3), SetDataTip(0x0, STR_BUILD_AIRPORT_INFRA_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BAIWC_BUILDING_FLAT), SetDataTip(0x0, STR_BUILD_AIRPORT_INFRA_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BAIWC_BUILDING_TERMINAL), SetDataTip(0x0, STR_BUILD_AIRPORT_INFRA_TOOLTIP), + EndContainer(), + /* Rotation */ + NWidget(NWID_HORIZONTAL), + NWidget(NWID_SPACER), SetMinimalSize(3, 0), SetFill(1, 0), + NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_BAIWC_ROTATION_DECREASE), SetMinimalSize(12, 0), SetDataTip(AWV_DECREASE, STR_NULL), + NWidget(WWT_LABEL, COLOUR_GREY, WID_BAIWC_ROTATION), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_NULL), + NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_BAIWC_ROTATION_INCREASE), SetMinimalSize(12, 0), SetDataTip(AWV_INCREASE, STR_NULL), + NWidget(NWID_SPACER), SetMinimalSize(3, 0), SetFill(1, 0), + EndContainer(), + NWidget(NWID_SPACER), SetMinimalSize(0, 3), SetFill(1, 0), + EndContainer(), +}; + +static WindowDesc _build_airport_infra_with_catch_desc( + WDP_ALIGN_TOOLBAR, nullptr, 0, 0, + WC_BUILD_AIRPORT_INFRASTRUCTURE, WC_BUILD_TOOLBAR, + WDF_CONSTRUCTION, + _nested_build_airport_infra_with_catch_widgets +); + +static void ShowAirportInfraWithCatchPicker(Window *parent) +{ + new BuildAirportInfraWithCatchWindow(_build_airport_infra_with_catch_desc, parent); +} + + +/** Set the initial (default) airtype to use */ +static void SetDefaultAirGui() +{ + if (_local_company == COMPANY_SPECTATOR || !Company::IsValidID(_local_company)) return; + + extern AirType _last_built_airtype; + AirType rt; + switch (_settings_client.gui.default_air_type) { + case 2: { + /* Find the most used air type */ + uint count[AIRTYPE_END]; + memset(count, 0, sizeof(count)); + for (TileIndex t = 0; t < Map::Size(); t++) { + if (IsAirportTile(t) && GetTileOwner(t) == _local_company) { + count[GetAirType(t)]++; + } + } + + rt = static_cast(std::max_element(count + AIRTYPE_BEGIN, count + AIRTYPE_END) - count); + if (count[rt] > 0) break; + + /* No air, just get the first available one */ + [[fallthrough]]; + } + case 0: { + /* Use first available type */ + std::vector::const_iterator it = std::find_if(_sorted_airtypes.begin(), _sorted_airtypes.end(), + [](AirType r){ return HasAirTypeAvail(_local_company, r); }); + rt = it != _sorted_airtypes.end() ? *it : AIRTYPE_BEGIN; + break; + } + case 1: { + /* Use last available type */ + std::vector::const_reverse_iterator it = std::find_if(_sorted_airtypes.rbegin(), _sorted_airtypes.rend(), + [](AirType r){ return HasAirTypeAvail(_local_company, r); }); + rt = it != _sorted_airtypes.rend() ? *it : AIRTYPE_BEGIN; + break; + } + default: + NOT_REACHED(); + } + + _last_built_airtype = _cur_airtype = rt; + BuildAirToolbarWindow *w = dynamic_cast(FindWindowById(WC_BUILD_TOOLBAR, TRANSPORT_AIR)); + if (w != nullptr) w->ModifyAirType(_cur_airtype); +} + +/** + * Create a drop down list for all the air types of the local company. + * @param for_replacement Whether this list is for the replacement window. + * @param all_option Whether to add an 'all types' item. + * @return The populated and sorted #DropDownList. + */ +DropDownList GetAirTypeDropDownList(bool for_replacement, bool all_option) +{ + AirTypes used_airtypes; + AirTypes avail_airtypes; + + const Company *c = Company::Get(_local_company); + + /* Find the used airtypes. */ + if (for_replacement) { + avail_airtypes = GetCompanyAirTypes(c->index, false); + used_airtypes = GetAirTypes(false); + } else { + avail_airtypes = c->avail_airtypes; + used_airtypes = GetAirTypes(true); + } + + DropDownList list; + + if (all_option) { + list.push_back(MakeDropDownListStringItem(STR_REPLACE_ALL_AIRTYPE, INVALID_AIRTYPE, false)); + } + + Dimension d = { 0, 0 }; + /* Get largest icon size, to ensure text is aligned on each menu item. */ + if (!for_replacement) { + for (const auto &at : _sorted_airtypes) { + if (!HasBit(used_airtypes, at)) continue; + const AirTypeInfo *ati = GetAirTypeInfo(at); + d = maxdim(d, GetSpriteSize(ati->gui_sprites.build_helipad)); + } + } + + for (const auto &at : _sorted_airtypes) { + /* If it's not used ever, don't show it to the user. */ + if (!HasBit(used_airtypes, at)) continue; + + const AirTypeInfo *ati = GetAirTypeInfo(at); + SetDParam(0, ati->strings.menu_text); + SetDParam(1, ati->max_speed); + + if (for_replacement) { + list.push_back(MakeDropDownListStringItem(ati->strings.replace_text, at, !HasBit(avail_airtypes, at))); + } else { + StringID str = ati->max_speed > 0 ? STR_TOOLBAR_RAILTYPE_VELOCITY : STR_JUST_STRING; + auto iconitem = MakeDropDownListIconItem(d, ati->gui_sprites.build_helipad, PAL_NONE, str, at, !HasBit(avail_airtypes, at)); + list.push_back(std::move(iconitem)); + } + } + + if (list.empty()) { + /* Empty dropdowns are not allowed */ + list.push_back(MakeDropDownListStringItem(STR_NONE, INVALID_AIRTYPE, true)); + } + + return list; +} + void InitializeAirportGui() { + SetDefaultAirGui(); + _selected_airport_class = APC_BEGIN; _selected_airport_index = -1; + _selected_infra_catch_rotation = 0; + _selected_infra_catch = ATTG_DEFAULT_GFX; + _selected_infra_nocatch_rotation = 0; + _selected_infra_nocatch = ATTG_DEFAULT_GFX; } + diff --git a/src/airport_gui.h b/src/airport_gui.h new file mode 100644 index 0000000000000..cd64dcc2cd277 --- /dev/null +++ b/src/airport_gui.h @@ -0,0 +1,21 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file airport_gui.h Various declarations for airports */ + +#ifndef AIRPORT_GUI_H +#define AIRPORT_GUI_H + +#include "air_type.h" +#include "dropdown_type.h" + +struct Window *ShowBuildAirToolbar(AirType airtype); +DropDownList GetAirTypeDropDownList(bool for_replacement = false, bool all_option = false); + +void InitializeAirportGui(); + +#endif /* AIRPORT_GUI_H */ diff --git a/src/autoreplace_cmd.cpp b/src/autoreplace_cmd.cpp index 757f18d371da3..d94249e188084 100644 --- a/src/autoreplace_cmd.cpp +++ b/src/autoreplace_cmd.cpp @@ -20,6 +20,7 @@ #include "core/random_func.hpp" #include "vehiclelist.h" #include "road.h" +#include "ship.h" #include "ai/ai.hpp" #include "news_func.h" #include "strings_func.h" @@ -28,6 +29,9 @@ #include "order_cmd.h" #include "train_cmd.h" #include "vehicle_cmd.h" +#include "depot_map.h" +#include "train_placement.h" +#include "news_func.h" #include "table/strings.h" @@ -71,7 +75,10 @@ bool CheckAutoreplaceValidity(EngineID from, EngineID to, CompanyID company) switch (type) { case VEH_TRAIN: { /* make sure the railtypes are compatible */ - if ((GetRailTypeInfo(e_from->u.rail.railtype)->compatible_railtypes & GetRailTypeInfo(e_to->u.rail.railtype)->compatible_railtypes) == 0) return false; + if (!_settings_game.depot.allow_no_comp_railtype_replacements && + (GetRailTypeInfo(e_from->u.rail.railtype)->compatible_railtypes & GetRailTypeInfo(e_to->u.rail.railtype)->compatible_railtypes) == 0) { + return false; + } /* make sure we do not replace wagons with engines or vice versa */ if ((e_from->u.rail.railveh_type == RAILVEH_WAGON) != (e_to->u.rail.railveh_type == RAILVEH_WAGON)) return false; @@ -79,11 +86,15 @@ bool CheckAutoreplaceValidity(EngineID from, EngineID to, CompanyID company) } case VEH_ROAD: - /* make sure the roadtypes are compatible */ - if ((GetRoadTypeInfo(e_from->u.road.roadtype)->powered_roadtypes & GetRoadTypeInfo(e_to->u.road.roadtype)->powered_roadtypes) == ROADTYPES_NONE) return false; + if (!_settings_game.depot.allow_no_comp_roadtype_replacements) { + /* make sure the roadtypes are compatible */ + if ((GetRoadTypeInfo(e_from->u.road.roadtype)->powered_roadtypes & GetRoadTypeInfo(e_to->u.road.roadtype)->powered_roadtypes) == ROADTYPES_NONE) { + return false; + } - /* make sure that we do not replace a tram with a normal road vehicles or vice versa */ - if (HasBit(e_from->info.misc_flags, EF_ROAD_TRAM) != HasBit(e_to->info.misc_flags, EF_ROAD_TRAM)) return false; + /* make sure that we do not replace a tram with a normal road vehicles or vice versa */ + if (HasBit(e_from->info.misc_flags, EF_ROAD_TRAM) != HasBit(e_to->info.misc_flags, EF_ROAD_TRAM)) return false; + } break; case VEH_AIRCRAFT: @@ -364,13 +375,13 @@ static CommandCost BuildReplacementVehicle(Vehicle *old_veh, Vehicle **new_vehic if (refit_cargo != CARGO_NO_REFIT) { uint8_t subtype = GetBestFittingSubType(old_veh, new_veh, refit_cargo); - cost.AddCost(std::get<0>(Command::Do(DC_EXEC, new_veh->index, refit_cargo, subtype, false, false, 0))); + cost.AddCost(std::get<0>(Command::Do(DC_EXEC | DC_AUTOREPLACE, new_veh->index, refit_cargo, subtype, false, false, 0))); assert(cost.Succeeded()); // This should be ensured by GetNewCargoTypeForReplace() } /* Try to reverse the vehicle, but do not care if it fails as the new type might not be reversible */ if (new_veh->type == VEH_TRAIN && HasBit(Train::From(old_veh)->flags, VRF_REVERSE_DIRECTION)) { - Command::Do(DC_EXEC, new_veh->index, true); + Command::Do(DC_EXEC | DC_AUTOREPLACE, new_veh->index, true); } return cost; @@ -465,7 +476,7 @@ static CommandCost ReplaceFreeUnit(Vehicle **single_unit, DoCommandFlag flags, b if ((flags & DC_EXEC) != 0) { /* Move the new vehicle behind the old */ - CmdMoveVehicle(new_v, old_v, DC_EXEC, false); + CmdMoveVehicle(new_v, old_v, DC_EXEC | DC_AUTOREPLACE, false); /* Take over cargo * Note: We do only transfer cargo from the old to the new vehicle. @@ -485,7 +496,7 @@ static CommandCost ReplaceFreeUnit(Vehicle **single_unit, DoCommandFlag flags, b /* If we are not in DC_EXEC undo everything */ if ((flags & DC_EXEC) == 0) { - Command::Do(DC_EXEC, new_v->index, false, false, INVALID_CLIENT_ID); + Command::Do(DC_EXEC | DC_AUTOREPLACE, new_v->index, false, false, INVALID_CLIENT_ID); } } @@ -507,6 +518,25 @@ struct ReplaceChainItem { Vehicle *GetVehicle() const { return new_veh == nullptr ? old_veh : new_veh; } }; +/** + * When replacing a ship in an extended depot, copy the direction as well. + * @param old_ship The ship being replaced. + * @param new_ship The new ship that will replace the old one. + */ +void CopyShipStatusInExtendedDepot(const Ship *old_ship, Ship *new_ship) +{ + assert(IsExtendedDepotTile(old_ship->tile)); + assert(old_ship->tile == new_ship->tile); + + new_ship->x_pos = old_ship->x_pos; + new_ship->y_pos = old_ship->y_pos; + new_ship->z_pos = old_ship->z_pos; + new_ship->state = old_ship->state; + new_ship->direction = old_ship->direction; + new_ship->rotation = old_ship->rotation; + new_ship->GetImage(new_ship->direction, EIT_ON_MAP, &new_ship->sprite_cache.sprite_seq); +} + /** * Replace a whole vehicle chain * @param chain vehicle chain to let autoreplace/renew operator on @@ -517,8 +547,11 @@ struct ReplaceChainItem { */ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon_removal, bool *nothing_to_do) { + assert(flags & DC_AUTOREPLACE); + Vehicle *old_head = *chain; assert(old_head->IsPrimaryVehicle()); + TileIndex tile = old_head->tile; CommandCost cost = CommandCost(EXPENSES_NEW_VEHICLES, (Money)0); @@ -569,7 +602,7 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon } if (last_engine == nullptr) last_engine = append; - cost.AddCost(CmdMoveVehicle(append, new_head, DC_EXEC, false)); + cost.AddCost(CmdMoveVehicle(append, new_head, DC_EXEC | DC_AUTOREPLACE, false)); if (cost.Failed()) break; } if (last_engine == nullptr) last_engine = new_head; @@ -588,7 +621,7 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon if (RailVehInfo(append->engine_type)->railveh_type == RAILVEH_WAGON) { /* Insert wagon after 'last_engine' */ - CommandCost res = CmdMoveVehicle(append, last_engine, DC_EXEC, false); + CommandCost res = CmdMoveVehicle(append, last_engine, DC_EXEC | DC_AUTOREPLACE, false); /* When we allow removal of wagons, either the move failing due * to the train becoming too long, or the train becoming longer @@ -619,7 +652,7 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon assert(RailVehInfo(wagon->engine_type)->railveh_type == RAILVEH_WAGON); /* Sell wagon */ - [[maybe_unused]] CommandCost ret = Command::Do(DC_EXEC, wagon->index, false, false, INVALID_CLIENT_ID); + [[maybe_unused]] CommandCost ret = Command::Do(DC_EXEC | DC_AUTOREPLACE, wagon->index, false, false, INVALID_CLIENT_ID); assert(ret.Succeeded()); it->new_veh = nullptr; @@ -661,6 +694,9 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon if ((flags & DC_EXEC) != 0) CheckCargoCapacity(new_head); } + assert(IsValidTile(tile)); + if (!HasCompatibleDepotTile(tile, Train::From(new_head))) cost.MakeError(STR_ERROR_UNABLE_TO_FIND_APPROPRIATE_DEPOT_TILE); + /* If we are not in DC_EXEC undo everything, i.e. rearrange old vehicles. * We do this from back to front, so that the head of the temporary vehicle chain does not change all the time. * Note: The vehicle attach callback is disabled here :) */ @@ -682,7 +718,7 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon if ((flags & DC_EXEC) == 0) { for (auto it = std::rbegin(replacements); it != std::rend(replacements); ++it) { if (it->new_veh != nullptr) { - Command::Do(DC_EXEC, it->new_veh->index, false, false, INVALID_CLIENT_ID); + Command::Do(DC_EXEC | DC_AUTOREPLACE, it->new_veh->index, false, false, INVALID_CLIENT_ID); it->new_veh = nullptr; } } @@ -700,6 +736,11 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon cost.AddCost(CopyHeadSpecificThings(old_head, new_head, flags)); if (cost.Succeeded()) { + /* Copy position and direction for ships in extended depots. */ + if (old_head->type == VEH_SHIP && IsExtendedDepotTile(old_head->tile)) { + CopyShipStatusInExtendedDepot(Ship::From(old_head), Ship::From(new_head)); + } + /* The new vehicle is constructed, now take over cargo */ if ((flags & DC_EXEC) != 0) { TransferCargo(old_head, new_head, true); @@ -714,7 +755,7 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon /* If we are not in DC_EXEC undo everything */ if ((flags & DC_EXEC) == 0) { - Command::Do(DC_EXEC, new_head->index, false, false, INVALID_CLIENT_ID); + Command::Do(DC_EXEC | DC_AUTOREPLACE, new_head->index, false, false, INVALID_CLIENT_ID); } } } @@ -766,45 +807,78 @@ CommandCost CmdAutoreplaceVehicle(DoCommandFlag flags, VehicleID veh_id) any_replacements |= (e != INVALID_ENGINE); w = (!free_wagon && w->type == VEH_TRAIN ? Train::From(w)->GetNextUnit() : nullptr); } + if (!any_replacements) return_cmd_error(STR_ERROR_AUTOREPLACE_NOTHING_TO_DO); CommandCost cost = CommandCost(EXPENSES_NEW_VEHICLES, (Money)0); bool nothing_to_do = true; + bool was_stopped = free_wagon || ((v->vehstatus & VS_STOPPED) != 0); - if (any_replacements) { - bool was_stopped = free_wagon || ((v->vehstatus & VS_STOPPED) != 0); + /* Stop the vehicle */ + if (!was_stopped) cost.AddCost(DoCmdStartStopVehicle(v, true)); + if (cost.Failed()) return cost; - /* Stop the vehicle */ - if (!was_stopped) cost.AddCost(DoCmdStartStopVehicle(v, true)); - if (cost.Failed()) return cost; + assert(free_wagon || v->IsStoppedInDepot()); + if (flags & DC_EXEC) v->StopServicing(); - assert(free_wagon || v->IsStoppedInDepot()); + TrainPlacement train_placement; + if (v->type == VEH_TRAIN) { + train_placement.LiftTrain(Train::From(v), flags); + } else if (IsExtendedDepotTile(v->tile)) { + UpdateExtendedDepotReservation(v, false); + } - /* We have to construct the new vehicle chain to test whether it is valid. - * Vehicle construction needs random bits, so we have to save the random seeds - * to prevent desyncs and to replay newgrf callbacks during DC_EXEC */ - SavedRandomSeeds saved_seeds; - SaveRandomSeeds(&saved_seeds); + /* Start autoreplacing the vehicle. */ + flags |= DC_AUTOREPLACE; + + /* We have to construct the new vehicle chain to test whether it is valid. + * Vehicle construction needs random bits, so we have to save the random seeds + * to prevent desyncs and to replay newgrf callbacks during DC_EXEC */ + SavedRandomSeeds saved_seeds; + SaveRandomSeeds(&saved_seeds); + if (free_wagon) { + cost.AddCost(ReplaceFreeUnit(&v, flags & ~DC_EXEC, ¬hing_to_do)); + } else { + cost.AddCost(ReplaceChain(&v, flags & ~DC_EXEC, wagon_removal, ¬hing_to_do)); + } + RestoreRandomSeeds(saved_seeds); + + if (cost.Succeeded() && (flags & DC_EXEC) != 0) { if (free_wagon) { - cost.AddCost(ReplaceFreeUnit(&v, flags & ~DC_EXEC, ¬hing_to_do)); + ret = ReplaceFreeUnit(&v, flags, ¬hing_to_do); } else { - cost.AddCost(ReplaceChain(&v, flags & ~DC_EXEC, wagon_removal, ¬hing_to_do)); + ret = ReplaceChain(&v, flags, wagon_removal, ¬hing_to_do); } - RestoreRandomSeeds(saved_seeds); + assert(ret.Succeeded()); + assert(ret.GetCost() == cost.GetCost()); + } + + /* Check whether the train can be placed on tracks. */ + bool platform_error = false; + + /* Autoreplacing is done. */ + flags &= ~DC_AUTOREPLACE; + + if (v->type == VEH_TRAIN) { if (cost.Succeeded() && (flags & DC_EXEC) != 0) { - if (free_wagon) { - ret = ReplaceFreeUnit(&v, flags, ¬hing_to_do); - } else { - ret = ReplaceChain(&v, flags, wagon_removal, ¬hing_to_do); + train_placement.LookForPlaceInDepot(Train::From(v), false); + if (train_placement.info < PI_WONT_LEAVE) { + platform_error = true; + if (v->owner == _local_company && v->IsFrontEngine()) { + SetDParam(0, v->index); + AddVehicleAdviceNewsItem(STR_ADVICE_PLATFORM_TYPE + train_placement.info - PI_ERROR_BEGIN, v->index); + } } - assert(ret.Succeeded() && ret.GetCost() == cost.GetCost()); } - - /* Restart the vehicle */ - if (!was_stopped) cost.AddCost(DoCmdStartStopVehicle(v, false)); + train_placement.PlaceTrain(Train::From(v), flags); + } else if (IsExtendedDepotTile(v->tile)) { + UpdateExtendedDepotReservation(v, true); } - if (cost.Succeeded() && nothing_to_do) cost = CommandCost(STR_ERROR_AUTOREPLACE_NOTHING_TO_DO); + /* Restart the vehicle */ + if (!platform_error && !was_stopped) cost.AddCost(DoCmdStartStopVehicle(v, false)); + + assert(cost.Failed() || !nothing_to_do); return cost; } diff --git a/src/base_station_base.h b/src/base_station_base.h index 0c0ad2277078c..de8688006e613 100644 --- a/src/base_station_base.h +++ b/src/base_station_base.h @@ -140,24 +140,6 @@ struct BaseStation : StationPool::PoolItem<&_station_pool> { */ virtual void GetTileArea(TileArea *ta, StationType type) const = 0; - - /** - * Obtain the length of a platform - * @pre tile must be a rail station tile - * @param tile A tile that contains the platform in question - * @return The length of the platform - */ - virtual uint GetPlatformLength(TileIndex tile) const = 0; - - /** - * Determines the REMAINING length of a platform, starting at (and including) - * the given tile. - * @param tile the tile from which to start searching. Must be a rail station tile - * @param dir The direction in which to search. - * @return The platform length - */ - virtual uint GetPlatformLength(TileIndex tile, DiagDirection dir) const = 0; - /** * Get the base station belonging to a specific tile. * @param tile The tile to get the base station from. diff --git a/src/build_vehicle_gui.cpp b/src/build_vehicle_gui.cpp index 1a985eb64a251..7311c5b9e91fe 100644 --- a/src/build_vehicle_gui.cpp +++ b/src/build_vehicle_gui.cpp @@ -38,6 +38,9 @@ #include "querystring_gui.h" #include "stringfilter_type.h" #include "hotkeys.h" +#include "depot_base.h" +#include "depot_func.h" +#include "air.h" #include "widgets/build_vehicle_widget.h" @@ -1163,12 +1166,32 @@ enum BuildVehicleHotkeys { BVHK_FOCUS_FILTER_BOX, ///< Focus the edit box for editing the filter string }; +/** + * Return a unique window number for the BuildVehicleWindow. + * The BuildVehicleWindow can be opened for a valid depot or + * for a specific vehicle type ("Available Trains", "Available Ships"...). + * The corresponding unique window number is chosen as: + * - For existing depots, the depot id. + * - For vehicle types, it is MAX_DEPOTS + vehicle_type + * @param depot_id the depot id + * @param type the vehicle type + * @return the depot id for valid depots or MAX_DEPOTS + vehicle_type otherwise. + */ +DepotID GetBuildVehicleWindowNumber(DepotID depot_id, VehicleType type) +{ + assert(depot_id == INVALID_DEPOT || Depot::IsValidID(depot_id)); + assert(IsCompanyBuildableVehicleType(type)); + if (depot_id != INVALID_DEPOT) return depot_id; + return MAX_DEPOTS + type; +} + /** GUI for building vehicles. */ struct BuildVehicleWindow : Window { VehicleType vehicle_type; ///< Type of vehicles shown in the window. union { - RailType railtype; ///< Rail type to show, or #INVALID_RAILTYPE. - RoadType roadtype; ///< Road type to show, or #INVALID_ROADTYPE. + RailTypes railtypes; ///< Rail types to show, or #INVALID_RAILTYPES. + RoadTypes roadtypes; ///< Road types to show, or #INVALID_ROADTYPES. + AirTypes airtypes; ///< Air types to show, or #INVALID_AIRTYPES. } filter; ///< Filter to apply. bool descending_sort_order; ///< Sort direction, @see _engine_sort_direction uint8_t sort_criteria; ///< Current sort criterium. @@ -1201,11 +1224,11 @@ struct BuildVehicleWindow : Window { } } - BuildVehicleWindow(WindowDesc &desc, TileIndex tile, VehicleType type) : Window(desc), vehicle_editbox(MAX_LENGTH_VEHICLE_NAME_CHARS * MAX_CHAR_LENGTH, MAX_LENGTH_VEHICLE_NAME_CHARS) + BuildVehicleWindow(WindowDesc &desc, DepotID depot_id, VehicleType type) : Window(desc), vehicle_editbox(MAX_LENGTH_VEHICLE_NAME_CHARS * MAX_CHAR_LENGTH, MAX_LENGTH_VEHICLE_NAME_CHARS) { this->vehicle_type = type; - this->listview_mode = tile == INVALID_TILE; - this->window_number = this->listview_mode ? (int)type : tile.base(); + this->listview_mode = depot_id == INVALID_DEPOT; + this->window_number = GetBuildVehicleWindowNumber(depot_id, type); this->sel_engine = INVALID_ENGINE; @@ -1240,16 +1263,13 @@ struct BuildVehicleWindow : Window { this->details_height = ((this->vehicle_type == VEH_TRAIN) ? 10 : 9); - if (tile == INVALID_TILE) { - this->FinishInitNested(type); - } else { - this->FinishInitNested(tile); - } + this->FinishInitNested(this->window_number); this->querystrings[WID_BV_FILTER] = &this->vehicle_editbox; this->vehicle_editbox.cancel_button = QueryString::ACTION_CLEAR; - this->owner = (tile != INVALID_TILE) ? GetTileOwner(tile) : _local_company; + Depot *depot = Depot::GetIfValid(depot_id); + this->owner = depot != nullptr ? depot->owner : _local_company; this->eng_list.ForceRebuild(); this->GenerateBuildList(); // generate the list, since we need it in the next line @@ -1264,29 +1284,24 @@ struct BuildVehicleWindow : Window { /** Set the filter type according to the depot type */ void UpdateFilterByTile() { + Depot *depot = this->listview_mode ? nullptr : Depot::Get(this->window_number); + switch (this->vehicle_type) { default: NOT_REACHED(); case VEH_TRAIN: - if (this->listview_mode) { - this->filter.railtype = INVALID_RAILTYPE; - } else { - this->filter.railtype = GetRailType(this->window_number); - } + this->filter.railtypes = this->listview_mode ? INVALID_RAILTYPES : depot->r_types.rail_types; break; case VEH_ROAD: - if (this->listview_mode) { - this->filter.roadtype = INVALID_ROADTYPE; - } else { - this->filter.roadtype = GetRoadTypeRoad(this->window_number); - if (this->filter.roadtype == INVALID_ROADTYPE) { - this->filter.roadtype = GetRoadTypeTram(this->window_number); - } - } + this->filter.roadtypes = this->listview_mode ? INVALID_ROADTYPES : depot->r_types.road_types; break; case VEH_SHIP: + this->filter.railtypes = this->listview_mode ? INVALID_RAILTYPES : depot->r_types.rail_types; + break; + case VEH_AIRCRAFT: + this->filter.airtypes = this->listview_mode ? INVALID_AIRTYPES : depot->r_types.air_types; break; } } @@ -1326,7 +1341,7 @@ struct BuildVehicleWindow : Window { if (!this->listview_mode) { /* Query for cost and refitted capacity */ - auto [ret, veh_id, refit_capacity, refit_mail, cargo_capacities] = Command::Do(DC_QUERY_COST, this->window_number, this->sel_engine, true, cargo, INVALID_CLIENT_ID); + auto [ret, veh_id, refit_capacity, refit_mail, cargo_capacities] = Command::Do(DC_QUERY_COST, Depot::Get(this->window_number)->xy, this->sel_engine, true, cargo, INVALID_CLIENT_ID); if (ret.Succeeded()) { this->te.cost = ret.GetCost() - e->GetCost(); this->te.capacity = refit_capacity; @@ -1401,7 +1416,7 @@ struct BuildVehicleWindow : Window { EngineID eid = e->index; const RailVehicleInfo *rvi = &e->u.rail; - if (this->filter.railtype != INVALID_RAILTYPE && !HasPowerOnRail(rvi->railtype, this->filter.railtype)) continue; + if (!this->listview_mode && !HasPowerOnRails(rvi->railtype, this->filter.railtypes)) continue; if (!IsEngineBuildable(eid, VEH_TRAIN, _local_company)) continue; /* Filter now! So num_engines and num_wagons is valid */ @@ -1461,7 +1476,7 @@ struct BuildVehicleWindow : Window { if (!this->show_hidden_engines && e->IsVariantHidden(_local_company)) continue; EngineID eid = e->index; if (!IsEngineBuildable(eid, VEH_ROAD, _local_company)) continue; - if (this->filter.roadtype != INVALID_ROADTYPE && !HasPowerOnRoad(e->u.road.roadtype, this->filter.roadtype)) continue; + if (!this->listview_mode && !HasPowerOnRoads(e->u.road.roadtype, this->filter.roadtypes)) continue; /* Filter by name or NewGRF extra text */ if (!FilterByText(e)) continue; @@ -1479,18 +1494,17 @@ struct BuildVehicleWindow : Window { EngineID sel_id = INVALID_ENGINE; this->eng_list.clear(); - for (const Engine *e : Engine::IterateType(VEH_SHIP)) { - if (!this->show_hidden_engines && e->IsVariantHidden(_local_company)) continue; - EngineID eid = e->index; - if (!IsEngineBuildable(eid, VEH_SHIP, _local_company)) continue; - - /* Filter by name or NewGRF extra text */ - if (!FilterByText(e)) continue; - - this->eng_list.emplace_back(eid, e->info.variant_id, e->display_flags, 0); + if (this->listview_mode || this->filter.railtypes != RAILTYPES_NONE) { + for (const Engine *e : Engine::IterateType(VEH_SHIP)) { + if (!this->show_hidden_engines && e->IsVariantHidden(_local_company)) continue; + EngineID eid = e->index; + if (!IsEngineBuildable(eid, VEH_SHIP, _local_company)) continue; + this->eng_list.emplace_back(eid, e->info.variant_id, e->display_flags, 0); - if (eid == this->sel_engine) sel_id = eid; + if (eid == this->sel_engine) sel_id = eid; + } } + this->SelectEngine(sel_id); } @@ -1501,25 +1515,25 @@ struct BuildVehicleWindow : Window { this->eng_list.clear(); - const Station *st = this->listview_mode ? nullptr : Station::GetByTile(this->window_number); - - /* Make list of all available planes. - * Also check to see if the previously selected plane is still available, - * and if not, reset selection to INVALID_ENGINE. This could be the case - * when planes become obsolete and are removed */ - for (const Engine *e : Engine::IterateType(VEH_AIRCRAFT)) { - if (!this->show_hidden_engines && e->IsVariantHidden(_local_company)) continue; - EngineID eid = e->index; - if (!IsEngineBuildable(eid, VEH_AIRCRAFT, _local_company)) continue; - /* First VEH_END window_numbers are fake to allow a window open for all different types at once */ - if (!this->listview_mode && !CanVehicleUseStation(eid, st)) continue; + if (this->listview_mode || !Depot::Get(this->window_number)->depot_tiles.empty()) { + const Station *st = this->listview_mode ? nullptr : Depot::Get(this->window_number)->station; - /* Filter by name or NewGRF extra text */ - if (!FilterByText(e)) continue; + /* Make list of all available planes. + * Also check to see if the previously selected plane is still available, + * and if not, reset selection to INVALID_ENGINE. This could be the case + * when planes become obsolete and are removed */ + for (const Engine *e : Engine::IterateType(VEH_AIRCRAFT)) { + if (!this->show_hidden_engines && e->IsVariantHidden(_local_company)) continue; + EngineID eid = e->index; + if (!IsEngineBuildable(eid, VEH_AIRCRAFT, _local_company)) continue; + if (!this->listview_mode && !CanVehicleUseStation(eid, st)) continue; - this->eng_list.emplace_back(eid, e->info.variant_id, e->display_flags, 0); + /* Filter by name or NewGRF extra text */ + if (!FilterByText(e)) continue; - if (eid == this->sel_engine) sel_id = eid; + this->eng_list.emplace_back(eid, e->info.variant_id, e->display_flags, 0); + if (eid == this->sel_engine) sel_id = eid; + } } this->SelectEngine(sel_id); @@ -1613,10 +1627,15 @@ struct BuildVehicleWindow : Window { CargoID cargo = this->cargo_filter_criteria; if (cargo == CargoFilterCriteria::CF_ANY || cargo == CargoFilterCriteria::CF_ENGINES || cargo == CargoFilterCriteria::CF_NONE) cargo = INVALID_CARGO; + + assert(Depot::IsValidID(this->window_number)); + Depot *depot = Depot::Get(this->window_number); + assert(depot->xy != INVALID_TILE); + if (this->vehicle_type == VEH_TRAIN && RailVehInfo(sel_eng)->railveh_type == RAILVEH_WAGON) { - Command::Post(GetCmdBuildVehMsg(this->vehicle_type), CcBuildWagon, this->window_number, sel_eng, true, cargo, INVALID_CLIENT_ID); + Command::Post(GetCmdBuildVehMsg(this->vehicle_type), CcBuildWagon, depot->xy, sel_eng, true, cargo, INVALID_CLIENT_ID); } else { - Command::Post(GetCmdBuildVehMsg(this->vehicle_type), CcBuildPrimaryVehicle, this->window_number, sel_eng, true, cargo, INVALID_CLIENT_ID); + Command::Post(GetCmdBuildVehMsg(this->vehicle_type), CcBuildPrimaryVehicle, depot->xy, sel_eng, true, cargo, INVALID_CLIENT_ID); } /* Update last used variant in hierarchy and refresh if necessary. */ @@ -1736,11 +1755,21 @@ struct BuildVehicleWindow : Window { switch (widget) { case WID_BV_CAPTION: if (this->vehicle_type == VEH_TRAIN && !this->listview_mode) { - const RailTypeInfo *rti = GetRailTypeInfo(this->filter.railtype); - SetDParam(0, rti->strings.build_caption); + uint num_railtypes = CountBits(this->filter.railtypes); + if (num_railtypes != 1) { + SetDParam(0, STR_BUY_VEHICLE_TRAIN_ALL_CAPTION); + } else { + const RailTypeInfo *rti = GetRailTypeInfo((RailType)FindFirstBit(this->filter.railtypes)); + SetDParam(0, rti->strings.build_caption); + } } else if (this->vehicle_type == VEH_ROAD && !this->listview_mode) { - const RoadTypeInfo *rti = GetRoadTypeInfo(this->filter.roadtype); - SetDParam(0, rti->strings.build_caption); + uint num_roadtypes = CountBits(this->filter.roadtypes); + if (num_roadtypes != 1) { + SetDParam(0, STR_BUY_VEHICLE_ROAD_VEHICLE_CAPTION); + } else { + const RoadTypeInfo *rti = GetRoadTypeInfo((RoadType)FindFirstBit(this->filter.roadtypes)); + SetDParam(0, rti->strings.build_caption); + } } else { SetDParam(0, (this->listview_mode ? STR_VEHICLE_LIST_AVAILABLE_TRAINS : STR_BUY_VEHICLE_TRAIN_ALL_CAPTION) + this->vehicle_type); } @@ -1930,17 +1959,11 @@ static WindowDesc _build_vehicle_desc( &BuildVehicleWindow::hotkeys ); -void ShowBuildVehicleWindow(TileIndex tile, VehicleType type) +void ShowBuildVehicleWindow(DepotID depot_id, VehicleType type) { - /* We want to be able to open both Available Train as Available Ships, - * so if tile == INVALID_TILE (Available XXX Window), use 'type' as unique number. - * As it always is a low value, it won't collide with any real tile - * number. */ - uint num = (tile == INVALID_TILE) ? (int)type : tile.base(); - assert(IsCompanyBuildableVehicleType(type)); - CloseWindowById(WC_BUILD_VEHICLE, num); + CloseWindowById(WC_BUILD_VEHICLE, GetBuildVehicleWindowNumber(depot_id, type)); - new BuildVehicleWindow(_build_vehicle_desc, tile, type); + new BuildVehicleWindow(_build_vehicle_desc, depot_id, type); } diff --git a/src/cheat_gui.cpp b/src/cheat_gui.cpp index 5d3475ee80e4d..0a23c5a2dc68f 100644 --- a/src/cheat_gui.cpp +++ b/src/cheat_gui.cpp @@ -192,6 +192,7 @@ struct CheatEntry { void *variable; ///< pointer to the variable bool *been_used; ///< has this cheat been used before? CheckButtonClick *proc;///< procedure + bool available; }; /** @@ -199,15 +200,15 @@ struct CheatEntry { * Order matches with the values of #CheatNumbers */ static const CheatEntry _cheats_ui[] = { - {SLE_INT32, STR_CHEAT_MONEY, &_money_cheat_amount, &_cheats.money.been_used, &ClickMoneyCheat }, - {SLE_UINT8, STR_CHEAT_CHANGE_COMPANY, &_local_company, &_cheats.switch_company.been_used, &ClickChangeCompanyCheat }, - {SLE_BOOL, STR_CHEAT_EXTRA_DYNAMITE, &_cheats.magic_bulldozer.value, &_cheats.magic_bulldozer.been_used, nullptr }, - {SLE_BOOL, STR_CHEAT_CROSSINGTUNNELS, &_cheats.crossing_tunnels.value, &_cheats.crossing_tunnels.been_used, nullptr }, - {SLE_BOOL, STR_CHEAT_NO_JETCRASH, &_cheats.no_jetcrash.value, &_cheats.no_jetcrash.been_used, nullptr }, - {SLE_BOOL, STR_CHEAT_SETUP_PROD, &_cheats.setup_prod.value, &_cheats.setup_prod.been_used, &ClickSetProdCheat }, - {SLE_BOOL, STR_CHEAT_STATION_RATING, &_cheats.station_rating.value, &_cheats.station_rating.been_used, nullptr }, - {SLE_UINT8, STR_CHEAT_EDIT_MAX_HL, &_settings_game.construction.map_height_limit, &_cheats.edit_max_hl.been_used, &ClickChangeMaxHlCheat }, - {SLE_INT32, STR_CHEAT_CHANGE_DATE, &TimerGameCalendar::year, &_cheats.change_date.been_used, &ClickChangeDateCheat }, + {SLE_INT32, STR_CHEAT_MONEY, &_money_cheat_amount, &_cheats.money.been_used, &ClickMoneyCheat, true }, + {SLE_UINT8, STR_CHEAT_CHANGE_COMPANY, &_local_company, &_cheats.switch_company.been_used, &ClickChangeCompanyCheat, true }, + {SLE_BOOL, STR_CHEAT_EXTRA_DYNAMITE, &_cheats.magic_bulldozer.value, &_cheats.magic_bulldozer.been_used, nullptr, true }, + {SLE_BOOL, STR_CHEAT_CROSSINGTUNNELS, &_cheats.crossing_tunnels.value, &_cheats.crossing_tunnels.been_used, nullptr, true }, + {SLE_BOOL, STR_CHEAT_NO_JETCRASH, &_cheats.no_jetcrash.value, &_cheats.no_jetcrash.been_used, nullptr, false }, + {SLE_BOOL, STR_CHEAT_SETUP_PROD, &_cheats.setup_prod.value, &_cheats.setup_prod.been_used, &ClickSetProdCheat, true }, + {SLE_BOOL, STR_CHEAT_STATION_RATING, &_cheats.station_rating.value, &_cheats.station_rating.been_used, nullptr, true }, + {SLE_UINT8, STR_CHEAT_EDIT_MAX_HL, &_settings_game.construction.map_height_limit, &_cheats.edit_max_hl.been_used, &ClickChangeMaxHlCheat, true }, + {SLE_INT32, STR_CHEAT_CHANGE_DATE, &TimerGameCalendar::year, &_cheats.change_date.been_used, &ClickChangeDateCheat, true }, }; static_assert(CHT_NUM_CHEATS == lengthof(_cheats_ui)); @@ -263,7 +264,7 @@ struct CheatWindow : Window { case SLE_BOOL: { bool on = (*(bool*)ce->variable); - DrawBoolButton(button_left, y + button_y_offset, on, true); + DrawBoolButton(button_left, y + button_y_offset, on, ce->available); SetDParam(0, on ? STR_CONFIG_SETTING_ON : STR_CONFIG_SETTING_OFF); break; } @@ -355,6 +356,9 @@ struct CheatWindow : Window { if (btn >= lengthof(_cheats_ui)) return; const CheatEntry *ce = &_cheats_ui[btn]; + + if (!ce->available) return; + int value = (int32_t)ReadValue(ce->variable, ce->type); int oldvalue = value; diff --git a/src/command.cpp b/src/command.cpp index b975c51fbbd30..e98d64de6a4a7 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -24,6 +24,7 @@ #include "signal_func.h" #include "core/backup_type.hpp" #include "object_base.h" +#include "airport_cmd.h" #include "autoreplace_cmd.h" #include "company_cmd.h" #include "depot_cmd.h" diff --git a/src/command_type.h b/src/command_type.h index 2fd494b5cac06..f52dbb32dadec 100644 --- a/src/command_type.h +++ b/src/command_type.h @@ -193,6 +193,7 @@ enum Commands : uint16_t { CMD_BUILD_BRIDGE, ///< build a bridge CMD_BUILD_RAIL_STATION, ///< build a rail station CMD_BUILD_TRAIN_DEPOT, ///< build a train depot + CMD_REMOVE_TRAIN_DEPOT, ///< remove a train depot CMD_BUILD_SINGLE_SIGNAL, ///< build a signal CMD_REMOVE_SINGLE_SIGNAL, ///< remove a signal CMD_TERRAFORM_LAND, ///< terraform a tile @@ -218,7 +219,13 @@ enum Commands : uint16_t { CMD_BUILD_ROAD_DEPOT, ///< build a road depot CMD_CONVERT_ROAD, ///< convert a road type - CMD_BUILD_AIRPORT, ///< build an airport + CMD_CHANGE_AIRPORT, ///< change pieces of airport + CMD_ADD_REM_AIRPORT, ///< build/remove tiles for airport tracks + CMD_ADD_REM_TRACKS, ///< build/remove tracks + CMD_CONVERT_AIRPORT, ///< change the aiport type (gravel, asphalt, etc.) + CMD_BUILD_AIRPORT, ///< build an airport layout + CMD_AIRPORT_CHANGE_GFX, ///< change the graphics of an airport tile, if possible + CMD_AIRPORT_TOGGLE_GROUND, ///< toggle between showing the specific airtype ground or not, if possible CMD_BUILD_DOCK, ///< build a dock diff --git a/src/company_base.h b/src/company_base.h index 814665777b2e9..e765c77fbbd92 100644 --- a/src/company_base.h +++ b/src/company_base.h @@ -32,10 +32,10 @@ struct CompanyEconomyEntry { struct CompanyInfrastructure { std::array rail{}; ///< Count of company owned track bits for each rail type. std::array road{}; ///< Count of company owned track bits for each road type. + std::array air{}; ///< Count of company owned airport tiles for each air type. uint32_t signal; ///< Count of company owned signals. uint32_t water; ///< Count of company owned track bits for canals. uint32_t station; ///< Count of company owned station tiles. - uint32_t airport; ///< Count of company owned airports. auto operator<=>(const CompanyInfrastructure &) const = default; @@ -45,6 +45,12 @@ struct CompanyInfrastructure { return std::accumulate(std::begin(this->rail), std::end(this->rail), 0U); } + /** Get total sum of all owned airport tiles. */ + uint32_t GetAirTotal() const + { + return std::accumulate(std::begin(this->air), std::end(this->air), 0U); + } + uint32_t GetRoadTotal() const; uint32_t GetTramTotal() const; }; @@ -133,6 +139,7 @@ struct Company : CompanyProperties, CompanyPool::PoolItem<&_company_pool> { Company(uint16_t name_1 = 0, bool is_ai = false); ~Company(); + AirTypes avail_airtypes; ///< Air types available to the company. RailTypes avail_railtypes; ///< Rail types available to this company. RoadTypes avail_roadtypes; ///< Road types available to this company. diff --git a/src/company_cmd.cpp b/src/company_cmd.cpp index 74ad2893f4054..304488702985a 100644 --- a/src/company_cmd.cpp +++ b/src/company_cmd.cpp @@ -26,6 +26,7 @@ #include "window_func.h" #include "strings_func.h" #include "sound_func.h" +#include "air.h" #include "rail.h" #include "core/pool_func.hpp" #include "settings_func.h" @@ -607,6 +608,7 @@ Company *DoStartupNewCompany(bool is_ai, CompanyID company = INVALID_COMPANY) c->avail_railtypes = GetCompanyRailTypes(c->index); c->avail_roadtypes = GetCompanyRoadTypes(c->index); + c->avail_airtypes = GetCompanyAirTypes(c->index); c->inaugurated_year = TimerGameEconomy::year; /* If starting a player company in singleplayer and a favorite company manager face is selected, choose it. Otherwise, use a random face. diff --git a/src/company_gui.cpp b/src/company_gui.cpp index f7acd9831d7f6..6e48562f0a10a 100644 --- a/src/company_gui.cpp +++ b/src/company_gui.cpp @@ -31,6 +31,7 @@ #include "object_type.h" #include "rail.h" #include "road.h" +#include "air.h" #include "engine_base.h" #include "window_func.h" #include "road_func.h" @@ -1748,6 +1749,10 @@ static constexpr NWidgetPart _nested_company_infrastructure_widgets[] = { NWidget(WWT_EMPTY, COLOUR_GREY, WID_CI_WATER_DESC), SetMinimalTextLines(2, 0), SetFill(1, 0), NWidget(WWT_EMPTY, COLOUR_GREY, WID_CI_WATER_COUNT), SetMinimalTextLines(2, 0), SetFill(0, 1), EndContainer(), + NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0), + NWidget(WWT_EMPTY, COLOUR_GREY, WID_CI_AIRPORT_DESC), SetMinimalTextLines(2, 0), SetFill(1, 0), + NWidget(WWT_EMPTY, COLOUR_GREY, WID_CI_AIRPORT_COUNT), SetMinimalTextLines(2, 0), SetFill(0, 1), + EndContainer(), NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0), NWidget(WWT_EMPTY, COLOUR_GREY, WID_CI_STATION_DESC), SetMinimalTextLines(3, 0), SetFill(1, 0), NWidget(WWT_EMPTY, COLOUR_GREY, WID_CI_STATION_COUNT), SetMinimalTextLines(3, 0), SetFill(0, 1), @@ -1767,6 +1772,7 @@ struct CompanyInfrastructureWindow : Window { RailTypes railtypes; ///< Valid railtypes. RoadTypes roadtypes; ///< Valid roadtypes. + AirTypes airtypes; ///< Valid airtypes. uint total_width; ///< String width of the total cost line. @@ -1782,6 +1788,7 @@ struct CompanyInfrastructureWindow : Window { this->railtypes = RAILTYPES_NONE; this->roadtypes = ROADTYPES_NONE; + this->airtypes = AIRTYPES_NONE; /* Find the used railtypes. */ for (const Engine *e : Engine::IterateType(VEH_TRAIN)) { @@ -1793,6 +1800,16 @@ struct CompanyInfrastructureWindow : Window /* Get the date introduced railtypes as well. */ this->railtypes = AddDateIntroducedRailTypes(this->railtypes, CalendarTime::MAX_DATE); + /* Find the used airtypes. */ + for (const Engine *e : Engine::IterateType(VEH_AIRCRAFT)) { + if (!HasBit(e->info.climates, _settings_game.game_creation.landscape)) continue; + + this->airtypes |= GetAirTypeInfo(e->u.air.airtype)->introduces_airtypes; + } + + /* Get the date introduced airtypes as well. */ + this->airtypes = AddDateIntroducedAirTypes(this->airtypes, CalendarTime::MAX_DATE); + /* Find the used roadtypes. */ for (const Engine *e : Engine::IterateType(VEH_ROAD)) { if (!HasBit(e->info.climates, _settings_game.game_creation.landscape)) continue; @@ -1823,9 +1840,13 @@ struct CompanyInfrastructureWindow : Window if (HasBit(this->roadtypes, rt)) total += RoadMaintenanceCost(rt, c->infrastructure.road[rt], RoadTypeIsRoad(rt) ? road_total : tram_total); } + uint32_t air_total = c->infrastructure.GetAirTotal(); + for (AirType at = AIRTYPE_BEGIN; at != AIRTYPE_END; at++) { + if (HasBit(this->airtypes, at)) total += AirMaintenanceCost(at, c->infrastructure.air[at], air_total); + } + total += CanalMaintenanceCost(c->infrastructure.water); total += StationMaintenanceCost(c->infrastructure.station); - total += AirportMaintenanceCost(c->index); return total; } @@ -1886,10 +1907,25 @@ struct CompanyInfrastructureWindow : Window size.width = std::max(size.width, GetStringBoundingBox(STR_COMPANY_INFRASTRUCTURE_VIEW_CANALS).width + padding.width + WidgetDimensions::scaled.hsep_indent); break; + case WID_CI_AIRPORT_DESC: { + uint lines = 1; + size.width = std::max(size.width, GetStringBoundingBox(STR_COMPANY_INFRASTRUCTURE_VIEW_AIRPORT_SECT).width); + + for (const auto &at : _sorted_airtypes) { + if (HasBit(this->airtypes, at)) { + lines++; + SetDParam(0, GetAirTypeInfo(at)->strings.name); + size.width = std::max(size.width, GetStringBoundingBox(STR_JUST_STRING).width + WidgetDimensions::scaled.frametext.left); + } + } + + size.height = std::max(size.height, lines * GetCharacterHeight(FS_NORMAL)); + break; + } + case WID_CI_STATION_DESC: size.width = std::max(size.width, GetStringBoundingBox(STR_COMPANY_INFRASTRUCTURE_VIEW_STATION_SECT).width + padding.width); size.width = std::max(size.width, GetStringBoundingBox(STR_COMPANY_INFRASTRUCTURE_VIEW_STATIONS).width + padding.width + WidgetDimensions::scaled.hsep_indent); - size.width = std::max(size.width, GetStringBoundingBox(STR_COMPANY_INFRASTRUCTURE_VIEW_AIRPORTS).width + padding.width + WidgetDimensions::scaled.hsep_indent); break; case WID_CI_RAIL_COUNT: @@ -1897,6 +1933,7 @@ struct CompanyInfrastructureWindow : Window case WID_CI_TRAM_COUNT: case WID_CI_WATER_COUNT: case WID_CI_STATION_COUNT: + case WID_CI_AIRPORT_COUNT: case WID_CI_TOTAL: { /* Find the maximum count that is displayed. */ uint32_t max_val = 1000; // Some random number to reserve enough space. @@ -1917,10 +1954,13 @@ struct CompanyInfrastructureWindow : Window } max_val = std::max(max_val, c->infrastructure.water); max_cost = std::max(max_cost, CanalMaintenanceCost(c->infrastructure.water)); + uint32_t air_total = c->infrastructure.GetAirTotal(); + for (AirType at = AIRTYPE_BEGIN; at < AIRTYPE_END; at++) { + max_val = std::max(max_val, c->infrastructure.air[at]); + max_cost = std::max(max_cost, AirMaintenanceCost(at, c->infrastructure.air[at], air_total)); + } max_val = std::max(max_val, c->infrastructure.station); max_cost = std::max(max_cost, StationMaintenanceCost(c->infrastructure.station)); - max_val = std::max(max_val, c->infrastructure.airport); - max_cost = std::max(max_cost, AirportMaintenanceCost(c->index)); SetDParamMaxValue(0, max_val); uint count_width = GetStringBoundingBox(STR_JUST_COMMA).width + WidgetDimensions::scaled.hsep_indent; // Reserve some wiggle room @@ -2041,6 +2081,35 @@ struct CompanyInfrastructureWindow : Window this->DrawCountLine(r, y, c->infrastructure.water, CanalMaintenanceCost(c->infrastructure.water)); break; + case WID_CI_AIRPORT_DESC: + DrawString(r.left, r.right, y, STR_COMPANY_INFRASTRUCTURE_VIEW_AIRPORT_SECT); + + if (this->airtypes != AIRTYPES_NONE) { + /* Draw name of each valid airtype. */ + for (const auto &at : _sorted_airtypes) { + if (HasBit(this->railtypes, at)) { + SetDParam(0, GetAirTypeInfo(at)->strings.name); + DrawString(ir.left, ir.right, y += GetCharacterHeight(FS_NORMAL), STR_JUST_STRING, TC_WHITE); + } + } + } else { + /* No valid airtype. */ + DrawString(ir.left, ir.right, y += GetCharacterHeight(FS_NORMAL), STR_COMPANY_VIEW_INFRASTRUCTURE_NONE); + } + + break; + + case WID_CI_AIRPORT_COUNT: { + /* Draw infrastructure count for each valid airtype. */ + uint32_t air_total = c->infrastructure.GetAirTotal(); + for (const auto &at : _sorted_airtypes) { + if (HasBit(this->airtypes, at)) { + this->DrawCountLine(r, y, c->infrastructure.air[at], AirMaintenanceCost(at, c->infrastructure.air[at], air_total)); + } + } + break; + } + case WID_CI_TOTAL: if (_settings_game.economy.infrastructure_maintenance) { Rect tr = r.WithWidth(this->total_width, _current_text_dir == TD_RTL); @@ -2056,12 +2125,10 @@ struct CompanyInfrastructureWindow : Window case WID_CI_STATION_DESC: DrawString(r.left, r.right, y, STR_COMPANY_INFRASTRUCTURE_VIEW_STATION_SECT); DrawString(ir.left, ir.right, y += GetCharacterHeight(FS_NORMAL), STR_COMPANY_INFRASTRUCTURE_VIEW_STATIONS); - DrawString(ir.left, ir.right, y += GetCharacterHeight(FS_NORMAL), STR_COMPANY_INFRASTRUCTURE_VIEW_AIRPORTS); break; case WID_CI_STATION_COUNT: this->DrawCountLine(r, y, c->infrastructure.station, StationMaintenanceCost(c->infrastructure.station)); - this->DrawCountLine(r, y, c->infrastructure.airport, AirportMaintenanceCost(c->index)); break; } } @@ -2349,10 +2416,10 @@ struct CompanyWindow : Window y += GetCharacterHeight(FS_NORMAL); } - if (c->infrastructure.airport != 0) { - SetDParam(0, c->infrastructure.airport); + uint air_pieces = c->infrastructure.GetAirTotal(); + if (air_pieces != 0) { + SetDParam(0, air_pieces); DrawString(r.left, r.right, y, STR_COMPANY_VIEW_INFRASTRUCTURE_AIRPORT); - y += GetCharacterHeight(FS_NORMAL); } if (y == r.top) { diff --git a/src/depot.cpp b/src/depot.cpp index 13317e8a35dad..9bba7f67546f8 100644 --- a/src/depot.cpp +++ b/src/depot.cpp @@ -15,9 +15,15 @@ #include "core/pool_func.hpp" #include "vehicle_gui.h" #include "vehiclelist.h" +#include "command_func.h" +#include "vehicle_base.h" +#include "viewport_kdtree.h" +#include "platform_func.h" #include "safeguards.h" +#include "table/strings.h" + /** All our depots tucked away in a pool. */ DepotPool _depot_pool("Depot"); INSTANTIATE_POOL_METHODS(Depot) @@ -29,21 +35,309 @@ Depot::~Depot() { if (CleaningPool()) return; - if (!IsDepotTile(this->xy) || GetDepotIndex(this->xy) != this->index) { - /* It can happen there is no depot here anymore (TTO/TTD savegames) */ + if (this->owner == INVALID_OWNER) { + /* Deleting depot remnants of TTD savegames while saveload conversion. */ + assert(this->veh_type == VEH_INVALID); return; } /* Clear the order backup. */ - OrderBackup::Reset(this->xy, false); + OrderBackup::Reset(this->index, false); + + if (this->veh_type == VEH_AIRCRAFT) { + this->station->airport.hangar = nullptr; + } + + /* Make sure no vehicle is going to the old depot. */ + for (Vehicle *v : Vehicle::Iterate()) { + if (v->First() != v) continue; + if (!v->current_order.IsType(OT_GOTO_DEPOT)) continue; + if (v->current_order.GetDestination() != this->index) continue; + + v->current_order.MakeDummy(); + } /* Clear the depot from all order-lists */ RemoveOrderFromAllVehicles(OT_GOTO_DEPOT, this->index); /* Delete the depot-window */ - CloseWindowById(WC_VEHICLE_DEPOT, this->xy); + CloseWindowById(WC_VEHICLE_DEPOT, this->index); /* Delete the depot list */ - VehicleType vt = GetDepotVehicleType(this->xy); - CloseWindowById(GetWindowClassForVehicleType(vt), VehicleListIdentifier(VL_DEPOT_LIST, vt, GetTileOwner(this->xy), this->index).Pack()); + CloseWindowById(GetWindowClassForVehicleType(this->veh_type), + VehicleListIdentifier(VL_DEPOT_LIST, + this->veh_type, this->owner, this->index).Pack()); + + InvalidateWindowData(WC_SELECT_DEPOT, this->veh_type); + + /* The sign will now disappear. */ + _viewport_sign_kdtree.Remove(ViewportSignKdtreeItem::MakeDepot(this->index)); + this->sign.MarkDirty(); +} + +/** + * Cancel deletion of this depot (reuse it). + * @param xy New location of the depot. + * @see Depot::IsInUse + * @see Depot::Disuse + */ +void Depot::Reuse(TileIndex xy) +{ + this->delete_ctr = 0; + this->xy = xy; + this->ta.tile = xy; + this->ta.h = this->ta.w = 1; + + /* Ensure the sign is not drawn */ + _viewport_sign_kdtree.Remove(ViewportSignKdtreeItem::MakeDepot(this->index)); + this->sign.MarkDirty(); +} + +/** + * Schedule deletion of this depot. + * + * This method is ought to be called after demolishing last depot part. + * The depot will be kept in the pool for a while so it can be + * placed again later without messing vehicle orders. + * + * @see Depot::IsInUse + * @see Depot::Reuse + */ +void Depot::Disuse() +{ + /* Mark that the depot is demolished and start the countdown. */ + this->delete_ctr = 8; + + /* Update the sign, it will be visible from now. */ + this->UpdateVirtCoord(); + _viewport_sign_kdtree.Insert(ViewportSignKdtreeItem::MakeDepot(this->index)); +} + +/** + * Of all the depot parts a depot has, return the best destination for a vehicle. + * @param v The vehicle. + * @param dep The depot vehicle \a v is heading for. + * @return The free and closest (if none is free, just closest) part of depot to vehicle v. + */ +TileIndex Depot::GetBestDepotTile(Vehicle *v) const +{ + assert(this->veh_type == v->type); + TileIndex best_depot = INVALID_TILE; + DepotReservation best_found_type = DEPOT_RESERVATION_END; + uint best_distance = UINT_MAX; + + for (const auto &tile : this->depot_tiles) { + bool check_south = v->type == VEH_ROAD; + uint new_distance = DistanceManhattan(v->tile, tile); +again: + DepotReservation depot_reservation = GetDepotReservation(tile, check_south); + if (((best_found_type == depot_reservation) && new_distance < best_distance) || (depot_reservation < best_found_type)) { + best_depot = tile; + best_distance = new_distance; + best_found_type = depot_reservation; + } + if (check_south) { + /* For road vehicles, check north direction as well. */ + check_south = false; + goto again; + } + } + + return best_depot; +} + +/** + * Check we can add some tiles to this depot. + * @param ta The affected tile area. + * @return Whether it is possible to add the tiles or an error message. + */ +CommandCost Depot::BeforeAddTiles(TileArea ta) +{ + assert(ta.tile != INVALID_TILE); + + if (this->ta.tile != INVALID_TILE && this->IsInUse()) { + /* Important when the old rect is completely inside the new rect, resp. the old one was empty. */ + ta.Add(this->ta.tile); + ta.Add(TileAddXY(this->ta.tile, this->ta.w - 1, this->ta.h - 1)); + } + + /* A max depot spread of 1 for VEH_SHIP is a special case, + * as ship depots consist of two tiles. */ + if (this->veh_type == VEH_SHIP && _settings_game.depot.depot_spread == 1) { + /* (ta.w, ta.h) must be equal to (1, 2) or (2, 1). + * This means that ta.w * ta.h must be equal to 2. */ + if (ta.w * ta.h != 2) return_cmd_error(STR_ERROR_DEPOT_TOO_SPREAD_OUT); + } else if (std::max(ta.w, ta.h) > _settings_game.depot.depot_spread) { + return_cmd_error(STR_ERROR_DEPOT_TOO_SPREAD_OUT); + } + return CommandCost(); +} + +/** + * Add some tiles to this depot and rescan area for depot_tiles. + * @param ta Affected tile area + * @param adding Whether adding or removing depot tiles. + */ +void Depot::AfterAddRemove(TileArea ta, bool adding) +{ + assert(ta.tile != INVALID_TILE); + + if (adding) { + if (this->ta.tile != INVALID_TILE) { + ta.Add(this->ta.tile); + ta.Add(TileAddXY(this->ta.tile, this->ta.w - 1, this->ta.h - 1)); + } + } else { + ta = this->ta; + } + + this->ta.Clear(); + + for (TileIndex tile : ta) { + if (!IsDepotTile(tile)) continue; + if (GetDepotIndex(tile) != this->index) continue; + this->ta.Add(tile); + } + + if (this->ta.tile != INVALID_TILE) { + this->RescanDepotTiles(); + assert(!this->depot_tiles.empty()); + this->xy = this->depot_tiles[0]; + InvalidateWindowData(WC_VEHICLE_DEPOT, this->index); + } else { + assert(this->IsInUse()); + this->Disuse(); + TileIndex old_tile = this->xy; + this->RescanDepotTiles(); + assert(this->depot_tiles.empty()); + this->xy = old_tile; + } + + InvalidateWindowData(WC_VEHICLE_DEPOT, this->index); + InvalidateWindowData(WC_SELECT_DEPOT, this->veh_type); +} + +/** + * Check whether a tile is a destination tile, such as the starting tiles of + * rail platforms (and not the middle tiles of the platforms). + * @param dep The depot being checked + * @param tile The tile being checked + * @return Whether the tile is of the given depot. + */ +bool IsDepotDestTile(Depot *dep, TileIndex tile) +{ + assert(IsDepotTile(tile)); + assert(GetDepotIndex(tile) == dep->index); + + switch (dep->veh_type) { + case VEH_TRAIN: + assert(IsRailDepotTile(tile)); + return !IsExtendedRailDepot(tile) || IsAnyStartPlatformTile(tile); + case VEH_ROAD: + case VEH_SHIP: + case VEH_AIRCRAFT: + return true; + default: NOT_REACHED(); + } +} + +/** + * Rescan depot_tiles. Done after AfterAddRemove and SaveLoad. + * Updates the tiles of the depot and its railtypes/roadtypes... + */ +void Depot::RescanDepotTiles() +{ + this->depot_tiles.clear(); + RailTypes old_rail_types = this->r_types.rail_types; + this->r_types.rail_types = RAILTYPES_NONE; + + for (TileIndex tile : this->ta) { + if (!IsDepotTile(tile)) continue; + if (GetDepotIndex(tile) != this->index) continue; + if (IsDepotDestTile(this, tile)) this->depot_tiles.push_back(tile); + switch (veh_type) { + case VEH_ROAD: + this->r_types.road_types |= GetPresentRoadTypes(tile); + break; + case VEH_TRAIN: + this->r_types.rail_types |= (RailTypes)(1 << GetRailType(tile)); + break; + case VEH_SHIP: + /* Mark this ship depot has at least one part, so ships can be built. */ + this->r_types.rail_types |= INVALID_RAILTYPES; + break; + case VEH_AIRCRAFT: + NOT_REACHED(); + default: break; + } + } + + if (old_rail_types != this->r_types.rail_types) { + InvalidateWindowData(WC_BUILD_VEHICLE, this->index, 0, true); + } +} + +/** + * Fix tile reservations and vehicle on extended depots. + * @param v Vehicle to be checked. + * @param reserve Whether to reserve or free the position v is occupying. + */ +void UpdateExtendedDepotReservation(Vehicle *v, bool reserve) +{ + assert(v != nullptr); + assert(IsExtendedDepotTile(v->tile)); + DepotReservation res_type = DEPOT_RESERVATION_EMPTY; + + if (reserve) { + res_type = (v->vehstatus & VS_STOPPED) ? + DEPOT_RESERVATION_FULL_STOPPED_VEH : DEPOT_RESERVATION_IN_USE; + } + + switch (v->type) { + case VEH_ROAD: { + assert(v == v->First()); + assert(IsDiagonalDirection(v->direction)); + bool is_facing_south = IsDiagDirFacingSouth(DirToDiagDir(v->direction)); + TileArea ta; + for (Vehicle *u = v; u != nullptr; u = u->Next()) ta.Add(u->tile); + for (TileIndex t : ta) { + res_type = DEPOT_RESERVATION_EMPTY; + + if (reserve) { + res_type = (v->vehstatus & VS_STOPPED) ? + DEPOT_RESERVATION_FULL_STOPPED_VEH : DEPOT_RESERVATION_IN_USE; + } + for (Vehicle *rv : Vehicle::Iterate()) { + if (res_type == DEPOT_RESERVATION_FULL_STOPPED_VEH) break; + if (rv->IsInDepot() && ta.Contains(rv->tile) && rv->First() != v) { + assert(rv->type == v->type); + [[maybe_unused]] DiagDirection diag_dir = DirToDiagDir(rv->direction); + assert(DiagDirToAxis(DirToDiagDir(v->direction)) == DiagDirToAxis(diag_dir)); + if (is_facing_south == IsDiagDirFacingSouth(DirToDiagDir(rv->direction))) { + res_type = (rv->vehstatus & VS_STOPPED) ? + DEPOT_RESERVATION_FULL_STOPPED_VEH : DEPOT_RESERVATION_IN_USE; + } + } + } + SetDepotReservation(t, res_type, is_facing_south); + } + break; + } + + case VEH_SHIP: + SetDepotReservation(v->tile, res_type); + break; + + case VEH_TRAIN: { + DiagDirection dir = GetRailDepotDirection(v->tile); + SetDepotReservation(GetPlatformExtremeTile(v->tile, dir), res_type); + SetDepotReservation(GetPlatformExtremeTile(v->tile, ReverseDiagDir(dir)), res_type); + break; + } + + case VEH_AIRCRAFT: + break; + + default: NOT_REACHED(); + } } diff --git a/src/depot_base.h b/src/depot_base.h index 1d8330fc74035..246ec50ffa755 100644 --- a/src/depot_base.h +++ b/src/depot_base.h @@ -11,12 +11,26 @@ #define DEPOT_BASE_H #include "depot_map.h" +#include "viewport_type.h" #include "core/pool_type.hpp" #include "timer/timer_game_calendar.h" +#include "rail_type.h" +#include "road_type.h" -typedef Pool DepotPool; +static const DepotID MAX_DEPOTS = 64000; +/** + * For build_vehicle_window, each vehicle type needs its own unique value. + * So we need some special indexes: MAX_DEPOTS + VEH_TYPE_XXX. + * @see GetBuildVehicleWindowNumber + */ +static_assert(MAX_DEPOTS + VEH_COMPANY_END - 1 <= INVALID_DEPOT); + +typedef Pool DepotPool; extern DepotPool _depot_pool; +class CommandCost; +struct Vehicle; + struct Depot : DepotPool::PoolItem<&_depot_pool> { /* DepotID index member of DepotPool is 2 bytes. */ uint16_t town_cn; ///< The N-1th depot for this town (consecutive number) @@ -25,14 +39,38 @@ struct Depot : DepotPool::PoolItem<&_depot_pool> { std::string name; TimerGameCalendar::Date build_date; ///< Date of construction - Depot(TileIndex xy = INVALID_TILE) : xy(xy) {} + VehicleType veh_type; ///< Vehicle type of the depot. + Owner owner; ///< Owner of the depot. + uint8_t delete_ctr; ///< Delete counter. If greater than 0 then it is decremented until it reaches 0; the depot is then deleted. + ViewportSign sign; ///< NOSAVE: Dimensions of sign + Station *station; ///< For aircraft, station associated with this hangar. + + union { + RoadTypes road_types; + RailTypes rail_types; + AirTypes air_types; + } r_types; + + TileArea ta; + std::vector depot_tiles; + + Depot(TileIndex xy = INVALID_TILE, VehicleType type = VEH_INVALID, Owner owner = INVALID_OWNER, Station *station = nullptr) : + xy(xy), + veh_type(type), + owner(owner), + station(station), + ta(xy, 1, 1) {} + ~Depot(); static inline Depot *GetByTile(TileIndex tile) { + assert(Depot::IsValidID(GetDepotIndex(tile))); return Depot::Get(GetDepotIndex(tile)); } + TileIndex GetBestDepotTile(Vehicle *v) const; + /** * Is the "type" of depot the same as the given depot, * i.e. are both a rail, road or ship depots? @@ -41,8 +79,35 @@ struct Depot : DepotPool::PoolItem<&_depot_pool> { */ inline bool IsOfType(const Depot *d) const { - return GetTileType(d->xy) == GetTileType(this->xy); + return d->veh_type == this->veh_type; } + + /** + * Check whether the depot currently is in use; in use means + * that it is not scheduled for deletion and that it still has + * a building on the map. Otherwise the building is demolished + * and the depot awaits to be deleted. + * @return true iff still in use + * @see Depot::Disuse + * @see Depot::Reuse + */ + inline bool IsInUse() const + { + return this->delete_ctr == 0; + } + + void Reuse(TileIndex xy); + void Disuse(); + void UpdateVirtCoord(); + + /* Check we can add some tiles to this depot. */ + CommandCost BeforeAddTiles(TileArea ta); + + /* Add some tiles to this depot and rescan area for depot_tiles. */ + void AfterAddRemove(TileArea ta, bool adding); + + /* Rescan depot_tiles. Done after AfterAddRemove and SaveLoad. */ + void RescanDepotTiles(); }; #endif /* DEPOT_BASE_H */ diff --git a/src/depot_cmd.cpp b/src/depot_cmd.cpp index 294de69e32ea7..1364800dd82b7 100644 --- a/src/depot_cmd.cpp +++ b/src/depot_cmd.cpp @@ -17,6 +17,10 @@ #include "vehiclelist.h" #include "window_func.h" #include "depot_cmd.h" +#include "strings_func.h" +#include "landscape.h" +#include "viewport_kdtree.h" +#include "timer/timer_game_tick.h" #include "table/strings.h" @@ -48,7 +52,7 @@ CommandCost CmdRenameDepot(DoCommandFlag flags, DepotID depot_id, const std::str Depot *d = Depot::GetIfValid(depot_id); if (d == nullptr) return CMD_ERROR; - CommandCost ret = CheckTileOwnership(d->xy); + CommandCost ret = CheckOwnership(d->owner); if (ret.Failed()) return ret; bool reset = text.empty(); @@ -59,6 +63,8 @@ CommandCost CmdRenameDepot(DoCommandFlag flags, DepotID depot_id, const std::str } if (flags & DC_EXEC) { + /* _viewport_sign_kdtree does not need to be updated, only in-use depots can be renamed */ + if (reset) { d->name.clear(); MakeDefaultName(d); @@ -68,11 +74,135 @@ CommandCost CmdRenameDepot(DoCommandFlag flags, DepotID depot_id, const std::str /* Update the orders and depot */ SetWindowClassesDirty(WC_VEHICLE_ORDERS); - SetWindowDirty(WC_VEHICLE_DEPOT, d->xy); + SetWindowDirty(WC_VEHICLE_DEPOT, d->index); /* Update the depot list */ - VehicleType vt = GetDepotVehicleType(d->xy); - SetWindowDirty(GetWindowClassForVehicleType(vt), VehicleListIdentifier(VL_DEPOT_LIST, vt, GetTileOwner(d->xy), d->index).Pack()); + SetWindowDirty(GetWindowClassForVehicleType(d->veh_type), VehicleListIdentifier(VL_DEPOT_LIST, d->veh_type, d->owner, d->index).Pack()); + } + return CommandCost(); +} + +/** Update the virtual coords needed to draw the depot sign. */ +void Depot::UpdateVirtCoord() +{ + Point pt = RemapCoords2(TileX(this->xy) * TILE_SIZE, TileY(this->xy) * TILE_SIZE); + + pt.y -= 32 * ZOOM_BASE; + + SetDParam(0, this->veh_type); + SetDParam(1, this->index); + this->sign.UpdatePosition(pt.x, pt.y, STR_VIEWPORT_DEPOT, STR_VIEWPORT_DEPOT_TINY); + + SetWindowDirty(WC_VEHICLE_DEPOT, this->index); +} + +/** Update the virtual coords needed to draw the depot sign for all depots. */ +void UpdateAllDepotVirtCoords() +{ + /* Only demolished depots have signs. */ + for (Depot *d : Depot::Iterate()) if (!d->IsInUse()) d->UpdateVirtCoord(); +} + +/** + * Find a demolished depot close to a tile. + * @param ta Tile area to search for. + * @param type Depot type. + * @param cid Previous owner of the depot. + * @return The index of a demolished nearby depot, or INVALID_DEPOT if none. + */ +DepotID FindDeletedDepotCloseTo(TileArea ta, VehicleType type, CompanyID cid) +{ + for (Depot *depot : Depot::Iterate()) { + if (depot->IsInUse() || depot->veh_type != type || depot->owner != cid) continue; + if (ta.Contains(depot->xy)) return depot->index; + } + + return INVALID_DEPOT; +} + +void OnTick_Depot() +{ + if (_game_mode == GM_EDITOR) return; + + /* Clean up demolished depots. */ + for (Depot *d : Depot::Iterate()) { + if (d->IsInUse()) continue; + if ((TimerGameTick::counter + d->index) % Ticks::DEPOT_REMOVAL_TICKS != 0) continue; + if (--d->delete_ctr != 0) continue; + delete d; + } +} + + +/** + * Look for or check depot to join to, building a new one if necessary. + * @param ta The area of the new depot. + * @param veh_type The vehicle type of the new depot. + * @param join_to DepotID of the depot to join to. + * If INVALID_DEPOT, look whether it is possible to join to an existing depot. + * If NEW_DEPOT, directly create a new depot. + * @param depot The pointer to the depot. + * @param adjacent Whether adjacent depots are allowed + * @return command cost with the error or 'okay' + */ +CommandCost FindJoiningDepot(TileArea ta, VehicleType veh_type, DepotID &join_to, Depot *&depot, bool adjacent, DoCommandFlag flags) +{ + /* Look for a joining depot if needed. */ + if (join_to == INVALID_DEPOT) { + assert(depot == nullptr); + DepotID closest_depot = INVALID_DEPOT; + + TileArea check_area(ta); + check_area.Expand(1); + + /* Check around to see if there's any depot there. */ + for (TileIndex tile_cur : check_area) { + if (IsValidTile(tile_cur) && IsDepotTile(tile_cur)) { + Depot *d = Depot::GetByTile(tile_cur); + assert(d != nullptr); + if (d->veh_type != veh_type) continue; + if (d->owner != _current_company) continue; + + if (closest_depot == INVALID_DEPOT) { + closest_depot = d->index; + } else if (closest_depot != d->index) { + if (!adjacent) return_cmd_error(STR_ERROR_ADJOINS_MORE_THAN_ONE_EXISTING_DEPOT); + } + } + } + + if (closest_depot == INVALID_DEPOT) { + /* Check for close unused depots. */ + check_area.Expand(7); // total distance of 8 + closest_depot = FindDeletedDepotCloseTo(check_area, veh_type, _current_company); + } + + if (closest_depot != INVALID_DEPOT) { + assert(Depot::IsValidID(closest_depot)); + depot = Depot::Get(closest_depot); + } + + join_to = depot == nullptr ? NEW_DEPOT : depot->index; } + + /* At this point, join_to is NEW_DEPOT or a valid DepotID. */ + + if (join_to == NEW_DEPOT) { + /* New depot needed. */ + if (!Depot::CanAllocateItem()) return CMD_ERROR; + if (flags & DC_EXEC) { + depot = new Depot(ta.tile, veh_type, _current_company); + depot->build_date = TimerGameCalendar::date; + } + } else { + /* Joining depots. */ + assert(Depot::IsValidID(join_to)); + depot = Depot::Get(join_to); + assert(depot->owner == _current_company); + assert(depot->veh_type == veh_type); + if (!depot->IsInUse() && (flags & DC_EXEC)) depot->Reuse(ta.tile); + return depot->BeforeAddTiles(ta); + } + return CommandCost(); } diff --git a/src/depot_func.h b/src/depot_func.h index 208e02110c9f3..d1890c87285a3 100644 --- a/src/depot_func.h +++ b/src/depot_func.h @@ -10,13 +10,16 @@ #ifndef DEPOT_FUNC_H #define DEPOT_FUNC_H +#include "depot_type.h" #include "vehicle_type.h" #include "slope_func.h" +#include "command_type.h" -void ShowDepotWindow(TileIndex tile, VehicleType type); +void ShowDepotWindow(DepotID depot_id); void InitDepotWindowBlockSizes(); void DeleteDepotHighlightOfVehicle(const Vehicle *v); +void UpdateAllDepotVirtCoords(); /** * Find out if the slope of the tile is suitable to build a depot of given direction @@ -33,4 +36,13 @@ inline bool CanBuildDepotByTileh(DiagDirection direction, Slope tileh) return IsSteepSlope(tileh) ? (tileh & entrance_corners) == entrance_corners : (tileh & entrance_corners) != 0; } +struct Depot; +CommandCost FindJoiningDepot(TileArea ta, VehicleType veh_type, DepotID &join_to, Depot *&depot, bool adjacent, DoCommandFlag flags); + +using DepotPickerCmdProc = std::function; +void ShowSelectDepotIfNeeded(TileArea ta, DepotPickerCmdProc proc, VehicleType veh_type); + +struct Window; +void CheckRedrawDepotHighlight(const Window *w, VehicleType veh_type); + #endif /* DEPOT_FUNC_H */ diff --git a/src/depot_gui.cpp b/src/depot_gui.cpp index e62915fe2c5bb..d31eb47d8a59c 100644 --- a/src/depot_gui.cpp +++ b/src/depot_gui.cpp @@ -29,9 +29,12 @@ #include "zoom_func.h" #include "error.h" #include "depot_cmd.h" +#include "station_base.h" #include "train_cmd.h" #include "vehicle_cmd.h" #include "core/geometry_func.hpp" +#include "depot_func.h" +#include "train_placement.h" #include "widgets/depot_widget.h" @@ -78,6 +81,7 @@ static constexpr NWidgetPart _nested_train_depot_widgets[] = { NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_D_BUILD), SetDataTip(0x0, STR_NULL), SetFill(1, 1), SetResize(1, 0), NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_D_CLONE), SetDataTip(0x0, STR_NULL), SetFill(1, 1), SetResize(1, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_D_HIGHLIGHT), SetDataTip(STR_BUTTON_HIGHLIGHT_DEPOT, STR_TOOLTIP_HIGHLIGHT_DEPOT), SetFill(1, 1), SetResize(1, 0), NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_D_VEHICLE_LIST), SetDataTip(0x0, STR_NULL), SetAspect(WidgetDimensions::ASPECT_VEHICLE_ICON), SetFill(0, 1), NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_D_STOP_ALL), SetDataTip(SPR_FLAG_VEH_STOPPED, STR_NULL), SetAspect(WidgetDimensions::ASPECT_VEHICLE_FLAG), SetFill(0, 1), NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_D_START_ALL), SetDataTip(SPR_FLAG_VEH_RUNNING, STR_NULL), SetAspect(WidgetDimensions::ASPECT_VEHICLE_FLAG), SetFill(0, 1), @@ -259,6 +263,7 @@ struct DepotWindow : Window { VehicleType type; bool generate_list; WidgetID hovered_widget; ///< Index of the widget being hovered during drag/drop. -1 if no drag is in progress. + std::vector problematic_vehicles; ///< Vector associated to vehicle_list, with a value of true for vehicles that cannot leave the depot. VehicleList vehicle_list; VehicleList wagon_list; uint unitnumber_digits; @@ -266,15 +271,17 @@ struct DepotWindow : Window { Scrollbar *hscroll; ///< Only for trains. Scrollbar *vscroll; - DepotWindow(WindowDesc &desc, TileIndex tile, VehicleType type) : Window(desc) + DepotWindow(WindowDesc &desc, DepotID depot_id) : Window(desc) { - assert(IsCompanyBuildableVehicleType(type)); // ensure that we make the call with a valid type + assert(Depot::IsValidID(depot_id)); + Depot *depot = Depot::Get(depot_id); + assert(IsCompanyBuildableVehicleType(depot->veh_type)); this->sel = INVALID_VEHICLE; this->vehicle_over = INVALID_VEHICLE; this->generate_list = true; this->hovered_widget = -1; - this->type = type; + this->type = depot->veh_type; this->num_columns = 1; // for non-trains this gets set in FinishInitNested() this->unitnumber_digits = 2; @@ -282,23 +289,24 @@ struct DepotWindow : Window { this->hscroll = (this->type == VEH_TRAIN ? this->GetScrollbar(WID_D_H_SCROLL) : nullptr); this->vscroll = this->GetScrollbar(WID_D_V_SCROLL); /* Don't show 'rename button' of aircraft hangar */ - this->GetWidget(WID_D_SHOW_RENAME)->SetDisplayedPlane(type == VEH_AIRCRAFT ? SZSP_NONE : 0); + this->GetWidget(WID_D_SHOW_RENAME)->SetDisplayedPlane(this->type == VEH_AIRCRAFT ? SZSP_NONE : 0); /* Only train depots have a horizontal scrollbar and a 'sell chain' button */ - if (type == VEH_TRAIN) this->GetWidget(WID_D_MATRIX)->widget_data = 1 << MAT_COL_START; - this->GetWidget(WID_D_SHOW_H_SCROLL)->SetDisplayedPlane(type == VEH_TRAIN ? 0 : SZSP_HORIZONTAL); - this->GetWidget(WID_D_SHOW_SELL_CHAIN)->SetDisplayedPlane(type == VEH_TRAIN ? 0 : SZSP_NONE); - this->SetupWidgetData(type); - this->FinishInitNested(tile); + if (this->type == VEH_TRAIN) this->GetWidget(WID_D_MATRIX)->widget_data = 1 << MAT_COL_START; + this->GetWidget(WID_D_SHOW_H_SCROLL)->SetDisplayedPlane(this->type == VEH_TRAIN ? 0 : SZSP_HORIZONTAL); + this->GetWidget(WID_D_SHOW_SELL_CHAIN)->SetDisplayedPlane(this->type == VEH_TRAIN ? 0 : SZSP_NONE); + this->SetupWidgetData(this->type); + this->FinishInitNested(depot_id); - this->owner = GetTileOwner(tile); + this->owner = depot->owner; OrderBackup::Reset(); } void Close([[maybe_unused]] int data = 0) override { CloseWindowById(WC_BUILD_VEHICLE, this->window_number); - CloseWindowById(GetWindowClassForVehicleType(this->type), VehicleListIdentifier(VL_DEPOT_LIST, this->type, this->owner, this->GetDepotIndex()).Pack(), false); + CloseWindowById(GetWindowClassForVehicleType(this->type), VehicleListIdentifier(VL_DEPOT_LIST, this->type, this->owner, this->window_number).Pack(), false); OrderBackup::Reset(this->window_number); + SetViewportHighlightDepot(this->window_number, false); this->Window::Close(); } @@ -405,6 +413,11 @@ struct DepotWindow : Window { for (; num < maxval; ir = ir.Translate(0, this->resize.step_height)) { // Draw the rows Rect cell = ir; /* Keep track of horizontal cells */ for (uint i = 0; i < this->num_columns && num < maxval; i++, num++) { + /* Draw a dark red background if train cannot be placed. */ + if (this->type == VEH_TRAIN && this->problematic_vehicles[num] == 1) { + GfxFillRect(cell.left, cell.top, cell.right, cell.bottom, PC_DARK_GREY); + } + /* Draw all vehicles in the current row */ const Vehicle *v = this->vehicle_list[num]; this->DrawVehicleInDepot(v, cell); @@ -426,7 +439,7 @@ struct DepotWindow : Window { if (widget != WID_D_CAPTION) return; SetDParam(0, this->type); - SetDParam(1, this->GetDepotIndex()); + SetDParam(1, (this->type == VEH_AIRCRAFT) ? Depot::Get(this->window_number)->station->index : this->window_number); } struct GetDepotVehiclePtData { @@ -708,12 +721,23 @@ struct DepotWindow : Window { void OnPaint() override { + extern DepotID _viewport_highlight_depot; + this->SetWidgetLoweredState(WID_D_HIGHLIGHT, _viewport_highlight_depot == this->window_number); + if (this->generate_list) { /* Generate the vehicle list * It's ok to use the wagon pointers for non-trains as they will be ignored */ BuildDepotVehicleList(this->type, this->window_number, &this->vehicle_list, &this->wagon_list); this->generate_list = false; DepotSortList(&this->vehicle_list); + if (this->type == VEH_TRAIN) { + this->problematic_vehicles.clear(); + TrainPlacement tp; + for (uint num = 0; num < this->vehicle_list.size(); ++num) { + const Vehicle *v = this->vehicle_list[num]; + this->problematic_vehicles.push_back(!tp.CanFindAppropriatePlatform(Train::From(v), false)); + } + } uint new_unitnumber_digits = GetUnitNumberDigits(this->vehicle_list); /* Only increase the size; do not decrease to prevent constant changes */ @@ -742,8 +766,7 @@ struct DepotWindow : Window { } /* Setup disabled buttons. */ - TileIndex tile = this->window_number; - this->SetWidgetsDisabledState(!IsTileOwner(tile, _local_company), + this->SetWidgetsDisabledState(this->owner != _local_company, WID_D_STOP_ALL, WID_D_START_ALL, WID_D_SELL, @@ -759,6 +782,8 @@ struct DepotWindow : Window { void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override { + TileIndex tile = Depot::Get(this->window_number)->xy; + switch (widget) { case WID_D_MATRIX: // List this->DepotClick(pt.x, pt.y); @@ -789,20 +814,25 @@ struct DepotWindow : Window { if (_ctrl_pressed) { ShowExtraViewportWindow(this->window_number); } else { - ScrollMainWindowToTile(this->window_number); + ScrollMainWindowToTile(tile); } break; case WID_D_RENAME: // Rename button SetDParam(0, this->type); - SetDParam(1, Depot::GetByTile((TileIndex)this->window_number)->index); + SetDParam(1, this->window_number); ShowQueryString(STR_DEPOT_NAME, STR_DEPOT_RENAME_DEPOT_CAPTION, MAX_LENGTH_DEPOT_NAME_CHARS, this, CS_ALPHANUMERAL, QSF_ENABLE_DEFAULT | QSF_LEN_IN_CHARS); break; + case WID_D_HIGHLIGHT: + this->SetWidgetDirty(WID_D_HIGHLIGHT); + SetViewportHighlightDepot(this->window_number, !this->IsWidgetLowered(WID_D_HIGHLIGHT)); + break; + case WID_D_STOP_ALL: case WID_D_START_ALL: { VehicleListIdentifier vli(VL_DEPOT_LIST, this->type, this->owner); - Command::Post(this->window_number, widget == WID_D_START_ALL, false, vli); + Command::Post(STR_ERROR_CAN_T_START_STOP_VEHICLES, tile, widget == WID_D_START_ALL, false, vli); break; } @@ -810,7 +840,7 @@ struct DepotWindow : Window { /* Only open the confirmation window if there are anything to sell */ if (!this->vehicle_list.empty() || !this->wagon_list.empty()) { SetDParam(0, this->type); - SetDParam(1, this->GetDepotIndex()); + SetDParam(1, this->window_number); ShowQuery( STR_DEPOT_CAPTION, STR_DEPOT_SELL_CONFIRMATION_TEXT, @@ -821,11 +851,11 @@ struct DepotWindow : Window { break; case WID_D_VEHICLE_LIST: - ShowVehicleListWindow(GetTileOwner(this->window_number), this->type, (TileIndex)this->window_number); + ShowVehicleListWindowDepot(this->owner, this->type, this->window_number); break; case WID_D_AUTOREPLACE: - Command::Post(this->window_number, this->type); + Command::Post(STR_ERROR_CAN_T_REPLACE_VEHICLES, tile, this->type); break; } @@ -836,7 +866,7 @@ struct DepotWindow : Window { if (!str.has_value()) return; /* Do depot renaming */ - Command::Post(STR_ERROR_CAN_T_RENAME_DEPOT, this->GetDepotIndex(), *str); + Command::Post(STR_ERROR_CAN_T_RENAME_DEPOT, this->window_number, *str); } bool OnRightClick([[maybe_unused]] Point pt, WidgetID widget) override @@ -902,10 +932,10 @@ struct DepotWindow : Window { { if (_ctrl_pressed) { /* Share-clone, do not open new viewport, and keep tool active */ - Command::Post(STR_ERROR_CAN_T_BUY_TRAIN + v->type, this->window_number, v->index, true); + Command::Post(STR_ERROR_CAN_T_BUY_TRAIN + v->type, Depot::Get(this->window_number)->xy, v->index, true); } else { /* Copy-clone, open viewport for new vehicle, and deselect the tool (assume player wants to change things on new vehicle) */ - if (Command::Post(STR_ERROR_CAN_T_BUY_TRAIN + v->type, CcCloneVehicle, this->window_number, v->index, false)) { + if (Command::Post(STR_ERROR_CAN_T_BUY_TRAIN + v->type, CcCloneVehicle, Depot::Get(this->window_number)->xy, v->index, false)) { ResetObjectToPlace(); } } @@ -1111,43 +1141,34 @@ struct DepotWindow : Window { return ES_NOT_HANDLED; } - - /** - * Gets the DepotID of the current window. - * In the case of airports, this is the station ID. - * @return Depot or station ID of this window. - */ - inline uint16_t GetDepotIndex() const - { - return (this->type == VEH_AIRCRAFT) ? ::GetStationIndex(this->window_number) : ::GetDepotIndex(this->window_number); - } }; static void DepotSellAllConfirmationCallback(Window *win, bool confirmed) { if (confirmed) { - DepotWindow *w = (DepotWindow*)win; - TileIndex tile = w->window_number; - VehicleType vehtype = w->type; - Command::Post(tile, vehtype); + assert(Depot::IsValidID(win->window_number)); + Depot *d = Depot::Get(win->window_number); + if (!d->IsInUse()) return; + Command::Post(d->xy, d->veh_type); } } /** - * Opens a depot window - * @param tile The tile where the depot/hangar is located - * @param type The type of vehicles in the depot + * Opens a depot window. + * @param depot_id Index of the depot. */ -void ShowDepotWindow(TileIndex tile, VehicleType type) +void ShowDepotWindow(DepotID depot_id) { - if (BringWindowToFrontById(WC_VEHICLE_DEPOT, tile) != nullptr) return; + assert(Depot::IsValidID(depot_id)); + if (BringWindowToFrontById(WC_VEHICLE_DEPOT, depot_id) != nullptr) return; - switch (type) { + Depot *d = Depot::Get(depot_id); + switch (d->veh_type) { default: NOT_REACHED(); - case VEH_TRAIN: new DepotWindow(_train_depot_desc, tile, type); break; - case VEH_ROAD: new DepotWindow(_road_depot_desc, tile, type); break; - case VEH_SHIP: new DepotWindow(_ship_depot_desc, tile, type); break; - case VEH_AIRCRAFT: new DepotWindow(_aircraft_depot_desc, tile, type); break; + case VEH_TRAIN: new DepotWindow(_train_depot_desc, depot_id); break; + case VEH_ROAD: new DepotWindow(_road_depot_desc, depot_id); break; + case VEH_SHIP: new DepotWindow(_ship_depot_desc, depot_id); break; + case VEH_AIRCRAFT: new DepotWindow(_aircraft_depot_desc, depot_id); break; } } @@ -1164,8 +1185,356 @@ void DeleteDepotHighlightOfVehicle(const Vehicle *v) */ if (_special_mouse_mode != WSM_DRAGDROP) return; - w = dynamic_cast(FindWindowById(WC_VEHICLE_DEPOT, v->tile)); + /* For shadows and rotors, do nothing. */ + if (v->type == VEH_AIRCRAFT && !Aircraft::From(v)->IsNormalAircraft()) return; + + assert(IsDepotTile(v->tile)); + w = dynamic_cast(FindWindowById(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile))); if (w != nullptr) { if (w->sel == v->index) ResetObjectToPlace(); } } + +static std::vector _depots_nearby_list; + +/** Structure with user-data for AddNearbyDepot. */ +struct AddNearbyDepotData { + TileArea search_area; ///< Search area. + VehicleType type; ///< Vehicle type of the searched depots. +}; + +/** + * Add depot on this tile to _depots_nearby_list if it's fully within the + * depot spread. + * @param tile Tile just being checked + * @param user_data Pointer to TileArea context + */ +static bool AddNearbyDepot(TileIndex tile, void *user_data) +{ + AddNearbyDepotData *andd = (AddNearbyDepotData *)user_data; + + /* Check if own depot and if we stay within station spread */ + if (!IsDepotTile(tile)) return false; + Depot *dep = Depot::GetByTile(tile); + if (dep->owner != _local_company || dep->veh_type != andd->type || + (find(_depots_nearby_list.begin(), _depots_nearby_list.end(), dep->index) != _depots_nearby_list.end())) { + return false; + } + + CommandCost cost = dep->BeforeAddTiles(andd->search_area); + if (cost.Succeeded()) { + _depots_nearby_list.push_back(dep->index); + } + + return false; +} + +/** + * Circulate around the to-be-built depot to find depots we could join. + * Make sure that only depots are returned where joining wouldn't exceed + * depot spread and are our own depot. + * @param ta Base tile area of the to-be-built depot + * @param veh_type Vehicle type depots to look for + * @param distant_join Search for adjacent depots (false) or depots fully + * within depot spread + */ +static const Depot *FindDepotsNearby(TileArea ta, VehicleType veh_type, bool distant_join) +{ + _depots_nearby_list.clear(); + _depots_nearby_list.push_back(NEW_DEPOT); + + /* Check the inside, to return, if we sit on another big depot */ + Depot *depot; + for (TileIndex t : ta) { + if (!IsDepotTile(t)) continue; + depot = Depot::GetByTile(t); + if (depot->veh_type == veh_type && depot->owner == _current_company) return depot; + } + + /* Only search tiles where we have a chance to stay within the depot spread. + * The complete check needs to be done in the callback as we don't know the + * extent of the found depot, yet. */ + if (distant_join && std::min(ta.w, ta.h) >= _settings_game.depot.depot_spread) return nullptr; + uint max_dist = distant_join ? _settings_game.depot.depot_spread - std::min(ta.w, ta.h) : 1; + + AddNearbyDepotData andd; + andd.search_area = ta; + andd.type = veh_type; + + TileIndex tile = TileAddByDir(andd.search_area.tile, DIR_N); + CircularTileSearch(&tile, max_dist, ta.w, ta.h, AddNearbyDepot, &andd); + + /* Add reusable depots. */ + ta.Expand(8); + for (Depot *d : Depot::Iterate()) { + if (d->IsInUse()) continue; + if (d->veh_type != veh_type || d->owner != _current_company) continue; + if (!ta.Contains(d->xy)) continue; + _depots_nearby_list.push_back(d->index); + } + + return nullptr; +} + +/** + * Check whether we need to show the depot selection window. + * @param ta Tile area of the to-be-built depot. + * @param proc The procedure for the depot picker. + * @param veh_type the vehicle type of the depot. + * @return whether we need to show the depot selection window. + */ +static bool DepotJoinerNeeded(TileArea ta, VehicleType veh_type) +{ + /* If a window is already opened and we didn't ctrl-click, + * return true (i.e. just flash the old window) */ + Window *selection_window = FindWindowById(WC_SELECT_DEPOT, veh_type); + if (selection_window != nullptr) { + /* Abort current distant-join and start new one */ + selection_window->Close(); + UpdateTileSelection(); + } + + /* Only show the popup if we press ctrl. */ + if (!_ctrl_pressed) return false; + + /* Test for adjacent depot or depot below selection. + * If adjacent-stations is disabled and we are building next to a depot, do not show the selection window. + * but join the other depot immediately. */ + return FindDepotsNearby(ta, veh_type, false) == nullptr; +} + +/** + * Window for selecting depots to (distant) join to. + */ +struct SelectDepotWindow : Window { + DepotPickerCmdProc select_depot_proc; ///< The procedure params + TileArea area; ///< Location of new depot + Scrollbar *vscroll; ///< Vertical scrollbar for the window + + SelectDepotWindow(WindowDesc &desc, TileArea ta, DepotPickerCmdProc& proc, VehicleType veh_type) : + Window(desc), + select_depot_proc(std::move(proc)), + area(ta) + { + this->CreateNestedTree(); + this->vscroll = this->GetScrollbar(WID_JD_SCROLLBAR); + this->FinishInitNested(veh_type); + this->OnInvalidateData(0); + + _thd.freeze = true; + } + + ~SelectDepotWindow() + { + SetViewportHighlightDepot(INVALID_DEPOT, true); + + _thd.freeze = false; + } + + void UpdateWidgetSize(int widget, Dimension &size, const Dimension &padding, [[maybe_unused]] Dimension &fill, Dimension &resize) override + { + if (widget != WID_JD_PANEL) return; + + resize.height = GetCharacterHeight(FS_NORMAL); + size.height = 5 * resize.height + padding.height; + + /* Determine the widest string. */ + Dimension d = GetStringBoundingBox(STR_JOIN_DEPOT_CREATE_SPLITTED_DEPOT); + for (const auto &depot : _depots_nearby_list) { + if (depot == NEW_DEPOT) continue; + const Depot *dep = Depot::Get(depot); + SetDParam(0, this->window_number); + SetDParam(1, dep->index); + d = maxdim(d, GetStringBoundingBox(STR_DEPOT_LIST_DEPOT)); + } + + d.height = 5 * resize.height; + d.width += padding.width; + d.height += padding.height; + size = d; + } + + void DrawWidget(const Rect &r, int widget) const override + { + if (widget != WID_JD_PANEL) return; + + Rect tr = r.Shrink(WidgetDimensions::scaled.framerect); + + auto [first, last] = this->vscroll->GetVisibleRangeIterators(_depots_nearby_list); + for (auto it = first; it != last; ++it, tr.top += this->resize.step_height) { + if (*it == NEW_DEPOT) { + DrawString(tr, STR_JOIN_DEPOT_CREATE_SPLITTED_DEPOT); + } else { + SetDParam(0, this->window_number); + SetDParam(1, *it); + [[maybe_unused]] Depot *depot = Depot::GetIfValid(*it); + assert(depot != nullptr); + DrawString(tr, STR_DEPOT_LIST_DEPOT); + } + } + } + + void OnClick(Point pt, int widget, [[maybe_unused]] int click_count) override + { + if (widget != WID_JD_PANEL) return; + + auto it = this->vscroll->GetScrolledItemFromWidget(_depots_nearby_list, pt.y, this, WID_JD_PANEL, WidgetDimensions::scaled.framerect.top); + if (it == _depots_nearby_list.end()) return; + + /* Execute stored Command */ + this->select_depot_proc(*it); + + InvalidateWindowData(WC_SELECT_DEPOT, window_number); + this->Close(); + } + + void OnRealtimeTick([[maybe_unused]] uint delta_ms) override + { + if (_thd.dirty & 2) { + _thd.dirty &= ~2; + this->SetDirty(); + } + } + + void OnResize() override + { + this->vscroll->SetCapacityFromWidget(this, WID_JD_PANEL, WidgetDimensions::scaled.framerect.Vertical()); + } + + /** + * Some data on this window has become invalid. + * @param data Information about the changed data. + * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details. + */ + void OnInvalidateData([[maybe_unused]] int data = 0, bool gui_scope = true) override + { + if (!gui_scope) return; + FindDepotsNearby(this->area, (VehicleType)this->window_number, true); + this->vscroll->SetCount((uint)_depots_nearby_list.size()); + this->SetDirty(); + } + + void OnMouseOver(Point pt, int widget) override + { + if (widget != WID_JD_PANEL) { + SetViewportHighlightDepot(INVALID_DEPOT, true); + return; + } + + /* Highlight depot under cursor */ + auto it = this->vscroll->GetScrolledItemFromWidget(_depots_nearby_list, pt.y, this, WID_JD_PANEL, WidgetDimensions::scaled.framerect.top); + SetViewportHighlightDepot(*it, true); + } + +}; + +static const NWidgetPart _nested_select_depot_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN), + NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_JD_CAPTION), SetDataTip(STR_JOIN_DEPOT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_JD_PANEL), SetResize(1, 0), SetScrollbar(WID_JD_SCROLLBAR), EndContainer(), + NWidget(NWID_VERTICAL), + NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_JD_SCROLLBAR), + NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN), + EndContainer(), + EndContainer(), +}; + +static WindowDesc _select_depot_desc( + WDP_AUTO, "build_depot_join", 200, 180, + WC_SELECT_DEPOT, WC_NONE, + WDF_CONSTRUCTION, + _nested_select_depot_widgets +); + +/** + * Show the depot selection window when needed. If not, build the depot. + * @param ta Area to build the depot in. + * @param proc Details of the procedure for the depot picker. + * @param veh_type Vehicle type of the depot to be built. + */ +void ShowSelectDepotIfNeeded(TileArea ta, DepotPickerCmdProc proc, VehicleType veh_type) +{ + if (DepotJoinerNeeded(ta, veh_type)) { + if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace(); + new SelectDepotWindow(_select_depot_desc, ta, proc, veh_type); + } else { + proc(INVALID_DEPOT); + } +} + +/** + * Find depots adjacent to the current tile highlight area, so that all depot tiles + * can be highlighted. + * @param v_type Vehicle type to check. + */ +static void HighlightSingleAdjacentDepot(VehicleType v_type) +{ + /* With distant join we don't know which depot will be selected, so don't show any */ + if (_ctrl_pressed) { + SetViewportHighlightDepot(INVALID_DEPOT, true); + return; + } + + /* Tile area for TileHighlightData */ + TileArea location(TileVirtXY(_thd.pos.x, _thd.pos.y), _thd.size.x / TILE_SIZE - 1, _thd.size.y / TILE_SIZE - 1); + + /* If the current tile is already a depot, then it must be the nearest depot. */ + if (IsDepotTypeTile(location.tile, (TransportType)v_type) && + GetTileOwner(location.tile) == _local_company) { + SetViewportHighlightDepot(GetDepotIndex(location.tile), true); + return; + } + + /* Extended area by one tile */ + uint x = TileX(location.tile); + uint y = TileY(location.tile); + + int max_c = 1; + TileArea ta(TileXY(std::max(0, x - max_c), std::max(0, y - max_c)), TileXY(std::min(Map::MaxX(), x + location.w + max_c), std::min(Map::MaxY(), y + location.h + max_c))); + + DepotID adjacent = INVALID_DEPOT; + + for (TileIndex tile : ta) { + if (IsDepotTile(tile) && GetTileOwner(tile) == _local_company) { + Depot *depot = Depot::GetByTile(tile); + if (depot == nullptr) continue; + if (depot->veh_type != v_type) continue; + if (adjacent != INVALID_DEPOT && depot->index != adjacent) { + /* Multiple nearby, distant join is required. */ + adjacent = INVALID_DEPOT; + break; + } + adjacent = depot->index; + } + } + SetViewportHighlightDepot(adjacent, true); +} + +/** + * Check whether we need to redraw the depot highlight. + * If it is needed actually make the window for redrawing. + * @param w the window to check. + * @param veh_type vehicle type to check. + */ +void CheckRedrawDepotHighlight(const Window *w, VehicleType veh_type) +{ + /* Test if ctrl state changed */ + static bool _last_ctrl_pressed; + if (_ctrl_pressed != _last_ctrl_pressed) { + _thd.dirty = 0xff; + _last_ctrl_pressed = _ctrl_pressed; + } + + if (_thd.dirty & 1) { + _thd.dirty &= ~1; + w->SetDirty(); + + if (_thd.drawstyle == HT_RECT) { + HighlightSingleAdjacentDepot(veh_type); + } + } +} diff --git a/src/depot_map.h b/src/depot_map.h index 87bd91543145c..223c30a2cfd06 100644 --- a/src/depot_map.h +++ b/src/depot_map.h @@ -12,24 +12,25 @@ #include "station_map.h" +static const uint8_t DEPOT_TYPE = 0x02; + /** * Check if a tile is a depot and it is a depot of the given type. */ inline bool IsDepotTypeTile(Tile tile, TransportType type) { + if (GB(tile.m5(), 6, 2) != DEPOT_TYPE) return false; + switch (type) { default: NOT_REACHED(); case TRANSPORT_RAIL: - return IsRailDepotTile(tile); - + return IsTileType(tile, MP_RAILWAY); case TRANSPORT_ROAD: - return IsRoadDepotTile(tile); - + return IsTileType(tile, MP_ROAD); case TRANSPORT_WATER: - return IsShipDepotTile(tile); - + return IsTileType(tile, MP_WATER); case TRANSPORT_AIR: - return IsHangarTile(tile); + return IsAirportTile(tile); } } @@ -40,19 +41,26 @@ inline bool IsDepotTypeTile(Tile tile, TransportType type) */ inline bool IsDepotTile(Tile tile) { - return IsRailDepotTile(tile) || IsRoadDepotTile(tile) || IsShipDepotTile(tile) || IsHangarTile(tile); + if (GB(tile.m5(), 6, 2) != DEPOT_TYPE) return false; + TileType type = GetTileType(tile); + return type == MP_RAILWAY || type == MP_ROAD || type == MP_WATER || IsAirportTile(tile); } +extern DepotID GetHangarIndex(TileIndex t); + /** * Get the index of which depot is attached to the tile. * @param t the tile - * @pre IsRailDepotTile(t) || IsRoadDepotTile(t) || IsShipDepotTile(t) + * @pre IsDepotTile(t) * @return DepotID */ inline DepotID GetDepotIndex(Tile t) { - /* Hangars don't have a Depot class, thus store no DepotID. */ - assert(IsRailDepotTile(t) || IsRoadDepotTile(t) || IsShipDepotTile(t)); + assert(IsDepotTile(t)); + + /* Hangars don't store depot id on m2. */ + if (IsTileType(t, MP_STATION)) return GetHangarIndex(t); + return t.m2(); } @@ -73,4 +81,97 @@ inline VehicleType GetDepotVehicleType(Tile t) } } +/** Return true if a tile belongs to an extended depot. */ +static inline bool IsExtendedDepot(Tile tile) { + assert(IsValidTile(tile)); + assert(IsDepotTile(tile)); + return HasBit(tile.m5(), 5); +} + +/** Return true if a tile belongs to an extended depot. */ +static inline bool IsExtendedDepotTile(TileIndex tile) { + if (!IsValidTile(tile)) return false; + if (!IsDepotTile(tile)) return false; + return IsExtendedDepot(tile); +} + +/** + * Has this depot some vehicle servicing or stopped inside? + * @param tile tile of the depot. + * @param south_dir In case of road transport, return reservation facing south if true. + * @return The type of reservation on this tile (empty, servicing or occupied). + * @pre is a depot tile + */ +static inline DepotReservation GetDepotReservation(Tile t, bool south_dir = false) +{ + assert(IsDepotTile(t)); + if (!IsExtendedDepot(t)) return DEPOT_RESERVATION_EMPTY; + if (south_dir) { + assert(GetDepotVehicleType(t) == VEH_ROAD); + return (DepotReservation)GB(t.m6(), 4, 2); + } + return (DepotReservation)GB(t.m4(), 6, 2); +} + +/** + * Is this a platform/depot tile full with stopped vehicles? + * @param tile tile of the depot. + * @param south_dir In case of road transport, check reservation facing south if true. + * @return the type of reservation of the depot. + * @pre is a depot tile + */ +static inline bool IsDepotFullWithStoppedVehicles(TileIndex t, bool south_dir = false) +{ + assert(IsDepotTile(t)); + if (!IsExtendedDepot(t)) return false; + return GetDepotReservation(t, south_dir) == DEPOT_RESERVATION_FULL_STOPPED_VEH; +} + + +/** + * Has this depot tile/platform some vehicle inside? + * @param tile tile of the depot. + * @param south_dir In case of road transport, check reservation facing south if true. + * @return true iff depot tile/platform has no vehicle. + * @pre IsExtendedDepotTile + */ +static inline bool IsExtendedDepotEmpty(TileIndex t, bool south_dir = false) +{ + assert(IsExtendedDepotTile(t)); + return GetDepotReservation(t, south_dir) == DEPOT_RESERVATION_EMPTY; +} + +/** + * Mark whether this depot has a ship inside. + * @param tile of the depot. + * @param reservation type of reservation + * @param south_dir Whether to set south direction reservation. + * @pre tile is an extended ship depot. + */ +static inline void SetDepotReservation(Tile t, DepotReservation reservation, bool south_dir = false) +{ + assert(IsDepotTile(t)); + if (!IsExtendedDepot(t)) return; + switch (GetTileType(t)) { + default: NOT_REACHED(); + case MP_RAILWAY: + break; + case MP_ROAD: + if (south_dir) { + SB(t.m6(), 4, 2, reservation); + return; + } + break; + case MP_WATER: + assert(GetDepotReservation(t) == GetDepotReservation(GetOtherShipDepotTile(t))); + SB(Tile(GetOtherShipDepotTile(t)).m4(), 6, 2, reservation); + break; + case MP_STATION: return; + } + + SB(t.m4(), 6, 2, reservation); +} + +void UpdateExtendedDepotReservation(Vehicle *v, bool state); + #endif /* DEPOT_MAP_H */ diff --git a/src/depot_type.h b/src/depot_type.h index 4e61c1bcbd5e7..62b222289ad34 100644 --- a/src/depot_type.h +++ b/src/depot_type.h @@ -14,7 +14,18 @@ typedef uint16_t DepotID; ///< Type for the unique identifier of depots. struct Depot; static const DepotID INVALID_DEPOT = UINT16_MAX; +static const DepotID NEW_DEPOT = INVALID_DEPOT - 1; static const uint MAX_LENGTH_DEPOT_NAME_CHARS = 32; ///< The maximum length of a depot name in characters including '\0' +static const uint DEF_MAX_DEPOT_SPREAD = 12; + +/** Type of reservation of extended ship depots. */ +enum DepotReservation { + DEPOT_RESERVATION_EMPTY = 0, ///< No vehicle servicing/stopped on depot tile/platform. + DEPOT_RESERVATION_IN_USE = 1, ///< At least a vehicle is in the depot, but the depot tile is not full of stopped vehicles. + DEPOT_RESERVATION_FULL_STOPPED_VEH = 2, ///< The depot tile/platform is full with stopped vehicles. + DEPOT_RESERVATION_END +}; + #endif /* DEPOT_TYPE_H */ diff --git a/src/direction_func.h b/src/direction_func.h index c554873a0d6f1..861a8c0e33a88 100644 --- a/src/direction_func.h +++ b/src/direction_func.h @@ -108,6 +108,19 @@ inline Direction ChangeDir(Direction d, DirDiff delta) return static_cast((static_cast(d) + static_cast(delta)) % 8); } +/** + * Calculate the non-oriented difference between two Direction values + * + * @param d0 The first direction as the base + * @param d1 The second direction as the offset from the base + * @return The number of non-oriented 45 degrees difference. + */ +static inline DirDiff NonOrientedDirDifference(Direction d0, Direction d1) +{ + assert(IsValidDirection(d0)); + assert(IsValidDirection(d1)); + return std::min(DirDifference(d0, d1), DirDifference(d1, d0)); +} /** * Returns the reverse direction of the given DiagDirection @@ -276,4 +289,36 @@ inline bool IsDiagonalDirection(Direction dir) return (dir & 1) != 0; } +/** + * Checks if a given DiagDirection is facing south. + * @param diag_dir Diagonal direction to check + * @return true iff the diagonal direction is facing south. + */ +static inline bool IsDiagDirFacingSouth(DiagDirection diag_dir) +{ + return diag_dir == DIAGDIR_SE || diag_dir == DIAGDIR_SW; +} + +/** + * Rotate a diagonal direction with a given number of right angles clockwise. + * @param dir original diagonal direcion. + * @param rotate number of right angles for clockwise rotation. + * @return the original diagonal direction rotated 90 degrees clockwise as many times as indicaded by rotate. + */ +static inline Direction RotateDirection(Direction dir, DiagDirection rotate) +{ + return (Direction)((dir + 2 * rotate) % DIR_END); +} + +/** + * Rotate a direction with a given number of right angles clockwise. + * @param dir original direcion. + * @param rotate number of right angles for clockwise rotation. + * @return the original direction rotated 90 degrees clockwise as many times as indicaded by rotate. + */ +static inline DiagDirection RotateDiagDir(DiagDirection dir, DiagDirection rotate) +{ + return (DiagDirection)((dir + rotate) % DIAGDIR_END); +} + #endif /* DIRECTION_FUNC_H */ diff --git a/src/disaster_vehicle.cpp b/src/disaster_vehicle.cpp index f5b311f835ef4..82fdab5c7f16a 100644 --- a/src/disaster_vehicle.cpp +++ b/src/disaster_vehicle.cpp @@ -40,6 +40,8 @@ #include "effectvehicle_func.h" #include "roadveh.h" #include "train.h" +#include "air_map.h" +#include "pbs_air.h" #include "ai/ai.hpp" #include "game/game.hpp" #include "company_base.h" @@ -213,6 +215,17 @@ void DisasterVehicle::UpdatePosition(int x, int y, int z) } } +/** + * Return whether a zeppelin can crash on a given tile. + * @param tile The tile to check. + * @return whether tile is a free plane apron to crash into. + */ +bool IsValidZeppelinCrashSite(TileIndex tile) +{ + assert(IsValidTile(tile)); + return IsPlaneApronTile(tile) && !HasAirportTileAnyReservation(tile); +} + /** * Zeppeliner handling, v->state states: * 0: Zeppeliner initialization has found a small airport, go there and crash @@ -241,13 +254,18 @@ static bool DisasterTick_Zeppeliner(DisasterVehicle *v) if (GB(v->tick_counter, 0, 3) == 0) CreateEffectVehicleRel(v, 0, -17, 2, EV_CRASH_SMOKE); } else if (v->state == 0) { - if (IsValidTile(v->tile) && IsAirportTile(v->tile)) { + if (IsValidZeppelinCrashSite(v->tile)) { v->state = 1; v->age = 0; + assert(IsAirportTile(v->tile)); SetDParam(0, GetStationIndex(v->tile)); AddTileNewsItem(STR_NEWS_DISASTER_ZEPPELIN, NT_ACCIDENT, v->tile); AI::NewEvent(GetTileOwner(v->tile), new ScriptEventDisasterZeppelinerCrashed(GetStationIndex(v->tile))); + TrackBits tracks = GetAirportTileTracks(v->tile); + for (Track track = RemoveFirstTrack(&tracks); track != INVALID_TRACK; track = RemoveFirstTrack(&tracks)) { + SetAirportTrackReservation(v->tile, track); + } } } @@ -262,10 +280,14 @@ static bool DisasterTick_Zeppeliner(DisasterVehicle *v) if (v->state > 2) { if (++v->age <= 13320) return true; - if (IsValidTile(v->tile) && IsAirportTile(v->tile)) { + if (IsValidZeppelinCrashSite(v->tile)) { Station *st = Station::GetByTile(v->tile); - CLRBITS(st->airport.flags, RUNWAY_IN_block); AI::NewEvent(GetTileOwner(v->tile), new ScriptEventDisasterZeppelinerCleared(st->index)); + + TrackBits tracks = GetAirportTileTracks(v->tile); + for (Track track = RemoveFirstTrack(&tracks); track != INVALID_TRACK; track = RemoveFirstTrack(&tracks)) { + RemoveAirportTrackReservation(v->tile, track); + } } v->UpdatePosition(v->x_pos, v->y_pos, GetAircraftFlightLevel(v)); @@ -300,10 +322,6 @@ static bool DisasterTick_Zeppeliner(DisasterVehicle *v) v->age = 0; } - if (IsValidTile(v->tile) && IsAirportTile(v->tile)) { - SETBITS(Station::GetByTile(v->tile)->airport.flags, RUNWAY_IN_block); - } - return true; } @@ -736,8 +754,8 @@ static void Disaster_Zeppeliner_Init() int x = TileX(Random()) * TILE_SIZE + TILE_SIZE / 2; for (const Station *st : Station::Iterate()) { - if (st->airport.tile != INVALID_TILE && (st->airport.type == AT_SMALL || st->airport.type == AT_LARGE)) { - x = (TileX(st->airport.tile) + 2) * TILE_SIZE; + if (st->airport.tile != INVALID_TILE && !st->airport.aprons.empty()) { + x = TileX(st->airport.aprons[0]) * TILE_SIZE; break; } } @@ -994,6 +1012,26 @@ void ReleaseDisasterVehicle(VehicleID vehicle) v->age = 0; } +/** + * Delete old crashed zeppelins, as they are not adapted to new airports. + */ +void DeleteCrashedZeppelins() +{ + for (DisasterVehicle *v : DisasterVehicle::Iterate()) { + if (v->subtype == ST_ZEPPELINER && v->current_order.GetDestination() > 2) { + /* If a zeppelin has crashed... */ + if (IsValidTile(v->tile) && IsAirportTile(v->tile)) { + // revise: is this called after script initialization. + Station *st = Station::GetByTile(v->tile); + AI::NewEvent(GetTileOwner(v->tile), new ScriptEventDisasterZeppelinerCleared(st->index)); + } + + v->UpdatePosition(v->x_pos, v->y_pos, GetAircraftFlightLevel(v)); + delete v; + } + } +} + void DisasterVehicle::UpdateDeltaXY() { this->x_offs = -1; diff --git a/src/dock_gui.cpp b/src/dock_gui.cpp index ffbf5aa63e3c2..2b2a369665f12 100644 --- a/src/dock_gui.cpp +++ b/src/dock_gui.cpp @@ -32,6 +32,7 @@ #include "waypoint_cmd.h" #include "timer/timer.h" #include "timer/timer_game_calendar.h" +#include "depot_func.h" #include "widgets/dock_widget.h" @@ -112,6 +113,13 @@ struct BuildDocksToolbarWindow : Window { { if (_game_mode == GM_NORMAL && this->IsWidgetLowered(WID_DT_STATION)) SetViewportCatchmentStation(nullptr, true); if (_settings_client.gui.link_terraform_toolbar) CloseWindowById(WC_SCEN_LAND_GEN, 0, false); + + if (_game_mode == GM_NORMAL && + ((this->HasWidget(WID_DT_DEPOT) && this->IsWidgetLowered(WID_DT_DEPOT)) || + (this->HasWidget(WID_DT_EXTENDED_DEPOT) && this->IsWidgetLowered(WID_DT_EXTENDED_DEPOT)))) { + SetViewportHighlightDepot(INVALID_DEPOT, true); + } + this->Window::Close(); } @@ -127,6 +135,7 @@ struct BuildDocksToolbarWindow : Window { bool can_build = CanBuildVehicleInfrastructure(VEH_SHIP); this->SetWidgetsDisabledState(!can_build, WID_DT_DEPOT, + WID_DT_EXTENDED_DEPOT, WID_DT_STATION, WID_DT_BUOY); if (!can_build) { @@ -137,11 +146,13 @@ struct BuildDocksToolbarWindow : Window { if (_game_mode != GM_EDITOR) { if (!can_build) { /* Show in the tooltip why this button is disabled. */ - this->GetWidget(WID_DT_DEPOT)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); + if (this->HasWidget(WID_DT_DEPOT)) this->GetWidget(WID_DT_DEPOT)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); + if (this->HasWidget(WID_DT_EXTENDED_DEPOT)) this->GetWidget(WID_DT_EXTENDED_DEPOT)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); this->GetWidget(WID_DT_STATION)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); this->GetWidget(WID_DT_BUOY)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); } else { - this->GetWidget(WID_DT_DEPOT)->SetToolTip(STR_WATERWAYS_TOOLBAR_BUILD_DEPOT_TOOLTIP); + if (this->HasWidget(WID_DT_DEPOT)) this->GetWidget(WID_DT_DEPOT)->SetToolTip(STR_WATERWAYS_TOOLBAR_BUILD_DEPOT_TOOLTIP); + if (this->HasWidget(WID_DT_EXTENDED_DEPOT)) this->GetWidget(WID_DT_EXTENDED_DEPOT)->SetToolTip(STR_WATERWAYS_TOOLBAR_BUILD_EXTENDED_DEPOT_TOOLTIP); this->GetWidget(WID_DT_STATION)->SetToolTip(STR_WATERWAYS_TOOLBAR_BUILD_DOCK_TOOLTIP); this->GetWidget(WID_DT_BUOY)->SetToolTip(STR_WATERWAYS_TOOLBAR_BUOY_TOOLTIP); } @@ -164,7 +175,10 @@ struct BuildDocksToolbarWindow : Window { break; case WID_DT_DEPOT: // Build depot button - if (HandlePlacePushButton(this, WID_DT_DEPOT, SPR_CURSOR_SHIP_DEPOT, HT_RECT)) ShowBuildDocksDepotPicker(this); + case WID_DT_EXTENDED_DEPOT: + if (HandlePlacePushButton(this, widget, SPR_CURSOR_SHIP_DEPOT, HT_RECT)) { + ShowBuildDocksDepotPicker(this); + } break; case WID_DT_STATION: // Build station button @@ -205,8 +219,16 @@ struct BuildDocksToolbarWindow : Window { break; case WID_DT_DEPOT: // Build depot button - Command::Post(STR_ERROR_CAN_T_BUILD_SHIP_DEPOT, CcBuildDocks, tile, _ship_depot_direction); + case WID_DT_EXTENDED_DEPOT: { + CloseWindowById(WC_SELECT_DEPOT, VEH_SHIP); + + ViewportPlaceMethod vpm = _ship_depot_direction != AXIS_X ? VPM_LIMITED_X_FIXED_Y : VPM_LIMITED_Y_FIXED_X; + VpSetPlaceSizingLimit(_settings_game.depot.depot_spread); + VpStartPlaceSizing(tile, vpm, DDSP_BUILD_DEPOT); + /* Select tiles now to prevent selection from flickering. */ + VpSelectTilesWithMethod(pt.x, pt.y, vpm); break; + } case WID_DT_STATION: { // Build station button /* Determine the watery part of the dock. */ @@ -260,6 +282,17 @@ struct BuildDocksToolbarWindow : Window { case DDSP_CREATE_RIVER: Command::Post(STR_ERROR_CAN_T_PLACE_RIVERS, CcPlaySound_CONSTRUCTION_WATER, end_tile, start_tile, WATER_CLASS_RIVER, _ctrl_pressed); break; + case DDSP_BUILD_DEPOT: { + bool adjacent = _ctrl_pressed; + bool extended = this->last_clicked_widget == WID_DT_EXTENDED_DEPOT; + + auto proc = [=](DepotID join_to) -> bool { + return Command::Post(STR_ERROR_CAN_T_BUILD_SHIP_DEPOT, CcBuildDocks, start_tile, _ship_depot_direction, adjacent, extended, join_to, end_tile); + }; + + ShowSelectDepotIfNeeded(TileArea(start_tile, end_tile), proc, VEH_SHIP); + break; + } default: break; } @@ -270,11 +303,18 @@ struct BuildDocksToolbarWindow : Window { { if (_game_mode != GM_EDITOR && this->IsWidgetLowered(WID_DT_STATION)) SetViewportCatchmentStation(nullptr, true); + if (_game_mode != GM_EDITOR && + ((this->HasWidget(WID_DT_DEPOT) && this->IsWidgetLowered(WID_DT_DEPOT)) || + (this->HasWidget(WID_DT_EXTENDED_DEPOT) && this->IsWidgetLowered(WID_DT_EXTENDED_DEPOT)))) { + SetViewportHighlightDepot(INVALID_DEPOT, true); + } + this->RaiseButtons(); CloseWindowById(WC_BUILD_STATION, TRANSPORT_WATER); CloseWindowById(WC_BUILD_DEPOT, TRANSPORT_WATER); CloseWindowById(WC_SELECT_STATION, 0); + CloseWindowById(WC_SELECT_DEPOT, VEH_SHIP); CloseWindowByClass(WC_BUILD_BRIDGE); } @@ -322,6 +362,25 @@ struct BuildDocksToolbarWindow : Window { }, DockToolbarGlobalHotkeys}; }; +/** + * Add the depot icons depending on availability of construction. + * @return Panel with water depot buttons. + */ +static std::unique_ptr MakeNWidgetWaterDepot() +{ + auto hor = std::make_unique(); + + if (HasBit(_settings_game.depot.water_depot_types, 0)) { + hor->Add(std::make_unique(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_DT_DEPOT, SPR_IMG_SHIP_DEPOT, STR_WATERWAYS_TOOLBAR_BUILD_DEPOT_TOOLTIP)); + } + + if (HasBit(_settings_game.depot.water_depot_types, 1)) { + hor->Add(std::make_unique(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_DT_EXTENDED_DEPOT, SPR_IMG_SHIP_DEPOT, STR_WATERWAYS_TOOLBAR_BUILD_EXTENDED_DEPOT_TOOLTIP)); + } + + return hor; +} + /** * Nested widget parts of docks toolbar, game version. * Position of #WID_DT_RIVER widget has changed. @@ -337,7 +396,7 @@ static constexpr NWidgetPart _nested_build_docks_toolbar_widgets[] = { NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_DT_LOCK), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_BUILD_LOCK, STR_WATERWAYS_TOOLBAR_BUILD_LOCKS_TOOLTIP), NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(5, 22), SetFill(1, 1), EndContainer(), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_DT_DEMOLISH), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC), - NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_DT_DEPOT), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_SHIP_DEPOT, STR_WATERWAYS_TOOLBAR_BUILD_DEPOT_TOOLTIP), + NWidgetFunction(MakeNWidgetWaterDepot), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_DT_STATION), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_SHIP_DOCK, STR_WATERWAYS_TOOLBAR_BUILD_DOCK_TOOLTIP), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_DT_BUOY), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_BUOY, STR_WATERWAYS_TOOLBAR_BUOY_TOOLTIP), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_DT_BUILD_AQUEDUCT), SetMinimalSize(23, 22), SetFill(0, 1), SetDataTip(SPR_IMG_AQUEDUCT, STR_WATERWAYS_TOOLBAR_BUILD_AQUEDUCT_TOOLTIP), @@ -514,10 +573,13 @@ struct BuildDocksDepotWindow : public PickerWindowBase { private: static void UpdateDocksDirection() { + VpSetPlaceFixedSize(2); if (_ship_depot_direction != AXIS_X) { SetTileSelectSize(1, 2); + _thd.select_method = VPM_LIMITED_X_FIXED_Y; } else { SetTileSelectSize(2, 1); + _thd.select_method = VPM_LIMITED_Y_FIXED_X; } } @@ -529,6 +591,13 @@ struct BuildDocksDepotWindow : public PickerWindowBase { UpdateDocksDirection(); } + void Close([[maybe_unused]] int data = 0) override + { + CloseWindowById(WC_SELECT_DEPOT, VEH_SHIP); + VpResetFixedSize(); + this->PickerWindowBase::Close(); + } + void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override { switch (widget) { @@ -569,6 +638,7 @@ struct BuildDocksDepotWindow : public PickerWindowBase { switch (widget) { case WID_BDD_X: case WID_BDD_Y: + CloseWindowById(WC_SELECT_DEPOT, VEH_SHIP); this->RaiseWidget(WID_BDD_X + _ship_depot_direction); _ship_depot_direction = (widget == WID_BDD_X ? AXIS_X : AXIS_Y); this->LowerWidget(WID_BDD_X + _ship_depot_direction); @@ -578,6 +648,11 @@ struct BuildDocksDepotWindow : public PickerWindowBase { break; } } + + void OnRealtimeTick([[maybe_unused]] uint delta_ms) override + { + CheckRedrawDepotHighlight(this, VEH_SHIP); + } }; static constexpr NWidgetPart _nested_build_docks_depot_widgets[] = { diff --git a/src/economy.cpp b/src/economy.cpp index cd990942399de..46fa3c3ea640e 100644 --- a/src/economy.cpp +++ b/src/economy.cpp @@ -44,6 +44,7 @@ #include "core/container_func.hpp" #include "cargo_type.h" #include "water.h" +#include "air.h" #include "game/game.hpp" #include "cargomonitor.h" #include "goal_base.h" @@ -55,6 +56,8 @@ #include "timer/timer.h" #include "timer/timer_game_calendar.h" #include "timer/timer_game_economy.h" +#include "depot_base.h" +#include "platform_func.h" #include "table/strings.h" #include "table/pricebase.h" @@ -374,6 +377,12 @@ void ChangeOwnershipOfCompanyItems(Owner old_owner, Owner new_owner) } if (new_owner == INVALID_OWNER) RebuildSubsidisedSourceAndDestinationCache(); + for (Depot *dep : Depot::Iterate()) { + if (dep->owner == old_owner && new_owner != INVALID_OWNER) { + dep->owner = new_owner; + } + } + /* Take care of rating and transport rights in towns */ for (Town *t : Town::Iterate()) { /* If a company takes over, give the ratings to that company. */ @@ -518,9 +527,6 @@ void ChangeOwnershipOfCompanyItems(Owner old_owner, Owner new_owner) UpdateSignalsInBuffer(); } - /* Add airport infrastructure count of the old company to the new one. */ - if (new_owner != INVALID_OWNER) Company::Get(new_owner)->infrastructure.airport += Company::Get(old_owner)->infrastructure.airport; - /* convert owner of stations (including deleted ones, but excluding buoys) */ for (Station *st : Station::Iterate()) { if (st->owner == old_owner) { @@ -684,9 +690,12 @@ static void CompaniesGenStatistics() for (RoadType rt = ROADTYPE_BEGIN; rt < ROADTYPE_END; rt++) { if (c->infrastructure.road[rt] != 0) cost.AddCost(RoadMaintenanceCost(rt, c->infrastructure.road[rt], RoadTypeIsRoad(rt) ? road_total : tram_total)); } + uint32_t air_total = c->infrastructure.GetAirTotal(); + for (AirType at = AIRTYPE_BEGIN; at < AIRTYPE_END; at++) { + if (c->infrastructure.air[at] != 0) cost.AddCost(AirMaintenanceCost(at, c->infrastructure.air[at], air_total)); + } cost.AddCost(CanalMaintenanceCost(c->infrastructure.water)); cost.AddCost(StationMaintenanceCost(c->infrastructure.station)); - cost.AddCost(AirportMaintenanceCost(c->index)); SubtractMoneyFromCompany(cost); } @@ -1614,14 +1623,13 @@ static void ReserveConsist(Station *st, Vehicle *u, CargoArray *consist_capleft, * Update the vehicle's load_unload_ticks, the time it will wait until it tries to load or unload * again. Adjust for overhang of trains and set it at least to 1. * @param front The vehicle to be updated. - * @param st The station the vehicle is loading at. * @param ticks The time it would normally wait, based on cargo loaded and unloaded. */ -static void UpdateLoadUnloadTicks(Vehicle *front, const Station *st, int ticks) +static void UpdateLoadUnloadTicks(Vehicle *front, int ticks) { if (front->type == VEH_TRAIN && _settings_game.order.station_length_loading_penalty) { /* Each platform tile is worth 2 rail vehicles. */ - int overhang = front->GetGroundVehicleCache()->cached_total_length - st->GetPlatformLength(front->tile) * TILE_SIZE; + int overhang = front->GetGroundVehicleCache()->cached_total_length - GetPlatformLength(front->tile) * TILE_SIZE; if (overhang > 0) { ticks <<= 1; ticks += (overhang * ticks) / 8; @@ -1883,9 +1891,9 @@ static void LoadUnloadVehicle(Vehicle *front) SetBit(front->vehicle_flags, VF_STOP_LOADING); } - UpdateLoadUnloadTicks(front, st, new_load_unload_ticks); + UpdateLoadUnloadTicks(front, new_load_unload_ticks); } else { - UpdateLoadUnloadTicks(front, st, 20); // We need the ticks for link refreshing. + UpdateLoadUnloadTicks(front, 20); // We need the ticks for link refreshing. bool finished_loading = true; if (front->current_order.GetLoadType() & OLFB_FULL_LOAD) { if (front->current_order.GetLoadType() == OLF_FULL_LOAD_ANY) { diff --git a/src/economy_type.h b/src/economy_type.h index e4d0ea077acf3..a4c08c4919463 100644 --- a/src/economy_type.h +++ b/src/economy_type.h @@ -241,8 +241,6 @@ static const int INVALID_PRICE_MODIFIER = MIN_PRICE_MODIFIER - 1; static const uint TUNNELBRIDGE_TRACKBIT_FACTOR = 4; /** Multiplier for how many regular track bits a level crossing counts. */ static const uint LEVELCROSSING_TRACKBIT_FACTOR = 2; -/** Multiplier for how many regular track bits a road depot counts. */ -static const uint ROAD_DEPOT_TRACKBIT_FACTOR = 2; /** Multiplier for how many regular track bits a bay stop counts. */ static const uint ROAD_STOP_TRACKBIT_FACTOR = 2; /** Multiplier for how many regular tiles a lock counts. */ diff --git a/src/engine.cpp b/src/engine.cpp index 979c6a7e3199f..da3e950c6f59e 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -32,6 +32,7 @@ #include "timer/timer.h" #include "timer/timer_game_tick.h" #include "timer/timer_game_calendar.h" +#include "air.h" #include "table/strings.h" #include "table/engines.h" @@ -778,6 +779,7 @@ void StartupEngines() for (Company *c : Company::Iterate()) { c->avail_railtypes = GetCompanyRailTypes(c->index); c->avail_roadtypes = GetCompanyRoadTypes(c->index); + c->avail_airtypes = GetCompanyAirTypes(c->index); } /* Invalidate any open purchase lists */ @@ -798,10 +800,22 @@ static void EnableEngineForCompany(EngineID eid, CompanyID company) Company *c = Company::Get(company); SetBit(e->company_avail, company); - if (e->type == VEH_TRAIN) { - c->avail_railtypes = GetCompanyRailTypes(c->index); - } else if (e->type == VEH_ROAD) { - c->avail_roadtypes = GetCompanyRoadTypes(c->index); + switch (e->type) { + case VEH_TRAIN: + c->avail_railtypes = GetCompanyRailTypes(c->index); + break; + + case VEH_ROAD: + c->avail_roadtypes = GetCompanyRoadTypes(c->index); + break; + + case VEH_AIRCRAFT: + assert(e->u.air.airtype < AIRTYPE_END); + c->avail_airtypes = GetCompanyAirTypes(c->index); + break; + + default: + break; } if (company == _local_company) { @@ -830,6 +844,8 @@ static void DisableEngineForCompany(EngineID eid, CompanyID company) c->avail_railtypes = GetCompanyRailTypes(c->index); } else if (e->type == VEH_ROAD) { c->avail_roadtypes = GetCompanyRoadTypes(c->index); + } else if (e->type == VEH_AIRCRAFT) { + c->avail_airtypes = GetCompanyAirTypes(c->index); } if (company == _local_company) { @@ -928,6 +944,7 @@ static IntervalTimer _calendar_engines_daily({TimerGameCalend for (Company *c : Company::Iterate()) { c->avail_railtypes = AddDateIntroducedRailTypes(c->avail_railtypes, TimerGameCalendar::date); c->avail_roadtypes = AddDateIntroducedRoadTypes(c->avail_roadtypes, TimerGameCalendar::date); + c->avail_airtypes = AddDateIntroducedAirTypes(c->avail_airtypes, TimerGameCalendar::date); } if (TimerGameCalendar::year >= _year_engine_aging_stops) return; @@ -1086,6 +1103,10 @@ static void NewVehicleAvailable(Engine *e) /* maybe make another road type available */ assert(e->u.road.roadtype < ROADTYPE_END); for (Company *c : Company::Iterate()) c->avail_roadtypes = AddDateIntroducedRoadTypes(c->avail_roadtypes | GetRoadTypeInfo(e->u.road.roadtype)->introduces_roadtypes, TimerGameCalendar::date); + } else if (e->type == VEH_AIRCRAFT) { + /* maybe make another air type available */ + assert(e->u.air.airtype < AIRTYPE_END); + for (Company *c : Company::Iterate()) c->avail_airtypes = AddDateIntroducedAirTypes(c->avail_airtypes | GetAirTypeInfo(e->u.air.airtype)->introduces_airtypes, TimerGameCalendar::date); } /* Only broadcast event if AIs are able to build this vehicle type. */ @@ -1236,15 +1257,28 @@ bool IsEngineBuildable(EngineID engine, VehicleType type, CompanyID company) if (!e->IsEnabled()) return false; - if (type == VEH_TRAIN && company != OWNER_DEITY) { - /* Check if the rail type is available to this company */ - const Company *c = Company::Get(company); - if (((GetRailTypeInfo(e->u.rail.railtype))->compatible_railtypes & c->avail_railtypes) == 0) return false; - } - if (type == VEH_ROAD && company != OWNER_DEITY) { - /* Check if the road type is available to this company */ - const Company *c = Company::Get(company); - if ((GetRoadTypeInfo(e->u.road.roadtype)->powered_roadtypes & c->avail_roadtypes) == ROADTYPES_NONE) return false; + if (company == OWNER_DEITY) return true; + + const Company *c = Company::Get(company); + + switch (type) { + case VEH_TRAIN: + /* Check if the rail type is available to this company. */ + if (((GetRailTypeInfo(e->u.rail.railtype))->compatible_railtypes & c->avail_railtypes) == RAILTYPES_NONE) return false; + break; + + case VEH_AIRCRAFT: + /* Check whether the air type is available to this company. */ + if (((GetAirTypeInfo(e->u.air.airtype))->compatible_airtypes & c->avail_airtypes) == AIRTYPES_NONE) return false; + break; + + case VEH_ROAD: + /* Check if the road type is available to this company. */ + if ((GetRoadTypeInfo(e->u.road.roadtype)->powered_roadtypes & c->avail_roadtypes) == ROADTYPES_NONE) return false; + break; + + default: + break; } return true; diff --git a/src/engine_type.h b/src/engine_type.h index d5a28ced3ea15..f7fcbdd8e3c60 100644 --- a/src/engine_type.h +++ b/src/engine_type.h @@ -11,6 +11,7 @@ #define ENGINE_TYPE_H #include "economy_type.h" +#include "air_type.h" #include "rail_type.h" #include "road_type.h" #include "cargo_type.h" @@ -108,6 +109,7 @@ struct AircraftVehicleInfo { uint8_t mail_capacity; ///< Mail capacity (bags). uint16_t passenger_capacity; ///< Passenger capacity (persons). uint16_t max_range; ///< Maximum range of this aircraft. + AirType airtype; ///< Airport tile types this aircraft can use. }; /** Information about a road vehicle. */ diff --git a/src/ground_vehicle.cpp b/src/ground_vehicle.cpp index a6ab9758c0cbd..5981d22b7715a 100644 --- a/src/ground_vehicle.cpp +++ b/src/ground_vehicle.cpp @@ -197,7 +197,10 @@ bool GroundVehicle::IsChainInDepot() const /* Check whether the rest is also already trying to enter the depot. */ for (; v != nullptr; v = v->Next()) { - if (!v->T::IsInDepot() || v->tile != this->tile) return false; + if (!v->T::IsInDepot()) return false; + assert(IsDepotTile(v->tile)); + assert(IsDepotTile(this->tile)); + assert(GetDepotIndex(this->tile) == GetDepotIndex(v->tile)); } return true; diff --git a/src/group_cmd.cpp b/src/group_cmd.cpp index 96aa2d1c9fe79..3930c81469d93 100644 --- a/src/group_cmd.cpp +++ b/src/group_cmd.cpp @@ -19,6 +19,7 @@ #include "core/pool_func.hpp" #include "order_backup.h" #include "group_cmd.h" +#include "depot_map.h" #include "table/strings.h" @@ -579,7 +580,7 @@ std::tuple CmdAddVehicleGroup(DoCommandFlag flags, GroupID } } - SetWindowDirty(WC_VEHICLE_DEPOT, v->tile); + if (IsDepotTypeTile(v->tile, (TransportType)v->type)) SetWindowDirty(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); SetWindowDirty(WC_VEHICLE_VIEW, v->index); SetWindowDirty(WC_VEHICLE_DETAILS, v->index); InvalidateWindowData(WC_VEHICLE_VIEW, v->index); diff --git a/src/group_gui.cpp b/src/group_gui.cpp index bb2a8d840d410..ff3244c3a8972 100644 --- a/src/group_gui.cpp +++ b/src/group_gui.cpp @@ -842,7 +842,7 @@ class VehicleGroupWindow : public BaseVehicleListWindow { break; case WID_GL_AVAILABLE_VEHICLES: - ShowBuildVehicleWindow(INVALID_TILE, this->vli.vtype); + ShowBuildVehicleWindow(INVALID_DEPOT, this->vli.vtype); break; case WID_GL_MANAGE_VEHICLES_DROPDOWN: { diff --git a/src/gui.h b/src/gui.h index 87df7a3283ddf..d38f5d22f51f2 100644 --- a/src/gui.h +++ b/src/gui.h @@ -33,9 +33,6 @@ void ShowOrdersWindow(const Vehicle *v); Window *ShowBuildDocksToolbar(); Window *ShowBuildDocksScenToolbar(); -/* airport_gui.cpp */ -Window *ShowBuildAirToolbar(); - /* tgp_gui.cpp */ void ShowGenerateLandscape(); void ShowHeightmapLoad(); diff --git a/src/industry_cmd.cpp b/src/industry_cmd.cpp index 0707ec9c89829..b56e1d90c68b1 100644 --- a/src/industry_cmd.cpp +++ b/src/industry_cmd.cpp @@ -58,7 +58,7 @@ IndustryPool _industry_pool("Industry"); INSTANTIATE_POOL_METHODS(Industry) void ShowIndustryViewWindow(int industry); -void BuildOilRig(TileIndex tile); +void BuildBuiltInHeliport(Tile tile); static uint8_t _industry_sound_ctr; static TileIndex _industry_sound_tile; @@ -159,8 +159,8 @@ Industry::~Industry() /* MakeWaterKeepingClass() can also handle 'land' */ MakeWaterKeepingClass(tile_cur, OWNER_NONE); } - } else if (IsTileType(tile_cur, MP_STATION) && IsOilRig(tile_cur)) { - DeleteOilRig(tile_cur); + } else if (IsBuiltInHeliportTile(tile_cur)) { + DeleteBuiltInHeliport(tile_cur); } } @@ -784,7 +784,7 @@ static void MakeIndustryTileBigger(TileIndex tile) if (IsTileType(other, MP_INDUSTRY) && GetIndustryGfx(other) == GFX_OILRIG_1 && GetIndustryIndex(tile) == GetIndustryIndex(other)) { - BuildOilRig(tile); + BuildBuiltInHeliport(tile); } break; } diff --git a/src/landscape.cpp b/src/landscape.cpp index 7964e3ba02b5a..5da1a2995f7b4 100644 --- a/src/landscape.cpp +++ b/src/landscape.cpp @@ -1654,6 +1654,7 @@ bool GenerateLandscape(uint8_t mode) void OnTick_Town(); void OnTick_Trees(); void OnTick_Station(); +void OnTick_Depot(); void OnTick_Industry(); void OnTick_Companies(); @@ -1667,6 +1668,7 @@ void CallLandscapeTick() OnTick_Town(); OnTick_Trees(); OnTick_Station(); + OnTick_Depot(); OnTick_Industry(); } diff --git a/src/lang/english.txt b/src/lang/english.txt index 87a8d3c594425..75e44e6940ce0 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -278,6 +278,8 @@ STR_TOOLTIP_FILTER_CRITERIA :{BLACK}Select f STR_BUTTON_SORT_BY :{BLACK}Sort by STR_BUTTON_CATCHMENT :{BLACK}Coverage STR_TOOLTIP_CATCHMENT :{BLACK}Toggle coverage area display +STR_BUTTON_HIGHLIGHT_DEPOT :{BLACK}Highlight +STR_TOOLTIP_HIGHLIGHT_DEPOT :{BLACK}Toggle highlight on viewport for this depot STR_TOOLTIP_CLOSE_WINDOW :{BLACK}Close window STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS :{BLACK}Window title - drag this to move window @@ -509,9 +511,6 @@ STR_ROAD_MENU_TRAM_CONSTRUCTION :Tramway constru # Waterways construction menu STR_WATERWAYS_MENU_WATERWAYS_CONSTRUCTION :Waterways construction -# Aairport construction menu -STR_AIRCRAFT_MENU_AIRPORT_CONSTRUCTION :Airport construction - # Landscaping menu STR_LANDSCAPING_MENU_LANDSCAPING :Landscaping STR_LANDSCAPING_MENU_PLANT_TREES :Plant trees @@ -843,7 +842,7 @@ STR_NEWS_TRAIN_CRASH :{BIG_FONT}{BLAC STR_NEWS_ROAD_VEHICLE_CRASH_DRIVER :{BIG_FONT}{BLACK}Road Vehicle Crash!{}Driver dies in fireball after collision with train STR_NEWS_ROAD_VEHICLE_CRASH :{BIG_FONT}{BLACK}Road Vehicle Crash!{}{COMMA} die in fireball after collision with train STR_NEWS_AIRCRAFT_CRASH :{BIG_FONT}{BLACK}Plane Crash!{}{COMMA} die in fireball at {STATION} -STR_NEWS_PLANE_CRASH_OUT_OF_FUEL :{BIG_FONT}{BLACK}Plane Crash!{}Aircraft ran out of fuel, {COMMA} die in fireball +STR_NEWS_AIRCRAFT_CRASH_NO_AIRPORT :{BIG_FONT}{BLACK}Aircraft Crash!{}{COMMA} die in fireball STR_NEWS_DISASTER_ZEPPELIN :{BIG_FONT}{BLACK}Zeppelin disaster at {STATION}! STR_NEWS_DISASTER_SMALL_UFO :{BIG_FONT}{BLACK}Road vehicle destroyed in 'UFO' collision! @@ -899,7 +898,6 @@ STR_NEWS_VEHICLE_HAS_TOO_FEW_ORDERS :{WHITE}{VEHICLE STR_NEWS_VEHICLE_HAS_VOID_ORDER :{WHITE}{VEHICLE} has a void order STR_NEWS_VEHICLE_HAS_DUPLICATE_ENTRY :{WHITE}{VEHICLE} has duplicate orders STR_NEWS_VEHICLE_HAS_INVALID_ENTRY :{WHITE}{VEHICLE} has an invalid station in its orders -STR_NEWS_PLANE_USES_TOO_SHORT_RUNWAY :{WHITE}{VEHICLE} has in its orders an airport whose runway is too short STR_NEWS_VEHICLE_IS_GETTING_OLD :{WHITE}{VEHICLE} is getting old STR_NEWS_VEHICLE_IS_GETTING_VERY_OLD :{WHITE}{VEHICLE} is getting very old @@ -908,7 +906,10 @@ STR_NEWS_TRAIN_IS_STUCK :{WHITE}{VEHICLE STR_NEWS_VEHICLE_IS_LOST :{WHITE}{VEHICLE} is lost STR_NEWS_VEHICLE_UNPROFITABLE_YEAR :{WHITE}{VEHICLE}'s profit last year was {CURRENCY_LONG} STR_NEWS_VEHICLE_UNPROFITABLE_PERIOD :{WHITE}{VEHICLE}'s profit last period was {CURRENCY_LONG} +STR_NEWS_VEHICLE_CAN_T_FIND_FREE_DEPOT :{WHITE}{VEHICLE} can't find a free depot +STR_NEWS_VEHICLE_TOO_LONG_FOR_SERVICING :{WHITE}{VEHICLE} couldn't service in short platform STR_NEWS_AIRCRAFT_DEST_TOO_FAR :{WHITE}{VEHICLE} can't get to the next destination because it is out of range +STR_NEWS_AIRCRAFT_CAN_T_LAND :{WHITE}{VEHICLE} can't land on airport STR_NEWS_ORDER_REFIT_FAILED :{WHITE}{VEHICLE} stopped because an ordered refit failed STR_NEWS_VEHICLE_AUTORENEW_FAILED :{WHITE}Autorenew failed on {VEHICLE}{}{STRING2} @@ -1448,9 +1449,9 @@ STR_CONFIG_SETTING_PLANE_SPEED_HELPTEXT :Set the relativ STR_CONFIG_SETTING_PLANE_SPEED_VALUE :1 / {COMMA} STR_CONFIG_SETTING_PLANE_CRASHES :Number of plane crashes: {STRING2} -STR_CONFIG_SETTING_PLANE_CRASHES_HELPTEXT :Set the chance of a random aircraft crash happening.{}* Large airplanes always have a risk of crashing when landing on small airports +STR_CONFIG_SETTING_PLANE_CRASHES_HELPTEXT :Set the chance of a random aircraft crash happening. ###length 3 -STR_CONFIG_SETTING_PLANE_CRASHES_NONE :None* +STR_CONFIG_SETTING_PLANE_CRASHES_NONE :None STR_CONFIG_SETTING_PLANE_CRASHES_REDUCED :Reduced STR_CONFIG_SETTING_PLANE_CRASHES_NORMAL :Normal @@ -1462,6 +1463,7 @@ STR_CONFIG_SETTING_STOP_ON_TOWN_ROAD_HELPTEXT :Allow construct STR_CONFIG_SETTING_STOP_ON_COMPETITOR_ROAD :Allow drive-through road stops on roads owned by competitors: {STRING2} STR_CONFIG_SETTING_STOP_ON_COMPETITOR_ROAD_HELPTEXT :Allow construction of drive-through road stops on roads owned by other companies STR_CONFIG_SETTING_DYNAMIC_ENGINES_EXISTING_VEHICLES :{WHITE}Changing this setting is not possible when there are vehicles +STR_CONFIG_SETTING_REPLACEMENTS_DIFF_TYPE :{WHITE}Disabling this setting is not possible during a game STR_CONFIG_SETTING_INFRASTRUCTURE_MAINTENANCE :Infrastructure maintenance: {STRING2} STR_CONFIG_SETTING_INFRASTRUCTURE_MAINTENANCE_HELPTEXT :When enabled, infrastructure causes maintenance costs. The cost grows over-proportional with the network size, thus affecting bigger companies more than smaller ones @@ -1474,6 +1476,8 @@ STR_CONFIG_SETTING_COMPANY_STARTING_COLOUR_SECONDARY_HELPTEXT :Choose starting STR_CONFIG_SETTING_NEVER_EXPIRE_AIRPORTS :Airports never expire: {STRING2} STR_CONFIG_SETTING_NEVER_EXPIRE_AIRPORTS_HELPTEXT :Enabling this setting makes each airport type stay available forever after its introduction +STR_CONFIG_SETTING_ALLOW_CUSTOMIZED_AIRPORTS :Allow building customized airports: {STRING2} +STR_CONFIG_SETTING_ALLOW_CUSTOMIZED_AIRPORTS_HELPTEXT :Enabling this setting allows each player being able to build customized airports. STR_CONFIG_SETTING_WARN_LOST_VEHICLE :Warn if vehicle is lost: {STRING2} STR_CONFIG_SETTING_WARN_LOST_VEHICLE_HELPTEXT :Trigger messages about vehicles unable to find a path to their ordered destination @@ -1615,6 +1619,30 @@ STR_CONFIG_SETTING_SE_FLAT_WORLD_HEIGHT :The height leve STR_CONFIG_SETTING_EDGES_NOT_EMPTY :{WHITE}One or more tiles at the northern edge are not empty STR_CONFIG_SETTING_EDGES_NOT_WATER :{WHITE}One or more tiles at one of the edges is not water +STR_CONFIG_SETTING_DISTANT_JOIN_DEPOTS :Allow to join depot parts not directly adjacent: {STRING2} +STR_CONFIG_SETTING_DISTANT_JOIN_DEPOTS_HELPTEXT :Allow adding parts to a depot without directly touching the existing parts. Needs Ctrl+Click while placing the new parts +STR_CONFIG_SETTING_DEPOT_SPREAD :Maximum depot spread: {STRING2} +STR_CONFIG_SETTING_DEPOT_SPREAD_HELPTEXT :Maximum area the parts of a single depot may be spread out on + +STR_CONFIG_SETTING_RAIL_DEPOT_TYPES :Rail depot types: {STRING2} +STR_CONFIG_SETTING_RAIL_DEPOT_TYPES_HELPTEXT :Available rail depot types for construction for human players +STR_CONFIG_SETTING_ROAD_DEPOT_TYPES :Road depot types: {STRING2} +STR_CONFIG_SETTING_ROAD_DEPOT_TYPES_HELPTEXT :Available road depot types for construction for human players +STR_CONFIG_SETTING_WATER_DEPOT_TYPES :Water depot types: {STRING2} +STR_CONFIG_SETTING_WATER_DEPOT_TYPES_HELPTEXT :Available water depot types for construction for human players +STR_CONFIG_SETTING_HANGAR_TYPES :Hangar types: {STRING2} +STR_CONFIG_SETTING_HANGAR_TYPES_HELPTEXT :Available hangar types for construction for human players +###length 3 +STR_CONFIG_SETTING_ONLY_STANDARD_DEPOT :Standard depots +STR_CONFIG_SETTING_ONLY_EXTENDED_DEPOT :Extended depots +STR_CONFIG_SETTING_BOTH_DEPOT_TYPES :Both depot types +###next-name-looks-similar + +STR_CONFIG_SETTING_REPLACE_INCOMPATIBLE_RAIL :Allow replacing rail vehicles with incompatible rail types: {STRING2} +STR_CONFIG_SETTING_REPLACE_INCOMPATIBLE_RAIL_HELPTEXT :Allow replacing rail vehicles even if they are not compatible by rail type +STR_CONFIG_SETTING_REPLACE_INCOMPATIBLE_ROAD :Allow replacing road vehicles with incompatible road types: {STRING2} +STR_CONFIG_SETTING_REPLACE_INCOMPATIBLE_ROAD_HELPTEXT :Allow replacing road vehicles even if they are not compatible by road type + STR_CONFIG_SETTING_STATION_SPREAD :Maximum station spread: {STRING2} STR_CONFIG_SETTING_STATION_SPREAD_HELPTEXT :Maximum area the parts of a single station may be spread out on. Note that high values will slow the game @@ -1741,6 +1769,8 @@ STR_CONFIG_SETTING_QUICKGOTO_HELPTEXT :Pre-select the STR_CONFIG_SETTING_DEFAULT_RAIL_TYPE :Default rail type (after new game/game load): {STRING2} STR_CONFIG_SETTING_DEFAULT_RAIL_TYPE_HELPTEXT :Rail type to select after starting or loading a game. 'first available' selects the oldest type of tracks, 'last available' selects the newest type of tracks, and 'most used' selects the type which is currently most in use +STR_CONFIG_SETTING_DEFAULT_AIR_TYPE :Default air type (after new game/game load): {STRING2} +STR_CONFIG_SETTING_DEFAULT_AIR_TYPE_HELPTEXT :Air type to select after starting or loading a game. 'first available' selects the oldest type of airport, 'last available' selects the newest one, and 'most used' selects the type which is currently most in use ###length 3 STR_CONFIG_SETTING_DEFAULT_RAIL_TYPE_FIRST :First available STR_CONFIG_SETTING_DEFAULT_RAIL_TYPE_LAST :Last available @@ -2137,6 +2167,7 @@ STR_CONFIG_SETTING_ENVIRONMENT_TREES :Trees STR_CONFIG_SETTING_AI :Competitors STR_CONFIG_SETTING_AI_NPC :Computer players STR_CONFIG_SETTING_NETWORK :Network +STR_CONFIG_SETTING_DEPOTS :Depots STR_CONFIG_SETTING_REVERSE_AT_SIGNALS :Automatic reversing at signals: {STRING2} STR_CONFIG_SETTING_REVERSE_AT_SIGNALS_HELPTEXT :Allow trains to reverse on a signal, if they waited there a long time @@ -2753,8 +2784,6 @@ STR_STATION_BUILD_COVERAGE_AREA_OFF_TOOLTIP :{BLACK}Don't hi STR_STATION_BUILD_COVERAGE_AREA_ON_TOOLTIP :{BLACK}Highlight coverage area of proposed site STR_STATION_BUILD_ACCEPTS_CARGO :{BLACK}Accepts: {GOLD}{CARGO_LIST} STR_STATION_BUILD_SUPPLIES_CARGO :{BLACK}Supplies: {GOLD}{CARGO_LIST} -STR_STATION_BUILD_INFRASTRUCTURE_COST_YEAR :{BLACK}Maintenance cost: {GOLD}{CURRENCY_SHORT}/year -STR_STATION_BUILD_INFRASTRUCTURE_COST_PERIOD :{BLACK}Maintenance cost: {GOLD}{CURRENCY_SHORT}/period # Join station window STR_JOIN_STATION_CAPTION :{WHITE}Join station @@ -2763,6 +2792,11 @@ STR_JOIN_STATION_CREATE_SPLITTED_STATION :{YELLOW}Build a STR_JOIN_WAYPOINT_CAPTION :{WHITE}Join waypoint STR_JOIN_WAYPOINT_CREATE_SPLITTED_WAYPOINT :{YELLOW}Build a separate waypoint +# Join depot window +STR_JOIN_DEPOT_CAPTION :{WHITE}Join depot +STR_JOIN_DEPOT_CREATE_SPLITTED_DEPOT :{YELLOW}Build a separate depot +STR_DEPOT_LIST_DEPOT :{YELLOW}{DEPOT} + # Generic toolbar STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE :{BLACK}Disabled as currently no vehicles are available for this infrastructure @@ -2774,7 +2808,8 @@ STR_RAIL_TOOLBAR_MAGLEV_CONSTRUCTION_CAPTION :Maglev Construc STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_TRACK :{BLACK}Build railway track. Ctrl+Click to remove railway track. Also press Shift to show cost estimate only STR_RAIL_TOOLBAR_TOOLTIP_BUILD_AUTORAIL :{BLACK}Build railway track using the Autorail mode. Ctrl+Click to remove railway track. Also press Shift to show cost estimate only -STR_RAIL_TOOLBAR_TOOLTIP_BUILD_TRAIN_DEPOT_FOR_BUILDING :{BLACK}Build train depot (for buying and servicing trains). Also press Shift to show cost estimate only +STR_RAIL_TOOLBAR_TOOLTIP_BUILD_TRAIN_DEPOT :{BLACK}Build standard train depot (for buying and servicing trains). Ctrl+Click to select another depot to join. Also press Shift to show cost estimate only +STR_RAIL_TOOLBAR_TOOLTIP_BUILD_EXTENDED_TRAIN_DEPOT :{BLACK}Build extended train depot (for buying and servicing trains). Ctrl+Click to select another depot to join. Also press Shift to show cost estimate only STR_RAIL_TOOLBAR_TOOLTIP_CONVERT_RAIL_TO_WAYPOINT :{BLACK}Build waypoint on railway. Ctrl+Click to select another waypoint to join. Also press Shift to show cost estimate only STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_STATION :{BLACK}Build railway station. Ctrl+Click to select another station to join. Also press Shift to show cost estimate only STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_SIGNALS :{BLACK}Build signal on railway. Ctrl+Click to build the alternate signal style{}Click+Drag to fill the selected section of rail with signals at the chosen spacing. Ctrl+Click+Drag to fill signals up to the next junction, station, or signal. Also press Shift to show cost estimate only @@ -2885,8 +2920,10 @@ STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_SECTION :{BLACK}Build ro STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAMWAY_SECTION :{BLACK}Build tramway section. Ctrl+Click to remove tramway section. Also press Shift to show cost estimate only STR_ROAD_TOOLBAR_TOOLTIP_BUILD_AUTOROAD :{BLACK}Build road section using the Autoroad mode. Ctrl+Click to remove road section. Also press Shift to show cost estimate only STR_ROAD_TOOLBAR_TOOLTIP_BUILD_AUTOTRAM :{BLACK}Build tramway section using the Autotram mode. Ctrl+Click to remove tramway section. Also press Shift to show cost estimate only -STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_VEHICLE_DEPOT :{BLACK}Build road vehicle depot (for buying and servicing vehicles). Also press Shift to show cost estimate only -STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAM_VEHICLE_DEPOT :{BLACK}Build tram vehicle depot (for buying and servicing vehicles). Also press Shift to show cost estimate only +STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_VEHICLE_DEPOT :{BLACK}Build standard road vehicle depot (for buying and servicing vehicles). Ctrl+Click to select another depot to join. Also press Shift to show cost estimate only +STR_ROAD_TOOLBAR_TOOLTIP_BUILD_EXTENDED_ROAD_VEHICLE_DEPOT :{BLACK}Build extended road vehicle depot (for buying and servicing vehicles). Ctrl+Click to select another depot to join. Also press Shift to show cost estimate only +STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAM_VEHICLE_DEPOT :{BLACK}Build standard tram vehicle depot (for buying and servicing vehicles). Ctrl+Click to select another depot to join. Also press Shift to show cost estimate only +STR_ROAD_TOOLBAR_TOOLTIP_BUILD_EXTENDED_TRAM_VEHICLE_DEPOT :{BLACK}Build extended tram vehicle depot (for buying and servicing vehicles). Ctrl+Click to select another depot to join. Also press Shift to show cost estimate only STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_ROAD_TO_WAYPOINT :{BLACK}Build waypoint on road. Ctrl+Click to select another waypoint to join. Also press Shift to show cost estimate only STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_TRAM_TO_WAYPOINT :{BLACK}Build waypoint on tramway. Ctrl+Click to select another waypoint to join. Also press Shift to show cost estimate only STR_ROAD_TOOLBAR_TOOLTIP_BUILD_BUS_STATION :{BLACK}Build bus station. Ctrl+Click to select another station to join. Also press Shift to show cost estimate only @@ -2927,7 +2964,8 @@ STR_WATERWAYS_TOOLBAR_CAPTION :{WHITE}Waterway STR_WATERWAYS_TOOLBAR_CAPTION_SE :{WHITE}Waterways STR_WATERWAYS_TOOLBAR_BUILD_CANALS_TOOLTIP :{BLACK}Build canals. Also press Shift to show cost estimate only STR_WATERWAYS_TOOLBAR_BUILD_LOCKS_TOOLTIP :{BLACK}Build locks. Also press Shift to show cost estimate only -STR_WATERWAYS_TOOLBAR_BUILD_DEPOT_TOOLTIP :{BLACK}Build ship depot (for buying and servicing ships). Also press Shift to show cost estimate only +STR_WATERWAYS_TOOLBAR_BUILD_DEPOT_TOOLTIP :{BLACK}Build standard ship depot (for buying and servicing ships). Ctrl+Click to select another depot to join. Also press Shift to show cost estimate only +STR_WATERWAYS_TOOLBAR_BUILD_EXTENDED_DEPOT_TOOLTIP :{BLACK}Build extended ship depot (for buying and servicing ships). Ctrl+Click to select another depot to join. Also press Shift to show cost estimate only STR_WATERWAYS_TOOLBAR_BUILD_DOCK_TOOLTIP :{BLACK}Build ship dock. Ctrl+Click to select another station to join. Also press Shift to show cost estimate only STR_WATERWAYS_TOOLBAR_BUOY_TOOLTIP :{BLACK}Place a buoy which can be used as a waypoint. Also press Shift to show cost estimate only STR_WATERWAYS_TOOLBAR_BUILD_AQUEDUCT_TOOLTIP :{BLACK}Build aqueduct. Also press Shift to show cost estimate only @@ -2942,8 +2980,67 @@ STR_DEPOT_BUILD_SHIP_ORIENTATION_TOOLTIP :{BLACK}Select s STR_STATION_BUILD_DOCK_CAPTION :{WHITE}Dock # Airport toolbar -STR_TOOLBAR_AIRCRAFT_CAPTION :{WHITE}Airports -STR_TOOLBAR_AIRCRAFT_BUILD_AIRPORT_TOOLTIP :{BLACK}Build airport. Ctrl+Click to select another station to join. Also press Shift to show cost estimate only +STR_TOOLBAR_AIRPORT_GRAVEL_CONSTRUCTION_CAPTION :Gravel Airport Construction +STR_TOOLBAR_AIRPORT_ASPHALT_CONSTRUCTION_CAPTION :Asphalt Airport Construction +STR_TOOLBAR_AIRPORT_WATER_CONSTRUCTION_CAPTION :Water Airport Construction +STR_TOOLBAR_AIRPORT_DARK_CONSTRUCTION_CAPTION :Darker Airport Construction +STR_TOOLBAR_AIRPORT_YELLOW_CONSTRUCTION_CAPTION :Drawn Tracks Airport Construction + +# Airport menu text +###length 5 +STR_TOOLBAR_AIRPORT_GRAVEL_MENU_TEXT :Gravel airport construction +STR_TOOLBAR_AIRPORT_ASPHALT_MENU_TEXT :Asphalt airport construction +STR_TOOLBAR_AIRPORT_WATER_MENU_TEXT :Water airport construction +STR_TOOLBAR_AIRPORT_DARK_MENU_TEXT :Darker airport construction +STR_TOOLBAR_AIRPORT_YELLOW_MENU_TEXT :Drawn tracks airport construction +###next-name-looks-similar + +STR_TOOLBAR_AIRPORT_CAPTION :{WHITE}Airports +STR_TOOLBAR_AIRPORT_BUILD_AIRPORT_TOOLTIP :{BLACK}Build airport. Ctrl+Click to select another station to join. Also press Shift to show cost estimate only + +STR_TOOLBAR_AIRPORT_ADD_TILES :{BLACK}Add or remove airport tiles to an airport +STR_TOOLBAR_AIRPORT_INFRASTRUCTURE_CATCHMENT :{BLACK}Define as infrastructure with catchment +STR_TOOLBAR_AIRPORT_INFRASTRUCTURE_NO_CATCHMENT :{BLACK}Define as infrastructure without catchment +STR_TOOLBAR_AIRPORT_SET_TRACKS :{BLACK}Set tracks +STR_TOOLBAR_AIRPORT_DEFINE_RUNWAY_LANDING :{BLACK}Define a runway allowing landing +STR_TOOLBAR_AIRPORT_DEFINE_RUNWAY_NO_LANDING :{BLACK}Define a runway forbidding landing +STR_TOOLBAR_AIRPORT_BUILD_APRON :{BLACK}Build an apron +STR_TOOLBAR_AIRPORT_BUILD_HELIPAD :{BLACK}Build a helipad +STR_TOOLBAR_AIRPORT_BUILD_HELIPORT :{BLACK}Build a heliport +STR_TOOLBAR_AIRPORT_BUILD_HANGAR_STANDARD :{BLACK}Build a standard hangar +STR_TOOLBAR_AIRPORT_BUILD_HANGAR_EXTENDED :{BLACK}Build an extended hangar +STR_TOOLBAR_AIRPORT_TOGGLE_BUILD_REMOVE :{BLACK}Toggle build/remove for airport tiles, infrastructure, aprons and hangars +STR_TOOLBAR_AIRPORT_BUILD_PRE_AIRPORT_TOOLTIP :{BLACK}Build a predefined airport layout. Ctrl enables joining stations. Shift toggles building/showing cost estimate +STR_TOOLBAR_AIRPORT_CHANGE_AIRTYPE :{BLACK}Change the airtype of an airport +STR_TOOLBAR_AIRPORT_ROTATE_GRAPHICS :{BLACK}Change track graphics +STR_TOOLBAR_AIRPORT_ROTATE_GRAPHICS_TOOLTIP :{BLACK}Click over a simple airport tile to change its graphics (experimental; only works on the Drawn tracks airtype) +STR_TOOLBAR_AIRPORT_TOGGLE_GROUND :{BLACK}Toggle ground graphics +STR_TOOLBAR_AIRPORT_TOGGLE_GROUND_TOOLBAR :{BLACK}Click over a simple airport tile to show or hide the airtype ground sprite. + +STR_AIRTYPE_NAME_GRAVEL :Gravel airport +STR_AIRTYPE_NAME_ASPHALT :Asphalt airport +STR_AIRTYPE_NAME_WATER :Water airport +STR_AIRTYPE_NAME_DARK :Darker airport +STR_AIRTYPE_NAME_YELLOW :Drawn tracks airport + +# Airport infrastructure construction window +STR_BUILD_AIRPORT_INFRA_NO_CATCH_CAPTION :{WHITE}Infrastructure without cargo catchment +STR_BUILD_AIRPORT_INFRA_WITH_CATCH_CAPTION :{WHITE}Infrastructure with cargo catchment +STR_BUILD_AIRPORT_INFRA_TOOLTIP :{BLACK}Select a piece of infrastructre and its orientation + +# Heliport construction window +STR_BUILD_HELIPORT_CAPTION :{WHITE}Heliport Orientation +STR_BUILD_HELIPORT_ORIENTATION_TOOLTIP :{BLACK}Select heliport orientation + +# Hangar construction window +STR_BUILD_HANGAR_CAPTION :{WHITE}Hangar Orientation +STR_BUILD_HANGAR_ORIENTATION_TOOLTIP :{BLACK}Select hangar orientation + +# Select gfx for airport track tiles +STR_SELECT_GFX_AIRPORT_TRACKS_CAPTION :{WHITE}Selection of graphics for track tiles +STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP :{BLACK}Select the graphics for airport track tiles and click on airport track tiles. +STR_SELECT_GFX_AIRPORT_TRACKS_DEFAULT :{BLACK}Default +STR_SELECT_GFX_AIRPORT_TRACKS_DETECT :{BLACK}Autodetect sprites # Airport construction window STR_STATION_BUILD_AIRPORT_CAPTION :{WHITE}Airport Selection @@ -2960,11 +3057,19 @@ STR_AIRPORT_INTERCONTINENTAL :Intercontinenta STR_AIRPORT_HELIPORT :Heliport STR_AIRPORT_HELIDEPOT :Helidepot STR_AIRPORT_HELISTATION :Helistation +STR_AIRPORT_CUSTOM :Customized STR_AIRPORT_CLASS_SMALL :Small airports STR_AIRPORT_CLASS_LARGE :Large airports STR_AIRPORT_CLASS_HUB :Hub airports STR_AIRPORT_CLASS_HELIPORTS :Helicopter airports +STR_AIRPORT_CLASS_CUSTOMIZED :Customized airports + +###length 4 +STR_AIRPORT_ROTATION_0 :Default orientation +STR_AIRPORT_ROTATION_90 :90º rotated +STR_AIRPORT_ROTATION_180 :180º rotated +STR_AIRPORT_ROTATION_270 :270º rotated STR_STATION_BUILD_NOISE :{BLACK}Noise generated: {GOLD}{COMMA} @@ -3096,6 +3201,7 @@ STR_LAND_AREA_INFORMATION_LANDINFO_INDEX :{BLACK}Tile ind STR_LAND_AREA_INFORMATION_BUILD_DATE :{BLACK}Built/renovated: {LTBLUE}{DATE_LONG} STR_LAND_AREA_INFORMATION_STATION_CLASS :{BLACK}Station class: {LTBLUE}{STRING} STR_LAND_AREA_INFORMATION_STATION_TYPE :{BLACK}Station type: {LTBLUE}{STRING} +STR_LANG_AREA_INFORMATION_AIR_TYPE :{BLACK}Air type: {LTBLUE}{STRING} STR_LAND_AREA_INFORMATION_AIRPORT_CLASS :{BLACK}Airport class: {LTBLUE}{STRING} STR_LAND_AREA_INFORMATION_AIRPORT_NAME :{BLACK}Airport name: {LTBLUE}{STRING} STR_LAND_AREA_INFORMATION_AIRPORTTILE_NAME :{BLACK}Airport tile name: {LTBLUE}{STRING} @@ -3140,12 +3246,14 @@ STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_EXIT_NOENTRYSIGNALS :Railway track w STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_COMBO_PBSSIGNALS :Railway track with combo- and path signals STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_COMBO_NOENTRYSIGNALS :Railway track with combo- and one-way path signals STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PBS_NOENTRYSIGNALS :Railway track with path and one-way path signals -STR_LAI_RAIL_DESCRIPTION_TRAIN_DEPOT :Railway train depot +STR_LAI_RAIL_DESCRIPTION_TRAIN_DEPOT :Standard railway train depot +STR_LAI_RAIL_DESCRIPTION_TRAIN_DEPOT_EXTENDED :Extended railway train depot STR_LAI_ROAD_DESCRIPTION_ROAD :Road STR_LAI_ROAD_DESCRIPTION_ROAD_WITH_STREETLIGHTS :Road with street lights STR_LAI_ROAD_DESCRIPTION_TREE_LINED_ROAD :Tree-lined road -STR_LAI_ROAD_DESCRIPTION_ROAD_VEHICLE_DEPOT :Road vehicle depot +STR_LAI_ROAD_DESCRIPTION_ROAD_VEHICLE_DEPOT :Standard road vehicle depot +STR_LAI_ROAD_DESCRIPTION_ROAD_VEHICLE_DEPOT_EXTENDED :Extended road vehicle depot STR_LAI_ROAD_DESCRIPTION_ROAD_RAIL_LEVEL_CROSSING :Road/rail level crossing STR_LAI_ROAD_DESCRIPTION_TRAMWAY :Tramway @@ -3157,20 +3265,36 @@ STR_LAI_TREE_NAME_RAINFOREST :Rainforest STR_LAI_TREE_NAME_CACTUS_PLANTS :Cactus plants STR_LAI_STATION_DESCRIPTION_RAILROAD_STATION :Railway station -STR_LAI_STATION_DESCRIPTION_AIRCRAFT_HANGAR :Aircraft hangar -STR_LAI_STATION_DESCRIPTION_AIRPORT :Airport STR_LAI_STATION_DESCRIPTION_TRUCK_LOADING_AREA :Lorry station STR_LAI_STATION_DESCRIPTION_BUS_STATION :Bus station STR_LAI_STATION_DESCRIPTION_SHIP_DOCK :Ship dock STR_LAI_STATION_DESCRIPTION_BUOY :Buoy STR_LAI_STATION_DESCRIPTION_WAYPOINT :Waypoint +STR_LAI_STATION_DESCRIPTION_AIRPORT :Airport +STR_LAI_STATION_DESCRIPTION_AIR_INFRASTRUCTURE_WITH_CATCHMENT :Airport infrastructure with cargo catchment +STR_LAI_STATION_DESCRIPTION_AIR_INFRASTRUCTURE_WITHOUT_CATCHMENT:Airport infrastructure with no cargo catchment +STR_LAI_STATION_DESCRIPTION_AIR_PLAIN :Airport tile for tracks +STR_LAI_STATION_DESCRIPTION_AIR_WAITING_POINT :Airport waiting point +###length 4 +STR_LAI_STATION_DESCRIPTION_AIR_TERMINAL :Terminal +STR_LAI_STATION_DESCRIPTION_AIR_HELIPAD :Helipad +STR_LAI_STATION_DESCRIPTION_AIR_HELIPORT :Heliport +STR_LAI_STATION_DESCRIPTION_AIR_BUILTIN_HELIPORT :Built-in heliport +###next-name-looks-similar +STR_LAI_STATION_DESCRIPTION_AIRCRAFT_HANGAR :Standard aircraft hangar +STR_LAI_STATION_DESCRIPTION_AIRCRAFT_EXTENDED_HANGAR :Extended aircraft hangar +STR_LAI_STATION_DESCRIPTION_AIR_RUNWAY_MIDDLE :Middle runway tile +STR_LAI_STATION_DESCRIPTION_AIR_RUNWAY_END :Runway end +STR_LAI_STATION_DESCRIPTION_AIR_RUNWAY_START_NO_LANDING :Runway start only for takeoffs +STR_LAI_STATION_DESCRIPTION_AIR_RUNWAY_START_LANDING :Runway start for landing STR_LAI_WATER_DESCRIPTION_WATER :Water STR_LAI_WATER_DESCRIPTION_CANAL :Canal STR_LAI_WATER_DESCRIPTION_LOCK :Lock STR_LAI_WATER_DESCRIPTION_RIVER :River STR_LAI_WATER_DESCRIPTION_COAST_OR_RIVERBANK :Coast or riverbank -STR_LAI_WATER_DESCRIPTION_SHIP_DEPOT :Ship depot +STR_LAI_WATER_DESCRIPTION_SHIP_DEPOT :Standard ship depot +STR_LAI_WATER_DESCRIPTION_SHIP_DEPOT_EXTENDED :Extended ship depot # Industries come directly from their industry names @@ -3497,6 +3621,7 @@ STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT :{STRING1} at {H STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT_OBJECT :Object STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT_RAIL_TYPE :Rail type STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT_ROAD_TYPE :Road type +STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT_AIR_TYPE :Air type STR_NEWGRF_INSPECT_QUERY_CAPTION :{WHITE}NewGRF variable 60+x parameter (hexadecimal) @@ -3908,7 +4033,7 @@ STR_COMPANY_VIEW_INFRASTRUCTURE_RAIL :{WHITE}{COMMA} STR_COMPANY_VIEW_INFRASTRUCTURE_ROAD :{WHITE}{COMMA} road piece{P "" s} STR_COMPANY_VIEW_INFRASTRUCTURE_WATER :{WHITE}{COMMA} water tile{P "" s} STR_COMPANY_VIEW_INFRASTRUCTURE_STATION :{WHITE}{COMMA} station tile{P "" s} -STR_COMPANY_VIEW_INFRASTRUCTURE_AIRPORT :{WHITE}{COMMA} airport{P "" s} +STR_COMPANY_VIEW_INFRASTRUCTURE_AIRPORT :{WHITE}{COMMA} airport tile{P "" s} STR_COMPANY_VIEW_INFRASTRUCTURE_NONE :{WHITE}None STR_COMPANY_VIEW_BUILD_HQ_BUTTON :{BLACK}Build HQ @@ -3950,9 +4075,9 @@ STR_COMPANY_INFRASTRUCTURE_VIEW_WATER_SECT :{GOLD}Water til STR_COMPANY_INFRASTRUCTURE_VIEW_CANALS :{WHITE}Canals STR_COMPANY_INFRASTRUCTURE_VIEW_STATION_SECT :{GOLD}Stations: STR_COMPANY_INFRASTRUCTURE_VIEW_STATIONS :{WHITE}Station tiles -STR_COMPANY_INFRASTRUCTURE_VIEW_AIRPORTS :{WHITE}Airports -STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_YEAR :{WHITE}{CURRENCY_LONG}/year -STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_PERIOD :{WHITE}{CURRENCY_LONG}/period +STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_YEAR :{WHITE}{1:CURRENCY_LONG}/year ({0:COMMA}) +STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_PERIOD :{WHITE}{1:CURRENCY_LONG}/period ({0:COMMA}) +STR_COMPANY_INFRASTRUCTURE_VIEW_AIRPORT_SECT :{GOLD}Airport pieces: # Industry directory STR_INDUSTRY_DIRECTORY_CAPTION :{WHITE}Industries ({COMMA} of {COMMA}) @@ -4328,6 +4453,7 @@ STR_REPLACE_ENGINES :Engines STR_REPLACE_WAGONS :Wagons STR_REPLACE_ALL_RAILTYPE :All rail vehicles STR_REPLACE_ALL_ROADTYPE :All road vehicles +STR_REPLACE_ALL_AIRTYPE :All aircraft vehicles ###length 2 STR_REPLACE_HELP_RAILTYPE :{BLACK}Choose the rail type you want to replace engines for @@ -4343,6 +4469,12 @@ STR_REPLACE_MAGLEV_VEHICLES :Maglev Vehicles STR_REPLACE_ROAD_VEHICLES :Road Vehicles STR_REPLACE_TRAM_VEHICLES :Tramway Vehicles +STR_REPLACE_AIRCRAFT_GRAVEL_VEHICLES :Gravel aircraft +STR_REPLACE_AIRCRAFT_ASPHALT_VEHICLES :Asphalt aircraft +STR_REPLACE_AIRCRAFT_WATER_VEHICLES :Water aircraft +STR_REPLACE_AIRCRAFT_DARK_VEHICLES :Dark aircraft +STR_REPLACE_AIRCRAFT_YELLOW_VEHICLES :Drawn tracks aircraft + STR_REPLACE_REMOVE_WAGON :{BLACK}Wagon removal ({STRING1}): {ORANGE}{STRING} STR_REPLACE_REMOVE_WAGON_HELP :{BLACK}Make autoreplace keep the length of a train the same by removing wagons (starting at the front), if replacing the engine would make the train longer STR_REPLACE_REMOVE_WAGON_GROUP_HELP :{STRING}. Ctrl+Click to also apply to sub-groups @@ -4402,11 +4534,13 @@ STR_VEHICLE_STATUS_LOADING_UNLOADING :{LTBLUE}Loading STR_VEHICLE_STATUS_LEAVING :{LTBLUE}Leaving STR_VEHICLE_STATUS_WAITING_UNBUNCHING :{LTBLUE}Waiting to unbunch STR_VEHICLE_STATUS_CRASHED :{RED}Crashed! +STR_VEHICLE_STATUS_FALLING :{RED}Falling! STR_VEHICLE_STATUS_BROKEN_DOWN :{RED}Broken down STR_VEHICLE_STATUS_STOPPED :{RED}Stopped STR_VEHICLE_STATUS_TRAIN_STOPPING_VEL :{RED}{VELOCITY} - Stopping STR_VEHICLE_STATUS_TRAIN_NO_POWER :{RED}No power STR_VEHICLE_STATUS_TRAIN_STUCK :{ORANGE}Waiting for free path +STR_VEHICLE_STATUS_SERVICING :{LTBLUE}Servicing vehicle STR_VEHICLE_STATUS_AIRCRAFT_TOO_FAR :{ORANGE}Too far to next destination STR_VEHICLE_STATUS_HEADING_FOR_STATION_VEL :{LTBLUE}{1:VELOCITY} - Heading for {0:STATION} @@ -4894,6 +5028,8 @@ STR_PERCENT_DOWN_SMALL :{TINY_FONT}{WHI STR_PERCENT_DOWN :{WHITE}{NUM}%{DOWN_ARROW} STR_PERCENT_UP_DOWN_SMALL :{TINY_FONT}{WHITE}{NUM}%{UP_ARROW}{DOWN_ARROW} STR_PERCENT_UP_DOWN :{WHITE}{NUM}%{UP_ARROW}{DOWN_ARROW} +STR_SERVICING_INDICATOR_SMALL :{TINY_FONT}{LTBLUE}Servicing ({NUM}{NBSP}%) +STR_SERVICING_INDICATOR :{LTBLUE}Servicing ({NUM}{NBSP}%) STR_PERCENT_NONE_SMALL :{TINY_FONT}{WHITE}{NUM}% STR_PERCENT_NONE :{WHITE}{NUM}% @@ -5074,6 +5210,28 @@ STR_ERROR_DRIVE_THROUGH_DIRECTION :{WHITE}... road STR_ERROR_DRIVE_THROUGH_CORNER :{WHITE}... drive through stops can't have corners STR_ERROR_DRIVE_THROUGH_JUNCTION :{WHITE}... drive through stops can't have junctions +STR_ERROR_AIRPORT_DISABLED_BY_TILE :{WHITE}... setting about building by tile is disabled. +STR_ERROR_AIRPORT_INCORRECT_AIRTYPE :{WHITE}... airport has a different air type. +STR_ERROR_AIRPORT_ALREADY_AIRTYPE :{WHITE}... airport has this air type already. +STR_ERROR_AIRPORT_CAN_T_CONVERT_WATER :{WHITE}... can't convert between water and non-water airtypes. +STR_ERROR_AIRPORT_CAN_T_CONVERT_HELIPORT :{WHITE}... can't convert heliport to another airtype. +STR_ERROR_AIRPORT_CAN_T_BUILD_HELIPORT :{WHITE}... can't build heliport with this airtype. +STR_ERROR_AIRPORT_TOO_MUCH_RUNWAYS :{WHITE}... exceeding maximum number of runways for this air type. +STR_ERROR_AIRPORT_NOT_SAME_LEVEL :{WHITE}... not at the same level. +STR_ERROR_AIRPORT_PLAIN_WATER :{WHITE}... must be built on water and plain terrain. +STR_ERROR_AIRPORT_INVALID_AIR_TYPE :{WHITE}... tile has a different air type. +STR_ERROR_AIRPORT_CAN_T_HAVE_TRACKS :{WHITE}... tile can't have tracks. +STR_ERROR_AIRPORT_NO_COMPATIBLE_NEIGHBOURS :{WHITE}... can't add track. New track must be compatible with neighbour tiles. +STR_ERROR_AIRPORT_CAN_T_ADD_TRACK_HANGAR :{WHITE}... hangars can only have tracks on x or y axis. revise +STR_ERROR_AIRPORT_RUNWAY_TOO_SHORT :{WHITE}... runway too short. +STR_ERROR_AIRPORT_RUNWAY_INCOMPLETE :{WHITE}... not all runway is on the same airport station. +STR_ERROR_AIRPORT_RUNWAY_CAN_T_BUILD_OVER :{WHITE}... it is not possible to build the runway over those tiles. +STR_ERROR_AIRPORT_PRESENT_AIRCRAFT :{WHITE}... aircraft present in the airport. + +STR_ERROR_AIRPORT_RUNWAY_OVERLAP :{WHITE}... runway overlaps other runways. +STR_ERROR_AIRPORT_RUNWAY_CAN_T_REMOVE :{WHITE}... remove runways correctly. +STR_ERROR_AIRPORT_REMOVE_RUNWAYS_FIRST :{WHITE}... must remove runways first. + # Station destruction related errors STR_ERROR_CAN_T_REMOVE_PART_OF_STATION :{WHITE}Can't remove part of station... STR_ERROR_MUST_REMOVE_RAILWAY_STATION_FIRST :{WHITE}Must remove railway station first @@ -5110,10 +5268,18 @@ STR_ERROR_BUOY_IS_IN_USE :{WHITE}... buoy # Depot related errors STR_ERROR_CAN_T_BUILD_TRAIN_DEPOT :{WHITE}Can't build train depot here... +STR_ERROR_CAN_T_REMOVE_TRAIN_DEPOT :{WHITE}Can't remove train depot here... STR_ERROR_CAN_T_BUILD_ROAD_DEPOT :{WHITE}Can't build road vehicle depot here... STR_ERROR_CAN_T_BUILD_TRAM_DEPOT :{WHITE}Can't build tram vehicle depot here... STR_ERROR_CAN_T_BUILD_SHIP_DEPOT :{WHITE}Can't build ship depot here... +STR_ERROR_ADJOINS_MORE_THAN_ONE_EXISTING_DEPOT :{WHITE}... adjoins more than one existing depot +STR_ERROR_DEPOT_TYPE_NOT_AVAILABLE :{WHITE}... depot type not available +STR_ERROR_DEPOT_EXTENDING_PLATFORMS :{WHITE}Extending already reserved depot platforms +STR_ERROR_DEPOT_EXTENDED_RAIL_DEPOT_IS_NOT_FREE :{WHITE}Extended rail depot has a reserved tile and can't be converted + +STR_ERROR_CAN_T_START_STOP_VEHICLES :{WHITE}Can't start at least one vehicle in the depot... +STR_ERROR_CAN_T_REPLACE_VEHICLES :{WHITE}Can't replace vehicles in the depot... STR_ERROR_CAN_T_RENAME_DEPOT :{WHITE}Can't rename depot... STR_ERROR_TRAIN_MUST_BE_STOPPED_INSIDE_DEPOT :{WHITE}... must be stopped inside a depot @@ -5123,6 +5289,7 @@ STR_ERROR_AIRCRAFT_MUST_BE_STOPPED_INSIDE_HANGAR :{WHITE}... must STR_ERROR_TRAINS_CAN_ONLY_BE_ALTERED_INSIDE_A_DEPOT :{WHITE}Trains can only be altered when stopped inside a depot STR_ERROR_TRAIN_TOO_LONG :{WHITE}Train too long +STR_ERROR_INCOMPATIBLE_RAILTYPES_WITH_DEPOT :{WHITE}Train chain is incompatible with any tile of this depot STR_ERROR_CAN_T_REVERSE_DIRECTION_RAIL_VEHICLE :{WHITE}Can't reverse direction of vehicle... STR_ERROR_CAN_T_REVERSE_DIRECTION_RAIL_VEHICLE_MULTIPLE_UNITS :{WHITE}... consists of multiple units STR_ERROR_INCOMPATIBLE_RAIL_TYPES :Incompatible rail types @@ -5131,8 +5298,19 @@ STR_ERROR_CAN_T_MOVE_VEHICLE :{WHITE}Can't mo STR_ERROR_REAR_ENGINE_FOLLOW_FRONT :{WHITE}The rear engine will always follow its front counterpart STR_ERROR_UNABLE_TO_FIND_ROUTE_TO :{WHITE}Unable to find route to local depot STR_ERROR_UNABLE_TO_FIND_LOCAL_DEPOT :{WHITE}Unable to find local depot +STR_ERROR_UNABLE_TO_FIND_APPROPRIATE_DEPOT_TILE :{WHITE}Unable to find appropriate depot tile +STR_ERROR_DEPOT_TOO_SPREAD_OUT :{WHITE}... depot too spread out STR_ERROR_DEPOT_WRONG_DEPOT_TYPE :Wrong depot type +STR_ERROR_CAN_T_START_PLATFORM_TYPE :{WHITE}{VEHICLE} can't be started because there is no compatible platform in the depot for this type of train +STR_ERROR_CAN_T_START_PLATFORM_LONG :{WHITE}{VEHICLE} can't be started because compatible platforms are not long enough +STR_ERROR_DEPOT_FULL_DEPOT :There is no free depot compatible with this type of vehicle +###length 5 +STR_ADVICE_PLATFORM_TYPE :{WHITE}{VEHICLE} can't leave depot because there is no compatible platform for this type of train +STR_ADVICE_PLATFORM_LONG :{WHITE}{VEHICLE} can't leave depot because compatible platforms are not long enough +STR_ADVICE_VEHICLE_HAS_NO_POWER :{WHITE}{VEHICLE} can't leave depot because it has no power in any tile of the depot +STR_ADVICE_PLATFORM_FREE_PLATFORM :{WHITE}{VEHICLE} can't leave depot because compatible platforms are occupied +STR_ADVICE_PLATFORM_SIGNALS :{WHITE}{VEHICLE} can't leave depot because the segments of the compatible free platforms are occupied. Check the signaling of those segments. # Depot unbunching related errors STR_ERROR_UNBUNCHING_ONLY_ONE_ALLOWED :{WHITE}... can only have one unbunching order @@ -5297,6 +5475,8 @@ STR_ERROR_TOO_MANY_VEHICLES_IN_GAME :{WHITE}Too many STR_ERROR_CAN_T_CHANGE_SERVICING :{WHITE}Can't change servicing interval... STR_ERROR_VEHICLE_IS_DESTROYED :{WHITE}... vehicle is destroyed +STR_ERROR_NO_FREE_DEPOT :{WHITE}... there is no free depot +STR_ERROR_NO_FREE_HANGAR :{WHITE}... there is no free hangar STR_ERROR_CAN_T_CLONE_VEHICLE_LIST :{WHITE}... not all vehicles are identical @@ -5314,6 +5494,7 @@ STR_ERROR_NO_TOWN_ROADTYPES_AVAILABLE_YET_EXPLANATION :{WHITE}Start a STR_ERROR_CAN_T_MAKE_TRAIN_PASS_SIGNAL :{WHITE}Can't make train pass signal at danger... STR_ERROR_CAN_T_REVERSE_DIRECTION_TRAIN :{WHITE}Can't reverse direction of train... STR_ERROR_TRAIN_START_NO_POWER :Train has no power +STR_ERROR_ROAD_VEHICLE_START_NO_POWER :Road vehicle has no power STR_ERROR_CAN_T_MAKE_ROAD_VEHICLE_TURN :{WHITE}Can't make road vehicle turn around... @@ -5825,6 +6006,9 @@ STR_VIEWPORT_STATION_TINY :{TINY_FONT}{STA STR_VIEWPORT_WAYPOINT :{WAYPOINT} STR_VIEWPORT_WAYPOINT_TINY :{TINY_FONT}{WAYPOINT} +STR_VIEWPORT_DEPOT :{DEPOT} +STR_VIEWPORT_DEPOT_TINY :{TINY_FONT}{DEPOT} + # Simple strings to get specific types of data STR_COMPANY_NAME :{COMPANY} STR_COMPANY_NAME_COMPANY_NUM :{COMPANY} {COMPANY_NUM} diff --git a/src/misc_gui.cpp b/src/misc_gui.cpp index cb97d7c8c3e15..00c9a77a95599 100644 --- a/src/misc_gui.cpp +++ b/src/misc_gui.cpp @@ -159,6 +159,7 @@ class LandInfoWindow : public Window { td.station_class = STR_NULL; td.station_name = STR_NULL; + td.airtype = STR_NULL; td.airport_class = STR_NULL; td.airport_name = STR_NULL; td.airport_tile_name = STR_NULL; @@ -246,6 +247,12 @@ class LandInfoWindow : public Window { this->landinfo_data.push_back(GetString(STR_LAND_AREA_INFORMATION_STATION_TYPE)); } + /* Airport type */ + if (td.airtype != STR_NULL) { + SetDParam(0, td.airtype); + this->landinfo_data.push_back(GetString(STR_LANG_AREA_INFORMATION_AIR_TYPE)); + } + /* Airport class */ if (td.airport_class != STR_NULL) { SetDParam(0, td.airport_class); diff --git a/src/newgrf.cpp b/src/newgrf.cpp index 3458535b18dbe..5446596e498ec 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -16,6 +16,7 @@ #include "engine_base.h" #include "bridge.h" #include "town.h" +#include "air.h" #include "newgrf_engine.h" #include "newgrf_text.h" #include "fontcache.h" @@ -51,9 +52,12 @@ #include "road.h" #include "newgrf_roadstop.h" +#include "table/airport_defaults.h" #include "table/strings.h" #include "table/build_industry.h" +#include + #include "safeguards.h" /* TTDPatch extended GRF format codec @@ -321,6 +325,7 @@ struct GRFTempEngineData { uint16_t cargo_allowed; uint16_t cargo_disallowed; RailTypeLabel railtypelabel; + AirTypeLabel airtypelabel; uint8_t roadtramtype; const GRFFile *defaultcargo_grf; ///< GRF defining the cargo translation table to use if the default cargo is the 'first refittable'. Refittability refittability; ///< Did the newgrf set any refittability property? If not, default refittability will be applied. @@ -1766,6 +1771,25 @@ static ChangeInfoResult AircraftVehicleChangeInfo(uint engine, int numinfo, int AircraftVehicleInfo *avi = &e->u.air; switch (prop) { + case 0x05: { // Air type + uint8_t airtype = buf.ReadByte(); + + if (airtype < _cur.grffile->airtype_list.size()) { + _gted[e->index].airtypelabel = _cur.grffile->airtype_list[airtype]; + break; + } + + switch (airtype) { + case 0: _gted[e->index].airtypelabel = AIRTYPE_LABEL_GRAVEL; break; + case 1: _gted[e->index].airtypelabel = AIRTYPE_LABEL_ASPHALT; break; + case 2: _gted[e->index].airtypelabel = AIRTYPE_LABEL_WATER; break; + default: + GrfMsg(1, "AircraftVehicleChangeInfo: Invalid air type {} specified, ignoring", airtype); + break; + } + break; + } + case 0x08: { // Sprite ID uint8_t spriteid = buf.ReadByte(); uint8_t orig_spriteid = spriteid; @@ -2715,6 +2739,9 @@ static ChangeInfoResult GlobalVarChangeInfo(uint gvid, int numinfo, int prop, By case 0x17: // Tram type translation table; loading during both reservation and activation stage (in case it is selected depending on defined railtypes) return LoadTranslationTable(gvid, numinfo, buf, _cur.grffile->tramtype_list, "Tram type"); + case 0x18: // Air type translation table; loading during both reservation and activation stage (in case it is selected depending on defined airtypes) + return LoadTranslationTable(gvid, numinfo, buf, _cur.grffile->airtype_list, "Air type"); + default: break; } @@ -2933,6 +2960,9 @@ static ChangeInfoResult GlobalVarReserveInfo(uint gvid, int numinfo, int prop, B case 0x17: // Tram type translation table; loading during both reservation and activation stage (in case it is selected depending on defined tramtypes) return LoadTranslationTable(gvid, numinfo, buf, _cur.grffile->tramtype_list, "Tram type"); + case 0x18: // Air type translation table; loading during both reservation and activation stage (in case it is selected depending on defined airtypes) + return LoadTranslationTable(gvid, numinfo, buf, _cur.grffile->airtype_list, "Air type"); + default: break; } @@ -3872,6 +3902,49 @@ static ChangeInfoResult IndustriesChangeInfo(uint indid, int numinfo, int prop, return ret; } +AirType GetConversionAirtype(uint airport) +{ + struct AirportTypesConversion { + AirportTypes airport_type; + AirType air_type; + }; + + switch (_cur.grffile->grfid) { + default: + Debug(misc, 0, "Trying to load airports of unknown airtype from grffile with id {}", _cur.grffile->grfid); + return AIRTYPE_GRAVEL; + case 16860225: + return AIRTYPE_WATER; + case 19680837: // North Korean Aviation Set: Small asphalt airports + return AIRTYPE_ASPHALT; + case 5259587: { // OpenGFX+ Airports + /* This table indicates how to convert the airports provided in OpenGFX+Airports, + * as long as it is the first NewGRF to be applied that modifies airports. */ + /* The "S" shows which airports have a (close) equivalent in original airports. */ + AirportTypesConversion opengfx_plus_airports[] = { + { AT_SMALL, AIRTYPE_GRAVEL }, // S NEW_AIRPORT_OFFSET + 0 Small gravel + { AT_SMALL, AIRTYPE_WATER }, // NEW_AIRPORT_OFFSET + 1 Small water + { AT_SMALL, AIRTYPE_ASPHALT }, // NEW_AIRPORT_OFFSET + 2 Small asphalt + { AT_COMMUTER, AIRTYPE_GRAVEL }, // NEW_AIRPORT_OFFSET + 3 Commuter gravel + { AT_COMMUTER, AIRTYPE_ASPHALT }, // S NEW_AIRPORT_OFFSET + 4 Commuter asphalt + { AT_LARGE, AIRTYPE_ASPHALT }, // S NEW_AIRPORT_OFFSET + 5 Large asphalt + { AT_METROPOLITAN, AIRTYPE_ASPHALT }, // S NEW_AIRPORT_OFFSET + 6 City asphalt + { AT_INTERNATIONAL, AIRTYPE_ASPHALT }, // S NEW_AIRPORT_OFFSET + 7 International asphalt + { AT_INTERCON, AIRTYPE_ASPHALT }, // S NEW_AIRPORT_OFFSET + 8 Intercontinental asphalt -- Uses a different and non-rectangular layout. + { AT_HELIPORT, AIRTYPE_ASPHALT }, // S NEW_AIRPORT_OFFSET + 9 Heliport + { AT_HELIDEPOT, AIRTYPE_GRAVEL }, // NEW_AIRPORT_OFFSET + 10 Helidepot gravel + { AT_HELIDEPOT, AIRTYPE_ASPHALT }, // S NEW_AIRPORT_OFFSET + 11 Helidepot asphalt + { AT_HELISTATION, AIRTYPE_GRAVEL }, // NEW_AIRPORT_OFFSET + 12 Helistation gravel + { AT_HELISTATION, AIRTYPE_ASPHALT }, // S NEW_AIRPORT_OFFSET + 13 Helistation asphalt + }; + const uint num_opengfx_plus_airports = sizeof(opengfx_plus_airports)/sizeof(opengfx_plus_airports[0]); + + if (airport >= num_opengfx_plus_airports) NOT_REACHED(); + return opengfx_plus_airports[airport].air_type; + } + } +} + /** * Define properties for airports * @param airport Local ID of the airport. @@ -3889,9 +3962,11 @@ static ChangeInfoResult AirportChangeInfo(uint airport, int numinfo, int prop, B return CIR_INVALID_ID; } - /* Allocate industry specs if they haven't been allocated already. */ + /* Allocate airport specs if they haven't been allocated already. */ if (_cur.grffile->airportspec.size() < airport + numinfo) _cur.grffile->airportspec.resize(airport + numinfo); + AirType conversion_airtype = GetConversionAirtype(airport); + for (int i = 0; i < numinfo; i++) { AirportSpec *as = _cur.grffile->airportspec[airport + i].get(); @@ -3925,6 +4000,7 @@ static ChangeInfoResult AirportChangeInfo(uint airport, int numinfo, int prop, B as->grf_prop.local_id = airport + i; as->grf_prop.subst_id = subs_id; as->grf_prop.grffile = _cur.grffile; + as->airtype = conversion_airtype; /* override the default airport */ _airport_mngr.Add(airport + i, _cur.grffile->grfid, subs_id); } @@ -3932,63 +4008,81 @@ static ChangeInfoResult AirportChangeInfo(uint airport, int numinfo, int prop, B } case 0x0A: { // Set airport layout - uint8_t num_layouts = buf.ReadByte(); - buf.ReadDWord(); // Total size of definition, unneeded. - uint8_t size_x = 0; - uint8_t size_y = 0; - - std::vector layouts; - layouts.reserve(num_layouts); - - for (uint8_t j = 0; j != num_layouts; ++j) { - auto &layout = layouts.emplace_back(); - layout.rotation = static_cast(buf.ReadByte() & 6); // Rotation can only be DIR_NORTH, DIR_EAST, DIR_SOUTH or DIR_WEST. + if (_cur.grffile->grf_version <= 8) { + /* Deal with the only NewGRF that modified airport layouts. */ + const uint max_airport_tiles = 4096; // 64 * 64, max station spread. + [[maybe_unused]] uint num_tiles = as->layouts[0].size_x * as->layouts[0].size_y; + assert(num_tiles <= max_airport_tiles); + uint8_t num_layouts = buf.ReadByte(); + std::bitset defined_tiles; + buf.ReadDWord(); // Total size of the definition, unneeded. + + as->layouts.resize(1); + auto &layout = as->layouts[0]; + assert(layout.tiles.size() == num_tiles); + + for (uint8_t j = 0; j != num_layouts; ++j) { + DiagDirection rotation = (DiagDirection)(buf.ReadByte() / 2); // rotation + + for (;;) { + uint8_t x = buf.ReadByte(); // Offsets from northermost tile + uint8_t y = buf.ReadByte(); + + if (x == 0 && y == 0x80) break; + + // Get the corresponding offset for the non-rotated version. + switch (rotation) { + case 0: + break; + case 1: + Swap(x, y); + x = as->layouts[0].size_x - 1 - x; + break; + case 2: + x = as->layouts[0].size_x - 1 - x; + y = as->layouts[0].size_y - 1 - y; + break; + case 3: + Swap(x, y); + y = as->layouts[0].size_y - 1 - y; + break; + default: + NOT_REACHED(); + } - for (;;) { - auto &tile = layout.tiles.emplace_back(); - tile.ti.x = buf.ReadByte(); - tile.ti.y = buf.ReadByte(); - if (tile.ti.x == 0 && tile.ti.y == 0x80) { - /* Convert terminator to our own. */ - tile.ti.x = -0x80; - tile.ti.y = 0; - tile.gfx = 0; - break; - } + uint16_t table_index = as->layouts[0].size_x * y + x; + assert(table_index < as->layouts[0].size_x * as->layouts[0].size_y); - tile.gfx = buf.ReadByte(); + // Only keep track of first layout. + if (j == 0) defined_tiles[table_index] = true; + auto &tile = layout.tiles[table_index]; - if (tile.gfx == 0xFE) { - /* Use a new tile from this GRF */ - int local_tile_id = buf.ReadWord(); + tile.gfx[rotation] = (AirportTiles)buf.ReadByte(); - /* Read the ID from the _airporttile_mngr. */ - uint16_t tempid = _airporttile_mngr.GetID(local_tile_id, _cur.grffile->grfid); + if (tile.gfx[rotation] == 0xFE) { // gfx + int local_tile_id = buf.ReadWord(); // use a new tile for this GRFC + /* Read the ID from the _airporttile_mngr. */ + uint16_t tempid = _airporttile_mngr.GetID(local_tile_id, _cur.grffile->grfid); - if (tempid == INVALID_AIRPORTTILE) { - GrfMsg(2, "AirportChangeInfo: Attempt to use airport tile {} with airport id {}, not yet defined. Ignoring.", local_tile_id, airport + i); - } else { - /* Declared as been valid, can be used */ - tile.gfx = tempid; + if (tempid == INVALID_AIRPORTTILE) { + GrfMsg(2, "AirportChangeInfo: Attempt to use airport tile {} with airport id {}, not yet defined. Ignoring.", local_tile_id, airport + i); + } else { + /* Declared as been valid, can be used */ + tile.gfx[rotation] = (AirportTiles)tempid; + } } - } else if (tile.gfx == 0xFF) { - tile.ti.x = static_cast(GB(tile.ti.x, 0, 8)); - tile.ti.y = static_cast(GB(tile.ti.y, 0, 8)); } + } - /* Determine largest size. */ - if (layout.rotation == DIR_E || layout.rotation == DIR_W) { - size_x = std::max(size_x, tile.ti.y + 1); - size_y = std::max(size_y, tile.ti.x + 1); - } else { - size_x = std::max(size_x, tile.ti.x + 1); - size_y = std::max(size_y, tile.ti.y + 1); - } + /* Set the empty tiles if any. */ + for (int i = 0; i < as->layouts[0].size_x * as->layouts[0].size_y; i++) { + if (defined_tiles[i]) continue; + layout.tiles[i].type = ATT_INVALID; } + + } else { + ret = CIR_UNKNOWN; } - as->layouts = std::move(layouts); - as->size_x = size_x; - as->size_y = size_y; break; } @@ -4003,11 +4097,11 @@ static ChangeInfoResult AirportChangeInfo(uint airport, int numinfo, int prop, B break; case 0x0E: - as->catchment = Clamp(buf.ReadByte(), 1, MAX_CATCHMENT); + buf.ReadByte(); // Old airport catchment break; case 0x0F: - as->noise_level = buf.ReadByte(); + buf.ReadByte(); // Old airport noise break; case 0x10: @@ -4015,9 +4109,17 @@ static ChangeInfoResult AirportChangeInfo(uint airport, int numinfo, int prop, B break; case 0x11: // Maintenance cost factor - as->maintenance_cost = buf.ReadWord(); + buf.ReadWord(); break; + /* + as->num_runways = buf->ReadByte(); + as->num_aprons = buf->ReadByte(); + as->num_helipads = buf->ReadByte(); + as->num_heliports = buf->ReadByte(); + as->min_runway_length = buf->ReadByte(); + */ + default: ret = CIR_UNKNOWN; break; @@ -4203,6 +4305,262 @@ static ChangeInfoResult ObjectChangeInfo(uint id, int numinfo, int prop, ByteRea return ret; } +/** + * Define properties for airtypes + * @param id ID of the airtype. + * @param numinfo Number of subsequent IDs to change the property for. + * @param prop The property to change. + * @param buf The property value. + * @return ChangeInfoResult. + */ +static ChangeInfoResult AirTypeChangeInfo(uint id, int numinfo, int prop, ByteReader &buf) +{ + ChangeInfoResult ret = CIR_SUCCESS; + + extern AirTypeInfo _airtypes[AIRTYPE_END]; + + if (id + numinfo > AIRTYPE_END) { + GrfMsg(1, "AirTypeChangeInfo: Rail type {} is invalid, max {}, ignoring", id + numinfo, AIRTYPE_END); + return CIR_INVALID_ID; + } + + for (int i = 0; i < numinfo; i++) { + AirType at = _cur.grffile->airtype_map[id + i]; + if (at == INVALID_AIRTYPE) return CIR_INVALID_ID; + + AirTypeInfo *ati = &_airtypes[at]; + + switch (prop) { + case 0x08: // Label of air type + /* Skipped here as this is loaded during reservation stage. */ + buf.ReadDWord(); + break; + + case 0x09: { // Toolbar caption of airtype + uint16_t str = buf.ReadWord(); + AddStringForMapping(str, &ati->strings.toolbar_caption); + break; + } + + case 0x0A: // Menu text of airtype + AddStringForMapping(buf.ReadWord(), &ati->strings.menu_text); + break; + + /*case 0x0B: // Build window caption + AddStringForMapping(buf->ReadWord(), &ati->strings.build_caption); + break;*/ + + case 0x0C: // Autoreplace text + AddStringForMapping(buf.ReadWord(), &ati->strings.replace_text); + break; + + /*case 0x0D: // New locomotive text + AddStringForMapping(buf->ReadWord(), &rti->strings.new_loco); + break;*/ + + case 0x0E: // Compatible airtype list + case 0x18: // AirType list required for date introduction + case 0x19: // Introduced airtype list + { + /* Air type compatibility bits are added to the existing bits + * to allow multiple GRFs to modify compatibility with the + * default air types. */ + int n = buf.ReadByte(); + for (int j = 0; j != n; j++) { + AirTypeLabel label = buf.ReadDWord(); + AirType resolved_at = GetAirTypeByLabel(BSWAP32(label), false); + if (resolved_at != INVALID_AIRTYPE) { + switch (prop) { + case 0x0E: SetBit(ati->compatible_airtypes, resolved_at); break; + case 0x18: SetBit(ati->introduction_required_airtypes, resolved_at); break; + case 0x19: SetBit(ati->introduces_airtypes, resolved_at); break; + } + } + } + break; + } + + /*case 0x10: // Rail Type flags + rti->flags = (RailTypeFlags)buf->ReadByte(); + break;*/ + + /*case 0x11: // Curve speed advantage + rti->curve_speed = buf->ReadByte(); + break;*/ + + case 0x12: // Station graphic + ati->fallback_airtype = Clamp(buf.ReadByte(), 0, 2); + break; + + case 0x13: // Construction cost factor + ati->cost_multiplier = buf.ReadWord(); + break; + + case 0x14: // Speed limit + ati->max_speed = buf.ReadWord(); + break; + + /*case 0x15: // Acceleration model + rti->acceleration_type = Clamp(buf->ReadByte(), 0, 2); + break;*/ + + case 0x16: // Map colour + ati->map_colour = buf.ReadByte(); + break; + + case 0x17: // Introduction date + ati->introduction_date = buf.ReadDWord(); + break; + + case 0x1A: // Sort order + ati->sorting_order = buf.ReadByte(); + break; + + case 0x1B: // Name of airtype + AddStringForMapping(buf.ReadWord(), &ati->strings.name); + break; + + case 0x1C: // Maintenance cost factor + ati->maintenance_multiplier = buf.ReadWord(); + break; + + case 0x1D: // Alternate air type label list + /* Skipped here as this is loaded during reservation stage. */ + for (int j = buf.ReadByte(); j != 0; j--) buf.ReadDWord(); + break; + + case 0x1E: // Catchment radius + ati->catchment_radius = buf.ReadByte(); + break; + + case 0x1F: // Max. runways + ati->max_num_runways = buf.ReadByte(); + break; + + case 0x20: // Min. runway length + ati->min_runway_length = buf.ReadByte(); + break; + + case 0x21: // Base noise level + ati->base_noise_level = buf.ReadByte(); + break; + + case 0x22: // Runway noise level + ati->runway_noise_level = buf.ReadByte(); + break; + + case 0x23: { // Heliport availability + uint8_t availability = buf.ReadByte(); + ati->heliport_availability = HasBit(availability, 0); + break; + } + + case 0x24: { // Build this airport type on water + uint8_t on_water = buf.ReadByte(); + ati->build_on_water = HasBit(on_water, 0); + break; + } + + default: + ret = CIR_UNKNOWN; + break; + } + } + + return ret; +} + +static ChangeInfoResult AirTypeReserveInfo(uint id, int numinfo, int prop, ByteReader &buf) +{ + ChangeInfoResult ret = CIR_SUCCESS; + + extern AirTypeInfo _airtypes[AIRTYPE_END]; + + if (id + numinfo > AIRTYPE_END) { + GrfMsg(1, "AirTypeReserveInfo: Air type {} is invalid, max {}, ignoring", id + numinfo, AIRTYPE_END); + return CIR_INVALID_ID; + } + + for (int i = 0; i < numinfo; i++) { + switch (prop) { + case 0x08: // Label of air type + { + AirTypeLabel atl = buf.ReadDWord(); + atl = BSWAP32(atl); + + AirType at = GetAirTypeByLabel(atl, false); + if (at == INVALID_AIRTYPE) { + /* Set up new air type */ + at = AllocateAirType(atl); + } + + _cur.grffile->airtype_map[id + i] = at; + break; + } + + case 0x09: // Toolbar caption of airtype + case 0x0A: // Menu text + //case 0x0B: // Build window caption + case 0x0C: // Autoreplace text + //case 0x0D: // New loco + case 0x13: // Construction cost + case 0x14: // Speed limit + case 0x1B: // Name of airtype + case 0x1C: // Maintenance cost factor + buf.ReadWord(); + break; + + case 0x1D: // Alternate air type label list + if (_cur.grffile->airtype_map[id + i] != INVALID_AIRTYPE) { + int n = buf.ReadByte(); + for (int j = 0; j != n; j++) { + _airtypes[_cur.grffile->airtype_map[id + i]].alternate_labels.push_back(BSWAP32(buf.ReadDWord())); + } + break; + } + GrfMsg(1, "AirTypeReserveInfo: Ignoring property 1D for air type {} because no label was set", id + i); + [[fallthrough]]; + + + case 0x0E: // Compatible railtype list + //case 0x0F: // Powered railtype list + case 0x18: // Railtype list required for date introduction + case 0x19: // Introduced railtype list + for (int j = buf.ReadByte(); j != 0; j--) buf.ReadDWord(); + break; + + //case 0x10: // Rail Type flags + //case 0x11: // Curve speed advantage + case 0x12: // Station graphic + //case 0x15: // Acceleration model + case 0x16: // Map colour + case 0x1A: // Sort order + buf.ReadByte(); + break; + + case 0x17: // Introduction date + buf.ReadDWord(); + break; + + case 0x1E: // Catchment radius + case 0x1F: // Max. runways + case 0x20: // Min. runway length + case 0x21: // Base noise level + case 0x22: // Runway noise level + case 0x23: // Heliport availability + case 0x24: // Build this airport type on water + buf.ReadByte(); + break; + + default: + ret = CIR_UNKNOWN; + break; + } + } + + return ret; +} + /** * Define properties for railtypes * @param id ID of the railtype. @@ -4678,7 +5036,7 @@ static ChangeInfoResult AirportTilesChangeInfo(uint airtid, int numinfo, int pro /* Allocate space for this airport tile. */ if (tsp == nullptr) { - _cur.grffile->airtspec[airtid + i] = std::make_unique(*AirportTileSpec::Get(subs_id)); + _cur.grffile->airtspec[airtid + i] = std::make_unique(*AirportTileSpec::GetAirportTileSpec(subs_id)); tsp = _cur.grffile->airtspec[airtid + i].get(); tsp->enabled = true; @@ -4702,6 +5060,7 @@ static ChangeInfoResult AirportTilesChangeInfo(uint airtid, int numinfo, int pro continue; } + Debug(misc, 0, "Overriding airport tile {} {}", airtid + i, override); _airporttile_mngr.Add(airtid + i, _cur.grffile->grfid, override); break; } @@ -4925,6 +5284,7 @@ static void FeatureChangeInfo(ByteReader &buf) /* GSF_ROADTYPES */ RoadTypeChangeInfo, /* GSF_TRAMTYPES */ TramTypeChangeInfo, /* GSF_ROADSTOPS */ RoadStopChangeInfo, + /* GSF_AIRTYPES */ AirTypeChangeInfo, }; static_assert(GSF_END == lengthof(handler)); @@ -4999,7 +5359,17 @@ static void ReserveChangeInfo(ByteReader &buf) { uint8_t feature = buf.ReadByte(); - if (feature != GSF_CARGOES && feature != GSF_GLOBALVAR && feature != GSF_RAILTYPES && feature != GSF_ROADTYPES && feature != GSF_TRAMTYPES) return; + switch (feature) { + case GSF_CARGOES: + case GSF_GLOBALVAR: + case GSF_RAILTYPES: + case GSF_ROADTYPES: + case GSF_TRAMTYPES: + case GSF_AIRTYPES: + break; + default: + return; + } uint8_t numprops = buf.ReadByte(); uint8_t numinfo = buf.ReadByte(); @@ -5030,6 +5400,10 @@ static void ReserveChangeInfo(ByteReader &buf) case GSF_TRAMTYPES: cir = TramTypeReserveInfo(index, numinfo, prop, buf); break; + + case GSF_AIRTYPES: + cir = AirTypeReserveInfo(index, numinfo, prop, buf); + break; } if (HandleChangeInfoResult("ReserveChangeInfo", cir, feature, prop)) return; @@ -5334,6 +5708,7 @@ static void NewSpriteGroup(ByteReader &buf) case GSF_CARGOES: case GSF_AIRPORTS: case GSF_RAILTYPES: + case GSF_AIRTYPES: case GSF_ROADTYPES: case GSF_TRAMTYPES: { @@ -5949,6 +6324,38 @@ static void RailTypeMapSpriteGroup(ByteReader &buf, uint8_t idcount) buf.ReadWord(); } +static void AirTypeMapSpriteGroup(ByteReader &buf, uint8_t idcount) +{ + std::vector airtypes; + airtypes.reserve(idcount); + for (uint i = 0; i < idcount; i++) { + uint16_t id = buf.ReadExtendedByte(); + airtypes.push_back(id < AIRTYPE_END ? _cur.grffile->airtype_map[id] : INVALID_AIRTYPE); + } + + uint8_t cidcount = buf.ReadByte(); + for (uint c = 0; c < cidcount; c++) { + uint8_t ctype = buf.ReadByte(); + uint16_t groupid = buf.ReadWord(); + if (!IsValidGroupID(groupid, "AirTypeMapSpriteGroup")) continue; + + if (ctype >= RTSG_END) continue; + + extern AirTypeInfo _airtypes[AIRTYPE_END]; + for (auto &airtype : airtypes) { + if (airtype != INVALID_AIRTYPE) { + AirTypeInfo *ati = &_airtypes[airtype]; + + ati->grffile[ctype] = _cur.grffile; + ati->group[ctype] = _cur.spritegroups[groupid]; + } + } + } + + /* AirTypes do not use the default group. */ + buf.ReadWord(); +} + static void RoadTypeMapSpriteGroup(ByteReader &buf, uint8_t idcount, RoadTramType rtt) { RoadType *type_map = (rtt == RTT_TRAM) ? _cur.grffile->tramtype_map : _cur.grffile->roadtype_map; @@ -6199,6 +6606,10 @@ static void FeatureMapSpriteGroup(ByteReader &buf) RoadTypeMapSpriteGroup(buf, idcount, RTT_TRAM); break; + case GSF_AIRTYPES: + AirTypeMapSpriteGroup(buf, idcount); + break; + case GSF_AIRPORTTILES: AirportTileMapSpriteGroup(buf, idcount); return; @@ -6385,6 +6796,7 @@ static constexpr auto _action5_types = std::to_array({ /* 0x17 */ { A5BLOCK_ALLOW_OFFSET, SPR_RAILTYPE_TUNNEL_BASE, 1, RAILTYPE_TUNNEL_BASE_COUNT, "Railtype tunnel base" }, /* 0x18 */ { A5BLOCK_ALLOW_OFFSET, SPR_PALETTE_BASE, 1, PALETTE_SPRITE_COUNT, "Palette" }, /* 0x19 */ { A5BLOCK_ALLOW_OFFSET, SPR_ROAD_WAYPOINTS_BASE, 1, ROAD_WAYPOINTS_SPRITE_COUNT, "Road waypoints" }, + /* 0x1A */ { A5BLOCK_ALLOW_OFFSET, SPR_AIRTYPE_BASE, 1, AIRTYPE_SPRITE_TOTAL_COUNT, "Airtype graphics" }, }); /** @@ -6865,6 +7277,10 @@ static void SkipIf(ByteReader &buf) result = rt != INVALID_ROADTYPE && RoadTypeIsTram(rt); break; } + case 0x13: result = GetAirTypeByLabel(BSWAP32(cond_val)) == INVALID_AIRTYPE; + break; + case 0x14: result = GetAirTypeByLabel(BSWAP32(cond_val)) != INVALID_AIRTYPE; + break; default: GrfMsg(1, "SkipIf: Unsupported condition type {:02X}. Ignoring", condtype); return; } } else if (param == 0x88) { @@ -8767,6 +9183,9 @@ void ResetNewGRFData() /* Copy/reset original road type info data */ ResetRoadTypes(); + /* Reset air type information */ + ResetAirTypes(); + /* Allocate temporary refit/cargo class data */ _gted.resize(Engine::GetPoolSize()); @@ -8775,6 +9194,12 @@ void ResetNewGRFData() _gted[e->index].railtypelabel = GetRailTypeInfo(e->u.rail.railtype)->label; } + /* Fill air type label temporary data for default aircraft */ + for (const Engine *e : Engine::IterateType(VEH_AIRCRAFT)) { + assert(e->u.air.airtype < AIRTYPE_END); + _gted[e->index].airtypelabel = GetAirTypeInfo(e->u.air.airtype)->label; + } + /* Reset GRM reservations */ memset(&_grm_engines, 0, sizeof(_grm_engines)); memset(&_grm_cargoes, 0, sizeof(_grm_cargoes)); @@ -8931,6 +9356,12 @@ GRFFile::GRFFile(const GRFConfig *config) std::fill(std::begin(this->tramtype_map), std::end(this->tramtype_map), INVALID_ROADTYPE); this->tramtype_map[0] = ROADTYPE_TRAM; + /* Initialise air type map with default air types */ + std::fill(std::begin(this->airtype_map), std::end(this->airtype_map), INVALID_AIRTYPE); + this->airtype_map[0] = AIRTYPE_GRAVEL; + this->airtype_map[1] = AIRTYPE_ASPHALT; + this->airtype_map[2] = AIRTYPE_WATER; + /* Copy the initial parameter list * 'Uninitialised' parameters are zeroed as that is their default value when dynamically creating them. */ this->param = config->param; @@ -9505,6 +9936,7 @@ static void FinaliseAirportsArray() for (auto &as : file->airportspec) { if (as != nullptr && as->enabled) { _airport_mngr.SetEntitySpec(as.get()); + /* Fill some missing data if not provided. */ } } @@ -9956,9 +10388,10 @@ static void AfterLoadGRFs() /* Load old tram depot sprites in new position, if no new ones are present */ ActivateOldTramDepot(); - /* Set up custom rail types */ + /* Set up custom rail, road and air types */ InitRailTypes(); InitRoadTypes(); + InitAirTypes(); for (Engine *e : Engine::IterateType(VEH_ROAD)) { if (_gted[e->index].rv_max_speed != 0) { @@ -10003,6 +10436,16 @@ static void AfterLoadGRFs() } } + for (Engine *e : Engine::IterateType(VEH_AIRCRAFT)) { + AirType airtype = GetAirTypeByLabel(_gted[e->index].airtypelabel); + if (airtype == INVALID_AIRTYPE) { + /* Air type is not available, so disable this engine */ + e->info.climates = 0; + } else { + e->u.air.airtype = airtype; + } + } + SetYearEngineAgingStops(); FinalisePriceBaseMultipliers(); diff --git a/src/newgrf.h b/src/newgrf.h index e49c22ee5c590..f6e326c47d5b2 100644 --- a/src/newgrf.h +++ b/src/newgrf.h @@ -13,6 +13,7 @@ #include "cargotype.h" #include "rail_type.h" #include "road_type.h" +#include "air_type.h" #include "fileio_type.h" #include "newgrf_text_type.h" #include "core/bitmath_func.hpp" @@ -86,6 +87,7 @@ enum GrfSpecFeature { GSF_ROADTYPES, GSF_TRAMTYPES, GSF_ROADSTOPS, + GSF_AIRTYPES, GSF_END, GSF_FAKE_TOWNS = GSF_END, ///< Fake town GrfSpecFeature for NewGRF debugging (parent scope) @@ -139,6 +141,9 @@ struct GRFFile : ZeroedMemoryAllocator { std::vector tramtype_list; ///< Roadtype translation table (tram) RoadType tramtype_map[ROADTYPE_END]; + std::vector airtype_list; ///< AirType translation table + AirType airtype_map[AIRTYPE_END]; + CanalProperties canal_local_properties[CF_END]; ///< Canal properties as set by this NewGRF std::unordered_map language_map; ///< Mappings related to the languages. diff --git a/src/newgrf_airport.cpp b/src/newgrf_airport.cpp index 0d90c0adad8f9..0d04b40604e6a 100644 --- a/src/newgrf_airport.cpp +++ b/src/newgrf_airport.cpp @@ -15,9 +15,17 @@ #include "station_base.h" #include "newgrf_class_func.h" #include "town.h" +#include "air.h" +#include "table/airport_defaults.h" #include "safeguards.h" +uint8_t AirportSpec::GetAirportNoise(AirType airtype) const +{ + const AirTypeInfo *ati = GetAirTypeInfo(airtype); + return this->num_aprons + this->num_helipads + this->num_heliports + this->num_runways * ati->runway_noise_level + ati->base_noise_level; +} + /** * Reset airport classes to their default state. * This includes initialising the defaults classes with an empty @@ -30,6 +38,7 @@ template <> AirportClass::Get(AirportClass::Allocate('LARG'))->name = STR_AIRPORT_CLASS_LARGE; AirportClass::Get(AirportClass::Allocate('HUB_'))->name = STR_AIRPORT_CLASS_HUB; AirportClass::Get(AirportClass::Allocate('HELI'))->name = STR_AIRPORT_CLASS_HELIPORTS; + AirportClass::Get(AirportClass::Allocate('CUST'))->name = STR_AIRPORT_CLASS_CUSTOMIZED; } template <> @@ -78,28 +87,54 @@ AirportSpec AirportSpec::specs[NUM_AIRPORTS]; ///< Airport specifications. return &AirportSpec::specs[type]; } -/** Check whether this airport is available to build. */ -bool AirportSpec::IsAvailable() const +/** + * Check whether this airport is available to build. + * @param airtype the airtype to check for, or INVALID_AIRTYPE + * to check against default airtype for this airport spec + * @return whether this airport spec is available. + */ +bool AirportSpec::IsAvailable(AirType air_type) const { if (!this->enabled) return false; if (TimerGameCalendar::year < this->min_year) return false; + + if (air_type != INVALID_AIRTYPE) { + const AirTypeInfo *ati = GetAirTypeInfo(air_type); + assert(ati != nullptr); + if (ati->max_num_runways < this->num_runways) return false; + if (this->num_runways > 0 && ati->min_runway_length > this->min_runway_length) return false; + + if (!GetAirTypeInfo(air_type)->heliport_availability) { + /* Check at least one layout doesn't have any heliport. */ + bool all_have_heliport = true; + for (uint layout_num = 0; all_have_heliport && layout_num < this->layouts.size(); layout_num++) { + bool has_heliport = false; + uint num_tiles = this->layouts[layout_num].size_x * this->layouts[layout_num].size_y; + for (uint tile_num = 0; (tile_num < num_tiles) && !has_heliport; tile_num++) { + if (this->layouts[layout_num].tiles[tile_num].type == ATT_APRON_HELIPORT) has_heliport = true; + } + if (!has_heliport) all_have_heliport = false; + } + if (all_have_heliport) return false; + } + } + if (_settings_game.station.never_expire_airports) return true; return TimerGameCalendar::year <= this->max_year; } /** * Check if the airport would be within the map bounds at the given tile. - * @param table Selected layout table. This affects airport rotation, and therefore dimensions. + * @param rotation Selected rotation. This affects airport rotation, and therefore dimensions. * @param tile Top corner of the airport. * @return true iff the airport would be within the map bounds at the given tile. */ -bool AirportSpec::IsWithinMapBounds(uint8_t table, TileIndex tile) const +bool AirportSpec::IsWithinMapBounds(uint8_t rotation, TileIndex tile, uint8_t layout) const { - if (table >= this->layouts.size()) return false; + uint8_t w = this->layouts[layout].size_x; + uint8_t h = this->layouts[layout].size_y; - uint8_t w = this->size_x; - uint8_t h = this->size_y; - if (this->layouts[table].rotation == DIR_E || this->layouts[table].rotation == DIR_W) Swap(w, h); + if (rotation % 2 != 0) Swap(w, h); return TileX(tile) + w < Map::SizeX() && TileY(tile) + h < Map::SizeY(); @@ -276,6 +311,9 @@ StringID GetAirportTextCallback(const AirportSpec *as, uint8_t layout, uint16_t AirportResolverObject object(INVALID_TILE, nullptr, as, layout, (CallbackID)callback); uint16_t cb_res = object.ResolveCallback(); if (cb_res == CALLBACK_FAILED || cb_res == 0x400) return STR_UNDEFINED; + + // Old GRF files that provided airport layouts, provided now unneeded rotated layouts. + if (callback == CBID_AIRPORT_LAYOUT_NAME && as->grf_prop.grffile->grf_version <= 8) return STR_UNDEFINED; if (cb_res > 0x400) { ErrorUnknownCallbackResult(as->grf_prop.grffile->grfid, callback, cb_res); return STR_UNDEFINED; diff --git a/src/newgrf_airport.h b/src/newgrf_airport.h index 3242e482c5842..b58bc66f67e6c 100644 --- a/src/newgrf_airport.h +++ b/src/newgrf_airport.h @@ -18,54 +18,11 @@ #include "newgrf_town.h" #include "tilearea_type.h" +#include "table/airporttile_ids.h" + /** Copy from station_map.h */ typedef uint8_t StationGfx; -/** Tile-offset / AirportTileID pair. */ -struct AirportTileTable { - TileIndexDiffC ti; ///< Tile offset from the top-most airport tile. - StationGfx gfx; ///< AirportTile to use for this tile. -}; - -/** Iterator to iterate over all tiles belonging to an airport spec. */ -class AirportTileTableIterator : public TileIterator { -private: - const AirportTileTable *att; ///< The offsets. - TileIndex base_tile; ///< The tile we base the offsets off. - -public: - /** - * Construct the iterator. - * @param att The TileTable we want to iterate over. - * @param base_tile The basetile for all offsets. - */ - AirportTileTableIterator(const AirportTileTable *att, TileIndex base_tile) : TileIterator(base_tile + ToTileIndexDiff(att->ti)), att(att), base_tile(base_tile) - { - } - - inline TileIterator& operator ++() override - { - this->att++; - if (this->att->ti.x == -0x80) { - this->tile = INVALID_TILE; - } else { - this->tile = this->base_tile + ToTileIndexDiff(this->att->ti); - } - return *this; - } - - /** Get the StationGfx for the current tile. */ - StationGfx GetStationGfx() const - { - return this->att->gfx; - } - - std::unique_ptr Clone() const override - { - return std::make_unique(*this); - } -}; - /** List of default airport classes. */ enum AirportClassID { APC_BEGIN = 0, ///< Lowest valid airport class id @@ -73,6 +30,7 @@ enum AirportClassID { APC_LARGE, ///< id for large airports class APC_HUB, ///< id for hub airports class APC_HELIPORT, ///< id for heliports + APC_CUSTOM, ///< customized airport class APC_MAX = 16, ///< maximum number of airport classes }; @@ -87,44 +45,117 @@ enum TTDPAirportType { ATP_TTDP_OILRIG, ///< Same as AT_OILRIG }; -/** A list of all hangar tiles in an airport */ -struct HangarTileTable { - TileIndexDiffC ti; ///< Tile offset from the top-most airport tile. - Direction dir; ///< Direction of the exit. - uint8_t hangar_num; ///< The hangar to which this tile belongs. +struct AirportTileTable { + AirportTileType type; // Use this tile will have (apron, tracks,...). + ApronType apron_type{APRON_INVALID}; // Subtype of apron. + DiagDirection dir{INVALID_DIAGDIR}; // Direction of runway or exit direction of hangars. + TrackBits trackbits{TRACK_BIT_NONE}; // Tracks for this tile. + Direction runway_directions{INVALID_DIR}; // Directions of the runways present on this tile. + // Maps a direction into the diagonal directions of the runways. + AirportTiles at_gfx{ATTG_DEFAULT_GFX}; // Sprite for this tile as provided by an airtype. + AirportTiles gfx[DIAGDIR_END] { // Sprites for this tile. + INVALID_AIRPORTTILE, + INVALID_AIRPORTTILE, + INVALID_AIRPORTTILE, + INVALID_AIRPORTTILE + }; + + void SetGfx(AirportTiles gfx) { + this->gfx[DIAGDIR_BEGIN] = gfx; + } + + /* Description for simple track tiles. */ + AirportTileTable(AirportTileType att, TrackBits trackbits, AirportTiles gfx = INVALID_AIRPORTTILE) : + type(att), + trackbits(trackbits) + { + assert(att == ATT_SIMPLE_TRACK); + SetGfx(gfx); + } + + /* Description for aprons, helipads and heliports. */ + AirportTileTable(AirportTileType att, TrackBits trackbits, ApronType type, + AirportTiles gfx = INVALID_AIRPORTTILE) : + type(att), + apron_type(type), + trackbits(trackbits) + { + assert(att >= ATT_APRON_NORMAL && att <= ATT_APRON_BUILTIN_HELIPORT); + SetGfx(gfx); + } + + /* Description for hangars and runway end and start. */ + AirportTileTable(AirportTileType att, TrackBits trackbits, DiagDirection dir, AirportTiles gfx = INVALID_AIRPORTTILE) : + type(att), + dir(dir), + trackbits(trackbits) + { + assert(att == ATT_HANGAR_STANDARD || + att == ATT_HANGAR_EXTENDED || + att == ATT_RUNWAY_END || + att == ATT_RUNWAY_START_ALLOW_LANDING || + att == ATT_RUNWAY_START_NO_LANDING); + SetGfx(gfx); + } + + /* Description for middle parts of runways. */ + AirportTileTable(AirportTileType att, TrackBits trackbits, Direction runway_directions, AirportTiles gfx = INVALID_AIRPORTTILE) : + type(att), + trackbits(trackbits), + runway_directions(runway_directions) + { + assert(att == ATT_RUNWAY_MIDDLE); + assert(IsValidDirection(runway_directions)); + SetGfx(gfx); + } + + /* Description for infrastructure. */ + AirportTileTable(AirportTileType att, AirportTiles at_gfx, DiagDirection rotation = DIAGDIR_NE, + AirportTiles gfx = INVALID_AIRPORTTILE) : + type(att), + dir(rotation), + at_gfx(at_gfx) + { + assert(att == ATT_INFRASTRUCTURE_WITH_CATCH || att == ATT_INFRASTRUCTURE_NO_CATCH); + SetGfx(gfx); + } + + /* Description for a non-airport tile, for non-rectangular airports. */ + AirportTileTable() : type(ATT_INVALID) {} }; struct AirportTileLayout { std::vector tiles; ///< List of all tiles in this layout. - Direction rotation; ///< The rotation of this layout. + uint8_t size_x; ///< size of airport in x direction + uint8_t size_y; ///< size of airport in y direction }; /** * Defines the data structure for an airport. */ struct AirportSpec : NewGRFSpecBase { - const struct AirportFTAClass *fsm; ///< the finite statemachine for the default airports - std::vector layouts; ///< List of layouts composing the airport. - std::span depots; ///< Position of the depots on the airports. - uint8_t size_x; ///< size of airport in x direction - uint8_t size_y; ///< size of airport in y direction - uint8_t noise_level; ///< noise that this airport generates - uint8_t catchment; ///< catchment area of this airport + std::vector layouts;///< list of the different layouts + AirType airtype; ///< the airtype for this set of layouts + uint8_t num_runways; ///< number of runways + uint8_t num_aprons; ///< number of aprons + uint8_t num_helipads; ///< number of helipads + uint8_t num_heliports; ///< number of heliports + uint8_t min_runway_length; ///< length of the shortest runway TimerGameCalendar::Year min_year; ///< first year the airport is available TimerGameCalendar::Year max_year; ///< last year the airport is available StringID name; ///< name of this airport TTDPAirportType ttd_airport_type; ///< ttdpatch airport type (Small/Large/Helipad/Oilrig) SpriteID preview_sprite; ///< preview sprite for this airport - uint16_t maintenance_cost; ///< maintenance cost multiplier - /* Newgrf data */ bool enabled; ///< Entity still available (by default true). Newgrf can disable it, though. + bool has_hangar; + bool has_heliport; struct GRFFileProps grf_prop; ///< Properties related to the grf file. static const AirportSpec *Get(uint8_t type); static AirportSpec *GetWithoutOverride(uint8_t type); - bool IsAvailable() const; - bool IsWithinMapBounds(uint8_t table, TileIndex index) const; + bool IsAvailable(AirType air_type = INVALID_AIRTYPE) const; + bool IsWithinMapBounds(uint8_t table, TileIndex index, uint8_t layout) const; static void ResetAirports(); @@ -135,7 +166,10 @@ struct AirportSpec : NewGRFSpecBase { return static_cast(std::distance(std::cbegin(specs), this)); } - static const AirportSpec dummy; ///< The dummy airport. + uint8_t GetAirportNoise(AirType airtype) const; + + static const AirportSpec custom; ///< The customized airports specs. + static const AirportSpec dummy; ///< The dummy airport. private: static AirportSpec specs[NUM_AIRPORTS]; ///< Specs of the airports. diff --git a/src/newgrf_airporttiles.cpp b/src/newgrf_airporttiles.cpp index 8cd612318212e..1b218c1a40753 100644 --- a/src/newgrf_airporttiles.cpp +++ b/src/newgrf_airporttiles.cpp @@ -16,6 +16,7 @@ #include "water.h" #include "landscape.h" #include "company_base.h" +#include "air_map.h" #include "town.h" #include "table/strings.h" #include "table/airporttiles.h" @@ -26,6 +27,7 @@ extern uint32_t GetRelativePosition(TileIndex tile, TileIndex ind_tile); AirportTileSpec AirportTileSpec::tiles[NUM_AIRPORTTILES]; +AirportTileSpec AirportTileSpec::airtype_tiles[NUM_AIRTYPE_INFRATILES]; AirportTileOverrideManager _airporttile_mngr(NEW_AIRPORTTILE_OFFSET, NUM_AIRPORTTILES, INVALID_AIRPORTTILE); @@ -34,7 +36,7 @@ AirportTileOverrideManager _airporttile_mngr(NEW_AIRPORTTILE_OFFSET, NUM_AIRPORT * @param gfx index of airport tile * @return A pointer to the corresponding AirportTileSpec */ -/* static */ const AirportTileSpec *AirportTileSpec::Get(StationGfx gfx) +/* static */ const AirportTileSpec *AirportTileSpec::GetAirportTileSpec(StationGfx gfx) { /* should be assert(gfx < lengthof(tiles)), but that gives compiler warnings * since it's always true if the following holds: */ @@ -42,6 +44,17 @@ AirportTileOverrideManager _airporttile_mngr(NEW_AIRPORTTILE_OFFSET, NUM_AIRPORT return &AirportTileSpec::tiles[gfx]; } +/** + * Retrieve airtype tile spec for the given airport tile + * @param gfx index of airport tile + * @return A pointer to the corresponding AirportTileSpec + */ +/* static */ const AirportTileSpec *AirportTileSpec::GetAirtypeTileSpec(StationGfx gfx) +{ + assert(gfx < ATTG_END); + return &AirportTileSpec::airtype_tiles[gfx]; +} + /** * Retrieve airport tile spec for the given airport tile. * @param tile The airport tile. @@ -49,7 +62,11 @@ AirportTileOverrideManager _airporttile_mngr(NEW_AIRPORTTILE_OFFSET, NUM_AIRPORT */ /* static */ const AirportTileSpec *AirportTileSpec::GetByTile(TileIndex tile) { - return AirportTileSpec::Get(GetAirportGfx(tile)); + assert(IsTileType(tile, MP_STATION) && IsAirport(tile)); + + if (HasAirtypeGfx(tile)) return AirportTileSpec::GetAirtypeTileSpec(GetAirportGfx(tile)); + + return AirportTileSpec::GetAirportTileSpec(GetAirportGfx(tile)); } /** @@ -60,6 +77,9 @@ void AirportTileSpec::ResetAirportTiles() auto insert = std::copy(std::begin(_origin_airporttile_specs), std::end(_origin_airporttile_specs), std::begin(AirportTileSpec::tiles)); std::fill(insert, std::end(AirportTileSpec::tiles), AirportTileSpec{}); + auto insert_airtype = std::copy(std::begin(_origin_airtype_specs), std::end(_origin_airtype_specs), std::begin(AirportTileSpec::airtype_tiles)); + std::fill(insert_airtype, std::end(AirportTileSpec::airtype_tiles), AirportTileSpec{}); + /* Reset any overrides that have been set. */ _airporttile_mngr.ResetOverride(); } @@ -95,8 +115,8 @@ void AirportTileOverrideManager::SetEntitySpec(const AirportTileSpec *airpts) */ StationGfx GetTranslatedAirportTileID(StationGfx gfx) { - const AirportTileSpec *it = AirportTileSpec::Get(gfx); - return it->grf_prop.override == INVALID_AIRPORTTILE ? gfx : it->grf_prop.override; + const AirportTileSpec *it = AirportTileSpec::GetAirportTileSpec(gfx); + return it->grf_prop.override == (StationGfx)INVALID_AIRPORTTILE ? gfx : it->grf_prop.override; } /** @@ -131,7 +151,7 @@ static uint32_t GetAirportTileIDAtOffset(TileIndex tile, const Station *st, uint } StationGfx gfx = GetAirportGfx(tile); - const AirportTileSpec *ats = AirportTileSpec::Get(gfx); + const AirportTileSpec *ats = AirportTileSpec::GetAirportTileSpec(gfx); if (gfx < NEW_AIRPORTTILE_OFFSET) { // Does it belongs to an old type? /* It is an old tile. We have to see if it's been overridden */ @@ -139,7 +159,7 @@ static uint32_t GetAirportTileIDAtOffset(TileIndex tile, const Station *st, uint return 0xFF << 8 | gfx; // no. Tag FF + the gfx id of that tile } /* Overridden */ - const AirportTileSpec *tile_ovr = AirportTileSpec::Get(ats->grf_prop.override); + const AirportTileSpec *tile_ovr = AirportTileSpec::GetAirportTileSpec(ats->grf_prop.override); if (tile_ovr->grf_prop.grffile->grfid == cur_grfid) { return tile_ovr->grf_prop.local_id; // same grf file diff --git a/src/newgrf_airporttiles.h b/src/newgrf_airporttiles.h index 34b36691286bf..2eeb97dcd18fc 100644 --- a/src/newgrf_airporttiles.h +++ b/src/newgrf_airporttiles.h @@ -73,13 +73,15 @@ struct AirportTileSpec { bool enabled; ///< entity still available (by default true). newgrf can disable it, though GRFFileProps grf_prop; ///< properties related the the grf file - static const AirportTileSpec *Get(StationGfx gfx); + static const AirportTileSpec *GetAirportTileSpec(StationGfx gfx); + static const AirportTileSpec *GetAirtypeTileSpec(StationGfx gfx); static const AirportTileSpec *GetByTile(TileIndex tile); static void ResetAirportTiles(); private: static AirportTileSpec tiles[NUM_AIRPORTTILES]; + static AirportTileSpec airtype_tiles[NUM_AIRTYPE_INFRATILES]; friend void AirportTileOverrideManager::SetEntitySpec(const AirportTileSpec *airpts); }; diff --git a/src/newgrf_airtype.cpp b/src/newgrf_airtype.cpp new file mode 100644 index 0000000000000..87f049399cb6a --- /dev/null +++ b/src/newgrf_airtype.cpp @@ -0,0 +1,148 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file newgrf_airtype.cpp NewGRF handling of air types. */ + +#include "stdafx.h" +#include "core/container_func.hpp" +#include "debug.h" +#include "newgrf_airtype.h" +#include "station_base.h" +#include "depot_base.h" +#include "town.h" + +#include "safeguards.h" + +/* virtual */ uint32_t AirTypeScopeResolver::GetRandomBits() const +{ + uint tmp = CountBits(this->tile.base() + (TileX(this->tile) + TileY(this->tile)) * TILE_SIZE); + return GB(tmp, 0, 2); +} + +/* virtual */ uint32_t AirTypeScopeResolver::GetVariable(uint8_t variable, [[maybe_unused]] uint32_t parameter, bool &available) const +{ + if (this->tile == INVALID_TILE) { + switch (variable) { + case 0x40: return 0; + case 0x41: return 0; + case 0x42: return 0; + case 0x43: return TimerGameCalendar::date.base(); + case 0x44: return HZB_TOWN_EDGE; + } + } + + switch (variable) { + case 0x40: return GetTerrainType(this->tile, this->context); + case 0x41: return 0; + case 0x42: return 0; + case 0x43: + if (IsHangarTile(this->tile)) return Depot::GetByTile(this->tile)->build_date.base(); + return TimerGameCalendar::date.base(); + case 0x44: { + const Town *t = nullptr; + t = Station::GetByTile(this->tile)->town; + return t != nullptr ? GetTownRadiusGroup(t, this->tile) : HZB_TOWN_EDGE; + } + } + + Debug(grf, 1, "Unhandled air type tile variable 0x{:X}", variable); + + available = false; + return UINT_MAX; +} + +GrfSpecFeature AirTypeResolverObject::GetFeature() const +{ + return GSF_AIRTYPES; +} + +uint32_t AirTypeResolverObject::GetDebugID() const +{ + return this->airtype_scope.ati->label; +} + +/** + * Resolver object for air types. + * @param ati AirType. nullptr in NewGRF Inspect window. + * @param tile %Tile containing the track. For track on a bridge this is the southern bridgehead. + * @param context Are we resolving sprites for the upper halftile, or on a bridge? + * @param rtsg Airpart of interest + * @param param1 Extra parameter (first parameter of the callback, except airtypes do not have callbacks). + * @param param2 Extra parameter (second parameter of the callback, except airtypes do not have callbacks). + */ +AirTypeResolverObject::AirTypeResolverObject(const AirTypeInfo *ati, TileIndex tile, TileContext context, AirTypeSpriteGroup rtsg, uint32_t param1, uint32_t param2) + : ResolverObject(ati != nullptr ? ati->grffile[rtsg] : nullptr, CBID_NO_CALLBACK, param1, param2), airtype_scope(*this, ati, tile, context) +{ + this->root_spritegroup = ati != nullptr ? ati->group[rtsg] : nullptr; +} + +/** + * Get the sprite to draw for the given tile. + * @param ati The air type data (spec). + * @param tile The tile to get the sprite for. + * @param rtsg The type of sprite to draw. + * @param content Where are we drawing the tile? + * @param [out] num_results If not nullptr, return the number of sprites in the spriteset. + * @return The sprite to draw. + */ +SpriteID GetCustomAirSprite(const AirTypeInfo *ati, TileIndex tile, AirTypeSpriteGroup atsg, TileContext context, uint *num_results) +{ + assert(atsg < ATSG_END); + + if (ati->group[atsg] == nullptr) return 0; + + AirTypeResolverObject object(ati, tile, context, atsg); + const SpriteGroup *group = object.Resolve(); + if (group == nullptr || group->GetNumResults() == 0) return 0; + + if (num_results != nullptr) *num_results = group->GetNumResults(); + + return group->GetResult(); +} + +/** + * Translate an index to the GRF-local airtype-translation table into an AirType. + * @param airtype Index into GRF-local translation table. + * @param grffile Originating GRF file. + * @return AirType or INVALID_AIRTYPE if the airtype is unknown. + */ +AirType GetAirTypeTranslation(uint8_t airtype, const GRFFile *grffile) +{ + if (grffile == nullptr || grffile->airtype_list.size() == 0) { + /* No airtype table present. Return airtype as-is (if valid), so it works for original airtypes. */ + if (airtype >= AIRTYPE_END || GetAirTypeInfo(static_cast(airtype))->label == 0) return INVALID_AIRTYPE; + + return static_cast(airtype); + } else { + /* AirType table present, but invalid index, return invalid type. */ + if (airtype >= grffile->airtype_list.size()) return INVALID_AIRTYPE; + + /* Look up airtype including alternate labels. */ + return GetAirTypeByLabel(grffile->airtype_list[airtype]); + } +} + +/** + * Perform a reverse airtype lookup to get the GRF internal ID. + * @param airtype The global (OpenTTD) airtype. + * @param grffile The GRF to do the lookup for. + * @return the GRF internal ID. + */ +uint8_t GetReverseAirTypeTranslation(AirType airtype, const GRFFile *grffile) +{ + /* No air type table present, return air type as-is */ + if (grffile == nullptr || grffile->airtype_list.size() == 0) return airtype; + + /* Look for a matching air type label in the table */ + AirTypeLabel label = GetAirTypeInfo(airtype)->label; + + int idx = find_index(grffile->airtype_list, label); + if (idx >= 0) return idx; + + /* If not found, return invalid. */ + return 0xFF; +} diff --git a/src/newgrf_airtype.h b/src/newgrf_airtype.h new file mode 100644 index 0000000000000..d5f54c9b5514d --- /dev/null +++ b/src/newgrf_airtype.h @@ -0,0 +1,61 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file newgrf_airtype.h NewGRF handling of air types. */ + +#ifndef NEWGRF_AIRTYPE_H +#define NEWGRF_AIRTYPE_H + +#include "air.h" +#include "newgrf_commons.h" +#include "newgrf_spritegroup.h" + +/** Resolver for the railtype scope. */ +struct AirTypeScopeResolver : public ScopeResolver { + TileIndex tile; ///< Tracktile. For track on a bridge this is the southern bridgehead. + TileContext context; ///< Are we resolving sprites for the upper halftile, or on a bridge? + const AirTypeInfo *ati; ///< The corresponding airtype info. + + /** + * Constructor of the airtype scope resolvers. + * @param ro Surrounding resolver. + * @param ati AirTypeInfo + * @param tile %Tile containing the track. For track on a bridge this is the southern bridgehead. + * @param context Are we resolving sprites for the upper halftile, or on a bridge? + */ + AirTypeScopeResolver(ResolverObject &ro, const AirTypeInfo *ati, TileIndex tile, TileContext context) : + ScopeResolver(ro), tile(tile), context(context), ati(ati) + { + } + + uint32_t GetRandomBits() const override; + uint32_t GetVariable(uint8_t variable, uint32_t parameter, bool &available) const override; +}; + +/** Resolver object for air types. */ +struct AirTypeResolverObject : public ResolverObject { + AirTypeScopeResolver airtype_scope; ///< Resolver for the airtype scope. + + AirTypeResolverObject(const AirTypeInfo *ati, TileIndex tile, TileContext context, AirTypeSpriteGroup rtsg, uint32_t param1 = 0, uint32_t param2 = 0); + + ScopeResolver *GetScope(VarSpriteGroupScope scope = VSG_SCOPE_SELF, uint8_t relative = 0) override + { + if (scope == VSG_SCOPE_SELF) return &this->airtype_scope; + + return ResolverObject::GetScope(scope, relative); + } + + GrfSpecFeature GetFeature() const override; + uint32_t GetDebugID() const override; +}; + +SpriteID GetCustomAirSprite(const AirTypeInfo *ati, TileIndex tile, AirTypeSpriteGroup atsg, TileContext context = TCX_NORMAL, uint *num_results = nullptr); + +AirType GetAirTypeTranslation(uint8_t airtype, const GRFFile *grffile); +uint8_t GetReverseAirTypeTranslation(AirType railtype, const GRFFile *grffile); + +#endif /* NEWGRF_AIRTYPE_H */ diff --git a/src/newgrf_debug_gui.cpp b/src/newgrf_debug_gui.cpp index 1b6c36752020d..1149f42891951 100644 --- a/src/newgrf_debug_gui.cpp +++ b/src/newgrf_debug_gui.cpp @@ -773,7 +773,7 @@ GrfSpecFeature GetGrfSpecFeature(TileIndex tile) case MP_STATION: switch (GetStationType(tile)) { case STATION_RAIL: return GSF_STATIONS; - case STATION_AIRPORT: return GSF_AIRPORTTILES; + case STATION_AIRPORT: return HasAirtypeGfx(tile) ? GSF_AIRTYPES : GSF_AIRPORTTILES; case STATION_BUS: return GSF_ROADSTOPS; case STATION_TRUCK: return GSF_ROADSTOPS; default: return GSF_INVALID; diff --git a/src/newgrf_engine.cpp b/src/newgrf_engine.cpp index b5659581bdfd9..1a19cfa87fd29 100644 --- a/src/newgrf_engine.cpp +++ b/src/newgrf_engine.cpp @@ -21,9 +21,11 @@ #include "aircraft.h" #include "station_base.h" #include "company_base.h" +#include "newgrf_airtype.h" #include "newgrf_railtype.h" #include "newgrf_roadtype.h" #include "ship.h" +#include "air_map.h" #include "safeguards.h" @@ -94,35 +96,35 @@ static int MapOldSubType(const Vehicle *v) /* TTDP style aircraft movement states for GRF Action 2 Var 0xE2 */ enum TTDPAircraftMovementStates { - AMS_TTDP_HANGAR, - AMS_TTDP_TO_HANGAR, - AMS_TTDP_TO_PAD1, - AMS_TTDP_TO_PAD2, - AMS_TTDP_TO_PAD3, - AMS_TTDP_TO_ENTRY_2_AND_3, - AMS_TTDP_TO_ENTRY_2_AND_3_AND_H, - AMS_TTDP_TO_JUNCTION, - AMS_TTDP_LEAVE_RUNWAY, - AMS_TTDP_TO_INWAY, - AMS_TTDP_TO_RUNWAY, - AMS_TTDP_TO_OUTWAY, - AMS_TTDP_WAITING, - AMS_TTDP_TAKEOFF, - AMS_TTDP_TO_TAKEOFF, - AMS_TTDP_CLIMBING, - AMS_TTDP_FLIGHT_APPROACH, - AMS_TTDP_UNUSED_0x11, - AMS_TTDP_FLIGHT_TO_TOWER, - AMS_TTDP_UNUSED_0x13, - AMS_TTDP_FLIGHT_FINAL, - AMS_TTDP_FLIGHT_DESCENT, - AMS_TTDP_BRAKING, - AMS_TTDP_HELI_TAKEOFF_AIRPORT, - AMS_TTDP_HELI_TO_TAKEOFF_AIRPORT, - AMS_TTDP_HELI_LAND_AIRPORT, - AMS_TTDP_HELI_TAKEOFF_HELIPORT, - AMS_TTDP_HELI_TO_TAKEOFF_HELIPORT, - AMS_TTDP_HELI_LAND_HELIPORT, + AMS_TTDP_HANGAR, // used (in hangar) + AMS_TTDP_TO_HANGAR, // used (moving towards hangar) + AMS_TTDP_TO_PAD1, // + AMS_TTDP_TO_PAD2, // + AMS_TTDP_TO_PAD3, // + AMS_TTDP_TO_ENTRY_2_AND_3, // + AMS_TTDP_TO_ENTRY_2_AND_3_AND_H, // + AMS_TTDP_TO_JUNCTION, // used (moving towards terminal or helipad) + AMS_TTDP_LEAVE_RUNWAY, // + AMS_TTDP_TO_INWAY, // + AMS_TTDP_TO_RUNWAY, // used (moving towards a runway) + AMS_TTDP_TO_OUTWAY, // used (moving towards the end of the runway; middle section: aircraft has already started accelerating) + AMS_TTDP_WAITING, // + AMS_TTDP_TAKEOFF, // used (moving towards the end of the runway; start section: aircraft is stopped and starts to accelerate) + AMS_TTDP_TO_TAKEOFF, // + AMS_TTDP_CLIMBING, // + AMS_TTDP_FLIGHT_APPROACH, // used (aircraft flying or on hold) + AMS_TTDP_UNUSED_0x11, // + AMS_TTDP_FLIGHT_TO_TOWER, // used (helicopter flying, on hold or approaching destination) + AMS_TTDP_UNUSED_0x13, // + AMS_TTDP_FLIGHT_FINAL, // + AMS_TTDP_FLIGHT_DESCENT, // used (aircraft approaching landing) + AMS_TTDP_BRAKING, // used (aircraft finished descending and touches the runway) + AMS_TTDP_HELI_TAKEOFF_AIRPORT, // used + AMS_TTDP_HELI_TO_TAKEOFF_AIRPORT, // + AMS_TTDP_HELI_LAND_AIRPORT, // used + AMS_TTDP_HELI_TAKEOFF_HELIPORT, // used + AMS_TTDP_HELI_TO_TAKEOFF_HELIPORT, // + AMS_TTDP_HELI_LAND_HELIPORT, // used }; @@ -135,96 +137,70 @@ static uint8_t MapAircraftMovementState(const Aircraft *v) const Station *st = GetTargetAirportIfValid(v); if (st == nullptr) return AMS_TTDP_FLIGHT_TO_TOWER; - const AirportFTAClass *afc = st->airport.GetFTA(); - uint16_t amdflag = afc->MovingData(v->pos)->flag; - switch (v->state) { - case HANGAR: - /* The international airport is a special case as helicopters can land in - * front of the hangar. Helicopters also change their air.state to - * AMED_HELI_LOWER some time before actually descending. */ + case AS_HANGAR: + return AMS_TTDP_HANGAR; - /* This condition only occurs for helicopters, during descent, - * to a landing by the hangar of an international airport. */ - if (amdflag & AMED_HELI_LOWER) return AMS_TTDP_HELI_LAND_AIRPORT; + case AS_APRON: + case AS_HELIPAD: + case AS_HELIPORT: + case AS_BUILTIN_HELIPORT: + return AMS_TTDP_TO_JUNCTION; - /* This condition only occurs for helicopters, before starting descent, - * to a landing by the hangar of an international airport. */ - if (amdflag & AMED_SLOWTURN) return AMS_TTDP_FLIGHT_TO_TOWER; + case AS_RUNNING: + switch (v->Next()->state) { + case AS_HANGAR: + return AMS_TTDP_TO_HANGAR; + case AS_START_TAKEOFF: + return AMS_TTDP_TO_RUNWAY; + default: + break; + } + return AMS_TTDP_TO_JUNCTION; - /* The final two conditions apply to helicopters or aircraft. - * Has reached hangar? */ - if (amdflag & AMED_EXACTPOS) return AMS_TTDP_HANGAR; + case AS_START_TAKEOFF: + if (!v->IsHelicopter()) return AMS_TTDP_TO_OUTWAY; - /* Still moving towards hangar. */ - return AMS_TTDP_TO_HANGAR; + assert(IsAirportTile(v->tile) && IsApron(v->tile)); + return IsHeliport(v->tile) ? AMS_TTDP_HELI_TAKEOFF_HELIPORT : AMS_TTDP_HELI_TAKEOFF_AIRPORT; - case TERM1: - if (amdflag & AMED_EXACTPOS) return AMS_TTDP_TO_PAD1; - return AMS_TTDP_TO_JUNCTION; + case AS_LANDED: + if (!v->IsHelicopter()) return AMS_TTDP_BRAKING; - case TERM2: - if (amdflag & AMED_EXACTPOS) return AMS_TTDP_TO_PAD2; - return AMS_TTDP_TO_ENTRY_2_AND_3_AND_H; - - case TERM3: - case TERM4: - case TERM5: - case TERM6: - case TERM7: - case TERM8: - /* TTDPatch only has 3 terminals, so treat these states the same */ - if (amdflag & AMED_EXACTPOS) return AMS_TTDP_TO_PAD3; - return AMS_TTDP_TO_ENTRY_2_AND_3_AND_H; - - case HELIPAD1: - case HELIPAD2: - case HELIPAD3: - /* Will only occur for helicopters.*/ - if (amdflag & AMED_HELI_LOWER) return AMS_TTDP_HELI_LAND_AIRPORT; // Descending. - if (amdflag & AMED_SLOWTURN) return AMS_TTDP_FLIGHT_TO_TOWER; // Still hasn't started descent. - return AMS_TTDP_TO_JUNCTION; // On the ground. - - case TAKEOFF: // Moving to takeoff position. - return AMS_TTDP_TO_OUTWAY; - - case STARTTAKEOFF: // Accelerating down runway. - return AMS_TTDP_TAKEOFF; + assert(IsAirportTile(v->tile) && IsApron(v->tile)); + return IsHeliport(v->tile) ? AMS_TTDP_HELI_LAND_HELIPORT : AMS_TTDP_HELI_LAND_AIRPORT; - case ENDTAKEOFF: // Ascent + case AS_FLYING_TAKEOFF: + case AS_FLYING_LEAVING_AIRPORT: return AMS_TTDP_CLIMBING; - case HELITAKEOFF: // Helicopter is moving to take off position. - if (afc->delta_z == 0) { - return amdflag & AMED_HELI_RAISE ? - AMS_TTDP_HELI_TAKEOFF_AIRPORT : AMS_TTDP_TO_JUNCTION; - } else { - return AMS_TTDP_HELI_TAKEOFF_HELIPORT; - } + case AS_TAKEOFF_BEFORE_FLYING: + return AMS_TTDP_TAKEOFF; - case FLYING: - return amdflag & AMED_HOLD ? AMS_TTDP_FLIGHT_APPROACH : AMS_TTDP_FLIGHT_TO_TOWER; + case AS_FLYING_LANDING: + return AMS_TTDP_BRAKING; - case LANDING: // Descent - return AMS_TTDP_FLIGHT_DESCENT; + case AS_FLYING_HELICOPTER_TAKEOFF: + case AS_FLYING_HELICOPTER_LANDING: + return IsHelipadTile(v->tile) ? AMS_TTDP_HELI_LAND_AIRPORT : AMS_TTDP_HELI_LAND_HELIPORT; - case ENDLANDING: // On the runway braking - if (amdflag & AMED_BRAKE) return AMS_TTDP_BRAKING; - /* Landed - moving off runway */ - return AMS_TTDP_TO_INWAY; + case AS_DESCENDING: + return v->IsHelicopter() ? AMS_TTDP_FLIGHT_TO_TOWER : AMS_TTDP_FLIGHT_DESCENT; - case HELILANDING: - case HELIENDLANDING: // Helicoptor is descending. - if (amdflag & AMED_HELI_LOWER) { - return afc->delta_z == 0 ? - AMS_TTDP_HELI_LAND_AIRPORT : AMS_TTDP_HELI_LAND_HELIPORT; - } else { - return AMS_TTDP_FLIGHT_TO_TOWER; - } + case AS_ON_HOLD_APPROACHING: + case AS_FLYING: + case AS_FLYING_NO_DEST: + case AS_ON_HOLD_WAITING: + return v->IsHelicopter() ? AMS_TTDP_FLIGHT_TO_TOWER : AMS_TTDP_FLIGHT_APPROACH; + + case AS_IDLE: + break; default: - return AMS_TTDP_HANGAR; + NOT_REACHED(); } + + return AMS_TTDP_HANGAR; } @@ -260,40 +236,31 @@ enum TTDPAircraftMovementActions { static uint8_t MapAircraftMovementAction(const Aircraft *v) { switch (v->state) { - case HANGAR: + case AS_HANGAR: return (v->cur_speed > 0) ? AMA_TTDP_LANDING_TO_HANGAR : AMA_TTDP_IN_HANGAR; - case TERM1: - case HELIPAD1: + case AS_APRON: + case AS_HELIPAD: return (v->current_order.IsType(OT_LOADING)) ? AMA_TTDP_ON_PAD1 : AMA_TTDP_LANDING_TO_PAD1; - case TERM2: - case HELIPAD2: - return (v->current_order.IsType(OT_LOADING)) ? AMA_TTDP_ON_PAD2 : AMA_TTDP_LANDING_TO_PAD2; - - case TERM3: - case TERM4: - case TERM5: - case TERM6: - case TERM7: - case TERM8: - case HELIPAD3: - return (v->current_order.IsType(OT_LOADING)) ? AMA_TTDP_ON_PAD3 : AMA_TTDP_LANDING_TO_PAD3; - - case TAKEOFF: // Moving to takeoff position - case STARTTAKEOFF: // Accelerating down runway - case ENDTAKEOFF: // Ascent - case HELITAKEOFF: + case AS_START_TAKEOFF: // Moving to takeoff position + case AS_TAKEOFF_BEFORE_FLYING: + case AS_FLYING_TAKEOFF: // Accelerating down runway + case AS_FLYING_LEAVING_AIRPORT: // Ascent + case AS_FLYING_HELICOPTER_TAKEOFF: /* @todo Need to find which terminal (or hangar) we've come from. How? */ return AMA_TTDP_PAD1_TO_TAKEOFF; - case FLYING: + case AS_ON_HOLD_WAITING: + case AS_FLYING: + case AS_FLYING_NO_DEST: return AMA_TTDP_IN_FLIGHT; - case LANDING: // Descent - case ENDLANDING: // On the runway braking - case HELILANDING: - case HELIENDLANDING: + case AS_ON_HOLD_APPROACHING: + case AS_DESCENDING: + case AS_FLYING_LANDING: // On the runway braking + case AS_LANDED: + case AS_FLYING_HELICOPTER_LANDING: /* @todo Need to check terminal we're landing to. Is it known yet? */ return (v->current_order.IsType(OT_GOTO_DEPOT)) ? AMA_TTDP_LANDING_TO_HANGAR : AMA_TTDP_LANDING_TO_PAD1; @@ -689,6 +656,13 @@ static uint32_t VehicleGetVariable(Vehicle *v, const VehicleScopeResolver *objec return (HasPowerOnRoad(param_type, tile_type) ? 0x06 : 0x00) | 0x01; } + case VEH_AIRCRAFT: { + AirType param_type = GetAirTypeTranslation(parameter, object->ro.grffile); + if (param_type == INVALID_AIRTYPE) return 0x00; + AirType tile_type = GetAirType(v->tile); + if (tile_type == param_type) return 0x0F; + return 0x01; + } default: return 0x00; } diff --git a/src/newgrf_station.cpp b/src/newgrf_station.cpp index af67fbf648bca..86462ff4b9da0 100644 --- a/src/newgrf_station.cpp +++ b/src/newgrf_station.cpp @@ -24,6 +24,7 @@ #include "newgrf_animation_base.h" #include "newgrf_class_func.h" #include "timer/timer_game_calendar.h" +#include "platform_func.h" #include "safeguards.h" diff --git a/src/openttd.cpp b/src/openttd.cpp index 25082be2c5b29..b5e2e72ea0209 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -28,6 +28,7 @@ #include "news_func.h" #include "fios.h" #include "aircraft.h" +#include "airport_gui.h" #include "roadveh.h" #include "train.h" #include "ship.h" @@ -891,6 +892,7 @@ static void MakeNewGameDone() InitializeRailGUI(); InitializeRoadGUI(); + InitializeAirportGui(); if (_settings_client.gui.pause_on_newgame) Command::Post(PM_PAUSED_NORMAL, true); diff --git a/src/order_backup.cpp b/src/order_backup.cpp index f696d8435dea5..053c407c8c7aa 100644 --- a/src/order_backup.cpp +++ b/src/order_backup.cpp @@ -19,6 +19,8 @@ #include "order_cmd.h" #include "group_cmd.h" #include "vehicle_func.h" +#include "depot_map.h" +#include "depot_base.h" #include "safeguards.h" @@ -45,8 +47,9 @@ OrderBackup::~OrderBackup() */ OrderBackup::OrderBackup(const Vehicle *v, uint32_t user) { + assert(IsDepotTile(v->tile)); this->user = user; - this->tile = v->tile; + this->depot_id = GetDepotIndex(v->tile); this->group = v->group_id; this->CopyConsistPropertiesFrom(v); @@ -123,8 +126,10 @@ void OrderBackup::DoRestore(Vehicle *v) */ /* static */ void OrderBackup::Restore(Vehicle *v, uint32_t user) { + assert(IsDepotTile(v->tile)); + DepotID depot_id_veh = GetDepotIndex(v->tile); for (OrderBackup *ob : OrderBackup::Iterate()) { - if (v->tile != ob->tile || ob->user != user) continue; + if (depot_id_veh != ob->depot_id || ob->user != user) continue; ob->DoRestore(v); delete ob; @@ -133,28 +138,28 @@ void OrderBackup::DoRestore(Vehicle *v) /** * Reset an OrderBackup given a tile and user. - * @param tile The tile associated with the OrderBackup. + * @param depot_id The depot associated with the OrderBackup. * @param user The user associated with the OrderBackup. * @note Must not be used from the GUI! */ -/* static */ void OrderBackup::ResetOfUser(TileIndex tile, uint32_t user) +/* static */ void OrderBackup::ResetOfUser(DepotID depot_id, uint32_t user) { for (OrderBackup *ob : OrderBackup::Iterate()) { - if (ob->user == user && (ob->tile == tile || tile == INVALID_TILE)) delete ob; + if (ob->user == user && (ob->depot_id == depot_id || depot_id == INVALID_DEPOT)) delete ob; } } /** * Clear an OrderBackup * @param flags For command. - * @param tile Tile related to the to-be-cleared OrderBackup. + * @param depot_id Tile related to the to-be-cleared OrderBackup. * @param user_id User that had the OrderBackup. * @return The cost of this operation or an error. */ -CommandCost CmdClearOrderBackup(DoCommandFlag flags, TileIndex tile, ClientID user_id) +CommandCost CmdClearOrderBackup(DoCommandFlag flags, DepotID depot_id, ClientID user_id) { - /* No need to check anything. If the tile or user don't exist we just ignore it. */ - if (flags & DC_EXEC) OrderBackup::ResetOfUser(tile == 0 ? INVALID_TILE : tile, user_id); + assert(Depot::IsValidID(depot_id)); + if (flags & DC_EXEC) OrderBackup::ResetOfUser(depot_id, user_id); return CommandCost(); } @@ -173,18 +178,18 @@ CommandCost CmdClearOrderBackup(DoCommandFlag flags, TileIndex tile, ClientID us /* If it's not a backup of us, ignore it. */ if (ob->user != user) continue; - Command::Post(0, static_cast(user)); + Command::Post(ob->depot_id, static_cast(user)); return; } } /** * Reset the OrderBackups from GUI/game logic. - * @param t The tile of the order backup. + * @param depot_id The index of the depot associated to the order backups. * @param from_gui Whether the call came from the GUI, i.e. whether * it must be synced over the network. */ -/* static */ void OrderBackup::Reset(TileIndex t, bool from_gui) +/* static */ void OrderBackup::Reset(DepotID depot_id, bool from_gui) { /* The user has CLIENT_ID_SERVER as default when network play is not active, * but compiled it. A network client has its own variable for the unique @@ -195,16 +200,16 @@ CommandCost CmdClearOrderBackup(DoCommandFlag flags, TileIndex tile, ClientID us for (OrderBackup *ob : OrderBackup::Iterate()) { /* If this is a GUI action, and it's not a backup of us, ignore it. */ if (from_gui && ob->user != user) continue; - /* If it's not for our chosen tile either, ignore it. */ - if (t != INVALID_TILE && t != ob->tile) continue; + /* If it's not for our chosen depot either, ignore it. */ + if (depot_id != INVALID_DEPOT && depot_id != ob->depot_id) continue; if (from_gui) { /* We need to circumvent the "prevention" from this command being executed * while the game is paused, so use the internal method. Nor do we want * this command to get its cost estimated when shift is pressed. */ - Command::Unsafe(STR_NULL, nullptr, true, false, ob->tile, CommandTraits::Args{ ob->tile, static_cast(user) }); + Command::Unsafe(STR_NULL, nullptr, true, false, ob->depot_id, CommandTraits::Args{ ob->depot_id, static_cast(user) }); } else { - /* The command came from the game logic, i.e. the clearing of a tile. + /* The command came from the game logic, i.e. the clearing of a depot. * In that case we have no need to actually sync this, just do it. */ delete ob; } @@ -246,18 +251,14 @@ CommandCost CmdClearOrderBackup(DoCommandFlag flags, TileIndex tile, ClientID us * Removes an order from all vehicles. Triggers when, say, a station is removed. * @param type The type of the order (OT_GOTO_[STATION|DEPOT|WAYPOINT]). * @param destination The destination. Can be a StationID, DepotID or WaypointID. - * @param hangar Only used for airports in the destination. - * When false, remove airport and hangar orders. - * When true, remove either airport or hangar order. */ -/* static */ void OrderBackup::RemoveOrder(OrderType type, DestinationID destination, bool hangar) +/* static */ void OrderBackup::RemoveOrder(OrderType type, DestinationID destination) { for (OrderBackup *ob : OrderBackup::Iterate()) { for (Order *order = ob->orders; order != nullptr; order = order->next) { OrderType ot = order->GetType(); if (ot == OT_GOTO_DEPOT && (order->GetDepotActionType() & ODATFB_NEAREST_DEPOT) != 0) continue; - if (ot == OT_GOTO_DEPOT && hangar && !IsHangarTile(ob->tile)) continue; // Not an aircraft? Can't have a hangar order. - if (ot == OT_IMPLICIT || (IsHangarTile(ob->tile) && ot == OT_GOTO_DEPOT && !hangar)) ot = OT_GOTO_STATION; + if (ot == OT_IMPLICIT) ot = OT_GOTO_STATION; if (ot == type && order->GetDestination() == destination) { /* Remove the order backup! If a station/depot gets removed, we can't/shouldn't restore those broken orders. */ delete ob; diff --git a/src/order_backup.h b/src/order_backup.h index 8616c564eed24..823e74b2f397f 100644 --- a/src/order_backup.h +++ b/src/order_backup.h @@ -16,6 +16,7 @@ #include "vehicle_type.h" #include "base_consist.h" #include "saveload/saveload.h" +#include "depot_type.h" /** Unique identifier for an order backup. */ typedef uint8_t OrderBackupID; @@ -34,8 +35,8 @@ struct OrderBackup : OrderBackupPool::PoolItem<&_order_backup_pool>, BaseConsist private: friend SaveLoadTable GetOrderBackupDescription(); ///< Saving and loading of order backups. friend struct BKORChunkHandler; ///< Creating empty orders upon savegame loading. - uint32_t user; ///< The user that requested the backup. - TileIndex tile; ///< Tile of the depot where the order was changed. + uint32_t user; ///< The user that requested the backup. + DepotID depot_id; ///< Depot where the order was changed. GroupID group; ///< The group the vehicle was part of. const Vehicle *clone; ///< Vehicle this vehicle was a clone of. @@ -53,13 +54,13 @@ struct OrderBackup : OrderBackupPool::PoolItem<&_order_backup_pool>, BaseConsist static void Backup(const Vehicle *v, uint32_t user); static void Restore(Vehicle *v, uint32_t user); - static void ResetOfUser(TileIndex tile, uint32_t user); + static void ResetOfUser(DepotID depot_id, uint32_t user); static void ResetUser(uint32_t user); - static void Reset(TileIndex tile = INVALID_TILE, bool from_gui = true); + static void Reset(DepotID depot = INVALID_DEPOT, bool from_gui = true); static void ClearGroup(GroupID group); static void ClearVehicle(const Vehicle *v); - static void RemoveOrder(OrderType type, DestinationID destination, bool hangar); + static void RemoveOrder(OrderType type, DestinationID destination); }; #endif /* ORDER_BACKUP_H */ diff --git a/src/order_base.h b/src/order_base.h index 8f2f10c252aa9..b223440c591fe 100644 --- a/src/order_base.h +++ b/src/order_base.h @@ -225,6 +225,7 @@ struct Order : OrderPool::PoolItem<&_order_pool> { inline void SetMaxSpeed(uint16_t speed) { this->max_speed = speed; } bool ShouldStopAtStation(const Vehicle *v, StationID station) const; + bool ShouldStopAtDepot(DepotID depot) const; bool CanLoadOrUnload() const; bool CanLeaveWithCargo(bool has_cargo) const; diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index 1d6743106efe2..b83ba11f52659 100644 --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -16,6 +16,7 @@ #include "timetable.h" #include "vehicle_func.h" #include "depot_base.h" +#include "depot_func.h" #include "core/pool_func.hpp" #include "core/random_func.hpp" #include "aircraft.h" @@ -630,7 +631,6 @@ static void DeleteOrderWarnings(const Vehicle *v) DeleteVehicleNews(v->index, STR_NEWS_VEHICLE_HAS_VOID_ORDER); DeleteVehicleNews(v->index, STR_NEWS_VEHICLE_HAS_DUPLICATE_ENTRY); DeleteVehicleNews(v->index, STR_NEWS_VEHICLE_HAS_INVALID_ENTRY); - DeleteVehicleNews(v->index, STR_NEWS_PLANE_USES_TOO_SHORT_RUNWAY); } /** @@ -650,7 +650,7 @@ TileIndex Order::GetLocation(const Vehicle *v, bool airport) const case OT_GOTO_DEPOT: if (this->GetDestination() == INVALID_DEPOT) return INVALID_TILE; - return (v->type == VEH_AIRCRAFT) ? Station::Get(this->GetDestination())->xy : Depot::Get(this->GetDestination())->xy; + return Depot::Get(this->GetDestination())->xy; default: return INVALID_TILE; @@ -684,6 +684,32 @@ uint GetOrderDistance(const Order *prev, const Order *cur, const Vehicle *v, int return v->type == VEH_AIRCRAFT ? DistanceSquare(prev_tile, cur_tile) : DistanceManhattan(prev_tile, cur_tile); } +/** + * Get the station or depot index associated to an order of a vehicle. + * For aircraft, it will return the index of the associated station, even for go to hangar orders. + * @param o Order to check. + * @param is_aircraft Whether the order is of an aircraft vehicle. + * @return index associated to a station or depot, or INVALID_STATION. + */ +DestinationID GetTargetDestination(const Order &o, bool is_aircraft) +{ + DestinationID destination_id = o.GetDestination(); + switch (o.GetType()) { + case OT_GOTO_STATION: + return destination_id; + case OT_GOTO_DEPOT: + assert(Depot::IsValidID(destination_id)); + if (is_aircraft) { + destination_id = Depot::Get(destination_id)->station->index; + assert(Station::IsValidID(destination_id)); + } + return destination_id; + default: + return INVALID_STATION; + } +} + + /** * Add an order to the orderlist of a vehicle. * @param flags operation to perform @@ -763,41 +789,15 @@ CommandCost CmdInsertOrder(DoCommandFlag flags, VehicleID veh, VehicleOrderID se case OT_GOTO_DEPOT: { if ((new_order.GetDepotActionType() & ODATFB_NEAREST_DEPOT) == 0) { - if (v->type == VEH_AIRCRAFT) { - const Station *st = Station::GetIfValid(new_order.GetDestination()); - - if (st == nullptr) return CMD_ERROR; - - ret = CheckOwnership(st->owner); - if (ret.Failed()) return ret; - - if (!CanVehicleUseStation(v, st) || !st->airport.HasHangar()) { - return CMD_ERROR; - } - } else { - const Depot *dp = Depot::GetIfValid(new_order.GetDestination()); - - if (dp == nullptr) return CMD_ERROR; - - ret = CheckOwnership(GetTileOwner(dp->xy)); - if (ret.Failed()) return ret; + const Depot *dp = Depot::GetIfValid(new_order.GetDestination()); - switch (v->type) { - case VEH_TRAIN: - if (!IsRailDepotTile(dp->xy)) return CMD_ERROR; - break; + if (dp == nullptr || !dp->IsInUse()) return CMD_ERROR; - case VEH_ROAD: - if (!IsRoadDepotTile(dp->xy)) return CMD_ERROR; - break; - - case VEH_SHIP: - if (!IsShipDepotTile(dp->xy)) return CMD_ERROR; - break; + ret = CheckOwnership(dp->owner); + if (ret.Failed()) return ret; - default: return CMD_ERROR; - } - } + if (v->type != dp->veh_type) return CMD_ERROR; + if (v->type == VEH_AIRCRAFT && !CanVehicleUseStation(v, Station::GetByTile(dp->xy))) return CMD_ERROR; } if (new_order.GetNonStopType() != ONSF_STOP_EVERYWHERE && !v->IsGroundVehicle()) return CMD_ERROR; @@ -1742,12 +1742,6 @@ void CheckOrders(const Vehicle *v) n_st++; if (!CanVehicleUseStation(v, st)) { message = STR_NEWS_VEHICLE_HAS_INVALID_ENTRY; - } else if (v->type == VEH_AIRCRAFT && - (AircraftVehInfo(v->engine_type)->subtype & AIR_FAST) && - (st->airport.GetFTA()->flags & AirportFTAClass::SHORT_STRIP) && - !_cheats.no_jetcrash.value && - message == INVALID_STRING_ID) { - message = STR_NEWS_PLANE_USES_TOO_SHORT_RUNWAY; } } } @@ -1780,24 +1774,11 @@ void CheckOrders(const Vehicle *v) * Removes an order from all vehicles. Triggers when, say, a station is removed. * @param type The type of the order (OT_GOTO_[STATION|DEPOT|WAYPOINT]). * @param destination The destination. Can be a StationID, DepotID or WaypointID. - * @param hangar Only used for airports in the destination. - * When false, remove airport and hangar orders. - * When true, remove either airport or hangar order. */ -void RemoveOrderFromAllVehicles(OrderType type, DestinationID destination, bool hangar) +void RemoveOrderFromAllVehicles(OrderType type, DestinationID destination) { - /* Aircraft have StationIDs for depot orders and never use DepotIDs - * This fact is handled specially below - */ - /* Go through all vehicles */ for (Vehicle *v : Vehicle::Iterate()) { - if ((v->type == VEH_AIRCRAFT && v->current_order.IsType(OT_GOTO_DEPOT) && !hangar ? OT_GOTO_STATION : v->current_order.GetType()) == type && - (!hangar || v->type == VEH_AIRCRAFT) && v->current_order.GetDestination() == destination) { - v->current_order.MakeDummy(); - SetWindowDirty(WC_VEHICLE_VIEW, v->index); - } - /* Clear the order from the order-list */ int id = -1; for (Order *order : v->Orders()) { @@ -1806,8 +1787,7 @@ void RemoveOrderFromAllVehicles(OrderType type, DestinationID destination, bool OrderType ot = order->GetType(); if (ot == OT_GOTO_DEPOT && (order->GetDepotActionType() & ODATFB_NEAREST_DEPOT) != 0) continue; - if (ot == OT_GOTO_DEPOT && hangar && v->type != VEH_AIRCRAFT) continue; // Not an aircraft? Can't have a hangar order. - if (ot == OT_IMPLICIT || (v->type == VEH_AIRCRAFT && ot == OT_GOTO_DEPOT && !hangar)) ot = OT_GOTO_STATION; + if (ot == OT_IMPLICIT) ot = OT_GOTO_STATION; if (ot == type && order->GetDestination() == destination) { /* We want to clear implicit orders, but we don't want to make them * dummy orders. They should just vanish. Also check the actual order @@ -1841,7 +1821,7 @@ void RemoveOrderFromAllVehicles(OrderType type, DestinationID destination, bool } } - OrderBackup::RemoveOrder(type, destination, hangar); + OrderBackup::RemoveOrder(type, destination); } /** @@ -2028,19 +2008,12 @@ bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth, bool /* PBS reservations cannot reverse */ if (pbs_look_ahead && closestDepot.reverse) return false; - v->SetDestTile(closestDepot.location); v->current_order.SetDestination(closestDepot.destination); + v->SetDestTile(closestDepot.location); /* If there is no depot in front, reverse automatically (trains only) */ if (v->type == VEH_TRAIN && closestDepot.reverse) Command::Do(DC_EXEC, v->index, false); - if (v->type == VEH_AIRCRAFT) { - Aircraft *a = Aircraft::From(v); - if (a->state == FLYING && a->targetairport != closestDepot.destination) { - /* The aircraft is now heading for a different hangar than the next in the orders */ - AircraftNextAirportPos_and_Order(a); - } - } return true; } @@ -2051,13 +2024,14 @@ bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth, bool v->IncrementRealOrderIndex(); } else { if (v->type != VEH_AIRCRAFT) { - v->SetDestTile(Depot::Get(order->GetDestination())->xy); + v->SetDestTile(Depot::Get(order->GetDestination())->GetBestDepotTile(v)); } else { Aircraft *a = Aircraft::From(v); - DestinationID destination = a->current_order.GetDestination(); - if (a->targetairport != destination) { + Depot *dep = Depot::Get(a->current_order.GetDestination()); + assert(dep->station != nullptr); + if (a->targetairport != dep->station->index) { /* The aircraft is now heading for a different hangar than the next in the orders */ - a->SetDestTile(a->GetOrderStationLocation(destination)); + a->SetDestTile(a->GetOrderStationLocation(dep->station->index)); } } return true; @@ -2133,11 +2107,8 @@ bool ProcessOrders(Vehicle *v) break; case OT_LOADING: - return false; - case OT_LEAVESTATION: - if (v->type != VEH_AIRCRAFT) return false; - break; + return false; default: break; } @@ -2189,7 +2160,7 @@ bool ProcessOrders(Vehicle *v) } /* If it is unchanged, keep it. */ - if (order->Equals(v->current_order) && (v->type == VEH_AIRCRAFT || v->dest_tile != 0) && + if (order->Equals(v->current_order) && v->dest_tile != 0 && (v->type != VEH_SHIP || !order->IsType(OT_GOTO_STATION) || Station::Get(order->GetDestination())->ship_station.tile != INVALID_TILE)) { return false; } @@ -2232,6 +2203,17 @@ bool Order::ShouldStopAtStation(const Vehicle *v, StationID station) const !(this->GetNonStopType() & (is_dest_station ? ONSF_NO_STOP_AT_DESTINATION_STATION : ONSF_NO_STOP_AT_INTERMEDIATE_STATIONS)); } +/** + * Check whether the given vehicle should stop at the given depot. + * @param v the vehicle that might be stopping. + * @param depot the depot to stop at. + * @return true if the vehicle should stop. + */ +bool Order::ShouldStopAtDepot(DepotID depot) const +{ + return this->IsType(OT_GOTO_DEPOT) && this->dest == depot; +} + bool Order::CanLoadOrUnload() const { return (this->IsType(OT_GOTO_STATION) || this->IsType(OT_IMPLICIT)) && diff --git a/src/order_cmd.h b/src/order_cmd.h index b0ab888d9421a..d59ca58adf857 100644 --- a/src/order_cmd.h +++ b/src/order_cmd.h @@ -21,7 +21,7 @@ CommandCost CmdInsertOrder(DoCommandFlag flags, VehicleID veh, VehicleOrderID se CommandCost CmdOrderRefit(DoCommandFlag flags, VehicleID veh, VehicleOrderID order_number, CargoID cargo); CommandCost CmdCloneOrder(DoCommandFlag flags, CloneOptions action, VehicleID veh_dst, VehicleID veh_src); CommandCost CmdMoveOrder(DoCommandFlag flags, VehicleID veh, VehicleOrderID moving_order, VehicleOrderID target_order); -CommandCost CmdClearOrderBackup(DoCommandFlag flags, TileIndex tile, ClientID user_id); +CommandCost CmdClearOrderBackup(DoCommandFlag flags, DepotID depot_id, ClientID user_id); DEF_CMD_TRAIT(CMD_MODIFY_ORDER, CmdModifyOrder, CMD_LOCATION, CMDT_ROUTE_MANAGEMENT) DEF_CMD_TRAIT(CMD_SKIP_TO_ORDER, CmdSkipToOrder, CMD_LOCATION, CMDT_ROUTE_MANAGEMENT) diff --git a/src/order_func.h b/src/order_func.h index 12f7d4684a9b9..797066e1eb57a 100644 --- a/src/order_func.h +++ b/src/order_func.h @@ -15,7 +15,7 @@ #include "company_type.h" /* Functions */ -void RemoveOrderFromAllVehicles(OrderType type, DestinationID destination, bool hangar = false); +void RemoveOrderFromAllVehicles(OrderType type, DestinationID destination); void InvalidateVehicleOrder(const Vehicle *v, int data); void CheckOrders(const Vehicle*); void DeleteVehicleOrders(Vehicle *v, bool keep_orderlist = false, bool reset_order_indices = true); @@ -23,6 +23,7 @@ bool ProcessOrders(Vehicle *v); bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth = 0, bool pbs_look_ahead = false); VehicleOrderID ProcessConditionalOrder(const Order *order, const Vehicle *v); uint GetOrderDistance(const Order *prev, const Order *cur, const Vehicle *v, int conditional_depth = 0); +DestinationID GetTargetDestination(const Order &o, bool is_aircraft); void DrawOrderString(const Vehicle *v, const Order *order, int order_index, int y, bool selected, bool timetable, int left, int middle, int right); diff --git a/src/order_gui.cpp b/src/order_gui.cpp index 77b68d9479b2f..3bcfda6c4904e 100644 --- a/src/order_gui.cpp +++ b/src/order_gui.cpp @@ -34,6 +34,7 @@ #include "error.h" #include "order_cmd.h" #include "company_cmd.h" +#include "depot_base.h" #include "widgets/order_widget.h" @@ -309,7 +310,7 @@ void DrawOrderString(const Vehicle *v, const Order *order, int order_index, int /* Going to a specific depot. */ SetDParam(0, STR_ORDER_GO_TO_DEPOT_FORMAT); SetDParam(2, v->type); - SetDParam(3, order->GetDestination()); + SetDParam(3, GetTargetDestination(*order, v->type == VEH_AIRCRAFT)); } if (order->GetDepotOrderType() & ODTFB_SERVICE) { @@ -384,8 +385,7 @@ static Order GetOrderCmdFromTile(const Vehicle *v, TileIndex tile) /* check depot first */ if (IsDepotTypeTile(tile, (TransportType)(uint)v->type) && IsTileOwner(tile, _local_company)) { - order.MakeGoToDepot(v->type == VEH_AIRCRAFT ? GetStationIndex(tile) : GetDepotIndex(tile), - ODTFB_PART_OF_ORDERS, + order.MakeGoToDepot(GetDepotIndex(tile), ODTFB_PART_OF_ORDERS, (_settings_client.gui.new_nonstop && v->IsGroundVehicle()) ? ONSF_NO_STOP_AT_INTERMEDIATE_STATIONS : ONSF_STOP_EVERYWHERE); if (_ctrl_pressed) { diff --git a/src/pathfinder/follow_track.hpp b/src/pathfinder/follow_track.hpp index d384568b62f74..fcb4bcd72927f 100644 --- a/src/pathfinder/follow_track.hpp +++ b/src/pathfinder/follow_track.hpp @@ -18,6 +18,9 @@ #include "../tunnelbridge_map.h" #include "../depot_map.h" #include "pathfinder_func.h" +#include "../platform_func.h" +#include "../air_map.h" +#include "../air.h" /** * Track follower helper template class (can serve pathfinders and vehicle @@ -46,7 +49,8 @@ struct CFollowTrackT bool m_is_tunnel; ///< last turn passed tunnel bool m_is_bridge; ///< last turn passed bridge ramp bool m_is_station; ///< last turn passed station - int m_tiles_skipped; ///< number of skipped tunnel or station tiles + bool m_is_extended_depot; ///< last turn passed depot + int m_tiles_skipped; ///< number of skipped tunnel, depot or station tiles ErrorCode m_err; RailTypes m_railtypes; @@ -57,7 +61,7 @@ struct CFollowTrackT inline CFollowTrackT(Owner o, RailTypes railtype_override = INVALID_RAILTYPES) { - assert(IsRailTT()); + assert(IsRailTT() || IsAirTT()); m_veh = nullptr; Init(o, railtype_override); } @@ -80,7 +84,7 @@ struct CFollowTrackT m_new_tile = INVALID_TILE; m_new_td_bits = TRACKDIR_BIT_NONE; m_exitdir = INVALID_DIAGDIR; - m_is_station = m_is_bridge = m_is_tunnel = false; + m_is_station = m_is_bridge = m_is_tunnel = m_is_extended_depot = false; m_tiles_skipped = 0; m_err = EC_NONE; m_railtypes = railtype_override; @@ -89,6 +93,7 @@ struct CFollowTrackT debug_inline static TransportType TT() { return Ttr_type_; } debug_inline static bool IsWaterTT() { return TT() == TRANSPORT_WATER; } debug_inline static bool IsRailTT() { return TT() == TRANSPORT_RAIL; } + debug_inline static bool IsAirTT() { return TT() == TRANSPORT_AIR; } inline bool IsTram() { return IsRoadTT() && RoadTypeIsTram(RoadVehicle::From(m_veh)->roadtype); } debug_inline static bool IsRoadTT() { return TT() == TRANSPORT_ROAD; } inline static bool Allow90degTurns() { return T90deg_turns_allowed_; } @@ -99,7 +104,7 @@ struct CFollowTrackT { assert(IsTram()); // this function shouldn't be called in other cases - if (IsNormalRoadTile(tile)) { + if (IsNormalRoadTile(tile) || IsExtendedRoadDepotTile(tile)) { RoadBits rb = GetRoadBits(tile, RTT_TRAM); switch (rb) { case ROAD_NW: return DIAGDIR_NW; @@ -123,17 +128,21 @@ struct CFollowTrackT m_err = EC_NONE; assert([&]() { + if (IsAirTT()) return true; if (IsTram() && GetSingleTramBit(m_old_tile) != INVALID_DIAGDIR) return true; // Skip the check for single tram bits const uint sub_mode = (IsRoadTT() && m_veh != nullptr) ? (this->IsTram() ? RTT_TRAM : RTT_ROAD) : 0; const TrackdirBits old_tile_valid_dirs = TrackStatusToTrackdirBits(GetTileTrackStatus(m_old_tile, TT(), sub_mode)); return (old_tile_valid_dirs & TrackdirToTrackdirBits(m_old_td)) != TRACKDIR_BIT_NONE; }()); + assert(!IsAirTT() || (GetTileTrackStatus(m_old_tile, TT(), 0) != 0)); + m_exitdir = TrackdirToExitdir(m_old_td); if (ForcedReverse()) return true; if (!CanExitOldTile()) return false; FollowTileExit(); if (!QueryNewTileTrackStatus()) return TryReverse(); + if (IsAirTT() && !CanEnterNewTile()) return false; m_new_td_bits &= DiagdirReachesTrackdirs(m_exitdir); if (m_new_td_bits == TRACKDIR_BIT_NONE || !CanEnterNewTile()) { /* In case we can't enter the next tile, but are @@ -170,11 +179,11 @@ struct CFollowTrackT { if (!DoTrackMasking()) return true; - if (m_is_station) { - /* Check skipped station tiles as well. */ + if (m_is_station || m_is_extended_depot) { + /* Check skipped station and depot tiles as well. */ TileIndexDiff diff = TileOffsByDiagDir(m_exitdir); for (TileIndex tile = m_new_tile - diff * m_tiles_skipped; tile != m_new_tile; tile += diff) { - if (HasStationReservation(tile)) { + if ((m_is_station && HasStationReservation(tile)) || (m_is_extended_depot && HasDepotReservation(tile))) { m_new_td_bits = TRACKDIR_BIT_NONE; m_err = EC_RESERVED; return false; @@ -200,7 +209,7 @@ struct CFollowTrackT /** Follow the m_exitdir from m_old_tile and fill m_new_tile and m_tiles_skipped */ inline void FollowTileExit() { - m_is_station = m_is_bridge = m_is_tunnel = false; + m_is_station = m_is_bridge = m_is_tunnel = m_is_extended_depot = false; m_tiles_skipped = 0; /* extra handling for tunnels and bridges in our direction */ @@ -224,9 +233,13 @@ struct CFollowTrackT /* normal or station tile, do one step */ m_new_tile = TileAddByDiagDir(m_old_tile, m_exitdir); - /* special handling for stations */ - if (IsRailTT() && HasStationTileRail(m_new_tile)) { - m_is_station = true; + /* special handling for stations and multi-tile depots */ + if (IsRailTT()) { + if (HasStationTileRail(m_new_tile)) { + m_is_station = true; + } else if (IsExtendedRailDepotTile(m_new_tile)) { + m_is_extended_depot = true; + } } else if (IsRoadTT() && IsStationRoadStopTile(m_new_tile)) { m_is_station = true; } @@ -235,8 +248,14 @@ struct CFollowTrackT /** stores track status (available trackdirs) for the new tile into m_new_td_bits */ inline bool QueryNewTileTrackStatus() { + if (IsAirTT()) { + if (!IsAirportTile(m_new_tile) || + !MayHaveAirTracks(m_new_tile)) return false; + m_new_td_bits = TrackBitsToTrackdirBits(GetAirportTileTracks(m_new_tile)); + return m_new_td_bits != TRACKDIR_BIT_NONE; + } if (IsRailTT() && IsPlainRailTile(m_new_tile)) { - m_new_td_bits = (TrackdirBits)(GetTrackBits(m_new_tile) * 0x101); + m_new_td_bits = TrackBitsToTrackdirBits(GetTrackBits(m_new_tile)); } else if (IsRoadTT()) { m_new_td_bits = GetTrackdirBitsForRoad(m_new_tile, this->IsTram() ? RTT_TRAM : RTT_ROAD); } else { @@ -248,6 +267,20 @@ struct CFollowTrackT /** return true if we can leave m_old_tile in m_exitdir */ inline bool CanExitOldTile() { + /* hangar can be left at one direction */ + if (IsAirTT()) { + if (IsHangarTile(m_old_tile)) { + DiagDirection exitdir = GetHangarDirection(m_old_tile); + if (exitdir != m_exitdir) { + m_err = EC_NO_WAY; + return false; + } + } else if (IsHeliportTile(m_old_tile)) { + m_err = EC_NO_WAY; + return false; + } + } + /* road stop can be left at one direction only unless it's a drive-through stop */ if (IsRoadTT() && IsBayRoadStopTile(m_old_tile)) { DiagDirection exitdir = GetRoadStopDir(m_old_tile); @@ -266,20 +299,36 @@ struct CFollowTrackT } } - /* road depots can be also left in one direction only */ + /* road depots can be also left in one direction sometimes */ if (IsRoadTT() && IsDepotTypeTile(m_old_tile, TT())) { - DiagDirection exitdir = GetRoadDepotDirection(m_old_tile); - if (exitdir != m_exitdir) { + RoadTramType rtt = IsTram() ? RTT_TRAM : RTT_ROAD; + RoadBits rb = GetRoadBits(m_old_tile, rtt); + if ((rb & DiagDirToRoadBits(m_exitdir)) == ROAD_NONE) { m_err = EC_NO_WAY; return false; } } + return true; } /** return true if we can enter m_new_tile from m_exitdir */ inline bool CanEnterNewTile() { + if (IsAirTT()) { + if (GetStationIndex(m_old_tile) != GetStationIndex(m_new_tile)) return false; + if (IsHangarTile(m_new_tile)) { + DiagDirection exitdir = GetHangarDirection(m_new_tile); + if (ReverseDiagDir(exitdir) != m_exitdir) { + m_err = EC_NO_WAY; + return false; + } + } else if (IsHeliportTile(m_new_tile)) { + m_err = EC_NO_WAY; + return false; + } + } + if (IsRoadTT() && IsBayRoadStopTile(m_new_tile)) { /* road stop can be entered from one direction only unless it's a drive-through stop */ DiagDirection exitdir = GetRoadStopDir(m_new_tile); @@ -300,18 +349,19 @@ struct CFollowTrackT /* road and rail depots can also be entered from one direction only */ if (IsRoadTT() && IsDepotTypeTile(m_new_tile, TT())) { - DiagDirection exitdir = GetRoadDepotDirection(m_new_tile); - if (ReverseDiagDir(exitdir) != m_exitdir) { - m_err = EC_NO_WAY; - return false; - } /* don't try to enter other company's depots */ if (GetTileOwner(m_new_tile) != m_veh_owner) { m_err = EC_OWNER; return false; } + RoadTramType rtt = IsTram() ? RTT_TRAM : RTT_ROAD; + RoadBits rb = GetRoadBits(m_new_tile, rtt); + if ((rb & DiagDirToRoadBits(ReverseDiagDir(m_exitdir))) == ROAD_NONE) { + m_err = EC_NO_WAY; + return false; + } } - if (IsRailTT() && IsDepotTypeTile(m_new_tile, TT())) { + if (IsRailTT() && IsStandardRailDepotTile(m_new_tile)) { DiagDirection exitdir = GetRailDepotDirection(m_new_tile); if (ReverseDiagDir(exitdir) != m_exitdir) { m_err = EC_NO_WAY; @@ -368,14 +418,14 @@ struct CFollowTrackT } } - /* special handling for rail stations - get to the end of platform */ - if (IsRailTT() && m_is_station) { - /* entered railway station - * get platform length */ - uint length = BaseStation::GetByTile(m_new_tile)->GetPlatformLength(m_new_tile, TrackdirToExitdir(m_old_td)); - /* how big step we must do to get to the last platform tile? */ - m_tiles_skipped = length - 1; - /* move to the platform end */ + /* special handling for rail platforms - get to the end of platform */ + if (IsRailTT() && (m_is_station || m_is_extended_depot)) { + /* Entered a platform. */ + assert(HasStationTileRail(m_new_tile) || IsExtendedRailDepotTile(m_new_tile)); + /* How big step we must do to get to the last platform tile? */ + m_tiles_skipped = GetPlatformLength(m_new_tile, TrackdirToExitdir(m_old_td)) - 1; + /* Move to the platform end. */ + TileIndexDiff diff = TileOffsByDiagDir(m_exitdir); diff *= m_tiles_skipped; m_new_tile = TileAdd(m_new_tile, diff); @@ -390,14 +440,29 @@ struct CFollowTrackT { /* rail and road depots cause reversing */ if (!IsWaterTT() && IsDepotTypeTile(m_old_tile, TT())) { - DiagDirection exitdir = IsRailTT() ? GetRailDepotDirection(m_old_tile) : GetRoadDepotDirection(m_old_tile); + DiagDirection exitdir; + switch (TT()) { + case TRANSPORT_AIR: + return false; + case TRANSPORT_RAIL: + if (IsExtendedRailDepot(m_old_tile)) return false; + exitdir = GetRailDepotDirection(m_old_tile); + break; + case TRANSPORT_ROAD: { + if (GetRoadBits(m_old_tile, IsTram() ? RTT_TRAM : RTT_ROAD) != DiagDirToRoadBits(m_exitdir)) return false; + exitdir = ReverseDiagDir(m_exitdir); + break; + } + default: NOT_REACHED(); + } + if (exitdir != m_exitdir) { /* reverse */ m_new_tile = m_old_tile; m_new_td_bits = TrackdirToTrackdirBits(ReverseTrackdir(m_old_td)); - m_exitdir = exitdir; + m_exitdir = IsAirTT() ? ReverseDiagDir(m_exitdir) : exitdir; m_tiles_skipped = 0; - m_is_tunnel = m_is_bridge = m_is_station = false; + m_is_tunnel = m_is_bridge = m_is_station = m_is_extended_depot = false; return true; } } @@ -460,6 +525,10 @@ struct CFollowTrackT uint16_t road_speed = GetRoadTypeInfo(GetRoadType(m_old_tile, GetRoadTramType(RoadVehicle::From(m_veh)->roadtype)))->max_speed; if (road_speed > 0) max_speed = std::min(max_speed, road_speed); } + if (IsAirTT()) { + uint16_t air_speed = GetAirTypeInfo(GetAirType(m_old_tile))->max_speed; + if (air_speed > 0) max_speed = std::min(max_speed, (int)air_speed); + } /* if min speed was requested, return it */ if (pmin_speed != nullptr) *pmin_speed = min_speed; @@ -470,6 +539,7 @@ struct CFollowTrackT typedef CFollowTrackT CFollowTrackWater; typedef CFollowTrackT CFollowTrackRoad; typedef CFollowTrackT CFollowTrackRail; +typedef CFollowTrackT CFollowTrackAirport; typedef CFollowTrackT CFollowTrackRailNo90; diff --git a/src/pathfinder/pathfinder_func.h b/src/pathfinder/pathfinder_func.h index 444b100ce7b0a..410776511b5b0 100644 --- a/src/pathfinder/pathfinder_func.h +++ b/src/pathfinder/pathfinder_func.h @@ -12,6 +12,7 @@ #include "../tile_cmd.h" #include "../waypoint_base.h" +#include "../depot_base.h" /** * Calculates the tile of given station that is closest to a given tile @@ -47,6 +48,46 @@ inline TileIndex CalcClosestStationTile(StationID station, TileIndex tile, Stati return TileXY(x, y); } +/** + * Calculates the tile of a depot that is closest to a given tile. + * @param depot_id The depot to calculate the distance to. + * @param tile The tile from where to calculate the distance. + * @return The closest depot tile to the given tile. + */ +static inline TileIndex CalcClosestDepotTile(DepotID depot_id, TileIndex tile) +{ + assert(Depot::IsValidID(depot_id)); + const Depot *dep = Depot::Get(depot_id); + + /* If tile area is empty, use the xy tile. */ + if (dep->ta.tile == INVALID_TILE) { + assert(dep->xy != INVALID_TILE); + return dep->xy; + } + + TileIndex best_tile = INVALID_TILE; + DepotReservation best_found_type = dep->veh_type == VEH_SHIP ? DEPOT_RESERVATION_END : DEPOT_RESERVATION_EMPTY; + uint best_distance = UINT_MAX; + + for (auto const &depot_tile : dep->depot_tiles) { + uint new_distance = DistanceManhattan(depot_tile, tile); + bool check_south_direction = dep->veh_type == VEH_ROAD; +again: + DepotReservation depot_reservation = GetDepotReservation(depot_tile, check_south_direction); + if (((best_found_type == depot_reservation) && new_distance < best_distance) || (depot_reservation < best_found_type)) { + best_tile = depot_tile; + best_distance = new_distance; + best_found_type = depot_reservation; + } + if (check_south_direction) { + check_south_direction = false; + goto again; + } + } + + return best_tile; +} + /** * Wrapper around GetTileTrackStatus() and TrackStatusToTrackdirBits(), as for * single tram bits GetTileTrackStatus() returns 0. The reason for this is diff --git a/src/pathfinder/yapf/CMakeLists.txt b/src/pathfinder/yapf/CMakeLists.txt index 6717233352b60..fdd1dd744a3e1 100644 --- a/src/pathfinder/yapf/CMakeLists.txt +++ b/src/pathfinder/yapf/CMakeLists.txt @@ -10,9 +10,11 @@ add_files( yapf_costrail.hpp yapf_destrail.hpp yapf_node.hpp + yapf_node_air.hpp yapf_node_rail.hpp yapf_node_road.hpp yapf_node_ship.hpp + yapf_air.cpp yapf_rail.cpp yapf_road.cpp yapf_ship.cpp diff --git a/src/pathfinder/yapf/yapf.h b/src/pathfinder/yapf/yapf.h index 186986ce57e57..80ab9e9c7fead 100644 --- a/src/pathfinder/yapf/yapf.h +++ b/src/pathfinder/yapf/yapf.h @@ -15,14 +15,26 @@ #include "../../vehicle_type.h" #include "../../ship.h" #include "../../roadveh.h" +#include "../../aircraft.h" #include "../pathfinder_type.h" +/** + * Finds the best path for given aircraft using YAPF. + * @param v the aircraft that is looking for a path + * @param best_dest [out] struct containing best dest tile and best trackdir + * @param path_found [out] whether a path has been found (true) or has been guessed (false) + * @param dest_state the state that the aircraft is trying to get + * @param path_cache [out] cache for the newly found path + * @return first trackdir on starting tile of the best path found, or INVALID_TRACKDIR if none found. + */ +Trackdir YapfAircraftFindPath(const Aircraft *v, struct PBSTileInfo &best_dest, bool &path_found, AircraftState dest_state, AircraftPathChoice &path_cache); + /** * Finds the best path for given ship using YAPF. - * @param v the ship that needs to find a path - * @param tile the tile to find the path from (should be next tile the ship is about to enter) - * @param path_found [out] Whether a path has been found (true) or has been guessed (false) - * @return the best trackdir for next turn or INVALID_TRACK if the path could not be found + * @param v the ship that needs to find a path + * @param tile the tile to find the path from (should be next tile the ship is about to enter) + * @param path_found [out] whether a path has been found (true) or has been guessed (false) + * @return the best trackdir for next turn or INVALID_TRACK if the path could not be found */ Track YapfShipChooseTrack(const Ship *v, TileIndex tile, bool &path_found, ShipPathCache &path_cache); @@ -36,26 +48,25 @@ bool YapfShipCheckReverse(const Ship *v, Trackdir *trackdir); /** * Finds the best path for given road vehicle using YAPF. - * @param v the RV that needs to find a path - * @param tile the tile to find the path from (should be next tile the RV is about to enter) - * @param enterdir diagonal direction which the RV will enter this new tile from - * @param trackdirs available trackdirs on the new tile (to choose from) - * @param path_found [out] Whether a path has been found (true) or has been guessed (false) - * @return the best trackdir for next turn or INVALID_TRACKDIR if the path could not be found + * @param v the RV that needs to find a path + * @param tile the tile to find the path from (should be next tile the RV is about to enter) + * @param enterdir diagonal direction which the RV will enter this new tile from + * @param trackdirs available trackdirs on the new tile (to choose from) + * @param path_found [out] whether a path has been found (true) or has been guessed (false) + * @return the best trackdir for next turn or INVALID_TRACKDIR if the path could not be found */ Trackdir YapfRoadVehicleChooseTrack(const RoadVehicle *v, TileIndex tile, DiagDirection enterdir, TrackdirBits trackdirs, bool &path_found, RoadVehPathCache &path_cache); /** * Finds the best path for given train using YAPF. - * @param v the train that needs to find a path - * @param tile the tile to find the path from (should be next tile the train is about to enter) - * @param enterdir diagonal direction which the RV will enter this new tile from - * @param tracks available trackdirs on the new tile (to choose from) - * @param path_found [out] Whether a path has been found (true) or has been guessed (false) + * @param v the train that needs to find a path + * @param tile the tile to find the path from (should be next tile the train is about to enter) + * @param enterdir diagonal direction which the RV will enter this new tile from + * @param tracks available trackdirs on the new tile (to choose from) + * @param path_found [out] whether a path has been found (true) or has been guessed (false) * @param reserve_track indicates whether YAPF should try to reserve the found path - * @param target [out] the target tile of the reservation, free is set to true if path was reserved - * @param dest [out] the final tile of the best path found - * @return the best track for next turn + * @param target [out] the target tile of the reservation, free is set to true if path was reserved + * @param dest the best track for next turn */ Track YapfTrainChooseTrack(const Train *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool &path_found, bool reserve_track, struct PBSTileInfo *target, TileIndex *dest); diff --git a/src/pathfinder/yapf/yapf_air.cpp b/src/pathfinder/yapf/yapf_air.cpp new file mode 100644 index 0000000000000..2ab7a044bd3c8 --- /dev/null +++ b/src/pathfinder/yapf/yapf_air.cpp @@ -0,0 +1,443 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file yapf_aircraft.cpp Implementation of YAPF for aircraft. */ + +#include "../../stdafx.h" +#include "../../aircraft.h" +#include "../../pbs_air.h" +#include "../../pbs_air.h" + +#include "track_func.h" +#include "yapf.hpp" +#include "yapf_node_air.hpp" + +#include "../../safeguards.h" + +/** + * When looking for aircraft paths, crossing a non-diagonal track + * may put the aircraft too close to another aircraft crossing the + * equivalent track on a neighbour tile (or getting too close to a hangar). + * Check whether the associated tile is available + * and its corresponding track is not reserved. + * @param tile Tile to check. + * @param track Involved track on tile. + * @return Whether the associated tile can be crossed and it is of the same station and is not reserved. + * @pre IsAirportTile + */ +bool IsAssociatedAirportTileFree(TileIndex tile, Track track) +{ + assert(IsAirportTile(tile)); + assert(IsValidTrack(track)); + + if (IsDiagonalTrack(track)) return true; + + static const Direction track_dir_table[TRACK_END] = { + INVALID_DIR, + INVALID_DIR, + DIR_N, + DIR_S, + DIR_W, + DIR_E, + }; + + TileIndex neighbour = TileAddByDir(tile, track_dir_table[track]); + + return IsValidTile(neighbour) + && IsAirportTileOfStation(neighbour, GetStationIndex(tile)) + && MayHaveAirTracks(neighbour) + && !IsHangar(neighbour) + && !(IsRunway(neighbour) && GetReservationAsRunway(neighbour)) + && !HasAirportTrackReserved(neighbour, TrackToOppositeTrack(track)); +} + +/** + * Check if a tile can be reserved and does not collide with another reserved path. + * @param tile The tile. + * @param trackdir The trackdir to check. + * @return True if reserving \a trackdir on tile \a tile doesn't collide with other paths. + */ +bool IsAirportTileFree(TileIndex tile, Trackdir trackdir) +{ + assert(IsAirportTile(tile)); + assert(MayHaveAirTracks(tile)); + assert(IsValidTrackdir(trackdir)); + + /* Reserved tiles are not free. */ + if (HasAirportTileAnyReservation(tile)) return false; + + /* With non-diagonal tracks, if there is a close reserved non-diagonal track, + * it is not a free tile. */ + Track track = TrackdirToTrack(trackdir); + return IsAssociatedAirportTileFree(tile, track); +} + +/** Node Follower module of YAPF for aircraft. */ +template +class CYapfFollowAircraftT +{ +public: + typedef typename Types::Tpf Tpf; ///< the pathfinder class (derived from THIS class) + typedef typename Types::TrackFollower TrackFollower; + typedef typename Types::NodeList::Titem Node; ///< this will be our node type + typedef typename Node::Key Key; ///< key to hash tables + +protected: + /** to access inherited path finder */ + inline Tpf& Yapf() + { + return *static_cast(this); + } + +public: + + /** + * Called by YAPF to move from the given node to the next tile. For each + * reachable trackdir on the new tile creates new node, initializes it + * and adds it to the open list by calling Yapf().AddNewNode(n) + */ + inline void PfFollowNode(Node &old_node) + { + TrackFollower F(Yapf().GetVehicle()); + bool try_reverse = true; // Whether the vehicle should try to reverse at the end of this tile. + bool reverse_reservable; // Whether if reservation while reversing on the edge of this tile is possible. + + /* Add nodes for rotation in middle of the tile if possible. */ + if (IsDiagonalTrackdir(old_node.m_key.m_td) && + (old_node.m_parent == nullptr || old_node.m_key.m_tile != old_node.m_parent->m_key.m_tile) && + (GetAirportTileTracks(old_node.m_key.m_tile) & TRACK_BIT_CROSS) == TRACK_BIT_CROSS) { + assert(IsValidTrackdir(old_node.m_key.m_td)); + try_reverse = false; + TrackBits tracks = TRACK_BIT_CROSS & ~TrackToTrackBits(TrackdirToTrack(old_node.m_key.m_td)); + for (TrackdirBits rtds = TrackBitsToTrackdirBits(tracks); + rtds != TRACKDIR_BIT_NONE; rtds = KillFirstBit(rtds)) { + Trackdir td = (Trackdir)FindFirstBit(rtds); + Node &n = Yapf().CreateNewNode(); + // Here there is a choice. + n.Set(&old_node, old_node.GetTile(), td, true, true); + Yapf().AddNewNode(n, F); + } + } + + if (F.Follow(old_node.m_key.m_tile, old_node.m_key.m_td)) { + for (TrackdirBits rtds = F.m_new_td_bits; rtds != TRACKDIR_BIT_NONE; rtds = KillFirstBit(rtds)) { + Trackdir td = (Trackdir)FindFirstBit(rtds); + Node &n = Yapf().CreateNewNode(); + n.Set(&old_node, F.m_new_tile, td, false, true); + Yapf().AddNewNode(n, F); + } + + if (!try_reverse) return; + + /* If next tile can have air tracks and the old tile has no track bit cross, + * then allow rotation at the end of this tile. */ + reverse_reservable = (GetReservedAirportTracks(F.m_new_tile) & TrackdirBitsToTrackBits(F.m_new_td_bits)) == TRACK_BIT_NONE; + + /* Add nodes for rotation at the end of the tile if possible. */ + DiagDirection reentry_dir = ReverseDiagDir(TrackdirToExitdir(old_node.m_key.m_td)); + TrackdirBits rtds = DiagdirReachesTrackdirs(reentry_dir) & + TrackBitsToTrackdirBits(GetAirportTileTracks(old_node.m_key.m_tile)) & + ~TrackdirToTrackdirBits(old_node.m_key.m_td); + for ( ; rtds != TRACKDIR_BIT_NONE; rtds = KillFirstBit(rtds)) { + Trackdir td = (Trackdir)FindFirstBit(rtds); + Node &n = Yapf().CreateNewNode(); + // reverse_reservable indicates whether there would be + // a path collision with another path on the next tile. + n.Set(&old_node, old_node.GetTile(), td, false, reverse_reservable); + Yapf().AddNewNode(n, F); + } + } + } + + /** return debug report character to identify the transportation type */ + inline char TransportTypeChar() const + { + return 'a'; + } + + static Trackdir ChooseAircraftPath(const Aircraft *v, PBSTileInfo *best_dest, bool &path_found, AircraftState dest_state, AircraftPathChoice &path_cache) + { + if (!path_cache.empty()) path_cache.clear(); + + /* Handle special case: when next tile is destination tile. */ + if (v->tile == v->GetNextTile()) { + path_found = true; + best_dest->okay = true; + best_dest->tile = v->tile; + best_dest->trackdir = v->trackdir; + return v->trackdir; + } + + assert(IsValidTrackdir(v->trackdir)); + Track track= TrackdirToTrack(v->trackdir); + TrackdirBits trackdirs; + if ((GetAirportTileTracks(v->tile) & TRACK_BIT_CROSS) == TRACK_BIT_CROSS && (v->x_pos & 0xF) == 8 && (v->y_pos & 0xF) == 8) { + trackdirs = TrackBitsToTrackdirBits(TRACK_BIT_CROSS); + } else { + trackdirs = TrackBitsToTrackdirBits(TrackToTrackBits(track)); + } + + /* Create pathfinder instance. */ + Tpf pf; + + /* Set origin and destination nodes. */ + pf.SetOrigin(v->tile, trackdirs); + pf.SetDestination(v->IsHelicopter(), dest_state); + + /* Find best path. */ + path_found = pf.FindPath(v); + bool do_track_reservation = path_found; + Node *pNode = pf.GetBestNode(); + + Trackdir next_trackdir = INVALID_TRACKDIR; + + if (pNode != nullptr) { + /* walk through the path back to the origin */ + while (pNode->m_parent != nullptr) { + do_track_reservation &= pNode->m_reservable; + pNode = pNode->m_parent; + } + assert(pNode->GetTile() == v->tile); + /* return trackdir from the best next node (origin) */ + next_trackdir = pNode->GetTrackdir(); + /* Reserve path from origin till last safe waiting tile */ + if (do_track_reservation) { + for (const Node *cur = pf.GetBestNode(); cur != nullptr; cur = cur->m_parent) { + assert(IsValidTrackdir(cur->GetTrackdir())); + SetAirportTrackReservation(cur->GetTile(), TrackdirToTrack(cur->GetTrackdir())); + if (cur->m_parent != nullptr && cur->GetIsChoice()) { + assert(cur->GetTile() == cur->m_parent->GetTile()); + path_cache.td.push_front(cur->GetTrackdir()); + path_cache.tile.push_front(cur->GetTile()); + } + } + } + + best_dest->tile = pf.GetBestNode()->GetTile(); + best_dest->trackdir = pf.GetBestNode()->GetTrackdir(); + } + + assert(!path_found || next_trackdir != INVALID_TRACKDIR); + best_dest->okay = do_track_reservation; + + return next_trackdir; + } +}; + +/** YAPF origin provider class for air. */ +template +class CYapfAirportOriginTileT +{ +public: + typedef typename Types::Tpf Tpf; ///< the pathfinder class (derived from THIS class) + typedef typename Types::NodeList::Titem Node; ///< this will be our node type + typedef typename Node::Key Key; ///< key to hash tables + +protected: + TileIndex m_orgTile; ///< origin tile + TrackdirBits m_orgTrackdirs; ///< origin trackdir mask + + /** to access inherited path finder */ + inline Tpf& Yapf() + { + return *static_cast(this); + } + +public: + /** Set origin tile / trackdir mask */ + void SetOrigin(TileIndex tile, TrackdirBits trackdirs) + { + m_orgTile = tile; + m_orgTrackdirs = trackdirs; + } + + /** Called when YAPF needs to place origin nodes into open list */ + void PfSetStartupNodes() + { + bool is_choice = (KillFirstBit(m_orgTrackdirs) != TRACKDIR_BIT_NONE); + for (TrackdirBits tdb = m_orgTrackdirs; tdb != TRACKDIR_BIT_NONE; tdb = KillFirstBit(tdb)) { + Trackdir td = (Trackdir)FindFirstBit(tdb); + Node &n1 = Yapf().CreateNewNode(); + n1.Set(nullptr, m_orgTile, td, is_choice, true); + DirDiff difference = NonOrientedDirDifference(TrackdirToDir(Yapf().GetVehicle()->trackdir), TrackdirToDir(td)); + assert(difference % 2 == 0); + assert(difference <= 4); + n1.m_cost = (difference / 2) * YAPF_TILE_LENGTH; + Yapf().AddStartupNode(n1); + } + } +}; + +/** Destination module of YAPF for aircraft. */ +template +class CYapfDestinationAircraftBase { +protected: + AircraftState dest_state; + bool is_helicopter; + +public: + typedef typename Types::Tpf Tpf; ///< the pathfinder class (derived from THIS class) + typedef typename Types::NodeList::Titem Node; ///< this will be our node type + typedef typename Node::Key Key; ///< key to hash tables + + /** to access inherited path finder */ + Tpf& Yapf() + { + return *static_cast(this); + } + + void SetDestination(bool is_helicopter, AircraftState dest_state) + { + this->dest_state = dest_state; + this->is_helicopter = is_helicopter; + } + + /** Called by YAPF to detect if node ends in the desired destination */ + inline bool PfDetectDestination(Node &n) + { + return PfDetectDestination(n.m_segment_last_tile, n.m_segment_last_td); + } + + /** Called by YAPF to detect if node ends in the desired destination */ + inline bool PfDetectDestination(TileIndex tile, Trackdir td) + { + if (!IsDiagonalTrackdir(td)) return false; + + switch (this->dest_state) { + case AS_HANGAR: + /* Reserved extended hangars may be occupied for a long time, + so better try finding another hangar tile. */ + return IsHangar(tile) && (IsStandardHangar(tile) || !HasAirportTileAnyReservation(tile)); + case AS_HELIPAD: + return IsHelipadTile(tile) || IsPlaneApronTile(tile); + case AS_APRON: + return IsPlaneApronTile(tile); + case AS_ON_HOLD_APPROACHING: + case AS_DESCENDING: + return IsRunwayStart(tile) && IsLandingTypeTile(tile); + case AS_START_TAKEOFF: + return this->is_helicopter ? IsApron(tile) : IsRunwayStart(tile); + case AS_IDLE: + return false; + default: + NOT_REACHED(); + } + } + + /** + * Called by YAPF to calculate cost estimate. Calculates distance to the destination + * adds it to the actual cost from origin and stores the sum to the Node::m_estimate + */ + inline bool PfCalcEstimate(Node &n) + { + n.m_estimate = n.m_cost; + return true; + } + +}; + +/** Cost Provider module of YAPF for aircraft. */ +template +class CYapfCostAircraftT +{ +public: + typedef typename Types::Tpf Tpf; ///< the pathfinder class (derived from THIS class) + typedef typename Types::TrackFollower TrackFollower; + typedef typename Types::NodeList::Titem Node; ///< this will be our node type + typedef typename Node::Key Key; ///< key to hash tables + +protected: + /** to access inherited path finder */ + Tpf& Yapf() + { + return *static_cast(this); + } + +public: + /** + * Called by YAPF to calculate the cost from the origin to the given node. + * Calculates only the cost of given node, adds it to the parent node cost + * and stores the result into Node::m_cost member + */ + inline bool PfCalcCost(Node &n, const TrackFollower *) + { + int c = 0; + + /* Is current trackdir a diagonal one? */ + c += IsDiagonalTrackdir(n.GetTrackdir()) ? YAPF_TILE_LENGTH : YAPF_TILE_CORNER_LENGTH; + + /* Reserved tiles. */ + if (!IsAirportTileFree(n.GetTile(), n.GetTrackdir())) { + n.m_reservable = false; + c *= 4; + /* Prefer other possible paths where the destination is not occupied. */ + if (Yapf().PfDetectDestination(n.GetTile(), n.GetTrackdir())) { + c *= 4; + } + } + + /* Add cost to avoid vehicles going over aprons and other special tiles as much as possible. */ + if (!IsSimpleTrack(n.GetTile())) c += 8 * YAPF_TILE_LENGTH; + + if (n.GetTile() == n.m_parent->GetTile()) { + /* Penalty for rotating in a tile (middle or edge position). */ + c += YAPF_TILE_LENGTH; + + if (!IsDiagonalTrack(TrackdirToTrack(n.GetTrackdir())) || + !IsDiagonalTrack(TrackdirToTrack(n.m_parent->GetTrackdir()))) { + /* Extra penalty for rotating at the edge of a tile. */ + c += YAPF_TILE_LENGTH; + } + } + + /* Penalty for curves. */ + if (n.GetTrackdir() != NextTrackdir(n.m_parent->GetTrackdir())) { + /* New trackdir does not match the next one when going straight. */ + c += YAPF_TILE_LENGTH; + } + + /* Apply it. */ + n.m_cost = n.m_parent->m_cost + c; + return true; + } +}; + +/** + * Config struct of YAPF for aircraft. + * Defines all 6 base YAPF modules as classes providing services for CYapfBaseT. + */ +template class TCYapfCostAircraftT> +struct CYapfAircraft_TypesT +{ + /** Types - shortcut for this struct type */ + typedef CYapfAircraft_TypesT Types; + + /** Tpf - pathfinder type */ + typedef Tpf_ Tpf; + /** track follower helper class */ + typedef Ttrack_follower TrackFollower; + /** node list type */ + typedef CAircraftNodeListTrackDir NodeList; + typedef Aircraft VehicleType; + /** pathfinder components (modules) */ + typedef CYapfBaseT PfBase; // base pathfinder class + typedef CYapfFollowAircraftT PfFollow; // node follower + typedef CYapfAirportOriginTileT PfOrigin; // origin provider + typedef CYapfDestinationAircraftBase PfDestination; // destination/distance provider + typedef CYapfSegmentCostCacheNoneT PfCache; // segment cost cache provider + typedef TCYapfCostAircraftT PfCost; // cost provider +}; + +/* YAPF with reservation - uses TileIndex/Trackdir as Node key, allows 90-deg turns */ +struct CYapfAircraft : CYapfT > {}; + +/** Aircraft controller helper - path finder invoker */ +Trackdir YapfAircraftFindPath(const Aircraft *v, PBSTileInfo &best_dest, bool &path_found, AircraftState dest_state, AircraftPathChoice &path_cache) +{ + return CYapfAircraft::ChooseAircraftPath(v, &best_dest, path_found, dest_state, path_cache); +} + diff --git a/src/pathfinder/yapf/yapf_costrail.hpp b/src/pathfinder/yapf/yapf_costrail.hpp index f6217d2b245ee..196403aa97ff4 100644 --- a/src/pathfinder/yapf/yapf_costrail.hpp +++ b/src/pathfinder/yapf/yapf_costrail.hpp @@ -150,13 +150,24 @@ class CYapfCostRailT : public CYapfCostBase { return false; } + /** Check for a reserved depot platform. */ + inline bool IsAnyDepotTileReserved(TileIndex tile, Trackdir trackdir, int skipped) + { + TileIndexDiff diff = TileOffsByDiagDir(TrackdirToExitdir(ReverseTrackdir(trackdir))); + for (; skipped >= 0; skipped--, tile += diff) { + if (HasDepotReservation(tile)) return true; + } + return false; + } + /** The cost for reserved tiles, including skipped ones. */ inline int ReservationCost(Node &n, TileIndex tile, Trackdir trackdir, int skipped) { if (n.m_num_signals_passed >= m_sig_look_ahead_costs.size() / 2) return 0; if (!IsPbsSignal(n.m_last_signal_type)) return 0; - if (IsRailStationTile(tile) && IsAnyStationTileReserved(tile, trackdir, skipped)) { + if ((IsRailStationTile(tile) && IsAnyStationTileReserved(tile, trackdir, skipped)) || + (IsExtendedRailDepotTile(tile) && IsAnyDepotTileReserved(tile, trackdir, skipped))) { return Yapf().PfGetSettings().rail_pbs_station_penalty * (skipped + 1); } else if (TrackOverlapsTracks(GetReservedTrackbits(tile), TrackdirToTrack(trackdir))) { int cost = Yapf().PfGetSettings().rail_pbs_cross_penalty; @@ -389,13 +400,12 @@ class CYapfCostRailT : public CYapfCostBase { /* Tests for 'potential target' reasons to close the segment. */ if (cur.tile == prev.tile) { /* Penalty for reversing in a depot. */ - assert(IsRailDepot(cur.tile)); + assert(IsStandardRailDepot(cur.tile)); segment_cost += Yapf().PfGetSettings().rail_depot_reverse_penalty; - } else if (IsRailDepotTile(cur.tile)) { + } else if (IsStandardRailDepotTile(cur.tile)) { /* We will end in this pass (depot is possible target) */ end_segment_reason |= ESRB_DEPOT; - } else if (cur.tile_type == MP_STATION && IsRailWaypoint(cur.tile)) { if (v->current_order.IsType(OT_GOTO_WAYPOINT) && GetStationIndex(cur.tile) == v->current_order.GetDestination() && @@ -440,14 +450,14 @@ class CYapfCostRailT : public CYapfCostBase { /* Waypoint is also a good reason to finish. */ end_segment_reason |= ESRB_WAYPOINT; - } else if (tf->m_is_station) { + } else if (tf->m_is_station || tf->m_is_extended_depot) { /* Station penalties. */ uint platform_length = tf->m_tiles_skipped + 1; /* We don't know yet if the station is our target or not. Act like * if it is pass-through station (not our destination). */ segment_cost += Yapf().PfGetSettings().rail_station_penalty * platform_length; /* We will end in this pass (station is possible target) */ - end_segment_reason |= ESRB_STATION; + end_segment_reason |= ESRB_PLATFORM; } else if (TrackFollower::DoTrackMasking() && cur.tile_type == MP_RAILWAY) { /* Searching for a safe tile? */ @@ -591,13 +601,21 @@ class CYapfCostRailT : public CYapfCostBase { } } - /* Station platform-length penalty. */ - if ((end_segment_reason & ESRB_STATION) != ESRB_NONE) { - const BaseStation *st = BaseStation::GetByTile(n.GetLastTile()); - assert(st != nullptr); - uint platform_length = st->GetPlatformLength(n.GetLastTile(), ReverseDiagDir(TrackdirToExitdir(n.GetLastTrackdir()))); - /* Reduce the extra cost caused by passing-station penalty (each station receives it in the segment cost). */ + /* Platform-length penalty. */ + if ((end_segment_reason & ESRB_PLATFORM) != ESRB_NONE) { + assert(HasStationTileRail(n.GetLastTile()) || IsExtendedRailDepotTile(n.GetLastTile())); + uint platform_length = GetPlatformLength(n.GetLastTile(), ReverseDiagDir(TrackdirToExitdir(n.GetLastTrackdir()))); + /* Reduce the extra cost caused by passing-platform penalty (each platform receives it in the segment cost). */ extra_cost -= Yapf().PfGetSettings().rail_station_penalty * platform_length; + if (tf->m_is_extended_depot) { + DepotReservation depot_reservation = GetDepotReservation(n.GetLastTile()); + if (depot_reservation == DEPOT_RESERVATION_FULL_STOPPED_VEH) { + extra_cost += YAPF_INFINITE_PENALTY; + } else { + extra_cost += (HasDepotReservation(n.GetLastTile()) ? 2 : 1) * platform_length * Yapf().PfGetSettings().rail_station_penalty; + } + } + /* Add penalty for the inappropriate platform length. */ extra_cost += PlatformLengthPenalty(platform_length); } diff --git a/src/pathfinder/yapf/yapf_destrail.hpp b/src/pathfinder/yapf/yapf_destrail.hpp index f39a8a2c4b40e..f6b871ea2c656 100644 --- a/src/pathfinder/yapf/yapf_destrail.hpp +++ b/src/pathfinder/yapf/yapf_destrail.hpp @@ -118,6 +118,7 @@ class CYapfDestinationTileOrStationRailT : public CYapfDestinationRailBase { TileIndex m_destTile; TrackdirBits m_destTrackdirs; StationID m_dest_station_id; + DepotID m_dest_depot_id; bool m_any_depot; /** to access inherited path finder */ @@ -149,14 +150,25 @@ class CYapfDestinationTileOrStationRailT : public CYapfDestinationRailBase { break; case OT_GOTO_DEPOT: + m_dest_station_id = INVALID_STATION; + if (v->current_order.GetDepotActionType() & ODATFB_NEAREST_DEPOT) { m_any_depot = true; + m_dest_depot_id = INVALID_DEPOT; + m_destTile = v->dest_tile; + m_destTrackdirs = TrackStatusToTrackdirBits(GetTileTrackStatus(v->dest_tile, TRANSPORT_RAIL, 0)); + } else { + m_dest_depot_id = v->current_order.GetDestination(); + assert(Depot::IsValidID(m_dest_depot_id)); + m_destTile = CalcClosestDepotTile(m_dest_depot_id, v->tile); + m_destTrackdirs = INVALID_TRACKDIR_BIT; } - [[fallthrough]]; + break; default: m_destTile = v->dest_tile; m_dest_station_id = INVALID_STATION; + m_dest_depot_id = INVALID_DEPOT; m_destTrackdirs = TrackStatusToTrackdirBits(GetTileTrackStatus(v->dest_tile, TRANSPORT_RAIL, 0)); break; } @@ -176,6 +188,10 @@ class CYapfDestinationTileOrStationRailT : public CYapfDestinationRailBase { return HasStationTileRail(tile) && (GetStationIndex(tile) == m_dest_station_id) && (GetRailStationTrack(tile) == TrackdirToTrack(td)); + } else if (m_dest_depot_id != INVALID_DEPOT) { + return IsRailDepotTile(tile) + && (GetDepotIndex(tile) == m_dest_depot_id) + && (GetRailDepotTrack(tile) == TrackdirToTrack(td)); } if (m_any_depot) { diff --git a/src/pathfinder/yapf/yapf_node_air.hpp b/src/pathfinder/yapf/yapf_node_air.hpp new file mode 100644 index 0000000000000..1abc2ed1b8323 --- /dev/null +++ b/src/pathfinder/yapf/yapf_node_air.hpp @@ -0,0 +1,34 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file yapf_node_aircraft.hpp Node tailored for aircraft pathfinding. */ + +#ifndef YAPF_NODE_AIRCRAFT_HPP +#define YAPF_NODE_AIRCRAFT_HPP + +/** Yapf Node for aircraft YAPF */ +template +struct CYapfAircraftNodeT : CYapfNodeT > { + typedef CYapfNodeT > base; + + TileIndex m_segment_last_tile; + Trackdir m_segment_last_td; + bool m_reservable; + + void Set(CYapfAircraftNodeT *parent, TileIndex tile, Trackdir td, bool is_choice, bool reservable) + { + base::Set(parent, tile, td, is_choice); + m_segment_last_tile = tile; + m_segment_last_td = td; + m_reservable = reservable; + } +}; + +typedef CYapfAircraftNodeT CYapfAircraftNodeTrackDir; +typedef CNodeList_HashTableT CAircraftNodeListTrackDir; + +#endif /* YAPF_NODE_AIRCRAFT_HPP */ diff --git a/src/pathfinder/yapf/yapf_rail.cpp b/src/pathfinder/yapf/yapf_rail.cpp index 95800867991dc..d88e88110fb14 100644 --- a/src/pathfinder/yapf/yapf_rail.cpp +++ b/src/pathfinder/yapf/yapf_rail.cpp @@ -16,6 +16,7 @@ #include "yapf_destrail.hpp" #include "../../viewport_func.h" #include "../../newgrf_station.h" +#include "../../platform_func.h" #include "../../safeguards.h" @@ -85,6 +86,23 @@ class CYapfReserveTrack return true; } + /** Reserve a railway platform. Tile contains the failed tile on abort. */ + bool ReserveRailDepotPlatform(TileIndex &tile, DiagDirection dir) + { + assert(IsExtendedRailDepotTile(tile)); + TileIndex start = tile; + TileIndexDiff diff = TileOffsByDiagDir(dir); + + do { + if (HasDepotReservation(tile)) return false; + SetDepotReservation(tile, true); + MarkTileDirtyByTile(tile); + tile = TileAdd(tile, diff); + } while (IsCompatibleTrainDepotTile(tile, start) && tile != m_origin_tile); + + return true; + } + /** Try to reserve a single track/platform. */ bool ReserveSingleTrack(TileIndex tile, Trackdir td) { @@ -94,6 +112,12 @@ class CYapfReserveTrack m_res_fail_tile = tile; m_res_fail_td = td; } + } else if (IsExtendedRailDepotTile(tile)) { + if (!ReserveRailDepotPlatform(tile, TrackdirToExitdir(ReverseTrackdir(td)))) { + /* Platform could not be reserved, undo. */ + m_res_fail_tile = tile; + m_res_fail_td = td; + } } else { if (!TryReserveRailTrack(tile, TrackdirToTrack(td))) { /* Tile couldn't be reserved, undo. */ @@ -116,6 +140,13 @@ class CYapfReserveTrack SetRailStationReservation(tile, false); tile = TileAdd(tile, diff); } + } else if (IsExtendedRailDepotTile(tile)) { + TileIndex start = tile; + TileIndexDiff diff = TileOffsByDiagDir(TrackdirToExitdir(ReverseTrackdir(td))); + while ((tile != m_res_fail_tile || td != m_res_fail_td) && IsCompatibleTrainDepotTile(tile, start)) { + SetDepotReservation(tile, false); + tile = TileAdd(tile, diff); + } } else if (tile != m_res_fail_tile || td != m_res_fail_td) { UnreserveRailTrack(tile, TrackdirToTrack(td)); } @@ -646,7 +677,9 @@ bool YapfTrainFindNearestSafeTile(const Train *v, TileIndex tile, Trackdir td, b /** if any track changes, this counter is incremented - that will invalidate segment cost cache */ int CSegmentCostCacheBase::s_rail_change_counter = 0; +extern void FixBigRailDepotSprites(Tile tile); void YapfNotifyTrackLayoutChange(TileIndex tile, Track track) { + FixBigRailDepotSprites(tile); CSegmentCostCacheBase::NotifyTrackLayoutChange(tile, track); } diff --git a/src/pathfinder/yapf/yapf_road.cpp b/src/pathfinder/yapf/yapf_road.cpp index 209b64b52afe4..c1876dad13fa2 100644 --- a/src/pathfinder/yapf/yapf_road.cpp +++ b/src/pathfinder/yapf/yapf_road.cpp @@ -66,6 +66,19 @@ class CYapfCostRoadT /* Increase the cost for level crossings */ if (IsLevelCrossing(tile)) { cost += Yapf().PfGetSettings().road_crossing_penalty; + } else if (IsRoadDepot(tile) && IsExtendedRoadDepot(tile)) { + switch (GetDepotReservation(tile, IsDiagDirFacingSouth(TrackdirToExitdir(trackdir)))) { + case DEPOT_RESERVATION_FULL_STOPPED_VEH: + cost += 16 * YAPF_TILE_LENGTH; + break; + case DEPOT_RESERVATION_IN_USE: + cost += 8 * YAPF_TILE_LENGTH; + break; + case DEPOT_RESERVATION_EMPTY: + cost += YAPF_TILE_LENGTH; + break; + default: NOT_REACHED(); + } } break; @@ -135,7 +148,7 @@ class CYapfCostRoadT } /* stop if we have just entered the depot */ - if (IsRoadDepotTile(tile) && trackdir == DiagDirToDiagTrackdir(ReverseDiagDir(GetRoadDepotDirection(tile)))) { + if (IsRoadDepotTile(tile) && !IsExtendedRoadDepotTile(tile) && trackdir == DiagDirToDiagTrackdir(ReverseDiagDir(GetRoadDepotDirection(tile)))) { /* next time we will reverse and leave the depot */ break; } @@ -201,7 +214,7 @@ class CYapfDestinationAnyDepotRoadT /** Called by YAPF to detect if node ends in the desired destination */ inline bool PfDetectDestination(Node &n) { - return IsRoadDepotTile(n.m_segment_last_tile); + return PfDetectDestinationTile(n.m_segment_last_tile, n.m_segment_last_td); } inline bool PfDetectDestinationTile(TileIndex tile, Trackdir) @@ -234,7 +247,9 @@ class CYapfDestinationTileRoadT TileIndex m_destTile; TrackdirBits m_destTrackdirs; StationID m_dest_station; + DepotID m_dest_depot; StationType m_station_type; + bool m_bus; bool m_non_artic; public: @@ -252,8 +267,14 @@ class CYapfDestinationTileRoadT m_destTile = CalcClosestStationTile(m_dest_station, v->tile, m_station_type); m_non_artic = !v->HasArticulatedPart(); m_destTrackdirs = INVALID_TRACKDIR_BIT; + } else if (v->current_order.IsType(OT_GOTO_DEPOT)) { + m_dest_station = INVALID_STATION; + m_dest_depot = v->current_order.GetDestination(); + m_destTile = CalcClosestDepotTile(m_dest_depot, v->tile); + m_destTrackdirs = INVALID_TRACKDIR_BIT; } else { m_dest_station = INVALID_STATION; + m_dest_depot = INVALID_DEPOT; m_destTile = v->dest_tile; m_destTrackdirs = TrackStatusToTrackdirBits(GetTileTrackStatus(v->dest_tile, TRANSPORT_ROAD, GetRoadTramType(v->roadtype))); } @@ -287,6 +308,11 @@ class CYapfDestinationTileRoadT (m_non_artic || IsDriveThroughStopTile(tile)); } + if (m_dest_depot != INVALID_DEPOT) { + return IsRoadDepotTile(tile) && + GetDepotIndex(tile) == m_dest_depot; + } + return tile == m_destTile && HasTrackdir(m_destTrackdirs, trackdir); } diff --git a/src/pathfinder/yapf/yapf_ship.cpp b/src/pathfinder/yapf/yapf_ship.cpp index 57d5e9d87742d..b2b998ba8b4e0 100644 --- a/src/pathfinder/yapf/yapf_ship.cpp +++ b/src/pathfinder/yapf/yapf_ship.cpp @@ -382,6 +382,10 @@ class CYapfCostShipT c += count * 3 * YAPF_TILE_LENGTH; } + if (IsShipDepotTile(n.GetTile())) { + if (IsExtendedDepot(n.GetTile()) && IsDepotFullWithStoppedVehicles(n.GetTile())) c += YAPF_INFINITE_PENALTY; + } + /* Skipped tile cost for aqueducts. */ c += YAPF_TILE_LENGTH * tf->m_tiles_skipped; diff --git a/src/pathfinder/yapf/yapf_type.hpp b/src/pathfinder/yapf/yapf_type.hpp index 4f301b0fb719b..511f89be83460 100644 --- a/src/pathfinder/yapf/yapf_type.hpp +++ b/src/pathfinder/yapf/yapf_type.hpp @@ -23,7 +23,7 @@ enum EndSegmentReason { ESR_CHOICE_FOLLOWS, ///< the next tile contains a choice (the track splits to more than one segments) ESR_DEPOT, ///< stop in the depot (could be a target next time) ESR_WAYPOINT, ///< waypoint encountered (could be a target next time) - ESR_STATION, ///< station encountered (could be a target next time) + ESR_PLATFORM, ///< platform (station/extended depot) encountered (could be a target next time) ESR_SAFE_TILE, ///< safe waiting position found (could be a target) /* The following reasons are used only internally by PfCalcCost(). @@ -47,7 +47,7 @@ enum EndSegmentReasonBits { ESRB_CHOICE_FOLLOWS = 1 << ESR_CHOICE_FOLLOWS, ESRB_DEPOT = 1 << ESR_DEPOT, ESRB_WAYPOINT = 1 << ESR_WAYPOINT, - ESRB_STATION = 1 << ESR_STATION, + ESRB_PLATFORM = 1 << ESR_PLATFORM, ESRB_SAFE_TILE = 1 << ESR_SAFE_TILE, ESRB_PATH_TOO_LONG = 1 << ESR_PATH_TOO_LONG, @@ -58,10 +58,10 @@ enum EndSegmentReasonBits { /* Additional (composite) values. */ /* What reasons mean that the target can be found and needs to be detected. */ - ESRB_POSSIBLE_TARGET = ESRB_DEPOT | ESRB_WAYPOINT | ESRB_STATION | ESRB_SAFE_TILE, + ESRB_POSSIBLE_TARGET = ESRB_DEPOT | ESRB_WAYPOINT | ESRB_PLATFORM | ESRB_SAFE_TILE, /* What reasons can be stored back into cached segment. */ - ESRB_CACHED_MASK = ESRB_DEAD_END | ESRB_RAIL_TYPE | ESRB_INFINITE_LOOP | ESRB_SEGMENT_TOO_LONG | ESRB_CHOICE_FOLLOWS | ESRB_DEPOT | ESRB_WAYPOINT | ESRB_STATION | ESRB_SAFE_TILE, + ESRB_CACHED_MASK = ESRB_DEAD_END | ESRB_RAIL_TYPE | ESRB_INFINITE_LOOP | ESRB_SEGMENT_TOO_LONG | ESRB_CHOICE_FOLLOWS | ESRB_DEPOT | ESRB_WAYPOINT | ESRB_PLATFORM | ESRB_SAFE_TILE, /* Reasons to abort pathfinding in this direction. */ ESRB_ABORT_PF_MASK = ESRB_DEAD_END | ESRB_PATH_TOO_LONG | ESRB_INFINITE_LOOP | ESRB_FIRST_TWO_WAY_RED, diff --git a/src/pbs.cpp b/src/pbs.cpp index 363404330c5fc..27a366ff919e0 100644 --- a/src/pbs.cpp +++ b/src/pbs.cpp @@ -12,6 +12,8 @@ #include "vehicle_func.h" #include "newgrf_station.h" #include "pathfinder/follow_track.hpp" +#include "platform_func.h" +#include "depot_map.h" #include "safeguards.h" @@ -47,28 +49,6 @@ TrackBits GetReservedTrackbits(TileIndex t) return TRACK_BIT_NONE; } -/** - * Set the reservation for a complete station platform. - * @pre IsRailStationTile(start) - * @param start starting tile of the platform - * @param dir the direction in which to follow the platform - * @param b the state the reservation should be set to - */ -void SetRailStationPlatformReservation(TileIndex start, DiagDirection dir, bool b) -{ - TileIndex tile = start; - TileIndexDiff diff = TileOffsByDiagDir(dir); - - assert(IsRailStationTile(start)); - assert(GetRailStationAxis(start) == DiagDirToAxis(dir)); - - do { - SetRailStationReservation(tile, b); - MarkTileDirtyByTile(tile); - tile = TileAdd(tile, diff); - } while (IsCompatibleTrainStationTile(tile, start)); -} - /** * Try to reserve a specific track on a tile * @param tile the tile @@ -202,12 +182,12 @@ static PBSTileInfo FollowReservation(Owner o, RailTypes rts, TileIndex tile, Tra /* No reservation --> path end found */ if (reserved == TRACKDIR_BIT_NONE) { - if (ft.m_is_station) { + if (ft.m_is_station || ft.m_is_extended_depot) { /* Check skipped station tiles as well, maybe our reservation ends inside the station. */ TileIndexDiff diff = TileOffsByDiagDir(ft.m_exitdir); while (ft.m_tiles_skipped-- > 0) { ft.m_new_tile -= diff; - if (HasStationReservation(ft.m_new_tile)) { + if ((ft.m_is_station && HasStationReservation(ft.m_new_tile)) || (ft.m_is_extended_depot && HasDepotReservation(ft.m_new_tile))) { tile = ft.m_new_tile; trackdir = DiagDirToDiagTrackdir(ft.m_exitdir); break; @@ -240,7 +220,7 @@ static PBSTileInfo FollowReservation(Owner o, RailTypes rts, TileIndex tile, Tra if (tile == start_tile && trackdir == start_trackdir) break; } /* Depot tile? Can't continue. */ - if (IsRailDepotTile(tile)) break; + if (IsStandardRailDepotTile(tile)) break; /* Non-pbs signal? Reservation can't continue. */ if (IsTileType(tile, MP_RAILWAY) && HasSignalOnTrackdir(tile, trackdir) && !IsPbsSignal(GetSignalType(tile, TrackdirToTrack(trackdir)))) break; } @@ -292,7 +272,7 @@ PBSTileInfo FollowTrainReservation(const Train *v, Vehicle **train_on_res) TileIndex tile = v->tile; Trackdir trackdir = v->GetVehicleTrackdir(); - if (IsRailDepotTile(tile) && !GetDepotReservationTrackBits(tile)) return PBSTileInfo(tile, trackdir, false); + if (IsStandardRailDepotTile(tile) && !GetDepotReservationTrackBits(tile)) return PBSTileInfo(tile, trackdir, false); FindTrainOnTrackInfo ftoti; ftoti.res = FollowReservation(v->owner, GetRailTypeInfo(v->railtype)->compatible_railtypes, tile, trackdir); @@ -300,14 +280,14 @@ PBSTileInfo FollowTrainReservation(const Train *v, Vehicle **train_on_res) if (train_on_res != nullptr) { FindVehicleOnPos(ftoti.res.tile, &ftoti, FindTrainOnTrackEnum); if (ftoti.best != nullptr) *train_on_res = ftoti.best->First(); - if (*train_on_res == nullptr && IsRailStationTile(ftoti.res.tile)) { - /* The target tile is a rail station. The track follower + if (*train_on_res == nullptr && (IsRailStationTile(ftoti.res.tile) || IsExtendedRailDepotTile(ftoti.res.tile))) { + /* The target tile is a rail station or extended depot. The track follower * has stopped on the last platform tile where we haven't * found a train. Also check all previous platform tiles * for a possible train. */ TileIndexDiff diff = TileOffsByDiagDir(TrackdirToExitdir(ReverseTrackdir(ftoti.res.trackdir))); - for (TileIndex st_tile = ftoti.res.tile + diff; *train_on_res == nullptr && IsCompatibleTrainStationTile(st_tile, ftoti.res.tile); st_tile += diff) { - FindVehicleOnPos(st_tile, &ftoti, FindTrainOnTrackEnum); + for (TileIndex pt_tile = ftoti.res.tile + diff; *train_on_res == nullptr && IsCompatiblePlatformTile(pt_tile, ftoti.res.tile); pt_tile += diff) { + FindVehicleOnPos(pt_tile, &ftoti, FindTrainOnTrackEnum); if (ftoti.best != nullptr) *train_on_res = ftoti.best->First(); } } @@ -348,11 +328,11 @@ Train *GetTrainForReservation(TileIndex tile, Track track) FindVehicleOnPos(ftoti.res.tile, &ftoti, FindTrainOnTrackEnum); if (ftoti.best != nullptr) return ftoti.best; - /* Special case for stations: check the whole platform for a vehicle. */ - if (IsRailStationTile(ftoti.res.tile)) { + /* Special case for stations and extended depots: check the whole platform for a vehicle. */ + if (IsRailStationTile(ftoti.res.tile) || IsExtendedRailDepotTile(ftoti.res.tile)) { TileIndexDiff diff = TileOffsByDiagDir(TrackdirToExitdir(ReverseTrackdir(ftoti.res.trackdir))); - for (TileIndex st_tile = ftoti.res.tile + diff; IsCompatibleTrainStationTile(st_tile, ftoti.res.tile); st_tile += diff) { - FindVehicleOnPos(st_tile, &ftoti, FindTrainOnTrackEnum); + for (TileIndex pt_tile = ftoti.res.tile + diff; IsCompatiblePlatformTile(pt_tile, ftoti.res.tile); pt_tile += diff) { + FindVehicleOnPos(pt_tile, &ftoti, FindTrainOnTrackEnum); if (ftoti.best != nullptr) return ftoti.best; } } @@ -379,7 +359,7 @@ Train *GetTrainForReservation(TileIndex tile, Track track) */ bool IsSafeWaitingPosition(const Train *v, TileIndex tile, Trackdir trackdir, bool include_line_end, bool forbid_90deg) { - if (IsRailDepotTile(tile)) return true; + if (IsStandardRailDepotTile(tile)) return true; if (IsTileType(tile, MP_RAILWAY)) { /* For non-pbs signals, stop on the signal tile. */ @@ -432,7 +412,7 @@ bool IsWaitingPositionFree(const Train *v, TileIndex tile, Trackdir trackdir, bo if (TrackOverlapsTracks(reserved, track)) return false; /* Not reserved and depot or not a pbs signal -> free. */ - if (IsRailDepotTile(tile)) return true; + if (IsStandardRailDepotTile(tile)) return true; if (IsTileType(tile, MP_RAILWAY) && HasSignalOnTrackdir(tile, trackdir) && !IsPbsSignal(GetSignalType(tile, track))) return true; /* Check the next tile, if it's a PBS signal, it has to be free as well. */ @@ -446,3 +426,26 @@ bool IsWaitingPositionFree(const Train *v, TileIndex tile, Trackdir trackdir, bo return !HasReservedTracks(ft.m_new_tile, TrackdirBitsToTrackBits(ft.m_new_td_bits)); } + +/** + * Fix the sprites of depots to show it opened or closed depending on its neighbours. + * @param t Tile that has changed. + */ +void FixBigRailDepotSprites(Tile t) +{ + if (t == INVALID_TILE) return; + + /* Expand tile area to check. */ + TileArea ta = TileArea(t).Expand(1); + + for (Tile tile : ta) { + if (!IsExtendedRailDepotTile(tile)) continue; + CFollowTrackRail ft(GetTileOwner(tile), GetRailTypeInfo(GetTileRailType(tile))->compatible_railtypes); + Track track = GetRailDepotTrack(tile); + Trackdir trackdir = TrackToTrackdir(track); + if (track == TRACK_X) trackdir = ReverseTrackdir(trackdir); + bool opened = ft.Follow(tile, trackdir); + if (track == TRACK_Y) opened = !opened; + SB(tile.m5(), 1, 1, opened); + } +} diff --git a/src/pbs_air.cpp b/src/pbs_air.cpp new file mode 100644 index 0000000000000..e5423af928fa0 --- /dev/null +++ b/src/pbs_air.cpp @@ -0,0 +1,134 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file pbs_air.cpp Path based system routines for air vehicles. */ + +#include "stdafx.h" +#include "air_map.h" +#include "aircraft.h" +#include "pathfinder/follow_track.hpp" + +#include "safeguards.h" + +/** + * When arriving at the end of a landing runway, choose an appropriate trackdir. + * Or when an helicopter lands in an apron, choose an appropriate one. + * @param tile + * @param preferred_trackdir + * @return a valid free trackdir (\a preferred_trackdir if possible). + */ +Trackdir GetFreeAirportTrackdir(TileIndex tile, Trackdir preferred_trackdir) +{ + if (tile == INVALID_TILE) return INVALID_TRACKDIR; + assert(IsValidTrackdir(preferred_trackdir)); + assert(IsDiagonalTrackdir(preferred_trackdir)); + assert(IsAirportTile(tile)); + assert(MayHaveAirTracks(tile)); + assert(IsApron(tile) || (IsRunwayExtreme(tile) && IsRunwayEnd(tile))); + if (HasAirportTrackReserved(tile)) return INVALID_TRACKDIR; + + TrackBits tracks = GetAirportTileTracks(tile) & TRACK_BIT_CROSS; + if (tracks == TRACK_BIT_NONE) return INVALID_TRACKDIR; + + Track preferred_track = TrackdirToTrack(preferred_trackdir); + if (HasTrack(tracks, preferred_track)) return preferred_trackdir; + + tracks &= ~TrackToTrackBits(preferred_track); + if (tracks == TRACK_BIT_NONE) return INVALID_TRACKDIR; + + preferred_track = RemoveFirstTrack(&tracks); + + /* Get one trackdir of the two available trackdirs, better if the trackdir reaches tracks on next tile. */ + CFollowTrackAirport fs(INVALID_COMPANY); + Trackdir trackdir = TrackToTrackdir(preferred_track); + if (fs.Follow(tile, trackdir)) return trackdir; + return ReverseTrackdir(trackdir); +} + +/** + * Remove reservation of given aircraft. + * @param v vehicle that frees some reservations of tracks. + * @param skip_first_track whether skip the first reserved vehicle track. + */ +void LiftAirportPathReservation(Aircraft *v, bool skip_first_track) +{ + if ((v->vehstatus & VS_HIDDEN) != 0) return; + + if (IsHeliportTile(v->tile)) { + /* Special case for heliports. */ + assert(IsValidTrackdir(v->trackdir)); + assert(IsDiagonalTrackdir(v->trackdir)); + if (!skip_first_track) RemoveAirportTrackReservation(v->tile, TrackdirToTrack(v->trackdir)); + return; + } + + /* If not rotating, v->trackdir is the first trackdir. + * If rotating, v->next_trackdir contains the first trackdir (once it has rotated). */ + Trackdir trackdir = v->next_trackdir == INVALID_TRACKDIR ? v->trackdir : v->next_trackdir; + assert(IsValidTrackdir(trackdir)); + TileIndex tile = v->tile; + assert(IsAirportTile(tile)); + assert(MayHaveAirTracks(tile)); + + CFollowTrackAirport fs(INVALID_COMPANY); + for (;;) { + assert(IsAirportTile(tile)); + assert(MayHaveAirTracks(tile)); + Track track = TrackdirToTrack(trackdir); + assert(HasAirportTrackReserved(tile, track)); + RemoveAirportTrackReservation(tile, track); + TrackBits reserved = GetReservedAirportTracks(tile); + + /* Find next part of the path. */ + if ((reserved | TrackToTrackBits(track)) == TRACK_BIT_CROSS) { + /* Path continues in the same tile (middle tile rotation). */ + assert(!v->path.empty()); + assert(v->path.tile.front() == tile); + trackdir = v->path.td.front(); + v->path.pop_front(); + assert(IsValidTrackdir(trackdir)); + continue; + } + + DiagDirection exit_dir = TrackdirToExitdir(trackdir); + TrackdirBits edge_trackdirs = DiagdirReachesTrackdirs(ReverseDiagDir(exit_dir)) & + TrackBitsToTrackdirBits(reserved); + if (edge_trackdirs != TRACKDIR_BIT_NONE) { + assert(CountBits(edge_trackdirs) == 1); + trackdir = FindFirstTrackdir(edge_trackdirs); + /* Path continues in the same tile (rotation at the edge of the tile). */ + continue; + } + + if (!fs.Follow(tile, trackdir)) { + /* Can't follow path. Path end. */ + assert(IsDiagonalTrackdir(trackdir)); + break; + } + + /* Path may continue ahead. Get the corresponding tile and trackdir, if any. */ + fs.m_new_td_bits &= TrackBitsToTrackdirBits(GetReservedAirportTracks(fs.m_new_tile)); + assert(CountBits(fs.m_new_td_bits) < 2); + if (fs.m_new_td_bits == TRACKDIR_BIT_NONE) { + /* Path reservation ended. */ + assert(IsDiagonalTrackdir(trackdir)); + break; + } + + tile = fs.m_new_tile; + trackdir = FindFirstTrackdir(fs.m_new_td_bits); + } + + if (skip_first_track) { + /* Full path unreserved, but must keep the first reserved track. + * Reserve it again. */ + trackdir = v->next_trackdir == INVALID_TRACKDIR ? v->trackdir : v->next_trackdir; + SetAirportTrackReservation(v->tile, TrackdirToTrack(trackdir)); + } + + assert(v->path.empty()); +} diff --git a/src/pbs_air.h b/src/pbs_air.h new file mode 100644 index 0000000000000..7801c18563be9 --- /dev/null +++ b/src/pbs_air.h @@ -0,0 +1,20 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file pbs_air.h PBS support routines for aircraft crossing tiles of an airport or just flying. */ + +#ifndef PBS_AIR_H +#define PBS_AIR_H + +#include "track_type.h" + +struct Aircraft; + +Trackdir GetFreeAirportTrackdir(TileIndex tile, Trackdir preferred_trackdir); +void LiftAirportPathReservation(Aircraft *v, bool skip_first_track); + +#endif /* PBS_AIR_H */ diff --git a/src/pbs_water.cpp b/src/pbs_water.cpp new file mode 100644 index 0000000000000..ce02e21f4c944 --- /dev/null +++ b/src/pbs_water.cpp @@ -0,0 +1,589 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file pbs_water.cpp Path based system routines for water. */ + +#include "stdafx.h" +#include "viewport_func.h" +#include "ship.h" +#include "vehicle_func.h" +#include "pathfinder/follow_track.hpp" +#include "pbs_water.h" + +/** + * Return the reserved water track bits of the tile. + * @param t Tile to query. + * @return Reserved trackbits. + * @pre WaterTrackMayExist + */ +TrackBits GetReservedWaterTracks(TileIndex t) +{ + assert(WaterTrackMayExist(t)); + if (IsTileType(t, MP_TUNNELBRIDGE)) { + if (HasTunnelBridgeReservation(t)) { + return (GetTunnelBridgeDirection(t) % 2) == 0 ? TRACK_BIT_X : TRACK_BIT_Y; + } else { + return TRACK_BIT_NONE; + } + } + + byte track_b = GB(t.m8(), 12, 3); + if (track_b == 0) return TRACK_BIT_NONE; + Track track = (Track)(track_b - 1); // map array saves Track+1 + return TrackToTrackBits(track) | (HasBit(t.m8(), 15) ? TrackToTrackBits(TrackToOppositeTrack(track)) : TRACK_BIT_NONE); +} + +/** + * When trying to enter a tile with possible trackdirs, + * return only the tracks that don't collide with another path. + * @param t Tile + * @param trackdirs Track directions that are free. + * @return Track directions that, if taken, don't collide with other paths. + */ +TrackBits GetFreeWaterTrackReservation(TileIndex t, TrackdirBits trackdirs) +{ + TrackBits trackbits = TRACK_BIT_NONE; + for (Trackdir trackdir = RemoveFirstTrackdir(&trackdirs); trackdir != INVALID_TRACKDIR; trackdir = RemoveFirstTrackdir(&trackdirs)) { + if (IsWaterPositionFree(t, trackdir)) trackbits |= TrackToTrackBits(TrackdirToTrack(trackdir)); + } + return trackbits; +} + +/** + * Set the reserved tracks of a tile. + * @param t Tile to set. + * @param tracks Tracks to reserve on the tile \a t. + * @pre WaterTrackMayExist + */ +void SetWaterTrackReservation(TileIndex t, TrackBits tracks) +{ + if (!_settings_game.pf.ship_path_reservation) return; + + Track track = RemoveFirstTrack(&tracks); + SB(t.m8(), 12, 3, track == INVALID_TRACK ? 0 : track + 1); + SB(t.m8(), 15, 1, tracks != TRACK_BIT_NONE); +} + +/** + * Return true if a ship can try to cross the tile. + * @tile Tile to check. + * @return True if the tile may contain water tracks, false otherwise. + */ +bool WaterTrackMayExist(TileIndex t) +{ + return IsValidTile(t) && ( + IsTileType(t, MP_WATER) || + IsBuoyTile(t) || IsDockTile(t) || + (IsTileType(t, MP_RAILWAY) && GetRailGroundType(t) == RAIL_GROUND_WATER) || + (IsTileType(t, MP_TUNNELBRIDGE) && GetTunnelBridgeTransportType(t) == TRANSPORT_WATER) + ); +} + +/** + * Reserve a track of a tile. + * @param t Tile where to reserve the (water) track. + * @param b Track to reserve. + * @param value true if adding a reservation, false if removing it. + * @return True if the track has been reserved. + */ +bool SetWaterTrackReservation(TileIndex t, Track track, bool value) +{ + assert(WaterTrackMayExist(t)); + assert(track != INVALID_TRACK); + + if (!_settings_game.pf.ship_path_reservation && value) return true; + + if (IsTileType(t, MP_TUNNELBRIDGE)) { + assert(IsDiagonalTrack(track)); + if (value == HasTunnelBridgeReservation(t)) return false; + + SetTunnelBridgeReservation(t, value); + TileArea ta(t); + t = GetOtherTunnelBridgeEnd(t); + SetTunnelBridgeReservation(t, value); + ta.Add(t); + + for (TileIndex t : ta) MarkTileDirtyByTile(t); + + return true; + } + + TrackBits trackbits = GetReservedWaterTracks(t); + + if (value) { + if (TrackOverlapsTracks(trackbits, track)) return false; + trackbits |= TrackToTrackBits(track); + + SB(t.m8(), 12, 3, RemoveFirstTrack(&trackbits) + 1); + SB(t.m8(), 15, 1, trackbits != TRACK_BIT_NONE); + } else { + TrackBits removing_track = TrackToTrackBits(track); + if (!(trackbits & removing_track)) return false; + trackbits &= ~removing_track; + track = RemoveFirstTrack(&trackbits); + assert(trackbits == TRACK_BIT_NONE); + SB(t.m8(), 12, 3, track == INVALID_TRACK ? 0 : track + 1); + SB(t.m8(), 15, 1, 0); + } + + MarkTileDirtyByTile(t); + return true; +} + +/** + * There is a track already reserved? + * @param tile Tile to check. + * @return True if there is a track reserved on \a tile, false if none is reserved. + * @pre WaterTrackMayExist + */ +bool HasWaterTrackReservation(TileIndex t) +{ + assert(WaterTrackMayExist(t)); + return GetReservedWaterTracks(t) != TRACK_BIT_NONE; +} + +/** + * Are some of this tracks reserved? + * @param t Tile to check. + * @param tracks Tracks to check. + * @return True if any of the given tracks \a tracks is reserved on tile \a t. + */ +bool HasWaterTracksReserved(TileIndex t, TrackBits tracks) +{ + assert(WaterTrackMayExist(t)); + return (GetReservedWaterTracks(t) & tracks) != TRACK_BIT_NONE; +} + +/** + * Check if a track collides with the water tracks reserved on a tile. + * @param t Tile to check. + * @param track Track to check. + * @return True if the track can be reserved on tile \a t without colliding other reserved paths. + * @note It doesn't check next tile. + */ +bool TrackCollidesTrackReservation(TileIndex t, Track track) +{ + assert(WaterTrackMayExist(t)); + return TrackOverlapsTracks(GetReservedWaterTracks(t), track); +} + +/** + * Check if a tile can be reserved and does not collide with another path on next tile. + * @param tile The tile. + * @param trackdir The trackdir to check. + * @return True if reserving track direction \a trackdir on tile \a tile + * doesn't collide with other paths. + */ +bool IsWaterPositionFree(TileIndex tile, Trackdir trackdir) +{ + if (!_settings_game.pf.ship_path_reservation) return true; + + /* Check the next tile, if a path collides, then it isn't a waiting position at all. */ + CFollowTrackWater ft(INVALID_COMPANY); + + /* Skip tiles of a lock. */ + if (IsLockTile(tile)) { + while (ft.Follow(tile, trackdir) && CheckSameLock(tile, ft.m_new_tile)) { + tile = ft.m_new_tile; + } + } + + Track track = TrackdirToTrack(trackdir); + + /* Tile reserved? Can never be a free waiting position. */ + if (TrackCollidesTrackReservation(tile, track)) return false; + + if (!ft.Follow(tile, trackdir)) { + if (IsTileType(ft.m_new_tile, MP_INDUSTRY)) { + /* Let ships approach oil rigs. */ + return true; + } else if (!(IsDockTile(tile) && IsDockTile(ft.m_new_tile))) { + /* Let ships cross docks when next tile is a dock. */ + /* We can reverse on docks if needed, but only when next tile is a dock as well. */ + return false; + } + } + + /* On tunnels and bridges we must check the other bridge end. */ + if (IsTileType(tile, MP_TUNNELBRIDGE) && IsTileType(ft.m_new_tile, MP_TUNNELBRIDGE) && + GetOtherTunnelBridgeEnd(tile) == ft.m_new_tile) { + tile = ft.m_new_tile; + if (!ft.Follow(ft.m_new_tile, trackdir)) return false; + } + + /* Check for reachable tracks. + * Don't discard 90deg turns as we don't want two paths to collide + * even if they cannot really collide because of a 90deg turn */ + ft.m_new_td_bits &= DiagdirReachesTrackdirs(ft.m_exitdir); + + return !HasWaterTracksReserved(ft.m_new_tile, TrackdirBitsToTrackBits(ft.m_new_td_bits)); +} + +/** + * Follow a reservation starting from a specific tile to its end. + * @param o Owner (unused: as ships can cross tiles of other owners). + * @param rts Former railtypes (unused: can be converted to water_types? sea, river, canal). + * @param tile Start tile. + * @param trackdir Track direction to look for. + * @return Last tile and info of the reserved path. + */ +static PBSTileInfo FollowShipReservation(Owner o, RailTypes rts, TileIndex tile, Trackdir trackdir) +{ + /* Start track not reserved? */ + assert(HasWaterTracksReserved(tile, TrackToTrackBits(TrackdirToTrack(trackdir)))); + + /* Do not disallow 90 deg turns as the setting might have changed between reserving and now. */ + CFollowTrackWater fs(o, rts); + while (fs.Follow(tile, trackdir)) { + TrackdirBits reserved = fs.m_new_td_bits & TrackBitsToTrackdirBits(GetReservedWaterTracks(fs.m_new_tile)); + if (reserved == TRACKDIR_BIT_NONE) break; + + /* Can't have more than one reserved trackdir */ + trackdir = FindFirstTrackdir(reserved); + tile = fs.m_new_tile; + } + + return PBSTileInfo(tile, trackdir, false); +} + +/** + * Helper struct for finding the best matching vehicle on a specific track. + */ +struct FindShipOnTrackInfo { + PBSTileInfo res; ///< Information about the track. + Ship *best; ///< The currently "best" vehicle we have found. + + /** Init the best ship to nullptr always! */ + FindShipOnTrackInfo() : best(nullptr) {} +}; + +/** + * Callback for Has/FindVehicleOnPos to find a train on a specific track. + * @param v Vehicle to test. + * @param data FindshipOnTrackInfo. + * @return The vehicle on track or nullptr if none found. + */ +static Vehicle *FindShipOnTrackEnum(Vehicle *v, void *data) +{ + FindShipOnTrackInfo *info = (FindShipOnTrackInfo *)data; + + if (v->type != VEH_SHIP) return nullptr; + + Ship *s = Ship::From(v); + TrackBits tracks = s->state; + + if (tracks == TRACK_BIT_DEPOT) return nullptr; + + if (tracks == TRACK_BIT_WORMHOLE || HasBit(tracks, TrackdirToTrack(info->res.trackdir))) { + /* ALWAYS return the lowest ID (anti-desync!) */ + if (info->best == nullptr || s->index < info->best->index) info->best = s; + return s; + } + + return nullptr; +} + +/** + * Follow the reserved path of a ship to its end. + * @param v The vehicle. + * @return The last tile of the reservation or the current ship tile if no reservation is present. + */ +PBSTileInfo FollowShipReservation(const Ship *v) +{ + assert(v->type == VEH_SHIP); + + return FollowShipReservation(v->owner, (RailTypes)0, v->tile, v->GetVehicleTrackdir()); +} + +/** + * Find the ship which has reserved a specific path. + * @param tile A tile on the path. + * @param track A reserved track on the tile. + * @return The vehicle holding the reservation or nullptr if the path is stray. + */ +Ship *GetShipForReservation(TileIndex tile, Track track) +{ + assert(HasWaterTracksReserved(tile, TrackToTrackBits(track))); + Trackdir trackdir = TrackToTrackdir(track); + + /* Follow the path from tile to both ends. + * One of the end tiles should have a ship on it. */ + for (int i = 0; i < 2; ++i, trackdir = ReverseTrackdir(trackdir)) { + FindShipOnTrackInfo fsoti; + fsoti.res = FollowShipReservation(GetTileOwner(tile), (RailTypes)0, tile, trackdir); + + FindVehicleOnPos(fsoti.res.tile, &fsoti, FindShipOnTrackEnum); + if (fsoti.best != nullptr) return fsoti.best; + + /* Special case for bridges/tunnels: check the other end as well. */ + if (IsTileType(fsoti.res.tile, MP_TUNNELBRIDGE)) { + FindVehicleOnPos(GetOtherTunnelBridgeEnd(fsoti.res.tile), &fsoti, FindShipOnTrackEnum); + if (fsoti.best != nullptr) return fsoti.best; + } + + /* Special case for locks: check all lock tiles. */ + if (IsLockTile(fsoti.res.tile)) { + TileIndex t = GetLockMiddleTile(fsoti.res.tile); + TileIndexDiff tilediff_to_lower = GetLockTileIndexDiffToLastLowerTile(t); + TileArea ta(t + tilediff_to_lower, t - tilediff_to_lower); + + for (TileIndex t : ta) { + FindVehicleOnPos(t, &fsoti, FindShipOnTrackEnum); + if (fsoti.best != nullptr) return fsoti.best; + } + } + } + + /* Ship that reserved a given path not found. */ + return nullptr; +} + +/** + * Remove a reservation starting on given tile with a given trackdir. + * @param tile Starting tile. + * @param trackdir Starting trackdir. + */ +void LiftShipPathReservation(TileIndex tile, Trackdir trackdir) +{ + if (!SetWaterTrackReservation(tile, TrackdirToTrack(trackdir), false)) NOT_REACHED(); + + CFollowTrackWater fs(INVALID_COMPANY); + + while (fs.Follow(tile, trackdir)) { + /* Skip 2nd tile of an aqueduct. */ + if (IsBridgeTile(tile) && IsBridgeTile(fs.m_new_tile) && + GetOtherTunnelBridgeEnd(fs.m_new_tile) == tile) { + tile = fs.m_new_tile; + continue; + } + + fs.m_new_td_bits &= TrackBitsToTrackdirBits(GetReservedWaterTracks(fs.m_new_tile)); + + /* Can't have more than one reserved trackdir */ + trackdir = FindFirstTrackdir(fs.m_new_td_bits); + if (trackdir == INVALID_TRACKDIR) break; + tile = fs.m_new_tile; + + if (!SetWaterTrackReservation(tile, TrackdirToTrack(trackdir), false)) NOT_REACHED(); + } +} + +/** + * Unreserve the path of a ship, keeping of course the current tile and track reserved. + * @param ship The ship we want to free the path of. + * @param tile The tile that asks the path to be freed (see note 2). + * @param track The track that asks to be freed (see note 2). + * @param keep_pref_water_trackdirs Whether to keep initial reservation for consistence + * with preferred water trackdirs. + * @note 1.- The path will not be freed if the ship tile and track is + * the same as the tile and track that ask to free the path. + * @note 2.- If @param tile is INVALID_TILE, then the algorithm removes the full path + * the ship, keeping a consistent path with preferred trackdirs + * if @param keep_pref_water_trackdirs is true. + */ +void LiftShipPathReservation(Ship *v, TileIndex tile, Track track, bool keep_pref_water_trackdirs) +{ + assert(v != nullptr); + if (v->tile == tile && TrackdirToTrack(v->GetVehicleTrackdir()) == track) return; + + /* Do not disallow 90 deg turns as the setting might have changed between reserving and now. */ + CFollowTrackWater fs(v->owner); + + tile = v->tile; + Trackdir trackdir = v->GetVehicleTrackdir(); + + /* Skip first tile of a tunnel. */ + if (IsTileType(tile, MP_TUNNELBRIDGE) && GetTunnelBridgeDirection(tile) == TrackdirToExitdir(trackdir)) { + fs.Follow(tile, trackdir); + tile = fs.m_new_tile; + trackdir = FindFirstTrackdir(fs.m_new_td_bits & TrackBitsToTrackdirBits(GetReservedWaterTracks(tile))); + } + + bool check_first = true; + keep_pref_water_trackdirs &= HasPreferredWaterTrackdirs(v->tile) && + !HasTrackdir(GetPreferredWaterTrackdirs(v->tile), trackdir); + TileIndex keep_lock_reserved = v->tile; + + while (fs.Follow(tile, trackdir)) { + /* Skip 2nd tile of an aqueduct. */ + if (IsBridgeTile(tile) && IsBridgeTile(fs.m_new_tile) && + GetOtherTunnelBridgeEnd(fs.m_new_tile) == tile) { + tile = fs.m_new_tile; + continue; + } + + fs.m_new_td_bits &= TrackBitsToTrackdirBits(GetReservedWaterTracks(fs.m_new_tile)); + + /* Can't have more than one reserved trackdir */ + trackdir = FindFirstTrackdir(fs.m_new_td_bits); + if (trackdir == INVALID_TRACKDIR) break; + + tile = fs.m_new_tile; + + if (check_first) { + if (tile == v->dest_tile) return; + check_first = false; + } + + /* Skip tiles of the same lock. */ + if (CheckSameLock(keep_lock_reserved, tile)) continue; + + if (keep_pref_water_trackdirs) { + if (HasPreferredWaterTrackdirs(tile) && + !HasTrackdir(GetPreferredWaterTrackdirs(tile), trackdir)) continue; + + /* A path must keep all lock tiles reserved. */ + if (IsLockTile(tile)) keep_lock_reserved = tile; + + keep_pref_water_trackdirs = false; + continue; + } + + if (!SetWaterTrackReservation(tile, TrackdirToTrack(trackdir), false)) NOT_REACHED(); + } +} + +/** + * Free ship paths on a tile. + * @param tile Tile we want to free. + * @param keep_pref_water_trackdirs Whether keep preferred water trackdir paths if possible. + * @return True if tile has no reservation after the paths have been freed. + */ +bool LiftShipPathsReservations(TileIndex tile, bool keep_pref_water_trackdirs) +{ + if (!HasWaterTrackReservation(tile)) return true; + + Track track; + for (Track track : SetTrackBitIterator(GetReservedWaterTracks(tile))) { + Ship *s = GetShipForReservation(tile, track); + LiftShipPathReservation(s, tile, track, keep_pref_water_trackdirs); + } + + return !HasWaterTrackReservation(tile); +} + +/** + * Check whether a tile has some preference for water trackdirs. + * @param tile Tile to check. + * @return true if there is some preferred trackdir. + */ +bool HasPreferredWaterTrackdirs(TileIndex tile) +{ + assert(WaterTrackMayExist(tile)); + + switch (GetTileType(tile)) { + case MP_STATION: return GB(t.m7(), 7, 1); + case MP_TUNNELBRIDGE: return GB(_m[tile].m3, 7, 1); + default: return GB(_m[tile].m2, 15, 1); + } +} + +/** + * Get the preferred trackdirs for a water tile, if any is set. + * @param tile Tile to check. + * @return trackdir bits preferred on tile. + */ +TrackdirBits GetPreferredWaterTrackdirs(TileIndex tile) +{ + assert(WaterTrackMayExist(tile)); + + switch (GetTileType(tile)) { + case MP_RAILWAY: + return (HasBit(_m[tile].m2, 12) ? TRACKDIR_BIT_MASK_ES : TRACKDIR_BIT_NONE) | + (HasBit(_m[tile].m2, 13) ? TRACKDIR_BIT_MASK_WN : TRACKDIR_BIT_NONE); + case MP_WATER: + return (TrackdirBits)((GB(t.m6(), 2, 6) << 8) | + (GB(t.m6(), 0, 2) << 4) | GB(t.m7(), 4, 4)); + case MP_STATION: + if (IsBuoy(tile)) { + return (TrackdirBits)(GB(tile.m5(), 2, 6) << 8 | GB(tile.m5(), 0, 2) << 4 | + GB(t.m6(), 0, 3) << 1 | GB(t.m7(), 6, 1)); + } else { + assert(IsDock(tile)); + return (TrackdirBits)(GB(tile.m4, 2, 2) << 8 | GB(tile.m4, 0, 2)); + } + case MP_TUNNELBRIDGE: { + TrackdirBits trackdirs = TrackBitsToTrackdirBits(DiagDirToDiagTrackBits(GetTunnelBridgeDirection(tile))); + if (GB(_m[tile].m3, 5, 1) == 0) trackdirs &= TRACKDIR_BIT_MASK_WN; + if (GB(_m[tile].m3, 6, 1) == 0) trackdirs &= TRACKDIR_BIT_MASK_ES; + return trackdirs; + } + default: + NOT_REACHED(); + } +} + +/** + * Set some trackdir bits to a given value (1 preferred, 0 not preferred). + * @param tile Tile to modify. + * @param change_trackdirs TrackdirBits to modify. + * @param preference Value to set (1 preferred, 0 not preferred). + */ +void SetPreferredWaterTrackdirs(TileIndex tile, TrackdirBits change_trackdirs, bool preference) +{ + assert(WaterTrackMayExist(tile)); + TrackdirBits present_trackdirs = GetPreferredWaterTrackdirs(tile); + + if (preference == true) { + present_trackdirs |= change_trackdirs; + } else { + present_trackdirs &= ~change_trackdirs; + } + + // Save updated trackdirs. + switch (GetTileType(tile)) { + case MP_RAILWAY: + SB(_m[tile].m2, 12, 1, ((present_trackdirs & TRACKDIR_BIT_MASK_ES) != TRACKDIR_BIT_NONE)); + SB(_m[tile].m2, 13, 1, ((present_trackdirs & TRACKDIR_BIT_MASK_WN) != TRACKDIR_BIT_NONE)); + break; + case MP_WATER: + SB(t.m6(), 0, 2, present_trackdirs >> 4); + SB(t.m6(), 2, 6, present_trackdirs >> 8); + SB(t.m7(), 4, 4, present_trackdirs); + break; + case MP_STATION: + if (IsBuoy(tile)) { + SB(tile.m5(), 0, 2, present_trackdirs >> 4); + SB(tile.m5(), 2, 6, present_trackdirs >> 8); + SB(t.m6(), 0, 3, present_trackdirs >> 1); + SB(t.m7(), 6, 1, present_trackdirs); + } else { + assert(IsDock(tile)); + SB(tile.m4, 0, 2, present_trackdirs); + SB(tile.m4, 2, 2, present_trackdirs >> 8); + } + break; + case MP_TUNNELBRIDGE: + SB(_m[tile].m3, 5, 1, (present_trackdirs & TRACKDIR_BIT_MASK_ES) != TRACKDIR_BIT_NONE); + SB(_m[tile].m3, 6, 1, (present_trackdirs & TRACKDIR_BIT_MASK_WN) != TRACKDIR_BIT_NONE); + break; + default: + NOT_REACHED(); + } + + // Update whether tile has preferred water trackdirs. + switch (GetTileType(tile)) { + case MP_STATION: + SB(t.m7(), 7, 1, present_trackdirs != TRACKDIR_BIT_NONE); + return; + case MP_TUNNELBRIDGE: + SB(_m[tile].m3, 7, 1, present_trackdirs != TRACKDIR_BIT_NONE); + return; + default: + SB(_m[tile].m2, 15, 1, present_trackdirs != TRACKDIR_BIT_NONE); + return; + } +} + +/** + * It clears all preferences on water trackdirs and then establishes the ones + * specified in trackdirs. */ +void ClearAndSetPreferredWaterTrackdirs(TileIndex tile, TrackdirBits trackdirs) +{ + SetPreferredWaterTrackdirs(tile, TRACKDIR_BIT_MASK, false); + if (trackdirs != TRACKDIR_BIT_NONE) SetPreferredWaterTrackdirs(tile, trackdirs, true); +} diff --git a/src/platform.cpp b/src/platform.cpp new file mode 100644 index 0000000000000..55e0871d62e1b --- /dev/null +++ b/src/platform.cpp @@ -0,0 +1,406 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file platform.cpp Implementation of platform functions. */ + +#include "stdafx.h" +#include "station_map.h" +#include "platform_func.h" +#include "viewport_func.h" +#include "depot_base.h" +#include "vehicle_base.h" +#include "engine_base.h" + +/** + * Set the reservation for a complete station platform. + * @pre IsRailStationTile(start) + * @param start starting tile of the platform + * @param dir the direction in which to follow the platform + * @param b the state the reservation should be set to + */ +void SetRailStationPlatformReservation(TileIndex start, DiagDirection dir, bool b) +{ + TileIndex tile = start; + TileIndexDiff diff = TileOffsByDiagDir(dir); + + assert(IsRailStationTile(start)); + assert(GetRailStationAxis(start) == DiagDirToAxis(dir)); + + do { + SetRailStationReservation(tile, b); + MarkTileDirtyByTile(tile); + tile = TileAdd(tile, diff); + } while (IsCompatibleTrainStationTile(tile, start)); +} + + +/** + * Set the reservation for a complete depot platform. + * @pre IsExtendedRailDepotTile(start) + * @param start starting tile of the platform + * @param dir the direction in which to follow the platform + * @param b the state the reservation should be set to + */ +void SetRailDepotPlatformReservation(TileIndex start, DiagDirection dir, bool b) +{ + TileIndex tile = start; + TileIndexDiff diff = TileOffsByDiagDir(dir); + + assert(IsExtendedRailDepotTile(start)); + assert(GetRailDepotTrack(start) == DiagDirToDiagTrack(dir)); + + do { + SetDepotReservation(tile, b); + MarkTileDirtyByTile(tile); + tile = TileAdd(tile, diff); + } while (IsCompatibleTrainDepotTile(tile, start)); +} + +/** + * Set the reservation for a complete platform in a given direction. + * @param start starting tile of the platform + * @param dir the direction in which to follow the platform + * @param b the state the reservation should be set to + */ +void SetPlatformReservation(TileIndex start, DiagDirection dir, bool b) +{ + switch (GetPlatformType(start)) { + case PT_RAIL_STATION: + SetRailStationPlatformReservation(start, dir, b); + return; + case PT_RAIL_WAYPOINT: + SetRailStationReservation(start, b); + return; + case PT_RAIL_DEPOT: + SetRailDepotPlatformReservation(start, dir, b); + return; + default: NOT_REACHED(); + } +} + +/** + * Set the reservation for a complete platform. + * @param start A tile of the platform + * @param b the state the reservation should be set to + */ +void SetPlatformReservation(TileIndex start, bool b) +{ + DiagDirection dir; + switch (GetPlatformType(start)) { + case PT_RAIL_STATION: + NOT_REACHED(); + case PT_RAIL_WAYPOINT: + NOT_REACHED(); + case PT_RAIL_DEPOT: + assert(IsExtendedRailDepotTile(start)); + dir = GetRailDepotDirection(start); + SetRailDepotPlatformReservation(start, dir, b); + SetRailDepotPlatformReservation(start, ReverseDiagDir(dir), b); + return; + default: NOT_REACHED(); + } +} + +/** + * Get the length of a rail station platform. + * @pre IsRailStationTile(tile) + * @param tile Tile to check + * @return The length of the platform in tile length. + */ +uint GetRailStationPlatformLength(TileIndex tile) +{ + assert(IsRailStationTile(tile)); + + TileIndexDiff delta = (GetRailStationAxis(tile) == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1)); + + TileIndex t = tile; + uint len = 0; + do { + t -= delta; + len++; + } while (IsCompatibleTrainStationTile(t, tile)); + + t = tile; + do { + t += delta; + len++; + } while (IsCompatibleTrainStationTile(t, tile)); + + return len - 1; +} + +/** + * Get the length of a rail station platform in a given direction. + * @pre IsRailStationTile(tile) + * @param tile Tile to check + * @param dir Direction to check + * @return The length of the platform in tile length in the given direction. + */ +uint GetRailStationPlatformLength(TileIndex tile, DiagDirection dir) +{ + TileIndex start_tile = tile; + uint length = 0; + assert(IsRailStationTile(tile)); + assert(dir < DIAGDIR_END); + + do { + length++; + tile += TileOffsByDiagDir(dir); + } while (IsCompatibleTrainStationTile(tile, start_tile)); + + return length; +} + +/** + * Get the length of a rail depot platform. + * @pre IsDepotTypeTile(tile, TRANSPORT_RAIL) + * @param tile Tile to check + * @return The length of the platform in tile length. + */ +uint GetRailDepotPlatformLength(TileIndex tile) +{ + assert(IsExtendedRailDepotTile(tile)); + + TileIndexDiff delta = (GetRailDepotTrack(tile) == TRACK_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1)); + + TileIndex t = tile; + uint len = 0; + do { + t -= delta; + len++; + } while (IsCompatibleTrainDepotTile(t, tile)); + + t = tile; + do { + t += delta; + len++; + } while (IsCompatibleTrainDepotTile(t, tile)); + + return len - 1; +} + +/** + * Get the length of a road depot platform. + * @pre IsDepotTypeTile(tile, TRANSPORT_ROAD) + * @param tile Tile to check + * @param rtt Whether to check for road or tram type. + * @return The length of the platform in tile length. + */ +uint GetRoadDepotPlatformLength(TileIndex tile, RoadTramType rtt) +{ + assert(IsExtendedRoadDepotTile(tile)); + + DiagDirection dir = GetRoadDepotDirection(tile); + TileIndexDiff delta = TileOffsByDiagDir(dir); + + TileIndex t = tile; + uint len = 0; + do { + len++; + if ((GetRoadBits(t, rtt) & DiagDirToRoadBits(dir)) == ROAD_NONE) break; + t -= delta; + } while (IsCompatibleRoadDepotTile(t, tile, rtt)); + + t = tile; + dir = ReverseDiagDir(dir); + do { + len++; + if ((GetRoadBits(t, rtt) & DiagDirToRoadBits(dir)) == ROAD_NONE) break; + t += delta; + } while (IsCompatibleRoadDepotTile(t, tile, rtt)); + + return len - 1; +} + + +/** + * Get the length of a rail depot platform in a given direction. + * @pre IsRailDepotTile(tile) + * @param tile Tile to check + * @param dir Direction to check + * @return The length of the platform in tile length in the given direction. + */ +uint GetRailDepotPlatformLength(TileIndex tile, DiagDirection dir) +{ + TileIndex start_tile = tile; + uint length = 0; + assert(IsExtendedRailDepotTile(tile)); + assert(dir < DIAGDIR_END); + + do { + length++; + tile += TileOffsByDiagDir(dir); + } while (IsCompatibleTrainDepotTile(tile, start_tile)); + + return length; +} + +/** + * Get the length of a road depot platform in a given direction. + * @pre IsRoadDepotTile(tile) + * @param tile Tile to check + * @param dir Direction to check + * @param rtt Whether to check for road or tram type. + * @return The length of the platform in tile length in the given direction. + */ +uint GetRoadDepotPlatformLength(TileIndex tile, DiagDirection dir, RoadTramType rtt) +{ + TileIndex start_tile = tile; + uint length = 0; + assert(IsExtendedRoadDepotTile(tile)); + assert(dir < DIAGDIR_END); + + do { + length++; + if ((GetRoadBits(tile, rtt) & DiagDirToRoadBits(dir)) == ROAD_NONE) break; + tile += TileOffsByDiagDir(dir); + } while (IsCompatibleRoadDepotTile(tile, start_tile, rtt)); + + return length; +} + +/** + * Get the length of a platform. + * @param tile Tile to check + * @param rtt Whether to check for road or tram type (only for road transport). + * @return The length of the platform in tile length. + */ +uint GetPlatformLength(TileIndex tile, RoadTramType rtt) +{ + switch (GetPlatformType(tile)) { + case PT_RAIL_STATION: + return GetRailStationPlatformLength(tile); + case PT_RAIL_WAYPOINT: + return 1; + case PT_RAIL_DEPOT: + return GetRailDepotPlatformLength(tile); + case PT_ROAD_DEPOT: + return GetRoadDepotPlatformLength(tile, rtt); + default: NOT_REACHED(); + } +} + +/** + * Get the length of a rail depot platform in a given direction. + * @pre IsRailDepotTile(tile) + * @param tile Tile to check + * @param dir Direction to check + * @param rtt Whether to check for road or tram type (only for road transport). + * @return The length of the platform in tile length in the given direction. + */ +uint GetPlatformLength(TileIndex tile, DiagDirection dir, RoadTramType rtt) +{ + switch (GetPlatformType(tile)) { + case PT_RAIL_STATION: + return GetRailStationPlatformLength(tile, dir); + case PT_RAIL_WAYPOINT: + return 1; + case PT_RAIL_DEPOT: + return GetRailDepotPlatformLength(tile, dir); + case PT_ROAD_DEPOT: + return GetRoadDepotPlatformLength(tile, dir, rtt); + default: NOT_REACHED(); + } +} + +/** + * Get a tile where a rail station platform begins or ends. + * @pre IsRailStationTile(tile) + * @param tile Tile to check + * @param dir The diagonal direction to check + * @return The last tile of the platform seen from tile with direction dir. + */ +TileIndex GetRailStationExtreme(TileIndex tile, DiagDirection dir) +{ + assert(IsRailStationTile(tile)); + assert(GetRailStationAxis(tile) == DiagDirToAxis(dir)); + TileIndexDiff delta = TileOffsByDiagDir(dir); + + TileIndex t = tile; + do { + t -= delta; + } while (IsCompatibleTrainStationTile(t, tile)); + + return t + delta; +} + +/** + * Get a tile where a depot platform begins or ends. + * @pre IsExtendedDepotTile(tile) + * @param tile Tile to check + * @param dir The diagonal direction to check + * @return The last tile of the platform seen from tile with direction dir. + */ +TileIndex GetRailDepotExtreme(TileIndex tile, DiagDirection dir) +{ + assert(IsExtendedDepotTile(tile)); + assert(GetRailDepotTrack(tile) == DiagDirToDiagTrack(dir)); + TileIndexDiff delta = TileOffsByDiagDir(dir); + + TileIndex t = tile; + do { + t -= delta; + } while (IsCompatibleTrainDepotTile(t, tile)); + + return t + delta; +} + +/** + * Get a tile where a platform begins or ends. + * @param tile Tile to check + * @param dir Direction to check + * @return The last tile of the platform seen from tile with direction dir. + */ +TileIndex GetPlatformExtremeTile(TileIndex tile, DiagDirection dir) +{ + switch (GetPlatformType(tile)) { + case PT_RAIL_STATION: + return GetRailStationExtreme(tile, dir); + case PT_RAIL_WAYPOINT: + return tile; + case PT_RAIL_DEPOT: + return GetRailDepotExtreme(tile, dir); + default: NOT_REACHED(); + } +} + +/** + * Get the tiles belonging to a platform. + * @param tile Tile of a platform + * @return the tile area of the platform + */ +TileArea GetPlatformTileArea(TileIndex tile) +{ + switch (GetPlatformType(tile)) { + case PT_RAIL_STATION: { + assert(IsRailStationTile(tile)); + DiagDirection dir = AxisToDiagDir(GetRailStationAxis(tile)); + return TileArea(GetRailStationExtreme(tile, dir), GetRailStationExtreme(tile, ReverseDiagDir(dir))); + } + case PT_RAIL_WAYPOINT: + return TileArea(tile); + case PT_RAIL_DEPOT: { + assert(IsExtendedRailDepotTile(tile)); + DiagDirection dir = GetRailDepotDirection(tile); + return TileArea(GetRailDepotExtreme(tile, dir), GetRailDepotExtreme(tile, ReverseDiagDir(dir))); + } + default: NOT_REACHED(); + } +} + + +/** + * Check whether this tile is an extreme of a platform. + * @param tile Tile to check + * @return Whether the tile is the extreme of a platform. + */ +bool IsAnyStartPlatformTile(TileIndex tile) +{ + assert(IsExtendedRailDepotTile(tile)); + DiagDirection dir = GetRailDepotDirection(tile); + return tile == GetPlatformExtremeTile(tile, dir) || tile == GetPlatformExtremeTile(tile, ReverseDiagDir(dir)); +} diff --git a/src/platform_func.h b/src/platform_func.h new file mode 100644 index 0000000000000..baa59128741b8 --- /dev/null +++ b/src/platform_func.h @@ -0,0 +1,171 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file platform_func.h Functions related with platforms (tiles in a row that are connected somehow). */ + +#ifndef PLATFORM_FUNC_H +#define PLATFORM_FUNC_H + +#include "station_map.h" +#include "depot_map.h" +#include "platform_type.h" +#include "road_map.h" + +/** + * Check if a tile is a valid continuation to a railstation tile. + * The tile \a test_tile is a valid continuation to \a station_tile, if all of the following are true: + * \li \a test_tile is a rail station tile + * \li the railtype of \a test_tile is compatible with the railtype of \a station_tile + * \li the tracks on \a test_tile and \a station_tile are in the same direction + * \li both tiles belong to the same station + * \li \a test_tile is not blocked (@see IsStationTileBlocked) + * @param test_tile Tile to test + * @param station_tile Station tile to compare with + * @pre IsRailStationTile(station_tile) + * @return true if the two tiles are compatible + */ +static inline bool IsCompatibleTrainStationTile(TileIndex test_tile, TileIndex station_tile) +{ + assert(IsRailStationTile(station_tile)); + return IsRailStationTile(test_tile) && !IsStationTileBlocked(test_tile) && + IsCompatibleRail(GetRailType(test_tile), GetRailType(station_tile)) && + GetRailStationAxis(test_tile) == GetRailStationAxis(station_tile) && + GetStationIndex(test_tile) == GetStationIndex(station_tile); +} + +/** + * Check if a tile is a valid continuation to an extended rail depot tile. + * The tile \a test_tile is a valid continuation to \a depot_tile, if all of the following are true: + * \li \a test_tile is an extended depot tile + * \li \a test_tile and \a depot_tile have the same rail type + * \li the tracks on \a test_tile and \a depot_tile are in the same direction + * \li both tiles belong to the same depot + * @param test_tile Tile to test + * @param depot_tile Depot tile to compare with + * @pre IsExtendedRailDepotTile(depot_tile) + * @return true if the two tiles are compatible + */ +static inline bool IsCompatibleTrainDepotTile(TileIndex test_tile, TileIndex depot_tile) +{ + assert(IsExtendedRailDepotTile(depot_tile)); + return IsExtendedRailDepotTile(test_tile) && + GetRailType(test_tile) == GetRailType(depot_tile) && + GetRailDepotTrack(test_tile) == GetRailDepotTrack(depot_tile) && + GetDepotIndex(test_tile) == GetDepotIndex(depot_tile); +} + +/** + * Check if a tile is a valid continuation to an extended road depot tile. + * The tile \a test_tile is a valid continuation to \a depot_tile, if all of the following are true: + * \li \a test_tile is an extended depot tile + * \li \a test_tile and \a depot_tile have the same road type and appropriate road bits + * \li the tracks on \a test_tile and \a depot_tile are in the same direction + * \li both tiles belong to the same depot + * @param test_tile Tile to test + * @param depot_tile Depot tile to compare with + * @param rtt Whether road or tram type. + * @pre IsExtendedRoadDepotTile(depot_tile) + * @return true if the two tiles are compatible + */ +static inline bool IsCompatibleRoadDepotTile(TileIndex test_tile, TileIndex depot_tile, RoadTramType rtt) +{ + assert(IsExtendedRoadDepotTile(depot_tile)); + if (!IsExtendedRoadDepotTile(test_tile)) return false; + if (GetDepotIndex(test_tile) != GetDepotIndex(depot_tile)) return false; + if (GetRoadType(depot_tile, rtt) != GetRoadType(test_tile, rtt)) return false; + + DiagDirection dir = DiagdirBetweenTiles(test_tile, depot_tile); + assert(dir != INVALID_DIAGDIR); + return (GetRoadBits(test_tile, rtt) & DiagDirToRoadBits(dir)) != ROAD_NONE; +} + +/** + * Returns the type of platform of a given tile. + * @param tile Tile to check + * @return the type of platform (rail station, rail waypoint...) + */ +static inline PlatformType GetPlatformType(TileIndex tile) +{ + switch (GetTileType(tile)) { + case MP_STATION: + if (IsRailStation(tile)) return PT_RAIL_STATION; + if (IsRailWaypoint(tile)) return PT_RAIL_WAYPOINT; + break; + case MP_RAILWAY: + if (IsExtendedRailDepotTile(tile)) return PT_RAIL_DEPOT; + break; + case MP_ROAD: + if (IsExtendedRoadDepotTile(tile)) return PT_ROAD_DEPOT; + break; + default: break; + } + + return INVALID_PLATFORM_TYPE; +} + +/** + * Check whether a tile is a known platform type. + * @param tile to check + * @return whether the tile is a known platform type. + */ +static inline bool IsPlatformTile(TileIndex tile) +{ + return GetPlatformType(tile) != INVALID_PLATFORM_TYPE; +} + +/** + * Check whether a platform tile is reserved. + * @param tile to check + * @return whether the platform tile is reserved + */ +static inline bool HasPlatformReservation(TileIndex tile) +{ + switch(GetPlatformType(tile)) { + case PT_RAIL_STATION: + case PT_RAIL_WAYPOINT: + return HasStationReservation(tile); + case PT_RAIL_DEPOT: + return HasDepotReservation(tile); + default: NOT_REACHED(); + } +} + +/** + * Check whether two tiles are compatible platform tiles: they must have the same + * platform type and (depending on the platform type) its railtype or other specs. + * @param test_tile the tile to check + * @param orig_tile the tile with the platform type we are interested in + * @param rtt Whether to check road or tram types (only for road transport); + * @return whether the two tiles are compatible tiles for defining a platform + */ +static inline bool IsCompatiblePlatformTile(TileIndex test_tile, TileIndex orig_tile, RoadTramType rtt = RTT_ROAD) +{ + switch (GetPlatformType(orig_tile)) { + case PT_RAIL_STATION: + return IsCompatibleTrainStationTile(test_tile, orig_tile); + case PT_RAIL_WAYPOINT: + return test_tile == orig_tile; + case PT_RAIL_DEPOT: + return IsCompatibleTrainDepotTile(test_tile, orig_tile); + case PT_ROAD_DEPOT: + return IsCompatibleRoadDepotTile(test_tile, orig_tile, rtt); + default: NOT_REACHED(); + } +} + +void SetPlatformReservation(TileIndex start, DiagDirection dir, bool b); +void SetPlatformReservation(TileIndex start, bool b); + +uint GetPlatformLength(TileIndex tile, RoadTramType rtt = RTT_ROAD); +uint GetPlatformLength(TileIndex tile, DiagDirection dir, RoadTramType rtt = RTT_ROAD); + +TileIndex GetPlatformExtremeTile(TileIndex tile, DiagDirection dir); +TileArea GetPlatformTileArea(TileIndex tile); + +bool IsAnyStartPlatformTile(TileIndex tile); + +#endif /* PLATFORM_FUNC_H */ diff --git a/src/platform_type.h b/src/platform_type.h new file mode 100644 index 0000000000000..e6c543465a91d --- /dev/null +++ b/src/platform_type.h @@ -0,0 +1,24 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file platform_type.h Types related to platforms. */ + +#ifndef PLATFORM_TYPE_H +#define PLATFORM_TYPE_H + +#include "core/enum_type.hpp" + +enum PlatformType { + PT_RAIL_STATION, + PT_RAIL_WAYPOINT, + PT_RAIL_DEPOT, + PT_ROAD_DEPOT, + PT_END, + INVALID_PLATFORM_TYPE = PT_END, +}; + +#endif /* PLATFORM_TYPE_H */ diff --git a/src/rail.cpp b/src/rail.cpp index 290248ac88212..ec89178e8cdfb 100644 --- a/src/rail.cpp +++ b/src/rail.cpp @@ -73,6 +73,13 @@ extern const TrackBits _track_crosses_tracks[] = { TRACK_BIT_HORZ // TRACK_RIGHT }; +/* Maps a trackdir to the (4-way) direction the tile was entered from to follow + * that trackdir */ +extern const DiagDirection _trackdir_to_entrydir[TRACKDIR_END] = { + DIAGDIR_NE, DIAGDIR_SE, DIAGDIR_SE, DIAGDIR_NE, DIAGDIR_SE, DIAGDIR_SW, INVALID_DIAGDIR, INVALID_DIAGDIR, + DIAGDIR_SW, DIAGDIR_NW, DIAGDIR_SW, DIAGDIR_NW, DIAGDIR_NE, DIAGDIR_NW, +}; + /* Maps a trackdir to the (4-way) direction the tile is exited when following * that trackdir */ extern const DiagDirection _trackdir_to_exitdir[TRACKDIR_END] = { @@ -80,6 +87,12 @@ extern const DiagDirection _trackdir_to_exitdir[TRACKDIR_END] = { DIAGDIR_SW, DIAGDIR_NW, DIAGDIR_NW, DIAGDIR_SW, DIAGDIR_NW, DIAGDIR_NE, }; +/* Maps a trackdir to the corresponding 8-way direction */ +extern const Direction _trackdir_to_direction[TRACKDIR_END] = { + DIR_NE, DIR_SE, DIR_E, DIR_E, DIR_S, DIR_S, INVALID_DIR, INVALID_DIR, + DIR_SW, DIR_NW, DIR_W, DIR_W, DIR_N, DIR_N, INVALID_DIR, INVALID_DIR, +}; + extern const Trackdir _track_exitdir_to_trackdir[][DIAGDIR_END] = { {TRACKDIR_X_NE, INVALID_TRACKDIR, TRACKDIR_X_SW, INVALID_TRACKDIR}, {INVALID_TRACKDIR, TRACKDIR_Y_SE, INVALID_TRACKDIR, TRACKDIR_Y_NW}, diff --git a/src/rail.h b/src/rail.h index 149996aae0cdc..f15b9d67039ba 100644 --- a/src/rail.h +++ b/src/rail.h @@ -337,6 +337,19 @@ inline bool HasPowerOnRail(RailType enginetype, RailType tiletype) return HasBit(GetRailTypeInfo(enginetype)->powered_railtypes, tiletype); } +/** + * Checks if an engine with a given \a enginetype is powered on \a rail_types. + * This would normally just be an equality check, + * but for electric rails (which also support non-electric vehicles). + * @param enginetype The RailType of the engine we are considering. + * @param rail_types The RailTypes we are considering. + * @return Whether the engine got power on this tile. + */ +static inline bool HasPowerOnRails(RailType enginetype, RailTypes rail_types) +{ + return (GetRailTypeInfo(enginetype)->powered_railtypes & rail_types) != 0; +} + /** * Test if a RailType disallows build of level crossings. * @param rt The RailType to check. diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp index 0429661eda40c..a6e6949b74bfa 100644 --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -33,6 +33,7 @@ #include "object_map.h" #include "rail_cmd.h" #include "landscape_cmd.h" +#include "platform_func.h" #include "table/strings.h" #include "table/railtypes.h" @@ -814,7 +815,7 @@ static const TileIndexDiffC _trackdelta[] = { }; -static CommandCost ValidateAutoDrag(Trackdir *trackdir, TileIndex start, TileIndex end) +CommandCost ValidateAutoDrag(Trackdir *trackdir, TileIndex start, TileIndex end) { int x = TileX(start); int y = TileY(start); @@ -952,81 +953,163 @@ CommandCost CmdRemoveRailroadTrack(DoCommandFlag flags, TileIndex end_tile, Tile /** * Build a train depot * @param flags operation to perform - * @param tile position of the train depot + * @param tile first position of the train depot * @param railtype rail type * @param dir entrance direction + * @param adjacent allow adjacent depots + * @param extended build extended depots + * @param join_to depot to join to + * @param end_tile end tile of the area to be built * @return the cost of this operation or an error * * @todo When checking for the tile slope, * distinguish between "Flat land required" and "land sloped in wrong direction" */ -CommandCost CmdBuildTrainDepot(DoCommandFlag flags, TileIndex tile, RailType railtype, DiagDirection dir) +CommandCost CmdBuildTrainDepot(DoCommandFlag flags, TileIndex tile, RailType railtype, DiagDirection dir, bool adjacent, bool extended, DepotID join_to, TileIndex end_tile) { /* check railtype and valid direction for depot (0 through 3), 4 in total */ if (!ValParamRailType(railtype) || !IsValidDiagDirection(dir)) return CMD_ERROR; - Slope tileh = GetTileSlope(tile); + if (Company::IsValidHumanID(_current_company) && !HasBit(_settings_game.depot.rail_depot_types, extended)) return_cmd_error(STR_ERROR_DEPOT_TYPE_NOT_AVAILABLE); CommandCost cost(EXPENSES_CONSTRUCTION); + TileArea ta(tile, end_tile); + Depot *depot = nullptr; + + /* Create a new depot or find a depot to join to. */ + CommandCost ret = FindJoiningDepot(ta, VEH_TRAIN, join_to, depot, adjacent, flags); + if (ret.Failed()) return ret; + + Axis axis = DiagDirToAxis(dir); + /* Do not allow extending already occupied platforms. */ + if (extended && join_to != NEW_DEPOT) { + TileArea ta_ext = TileArea(ta.tile, ta.w, ta.h).Expand(1); + + uint max_coord; + uint min_coord; + if (axis == AXIS_X) { + min_coord = TileY(ta.tile); + max_coord = min_coord + ta.h; + } else { + min_coord = TileX(ta.tile); + max_coord = min_coord + ta.w; + } + + for (Tile t : ta_ext) { + if (!IsExtendedRailDepotTile(t)) continue; + if (GetDepotIndex(t) != depot->index) continue; + if (GetRailType(t) != railtype) continue; + if (!HasDepotReservation(t)) continue; + if (DiagDirToAxis(GetRailDepotDirection(t)) != axis) continue; + uint current = (axis == AXIS_X) ? TileY(t) : TileX(t); + if (!IsInsideMM(current, min_coord, max_coord)) continue; + return_cmd_error(STR_ERROR_DEPOT_EXTENDING_PLATFORMS); + } + } + + uint8_t num_new_depot_tiles = 0; + uint8_t num_overbuilt_depot_tiles = 0; /* Prohibit construction if * The tile is non-flat AND * 1) build-on-slopes is disabled * 2) the tile is steep i.e. spans two height levels * 3) the exit points in the wrong direction + * 4) the tile is not an already built depot (or it is a compatible single rail tile for building extended depots) */ + for (Tile t : ta) { + if (IsBridgeAbove(t)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); + + Slope tileh = GetTileSlope(t); + if (tileh != SLOPE_FLAT) { + if (!_settings_game.construction.build_on_slopes || + !CanBuildDepotByTileh(dir, tileh)) { + return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); + } + if (extended && !CanBuildDepotByTileh(ReverseDiagDir(dir), tileh)) { + return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); + } + cost.AddCost(_price[PR_BUILD_FOUNDATION]); + } + + if (extended) { + if (IsPlainRailTile(t) && !HasSignals(t) && GetRailType(t) == railtype) { + /* Allow overbuilding if the tile: + * - has rail, but no signals + * - it has exactly one track + * - the track is in line with the depot + * - the current rail type is the same as the to-be-built + */ + TrackBits tracks = GetTrackBits(t); + Track track = RemoveFirstTrack(&tracks); + uint invalid_dirs = 5 << DiagDirToAxis(dir); + Track expected_track = HasBit(invalid_dirs, DIAGDIR_NE) ? TRACK_X : TRACK_Y; + + if (tracks == TRACK_BIT_NONE && track == expected_track) { + cost.AddCost(Command::Do(flags, t, track).GetCost()); + /* With flags & ~DC_EXEC CmdLandscapeClear would fail since the rail still exists */ + if (cost.Failed()) return cost; + goto new_depot_tile; + } + } - if (tileh != SLOPE_FLAT) { - if (!_settings_game.construction.build_on_slopes || !CanBuildDepotByTileh(dir, tileh)) { - return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); - } - cost.AddCost(_price[PR_BUILD_FOUNDATION]); - } - - /* Allow the user to rotate the depot instead of having to destroy it and build it again */ - bool rotate_existing_depot = false; - if (IsRailDepotTile(tile) && railtype == GetRailType(tile)) { - CommandCost ret = CheckTileOwnership(tile); - if (ret.Failed()) return ret; - - if (dir == GetRailDepotDirection(tile)) return CommandCost(); + /* Skip already existing and compatible extended depots. */ + if (IsRailDepotTile(t) && IsExtendedRailDepotTile(t) && + GetDepotIndex(t) == join_to && railtype == GetRailType(t)) { + if (axis == DiagDirToAxis(GetRailDepotDirection(t))) continue; + } + } else { + /* Check whether this is a standard depot tile and it needs to be rotated. */ + if (IsRailDepotTile(t) && IsStandardRailDepotTile(t) && + GetDepotIndex(t) == join_to && railtype == GetRailType(t)) { + if (dir == GetRailDepotDirection(t)) continue; - ret = EnsureNoVehicleOnGround(tile); - if (ret.Failed()) return ret; + ret = EnsureNoVehicleOnGround(t); + if (ret.Failed()) return ret; - rotate_existing_depot = true; - } + num_overbuilt_depot_tiles++; + if (flags & DC_EXEC) { + SetRailDepotExitDirection(t, dir); + AddSideToSignalBuffer(t, INVALID_DIAGDIR, _current_company); + YapfNotifyTrackLayoutChange(t, DiagDirToDiagTrack(dir)); + MarkTileDirtyByTile(t); + } + continue; + } + } - if (!rotate_existing_depot) { - cost.AddCost(Command::Do(flags, tile)); + cost.AddCost(Command::Do(flags, t)); if (cost.Failed()) return cost; - if (IsBridgeAbove(tile)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); +new_depot_tile: + num_new_depot_tiles++; - if (!Depot::CanAllocateItem()) return CMD_ERROR; - } + if (flags & DC_EXEC) { + MakeRailDepot(t, _current_company, depot->index, dir, railtype); + SB(t.m5(), 5, 1, extended); - if (flags & DC_EXEC) { - if (rotate_existing_depot) { - SetRailDepotExitDirection(tile, dir); - } else { - Depot *d = new Depot(tile); - d->build_date = TimerGameCalendar::date; + if (extended) { + AddTrackToSignalBuffer(t, DiagDirToDiagTrack(dir), _current_company); + } else { + AddSideToSignalBuffer(t, INVALID_DIAGDIR, _current_company); + } + YapfNotifyTrackLayoutChange(t, DiagDirToDiagTrack(dir)); + MarkTileDirtyByTile(t); + } + } - MakeRailDepot(tile, _current_company, d->index, dir, railtype); - MakeDefaultName(d); + if (num_new_depot_tiles + num_overbuilt_depot_tiles == 0) return CommandCost(); - Company::Get(_current_company)->infrastructure.rail[railtype]++; - DirtyCompanyInfrastructureWindows(_current_company); - } + cost.AddCost(_price[PR_BUILD_DEPOT_TRAIN] * (num_new_depot_tiles + num_overbuilt_depot_tiles)); + cost.AddCost(RailBuildCost(railtype) * (num_new_depot_tiles + num_overbuilt_depot_tiles)); - MarkTileDirtyByTile(tile); - AddSideToSignalBuffer(tile, INVALID_DIAGDIR, _current_company); - YapfNotifyTrackLayoutChange(tile, DiagDirToDiagTrack(dir)); + if (flags & DC_EXEC) { + Company::Get(_current_company)->infrastructure.rail[railtype] += num_new_depot_tiles; + DirtyCompanyInfrastructureWindows(_current_company); + depot->AfterAddRemove(ta, true); + if (join_to == NEW_DEPOT) MakeDefaultName(depot); } - cost.AddCost(_price[PR_BUILD_DEPOT_TRAIN]); - cost.AddCost(RailBuildCost(railtype)); return cost; } @@ -1540,6 +1623,56 @@ static Vehicle *UpdateTrainPowerProc(Vehicle *v, void *data) return nullptr; } +/** + * Returns whether a depot has an extended depot + * tile which is reserved. + * @param Depot pointer to a depot + * @return true iff \a dep has an extended depot tile reserved. + */ +bool HasAnyExtendedDepotReservedTile(Depot *dep) +{ + assert(dep != nullptr); + for (TileIndex tile : dep->ta) { + if (!IsExtendedDepotTile(tile)) continue; + if (GetDepotIndex(tile) != dep->index) continue; + if (HasDepotReservation(tile)) return true; + } + + return false; +} + +CommandCost ConvertExtendedDepot(DoCommandFlag flags, Depot *dep, RailType rail_type) +{ + CommandCost cost(EXPENSES_CONSTRUCTION); + assert(dep->owner == _current_company); + Company *c = Company::Get(dep->owner); + + for (TileIndex tile : dep->ta) { + if (!IsDepotTile(tile)) continue; + if (GetDepotIndex(tile) != dep->index) continue; + assert(!HasDepotReservation(tile)); + assert(dep->owner == GetTileOwner(tile)); + + /* Original railtype we are converting from */ + RailType type = GetRailType(tile); + + if (type == rail_type || (_settings_game.vehicle.disable_elrails && rail_type == RAILTYPE_RAIL && type == RAILTYPE_ELECTRIC)) continue; + + cost.AddCost(RailConvertCost(type, rail_type)); + + if (flags & DC_EXEC) { + c->infrastructure.rail[type]--; + c->infrastructure.rail[rail_type]++; + SetRailType(tile, rail_type); + MarkTileDirtyByTile(tile); + YapfNotifyTrackLayoutChange(tile, GetRailDepotTrack(tile)); + DirtyCompanyInfrastructureWindows(c->index); + } + } + + return cost; +} + /** * Convert one rail type to the other. You can convert normal rail to * monorail/maglev easily or vice-versa. @@ -1558,6 +1691,7 @@ CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_s if (area_start >= Map::Size()) return CMD_ERROR; TrainList affected_trains; + std::vector affected_depots; CommandCost cost(EXPENSES_CONSTRUCTION); CommandCost error = CommandCost(STR_ERROR_NO_SUITABLE_RAILROAD_TRACK); // by default, there is no track to convert. @@ -1647,31 +1781,27 @@ CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_s switch (tt) { case MP_RAILWAY: - switch (GetRailTileType(tile)) { - case RAIL_TILE_DEPOT: - if (flags & DC_EXEC) { - /* notify YAPF about the track layout change */ - YapfNotifyTrackLayoutChange(tile, GetRailDepotTrack(tile)); - - /* Update build vehicle window related to this depot */ - InvalidateWindowData(WC_VEHICLE_DEPOT, tile); - InvalidateWindowData(WC_BUILD_VEHICLE, tile); - } - found_convertible_track = true; - cost.AddCost(RailConvertCost(type, totype)); - break; + found_convertible_track = true; + if (GetRailTileType(tile) == RAIL_TILE_DEPOT) { + if (flags & DC_EXEC) { + /* notify YAPF about the track layout change */ + YapfNotifyTrackLayoutChange(tile, GetRailDepotTrack(tile)); + } + + if (find(affected_depots.begin(), affected_depots.end(), (tile)) == affected_depots.end()) { + affected_depots.push_back(GetDepotIndex(tile)); + } - default: // RAIL_TILE_NORMAL, RAIL_TILE_SIGNALS - if (flags & DC_EXEC) { - /* notify YAPF about the track layout change */ - TrackBits tracks = GetTrackBits(tile); - while (tracks != TRACK_BIT_NONE) { - YapfNotifyTrackLayoutChange(tile, RemoveFirstTrack(&tracks)); - } + cost.AddCost(RailConvertCost(type, totype)); + } else { // RAIL_TILE_NORMAL, RAIL_TILE_SIGNALS + if (flags & DC_EXEC) { + /* notify YAPF about the track layout change */ + TrackBits tracks = GetTrackBits(tile); + while (tracks != TRACK_BIT_NONE) { + YapfNotifyTrackLayoutChange(tile, RemoveFirstTrack(&tracks)); } - found_convertible_track = true; - cost.AddCost(RailConvertCost(type, totype) * CountBits(GetTrackBits(tile))); - break; + } + cost.AddCost(RailConvertCost(type, totype) * CountBits(GetTrackBits(tile))); } break; @@ -1753,6 +1883,17 @@ CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_s } } + /* Update affected depots. */ + for (auto &depot_tile : affected_depots) { + Depot *dep = Depot::Get(depot_tile); + if (HasAnyExtendedDepotReservedTile(dep)) cost.MakeError(STR_ERROR_DEPOT_EXTENDED_RAIL_DEPOT_IS_NOT_FREE); + + if (flags & DC_EXEC) { + dep->RescanDepotTiles(); + InvalidateWindowData(WC_VEHICLE_DEPOT, dep->index); + } + } + if (flags & DC_EXEC) { /* Railtype changed, update trains as when entering different track */ for (Train *v : affected_trains) { @@ -1763,8 +1904,10 @@ CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_s return found_convertible_track ? cost : error; } -static CommandCost RemoveTrainDepot(TileIndex tile, DoCommandFlag flags) +static CommandCost RemoveTrainDepot(TileIndex tile, DoCommandFlag flags, bool keep_rail) { + assert(IsRailDepotTile(tile)); + if (_current_company != OWNER_WATER) { CommandCost ret = CheckTileOwnership(tile); if (ret.Failed()) return ret; @@ -1773,10 +1916,21 @@ static CommandCost RemoveTrainDepot(TileIndex tile, DoCommandFlag flags) CommandCost ret = EnsureNoVehicleOnGround(tile); if (ret.Failed()) return ret; + if (HasDepotReservation(tile)) return CMD_ERROR; + + CommandCost total_cost(EXPENSES_CONSTRUCTION); + + if (keep_rail) { + /* Don't refund the 'steel' of the track when we keep the rail. */ + total_cost.AddCost(-_price[PR_CLEAR_RAIL]); + } + if (flags & DC_EXEC) { - /* read variables before the depot is removed */ + Depot *depot = Depot::GetByTile(tile); + Company *c = Company::GetIfValid(depot->owner); + assert(c != nullptr); + DiagDirection dir = GetRailDepotDirection(tile); - Owner owner = GetTileOwner(tile); Train *v = nullptr; if (HasDepotReservation(tile)) { @@ -1784,17 +1938,57 @@ static CommandCost RemoveTrainDepot(TileIndex tile, DoCommandFlag flags) if (v != nullptr) FreeTrainTrackReservation(v); } - Company::Get(owner)->infrastructure.rail[GetRailType(tile)]--; - DirtyCompanyInfrastructureWindows(owner); + Track track = GetRailDepotTrack(tile); + RailType rt = GetRailType(tile); + bool is_extended_depot = IsExtendedDepot(tile); - delete Depot::GetByTile(tile); DoClearSquare(tile); - AddSideToSignalBuffer(tile, dir, owner); + + if (keep_rail) { + MakeRailNormal(tile, depot->owner, TrackToTrackBits(track), rt); + } else { + c->infrastructure.rail[GetRailType(tile)]--; + DirtyCompanyInfrastructureWindows(c->index); + } + + if (is_extended_depot) { + AddTrackToSignalBuffer(tile, DiagDirToDiagTrack(dir), c->index); + } else { + AddSideToSignalBuffer(tile, dir, c->index); + } + YapfNotifyTrackLayoutChange(tile, DiagDirToDiagTrack(dir)); if (v != nullptr) TryPathReserve(v, true); + + depot->AfterAddRemove(TileArea(tile), false); } - return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_TRAIN]); + total_cost.AddCost(_price[PR_CLEAR_DEPOT_TRAIN]); + return total_cost; +} + +/** + * Remove train depots from an area + * @param flags operation to perform + * @param start_tile start tile of the area + * @param end_tile end tile of the area + * @return the cost of this operation or an error + */ +CommandCost CmdRemoveTrainDepot(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile) +{ + assert(IsValidTile(start_tile)); + assert(IsValidTile(end_tile)); + + CommandCost cost(EXPENSES_CONSTRUCTION); + TileArea ta(start_tile, end_tile); + for (TileIndex t : ta) { + if (!IsRailDepotTile(t)) continue; + CommandCost ret = RemoveTrainDepot(t, flags, IsExtendedDepot(t)); + if (ret.Failed()) return ret; + cost.AddCost(ret); + } + + return cost; } static CommandCost ClearTile_Track(TileIndex tile, DoCommandFlag flags) @@ -1845,7 +2039,7 @@ static CommandCost ClearTile_Track(TileIndex tile, DoCommandFlag flags) } case RAIL_TILE_DEPOT: - return RemoveTrainDepot(tile, flags); + return RemoveTrainDepot(tile, flags, false); default: return CMD_ERROR; @@ -2072,7 +2266,12 @@ static inline void DrawTrackSprite(SpriteID sprite, PaletteID pal, const TileInf static void DrawTrackBitsOverlay(TileInfo *ti, TrackBits track, const RailTypeInfo *rti) { RailGroundType rgt = GetRailGroundType(ti->tile); - Foundation f = GetRailFoundation(ti->tileh, track); + Foundation f = FOUNDATION_NONE; + if (IsRailDepot(ti->tile)) { + if (ti->tileh != SLOPE_FLAT) f = FOUNDATION_LEVELED; + } else { + f = GetRailFoundation(ti->tileh, track); + } Corner halftile_corner = CORNER_INVALID; if (IsNonContinuousFoundation(f)) { @@ -2112,7 +2311,18 @@ static void DrawTrackBitsOverlay(TileInfo *ti, TrackBits track, const RailTypeIn bool no_combine = ti->tileh == SLOPE_FLAT && HasBit(rti->flags, RTF_NO_SPRITE_COMBINE); SpriteID overlay = GetCustomRailSprite(rti, ti->tile, RTSG_OVERLAY); SpriteID ground = GetCustomRailSprite(rti, ti->tile, no_combine ? RTSG_GROUND_COMPLETE : RTSG_GROUND); - TrackBits pbs = _settings_client.gui.show_track_reservation ? GetRailReservationTrackBits(ti->tile) : TRACK_BIT_NONE; + + TrackBits pbs = TRACK_BIT_NONE; + if (_settings_client.gui.show_track_reservation) { + if (IsPlainRail(ti->tile)) { + pbs = GetRailReservationTrackBits(ti->tile); + } else { + assert(IsRailDepot(ti->tile)); + if (HasDepotReservation(ti->tile)) { + pbs = track; + } + } + } if (track == TRACK_BIT_NONE) { /* Half-tile foundation, no track here? */ @@ -2342,7 +2552,14 @@ static void DrawTrackBits(TileInfo *ti, TrackBits track) /* PBS debugging, draw reserved tracks darker */ if (_game_mode != GM_MENU && _settings_client.gui.show_track_reservation) { /* Get reservation, but mask track on halftile slope */ - TrackBits pbs = GetRailReservationTrackBits(ti->tile) & track; + TrackBits pbs = TRACK_BIT_NONE; + if (IsPlainRail(ti->tile)) { + pbs = GetRailReservationTrackBits(ti->tile) & track; + } else { + assert(IsRailDepot(ti->tile)); + if (HasDepotReservation(ti->tile)) pbs = track; + } + if (pbs & TRACK_BIT_X) { if (ti->tileh == SLOPE_FLAT || ti->tileh == SLOPE_ELEVATED) { DrawGroundSprite(rti->base_sprites.single_x, PALETTE_CRASH); @@ -2425,124 +2642,40 @@ static void DrawTile_Track(TileInfo *ti) _drawtile_track_palette = COMPANY_SPRITE_COLOUR(GetTileOwner(ti->tile)); + TrackBits rails = TRACK_BIT_NONE; if (IsPlainRail(ti->tile)) { - TrackBits rails = GetTrackBits(ti->tile); - - DrawTrackBits(ti, rails); - - if (HasBit(_display_opt, DO_FULL_DETAIL)) DrawTrackDetails(ti, rti); - - if (HasRailCatenaryDrawn(GetRailType(ti->tile))) DrawRailCatenary(ti); - - if (HasSignals(ti->tile)) DrawSignals(ti->tile, rails, rti); + rails = GetTrackBits(ti->tile); } else { - /* draw depot */ - const DrawTileSprites *dts; - PaletteID pal = PAL_NONE; - SpriteID relocation; - - if (ti->tileh != SLOPE_FLAT) DrawFoundation(ti, FOUNDATION_LEVELED); - - if (IsInvisibilitySet(TO_BUILDINGS)) { - /* Draw rail instead of depot */ - dts = &_depot_invisible_gfx_table[GetRailDepotDirection(ti->tile)]; - } else { - dts = &_depot_gfx_table[GetRailDepotDirection(ti->tile)]; - } - - SpriteID image; - if (rti->UsesOverlay()) { - image = SPR_FLAT_GRASS_TILE; - } else { - image = dts->ground.sprite; - if (image != SPR_FLAT_GRASS_TILE) image += rti->GetRailtypeSpriteOffset(); - } - - /* Adjust ground tile for desert and snow. */ - if (IsSnowRailGround(ti->tile)) { - if (image != SPR_FLAT_GRASS_TILE) { - image += rti->snow_offset; // tile with tracks - } else { - image = SPR_FLAT_SNOW_DESERT_TILE; // flat ground - } + assert(IsRailDepot(ti->tile)); + DiagDirection dir = GetRailDepotDirection(ti->tile); + if (IsDiagDirFacingSouth(dir) || IsTransparencySet(TO_BUILDINGS)) { + rails = TrackToTrackBits(GetRailDepotTrack(ti->tile)); } + } - DrawGroundSprite(image, GroundSpritePaletteTransform(image, pal, _drawtile_track_palette)); - - if (rti->UsesOverlay()) { - SpriteID ground = GetCustomRailSprite(rti, ti->tile, RTSG_GROUND); + DrawTrackBits(ti, rails); - switch (GetRailDepotDirection(ti->tile)) { - case DIAGDIR_NE: - if (!IsInvisibilitySet(TO_BUILDINGS)) break; - [[fallthrough]]; - case DIAGDIR_SW: - DrawGroundSprite(ground + RTO_X, PAL_NONE); - break; - case DIAGDIR_NW: - if (!IsInvisibilitySet(TO_BUILDINGS)) break; - [[fallthrough]]; - case DIAGDIR_SE: - DrawGroundSprite(ground + RTO_Y, PAL_NONE); - break; - default: - break; - } + if (IsPlainRail(ti->tile) && HasBit(_display_opt, DO_FULL_DETAIL)) DrawTrackDetails(ti, rti); - if (_settings_client.gui.show_track_reservation && HasDepotReservation(ti->tile)) { - SpriteID overlay = GetCustomRailSprite(rti, ti->tile, RTSG_OVERLAY); + if (HasRailCatenaryDrawn(GetRailType(ti->tile))) DrawRailCatenary(ti); - switch (GetRailDepotDirection(ti->tile)) { - case DIAGDIR_NE: - if (!IsInvisibilitySet(TO_BUILDINGS)) break; - [[fallthrough]]; - case DIAGDIR_SW: - DrawGroundSprite(overlay + RTO_X, PALETTE_CRASH); - break; - case DIAGDIR_NW: - if (!IsInvisibilitySet(TO_BUILDINGS)) break; - [[fallthrough]]; - case DIAGDIR_SE: - DrawGroundSprite(overlay + RTO_Y, PALETTE_CRASH); - break; - default: - break; - } - } - } else { - /* PBS debugging, draw reserved tracks darker */ - if (_game_mode != GM_MENU && _settings_client.gui.show_track_reservation && HasDepotReservation(ti->tile)) { - switch (GetRailDepotDirection(ti->tile)) { - case DIAGDIR_NE: - if (!IsInvisibilitySet(TO_BUILDINGS)) break; - [[fallthrough]]; - case DIAGDIR_SW: - DrawGroundSprite(rti->base_sprites.single_x, PALETTE_CRASH); - break; - case DIAGDIR_NW: - if (!IsInvisibilitySet(TO_BUILDINGS)) break; - [[fallthrough]]; - case DIAGDIR_SE: - DrawGroundSprite(rti->base_sprites.single_y, PALETTE_CRASH); - break; - default: - break; - } - } - } + if (IsRailDepot(ti->tile) && !IsInvisibilitySet(TO_BUILDINGS)) { + /* draw depot */ + const DrawTileSprites *dts = &_depot_gfx_table[GetRailDepotDirection(ti->tile)]; int depot_sprite = GetCustomRailSprite(rti, ti->tile, RTSG_DEPOT); - relocation = depot_sprite != 0 ? depot_sprite - SPR_RAIL_DEPOT_SE_1 : rti->GetRailtypeSpriteOffset(); - - if (HasRailCatenaryDrawn(GetRailType(ti->tile))) DrawRailCatenary(ti); + SpriteID relocation = depot_sprite != 0 ? depot_sprite - SPR_RAIL_DEPOT_SE_1 : rti->GetRailtypeSpriteOffset(); DrawRailTileSeq(ti, dts, TO_BUILDINGS, relocation, 0, _drawtile_track_palette); } + + if (HasSignals(ti->tile)) DrawSignals(ti->tile, rails, rti); + DrawBridgeMiddle(ti); } void DrawTrainDepotSprite(int x, int y, int dir, RailType railtype) { - const DrawTileSprites *dts = &_depot_gfx_table[dir]; + const DrawTileSprites *dts = &_depot_gfx_gui_table[dir]; const RailTypeInfo *rti = GetRailTypeInfo(railtype); SpriteID image = rti->UsesOverlay() ? SPR_FLAT_GRASS_TILE : dts->ground.sprite; uint32_t offset = rti->GetRailtypeSpriteOffset(); @@ -2758,6 +2891,13 @@ static TrackStatus GetTileTrackStatus_Track(TileIndex tile, TransportType mode, } case RAIL_TILE_DEPOT: { + if (IsExtendedRailDepot(tile)) { + Track track = GetRailDepotTrack(tile); + trackbits = TrackToTrackBits(track); + break; + } + + /* Small depot. */ DiagDirection dir = GetRailDepotDirection(tile); if (side != INVALID_DIAGDIR && side != dir) break; @@ -2774,7 +2914,7 @@ static bool ClickTile_Track(TileIndex tile) { if (!IsRailDepot(tile)) return false; - ShowDepotWindow(tile, VEH_TRAIN); + ShowDepotWindow(GetDepotIndex(tile)); return true; } @@ -2855,7 +2995,7 @@ static void GetTileDesc_Track(TileIndex tile, TileDesc *td) } case RAIL_TILE_DEPOT: - td->str = STR_LAI_RAIL_DESCRIPTION_TRAIN_DEPOT; + td->str = IsExtendedDepot(tile) ? STR_LAI_RAIL_DESCRIPTION_TRAIN_DEPOT_EXTENDED : STR_LAI_RAIL_DESCRIPTION_TRAIN_DEPOT; if (_settings_game.vehicle.train_acceleration_model != AM_ORIGINAL) { if (td->rail_speed > 0) { td->rail_speed = std::min(td->rail_speed, 61); @@ -2915,6 +3055,7 @@ static const int8_t _deltacoord_leaveoffset[8] = { */ int TicksToLeaveDepot(const Train *v) { + assert(IsStandardRailDepotTile(v->tile)); DiagDirection dir = GetRailDepotDirection(v->tile); int length = v->CalcNextVehicleOffset(); @@ -2936,6 +3077,38 @@ static VehicleEnterTileStatus VehicleEnter_Track(Vehicle *u, TileIndex tile, int /* This routine applies only to trains in depot tiles. */ if (u->type != VEH_TRAIN || !IsRailDepotTile(tile)) return VETSB_CONTINUE; + Train *v = Train::From(u); + + if (IsExtendedRailDepot(tile)) { + DepotID depot_id = GetDepotIndex(tile); + if (!v->current_order.ShouldStopAtDepot(depot_id)) return VETSB_CONTINUE; + + /* Stop position on platform is half the front vehicle length of the train. */ + int stop_pos = v->gcache.cached_veh_length / 2; + + int depot_ahead = (GetPlatformLength(tile, DirToDiagDir(v->direction)) - 1) * TILE_SIZE; + if (depot_ahead > stop_pos) return VETSB_CONTINUE; + + DiagDirection dir = DirToDiagDir(v->direction); + + x &= 0xF; + y &= 0xF; + + if (DiagDirToAxis(dir) != AXIS_X) Swap(x, y); + if (y == TILE_SIZE / 2) { + if (dir == DIAGDIR_SE || dir == DIAGDIR_SW) x = TILE_SIZE - 1 - x; + + if (stop_pos == x) { + return VETSB_ENTERED_DEPOT_PLATFORM; + } else if (stop_pos < x) { + v->vehstatus |= VS_TRAIN_SLOWING; + uint16_t spd = std::max(0, stop_pos * 20 - 15); + if (spd < v->cur_speed) v->cur_speed = spd; + } + } + return VETSB_CONTINUE; + } + /* Depot direction. */ DiagDirection dir = GetRailDepotDirection(tile); @@ -2944,8 +3117,6 @@ static VehicleEnterTileStatus VehicleEnter_Track(Vehicle *u, TileIndex tile, int /* Make sure a train is not entering the tile from behind. */ if (_fractcoords_behind[dir] == fract_coord) return VETSB_CANNOT_ENTER; - Train *v = Train::From(u); - /* Leaving depot? */ if (v->direction == DiagDirToDir(dir)) { /* Calculate the point where the following wagon should be activated. */ @@ -2970,10 +3141,10 @@ static VehicleEnterTileStatus VehicleEnter_Track(Vehicle *u, TileIndex tile, int v->track = TRACK_BIT_DEPOT, v->vehstatus |= VS_HIDDEN; v->direction = ReverseDir(v->direction); - if (v->Next() == nullptr) VehicleEnterDepot(v->First()); + if (v->Next() == nullptr) HandleTrainEnterDepot(v->First()); v->tile = tile; - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); return VETSB_ENTERED_WORMHOLE; } @@ -3080,6 +3251,14 @@ static CommandCost TerraformTile_Track(TileIndex tile, DoCommandFlag flags, int return CommandCost(EXPENSES_CONSTRUCTION, was_water ? _price[PR_CLEAR_WATER] : (Money)0); } else if (_settings_game.construction.build_on_slopes && AutoslopeEnabled() && AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, GetRailDepotDirection(tile))) { + if (IsExtendedRailDepotTile(tile) && GetTileMaxZ(tile) == z_new + GetSlopeMaxZ(tileh_new)) { + DiagDirection direction = GetRailDepotDirection(tile); + if (!AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, direction) || + !AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, ReverseDiagDir(direction))) { + return Command::Do(flags, tile); + } + } + return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]); } return Command::Do(flags, tile); diff --git a/src/rail_cmd.h b/src/rail_cmd.h index b9f6c0cc034a2..bcd28fa2e3f2d 100644 --- a/src/rail_cmd.h +++ b/src/rail_cmd.h @@ -19,7 +19,8 @@ CommandCost CmdBuildRailroadTrack(DoCommandFlag flags, TileIndex end_tile, TileI CommandCost CmdRemoveRailroadTrack(DoCommandFlag flags, TileIndex end_tile, TileIndex start_tile, Track track); CommandCost CmdBuildSingleRail(DoCommandFlag flags, TileIndex tile, RailType railtype, Track track, bool auto_remove_signals); CommandCost CmdRemoveSingleRail(DoCommandFlag flags, TileIndex tile, Track track); -CommandCost CmdBuildTrainDepot(DoCommandFlag flags, TileIndex tile, RailType railtype, DiagDirection dir); +CommandCost CmdBuildTrainDepot(DoCommandFlag flags, TileIndex tile, RailType railtype, DiagDirection dir, bool adjacent, bool extended, DepotID depot_id, TileIndex end_tile); +CommandCost CmdRemoveTrainDepot(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile); CommandCost CmdBuildSingleSignal(DoCommandFlag flags, TileIndex tile, Track track, SignalType sigtype, SignalVariant sigvar, bool convert_signal, bool skip_existing_signals, bool ctrl_pressed, SignalType cycle_start, SignalType cycle_stop, uint8_t num_dir_cycle, uint8_t signals_copy); CommandCost CmdRemoveSingleSignal(DoCommandFlag flags, TileIndex tile, Track track); CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_start, RailType totype, bool diagonal); @@ -31,6 +32,7 @@ DEF_CMD_TRAIT(CMD_REMOVE_RAILROAD_TRACK, CmdRemoveRailroadTrack, CMD_AUTO, DEF_CMD_TRAIT(CMD_BUILD_SINGLE_RAIL, CmdBuildSingleRail, CMD_AUTO | CMD_NO_WATER, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_REMOVE_SINGLE_RAIL, CmdRemoveSingleRail, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_BUILD_TRAIN_DEPOT, CmdBuildTrainDepot, CMD_AUTO | CMD_NO_WATER, CMDT_LANDSCAPE_CONSTRUCTION) +DEF_CMD_TRAIT(CMD_REMOVE_TRAIN_DEPOT, CmdRemoveTrainDepot, CMD_AUTO | CMD_NO_WATER, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_BUILD_SINGLE_SIGNAL, CmdBuildSingleSignal, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_REMOVE_SINGLE_SIGNAL, CmdRemoveSingleSignal, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_CONVERT_RAIL, CmdConvertRail, 0, CMDT_LANDSCAPE_CONSTRUCTION) @@ -40,6 +42,6 @@ DEF_CMD_TRAIT(CMD_REMOVE_SIGNAL_TRACK, CmdRemoveSignalTrack, CMD_AUTO, CommandCallback CcPlaySound_CONSTRUCTION_RAIL; CommandCallback CcStation; CommandCallback CcBuildRailTunnel; -void CcRailDepot(Commands cmd, const CommandCost &result, TileIndex tile, RailType rt, DiagDirection dir); +void CcRailDepot(Commands cmd, const CommandCost &result, TileIndex tile, RailType rt, DiagDirection dir, bool adjacent, bool extended, DepotID join_to, TileIndex end_tile); #endif /* RAIL_CMD_H */ diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp index 06819e4c3dd9b..94d67ab5ce330 100644 --- a/src/rail_gui.cpp +++ b/src/rail_gui.cpp @@ -41,6 +41,7 @@ #include "timer/timer.h" #include "timer/timer_game_calendar.h" #include "picker_gui.h" +#include "depot_func.h" #include "station_map.h" #include "tunnelbridge_map.h" @@ -72,7 +73,7 @@ static StationPickerSelection _station_gui; ///< Settings of the station picker. static void HandleStationPlacement(TileIndex start, TileIndex end); -static void ShowBuildTrainDepotPicker(Window *parent); +static void ShowBuildTrainDepotPicker(Window *parent, bool extended_depot); static void ShowBuildWaypointPicker(Window *parent); static Window *ShowStationBuilder(Window *parent); static void ShowSignalBuilder(Window *parent); @@ -116,7 +117,7 @@ static void GenericPlaceRail(TileIndex tile, Track track) */ static void PlaceExtraDepotRail(TileIndex tile, DiagDirection dir, Track track) { - if (GetRailTileType(tile) == RAIL_TILE_DEPOT) return; + if (IsRailDepot(tile)) return; if (GetRailTileType(tile) == RAIL_TILE_SIGNALS && !_settings_client.gui.auto_remove_signals) return; if ((GetTrackBits(tile) & DiagdirReachesTracks(dir)) == 0) return; @@ -137,24 +138,27 @@ static const DiagDirection _place_depot_extra_dir[12] = { DIAGDIR_NW, DIAGDIR_NE, DIAGDIR_NW, DIAGDIR_NE, }; -void CcRailDepot(Commands, const CommandCost &result, TileIndex tile, RailType, DiagDirection dir) +void CcRailDepot(Commands, const CommandCost &result, TileIndex start_tile, RailType, DiagDirection dir, bool, bool extended, DepotID, TileIndex end_tile) { if (result.Failed()) return; + if (extended) return; - if (_settings_client.sound.confirm) SndPlayTileFx(SND_20_CONSTRUCTION_RAIL, tile); + if (_settings_client.sound.confirm) SndPlayTileFx(SND_20_CONSTRUCTION_RAIL, start_tile); if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace(); - tile += TileOffsByDiagDir(dir); + TileArea ta(start_tile, end_tile); + for (TileIndex t : ta) { + TileIndex tile = t + TileOffsByDiagDir(dir); - if (IsTileType(tile, MP_RAILWAY)) { - PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir], _place_depot_extra_track[dir]); + if (IsTileType(tile, MP_RAILWAY)) { + PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir], _place_depot_extra_track[dir]); - /* Don't place the rail straight out of the depot of there is another depot across from it. */ - Tile double_depot_tile = tile + TileOffsByDiagDir(dir); - bool is_double_depot = IsValidTile(double_depot_tile) && IsRailDepotTile(double_depot_tile); - if (!is_double_depot) PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir + 4], _place_depot_extra_track[dir + 4]); + Tile double_depot_tile = tile + TileOffsByDiagDir(dir); + bool is_double_depot = IsValidTile(double_depot_tile) && IsRailDepotTile(double_depot_tile); + if (!is_double_depot) PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir + 4], _place_depot_extra_track[dir + 4]); - PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir + 8], _place_depot_extra_track[dir + 8]); + PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir + 8], _place_depot_extra_track[dir + 8]); + } } } @@ -317,6 +321,7 @@ void CcBuildRailTunnel(Commands, const CommandCost &result, TileIndex tile) static void ToggleRailButton_Remove(Window *w) { CloseWindowById(WC_SELECT_STATION, 0); + CloseWindowById(WC_SELECT_DEPOT, VEH_TRAIN); w->ToggleWidgetLoweredState(WID_RAT_REMOVE); w->SetWidgetDirty(WID_RAT_REMOVE); _remove_button_clicked = w->IsWidgetLowered(WID_RAT_REMOVE); @@ -334,7 +339,7 @@ static bool RailToolbar_CtrlChanged(Window *w) /* allow ctrl to switch remove mode only for these widgets */ for (WidgetID i = WID_RAT_BUILD_NS; i <= WID_RAT_BUILD_STATION; i++) { - if ((i <= WID_RAT_AUTORAIL || i >= WID_RAT_BUILD_WAYPOINT) && w->IsWidgetLowered(i)) { + if ((i <= WID_RAT_AUTORAIL || i >= WID_RAT_BUILD_DEPOT) && w->HasWidget(i) && w->IsWidgetLowered(i)) { ToggleRailButton_Remove(w); return true; } @@ -454,6 +459,11 @@ struct BuildRailToolbarWindow : Window { if (this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT)) SetViewportCatchmentWaypoint(nullptr, true); if (_settings_client.gui.link_terraform_toolbar) CloseWindowById(WC_SCEN_LAND_GEN, 0, false); CloseWindowById(WC_SELECT_STATION, 0); + if ((this->HasWidget(WID_RAT_BUILD_DEPOT) && this->IsWidgetLowered(WID_RAT_BUILD_DEPOT)) || + (this->HasWidget(WID_RAT_BUILD_EXTENDED_DEPOT) && this->IsWidgetLowered(WID_RAT_BUILD_EXTENDED_DEPOT))) { + SetViewportHighlightDepot(INVALID_DEPOT, true); + } + this->Window::Close(); } @@ -505,7 +515,8 @@ struct BuildRailToolbarWindow : Window { this->GetWidget(WID_RAT_BUILD_EW)->widget_data = rti->gui_sprites.build_ew_rail; this->GetWidget(WID_RAT_BUILD_Y)->widget_data = rti->gui_sprites.build_y_rail; this->GetWidget(WID_RAT_AUTORAIL)->widget_data = rti->gui_sprites.auto_rail; - this->GetWidget(WID_RAT_BUILD_DEPOT)->widget_data = rti->gui_sprites.build_depot; + if (this->HasWidget(WID_RAT_BUILD_DEPOT)) this->GetWidget(WID_RAT_BUILD_DEPOT)->widget_data = rti->gui_sprites.build_depot; + if (this->HasWidget(WID_RAT_BUILD_EXTENDED_DEPOT)) this->GetWidget(WID_RAT_BUILD_EXTENDED_DEPOT)->widget_data = rti->gui_sprites.build_depot; this->GetWidget(WID_RAT_CONVERT_RAIL)->widget_data = rti->gui_sprites.convert_rail; this->GetWidget(WID_RAT_BUILD_TUNNEL)->widget_data = rti->gui_sprites.build_tunnel; } @@ -533,6 +544,8 @@ struct BuildRailToolbarWindow : Window { case WID_RAT_BUILD_EW: case WID_RAT_BUILD_Y: case WID_RAT_AUTORAIL: + case WID_RAT_BUILD_DEPOT: + case WID_RAT_BUILD_EXTENDED_DEPOT: case WID_RAT_BUILD_WAYPOINT: case WID_RAT_BUILD_STATION: case WID_RAT_BUILD_SIGNALS: @@ -601,8 +614,9 @@ struct BuildRailToolbarWindow : Window { break; case WID_RAT_BUILD_DEPOT: - if (HandlePlacePushButton(this, WID_RAT_BUILD_DEPOT, GetRailTypeInfo(_cur_railtype)->cursor.depot, HT_RECT)) { - ShowBuildTrainDepotPicker(this); + case WID_RAT_BUILD_EXTENDED_DEPOT: + if (HandlePlacePushButton(this, widget, GetRailTypeInfo(_cur_railtype)->cursor.depot, HT_RECT)) { + ShowBuildTrainDepotPicker(this, widget == WID_RAT_BUILD_EXTENDED_DEPOT); this->last_user_action = widget; } break; @@ -689,8 +703,17 @@ struct BuildRailToolbarWindow : Window { break; case WID_RAT_BUILD_DEPOT: - Command::Post(STR_ERROR_CAN_T_BUILD_TRAIN_DEPOT, CcRailDepot, tile, _cur_railtype, _build_depot_direction); + case WID_RAT_BUILD_EXTENDED_DEPOT: { + CloseWindowById(WC_SELECT_DEPOT, VEH_TRAIN); + + ViewportPlaceMethod vpm = VPM_X_AND_Y_LIMITED; + if (this->last_user_action == WID_RAT_BUILD_DEPOT) { + vpm = (DiagDirToAxis(_build_depot_direction) == 0) ? VPM_X_LIMITED : VPM_Y_LIMITED; + } + VpStartPlaceSizing(tile, vpm, _remove_button_clicked ? DDSP_REMOVE_DEPOT : DDSP_BUILD_DEPOT); + VpSetPlaceSizingLimit(_settings_game.depot.depot_spread); break; + } case WID_RAT_BUILD_WAYPOINT: PlaceRail_Waypoint(tile); @@ -786,6 +809,22 @@ struct BuildRailToolbarWindow : Window { } } break; + + case DDSP_BUILD_DEPOT: + case DDSP_REMOVE_DEPOT: + if (_remove_button_clicked) { + Command::Post(STR_ERROR_CAN_T_REMOVE_TRAIN_DEPOT, CcPlaySound_CONSTRUCTION_RAIL, start_tile, end_tile); + } else { + bool adjacent = _ctrl_pressed; + bool extended = this->last_user_action == WID_RAT_BUILD_EXTENDED_DEPOT; + + auto proc = [=](DepotID join_to) -> bool { + return Command::Post(STR_ERROR_CAN_T_BUILD_TRAIN_DEPOT, CcRailDepot, start_tile, _cur_railtype, _build_depot_direction, adjacent, extended, join_to, end_tile); + }; + + ShowSelectDepotIfNeeded(TileArea(start_tile, end_tile), proc, VEH_TRAIN); + } + break; } } } @@ -795,6 +834,11 @@ struct BuildRailToolbarWindow : Window { if (this->IsWidgetLowered(WID_RAT_BUILD_STATION)) SetViewportCatchmentStation(nullptr, true); if (this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT)) SetViewportCatchmentWaypoint(nullptr, true); + if ((this->HasWidget(WID_RAT_BUILD_DEPOT) && this->IsWidgetLowered(WID_RAT_BUILD_DEPOT)) || + (this->HasWidget(WID_RAT_BUILD_EXTENDED_DEPOT) && this->IsWidgetLowered(WID_RAT_BUILD_EXTENDED_DEPOT))) { + SetViewportHighlightDepot(INVALID_DEPOT, true); + } + this->RaiseButtons(); this->DisableWidget(WID_RAT_REMOVE); this->SetWidgetDirty(WID_RAT_REMOVE); @@ -804,6 +848,7 @@ struct BuildRailToolbarWindow : Window { CloseWindowById(WC_BUILD_DEPOT, TRANSPORT_RAIL); CloseWindowById(WC_BUILD_WAYPOINT, TRANSPORT_RAIL); CloseWindowById(WC_SELECT_STATION, 0); + CloseWindowById(WC_SELECT_DEPOT, VEH_TRAIN); CloseWindowByClass(WC_BUILD_BRIDGE); } @@ -815,8 +860,12 @@ struct BuildRailToolbarWindow : Window { EventState OnCTRLStateChange() override { - /* do not toggle Remove button by Ctrl when placing station */ - if (!this->IsWidgetLowered(WID_RAT_BUILD_STATION) && !this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT) && RailToolbar_CtrlChanged(this)) return ES_HANDLED; + /* do not toggle Remove button by Ctrl when placing station or depot */ + if (!this->IsWidgetLowered(WID_RAT_BUILD_STATION) && + !this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT) && + !(this->HasWidget(WID_RAT_BUILD_DEPOT) && this->IsWidgetLowered(WID_RAT_BUILD_DEPOT)) && + !(this->HasWidget(WID_RAT_BUILD_EXTENDED_DEPOT) && this->IsWidgetLowered(WID_RAT_BUILD_EXTENDED_DEPOT)) && + RailToolbar_CtrlChanged(this)) return ES_HANDLED; return ES_NOT_HANDLED; } @@ -857,6 +906,27 @@ struct BuildRailToolbarWindow : Window { }, RailToolbarGlobalHotkeys}; }; +/** + * Add the depot icons depending on availability of construction. + * @return Panel with rail depot buttons. + */ +static std::unique_ptr MakeNWidgetRailDepot() +{ + auto hor = std::make_unique(); + + if (HasBit(_settings_game.depot.rail_depot_types, 0)) { + /* Add the widget for building standard rail depot. */ + hor->Add(std::make_unique(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_DEPOT, SPR_IMG_DEPOT_RAIL, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_TRAIN_DEPOT)); + } + + if (HasBit(_settings_game.depot.rail_depot_types, 1)) { + /* Add the widget for building extended rail depot. */ + hor->Add(std::make_unique(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_EXTENDED_DEPOT, SPR_IMG_DEPOT_RAIL, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_EXTENDED_TRAIN_DEPOT)); + } + + return hor; +} + static constexpr NWidgetPart _nested_build_rail_widgets[] = { NWidget(NWID_HORIZONTAL), NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN), @@ -879,8 +949,7 @@ static constexpr NWidgetPart _nested_build_rail_widgets[] = { NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_DEMOLISH), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC), - NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_DEPOT), - SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DEPOT_RAIL, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_TRAIN_DEPOT_FOR_BUILDING), + NWidgetFunction(MakeNWidgetRailDepot), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_WAYPOINT), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_WAYPOINT, STR_RAIL_TOOLBAR_TOOLTIP_CONVERT_RAIL_TO_WAYPOINT), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_STATION), @@ -1699,12 +1768,33 @@ static void ShowSignalBuilder(Window *parent) } struct BuildRailDepotWindow : public PickerWindowBase { - BuildRailDepotWindow(WindowDesc &desc, Window *parent) : PickerWindowBase(desc, parent) + + BuildRailDepotWindow(WindowDesc &desc, Window *parent, bool extended_depot) : PickerWindowBase(desc, parent) { this->InitNested(TRANSPORT_RAIL); + + /* Fix direction for extended depots. */ + if (extended_depot) { + switch ((BuildRailDepotWidgets)_build_depot_direction) { + case WID_BRAD_DEPOT_NE: + _build_depot_direction++; + break; + case WID_BRAD_DEPOT_NW: + _build_depot_direction--; + break; + default: break; + } + } + this->LowerWidget(WID_BRAD_DEPOT_NE + _build_depot_direction); } + void Close([[maybe_unused]] int data = 0) override + { + CloseWindowById(WC_SELECT_DEPOT, VEH_TRAIN); + this->PickerWindowBase::Close(); + } + void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override { if (!IsInsideMM(widget, WID_BRAD_DEPOT_NE, WID_BRAD_DEPOT_NW + 1)) return; @@ -1734,6 +1824,7 @@ struct BuildRailDepotWindow : public PickerWindowBase { case WID_BRAD_DEPOT_SE: case WID_BRAD_DEPOT_SW: case WID_BRAD_DEPOT_NW: + CloseWindowById(WC_SELECT_DEPOT, VEH_TRAIN); this->RaiseWidget(WID_BRAD_DEPOT_NE + _build_depot_direction); _build_depot_direction = (DiagDirection)(widget - WID_BRAD_DEPOT_NE); this->LowerWidget(WID_BRAD_DEPOT_NE + _build_depot_direction); @@ -1742,6 +1833,11 @@ struct BuildRailDepotWindow : public PickerWindowBase { break; } } + + void OnRealtimeTick([[maybe_unused]] uint delta_ms) override + { + CheckRedrawDepotHighlight(this, VEH_TRAIN); + } }; /** Nested widget definition of the build rail depot window */ @@ -1771,9 +1867,30 @@ static WindowDesc _build_depot_desc( _nested_build_depot_widgets ); -static void ShowBuildTrainDepotPicker(Window *parent) +/** Nested widget definition of the build extended rail depot window */ +static const NWidgetPart _nested_build_extended_depot_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN), + NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetDataTip(STR_BUILD_DEPOT_TRAIN_ORIENTATION_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + EndContainer(), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), + NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1), SetPadding(WidgetDimensions::unscaled.picker), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAD_DEPOT_SW), SetMinimalSize(66, 50), SetDataTip(0x0, STR_BUILD_DEPOT_TRAIN_ORIENTATION_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAD_DEPOT_SE), SetMinimalSize(66, 50), SetDataTip(0x0, STR_BUILD_DEPOT_TRAIN_ORIENTATION_TOOLTIP), + EndContainer(), + EndContainer(), +}; + +static WindowDesc _build_extended_depot_desc( + WDP_AUTO, nullptr, 0, 0, + WC_BUILD_DEPOT, WC_BUILD_TOOLBAR, + WDF_CONSTRUCTION, + _nested_build_extended_depot_widgets +); + +static void ShowBuildTrainDepotPicker(Window *parent, bool extended_depot) { - new BuildRailDepotWindow(_build_depot_desc, parent); + new BuildRailDepotWindow(extended_depot ? _build_extended_depot_desc : _build_depot_desc, parent, extended_depot); } class WaypointPickerCallbacks : public PickerCallbacksNewGRFClass { diff --git a/src/rail_map.h b/src/rail_map.h index 5ff2fb9ec36db..a715fc67fe2f6 100644 --- a/src/rail_map.h +++ b/src/rail_map.h @@ -23,7 +23,7 @@ enum RailTileType { RAIL_TILE_NORMAL = 0, ///< Normal rail tile without signals RAIL_TILE_SIGNALS = 1, ///< Normal rail tile with signals - RAIL_TILE_DEPOT = 3, ///< Depot (one entrance) + RAIL_TILE_DEPOT = 2, ///< Depot }; /** @@ -107,6 +107,50 @@ debug_inline static bool IsRailDepotTile(Tile t) return IsTileType(t, MP_RAILWAY) && IsRailDepot(t); } +/** + * Is this rail depot tile an extended depot? + * @param t the tile to get the information from + * @pre IsRailDepotTile(t) + * @return true if and only if the tile is an extended rail depot + */ +static inline bool IsExtendedRailDepot(Tile t) +{ + assert(IsRailDepotTile(t)); + return HasBit(t.m5(), 5); +} + +/** + * Is this rail tile a standard rail depot? + * @param t the tile to get the information from + * @pre IsTileType(t, MP_RAILWAY) + * @return true if and only if the tile is a standard rail depot + */ +static inline bool IsStandardRailDepot(Tile t) +{ + assert(IsTileType(t, MP_RAILWAY)); + return IsRailDepot(t) && !IsExtendedRailDepot(t); +} + +/** + * Is this tile a standard rail depot? + * @param t the tile to get the information from + * @return true if and only if the tile is a standard rail depot + */ +static inline bool IsStandardRailDepotTile(TileIndex t) +{ + return IsTileType(t, MP_RAILWAY) && IsStandardRailDepot(t); +} + +/** + * Is this tile rail tile and an extended rail depot? + * @param t the tile to get the information from + * @return true if and only if the tile is an extended rail depot + */ +static inline bool IsExtendedRailDepotTile(TileIndex t) +{ + return IsTileType(t, MP_RAILWAY) && IsRailDepotTile(t) && IsExtendedRailDepot(t); +} + /** * Gets the rail type of the given tile * @param t the tile to get the rail type from diff --git a/src/road.h b/src/road.h index 44f4fcd198e2a..c2591d2f1d907 100644 --- a/src/road.h +++ b/src/road.h @@ -244,6 +244,19 @@ inline bool HasPowerOnRoad(RoadType enginetype, RoadType tiletype) return HasBit(GetRoadTypeInfo(enginetype)->powered_roadtypes, tiletype); } +/** + * Checks if an engine with a given \a enginetype is powered on \a road_types. + * This would normally just be an equality check, + * but for electrified roads (which also support non-electric vehicles). + * @param enginetype The RoadType of the engine we are considering. + * @param rail_types The RoadTypes we are considering. + * @return Whether the engine got power on this tile. + */ +static inline bool HasPowerOnRoads(RoadType enginetype, RoadTypes road_types) +{ + return (GetRoadTypeInfo(enginetype)->powered_roadtypes & road_types) != 0; +} + /** * Returns the cost of building the specified roadtype. * @param roadtype The roadtype being built. diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp index e4489c86218fa..e6c8bca26e191 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -42,6 +42,7 @@ #include "road_cmd.h" #include "landscape_cmd.h" #include "rail_cmd.h" +#include "platform_func.h" #include "table/strings.h" #include "table/roadtypes.h" @@ -313,6 +314,22 @@ CommandCost CheckAllowRemoveRoad(TileIndex tile, RoadBits remove, Owner owner, R return CommandCost(); } +void UpdateRoadDepotDir(TileIndex tile) +{ + assert(IsExtendedRoadDepot(tile)); + RoadBits rb = GetAllRoadBits(tile); + DiagDirection dir = DIAGDIR_NE; + if (rb & ROAD_SE) { + dir = DIAGDIR_SE; + } else if (rb & ROAD_SW) { + dir = DIAGDIR_SW; + } else if (rb & ROAD_NW) { + dir = DIAGDIR_NW; + } else { + assert(rb & ROAD_NE); + } + SetRoadDepotDirection(tile, dir); +} /** * Delete a piece of road. @@ -524,9 +541,33 @@ static CommandCost RemoveRoad(TileIndex tile, DoCommandFlag flags, RoadBits piec return CommandCost(EXPENSES_CONSTRUCTION, RoadClearCost(existing_rt) * 2); } - default: - case ROAD_TILE_DEPOT: - return CMD_ERROR; + case ROAD_TILE_DEPOT: { + /* Depot must have at least one road bit. */ + RoadBits new_rb = (GetRoadBits(tile, rtt) & ~pieces); + if (new_rb == ROAD_NONE && GetRoadType(tile, OtherRoadTramType(rtt)) == INVALID_ROADTYPE) return CMD_ERROR; + + uint num_removed_bits = CountBits(pieces & GetRoadBits(tile, rtt)); + CommandCost cost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_ROAD] * num_removed_bits); + + if ((flags & DC_EXEC) && num_removed_bits != 0) { + SetRoadBits(tile, new_rb, rtt); + + Company *c = Company::GetIfValid(GetTileOwner(tile)); + c->infrastructure.road[GetRoadType(tile, rtt)] -= num_removed_bits; + DirtyCompanyInfrastructureWindows(c->index); + + if (new_rb == ROAD_NONE) { + SetRoadType(tile, rtt, INVALID_ROADTYPE); + Depot::GetByTile(tile)->AfterAddRemove(TileArea(tile), false); + } + + if (IsExtendedRoadDepot(tile)) UpdateRoadDepotDir(tile); + MarkTileDirtyByTile(tile); + } + return cost; + } + + default: NOT_REACHED(); } } @@ -711,8 +752,34 @@ CommandCost CmdBuildRoad(DoCommandFlag flags, TileIndex tile, RoadBits pieces, R if (HasTileRoadType(tile, rtt)) return_cmd_error(STR_ERROR_ALREADY_BUILT); break; - case ROAD_TILE_DEPOT: - if ((GetAnyRoadBits(tile, rtt) & pieces) == pieces) return_cmd_error(STR_ERROR_ALREADY_BUILT); + case ROAD_TILE_DEPOT: { + Owner owner = GetRoadOwner(tile, rtt); + if (owner != OWNER_NONE) { + CommandCost ret = CheckOwnership(owner, tile); + if (ret.Failed()) return ret; + } + + if (IsExtendedRoadDepot(tile)) { + RoadType tile_rt = GetRoadType(tile, rtt); + if (tile_rt != INVALID_ROADTYPE && rt != tile_rt) return CMD_ERROR; + Axis axis = DiagDirToAxis(GetRoadDepotDirection(tile)); + RoadBits rb = (axis == AXIS_X ? ROAD_X : ROAD_Y) & pieces; + if (rb != pieces) return CMD_ERROR; + existing = GetRoadBits(tile, rtt); + if ((rb & ~existing) == ROAD_NONE) return_cmd_error(STR_ERROR_ALREADY_BUILT); + cost.AddCost(_price[PR_BUILD_DEPOT_ROAD] * CountBits(rb & ~existing)); + break; + } else if (GetRoadBits(tile, OtherRoadTramType(rtt)) == pieces) { + /* Check if we can add a new road/tram type if none present. */ + if (HasTileRoadType(tile, rtt)) { + return_cmd_error(STR_ERROR_ALREADY_BUILT); + } + /* We may add a new road type. */ + cost.AddCost(_price[PR_BUILD_DEPOT_ROAD]); + break; + } + } + goto do_clear; default: NOT_REACHED(); @@ -886,7 +953,18 @@ do_clear:; switch (GetTileType(tile)) { case MP_ROAD: { RoadTileType rttype = GetRoadTileType(tile); - if (existing == ROAD_NONE || rttype == ROAD_TILE_CROSSING) { + if (rttype == ROAD_TILE_DEPOT) { + SetRoadType(tile, rtt, rt); + if (IsExtendedRoadDepot(tile)) { + SetRoadBits(tile, pieces | GetRoadBits(tile, rtt), rtt); + /* Do not add or remove to company infrastructure for depots. Already acounted for. */ + UpdateRoadDepotDir(tile); + } else { + SetRoadBits(tile, GetRoadBits(tile, OtherRoadTramType(rtt)), rtt); + } + Depot::GetByTile(tile)->AfterAddRemove(TileArea(tile), true); + break; + } else if (existing == ROAD_NONE || rttype == ROAD_TILE_CROSSING) { SetRoadType(tile, rtt, rt); SetRoadOwner(tile, rtt, company); if (rtt == RTT_ROAD) SetTownIndex(tile, town_id); @@ -1140,67 +1218,184 @@ std::tuple CmdRemoveLongRoad(DoCommandFlag flags, TileIndex * @param tile tile where to build the depot * @param flags operation to perform * @param rt road type - * @param dir entrance direction + * @param orig_dir entrance direction + * @param adjacent allow adjacent depots + * @param extended build extended depot + * @param half_start build only one trackbit in start tile if building an extended depot + * @param half_end build only one trackbit in end tile if building an extended depot + * @param depot_id depot to join to + * @param end_tile end tile of the depot to be built * @return the cost of this operation or an error * * @todo When checking for the tile slope, * distinguish between "Flat land required" and "land sloped in wrong direction" */ -CommandCost CmdBuildRoadDepot(DoCommandFlag flags, TileIndex tile, RoadType rt, DiagDirection dir) +CommandCost CmdBuildRoadDepot(DoCommandFlag flags, TileIndex tile, RoadType rt, DiagDirection orig_dir, bool adjacent, bool extended, bool half_start, bool half_end, DepotID join_to, TileIndex end_tile) { - if (!ValParamRoadType(rt) || !IsValidDiagDirection(dir)) return CMD_ERROR; + if (!ValParamRoadType(rt) || !IsValidDiagDirection(orig_dir)) return CMD_ERROR; - CommandCost cost(EXPENSES_CONSTRUCTION); + if (Company::IsValidHumanID(_current_company) && !HasBit(_settings_game.depot.road_depot_types, extended)) { + return_cmd_error(STR_ERROR_DEPOT_TYPE_NOT_AVAILABLE); + } - Slope tileh = GetTileSlope(tile); - if (tileh != SLOPE_FLAT) { - if (!_settings_game.construction.build_on_slopes || !CanBuildDepotByTileh(dir, tileh)) { - return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); + TileArea ta(tile, end_tile); + assert(extended || ta.w == 1 || ta.h == 1); + + Axis axis = DiagDirToAxis(orig_dir); + RoadTramType rtt = GetRoadTramType(rt); + uint start_coord = 0; + uint end_coord = 0; + + DiagDirection dir = orig_dir; + if (extended) { + start_coord = axis == AXIS_X ? TileX(tile) : TileY(tile); + end_coord = axis == AXIS_X ? TileX(end_tile) : TileY(end_tile); + + dir = AxisToDiagDir(axis); + + /* Swap direction, also the half-tile drag var (bit 0 and 1) */ + if (start_coord > end_coord || start_coord == end_coord) { + dir = ReverseDiagDir(dir); + half_start = !half_start; + half_end = !half_end; } - cost.AddCost(_price[PR_BUILD_FOUNDATION]); } - /* Allow the user to rotate the depot instead of having to destroy it and build it again */ - bool rotate_existing_depot = false; - if (IsRoadDepotTile(tile) && (HasRoadTypeTram(tile) ? rt == GetRoadTypeTram(tile) : rt == GetRoadTypeRoad(tile))) - { - CommandCost ret = CheckTileOwnership(tile); - if (ret.Failed()) return ret; + /* Create a new depot or find a depot to join to. */ + Depot *depot = nullptr; + CommandCost ret = FindJoiningDepot(ta, VEH_ROAD, join_to, depot, adjacent, flags); + if (ret.Failed()) return ret; - if (dir == GetRoadDepotDirection(tile)) return CommandCost(); + uint8_t num_new_depot_tiles = 0; + uint8_t num_overbuilt_depot_tiles = 0; - ret = EnsureNoVehicleOnGround(tile); - if (ret.Failed()) return ret; + CommandCost cost(EXPENSES_CONSTRUCTION); + int allowed_z = -1; + uint num_new_pieces = 0; + uint invalid_dirs = extended ? 5 << axis : 1 << dir; + for (Tile t : ta) { + RoadBits rb = ROAD_NONE; + if (IsBridgeAbove(t)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); + + auto [tileh, z] = GetTileSlopeZ(t); + int flat_z = z + GetSlopeMaxZ(tileh); + + if (tileh != SLOPE_FLAT) { + if (!_settings_game.construction.build_on_slopes || !CanBuildDepotByTileh(dir, tileh)) { + return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); + } - rotate_existing_depot = true; - } + if (extended && IsSteepSlope(tileh)) return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); + /* Forbid building if the tile faces a slope in a invalid direction. */ + for (DiagDirection dir = DIAGDIR_BEGIN; dir != DIAGDIR_END; dir++) { + if (HasBit(invalid_dirs, dir) && !CanBuildDepotByTileh(dir, tileh)) { + return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); + } + } - if (!rotate_existing_depot) { - cost.AddCost(Command::Do(flags, tile)); - if (cost.Failed()) return cost; + cost.AddCost(_price[PR_BUILD_FOUNDATION]); + } + + /* The level of this tile must be equal to allowed_z. */ + if (allowed_z < 0) { + /* First tile. */ + allowed_z = flat_z; + } else if (allowed_z != flat_z) { + return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); + } - if (IsBridgeAbove(tile)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); + /* Check whether this is a compatible depot tile. */ + if (IsRoadDepotTile(t) && GetDepotIndex(t) == join_to && rt == GetRoadType(t, rtt)) { + if (extended) { + if (IsExtendedRoadDepotTile(t) && + axis == DiagDirToAxis(GetRoadDepotDirection(t))) { + /* Already exists and has the right axis: Check new roadbits. */ + goto rb_for_extended_depot; + } + } else { + if (!IsExtendedRoadDepotTile(t)) { + if (dir == GetRoadDepotDirection(t)) continue; - if (!Depot::CanAllocateItem()) return CMD_ERROR; - } + /* If another roadtype exists (road/tram), depot cannot be rotated. */ + if (GetRoadTypeRoad(t) != INVALID_ROADTYPE && GetRoadTypeTram(t) != INVALID_ROADTYPE) { + return_cmd_error(STR_ERROR_BUILDING_MUST_BE_DEMOLISHED); + } - if (flags & DC_EXEC) { - if (rotate_existing_depot) { - SetRoadDepotExitDirection(tile, dir); + /* Overbuild the depot tile and change its exit direction. */ + num_overbuilt_depot_tiles++; + if (flags & DC_EXEC) { + rb = DiagDirToRoadBits(orig_dir); + SetRoadBits(t, rb, rtt); + SetRoadDepotDirection(t, orig_dir); + MarkTileDirtyByTile(t); + } + continue; + } + } + } + + cost.AddCost(Command::Do(flags, t)); + if (cost.Failed()) return cost; + + /* Check which road bits to build. */ + if (extended) { +rb_for_extended_depot: + uint axis_coord = axis == AXIS_X ? TileX(t) : TileY(t); + /* Road parts only have to be built at the start tile or at the end tile. */ + if (!half_end && axis_coord == end_coord) { + rb = DiagDirToRoadBits(ReverseDiagDir(dir)); + } + if (half_start && axis_coord == start_coord) { + rb = DiagDirToRoadBits(dir); + } + if (rb == ROAD_NONE) { + rb = AxisToRoadBits(axis); + } + assert(rb != ROAD_NONE); + if (IsRoadDepotTile(t)) { + RoadType old_rt = GetRoadType(t, rtt); + if (old_rt != INVALID_ROADTYPE && old_rt != rt) return_cmd_error(STR_ERROR_BUILDING_MUST_BE_DEMOLISHED); + RoadBits old_rb = GetAllRoadBits(t); + if ((old_rb & AxisToRoadBits(axis)) != old_rb) return_cmd_error(STR_ERROR_BUILDING_MUST_BE_DEMOLISHED); + old_rb = GetRoadBits(t, rtt); + if ((rb & ~old_rb) == ROAD_NONE) return_cmd_error(STR_ERROR_ALREADY_BUILT); + num_new_pieces += CountBits(rb & ~old_rb); + num_overbuilt_depot_tiles++; + rb |= old_rb; + } else { + num_new_pieces += CountBits(rb); + num_new_depot_tiles++; + } } else { - Depot *dep = new Depot(tile); - dep->build_date = TimerGameCalendar::date; - MakeRoadDepot(tile, _current_company, dep->index, dir, rt); - MakeDefaultName(dep); + rb = DiagDirToRoadBits(orig_dir); + num_new_pieces += 1; + num_new_depot_tiles++; + } + + if (flags & DC_EXEC) { + if (!IsRoadDepotTile(t)) MakeRoadDepot(t, _current_company, depot->index, orig_dir, rt); + if (GetRoadType(t, rtt) == INVALID_ROADTYPE) SetRoadType(t, rtt, rt); + SetRoadBits(t, rb, rtt); + if (extended) { + SB(t.m5(), 5, 1, true); + UpdateRoadDepotDir(t); + } - /* A road depot has two road bits. */ - UpdateCompanyRoadInfrastructure(rt, _current_company, ROAD_DEPOT_TRACKBIT_FACTOR); + MarkTileDirtyByTile(t); } + } - MarkTileDirtyByTile(tile); + if (num_new_depot_tiles + num_overbuilt_depot_tiles == 0) return CommandCost(); + + cost.AddCost(_price[PR_BUILD_DEPOT_ROAD] * (num_new_depot_tiles + num_overbuilt_depot_tiles)); + + if (flags & DC_EXEC) { + UpdateCompanyRoadInfrastructure(rt, _current_company, num_new_pieces); + + depot->AfterAddRemove(ta, true); + if (join_to == NEW_DEPOT) MakeDefaultName(depot); } - cost.AddCost(_price[PR_BUILD_DEPOT_ROAD]); return cost; } @@ -1214,21 +1409,27 @@ static CommandCost RemoveRoadDepot(TileIndex tile, DoCommandFlag flags) CommandCost ret = EnsureNoVehicleOnGround(tile); if (ret.Failed()) return ret; + CommandCost cost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_ROAD]); + RoadType rt = GetRoadTypeRoad(tile); + RoadType tt = GetRoadTypeTram(tile); + if (rt != INVALID_ROADTYPE) cost.AddCost(_price[PR_CLEAR_DEPOT_ROAD]); + if (tt != INVALID_ROADTYPE) cost.AddCost(_price[PR_CLEAR_DEPOT_ROAD]); + if (flags & DC_EXEC) { - Company *c = Company::GetIfValid(GetTileOwner(tile)); + Depot *depot = Depot::GetByTile(tile); + Company *c = Company::GetIfValid(depot->owner); if (c != nullptr) { - /* A road depot has two road bits. */ - RoadType rt = GetRoadTypeRoad(tile); - if (rt == INVALID_ROADTYPE) rt = GetRoadTypeTram(tile); - c->infrastructure.road[rt] -= ROAD_DEPOT_TRACKBIT_FACTOR; + /* A road depot has two road types. */ + if (rt != INVALID_ROADTYPE) c->infrastructure.road[rt] -= CountBits(GetRoadBits(tile, RTT_ROAD)); + if (tt != INVALID_ROADTYPE) c->infrastructure.road[tt] -= CountBits(GetRoadBits(tile, RTT_TRAM)); DirtyCompanyInfrastructureWindows(c->index); } - delete Depot::GetByTile(tile); DoClearSquare(tile); + depot->AfterAddRemove(TileArea(tile), false); } - return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_ROAD]); + return cost; } static CommandCost ClearTile_Road(TileIndex tile, DoCommandFlag flags) @@ -1454,11 +1655,16 @@ void DrawRoadCatenary(const TileInfo *ti) RoadBits tram = ROAD_NONE; if (IsTileType(ti->tile, MP_ROAD)) { - if (IsNormalRoad(ti->tile)) { - road = GetRoadBits(ti->tile, RTT_ROAD); - tram = GetRoadBits(ti->tile, RTT_TRAM); - } else if (IsLevelCrossing(ti->tile)) { - tram = road = (GetCrossingRailAxis(ti->tile) == AXIS_Y ? ROAD_X : ROAD_Y); + switch (GetRoadTileType(ti->tile)) { + case ROAD_TILE_NORMAL: + case ROAD_TILE_DEPOT: + road = GetRoadBits(ti->tile, RTT_ROAD); + tram = GetRoadBits(ti->tile, RTT_TRAM); + break; + case ROAD_TILE_CROSSING: + tram = road = (GetCrossingRailAxis(ti->tile) == AXIS_Y ? ROAD_X : ROAD_Y); + break; + default: NOT_REACHED(); } } else if (IsTileType(ti->tile, MP_STATION)) { if (IsAnyRoadStop(ti->tile)) { @@ -1821,17 +2027,30 @@ static void DrawTile_Road(TileInfo *ti) case ROAD_TILE_DEPOT: { if (ti->tileh != SLOPE_FLAT) DrawFoundation(ti, FOUNDATION_LEVELED); - PaletteID palette = COMPANY_SPRITE_COLOUR(GetTileOwner(ti->tile)); - RoadType road_rt = GetRoadTypeRoad(ti->tile); RoadType tram_rt = GetRoadTypeTram(ti->tile); - const RoadTypeInfo *rti = GetRoadTypeInfo(road_rt == INVALID_ROADTYPE ? tram_rt : road_rt); + assert(road_rt != INVALID_ROADTYPE || tram_rt != INVALID_ROADTYPE); + const RoadTypeInfo *road_rti = road_rt != INVALID_ROADTYPE ? GetRoadTypeInfo(road_rt) : nullptr; + const RoadTypeInfo *tram_rti = tram_rt != INVALID_ROADTYPE ? GetRoadTypeInfo(tram_rt) : nullptr; + const RoadTypeInfo *main_rti = tram_rti != nullptr ? tram_rti : road_rti; + + DiagDirection dir = GetRoadDepotDirection(ti->tile); + uint road_offset = GetRoadSpriteOffset(SLOPE_FLAT, GetRoadBits(ti->tile, RTT_ROAD)); + uint tram_offset = GetRoadSpriteOffset(SLOPE_FLAT, GetRoadBits(ti->tile, RTT_TRAM)); + - int relocation = GetCustomRoadSprite(rti, ti->tile, ROTSG_DEPOT); + PaletteID pal = PAL_NONE; + const DrawTileSprites *dts = &_road_depot[dir]; + SpriteID image = SPR_ROAD_Y + (road_rti == nullptr ? tram_offset : road_offset) - 19; + DrawGroundSprite(image, pal); + + DrawRoadOverlays(ti, pal, road_rti, tram_rti, road_offset, tram_offset); + + int relocation = GetCustomRoadSprite(main_rti, ti->tile, ROTSG_DEPOT); bool default_gfx = relocation == 0; if (default_gfx) { - if (HasBit(rti->flags, ROTF_CATENARY)) { - if (_loaded_newgrf_features.tram == TRAMWAY_REPLACE_DEPOT_WITH_TRACK && road_rt == INVALID_ROADTYPE && !rti->UsesOverlay()) { + if (HasBit(main_rti->flags, ROTF_CATENARY)) { + if (_loaded_newgrf_features.tram == TRAMWAY_REPLACE_DEPOT_WITH_TRACK && road_rt == INVALID_ROADTYPE && !main_rti->UsesOverlay()) { /* Sprites with track only work for default tram */ relocation = SPR_TRAMWAY_DEPOT_WITH_TRACK - SPR_ROAD_DEPOT; default_gfx = false; @@ -1844,21 +2063,11 @@ static void DrawTile_Road(TileInfo *ti) relocation -= SPR_ROAD_DEPOT; } - DiagDirection dir = GetRoadDepotDirection(ti->tile); - const DrawTileSprites *dts = &_road_depot[dir]; - DrawGroundSprite(dts->ground.sprite, PAL_NONE); + /* Draw road, tram catenary */ + DrawRoadCatenary(ti); - if (default_gfx) { - uint offset = GetRoadSpriteOffset(SLOPE_FLAT, DiagDirToRoadBits(dir)); - if (rti->UsesOverlay()) { - SpriteID ground = GetCustomRoadSprite(rti, ti->tile, ROTSG_OVERLAY); - if (ground != 0) DrawGroundSprite(ground + offset, PAL_NONE); - } else if (road_rt == INVALID_ROADTYPE) { - DrawGroundSprite(SPR_TRAMWAY_OVERLAY + offset, PAL_NONE); - } - } + DrawRailTileSeq(ti, dts, TO_BUILDINGS, relocation, 0, COMPANY_SPRITE_COLOUR(GetTileOwner(ti->tile))); - DrawRailTileSeq(ti, dts, TO_BUILDINGS, relocation, 0, palette); break; } } @@ -1876,7 +2085,9 @@ void DrawRoadDepotSprite(int x, int y, DiagDirection dir, RoadType rt) { PaletteID palette = COMPANY_SPRITE_COLOUR(_local_company); + RoadTramType rtt = GetRoadTramType(rt); const RoadTypeInfo *rti = GetRoadTypeInfo(rt); + uint road_offset = GetRoadSpriteOffset(SLOPE_FLAT, DiagDirToRoadBits(dir)); int relocation = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_DEPOT); bool default_gfx = relocation == 0; if (default_gfx) { @@ -1897,6 +2108,18 @@ void DrawRoadDepotSprite(int x, int y, DiagDirection dir, RoadType rt) const DrawTileSprites *dts = &_road_depot[dir]; DrawSprite(dts->ground.sprite, PAL_NONE, x, y); + if (rti->UsesOverlay()) { + SpriteID ground = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_GROUND); + DrawSprite(ground + road_offset, PAL_NONE, x, y); + ground = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_OVERLAY); + if (ground != 0) DrawSprite(ground + road_offset, PAL_NONE, x, y); + } else if (rtt == RTT_TRAM) { + DrawSprite(SPR_TRAMWAY_TRAM + road_offset, PAL_NONE, x, y); + DrawSprite(SPR_TRAMWAY_OVERLAY + road_offset, PAL_NONE, x, y); + } else { + DrawSprite(SPR_ROAD_Y + road_offset - 19, PAL_NONE, x, y); + } + if (default_gfx) { uint offset = GetRoadSpriteOffset(SLOPE_FLAT, DiagDirToRoadBits(dir)); if (rti->UsesOverlay()) { @@ -2081,7 +2304,7 @@ static bool ClickTile_Road(TileIndex tile) { if (!IsRoadDepot(tile)) return false; - ShowDepotWindow(tile, VEH_ROAD); + ShowDepotWindow(GetDepotIndex(tile)); return true; } @@ -2153,11 +2376,11 @@ static TrackStatus GetTileTrackStatus_Road(TileIndex tile, TransportType mode, u default: case ROAD_TILE_DEPOT: { - DiagDirection dir = GetRoadDepotDirection(tile); + Axis axis = DiagDirToAxis(GetRoadDepotDirection(tile)); - if (side != INVALID_DIAGDIR && side != dir) break; + if (side != INVALID_DIAGDIR && axis != DiagDirToAxis(side)) break; - trackdirbits = TrackBitsToTrackdirBits(DiagDirToDiagTrackBits(dir)); + trackdirbits = TrackBitsToTrackdirBits(AxisToTrackBits(axis)); break; } } @@ -2214,7 +2437,7 @@ static void GetTileDesc_Road(TileIndex tile, TileDesc *td) } case ROAD_TILE_DEPOT: - td->str = STR_LAI_ROAD_DESCRIPTION_ROAD_VEHICLE_DEPOT; + td->str = IsExtendedDepot(tile) ? STR_LAI_ROAD_DESCRIPTION_ROAD_VEHICLE_DEPOT_EXTENDED : STR_LAI_ROAD_DESCRIPTION_ROAD_VEHICLE_DEPOT; td->build_date = Depot::GetByTile(tile)->build_date; break; @@ -2254,29 +2477,53 @@ static const uint8_t _roadveh_enter_depot_dir[4] = { TRACKDIR_X_SW, TRACKDIR_Y_NW, TRACKDIR_X_NE, TRACKDIR_Y_SE }; -static VehicleEnterTileStatus VehicleEnter_Road(Vehicle *v, TileIndex tile, int, int) +static VehicleEnterTileStatus VehicleEnter_Road(Vehicle *v, TileIndex tile, int x, int y) { - switch (GetRoadTileType(tile)) { - case ROAD_TILE_DEPOT: { - if (v->type != VEH_ROAD) break; - - RoadVehicle *rv = RoadVehicle::From(v); - if (rv->frame == RVC_DEPOT_STOP_FRAME && - _roadveh_enter_depot_dir[GetRoadDepotDirection(tile)] == rv->state) { - rv->state = RVSB_IN_DEPOT; - rv->vehstatus |= VS_HIDDEN; - rv->direction = ReverseDir(rv->direction); - if (rv->Next() == nullptr) VehicleEnterDepot(rv->First()); - rv->tile = tile; - - InvalidateWindowData(WC_VEHICLE_DEPOT, rv->tile); - return VETSB_ENTERED_WORMHOLE; + if (GetRoadTileType(tile) != ROAD_TILE_DEPOT || v->type != VEH_ROAD) return VETSB_CONTINUE; + + if (IsExtendedRoadDepot(tile)) { + v = v->First(); + if (!IsExtendedRoadDepotTile(v->tile)) return VETSB_CONTINUE; + DepotID depot_id = GetDepotIndex(v->tile); + if (!v->current_order.IsType(OT_GOTO_DEPOT) || + v->current_order.GetDestination() != depot_id) { + return VETSB_CONTINUE; + } + for (Vehicle *u = v; u != nullptr; u = u->Next()) { + if (!IsExtendedRoadDepotTile(u->tile) || GetDepotIndex(u->tile) != depot_id) return VETSB_CONTINUE; + if (!IsDiagonalDirection(u->direction)) return VETSB_CONTINUE; + if (DiagDirToAxis(DirToDiagDir(u->direction)) != + DiagDirToAxis(GetRoadDepotDirection(v->tile))) { + return VETSB_CONTINUE; } - break; } - default: break; + /* Stop position on platform is half the front vehicle length of the road vehicle. */ + int stop_pos = RoadVehicle::From(v)->gcache.cached_veh_length / 2; + DiagDirection dir = DirToDiagDir(v->direction); + int depot_ahead = (GetPlatformLength(tile, dir, GetRoadTramType(RoadVehicle::From(v)->roadtype)) - 1) * TILE_SIZE; + if (depot_ahead > stop_pos) return VETSB_CONTINUE; + + x = v->x_pos & 0xF; + y = v->y_pos & 0xF; + + if (DiagDirToAxis(dir) != AXIS_X) Swap(x, y); + if (dir == DIAGDIR_SE || dir == DIAGDIR_SW) x = TILE_SIZE - x; + if (abs(stop_pos - x) <= 1) return VETSB_ENTERED_DEPOT_PLATFORM; + } else { + RoadVehicle *rv = RoadVehicle::From(v); + if (rv->frame == RVC_DEPOT_STOP_FRAME && + _roadveh_enter_depot_dir[GetRoadDepotDirection(tile)] == rv->state) { + rv->state = RVSB_IN_DEPOT; + rv->vehstatus |= VS_HIDDEN; + rv->direction = ReverseDir(rv->direction); + if (rv->Next() == nullptr) VehicleEnterDepot(rv->First()); + rv->tile = tile; + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(rv->tile)); + return VETSB_ENTERED_WORMHOLE; + } } + return VETSB_CONTINUE; } @@ -2288,14 +2535,14 @@ static void ChangeTileOwner_Road(TileIndex tile, Owner old_owner, Owner new_owne if (new_owner == INVALID_OWNER) { Command::Do(DC_EXEC | DC_BANKRUPT, tile); } else { - /* A road depot has two road bits. No need to dirty windows here, we'll redraw the whole screen anyway. */ - RoadType rt = GetRoadTypeRoad(tile); - if (rt == INVALID_ROADTYPE) rt = GetRoadTypeTram(tile); - Company::Get(old_owner)->infrastructure.road[rt] -= 2; - Company::Get(new_owner)->infrastructure.road[rt] += 2; - SetTileOwner(tile, new_owner); for (RoadTramType rtt : _roadtramtypes) { + RoadType rt = GetRoadTypeRoad(tile); + if (rt != INVALID_ROADTYPE) { + uint pieces = CountBits(GetRoadBits(tile, rtt)); + Company::Get(old_owner)->infrastructure.road[rt] -= pieces; + Company::Get(new_owner)->infrastructure.road[rt] += pieces; + } if (GetRoadOwner(tile, rtt) == old_owner) { SetRoadOwner(tile, rtt, new_owner); } @@ -2344,7 +2591,10 @@ static CommandCost TerraformTile_Road(TileIndex tile, DoCommandFlag flags, int z break; case ROAD_TILE_DEPOT: - if (AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, GetRoadDepotDirection(tile))) return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]); + if (AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, GetRoadDepotDirection(tile)) && + (!IsExtendedRoadDepot(tile) || AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, ReverseDiagDir(GetRoadDepotDirection(tile))))) { + return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]); + } break; case ROAD_TILE_NORMAL: { @@ -2521,8 +2771,6 @@ CommandCost CmdConvertRoad(DoCommandFlag flags, TileIndex tile, TileIndex area_s uint num_pieces = CountBits(GetAnyRoadBits(tile, rtt)); if (tt == MP_STATION && IsBayRoadStopTile(tile)) { num_pieces *= ROAD_STOP_TRACKBIT_FACTOR; - } else if (tt == MP_ROAD && IsRoadDepot(tile)) { - num_pieces *= ROAD_DEPOT_TRACKBIT_FACTOR; } found_convertible_road = true; @@ -2543,8 +2791,9 @@ CommandCost CmdConvertRoad(DoCommandFlag flags, TileIndex tile, TileIndex area_s if (IsRoadDepotTile(tile)) { /* Update build vehicle window related to this depot */ - InvalidateWindowData(WC_VEHICLE_DEPOT, tile); - InvalidateWindowData(WC_BUILD_VEHICLE, tile); + DepotID depot_id = GetDepotIndex(tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, depot_id); + InvalidateWindowData(WC_BUILD_VEHICLE, depot_id); } } } else { diff --git a/src/road_cmd.h b/src/road_cmd.h index 71883ddadac77..b7a54c5c4aa9a 100644 --- a/src/road_cmd.h +++ b/src/road_cmd.h @@ -13,6 +13,7 @@ #include "direction_type.h" #include "road_type.h" #include "command_type.h" +#include "depot_type.h" enum RoadStopClassID : uint16_t; @@ -22,7 +23,7 @@ void UpdateNearestTownForRoadTiles(bool invalidate); CommandCost CmdBuildLongRoad(DoCommandFlag flags, TileIndex end_tile, TileIndex start_tile, RoadType rt, Axis axis, DisallowedRoadDirections drd, bool start_half, bool end_half, bool is_ai); std::tuple CmdRemoveLongRoad(DoCommandFlag flags, TileIndex end_tile, TileIndex start_tile, RoadType rt, Axis axis, bool start_half, bool end_half); CommandCost CmdBuildRoad(DoCommandFlag flags, TileIndex tile, RoadBits pieces, RoadType rt, DisallowedRoadDirections toggle_drd, TownID town_id); -CommandCost CmdBuildRoadDepot(DoCommandFlag flags, TileIndex tile, RoadType rt, DiagDirection dir); +CommandCost CmdBuildRoadDepot(DoCommandFlag flags, TileIndex tile, RoadType rt, DiagDirection dir, bool adjacent, bool extended, bool half_start, bool half_end, DepotID join_to, TileIndex end_tile); CommandCost CmdConvertRoad(DoCommandFlag flags, TileIndex tile, TileIndex area_start, RoadType to_type); DEF_CMD_TRAIT(CMD_BUILD_LONG_ROAD, CmdBuildLongRoad, CMD_AUTO | CMD_NO_WATER | CMD_DEITY, CMDT_LANDSCAPE_CONSTRUCTION) @@ -33,7 +34,7 @@ DEF_CMD_TRAIT(CMD_CONVERT_ROAD, CmdConvertRoad, 0, CommandCallback CcPlaySound_CONSTRUCTION_OTHER; CommandCallback CcBuildRoadTunnel; -void CcRoadDepot(Commands cmd, const CommandCost &result, TileIndex tile, RoadType rt, DiagDirection dir); +void CcRoadDepot(Commands cmd, const CommandCost &result, TileIndex start_tile, RoadType rt, DiagDirection dir, bool adjacent, bool extended, bool half_start, bool half_end, DepotID join_to, TileIndex end_tile); void CcRoadStop(Commands cmd, const CommandCost &result, TileIndex tile, uint8_t width, uint8_t length, RoadStopType, bool is_drive_through, DiagDirection dir, RoadType, RoadStopClassID spec_class, uint16_t spec_index, StationID, bool); #endif /* ROAD_CMD_H */ diff --git a/src/road_gui.cpp b/src/road_gui.cpp index 27066a2144955..4b99c39f845d3 100644 --- a/src/road_gui.cpp +++ b/src/road_gui.cpp @@ -43,6 +43,7 @@ #include "picker_gui.h" #include "timer/timer.h" #include "timer/timer_game_calendar.h" +#include "depot_func.h" #include "widgets/road_widget.h" @@ -51,7 +52,7 @@ #include "safeguards.h" static void ShowRVStationPicker(Window *parent, RoadStopType rs); -static void ShowRoadDepotPicker(Window *parent); +static void ShowRoadDepotPicker(Window *parent, bool extended_depot); static void ShowBuildRoadWaypointPicker(Window *parent); static bool _remove_button_clicked; @@ -170,13 +171,46 @@ void ConnectRoadToStructure(TileIndex tile, DiagDirection direction) } } -void CcRoadDepot(Commands, const CommandCost &result, TileIndex tile, RoadType, DiagDirection dir) +void CcRoadDepot(Commands , const CommandCost &result, TileIndex start_tile, RoadType, DiagDirection orig_dir, bool, bool extended, bool half_start, bool half_end, DepotID, TileIndex end_tile) { if (result.Failed()) return; - if (_settings_client.sound.confirm) SndPlayTileFx(SND_1F_CONSTRUCTION_OTHER, tile); + if (_settings_client.sound.confirm) SndPlayTileFx(SND_1F_CONSTRUCTION_OTHER, start_tile); if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace(); - ConnectRoadToStructure(tile, dir); + + Axis axis = DiagDirToAxis(orig_dir); + uint start_coord; + uint end_coord; + bool build_start = true; + bool build_end = false; + + DiagDirection dir = orig_dir; + if (extended) { + build_start = half_end; + build_end = !half_start; + start_coord = axis == AXIS_X ? TileX(start_tile) : TileY(start_tile); + end_coord = axis == AXIS_X ? TileX(end_tile) : TileY(end_tile); + + dir = AxisToDiagDir(axis); + + /* Swap direction, also the half-tile drag var (bit 0 and 1) */ + if (start_coord > end_coord || start_coord == end_coord) { + dir = ReverseDiagDir(dir); + build_start = !build_start; + build_end = !build_end; + } + } + + + TileArea ta(start_tile, end_tile); + for (TileIndex tile : ta) { + if (build_start && !ta.Contains(tile + TileOffsByDiagDir(dir))) { + ConnectRoadToStructure(tile, dir); + } + if (build_end && !ta.Contains(tile + TileOffsByDiagDir(ReverseDiagDir(dir)))) { + ConnectRoadToStructure(tile, ReverseDiagDir(dir)); + } + } } /** @@ -368,6 +402,13 @@ struct BuildRoadToolbarWindow : Window { { if (_game_mode == GM_NORMAL && (this->IsWidgetLowered(WID_ROT_BUS_STATION) || this->IsWidgetLowered(WID_ROT_TRUCK_STATION))) SetViewportCatchmentStation(nullptr, true); if (_settings_client.gui.link_terraform_toolbar) CloseWindowById(WC_SCEN_LAND_GEN, 0, false); + + if (_game_mode == GM_NORMAL && + ((this->HasWidget(WID_ROT_DEPOT) && this->IsWidgetLowered(WID_ROT_DEPOT)) || + (this->HasWidget(WID_ROT_EXTENDED_DEPOT) && this->IsWidgetLowered(WID_ROT_EXTENDED_DEPOT)))) { + SetViewportHighlightDepot(INVALID_DEPOT, true); + } + this->Window::Close(); } @@ -384,6 +425,7 @@ struct BuildRoadToolbarWindow : Window { bool can_build = CanBuildVehicleInfrastructure(VEH_ROAD, rtt); this->SetWidgetsDisabledState(!can_build, WID_ROT_DEPOT, + WID_ROT_EXTENDED_DEPOT, WID_ROT_BUILD_WAYPOINT, WID_ROT_BUS_STATION, WID_ROT_TRUCK_STATION); @@ -397,12 +439,14 @@ struct BuildRoadToolbarWindow : Window { if (_game_mode != GM_EDITOR) { if (!can_build) { /* Show in the tooltip why this button is disabled. */ - this->GetWidget(WID_ROT_DEPOT)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); + if (this->HasWidget(WID_ROT_DEPOT)) this->GetWidget(WID_ROT_DEPOT)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); + if (this->HasWidget(WID_ROT_EXTENDED_DEPOT)) this->GetWidget(WID_ROT_EXTENDED_DEPOT)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); this->GetWidget(WID_ROT_BUILD_WAYPOINT)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); this->GetWidget(WID_ROT_BUS_STATION)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); this->GetWidget(WID_ROT_TRUCK_STATION)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); } else { - this->GetWidget(WID_ROT_DEPOT)->SetToolTip(rtt == RTT_ROAD ? STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_VEHICLE_DEPOT : STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAM_VEHICLE_DEPOT); + if (this->HasWidget(WID_ROT_DEPOT)) this->GetWidget(WID_ROT_DEPOT)->SetToolTip(rtt == RTT_ROAD ? STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_VEHICLE_DEPOT : STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAM_VEHICLE_DEPOT); + if (this->HasWidget(WID_ROT_EXTENDED_DEPOT)) this->GetWidget(WID_ROT_EXTENDED_DEPOT)->SetToolTip(rtt == RTT_ROAD ? STR_ROAD_TOOLBAR_TOOLTIP_BUILD_EXTENDED_ROAD_VEHICLE_DEPOT : STR_ROAD_TOOLBAR_TOOLTIP_BUILD_EXTENDED_TRAM_VEHICLE_DEPOT); this->GetWidget(WID_ROT_BUILD_WAYPOINT)->SetToolTip(rtt == RTT_ROAD ? STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_ROAD_TO_WAYPOINT : STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_TRAM_TO_WAYPOINT); this->GetWidget(WID_ROT_BUS_STATION)->SetToolTip(rtt == RTT_ROAD ? STR_ROAD_TOOLBAR_TOOLTIP_BUILD_BUS_STATION : STR_ROAD_TOOLBAR_TOOLTIP_BUILD_PASSENGER_TRAM_STATION); this->GetWidget(WID_ROT_TRUCK_STATION)->SetToolTip(rtt == RTT_ROAD ? STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRUCK_LOADING_BAY : STR_ROAD_TOOLBAR_TOOLTIP_BUILD_CARGO_TRAM_STATION); @@ -427,7 +471,8 @@ struct BuildRoadToolbarWindow : Window { this->GetWidget(WID_ROT_ROAD_Y)->widget_data = rti->gui_sprites.build_y_road; this->GetWidget(WID_ROT_AUTOROAD)->widget_data = rti->gui_sprites.auto_road; if (_game_mode != GM_EDITOR) { - this->GetWidget(WID_ROT_DEPOT)->widget_data = rti->gui_sprites.build_depot; + if (this->HasWidget(WID_ROT_DEPOT)) this->GetWidget(WID_ROT_DEPOT)->widget_data = rti->gui_sprites.build_depot; + if (this->HasWidget(WID_ROT_EXTENDED_DEPOT)) this->GetWidget(WID_ROT_EXTENDED_DEPOT)->widget_data = rti->gui_sprites.build_depot; } this->GetWidget(WID_ROT_CONVERT_ROAD)->widget_data = rti->gui_sprites.convert_road; this->GetWidget(WID_ROT_BUILD_TUNNEL)->widget_data = rti->gui_sprites.build_tunnel; @@ -538,8 +583,9 @@ struct BuildRoadToolbarWindow : Window { break; case WID_ROT_DEPOT: - if (HandlePlacePushButton(this, WID_ROT_DEPOT, this->rti->cursor.depot, HT_RECT)) { - ShowRoadDepotPicker(this); + case WID_ROT_EXTENDED_DEPOT: + if (HandlePlacePushButton(this, widget, this->rti->cursor.depot, HT_RECT)) { + ShowRoadDepotPicker(this, widget == WID_ROT_EXTENDED_DEPOT); this->last_started_action = widget; } break; @@ -636,9 +682,19 @@ struct BuildRoadToolbarWindow : Window { break; case WID_ROT_DEPOT: - Command::Post(this->rti->strings.err_depot, CcRoadDepot, - tile, _cur_roadtype, _road_depot_orientation); + case WID_ROT_EXTENDED_DEPOT: { + CloseWindowById(WC_SELECT_DEPOT, VEH_ROAD); + + _place_road_dir = DiagDirToAxis(_road_depot_orientation); + _place_road_start_half_x = (_place_road_dir == AXIS_X) && (_tile_fract_coords.x >= 8); + _place_road_start_half_y = (_place_road_dir == AXIS_Y) && (_tile_fract_coords.y >= 8); + + VpSetPlaceSizingLimit(_settings_game.depot.depot_spread); + ViewportPlaceMethod vpm = VPM_X_AND_Y_LIMITED; + if (this->last_started_action == WID_ROT_DEPOT) vpm = (DiagDirToAxis(_road_depot_orientation) == AXIS_X) ? VPM_X_LIMITED : VPM_Y_LIMITED; + VpStartPlaceSizing(tile, vpm, DDSP_BUILD_DEPOT); break; + } case WID_ROT_BUILD_WAYPOINT: PlaceRoad_Waypoint(tile); @@ -673,6 +729,12 @@ struct BuildRoadToolbarWindow : Window { { if (_game_mode != GM_EDITOR && (this->IsWidgetLowered(WID_ROT_BUS_STATION) || this->IsWidgetLowered(WID_ROT_TRUCK_STATION))) SetViewportCatchmentStation(nullptr, true); + if (_game_mode == GM_NORMAL && + ((this->HasWidget(WID_ROT_DEPOT) && this->IsWidgetLowered(WID_ROT_DEPOT)) || + (this->HasWidget(WID_ROT_EXTENDED_DEPOT) && this->IsWidgetLowered(WID_ROT_EXTENDED_DEPOT)))) { + SetViewportHighlightDepot(INVALID_DEPOT, true); + } + this->RaiseButtons(); this->SetWidgetDisabledState(WID_ROT_REMOVE, true); this->SetWidgetDirty(WID_ROT_REMOVE); @@ -687,6 +749,7 @@ struct BuildRoadToolbarWindow : Window { CloseWindowById(WC_BUILD_DEPOT, TRANSPORT_ROAD); CloseWindowById(WC_BUILD_WAYPOINT, TRANSPORT_ROAD); CloseWindowById(WC_SELECT_STATION, 0); + CloseWindowById(WC_SELECT_DEPOT, VEH_ROAD); CloseWindowByClass(WC_BUILD_BRIDGE); } @@ -719,7 +782,14 @@ struct BuildRoadToolbarWindow : Window { _place_road_dir = AXIS_Y; _place_road_end_half = pt.y & 8; } + break; + case DDSP_BUILD_DEPOT: + if (_place_road_dir == AXIS_X) { + _place_road_end_half = pt.x & 8; + } else { + _place_road_end_half = pt.y & 8; + } break; default: @@ -805,6 +875,21 @@ struct BuildRoadToolbarWindow : Window { } break; + case DDSP_BUILD_DEPOT: { + StringID error_string = this->rti->strings.err_depot; + bool adjacent = _ctrl_pressed; + bool extended = last_started_action == WID_ROT_EXTENDED_DEPOT; + bool half_start = _place_road_start_half_x || _place_road_start_half_y; + bool half_end = _place_road_end_half; + + auto proc = [=](DepotID join_to) -> bool { + return Command::Post(error_string, CcRoadDepot, start_tile, _cur_roadtype, _road_depot_orientation, adjacent, extended, half_start, half_end, join_to, end_tile); + }; + + ShowSelectDepotIfNeeded(TileArea(start_tile, end_tile), proc, VEH_ROAD); + break; + } + case DDSP_CONVERT_ROAD: Command::Post(rti->strings.err_convert_road, CcPlaySound_CONSTRUCTION_OTHER, end_tile, start_tile, _cur_roadtype); break; @@ -900,6 +985,27 @@ struct BuildRoadToolbarWindow : Window { }, TramToolbarGlobalHotkeys}; }; +/** + * Add the depot icons depending on availability of construction. + * @return Panel with road depot buttons. + */ +static std::unique_ptr MakeNWidgetRoadDepot() +{ + auto hor = std::make_unique(); + + if (HasBit(_settings_game.depot.road_depot_types, 0)) { + /* Add the widget for building standard road depots. */ + hor->Add(std::make_unique(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_DEPOT, SPR_IMG_ROAD_DEPOT, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_VEHICLE_DEPOT)); + } + + if (HasBit(_settings_game.depot.road_depot_types, 1)) { + /* Add the widget for building extended road depots. */ + hor->Add(std::make_unique(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_EXTENDED_DEPOT, SPR_IMG_ROAD_DEPOT, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_EXTENDED_ROAD_VEHICLE_DEPOT)); + } + + return hor; +} + static constexpr NWidgetPart _nested_build_road_widgets[] = { NWidget(NWID_HORIZONTAL), NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN), @@ -915,8 +1021,7 @@ static constexpr NWidgetPart _nested_build_road_widgets[] = { SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_AUTOROAD, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_AUTOROAD), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_DEMOLISH), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC), - NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_DEPOT), - SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_DEPOT, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_VEHICLE_DEPOT), + NWidgetFunction(MakeNWidgetRoadDepot), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUILD_WAYPOINT), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_WAYPOINT, STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_ROAD_TO_WAYPOINT), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUS_STATION), @@ -960,8 +1065,7 @@ static constexpr NWidgetPart _nested_build_tramway_widgets[] = { SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_AUTOTRAM, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_AUTOTRAM), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_DEMOLISH), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC), - NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_DEPOT), - SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_DEPOT, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAM_VEHICLE_DEPOT), + NWidgetFunction(MakeNWidgetRoadDepot), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUILD_WAYPOINT), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_WAYPOINT, STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_TRAM_TO_WAYPOINT), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUS_STATION), @@ -1091,14 +1195,28 @@ Window *ShowBuildRoadScenToolbar(RoadType roadtype) } struct BuildRoadDepotWindow : public PickerWindowBase { - BuildRoadDepotWindow(WindowDesc &desc, Window *parent) : PickerWindowBase(desc, parent) + BuildRoadDepotWindow(WindowDesc &desc, Window *parent, bool extended_depot) : PickerWindowBase(desc, parent) { this->CreateNestedTree(); + /* Fix direction for extended depots. */ + if (extended_depot) { + switch (_road_depot_orientation) { + case DIAGDIR_NE: + _road_depot_orientation++; + break; + case DIAGDIR_NW: + _road_depot_orientation--; + break; + default: break; + } + } + this->LowerWidget(WID_BROD_DEPOT_NE + _road_depot_orientation); if (RoadTypeIsTram(_cur_roadtype)) { this->GetWidget(WID_BROD_CAPTION)->widget_data = STR_BUILD_DEPOT_TRAM_ORIENTATION_CAPTION; for (WidgetID i = WID_BROD_DEPOT_NE; i <= WID_BROD_DEPOT_NW; i++) { + if (!this->HasWidget(i)) continue; this->GetWidget(i)->tool_tip = STR_BUILD_DEPOT_TRAM_ORIENTATION_SELECT_TOOLTIP; } } @@ -1106,6 +1224,12 @@ struct BuildRoadDepotWindow : public PickerWindowBase { this->FinishInitNested(TRANSPORT_ROAD); } + void Close([[maybe_unused]] int data = 0) override + { + CloseWindowById(WC_SELECT_DEPOT, VEH_ROAD); + this->PickerWindowBase::Close(); + } + void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override { if (!IsInsideMM(widget, WID_BROD_DEPOT_NE, WID_BROD_DEPOT_NW + 1)) return; @@ -1135,6 +1259,7 @@ struct BuildRoadDepotWindow : public PickerWindowBase { case WID_BROD_DEPOT_NE: case WID_BROD_DEPOT_SW: case WID_BROD_DEPOT_SE: + CloseWindowById(WC_SELECT_DEPOT, VEH_ROAD); this->RaiseWidget(WID_BROD_DEPOT_NE + _road_depot_orientation); _road_depot_orientation = (DiagDirection)(widget - WID_BROD_DEPOT_NE); this->LowerWidget(WID_BROD_DEPOT_NE + _road_depot_orientation); @@ -1146,6 +1271,11 @@ struct BuildRoadDepotWindow : public PickerWindowBase { break; } } + + void OnRealtimeTick([[maybe_unused]] uint delta_ms) override + { + CheckRedrawDepotHighlight(this, VEH_ROAD); + } }; static constexpr NWidgetPart _nested_build_road_depot_widgets[] = { @@ -1174,9 +1304,29 @@ static WindowDesc _build_road_depot_desc( _nested_build_road_depot_widgets ); -static void ShowRoadDepotPicker(Window *parent) +static const NWidgetPart _nested_build_extended_road_depot_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN), + NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_BROD_CAPTION), SetDataTip(STR_BUILD_DEPOT_ROAD_ORIENTATION_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + EndContainer(), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), + NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1), SetPadding(WidgetDimensions::unscaled.picker), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROD_DEPOT_SW), SetMinimalSize(66, 50), SetFill(0, 0), SetDataTip(0x0, STR_BUILD_DEPOT_ROAD_ORIENTATION_SELECT_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROD_DEPOT_SE), SetMinimalSize(66, 50), SetFill(0, 0), SetDataTip(0x0, STR_BUILD_DEPOT_ROAD_ORIENTATION_SELECT_TOOLTIP), + EndContainer(), + EndContainer(), +}; + +static WindowDesc _build_extended_road_depot_desc( + WDP_AUTO, nullptr, 0, 0, + WC_BUILD_DEPOT, WC_BUILD_TOOLBAR, + WDF_CONSTRUCTION, + _nested_build_extended_road_depot_widgets +); + +static void ShowRoadDepotPicker(Window *parent, bool extended_depot) { - new BuildRoadDepotWindow(_build_road_depot_desc, parent); + new BuildRoadDepotWindow(extended_depot ? _build_extended_road_depot_desc : _build_road_depot_desc, parent, extended_depot); } template diff --git a/src/road_map.cpp b/src/road_map.cpp index 66fe49010f6e1..9fb6b03dacb3c 100644 --- a/src/road_map.cpp +++ b/src/road_map.cpp @@ -38,9 +38,11 @@ RoadBits GetAnyRoadBits(Tile tile, RoadTramType rtt, bool straight_tunnel_bridge case MP_ROAD: switch (GetRoadTileType(tile)) { default: - case ROAD_TILE_NORMAL: return GetRoadBits(tile, rtt); - case ROAD_TILE_CROSSING: return GetCrossingRoadBits(tile); - case ROAD_TILE_DEPOT: return DiagDirToRoadBits(GetRoadDepotDirection(tile)); + case ROAD_TILE_NORMAL: + case ROAD_TILE_DEPOT: + return GetRoadBits(tile, rtt); + case ROAD_TILE_CROSSING: + return GetCrossingRoadBits(tile); } case MP_STATION: diff --git a/src/road_map.h b/src/road_map.h index 06c000384cd71..ac184a25f228b 100644 --- a/src/road_map.h +++ b/src/road_map.h @@ -118,16 +118,38 @@ debug_inline static bool IsRoadDepotTile(Tile t) return IsTileType(t, MP_ROAD) && IsRoadDepot(t); } +/** + * Return whether a road depot tile is an extended one. + * @param t Tile to query. + * @return True if extended road depot tile. + */ +static inline bool IsExtendedRoadDepot(Tile t) +{ + assert(IsTileType(t, MP_ROAD)); + assert(IsRoadDepot(t)); + return HasBit(t.m5(), 5); +} + +/** + * Return whether a tile is an extended road depot tile. + * @param t Tile to query. + * @return True if extended road depot tile. + */ +static inline bool IsExtendedRoadDepotTile(Tile t) +{ + return IsTileType(t, MP_ROAD) && IsRoadDepot(t) && IsExtendedRoadDepot(t); +} + /** * Get the present road bits for a specific road type. * @param t The tile to query. - * @param rt Road type. - * @pre IsNormalRoad(t) + * @param rtt Road tram type. + * @pre IsNormalRoad(t) || IsRoadDepotTile(t) * @return The present road bits for the road type. */ inline RoadBits GetRoadBits(Tile t, RoadTramType rtt) { - assert(IsNormalRoad(t)); + assert(IsNormalRoad(t) || IsRoadDepotTile(t)); if (rtt == RTT_TRAM) return (RoadBits)GB(t.m3(), 0, 4); return (RoadBits)GB(t.m5(), 0, 4); } @@ -152,7 +174,7 @@ inline RoadBits GetAllRoadBits(Tile tile) */ inline void SetRoadBits(Tile t, RoadBits r, RoadTramType rtt) { - assert(IsNormalRoad(t)); // XXX incomplete + assert(IsNormalRoad(t) || IsRoadDepotTile(t)); if (rtt == RTT_TRAM) { SB(t.m3(), 0, 4, r); } else { @@ -556,19 +578,28 @@ inline void TerminateRoadWorks(Tile t) SB(t.m7(), 0, 4, 0); } +/** + * Set the direction of the exit of a road depot. + * @param t The tile to query. + * @return Diagonal direction of the depot exit. + */ +static inline void SetRoadDepotDirection(Tile t, DiagDirection dir) +{ + assert(IsRoadDepot(t)); + SB(t.m6(), 6, 2, dir); +} /** - * Get the direction of the exit of a road depot. + * Get the direction of the exit of a road depot (or the image of the depot for extended road depots). * @param t The tile to query. * @return Diagonal direction of the depot exit. */ inline DiagDirection GetRoadDepotDirection(Tile t) { assert(IsRoadDepot(t)); - return (DiagDirection)GB(t.m5(), 0, 2); + return (DiagDirection)GB(t.m6(), 6, 2); } - RoadBits GetAnyRoadBits(Tile tile, RoadTramType rtt, bool straight_tunnel_bridge_entrance = false); /** @@ -680,7 +711,7 @@ inline void MakeRoadCrossing(Tile t, Owner road, Owner tram, Owner rail, Axis ro inline void SetRoadDepotExitDirection(Tile tile, DiagDirection dir) { assert(IsRoadDepotTile(tile)); - SB(tile.m5(), 0, 2, dir); + SB(tile.m6(), 6, 2, dir); } /** @@ -698,8 +729,9 @@ inline void MakeRoadDepot(Tile tile, Owner owner, DepotID depot_id, DiagDirectio tile.m2() = depot_id; tile.m3() = 0; tile.m4() = INVALID_ROADTYPE; - tile.m5() = ROAD_TILE_DEPOT << 6 | dir; - SB(tile.m6(), 2, 4, 0); + tile.m5() = ROAD_TILE_DEPOT << 6; + SB(tile.m6(), 0, 6, 0); + SB(tile.m6(), 6, 2, dir); tile.m7() = owner; tile.m8() = INVALID_ROADTYPE << 6; SetRoadType(tile, GetRoadTramType(rt), rt); diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index 957dda01caa29..020b1ea690423 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -37,6 +37,7 @@ #include "framerate_type.h" #include "roadveh_cmd.h" #include "road_cmd.h" +#include "depot_base.h" #include "table/strings.h" @@ -250,6 +251,62 @@ void RoadVehUpdateCache(RoadVehicle *v, bool same_length) v->vcache.cached_max_speed = (max_speed != 0) ? max_speed * 4 : RoadVehInfo(v->engine_type)->max_speed; } +/** + * Find an adequate tile for placing an engine. + * @param[in,out] tile A tile of the depot. + * @param[in,out] is_exit_facing_south Whether the depot tile is facing south. + * @param e Engine to be built. + * @param already_built Whether the vehicle already exists (for vehicle replacement). + * @return CommandCost() or an error message if the depot has no appropriate tiles. + */ +CommandCost FindDepotTileForPlacingEngine(TileIndex &tile, bool &is_exit_facing_south, const Engine *e, bool already_built) +{ + assert(IsRoadDepotTile(tile)); + + Depot *dep = Depot:: GetByTile(tile); + + /* Check that the vehicle can drive on some tile of the depot */ + RoadType rt = e->u.road.roadtype; + const RoadTypeInfo *rti = GetRoadTypeInfo(rt); + if ((dep->r_types.road_types & rti->powered_roadtypes) == 0) return_cmd_error(STR_ERROR_DEPOT_WRONG_DEPOT_TYPE); + + /* Use same tile if possible when replacing or trying to leave the depot. */ + if (HasTileAnyRoadType(tile, rti->powered_roadtypes) && already_built) return CommandCost(); + + for (auto t : dep->depot_tiles) { + if (!HasTileAnyRoadType(t, rti->powered_roadtypes)) continue; + if (!IsExtendedDepot(t)) return CommandCost(); + if (GetDepotReservation(t, is_exit_facing_south) == DEPOT_RESERVATION_EMPTY) { + tile = t; + return CommandCost(); + } else if (GetDepotReservation(t, !is_exit_facing_south) == DEPOT_RESERVATION_EMPTY) { + is_exit_facing_south = !is_exit_facing_south; + tile = t; + return CommandCost(); + } + } + + return_cmd_error(STR_ERROR_DEPOT_FULL_DEPOT); +} + +DiagDirection GetRoadDepotExit(TileIndex tile, RoadTramType rtt, DiagDirection dir) +{ + assert(IsRoadDepot(tile)); + RoadBits rb = GetRoadBits(tile, rtt); + if ((rb & DiagDirToRoadBits(dir)) != ROAD_NONE) return dir; + if (rb & ROAD_SE) return DIAGDIR_SE; + if (rb & ROAD_SW) return DIAGDIR_SW; + if (rb & ROAD_NE) return DIAGDIR_NE; + if (rb & ROAD_NW) return DIAGDIR_NW; + return INVALID_DIAGDIR; +} + +struct RoadDriveEntry { + uint8_t x, y; +}; + +#include "table/roadveh_movement.h" + /** * Build a road vehicle. * @param flags type of operation. @@ -260,25 +317,39 @@ void RoadVehUpdateCache(RoadVehicle *v, bool same_length) */ CommandCost CmdBuildRoadVehicle(DoCommandFlag flags, TileIndex tile, const Engine *e, Vehicle **ret) { - /* Check that the vehicle can drive on the road in question */ + assert(IsRoadDepotTile(tile)); RoadType rt = e->u.road.roadtype; const RoadTypeInfo *rti = GetRoadTypeInfo(rt); - if (!HasTileAnyRoadType(tile, rti->powered_roadtypes)) return_cmd_error(STR_ERROR_DEPOT_WRONG_DEPOT_TYPE); + DiagDirection dir = GetRoadDepotExit(tile, RoadTramType(rt), GetRoadDepotDirection(tile)); + bool facing_south = IsValidDiagDirection(dir) ? IsDiagDirFacingSouth(dir) : false; + + if ((flags & DC_AUTOREPLACE) == 0) { + CommandCost check = FindDepotTileForPlacingEngine(tile, facing_south, e, false); + if (check.Failed()) return check; + dir = GetRoadDepotExit(tile, RoadTramType(rt), GetRoadDepotDirection(tile)); + } + + if (IsExtendedRoadDepotTile(tile) && facing_south != IsDiagDirFacingSouth(dir)) dir = ReverseDiagDir(dir); if (flags & DC_EXEC) { const RoadVehicleInfo *rvi = &e->u.road; RoadVehicle *v = new RoadVehicle(); *ret = v; - v->direction = DiagDirToDir(GetRoadDepotDirection(tile)); + v->direction = DiagDirToDir(dir); v->owner = _current_company; v->tile = tile; - int x = TileX(tile) * TILE_SIZE + TILE_SIZE / 2; - int y = TileY(tile) * TILE_SIZE + TILE_SIZE / 2; - v->x_pos = x; - v->y_pos = y; - v->z_pos = GetSlopePixelZ(x, y, true); + if (IsExtendedRoadDepotTile(tile)) { + const RoadDriveEntry *rdp = _road_drive_data[GetRoadTramType(rt)][(_settings_game.vehicle.road_side << RVS_DRIVE_SIDE) + DiagDirToDiagTrackdir(dir)]; + v->frame = RVC_DEPOT_START_FRAME; + v->x_pos = TileX(v->tile) * TILE_SIZE + (rdp[RVC_DEPOT_START_FRAME].x & 0xF); + v->y_pos = TileY(v->tile) * TILE_SIZE + (rdp[RVC_DEPOT_START_FRAME].y & 0xF); + } else { + v->x_pos = TileX(tile) * TILE_SIZE + TILE_SIZE / 2; + v->y_pos = TileY(tile) * TILE_SIZE + TILE_SIZE / 2; + } + v->z_pos = GetSlopePixelZ(v->x_pos, v->y_pos, true); v->state = RVSB_IN_DEPOT; v->vehstatus = VS_HIDDEN | VS_STOPPED | VS_DEFPAL; @@ -322,6 +393,7 @@ CommandCost CmdBuildRoadVehicle(DoCommandFlag flags, TileIndex tile, const Engin for (RoadVehicle *u = v; u != nullptr; u = u->Next()) { u->cargo_cap = u->GetEngine()->DetermineCapacity(u); u->refit_cap = 0; + u->state = RVSB_IN_DEPOT; v->InvalidateNewGRFCache(); u->InvalidateNewGRFCache(); } @@ -331,6 +403,12 @@ CommandCost CmdBuildRoadVehicle(DoCommandFlag flags, TileIndex tile, const Engin v->UpdatePosition(); + if (IsExtendedDepot(v->tile) && (flags & DC_AUTOREPLACE) == 0) { + v->vehstatus &= ~VS_HIDDEN; + UpdateExtendedDepotReservation(v, true); + v->UpdateViewport(true, true); + } + CheckConsistencyOfArticulatedVehicle(v); } @@ -616,34 +694,56 @@ struct RoadVehFindData { static Vehicle *EnumCheckRoadVehClose(Vehicle *v, void *data) { - static const int8_t dist_x[] = { -4, -8, -4, -1, 4, 8, 4, 1 }; - static const int8_t dist_y[] = { -4, -1, 4, 8, 4, 1, -4, -8 }; + if (v->type != VEH_ROAD || (v->vehstatus & VS_HIDDEN) == 0) return nullptr; RoadVehFindData *rvf = (RoadVehFindData*)data; + if (abs(v->z_pos - rvf->veh->z_pos) >= 6 || + v->direction != rvf->dir || + rvf->veh->First() == v->First()) return nullptr; + + static const int8_t dist_x[] = { -4, -8, -4, -1, 4, 8, 4, 1 }; + static const int8_t dist_y[] = { -4, -1, 4, 8, 4, 1, -4, -8 }; short x_diff = v->x_pos - rvf->x; short y_diff = v->y_pos - rvf->y; - if (v->type == VEH_ROAD && - !v->IsInDepot() && - abs(v->z_pos - rvf->veh->z_pos) < 6 && - v->direction == rvf->dir && - rvf->veh->First() != v->First() && - (dist_x[v->direction] >= 0 || (x_diff > dist_x[v->direction] && x_diff <= 0)) && - (dist_x[v->direction] <= 0 || (x_diff < dist_x[v->direction] && x_diff >= 0)) && - (dist_y[v->direction] >= 0 || (y_diff > dist_y[v->direction] && y_diff <= 0)) && - (dist_y[v->direction] <= 0 || (y_diff < dist_y[v->direction] && y_diff >= 0))) { - uint diff = abs(x_diff) + abs(y_diff); - - if (diff < rvf->best_diff || (diff == rvf->best_diff && v->index < rvf->best->index)) { - rvf->best = v; - rvf->best_diff = diff; - } + /* Check if vehicle is not close. */ + if ((dist_x[v->direction] < 0 && (x_diff > 0 || x_diff <= dist_x[v->direction]))) return nullptr; + if ((dist_x[v->direction] > 0 && (x_diff < 0 || x_diff >= dist_x[v->direction]))) return nullptr; + if ((dist_y[v->direction] < 0 && (y_diff > 0 || y_diff <= dist_y[v->direction]))) return nullptr; + if ((dist_y[v->direction] > 0 && (y_diff < 0 || y_diff >= dist_y[v->direction]))) return nullptr; + + uint diff = abs(x_diff) + abs(y_diff); + + if (diff < rvf->best_diff || (diff == rvf->best_diff && v->index < rvf->best->index)) { + rvf->best = v; + rvf->best_diff = diff; } return nullptr; } +/** + * Hide a stopped and visible road vehicle in an extended depot. + * @param v The road vehicle + * @pre v->IsStoppedInDepot() && IsExtendedRoadDepotTile(v->tile) + */ +static void LiftRoadVehicleInDepot(RoadVehicle *v) +{ + assert(v->IsStoppedInDepot()); + assert(IsExtendedRoadDepotTile(v->tile)); + for (RoadVehicle *rv = v; rv != nullptr; rv = rv->Next()) { + rv->vehstatus |= VS_HIDDEN; + rv->tile = v->tile; + rv->direction = v->direction; + rv->x_pos = v->x_pos; + rv->y_pos = v->y_pos; + rv->UpdatePosition(); + rv->Vehicle::UpdateViewport(true); + } + UpdateExtendedDepotReservation(v, false); +} + static RoadVehicle *RoadVehFindCloseTo(RoadVehicle *v, int x, int y, Direction dir, bool update_blocked_ctr = true) { RoadVehFindData rvf; @@ -675,6 +775,17 @@ static RoadVehicle *RoadVehFindCloseTo(RoadVehicle *v, int x, int y, Direction d if (update_blocked_ctr && ++front->blocked_ctr > 1480) return nullptr; + rvf.best = rvf.best->First(); + + /* If the best vehicle is a road vehicle stopped in an extended depot, + * it is in the way of the moving vehicle. Hide the stopped vehicle + * inside the depot. */ + if (rvf.best->IsStoppedInDepot()) { + assert(IsExtendedRoadDepotTile(rvf.best->tile)); + LiftRoadVehicleInDepot(RoadVehicle::From(rvf.best)); + return nullptr; + } + return RoadVehicle::From(rvf.best); } @@ -772,7 +883,7 @@ static Vehicle *EnumFindVehBlockingOvertake(Vehicle *v, void *data) { const OvertakeData *od = (OvertakeData*)data; - return (v->type == VEH_ROAD && v->First() == v && v != od->u && v != od->v) ? v : nullptr; + return (v->type == VEH_ROAD && v->First() == v && v != od->u && v != od->v && ((v->vehstatus & VS_HIDDEN) == 0)) ? v : nullptr; } /** @@ -809,6 +920,9 @@ static void RoadVehCheckOvertake(RoadVehicle *v, RoadVehicle *u) /* Don't overtake in stations */ if (IsTileType(v->tile, MP_STATION) || IsTileType(u->tile, MP_STATION)) return; + /* Don't overtake in road depot platforms. */ + if (IsExtendedRoadDepotTile(v->tile)) return; + /* For now, articulated road vehicles can't overtake anything. */ if (v->HasArticulatedPart()) return; @@ -890,9 +1004,17 @@ static Trackdir RoadFindPathToDest(RoadVehicle *v, TileIndex tile, DiagDirection TrackdirBits trackdirs = TrackStatusToTrackdirBits(ts); if (IsTileType(tile, MP_ROAD)) { - if (IsRoadDepot(tile) && (!IsTileOwner(tile, v->owner) || GetRoadDepotDirection(tile) == enterdir)) { - /* Road depot owned by another company or with the wrong orientation */ - trackdirs = TRACKDIR_BIT_NONE; + if (IsRoadDepot(tile)) { + if (!IsTileOwner(tile, v->owner)) { + trackdirs = TRACKDIR_BIT_NONE; + } else if (IsExtendedRoadDepotTile(tile)) { + if (tile != v->tile) { + RoadBits rb = GetRoadBits(tile, GetRoadTramType(v->roadtype)) & DiagDirToRoadBits(ReverseDiagDir(enterdir)); + if (rb == ROAD_NONE) trackdirs = TRACKDIR_BIT_NONE; + } + } else if (GetRoadDepotDirection(tile) == enterdir) { // Standard depot + trackdirs = TRACKDIR_BIT_NONE; + } } } else if (IsTileType(tile, MP_STATION) && IsBayRoadStopTile(tile)) { /* Standard road stop (drive-through stops are treated as normal road) */ @@ -994,58 +1116,118 @@ found_best_track:; return best_track; } -struct RoadDriveEntry { - uint8_t x, y; -}; +void HandleRoadVehicleEnterDepot(RoadVehicle *v) +{ + assert(IsRoadDepotTile(v->tile)); -#include "table/roadveh_movement.h" + if (IsExtendedRoadDepot(v->tile)) { + assert(v == v->First()); + for (RoadVehicle *u = v; u != nullptr; u = u->Next()) { + assert(u->direction == v->direction); + assert(IsExtendedRoadDepotTile(u->tile)); + assert(GetDepotIndex(u->tile) == GetDepotIndex(v->tile)); + u->state = RVSB_IN_DEPOT; + u->cur_speed = 0; + u->UpdateViewport(true, true); // revise: probably unneded + } + + v->StartService(); + UpdateExtendedDepotReservation(v, true); + + SetWindowClassesDirty(WC_ROADVEH_LIST); + SetWindowDirty(WC_VEHICLE_VIEW, v->index); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); + } else { + VehicleEnterDepot(v); + } +} bool RoadVehLeaveDepot(RoadVehicle *v, bool first) { /* Don't leave unless v and following wagons are in the depot. */ for (const RoadVehicle *u = v; u != nullptr; u = u->Next()) { - if (u->state != RVSB_IN_DEPOT || u->tile != v->tile) return false; + if (!u->IsInDepot()) return false; + } + + bool visible_vehicle = first && (v->vehstatus & VS_HIDDEN) == 0; + + if (first && (v->vehstatus & VS_HIDDEN) != 0) { + TileIndex new_tile = v->tile; + bool facing_south = IsDiagDirFacingSouth(DirToDiagDir(v->direction)); + if (FindDepotTileForPlacingEngine(new_tile, facing_south, Engine::Get(v->engine_type), true).Failed()) return false; + if (IsExtendedDepot(v->tile)) { + UpdateExtendedDepotReservation(v, false); + v->tile = new_tile; + UpdateExtendedDepotReservation(v, true); + } + + DiagDirection dir = GetRoadDepotExit(v->tile, RoadTramType(v->roadtype), DirToDiagDir(v->direction)); + if (facing_south != IsDiagDirFacingSouth(dir)) dir = ReverseDiagDir(dir); + assert(dir != INVALID_DIAGDIR); + for (Vehicle *u = v; u != nullptr; u = u->Next()) { + u->direction = DiagDirToDir(dir); + u->tile = v->tile; + } } - DiagDirection dir = GetRoadDepotDirection(v->tile); - v->direction = DiagDirToDir(dir); + int x = v->x_pos; + int y = v->y_pos; + Trackdir tdir = v->GetVehicleTrackdir(); - Trackdir tdir = DiagDirToDiagTrackdir(dir); - const RoadDriveEntry *rdp = _road_drive_data[GetRoadTramType(v->roadtype)][(_settings_game.vehicle.road_side << RVS_DRIVE_SIDE) + tdir]; + if ((v->vehstatus & VS_HIDDEN) != 0) { + const RoadDriveEntry *rdp = _road_drive_data[GetRoadTramType(v->roadtype)][(_settings_game.vehicle.road_side << RVS_DRIVE_SIDE) + tdir]; - int x = TileX(v->tile) * TILE_SIZE + (rdp[RVC_DEPOT_START_FRAME].x & 0xF); - int y = TileY(v->tile) * TILE_SIZE + (rdp[RVC_DEPOT_START_FRAME].y & 0xF); + x = TileX(v->tile) * TILE_SIZE + (rdp[RVC_DEPOT_START_FRAME].x & 0xF); + y = TileY(v->tile) * TILE_SIZE + (rdp[RVC_DEPOT_START_FRAME].y & 0xF); + } if (first) { /* We are leaving a depot, but have to go to the exact same one; re-enter */ - if (v->current_order.IsType(OT_GOTO_DEPOT) && v->tile == v->dest_tile) { - VehicleEnterDepot(v); + if (v->current_order.IsType(OT_GOTO_DEPOT) && + IsRoadDepotTile(v->tile) && + v->current_order.GetDestination() == GetDepotIndex(v->tile)) { + if (IsExtendedRoadDepot(v->tile)) { + v->StartService(); + } else { + VehicleEnterDepot(v); + } return true; } - - if (RoadVehFindCloseTo(v, x, y, v->direction, false) != nullptr) return true; - + if ((v->vehstatus & VS_HIDDEN) != 0 && RoadVehFindCloseTo(v, x, y, v->direction, false) != nullptr) return true; VehicleServiceInDepot(v); + v->LeaveUnbunchingDepot(); StartRoadVehSound(v); - - /* Vehicle is about to leave a depot */ + /* Vehicle is about to leave a depot. */ v->cur_speed = 0; } - v->vehstatus &= ~VS_HIDDEN; v->state = tdir; - v->frame = RVC_DEPOT_START_FRAME; - v->x_pos = x; - v->y_pos = y; + if ((v->vehstatus & VS_HIDDEN) != 0) { + v->vehstatus &= ~VS_HIDDEN; + v->x_pos = x; + v->y_pos = y; + v->frame = RVC_DEPOT_START_FRAME; + } else if (v->Next() != nullptr && (v->Next()->vehstatus & VS_HIDDEN) == 0){ + for (RoadVehicle *u = v->Next(); u != nullptr; u = u->Next()) { + u->state = DiagDirToDiagTrackdir(DirToDiagDir(u->direction)); + u->UpdatePosition(); + u->UpdateInclination(true, true); + } + } + v->UpdatePosition(); v->UpdateInclination(true, true); - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + if (first && IsExtendedDepot(v->tile)) { + UpdateExtendedDepotReservation(v, false); + } + + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); - return true; + return !visible_vehicle; } static Trackdir FollowPreviousRoadVehicle(const RoadVehicle *v, const RoadVehicle *prev, TileIndex tile, DiagDirection entry_dir, bool already_reversed) @@ -1065,7 +1247,7 @@ static Trackdir FollowPreviousRoadVehicle(const RoadVehicle *v, const RoadVehicl if (IsTileType(tile, MP_TUNNELBRIDGE)) { diag_dir = GetTunnelBridgeDirection(tile); } else if (IsRoadDepotTile(tile)) { - diag_dir = ReverseDiagDir(GetRoadDepotDirection(tile)); + diag_dir = ReverseDiagDir(IsExtendedRoadDepot(tile) ? DirToDiagDir(v->direction) : GetRoadDepotDirection(tile)); } if (diag_dir == INVALID_DIAGDIR) return INVALID_TRACKDIR; @@ -1133,6 +1315,53 @@ static bool CanBuildTramTrackOnTile(CompanyID c, TileIndex t, RoadType rt, RoadB return ret.Succeeded(); } +/** Check whether there is a close vehicle ahead and act as needed. + * @param v Moving vehicle + * @param x x coordinate to check + * @param y y coordinate to check + * @param dir direction of the vehicle + * @return whether a close vehicle is found. + */ +bool CheckCloseVehicle(RoadVehicle *v, int x, int y, Direction dir) +{ + if (!v->IsFrontEngine() || IsInsideMM(v->state, RVSB_IN_ROAD_STOP, RVSB_IN_ROAD_STOP_END)) return false; + /* Vehicle is not in a road stop. + * Check for another vehicle to overtake */ + RoadVehicle *u = RoadVehFindCloseTo(v, x, y, dir); + + if (u == nullptr) return false; + assert(u == u->First()); + + /* There is a vehicle in front overtake it if possible */ + if (v->overtaking == 0) RoadVehCheckOvertake(v, u); + if (v->overtaking == 0) v->cur_speed = u->cur_speed; + + /* In case we are in a road depot platform, why not try to start servicing? */ + if (IsExtendedRoadDepotTile(v->tile) && v->current_order.IsType(OT_GOTO_DEPOT)) { + DepotID depot_id = GetDepotIndex(v->tile); + if (v->current_order.GetDestination() != depot_id) return true; + if (!u->IsInDepot() || GetDepotIndex(u->tile) != depot_id) return true; + for (u = v; u != nullptr; u = u->Next()) { + if (!IsExtendedRoadDepotTile(u->tile) || GetDepotIndex(u->tile) != depot_id) return true; + if (v->direction != u->direction) return true; + } + HandleRoadVehicleEnterDepot(v); + return true; + } + + /* In case an RV is stopped in a road stop, why not try to load? */ + if (v->cur_speed == 0 && IsInsideMM(v->state, RVSB_IN_DT_ROAD_STOP, RVSB_IN_DT_ROAD_STOP_END) && + v->current_order.ShouldStopAtStation(v, GetStationIndex(v->tile)) && + v->owner == GetTileOwner(v->tile) && !v->current_order.IsType(OT_LEAVESTATION) && + GetRoadStopType(v->tile) == (v->IsBus() ? ROADSTOP_BUS : ROADSTOP_TRUCK)) { + Station *st = Station::GetByTile(v->tile); + v->last_station_visited = st->index; + RoadVehArrivesAt(v, st); + v->BeginLoading(); + } + return true; +} + bool IndividualRoadVehicleController(RoadVehicle *v, const RoadVehicle *prev) { if (v->overtaking != 0) { @@ -1161,7 +1390,8 @@ bool IndividualRoadVehicleController(RoadVehicle *v, const RoadVehicle *prev) if (v->IsFrontEngine()) { const Vehicle *u = RoadVehFindCloseTo(v, gp.x, gp.y, v->direction); if (u != nullptr) { - v->cur_speed = u->First()->cur_speed; + assert(u == u->First()); + v->cur_speed = u->cur_speed; return false; } } @@ -1190,18 +1420,21 @@ bool IndividualRoadVehicleController(RoadVehicle *v, const RoadVehicle *prev) (_settings_game.vehicle.road_side << RVS_DRIVE_SIDE)) ^ v->overtaking][v->frame + 1]; if (rd.x & RDE_NEXT_TILE) { - TileIndex tile = v->tile + TileOffsByDiagDir((DiagDirection)(rd.x & 3)); + DiagDirection diag_dir = (DiagDirection)(rd.x & 3); + TileIndex tile = v->tile + TileOffsByDiagDir(diag_dir); Trackdir dir; + bool extended_depot_turn = IsExtendedRoadDepotTile(v->tile) && + (GetRoadBits(v->tile, GetRoadTramType(v->roadtype)) & DiagDirToRoadBits(diag_dir)) == ROAD_NONE; if (v->IsFrontEngine()) { /* If this is the front engine, look for the right path. */ - if (HasTileAnyRoadType(tile, v->compatible_roadtypes)) { - dir = RoadFindPathToDest(v, tile, (DiagDirection)(rd.x & 3)); + if (HasTileAnyRoadType(tile, v->compatible_roadtypes) && !extended_depot_turn) { + dir = RoadFindPathToDest(v, tile, diag_dir); } else { - dir = _road_reverse_table[(DiagDirection)(rd.x & 3)]; + dir = _road_reverse_table[diag_dir]; } } else { - dir = FollowPreviousRoadVehicle(v, prev, tile, (DiagDirection)(rd.x & 3), false); + dir = FollowPreviousRoadVehicle(v, prev, tile, diag_dir, false); } if (dir == INVALID_TRACKDIR) { @@ -1228,7 +1461,10 @@ bool IndividualRoadVehicleController(RoadVehicle *v, const RoadVehicle *prev) case TRACKDIR_RVREV_SW: needed = ROAD_NE; break; case TRACKDIR_RVREV_NW: needed = ROAD_SE; break; } - if ((v->Previous() != nullptr && v->Previous()->tile == tile) || + if (extended_depot_turn) { + tile = v->tile; + start_frame = RVC_TURN_AROUND_START_FRAME_SHORT_TRAM; + } else if ((v->Previous() != nullptr && v->Previous()->tile == tile) || (v->IsFrontEngine() && IsNormalRoadTile(tile) && !HasRoadWorks(tile) && HasTileAnyRoadType(tile, v->compatible_roadtypes) && (needed & GetRoadBits(tile, RTT_TRAM)) != ROAD_NONE)) { @@ -1245,8 +1481,9 @@ bool IndividualRoadVehicleController(RoadVehicle *v, const RoadVehicle *prev) } else if (!v->IsFrontEngine() || !CanBuildTramTrackOnTile(v->owner, tile, v->roadtype, needed) || ((~needed & GetAnyRoadBits(v->tile, RTT_TRAM, false)) == ROAD_NONE)) { /* * Taking the 'small' corner for trams only happens when: - * - We are not the from vehicle of an articulated tram. + * - We are not the front vehicle of an articulated tram. * - Or when the company cannot build on the next tile. + * - Or when the extended depot doesn't have the appropriate tram bit to continue. * * The 'small' corner means that the vehicle is on the end of a * tram track and needs to start turning there. To do this properly @@ -1279,7 +1516,8 @@ bool IndividualRoadVehicleController(RoadVehicle *v, const RoadVehicle *prev) if (v->IsFrontEngine()) { const Vehicle *u = RoadVehFindCloseTo(v, x, y, new_dir); if (u != nullptr) { - v->cur_speed = u->First()->cur_speed; + assert(u == u->First()); + v->cur_speed = u->cur_speed; /* We might be blocked, prevent pathfinding rerun as we already know where we are heading to. */ v->path.tile.push_front(tile); v->path.td.push_front(dir); @@ -1288,6 +1526,11 @@ bool IndividualRoadVehicleController(RoadVehicle *v, const RoadVehicle *prev) } uint32_t r = VehicleEnterTile(v, tile, x, y); + if (HasBit(r, VETS_ENTERED_DEPOT_PLATFORM) && v->Next() == nullptr && v == v->First()) { + HandleRoadVehicleEnterDepot(RoadVehicle::From(v)); + return false; + } + if (HasBit(r, VETS_CANNOT_ENTER)) { if (!IsTileType(tile, MP_TUNNELBRIDGE)) { v->cur_speed = 0; @@ -1344,6 +1587,9 @@ bool IndividualRoadVehicleController(RoadVehicle *v, const RoadVehicle *prev) } v->x_pos = x; v->y_pos = y; + if (prev != nullptr && prev->IsInDepot() && (prev->vehstatus & VS_HIDDEN) == 0) { + v->state = RVSB_IN_DEPOT; + } v->UpdatePosition(); RoadZPosAffectSpeed(v, v->UpdateInclination(true, true)); return true; @@ -1354,7 +1600,7 @@ bool IndividualRoadVehicleController(RoadVehicle *v, const RoadVehicle *prev) Trackdir dir; uint turn_around_start_frame = RVC_TURN_AROUND_START_FRAME; - if (RoadTypeIsTram(v->roadtype) && !IsRoadDepotTile(v->tile) && HasExactlyOneBit(GetAnyRoadBits(v->tile, RTT_TRAM, true))) { + if (RoadTypeIsTram(v->roadtype) && !IsRoadDepotTile(v->tile) && !IsExtendedRoadDepotTile(v->tile) && HasExactlyOneBit(GetAnyRoadBits(v->tile, RTT_TRAM, true))) { /* * The tram is turning around with one tram 'roadbit'. This means that * it is using the 'big' corner 'drive data'. However, to support the @@ -1395,7 +1641,8 @@ bool IndividualRoadVehicleController(RoadVehicle *v, const RoadVehicle *prev) if (v->IsFrontEngine()) { const Vehicle *u = RoadVehFindCloseTo(v, x, y, new_dir); if (u != nullptr) { - v->cur_speed = u->First()->cur_speed; + assert(u == u->First()); + v->cur_speed = u->cur_speed; /* We might be blocked, prevent pathfinding rerun as we already know where we are heading to. */ v->path.tile.push_front(v->tile); v->path.td.push_front(dir); @@ -1419,6 +1666,9 @@ bool IndividualRoadVehicleController(RoadVehicle *v, const RoadVehicle *prev) v->x_pos = x; v->y_pos = y; + if (prev != nullptr && prev->IsInDepot() && (prev->vehstatus & VS_HIDDEN) == 0) { + v->state = RVSB_IN_DEPOT; + } v->UpdatePosition(); RoadZPosAffectSpeed(v, v->UpdateInclination(true, true)); return true; @@ -1428,8 +1678,10 @@ bool IndividualRoadVehicleController(RoadVehicle *v, const RoadVehicle *prev) * it's on a depot tile, check if it's time to activate the next vehicle in * the chain yet. */ if (v->Next() != nullptr && IsRoadDepotTile(v->tile)) { - if (v->frame == v->gcache.cached_veh_length + RVC_DEPOT_START_FRAME) { - RoadVehLeaveDepot(v->Next(), false); + if ((v->Next()->vehstatus & VS_HIDDEN)) { + if (v->frame == v->gcache.cached_veh_length + RVC_DEPOT_START_FRAME) { + RoadVehLeaveDepot(v->Next(), false); + } } } @@ -1439,30 +1691,7 @@ bool IndividualRoadVehicleController(RoadVehicle *v, const RoadVehicle *prev) Direction new_dir = RoadVehGetSlidingDirection(v, x, y); - if (v->IsFrontEngine() && !IsInsideMM(v->state, RVSB_IN_ROAD_STOP, RVSB_IN_ROAD_STOP_END)) { - /* Vehicle is not in a road stop. - * Check for another vehicle to overtake */ - RoadVehicle *u = RoadVehFindCloseTo(v, x, y, new_dir); - - if (u != nullptr) { - u = u->First(); - /* There is a vehicle in front overtake it if possible */ - if (v->overtaking == 0) RoadVehCheckOvertake(v, u); - if (v->overtaking == 0) v->cur_speed = u->cur_speed; - - /* In case an RV is stopped in a road stop, why not try to load? */ - if (v->cur_speed == 0 && IsInsideMM(v->state, RVSB_IN_DT_ROAD_STOP, RVSB_IN_DT_ROAD_STOP_END) && - v->current_order.ShouldStopAtStation(v, GetStationIndex(v->tile)) && - v->owner == GetTileOwner(v->tile) && !v->current_order.IsType(OT_LEAVESTATION) && - GetRoadStopType(v->tile) == (v->IsBus() ? ROADSTOP_BUS : ROADSTOP_TRUCK)) { - Station *st = Station::GetByTile(v->tile); - v->last_station_visited = st->index; - RoadVehArrivesAt(v, st); - v->BeginLoading(); - } - return false; - } - } + if (CheckCloseVehicle(v, x, y, new_dir)) return false; Direction old_dir = v->direction; if (new_dir != old_dir) { @@ -1558,6 +1787,16 @@ bool IndividualRoadVehicleController(RoadVehicle *v, const RoadVehicle *prev) v->y_pos = y; v->UpdatePosition(); RoadZPosAffectSpeed(v, v->UpdateInclination(false, true)); + + /* After updating the position, check whether the vehicle can stop in a depot platform. */ + if (IsExtendedRoadDepotTile(v->tile) && v->Next() == nullptr) { + RoadVehicle *first = RoadVehicle::From(v)->First(); + if (HasBit(VehicleEnterTile(first, first->tile, first->x_pos, first->y_pos), VETS_ENTERED_DEPOT_PLATFORM)) { + HandleRoadVehicleEnterDepot(first); + return false; + } + } + return true; } @@ -1579,6 +1818,8 @@ static bool RoadVehController(RoadVehicle *v) return true; } + if (v->ContinueServicing()) return true; + ProcessOrders(v); v->HandleLoading(); @@ -1740,6 +1981,9 @@ Trackdir RoadVehicle::GetVehicleTrackdir() const if (this->vehstatus & VS_CRASHED) return INVALID_TRACKDIR; if (this->IsInDepot()) { + if (IsExtendedRoadDepot(this->tile)) { + return DiagDirToDiagTrackdir(DirToDiagDir(this->direction)); + } /* We'll assume the road vehicle is facing outwards */ return DiagDirToDiagTrackdir(GetRoadDepotDirection(this->tile)); } diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 06102930b7e75..57cbc2cbe442b 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -11,6 +11,7 @@ #include "../void_map.h" #include "../signs_base.h" #include "../depot_base.h" +#include "../depot_func.h" #include "../fios.h" #include "../gamelog_internal.h" #include "../network/network.h" @@ -49,6 +50,8 @@ #include "../newgrf.h" #include "../newgrf_station.h" #include "../engine_func.h" +#include "../airport_gui.h" +#include "../air.h" #include "../rail_gui.h" #include "../core/backup_type.hpp" #include "../smallmap_gui.h" @@ -62,6 +65,8 @@ #include "../timer/timer_game_calendar.h" #include "../timer/timer_game_economy.h" #include "../timer/timer_game_tick.h" +#include "../air.h" +#include "../air_map.h" #include "saveload_internal.h" @@ -222,6 +227,7 @@ static inline RailType UpdateRailType(RailType rt, RailType min) void UpdateAllVirtCoords() { UpdateAllStationVirtCoords(); + UpdateAllDepotVirtCoords(); UpdateAllSignVirtCoords(); UpdateAllTownVirtCoords(); UpdateAllTextEffectVirtCoords(); @@ -276,12 +282,6 @@ static void InitializeWindowsAndCaches() i->psa->tile = i->location.tile; } } - for (Station *s : Station::Iterate()) { - if (s->airport.psa != nullptr) { - s->airport.psa->feature = GSF_AIRPORTS; - s->airport.psa->tile = s->airport.tile; - } - } for (Town *t : Town::Iterate()) { for (auto &it : t->psa_list) { it->feature = GSF_FAKE_TOWNS; @@ -294,6 +294,10 @@ static void InitializeWindowsAndCaches() } } + for (Depot *dep : Depot::Iterate()) { + if (dep->veh_type != VEH_AIRCRAFT) dep->RescanDepotTiles(); + } + RecomputePrices(); GroupStatistics::UpdateAfterLoad(); @@ -641,6 +645,15 @@ bool AfterLoadGame() } } + if (IsSavegameVersionBefore(SLV_DEPOTS_ALIGN_RAIL_DEPOT_BITS)) { + for (auto t : Map::Iterate()) { + if (IsTileType(t, MP_RAILWAY) && GetRailTileType(t) == 3) { + /* Change the rail type for depots from old value 3 to new value 2. */ + SB(t.m5(), 6, 2, RAIL_TILE_DEPOT); + } + } + } + /* in version 2.1 of the savegame, town owner was unified. */ if (IsSavegameVersionBefore(SLV_2, 1)) ConvertTownOwner(); @@ -795,6 +808,28 @@ bool AfterLoadGame() _settings_game.linkgraph.recalc_time *= CalendarTime::SECONDS_PER_DAY; } + if (IsSavegameVersionBefore(SLV_DEPOT_SPREAD)) { + _settings_game.depot.depot_spread = DEF_MAX_DEPOT_SPREAD; + _settings_game.depot.distant_join_depots = true; + } + + if (IsSavegameVersionBefore(SLV_ALLOW_INCOMPATIBLE_REPLACEMENTS)) { + _settings_game.depot.allow_no_comp_railtype_replacements = false; + _settings_game.depot.allow_no_comp_roadtype_replacements = false; + } + + if (IsSavegameVersionBefore(SLV_EXTENDED_DEPOTS)) { + /* Set standard depots as the only available depots. */ + _settings_game.depot.rail_depot_types = 1; + _settings_game.depot.road_depot_types = 1; + _settings_game.depot.water_depot_types = 1; + } + + if (IsSavegameVersionBefore(SLV_MULTITILE_AIRPORTS)) { + _settings_game.station.allow_modify_airports = false; + _settings_game.depot.hangar_types = 1; + } + /* Load the sprites */ GfxLoadSprites(); LoadStringWidthTable(); @@ -878,7 +913,7 @@ bool AfterLoadGame() st = STATION_BUS; SetStationGfx(t, gfx - 71); } else if (gfx == 75) { // Oil rig - st = STATION_OILRIG; + st = STATION_OLD_OILRIG; SetStationGfx(t, gfx - 75); } else if (IsInsideMM(gfx, 76, 82)) { // Dock st = STATION_DOCK; @@ -954,10 +989,10 @@ bool AfterLoadGame() } break; - case STATION_OILRIG: { + case STATION_OLD_OILRIG: { /* The internal encoding of oil rigs was changed twice. * It was 3 (till 2.2) and later 5 (till 5.1). - * DeleteOilRig asserts on the correct type, and + * DeleteBuiltInHeliport asserts on the correct type, and * setting it unconditionally does not hurt. */ Station::GetByTile(t)->airport.type = AT_OILRIG; @@ -968,7 +1003,7 @@ bool AfterLoadGame() */ TileIndex t1 = TileAddXY(t, 0, 1); if (!IsTileType(t1, MP_INDUSTRY) || GetIndustryGfx(t1) != GFX_OILRIG_1) { - DeleteOilRig(t); + DeleteOldBuiltInHeliport(t); } break; } @@ -1434,6 +1469,7 @@ bool AfterLoadGame() for (Company *c : Company::Iterate()) { c->avail_railtypes = GetCompanyRailTypes(c->index); c->avail_roadtypes = GetCompanyRoadTypes(c->index); + c->avail_airtypes = GetCompanyAirTypes(c->index); } AfterLoadStations(); @@ -1802,7 +1838,7 @@ bool AfterLoadGame() switch (GetTileType(t)) { case MP_STATION: switch (GetStationType(t)) { - case STATION_OILRIG: + case STATION_OLD_OILRIG: case STATION_DOCK: case STATION_BUOY: SetWaterClass(t, (WaterClass)GB(t.m3(), 0, 2)); @@ -1934,7 +1970,7 @@ bool AfterLoadGame() if (IsSavegameVersionBefore(SLV_99)) { for (auto t : Map::Iterate()) { /* Set newly introduced WaterClass of industry tiles */ - if (IsTileType(t, MP_STATION) && IsOilRig(t)) { + if (IsTileType(t, MP_STATION) && GetStationType(t) == STATION_OLD_OILRIG) { SetWaterClassDependingOnSurroundings(t, true); } if (IsTileType(t, MP_INDUSTRY)) { @@ -2401,9 +2437,27 @@ bool AfterLoadGame() if (IsSavegameVersionBefore(SLV_140)) { for (Station *st : Station::Iterate()) { if (st->airport.tile != INVALID_TILE) { - st->airport.w = st->airport.GetSpec()->size_x; - st->airport.h = st->airport.GetSpec()->size_y; + st->airport.w = st->airport.GetSpec()->layouts[0].size_x; + st->airport.h = st->airport.GetSpec()->layouts[0].size_y; + } + } + } + + /* Data structure on airport has changed. */ + if (IsSavegameVersionBefore(SLV_MULTITILE_AIRPORTS)) { + for (auto t : Map::Iterate()) { + if (!IsTileType(t, MP_STATION)) continue; + if (GetStationType(t) == STATION_OLD_OILRIG) { + SetStationType(t, STATION_AIRPORT); } + if (GetStationType(t) != STATION_AIRPORT) continue; + t.m4() = t.m5(); + t.m5() = 0; + } + AfterLoadSetAirportTileTypes(); + } else { + for (Station *st : Station::Iterate()) { + st->UpdateAirportDataStructure(); } } @@ -2425,28 +2479,6 @@ bool AfterLoadGame() for (Depot *d : Depot::Iterate()) d->build_date = TimerGameCalendar::date; } - /* In old versions it was possible to remove an airport while a plane was - * taking off or landing. This gives all kind of problems when building - * another airport in the same station so we don't allow that anymore. - * For old savegames with such aircraft we just throw them in the air and - * treat the aircraft like they were flying already. */ - if (IsSavegameVersionBefore(SLV_146)) { - for (Aircraft *v : Aircraft::Iterate()) { - if (!v->IsNormalAircraft()) continue; - Station *st = GetTargetAirportIfValid(v); - if (st == nullptr && v->state != FLYING) { - v->state = FLYING; - UpdateAircraftCache(v); - AircraftNextAirportPos_and_Order(v); - /* get aircraft back on running altitude */ - if ((v->vehstatus & VS_CRASHED) == 0) { - GetAircraftFlightLevelBounds(v, &v->z_pos, nullptr); - SetAircraftPosition(v, v->x_pos, v->y_pos, GetAircraftFlightLevel(v)); - } - } - } - } - /* Move the animation frame to the same location (m7) for all objects. */ if (IsSavegameVersionBefore(SLV_147)) { for (auto t : Map::Iterate()) { @@ -2490,7 +2522,7 @@ bool AfterLoadGame() if (IsSavegameVersionBefore(SLV_149)) { for (auto t : Map::Iterate()) { if (!IsTileType(t, MP_STATION)) continue; - if (!IsBuoy(t) && !IsOilRig(t) && !(IsDock(t) && IsTileFlat(t))) { + if (!IsBuoy(t) && !IsBuiltInHeliportTile(t) && !(IsDock(t) && IsTileFlat(t))) { SetWaterClass(t, WATER_CLASS_INVALID); } } @@ -2792,6 +2824,75 @@ bool AfterLoadGame() } } + if (IsSavegameVersionBefore(SLV_DEPOTID_IN_HANGAR_ORDERS)) { + /* Update go to hangar orders so they store the DepotID instead of StationID. */ + for (Aircraft *a : Aircraft::Iterate()) { + if (!a->IsNormalAircraft()) continue; + + /* Update current order. */ + if (a->current_order.IsType(OT_GOTO_DEPOT)) { + Depot *dep = Station::Get(a->current_order.GetDestination())->airport.hangar; + if (dep == nullptr) { + /* Aircraft heading to a removed hangar. */ + a->current_order.MakeDummy(); + } else { + a->current_order.SetDestination(dep->index); + } + } + + /* Update each aircraft order list once. */ + if (a->orders == nullptr) continue; + if (a->orders->GetFirstSharedVehicle() != a) continue; + + for (Order *order : a->Orders()) { + if (!order->IsType(OT_GOTO_DEPOT)) continue; + StationID station_id = order->GetDestination(); + Station *st = Station::Get(station_id); + order->SetDestination(st->airport.hangar->index); + } + } + } + + if (IsSavegameVersionBefore(SLV_ADD_MEMBERS_TO_DEPOT_STRUCT)) { + for (Depot *depot : Depot::Iterate()) { + if (!IsDepotTile(depot->xy) || GetDepotIndex(depot->xy) != depot->index) { + /* It can happen there is no depot here anymore (TTO/TTD savegames) */ + depot->veh_type = VEH_INVALID; + depot->owner = INVALID_OWNER; + depot->Disuse(); + delete depot; + continue; + } + + depot->owner = GetTileOwner(depot->xy); + depot->veh_type = GetDepotVehicleType(depot->xy); + switch (depot->veh_type) { + case VEH_SHIP: + depot->AfterAddRemove(TileArea(depot->xy, 2, 2), true); + break; + case VEH_ROAD: + case VEH_TRAIN: + depot->AfterAddRemove(TileArea(depot->xy, 1, 1), true); + break; + case VEH_AIRCRAFT: + assert(IsHangarTile(depot->xy)); + depot->station = Station::GetByTile(depot->xy); + break; + default: + break; + } + } + + for (auto t : Map::Iterate()) { + if (!IsRoadDepotTile(t)) continue; + DiagDirection dir = (DiagDirection)GB(t.m5(), 0, 2); + SB(t.m5(), 0, 6, 0); + RoadBits rb = DiagDirToRoadBits(dir); + SetRoadBits(t, rb, HasRoadTypeRoad(t) ? RTT_ROAD : RTT_TRAM); + SB(t.m6(), 6, 2, dir); + } + } + /* This triggers only when old snow_lines were copied into the snow_line_height. */ if (IsSavegameVersionBefore(SLV_164) && _settings_game.game_creation.snow_line_height >= MIN_SNOWLINE_HEIGHT * TILE_HEIGHT) { _settings_game.game_creation.snow_line_height /= TILE_HEIGHT; @@ -2925,10 +3026,6 @@ bool AfterLoadGame() } } - /* In version 2.2 of the savegame, we have new airports, so status of all aircraft is reset. - * This has to be called after all map array updates */ - if (IsSavegameVersionBefore(SLV_2, 2)) UpdateOldAircraft(); - if (IsSavegameVersionBefore(SLV_188)) { /* Fix articulated road vehicles. * Some curves were shorter than other curves. @@ -3088,7 +3185,7 @@ bool AfterLoadGame() /* Link oil rigs to their industry and back. */ for (Station *st : Station::Iterate()) { - if (IsTileType(st->xy, MP_STATION) && IsOilRig(st->xy)) { + if (IsBuiltInHeliportTile(st->xy)) { /* Industry tile is always adjacent during construction by TileDiffXY(0, 1) */ st->industry = Industry::GetByTile(st->xy + TileDiffXY(0, 1)); st->industry->neutral_station = st; @@ -3116,7 +3213,7 @@ bool AfterLoadGame() } /* Add docks and oilrigs to Station::ship_station. */ if (IsTileType(t, MP_STATION)) { - if (IsDock(t) || IsOilRig(t)) Station::GetByTile(t)->ship_station.Add(t); + if (IsDock(t) || IsBuiltInHeliportTile(t)) Station::GetByTile(t)->ship_station.Add(t); } } } @@ -3287,6 +3384,59 @@ bool AfterLoadGame() } } + if (IsSavegameVersionBefore(SLV_MULTITILE_AIRPORTS)) { + /* Delete already crashed zeppelins. */ + DeleteCrashedZeppelins(); + + /* We have to redeploy aircraft. */ + for (Aircraft *v : Aircraft::Iterate()) { + if (!v->IsPrimaryVehicle()) continue; + + Aircraft *u = v->Next(); // shadow + assert(u != nullptr); + v->flags = 0; + + /* Assign dest_tile. */ + v->dest_tile = 0; + + int z = v->z_pos; + if ((v->vehstatus & VS_HIDDEN) != 0) { + assert(IsHangarTile(v->tile)); + /* Keep aircraft in hangars. */ + v->state = AS_HANGAR; + v->dest_tile = v->tile; + v->direction = u->direction = DiagDirToDir(GetHangarDirection(v->tile)); + v->trackdir = u->trackdir = DiagDirToDiagTrackdir(GetHangarDirection(v->tile)); + v->next_trackdir = INVALID_TRACKDIR; + v->wait_counter = 0; + v->x_pos = (v->x_pos & ~0xF) + 8; + v->y_pos = (v->y_pos & ~0xF) + 8; + v->current_order.Free(); + ProcessOrders(v); + } else { + if (v->current_order.IsType(OT_LOADING)) { + ClrBit(v->vehicle_flags, VF_LOADING_FINISHED); + v->LeaveStation(); + } + v->current_order.Free(); + v->state = AS_FLYING_NO_DEST; + v->next_trackdir = INVALID_TRACKDIR; + v->trackdir = v->Next()->trackdir = TRACKDIR_X_NE; + v->direction = u->direction = DIR_NE; + v->x_pos = (v->x_pos & ~0xF) + 8; + v->y_pos = (v->y_pos & ~0xF) + 8; + v->tile = TileVirtXY(v->x_pos, v->y_pos); + GetAircraftFlightLevelBounds(v, nullptr, &z); + ProcessOrders(v); + AircraftUpdateNextPos(v); + } + + SetAircraftPosition(v, v->x_pos, v->y_pos, z); + } + + InitializeAirportGui(); + } + AfterLoadLabelMaps(); AfterLoadCompanyStats(); AfterLoadStoryBook(); diff --git a/src/saveload/company_sl.cpp b/src/saveload/company_sl.cpp index 8e49af0eedce4..f9fcfe3400367 100644 --- a/src/saveload/company_sl.cpp +++ b/src/saveload/company_sl.cpp @@ -19,6 +19,7 @@ #include "../tunnelbridge.h" #include "../station_base.h" #include "../strings_func.h" +#include "../air_map.h" #include "table/strings.h" @@ -98,13 +99,6 @@ void AfterLoadCompanyStats() /* Reset infrastructure statistics to zero. */ for (Company *c : Company::Iterate()) c->infrastructure = {}; - /* Collect airport count. */ - for (const Station *st : Station::Iterate()) { - if ((st->facilities & FACIL_AIRPORT) && Company::IsValidID(st->owner)) { - Company::Get(st->owner)->infrastructure.airport++; - } - } - Company *c; for (TileIndex tile = 0; tile < Map::Size(); tile++) { switch (GetTileType(tile)) { @@ -134,15 +128,15 @@ void AfterLoadCompanyStats() RoadType rt = GetRoadType(tile, rtt); if (rt == INVALID_ROADTYPE) continue; c = Company::GetIfValid(IsRoadDepot(tile) ? GetTileOwner(tile) : GetRoadOwner(tile, rtt)); - /* A level crossings and depots have two road bits. */ - if (c != nullptr) c->infrastructure.road[rt] += IsNormalRoad(tile) ? CountBits(GetRoadBits(tile, rtt)) : 2; + /* Level crossings have two road bits. */ + if (c != nullptr) c->infrastructure.road[rt] += (IsNormalRoad(tile) || IsRoadDepot(tile)) ? CountBits(GetRoadBits(tile, rtt)) : 2; } break; } case MP_STATION: c = Company::GetIfValid(GetTileOwner(tile)); - if (c != nullptr && GetStationType(tile) != STATION_AIRPORT && !IsBuoy(tile)) c->infrastructure.station++; + if (c != nullptr && !IsBuoy(tile)) c->infrastructure.station++; switch (GetStationType(tile)) { case STATION_RAIL: @@ -163,6 +157,10 @@ void AfterLoadCompanyStats() break; } + case STATION_AIRPORT: + if (c != nullptr) c->infrastructure.air[GetAirType(tile)]++; + break; + case STATION_DOCK: case STATION_BUOY: if (GetWaterClass(tile) == WATER_CLASS_CANAL) { diff --git a/src/saveload/compat/station_sl_compat.h b/src/saveload/compat/station_sl_compat.h index 1c24a8d5d9220..e7b1dbab681fb 100644 --- a/src/saveload/compat/station_sl_compat.h +++ b/src/saveload/compat/station_sl_compat.h @@ -108,6 +108,7 @@ const SaveLoadCompat _station_normal_sl_compat[] = { SLC_VAR("airport.layout"), SLC_VAR("airport.flags"), SLC_VAR("airport.rotation"), + SLC_VAR("airport.hangar"), SLC_VAR("storage"), SLC_VAR("airport.psa"), SLC_VAR("indtype"), diff --git a/src/saveload/compat/vehicle_sl_compat.h b/src/saveload/compat/vehicle_sl_compat.h index 4587dd2824365..431953004ede7 100644 --- a/src/saveload/compat/vehicle_sl_compat.h +++ b/src/saveload/compat/vehicle_sl_compat.h @@ -141,10 +141,12 @@ const SaveLoadCompat _vehicle_ship_sl_compat[] = { /** Original field order for SlVehicleAircraft. */ const SaveLoadCompat _vehicle_aircraft_sl_compat[] = { SLC_VAR("common"), + SLC_VAR("trackdir"), + SLC_VAR("state"), SLC_VAR("crashed_counter"), - SLC_VAR("pos"), + SLC_VAR("aircraft_pos"), SLC_VAR("targetairport"), - SLC_VAR("state"), + SLC_VAR("old_state"), SLC_VAR("previous_pos"), SLC_VAR("last_direction"), SLC_VAR("number_consecutive_turns"), diff --git a/src/saveload/depot_sl.cpp b/src/saveload/depot_sl.cpp index 2a7859211ce3e..4bc5dde166d96 100644 --- a/src/saveload/depot_sl.cpp +++ b/src/saveload/depot_sl.cpp @@ -27,6 +27,13 @@ static const SaveLoad _depot_desc[] = { SLE_CONDVAR(Depot, town_cn, SLE_UINT16, SLV_141, SL_MAX_VERSION), SLE_CONDSSTR(Depot, name, SLE_STR, SLV_141, SL_MAX_VERSION), SLE_CONDVAR(Depot, build_date, SLE_INT32, SLV_142, SL_MAX_VERSION), + SLE_CONDVAR(Depot, owner, SLE_UINT8, SLV_ADD_MEMBERS_TO_DEPOT_STRUCT, SL_MAX_VERSION), + SLE_CONDVAR(Depot, veh_type, SLE_UINT8, SLV_ADD_MEMBERS_TO_DEPOT_STRUCT, SL_MAX_VERSION), + SLE_CONDVAR(Depot, ta.tile, SLE_UINT32, SLV_ADD_MEMBERS_TO_DEPOT_STRUCT, SL_MAX_VERSION), + SLE_CONDVAR(Depot, ta.w, SLE_FILE_U8 | SLE_VAR_U16, SLV_ADD_MEMBERS_TO_DEPOT_STRUCT, SL_MAX_VERSION), + SLE_CONDVAR(Depot, ta.h, SLE_FILE_U8 | SLE_VAR_U16, SLV_ADD_MEMBERS_TO_DEPOT_STRUCT, SL_MAX_VERSION), + SLE_CONDREF(Depot, station, REF_STATION, SLV_ADD_MEMBERS_TO_DEPOT_STRUCT, SL_MAX_VERSION), + SLE_CONDVAR(Depot, delete_ctr, SLE_UINT8, SLV_KEEP_REMOVED_DEPOTS, SL_MAX_VERSION), }; struct DEPTChunkHandler : ChunkHandler { diff --git a/src/saveload/map_sl.cpp b/src/saveload/map_sl.cpp index 3ce23a42b28f9..8d13947bc0b14 100644 --- a/src/saveload/map_sl.cpp +++ b/src/saveload/map_sl.cpp @@ -15,6 +15,7 @@ #include "../map_func.h" #include "../core/bitmath_func.hpp" #include "../fios.h" +#include "../tile_map.h" #include "../safeguards.h" @@ -243,6 +244,16 @@ struct MAP5ChunkHandler : ChunkHandler { SlCopy(buf.data(), MAP_SL_BUF_SIZE, SLE_UINT8); for (uint j = 0; j != MAP_SL_BUF_SIZE; j++) Tile(i++).m5() = buf[j]; } + + if (IsSavegameVersionBefore(SLV_ALIGN_WATER_BITS)) { + /* Move some bits for alignment purposes. */ + for (TileIndex i = 0; i != size; i++) { + if (IsTileType(i, MP_WATER)) { + SB(Tile(i).m5(), 6, 1, GB(Tile(i).m5(), 4, 1)); + SB(Tile(i).m5(), 4, 1, 0); + } + } + } } void Save() const override diff --git a/src/saveload/oldloader_sl.cpp b/src/saveload/oldloader_sl.cpp index 538c1336c37dc..a57d96bda9bc6 100644 --- a/src/saveload/oldloader_sl.cpp +++ b/src/saveload/oldloader_sl.cpp @@ -690,6 +690,8 @@ static bool LoadOldDepot(LoadgameState *ls, int num) if (d->xy != 0) { d->town = RemapTown(d->xy); } else { + d->owner = INVALID_OWNER; + d->veh_type = VEH_INVALID; delete d; } @@ -1086,7 +1088,7 @@ static const OldChunks vehicle_ship_chunk[] = { }; static const OldChunks vehicle_air_chunk[] = { - OCL_SVAR( OC_UINT8, Aircraft, pos ), + OCL_NULL( 1 ), // Old aircraft position OCL_SVAR( OC_FILE_U8 | OC_VAR_U16, Aircraft, targetairport ), OCL_SVAR( OC_UINT16, Aircraft, crashed_counter ), OCL_SVAR( OC_UINT8, Aircraft, state ), diff --git a/src/saveload/order_sl.cpp b/src/saveload/order_sl.cpp index 0ce1bd206e452..fe7e6d22d37f0 100644 --- a/src/saveload/order_sl.cpp +++ b/src/saveload/order_sl.cpp @@ -16,6 +16,7 @@ #include "../order_backup.h" #include "../settings_type.h" #include "../network/network.h" +#include "../depot_map.h" #include "../safeguards.h" @@ -243,11 +244,14 @@ struct ORDLChunkHandler : ChunkHandler { } }; +static TileIndex _tile; + SaveLoadTable GetOrderBackupDescription() { static const SaveLoad _order_backup_desc[] = { SLE_VAR(OrderBackup, user, SLE_UINT32), - SLE_VAR(OrderBackup, tile, SLE_UINT32), + SLEG_CONDVAR("tile", _tile, SLE_UINT32, SL_MIN_VERSION, SLV_DEPOTID_BACKUP_ORDERS), + SLE_CONDVAR(OrderBackup, depot_id, SLE_UINT16, SLV_DEPOTID_BACKUP_ORDERS, SL_MAX_VERSION), SLE_VAR(OrderBackup, group, SLE_UINT16), SLE_CONDVAR(OrderBackup, service_interval, SLE_FILE_U32 | SLE_VAR_U16, SL_MIN_VERSION, SLV_192), SLE_CONDVAR(OrderBackup, service_interval, SLE_UINT16, SLV_192, SL_MAX_VERSION), @@ -297,6 +301,8 @@ struct BKORChunkHandler : ChunkHandler { OrderBackup *ob = new (index) OrderBackup(); SlObject(ob, slt); } + + if (IsSavegameVersionBefore(SLV_DEPOTID_BACKUP_ORDERS)) _order_backup_pool.CleanPool(); } void FixPointers() const override diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp index 24eb4aa54dc4e..e1962e98c7ec6 100644 --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -23,6 +23,7 @@ #include "../stdafx.h" #include "../debug.h" #include "../station_base.h" +#include "../depot_base.h" #include "../thread.h" #include "../town.h" #include "../network/network.h" @@ -1121,6 +1122,7 @@ static size_t ReferenceToInt(const void *obj, SLRefType rt) case REF_STORAGE: return ((const PersistentStorage*)obj)->index + 1; case REF_LINK_GRAPH: return ((const LinkGraph*)obj)->index + 1; case REF_LINK_GRAPH_JOB: return ((const LinkGraphJob*)obj)->index + 1; + case REF_DEPOT: return ((const Depot*)obj)->index + 1; default: NOT_REACHED(); } } @@ -1202,6 +1204,10 @@ static void *IntToReference(size_t index, SLRefType rt) if (LinkGraphJob::IsValidID(index)) return LinkGraphJob::Get(index); SlErrorCorrupt("Referencing invalid LinkGraphJob"); + case REF_DEPOT: + if (Depot::IsValidID(index)) return Depot::Get(index); + SlErrorCorrupt("Referencing invalid Depot"); + default: NOT_REACHED(); } } diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 9149d42711420..66dd1dfab3e51 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -383,7 +383,24 @@ enum SaveLoadVersion : uint16_t { SLV_GROUP_NUMBERS, ///< 336 PR#12297 Add per-company group numbers. SLV_INCREASE_STATION_TYPE_FIELD_SIZE, ///< 337 PR#12572 Increase size of StationType field in map array SLV_ROAD_WAYPOINTS, ///< 338 PR#12572 Road waypoints + SLV_ADD_DEPOTS_TO_HANGARS, ///< 339 PR#10691 Add depots to airports that have a hangar. + SLV_DEPOTID_IN_HANGAR_ORDERS, ///< 340 PR#10691 Go to hangar orders store the DepotID instead of StationID. + SLV_DEPOTID_BACKUP_ORDERS, ///< 341 PR#XXXXX Backup orders are indexed through DepotIDs. + SLV_ADD_MEMBERS_TO_DEPOT_STRUCT, ///< 342 PR#XXXXX Add some members to depot struct. + SLV_DEPOT_SPREAD, ///< 343 PR#XXXXX Add a setting for max depot spread. + SLV_ALLOW_INCOMPATIBLE_REPLACEMENTS, ///< 344 PR#XXXXX Allow incompatible vehicle replacements. + + SLV_ALIGN_WATER_BITS, ///< 315 PR#XXXXX Align some water bits in the map array. + SLV_DEPOTS_ALIGN_RAIL_DEPOT_BITS, ///< 316 PR#XXXXX Align one bit for rail depots. + + SLV_KEEP_REMOVED_DEPOTS, ///< 320 PR#XXXXX Keep remove depots for a while. + + SLV_EXTENDED_DEPOTS, ///< 321 PR#8480 Extended depots for rail, road and water transport. + SLV_MULTITILE_AIRPORTS, ///< 322 PR#XXXX Multi-tile airports. + + SLV_PATCHED = UINT16_MAX - 6, ///< Make it difficult to load any savegame made with + // this patched version in any other version of OpenTTD (unless it uses the same saveload version trick). SL_MAX_VERSION, ///< Highest possible saveload version }; @@ -598,6 +615,7 @@ enum SLRefType { REF_STORAGE = 9, ///< Load/save a reference to a persistent storage. REF_LINK_GRAPH = 10, ///< Load/save a reference to a link graph. REF_LINK_GRAPH_JOB = 11, ///< Load/save a reference to a link graph job. + REF_DEPOT = 12, ///< Load/save a reference to a depot. }; /** diff --git a/src/saveload/station_sl.cpp b/src/saveload/station_sl.cpp index 2f427d8d52989..764316c43890e 100644 --- a/src/saveload/station_sl.cpp +++ b/src/saveload/station_sl.cpp @@ -384,6 +384,11 @@ class SlStationGoods : public DefaultSaveLoadHandlerfacilities & FACIL_AIRPORT) != 0) { + st->airport.flags = HasBit(st->airport.flags, 63) ? AF_CLOSED_MANUAL : AF_NONE; + } + /* Before savegame version 161, persistent storages were not stored in a pool. */ if (IsSavegameVersionBefore(SLV_161) && !IsSavegameVersionBefore(SLV_145) && st->facilities & FACIL_AIRPORT) { /* Store the old persistent storage. The GRFID will be added later. */ @@ -610,6 +615,7 @@ class SlStationNormal : public DefaultSaveLoadHandlerairport.flags = 0; // reset airport - } - - for (Aircraft *a : Aircraft::Iterate()) { - /* airplane has another vehicle with subtype 4 (shadow), helicopter also has 3 (rotor) - * skip those */ - if (a->IsNormalAircraft()) { - /* airplane in terminal stopped doesn't hurt anyone, so goto next */ - if ((a->vehstatus & VS_STOPPED) && a->state == 0) { - a->state = HANGAR; - continue; - } - - AircraftLeaveHangar(a, a->direction); // make airplane visible if it was in a depot for example - a->vehstatus &= ~VS_STOPPED; // make airplane moving - UpdateAircraftCache(a); - a->cur_speed = a->vcache.cached_max_speed; // so aircraft don't have zero speed while in air - if (!a->current_order.IsType(OT_GOTO_STATION) && !a->current_order.IsType(OT_GOTO_DEPOT)) { - /* reset current order so aircraft doesn't have invalid "station-only" order */ - a->current_order.MakeDummy(); - } - a->state = FLYING; - AircraftNextAirportPos_and_Order(a); // move it to the entry point of the airport - GetNewVehiclePosResult gp = GetNewVehiclePos(a); - a->tile = 0; // aircraft in air is tile=0 - - /* correct speed of helicopter-rotors */ - if (a->subtype == AIR_HELICOPTER) a->Next()->Next()->cur_speed = 32; - - /* set new position x,y,z */ - GetAircraftFlightLevelBounds(a, &a->z_pos, nullptr); - SetAircraftPosition(a, gp.x, gp.y, GetAircraftFlightLevel(a)); - } - } - - /* Clear aircraft from loading vehicles, if we bumped them into the air. */ - for (Station *st : Station::Iterate()) { - for (auto iter = st->loading_vehicles.begin(); iter != st->loading_vehicles.end(); /* nothing */) { - Vehicle *v = *iter; - if (v->type == VEH_AIRCRAFT && !v->current_order.IsType(OT_LOADING)) { - iter = st->loading_vehicles.erase(iter); - delete v->cargo_payment; - } else { - ++iter; - } - } - } -} - /** * Check all vehicles to ensure their engine type is valid * for the currently loaded NewGRFs (that includes none...) @@ -665,6 +610,7 @@ class SlVehicleCommon : public DefaultSaveLoadHandler SLE_VAR(Vehicle, progress, SLE_UINT8), SLE_VAR(Vehicle, vehstatus, SLE_UINT8), + SLE_CONDVAR(Vehicle, wait_counter, SLE_UINT16, SLV_EXTENDED_DEPOTS, SL_MAX_VERSION), SLE_CONDVAR(Vehicle, last_station_visited, SLE_FILE_U8 | SLE_VAR_U16, SL_MIN_VERSION, SLV_5), SLE_CONDVAR(Vehicle, last_station_visited, SLE_UINT16, SLV_5, SL_MAX_VERSION), SLE_CONDVAR(Vehicle, last_loading_station, SLE_UINT16, SLV_182, SL_MAX_VERSION), @@ -793,7 +739,7 @@ class SlVehicleTrain : public DefaultSaveLoadHandler { SLE_CONDVAR(Train, flags, SLE_FILE_U8 | SLE_VAR_U16, SLV_2, SLV_100), SLE_CONDVAR(Train, flags, SLE_UINT16, SLV_100, SL_MAX_VERSION), - SLE_CONDVAR(Train, wait_counter, SLE_UINT16, SLV_136, SL_MAX_VERSION), + SLE_CONDVAR(Train, wait_counter, SLE_UINT16, SLV_136, SLV_EXTENDED_DEPOTS), SLE_CONDVAR(Train, gv_flags, SLE_UINT16, SLV_139, SL_MAX_VERSION), }; inline const static SaveLoadCompatTable compat_description = _vehicle_train_sl_compat; @@ -882,24 +828,34 @@ class SlVehicleShip : public DefaultSaveLoadHandler { } }; +uint8_t _old_state; +uint8_t _pos; +StationID _targetairport; + class SlVehicleAircraft : public DefaultSaveLoadHandler { public: inline static const SaveLoad description[] = { SLEG_STRUCT("common", SlVehicleCommon), + SLE_CONDVAR(Aircraft, trackdir, SLE_UINT8, SLV_MULTITILE_AIRPORTS, SL_MAX_VERSION), + SLE_CONDVAR(Aircraft, state, SLE_UINT8, SLV_MULTITILE_AIRPORTS, SL_MAX_VERSION), + SLE_CONDVAR(Aircraft, next_trackdir, SLE_UINT8, SLV_MULTITILE_AIRPORTS, SL_MAX_VERSION), + SLE_CONDVAR(Aircraft, next_pos.x, SLE_UINT32, SLV_MULTITILE_AIRPORTS, SL_MAX_VERSION), + SLE_CONDVAR(Aircraft, next_pos.y, SLE_UINT32, SLV_MULTITILE_AIRPORTS, SL_MAX_VERSION), + SLE_CONDVAR(Aircraft, next_pos.pos, SLE_UINT8, SLV_MULTITILE_AIRPORTS, SL_MAX_VERSION), SLE_VAR(Aircraft, crashed_counter, SLE_UINT16), - SLE_VAR(Aircraft, pos, SLE_UINT8), - + SLEG_CONDVAR("aircraft_pos", _pos, SLE_UINT8, SL_MIN_VERSION, SLV_MULTITILE_AIRPORTS), SLE_CONDVAR(Aircraft, targetairport, SLE_FILE_U8 | SLE_VAR_U16, SL_MIN_VERSION, SLV_5), SLE_CONDVAR(Aircraft, targetairport, SLE_UINT16, SLV_5, SL_MAX_VERSION), - SLE_VAR(Aircraft, state, SLE_UINT8), - - SLE_CONDVAR(Aircraft, previous_pos, SLE_UINT8, SLV_2, SL_MAX_VERSION), + SLEG_CONDVAR("old_state", _old_state, SLE_UINT8, SL_MIN_VERSION, SLV_MULTITILE_AIRPORTS), + SLEG_CONDVAR("previous_pos", _pos, SLE_UINT8, SLV_2, SLV_MULTITILE_AIRPORTS), SLE_CONDVAR(Aircraft, last_direction, SLE_UINT8, SLV_2, SL_MAX_VERSION), SLE_CONDVAR(Aircraft, number_consecutive_turns, SLE_UINT8, SLV_2, SL_MAX_VERSION), SLE_CONDVAR(Aircraft, turn_counter, SLE_UINT8, SLV_136, SL_MAX_VERSION), SLE_CONDVAR(Aircraft, flags, SLE_UINT8, SLV_167, SL_MAX_VERSION), + SLE_CONDDEQUE(Aircraft, path.td, SLE_UINT8, SLV_MULTITILE_AIRPORTS, SL_MAX_VERSION), + SLE_CONDDEQUE(Aircraft, path.tile, SLE_UINT32, SLV_MULTITILE_AIRPORTS, SL_MAX_VERSION), }; inline const static SaveLoadCompatTable compat_description = _vehicle_aircraft_sl_compat; @@ -1072,6 +1028,7 @@ struct VEHSChunkHandler : ChunkHandler { default: SlErrorCorrupt("Invalid vehicle type"); } + if (IsSavegameVersionBefore(SLV_EXTENDED_DEPOTS)) assert(v->type == VEH_TRAIN || v->wait_counter == 0); SlObject(v, slt); if (_cargo_count != 0 && IsCompanyBuildableVehicleType(v) && CargoPacket::CanAllocateItem()) { diff --git a/src/script/api/script_airport.cpp b/src/script/api/script_airport.cpp index 11817485cec74..b8ad30d53fd48 100644 --- a/src/script/api/script_airport.cpp +++ b/src/script/api/script_airport.cpp @@ -14,6 +14,9 @@ #include "../../town.h" #include "../../landscape_cmd.h" #include "../../station_cmd.h" +#include "../../airport_cmd.h" +#include "../../depot_base.h" +#include "../../air.h" #include "../../safeguards.h" @@ -32,14 +35,14 @@ if (!IsValidAirportType(type)) return -1; const AirportSpec *as = ::AirportSpec::Get(type); - return _price[PR_BUILD_STATION_AIRPORT] * as->size_x * as->size_y; + return _price[PR_BUILD_STATION_AIRPORT] * as->layouts[0].size_x * as->layouts[0].size_y; } /* static */ bool ScriptAirport::IsHangarTile(TileIndex tile) { if (!::IsValidTile(tile)) return false; - return ::IsTileType(tile, MP_STATION) && ::IsHangar(tile); + return ::IsTileType(tile, MP_STATION) && ::IsAirport(tile) && ::IsHangar(tile); } /* static */ bool ScriptAirport::IsAirportTile(TileIndex tile) @@ -53,21 +56,25 @@ { if (!IsAirportInformationAvailable(type)) return -1; - return ::AirportSpec::Get(type)->size_x; + return ::AirportSpec::Get(type)->layouts[0].size_x; } /* static */ SQInteger ScriptAirport::GetAirportHeight(AirportType type) { if (!IsAirportInformationAvailable(type)) return -1; - return ::AirportSpec::Get(type)->size_y; + return ::AirportSpec::Get(type)->layouts[0].size_y; } /* static */ SQInteger ScriptAirport::GetAirportCoverageRadius(AirportType type) { if (!IsAirportInformationAvailable(type)) return -1; - return _settings_game.station.modified_catchment ? ::AirportSpec::Get(type)->catchment : (uint)CA_UNMODIFIED; + const AirportSpec *as = ::AirportSpec::Get(type); + const AirTypeInfo *ati = GetAirTypeInfo(as->airtype); + assert(ati->catchment_radius <= MAX_CATCHMENT); + + return _settings_game.station.modified_catchment ? ati->catchment_radius : (uint)CA_UNMODIFIED; } /* static */ bool ScriptAirport::BuildAirport(TileIndex tile, AirportType type, StationID station_id) @@ -77,7 +84,7 @@ EnforcePrecondition(false, IsValidAirportType(type)); EnforcePrecondition(false, station_id == ScriptStation::STATION_NEW || station_id == ScriptStation::STATION_JOIN_ADJACENT || ScriptStation::IsValidStation(station_id)); - return ScriptObject::Command::Do(tile, type, 0, (ScriptStation::IsValidStation(station_id) ? station_id : INVALID_STATION), station_id != ScriptStation::STATION_JOIN_ADJACENT); + return ScriptObject::Command::Do(tile, type, 0, INVALID_AIRTYPE, DIAGDIR_NE, (ScriptStation::IsValidStation(station_id) ? station_id : INVALID_STATION), station_id != ScriptStation::STATION_JOIN_ADJACENT); } /* static */ bool ScriptAirport::RemoveAirport(TileIndex tile) @@ -99,7 +106,9 @@ if (st->owner != ScriptObject::GetCompany() && ScriptCompanyMode::IsValid()) return -1; if ((st->facilities & FACIL_AIRPORT) == 0) return -1; - return st->airport.GetNumHangars(); + if (!st->airport.HasHangar()) return 0; + + return (int32_t)st->airport.hangar->depot_tiles.size(); } /* static */ TileIndex ScriptAirport::GetHangarOfAirport(TileIndex tile) @@ -107,13 +116,13 @@ EnforceDeityOrCompanyModeValid(INVALID_TILE); if (!::IsValidTile(tile)) return INVALID_TILE; if (!::IsTileType(tile, MP_STATION)) return INVALID_TILE; - if (GetNumHangars(tile) < 1) return INVALID_TILE; const Station *st = ::Station::GetByTile(tile); if (st->owner != ScriptObject::GetCompany() && ScriptCompanyMode::IsValid()) return INVALID_TILE; if ((st->facilities & FACIL_AIRPORT) == 0) return INVALID_TILE; + if (st->airport.hangar == nullptr) return INVALID_TILE; - return st->airport.GetHangarTile(0); + return st->airport.hangar->depot_tiles[0]; } /* static */ ScriptAirport::AirportType ScriptAirport::GetAirportType(TileIndex tile) @@ -127,6 +136,9 @@ return (AirportType)::Station::Get(station_id)->airport.type; } +extern CommandCost AddAirportTileTableToBitmapTileArea(const AirportTileLayout &atl, BitmapTileArea *bta, DiagDirection rotation, uint cost_multiplier); +extern Town *AirportGetNearestTown(BitmapTileArea bta, uint &mindist); +extern uint8_t GetAirportNoiseLevelForDistance(uint noise_level, uint distance); /* static */ SQInteger ScriptAirport::GetNoiseLevelIncrease(TileIndex tile, AirportType type) { @@ -134,13 +146,17 @@ if (!IsAirportInformationAvailable(type)) return -1; const AirportSpec *as = ::AirportSpec::Get(type); - if (!as->IsWithinMapBounds(0, tile)) return -1; + if (!as->IsWithinMapBounds(0, tile, 0)) return -1; if (_settings_game.economy.station_noise_level) { + BitmapTileArea bta; + const AirTypeInfo *ati = GetAirTypeInfo(as->airtype); + TileArea ta(tile, as->layouts[0].size_x, as->layouts[0].size_y); + AddAirportTileTableToBitmapTileArea(as->layouts[0], &bta, DIAGDIR_BEGIN, ati->cost_multiplier); uint dist; - const auto &layout = as->layouts[0]; - AirportGetNearestTown(as, layout.rotation, tile, AirportTileTableIterator(layout.tiles.data(), tile), dist); - return GetAirportNoiseLevelForDistance(as, dist); + + AirportGetNearestTown(bta, dist); + return GetAirportNoiseLevelForDistance(as->GetAirportNoise(as->airtype), dist); } return 1; @@ -152,30 +168,19 @@ if (!IsAirportInformationAvailable(type)) return INVALID_TOWN; const AirportSpec *as = AirportSpec::Get(type); - if (!as->IsWithinMapBounds(0, tile)) return INVALID_TOWN; + if (!as->IsWithinMapBounds(0, tile, 0)) return INVALID_TOWN; + BitmapTileArea bta; + const AirTypeInfo *ati = GetAirTypeInfo(as->airtype); + TileArea ta(tile, as->layouts[0].size_x, as->layouts[0].size_y); + AddAirportTileTableToBitmapTileArea(as->layouts[0], &bta, DIAGDIR_BEGIN, ati->cost_multiplier); uint dist; - const auto &layout = as->layouts[0]; - return AirportGetNearestTown(as, layout.rotation, tile, AirportTileTableIterator(layout.tiles.data(), tile), dist)->index; -} - -/* static */ SQInteger ScriptAirport::GetMaintenanceCostFactor(AirportType type) -{ - if (!IsAirportInformationAvailable(type)) return 0; - - return AirportSpec::Get(type)->maintenance_cost; -} - -/* static */ Money ScriptAirport::GetMonthlyMaintenanceCost(AirportType type) -{ - if (!IsAirportInformationAvailable(type)) return -1; - - return (int64_t)GetMaintenanceCostFactor(type) * _price[PR_INFRASTRUCTURE_AIRPORT] >> 3; + return AirportGetNearestTown(bta, dist)->index; } /* static */ SQInteger ScriptAirport::GetAirportNumHelipads(AirportType type) { if (!IsAirportInformationAvailable(type)) return -1; - return ::AirportSpec::Get(type)->fsm->num_helipads; + return ::AirportSpec::Get(type)->num_helipads; } diff --git a/src/script/api/script_airport.hpp b/src/script/api/script_airport.hpp index cb3fc3c227892..c463968328046 100644 --- a/src/script/api/script_airport.hpp +++ b/src/script/api/script_airport.hpp @@ -33,6 +33,7 @@ class ScriptAirport : public ScriptObject { AT_HELIPORT = ::AT_HELIPORT, ///< The heliport. AT_HELISTATION = ::AT_HELISTATION, ///< The helistation. AT_HELIDEPOT = ::AT_HELIDEPOT, ///< The helidepot. + AT_CUSTOM = ::AT_CUSTOM, ///< A customized airport. AT_INVALID = ::AT_INVALID, ///< Invalid airport. }; @@ -192,23 +193,6 @@ class ScriptAirport : public ScriptObject { */ static TownID GetNearestTown(TileIndex tile, AirportType type); - /** - * Get the maintenance cost factor of an airport type. - * @param type The airport type to get the maintenance factor of. - * @pre IsAirportInformationAvailable(type) - * @return Maintenance cost factor of the airport type. - */ - static SQInteger GetMaintenanceCostFactor(AirportType type); - - /** - * Get the monthly maintenance cost of an airport type. - * @param type The airport type to get the monthly maintenance cost of. - * @pre IsAirportInformationAvailable(type) - * @return Maintenance cost of the airport type per economy-month. - * @see \ref ScriptEconomyTime - */ - static Money GetMonthlyMaintenanceCost(AirportType type); - /** * Get the number of helipads of this airport type. * @param type The airport type. diff --git a/src/script/api/script_depotlist.cpp b/src/script/api/script_depotlist.cpp index eb43139165d1a..0906a86da9a91 100644 --- a/src/script/api/script_depotlist.cpp +++ b/src/script/api/script_depotlist.cpp @@ -17,33 +17,16 @@ ScriptDepotList::ScriptDepotList(ScriptTile::TransportType transport_type) { EnforceDeityOrCompanyModeValid_Void(); - ::TileType tile_type; - switch (transport_type) { - default: return; + static_assert(VEH_TRAIN == (int)ScriptTile::TRANSPORT_RAIL); + static_assert(VEH_ROAD == (int)ScriptTile::TRANSPORT_ROAD); + static_assert(VEH_SHIP == (int)ScriptTile::TRANSPORT_WATER); - case ScriptTile::TRANSPORT_ROAD: tile_type = ::MP_ROAD; break; - case ScriptTile::TRANSPORT_RAIL: tile_type = ::MP_RAILWAY; break; - case ScriptTile::TRANSPORT_WATER: tile_type = ::MP_WATER; break; - - case ScriptTile::TRANSPORT_AIR: { - /* Hangars are not seen as real depots by the depot code. */ - bool is_deity = ScriptCompanyMode::IsDeity(); - CompanyID owner = ScriptObject::GetCompany(); - for (const Station *st : Station::Iterate()) { - if (is_deity || st->owner == owner) { - for (uint i = 0; i < st->airport.GetNumHangars(); i++) { - this->AddItem(st->airport.GetHangarTile(i).base()); - } - } - } - return; - } - } - - /* Handle 'standard' depots. */ bool is_deity = ScriptCompanyMode::IsDeity(); CompanyID owner = ScriptObject::GetCompany(); for (const Depot *depot : Depot::Iterate()) { - if ((is_deity || ::GetTileOwner(depot->xy) == owner) && ::IsTileType(depot->xy, tile_type)) this->AddItem(depot->xy.base()); + if (!depot->IsInUse() || depot->veh_type != (VehicleType)transport_type || + (!is_deity && ::GetTileOwner(depot->xy) != owner)) continue; + + this->AddItem(depot->xy.base()); } } diff --git a/src/script/api/script_event.hpp b/src/script/api/script_event.hpp index 2ff813aa8161d..4426a8e6a78f8 100644 --- a/src/script/api/script_event.hpp +++ b/src/script/api/script_event.hpp @@ -47,6 +47,7 @@ class ScriptEvent : public ScriptObject { ET_DISASTER_ZEPPELINER_CRASHED, ET_DISASTER_ZEPPELINER_CLEARED, ET_TOWN_FOUNDED, + ET_AIRCRAFT_CAN_T_LAND, ET_AIRCRAFT_DEST_TOO_FAR, ET_ADMIN_PORT, ET_WINDOW_WIDGET_CLICK, diff --git a/src/script/api/script_event_types.hpp b/src/script/api/script_event_types.hpp index 776a31ff8dd34..c2b3e14fddf89 100644 --- a/src/script/api/script_event_types.hpp +++ b/src/script/api/script_event_types.hpp @@ -848,6 +848,39 @@ class ScriptEventTownFounded : public ScriptEvent { TownID town; ///< The town that got founded. }; +/** + * Event AircraftNoLandDest, indicating the aircraft cannot land on an airport. + * This event can be triggered when the aircraft is flying towards an airport and + * cannot find a tile for landing. + * @api ai + */ +class ScriptEventAircraftNoLandDest : public ScriptEvent { +public: + /** + * @param vehicle_id The aircraft that cannot land. + */ + ScriptEventAircraftNoLandDest(VehicleID vehicle_id) : + ScriptEvent(ET_AIRCRAFT_CAN_T_LAND), + vehicle_id(vehicle_id) + {} + + /** + * Convert an ScriptEvent to the real instance. + * @param instance The instance to convert. + * @return The converted instance. + */ + static ScriptEventAircraftNoLandDest *Convert(ScriptEvent *instance) { return (ScriptEventAircraftNoLandDest *)instance; } + + /** + * Get the VehicleID of the aircraft that cannot land. + * @return The VehicleID of the aircraft that cannot land. + */ + VehicleID GetVehicleID() { return this->vehicle_id; } + +private: + VehicleID vehicle_id; ///< The vehicle aircraft that cannot land. +}; + /** * Event AircraftDestTooFar, indicating the next destination of an aircraft is too far away. * This event can be triggered when the current order of an aircraft changes, usually either when diff --git a/src/script/api/script_industry.cpp b/src/script/api/script_industry.cpp index 79e89a075f0e1..eda73b8132d0e 100644 --- a/src/script/api/script_industry.cpp +++ b/src/script/api/script_industry.cpp @@ -182,7 +182,7 @@ const Industry *ind = ::Industry::Get(industry_id); for (TileIndex tile_cur : ind->location) { - if (IsTileType(tile_cur, MP_STATION) && IsOilRig(tile_cur)) { + if (IsBuiltInHeliportTile(tile_cur)) { return tile_cur; } } @@ -204,7 +204,7 @@ const Industry *ind = ::Industry::Get(industry_id); for (TileIndex tile_cur : ind->location) { - if (IsTileType(tile_cur, MP_STATION) && IsOilRig(tile_cur)) { + if (IsWateredBuiltInHeliportTile(tile_cur)) { return tile_cur; } } diff --git a/src/script/api/script_infrastructure.cpp b/src/script/api/script_infrastructure.cpp index a8d0e0551018c..873f9cad962f2 100644 --- a/src/script/api/script_infrastructure.cpp +++ b/src/script/api/script_infrastructure.cpp @@ -13,6 +13,7 @@ #include "../../rail.h" #include "../../road_func.h" #include "../../water.h" +#include "../../air.h" #include "../../station_func.h" #include "../../safeguards.h" @@ -57,7 +58,7 @@ return c->infrastructure.station; case INFRASTRUCTURE_AIRPORT: - return c->infrastructure.airport; + return c->infrastructure.GetAirTotal(); default: return 0; @@ -116,8 +117,14 @@ case INFRASTRUCTURE_STATION: return StationMaintenanceCost(c->infrastructure.station); - case INFRASTRUCTURE_AIRPORT: - return AirportMaintenanceCost(c->index); + case INFRASTRUCTURE_AIRPORT: { + Money cost; + uint32_t air_total = c->infrastructure.GetAirTotal(); + for (::AirType at = ::AIRTYPE_BEGIN; at != ::AIRTYPE_END; at++) { + cost += AirMaintenanceCost(at, c->infrastructure.air[at], air_total); + } + return cost; + } default: return 0; diff --git a/src/script/api/script_marine.cpp b/src/script/api/script_marine.cpp index 6c04e1904630b..5b30290d4be49 100644 --- a/src/script/api/script_marine.cpp +++ b/src/script/api/script_marine.cpp @@ -83,7 +83,7 @@ EnforcePrecondition(false, ::IsValidTile(front)); EnforcePrecondition(false, (::TileX(front) == ::TileX(tile)) != (::TileY(front) == ::TileY(tile))); - return ScriptObject::Command::Do(tile, ::TileX(front) == ::TileX(tile) ? AXIS_Y : AXIS_X); + return ScriptObject::Command::Do(tile, ::TileX(front) == ::TileX(tile) ? AXIS_Y : AXIS_X, false, false, INVALID_DEPOT, tile); } /* static */ bool ScriptMarine::BuildDock(TileIndex tile, StationID station_id) diff --git a/src/script/api/script_order.cpp b/src/script/api/script_order.cpp index b68f7fd67a7f2..3ac5c9d2b7dea 100644 --- a/src/script/api/script_order.cpp +++ b/src/script/api/script_order.cpp @@ -35,7 +35,7 @@ static OrderType GetOrderTypeByTile(TileIndex t) default: break; case MP_STATION: if (IsBuoy(t) || IsRailWaypoint(t)) return OT_GOTO_WAYPOINT; - if (IsHangar(t)) return OT_GOTO_DEPOT; + if (IsAirport(t) && IsHangar(t)) return OT_GOTO_DEPOT; return OT_GOTO_STATION; case MP_WATER: if (::IsShipDepot(t)) return OT_GOTO_DEPOT; break; @@ -245,18 +245,13 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr const Order *order = ::ResolveOrder(vehicle_id, order_position); if (order == nullptr || order->GetType() == OT_CONDITIONAL) return INVALID_TILE; - const Vehicle *v = ::Vehicle::Get(vehicle_id); switch (order->GetType()) { case OT_GOTO_DEPOT: { /* We don't know where the nearest depot is... (yet) */ if (order->GetDepotActionType() & ODATFB_NEAREST_DEPOT) return INVALID_TILE; - if (v->type != VEH_AIRCRAFT) return ::Depot::Get(order->GetDestination())->xy; - /* Aircraft's hangars are referenced by StationID, not DepotID */ - const Station *st = ::Station::Get(order->GetDestination()); - if (!st->airport.HasHangar()) return INVALID_TILE; - return st->airport.GetHangarTile(0); + return ::Depot::Get(order->GetDestination())->xy; } case OT_GOTO_STATION: { @@ -267,7 +262,7 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr } } else if (st->ship_station.tile != INVALID_TILE) { for (TileIndex t : st->ship_station) { - if (IsTileType(t, MP_STATION) && (IsDock(t) || IsOilRig(t)) && GetStationIndex(t) == st->index) return t; + if (IsTileType(t, MP_STATION) && (IsDock(t) || IsBuiltInHeliportTile(t)) && GetStationIndex(t) == st->index) return t; } } else if (st->bus_stops != nullptr) { return st->bus_stops->xy; @@ -490,11 +485,10 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr * to a depot (other vehicle types). */ if (::Vehicle::Get(vehicle_id)->type == VEH_AIRCRAFT) { if (!::IsTileType(destination, MP_STATION)) return false; - order.MakeGoToDepot(::GetStationIndex(destination), odtf, onsf, odaf); } else { if (::IsTileType(destination, MP_STATION)) return false; - order.MakeGoToDepot(::GetDepotIndex(destination), odtf, onsf, odaf); } + order.MakeGoToDepot(::GetDepotIndex(destination), odtf, onsf, odaf); } break; } diff --git a/src/script/api/script_rail.cpp b/src/script/api/script_rail.cpp index ac1b9fc5a9b5d..d92010de7fab2 100644 --- a/src/script/api/script_rail.cpp +++ b/src/script/api/script_rail.cpp @@ -145,7 +145,16 @@ DiagDirection entrance_dir = (::TileX(tile) == ::TileX(front)) ? (::TileY(tile) < ::TileY(front) ? DIAGDIR_SE : DIAGDIR_NW) : (::TileX(tile) < ::TileX(front) ? DIAGDIR_SW : DIAGDIR_NE); - return ScriptObject::Command::Do(tile, (::RailType)ScriptObject::GetRailType(), entrance_dir); + return ScriptObject::Command::Do(tile, (::RailType)ScriptObject::GetRailType(), entrance_dir, false, false, INVALID_DEPOT, tile); +} + +/* static */ bool ScriptRail::RemoveRailDepot(TileIndex start_tile, TileIndex end_tile) +{ + EnforceCompanyModeValid(false); + EnforcePrecondition(false, ::IsValidTile(start_tile)); + EnforcePrecondition(false, ::IsValidTile(end_tile)); + + return ScriptObject::Command::Do(start_tile, end_tile); } /* static */ bool ScriptRail::BuildRailStation(TileIndex tile, RailTrack direction, SQInteger num_platforms, SQInteger platform_length, StationID station_id) diff --git a/src/script/api/script_rail.hpp b/src/script/api/script_rail.hpp index 2a55a72048fb3..71e5e8032f5df 100644 --- a/src/script/api/script_rail.hpp +++ b/src/script/api/script_rail.hpp @@ -240,6 +240,18 @@ class ScriptRail : public ScriptObject { */ static bool BuildRailDepot(TileIndex tile, TileIndex front); + /** + * Removes rail depots from an area. + * @param start_tile Start tile of the area. + * @param end_tile End tile of the area. + * @pre ScriptMap::IsValidTile(start_tile). + * @pre ScriptMap::IsValidTile(end_tile). + * @game @pre Valid ScriptCompanyMode active in scope. + * @exception ScriptError::ERR_FLAT_LAND_REQUIRED + * @return Whether all depot tiles of the owner in the area have been/can be cleared or not. + */ + static bool RemoveRailDepot(TileIndex start_tile, TileIndex end_tile); + /** * Build a rail station. * @param tile Place to build the station. diff --git a/src/script/api/script_road.cpp b/src/script/api/script_road.cpp index 25b7fc2631c98..73c6fa5da7aa2 100644 --- a/src/script/api/script_road.cpp +++ b/src/script/api/script_road.cpp @@ -535,7 +535,7 @@ static bool NeighbourHasReachableRoad(::RoadType rt, TileIndex start_tile, DiagD DiagDirection entrance_dir = (::TileX(tile) == ::TileX(front)) ? (::TileY(tile) < ::TileY(front) ? DIAGDIR_SE : DIAGDIR_NW) : (::TileX(tile) < ::TileX(front) ? DIAGDIR_SW : DIAGDIR_NE); - return ScriptObject::Command::Do(tile, ScriptObject::GetRoadType(), entrance_dir); + return ScriptObject::Command::Do(tile, ScriptObject::GetRoadType(), entrance_dir, false, false, false, false, INVALID_DEPOT, tile); } /* static */ bool ScriptRoad::_BuildRoadStationInternal(TileIndex tile, TileIndex front, RoadVehicleType road_veh_type, bool drive_through, StationID station_id) diff --git a/src/script/api/script_station.cpp b/src/script/api/script_station.cpp index ee3ff0d5d652d..065c0e6bbbc09 100644 --- a/src/script/api/script_station.cpp +++ b/src/script/api/script_station.cpp @@ -16,6 +16,7 @@ #include "../../roadstop_base.h" #include "../../town.h" #include "../../station_cmd.h" +#include "../../airport_cmd.h" #include "../../safeguards.h" @@ -233,7 +234,7 @@ template EnforcePrecondition(false, IsValidStation(station_id)); EnforcePrecondition(false, HasStationType(station_id, STATION_AIRPORT)); - return (::Station::Get(station_id)->airport.flags & AIRPORT_CLOSED_block) != 0; + return ::Station::Get(station_id)->airport.IsClosed(); } /* static */ bool ScriptStation::OpenCloseAirport(StationID station_id) diff --git a/src/script/api/script_tilelist.cpp b/src/script/api/script_tilelist.cpp index db5aafddafd1e..ac62411d13a01 100644 --- a/src/script/api/script_tilelist.cpp +++ b/src/script/api/script_tilelist.cpp @@ -136,8 +136,8 @@ ScriptTileList_StationType::ScriptTileList_StationType(StationID station_id, Scr if ((station_type & ScriptStation::STATION_TRAIN) != 0) station_type_value |= (1 << ::STATION_RAIL); if ((station_type & ScriptStation::STATION_TRUCK_STOP) != 0) station_type_value |= (1 << ::STATION_TRUCK); if ((station_type & ScriptStation::STATION_BUS_STOP) != 0) station_type_value |= (1 << ::STATION_BUS); - if ((station_type & ScriptStation::STATION_AIRPORT) != 0) station_type_value |= (1 << ::STATION_AIRPORT) | (1 << ::STATION_OILRIG); - if ((station_type & ScriptStation::STATION_DOCK) != 0) station_type_value |= (1 << ::STATION_DOCK) | (1 << ::STATION_OILRIG); + if ((station_type & ScriptStation::STATION_AIRPORT) != 0) station_type_value |= (1 << ::STATION_AIRPORT); + if ((station_type & ScriptStation::STATION_DOCK) != 0) station_type_value |= (1 << ::STATION_DOCK); TileArea ta(::TileXY(rect->left, rect->top), rect->Width(), rect->Height()); for (TileIndex cur_tile : ta) { diff --git a/src/script/api/script_vehiclelist.cpp b/src/script/api/script_vehiclelist.cpp index 73bb4f4d30ad9..267dac241a72e 100644 --- a/src/script/api/script_vehiclelist.cpp +++ b/src/script/api/script_vehiclelist.cpp @@ -60,7 +60,7 @@ ScriptVehicleList_Depot::ScriptVehicleList_Depot(TileIndex tile) case MP_STATION: // Aircraft if (!IsAirport(tile)) return; type = VEH_AIRCRAFT; - dest = GetStationIndex(tile); + dest = GetDepotIndex(tile); break; case MP_RAILWAY: diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index fb5c08ec8668b..4c9c717b2c4df 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -2042,6 +2042,7 @@ static SettingsContainer &GetSettingsTree() construction->Add(new SettingEntry("gui.link_terraform_toolbar")); construction->Add(new SettingEntry("gui.persistent_buildingtools")); construction->Add(new SettingEntry("gui.default_rail_type")); + construction->Add(new SettingEntry("gui.default_air_type")); construction->Add(new SettingEntry("gui.semaphore_build_before")); construction->Add(new SettingEntry("gui.signal_gui_mode")); construction->Add(new SettingEntry("gui.cycle_signal_types")); @@ -2145,6 +2146,20 @@ static SettingsContainer &GetSettingsTree() SettingsPage *limitations = main->Add(new SettingsPage(STR_CONFIG_SETTING_LIMITATIONS)); { + SettingsPage *depots = limitations->Add(new SettingsPage(STR_CONFIG_SETTING_DEPOTS)); + { + depots->Add(new SettingEntry("depot.depot_spread")); + depots->Add(new SettingEntry("depot.distant_join_depots")); + + depots->Add(new SettingEntry("depot.rail_depot_types")); + depots->Add(new SettingEntry("depot.road_depot_types")); + depots->Add(new SettingEntry("depot.water_depot_types")); + depots->Add(new SettingEntry("depot.hangar_types")); + + depots->Add(new SettingEntry("depot.allow_no_comp_railtype_replacements")); + depots->Add(new SettingEntry("depot.allow_no_comp_roadtype_replacements")); + } + limitations->Add(new SettingEntry("construction.command_pause_level")); limitations->Add(new SettingEntry("construction.autoslope")); limitations->Add(new SettingEntry("construction.extra_dynamite")); @@ -2153,6 +2168,7 @@ static SettingsContainer &GetSettingsTree() limitations->Add(new SettingEntry("construction.max_bridge_height")); limitations->Add(new SettingEntry("construction.max_tunnel_length")); limitations->Add(new SettingEntry("station.never_expire_airports")); + limitations->Add(new SettingEntry("station.allow_modify_airports")); limitations->Add(new SettingEntry("vehicle.never_expire_vehicles")); limitations->Add(new SettingEntry("vehicle.max_trains")); limitations->Add(new SettingEntry("vehicle.max_roadveh")); diff --git a/src/settings_table.cpp b/src/settings_table.cpp index 0fd874a38c007..cf4b937d794b7 100644 --- a/src/settings_table.cpp +++ b/src/settings_table.cpp @@ -380,6 +380,34 @@ static void SpriteZoomMinChanged(int32_t) MarkWholeScreenDirty(); } +static bool CheckDifferentRailRoadTypesReplacements(int32_t &new_value) +{ + if (_game_mode == GM_NORMAL) { + if (new_value == 0) { + ShowErrorMessage(STR_CONFIG_SETTING_REPLACEMENTS_DIFF_TYPE, INVALID_STRING_ID, WL_ERROR); + return false; + } + } + return true; +} + +static void InvalidateReplacementWindows(int32_t) +{ + InvalidateWindowClassesData(WC_REPLACE_VEHICLE); +} + +static void DepotSettingsChanged(int32_t) +{ + CloseWindowByClass(WC_BUILD_TOOLBAR); +} + +static void ModifyAirportLayout(int32_t) +{ + CloseWindowByClass(WC_BUILD_TOOLBAR); + extern AirType _last_built_airtype; + _last_built_airtype = AIRTYPE_BEGIN; +} + /** * Update any possible saveload window and delete any newgrf dialogue as * its widget parts might change. Reinit all windows as it allows access to the diff --git a/src/settings_type.h b/src/settings_type.h index ae6f22c42e950..41ee72c800af4 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -158,6 +158,7 @@ struct GUISettings { uint8_t advanced_vehicle_list; ///< use the "advanced" vehicle list uint8_t loading_indicators; ///< show loading indicators uint8_t default_rail_type; ///< the default rail type for the rail GUI + uint8_t default_air_type; ///< the default air type for the rail GUI uint8_t toolbar_pos; ///< position of toolbars, 0=left, 1=center, 2=right uint8_t statusbar_pos; ///< position of statusbar, 0=left, 1=center, 2=right uint8_t window_snap_radius; ///< windows snap at each other if closer than this @@ -567,9 +568,30 @@ struct StationSettings { bool adjacent_stations; ///< allow stations to be built directly adjacent to other stations bool distant_join_stations; ///< allow to join non-adjacent stations bool never_expire_airports; ///< never expire airports + bool allow_modify_airports; ///< allow change the layout of airports uint8_t station_spread; ///< amount a station may spread }; +enum DepotTypes : uint8_t { + ONLY_STANDARD_DEPOT_TYPE = 1, + ONLY_EXTENDED_DEPOT_TYPE = 2, + BOTH_DEPOT_TYPES = 3, +}; + +/** Settings related to depots. */ +struct DepotSettings { + uint8_t depot_spread; ///< amount a depot may spread + bool distant_join_depots; ///< allow to join non-adjacent depots + + uint8_t rail_depot_types; ///< allowed rail depot types for contruction + uint8_t road_depot_types; ///< allowed road depot types for contruction + uint8_t water_depot_types; ///< allowed water depot types for contruction + uint8_t hangar_types; ///< allowed hangar types for contruction + + bool allow_no_comp_railtype_replacements; ///< allow replacing rail vehicles even if rail type is not compatible + bool allow_no_comp_roadtype_replacements; ///< allow replacing road vehicles even if road type is not compatible +}; + /** Default settings for vehicles. */ struct VehicleDefaultSettings { bool servint_ispercent; ///< service intervals are in percents @@ -603,6 +625,7 @@ struct GameSettings { EconomySettings economy; ///< settings to change the economy LinkGraphSettings linkgraph; ///< settings for link graph calculations StationSettings station; ///< settings related to station management + DepotSettings depot; ///< settings related to depot management LocaleSettings locale; ///< settings related to used currency/unit system in the current game }; diff --git a/src/ship.h b/src/ship.h index c47be05436a4e..42d9392e88d19 100644 --- a/src/ship.h +++ b/src/ship.h @@ -31,7 +31,7 @@ struct Ship final : public SpecializedVehicle { /** We don't want GCC to zero our struct! It already is zeroed and has an index! */ Ship() : SpecializedVehicleBase() {} /** We want to 'destruct' the right class. */ - virtual ~Ship() { this->PreDestructor(); } + virtual ~Ship(); void MarkDirty() override; void UpdateDeltaXY() override; @@ -43,7 +43,7 @@ struct Ship final : public SpecializedVehicle { int GetDisplayMaxSpeed() const override { return this->vcache.cached_max_speed / 2; } int GetCurrentMaxSpeed() const override { return std::min(this->vcache.cached_max_speed, this->current_order.GetMaxSpeed() * 2); } Money GetRunningCost() const override; - bool IsInDepot() const override { return this->state == TRACK_BIT_DEPOT; } + bool IsInDepot() const override { return HasBit((uint8_t)this->state, TRACK_DEPOT); } bool Tick() override; void OnNewCalendarDay() override; void OnNewEconomyDay() override; diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index dcd2f6825fe32..8f90445e3dd85 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -36,6 +36,7 @@ #include "industry.h" #include "industry_map.h" #include "ship_cmd.h" +#include "command_func.h" #include "table/strings.h" @@ -187,14 +188,14 @@ static const Depot *FindClosestShipDepot(const Vehicle *v, uint max_distance) const Depot *best_depot = nullptr; uint best_dist_sq = std::numeric_limits::max(); for (const Depot *depot : Depot::Iterate()) { + if (depot->veh_type != VEH_SHIP || depot->owner != v->owner || !depot->IsInUse()) continue; + const TileIndex tile = depot->xy; - if (IsShipDepotTile(tile) && IsTileOwner(tile, v->owner)) { - const uint dist_sq = DistanceSquare(tile, v->tile); - if (dist_sq < best_dist_sq && dist_sq <= max_distance * max_distance && - visited_patch_hashes.count(CalculateWaterRegionPatchHash(GetWaterRegionPatchInfo(tile))) > 0) { - best_dist_sq = dist_sq; - best_depot = depot; - } + const uint dist_sq = DistanceSquare(tile, v->tile); + if (dist_sq < best_dist_sq && dist_sq <= max_distance * max_distance && + visited_patch_hashes.count(CalculateWaterRegionPatchHash(GetWaterRegionPatchInfo(tile))) > 0) { + best_dist_sq = dist_sq; + best_depot = depot; } } @@ -222,7 +223,7 @@ static void CheckIfShipNeedsService(Vehicle *v) } v->current_order.MakeGoToDepot(depot->index, ODTFB_SERVICE); - v->SetDestTile(depot->xy); + v->SetDestTile(depot->GetBestDepotTile(v)); SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); } @@ -290,11 +291,12 @@ Trackdir Ship::GetVehicleTrackdir() const if (this->vehstatus & VS_CRASHED) return INVALID_TRACKDIR; if (this->IsInDepot()) { - /* We'll assume the ship is facing outwards */ - return DiagDirToDiagTrackdir(GetShipDepotDirection(this->tile)); + /* Only old depots need it. */ + /* We'll assume the ship is facing outwards. */ + if (this->state == TRACK_BIT_DEPOT) return DiagDirToDiagTrackdir(GetShipDepotDirection(this->tile)); } - if (this->state == TRACK_BIT_WORMHOLE) { + if (this->state == TRACK_BIT_WORMHOLE || this->IsInDepot()) { /* ship on aqueduct, so just use its direction and assume a diagonal track */ return DiagDirToDiagTrackdir(DirToDiagDir(this->direction)); } @@ -302,6 +304,15 @@ Trackdir Ship::GetVehicleTrackdir() const return TrackDirectionToTrackdir(FindFirstTrack(this->state), this->direction); } +Ship::~Ship() +{ + if (CleaningPool()) return; + + if (this->IsInDepot()) SetDepotReservation(this->tile, DEPOT_RESERVATION_EMPTY); + + this->PreDestructor(); +} + void Ship::MarkDirty() { this->colourmap = PAL_NONE; @@ -373,6 +384,32 @@ static bool CheckReverseShip(const Ship *v, Trackdir *trackdir = nullptr) return YapfShipCheckReverse(v, trackdir); } +static bool CheckPlaceShipOnDepot(TileIndex tile) +{ + assert(IsShipDepotTile(tile)); + return !IsExtendedDepot(tile) || IsExtendedDepotEmpty(tile); +} + +void HandleShipEnterDepot(Ship *v) +{ + assert(IsShipDepotTile(v->tile)); + + if (IsExtendedDepot(v->tile)) { + SetDepotReservation(v->tile, DEPOT_RESERVATION_IN_USE); + v->state |= TRACK_BIT_DEPOT; + v->cur_speed = 0; + v->UpdateCache(); + v->UpdateViewport(true, true); + SetWindowClassesDirty(WC_SHIPS_LIST); + SetWindowDirty(WC_VEHICLE_VIEW, v->index); + + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); + v->StartService(); + } else { + VehicleEnterDepot(v); + } +} + static bool CheckShipLeaveDepot(Ship *v) { if (!v->IsChainInDepot()) return false; @@ -383,53 +420,66 @@ static bool CheckShipLeaveDepot(Ship *v) /* We are leaving a depot, but have to go to the exact same one; re-enter */ if (v->current_order.IsType(OT_GOTO_DEPOT) && IsShipDepotTile(v->tile) && GetDepotIndex(v->tile) == v->current_order.GetDestination()) { - VehicleEnterDepot(v); + HandleShipEnterDepot(v); return true; } /* Don't leave depot if no destination set */ if (v->dest_tile == 0) return true; - /* Don't leave depot if another vehicle is already entering/leaving */ - /* This helps avoid CPU load if many ships are set to start at the same time */ - if (HasVehicleOnPos(v->tile, nullptr, &EnsureNoMovingShipProc)) return true; + if (IsExtendedDepot(v->tile)) { + SetDepotReservation(v->tile, DEPOT_RESERVATION_EMPTY); + } else { + /* Don't leave depot if another vehicle is already entering/leaving */ + /* This helps avoid CPU load if many ships are set to start at the same time */ + if (HasVehicleOnPos(v->tile, nullptr, &EnsureNoMovingShipProc)) return true; + + TileIndex tile = v->tile; + Axis axis = GetShipDepotAxis(tile); + bool reverse = false; + + DiagDirection north_dir = ReverseDiagDir(AxisToDiagDir(axis)); + TileIndex north_neighbour = TileAdd(tile, TileOffsByDiagDir(north_dir)); + DiagDirection south_dir = AxisToDiagDir(axis); + TileIndex south_neighbour = TileAdd(tile, 2 * TileOffsByDiagDir(south_dir)); + + TrackBits north_tracks = DiagdirReachesTracks(north_dir) & GetTileShipTrackStatus(north_neighbour); + TrackBits south_tracks = DiagdirReachesTracks(south_dir) & GetTileShipTrackStatus(south_neighbour); + if (north_tracks && south_tracks) { + if (CheckReverseShip(v)) north_tracks = TRACK_BIT_NONE; + } - TileIndex tile = v->tile; - Axis axis = GetShipDepotAxis(tile); + if (north_tracks) { + /* Leave towards north */ + v->rotation = v->direction = DiagDirToDir(north_dir); + } else if (south_tracks) { + /* Leave towards south */ + v->rotation = v->direction = DiagDirToDir(south_dir); + } else { + /* Both ways blocked */ + return false; + } - DiagDirection north_dir = ReverseDiagDir(AxisToDiagDir(axis)); - TileIndex north_neighbour = TileAdd(tile, TileOffsByDiagDir(north_dir)); - DiagDirection south_dir = AxisToDiagDir(axis); - TileIndex south_neighbour = TileAdd(tile, 2 * TileOffsByDiagDir(south_dir)); + v->state = AxisToTrackBits(axis); + v->vehstatus &= ~VS_HIDDEN; - TrackBits north_tracks = DiagdirReachesTracks(north_dir) & GetTileShipTrackStatus(north_neighbour); - TrackBits south_tracks = DiagdirReachesTracks(south_dir) & GetTileShipTrackStatus(south_neighbour); - if (north_tracks && south_tracks) { - if (CheckReverseShip(v)) north_tracks = TRACK_BIT_NONE; - } + /* Leave towards south if reverse. */ + v->rotation = v->direction = DiagDirToDir(reverse ? south_dir : north_dir); - if (north_tracks) { - /* Leave towards north */ - v->rotation = v->direction = DiagDirToDir(north_dir); - } else if (south_tracks) { - /* Leave towards south */ - v->rotation = v->direction = DiagDirToDir(south_dir); - } else { - /* Both ways blocked */ - return false; + v->state = AxisToTrackBits(axis); + v->vehstatus &= ~VS_HIDDEN; } - v->state = AxisToTrackBits(axis); - v->vehstatus &= ~VS_HIDDEN; - + v->state &= ~TRACK_BIT_DEPOT; v->cur_speed = 0; v->UpdateViewport(true, true); - SetWindowDirty(WC_VEHICLE_DEPOT, v->tile); + DepotID depot_id = GetDepotIndex(v->tile); + SetWindowDirty(WC_VEHICLE_DEPOT, depot_id); VehicleServiceInDepot(v); v->LeaveUnbunchingDepot(); v->PlayLeaveStationSound(); - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, depot_id); SetWindowClassesDirty(WC_SHIPS_LIST); return false; @@ -494,6 +544,13 @@ static void ShipArrivesAt(const Vehicle *v, Station *st) */ static Track ChooseShipTrack(Ship *v, TileIndex tile, TrackBits tracks) { + /* Before choosing a track, if close to the destination station or depot (not an oil rig)... */ + if (DistanceManhattan(v->dest_tile, tile) <= 5 && (v->current_order.IsType(OT_GOTO_DEPOT) && + (!IsShipDepotTile(v->dest_tile) || (IsExtendedDepotTile(v->dest_tile) && !IsExtendedDepotEmpty(v->dest_tile))))) { + /* Try to get a depot tile. */ + v->dest_tile = Depot::Get(v->current_order.GetDestination())->GetBestDepotTile(v); + } + bool path_found = true; Track track; @@ -656,7 +713,7 @@ bool IsShipDestinationTile(TileIndex tile, StationID station) const Industry *i = Industry::GetByTile(t); if (i->neutral_station != nullptr && i->neutral_station->index == station) return true; } - if (IsTileType(t, MP_STATION) && IsOilRig(t) && GetStationIndex(t) == station) return true; + if (IsWateredBuiltInHeliportTile(t) && GetStationIndex(t) == station) return true; } return false; } @@ -705,6 +762,8 @@ static void ShipController(Ship *v) if (v->vehstatus & VS_STOPPED) return; + if (v->ContinueServicing()) return; + if (ProcessOrders(v) && CheckReverseShip(v)) return ReverseShip(v); v->HandleLoading(); @@ -764,13 +823,6 @@ static void ShipController(Ship *v) UpdateVehicleTimetable(v, true); v->IncrementRealOrderIndex(); v->current_order.MakeDummy(); - } else if (v->current_order.IsType(OT_GOTO_DEPOT) && - v->dest_tile == gp.new_tile) { - /* Depot orders really need to reach the tile */ - if ((gp.x & 0xF) == 8 && (gp.y & 0xF) == 8) { - VehicleEnterDepot(v); - return; - } } else if (v->current_order.IsType(OT_GOTO_STATION) && IsDockingTile(gp.new_tile)) { /* Process station in the orderlist. */ Station *st = Station::Get(v->current_order.GetDestination()); @@ -791,6 +843,18 @@ static void ShipController(Ship *v) /* New tile */ if (!IsValidTile(gp.new_tile)) return ReverseShip(v); + if (v->current_order.IsType(OT_GOTO_DEPOT) && + IsShipDepotTile(gp.new_tile) && + GetOtherShipDepotTile(gp.new_tile) == gp.old_tile && + v->current_order.GetDestination() == GetDepotIndex(gp.new_tile)) { + if (CheckPlaceShipOnDepot(v->tile)) { + HandleShipEnterDepot(v); + v->UpdatePosition(); + v->UpdateViewport(true, true); + return; + } + } + const DiagDirection diagdir = DiagdirBetweenTiles(gp.old_tile, gp.new_tile); assert(diagdir != INVALID_DIAGDIR); const TrackBits tracks = GetAvailShipTracks(gp.new_tile, diagdir); @@ -894,10 +958,22 @@ void Ship::SetDestTile(TileIndex tile) */ CommandCost CmdBuildShip(DoCommandFlag flags, TileIndex tile, const Engine *e, Vehicle **ret) { - tile = GetShipDepotNorthTile(tile); + assert(IsShipDepotTile(tile)); + if (!(flags & DC_AUTOREPLACE)) { + std::vector *depot_tiles = &(Depot::GetByTile(tile)->depot_tiles); + tile = INVALID_TILE; + for (std::vector::iterator it = depot_tiles->begin(); it != depot_tiles->end(); ++it) { + if (CheckPlaceShipOnDepot(*it)) { + tile = *it; + break; + } + } + if (tile == INVALID_TILE) return_cmd_error(STR_ERROR_NO_FREE_DEPOT); + } + if (flags & DC_EXEC) { - int x; - int y; + bool is_extended_depot = IsExtendedDepot(tile); + TileIndexDiffC offset = TileIndexDiffCByDiagDir(ReverseDiagDir(GetShipDepotDirection(tile))); const ShipVehicleInfo *svi = &e->u.ship; @@ -906,14 +982,22 @@ CommandCost CmdBuildShip(DoCommandFlag flags, TileIndex tile, const Engine *e, V v->owner = _current_company; v->tile = tile; - x = TileX(tile) * TILE_SIZE + TILE_SIZE / 2; - y = TileY(tile) * TILE_SIZE + TILE_SIZE / 2; - v->x_pos = x; - v->y_pos = y; - v->z_pos = GetSlopePixelZ(x, y); + v->x_pos = TileX(tile) * TILE_SIZE + TILE_SIZE / 2 + offset.x * (TILE_SIZE / 2 - 1); + v->y_pos = TileY(tile) * TILE_SIZE + TILE_SIZE / 2 + offset.y * (TILE_SIZE / 2 - 1); + v->z_pos = GetSlopePixelZ(v->x_pos, v->y_pos); + v->state = TRACK_BIT_DEPOT; + if (is_extended_depot) { + v->state |= AxisToTrackBits(GetShipDepotAxis(tile)); + v->direction = AxisToDirection(GetShipDepotAxis(v->tile)); + SetDepotReservation(v->tile, DEPOT_RESERVATION_FULL_STOPPED_VEH); + } else { + v->vehstatus |= VS_HIDDEN; + } + + v->rotation = v->direction; v->UpdateDeltaXY(); - v->vehstatus = VS_HIDDEN | VS_STOPPED | VS_DEFPAL; + v->vehstatus |= VS_STOPPED | VS_DEFPAL; v->spritenum = svi->image_index; v->cargo_type = e->GetDefaultCargoType(); @@ -929,8 +1013,6 @@ CommandCost CmdBuildShip(DoCommandFlag flags, TileIndex tile, const Engine *e, V v->reliability_spd_dec = e->reliability_spd_dec; v->max_age = e->GetLifeLengthInDays(); - v->state = TRACK_BIT_DEPOT; - v->SetServiceInterval(Company::Get(_current_company)->settings.vehicle.servint_ships); v->date_of_last_service = TimerGameEconomy::date; v->date_of_last_service_newgrf = TimerGameCalendar::date; @@ -951,6 +1033,8 @@ CommandCost CmdBuildShip(DoCommandFlag flags, TileIndex tile, const Engine *e, V v->InvalidateNewGRFCacheOfChain(); v->UpdatePosition(); + + if (is_extended_depot) v->MarkDirty(); } return CommandCost(); @@ -961,5 +1045,5 @@ ClosestDepot Ship::FindClosestDepot() const Depot *depot = FindClosestShipDepot(this, MAX_SHIP_DEPOT_SEARCH_DISTANCE); if (depot == nullptr) return ClosestDepot(); - return ClosestDepot(depot->xy, depot->index); + return ClosestDepot(depot->GetBestDepotTile(this), depot->index); } diff --git a/src/signal.cpp b/src/signal.cpp index d060c58d2248b..e7ef7f7b5b5a0 100644 --- a/src/signal.cpp +++ b/src/signal.cpp @@ -278,6 +278,13 @@ static SigFlags ExploreSegment(Owner owner) if (GetTileOwner(tile) != owner) continue; // do not propagate signals on others' tiles (remove for tracksharing) if (IsRailDepot(tile)) { + if (IsExtendedRailDepot(tile)) { + assert(enterdir != INVALID_DIAGDIR); + if (DiagDirToDiagTrack(enterdir) != GetRailDepotTrack(tile)) continue; // different axis + if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, nullptr, &TrainOnTileEnum)) flags |= SF_TRAIN; + tile += TileOffsByDiagDir(exitdir); + break; + } if (enterdir == INVALID_DIAGDIR) { // from 'inside' - train just entered or left the depot if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, nullptr, &TrainOnTileEnum)) flags |= SF_TRAIN; exitdir = GetRailDepotDirection(tile); @@ -496,8 +503,14 @@ static SigSegState UpdateSignalsInBuffer(Owner owner) case MP_RAILWAY: if (IsRailDepot(tile)) { /* 'optimization assert' do not try to update signals in other cases */ - assert(dir == INVALID_DIAGDIR || dir == GetRailDepotDirection(tile)); - _tbdset.Add(tile, INVALID_DIAGDIR); // start from depot inside + if (IsExtendedRailDepot(tile)) { + dir = GetRailDepotDirection(tile); + _tbdset.Add(tile, dir); + _tbdset.Add(tile + TileOffsByDiagDir(dir), ReverseDiagDir(dir)); + } else { + assert(dir == INVALID_DIAGDIR || dir == GetRailDepotDirection(tile)); + _tbdset.Add(tile, INVALID_DIAGDIR); // start from depot inside + } break; } [[fallthrough]]; diff --git a/src/station.cpp b/src/station.cpp index e0dab59067147..dd8b4807d3917 100644 --- a/src/station.cpp +++ b/src/station.cpp @@ -26,6 +26,9 @@ #include "core/random_func.hpp" #include "linkgraph/linkgraph.h" #include "linkgraph/linkgraphschedule.h" +#include "depot_base.h" +#include "air.h" +#include "air_map.h" #include "table/strings.h" @@ -268,43 +271,6 @@ void Station::MarkTilesDirty(bool cargo_change) const } } -/* virtual */ uint Station::GetPlatformLength(TileIndex tile) const -{ - assert(this->TileBelongsToRailStation(tile)); - - TileIndexDiff delta = (GetRailStationAxis(tile) == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1)); - - TileIndex t = tile; - uint len = 0; - do { - t -= delta; - len++; - } while (IsCompatibleTrainStationTile(t, tile)); - - t = tile; - do { - t += delta; - len++; - } while (IsCompatibleTrainStationTile(t, tile)); - - return len - 1; -} - -/* virtual */ uint Station::GetPlatformLength(TileIndex tile, DiagDirection dir) const -{ - TileIndex start_tile = tile; - uint length = 0; - assert(IsRailStationTile(tile)); - assert(dir < DIAGDIR_END); - - do { - length++; - tile += TileOffsByDiagDir(dir); - } while (IsCompatibleTrainStationTile(tile, start_tile)); - - return length; -} - /** * Get the catchment size of an individual station tile. * @param tile Station tile to get catchment size of. @@ -319,8 +285,8 @@ static uint GetTileCatchmentRadius(TileIndex tile, const Station *st) if (_settings_game.station.modified_catchment) { switch (GetStationType(tile)) { case STATION_RAIL: return CA_TRAIN; - case STATION_OILRIG: return CA_UNMODIFIED; - case STATION_AIRPORT: return st->airport.GetSpec()->catchment; + case STATION_AIRPORT: + return HasAirportCatchment(tile) ? st->airport.AirportCatchmentRadius() : CA_NONE; case STATION_TRUCK: return CA_TRUCK; case STATION_BUS: return CA_BUS; case STATION_DOCK: return CA_DOCK; @@ -353,7 +319,7 @@ uint Station::GetCatchmentRadius() const if (this->truck_stops != nullptr) ret = std::max(ret, CA_TRUCK); if (this->train_station.tile != INVALID_TILE) ret = std::max(ret, CA_TRAIN); if (this->ship_station.tile != INVALID_TILE) ret = std::max(ret, CA_DOCK); - if (this->airport.tile != INVALID_TILE) ret = std::max(ret, this->airport.GetSpec()->catchment); + if (this->airport.tile != INVALID_TILE) ret = std::max(ret, this->airport.AirportCatchmentRadius()); } else { if (this->bus_stops != nullptr || this->truck_stops != nullptr || this->train_station.tile != INVALID_TILE || this->ship_station.tile != INVALID_TILE || this->airport.tile != INVALID_TILE) { ret = CA_UNMODIFIED; @@ -709,25 +675,24 @@ StationRect& StationRect::operator = (const Rect &src) return *this; } -/** - * Calculates the maintenance cost of all airports of a company. - * @param owner Company. - * @return Total cost. - */ -Money AirportMaintenanceCost(Owner owner) -{ - Money total_cost = 0; - - for (const Station *st : Station::Iterate()) { - if (st->owner == owner && (st->facilities & FACIL_AIRPORT)) { - total_cost += _price[PR_INFRASTRUCTURE_AIRPORT] * st->airport.GetSpec()->maintenance_cost; - } - } - /* 3 bits fraction for the maintenance cost factor. */ - return total_cost >> 3; +DepotID GetHangarIndex(TileIndex t) { + assert(IsAirportTile(t)); + assert(Station::GetByTile(t)->airport.hangar != nullptr); + return Station::GetByTile(t)->airport.hangar->index; } bool StationCompare::operator() (const Station *lhs, const Station *rhs) const { return lhs->index < rhs->index; } + +/** + * Get the corresponent catchment radius for each infrastructure tile of this airport. + * @return the catchment radius. + */ +CatchmentArea Airport::AirportCatchmentRadius() const +{ + const AirTypeInfo *ati = GetAirTypeInfo(this->air_type); + assert(ati->catchment_radius <= MAX_CATCHMENT); + return (CatchmentArea)ati->catchment_radius; +} diff --git a/src/station_base.h b/src/station_base.h index 6dda3ca8435cb..c50a72b4f177d 100644 --- a/src/station_base.h +++ b/src/station_base.h @@ -15,9 +15,11 @@ #include "newgrf_airport.h" #include "cargopacket.h" #include "industry_type.h" +#include "air_type.h" #include "linkgraph/linkgraph_type.h" #include "newgrf_storage.h" #include "bitmap_type.h" +#include "depot_type.h" static const uint8_t INITIAL_STATION_RATING = 175; static const uint8_t MAX_STATION_RATING = 255; @@ -290,13 +292,22 @@ struct GoodsEntry { struct Airport : public TileArea { Airport() : TileArea(INVALID_TILE, 0, 0) {} - uint64_t flags; ///< stores which blocks on the airport are taken. was 16 bit earlier on, then 32 - uint8_t type; ///< Type of this airport, @see AirportTypes - uint8_t layout; ///< Airport layout number. + uint64_t flags; ///< Stores some data of the airport. + uint8_t type; ///< Type of this airport, @see AirportTypes + uint8_t layout; ///< Airport layout number. Direction rotation; ///< How this airport is rotated. + AirType air_type; ///< NOSAVE: airport type. + Depot *hangar; ///< The corresponding hangar of this airport, if any. + + std::vector aprons; ///< NOSAVE: aprons this airport has. + std::vector helipads; ///< NOSAVE: helipads of this airport. + std::vector heliports; ///< NOSAVE: heliports of this airport (cannot move through the airport without flying) + std::vector runways; ///< NOSAVE: runways of this airport. PersistentStorage *psa; ///< Persistent storage for NewGRF airports. + CatchmentArea AirportCatchmentRadius() const; + /** * Get the AirportSpec that from the airport type of this airport. If there * is no airport (\c tile == INVALID_TILE) then return the dummy AirportSpec. @@ -305,120 +316,25 @@ struct Airport : public TileArea { const AirportSpec *GetSpec() const { if (this->tile == INVALID_TILE) return &AirportSpec::dummy; + if (this->type == AT_CUSTOM) return &AirportSpec::custom; return AirportSpec::Get(this->type); } - /** - * Get the finite-state machine for this airport or the finite-state machine - * for the dummy airport in case this isn't an airport. - * @pre this->type < NEW_AIRPORT_OFFSET. - * @return The state machine for this airport. - */ - const AirportFTAClass *GetFTA() const - { - return this->GetSpec()->fsm; - } - /** Check if this airport has at least one hangar. */ inline bool HasHangar() const { - return !this->GetSpec()->depots.empty(); + return HasBit(this->flags, AFB_HANGAR); } - /** - * Add the tileoffset to the base tile of this airport but rotate it first. - * The base tile is the northernmost tile of this airport. This function - * helps to make sure that getting the tile of a hangar works even for - * rotated airport layouts without requiring a rotated array of hangar tiles. - * @param tidc The tilediff to add to the airport tile. - * @return The tile of this airport plus the rotated offset. - */ - inline TileIndex GetRotatedTileFromOffset(TileIndexDiffC tidc) const + /** Check if this airport has at least one landing runway. */ + inline bool HasLandingRunway() const { - const AirportSpec *as = this->GetSpec(); - switch (this->rotation) { - case DIR_N: return this->tile + ToTileIndexDiff(tidc); - - case DIR_E: return this->tile + TileDiffXY(tidc.y, as->size_x - 1 - tidc.x); - - case DIR_S: return this->tile + TileDiffXY(as->size_x - 1 - tidc.x, as->size_y - 1 - tidc.y); - - case DIR_W: return this->tile + TileDiffXY(as->size_y - 1 - tidc.y, tidc.x); - - default: NOT_REACHED(); - } + return HasBit(this->flags, AFB_LANDING_RUNWAY); } - /** - * Get the first tile of the given hangar. - * @param hangar_num The hangar to get the location of. - * @pre hangar_num < GetNumHangars(). - * @return A tile with the given hangar. - */ - inline TileIndex GetHangarTile(uint hangar_num) const + inline bool IsClosed() const { - for (const auto &depot : this->GetSpec()->depots) { - if (depot.hangar_num == hangar_num) { - return this->GetRotatedTileFromOffset(depot.ti); - } - } - NOT_REACHED(); - } - - /** - * Get the exit direction of the hangar at a specific tile. - * @param tile The tile to query. - * @pre IsHangarTile(tile). - * @return The exit direction of the hangar, taking airport rotation into account. - */ - inline Direction GetHangarExitDirection(TileIndex tile) const - { - const AirportSpec *as = this->GetSpec(); - const HangarTileTable *htt = GetHangarDataByTile(tile); - return ChangeDir(htt->dir, DirDifference(this->rotation, as->layouts[0].rotation)); - } - - /** - * Get the hangar number of the hangar at a specific tile. - * @param tile The tile to query. - * @pre IsHangarTile(tile). - * @return The hangar number of the hangar at the given tile. - */ - inline uint GetHangarNum(TileIndex tile) const - { - const HangarTileTable *htt = GetHangarDataByTile(tile); - return htt->hangar_num; - } - - /** Get the number of hangars on this airport. */ - inline uint GetNumHangars() const - { - uint num = 0; - uint counted = 0; - for (const auto &depot : this->GetSpec()->depots) { - if (!HasBit(counted, depot.hangar_num)) { - num++; - SetBit(counted, depot.hangar_num); - } - } - return num; - } - -private: - /** - * Retrieve hangar information of a hangar at a given tile. - * @param tile %Tile containing the hangar. - * @return The requested hangar information. - * @pre The \a tile must be at a hangar tile at an airport. - */ - inline const HangarTileTable *GetHangarDataByTile(TileIndex tile) const - { - for (const auto &depot : this->GetSpec()->depots) { - if (this->GetRotatedTileFromOffset(depot.ti) == tile) { - return &depot; - } - } - NOT_REACHED(); + return HasBit(this->flags, AFB_CLOSED_MANUAL); } }; @@ -483,9 +399,6 @@ struct Station final : SpecializedStation { void MoveSign(TileIndex new_xy) override; void AfterStationTileSetChange(bool adding, StationType type); - - uint GetPlatformLength(TileIndex tile, DiagDirection dir) const override; - uint GetPlatformLength(TileIndex tile) const override; void RecomputeCatchment(bool no_clear_nearby_lists = false); static void RecomputeCatchmentForAll(); @@ -496,6 +409,10 @@ struct Station final : SpecializedStation { void RemoveIndustryToDeliver(Industry *ind); void RemoveFromAllNearbyLists(); + void LoadAirportTilesFromSpec(TileArea ta, DiagDirection rotation, AirType airtype); + void ClearAirportDataInfrastructure(); + void UpdateAirportDataStructure(); + inline bool TileIsInCatchment(TileIndex tile) const { return this->catchment_tiles.HasTile(tile); diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 0dd4edb779406..5cfa1db177e59 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -8,7 +8,6 @@ /** @file station_cmd.cpp Handling of station tiles. */ #include "stdafx.h" -#include "aircraft.h" #include "bridge_map.h" #include "vehiclelist_func.h" #include "viewport_func.h" @@ -27,6 +26,7 @@ #include "pathfinder/yapf/yapf_cache.h" #include "road_internal.h" /* For drawing catenary/checking road removal */ #include "autoslope.h" +#include "air.h" #include "water.h" #include "strings_internal.h" #include "clear_func.h" @@ -48,7 +48,6 @@ #include "core/random_func.hpp" #include "core/container_func.hpp" #include "company_base.h" -#include "table/airporttile_ids.h" #include "newgrf_airporttiles.h" #include "order_backup.h" #include "newgrf_house.h" @@ -67,10 +66,16 @@ #include "timer/timer_game_tick.h" #include "cheat_type.h" #include "road_func.h" +#include "depot_base.h" +#include "platform_func.h" +#include "tilehighlight_type.h" +#include "air_map.h" #include "widgets/station_widget.h" #include "table/strings.h" +#include "table/autorail.h" +#include "table/clear_land.h" #include @@ -83,29 +88,6 @@ */ /* static */ const FlowStat::SharesMap FlowStat::empty_sharesmap; -/** - * Check whether the given tile is a hangar. - * @param t the tile to of whether it is a hangar. - * @pre IsTileType(t, MP_STATION) - * @return true if and only if the tile is a hangar. - */ -bool IsHangar(Tile t) -{ - assert(IsTileType(t, MP_STATION)); - - /* If the tile isn't an airport there's no chance it's a hangar. */ - if (!IsAirport(t)) return false; - - const Station *st = Station::GetByTile(t); - const AirportSpec *as = st->airport.GetSpec(); - - for (const auto &depot : as->depots) { - if (st->airport.GetRotatedTileFromOffset(depot.ti) == TileIndex(t)) return true; - } - - return false; -} - /** * Look for a station owned by the given company around the given tile area. * @param ta the area to search over @@ -212,15 +194,6 @@ static bool CMSATree(TileIndex tile) #define M(x) ((x) - STR_SV_STNAME) -enum StationNaming { - STATIONNAMING_RAIL, - STATIONNAMING_ROAD, - STATIONNAMING_AIRPORT, - STATIONNAMING_OILRIG, - STATIONNAMING_DOCK, - STATIONNAMING_HELIPORT, -}; - /** Information to handle station action 0 property 24 correctly */ struct StationNameInformation { uint32_t free_names; ///< Current bitset of free names (we can remove names). @@ -251,7 +224,7 @@ static bool FindNearIndustryName(TileIndex tile, void *user_data) return !sni->indtypes[indtype]; } -static StringID GenerateStationName(Station *st, TileIndex tile, StationNaming name_class) +StringID GenerateStationName(Station *st, TileIndex tile, StationNaming name_class) { static const uint32_t _gen_station_name_bits[] = { 0, // STATIONNAMING_RAIL @@ -428,7 +401,6 @@ void Station::GetTileArea(TileArea *ta, StationType type) const return; case STATION_DOCK: - case STATION_OILRIG: *ta = this->docking_station; return; @@ -698,7 +670,7 @@ static void UpdateStationSignCoord(BaseStation *st) * @param name_class Station naming class to use to generate the new station's name * @return Command error that occurred, if any */ -static CommandCost BuildStationPart(Station **st, DoCommandFlag flags, bool reuse, TileArea area, StationNaming name_class) +CommandCost BuildStationPart(Station **st, DoCommandFlag flags, bool reuse, TileArea area, StationNaming name_class) { /* Find a deleted station close to us */ if (*st == nullptr && reuse) *st = GetClosestDeletedStation(area.tile); @@ -843,30 +815,6 @@ CommandCost CheckBuildableTile(TileIndex tile, uint invalid_dirs, int &allowed_z return cost; } -/** - * Checks if an airport can be built at the given location and clear the area. - * @param tile_iter Airport tile iterator. - * @param flags Operation to perform. - * @return The cost in case of success, or an error code if it failed. - */ -static CommandCost CheckFlatLandAirport(AirportTileTableIterator tile_iter, DoCommandFlag flags) -{ - CommandCost cost(EXPENSES_CONSTRUCTION); - int allowed_z = -1; - - for (; tile_iter != INVALID_TILE; ++tile_iter) { - CommandCost ret = CheckBuildableTile(tile_iter, 0, allowed_z, true); - if (ret.Failed()) return ret; - cost.AddCost(ret); - - ret = Command::Do(flags, tile_iter); - if (ret.Failed()) return ret; - cost.AddCost(ret); - } - - return cost; -} - /** * Checks if a rail station can be built at the given tile. * @param tile_cur Tile to check. @@ -1199,6 +1147,20 @@ CommandCost FindJoiningBaseStation(StationID existing_station, StationID station return CommandCost(); } +/** + * Find a nearby airport that joins this station. + * @param existing_station an existing station we build over + * @param station_to_join the station to join to + * @param adjacent whether adjacent stations are allowed + * @param ta the area of the newly build station + * @param st 'return' pointer for the found station + * @return command cost with the error or 'okay' + */ +CommandCost FindJoiningAirport(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, Station **st) +{ + return FindJoiningBaseStation(existing_station, station_to_join, adjacent, ta, st, [](const Station *) -> bool { return true; }); +} + /** * Find a nearby station that joins this station. * @param existing_station an existing station we build over @@ -1208,7 +1170,7 @@ CommandCost FindJoiningBaseStation(StationID existing_station, StationID station * @param st 'return' pointer for the found station * @return command cost with the error or 'okay' */ -static CommandCost FindJoiningStation(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, Station **st) +CommandCost FindJoiningStation(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, Station **st) { return FindJoiningBaseStation(existing_station, station_to_join, adjacent, ta, st, [](const Station *) -> bool { return true; }); } @@ -1239,9 +1201,10 @@ CommandCost FindJoiningWaypoint(StationID existing_waypoint, StationID waypoint_ static void FreeTrainReservation(Train *v) { FreeTrainTrackReservation(v); - if (IsRailStationTile(v->tile)) SetRailStationPlatformReservation(v->tile, TrackdirToExitdir(v->GetVehicleTrackdir()), false); + if (IsPlatformTile(v->tile)) SetPlatformReservation(v->tile, TrackdirToExitdir(v->GetVehicleTrackdir()), false); v = v->Last(); - if (IsRailStationTile(v->tile)) SetRailStationPlatformReservation(v->tile, TrackdirToExitdir(ReverseTrackdir(v->GetVehicleTrackdir())), false); + if (IsPlatformTile(v->tile)) SetPlatformReservation(v->tile, TrackdirToExitdir(ReverseTrackdir(v->GetVehicleTrackdir())), false); + } /** @@ -1250,10 +1213,10 @@ static void FreeTrainReservation(Train *v) */ static void RestoreTrainReservation(Train *v) { - if (IsRailStationTile(v->tile)) SetRailStationPlatformReservation(v->tile, TrackdirToExitdir(v->GetVehicleTrackdir()), true); + if (IsPlatformTile(v->tile)) SetPlatformReservation(v->tile, TrackdirToExitdir(v->GetVehicleTrackdir()), true); TryPathReserve(v, true, true); v = v->Last(); - if (IsRailStationTile(v->tile)) SetRailStationPlatformReservation(v->tile, TrackdirToExitdir(ReverseTrackdir(v->GetVehicleTrackdir())), true); + if (IsPlatformTile(v->tile)) SetPlatformReservation(v->tile, TrackdirToExitdir(ReverseTrackdir(v->GetVehicleTrackdir())), true); } /** @@ -1521,21 +1484,21 @@ CommandCost CmdBuildRailStation(DoCommandFlag flags, TileIndex tile_org, RailTyp TileIndex platform_end = tile; /* We can only account for tiles that are reachable from this tile, so ignore primarily blocked tiles while finding the platform begin and end. */ - for (TileIndex next_tile = platform_begin - tile_offset; IsCompatibleTrainStationTile(next_tile, platform_begin); next_tile -= tile_offset) { + for (TileIndex next_tile = platform_begin - tile_offset; IsCompatiblePlatformTile(next_tile, platform_begin); next_tile -= tile_offset) { platform_begin = next_tile; } - for (TileIndex next_tile = platform_end + tile_offset; IsCompatibleTrainStationTile(next_tile, platform_end); next_tile += tile_offset) { + for (TileIndex next_tile = platform_end + tile_offset; IsCompatiblePlatformTile(next_tile, platform_end); next_tile += tile_offset) { platform_end = next_tile; } /* If there is at least on reservation on the platform, we reserve the whole platform. */ bool reservation = false; for (TileIndex t = platform_begin; !reservation && t <= platform_end; t += tile_offset) { - reservation = HasStationReservation(t); + reservation = HasPlatformReservation(t); } if (reservation) { - SetRailStationPlatformReservation(platform_begin, dir, true); + SetPlatformReservation(platform_begin, dir, true); } } @@ -2412,346 +2375,6 @@ CommandCost CmdRemoveFromRoadWaypoint(DoCommandFlag flags, TileIndex start, Tile return RemoveGenericRoadStop(flags, roadstop_area, STATION_ROADWAYPOINT, false); } -/** - * Get a possible noise reduction factor based on distance from town center. - * The further you get, the less noise you generate. - * So all those folks at city council can now happily slee... work in their offices - * @param as airport information - * @param distance minimum distance between town and airport - * @return the noise that will be generated, according to distance - */ -uint8_t GetAirportNoiseLevelForDistance(const AirportSpec *as, uint distance) -{ - /* 0 cannot be accounted, and 1 is the lowest that can be reduced from town. - * So no need to go any further*/ - if (as->noise_level < 2) return as->noise_level; - - /* The steps for measuring noise reduction are based on the "magical" (and arbitrary) 8 base distance - * adding the town_council_tolerance 4 times, as a way to graduate, depending of the tolerance. - * Basically, it says that the less tolerant a town is, the bigger the distance before - * an actual decrease can be granted */ - uint8_t town_tolerance_distance = 8 + (_settings_game.difficulty.town_council_tolerance * 4); - - /* now, we want to have the distance segmented using the distance judged bareable by town - * This will give us the coefficient of reduction the distance provides. */ - uint noise_reduction = distance / town_tolerance_distance; - - /* If the noise reduction equals the airport noise itself, don't give it for free. - * Otherwise, simply reduce the airport's level. */ - return noise_reduction >= as->noise_level ? 1 : as->noise_level - noise_reduction; -} - -/** - * Finds the town nearest to given airport. Based on minimal manhattan distance to any airport's tile. - * If two towns have the same distance, town with lower index is returned. - * @param as airport's description - * @param rotation airport's rotation - * @param tile origin tile (top corner of the airport) - * @param it An iterator over all airport tiles (consumed) - * @param[out] mindist Minimum distance to town - * @return nearest town to airport - */ -Town *AirportGetNearestTown(const AirportSpec *as, Direction rotation, TileIndex tile, TileIterator &&it, uint &mindist) -{ - assert(Town::GetNumItems() > 0); - - Town *nearest = nullptr; - - auto width = as->size_x; - auto height = as->size_y; - if (rotation == DIR_E || rotation == DIR_W) std::swap(width, height); - - uint perimeter_min_x = TileX(tile); - uint perimeter_min_y = TileY(tile); - uint perimeter_max_x = perimeter_min_x + width - 1; - uint perimeter_max_y = perimeter_min_y + height - 1; - - mindist = UINT_MAX - 1; // prevent overflow - - for (TileIndex cur_tile = *it; cur_tile != INVALID_TILE; cur_tile = ++it) { - assert(IsInsideBS(TileX(cur_tile), perimeter_min_x, width)); - assert(IsInsideBS(TileY(cur_tile), perimeter_min_y, height)); - if (TileX(cur_tile) == perimeter_min_x || TileX(cur_tile) == perimeter_max_x || TileY(cur_tile) == perimeter_min_y || TileY(cur_tile) == perimeter_max_y) { - Town *t = CalcClosestTownFromTile(cur_tile, mindist + 1); - if (t == nullptr) continue; - - uint dist = DistanceManhattan(t->xy, cur_tile); - if (dist == mindist && t->index < nearest->index) nearest = t; - if (dist < mindist) { - nearest = t; - mindist = dist; - } - } - } - - return nearest; -} - -/** - * Finds the town nearest to given existing airport. Based on minimal manhattan distance to any airport's tile. - * If two towns have the same distance, town with lower index is returned. - * @param station existing station with airport - * @param[out] mindist Minimum distance to town - * @return nearest town to airport - */ -static Town *AirportGetNearestTown(const Station *st, uint &mindist) -{ - return AirportGetNearestTown(st->airport.GetSpec(), st->airport.rotation, st->airport.tile, AirportTileIterator(st), mindist); -} - - -/** Recalculate the noise generated by the airports of each town */ -void UpdateAirportsNoise() -{ - for (Town *t : Town::Iterate()) t->noise_reached = 0; - - for (const Station *st : Station::Iterate()) { - if (st->airport.tile != INVALID_TILE && st->airport.type != AT_OILRIG) { - uint dist; - Town *nearest = AirportGetNearestTown(st, dist); - nearest->noise_reached += GetAirportNoiseLevelForDistance(st->airport.GetSpec(), dist); - } - } -} - -/** - * Place an Airport. - * @param flags operation to perform - * @param tile tile where airport will be built - * @param airport_type airport type, @see airport.h - * @param layout airport layout - * @param station_to_join station ID to join (NEW_STATION if build new one) - * @param allow_adjacent allow airports directly adjacent to other airports. - * @return the cost of this operation or an error - */ -CommandCost CmdBuildAirport(DoCommandFlag flags, TileIndex tile, uint8_t airport_type, uint8_t layout, StationID station_to_join, bool allow_adjacent) -{ - bool reuse = (station_to_join != NEW_STATION); - if (!reuse) station_to_join = INVALID_STATION; - bool distant_join = (station_to_join != INVALID_STATION); - - if (distant_join && (!_settings_game.station.distant_join_stations || !Station::IsValidID(station_to_join))) return CMD_ERROR; - - if (airport_type >= NUM_AIRPORTS) return CMD_ERROR; - - CommandCost ret = CheckIfAuthorityAllowsNewStation(tile, flags); - if (ret.Failed()) return ret; - - /* Check if a valid, buildable airport was chosen for construction */ - const AirportSpec *as = AirportSpec::Get(airport_type); - if (!as->IsAvailable() || layout >= as->layouts.size()) return CMD_ERROR; - if (!as->IsWithinMapBounds(layout, tile)) return CMD_ERROR; - - Direction rotation = as->layouts[layout].rotation; - int w = as->size_x; - int h = as->size_y; - if (rotation == DIR_E || rotation == DIR_W) Swap(w, h); - TileArea airport_area = TileArea(tile, w, h); - - if (w > _settings_game.station.station_spread || h > _settings_game.station.station_spread) { - return_cmd_error(STR_ERROR_STATION_TOO_SPREAD_OUT); - } - - AirportTileTableIterator tile_iter(as->layouts[layout].tiles.data(), tile); - CommandCost cost = CheckFlatLandAirport(tile_iter, flags); - if (cost.Failed()) return cost; - - /* The noise level is the noise from the airport and reduce it to account for the distance to the town center. */ - uint dist; - Town *nearest = AirportGetNearestTown(as, rotation, tile, std::move(tile_iter), dist); - uint newnoise_level = GetAirportNoiseLevelForDistance(as, dist); - - /* Check if local auth would allow a new airport */ - StringID authority_refuse_message = STR_NULL; - Town *authority_refuse_town = nullptr; - - if (_settings_game.economy.station_noise_level) { - /* do not allow to build a new airport if this raise the town noise over the maximum allowed by town */ - if ((nearest->noise_reached + newnoise_level) > nearest->MaxTownNoise()) { - authority_refuse_message = STR_ERROR_LOCAL_AUTHORITY_REFUSES_NOISE; - authority_refuse_town = nearest; - } - } else if (_settings_game.difficulty.town_council_tolerance != TOWN_COUNCIL_PERMISSIVE) { - Town *t = ClosestTownFromTile(tile, UINT_MAX); - uint num = 0; - for (const Station *st : Station::Iterate()) { - if (st->town == t && (st->facilities & FACIL_AIRPORT) && st->airport.type != AT_OILRIG) num++; - } - if (num >= 2) { - authority_refuse_message = STR_ERROR_LOCAL_AUTHORITY_REFUSES_AIRPORT; - authority_refuse_town = t; - } - } - - if (authority_refuse_message != STR_NULL) { - SetDParam(0, authority_refuse_town->index); - return_cmd_error(authority_refuse_message); - } - - Station *st = nullptr; - ret = FindJoiningStation(INVALID_STATION, station_to_join, allow_adjacent, airport_area, &st); - if (ret.Failed()) return ret; - - /* Distant join */ - if (st == nullptr && distant_join) st = Station::GetIfValid(station_to_join); - - ret = BuildStationPart(&st, flags, reuse, airport_area, (GetAirport(airport_type)->flags & AirportFTAClass::AIRPLANES) ? STATIONNAMING_AIRPORT : STATIONNAMING_HELIPORT); - if (ret.Failed()) return ret; - - if (st != nullptr && st->airport.tile != INVALID_TILE) { - return_cmd_error(STR_ERROR_TOO_CLOSE_TO_ANOTHER_AIRPORT); - } - - for (AirportTileTableIterator iter(as->layouts[layout].tiles.data(), tile); iter != INVALID_TILE; ++iter) { - cost.AddCost(_price[PR_BUILD_STATION_AIRPORT]); - } - - if (flags & DC_EXEC) { - /* Always add the noise, so there will be no need to recalculate when option toggles */ - nearest->noise_reached += newnoise_level; - - st->AddFacility(FACIL_AIRPORT, tile); - st->airport.type = airport_type; - st->airport.layout = layout; - st->airport.flags = 0; - st->airport.rotation = rotation; - - st->rect.BeforeAddRect(tile, w, h, StationRect::ADD_TRY); - - for (AirportTileTableIterator iter(as->layouts[layout].tiles.data(), tile); iter != INVALID_TILE; ++iter) { - Tile t(iter); - MakeAirport(t, st->owner, st->index, iter.GetStationGfx(), WATER_CLASS_INVALID); - SetStationTileRandomBits(t, GB(Random(), 0, 4)); - st->airport.Add(iter); - - if (AirportTileSpec::Get(GetTranslatedAirportTileID(iter.GetStationGfx()))->animation.status != ANIM_STATUS_NO_ANIMATION) AddAnimatedTile(t); - } - - /* Only call the animation trigger after all tiles have been built */ - for (AirportTileTableIterator iter(as->layouts[layout].tiles.data(), tile); iter != INVALID_TILE; ++iter) { - AirportTileAnimationTrigger(st, iter, AAT_BUILT); - } - - UpdateAirplanesOnNewStation(st); - - Company::Get(st->owner)->infrastructure.airport++; - - st->AfterStationTileSetChange(true, STATION_AIRPORT); - InvalidateWindowData(WC_STATION_VIEW, st->index, -1); - - if (_settings_game.economy.station_noise_level) { - SetWindowDirty(WC_TOWN_VIEW, nearest->index); - } - } - - return cost; -} - -/** - * Remove an airport - * @param tile TileIndex been queried - * @param flags operation to perform - * @return cost or failure of operation - */ -static CommandCost RemoveAirport(TileIndex tile, DoCommandFlag flags) -{ - Station *st = Station::GetByTile(tile); - - if (_current_company != OWNER_WATER) { - CommandCost ret = CheckOwnership(st->owner); - if (ret.Failed()) return ret; - } - - tile = st->airport.tile; - - CommandCost cost(EXPENSES_CONSTRUCTION); - - for (const Aircraft *a : Aircraft::Iterate()) { - if (!a->IsNormalAircraft()) continue; - if (a->targetairport == st->index && a->state != FLYING) { - return_cmd_error(STR_ERROR_AIRCRAFT_IN_THE_WAY); - } - } - - if (flags & DC_EXEC) { - for (uint i = 0; i < st->airport.GetNumHangars(); ++i) { - TileIndex tile_cur = st->airport.GetHangarTile(i); - OrderBackup::Reset(tile_cur, false); - CloseWindowById(WC_VEHICLE_DEPOT, tile_cur); - } - - /* The noise level is the noise from the airport and reduce it to account for the distance to the town center. - * And as for construction, always remove it, even if the setting is not set, in order to avoid the - * need of recalculation */ - uint dist; - Town *nearest = AirportGetNearestTown(st, dist); - nearest->noise_reached -= GetAirportNoiseLevelForDistance(st->airport.GetSpec(), dist); - - if (_settings_game.economy.station_noise_level) { - SetWindowDirty(WC_TOWN_VIEW, nearest->index); - } - } - - for (TileIndex tile_cur : st->airport) { - if (!st->TileBelongsToAirport(tile_cur)) continue; - - CommandCost ret = EnsureNoVehicleOnGround(tile_cur); - if (ret.Failed()) return ret; - - cost.AddCost(_price[PR_CLEAR_STATION_AIRPORT]); - - if (flags & DC_EXEC) { - DeleteAnimatedTile(tile_cur); - DoClearSquare(tile_cur); - DeleteNewGRFInspectWindow(GSF_AIRPORTTILES, tile_cur.base()); - } - } - - if (flags & DC_EXEC) { - /* Clear the persistent storage. */ - delete st->airport.psa; - - st->rect.AfterRemoveRect(st, st->airport); - - st->airport.Clear(); - st->facilities &= ~FACIL_AIRPORT; - SetWindowClassesDirty(WC_VEHICLE_ORDERS); - - InvalidateWindowData(WC_STATION_VIEW, st->index, -1); - - Company::Get(st->owner)->infrastructure.airport--; - - st->AfterStationTileSetChange(false, STATION_AIRPORT); - - DeleteNewGRFInspectWindow(GSF_AIRPORTS, st->index); - } - - return cost; -} - -/** - * Open/close an airport to incoming aircraft. - * @param flags Operation to perform. - * @param station_id Station ID of the airport. - * @return the cost of this operation or an error - */ -CommandCost CmdOpenCloseAirport(DoCommandFlag flags, StationID station_id) -{ - if (!Station::IsValidID(station_id)) return CMD_ERROR; - Station *st = Station::Get(station_id); - - if (!(st->facilities & FACIL_AIRPORT) || st->owner == OWNER_NONE) return CMD_ERROR; - - CommandCost ret = CheckOwnership(st->owner); - if (ret.Failed()) return ret; - - if (flags & DC_EXEC) { - st->airport.flags ^= AIRPORT_CLOSED_block; - SetWindowWidgetDirty(WC_STATION_VIEW, st->index, WID_SV_CLOSE_AIRPORT); - } - return CommandCost(); -} - /** * Tests whether the company's vehicles have this station in orders * @param station station ID @@ -3078,8 +2701,281 @@ bool SplitGroundSpriteForOverlay(const TileInfo *ti, SpriteID *ground, RailTrack return true; } +/** + * Draw the reserved airport tracks of a tile if setting show reserved tracks is enabled + * @param t the tile + */ +void DrawAirportTracks(const TileInfo *ti) +{ + assert(IsAirportTile(ti->tile)); + + if (!MayHaveAirTracks(ti->tile)) return; + + if (IsRunwayStart(ti->tile)) + { + PaletteID palette = IsLandingTypeTile(ti->tile) ? PALETTE_SEL_TILE_BLUE : PALETTE_SEL_TILE_RED; + extern const uint8_t _slope_to_sprite_offset[32]; + DrawSelectionSprite(SPR_SELECT_TILE + _slope_to_sprite_offset[ti->tileh], palette, ti, + 7, FOUNDATION_PART_NORMAL); + } + + TrackBits trackbits = GetAirportTileTracks(ti->tile); + TrackBits reserved = GetReservedAirportTracks(ti->tile); + TrackBits runway_tracks = (IsRunway(ti->tile) && GetReservationAsRunway(ti->tile)) ? + GetRunwayTracks(ti->tile) : TRACK_BIT_NONE; + Slope autorail_tileh = RemoveHalftileSlope(ti->tileh); + + /* No tracks: return */ + if ((trackbits | runway_tracks) == TRACK_BIT_NONE) return; + + /* Draw unreserved normal tracks. */ + for (Track track : SetTrackBitIterator(trackbits)) { + TrackBits tracks = TrackToTrackBits(track); + if (((reserved | runway_tracks) & tracks) != 0) continue; + + int offset = abs(_AutorailTilehSprite[autorail_tileh][track]); + DrawGroundSpriteAt(SPR_AUTORAIL_BASE + offset, PALETTE_CRASH, 0, 0, TILE_HEIGHT); + } + + /* Draw reserved tracks and runways. */ + for (Track track : SetTrackBitIterator(reserved | runway_tracks)) { + TrackBits tracks = TrackToTrackBits(track); + int offset = abs(_AutorailTilehSprite[autorail_tileh][track]); + PaletteID palette = (runway_tracks & tracks) != 0 ? PALETTE_SEL_TILE_RED : PALETTE_SEL_TILE_BLUE; + DrawGroundSpriteAt(SPR_AUTORAIL_BASE + offset, palette, 0, 0, TILE_HEIGHT); + } +} + +static void DrawAirportFences(TileInfo *ti) +{ + assert(IsAirportTile(ti->tile)); + StationID st_id = GetStationIndex(ti->tile); + static const uint8_t x_off_air_fence[4] = {0, 0, TILE_SIZE, 0}; + static const uint8_t y_off_air_fence[4] = {0, TILE_SIZE, 0, 0}; + + PaletteID palette = COMPANY_SPRITE_COLOUR(GetTileOwner(ti->tile)); + for (DiagDirection dir = DIAGDIR_BEGIN; dir < DIAGDIR_END; dir++) { + TileIndex neighbour = TileAddByDiagDir(ti->tile, dir); + if (IsValidTile(neighbour) && (IsAirportTileOfStation(neighbour, st_id) || IsTileType(neighbour, MP_OBJECT))) continue; + AddSortableSpriteToDraw(SPR_TRACK_FENCE_FLAT_Y - (dir % 2), palette, + ti->x + x_off_air_fence[dir], ti->y + y_off_air_fence[dir], (dir % 2) ? 16 : 1, (dir % 2) ? 1 : 16, 4, ti->z); + } +} + +uint8_t GetAirOffset(TileIndex tile) { + assert(IsAirportTile(tile)); + + AirportTileType att = GetAirportTileType(tile); + switch (att) { + default: + NOT_REACHED(); + case ATT_RUNWAY_MIDDLE: { + return GetPlainRunwayDirections(tile); + } + case ATT_RUNWAY_END: + case ATT_RUNWAY_START_NO_LANDING: + case ATT_RUNWAY_START_ALLOW_LANDING: { + uint8_t offset = AIRPORT_SPRITES_OFFSET_RUNWAYS_START; + if (att == ATT_RUNWAY_END) offset = AIRPORT_SPRITES_OFFSET_RUNWAYS_END; + if (att == ATT_RUNWAY_START_NO_LANDING) offset += AIRPORT_SPRITES_OFFSET_RUNWAYS_DONT_ALLOW_LANDING; + return offset + GetRunwayExtremeDirection(tile); + } + case ATT_APRON_NORMAL: + case ATT_APRON_HELIPAD: + case ATT_APRON_HELIPORT: + case ATT_APRON_BUILTIN_HELIPORT: + return GetApronType(tile); + } +} + +static inline void DrawAirportGround(TileInfo *ti, const AirTypeInfo *ati, PaletteID palette) +{ + if (IsTileOnWater(ti->tile)) { + DrawWaterClassGround(ti); + return; + } + + switch (GetAirportGround(ti->tile)) { + case AG_GRASS: + DrawClearLandTile(ti, GetAirportGroundDensity(ti->tile)); + break; + case AG_DESERT: + case AG_SNOW: + DrawGroundSprite(_clear_land_sprites_snow_desert[GetAirportGroundDensity(ti->tile)] + SlopeToSpriteOffset(ti->tileh), PAL_NONE); + break; + case AG_AIRTYPE: { + SpriteID image = ati->base_sprites.ground[0]; + DrawGroundSprite(image, GroundSpritePaletteTransform(image, PAL_NONE, palette)); + break; + } + } +} + +/** + * Draw an airport tile. + * @param ti TileInfo of the tile to draw. + */ +static void DrawTile_Airport(TileInfo *ti) +{ + assert(IsAirportTile(ti->tile)); + + const AirTypeInfo *ati = GetAirTypeInfo(GetAirType(ti->tile)); + const DrawTileSprites *t = nullptr; + int32_t total_offset = 0; + Owner owner = GetTileOwner(ti->tile); + PaletteID palette = Company::IsValidID(owner) ? COMPANY_SPRITE_COLOUR(owner) : PALETTE_TO_GREY; + + if (ti->tileh != SLOPE_FLAT) DrawFoundation(ti, FOUNDATION_LEVELED); + DrawAirportGround(ti, ati, palette); + + SpriteID image = 0; + if (IsRunway(ti->tile)) image = ati->base_sprites.runways[GetAirOffset(ti->tile)]; + if (IsSimpleTrack(ti->tile)) { + uint8_t index = GetTileAirportGfx(ti->tile); + assert(index <= 21); + if (index == 0) { + if (GetAirportGround(ti->tile) != AG_AIRTYPE) { + TrackBits tracks = GetAirportTileTracks(ti->tile); + if ((tracks & TRACK_BIT_CROSS) != TRACK_BIT_NONE || + (tracks & TRACK_BIT_HORZ) == TRACK_BIT_HORZ || + (tracks & TRACK_BIT_VERT) == TRACK_BIT_VERT) { + image = ati->base_sprites.ground[0]; + } else { + if (tracks & TRACK_BIT_LEFT) { + image = ati->base_sprites.ground[1]; + DrawGroundSprite(image, GroundSpritePaletteTransform(image, PAL_NONE, palette)); + } + if (tracks & TRACK_BIT_RIGHT) { + image = ati->base_sprites.ground[2]; + DrawGroundSprite(image, GroundSpritePaletteTransform(image, PAL_NONE, palette)); + } + if (tracks & TRACK_BIT_UPPER) { + image = ati->base_sprites.ground[3]; + DrawGroundSprite(image, GroundSpritePaletteTransform(image, PAL_NONE, palette)); + } + if (tracks & TRACK_BIT_LOWER) { + image = ati->base_sprites.ground[4]; + DrawGroundSprite(image, GroundSpritePaletteTransform(image, PAL_NONE, palette)); + } + image = 0; + } + } + } else { + image = ati->base_sprites.ground[index - 1]; + } + } + + /* Draw ground. */ + if (image != 0) DrawGroundSprite(image, GroundSpritePaletteTransform(image, PAL_NONE, palette)); + + /* Get the data of the sprites to draw, if any. */ + AirportTileType att = GetAirportTileType(ti->tile); + switch (att) { + case ATT_SIMPLE_TRACK: + case ATT_RUNWAY_MIDDLE: + case ATT_RUNWAY_START_ALLOW_LANDING: + case ATT_RUNWAY_START_NO_LANDING: + case ATT_RUNWAY_END: + t = nullptr; + break; + + case ATT_HANGAR_EXTENDED: + case ATT_HANGAR_STANDARD: { + total_offset = ati->base_sprites.ground[0]; + uint tile_sprites_offset = GetHangarDirection(ti->tile); + t = &_airtype_display_datas_hangars[tile_sprites_offset + (HasAirportGroundSnow(ti->tile) ? 4 : 0)]; + break; + } + + case ATT_APRON_NORMAL: + case ATT_APRON_HELIPAD: + total_offset = ati->base_sprites.ground[0]; + t = &_airtype_display_datas_aprons[GetApronType(ti->tile)]; + break; + + case ATT_APRON_HELIPORT: + total_offset = ati->base_sprites.ground[0] + GetAirportTileRotation(ti->tile) + (HasAirportGroundSnow(ti->tile) ? 4 : 0); + t = &_airtype_display_datas_aprons[GetApronType(ti->tile)]; + break; + + + case ATT_INFRASTRUCTURE_NO_CATCH: { + total_offset = 0; + AirportTiles at = GetTileAirportGfx(ti->tile); + DiagDirection dir = GetAirportTileRotation(ti->tile); + switch (at) { + case ATTG_NO_CATCH_FLAG: + t = &_airtype_display_datas_flags[dir][GetAnimationFrame(ti->tile)]; + break; + case ATTG_NO_CATCH_RADAR: + t = &_airtype_display_datas_radar[GetAnimationFrame(ti->tile)]; + break; + case ATTG_NO_CATCH_TOWER: + total_offset = ati->base_sprites.ground[0] + (HasAirportGroundSnow(ti->tile) ? 4 : 0); + t = &_airtype_display_datas_tower[dir]; + break; + case ATTG_NO_CATCH_TRANSMITTER: + total_offset = ati->base_sprites.ground[0] + (HasAirportGroundSnow(ti->tile) ? 4 : 0); + t = &_airtype_display_datas_transmitter[dir]; + break; + + case ATTG_NO_CATCH_EMPTY: + case ATTG_NO_CATCH_PIER: + total_offset = ati->base_sprites.ground[0]; + t = &_airtype_display_datas[at * 4 + dir]; + break; + default: + NOT_REACHED(); + } + break; + } + + case ATT_INFRASTRUCTURE_WITH_CATCH: { + total_offset = ati->base_sprites.ground[0]; + AirportTiles at = GetTileAirportGfx(ti->tile); + DiagDirection dir = GetAirportTileRotation(ti->tile); + switch (at) { + default: + NOT_REACHED(); + case ATTG_WITH_CATCH_BUILDING_1: + case ATTG_WITH_CATCH_BUILDING_2: + case ATTG_WITH_CATCH_BUILDING_3: + case ATTG_WITH_CATCH_BUILDING_FLAT: + case ATTG_WITH_CATCH_BUILDING_TERMINAL: + if (HasAirportGroundSnow(ti->tile)) total_offset += 20; + t = &_airtype_display_datas[at * 4 + dir]; + break; + } + break; + } + + case ATT_WAITING_POINT: + NOT_REACHED(); + case ATT_APRON_BUILTIN_HELIPORT: + t = nullptr; + break; + + default: + NOT_REACHED(); + } + + if (t != nullptr) DrawRailTileSeq(ti, t, TO_BUILDINGS, total_offset, 0, palette); + + if (_show_airport_tracks) DrawAirportTracks(ti); + + if (IsApron(ti->tile) && IsBuiltInHeliportTile(ti->tile)) return; + + DrawAirportFences(ti); +} + + static void DrawTile_Station(TileInfo *ti) { + if (IsAirportTile(ti->tile) && HasAirtypeGfx(ti->tile)) { + DrawTile_Airport(ti); + return; + } + const NewGRFSpriteLayout *layout = nullptr; DrawTileSprites tmp_rail_layout; const DrawTileSprites *t = nullptr; @@ -3090,6 +2986,7 @@ static void DrawTile_Station(TileInfo *ti) BaseStation *st = nullptr; const StationSpec *statspec = nullptr; uint tile_layout = 0; + StationGfx gfx; if (HasStationRail(ti->tile)) { rti = GetRailTypeInfo(GetRailType(ti->tile)); @@ -3122,12 +3019,12 @@ static void DrawTile_Station(TileInfo *ti) total_offset = 0; } - StationGfx gfx = GetStationGfx(ti->tile); if (IsAirport(ti->tile)) { - gfx = GetAirportGfx(ti->tile); + gfx = GetTranslatedAirportTileID(GetTileAirportGfx(ti->tile)); if (gfx >= NEW_AIRPORTTILE_OFFSET) { - const AirportTileSpec *ats = AirportTileSpec::Get(gfx); + const AirportTileSpec *ats = AirportTileSpec::GetAirportTileSpec(gfx); if (ats->grf_prop.spritegroup[0] != nullptr && DrawNewAirportTile(ti, Station::GetByTile(ti->tile), ats)) { + if (_show_airport_tracks) DrawAirportTracks(ti); return; } /* No sprite group (or no valid one) found, meaning no graphics associated. @@ -3152,6 +3049,8 @@ static void DrawTile_Station(TileInfo *ti) t = &_station_display_datas_airport_flag_grass_fence_ne_2[GetAnimationFrame(ti->tile)]; break; } + } else { + gfx = GetStationGfx(ti->tile); } Owner owner = GetTileOwner(ti->tile); @@ -3242,7 +3141,7 @@ static void DrawTile_Station(TileInfo *ti) DrawWaterClassGround(ti); SpriteID sprite = GetCanalSprite(CF_BUOY, ti->tile); if (sprite != 0) total_offset = sprite - SPR_IMG_BUOY; - } else if (IsDock(ti->tile) || (IsOilRig(ti->tile) && IsTileOnWater(ti->tile))) { + } else if (IsDock(ti->tile) || (IsBuiltInHeliportTile(ti->tile) && IsTileOnWater(ti->tile))) { if (ti->tileh == SLOPE_FLAT) { DrawWaterClassGround(ti); } else { @@ -3387,6 +3286,8 @@ static void DrawTile_Station(TileInfo *ti) total_offset = 0; } + if (_show_airport_tracks && IsAirport(ti->tile)) DrawAirportTracks(ti); + DrawRailTileSeq(ti, t, TO_BUILDINGS, total_offset, relocation, palette); } @@ -3515,7 +3416,6 @@ void FillTileDescAirport(TileIndex tile, TileDesc *td) td->airport_name = as->name; const AirportTileSpec *ats = AirportTileSpec::GetByTile(tile); - td->airport_tile_name = ats->name; if (as->grf_prop.grffile != nullptr) { const GRFConfig *gc = GetGRFConfig(as->grf_prop.grffile->grfid); @@ -3524,6 +3424,51 @@ void FillTileDescAirport(TileIndex tile, TileDesc *td) const GRFConfig *gc = GetGRFConfig(ats->grf_prop.grffile->grfid); td->grf = gc->GetName(); } + + const AirTypeInfo *ati = GetAirTypeInfo(GetAirType(tile)); + td->airtype = ati->strings.name; + + AirportTileType att = GetAirportTileType(tile); + switch (att) { + case ATT_INFRASTRUCTURE_WITH_CATCH: + td->airport_tile_name = STR_LAI_STATION_DESCRIPTION_AIR_INFRASTRUCTURE_WITH_CATCHMENT; + break; + case ATT_INFRASTRUCTURE_NO_CATCH: + td->airport_tile_name = STR_LAI_STATION_DESCRIPTION_AIR_INFRASTRUCTURE_WITHOUT_CATCHMENT; + break; + case ATT_SIMPLE_TRACK: + td->airport_tile_name = STR_LAI_STATION_DESCRIPTION_AIR_PLAIN; + break; + case ATT_APRON_NORMAL: + case ATT_APRON_HELIPAD: + case ATT_APRON_HELIPORT: + case ATT_APRON_BUILTIN_HELIPORT: + td->airport_tile_name = STR_LAI_STATION_DESCRIPTION_AIR_TERMINAL + att - ATT_APRON_NORMAL; + break; + case ATT_HANGAR_STANDARD: + td->airport_tile_name = STR_LAI_STATION_DESCRIPTION_AIRCRAFT_HANGAR; + break; + case ATT_HANGAR_EXTENDED: + td->airport_tile_name = STR_LAI_STATION_DESCRIPTION_AIRCRAFT_EXTENDED_HANGAR; + break; + case ATT_RUNWAY_MIDDLE: + td->airport_tile_name = STR_LAI_STATION_DESCRIPTION_AIR_RUNWAY_MIDDLE; + break; + case ATT_RUNWAY_END: + td->airport_tile_name = STR_LAI_STATION_DESCRIPTION_AIR_RUNWAY_END; + break; + case ATT_RUNWAY_START_ALLOW_LANDING: + td->airport_tile_name = STR_LAI_STATION_DESCRIPTION_AIR_RUNWAY_START_LANDING; + break; + case ATT_RUNWAY_START_NO_LANDING: + td->airport_tile_name = STR_LAI_STATION_DESCRIPTION_AIR_RUNWAY_START_NO_LANDING; + break; + case ATT_WAITING_POINT: + td->airport_tile_name = STR_LAI_STATION_DESCRIPTION_AIR_WAITING_POINT; + break; + + default: NOT_REACHED(); + } } static void GetTileDesc_Station(TileIndex tile, TileDesc *td) @@ -3540,18 +3485,11 @@ static void GetTileDesc_Station(TileIndex tile, TileDesc *td) default: NOT_REACHED(); case STATION_RAIL: str = STR_LAI_STATION_DESCRIPTION_RAILROAD_STATION; break; case STATION_AIRPORT: - str = (IsHangar(tile) ? STR_LAI_STATION_DESCRIPTION_AIRCRAFT_HANGAR : STR_LAI_STATION_DESCRIPTION_AIRPORT); + str = IsBuiltInHeliportTile(tile) ? STR_INDUSTRY_NAME_OIL_RIG : + STR_LAI_STATION_DESCRIPTION_AIRPORT; break; case STATION_TRUCK: str = STR_LAI_STATION_DESCRIPTION_TRUCK_LOADING_AREA; break; case STATION_BUS: str = STR_LAI_STATION_DESCRIPTION_BUS_STATION; break; - case STATION_OILRIG: { - const Industry *i = Station::GetByTile(tile)->industry; - const IndustrySpec *is = GetIndustrySpec(i->type); - td->owner[0] = i->owner; - str = is->name; - if (is->grf_prop.grffile != nullptr) td->grf = GetGRFConfig(is->grf_prop.grffile->grfid)->GetName(); - break; - } case STATION_DOCK: str = STR_LAI_STATION_DESCRIPTION_SHIP_DOCK; break; case STATION_BUOY: str = STR_LAI_STATION_DESCRIPTION_BUOY; break; case STATION_WAYPOINT: str = STR_LAI_STATION_DESCRIPTION_WAYPOINT; break; @@ -3599,6 +3537,10 @@ static TrackStatus GetTileTrackStatus_Station(TileIndex tile, TransportType mode } break; + case TRANSPORT_AIR: + if (IsAirportTile(tile) && MayHaveAirTracks(tile)) trackbits = GetAirportTileTracks(tile); + break; + default: break; } @@ -3606,6 +3548,83 @@ static TrackStatus GetTileTrackStatus_Station(TileIndex tile, TransportType mode return CombineTrackStatus(TrackBitsToTrackdirBits(trackbits), TRACKDIR_BIT_NONE); } +/** Convert to or from snowy tiles. */ +static void TileLoopAirportAlps(TileIndex tile) +{ + assert(IsAirportTile(tile)); + int k = GetTileZ(tile) - GetSnowLine() + 1; + + AirportGround airport_ground = GetAirportGround(tile); + switch (airport_ground) { + case AG_AIRTYPE: + case AG_SNOW: + break; + default: + /* Below the snow line, do nothing if no snow. */ + /* At or above the snow line, make snow tile if needed. */ + if (k >= 0) { + SetAirportGroundAndDensity(tile, AG_SNOW, 0); + MarkTileDirtyByTile(tile); + } + return; + } + + /* Update snow density. */ + uint current_density = GetAirportGroundDensity(tile); + uint req_density = (k < 0) ? 0u : std::min(k, 3u); + + if (current_density == req_density) { + /* Density at the required level. */ + if (k >= 0 || airport_ground == AG_AIRTYPE) return; + SetAirportGroundAndDensity(tile, AG_GRASS, 3); + } else { + AddAirportGroundDensity(tile, current_density < req_density ? 1 : -1); + } + MarkTileDirtyByTile(tile); +} + +/** + * Tests if at least one surrounding tile is non-desert + * @param tile tile to check + * @return does this tile have at least one non-desert tile around? + */ +static inline bool AirportNeighbourIsNormal(TileIndex tile) +{ + assert(IsAirportTile(tile)); + + for (DiagDirection dir = DIAGDIR_BEGIN; dir < DIAGDIR_END; dir++) { + TileIndex t = tile + TileOffsByDiagDir(dir); + if (!IsValidTile(t)) continue; + if (GetTropicZone(t) != TROPICZONE_DESERT) return true; + if (HasTileWaterClass(t) && GetWaterClass(t) == WATER_CLASS_SEA) return true; + } + return false; +} + +static void TileLoopAirportDesert(TileIndex tile) +{ + assert(IsAirportTile(tile)); + /* Current desert level - 0 if it is not desert */ + uint current = 0; + if (GetAirportGround(tile) == AG_DESERT) current = GetAirportGroundDensity(tile); + + /* Expected desert level - 0 if it shouldn't be desert */ + uint expected = 0; + if (GetTropicZone(tile) == TROPICZONE_DESERT) { + expected = AirportNeighbourIsNormal(tile) ? 1 : 3; + } + + if (current == expected) return; + + if (expected == 0) { + SetAirportGroundAndDensity(tile, AG_GRASS, 3); + } else { + /* Transition from clear to desert is not smooth (after clearing desert tile) */ + SetAirportGroundAndDensity(tile, AG_DESERT, expected); + } + + MarkTileDirtyByTile(tile); +} static void TileLoop_Station(TileIndex tile) { @@ -3613,6 +3632,33 @@ static void TileLoop_Station(TileIndex tile) * hardcoded.....not good */ switch (GetStationType(tile)) { case STATION_AIRPORT: + if (IsTileOnWater(tile)) { + TileLoop_Water(tile); + } else { + switch (_settings_game.game_creation.landscape) { + case LT_TROPIC: TileLoopAirportDesert(tile); break; + case LT_ARCTIC: TileLoopAirportAlps(tile); break; + } + + switch (GetAirportGround(tile)) { + case AG_GRASS: + if (GetAirportGroundDensity(tile) == 3) return; + + if (GetAirportGroundCounter(tile) < 7) { + AddAirportGroundCounter(tile, 1); + } else { + SetAirportGroundCounter(tile, 0); + AddAirportGroundDensity(tile, 1); + MarkTileDirtyByTile(tile); + } + break; + case AG_SNOW: + case AG_DESERT: + case AG_AIRTYPE: + default: + break; + } + } AirportTileAnimationTrigger(Station::GetByTile(tile), tile, AAT_TILELOOP); break; @@ -3620,7 +3666,6 @@ static void TileLoop_Station(TileIndex tile) if (!IsTileFlat(tile)) break; // only handle water part [[fallthrough]]; - case STATION_OILRIG: //(station part) case STATION_BUOY: TileLoop_Water(tile); break; @@ -3691,9 +3736,9 @@ static bool ClickTile_Station(TileIndex tile) if (bst->facilities & FACIL_WAYPOINT) { ShowWaypointWindow(Waypoint::From(bst)); - } else if (IsHangar(tile)) { - const Station *st = Station::From(bst); - ShowDepotWindow(st->airport.GetHangarTile(st->airport.GetHangarNum(tile)), VEH_AIRCRAFT); + } else if (IsHangarTile(tile)) { + assert(Station::From(bst)->airport.HasHangar()); + ShowDepotWindow(Station::From(bst)->airport.hangar->index); } else { ShowStationViewWindow(bst->index); } @@ -3781,7 +3826,17 @@ void TriggerWatchedCargoCallbacks(Station *st) static bool StationHandleBigTick(BaseStation *st) { if (!st->IsInUse()) { - if (++st->delete_ctr >= 8) delete st; + if (++st->delete_ctr >= 8) { + if (Station::IsExpected(st)) { + Station *s = Station::From(st); + if (s->airport.hangar != nullptr) { + delete s->airport.hangar; + s->airport.hangar = nullptr; + } + } + + delete st; + } return false; } @@ -4499,69 +4554,6 @@ void UpdateStationDockingTiles(Station *st) } } -void BuildOilRig(TileIndex tile) -{ - if (!Station::CanAllocateItem()) { - Debug(misc, 0, "Can't allocate station for oilrig at 0x{:X}, reverting to oilrig only", tile); - return; - } - - Station *st = new Station(tile); - _station_kdtree.Insert(st->index); - st->town = ClosestTownFromTile(tile, UINT_MAX); - - st->string_id = GenerateStationName(st, tile, STATIONNAMING_OILRIG); - - assert(IsTileType(tile, MP_INDUSTRY)); - /* Mark industry as associated both ways */ - st->industry = Industry::GetByTile(tile); - st->industry->neutral_station = st; - DeleteAnimatedTile(tile); - MakeOilrig(tile, st->index, GetWaterClass(tile)); - - st->owner = OWNER_NONE; - st->airport.type = AT_OILRIG; - st->airport.Add(tile); - st->ship_station.Add(tile); - st->facilities = FACIL_AIRPORT | FACIL_DOCK; - st->build_date = TimerGameCalendar::date; - UpdateStationDockingTiles(st); - - st->rect.BeforeAddTile(tile, StationRect::ADD_FORCE); - - st->UpdateVirtCoord(); - - /* An industry tile has now been replaced with a station tile, this may change the overlap between station catchments and industry tiles. - * Recalculate the station catchment for all stations currently in the industry's nearby list. - * Clear the industry's station nearby list first because Station::RecomputeCatchment cannot remove nearby industries in this case. */ - if (_settings_game.station.serve_neutral_industries) { - StationList nearby = std::move(st->industry->stations_near); - st->industry->stations_near.clear(); - for (Station *near : nearby) { - near->RecomputeCatchment(true); - UpdateStationAcceptance(near, true); - } - } - - st->RecomputeCatchment(); - UpdateStationAcceptance(st, false); -} - -void DeleteOilRig(TileIndex tile) -{ - Station *st = Station::GetByTile(tile); - - MakeWaterKeepingClass(tile, OWNER_NONE); - - /* The oil rig station is not supposed to be shared with anything else */ - assert(st->facilities == (FACIL_AIRPORT | FACIL_DOCK) && st->airport.type == AT_OILRIG); - if (st->industry != nullptr && st->industry->neutral_station == st) { - /* Don't leave dangling neutral station pointer */ - st->industry->neutral_station = nullptr; - } - delete st; -} - static void ChangeTileOwner_Station(TileIndex tile, Owner old_owner, Owner new_owner) { if (IsAnyRoadStopTile(tile)) { @@ -4599,6 +4591,11 @@ static void ChangeTileOwner_Station(TileIndex tile, Owner old_owner, Owner new_o } break; + case STATION_AIRPORT: + old_company->infrastructure.air[GetAirType(tile)]--; + new_company->infrastructure.air[GetAirType(tile)]++; + break; + case STATION_BUS: case STATION_TRUCK: case STATION_ROADWAYPOINT: @@ -4684,6 +4681,9 @@ static CommandCost CanRemoveRoadWithStop(TileIndex tile, DoCommandFlag flags) return CommandCost(); } +extern CommandCost RemoveAirport(TileIndex tile, DoCommandFlag flags); +extern CommandCost ClearAirportTile(TileIndex tile, DoCommandFlag flags); + /** * Clear a single tile of a station. * @param tile The tile to clear. @@ -4703,16 +4703,20 @@ CommandCost ClearTile_Station(TileIndex tile, DoCommandFlag flags) case STATION_ROADWAYPOINT: return_cmd_error(STR_ERROR_BUILDING_MUST_BE_DEMOLISHED); case STATION_BUOY: return_cmd_error(STR_ERROR_BUOY_IN_THE_WAY); case STATION_DOCK: return_cmd_error(STR_ERROR_MUST_DEMOLISH_DOCK_FIRST); - case STATION_OILRIG: - SetDParam(1, STR_INDUSTRY_NAME_OIL_RIG); - return_cmd_error(STR_ERROR_GENERIC_OBJECT_IN_THE_WAY); } } switch (GetStationType(tile)) { case STATION_RAIL: return RemoveRailStation(tile, flags); case STATION_WAYPOINT: return RemoveRailWaypoint(tile, flags); - case STATION_AIRPORT: return RemoveAirport(tile, flags); + case STATION_AIRPORT: // airport can be built on water + if (_current_company == OWNER_WATER && IsTileOnWater(tile)) break; + if (_settings_game.station.allow_modify_airports) { + return ClearAirportTile(tile, flags); + } else { + return RemoveAirport(tile, flags); + } + break; case STATION_TRUCK: [[fallthrough]]; case STATION_BUS: if (IsDriveThroughStopTile(tile)) { diff --git a/src/station_cmd.h b/src/station_cmd.h index 00f98a0d35884..c34deba192e53 100644 --- a/src/station_cmd.h +++ b/src/station_cmd.h @@ -16,25 +16,18 @@ enum StationClassID : uint16_t; enum RoadStopClassID : uint16_t; -extern Town *AirportGetNearestTown(const struct AirportSpec *as, Direction rotation, TileIndex tile, TileIterator &&it, uint &mindist); -extern uint8_t GetAirportNoiseLevelForDistance(const struct AirportSpec *as, uint distance); - -CommandCost CmdBuildAirport(DoCommandFlag flags, TileIndex tile, uint8_t airport_type, uint8_t layout, StationID station_to_join, bool allow_adjacent); CommandCost CmdBuildDock(DoCommandFlag flags, TileIndex tile, StationID station_to_join, bool adjacent); CommandCost CmdBuildRailStation(DoCommandFlag flags, TileIndex tile_org, RailType rt, Axis axis, uint8_t numtracks, uint8_t plat_len, StationClassID spec_class, uint16_t spec_index, StationID station_to_join, bool adjacent); CommandCost CmdRemoveFromRailStation(DoCommandFlag flags, TileIndex start, TileIndex end, bool keep_rail); CommandCost CmdBuildRoadStop(DoCommandFlag flags, TileIndex tile, uint8_t width, uint8_t length, RoadStopType stop_type, bool is_drive_through, DiagDirection ddir, RoadType rt, RoadStopClassID spec_class, uint16_t spec_index, StationID station_to_join, bool adjacent); CommandCost CmdRemoveRoadStop(DoCommandFlag flags, TileIndex tile, uint8_t width, uint8_t height, RoadStopType stop_type, bool remove_road); CommandCost CmdRenameStation(DoCommandFlag flags, StationID station_id, const std::string &text); -CommandCost CmdOpenCloseAirport(DoCommandFlag flags, StationID station_id); -DEF_CMD_TRAIT(CMD_BUILD_AIRPORT, CmdBuildAirport, CMD_AUTO | CMD_NO_WATER, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_BUILD_DOCK, CmdBuildDock, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_BUILD_RAIL_STATION, CmdBuildRailStation, CMD_AUTO | CMD_NO_WATER, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_REMOVE_FROM_RAIL_STATION, CmdRemoveFromRailStation, 0, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_BUILD_ROAD_STOP, CmdBuildRoadStop, CMD_AUTO | CMD_NO_WATER, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_REMOVE_ROAD_STOP, CmdRemoveRoadStop, 0, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_RENAME_STATION, CmdRenameStation, 0, CMDT_OTHER_MANAGEMENT) -DEF_CMD_TRAIT(CMD_OPEN_CLOSE_AIRPORT, CmdOpenCloseAirport, 0, CMDT_ROUTE_MANAGEMENT) #endif /* STATION_CMD_H */ diff --git a/src/station_func.h b/src/station_func.h index ab24b6cc9f9ae..43e0b8e194aba 100644 --- a/src/station_func.h +++ b/src/station_func.h @@ -39,12 +39,16 @@ void StationPickerDrawSprite(int x, int y, StationType st, RailType railtype, Ro bool HasStationInUse(StationID station, bool include_company, CompanyID company); -void DeleteOilRig(TileIndex t); +StringID GenerateStationName(Station *st, TileIndex tile, StationNaming name_class); + +void DeleteOldBuiltInHeliport(TileIndex t); +void DeleteBuiltInHeliport(TileIndex t); void UpdateStationDockingTiles(Station *st); void RemoveDockingTile(TileIndex t); void ClearDockingTilesCheckingNeighbours(TileIndex tile); void UpdateAirportsNoise(); +void FloodAircraftOnAirport(const Station *st); bool SplitGroundSpriteForOverlay(const TileInfo *ti, SpriteID *ground, RailTrackOffset *overlay_offset); @@ -62,6 +66,4 @@ inline Money StationMaintenanceCost(uint32_t num) return (_price[PR_INFRASTRUCTURE_STATION] * num * (1 + IntSqrt(num))) >> 7; // 7 bits scaling. } -Money AirportMaintenanceCost(Owner owner); - #endif /* STATION_FUNC_H */ diff --git a/src/station_gui.cpp b/src/station_gui.cpp index 4ba355b721ac1..1b73e2552dbaa 100644 --- a/src/station_gui.cpp +++ b/src/station_gui.cpp @@ -34,6 +34,7 @@ #include "linkgraph/linkgraph.h" #include "zoom_func.h" #include "station_cmd.h" +#include "airport_cmd.h" #include "widgets/station_widget.h" @@ -1471,7 +1472,7 @@ struct StationViewWindow : public Window { this->SetWidgetDisabledState(WID_SV_SHIPS, !(st->facilities & FACIL_DOCK)); this->SetWidgetDisabledState(WID_SV_PLANES, !(st->facilities & FACIL_AIRPORT)); this->SetWidgetDisabledState(WID_SV_CLOSE_AIRPORT, !(st->facilities & FACIL_AIRPORT) || st->owner != _local_company || st->owner == OWNER_NONE); // Also consider SE, where _local_company == OWNER_NONE - this->SetWidgetLoweredState(WID_SV_CLOSE_AIRPORT, (st->facilities & FACIL_AIRPORT) && (st->airport.flags & AIRPORT_CLOSED_block) != 0); + this->SetWidgetLoweredState(WID_SV_CLOSE_AIRPORT, (st->facilities & FACIL_AIRPORT) && st->airport.IsClosed()); extern const Station *_viewport_highlight_station; this->SetWidgetDisabledState(WID_SV_CATCHMENT, st->facilities == FACIL_NONE); diff --git a/src/station_map.h b/src/station_map.h index 906ad6193b435..b2adba2abd08f 100644 --- a/src/station_map.h +++ b/src/station_map.h @@ -10,6 +10,7 @@ #ifndef STATION_MAP_H #define STATION_MAP_H +#include "air_type.h" #include "rail_map.h" #include "road_map.h" #include "water_map.h" @@ -47,6 +48,18 @@ inline StationType GetStationType(Tile t) return (StationType)GB(t.m6(), 3, 4); } +/** + * Get the station type of this tile + * @param t the tile to query + * @pre IsTileType(t, MP_STATION) + * @return the station type + */ +static inline void SetStationType(Tile t, StationType type) +{ + assert(IsTileType(t, MP_STATION)); + SB(t.m6(), 3, 3, type); +} + /** * Get the road stop type of this tile * @param t the tile to query @@ -156,6 +169,7 @@ inline bool HasStationTileRail(Tile t) */ inline bool IsAirport(Tile t) { + assert(IsTileType(t, MP_STATION)); return GetStationType(t) == STATION_AIRPORT; } @@ -169,7 +183,124 @@ inline bool IsAirportTile(Tile t) return IsTileType(t, MP_STATION) && IsAirport(t); } -bool IsHangar(Tile t); +/** + * Check if an airport tile is a hangar. + * @param t Tile to check. + * @return Whether the tile is a hangar. + * @pre IsAirportTile + */ +static inline bool IsHangar(Tile t) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + + return GB(t.m5(), 8 - ATT_HANGAR_LAYOUT_NUM_BITS, ATT_HANGAR_LAYOUT_NUM_BITS) == ATT_HANGAR_LAYOUT_BITS; +} + +/** + * Check if an airport tile is a generic apron. + * (apron, heliport, helipad, built-in heliport). + * @param t Tile to check. + * @return Whether the tile is an apron. + * @pre IsAirportTile + */ +static inline bool IsApron(Tile t) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + + return GB(t.m5(), 8 - ATT_APRON_LAYOUT_NUM_BITS, ATT_APRON_LAYOUT_NUM_BITS) == ATT_APRON_LAYOUT_BITS; +} + +/** + * Get the type of apron. + * @param t Tile to get the type of. + * @return The type of apron. + * @pre IsApron + */ +static inline ApronType GetApronType(Tile t) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(IsApron(t)); + + ApronType type = (ApronType)GB(t.m5(), 4, 2); + + assert(type < APRON_END); + + return type; +} + +/** + * Is a given tile a built-in heliport? + * @param t Tile to get the type of. + * @return True if it is a built-in heliport. + * @pre IsTerminal + */ +static inline bool IsBuiltInHeliport(TileIndex t) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(IsApron(t)); + + return GetApronType(t) == APRON_BUILTIN_HELIPORT; +} + +/** + * Is this tile a built-in heliport? + * @param t the tile to get the information from. + * @return true if and only if the tile is a built-in heliport. + */ +static inline bool IsBuiltInHeliportTile(TileIndex t) +{ + return IsTileType(t, MP_STATION) && + IsAirport(t) && + IsApron(t) && + IsBuiltInHeliport(t); +} + +/** + * Is a given tile a built-in heliport on water? + * @param t Tile to get the type of. + * @return True if it is a built-in heliport on water. + * @pre IsTerminal + */ +static inline bool IsWateredBuiltInHeliport(TileIndex t) +{ + assert(IsValidTile(t)); + assert(IsTileType(t, MP_STATION)); + assert(IsAirport(t)); + assert(IsApron(t)); + + return GetApronType(t) == APRON_BUILTIN_HELIPORT && IsTileOnWater(t); +} + +/** + * Is this tile a built-in heliport on water? + * @param t the tile to get the information from. + * @return true if and only if the tile is a built-in heliport on water. + */ +static inline bool IsWateredBuiltInHeliportTile(Tile t) +{ + return IsTileType(t, MP_STATION) && + IsAirport(t) && + IsApron(t) && + IsWateredBuiltInHeliport(t); +} + +/** + * Is tile \a t an hangar tile? + * @param t Tile to check + * @return \c true if the tile is an hangar + */ +static inline bool IsHangarTile(Tile t) +{ + return IsTileType(t, MP_STATION) && IsAirport(t) && IsHangar(t); +} /** * Is the station at \a t a truck stop? @@ -278,8 +409,6 @@ inline bool IsDriveThroughStopTile(Tile t) return IsAnyRoadStopTile(t) && GetStationGfx(t) >= GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET; } -StationGfx GetTranslatedAirportTileID(StationGfx gfx); - /** * Get the decorations of a road waypoint. * @param tile The tile to query. @@ -323,18 +452,6 @@ static inline void ToggleRoadWaypointOnSnowOrDesert(Tile t) ToggleBit(t.m8(), 15); } -/** - * Get the station graphics of this airport tile - * @param t the tile to query - * @pre IsAirport(t) - * @return the station graphics - */ -inline StationGfx GetAirportGfx(Tile t) -{ - assert(IsAirport(t)); - return GetTranslatedAirportTileID(GetStationGfx(t)); -} - /** * Gets the direction the road stop entrance points towards. * @param t the tile of the road stop @@ -352,17 +469,6 @@ inline DiagDirection GetRoadStopDir(Tile t) } } -/** - * Is tile \a t part of an oilrig? - * @param t Tile to check - * @pre IsTileType(t, MP_STATION) - * @return \c true if the tile is an oilrig tile - */ -inline bool IsOilRig(Tile t) -{ - return GetStationType(t) == STATION_OILRIG; -} - /** * Is tile \a t a dock tile? * @param t Tile to check @@ -405,16 +511,6 @@ inline bool IsBuoyTile(Tile t) return IsTileType(t, MP_STATION) && IsBuoy(t); } -/** - * Is tile \a t an hangar tile? - * @param t Tile to check - * @return \c true if the tile is an hangar - */ -inline bool IsHangarTile(Tile t) -{ - return IsTileType(t, MP_STATION) && IsHangar(t); -} - /** * Is tile \a t a blocked tile? * @pre HasStationRail(t) @@ -521,28 +617,6 @@ inline TrackBits GetRailStationTrackBits(Tile t) return AxisToTrackBits(GetRailStationAxis(t)); } -/** - * Check if a tile is a valid continuation to a railstation tile. - * The tile \a test_tile is a valid continuation to \a station_tile, if all of the following are true: - * \li \a test_tile is a rail station tile - * \li the railtype of \a test_tile is compatible with the railtype of \a station_tile - * \li the tracks on \a test_tile and \a station_tile are in the same direction - * \li both tiles belong to the same station - * \li \a test_tile is not blocked (@see IsStationTileBlocked) - * @param test_tile Tile to test - * @param station_tile Station tile to compare with - * @pre IsRailStationTile(station_tile) - * @return true if the two tiles are compatible - */ -inline bool IsCompatibleTrainStationTile(Tile test_tile, Tile station_tile) -{ - assert(IsRailStationTile(station_tile)); - return IsRailStationTile(test_tile) && !IsStationTileBlocked(test_tile) && - IsCompatibleRail(GetRailType(test_tile), GetRailType(station_tile)) && - GetRailStationAxis(test_tile) == GetRailStationAxis(station_tile) && - GetStationIndex(test_tile) == GetStationIndex(station_tile); -} - /** * Get the reservation state of the rail station * @pre HasStationRail(t) @@ -834,15 +908,4 @@ inline void MakeDock(Tile t, Owner o, StationID sid, DiagDirection d, WaterClass MakeStation(TileIndex(t) + TileOffsByDiagDir(d), o, sid, STATION_DOCK, GFX_DOCK_BASE_WATER_PART + DiagDirToAxis(d), wc); } -/** - * Make the given tile an oilrig tile. - * @param t the tile to make an oilrig - * @param sid the station to which this tile belongs - * @param wc the type of water on this tile - */ -inline void MakeOilrig(Tile t, StationID sid, WaterClass wc) -{ - MakeStation(t, OWNER_NONE, sid, STATION_OILRIG, 0, wc); -} - #endif /* STATION_MAP_H */ diff --git a/src/station_type.h b/src/station_type.h index 54d7939e66162..98d6fb0037c70 100644 --- a/src/station_type.h +++ b/src/station_type.h @@ -33,7 +33,7 @@ enum StationType { STATION_AIRPORT, STATION_TRUCK, STATION_BUS, - STATION_OILRIG, + STATION_OLD_OILRIG, // Value can be reused. STATION_DOCK, STATION_BUOY, STATION_WAYPOINT, @@ -85,6 +85,15 @@ enum CatchmentArea { MAX_CATCHMENT = 10, ///< Maximum catchment for airports with "modified catchment" enabled }; +enum StationNaming { + STATIONNAMING_RAIL, + STATIONNAMING_ROAD, + STATIONNAMING_AIRPORT, + STATIONNAMING_OILRIG, + STATIONNAMING_DOCK, + STATIONNAMING_HELIPORT, +}; + static const uint MAX_LENGTH_STATION_NAME_CHARS = 32; ///< The maximum length of a station name in characters including '\0' struct StationCompare { diff --git a/src/survey.cpp b/src/survey.cpp index 123c82563e634..3106a4c432b5f 100644 --- a/src/survey.cpp +++ b/src/survey.cpp @@ -324,10 +324,10 @@ void SurveyCompanies(nlohmann::json &survey) company["infrastructure"]["road"] = c->infrastructure.GetRoadTotal(); company["infrastructure"]["tram"] = c->infrastructure.GetTramTotal(); company["infrastructure"]["rail"] = c->infrastructure.GetRailTotal(); + company["infrastructure"]["air"] = c->infrastructure.GetAirTotal(); company["infrastructure"]["signal"] = c->infrastructure.signal; company["infrastructure"]["water"] = c->infrastructure.water; company["infrastructure"]["station"] = c->infrastructure.station; - company["infrastructure"]["airport"] = c->infrastructure.airport; } } diff --git a/src/table/CMakeLists.txt b/src/table/CMakeLists.txt index 3a615135ef569..8fdcba486902c 100644 --- a/src/table/CMakeLists.txt +++ b/src/table/CMakeLists.txt @@ -2,9 +2,9 @@ add_subdirectory(settings) add_files( airport_defaults.h - airport_movement.h airporttile_ids.h airporttiles.h + airtypes.h animcursors.h autorail.h bridge_land.h diff --git a/src/table/airport_defaults.h b/src/table/airport_defaults.h index 0a41f7ec8ca86..b625b67f35403 100644 --- a/src/table/airport_defaults.h +++ b/src/table/airport_defaults.h @@ -10,406 +10,450 @@ #ifndef AIRPORT_DEFAULTS_H #define AIRPORT_DEFAULTS_H -#include "timer/timer_game_calendar.h" - -/** - * Definition of an airport tiles layout. - * @param x offset x of this tile - * @param y offset y of this tile - * @param m StationGfx of the tile - * @see _airport_specs - * @see AirportTileTable - */ -#define MK(x, y, m) {{x, y}, m} +#include "../track_type.h" +#include "../air_type.h" +#include "airporttile_ids.h" + +/** Tiles for Country Airfield (small) with hangar in the western tile. */ +static const std::initializer_list _description_country = { + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_3, DIAGDIR_NW, APT_SMALL_BUILDING_1), + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_2, DIAGDIR_NW, APT_SMALL_BUILDING_2), + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_1, DIAGDIR_NW, APT_SMALL_BUILDING_3), + AirportTileTable(ATT_HANGAR_STANDARD, TRACK_BIT_Y, DIAGDIR_SE, APT_SMALL_DEPOT_SE), + + AirportTileTable(ATT_INFRASTRUCTURE_NO_CATCH, ATTG_NO_CATCH_FLAG, DIAGDIR_NE, APT_GRASS_FENCE_NE_FLAG), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_CROSS, APRON_APRON, APT_GRASS_1), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_CROSS, APRON_APRON, APT_GRASS_2), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS | TRACK_BIT_RIGHT, APT_GRASS_FENCE_SW), + + AirportTileTable(ATT_RUNWAY_END, TRACK_BIT_X, DIAGDIR_NE, APT_RUNWAY_SMALL_FAR_END), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_CROSS | TRACK_BIT_LEFT, DIR_NE, APT_RUNWAY_SMALL_MIDDLE), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_ALL & ~TRACK_BIT_3WAY_SE, DIR_NE, APT_RUNWAY_SMALL_MIDDLE), + AirportTileTable(ATT_RUNWAY_START_ALLOW_LANDING, TRACK_BIT_CROSS | TRACK_BIT_UPPER, DIAGDIR_NE, APT_RUNWAY_SMALL_NEAR_END), +}; -/** - * Terminator of airport tiles layout definition - */ -#define MKEND {{-0x80, 0}, 0} - -/** Tiles for Country Airfield (small) */ -static const std::initializer_list _tile_table_country_0 = { - MK(0, 0, APT_SMALL_BUILDING_1), - MK(1, 0, APT_SMALL_BUILDING_2), - MK(2, 0, APT_SMALL_BUILDING_3), - MK(3, 0, APT_SMALL_DEPOT_SE), - MK(0, 1, APT_GRASS_FENCE_NE_FLAG), - MK(1, 1, APT_GRASS_1), - MK(2, 1, APT_GRASS_2), - MK(3, 1, APT_GRASS_FENCE_SW), - MK(0, 2, APT_RUNWAY_SMALL_FAR_END), - MK(1, 2, APT_RUNWAY_SMALL_MIDDLE), - MK(2, 2, APT_RUNWAY_SMALL_MIDDLE), - MK(3, 2, APT_RUNWAY_SMALL_NEAR_END), - MKEND +/** Tiles for Country Airfield (small) with hangar in the northern tile. */ +static const std::initializer_list _description_country_2 = { + AirportTileTable(ATT_HANGAR_STANDARD, TRACK_BIT_Y, DIAGDIR_SE ), + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_3, DIAGDIR_NW ), + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_2, DIAGDIR_NW ), + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_1, DIAGDIR_NW ), + + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS | TRACK_BIT_LOWER ), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_CROSS, APRON_APRON ), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_CROSS, APRON_APRON ), + AirportTileTable(ATT_INFRASTRUCTURE_NO_CATCH, ATTG_NO_CATCH_FLAG, DIAGDIR_SW ), + + AirportTileTable(ATT_RUNWAY_END, TRACK_BIT_CROSS | TRACK_BIT_UPPER, DIAGDIR_NE ), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_CROSS | TRACK_BIT_LEFT, DIR_NE ), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_ALL & ~TRACK_BIT_3WAY_SE, DIR_NE ), + AirportTileTable(ATT_RUNWAY_START_ALLOW_LANDING, TRACK_BIT_X, DIAGDIR_NE ), }; -static const std::initializer_list _tile_table_country = { - { _tile_table_country_0, DIR_N }, +/** Tiles for Country Airfield (small) non-rectangular. */ +static const std::initializer_list _description_country_3 = { + AirportTileTable( ), + AirportTileTable( ), + AirportTileTable( ), + AirportTileTable(ATT_HANGAR_STANDARD, TRACK_BIT_Y, DIAGDIR_SE ), + + AirportTileTable(ATT_INFRASTRUCTURE_NO_CATCH, ATTG_NO_CATCH_FLAG, DIAGDIR_NW ), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_CROSS, APRON_APRON ), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_CROSS, APRON_APRON ), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS ), + + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_LOWER ), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS | TRACK_BIT_LEFT ), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS | TRACK_BIT_VERT | TRACK_BIT_UPPER ), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS | TRACK_BIT_RIGHT ), + + AirportTileTable(ATT_RUNWAY_END, TRACK_BIT_Y, DIAGDIR_NE ), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_NE ), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_LEFT, DIR_NE ), + AirportTileTable(ATT_RUNWAY_START_ALLOW_LANDING, TRACK_BIT_CROSS, DIAGDIR_NE ), }; /** Tiles for Commuter Airfield (small) */ -static const std::initializer_list _tile_table_commuter_0 = { - MK(0, 0, APT_TOWER), - MK(1, 0, APT_BUILDING_3), - MK(2, 0, APT_HELIPAD_2_FENCE_NW), - MK(3, 0, APT_HELIPAD_2_FENCE_NW), - MK(4, 0, APT_DEPOT_SE), - MK(0, 1, APT_APRON_FENCE_NE), - MK(1, 1, APT_APRON), - MK(2, 1, APT_APRON), - MK(3, 1, APT_APRON), - MK(4, 1, APT_APRON_FENCE_SW), - MK(0, 2, APT_APRON_FENCE_NE), - MK(1, 2, APT_STAND), - MK(2, 2, APT_STAND), - MK(3, 2, APT_STAND), - MK(4, 2, APT_APRON_FENCE_SW), - MK(0, 3, APT_RUNWAY_END_FENCE_SE), - MK(1, 3, APT_RUNWAY_2), - MK(2, 3, APT_RUNWAY_2), - MK(3, 3, APT_RUNWAY_2), - MK(4, 3, APT_RUNWAY_END_FENCE_SE), - MKEND +static const std::initializer_list _description_commuter = { + AirportTileTable(ATT_INFRASTRUCTURE_NO_CATCH, ATTG_NO_CATCH_TOWER, DIAGDIR_NE, APT_TOWER), + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_3, DIAGDIR_SE, APT_BUILDING_3), + AirportTileTable(ATT_APRON_HELIPAD, TRACK_BIT_Y, APRON_HELIPAD, APT_HELIPAD_2_FENCE_NW), + AirportTileTable(ATT_APRON_HELIPAD, TRACK_BIT_Y, APRON_HELIPAD, APT_HELIPAD_2_FENCE_NW), + AirportTileTable(ATT_HANGAR_STANDARD, TRACK_BIT_Y, DIAGDIR_SE, APT_DEPOT_SE), + + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_LOWER, APT_APRON_FENCE_NE), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_X | TRACK_BIT_LOWER, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_ALL & ~TRACK_BIT_UPPER, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_ALL & ~TRACK_BIT_LEFT, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS | TRACK_BIT_RIGHT, APT_APRON_FENCE_SW), + + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_Y | TRACK_BIT_LOWER, APT_APRON_FENCE_NE), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_CROSS, APRON_APRON, APT_STAND), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_CROSS, APRON_APRON, APT_STAND), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_CROSS, APRON_APRON, APT_STAND), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_ALL & ~TRACK_BIT_3WAY_SW, APT_APRON_FENCE_SW), + + AirportTileTable(ATT_RUNWAY_END, TRACK_BIT_Y, DIAGDIR_NE, APT_RUNWAY_END_FENCE_SE), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_NE, APT_RUNWAY_2), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_NE, APT_RUNWAY_2), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_NE, APT_RUNWAY_2), + AirportTileTable(ATT_RUNWAY_START_ALLOW_LANDING, TRACK_BIT_Y, DIAGDIR_NE, APT_RUNWAY_END_FENCE_SE), }; -static const std::initializer_list _tile_table_commuter = { - { _tile_table_commuter_0, DIR_N }, +/** Tiles for City Airport (large) */ +static const std::initializer_list _description_city = { + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_1, DIAGDIR_NE, APT_BUILDING_1), + AirportTileTable(ATT_INFRASTRUCTURE_NO_CATCH, ATTG_NO_CATCH_EMPTY, DIAGDIR_NE, APT_APRON_FENCE_NW), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_X, APRON_APRON, APT_STAND_1), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_X, APT_APRON_FENCE_NW), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_RIGHT, APT_APRON_FENCE_NW), + AirportTileTable(ATT_HANGAR_STANDARD, TRACK_BIT_Y, DIAGDIR_SE, APT_DEPOT_SE), + + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_2, DIAGDIR_NE, APT_BUILDING_2), + AirportTileTable(ATT_INFRASTRUCTURE_NO_CATCH, ATTG_NO_CATCH_PIER, DIAGDIR_NE, APT_PIER), + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_TERMINAL, DIAGDIR_NE, APT_ROUND_TERMINAL), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_CROSS, APRON_APRON, APT_STAND_PIER_NE), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS | TRACK_BIT_LOWER, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS, APT_APRON_FENCE_SW), + + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_3, DIAGDIR_NE, APT_BUILDING_3), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_Y, APRON_APRON, APT_STAND), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_NONE, APT_PIER_NW_NE), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS | TRACK_BIT_LOWER, APT_APRON_S), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_UPPER, APT_APRON_HOR), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_NONE, APT_APRON_N_FENCE_SW), + + AirportTileTable(ATT_INFRASTRUCTURE_NO_CATCH, ATTG_NO_CATCH_TRANSMITTER, DIAGDIR_NE, APT_RADIO_TOWER_FENCE_NE), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS | TRACK_BIT_LEFT, APT_APRON_W), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_RIGHT | TRACK_BIT_LOWER | TRACK_BIT_X, APT_APRON_VER_CROSSING_S), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS | TRACK_BIT_UPPER, APT_APRON_HOR_CROSSING_E), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_NONE, APT_ARPON_N), + AirportTileTable(ATT_INFRASTRUCTURE_NO_CATCH, ATTG_NO_CATCH_TOWER, DIAGDIR_NE, APT_TOWER_FENCE_SW), + + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_NONE, APT_EMPTY_FENCE_NE), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_LOWER | TRACK_BIT_Y, APT_APRON_S), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_UPPER | TRACK_BIT_LEFT, APT_APRON_HOR_CROSSING_W), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_RIGHT | TRACK_BIT_Y, APT_APRON_VER_CROSSING_N), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_NONE, APT_APRON_E), + AirportTileTable(ATT_INFRASTRUCTURE_NO_CATCH, ATTG_NO_CATCH_RADAR, DIAGDIR_NE, APT_RADAR_GRASS_FENCE_SW), + + AirportTileTable(ATT_RUNWAY_END, TRACK_BIT_X, DIAGDIR_NE, APT_RUNWAY_END_FENCE_SE), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_UPPER, DIR_NE, APT_RUNWAY_1), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_NE, APT_RUNWAY_2), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_LEFT, DIR_NE, APT_RUNWAY_3), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_X, DIR_NE, APT_RUNWAY_4), + AirportTileTable(ATT_RUNWAY_START_ALLOW_LANDING, TRACK_BIT_X, DIAGDIR_NE, APT_RUNWAY_END_FENCE_SE), }; -/** Tiles for City Airport (large) */ -static const std::initializer_list _tile_table_city_0 = { - MK(0, 0, APT_BUILDING_1), - MK(1, 0, APT_APRON_FENCE_NW), - MK(2, 0, APT_STAND_1), - MK(3, 0, APT_APRON_FENCE_NW), - MK(4, 0, APT_APRON_FENCE_NW), - MK(5, 0, APT_DEPOT_SE), - MK(0, 1, APT_BUILDING_2), - MK(1, 1, APT_PIER), - MK(2, 1, APT_ROUND_TERMINAL), - MK(3, 1, APT_STAND_PIER_NE), - MK(4, 1, APT_APRON), - MK(5, 1, APT_APRON_FENCE_SW), - MK(0, 2, APT_BUILDING_3), - MK(1, 2, APT_STAND), - MK(2, 2, APT_PIER_NW_NE), - MK(3, 2, APT_APRON_S), - MK(4, 2, APT_APRON_HOR), - MK(5, 2, APT_APRON_N_FENCE_SW), - MK(0, 3, APT_RADIO_TOWER_FENCE_NE), - MK(1, 3, APT_APRON_W), - MK(2, 3, APT_APRON_VER_CROSSING_S), - MK(3, 3, APT_APRON_HOR_CROSSING_E), - MK(4, 3, APT_ARPON_N), - MK(5, 3, APT_TOWER_FENCE_SW), - MK(0, 4, APT_EMPTY_FENCE_NE), - MK(1, 4, APT_APRON_S), - MK(2, 4, APT_APRON_HOR_CROSSING_W), - MK(3, 4, APT_APRON_VER_CROSSING_N), - MK(4, 4, APT_APRON_E), - MK(5, 4, APT_RADAR_GRASS_FENCE_SW), - MK(0, 5, APT_RUNWAY_END_FENCE_SE), - MK(1, 5, APT_RUNWAY_1), - MK(2, 5, APT_RUNWAY_2), - MK(3, 5, APT_RUNWAY_3), - MK(4, 5, APT_RUNWAY_4), - MK(5, 5, APT_RUNWAY_END_FENCE_SE), - MKEND +/** Tiles for Metropolitan Airport (large) - 2 runways */ +static const std::initializer_list _description_metropolitan = { + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_1, DIAGDIR_NE, APT_BUILDING_1), + AirportTileTable(ATT_INFRASTRUCTURE_NO_CATCH, ATTG_NO_CATCH_EMPTY, DIAGDIR_NE, APT_APRON_FENCE_NW), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_X, APRON_APRON, APT_STAND_1), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_X, APT_APRON_FENCE_NW), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_RIGHT, APT_APRON_FENCE_NW), + AirportTileTable(ATT_HANGAR_STANDARD, TRACK_BIT_Y, DIAGDIR_SE, APT_DEPOT_SE), + + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_2, DIAGDIR_NE, APT_BUILDING_2), + AirportTileTable(ATT_INFRASTRUCTURE_NO_CATCH, ATTG_NO_CATCH_PIER, DIAGDIR_NE, APT_PIER), + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_TERMINAL, DIAGDIR_NE, APT_ROUND_TERMINAL), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_CROSS, APRON_APRON, APT_STAND_PIER_NE), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS | TRACK_BIT_LOWER, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS, APT_APRON_FENCE_SW), + + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_3, DIAGDIR_NE, APT_BUILDING_3), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_Y, APRON_APRON, APT_STAND), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_NONE, APT_PIER_NW_NE), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_Y | TRACK_BIT_LOWER, APT_APRON_S), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_Y | TRACK_BIT_UPPER, APT_APRON_HOR), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_NONE, APT_APRON_N_FENCE_SW), + + AirportTileTable(ATT_INFRASTRUCTURE_NO_CATCH, ATTG_NO_CATCH_RADAR, DIAGDIR_NE, APT_RADAR_FENCE_NE), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS | TRACK_BIT_LEFT, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_ALL & ~TRACK_BIT_3WAY_NW, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_ALL & ~TRACK_BIT_LOWER, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_ALL & ~TRACK_BIT_3WAY_SW, APT_APRON), + AirportTileTable(ATT_INFRASTRUCTURE_NO_CATCH, ATTG_NO_CATCH_TOWER, DIAGDIR_NE, APT_TOWER_FENCE_SW), + + AirportTileTable(ATT_RUNWAY_END, TRACK_BIT_NONE, DIAGDIR_NE, APT_RUNWAY_END), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_LOWER | TRACK_BIT_Y, DIR_NE, APT_RUNWAY_5), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_UPPER, DIR_NE, APT_RUNWAY_5), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_LEFT, DIR_NE, APT_RUNWAY_5), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_CROSS, DIR_NE, APT_RUNWAY_5), + AirportTileTable(ATT_RUNWAY_START_NO_LANDING, TRACK_BIT_X, DIAGDIR_NE, APT_RUNWAY_END), + + AirportTileTable(ATT_RUNWAY_END, TRACK_BIT_X, DIAGDIR_NE, APT_RUNWAY_END_FENCE_SE), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_UPPER, DIR_NE, APT_RUNWAY_2), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_NE, APT_RUNWAY_2), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_NE, APT_RUNWAY_2), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_NE, APT_RUNWAY_2), + AirportTileTable(ATT_RUNWAY_START_ALLOW_LANDING, TRACK_BIT_NONE, DIAGDIR_NE, APT_RUNWAY_END_FENCE_SE), }; -static const std::initializer_list _tile_table_city = { - { _tile_table_city_0, DIR_N }, +/** Tiles for International Airport (large) - 2 runways */ +static const std::initializer_list _description_international = { + AirportTileTable(ATT_RUNWAY_START_NO_LANDING, TRACK_BIT_X, DIAGDIR_SW, APT_RUNWAY_END_FENCE_NW), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_CROSS, DIR_SW, APT_RUNWAY_FENCE_NW), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_RIGHT, DIR_SW, APT_RUNWAY_FENCE_NW), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_SW, APT_RUNWAY_FENCE_NW), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_SW, APT_RUNWAY_FENCE_NW), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_SW, APT_RUNWAY_FENCE_NW), + AirportTileTable(ATT_RUNWAY_END, TRACK_BIT_NONE, DIAGDIR_SW, APT_RUNWAY_END_FENCE_NW), + + AirportTileTable(ATT_INFRASTRUCTURE_NO_CATCH, ATTG_NO_CATCH_TRANSMITTER, DIAGDIR_NE, APT_RADIO_TOWER_FENCE_NE), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_ALL & ~TRACK_BIT_3WAY_NE, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS | TRACK_BIT_RIGHT, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_X, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS | TRACK_BIT_LOWER, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_RIGHT | TRACK_BIT_CROSS, APT_APRON), + AirportTileTable(ATT_HANGAR_STANDARD, TRACK_BIT_Y, DIAGDIR_SE, APT_DEPOT_SE), + + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_3, DIAGDIR_NE, APT_BUILDING_3), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_ALL & ~TRACK_BIT_3WAY_NE, APT_APRON), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_CROSS, APRON_APRON, APT_STAND), + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_2, DIAGDIR_NE, APT_BUILDING_2), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_CROSS, APRON_APRON, APT_STAND), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS | TRACK_BIT_UPPER | TRACK_BIT_RIGHT, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS, APT_APRON_FENCE_SW), + + AirportTileTable(ATT_HANGAR_STANDARD, TRACK_BIT_Y, DIAGDIR_SE, APT_DEPOT_SE), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_ALL & ~TRACK_BIT_3WAY_NE, APT_APRON), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_X, APRON_APRON, APT_STAND), + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_2, DIAGDIR_NE, APT_BUILDING_2), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_X, APRON_APRON, APT_STAND), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_ALL, APT_APRON), + AirportTileTable(ATT_APRON_HELIPAD, TRACK_BIT_CROSS, APRON_HELIPAD, APT_HELIPAD_1), + + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS, APT_APRON_FENCE_NE), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS | TRACK_BIT_LOWER | TRACK_BIT_LEFT, APT_APRON), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_CROSS, APRON_APRON, APT_STAND), + AirportTileTable(ATT_INFRASTRUCTURE_NO_CATCH, ATTG_NO_CATCH_TOWER, DIAGDIR_NE, APT_TOWER), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_CROSS, APRON_APRON, APT_STAND), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_ALL & ~TRACK_BIT_LOWER, APT_APRON), + AirportTileTable(ATT_APRON_HELIPAD, TRACK_BIT_CROSS, APRON_HELIPAD, APT_HELIPAD_1), + + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_Y | TRACK_BIT_LOWER | TRACK_BIT_LEFT, APT_APRON_FENCE_NE), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_ALL & ~TRACK_BIT_3WAY_SE, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS | TRACK_BIT_UPPER, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_X, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS | TRACK_BIT_LEFT, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_UPPER | TRACK_BIT_CROSS, APT_APRON), + AirportTileTable(ATT_INFRASTRUCTURE_NO_CATCH, ATTG_NO_CATCH_RADAR, DIAGDIR_NE, APT_RADAR_FENCE_SW), + + AirportTileTable(ATT_RUNWAY_END, TRACK_BIT_Y, DIAGDIR_NE, APT_RUNWAY_END_FENCE_SE), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_NE, APT_RUNWAY_2), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_NE, APT_RUNWAY_2), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_NE, APT_RUNWAY_2), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_NE, APT_RUNWAY_2), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_NE, APT_RUNWAY_2), + AirportTileTable(ATT_RUNWAY_START_ALLOW_LANDING, TRACK_BIT_NONE, DIAGDIR_NE, APT_RUNWAY_END_FENCE_SE), }; -/** Tiles for Metropolitain Airport (large) - 2 runways */ -static const std::initializer_list _tile_table_metropolitan_0 = { - MK(0, 0, APT_BUILDING_1), - MK(1, 0, APT_APRON_FENCE_NW), - MK(2, 0, APT_STAND_1), - MK(3, 0, APT_APRON_FENCE_NW), - MK(4, 0, APT_APRON_FENCE_NW), - MK(5, 0, APT_DEPOT_SE), - MK(0, 1, APT_BUILDING_2), - MK(1, 1, APT_PIER), - MK(2, 1, APT_ROUND_TERMINAL), - MK(3, 1, APT_STAND_PIER_NE), - MK(4, 1, APT_APRON), - MK(5, 1, APT_APRON_FENCE_SW), - MK(0, 2, APT_BUILDING_3), - MK(1, 2, APT_STAND), - MK(2, 2, APT_PIER_NW_NE), - MK(3, 2, APT_APRON_S), - MK(4, 2, APT_APRON_HOR), - MK(5, 2, APT_APRON_N_FENCE_SW), - MK(0, 3, APT_RADAR_FENCE_NE), - MK(1, 3, APT_APRON), - MK(2, 3, APT_APRON), - MK(3, 3, APT_APRON), - MK(4, 3, APT_APRON), - MK(5, 3, APT_TOWER_FENCE_SW), - MK(0, 4, APT_RUNWAY_END), - MK(1, 4, APT_RUNWAY_5), - MK(2, 4, APT_RUNWAY_5), - MK(3, 4, APT_RUNWAY_5), - MK(4, 4, APT_RUNWAY_5), - MK(5, 4, APT_RUNWAY_END), - MK(0, 5, APT_RUNWAY_END_FENCE_SE), - MK(1, 5, APT_RUNWAY_2), - MK(2, 5, APT_RUNWAY_2), - MK(3, 5, APT_RUNWAY_2), - MK(4, 5, APT_RUNWAY_2), - MK(5, 5, APT_RUNWAY_END_FENCE_SE), - MKEND +/** Tiles for Intercontinental Airport (large) - 4 runways */ +static const std::initializer_list _description_intercontinental = { + AirportTileTable(ATT_INFRASTRUCTURE_NO_CATCH, ATTG_NO_CATCH_RADAR, DIAGDIR_NE, APT_RADAR_FENCE_NE), + AirportTileTable(ATT_RUNWAY_START_ALLOW_LANDING, TRACK_BIT_NONE, DIAGDIR_SW, APT_RUNWAY_END_FENCE_NE_NW), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_SW, APT_RUNWAY_FENCE_NW), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_SW, APT_RUNWAY_FENCE_NW), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_SW, APT_RUNWAY_FENCE_NW), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_SW, APT_RUNWAY_FENCE_NW), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_SW, APT_RUNWAY_FENCE_NW), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_SW, APT_RUNWAY_FENCE_NW), + AirportTileTable(ATT_RUNWAY_END, TRACK_BIT_Y, DIAGDIR_SW, APT_RUNWAY_END_FENCE_NW_SW), + + AirportTileTable(ATT_RUNWAY_START_NO_LANDING, TRACK_BIT_Y, DIAGDIR_SW, APT_RUNWAY_END_FENCE_NE_NW), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_SW, APT_RUNWAY_2), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_SW, APT_RUNWAY_2), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_SW, APT_RUNWAY_2), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_SW, APT_RUNWAY_2), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_SW, APT_RUNWAY_2), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_SW, APT_RUNWAY_2), + AirportTileTable(ATT_RUNWAY_END, TRACK_BIT_NONE, DIAGDIR_SW, APT_RUNWAY_END_FENCE_SE_SW), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_Y, APT_APRON_FENCE_NE_SW), + + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_Y, APT_APRON_FENCE_NE_SW), + AirportTileTable(ATT_INFRASTRUCTURE_NO_CATCH, ATTG_NO_CATCH_EMPTY, DIAGDIR_NE, APT_EMPTY), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS, APT_APRON_FENCE_NE), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_X, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS | TRACK_BIT_LOWER, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_ALL & ~TRACK_BIT_3WAY_NW, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS, APT_APRON), + AirportTileTable(ATT_INFRASTRUCTURE_NO_CATCH, ATTG_NO_CATCH_TRANSMITTER, DIAGDIR_NE, APT_RADIO_TOWER_FENCE_NE), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_Y, APT_APRON_FENCE_NE_SW), + + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_Y, APT_APRON_FENCE_NE), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_NONE, APT_APRON_HALF_EAST), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_Y, APT_APRON_FENCE_NE), + AirportTileTable(ATT_INFRASTRUCTURE_NO_CATCH, ATTG_NO_CATCH_TOWER, DIAGDIR_NE, APT_TOWER), + AirportTileTable(ATT_APRON_HELIPAD, TRACK_BIT_Y, APRON_HELIPAD, APT_HELIPAD_2), + AirportTileTable(ATT_APRON_HELIPAD, TRACK_BIT_CROSS, APRON_HELIPAD, APT_HELIPAD_2), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS | TRACK_BIT_UPPER | TRACK_BIT_RIGHT, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_X, APT_APRON_FENCE_NW), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS, APT_APRON_FENCE_SW), + + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_LEFT, APT_APRON_FENCE_NE), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_X, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS, APT_APRON), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_X, APRON_APRON, APT_STAND), + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_1, DIAGDIR_NE, APT_BUILDING_1), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_X, APRON_APRON, APT_STAND), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_ALL & ~TRACK_BIT_3WAY_SW, APT_APRON), + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_FLAT, DIAGDIR_NE, APT_LOW_BUILDING), + AirportTileTable(ATT_HANGAR_STANDARD, TRACK_BIT_Y, DIAGDIR_SE, APT_DEPOT_SE), + + AirportTileTable(ATT_HANGAR_STANDARD, TRACK_BIT_Y, DIAGDIR_SE, APT_DEPOT_SE), + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_FLAT, DIAGDIR_NE, APT_LOW_BUILDING), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_ALL & ~TRACK_BIT_3WAY_NE, APT_APRON), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_X, APRON_APRON, APT_STAND), + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_2, DIAGDIR_NE, APT_BUILDING_2), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_X, APRON_APRON, APT_STAND), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS | TRACK_BIT_UPPER | TRACK_BIT_RIGHT, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_X, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS | TRACK_BIT_RIGHT, APT_APRON_FENCE_SW), + + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS | TRACK_BIT_LOWER, APT_APRON_FENCE_NE), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_X, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_ALL & ~TRACK_BIT_UPPER, APT_APRON), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_X, APRON_APRON, APT_STAND), + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_3, DIAGDIR_NE, APT_BUILDING_3), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_X, APRON_APRON, APT_STAND), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_ALL & ~TRACK_BIT_LOWER, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_X, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_ALL & ~TRACK_BIT_3WAY_SW, APT_APRON_FENCE_SW), + + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS | TRACK_BIT_LEFT, APT_APRON_FENCE_NE), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_X, APT_APRON_FENCE_SE), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_ALL & ~TRACK_BIT_RIGHT & ~TRACK_BIT_LOWER, APT_APRON), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_CROSS, APRON_APRON, APT_STAND), + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_TERMINAL, DIAGDIR_NE, APT_ROUND_TERMINAL), + AirportTileTable(ATT_APRON_NORMAL, TRACK_BIT_CROSS, APRON_APRON, APT_STAND), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_Y | TRACK_BIT_UPPER, APT_APRON_FENCE_SW), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_NONE, APT_APRON_HALF_WEST), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_Y, APT_APRON_FENCE_SW), + + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_Y, APT_APRON_FENCE_NE), + AirportTileTable(ATT_INFRASTRUCTURE_NO_CATCH, ATTG_NO_CATCH_FLAG, DIAGDIR_NE, APT_GRASS_FENCE_NE_FLAG_2), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_LEFT | TRACK_BIT_CROSS, APT_APRON_FENCE_NE), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_X, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS, APT_APRON), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_UPPER | TRACK_BIT_CROSS, APT_APRON_FENCE_SW), + AirportTileTable(ATT_INFRASTRUCTURE_NO_CATCH, ATTG_NO_CATCH_EMPTY, DIAGDIR_NE, APT_EMPTY), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_Y, APT_APRON_FENCE_NE_SW), + + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_Y, APT_APRON_FENCE_NE), + AirportTileTable(ATT_RUNWAY_END, TRACK_BIT_NONE, DIAGDIR_NE, APT_RUNWAY_END_FENCE_NE_NW), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_NE, APT_RUNWAY_FENCE_NW), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_NE, APT_RUNWAY_FENCE_NW), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_NE, APT_RUNWAY_FENCE_NW), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_NE, APT_RUNWAY_FENCE_NW), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_NE, APT_RUNWAY_FENCE_NW), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_NE, APT_RUNWAY_FENCE_NW), + AirportTileTable(ATT_RUNWAY_START_NO_LANDING, TRACK_BIT_Y, DIAGDIR_NE, APT_RUNWAY_END_FENCE_SE_SW), + + AirportTileTable(ATT_RUNWAY_END, TRACK_BIT_Y, DIAGDIR_NE, APT_RUNWAY_END_FENCE_NE_SE), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_NE, APT_RUNWAY_2), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_NE, APT_RUNWAY_2), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_NE, APT_RUNWAY_2), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_NE, APT_RUNWAY_2), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_NE, APT_RUNWAY_2), + AirportTileTable(ATT_RUNWAY_MIDDLE, TRACK_BIT_NONE, DIR_NE, APT_RUNWAY_2), + AirportTileTable(ATT_RUNWAY_START_ALLOW_LANDING, TRACK_BIT_NONE, DIAGDIR_NE, APT_RUNWAY_END_FENCE_SE_SW), + AirportTileTable(ATT_INFRASTRUCTURE_NO_CATCH, ATTG_NO_CATCH_EMPTY, DIAGDIR_NE, APT_EMPTY), }; -static const std::initializer_list _tile_table_metropolitan = { - { _tile_table_metropolitan_0, DIR_N }, +/** Tiles for Heliport */ +static const std::initializer_list _description_heliport = { + AirportTileTable(ATT_APRON_HELIPORT, TRACK_BIT_CROSS, APRON_HELIPORT, APT_HELIPORT), }; -/** Tiles for International Airport (large) - 2 runways */ -static const std::initializer_list _tile_table_international_0 = { - MK(0, 0, APT_RUNWAY_END_FENCE_NW), - MK(1, 0, APT_RUNWAY_FENCE_NW), - MK(2, 0, APT_RUNWAY_FENCE_NW), - MK(3, 0, APT_RUNWAY_FENCE_NW), - MK(4, 0, APT_RUNWAY_FENCE_NW), - MK(5, 0, APT_RUNWAY_FENCE_NW), - MK(6, 0, APT_RUNWAY_END_FENCE_NW), - MK(0, 1, APT_RADIO_TOWER_FENCE_NE), - MK(1, 1, APT_APRON), - MK(2, 1, APT_APRON), - MK(3, 1, APT_APRON), - MK(4, 1, APT_APRON), - MK(5, 1, APT_APRON), - MK(6, 1, APT_DEPOT_SE), - MK(0, 2, APT_BUILDING_3), - MK(1, 2, APT_APRON), - MK(2, 2, APT_STAND), - MK(3, 2, APT_BUILDING_2), - MK(4, 2, APT_STAND), - MK(5, 2, APT_APRON), - MK(6, 2, APT_APRON_FENCE_SW), - MK(0, 3, APT_DEPOT_SE), - MK(1, 3, APT_APRON), - MK(2, 3, APT_STAND), - MK(3, 3, APT_BUILDING_2), - MK(4, 3, APT_STAND), - MK(5, 3, APT_APRON), - MK(6, 3, APT_HELIPAD_1), - MK(0, 4, APT_APRON_FENCE_NE), - MK(1, 4, APT_APRON), - MK(2, 4, APT_STAND), - MK(3, 4, APT_TOWER), - MK(4, 4, APT_STAND), - MK(5, 4, APT_APRON), - MK(6, 4, APT_HELIPAD_1), - MK(0, 5, APT_APRON_FENCE_NE), - MK(1, 5, APT_APRON), - MK(2, 5, APT_APRON), - MK(3, 5, APT_APRON), - MK(4, 5, APT_APRON), - MK(5, 5, APT_APRON), - MK(6, 5, APT_RADAR_FENCE_SW), - MK(0, 6, APT_RUNWAY_END_FENCE_SE), - MK(1, 6, APT_RUNWAY_2), - MK(2, 6, APT_RUNWAY_2), - MK(3, 6, APT_RUNWAY_2), - MK(4, 6, APT_RUNWAY_2), - MK(5, 6, APT_RUNWAY_2), - MK(6, 6, APT_RUNWAY_END_FENCE_SE), - MKEND +/** Tiles for Helidepot */ +static const std::initializer_list _description_helidepot = { + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_FLAT, DIAGDIR_NE, APT_LOW_BUILDING_FENCE_N), + AirportTileTable(ATT_HANGAR_STANDARD, TRACK_BIT_Y, DIAGDIR_SE, APT_DEPOT_SE), + + AirportTileTable(ATT_APRON_HELIPAD, TRACK_BIT_X, APRON_HELIPAD, APT_HELIPAD_2_FENCE_NE_SE), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS, APT_APRON_FENCE_SE_SW), }; -static const std::initializer_list _tile_table_international = { - { _tile_table_international_0, DIR_N }, +/** Tiles for Helistation */ +static const std::initializer_list _description_helistation = { + AirportTileTable(ATT_HANGAR_STANDARD, TRACK_BIT_Y, DIAGDIR_SE, APT_DEPOT_SE), + AirportTileTable(ATT_INFRASTRUCTURE_WITH_CATCH, ATTG_WITH_CATCH_BUILDING_FLAT, DIAGDIR_NE, APT_LOW_BUILDING_FENCE_NW), + AirportTileTable(ATT_APRON_HELIPAD, TRACK_BIT_Y | TRACK_BIT_LOWER, APRON_HELIPAD, APT_HELIPAD_3_FENCE_NW), + AirportTileTable(ATT_APRON_HELIPAD, TRACK_BIT_CROSS, APRON_HELIPAD, APT_HELIPAD_3_FENCE_NW_SW), + + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS, APT_APRON_FENCE_NE_SE), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_X, APT_APRON_FENCE_SE), + AirportTileTable(ATT_SIMPLE_TRACK, TRACK_BIT_CROSS | TRACK_BIT_LEFT, APT_APRON_FENCE_SE), + AirportTileTable(ATT_APRON_HELIPAD, TRACK_BIT_X | TRACK_BIT_UPPER, APRON_HELIPAD, APT_HELIPAD_3_FENCE_SE_SW), }; -/** Tiles for International Airport (large) - 2 runways */ -static const std::initializer_list _tile_table_intercontinental_0 = { - MK(0, 0, APT_RADAR_FENCE_NE), - MK(1, 0, APT_RUNWAY_END_FENCE_NE_NW), - MK(2, 0, APT_RUNWAY_FENCE_NW), - MK(3, 0, APT_RUNWAY_FENCE_NW), - MK(4, 0, APT_RUNWAY_FENCE_NW), - MK(5, 0, APT_RUNWAY_FENCE_NW), - MK(6, 0, APT_RUNWAY_FENCE_NW), - MK(7, 0, APT_RUNWAY_FENCE_NW), - MK(8, 0, APT_RUNWAY_END_FENCE_NW_SW), - MK(0, 1, APT_RUNWAY_END_FENCE_NE_NW), - MK(1, 1, APT_RUNWAY_2), - MK(2, 1, APT_RUNWAY_2), - MK(3, 1, APT_RUNWAY_2), - MK(4, 1, APT_RUNWAY_2), - MK(5, 1, APT_RUNWAY_2), - MK(6, 1, APT_RUNWAY_2), - MK(7, 1, APT_RUNWAY_END_FENCE_SE_SW), - MK(8, 1, APT_APRON_FENCE_NE_SW), - MK(0, 2, APT_APRON_FENCE_NE_SW), - MK(1, 2, APT_EMPTY), - MK(2, 2, APT_APRON_FENCE_NE), - MK(3, 2, APT_APRON), - MK(4, 2, APT_APRON), - MK(5, 2, APT_APRON), - MK(6, 2, APT_APRON), - MK(7, 2, APT_RADIO_TOWER_FENCE_NE), - MK(8, 2, APT_APRON_FENCE_NE_SW), - MK(0, 3, APT_APRON_FENCE_NE), - MK(1, 3, APT_APRON_HALF_EAST), - MK(2, 3, APT_APRON_FENCE_NE), - MK(3, 3, APT_TOWER), - MK(4, 3, APT_HELIPAD_2), - MK(5, 3, APT_HELIPAD_2), - MK(6, 3, APT_APRON), - MK(7, 3, APT_APRON_FENCE_NW), - MK(8, 3, APT_APRON_FENCE_SW), - MK(0, 4, APT_APRON_FENCE_NE), - MK(1, 4, APT_APRON), - MK(2, 4, APT_APRON), - MK(3, 4, APT_STAND), - MK(4, 4, APT_BUILDING_1), - MK(5, 4, APT_STAND), - MK(6, 4, APT_APRON), - MK(7, 4, APT_LOW_BUILDING), - MK(8, 4, APT_DEPOT_SE), - MK(0, 5, APT_DEPOT_SE), - MK(1, 5, APT_LOW_BUILDING), - MK(2, 5, APT_APRON), - MK(3, 5, APT_STAND), - MK(4, 5, APT_BUILDING_2), - MK(5, 5, APT_STAND), - MK(6, 5, APT_APRON), - MK(7, 5, APT_APRON), - MK(8, 5, APT_APRON_FENCE_SW), - MK(0, 6, APT_APRON_FENCE_NE), - MK(1, 6, APT_APRON), - MK(2, 6, APT_APRON), - MK(3, 6, APT_STAND), - MK(4, 6, APT_BUILDING_3), - MK(5, 6, APT_STAND), - MK(6, 6, APT_APRON), - MK(7, 6, APT_APRON), - MK(8, 6, APT_APRON_FENCE_SW), - MK(0, 7, APT_APRON_FENCE_NE), - MK(1, 7, APT_APRON_FENCE_SE), - MK(2, 7, APT_APRON), - MK(3, 7, APT_STAND), - MK(4, 7, APT_ROUND_TERMINAL), - MK(5, 7, APT_STAND), - MK(6, 7, APT_APRON_FENCE_SW), - MK(7, 7, APT_APRON_HALF_WEST), - MK(8, 7, APT_APRON_FENCE_SW), - MK(0, 8, APT_APRON_FENCE_NE), - MK(1, 8, APT_GRASS_FENCE_NE_FLAG_2), - MK(2, 8, APT_APRON_FENCE_NE), - MK(3, 8, APT_APRON), - MK(4, 8, APT_APRON), - MK(5, 8, APT_APRON), - MK(6, 8, APT_APRON_FENCE_SW), - MK(7, 8, APT_EMPTY), - MK(8, 8, APT_APRON_FENCE_NE_SW), - MK(0, 9, APT_APRON_FENCE_NE), - MK(1, 9, APT_RUNWAY_END_FENCE_NE_NW), - MK(2, 9, APT_RUNWAY_FENCE_NW), - MK(3, 9, APT_RUNWAY_FENCE_NW), - MK(4, 9, APT_RUNWAY_FENCE_NW), - MK(5, 9, APT_RUNWAY_FENCE_NW), - MK(6, 9, APT_RUNWAY_FENCE_NW), - MK(7, 9, APT_RUNWAY_FENCE_NW), - MK(8, 9, APT_RUNWAY_END_FENCE_SE_SW), - MK(0, 10, APT_RUNWAY_END_FENCE_NE_SE), - MK(1, 10, APT_RUNWAY_2), - MK(2, 10, APT_RUNWAY_2), - MK(3, 10, APT_RUNWAY_2), - MK(4, 10, APT_RUNWAY_2), - MK(5, 10, APT_RUNWAY_2), - MK(6, 10, APT_RUNWAY_2), - MK(7, 10, APT_RUNWAY_END_FENCE_SE_SW), - MK(8, 10, APT_EMPTY), - MKEND +/** Tiles for Oil rig */ +static const std::initializer_list _description_oilrig = { + AirportTileTable(ATT_APRON_BUILTIN_HELIPORT, TRACK_BIT_CROSS, APRON_BUILTIN_HELIPORT, APT_DEPOT_SE /* TODO */), }; -static const std::initializer_list _tile_table_intercontinental = { - { _tile_table_intercontinental_0, DIR_N }, +static const std::initializer_list _layouts_country = { + { _description_country, 4, 3 }, { _description_country_2, 4, 3 }, { _description_country_3, 4, 4 }, }; -/** Tiles for Heliport */ -static const std::initializer_list _tile_table_heliport_0 = { - MK(0, 0, APT_HELIPORT), - MKEND +static const std::initializer_list _layouts_city = { + { _description_city, 6, 6 }, }; -static const std::initializer_list _tile_table_heliport = { - { _tile_table_heliport_0, DIR_N }, +static const std::initializer_list _layouts_heliport = { + { _description_heliport, 1, 1 }, }; -/** Tiles for Helidepot */ -static const std::initializer_list _tile_table_helidepot_0 = { - MK(0, 0, APT_LOW_BUILDING_FENCE_N), - MK(1, 0, APT_DEPOT_SE), - MK(0, 1, APT_HELIPAD_2_FENCE_NE_SE), - MK(1, 1, APT_APRON_FENCE_SE_SW), - MKEND +static const std::initializer_list _layouts_metropolitan = { + { _description_metropolitan, 6, 6 }, }; -static const std::initializer_list _tile_table_helidepot = { - { _tile_table_helidepot_0, DIR_N }, +static const std::initializer_list _layouts_international = { + { _description_international, 7, 7 }, }; -/** Tiles for Helistation */ -static const std::initializer_list _tile_table_helistation_0 = { - MK(0, 0, APT_DEPOT_SE), - MK(1, 0, APT_LOW_BUILDING_FENCE_NW), - MK(2, 0, APT_HELIPAD_3_FENCE_NW), - MK(3, 0, APT_HELIPAD_3_FENCE_NW_SW), - MK(0, 1, APT_APRON_FENCE_NE_SE), - MK(1, 1, APT_APRON_FENCE_SE), - MK(2, 1, APT_APRON_FENCE_SE), - MK(3, 1, APT_HELIPAD_3_FENCE_SE_SW), - MKEND +static const std::initializer_list _layouts_commuter = { + { _description_commuter, 5, 4 }, }; -static const std::initializer_list _tile_table_helistation = { - { _tile_table_helistation_0, DIR_N }, +static const std::initializer_list _layouts_helidepot = { + { _description_helidepot, 2, 2 }, }; -#undef MK -#undef MKEND +static const std::initializer_list _layouts_intercontinental = { + { _description_intercontinental, 9, 11 }, +}; -/** General AirportSpec definition. */ -#define AS_GENERIC(fsm, layouts, depots, size_x, size_y, noise, catchment, min_year, max_year, maint_cost, ttdpatch_type, class_id, name, preview, enabled) \ - {{class_id, 0}, fsm, layouts, depots, size_x, size_y, noise, catchment, min_year, max_year, name, ttdpatch_type, preview, maint_cost, enabled, GRFFileProps(AT_INVALID)} +static const std::initializer_list _layouts_helistation = { + { _description_helistation, 4, 2 }, +}; -/** AirportSpec definition for airports without any depot. */ -#define AS_ND(ap_name, size_x, size_y, min_year, max_year, catchment, noise, maint_cost, ttdpatch_type, class_id, name, preview) \ - AS_GENERIC(&_airportfta_##ap_name, _tile_table_##ap_name, {}, \ - size_x, size_y, noise, catchment, min_year, max_year, maint_cost, ttdpatch_type, class_id, name, preview, true) +static const std::initializer_list _layouts_oilrig = { + { _description_oilrig, 1, 1 }, +}; -/** AirportSpec definition for airports with at least one depot. */ -#define AS(ap_name, size_x, size_y, min_year, max_year, catchment, noise, maint_cost, ttdpatch_type, class_id, name, preview) \ - AS_GENERIC(&_airportfta_##ap_name, _tile_table_##ap_name, _airport_depots_##ap_name, \ - size_x, size_y, noise, catchment, min_year, max_year, maint_cost, ttdpatch_type, class_id, name, preview, true) +/** General AirportSpec definition. */ +#define AS(dname, airtype, min_year, max_year, num_runways, num_aprons, num_helipads, num_heliports, min_runway_length, ttdpatch_type, class_id, name, preview, enabled, has_hangar, has_heliport) \ +{{class_id, 0}, _layouts_##dname, airtype, num_runways, num_aprons, num_helipads, num_heliports, min_runway_length, min_year, max_year, name, ttdpatch_type, preview, enabled, has_hangar, has_heliport, GRFFileProps(AT_INVALID) } /* The helidepot and helistation have ATP_TTDP_SMALL because they are at ground level */ -extern const AirportSpec _origin_airport_specs[] = { - AS(country, 4, 3, 0, 1959, 4, 3, 7, ATP_TTDP_SMALL, APC_SMALL, STR_AIRPORT_SMALL, SPR_AIRPORT_PREVIEW_SMALL), - AS(city, 6, 6, 1955, CalendarTime::MAX_YEAR, 5, 5, 24, ATP_TTDP_LARGE, APC_LARGE, STR_AIRPORT_CITY, SPR_AIRPORT_PREVIEW_LARGE), - AS_ND(heliport, 1, 1, 1963, CalendarTime::MAX_YEAR, 4, 1, 4, ATP_TTDP_HELIPORT, APC_HELIPORT, STR_AIRPORT_HELIPORT, SPR_AIRPORT_PREVIEW_HELIPORT), - AS(metropolitan, 6, 6, 1980, CalendarTime::MAX_YEAR, 6, 8, 28, ATP_TTDP_LARGE, APC_LARGE, STR_AIRPORT_METRO, SPR_AIRPORT_PREVIEW_METROPOLITAN), - AS(international, 7, 7, 1990, CalendarTime::MAX_YEAR, 8, 17, 42, ATP_TTDP_LARGE, APC_HUB, STR_AIRPORT_INTERNATIONAL, SPR_AIRPORT_PREVIEW_INTERNATIONAL), - AS(commuter, 5, 4, 1983, CalendarTime::MAX_YEAR, 4, 4, 20, ATP_TTDP_SMALL, APC_SMALL, STR_AIRPORT_COMMUTER, SPR_AIRPORT_PREVIEW_COMMUTER), - AS(helidepot, 2, 2, 1976, CalendarTime::MAX_YEAR, 4, 2, 7, ATP_TTDP_SMALL, APC_HELIPORT, STR_AIRPORT_HELIDEPOT, SPR_AIRPORT_PREVIEW_HELIDEPOT), - AS(intercontinental, 9, 11, 2002, CalendarTime::MAX_YEAR, 10, 25, 72, ATP_TTDP_LARGE, APC_HUB, STR_AIRPORT_INTERCONTINENTAL, SPR_AIRPORT_PREVIEW_INTERCONTINENTAL), - AS(helistation, 4, 2, 1980, CalendarTime::MAX_YEAR, 4, 3, 14, ATP_TTDP_SMALL, APC_HELIPORT, STR_AIRPORT_HELISTATION, SPR_AIRPORT_PREVIEW_HELISTATION), - AS_GENERIC(&_airportfta_oilrig, {}, {}, 1, 1, 0, 4, 0, 0, 0, ATP_TTDP_OILRIG, APC_HELIPORT, STR_NULL, 0, false), +const AirportSpec _origin_airport_specs[] = { + AS(country, AIRTYPE_GRAVEL, 0, 1959, 1, 2, 0, 0, 4, ATP_TTDP_SMALL, APC_SMALL, STR_AIRPORT_SMALL, SPR_AIRTYPE_PREVIEW_SMALL, true, true, false), + AS(city, AIRTYPE_ASPHALT, 1955, CalendarTime::MAX_YEAR, 1, 3, 0, 0, 6, ATP_TTDP_LARGE, APC_LARGE, STR_AIRPORT_CITY, SPR_AIRTYPE_PREVIEW_LARGE, true, true, false), + AS(heliport, AIRTYPE_ASPHALT, 1963, CalendarTime::MAX_YEAR, 0, 0, 0, 1, 0, ATP_TTDP_HELIPORT, APC_HELIPORT, STR_AIRPORT_HELIPORT, SPR_AIRTYPE_PREVIEW_HELIPORT, true, false, true), + AS(metropolitan, AIRTYPE_ASPHALT, 1980, CalendarTime::MAX_YEAR, 2, 3, 0, 0, 6, ATP_TTDP_LARGE, APC_LARGE, STR_AIRPORT_METRO, SPR_AIRTYPE_PREVIEW_METROPOLITAN, true, true, false), + AS(international, AIRTYPE_ASPHALT, 1990, CalendarTime::MAX_YEAR, 2, 6, 2, 0, 7, ATP_TTDP_LARGE, APC_HUB, STR_AIRPORT_INTERNATIONAL, SPR_AIRTYPE_PREVIEW_INTERNATIONAL, true, true, false), + AS(commuter, AIRTYPE_ASPHALT, 1983, CalendarTime::MAX_YEAR, 1, 3, 2, 0, 5, ATP_TTDP_SMALL, APC_SMALL, STR_AIRPORT_COMMUTER, SPR_AIRTYPE_PREVIEW_COMMUTER, true, true, false), + AS(helidepot, AIRTYPE_ASPHALT, 1976, CalendarTime::MAX_YEAR, 0, 0, 1, 0, 0, ATP_TTDP_SMALL, APC_HELIPORT, STR_AIRPORT_HELIDEPOT, SPR_AIRTYPE_PREVIEW_HELIDEPOT, true, true, true), + AS(intercontinental, AIRTYPE_ASPHALT, 2002, CalendarTime::MAX_YEAR, 4, 8, 2, 0, 8, ATP_TTDP_LARGE, APC_HUB, STR_AIRPORT_INTERCONTINENTAL, SPR_AIRTYPE_PREVIEW_INTERCONTINENTAL, true, true, false), + AS(helistation, AIRTYPE_ASPHALT, 1980, CalendarTime::MAX_YEAR, 0, 0, 3, 0, 0, ATP_TTDP_SMALL, APC_HELIPORT, STR_AIRPORT_HELISTATION, SPR_AIRTYPE_PREVIEW_HELISTATION, true, true, true), + AS(oilrig, AIRTYPE_WATER, 0, CalendarTime::MAX_YEAR, 0, 0, 0, 1, 0, ATP_TTDP_OILRIG, APC_HELIPORT, STR_NULL, 0, false, false, false), }; static_assert(NEW_AIRPORT_OFFSET == lengthof(_origin_airport_specs)); -const AirportSpec AirportSpec::dummy = AS_GENERIC(&_airportfta_dummy, {}, {}, 0, 0, 0, 0, CalendarTime::MIN_YEAR, CalendarTime::MIN_YEAR, 0, ATP_TTDP_LARGE, APC_BEGIN, STR_NULL, 0, false); - #undef AS -#undef AS_ND -#undef AS_GENERIC #endif /* AIRPORT_DEFAULTS_H */ diff --git a/src/table/airport_movement.h b/src/table/airport_movement.h deleted file mode 100644 index 10e213718ad7b..0000000000000 --- a/src/table/airport_movement.h +++ /dev/null @@ -1,835 +0,0 @@ -/* - * This file is part of OpenTTD. - * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. - * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . - */ - -/** @file airport_movement.h Heart of the airports and their finite state machines */ - -#ifndef AIRPORT_MOVEMENT_H -#define AIRPORT_MOVEMENT_H - - -/** - * State machine input struct (from external file, etc.) - * Finite sTate mAchine --> FTA - */ -struct AirportFTAbuildup { - uint8_t position; ///< The position that an airplane is at. - uint8_t heading; ///< The current orders (eg. TAKEOFF, HANGAR, ENDLANDING, etc.). - uint64_t block; ///< The block this position is on on the airport (st->airport.flags). - uint8_t next; ///< Next position from this position. -}; - -/////////////////////////////////////////////////////////////////////// -/////*********Movement Positions on Airports********************/////// - -/** - * Airport movement data creation macro. - * @param x X position. - * @param y Y position. - * @param flags Movement flags. - * @param dir Direction. - */ -#define AMD(x, y, flags, dir) { x, y, flags, dir } - -/** Dummy airport. */ -static const AirportMovingData _airport_moving_data_dummy[] = { - AMD( 0, 0, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), - AMD( 0, 96, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), - AMD( 96, 96, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), - AMD( 96, 0, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), -}; - -/** Country Airfield (small) 4x3. */ -static const AirportMovingData _airport_moving_data_country[22] = { - AMD( 53, 3, AMED_EXACTPOS, DIR_SE), // 00 In Hangar - AMD( 53, 27, 0, DIR_N ), // 01 Taxi to right outside depot - AMD( 32, 23, AMED_EXACTPOS, DIR_NW), // 02 Terminal 1 - AMD( 10, 23, AMED_EXACTPOS, DIR_NW), // 03 Terminal 2 - AMD( 43, 37, 0, DIR_N ), // 04 Going towards terminal 2 - AMD( 24, 37, 0, DIR_N ), // 05 Going towards terminal 2 - AMD( 53, 37, 0, DIR_N ), // 06 Going for takeoff - AMD( 61, 40, AMED_EXACTPOS, DIR_NE), // 07 Taxi to start of runway (takeoff) - AMD( 3, 40, AMED_NOSPDCLAMP, DIR_N ), // 08 Accelerate to end of runway - AMD( -79, 40, AMED_NOSPDCLAMP | AMED_TAKEOFF, DIR_N ), // 09 Take off - AMD( 177, 40, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 10 Fly to landing position in air - AMD( 56, 40, AMED_NOSPDCLAMP | AMED_LAND, DIR_N ), // 11 Going down for land - AMD( 3, 40, AMED_NOSPDCLAMP | AMED_BRAKE, DIR_N ), // 12 Just landed, brake until end of runway - AMD( 7, 40, 0, DIR_N ), // 13 Just landed, turn around and taxi 1 square - AMD( 53, 40, 0, DIR_N ), // 14 Taxi from runway to crossing - AMD( 1, 193, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 15 Fly around waiting for a landing spot (north-east) - AMD( 1, 1, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 16 Fly around waiting for a landing spot (north-west) - AMD( 257, 1, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 17 Fly around waiting for a landing spot (south-west) - AMD( 273, 47, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 18 Fly around waiting for a landing spot (south) - AMD( 44, 37, AMED_HELI_RAISE, DIR_N ), // 19 Helicopter takeoff - AMD( 44, 40, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 20 In position above landing spot helicopter - AMD( 44, 40, AMED_HELI_LOWER, DIR_N ), // 21 Helicopter landing -}; - -/** Commuter Airfield (small) 5x4. */ -static const AirportMovingData _airport_moving_data_commuter[38] = { - AMD( 69, 3, AMED_EXACTPOS, DIR_SE), // 00 In Hangar - AMD( 72, 22, 0, DIR_N ), // 01 Taxi to right outside depot - AMD( 8, 22, AMED_EXACTPOS, DIR_SW), // 02 Taxi to right outside depot - AMD( 24, 36, AMED_EXACTPOS, DIR_SE), // 03 Terminal 1 - AMD( 40, 36, AMED_EXACTPOS, DIR_SE), // 04 Terminal 2 - AMD( 56, 36, AMED_EXACTPOS, DIR_SE), // 05 Terminal 3 - AMD( 40, 8, AMED_EXACTPOS, DIR_NE), // 06 Helipad 1 - AMD( 56, 8, AMED_EXACTPOS, DIR_NE), // 07 Helipad 2 - AMD( 24, 22, 0, DIR_SW), // 08 Taxiing - AMD( 40, 22, 0, DIR_SW), // 09 Taxiing - AMD( 56, 22, 0, DIR_SW), // 10 Taxiing - AMD( 72, 40, 0, DIR_SE), // 11 Airport OUTWAY - AMD( 72, 54, AMED_EXACTPOS, DIR_NE), // 12 Accelerate to end of runway - AMD( 7, 54, AMED_NOSPDCLAMP, DIR_N ), // 13 Release control of runway, for smoother movement - AMD( 5, 54, AMED_NOSPDCLAMP, DIR_N ), // 14 End of runway - AMD( -79, 54, AMED_NOSPDCLAMP | AMED_TAKEOFF, DIR_N ), // 15 Take off - AMD( 145, 54, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 16 Fly to landing position in air - AMD( 73, 54, AMED_NOSPDCLAMP | AMED_LAND, DIR_N ), // 17 Going down for land - AMD( 3, 54, AMED_NOSPDCLAMP | AMED_BRAKE, DIR_N ), // 18 Just landed, brake until end of runway - AMD( 12, 54, AMED_SLOWTURN, DIR_NW), // 19 Just landed, turn around and taxi - AMD( 8, 32, 0, DIR_NW), // 20 Taxi from runway to crossing - AMD( 1, 149, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 21 Fly around waiting for a landing spot (north-east) - AMD( 1, 6, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 22 Fly around waiting for a landing spot (north-west) - AMD( 193, 6, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 23 Fly around waiting for a landing spot (south-west) - AMD( 225, 62, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 24 Fly around waiting for a landing spot (south) - /* Helicopter */ - AMD( 80, 0, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 25 Bufferspace before helipad - AMD( 80, 0, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 26 Bufferspace before helipad - AMD( 32, 8, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 27 Get in position for Helipad1 - AMD( 48, 8, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 28 Get in position for Helipad2 - AMD( 32, 8, AMED_HELI_LOWER, DIR_N ), // 29 Land at Helipad1 - AMD( 48, 8, AMED_HELI_LOWER, DIR_N ), // 30 Land at Helipad2 - AMD( 32, 8, AMED_HELI_RAISE, DIR_N ), // 31 Takeoff Helipad1 - AMD( 48, 8, AMED_HELI_RAISE, DIR_N ), // 32 Takeoff Helipad2 - AMD( 64, 22, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 33 Go to position for Hangarentrance in air - AMD( 64, 22, AMED_HELI_LOWER, DIR_N ), // 34 Land in front of hangar - AMD( 40, 8, AMED_EXACTPOS, DIR_N ), // 35 pre-helitakeoff helipad 1 - AMD( 56, 8, AMED_EXACTPOS, DIR_N ), // 36 pre-helitakeoff helipad 2 - AMD( 64, 25, AMED_HELI_RAISE, DIR_N ), // 37 Take off in front of hangar -}; - -/** City Airport (large) 6x6. */ -static const AirportMovingData _airport_moving_data_city[] = { - AMD( 85, 3, AMED_EXACTPOS, DIR_SE), // 00 In Hangar - AMD( 85, 22, 0, DIR_N ), // 01 Taxi to right outside depot - AMD( 26, 41, AMED_EXACTPOS, DIR_SW), // 02 Terminal 1 - AMD( 56, 22, AMED_EXACTPOS, DIR_SE), // 03 Terminal 2 - AMD( 38, 8, AMED_EXACTPOS, DIR_SW), // 04 Terminal 3 - AMD( 65, 6, 0, DIR_N ), // 05 Taxi to right in infront of terminal 2/3 - AMD( 80, 27, 0, DIR_N ), // 06 Taxiway terminals 2-3 - AMD( 44, 63, 0, DIR_N ), // 07 Taxi to Airport center - AMD( 58, 71, 0, DIR_N ), // 08 Towards takeoff - AMD( 72, 85, 0, DIR_N ), // 09 Taxi to runway (takeoff) - AMD( 89, 85, AMED_EXACTPOS, DIR_NE), // 10 Taxi to start of runway (takeoff) - AMD( 3, 85, AMED_NOSPDCLAMP, DIR_N ), // 11 Accelerate to end of runway - AMD( -79, 85, AMED_NOSPDCLAMP | AMED_TAKEOFF, DIR_N ), // 12 Take off - AMD( 177, 87, AMED_HOLD | AMED_SLOWTURN, DIR_N ), // 13 Fly to landing position in air - AMD( 89, 87, AMED_HOLD | AMED_LAND, DIR_N ), // 14 Going down for land - AMD( 20, 87, AMED_NOSPDCLAMP | AMED_BRAKE, DIR_N ), // 15 Just landed, brake until end of runway - AMD( 20, 87, 0, DIR_N ), // 16 Just landed, turn around and taxi 1 square // NOT USED - AMD( 36, 71, 0, DIR_N ), // 17 Taxi from runway to crossing - AMD( 160, 87, AMED_HOLD | AMED_SLOWTURN, DIR_N ), // 18 Fly around waiting for a landing spot (north-east) - AMD( 140, 1, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 19 Final approach fix - AMD( 257, 1, AMED_HOLD | AMED_SLOWTURN, DIR_N ), // 20 Fly around waiting for a landing spot (south-west) - AMD( 273, 49, AMED_HOLD | AMED_SLOWTURN, DIR_N ), // 21 Fly around waiting for a landing spot (south) - AMD( 44, 63, AMED_HELI_RAISE, DIR_N ), // 22 Helicopter takeoff - AMD( 28, 74, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 23 In position above landing spot helicopter - AMD( 28, 74, AMED_HELI_LOWER, DIR_N ), // 24 Helicopter landing - AMD( 145, 1, AMED_HOLD | AMED_SLOWTURN, DIR_N ), // 25 Fly around waiting for a landing spot (north-west) - AMD( -32, 1, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 26 Initial approach fix (north) - AMD( 300, -48, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 27 Initial approach fix (south) - AMD( 140, -48, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 28 Intermediate Approach fix (south), IAF (west) - AMD( -32, 120, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 29 Initial approach fix (east) -}; - -/** Metropolitan Airport (metropolitan) - 2 runways. */ -static const AirportMovingData _airport_moving_data_metropolitan[28] = { - AMD( 85, 3, AMED_EXACTPOS, DIR_SE), // 00 In Hangar - AMD( 85, 22, 0, DIR_N ), // 01 Taxi to right outside depot - AMD( 26, 41, AMED_EXACTPOS, DIR_SW), // 02 Terminal 1 - AMD( 56, 22, AMED_EXACTPOS, DIR_SE), // 03 Terminal 2 - AMD( 38, 8, AMED_EXACTPOS, DIR_SW), // 04 Terminal 3 - AMD( 65, 6, 0, DIR_N ), // 05 Taxi to right in infront of terminal 2/3 - AMD( 80, 27, 0, DIR_N ), // 06 Taxiway terminals 2-3 - AMD( 49, 58, 0, DIR_N ), // 07 Taxi to Airport center - AMD( 72, 58, 0, DIR_N ), // 08 Towards takeoff - AMD( 72, 69, 0, DIR_N ), // 09 Taxi to runway (takeoff) - AMD( 89, 69, AMED_EXACTPOS, DIR_NE), // 10 Taxi to start of runway (takeoff) - AMD( 3, 69, AMED_NOSPDCLAMP, DIR_N ), // 11 Accelerate to end of runway - AMD( -79, 69, AMED_NOSPDCLAMP | AMED_TAKEOFF, DIR_N ), // 12 Take off - AMD( 177, 85, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 13 Fly to landing position in air - AMD( 89, 85, AMED_NOSPDCLAMP | AMED_LAND, DIR_N ), // 14 Going down for land - AMD( 3, 85, AMED_NOSPDCLAMP | AMED_BRAKE, DIR_N ), // 15 Just landed, brake until end of runway - AMD( 21, 85, 0, DIR_N ), // 16 Just landed, turn around and taxi 1 square - AMD( 21, 69, 0, DIR_N ), // 17 On Runway-out taxiing to In-Way - AMD( 21, 58, AMED_EXACTPOS, DIR_SW), // 18 Taxi from runway to crossing - AMD( 1, 193, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 19 Fly around waiting for a landing spot (north-east) - AMD( 1, 1, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 20 Fly around waiting for a landing spot (north-west) - AMD( 257, 1, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 21 Fly around waiting for a landing spot (south-west) - AMD( 273, 49, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 22 Fly around waiting for a landing spot (south) - AMD( 44, 58, 0, DIR_N ), // 23 Helicopter takeoff spot on ground (to clear airport sooner) - AMD( 44, 63, AMED_HELI_RAISE, DIR_N ), // 24 Helicopter takeoff - AMD( 15, 54, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 25 Get in position above landing spot helicopter - AMD( 15, 54, AMED_HELI_LOWER, DIR_N ), // 26 Helicopter landing - AMD( 21, 58, AMED_EXACTPOS, DIR_SW), // 27 Transitions after landing to on-ground movement -}; - -/** International Airport (international) - 2 runways, 6 terminals, dedicated helipad. */ -static const AirportMovingData _airport_moving_data_international[53] = { - AMD( 7, 55, AMED_EXACTPOS, DIR_SE), // 00 In Hangar 1 - AMD( 100, 21, AMED_EXACTPOS, DIR_SE), // 01 In Hangar 2 - AMD( 7, 70, 0, DIR_N ), // 02 Taxi to right outside depot (Hangar 1) - AMD( 100, 36, 0, DIR_N ), // 03 Taxi to right outside depot (Hangar 2) - AMD( 38, 70, AMED_EXACTPOS, DIR_SW), // 04 Terminal 1 - AMD( 38, 54, AMED_EXACTPOS, DIR_SW), // 05 Terminal 2 - AMD( 38, 38, AMED_EXACTPOS, DIR_SW), // 06 Terminal 3 - AMD( 70, 70, AMED_EXACTPOS, DIR_NE), // 07 Terminal 4 - AMD( 70, 54, AMED_EXACTPOS, DIR_NE), // 08 Terminal 5 - AMD( 70, 38, AMED_EXACTPOS, DIR_NE), // 09 Terminal 6 - AMD( 104, 71, AMED_EXACTPOS, DIR_NE), // 10 Helipad 1 - AMD( 104, 55, AMED_EXACTPOS, DIR_NE), // 11 Helipad 2 - AMD( 22, 87, 0, DIR_N ), // 12 Towards Terminals 4/5/6, Helipad 1/2 - AMD( 60, 87, 0, DIR_N ), // 13 Towards Terminals 4/5/6, Helipad 1/2 - AMD( 66, 87, 0, DIR_N ), // 14 Towards Terminals 4/5/6, Helipad 1/2 - AMD( 86, 87, AMED_EXACTPOS, DIR_NW), // 15 Towards Terminals 4/5/6, Helipad 1/2 - AMD( 86, 70, 0, DIR_N ), // 16 In Front of Terminal 4 / Helipad 1 - AMD( 86, 54, 0, DIR_N ), // 17 In Front of Terminal 5 / Helipad 2 - AMD( 86, 38, 0, DIR_N ), // 18 In Front of Terminal 6 - AMD( 86, 22, 0, DIR_N ), // 19 Towards Terminals Takeoff (Taxiway) - AMD( 66, 22, 0, DIR_N ), // 20 Towards Terminals Takeoff (Taxiway) - AMD( 60, 22, 0, DIR_N ), // 21 Towards Terminals Takeoff (Taxiway) - AMD( 38, 22, 0, DIR_N ), // 22 Towards Terminals Takeoff (Taxiway) - AMD( 22, 70, 0, DIR_N ), // 23 In Front of Terminal 1 - AMD( 22, 58, 0, DIR_N ), // 24 In Front of Terminal 2 - AMD( 22, 38, 0, DIR_N ), // 25 In Front of Terminal 3 - AMD( 22, 22, AMED_EXACTPOS, DIR_NW), // 26 Going for Takeoff - AMD( 22, 6, 0, DIR_N ), // 27 On Runway-out, prepare for takeoff - AMD( 3, 6, AMED_EXACTPOS, DIR_SW), // 28 Accelerate to end of runway - AMD( 60, 6, AMED_NOSPDCLAMP, DIR_N ), // 29 Release control of runway, for smoother movement - AMD( 105, 6, AMED_NOSPDCLAMP, DIR_N ), // 30 End of runway - AMD( 190, 6, AMED_NOSPDCLAMP | AMED_TAKEOFF, DIR_N ), // 31 Take off - AMD( 193, 104, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 32 Fly to landing position in air - AMD( 105, 104, AMED_NOSPDCLAMP | AMED_LAND, DIR_N ), // 33 Going down for land - AMD( 3, 104, AMED_NOSPDCLAMP | AMED_BRAKE, DIR_N ), // 34 Just landed, brake until end of runway - AMD( 12, 104, AMED_SLOWTURN, DIR_N ), // 35 Just landed, turn around and taxi 1 square - AMD( 7, 84, 0, DIR_N ), // 36 Taxi from runway to crossing - AMD( 1, 209, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 37 Fly around waiting for a landing spot (north-east) - AMD( 1, 6, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 38 Fly around waiting for a landing spot (north-west) - AMD( 273, 6, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 39 Fly around waiting for a landing spot (south-west) - AMD( 305, 81, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 40 Fly around waiting for a landing spot (south) - /* Helicopter */ - AMD( 128, 80, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 41 Bufferspace before helipad - AMD( 128, 80, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 42 Bufferspace before helipad - AMD( 96, 71, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 43 Get in position for Helipad1 - AMD( 96, 55, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 44 Get in position for Helipad2 - AMD( 96, 71, AMED_HELI_LOWER, DIR_N ), // 45 Land at Helipad1 - AMD( 96, 55, AMED_HELI_LOWER, DIR_N ), // 46 Land at Helipad2 - AMD( 104, 71, AMED_HELI_RAISE, DIR_N ), // 47 Takeoff Helipad1 - AMD( 104, 55, AMED_HELI_RAISE, DIR_N ), // 48 Takeoff Helipad2 - AMD( 104, 32, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 49 Go to position for Hangarentrance in air - AMD( 104, 32, AMED_HELI_LOWER, DIR_N ), // 50 Land in HANGAR2_AREA to go to hangar - AMD( 7, 70, AMED_HELI_RAISE, DIR_N ), // 51 Takeoff from HANGAR1_AREA - AMD( 100, 36, AMED_HELI_RAISE, DIR_N ), // 52 Takeoff from HANGAR2_AREA -}; - -/** Intercontinental Airport - 4 runways, 8 terminals, 2 dedicated helipads. */ -static const AirportMovingData _airport_moving_data_intercontinental[77] = { - AMD( 8, 87, AMED_EXACTPOS, DIR_SE), // 00 In Hangar 1 - AMD( 136, 72, AMED_EXACTPOS, DIR_SE), // 01 In Hangar 2 - AMD( 8, 104, 0, DIR_N ), // 02 Taxi to right outside depot 1 - AMD( 136, 88, 0, DIR_N ), // 03 Taxi to right outside depot 2 - AMD( 56, 120, AMED_EXACTPOS, DIR_W ), // 04 Terminal 1 - AMD( 56, 104, AMED_EXACTPOS, DIR_SW), // 05 Terminal 2 - AMD( 56, 88, AMED_EXACTPOS, DIR_SW), // 06 Terminal 3 - AMD( 56, 72, AMED_EXACTPOS, DIR_SW), // 07 Terminal 4 - AMD( 88, 120, AMED_EXACTPOS, DIR_N ), // 08 Terminal 5 - AMD( 88, 104, AMED_EXACTPOS, DIR_NE), // 09 Terminal 6 - AMD( 88, 88, AMED_EXACTPOS, DIR_NE), // 10 Terminal 7 - AMD( 88, 72, AMED_EXACTPOS, DIR_NE), // 11 Terminal 8 - AMD( 88, 56, AMED_EXACTPOS, DIR_SE), // 12 Helipad 1 - AMD( 72, 56, AMED_EXACTPOS, DIR_NE), // 13 Helipad 2 - AMD( 40, 136, 0, DIR_N ), // 14 Term group 2 enter 1 a - AMD( 56, 136, 0, DIR_N ), // 15 Term group 2 enter 1 b - AMD( 88, 136, 0, DIR_N ), // 16 Term group 2 enter 2 a - AMD( 104, 136, 0, DIR_N ), // 17 Term group 2 enter 2 b - AMD( 104, 120, 0, DIR_N ), // 18 Term group 2 - opp term 5 - AMD( 104, 104, 0, DIR_N ), // 19 Term group 2 - opp term 6 & exit2 - AMD( 104, 88, 0, DIR_N ), // 20 Term group 2 - opp term 7 & hangar area 2 - AMD( 104, 72, 0, DIR_N ), // 21 Term group 2 - opp term 8 - AMD( 104, 56, 0, DIR_N ), // 22 Taxi Term group 2 exit a - AMD( 104, 40, 0, DIR_N ), // 23 Taxi Term group 2 exit b - AMD( 56, 40, 0, DIR_N ), // 24 Term group 2 exit 2a - AMD( 40, 40, 0, DIR_N ), // 25 Term group 2 exit 2b - AMD( 40, 120, 0, DIR_N ), // 26 Term group 1 - opp term 1 - AMD( 40, 104, 0, DIR_N ), // 27 Term group 1 - opp term 2 & hangar area 1 - AMD( 40, 88, 0, DIR_N ), // 28 Term group 1 - opp term 3 - AMD( 40, 72, 0, DIR_N ), // 29 Term group 1 - opp term 4 - AMD( 18, 72, 0, DIR_NW), // 30 Outway 1 - AMD( 8, 40, 0, DIR_NW), // 31 Airport OUTWAY - AMD( 8, 24, AMED_EXACTPOS, DIR_SW), // 32 Accelerate to end of runway - AMD( 119, 24, AMED_NOSPDCLAMP, DIR_N ), // 33 Release control of runway, for smoother movement - AMD( 117, 24, AMED_NOSPDCLAMP, DIR_N ), // 34 End of runway - AMD( 197, 24, AMED_NOSPDCLAMP | AMED_TAKEOFF, DIR_N ), // 35 Take off - AMD( 254, 84, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 36 Flying to landing position in air - AMD( 117, 168, AMED_NOSPDCLAMP | AMED_LAND, DIR_N ), // 37 Going down for land - AMD( 8, 168, AMED_NOSPDCLAMP | AMED_BRAKE, DIR_N ), // 38 Just landed, brake until end of runway - AMD( 8, 168, 0, DIR_N ), // 39 Just landed, turn around and taxi - AMD( 8, 144, 0, DIR_NW), // 40 Taxi from runway - AMD( 8, 128, 0, DIR_NW), // 41 Taxi from runway - AMD( 8, 120, AMED_EXACTPOS, DIR_NW), // 42 Airport entrance - AMD( 56, 344, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 43 Fly around waiting for a landing spot (north-east) - AMD( -200, 88, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 44 Fly around waiting for a landing spot (north-west) - AMD( 56, -168, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 45 Fly around waiting for a landing spot (south-west) - AMD( 312, 88, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 46 Fly around waiting for a landing spot (south) - /* Helicopter */ - AMD( 96, 40, AMED_NOSPDCLAMP, DIR_N ), // 47 Bufferspace before helipad - AMD( 96, 40, AMED_NOSPDCLAMP, DIR_N ), // 48 Bufferspace before helipad - AMD( 82, 54, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 49 Get in position for Helipad1 - AMD( 64, 56, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 50 Get in position for Helipad2 - AMD( 81, 55, AMED_HELI_LOWER, DIR_N ), // 51 Land at Helipad1 - AMD( 64, 56, AMED_HELI_LOWER, DIR_N ), // 52 Land at Helipad2 - AMD( 80, 56, AMED_HELI_RAISE, DIR_N ), // 53 Takeoff Helipad1 - AMD( 64, 56, AMED_HELI_RAISE, DIR_N ), // 54 Takeoff Helipad2 - AMD( 136, 96, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 55 Go to position for Hangarentrance in air - AMD( 136, 96, AMED_HELI_LOWER, DIR_N ), // 56 Land in front of hangar2 - AMD( 126, 104, 0, DIR_SE), // 57 Outway 2 - AMD( 136, 136, 0, DIR_NE), // 58 Airport OUTWAY 2 - AMD( 136, 152, AMED_EXACTPOS, DIR_NE), // 59 Accelerate to end of runway2 - AMD( 16, 152, AMED_NOSPDCLAMP, DIR_N ), // 60 Release control of runway2, for smoother movement - AMD( 20, 152, AMED_NOSPDCLAMP, DIR_N ), // 61 End of runway2 - AMD( -56, 152, AMED_NOSPDCLAMP | AMED_TAKEOFF, DIR_N ), // 62 Take off2 - AMD( 24, 8, AMED_NOSPDCLAMP | AMED_LAND, DIR_N ), // 63 Going down for land2 - AMD( 136, 8, AMED_NOSPDCLAMP | AMED_BRAKE, DIR_N ), // 64 Just landed, brake until end of runway2in - AMD( 136, 8, 0, DIR_N ), // 65 Just landed, turn around and taxi - AMD( 136, 24, 0, DIR_SE), // 66 Taxi from runway 2in - AMD( 136, 40, 0, DIR_SE), // 67 Taxi from runway 2in - AMD( 136, 56, AMED_EXACTPOS, DIR_NE), // 68 Airport entrance2 - AMD( -56, 8, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 69 Fly to landing position in air2 - AMD( 88, 40, 0, DIR_N ), // 70 Taxi Term group 2 exit - opp heli1 - AMD( 72, 40, 0, DIR_N ), // 71 Taxi Term group 2 exit - opp heli2 - AMD( 88, 57, AMED_EXACTPOS, DIR_SE), // 72 pre-helitakeoff helipad 1 - AMD( 71, 56, AMED_EXACTPOS, DIR_NE), // 73 pre-helitakeoff helipad 2 - AMD( 8, 120, AMED_HELI_RAISE, DIR_N ), // 74 Helitakeoff outside depot 1 - AMD( 136, 104, AMED_HELI_RAISE, DIR_N ), // 75 Helitakeoff outside depot 2 - AMD( 197, 168, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 76 Fly to landing position in air1 -}; - - -/** Heliport (heliport). */ -static const AirportMovingData _airport_moving_data_heliport[9] = { - AMD( 5, 9, AMED_EXACTPOS, DIR_NE), // 0 - At heliport terminal - AMD( 2, 9, AMED_HELI_RAISE, DIR_N ), // 1 - Take off (play sound) - AMD( -3, 9, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 2 - In position above landing spot helicopter - AMD( -3, 9, AMED_HELI_LOWER, DIR_N ), // 3 - Land - AMD( 2, 9, 0, DIR_N ), // 4 - Goto terminal on ground - AMD( -31, 59, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 5 - Circle #1 (north-east) - AMD( -31, -49, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 6 - Circle #2 (north-west) - AMD( 49, -49, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 7 - Circle #3 (south-west) - AMD( 70, 9, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 8 - Circle #4 (south) -}; - -/** HeliDepot 2x2 (heliport). */ -static const AirportMovingData _airport_moving_data_helidepot[18] = { - AMD( 24, 4, AMED_EXACTPOS, DIR_NE), // 0 - At depot - AMD( 24, 28, 0, DIR_N ), // 1 Taxi to right outside depot - AMD( 5, 38, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 2 Flying - AMD( -15, -15, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 3 - Circle #1 (north-east) - AMD( -15, -49, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 4 - Circle #2 (north-west) - AMD( 49, -49, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 5 - Circle #3 (south-west) - AMD( 49, -15, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 6 - Circle #4 (south-east) - AMD( 8, 32, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_NW), // 7 - PreHelipad - AMD( 8, 32, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_NW), // 8 - Helipad - AMD( 8, 16, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_NW), // 9 - Land - AMD( 8, 16, AMED_HELI_LOWER, DIR_NW), // 10 - Land - AMD( 8, 24, AMED_HELI_RAISE, DIR_N ), // 11 - Take off (play sound) - AMD( 32, 24, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_NW), // 12 Air to above hangar area - AMD( 32, 24, AMED_HELI_LOWER, DIR_NW), // 13 Taxi to right outside depot - AMD( 8, 24, AMED_EXACTPOS, DIR_NW), // 14 - on helipad1 - AMD( 24, 28, AMED_HELI_RAISE, DIR_N ), // 15 Takeoff right outside depot - AMD( 8, 24, AMED_HELI_RAISE, DIR_SW), // 16 - Take off (play sound) - AMD( 8, 24, AMED_SLOWTURN | AMED_EXACTPOS, DIR_E ), // 17 - turn on helipad1 for takeoff -}; - -/** HeliDepot 2x2 (heliport). */ -static const AirportMovingData _airport_moving_data_helistation[33] = { - AMD( 8, 3, AMED_EXACTPOS, DIR_SE), // 00 In Hangar2 - AMD( 8, 22, 0, DIR_N ), // 01 outside hangar 2 - AMD( 116, 24, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 02 Fly to landing position in air - AMD( 14, 22, AMED_HELI_RAISE, DIR_N ), // 03 Helitakeoff outside hangar1(play sound) - AMD( 24, 22, 0, DIR_N ), // 04 taxiing - AMD( 40, 22, 0, DIR_N ), // 05 taxiing - AMD( 40, 8, AMED_EXACTPOS, DIR_NE), // 06 Helipad 1 - AMD( 56, 8, AMED_EXACTPOS, DIR_NE), // 07 Helipad 2 - AMD( 56, 24, AMED_EXACTPOS, DIR_NE), // 08 Helipad 3 - AMD( 40, 8, AMED_EXACTPOS, DIR_N ), // 09 pre-helitakeoff helipad 1 - AMD( 56, 8, AMED_EXACTPOS, DIR_N ), // 10 pre-helitakeoff helipad 2 - AMD( 56, 24, AMED_EXACTPOS, DIR_N ), // 11 pre-helitakeoff helipad 3 - AMD( 32, 8, AMED_HELI_RAISE, DIR_N ), // 12 Takeoff Helipad1 - AMD( 48, 8, AMED_HELI_RAISE, DIR_N ), // 13 Takeoff Helipad2 - AMD( 48, 24, AMED_HELI_RAISE, DIR_N ), // 14 Takeoff Helipad3 - AMD( 84, 24, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 15 Bufferspace before helipad - AMD( 68, 24, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 16 Bufferspace before helipad - AMD( 32, 8, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 17 Get in position for Helipad1 - AMD( 48, 8, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 18 Get in position for Helipad2 - AMD( 48, 24, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_NE), // 19 Get in position for Helipad3 - AMD( 40, 8, AMED_HELI_LOWER, DIR_N ), // 20 Land at Helipad1 - AMD( 48, 8, AMED_HELI_LOWER, DIR_N ), // 21 Land at Helipad2 - AMD( 48, 24, AMED_HELI_LOWER, DIR_N ), // 22 Land at Helipad3 - AMD( 0, 22, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 23 Go to position for Hangarentrance in air - AMD( 0, 22, AMED_HELI_LOWER, DIR_N ), // 24 Land in front of hangar - AMD( 148, -8, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 25 Fly around waiting for a landing spot (south-east) - AMD( 148, 8, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 26 Fly around waiting for a landing spot (south-west) - AMD( 132, 24, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 27 Fly around waiting for a landing spot (south-west) - AMD( 100, 24, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 28 Fly around waiting for a landing spot (north-east) - AMD( 84, 8, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 29 Fly around waiting for a landing spot (south-east) - AMD( 84, -8, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 30 Fly around waiting for a landing spot (south-west) - AMD( 100, -24, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 31 Fly around waiting for a landing spot (north-west) - AMD( 132, -24, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 32 Fly around waiting for a landing spot (north-east) -}; - -/** Oilrig. */ -static const AirportMovingData _airport_moving_data_oilrig[9] = { - AMD( 31, 9, AMED_EXACTPOS, DIR_NE), // 0 - At oilrig terminal - AMD( 28, 9, AMED_HELI_RAISE, DIR_N ), // 1 - Take off (play sound) - AMD( 23, 9, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 2 - In position above landing spot helicopter - AMD( 23, 9, AMED_HELI_LOWER, DIR_N ), // 3 - Land - AMD( 28, 9, 0, DIR_N ), // 4 - Goto terminal on ground - AMD( -31, 69, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 5 - circle #1 (north-east) - AMD( -31, -49, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 6 - circle #2 (north-west) - AMD( 69, -49, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 7 - circle #3 (south-west) - AMD( 69, 9, AMED_NOSPDCLAMP | AMED_SLOWTURN, DIR_N ), // 8 - circle #4 (south) -}; - -#undef AMD - -/////////////////////////////////////////////////////////////////////// -/////**********Movement Machine on Airports*********************/////// -static const uint8_t _airport_entries_dummy[] = {0, 1, 2, 3}; -static const AirportFTAbuildup _airport_fta_dummy[] = { - { 0, TO_ALL, 0, 3}, - { 1, TO_ALL, 0, 0}, - { 2, TO_ALL, 0, 1}, - { 3, TO_ALL, 0, 2}, - { MAX_ELEMENTS, TO_ALL, 0, 0 } // end marker. DO NOT REMOVE -}; - -/* First element of terminals array tells us how many depots there are (to know size of array) - * this may be changed later when airports are moved to external file */ -static const HangarTileTable _airport_depots_country[] = { {{3, 0}, DIR_SE, 0} }; -static const uint8_t _airport_terminal_country[] = {1, 2}; -static const uint8_t _airport_entries_country[] = {16, 15, 18, 17}; -static const AirportFTAbuildup _airport_fta_country[] = { - { 0, HANGAR, NOTHING_block, 1 }, - { 1, TERMGROUP, AIRPORT_BUSY_block, 0 }, { 1, HANGAR, 0, 0 }, { 1, TERM1, TERM1_block, 2 }, { 1, TERM2, 0, 4 }, { 1, HELITAKEOFF, 0, 19 }, { 1, TO_ALL, 0, 6 }, - { 2, TERM1, TERM1_block, 1 }, - { 3, TERM2, TERM2_block, 5 }, - { 4, TERMGROUP, AIRPORT_BUSY_block, 0 }, { 4, TERM2, 0, 5 }, { 4, HANGAR, 0, 1 }, { 4, TAKEOFF, 0, 6 }, { 4, HELITAKEOFF, 0, 1 }, - { 5, TERMGROUP, AIRPORT_BUSY_block, 0 }, { 5, TERM2, TERM2_block, 3 }, { 5, TO_ALL, 0, 4 }, - { 6, 0, AIRPORT_BUSY_block, 7 }, - /* takeoff */ - { 7, TAKEOFF, AIRPORT_BUSY_block, 8 }, - { 8, STARTTAKEOFF, NOTHING_block, 9 }, - { 9, ENDTAKEOFF, NOTHING_block, 0 }, - /* landing */ - { 10, FLYING, NOTHING_block, 15 }, { 10, LANDING, 0, 11 }, { 10, HELILANDING, 0, 20 }, - { 11, LANDING, AIRPORT_BUSY_block, 12 }, - { 12, TO_ALL, AIRPORT_BUSY_block, 13 }, - { 13, ENDLANDING, AIRPORT_BUSY_block, 14 }, { 13, TERM2, 0, 5 }, { 13, TO_ALL, 0, 14 }, - { 14, TO_ALL, AIRPORT_BUSY_block, 1 }, - /* In air */ - { 15, TO_ALL, NOTHING_block, 16 }, - { 16, TO_ALL, NOTHING_block, 17 }, - { 17, TO_ALL, NOTHING_block, 18 }, - { 18, TO_ALL, NOTHING_block, 10 }, - { 19, HELITAKEOFF, NOTHING_block, 0 }, - { 20, HELILANDING, AIRPORT_BUSY_block, 21 }, - { 21, HELIENDLANDING, AIRPORT_BUSY_block, 1 }, - { MAX_ELEMENTS, TO_ALL, 0, 0 } // end marker. DO NOT REMOVE -}; - -static const HangarTileTable _airport_depots_commuter[] = { {{4, 0}, DIR_SE, 0} }; -static const uint8_t _airport_terminal_commuter[] = { 1, 3 }; -static const uint8_t _airport_entries_commuter[] = {22, 21, 24, 23}; -static const AirportFTAbuildup _airport_fta_commuter[] = { - { 0, HANGAR, NOTHING_block, 1 }, { 0, HELITAKEOFF, TAXIWAY_BUSY_block, 1 }, { 0, TO_ALL, 0, 1 }, - { 1, TERMGROUP, TAXIWAY_BUSY_block, 0 }, { 1, HANGAR, 0, 0 }, { 1, TAKEOFF, 0, 11 }, { 1, TERM1, TAXIWAY_BUSY_block, 10 }, { 1, TERM2, TAXIWAY_BUSY_block, 10 }, { 1, TERM3, TAXIWAY_BUSY_block, 10 }, { 1, HELIPAD1, TAXIWAY_BUSY_block, 10 }, { 1, HELIPAD2, TAXIWAY_BUSY_block, 10 }, { 1, HELITAKEOFF, TAXIWAY_BUSY_block, 37 }, { 1, TO_ALL, 0, 0 }, - { 2, TERMGROUP, AIRPORT_ENTRANCE_block, 2 }, { 2, HANGAR, 0, 8 }, { 2, TERM1, 0, 8 }, { 2, TERM2, 0, 8 }, { 2, TERM3, 0, 8 }, { 2, HELIPAD1, 0, 8 }, { 2, HELIPAD2, 0, 8 }, { 2, HELITAKEOFF, 0, 8 }, { 2, TO_ALL, 0, 2 }, - { 3, TERM1, TERM1_block, 8 }, { 3, HANGAR, 0, 8 }, { 3, TAKEOFF, 0, 8 }, { 3, TO_ALL, 0, 3 }, - { 4, TERM2, TERM2_block, 9 }, { 4, HANGAR, 0, 9 }, { 4, TAKEOFF, 0, 9 }, { 4, TO_ALL, 0, 4 }, - { 5, TERM3, TERM3_block, 10 }, { 5, HANGAR, 0, 10 }, { 5, TAKEOFF, 0, 10 }, { 5, TO_ALL, 0, 5 }, - { 6, HELIPAD1, HELIPAD1_block, 6 }, { 6, HANGAR, TAXIWAY_BUSY_block, 9 }, { 6, HELITAKEOFF, 0, 35 }, - { 7, HELIPAD2, HELIPAD2_block, 7 }, { 7, HANGAR, TAXIWAY_BUSY_block, 10 }, { 7, HELITAKEOFF, 0, 36 }, - { 8, TERMGROUP, TAXIWAY_BUSY_block, 8 }, { 8, TAKEOFF, TAXIWAY_BUSY_block, 9 }, { 8, HANGAR, TAXIWAY_BUSY_block, 9 }, { 8, TERM1, TERM1_block, 3 }, { 8, TO_ALL, TAXIWAY_BUSY_block, 9 }, - { 9, TERMGROUP, TAXIWAY_BUSY_block, 9 }, { 9, TAKEOFF, TAXIWAY_BUSY_block, 10 }, { 9, HANGAR, TAXIWAY_BUSY_block, 10 }, { 9, TERM2, TERM2_block, 4 }, { 9, HELIPAD1, HELIPAD1_block, 6 }, { 9, HELITAKEOFF, HELIPAD1_block, 6 }, { 9, TERM1, TAXIWAY_BUSY_block, 8 }, { 9, TO_ALL, TAXIWAY_BUSY_block, 10 }, - { 10, TERMGROUP, TAXIWAY_BUSY_block, 10 }, { 10, TERM3, TERM3_block, 5 }, { 10, HELIPAD1, 0, 9 }, { 10, HELIPAD2, HELIPAD2_block, 7 }, { 10, HELITAKEOFF, 0, 1 }, { 10, TAKEOFF, TAXIWAY_BUSY_block, 1 }, { 10, HANGAR, TAXIWAY_BUSY_block, 1 }, { 10, TO_ALL, TAXIWAY_BUSY_block, 9 }, - { 11, TO_ALL, OUT_WAY_block, 12 }, - /* takeoff */ - { 12, TAKEOFF, RUNWAY_IN_OUT_block, 13 }, - { 13, TO_ALL, RUNWAY_IN_OUT_block, 14 }, - { 14, STARTTAKEOFF, RUNWAY_IN_OUT_block, 15 }, - { 15, ENDTAKEOFF, NOTHING_block, 0 }, - /* landing */ - { 16, FLYING, NOTHING_block, 21 }, { 16, LANDING, IN_WAY_block, 17 }, { 16, HELILANDING, 0, 25 }, - { 17, LANDING, RUNWAY_IN_OUT_block, 18 }, - { 18, TO_ALL, RUNWAY_IN_OUT_block, 19 }, - { 19, TO_ALL, RUNWAY_IN_OUT_block, 20 }, - { 20, ENDLANDING, IN_WAY_block, 2 }, - /* In Air */ - { 21, TO_ALL, NOTHING_block, 22 }, - { 22, TO_ALL, NOTHING_block, 23 }, - { 23, TO_ALL, NOTHING_block, 24 }, - { 24, TO_ALL, NOTHING_block, 16 }, - /* Helicopter -- stay in air in special place as a buffer to choose from helipads */ - { 25, HELILANDING, PRE_HELIPAD_block, 26 }, - { 26, HELIENDLANDING, PRE_HELIPAD_block, 26 }, { 26, HELIPAD1, 0, 27 }, { 26, HELIPAD2, 0, 28 }, { 26, HANGAR, 0, 33 }, - { 27, TO_ALL, NOTHING_block, 29 }, // helipad1 approach - { 28, TO_ALL, NOTHING_block, 30 }, - /* landing */ - { 29, TERMGROUP, NOTHING_block, 0 }, { 29, HELIPAD1, HELIPAD1_block, 6 }, - { 30, TERMGROUP, NOTHING_block, 0 }, { 30, HELIPAD2, HELIPAD2_block, 7 }, - /* Helicopter -- takeoff */ - { 31, HELITAKEOFF, NOTHING_block, 0 }, - { 32, HELITAKEOFF, NOTHING_block, 0 }, - { 33, TO_ALL, TAXIWAY_BUSY_block, 34 }, // need to go to hangar when waiting in air - { 34, TO_ALL, TAXIWAY_BUSY_block, 1 }, - { 35, TO_ALL, HELIPAD1_block, 31 }, - { 36, TO_ALL, HELIPAD2_block, 32 }, - { 37, HELITAKEOFF, NOTHING_block, 0 }, - { MAX_ELEMENTS, TO_ALL, 0, 0 } // end marker. DO NOT REMOVE -}; - -static const HangarTileTable _airport_depots_city[] = { {{5, 0}, DIR_SE, 0} }; -static const uint8_t _airport_terminal_city[] = { 1, 3 }; -static const uint8_t _airport_entries_city[] = {26, 29, 27, 28}; -static const AirportFTAbuildup _airport_fta_city[] = { - { 0, HANGAR, NOTHING_block, 1 }, { 0, TAKEOFF, OUT_WAY_block, 1 }, { 0, TO_ALL, 0, 1 }, - { 1, TERMGROUP, TAXIWAY_BUSY_block, 0 }, { 1, HANGAR, 0, 0 }, { 1, TERM2, 0, 6 }, { 1, TERM3, 0, 6 }, { 1, TO_ALL, 0, 7 }, // for all else, go to 7 - { 2, TERM1, TERM1_block, 7 }, { 2, TAKEOFF, OUT_WAY_block, 7 }, { 2, TO_ALL, 0, 7 }, - { 3, TERM2, TERM2_block, 5 }, { 3, TAKEOFF, OUT_WAY_block, 6 }, { 3, TO_ALL, 0, 6 }, - { 4, TERM3, TERM3_block, 5 }, { 4, TAKEOFF, OUT_WAY_block, 5 }, { 4, TO_ALL, 0, 5 }, - { 5, TERMGROUP, TAXIWAY_BUSY_block, 0 }, { 5, TERM2, TERM2_block, 3 }, { 5, TERM3, TERM3_block, 4 }, { 5, TO_ALL, 0, 6 }, - { 6, TERMGROUP, TAXIWAY_BUSY_block, 0 }, { 6, TERM2, TERM2_block, 3 }, { 6, TERM3, 0, 5 }, { 6, HANGAR, 0, 1 }, { 6, TO_ALL, 0, 7 }, - { 7, TERMGROUP, TAXIWAY_BUSY_block, 0 }, { 7, TERM1, TERM1_block, 2 }, { 7, TAKEOFF, OUT_WAY_block, 8 }, { 7, HELITAKEOFF, 0, 22 }, { 7, HANGAR, 0, 1 }, { 7, TO_ALL, 0, 6 }, - { 8, 0, OUT_WAY_block, 9 }, - { 9, 0, RUNWAY_IN_OUT_block, 10 }, - /* takeoff */ - { 10, TAKEOFF, RUNWAY_IN_OUT_block, 11 }, - { 11, STARTTAKEOFF, NOTHING_block, 12 }, - { 12, ENDTAKEOFF, NOTHING_block, 0 }, - /* landing */ - { 13, FLYING, NOTHING_block, 18 }, { 13, LANDING, 0, 14 }, { 13, HELILANDING, 0, 23 }, - { 14, LANDING, RUNWAY_IN_OUT_block, 15 }, - { 15, TO_ALL, RUNWAY_IN_OUT_block, 17 }, - { 16, TO_ALL, RUNWAY_IN_OUT_block, 17 }, // not used, left for compatibility - { 17, ENDLANDING, IN_WAY_block, 7 }, - /* In Air */ - { 18, TO_ALL, NOTHING_block, 25 }, - { 19, TO_ALL, NOTHING_block, 20 }, - { 20, TO_ALL, NOTHING_block, 21 }, - { 21, TO_ALL, NOTHING_block, 13 }, - /* helicopter */ - { 22, HELITAKEOFF, NOTHING_block, 0 }, - { 23, HELILANDING, IN_WAY_block, 24 }, - { 24, HELIENDLANDING, IN_WAY_block, 17 }, - { 25, TO_ALL, NOTHING_block, 20}, - { 26, TO_ALL, NOTHING_block, 19}, - { 27, TO_ALL, NOTHING_block, 28}, - { 28, TO_ALL, NOTHING_block, 19}, - { 29, TO_ALL, NOTHING_block, 26}, - { MAX_ELEMENTS, TO_ALL, 0, 0 } // end marker. DO NOT REMOVE -}; - -static const HangarTileTable _airport_depots_metropolitan[] = { {{5, 0}, DIR_SE, 0} }; -static const uint8_t _airport_terminal_metropolitan[] = { 1, 3 }; -static const uint8_t _airport_entries_metropolitan[] = {20, 19, 22, 21}; -static const AirportFTAbuildup _airport_fta_metropolitan[] = { - { 0, HANGAR, NOTHING_block, 1 }, - { 1, TERMGROUP, TAXIWAY_BUSY_block, 0 }, { 1, HANGAR, 0, 0 }, { 1, TERM2, 0, 6 }, { 1, TERM3, 0, 6 }, { 1, TO_ALL, 0, 7 }, // for all else, go to 7 - { 2, TERM1, TERM1_block, 7 }, - { 3, TERM2, TERM2_block, 6 }, - { 4, TERM3, TERM3_block, 5 }, - { 5, TERMGROUP, TAXIWAY_BUSY_block, 0 }, { 5, TERM2, TERM2_block, 3 }, { 5, TERM3, TERM3_block, 4 }, { 5, TO_ALL, 0, 6 }, - { 6, TERMGROUP, TAXIWAY_BUSY_block, 0 }, { 6, TERM2, TERM2_block, 3 }, { 6, TERM3, 0, 5 }, { 6, HANGAR, 0, 1 }, { 6, TO_ALL, 0, 7 }, - { 7, TERMGROUP, TAXIWAY_BUSY_block, 0 }, { 7, TERM1, TERM1_block, 2 }, { 7, TAKEOFF, 0, 8 }, { 7, HELITAKEOFF, 0, 23 }, { 7, HANGAR, 0, 1 }, { 7, TO_ALL, 0, 6 }, - { 8, 0, OUT_WAY_block, 9 }, - { 9, 0, RUNWAY_OUT_block, 10 }, - /* takeoff */ - { 10, TAKEOFF, RUNWAY_OUT_block, 11 }, - { 11, STARTTAKEOFF, NOTHING_block, 12 }, - { 12, ENDTAKEOFF, NOTHING_block, 0 }, - /* landing */ - { 13, FLYING, NOTHING_block, 19 }, { 13, LANDING, 0, 14 }, { 13, HELILANDING, 0, 25 }, - { 14, LANDING, RUNWAY_IN_block, 15 }, - { 15, TO_ALL, RUNWAY_IN_block, 16 }, - { 16, TERMGROUP, RUNWAY_IN_block, 0 }, { 16, ENDLANDING, IN_WAY_block, 17 }, - { 17, TERMGROUP, RUNWAY_OUT_block, 0 }, { 17, ENDLANDING, IN_WAY_block, 18 }, - { 18, ENDLANDING, IN_WAY_block, 27 }, - /* In Air */ - { 19, TO_ALL, NOTHING_block, 20 }, - { 20, TO_ALL, NOTHING_block, 21 }, - { 21, TO_ALL, NOTHING_block, 22 }, - { 22, TO_ALL, NOTHING_block, 13 }, - /* helicopter */ - { 23, TO_ALL, NOTHING_block, 24 }, - { 24, HELITAKEOFF, NOTHING_block, 0 }, - { 25, HELILANDING, IN_WAY_block, 26 }, - { 26, HELIENDLANDING, IN_WAY_block, 18 }, - { 27, TERMGROUP, TAXIWAY_BUSY_block, 27 }, { 27, TERM1, TERM1_block, 2 }, { 27, TO_ALL, 0, 7 }, - { MAX_ELEMENTS, TO_ALL, 0, 0 } // end marker. DO NOT REMOVE -}; - -static const HangarTileTable _airport_depots_international[] = { {{0, 3}, DIR_SE, 0}, {{6, 1}, DIR_SE, 1} }; -static const uint8_t _airport_terminal_international[] = { 2, 3, 3 }; -static const uint8_t _airport_entries_international[] = { 38, 37, 40, 39 }; -static const AirportFTAbuildup _airport_fta_international[] = { - { 0, HANGAR, NOTHING_block, 2 }, { 0, TERMGROUP, TERM_GROUP1_block, 0 }, { 0, TERMGROUP, TERM_GROUP2_ENTER1_block, 1 }, { 0, HELITAKEOFF, AIRPORT_ENTRANCE_block, 2 }, { 0, TO_ALL, 0, 2 }, - { 1, HANGAR, NOTHING_block, 3 }, { 1, TERMGROUP, HANGAR2_AREA_block, 1 }, { 1, HELITAKEOFF, HANGAR2_AREA_block, 3 }, { 1, TO_ALL, 0, 3 }, - { 2, TERMGROUP, AIRPORT_ENTRANCE_block, 0 }, { 2, HANGAR, 0, 0 }, { 2, TERM4, 0, 12 }, { 2, TERM5, 0, 12 }, { 2, TERM6, 0, 12 }, { 2, HELIPAD1, 0, 12 }, { 2, HELIPAD2, 0, 12 }, { 2, HELITAKEOFF, 0, 51 }, { 2, TO_ALL, 0, 23 }, - { 3, TERMGROUP, HANGAR2_AREA_block, 0 }, { 3, HANGAR, 0, 1 }, { 3, HELITAKEOFF, 0, 52 }, { 3, TO_ALL, 0, 18 }, - { 4, TERM1, TERM1_block, 23 }, { 4, HANGAR, AIRPORT_ENTRANCE_block, 23 }, { 4, TO_ALL, 0, 23 }, - { 5, TERM2, TERM2_block, 24 }, { 5, HANGAR, AIRPORT_ENTRANCE_block, 24 }, { 5, TO_ALL, 0, 24 }, - { 6, TERM3, TERM3_block, 25 }, { 6, HANGAR, AIRPORT_ENTRANCE_block, 25 }, { 6, TO_ALL, 0, 25 }, - { 7, TERM4, TERM4_block, 16 }, { 7, HANGAR, HANGAR2_AREA_block, 16 }, { 7, TO_ALL, 0, 16 }, - { 8, TERM5, TERM5_block, 17 }, { 8, HANGAR, HANGAR2_AREA_block, 17 }, { 8, TO_ALL, 0, 17 }, - { 9, TERM6, TERM6_block, 18 }, { 9, HANGAR, HANGAR2_AREA_block, 18 }, { 9, TO_ALL, 0, 18 }, - { 10, HELIPAD1, HELIPAD1_block, 10 }, { 10, HANGAR, HANGAR2_AREA_block, 16 }, { 10, HELITAKEOFF, 0, 47 }, - { 11, HELIPAD2, HELIPAD2_block, 11 }, { 11, HANGAR, HANGAR2_AREA_block, 17 }, { 11, HELITAKEOFF, 0, 48 }, - { 12, TO_ALL, TERM_GROUP2_ENTER1_block, 13 }, - { 13, TO_ALL, TERM_GROUP2_ENTER1_block, 14 }, - { 14, TO_ALL, TERM_GROUP2_ENTER2_block, 15 }, - { 15, TO_ALL, TERM_GROUP2_ENTER2_block, 16 }, - { 16, TERMGROUP, TERM_GROUP2_block, 0 }, { 16, TERM4, TERM4_block, 7 }, { 16, HELIPAD1, HELIPAD1_block, 10 }, { 16, HELITAKEOFF, HELIPAD1_block, 10 }, { 16, TO_ALL, 0, 17 }, - { 17, TERMGROUP, TERM_GROUP2_block, 0 }, { 17, TERM5, TERM5_block, 8 }, { 17, TERM4, 0, 16 }, { 17, HELIPAD1, 0, 16 }, { 17, HELIPAD2, HELIPAD2_block, 11 }, { 17, HELITAKEOFF, HELIPAD2_block, 11 }, { 17, TO_ALL, 0, 18 }, - { 18, TERMGROUP, TERM_GROUP2_block, 0 }, { 18, TERM6, TERM6_block, 9 }, { 18, TAKEOFF, 0, 19 }, { 18, HANGAR, HANGAR2_AREA_block, 3 }, { 18, TO_ALL, 0, 17 }, - { 19, TO_ALL, TERM_GROUP2_EXIT1_block, 20 }, - { 20, TO_ALL, TERM_GROUP2_EXIT1_block, 21 }, - { 21, TO_ALL, TERM_GROUP2_EXIT2_block, 22 }, - { 22, TO_ALL, TERM_GROUP2_EXIT2_block, 26 }, - { 23, TERMGROUP, TERM_GROUP1_block, 0 }, { 23, TERM1, TERM1_block, 4 }, { 23, HANGAR, AIRPORT_ENTRANCE_block, 2 }, { 23, TO_ALL, 0, 24 }, - { 24, TERMGROUP, TERM_GROUP1_block, 0 }, { 24, TERM2, TERM2_block, 5 }, { 24, TERM1, 0, 23 }, { 24, HANGAR, 0, 23 }, { 24, TO_ALL, 0, 25 }, - { 25, TERMGROUP, TERM_GROUP1_block, 0 }, { 25, TERM3, TERM3_block, 6 }, { 25, TAKEOFF, 0, 26 }, { 25, TO_ALL, 0, 24 }, - { 26, TERMGROUP, TAXIWAY_BUSY_block, 0 }, { 26, TAKEOFF, 0, 27 }, { 26, TO_ALL, 0, 25 }, - { 27, TO_ALL, OUT_WAY_block, 28 }, - /* takeoff */ - { 28, TAKEOFF, OUT_WAY_block, 29 }, - { 29, TO_ALL, RUNWAY_OUT_block, 30 }, - { 30, STARTTAKEOFF, NOTHING_block, 31 }, - { 31, ENDTAKEOFF, NOTHING_block, 0 }, - /* landing */ - { 32, FLYING, NOTHING_block, 37 }, { 32, LANDING, 0, 33 }, { 32, HELILANDING, 0, 41 }, - { 33, LANDING, RUNWAY_IN_block, 34 }, - { 34, TO_ALL, RUNWAY_IN_block, 35 }, - { 35, TO_ALL, RUNWAY_IN_block, 36 }, - { 36, ENDLANDING, IN_WAY_block, 36 }, { 36, TERMGROUP, TERM_GROUP1_block, 0 }, { 36, TERMGROUP, TERM_GROUP2_ENTER1_block, 1 }, { 36, TERM4, 0, 12 }, { 36, TERM5, 0, 12 }, { 36, TERM6, 0, 12 }, { 36, TO_ALL, 0, 2 }, - /* In Air */ - { 37, TO_ALL, NOTHING_block, 38 }, - { 38, TO_ALL, NOTHING_block, 39 }, - { 39, TO_ALL, NOTHING_block, 40 }, - { 40, TO_ALL, NOTHING_block, 32 }, - /* Helicopter -- stay in air in special place as a buffer to choose from helipads */ - { 41, HELILANDING, PRE_HELIPAD_block, 42 }, - { 42, HELIENDLANDING, PRE_HELIPAD_block, 42 }, { 42, HELIPAD1, 0, 43 }, { 42, HELIPAD2, 0, 44 }, { 42, HANGAR, 0, 49 }, - { 43, TO_ALL, NOTHING_block, 45 }, - { 44, TO_ALL, NOTHING_block, 46 }, - /* landing */ - { 45, TERMGROUP, NOTHING_block, 0 }, { 45, HELIPAD1, HELIPAD1_block, 10 }, - { 46, TERMGROUP, NOTHING_block, 0 }, { 46, HELIPAD2, HELIPAD2_block, 11 }, - /* Helicopter -- takeoff */ - { 47, HELITAKEOFF, NOTHING_block, 0 }, - { 48, HELITAKEOFF, NOTHING_block, 0 }, - { 49, TO_ALL, HANGAR2_AREA_block, 50 }, // need to go to hangar when waiting in air - { 50, TO_ALL, HANGAR2_AREA_block, 3 }, - { 51, HELITAKEOFF, NOTHING_block, 0 }, - { 52, HELITAKEOFF, NOTHING_block, 0 }, - { MAX_ELEMENTS, TO_ALL, 0, 0 } // end marker. DO NOT REMOVE -}; - -/* intercontinental */ -static const HangarTileTable _airport_depots_intercontinental[] = { {{0, 5}, DIR_SE, 0}, {{8, 4}, DIR_SE, 1} }; -static const uint8_t _airport_terminal_intercontinental[] = { 2, 4, 4 }; -static const uint8_t _airport_entries_intercontinental[] = { 44, 43, 46, 45 }; -static const AirportFTAbuildup _airport_fta_intercontinental[] = { - { 0, HANGAR, NOTHING_block, 2 }, { 0, TERMGROUP, HANGAR1_AREA_block | TERM_GROUP1_block, 0 }, { 0, TERMGROUP, HANGAR1_AREA_block | TERM_GROUP1_block, 1 }, { 0, TAKEOFF, HANGAR1_AREA_block | TERM_GROUP1_block, 2 }, { 0, TO_ALL, 0, 2 }, - { 1, HANGAR, NOTHING_block, 3 }, { 1, TERMGROUP, HANGAR2_AREA_block, 1 }, { 1, TERMGROUP, HANGAR2_AREA_block, 0 }, { 1, TO_ALL, 0, 3 }, - { 2, TERMGROUP, HANGAR1_AREA_block, 0 }, { 2, TERMGROUP, TERM_GROUP1_block, 0 }, { 2, TERMGROUP, TERM_GROUP1_block, 1 }, { 2, HANGAR, 0, 0 }, { 2, TAKEOFF, TERM_GROUP1_block, 27 }, { 2, TERM5, 0, 26 }, { 2, TERM6, 0, 26 }, { 2, TERM7, 0, 26 }, { 2, TERM8, 0, 26 }, { 2, HELIPAD1, 0, 26 }, { 2, HELIPAD2, 0, 26 }, { 2, HELITAKEOFF, 0, 74 }, { 2, TO_ALL, 0, 27 }, - { 3, TERMGROUP, HANGAR2_AREA_block, 0 }, { 3, HANGAR, 0, 1 }, { 3, HELITAKEOFF, 0, 75 }, {3, TAKEOFF, 0, 59}, { 3, TO_ALL, 0, 20 }, - { 4, TERM1, TERM1_block, 26 }, { 4, HANGAR, HANGAR1_AREA_block | TERM_GROUP1_block, 26 }, { 4, TO_ALL, 0, 26 }, - { 5, TERM2, TERM2_block, 27 }, { 5, HANGAR, HANGAR1_AREA_block | TERM_GROUP1_block, 27 }, { 5, TO_ALL, 0, 27 }, - { 6, TERM3, TERM3_block, 28 }, { 6, HANGAR, HANGAR1_AREA_block | TERM_GROUP1_block, 28 }, { 6, TO_ALL, 0, 28 }, - { 7, TERM4, TERM4_block, 29 }, { 7, HANGAR, HANGAR1_AREA_block | TERM_GROUP1_block, 29 }, { 7, TO_ALL, 0, 29 }, - { 8, TERM5, TERM5_block, 18 }, { 8, HANGAR, HANGAR2_AREA_block, 18 }, { 8, TO_ALL, 0, 18 }, - { 9, TERM6, TERM6_block, 19 }, { 9, HANGAR, HANGAR2_AREA_block, 19 }, { 9, TO_ALL, 0, 19 }, - { 10, TERM7, TERM7_block, 20 }, { 10, HANGAR, HANGAR2_AREA_block, 20 }, { 10, TO_ALL, 0, 20 }, - { 11, TERM8, TERM8_block, 21 }, { 11, HANGAR, HANGAR2_AREA_block, 21 }, { 11, TO_ALL, 0, 21 }, - { 12, HELIPAD1, HELIPAD1_block, 12 }, { 12, HANGAR, 0, 70 }, { 12, HELITAKEOFF, 0, 72 }, - { 13, HELIPAD2, HELIPAD2_block, 13 }, { 13, HANGAR, 0, 71 }, { 13, HELITAKEOFF, 0, 73 }, - { 14, TO_ALL, TERM_GROUP2_ENTER1_block, 15 }, - { 15, TO_ALL, TERM_GROUP2_ENTER1_block, 16 }, - { 16, TO_ALL, TERM_GROUP2_ENTER2_block, 17 }, - { 17, TO_ALL, TERM_GROUP2_ENTER2_block, 18 }, - { 18, TERMGROUP, TERM_GROUP2_block, 0 }, { 18, TERM5, TERM5_block, 8 }, { 18, TAKEOFF, 0, 19 }, { 18, HELITAKEOFF, HELIPAD1_block, 19 }, { 18, TO_ALL, TERM_GROUP2_EXIT1_block, 19 }, - { 19, TERMGROUP, TERM_GROUP2_block, 0 }, { 19, TERM6, TERM6_block, 9 }, { 19, TERM5, 0, 18 }, { 19, TAKEOFF, 0, 57 }, { 19, HELITAKEOFF, HELIPAD1_block, 20 }, { 19, TO_ALL, TERM_GROUP2_EXIT1_block, 20 }, // add exit to runway out 2 - { 20, TERMGROUP, TERM_GROUP2_block, 0 }, { 20, TERM7, TERM7_block, 10 }, { 20, TERM5, 0, 19 }, { 20, TERM6, 0, 19 }, { 20, HANGAR, HANGAR2_AREA_block, 3 }, { 20, TAKEOFF, 0, 19 }, { 20, TO_ALL, TERM_GROUP2_EXIT1_block, 21 }, - { 21, TERMGROUP, TERM_GROUP2_block, 0 }, { 21, TERM8, TERM8_block, 11 }, { 21, HANGAR, HANGAR2_AREA_block, 20 }, { 21, TERM5, 0, 20 }, { 21, TERM6, 0, 20 }, { 21, TERM7, 0, 20 }, { 21, TAKEOFF, 0, 20 }, { 21, TO_ALL, TERM_GROUP2_EXIT1_block, 22 }, - { 22, TERMGROUP, TERM_GROUP2_block, 0 }, { 22, HANGAR, 0, 21 }, { 22, TERM5, 0, 21 }, { 22, TERM6, 0, 21 }, { 22, TERM7, 0, 21 }, { 22, TERM8, 0, 21 }, { 22, TAKEOFF, 0, 21 }, { 22, TO_ALL, 0, 23 }, - { 23, TO_ALL, TERM_GROUP2_EXIT1_block, 70 }, - { 24, TO_ALL, TERM_GROUP2_EXIT2_block, 25 }, - { 25, TERMGROUP, TERM_GROUP2_EXIT2_block, 0 }, { 25, HANGAR, HANGAR1_AREA_block | TERM_GROUP1_block, 29 }, { 25, TO_ALL, 0, 29 }, - { 26, TERMGROUP, TERM_GROUP1_block, 0 }, { 26, TERM1, TERM1_block, 4 }, { 26, HANGAR, HANGAR1_AREA_block, 27 }, { 26, TERM5, TERM_GROUP2_ENTER1_block, 14 }, { 26, TERM6, TERM_GROUP2_ENTER1_block, 14 }, { 26, TERM7, TERM_GROUP2_ENTER1_block, 14 }, { 26, TERM8, TERM_GROUP2_ENTER1_block, 14 }, { 26, HELIPAD1, TERM_GROUP2_ENTER1_block, 14 }, { 26, HELIPAD2, TERM_GROUP2_ENTER1_block, 14 }, { 26, HELITAKEOFF, TERM_GROUP2_ENTER1_block, 14 }, { 26, TO_ALL, 0, 27 }, - { 27, TERMGROUP, TERM_GROUP1_block, 0 }, { 27, TERM2, TERM2_block, 5 }, { 27, HANGAR, HANGAR1_AREA_block, 2 }, { 27, TERM1, 0, 26 }, { 27, TERM5, 0, 26 }, { 27, TERM6, 0, 26 }, { 27, TERM7, 0, 26 }, { 27, TERM8, 0, 26 }, { 27, HELIPAD1, 0, 14 }, { 27, HELIPAD2, 0, 14 }, { 27, TO_ALL, 0, 28 }, - { 28, TERMGROUP, TERM_GROUP1_block, 0 }, { 28, TERM3, TERM3_block, 6 }, { 28, HANGAR, HANGAR1_AREA_block, 27 }, { 28, TERM1, 0, 27 }, { 28, TERM2, 0, 27 }, { 28, TERM4, 0, 29 }, { 28, TERM5, 0, 14 }, { 28, TERM6, 0, 14 }, { 28, TERM7, 0, 14 }, { 28, TERM8, 0, 14 }, { 28, HELIPAD1, 0, 14 }, { 28, HELIPAD2, 0, 14 }, { 28, TO_ALL, 0, 29 }, - { 29, TERMGROUP, TERM_GROUP1_block, 0 }, { 29, TERM4, TERM4_block, 7 }, { 29, HANGAR, HANGAR1_AREA_block, 27 }, { 29, TAKEOFF, 0, 30 }, { 29, TO_ALL, 0, 28 }, - { 30, TO_ALL, OUT_WAY_block2, 31 }, - { 31, TO_ALL, OUT_WAY_block, 32 }, - /* takeoff */ - { 32, TAKEOFF, RUNWAY_OUT_block, 33 }, - { 33, TO_ALL, RUNWAY_OUT_block, 34 }, - { 34, STARTTAKEOFF, NOTHING_block, 35 }, - { 35, ENDTAKEOFF, NOTHING_block, 0 }, - /* landing */ - { 36, TO_ALL, 0, 0 }, - { 37, LANDING, RUNWAY_IN_block, 38 }, - { 38, TO_ALL, RUNWAY_IN_block, 39 }, - { 39, TO_ALL, RUNWAY_IN_block, 40 }, - { 40, ENDLANDING, RUNWAY_IN_block, 41 }, - { 41, TO_ALL, IN_WAY_block, 42 }, - { 42, TERMGROUP, IN_WAY_block, 0 }, { 42, TERMGROUP, TERM_GROUP1_block, 0 }, { 42, TERMGROUP, TERM_GROUP1_block, 1 }, { 42, HANGAR, 0, 2 }, { 42, TO_ALL, 0, 26 }, - /* In Air */ - { 43, TO_ALL, 0, 44 }, - { 44, FLYING, 0, 45 }, { 44, HELILANDING, 0, 47 }, { 44, LANDING, 0, 69 }, { 44, TO_ALL, 0, 45 }, - { 45, TO_ALL, 0, 46 }, - { 46, FLYING, 0, 43 }, { 46, LANDING, 0, 76 }, { 46, TO_ALL, 0, 43 }, - /* Helicopter -- stay in air in special place as a buffer to choose from helipads */ - { 47, HELILANDING, PRE_HELIPAD_block, 48 }, - { 48, HELIENDLANDING, PRE_HELIPAD_block, 48 }, { 48, HELIPAD1, 0, 49 }, { 48, HELIPAD2, 0, 50 }, { 48, HANGAR, 0, 55 }, - { 49, TO_ALL, NOTHING_block, 51 }, - { 50, TO_ALL, NOTHING_block, 52 }, - /* landing */ - { 51, TERMGROUP, NOTHING_block, 0 }, { 51, HELIPAD1, HELIPAD1_block, 12 }, { 51, HANGAR, 0, 55 }, { 51, TO_ALL, 0, 12 }, - { 52, TERMGROUP, NOTHING_block, 0 }, { 52, HELIPAD2, HELIPAD2_block, 13 }, { 52, HANGAR, 0, 55 }, { 52, TO_ALL, 0, 13 }, - /* Helicopter -- takeoff */ - { 53, HELITAKEOFF, NOTHING_block, 0 }, - { 54, HELITAKEOFF, NOTHING_block, 0 }, - { 55, TO_ALL, HANGAR2_AREA_block, 56 }, // need to go to hangar when waiting in air - { 56, TO_ALL, HANGAR2_AREA_block, 3 }, - /* runway 2 out support */ - { 57, TERMGROUP, OUT_WAY2_block, 0 }, { 57, TAKEOFF, 0, 58 }, { 57, TO_ALL, 0, 58 }, - { 58, TO_ALL, OUT_WAY2_block, 59 }, - { 59, TAKEOFF, RUNWAY_OUT2_block, 60 }, // takeoff - { 60, TO_ALL, RUNWAY_OUT2_block, 61 }, - { 61, STARTTAKEOFF, NOTHING_block, 62 }, - { 62, ENDTAKEOFF, NOTHING_block, 0 }, - /* runway 2 in support */ - { 63, LANDING, RUNWAY_IN2_block, 64 }, - { 64, TO_ALL, RUNWAY_IN2_block, 65 }, - { 65, TO_ALL, RUNWAY_IN2_block, 66 }, - { 66, ENDLANDING, RUNWAY_IN2_block, 0 }, { 66, TERMGROUP, 0, 1 }, { 66, TERMGROUP, 0, 0 }, { 66, TO_ALL, 0, 67 }, - { 67, TO_ALL, IN_WAY2_block, 68 }, - { 68, TERMGROUP, IN_WAY2_block, 0 }, { 68, TERMGROUP, TERM_GROUP2_block, 1 }, { 68, TERMGROUP, TERM_GROUP1_block, 0 }, { 68, HANGAR, HANGAR2_AREA_block, 22 }, { 68, TO_ALL, 0, 22 }, - { 69, TERMGROUP, RUNWAY_IN2_block, 0 }, { 69, TO_ALL, RUNWAY_IN2_block, 63 }, - { 70, TERMGROUP, TERM_GROUP2_EXIT1_block, 0 }, { 70, HELIPAD1, HELIPAD1_block, 12 }, { 70, HELITAKEOFF, HELIPAD1_block, 12 }, { 70, TO_ALL, 0, 71 }, - { 71, TERMGROUP, TERM_GROUP2_EXIT1_block, 0 }, { 71, HELIPAD2, HELIPAD2_block, 13 }, { 71, HELITAKEOFF, HELIPAD1_block, 12 }, { 71, TO_ALL, 0, 24 }, - { 72, TO_ALL, HELIPAD1_block, 53 }, - { 73, TO_ALL, HELIPAD2_block, 54 }, - { 74, HELITAKEOFF, NOTHING_block, 0 }, - { 75, HELITAKEOFF, NOTHING_block, 0 }, - { 76, TERMGROUP, RUNWAY_IN_block, 0 }, { 76, TO_ALL, RUNWAY_IN_block, 37 }, - { MAX_ELEMENTS, TO_ALL, 0, 0 } // end marker. DO NOT REMOVE -}; - - -/* heliports, oilrigs don't have depots */ -static const uint8_t _airport_entries_heliport[] = { 7, 7, 7, 7 }; -static const AirportFTAbuildup _airport_fta_heliport[] = { - { 0, HELIPAD1, HELIPAD1_block, 1 }, - { 1, HELITAKEOFF, NOTHING_block, 0 }, // takeoff - { 2, TERMGROUP, AIRPORT_BUSY_block, 0 }, { 2, HELILANDING, 0, 3 }, { 2, HELITAKEOFF, 0, 1 }, - { 3, HELILANDING, AIRPORT_BUSY_block, 4 }, - { 4, HELIENDLANDING, AIRPORT_BUSY_block, 4 }, { 4, HELIPAD1, HELIPAD1_block, 0 }, { 4, HELITAKEOFF, 0, 2 }, - /* In Air */ - { 5, TO_ALL, NOTHING_block, 6 }, - { 6, TO_ALL, NOTHING_block, 7 }, - { 7, TO_ALL, NOTHING_block, 8 }, - { 8, FLYING, NOTHING_block, 5 }, { 8, HELILANDING, HELIPAD1_block, 2 }, // landing - { MAX_ELEMENTS, TO_ALL, 0, 0 } // end marker. DO NOT REMOVE -}; -#define _airport_entries_oilrig _airport_entries_heliport -#define _airport_fta_oilrig _airport_fta_heliport - -/* helidepots */ -static const HangarTileTable _airport_depots_helidepot[] = { {{1, 0}, DIR_SE, 0} }; -static const uint8_t _airport_entries_helidepot[] = { 4, 4, 4, 4 }; -static const AirportFTAbuildup _airport_fta_helidepot[] = { - { 0, HANGAR, NOTHING_block, 1 }, - { 1, TERMGROUP, HANGAR2_AREA_block, 0 }, { 1, HANGAR, 0, 0 }, { 1, HELIPAD1, HELIPAD1_block, 14 }, { 1, HELITAKEOFF, 0, 15 }, { 1, TO_ALL, 0, 0 }, - { 2, FLYING, NOTHING_block, 3 }, { 2, HELILANDING, PRE_HELIPAD_block, 7 }, { 2, HANGAR, 0, 12 }, { 2, HELITAKEOFF, NOTHING_block, 16 }, - /* In Air */ - { 3, 0, NOTHING_block, 4 }, - { 4, 0, NOTHING_block, 5 }, - { 5, 0, NOTHING_block, 6 }, - { 6, 0, NOTHING_block, 2 }, - /* Helicopter -- stay in air in special place as a buffer to choose from helipads */ - { 7, HELILANDING, PRE_HELIPAD_block, 8 }, - { 8, HELIENDLANDING, PRE_HELIPAD_block, 8 }, { 8, HELIPAD1, 0, 9 }, { 8, HANGAR, 0, 12 }, { 8, TO_ALL, 0, 2 }, - { 9, 0, NOTHING_block, 10 }, - /* landing */ - { 10, TERMGROUP, NOTHING_block, 10 }, { 10, HELIPAD1, HELIPAD1_block, 14 }, { 10, HANGAR, 0, 1 }, { 10, TO_ALL, 0, 14 }, - /* Helicopter -- takeoff */ - { 11, HELITAKEOFF, NOTHING_block, 0 }, - { 12, TO_ALL, HANGAR2_AREA_block, 13 }, // need to go to hangar when waiting in air - { 13, TO_ALL, HANGAR2_AREA_block, 1 }, - { 14, HELIPAD1, HELIPAD1_block, 14 }, { 14, HANGAR, 0, 1 }, { 14, HELITAKEOFF, 0, 17 }, - { 15, HELITAKEOFF, NOTHING_block, 0 }, // takeoff outside depot - { 16, HELITAKEOFF, 0, 14 }, - { 17, TO_ALL, NOTHING_block, 11 }, - { MAX_ELEMENTS, TO_ALL, 0, 0 } // end marker. DO NOT REMOVE -}; - -/* helistation */ -static const HangarTileTable _airport_depots_helistation[] = { {{0, 0}, DIR_SE, 0} }; -static const uint8_t _airport_entries_helistation[] = { 25, 25, 25, 25 }; -static const AirportFTAbuildup _airport_fta_helistation[] = { - { 0, HANGAR, NOTHING_block, 8 }, { 0, HELIPAD1, 0, 1 }, { 0, HELIPAD2, 0, 1 }, { 0, HELIPAD3, 0, 1 }, { 0, HELITAKEOFF, 0, 1 }, { 0, TO_ALL, 0, 0 }, - { 1, TERMGROUP, HANGAR2_AREA_block, 0 }, { 1, HANGAR, 0, 0 }, { 1, HELITAKEOFF, 0, 3 }, { 1, TO_ALL, 0, 4 }, - /* landing */ - { 2, FLYING, NOTHING_block, 28 }, { 2, HELILANDING, 0, 15 }, { 2, TO_ALL, 0, 28 }, - /* helicopter side */ - { 3, HELITAKEOFF, NOTHING_block, 0 }, // helitakeoff outside hangar2 - { 4, TERMGROUP, TAXIWAY_BUSY_block, 0 }, { 4, HANGAR, HANGAR2_AREA_block, 1 }, { 4, HELITAKEOFF, 0, 1 }, { 4, TO_ALL, 0, 5 }, - { 5, TERMGROUP, TAXIWAY_BUSY_block, 0 }, { 5, HELIPAD1, HELIPAD1_block, 6 }, { 5, HELIPAD2, HELIPAD2_block, 7 }, { 5, HELIPAD3, HELIPAD3_block, 8 }, { 5, TO_ALL, 0, 4 }, - { 6, HELIPAD1, HELIPAD1_block, 5 }, { 6, HANGAR, HANGAR2_AREA_block, 5 }, { 6, HELITAKEOFF, 0, 9 }, { 6, TO_ALL, 0, 6 }, - { 7, HELIPAD2, HELIPAD2_block, 5 }, { 7, HANGAR, HANGAR2_AREA_block, 5 }, { 7, HELITAKEOFF, 0, 10 }, { 7, TO_ALL, 0, 7 }, - { 8, HELIPAD3, HELIPAD3_block, 5 }, { 8, HANGAR, HANGAR2_AREA_block, 5 }, { 8, HELITAKEOFF, 0, 11 }, { 8, TO_ALL, 0, 8 }, - { 9, 0, HELIPAD1_block, 12 }, - { 10, TO_ALL, HELIPAD2_block, 13 }, - { 11, TO_ALL, HELIPAD3_block, 14 }, - { 12, HELITAKEOFF, NOTHING_block, 0 }, - { 13, HELITAKEOFF, NOTHING_block, 0 }, - { 14, HELITAKEOFF, NOTHING_block, 0 }, - /* heli - in flight moves */ - { 15, HELILANDING, PRE_HELIPAD_block, 16 }, - { 16, HELIENDLANDING, PRE_HELIPAD_block, 16 }, { 16, HELIPAD1, 0, 17 }, { 16, HELIPAD2, 0, 18 }, { 16, HELIPAD3, 0, 19 }, { 16, HANGAR, 0, 23 }, - { 17, TO_ALL, NOTHING_block, 20 }, - { 18, TO_ALL, NOTHING_block, 21 }, - { 19, TO_ALL, NOTHING_block, 22 }, - /* heli landing */ - { 20, TERMGROUP, NOTHING_block, 0 }, { 20, HELIPAD1, HELIPAD1_block, 6 }, { 20, HANGAR, 0, 23 }, { 20, TO_ALL, 0, 6 }, - { 21, TERMGROUP, NOTHING_block, 0 }, { 21, HELIPAD2, HELIPAD2_block, 7 }, { 21, HANGAR, 0, 23 }, { 21, TO_ALL, 0, 7 }, - { 22, TERMGROUP, NOTHING_block, 0 }, { 22, HELIPAD3, HELIPAD3_block, 8 }, { 22, HANGAR, 0, 23 }, { 22, TO_ALL, 0, 8 }, - { 23, TO_ALL, HANGAR2_AREA_block, 24 }, // need to go to helihangar when waiting in air - { 24, TO_ALL, HANGAR2_AREA_block, 1 }, - { 25, TO_ALL, NOTHING_block, 26 }, - { 26, TO_ALL, NOTHING_block, 27 }, - { 27, TO_ALL, NOTHING_block, 2 }, - { 28, TO_ALL, NOTHING_block, 29 }, - { 29, TO_ALL, NOTHING_block, 30 }, - { 30, TO_ALL, NOTHING_block, 31 }, - { 31, TO_ALL, NOTHING_block, 32 }, - { 32, TO_ALL, NOTHING_block, 25 }, - { MAX_ELEMENTS, TO_ALL, 0, 0 } // end marker. DO NOT REMOVE -}; - -#endif /* AIRPORT_MOVEMENT_H */ diff --git a/src/table/airporttile_ids.h b/src/table/airporttile_ids.h index 73eafc8595272..79f405f932b66 100644 --- a/src/table/airporttile_ids.h +++ b/src/table/airporttile_ids.h @@ -10,7 +10,7 @@ #ifndef AIRPORTTILE_IDS_H #define AIRPORTTILE_IDS_H -enum AirportTiles { +enum AirportTiles : uint8_t { APT_APRON, APT_APRON_FENCE_NW, APT_APRON_FENCE_SW, @@ -85,6 +85,23 @@ enum AirportTiles { APT_APRON_HALF_EAST, APT_APRON_HALF_WEST, APT_GRASS_FENCE_NE_FLAG_2, + + /* Airtype related gfx. */ + ATTG_DEFAULT_GFX = 0, + ATTG_WITH_CATCH_BUILDING_1 = 0, + ATTG_WITH_CATCH_BUILDING_2, + ATTG_WITH_CATCH_BUILDING_3, + ATTG_WITH_CATCH_BUILDING_FLAT, + ATTG_WITH_CATCH_BUILDING_TERMINAL, + ATTG_NO_CATCH_FLAG, + ATTG_NO_CATCH_TRANSMITTER, + ATTG_NO_CATCH_TOWER, + ATTG_NO_CATCH_RADAR, + ATTG_NO_CATCH_PIER, + ATTG_NO_CATCH_EMPTY, + ATTG_END, + + INVALID_AIRPORTTILE = 255 }; #endif /* AIRPORTTILE_IDS_H */ diff --git a/src/table/airporttiles.h b/src/table/airporttiles.h index 0393cf7a5f472..d46249a5e6935 100644 --- a/src/table/airporttiles.h +++ b/src/table/airporttiles.h @@ -104,7 +104,28 @@ static const AirportTileSpec _origin_airporttile_specs[] = { AT(3, 1), // APT_GRASS_FENCE_NE_FLAG_2 }; +/** + * All default airtype tiles. + * @see AirportTiles for a list of names. + */ +static const AirportTileSpec _origin_airtype_specs[] = { + /* 0..9 */ + AT_NOANIM, + AT_NOANIM, + AT_NOANIM, + AT_NOANIM, + AT_NOANIM, + AT(3, 1), + AT_NOANIM, + AT_NOANIM, + AT(11, 2), + AT_NOANIM, + + AT_NOANIM, +}; + static_assert(NEW_AIRPORTTILE_OFFSET == lengthof(_origin_airporttile_specs)); +static_assert(ATTG_END == lengthof(_origin_airtype_specs)); #undef AT_NOANIM #undef AT diff --git a/src/table/airtypes.h b/src/table/airtypes.h new file mode 100644 index 0000000000000..7a20fc2f7c877 --- /dev/null +++ b/src/table/airtypes.h @@ -0,0 +1,1057 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** + * @file airtypes.h + * All the airtype-specific information is stored here. + */ + +#ifndef AIRTYPES_H +#define AIRTYPES_H + +#include "air.h" + +#include "table/strings.h" + +/** + * Global AirType definition + */ +static const AirTypeInfo _original_airtypes[] = { + /** Gravel */ + { // Main Sprites + { + { + SPR_AIRPORT_TYPE_GRAVEL + 22, + SPR_AIRPORT_TYPE_GRAVEL + 23, + SPR_AIRPORT_TYPE_GRAVEL + 24, + SPR_AIRPORT_TYPE_GRAVEL + 25, + SPR_AIRPORT_TYPE_GRAVEL + 26, + SPR_AIRPORT_TYPE_GRAVEL + 22, + SPR_AIRPORT_TYPE_GRAVEL + 22, + SPR_AIRPORT_TYPE_GRAVEL + 22, + SPR_AIRPORT_TYPE_GRAVEL + 22, + SPR_AIRPORT_TYPE_GRAVEL + 22, + SPR_AIRPORT_TYPE_GRAVEL + 22, + SPR_AIRPORT_TYPE_GRAVEL + 22, + SPR_AIRPORT_TYPE_GRAVEL + 22, + SPR_AIRPORT_TYPE_GRAVEL + 22, + SPR_AIRPORT_TYPE_GRAVEL + 22, + SPR_AIRPORT_TYPE_GRAVEL + 22, + SPR_AIRPORT_TYPE_GRAVEL + 22, + SPR_AIRPORT_TYPE_GRAVEL + 22, + SPR_AIRPORT_TYPE_GRAVEL + 22, + SPR_AIRPORT_TYPE_GRAVEL + 22, + }, + { + { + { SPR_AIRPORT_TYPE_GRAVEL + 89, SPR_AIRPORT_TYPE_GRAVEL + 90, + SPR_AIRPORT_TYPE_GRAVEL + 91, SPR_AIRPORT_TYPE_GRAVEL+ 92 + }, + { SPR_AIRPORT_TYPE_GRAVEL + 93, SPR_AIRPORT_TYPE_GRAVEL + 94, + SPR_AIRPORT_TYPE_GRAVEL + 95, SPR_AIRPORT_TYPE_GRAVEL + 96 + }, + { SPR_AIRPORT_TYPE_GRAVEL + 97, SPR_AIRPORT_TYPE_GRAVEL + 98, + SPR_AIRPORT_TYPE_GRAVEL + 99, SPR_AIRPORT_TYPE_GRAVEL + 100 + }, + { SPR_AIRPORT_TYPE_GRAVEL + 101, SPR_AIRPORT_TYPE_GRAVEL + 102, + SPR_AIRPORT_TYPE_GRAVEL + 103, SPR_AIRPORT_TYPE_GRAVEL + 104 + }, + { SPR_AIRPORT_TYPE_GRAVEL + 105, SPR_AIRPORT_TYPE_GRAVEL + 106, + SPR_AIRPORT_TYPE_GRAVEL + 107, SPR_AIRPORT_TYPE_GRAVEL + 108 + } + }, + { + { SPR_AIRPORT_TYPE_GRAVEL + 109, SPR_AIRPORT_TYPE_GRAVEL + 110, + SPR_AIRPORT_TYPE_GRAVEL + 111, SPR_AIRPORT_TYPE_GRAVEL + 112 + }, + { SPR_AIRPORT_TYPE_GRAVEL + 113, SPR_AIRPORT_TYPE_GRAVEL + 114, + SPR_AIRPORT_TYPE_GRAVEL + 115, SPR_AIRPORT_TYPE_GRAVEL + 116 + }, + { SPR_AIRPORT_TYPE_GRAVEL + 117, SPR_AIRPORT_TYPE_GRAVEL + 118, + SPR_AIRPORT_TYPE_GRAVEL + 119, SPR_AIRPORT_TYPE_GRAVEL + 120 + }, + { SPR_AIRPORT_TYPE_GRAVEL + 121, SPR_AIRPORT_TYPE_GRAVEL + 122, + SPR_AIRPORT_TYPE_GRAVEL + 123, SPR_AIRPORT_TYPE_GRAVEL + 124 + }, + { SPR_AIRPORT_TYPE_GRAVEL + 125, SPR_AIRPORT_TYPE_GRAVEL + 126, + SPR_AIRPORT_TYPE_GRAVEL + 127, SPR_AIRPORT_TYPE_GRAVEL + 128 + } + } + }, + { + { SPR_AIRFIELD_WIND_1, SPR_AIRFIELD_WIND_2, SPR_AIRFIELD_WIND_3, SPR_AIRFIELD_WIND_4 }, // wind + { SPR_AIRFIELD_WIND_1, SPR_AIRFIELD_WIND_2, SPR_AIRFIELD_WIND_3, SPR_AIRFIELD_WIND_4 }, + { SPR_AIRFIELD_WIND_1, SPR_AIRFIELD_WIND_2, SPR_AIRFIELD_WIND_3, SPR_AIRFIELD_WIND_4 }, + { SPR_AIRFIELD_WIND_1, SPR_AIRFIELD_WIND_2, SPR_AIRFIELD_WIND_3, SPR_AIRFIELD_WIND_4 } + }, + { SPR_AIRPORT_RADAR_1, SPR_AIRPORT_RADAR_2, SPR_AIRPORT_RADAR_3, SPR_AIRPORT_RADAR_4, // radar + SPR_AIRPORT_RADAR_5, SPR_AIRPORT_RADAR_6, SPR_AIRPORT_RADAR_7, SPR_AIRPORT_RADAR_8, + SPR_AIRPORT_RADAR_9, SPR_AIRPORT_RADAR_A, SPR_AIRPORT_RADAR_B, SPR_AIRPORT_RADAR_C + }, + { + { SPR_AIRPORT_TYPE_GRAVEL + 73, SPR_AIRPORT_TYPE_GRAVEL + 74, // transmitter + SPR_AIRPORT_TYPE_GRAVEL + 75, SPR_AIRPORT_TYPE_GRAVEL + 76 + }, + { SPR_AIRPORT_TYPE_GRAVEL + 77, SPR_AIRPORT_TYPE_GRAVEL + 78, + SPR_AIRPORT_TYPE_GRAVEL + 79, SPR_AIRPORT_TYPE_GRAVEL + 80 + }, + { SPR_AIRPORT_TYPE_GRAVEL + 81, SPR_AIRPORT_TYPE_GRAVEL + 82, // snowed transmitter + SPR_AIRPORT_TYPE_GRAVEL + 83, SPR_AIRPORT_TYPE_GRAVEL + 84 + }, + { SPR_AIRPORT_TYPE_GRAVEL + 85, SPR_AIRPORT_TYPE_GRAVEL + 86, + SPR_AIRPORT_TYPE_GRAVEL + 87, SPR_AIRPORT_TYPE_GRAVEL + 88 + } + }, + { SPR_AIRPORT_TYPE_GRAVEL + 27, SPR_AIRPORT_TYPE_GRAVEL + 28, SPR_AIRPORT_TYPE_GRAVEL +29, // runways + SPR_AIRPORT_TYPE_GRAVEL + 30, SPR_AIRPORT_TYPE_GRAVEL + 31, SPR_AIRPORT_TYPE_GRAVEL + 32, + SPR_AIRPORT_TYPE_GRAVEL +33, SPR_AIRPORT_TYPE_GRAVEL +34, SPR_AIRPORT_TYPE_GRAVEL +35, + SPR_AIRPORT_TYPE_GRAVEL + 36, SPR_AIRPORT_TYPE_GRAVEL + 37, SPR_AIRPORT_TYPE_GRAVEL + 38, + SPR_AIRPORT_TYPE_GRAVEL + 39, SPR_AIRPORT_TYPE_GRAVEL + 40, SPR_AIRPORT_TYPE_GRAVEL + 41, + SPR_AIRPORT_TYPE_GRAVEL + 42, SPR_AIRPORT_TYPE_GRAVEL + 43, SPR_AIRPORT_TYPE_GRAVEL + 44, + SPR_AIRPORT_TYPE_GRAVEL + 45, SPR_AIRPORT_TYPE_GRAVEL + 46, SPR_AIRPORT_TYPE_GRAVEL + 47, + SPR_AIRPORT_TYPE_GRAVEL + 48, SPR_AIRPORT_TYPE_GRAVEL + 49, SPR_AIRPORT_TYPE_GRAVEL + 50 + }, + { SPR_AIRPORT_TYPE_GRAVEL + 63, SPR_AIRPORT_TYPE_GRAVEL + 64, // plane apron, helipad + SPR_AIRPORT_TYPE_GRAVEL + 65, SPR_AIRPORT_TYPE_GRAVEL + 66, SPR_AIRPORT_TYPE_GRAVEL + 67, // 4 heliport rotations + 4 snow heliports + SPR_AIRPORT_TYPE_GRAVEL + 68, SPR_AIRPORT_TYPE_GRAVEL + 69, SPR_AIRPORT_TYPE_GRAVEL + 70, + SPR_AIRPORT_TYPE_GRAVEL + 71, SPR_AIRPORT_TYPE_GRAVEL + 72 + }, + { SPR_AIRPORT_TYPE_GRAVEL + 51, SPR_AIRPORT_TYPE_GRAVEL + 52, SPR_AIRPORT_TYPE_GRAVEL + 53, + SPR_AIRPORT_TYPE_GRAVEL + 54, SPR_AIRPORT_TYPE_GRAVEL + 55, SPR_AIRPORT_TYPE_GRAVEL + 56, + SPR_AIRPORT_TYPE_GRAVEL + 57, SPR_AIRPORT_TYPE_GRAVEL + 58, SPR_AIRPORT_TYPE_GRAVEL + 59, + SPR_AIRPORT_TYPE_GRAVEL + 60, SPR_AIRPORT_TYPE_GRAVEL + 61, SPR_AIRPORT_TYPE_GRAVEL + 62 + } + }, + + /* GUI sprites */ + { SPR_AIRPORT_TYPE_GRAVEL, // add tile to airport + SPR_AIRPORT_TYPE_GRAVEL + 1, // build track tile + SPR_AIRPORT_TYPE_GRAVEL + 2, // change air type + SPR_AIRPORT_TYPE_GRAVEL + 3, // define runway + SPR_AIRPORT_TYPE_GRAVEL + 4, // apron + SPR_AIRPORT_TYPE_GRAVEL + 5, // helipad + SPR_AIRPORT_TYPE_GRAVEL + 6, // heliport + SPR_AIRPORT_TYPE_GRAVEL + 7, // hangar + SPR_AIRPORT_TYPE_GRAVEL + 8, // build predefined + SPR_AIRPORT_TYPE_GRAVEL + 9, // copy + SPR_AIRPORT_TYPE_GRAVEL + 10 + }, + + /* cursor sprites */ + { SPR_AIRPORT_TYPE_GRAVEL + 11, + SPR_AIRPORT_TYPE_GRAVEL + 12, + SPR_AIRPORT_TYPE_GRAVEL + 13, + SPR_AIRPORT_TYPE_GRAVEL + 14, + SPR_AIRPORT_TYPE_GRAVEL + 15, + SPR_AIRPORT_TYPE_GRAVEL + 16, + SPR_AIRPORT_TYPE_GRAVEL + 17, + SPR_AIRPORT_TYPE_GRAVEL + 18, + SPR_AIRPORT_TYPE_GRAVEL + 19, + SPR_AIRPORT_TYPE_GRAVEL + 20, + SPR_AIRPORT_TYPE_GRAVEL + 21 + }, + + /* strings */ + { + STR_AIRTYPE_NAME_GRAVEL, + STR_TOOLBAR_AIRPORT_GRAVEL_CONSTRUCTION_CAPTION, + STR_TOOLBAR_AIRPORT_GRAVEL_MENU_TEXT, + STR_REPLACE_AIRCRAFT_GRAVEL_VEHICLES, + }, + + /* Offset of snow tiles */ + SPR_RAIL_SNOW_OFFSET, + + /* Compatible airtypes */ + AIRTYPES_GRAVEL | AIRTYPES_ASPHALT | AIRTYPES_YELLOW, + + /* fallback_airtype */ + 0, + + /* cost multiplier */ + 1, + + /* maintenance cost multiplier */ + 1, + + /* max speed */ + 40, + + /* Gravel type label. */ + AIRTYPE_LABEL_GRAVEL, + + /* alternate labels */ + AirTypeLabelList(), + + /* map colour */ + 0x0A, + + /* introduction date */ + CalendarTime::INVALID_DATE, + + /* airtypes required for this to be introduced */ + AIRTYPES_NONE, + + /* introduction air types */ + AIRTYPES_GRAVEL, + + /* sort order */ + 0 << 4 | 7, + + { nullptr }, + { nullptr }, + + /* Catchment. */ + 3, + + /* Max number of runways. */ + 2, + + /* Min runway length in tiles. */ + 4, + + /* Base noise level. */ + 0, + + /* Runway noise level. */ + 1, + + /* Heliport availability. */ + false, + + /* Build on water. */ + false, + }, + + /** Asphalt */ + { // Main Sprites + { + { + SPR_AIRPORT_TYPE_ASPHALT + 22, + SPR_AIRPORT_TYPE_ASPHALT + 23, + SPR_AIRPORT_TYPE_ASPHALT + 24, + SPR_AIRPORT_TYPE_ASPHALT + 25, + SPR_AIRPORT_TYPE_ASPHALT + 26, + SPR_AIRPORT_TYPE_ASPHALT + 22, + SPR_AIRPORT_TYPE_ASPHALT + 22, + SPR_AIRPORT_TYPE_ASPHALT + 22, + SPR_AIRPORT_TYPE_ASPHALT + 22, + SPR_AIRPORT_TYPE_ASPHALT + 22, + SPR_AIRPORT_TYPE_ASPHALT + 22, + SPR_AIRPORT_TYPE_ASPHALT + 22, + SPR_AIRPORT_TYPE_ASPHALT + 22, + SPR_AIRPORT_TYPE_ASPHALT + 22, + SPR_AIRPORT_TYPE_ASPHALT + 22, + SPR_AIRPORT_TYPE_ASPHALT + 22, + SPR_AIRPORT_TYPE_ASPHALT + 22, + SPR_AIRPORT_TYPE_ASPHALT + 22, + SPR_AIRPORT_TYPE_ASPHALT + 22, + SPR_AIRPORT_TYPE_ASPHALT + 22, + }, + { + { + { SPR_AIRPORT_TYPE_ASPHALT + 89, SPR_AIRPORT_TYPE_ASPHALT + 90, + SPR_AIRPORT_TYPE_ASPHALT + 91, SPR_AIRPORT_TYPE_ASPHALT+ 92 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 93, SPR_AIRPORT_TYPE_ASPHALT + 94, + SPR_AIRPORT_TYPE_ASPHALT + 95, SPR_AIRPORT_TYPE_ASPHALT + 96 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 97, SPR_AIRPORT_TYPE_ASPHALT + 98, + SPR_AIRPORT_TYPE_ASPHALT + 99, SPR_AIRPORT_TYPE_ASPHALT + 100 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 101, SPR_AIRPORT_TYPE_ASPHALT + 102, + SPR_AIRPORT_TYPE_ASPHALT + 103, SPR_AIRPORT_TYPE_ASPHALT + 104 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 105, SPR_AIRPORT_TYPE_ASPHALT + 106, + SPR_AIRPORT_TYPE_ASPHALT + 107, SPR_AIRPORT_TYPE_ASPHALT + 108 + } + }, + { + { SPR_AIRPORT_TYPE_ASPHALT + 109, SPR_AIRPORT_TYPE_ASPHALT + 110, + SPR_AIRPORT_TYPE_ASPHALT + 111, SPR_AIRPORT_TYPE_ASPHALT + 112 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 113, SPR_AIRPORT_TYPE_ASPHALT + 114, + SPR_AIRPORT_TYPE_ASPHALT + 115, SPR_AIRPORT_TYPE_ASPHALT + 116 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 117, SPR_AIRPORT_TYPE_ASPHALT + 118, + SPR_AIRPORT_TYPE_ASPHALT + 119, SPR_AIRPORT_TYPE_ASPHALT + 120 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 121, SPR_AIRPORT_TYPE_ASPHALT + 122, + SPR_AIRPORT_TYPE_ASPHALT + 123, SPR_AIRPORT_TYPE_ASPHALT + 124 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 125, SPR_AIRPORT_TYPE_ASPHALT + 126, + SPR_AIRPORT_TYPE_ASPHALT + 127, SPR_AIRPORT_TYPE_ASPHALT + 128 + } + } + }, + { + { SPR_AIRFIELD_WIND_1, SPR_AIRFIELD_WIND_2, SPR_AIRFIELD_WIND_3, SPR_AIRFIELD_WIND_4 }, // wind + { SPR_AIRFIELD_WIND_1, SPR_AIRFIELD_WIND_2, SPR_AIRFIELD_WIND_3, SPR_AIRFIELD_WIND_4 }, + { SPR_AIRFIELD_WIND_1, SPR_AIRFIELD_WIND_2, SPR_AIRFIELD_WIND_3, SPR_AIRFIELD_WIND_4 }, + { SPR_AIRFIELD_WIND_1, SPR_AIRFIELD_WIND_2, SPR_AIRFIELD_WIND_3, SPR_AIRFIELD_WIND_4 } + }, + { SPR_AIRPORT_RADAR_1, SPR_AIRPORT_RADAR_2, SPR_AIRPORT_RADAR_3, SPR_AIRPORT_RADAR_4, // radar + SPR_AIRPORT_RADAR_5, SPR_AIRPORT_RADAR_6, SPR_AIRPORT_RADAR_7, SPR_AIRPORT_RADAR_8, + SPR_AIRPORT_RADAR_9, SPR_AIRPORT_RADAR_A, SPR_AIRPORT_RADAR_B, SPR_AIRPORT_RADAR_C + }, + { + { SPR_AIRPORT_TYPE_ASPHALT + 73, SPR_AIRPORT_TYPE_ASPHALT + 74, // transmitter + SPR_AIRPORT_TYPE_ASPHALT + 75, SPR_AIRPORT_TYPE_ASPHALT + 76 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 77, SPR_AIRPORT_TYPE_ASPHALT + 78, + SPR_AIRPORT_TYPE_ASPHALT + 79, SPR_AIRPORT_TYPE_ASPHALT + 80 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 81, SPR_AIRPORT_TYPE_ASPHALT + 82, // snowed transmitter + SPR_AIRPORT_TYPE_ASPHALT + 83, SPR_AIRPORT_TYPE_ASPHALT + 84 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 85, SPR_AIRPORT_TYPE_ASPHALT + 86, + SPR_AIRPORT_TYPE_ASPHALT + 87, SPR_AIRPORT_TYPE_ASPHALT + 88 + } + }, + { SPR_AIRPORT_TYPE_ASPHALT + 27, SPR_AIRPORT_TYPE_ASPHALT + 28, SPR_AIRPORT_TYPE_ASPHALT +29, // runways + SPR_AIRPORT_TYPE_ASPHALT + 30, SPR_AIRPORT_TYPE_ASPHALT + 31, SPR_AIRPORT_TYPE_ASPHALT + 32, + SPR_AIRPORT_TYPE_ASPHALT +33, SPR_AIRPORT_TYPE_ASPHALT +34, SPR_AIRPORT_TYPE_ASPHALT +35, + SPR_AIRPORT_TYPE_ASPHALT + 36, SPR_AIRPORT_TYPE_ASPHALT + 37, SPR_AIRPORT_TYPE_ASPHALT + 38, + SPR_AIRPORT_TYPE_ASPHALT + 39, SPR_AIRPORT_TYPE_ASPHALT + 40, SPR_AIRPORT_TYPE_ASPHALT + 41, + SPR_AIRPORT_TYPE_ASPHALT + 42, SPR_AIRPORT_TYPE_ASPHALT + 43, SPR_AIRPORT_TYPE_ASPHALT + 44, + SPR_AIRPORT_TYPE_ASPHALT + 45, SPR_AIRPORT_TYPE_ASPHALT + 46, SPR_AIRPORT_TYPE_ASPHALT + 47, + SPR_AIRPORT_TYPE_ASPHALT + 48, SPR_AIRPORT_TYPE_ASPHALT + 49, SPR_AIRPORT_TYPE_ASPHALT + 50 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 63, SPR_AIRPORT_TYPE_ASPHALT + 64, // plane apron, helipad + SPR_AIRPORT_TYPE_ASPHALT + 65, SPR_AIRPORT_TYPE_ASPHALT + 66, SPR_AIRPORT_TYPE_ASPHALT + 67, // 4 heliport rotations + 4 snow heliports + SPR_AIRPORT_TYPE_ASPHALT + 68, SPR_AIRPORT_TYPE_ASPHALT + 69, SPR_AIRPORT_TYPE_ASPHALT + 70, + SPR_AIRPORT_TYPE_ASPHALT + 71, SPR_AIRPORT_TYPE_ASPHALT + 72 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 51, SPR_AIRPORT_TYPE_ASPHALT + 52, SPR_AIRPORT_TYPE_ASPHALT + 53, + SPR_AIRPORT_TYPE_ASPHALT + 54, SPR_AIRPORT_TYPE_ASPHALT + 55, SPR_AIRPORT_TYPE_ASPHALT + 56, + SPR_AIRPORT_TYPE_ASPHALT + 57, SPR_AIRPORT_TYPE_ASPHALT + 58, SPR_AIRPORT_TYPE_ASPHALT + 59, + SPR_AIRPORT_TYPE_ASPHALT + 60, SPR_AIRPORT_TYPE_ASPHALT + 61, SPR_AIRPORT_TYPE_ASPHALT + 62 + } + }, + + /* GUI sprites */ + { SPR_AIRPORT_TYPE_ASPHALT, // add tile to airport + SPR_AIRPORT_TYPE_ASPHALT + 1, // build track tile + SPR_AIRPORT_TYPE_ASPHALT + 2, // change air type + SPR_AIRPORT_TYPE_ASPHALT + 3, // define runway + SPR_AIRPORT_TYPE_ASPHALT + 4, // apron + SPR_AIRPORT_TYPE_ASPHALT + 5, // helipad + SPR_AIRPORT_TYPE_ASPHALT + 6, // heliport + SPR_AIRPORT_TYPE_ASPHALT + 7, // hangar + SPR_AIRPORT_TYPE_ASPHALT + 8, // build predefined + SPR_AIRPORT_TYPE_ASPHALT + 9, // copy + SPR_AIRPORT_TYPE_ASPHALT + 10 + }, + + /* cursor sprites */ + { SPR_AIRPORT_TYPE_ASPHALT + 11, + SPR_AIRPORT_TYPE_ASPHALT + 12, + SPR_AIRPORT_TYPE_ASPHALT + 13, + SPR_AIRPORT_TYPE_ASPHALT + 14, + SPR_AIRPORT_TYPE_ASPHALT + 15, + SPR_AIRPORT_TYPE_ASPHALT + 16, + SPR_AIRPORT_TYPE_ASPHALT + 17, + SPR_AIRPORT_TYPE_ASPHALT + 18, + SPR_AIRPORT_TYPE_ASPHALT + 19, + SPR_AIRPORT_TYPE_ASPHALT + 20, + SPR_AIRPORT_TYPE_ASPHALT + 21 + }, + + /* strings */ + { + STR_AIRTYPE_NAME_ASPHALT, + STR_TOOLBAR_AIRPORT_ASPHALT_CONSTRUCTION_CAPTION, + STR_TOOLBAR_AIRPORT_ASPHALT_CONSTRUCTION_CAPTION, + STR_REPLACE_AIRCRAFT_ASPHALT_VEHICLES, + }, + + /* Offset of snow tiles */ + SPR_RAIL_SNOW_OFFSET, + + /* Compatible airtypes */ + AIRTYPES_ASPHALT | AIRTYPES_YELLOW | AIRTYPES_DARK, + + /* fallback_airtype */ + 0, + + /* cost multiplier */ + 8, + + /* maintenance cost multiplier */ + 8, + + /* max speed */ + 80, + + /* Gravel type label. */ + AIRTYPE_LABEL_ASPHALT, + + /* alternate labels */ + AirTypeLabelList(), + + /* map colour */ + 0x0A, + + /* introduction date */ + CalendarTime::INVALID_DATE, + + /* airtypes required for this to be introduced */ + AIRTYPES_NONE, + + /* introduction air types */ + AIRTYPES_ASPHALT | AIRTYPES_DARK | AIRTYPES_YELLOW, + + /* sort order */ + 1 << 4 | 7, + + { nullptr }, + { nullptr }, + + /* Catchment. */ + 8, + + /* Max number of runways. */ + 4, + + /* Min runway length in tiles. */ + 5, + + /* Base noise level. */ + 1, + + /* Runway noise level. */ + 3, + + /* Heliport availability. */ + true, + + /* Build on water. */ + false, + }, + + /** Water */ + { // Main Sprites + { + { + SPR_AIRPORT_TYPE_WATER + 22, + SPR_AIRPORT_TYPE_WATER + 23, + SPR_AIRPORT_TYPE_WATER + 24, + SPR_AIRPORT_TYPE_WATER + 25, + SPR_AIRPORT_TYPE_WATER + 26, + SPR_AIRPORT_TYPE_WATER + 22, + SPR_AIRPORT_TYPE_WATER + 22, + SPR_AIRPORT_TYPE_WATER + 22, + SPR_AIRPORT_TYPE_WATER + 22, + SPR_AIRPORT_TYPE_WATER + 22, + SPR_AIRPORT_TYPE_WATER + 22, + SPR_AIRPORT_TYPE_WATER + 22, + SPR_AIRPORT_TYPE_WATER + 22, + SPR_AIRPORT_TYPE_WATER + 22, + SPR_AIRPORT_TYPE_WATER + 22, + SPR_AIRPORT_TYPE_WATER + 22, + SPR_AIRPORT_TYPE_WATER + 22, + SPR_AIRPORT_TYPE_WATER + 22, + SPR_AIRPORT_TYPE_WATER + 22, + SPR_AIRPORT_TYPE_WATER + 22, + }, + { + { + { SPR_AIRPORT_TYPE_WATER + 89, SPR_AIRPORT_TYPE_WATER + 90, + SPR_AIRPORT_TYPE_WATER + 91, SPR_AIRPORT_TYPE_WATER+ 92 + }, + { SPR_AIRPORT_TYPE_WATER + 93, SPR_AIRPORT_TYPE_WATER + 94, + SPR_AIRPORT_TYPE_WATER + 95, SPR_AIRPORT_TYPE_WATER + 96 + }, + { SPR_AIRPORT_TYPE_WATER + 97, SPR_AIRPORT_TYPE_WATER + 98, + SPR_AIRPORT_TYPE_WATER + 99, SPR_AIRPORT_TYPE_WATER + 100 + }, + { SPR_AIRPORT_TYPE_WATER + 101, SPR_AIRPORT_TYPE_WATER + 102, + SPR_AIRPORT_TYPE_WATER + 103, SPR_AIRPORT_TYPE_WATER + 104 + }, + { SPR_AIRPORT_TYPE_WATER + 105, SPR_AIRPORT_TYPE_WATER + 106, + SPR_AIRPORT_TYPE_WATER + 107, SPR_AIRPORT_TYPE_WATER + 108 + } + }, + { + { SPR_AIRPORT_TYPE_WATER + 109, SPR_AIRPORT_TYPE_WATER + 110, + SPR_AIRPORT_TYPE_WATER + 111, SPR_AIRPORT_TYPE_WATER + 112 + }, + { SPR_AIRPORT_TYPE_WATER + 113, SPR_AIRPORT_TYPE_WATER + 114, + SPR_AIRPORT_TYPE_WATER + 115, SPR_AIRPORT_TYPE_WATER + 116 + }, + { SPR_AIRPORT_TYPE_WATER + 117, SPR_AIRPORT_TYPE_WATER + 118, + SPR_AIRPORT_TYPE_WATER + 119, SPR_AIRPORT_TYPE_WATER + 120 + }, + { SPR_AIRPORT_TYPE_WATER + 121, SPR_AIRPORT_TYPE_WATER + 122, + SPR_AIRPORT_TYPE_WATER + 123, SPR_AIRPORT_TYPE_WATER + 124 + }, + { SPR_AIRPORT_TYPE_WATER + 125, SPR_AIRPORT_TYPE_WATER + 126, + SPR_AIRPORT_TYPE_WATER + 127, SPR_AIRPORT_TYPE_WATER + 128 + } + } + }, + { + { SPR_AIRFIELD_WIND_1, SPR_AIRFIELD_WIND_2, SPR_AIRFIELD_WIND_3, SPR_AIRFIELD_WIND_4 }, // wind + { SPR_AIRFIELD_WIND_1, SPR_AIRFIELD_WIND_2, SPR_AIRFIELD_WIND_3, SPR_AIRFIELD_WIND_4 }, + { SPR_AIRFIELD_WIND_1, SPR_AIRFIELD_WIND_2, SPR_AIRFIELD_WIND_3, SPR_AIRFIELD_WIND_4 }, + { SPR_AIRFIELD_WIND_1, SPR_AIRFIELD_WIND_2, SPR_AIRFIELD_WIND_3, SPR_AIRFIELD_WIND_4 } + }, + { SPR_AIRPORT_RADAR_1, SPR_AIRPORT_RADAR_2, SPR_AIRPORT_RADAR_3, SPR_AIRPORT_RADAR_4, // radar + SPR_AIRPORT_RADAR_5, SPR_AIRPORT_RADAR_6, SPR_AIRPORT_RADAR_7, SPR_AIRPORT_RADAR_8, + SPR_AIRPORT_RADAR_9, SPR_AIRPORT_RADAR_A, SPR_AIRPORT_RADAR_B, SPR_AIRPORT_RADAR_C + }, + { + { SPR_AIRPORT_TYPE_WATER + 73, SPR_AIRPORT_TYPE_WATER + 74, // transmitter + SPR_AIRPORT_TYPE_WATER + 75, SPR_AIRPORT_TYPE_WATER + 76 + }, + { SPR_AIRPORT_TYPE_WATER + 77, SPR_AIRPORT_TYPE_WATER + 78, + SPR_AIRPORT_TYPE_WATER + 79, SPR_AIRPORT_TYPE_WATER + 80 + }, + { SPR_AIRPORT_TYPE_WATER + 81, SPR_AIRPORT_TYPE_WATER + 82, // snowed transmitter + SPR_AIRPORT_TYPE_WATER + 83, SPR_AIRPORT_TYPE_WATER + 84 + }, + { SPR_AIRPORT_TYPE_WATER + 85, SPR_AIRPORT_TYPE_WATER + 86, + SPR_AIRPORT_TYPE_WATER + 87, SPR_AIRPORT_TYPE_WATER + 88 + } + }, + { SPR_AIRPORT_TYPE_WATER + 27, SPR_AIRPORT_TYPE_WATER + 28, SPR_AIRPORT_TYPE_WATER +29, // runways + SPR_AIRPORT_TYPE_WATER + 30, SPR_AIRPORT_TYPE_WATER + 31, SPR_AIRPORT_TYPE_WATER + 32, + SPR_AIRPORT_TYPE_WATER +33, SPR_AIRPORT_TYPE_WATER +34, SPR_AIRPORT_TYPE_WATER +35, + SPR_AIRPORT_TYPE_WATER + 36, SPR_AIRPORT_TYPE_WATER + 37, SPR_AIRPORT_TYPE_WATER + 38, + SPR_AIRPORT_TYPE_WATER + 39, SPR_AIRPORT_TYPE_WATER + 40, SPR_AIRPORT_TYPE_WATER + 41, + SPR_AIRPORT_TYPE_WATER + 42, SPR_AIRPORT_TYPE_WATER + 43, SPR_AIRPORT_TYPE_WATER + 44, + SPR_AIRPORT_TYPE_WATER + 45, SPR_AIRPORT_TYPE_WATER + 46, SPR_AIRPORT_TYPE_WATER + 47, + SPR_AIRPORT_TYPE_WATER + 48, SPR_AIRPORT_TYPE_WATER + 49, SPR_AIRPORT_TYPE_WATER + 50 + }, + { SPR_AIRPORT_TYPE_WATER + 63, SPR_AIRPORT_TYPE_WATER + 64, // plane apron, helipad + SPR_AIRPORT_TYPE_WATER + 65, SPR_AIRPORT_TYPE_WATER + 66, SPR_AIRPORT_TYPE_WATER + 67, // 4 heliport rotations + 4 snow heliports + SPR_AIRPORT_TYPE_WATER + 68, SPR_AIRPORT_TYPE_WATER + 69, SPR_AIRPORT_TYPE_WATER + 70, + SPR_AIRPORT_TYPE_WATER + 71, SPR_AIRPORT_TYPE_WATER + 72 + }, + { SPR_AIRPORT_TYPE_WATER + 51, SPR_AIRPORT_TYPE_WATER + 52, SPR_AIRPORT_TYPE_WATER + 53, + SPR_AIRPORT_TYPE_WATER + 54, SPR_AIRPORT_TYPE_WATER + 55, SPR_AIRPORT_TYPE_WATER + 56, + SPR_AIRPORT_TYPE_WATER + 57, SPR_AIRPORT_TYPE_WATER + 58, SPR_AIRPORT_TYPE_WATER + 59, + SPR_AIRPORT_TYPE_WATER + 60, SPR_AIRPORT_TYPE_WATER + 61, SPR_AIRPORT_TYPE_WATER + 62 + } + }, + + /* GUI sprites */ + { SPR_AIRPORT_TYPE_WATER, + SPR_AIRPORT_TYPE_WATER + 1, + SPR_AIRPORT_TYPE_WATER + 2, + SPR_AIRPORT_TYPE_WATER + 3, + SPR_AIRPORT_TYPE_WATER + 4, + SPR_AIRPORT_TYPE_WATER + 5, + SPR_AIRPORT_TYPE_WATER + 6, + SPR_AIRPORT_TYPE_WATER + 7, + SPR_AIRPORT_TYPE_WATER + 8, + SPR_AIRPORT_TYPE_WATER + 9, + SPR_AIRPORT_TYPE_WATER + 10 + }, + + /* cursor sprites */ + { SPR_AIRPORT_TYPE_WATER + 11, + SPR_AIRPORT_TYPE_WATER + 12, + SPR_AIRPORT_TYPE_WATER + 13, + SPR_AIRPORT_TYPE_WATER + 14, + SPR_AIRPORT_TYPE_WATER + 15, + SPR_AIRPORT_TYPE_WATER + 16, + SPR_AIRPORT_TYPE_WATER + 17, + SPR_AIRPORT_TYPE_WATER + 18, + SPR_AIRPORT_TYPE_WATER + 19, + SPR_AIRPORT_TYPE_WATER + 20, + SPR_AIRPORT_TYPE_WATER + 21 + }, + + /* strings */ + { + STR_AIRTYPE_NAME_WATER, + STR_TOOLBAR_AIRPORT_WATER_CONSTRUCTION_CAPTION, + STR_TOOLBAR_AIRPORT_WATER_CONSTRUCTION_CAPTION, + STR_REPLACE_AIRCRAFT_WATER_VEHICLES, + }, + + /* Offset of snow tiles */ + SPR_RAIL_SNOW_OFFSET, + + /* Compatible airtypes */ + AIRTYPES_GRAVEL | AIRTYPES_ASPHALT | AIRTYPES_WATER, + + /* fallback_airtype */ + 0, + + /* cost multiplier */ + 4, + + /* maintenance cost multiplier */ + 4, + + /* max speed */ + 35, + + /* Gravel type label. */ + AIRTYPE_LABEL_WATER, + + /* alternate labels */ + AirTypeLabelList(), + + /* map colour */ + 0x0A, + + /* introduction date */ + CalendarTime::INVALID_DATE, + + /* airtypes required for this to be introduced */ + AIRTYPES_NONE, + + /* introduction air types */ + AIRTYPES_WATER, + + /* sort order */ + 2 << 4 | 7, + + { nullptr }, + { nullptr }, + + /* Catchment. */ + 4, + + /* Max number of runways. */ + 2, + + /* Min runway length in tiles. */ + 5, + + /* Base noise level. */ + 1, + + /* Runway noise level. */ + 0, + + /* Heliport availability. */ + false, + + /* Build on water. */ + true, + }, + + /** Asphalt dark */ + { // Main Sprites + { + { + SPR_AIRPORT_TYPE_DARK + 22, + SPR_AIRPORT_TYPE_DARK + 23, + SPR_AIRPORT_TYPE_DARK + 24, + SPR_AIRPORT_TYPE_DARK + 25, + SPR_AIRPORT_TYPE_DARK + 26, + SPR_AIRPORT_TYPE_DARK + 22, + SPR_AIRPORT_TYPE_DARK + 22, + SPR_AIRPORT_TYPE_DARK + 22, + SPR_AIRPORT_TYPE_DARK + 22, + SPR_AIRPORT_TYPE_DARK + 22, + SPR_AIRPORT_TYPE_DARK + 22, + SPR_AIRPORT_TYPE_DARK + 22, + SPR_AIRPORT_TYPE_DARK + 22, + SPR_AIRPORT_TYPE_DARK + 22, + SPR_AIRPORT_TYPE_DARK + 22, + SPR_AIRPORT_TYPE_DARK + 22, + SPR_AIRPORT_TYPE_DARK + 22, + SPR_AIRPORT_TYPE_DARK + 22, + SPR_AIRPORT_TYPE_DARK + 22, + SPR_AIRPORT_TYPE_DARK + 22, + }, + { + { + { SPR_AIRPORT_TYPE_DARK + 89, SPR_AIRPORT_TYPE_DARK + 90, + SPR_AIRPORT_TYPE_ASPHALT + 91, SPR_AIRPORT_TYPE_ASPHALT+ 92 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 93, SPR_AIRPORT_TYPE_ASPHALT + 94, + SPR_AIRPORT_TYPE_ASPHALT + 95, SPR_AIRPORT_TYPE_ASPHALT + 96 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 97, SPR_AIRPORT_TYPE_ASPHALT + 98, + SPR_AIRPORT_TYPE_ASPHALT + 99, SPR_AIRPORT_TYPE_ASPHALT + 100 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 101, SPR_AIRPORT_TYPE_ASPHALT + 102, + SPR_AIRPORT_TYPE_ASPHALT + 103, SPR_AIRPORT_TYPE_ASPHALT + 104 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 105, SPR_AIRPORT_TYPE_ASPHALT + 106, + SPR_AIRPORT_TYPE_ASPHALT + 107, SPR_AIRPORT_TYPE_ASPHALT + 108 + } + }, + { + { SPR_AIRPORT_TYPE_ASPHALT + 109, SPR_AIRPORT_TYPE_ASPHALT + 110, + SPR_AIRPORT_TYPE_ASPHALT + 111, SPR_AIRPORT_TYPE_ASPHALT + 112 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 113, SPR_AIRPORT_TYPE_ASPHALT + 114, + SPR_AIRPORT_TYPE_ASPHALT + 115, SPR_AIRPORT_TYPE_ASPHALT + 116 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 117, SPR_AIRPORT_TYPE_ASPHALT + 118, + SPR_AIRPORT_TYPE_ASPHALT + 119, SPR_AIRPORT_TYPE_ASPHALT + 120 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 121, SPR_AIRPORT_TYPE_ASPHALT + 122, + SPR_AIRPORT_TYPE_ASPHALT + 123, SPR_AIRPORT_TYPE_ASPHALT + 124 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 125, SPR_AIRPORT_TYPE_ASPHALT + 126, + SPR_AIRPORT_TYPE_ASPHALT + 127, SPR_AIRPORT_TYPE_ASPHALT + 128 + } + } + }, + { + { SPR_AIRFIELD_WIND_1, SPR_AIRFIELD_WIND_2, SPR_AIRFIELD_WIND_3, SPR_AIRFIELD_WIND_4 }, // wind + { SPR_AIRFIELD_WIND_1, SPR_AIRFIELD_WIND_2, SPR_AIRFIELD_WIND_3, SPR_AIRFIELD_WIND_4 }, + { SPR_AIRFIELD_WIND_1, SPR_AIRFIELD_WIND_2, SPR_AIRFIELD_WIND_3, SPR_AIRFIELD_WIND_4 }, + { SPR_AIRFIELD_WIND_1, SPR_AIRFIELD_WIND_2, SPR_AIRFIELD_WIND_3, SPR_AIRFIELD_WIND_4 } + }, + { SPR_AIRPORT_RADAR_1, SPR_AIRPORT_RADAR_2, SPR_AIRPORT_RADAR_3, SPR_AIRPORT_RADAR_4, // radar + SPR_AIRPORT_RADAR_5, SPR_AIRPORT_RADAR_6, SPR_AIRPORT_RADAR_7, SPR_AIRPORT_RADAR_8, + SPR_AIRPORT_RADAR_9, SPR_AIRPORT_RADAR_A, SPR_AIRPORT_RADAR_B, SPR_AIRPORT_RADAR_C + }, + { + { SPR_AIRPORT_TYPE_ASPHALT + 73, SPR_AIRPORT_TYPE_ASPHALT + 74, // transmitter + SPR_AIRPORT_TYPE_ASPHALT + 75, SPR_AIRPORT_TYPE_ASPHALT + 76 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 77, SPR_AIRPORT_TYPE_ASPHALT + 78, + SPR_AIRPORT_TYPE_ASPHALT + 79, SPR_AIRPORT_TYPE_ASPHALT + 80 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 81, SPR_AIRPORT_TYPE_ASPHALT + 82, // snowed transmitter + SPR_AIRPORT_TYPE_ASPHALT + 83, SPR_AIRPORT_TYPE_ASPHALT + 84 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 85, SPR_AIRPORT_TYPE_ASPHALT + 86, + SPR_AIRPORT_TYPE_ASPHALT + 87, SPR_AIRPORT_TYPE_ASPHALT + 88 + } + }, + { SPR_AIRPORT_TYPE_ASPHALT + 27, SPR_AIRPORT_TYPE_ASPHALT + 28, SPR_AIRPORT_TYPE_ASPHALT +29, // runways + SPR_AIRPORT_TYPE_ASPHALT + 30, SPR_AIRPORT_TYPE_ASPHALT + 31, SPR_AIRPORT_TYPE_ASPHALT + 32, + SPR_AIRPORT_TYPE_ASPHALT +33, SPR_AIRPORT_TYPE_ASPHALT +34, SPR_AIRPORT_TYPE_ASPHALT +35, + SPR_AIRPORT_TYPE_ASPHALT + 36, SPR_AIRPORT_TYPE_ASPHALT + 37, SPR_AIRPORT_TYPE_ASPHALT + 38, + SPR_AIRPORT_TYPE_ASPHALT + 39, SPR_AIRPORT_TYPE_ASPHALT + 40, SPR_AIRPORT_TYPE_ASPHALT + 41, + SPR_AIRPORT_TYPE_ASPHALT + 42, SPR_AIRPORT_TYPE_ASPHALT + 43, SPR_AIRPORT_TYPE_ASPHALT + 44, + SPR_AIRPORT_TYPE_ASPHALT + 45, SPR_AIRPORT_TYPE_ASPHALT + 46, SPR_AIRPORT_TYPE_ASPHALT + 47, + SPR_AIRPORT_TYPE_ASPHALT + 48, SPR_AIRPORT_TYPE_ASPHALT + 49, SPR_AIRPORT_TYPE_ASPHALT + 50 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 63, SPR_AIRPORT_TYPE_ASPHALT + 64, // plane apron, helipad + SPR_AIRPORT_TYPE_ASPHALT + 65, SPR_AIRPORT_TYPE_ASPHALT + 66, SPR_AIRPORT_TYPE_ASPHALT + 67, // 4 heliport rotations + 4 snow heliports + SPR_AIRPORT_TYPE_ASPHALT + 68, SPR_AIRPORT_TYPE_ASPHALT + 69, SPR_AIRPORT_TYPE_ASPHALT + 70, + SPR_AIRPORT_TYPE_ASPHALT + 71, SPR_AIRPORT_TYPE_ASPHALT + 72 + }, + { SPR_AIRPORT_TYPE_ASPHALT + 51, SPR_AIRPORT_TYPE_ASPHALT + 52, SPR_AIRPORT_TYPE_ASPHALT + 53, + SPR_AIRPORT_TYPE_ASPHALT + 54, SPR_AIRPORT_TYPE_ASPHALT + 55, SPR_AIRPORT_TYPE_ASPHALT + 56, + SPR_AIRPORT_TYPE_ASPHALT + 57, SPR_AIRPORT_TYPE_ASPHALT + 58, SPR_AIRPORT_TYPE_ASPHALT + 59, + SPR_AIRPORT_TYPE_ASPHALT + 60, SPR_AIRPORT_TYPE_ASPHALT + 61, SPR_AIRPORT_TYPE_ASPHALT + 62 + } + }, + + /* GUI sprites */ + { SPR_AIRPORT_TYPE_DARK, // add tile to airport + SPR_AIRPORT_TYPE_DARK + 1, // build track tile + SPR_AIRPORT_TYPE_DARK + 2, // change air type + SPR_AIRPORT_TYPE_DARK + 3, // define runway + SPR_AIRPORT_TYPE_DARK + 4, // apron + SPR_AIRPORT_TYPE_DARK + 5, // helipad + SPR_AIRPORT_TYPE_DARK + 6, // heliport + SPR_AIRPORT_TYPE_DARK + 7, // hangar + SPR_AIRPORT_TYPE_DARK + 8, // build predefined + SPR_AIRPORT_TYPE_DARK + 9, // copy + SPR_AIRPORT_TYPE_DARK + 10 + }, + + /* cursor sprites */ + { SPR_AIRPORT_TYPE_DARK + 11, + SPR_AIRPORT_TYPE_DARK + 12, + SPR_AIRPORT_TYPE_DARK + 13, + SPR_AIRPORT_TYPE_DARK + 14, + SPR_AIRPORT_TYPE_DARK + 15, + SPR_AIRPORT_TYPE_DARK + 16, + SPR_AIRPORT_TYPE_DARK + 17, + SPR_AIRPORT_TYPE_DARK + 18, + SPR_AIRPORT_TYPE_DARK + 19, + SPR_AIRPORT_TYPE_DARK + 20, + SPR_AIRPORT_TYPE_DARK + 21 + }, + + /* strings */ + { + STR_AIRTYPE_NAME_DARK, + STR_TOOLBAR_AIRPORT_DARK_CONSTRUCTION_CAPTION, + STR_TOOLBAR_AIRPORT_DARK_CONSTRUCTION_CAPTION, + STR_REPLACE_AIRCRAFT_DARK_VEHICLES, + }, + + /* Offset of snow tiles */ + SPR_RAIL_SNOW_OFFSET, + + /* Compatible airtypes */ + AIRTYPES_YELLOW | AIRTYPES_ASPHALT, + + /* fallback_airtype */ + 0, + + /* cost multiplier */ + 8, + + /* maintenance cost multiplier */ + 8, + + /* max speed */ + 80, + + /* Gravel type label. */ + AIRTYPE_LABEL_ASPHALT_DARK, + + /* alternate labels */ + AirTypeLabelList(), + + /* map colour */ + 0x0A, + + /* introduction date */ + CalendarTime::INVALID_DATE, + + /* airtypes required for this to be introduced */ + AIRTYPES_NONE, + + /* introduction air types */ + AIRTYPES_DARK, + + /* sort order */ + 3 << 4 | 7, + + { nullptr }, + { nullptr }, + + /* Catchment. */ + 8, + + /* Max number of runways. */ + 10, + + /* Min runway length in tiles. */ + 5, + + /* Base noise level. */ + 1, + + /* Runway noise level. */ + 3, + + /* Heliport availability. */ + true, + + /* Build on water. */ + false, + }, + + /** Asphalt yellow lines */ + { // Main Sprites + { + { + SPR_AIRPORT_TYPE_YELLOW + 22, + SPR_AIRPORT_TYPE_YELLOW + 23, + SPR_AIRPORT_TYPE_YELLOW + 24, + SPR_AIRPORT_TYPE_YELLOW + 25, + SPR_AIRPORT_TYPE_YELLOW + 26, + SPR_AIRPORT_TYPE_YELLOW + 129, // x + SPR_AIRPORT_TYPE_YELLOW + 130, // y + SPR_AIRPORT_TYPE_YELLOW + 131, + SPR_AIRPORT_TYPE_YELLOW + 132, + SPR_AIRPORT_TYPE_YELLOW + 133, + SPR_AIRPORT_TYPE_YELLOW + 134, + SPR_AIRPORT_TYPE_YELLOW + 135, + SPR_AIRPORT_TYPE_YELLOW + 136, + SPR_AIRPORT_TYPE_YELLOW + 137, + SPR_AIRPORT_TYPE_YELLOW + 138, + SPR_AIRPORT_TYPE_YELLOW + 139, + SPR_AIRPORT_TYPE_YELLOW + 140, + SPR_AIRPORT_TYPE_YELLOW + 141, + SPR_AIRPORT_TYPE_YELLOW + 142, + SPR_AIRPORT_TYPE_YELLOW + 143, + }, + { + { + { SPR_AIRPORT_TYPE_YELLOW + 89, SPR_AIRPORT_TYPE_YELLOW + 90, + SPR_AIRPORT_TYPE_YELLOW + 91, SPR_AIRPORT_TYPE_YELLOW+ 92 + }, + { SPR_AIRPORT_TYPE_YELLOW + 93, SPR_AIRPORT_TYPE_YELLOW + 94, + SPR_AIRPORT_TYPE_YELLOW + 95, SPR_AIRPORT_TYPE_YELLOW + 96 + }, + { SPR_AIRPORT_TYPE_YELLOW + 97, SPR_AIRPORT_TYPE_YELLOW + 98, + SPR_AIRPORT_TYPE_YELLOW + 99, SPR_AIRPORT_TYPE_YELLOW + 100 + }, + { SPR_AIRPORT_TYPE_YELLOW + 101, SPR_AIRPORT_TYPE_YELLOW + 102, + SPR_AIRPORT_TYPE_YELLOW + 103, SPR_AIRPORT_TYPE_YELLOW + 104 + }, + { SPR_AIRPORT_TYPE_YELLOW + 105, SPR_AIRPORT_TYPE_YELLOW + 106, + SPR_AIRPORT_TYPE_YELLOW + 107, SPR_AIRPORT_TYPE_YELLOW + 108 + } + }, + { + { SPR_AIRPORT_TYPE_YELLOW + 109, SPR_AIRPORT_TYPE_YELLOW + 110, + SPR_AIRPORT_TYPE_YELLOW + 111, SPR_AIRPORT_TYPE_YELLOW + 112 + }, + { SPR_AIRPORT_TYPE_YELLOW + 113, SPR_AIRPORT_TYPE_YELLOW + 114, + SPR_AIRPORT_TYPE_YELLOW + 115, SPR_AIRPORT_TYPE_YELLOW + 116 + }, + { SPR_AIRPORT_TYPE_YELLOW + 117, SPR_AIRPORT_TYPE_YELLOW + 118, + SPR_AIRPORT_TYPE_YELLOW + 119, SPR_AIRPORT_TYPE_YELLOW + 120 + }, + { SPR_AIRPORT_TYPE_YELLOW + 121, SPR_AIRPORT_TYPE_YELLOW + 122, + SPR_AIRPORT_TYPE_YELLOW + 123, SPR_AIRPORT_TYPE_YELLOW + 124 + }, + { SPR_AIRPORT_TYPE_YELLOW + 125, SPR_AIRPORT_TYPE_YELLOW + 126, + SPR_AIRPORT_TYPE_YELLOW + 127, SPR_AIRPORT_TYPE_YELLOW + 128 + } + } + }, + { + { SPR_AIRFIELD_WIND_1, SPR_AIRFIELD_WIND_2, SPR_AIRFIELD_WIND_3, SPR_AIRFIELD_WIND_4 }, // wind + { SPR_AIRFIELD_WIND_1, SPR_AIRFIELD_WIND_2, SPR_AIRFIELD_WIND_3, SPR_AIRFIELD_WIND_4 }, + { SPR_AIRFIELD_WIND_1, SPR_AIRFIELD_WIND_2, SPR_AIRFIELD_WIND_3, SPR_AIRFIELD_WIND_4 }, + { SPR_AIRFIELD_WIND_1, SPR_AIRFIELD_WIND_2, SPR_AIRFIELD_WIND_3, SPR_AIRFIELD_WIND_4 } + }, + { SPR_AIRPORT_RADAR_1, SPR_AIRPORT_RADAR_2, SPR_AIRPORT_RADAR_3, SPR_AIRPORT_RADAR_4, // radar + SPR_AIRPORT_RADAR_5, SPR_AIRPORT_RADAR_6, SPR_AIRPORT_RADAR_7, SPR_AIRPORT_RADAR_8, + SPR_AIRPORT_RADAR_9, SPR_AIRPORT_RADAR_A, SPR_AIRPORT_RADAR_B, SPR_AIRPORT_RADAR_C + }, + { + { SPR_AIRPORT_TYPE_YELLOW + 73, SPR_AIRPORT_TYPE_YELLOW + 74, // transmitter + SPR_AIRPORT_TYPE_YELLOW + 75, SPR_AIRPORT_TYPE_YELLOW + 76 + }, + { SPR_AIRPORT_TYPE_YELLOW + 77, SPR_AIRPORT_TYPE_YELLOW + 78, + SPR_AIRPORT_TYPE_YELLOW + 79, SPR_AIRPORT_TYPE_YELLOW + 80 + }, + { SPR_AIRPORT_TYPE_YELLOW + 81, SPR_AIRPORT_TYPE_YELLOW + 82, // snowed transmitter + SPR_AIRPORT_TYPE_YELLOW + 83, SPR_AIRPORT_TYPE_YELLOW + 84 + }, + { SPR_AIRPORT_TYPE_YELLOW + 85, SPR_AIRPORT_TYPE_YELLOW + 86, + SPR_AIRPORT_TYPE_YELLOW + 87, SPR_AIRPORT_TYPE_YELLOW + 88 + } + }, + { SPR_AIRPORT_TYPE_YELLOW + 27, SPR_AIRPORT_TYPE_YELLOW + 28, SPR_AIRPORT_TYPE_YELLOW +29, // runways + SPR_AIRPORT_TYPE_YELLOW + 30, SPR_AIRPORT_TYPE_YELLOW + 31, SPR_AIRPORT_TYPE_YELLOW + 32, + SPR_AIRPORT_TYPE_YELLOW +33, SPR_AIRPORT_TYPE_YELLOW +34, SPR_AIRPORT_TYPE_YELLOW +35, + SPR_AIRPORT_TYPE_YELLOW + 36, SPR_AIRPORT_TYPE_YELLOW + 37, SPR_AIRPORT_TYPE_YELLOW + 38, + SPR_AIRPORT_TYPE_YELLOW + 39, SPR_AIRPORT_TYPE_YELLOW + 40, SPR_AIRPORT_TYPE_YELLOW + 41, + SPR_AIRPORT_TYPE_YELLOW + 42, SPR_AIRPORT_TYPE_YELLOW + 43, SPR_AIRPORT_TYPE_YELLOW + 44, + SPR_AIRPORT_TYPE_YELLOW + 45, SPR_AIRPORT_TYPE_YELLOW + 46, SPR_AIRPORT_TYPE_YELLOW + 47, + SPR_AIRPORT_TYPE_YELLOW + 48, SPR_AIRPORT_TYPE_YELLOW + 49, SPR_AIRPORT_TYPE_YELLOW + 50 + }, + { SPR_AIRPORT_TYPE_YELLOW + 63, SPR_AIRPORT_TYPE_YELLOW + 64, // plane apron, helipad + SPR_AIRPORT_TYPE_YELLOW + 65, SPR_AIRPORT_TYPE_YELLOW + 66, SPR_AIRPORT_TYPE_YELLOW + 67, // 4 heliport rotations + 4 snow heliports + SPR_AIRPORT_TYPE_YELLOW + 68, SPR_AIRPORT_TYPE_YELLOW + 69, SPR_AIRPORT_TYPE_YELLOW + 70, + SPR_AIRPORT_TYPE_YELLOW + 71, SPR_AIRPORT_TYPE_YELLOW + 72 + }, + { SPR_AIRPORT_TYPE_YELLOW + 51, SPR_AIRPORT_TYPE_YELLOW + 52, SPR_AIRPORT_TYPE_YELLOW + 53, + SPR_AIRPORT_TYPE_YELLOW + 54, SPR_AIRPORT_TYPE_YELLOW + 55, SPR_AIRPORT_TYPE_YELLOW + 56, + SPR_AIRPORT_TYPE_YELLOW + 57, SPR_AIRPORT_TYPE_YELLOW + 58, SPR_AIRPORT_TYPE_YELLOW + 59, + SPR_AIRPORT_TYPE_YELLOW + 60, SPR_AIRPORT_TYPE_YELLOW + 61, SPR_AIRPORT_TYPE_YELLOW + 62 + } + }, + + /* GUI sprites */ + { SPR_AIRPORT_TYPE_YELLOW, // add tile to airport + SPR_AIRPORT_TYPE_YELLOW + 1, // build track tile + SPR_AIRPORT_TYPE_YELLOW + 2, // change air type + SPR_AIRPORT_TYPE_YELLOW + 3, // define runway + SPR_AIRPORT_TYPE_YELLOW + 4, // apron + SPR_AIRPORT_TYPE_YELLOW + 5, // helipad + SPR_AIRPORT_TYPE_YELLOW + 6, // heliport + SPR_AIRPORT_TYPE_YELLOW + 7, // hangar + SPR_AIRPORT_TYPE_YELLOW + 8, // build predefined + SPR_AIRPORT_TYPE_YELLOW + 9, // copy + SPR_AIRPORT_TYPE_YELLOW + 10 + }, + + /* cursor sprites */ + { SPR_AIRPORT_TYPE_YELLOW + 11, + SPR_AIRPORT_TYPE_YELLOW + 12, + SPR_AIRPORT_TYPE_YELLOW + 13, + SPR_AIRPORT_TYPE_YELLOW + 14, + SPR_AIRPORT_TYPE_YELLOW + 15, + SPR_AIRPORT_TYPE_YELLOW + 16, + SPR_AIRPORT_TYPE_YELLOW + 17, + SPR_AIRPORT_TYPE_YELLOW + 18, + SPR_AIRPORT_TYPE_YELLOW + 19, + SPR_AIRPORT_TYPE_YELLOW + 20, + SPR_AIRPORT_TYPE_YELLOW + 21 + }, + + /* strings */ + { + STR_AIRTYPE_NAME_YELLOW, + STR_TOOLBAR_AIRPORT_YELLOW_CONSTRUCTION_CAPTION, + STR_TOOLBAR_AIRPORT_YELLOW_CONSTRUCTION_CAPTION, + STR_REPLACE_AIRCRAFT_YELLOW_VEHICLES, + }, + + /* Offset of snow tiles */ + SPR_RAIL_SNOW_OFFSET, + + /* Compatible airtypes */ + AIRTYPES_YELLOW | AIRTYPES_ASPHALT, + + /* fallback_airtype */ + 0, + + /* cost multiplier */ + 8, + + /* maintenance cost multiplier */ + 8, + + /* max speed */ + 80, + + /* Gravel type label. */ + AIRTYPE_LABEL_ASPHALT_YELLOW, + + /* alternate labels */ + AirTypeLabelList(), + + /* map colour */ + 0x0A, + + /* introduction date */ + CalendarTime::INVALID_DATE, + + /* airtypes required for this to be introduced */ + AIRTYPES_NONE, + + /* introduction air types */ + AIRTYPES_YELLOW, + + /* sort order */ + 4 << 4 | 7, + + { nullptr }, + { nullptr }, + + /* Catchment. */ + 8, + + /* Max number of runways. */ + 10, + + /* Min runway length in tiles. */ + 5, + + /* Base noise level. */ + 1, + + /* Runway noise level. */ + 3, + + /* Heliport availability. */ + true, + + /* Build on water. */ + false, + }, +}; + + + +#endif /* AIRTYPES_H */ diff --git a/src/table/engines.h b/src/table/engines.h index e1f42c1c1ed6c..8ad04dfcef7e6 100644 --- a/src/table/engines.h +++ b/src/table/engines.h @@ -542,6 +542,7 @@ static const RailVehicleInfo _orig_rail_vehicle_info[] = { #undef O #undef C #undef R +#undef A #undef V #undef N #undef E @@ -596,62 +597,74 @@ static const ShipVehicleInfo _orig_ship_vehicle_info[] = { * @param g max_speed (1 unit = 8 mph = 12.8 km-ish/h) (is converted to km-ish/h by the macro) * @param h mail_capacity (bags) * @param i passenger_capacity (persons) + * @param j air type */ -#define AVI(a, b, c, d, e, f, g, h, i) { a, b, c, d, e, f, (g * 128) / 10, h, i, 0 } +#define AVI(a, b, c, d, e, f, g, h, i, j) { a, b, c, d, e, f, (g * 128) / 10, h, i, 0, j } #define H AIR_HELI #define P AIR_CTOL #define J AIR_CTOL | AIR_FAST + +#define G AIRTYPE_GRAVEL +#define A AIRTYPE_ASPHALT +#define W AIRTYPE_WATER + static const AircraftVehicleInfo _orig_aircraft_vehicle_info[] = { /* image_index sfx acceleration * | cost_factor | | max_speed * | | running_cost | | mail_capacity * | | | subtype | | | | passenger_capacity - * | | | | | | | | | */ - AVI( 1, 14, 85, P, SND_08_TAKEOFF_PROPELLER, 18, 37, 4, 25 ), // 0 Sampson U52 - AVI( 0, 15, 100, P, SND_08_TAKEOFF_PROPELLER, 20, 37, 8, 65 ), // 1 Coleman Count - AVI( 2, 16, 130, J, SND_09_TAKEOFF_JET, 35, 74, 10, 90 ), // 2 FFP Dart - AVI( 8, 75, 250, J, SND_3B_TAKEOFF_JET_FAST, 50, 181, 20, 100 ), // 3 Yate Haugan - AVI( 5, 15, 98, P, SND_08_TAKEOFF_PROPELLER, 20, 37, 6, 30 ), // 4 Bakewell Cotswald LB-3 - AVI( 6, 18, 240, J, SND_09_TAKEOFF_JET, 40, 74, 30, 200 ), // 5 Bakewell Luckett LB-8 - AVI( 2, 17, 150, P, SND_09_TAKEOFF_JET, 35, 74, 15, 100 ), // 6 Bakewell Luckett LB-9 - AVI( 2, 18, 245, J, SND_09_TAKEOFF_JET, 40, 74, 30, 150 ), // 7 Bakewell Luckett LB80 - AVI( 3, 19, 192, J, SND_09_TAKEOFF_JET, 40, 74, 40, 220 ), // 8 Bakewell Luckett LB-10 - AVI( 3, 20, 190, J, SND_09_TAKEOFF_JET, 40, 74, 25, 230 ), // 9 Bakewell Luckett LB-11 - AVI( 2, 16, 135, J, SND_09_TAKEOFF_JET, 35, 74, 10, 95 ), // 10 Yate Aerospace YAC 1-11 - AVI( 2, 18, 240, J, SND_09_TAKEOFF_JET, 40, 74, 35, 170 ), // 11 Darwin 100 - AVI( 4, 17, 155, J, SND_09_TAKEOFF_JET, 40, 74, 15, 110 ), // 12 Darwin 200 - AVI( 7, 30, 253, J, SND_3D_TAKEOFF_JET_BIG, 40, 74, 50, 300 ), // 13 Darwin 300 - AVI( 4, 18, 210, J, SND_09_TAKEOFF_JET, 40, 74, 25, 200 ), // 14 Darwin 400 - AVI( 4, 19, 220, J, SND_09_TAKEOFF_JET, 40, 74, 25, 240 ), // 15 Darwin 500 - AVI( 4, 27, 230, J, SND_09_TAKEOFF_JET, 40, 74, 40, 260 ), // 16 Darwin 600 - AVI( 3, 25, 225, J, SND_09_TAKEOFF_JET, 40, 74, 35, 240 ), // 17 Guru Galaxy - AVI( 4, 20, 235, J, SND_09_TAKEOFF_JET, 40, 74, 30, 260 ), // 18 Airtaxi A21 - AVI( 4, 19, 220, J, SND_09_TAKEOFF_JET, 40, 74, 25, 210 ), // 19 Airtaxi A31 - AVI( 4, 18, 170, J, SND_09_TAKEOFF_JET, 40, 74, 20, 160 ), // 20 Airtaxi A32 - AVI( 4, 26, 210, J, SND_09_TAKEOFF_JET, 40, 74, 20, 220 ), // 21 Airtaxi A33 - AVI( 6, 16, 125, P, SND_09_TAKEOFF_JET, 50, 74, 10, 80 ), // 22 Yate Aerospace YAe46 - AVI( 2, 17, 145, P, SND_09_TAKEOFF_JET, 40, 74, 10, 85 ), // 23 Dinger 100 - AVI( 11, 16, 130, P, SND_09_TAKEOFF_JET, 40, 74, 10, 75 ), // 24 AirTaxi A34-1000 - AVI( 10, 16, 149, P, SND_09_TAKEOFF_JET, 40, 74, 10, 85 ), // 25 Yate Z-Shuttle - AVI( 15, 17, 170, P, SND_09_TAKEOFF_JET, 40, 74, 18, 65 ), // 26 Kelling K1 - AVI( 12, 18, 210, J, SND_09_TAKEOFF_JET, 40, 74, 25, 110 ), // 27 Kelling K6 - AVI( 13, 20, 230, J, SND_09_TAKEOFF_JET, 40, 74, 60, 180 ), // 28 Kelling K7 - AVI( 14, 21, 220, J, SND_09_TAKEOFF_JET, 40, 74, 65, 150 ), // 29 Darwin 700 - AVI( 16, 19, 160, J, SND_09_TAKEOFF_JET, 40, 181, 45, 85 ), // 30 FFP Hyperdart 2 - AVI( 17, 24, 248, J, SND_3D_TAKEOFF_JET_BIG, 40, 74, 80, 400 ), // 31 Dinger 200 - AVI( 18, 80, 251, J, SND_3B_TAKEOFF_JET_FAST, 50, 181, 45, 130 ), // 32 Dinger 1000 - AVI( 20, 13, 85, P, SND_45_TAKEOFF_PROPELLER_TOYLAND_1, 18, 37, 5, 25 ), // 33 Ploddyphut 100 - AVI( 21, 18, 100, P, SND_46_TAKEOFF_PROPELLER_TOYLAND_2, 20, 37, 9, 60 ), // 34 Ploddyphut 500 - AVI( 22, 25, 140, P, SND_09_TAKEOFF_JET, 40, 74, 12, 90 ), // 35 Flashbang X1 - AVI( 23, 32, 220, J, SND_3D_TAKEOFF_JET_BIG, 40, 74, 40, 200 ), // 36 Juggerplane M1 - AVI( 24, 80, 255, J, SND_3B_TAKEOFF_JET_FAST, 50, 181, 30, 100 ), // 37 Flashbang Wizzer - AVI( 9, 15, 81, H, SND_09_TAKEOFF_JET, 20, 25, 15, 40 ), // 38 Tricario Helicopter - AVI( 19, 17, 77, H, SND_09_TAKEOFF_JET, 20, 40, 20, 55 ), // 39 Guru X2 Helicopter - AVI( 25, 15, 80, H, SND_09_TAKEOFF_JET, 20, 25, 10, 40 ), // 40 Powernaut Helicopter + * | | | | | | | | | air type (gravel, asphalt, water...) + * | | | | | | | | | | */ + AVI( 1, 14, 85, P, SND_08_TAKEOFF_PROPELLER, 18, 37, 4, 25, W ), // 0 Sampson U52 + AVI( 0, 15, 100, P, SND_08_TAKEOFF_PROPELLER, 20, 37, 8, 65, G ), // 1 Coleman Count + AVI( 2, 16, 130, J, SND_09_TAKEOFF_JET, 35, 74, 10, 90, A ), // 2 FFP Dart + AVI( 8, 75, 250, J, SND_3B_TAKEOFF_JET_FAST, 50, 181, 20, 100, A ), // 3 Yate Haugan + AVI( 5, 15, 98, P, SND_08_TAKEOFF_PROPELLER, 20, 37, 6, 30, G ), // 4 Bakewell Cotswald LB-3 + AVI( 6, 18, 240, J, SND_09_TAKEOFF_JET, 40, 74, 30, 200, A ), // 5 Bakewell Luckett LB-8 + AVI( 2, 17, 150, P, SND_09_TAKEOFF_JET, 35, 74, 15, 100, G ), // 6 Bakewell Luckett LB-9 + AVI( 2, 18, 245, J, SND_09_TAKEOFF_JET, 40, 74, 30, 150, A ), // 7 Bakewell Luckett LB80 + AVI( 3, 19, 192, J, SND_09_TAKEOFF_JET, 40, 74, 40, 220, A ), // 8 Bakewell Luckett LB-10 + AVI( 3, 20, 190, J, SND_09_TAKEOFF_JET, 40, 74, 25, 230, A ), // 9 Bakewell Luckett LB-11 + AVI( 2, 16, 135, J, SND_09_TAKEOFF_JET, 35, 74, 10, 95, A ), // 10 Yate Aerospace YAC 1-11 + AVI( 2, 18, 240, J, SND_09_TAKEOFF_JET, 40, 74, 35, 170, A ), // 11 Darwin 100 + AVI( 4, 17, 155, J, SND_09_TAKEOFF_JET, 40, 74, 15, 110, A ), // 12 Darwin 200 + AVI( 7, 30, 253, J, SND_3D_TAKEOFF_JET_BIG, 40, 74, 50, 300, A ), // 13 Darwin 300 + AVI( 4, 18, 210, J, SND_09_TAKEOFF_JET, 40, 74, 25, 200, A ), // 14 Darwin 400 + AVI( 4, 19, 220, J, SND_09_TAKEOFF_JET, 40, 74, 25, 240, A ), // 15 Darwin 500 + AVI( 4, 27, 230, J, SND_09_TAKEOFF_JET, 40, 74, 40, 260, A ), // 16 Darwin 600 + AVI( 3, 25, 225, J, SND_09_TAKEOFF_JET, 40, 74, 35, 240, A ), // 17 Guru Galaxy + AVI( 4, 20, 235, J, SND_09_TAKEOFF_JET, 40, 74, 30, 260, A ), // 18 Airtaxi A21 + AVI( 4, 19, 220, J, SND_09_TAKEOFF_JET, 40, 74, 25, 210, A ), // 19 Airtaxi A31 + AVI( 4, 18, 170, J, SND_09_TAKEOFF_JET, 40, 74, 20, 160, A ), // 20 Airtaxi A32 + AVI( 4, 26, 210, J, SND_09_TAKEOFF_JET, 40, 74, 20, 220, A ), // 21 Airtaxi A33 + AVI( 6, 16, 125, P, SND_09_TAKEOFF_JET, 50, 74, 10, 80, G ), // 22 Yate Aerospace YAe46 + AVI( 2, 17, 145, P, SND_09_TAKEOFF_JET, 40, 74, 10, 85, G ), // 23 Dinger 100 + AVI( 11, 16, 130, P, SND_09_TAKEOFF_JET, 40, 74, 10, 75, G ), // 24 AirTaxi A34-1000 + AVI( 10, 16, 149, P, SND_09_TAKEOFF_JET, 40, 74, 10, 85, G ), // 25 Yate Z-Shuttle + AVI( 15, 17, 170, P, SND_09_TAKEOFF_JET, 40, 74, 18, 65, G ), // 26 Kelling K1 + AVI( 12, 18, 210, J, SND_09_TAKEOFF_JET, 40, 74, 25, 110, A ), // 27 Kelling K6 + AVI( 13, 20, 230, J, SND_09_TAKEOFF_JET, 40, 74, 60, 180, A ), // 28 Kelling K7 + AVI( 14, 21, 220, J, SND_09_TAKEOFF_JET, 40, 74, 65, 150, A ), // 29 Darwin 700 + AVI( 16, 19, 160, J, SND_09_TAKEOFF_JET, 40, 181, 45, 85, A ), // 30 FFP Hyperdart 2 + AVI( 17, 24, 248, J, SND_3D_TAKEOFF_JET_BIG, 40, 74, 80, 400, A ), // 31 Dinger 200 + AVI( 18, 80, 251, J, SND_3B_TAKEOFF_JET_FAST, 50, 181, 45, 130, A ), // 32 Dinger 1000 + AVI( 20, 13, 85, P, SND_45_TAKEOFF_PROPELLER_TOYLAND_1, 18, 37, 5, 25, G ), // 33 Ploddyphut 100 + AVI( 21, 18, 100, P, SND_46_TAKEOFF_PROPELLER_TOYLAND_2, 20, 37, 9, 60, G ), // 34 Ploddyphut 500 + AVI( 22, 25, 140, P, SND_09_TAKEOFF_JET, 40, 74, 12, 90, G ), // 35 Flashbang X1 + AVI( 23, 32, 220, J, SND_3D_TAKEOFF_JET_BIG, 40, 74, 40, 200, A ), // 36 Juggerplane M1 + AVI( 24, 80, 255, J, SND_3B_TAKEOFF_JET_FAST, 50, 181, 30, 100, A ), // 37 Flashbang Wizzer + AVI( 9, 15, 81, H, SND_09_TAKEOFF_JET, 20, 25, 15, 40, W ), // 38 Tricario Helicopter + AVI( 19, 17, 77, H, SND_09_TAKEOFF_JET, 20, 40, 20, 55, G ), // 39 Guru X2 Helicopter + AVI( 25, 15, 80, H, SND_09_TAKEOFF_JET, 20, 25, 10, 40, G ), // 40 Powernaut Helicopter }; #undef J #undef P #undef H + +#undef G +#undef A +#undef W + #undef AVI /** diff --git a/src/table/newgrf_debug_data.h b/src/table/newgrf_debug_data.h index e134d182e2307..445462c8bb0ef 100644 --- a/src/table/newgrf_debug_data.h +++ b/src/table/newgrf_debug_data.h @@ -7,8 +7,10 @@ /** @file newgrf_debug_data.h Data 'tables' for NewGRF debugging. */ +#include "../air_map.h" #include "../newgrf_house.h" #include "../newgrf_engine.h" +#include "../newgrf_airtype.h" #include "../newgrf_roadtype.h" #include "../newgrf_roadstop.h" @@ -497,12 +499,12 @@ static const NICallback _nic_airporttiles[] = { }; class NIHAirportTile : public NIHelper { - bool IsInspectable(uint index) const override { return AirportTileSpec::Get(GetAirportGfx(index))->grf_prop.grffile != nullptr; } + bool IsInspectable(uint index) const override { return AirportTileSpec::GetAirportTileSpec(GetAirportGfx(index))->grf_prop.grffile != nullptr; } uint GetParent(uint index) const override { return GetInspectWindowNumber(GSF_AIRPORTS, GetStationIndex(index)); } const void *GetInstance(uint)const override { return nullptr; } - const void *GetSpec(uint index) const override { return AirportTileSpec::Get(GetAirportGfx(index)); } + const void *GetSpec(uint index) const override { return AirportTileSpec::GetAirportTileSpec(GetAirportGfx(index)); } void SetStringParameters(uint index) const override { this->SetObjectAtStringParameters(STR_STATION_NAME, GetStationIndex(index), index); } - uint32_t GetGRFID(uint index) const override { return (this->IsInspectable(index)) ? AirportTileSpec::Get(GetAirportGfx(index))->grf_prop.grffile->grfid : 0; } + uint32_t GetGRFID(uint index) const override { return (this->IsInspectable(index)) ? AirportTileSpec::GetAirportTileSpec(GetAirportGfx(index))->grf_prop.grffile->grfid : 0; } uint Resolve(uint index, uint var, uint param, bool &avail) const override { @@ -711,6 +713,39 @@ class NIHRoadStop : public NIHelper { } }; +/*** NewGRF air types ***/ + +static const NIVariable _niv_airtypes[] = { + NIV(0x40, "terrain type"), + NIV(0x41, "enhanced tunnels"), + NIV(0x42, "level crossing status"), + NIV(0x43, "construction date"), + NIV(0x44, "town zone"), + NIV_END() +}; + +class NIHAirType : public NIHelper { + bool IsInspectable(uint) const override { return true; } + + uint GetParent(uint index) const override { + return HasAirtypeGfx(index) ? GetInspectWindowNumber(GSF_AIRPORTS, GetStationIndex(index)) : + GetInspectWindowNumber(GSF_AIRPORTTILES, index); + } + + const void *GetInstance(uint) const override { return nullptr; } + const void *GetSpec(uint) const override { return nullptr; } + void SetStringParameters(uint index) const override { this->SetObjectAtStringParameters(STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT_AIR_TYPE, INVALID_STRING_ID, index); } + uint32_t GetGRFID(uint) const override { return 0; } + + uint Resolve(uint index, uint var, uint param, bool &avail) const override + { + /* There is no unique GRFFile for the tile. Multiple GRFs can define different parts of the airtype. + * However, currently the NewGRF Debug GUI does not display variables depending on the GRF (like 0x7F) anyway. */ + AirTypeResolverObject ro(nullptr, index, TCX_NORMAL, ATSG_END); + return ro.GetScope(VSG_SCOPE_SELF)->GetVariable(var, param, avail); + } +}; + static const NIFeature _nif_roadstop = { nullptr, _nic_roadstops, @@ -718,6 +753,13 @@ static const NIFeature _nif_roadstop = { new NIHRoadStop(), }; +static const NIFeature _nif_airtype = { + nullptr, + nullptr, + _niv_airtypes, + new NIHAirType(), +}; + /** Table with all NIFeatures. */ static const NIFeature * const _nifeatures[] = { &_nif_vehicle, // GSF_TRAINS @@ -741,6 +783,7 @@ static const NIFeature * const _nifeatures[] = { &_nif_roadtype, // GSF_ROADTYPES &_nif_tramtype, // GSF_TRAMTYPES &_nif_roadstop, // GSF_ROADSTOPS + &_nif_airtype, // GSF_AIRTYPES &_nif_town, // GSF_FAKE_TOWNS }; static_assert(lengthof(_nifeatures) == GSF_FAKE_END); diff --git a/src/table/settings/game_settings.ini b/src/table/settings/game_settings.ini index 07adda5cc9d50..510651528c81f 100644 --- a/src/table/settings/game_settings.ini +++ b/src/table/settings/game_settings.ini @@ -21,6 +21,10 @@ static bool CheckRoadSide(int32_t &new_value); static bool CheckDynamicEngines(int32_t &new_value); static void StationCatchmentChanged(int32_t new_value); static void MaxVehiclesChanged(int32_t new_value); +static bool CheckDifferentRailRoadTypesReplacements(int32_t &new_value); +static void InvalidateReplacementWindows(int32_t new_value); +static void DepotSettingsChanged(int32_t new_value); +static void ModifyAirportLayout(int32_t new_value); static const SettingVariant _game_settings_table[] = { [post-amble] @@ -103,6 +107,16 @@ def = false str = STR_CONFIG_SETTING_NEVER_EXPIRE_AIRPORTS strhelp = STR_CONFIG_SETTING_NEVER_EXPIRE_AIRPORTS_HELPTEXT +[SDT_BOOL] +var = station.allow_modify_airports +from = SLV_MULTITILE_AIRPORTS +flags = SF_NO_NETWORK +def = false +str = STR_CONFIG_SETTING_ALLOW_CUSTOMIZED_AIRPORTS +strhelp = STR_CONFIG_SETTING_ALLOW_CUSTOMIZED_AIRPORTS_HELPTEXT +post_cb = ModifyAirportLayout +cat = SC_EXPERT + [SDT_VAR] var = station.station_spread type = SLE_UINT8 @@ -145,6 +159,107 @@ str = STR_CONFIG_SETTING_DISTANT_JOIN_STATIONS strhelp = STR_CONFIG_SETTING_DISTANT_JOIN_STATIONS_HELPTEXT post_cb = [](auto) { CloseWindowById(WC_SELECT_STATION, 0); } +[SDT_VAR] +var = depot.rail_depot_types +type = SLE_UINT8 +from = SLV_EXTENDED_DEPOTS +flags = SF_GUI_DROPDOWN +def = 1 +min = 1 +max = 3 +interval = 1 +str = STR_CONFIG_SETTING_RAIL_DEPOT_TYPES +strhelp = STR_CONFIG_SETTING_RAIL_DEPOT_TYPES_HELPTEXT +strval = STR_CONFIG_SETTING_ONLY_STANDARD_DEPOT +post_cb = DepotSettingsChanged +cat = SC_EXPERT + +[SDT_VAR] +var = depot.road_depot_types +type = SLE_UINT8 +from = SLV_EXTENDED_DEPOTS +flags = SF_GUI_DROPDOWN +def = 1 +min = 1 +max = 3 +interval = 1 +str = STR_CONFIG_SETTING_ROAD_DEPOT_TYPES +strhelp = STR_CONFIG_SETTING_ROAD_DEPOT_TYPES_HELPTEXT +strval = STR_CONFIG_SETTING_ONLY_STANDARD_DEPOT +post_cb = DepotSettingsChanged +cat = SC_EXPERT + +[SDT_VAR] +var = depot.water_depot_types +type = SLE_UINT8 +from = SLV_EXTENDED_DEPOTS +flags = SF_GUI_DROPDOWN +def = 1 +min = 1 +max = 3 +interval = 1 +str = STR_CONFIG_SETTING_WATER_DEPOT_TYPES +strhelp = STR_CONFIG_SETTING_WATER_DEPOT_TYPES_HELPTEXT +strval = STR_CONFIG_SETTING_ONLY_STANDARD_DEPOT +post_cb = DepotSettingsChanged +cat = SC_EXPERT + +[SDT_VAR] +var = depot.hangar_types +type = SLE_UINT8 +from = SLV_MULTITILE_AIRPORTS +flags = SF_GUI_DROPDOWN +def = 1 +min = 1 +max = 3 +interval = 1 +str = STR_CONFIG_SETTING_HANGAR_TYPES +strhelp = STR_CONFIG_SETTING_HANGAR_TYPES_HELPTEXT +strval = STR_CONFIG_SETTING_ONLY_STANDARD_DEPOT +post_cb = DepotSettingsChanged +cat = SC_EXPERT + +[SDT_BOOL] +var = depot.distant_join_depots +from = SLV_DEPOT_SPREAD +def = true +str = STR_CONFIG_SETTING_DISTANT_JOIN_DEPOTS +strhelp = STR_CONFIG_SETTING_DISTANT_JOIN_DEPOTS_HELPTEXT +post_cb = [](auto) { CloseWindowByClass(WC_SELECT_DEPOT); } + +[SDT_VAR] +var = depot.depot_spread +type = SLE_UINT8 +from = SLV_DEPOT_SPREAD +def = DEF_MAX_DEPOT_SPREAD +min = 1 +max = 64 +str = STR_CONFIG_SETTING_DEPOT_SPREAD +strhelp = STR_CONFIG_SETTING_DEPOT_SPREAD_HELPTEXT +strval = STR_CONFIG_SETTING_TILE_LENGTH +post_cb = [](auto) { CloseWindowByClass(WC_SELECT_DEPOT); } +cat = SC_BASIC + +[SDT_BOOL] +var = depot.allow_no_comp_railtype_replacements +from = SLV_ALLOW_INCOMPATIBLE_REPLACEMENTS +def = false +str = STR_CONFIG_SETTING_REPLACE_INCOMPATIBLE_RAIL +strhelp = STR_CONFIG_SETTING_REPLACE_INCOMPATIBLE_RAIL_HELPTEXT +pre_cb = CheckDifferentRailRoadTypesReplacements +post_cb = InvalidateReplacementWindows +cat = SC_EXPERT + +[SDT_BOOL] +var = depot.allow_no_comp_roadtype_replacements +from = SLV_ALLOW_INCOMPATIBLE_REPLACEMENTS +def = false +str = STR_CONFIG_SETTING_REPLACE_INCOMPATIBLE_ROAD +strhelp = STR_CONFIG_SETTING_REPLACE_INCOMPATIBLE_ROAD_HELPTEXT +pre_cb = CheckDifferentRailRoadTypesReplacements +post_cb = InvalidateReplacementWindows +cat = SC_EXPERT + [SDT_OMANY] var = vehicle.road_side type = SLE_UINT8 diff --git a/src/table/settings/gui_settings.ini b/src/table/settings/gui_settings.ini index 78dadf1c44041..2530c455fd51b 100644 --- a/src/table/settings/gui_settings.ini +++ b/src/table/settings/gui_settings.ini @@ -482,6 +482,18 @@ strhelp = STR_CONFIG_SETTING_DEFAULT_RAIL_TYPE_HELPTEXT strval = STR_CONFIG_SETTING_DEFAULT_RAIL_TYPE_FIRST cat = SC_BASIC +[SDTC_VAR] +var = gui.default_air_type +type = SLE_UINT8 +flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC | SF_GUI_DROPDOWN +def = 0 +min = 0 +max = 2 +str = STR_CONFIG_SETTING_DEFAULT_AIR_TYPE +strhelp = STR_CONFIG_SETTING_DEFAULT_AIR_TYPE_HELPTEXT +strval = STR_CONFIG_SETTING_DEFAULT_RAIL_TYPE_FIRST +cat = SC_BASIC + [SDTC_VAR] var = gui.signal_gui_mode type = SLE_UINT8 diff --git a/src/table/sprites.h b/src/table/sprites.h index 0ab4f1bc99a62..b32b9ad8d18ab 100644 --- a/src/table/sprites.h +++ b/src/table/sprites.h @@ -315,8 +315,44 @@ static const SpriteID SPR_ROAD_WAYPOINT_X_W = SPR_ROAD_WAYPOINTS_BASE + 2; static const SpriteID SPR_ROAD_WAYPOINT_X_E = SPR_ROAD_WAYPOINTS_BASE + 3; static const uint16_t ROAD_WAYPOINTS_SPRITE_COUNT = 4; +static const SpriteID SPR_AIRTYPE_BASE = SPR_ROAD_WAYPOINTS_BASE + ROAD_WAYPOINTS_SPRITE_COUNT; +static const uint16_t AIRTYPE_ICONS = 11; +static const uint16_t AIRTYPE_CURSORS = 11; +static const uint16_t AIRTYPE_TILES = 107; +static const uint16_t AIRTYPE_EXTRA_TRACKS_YELLOW = 19; +static const uint16_t AIRTYPE_SPRITES = AIRTYPE_ICONS + AIRTYPE_CURSORS + AIRTYPE_TILES; +static const SpriteID SPR_AIRPORT_TYPE_GRAVEL = SPR_AIRTYPE_BASE; +static const SpriteID SPR_AIRPORT_TYPE_ASPHALT = SPR_AIRPORT_TYPE_GRAVEL + AIRTYPE_SPRITES; +static const SpriteID SPR_AIRPORT_TYPE_WATER = SPR_AIRPORT_TYPE_ASPHALT + AIRTYPE_SPRITES; +static const SpriteID SPR_AIRPORT_TYPE_DARK = SPR_AIRPORT_TYPE_WATER + AIRTYPE_SPRITES; +static const SpriteID SPR_AIRPORT_TYPE_YELLOW = SPR_AIRPORT_TYPE_DARK + AIRTYPE_SPRITES; + +static const uint16_t AIRPORT_SPRITES_GROUND = 0; +static const uint16_t AIRPORT_SPRITES_OFFSET_RUNWAYS = 5; +static const uint16_t AIRPORT_SPRITES_OFFSET_RUNWAYS_START = 8; +static const uint16_t AIRPORT_SPRITES_OFFSET_RUNWAYS_END = 16; +static const uint16_t AIRPORT_SPRITES_OFFSET_RUNWAYS_DONT_ALLOW_LANDING = 4; +static const uint16_t AIRPORT_SPRITES_OFFSET_HANGARS = 29; +static const uint16_t AIRPORT_SPRITES_OFFSET_TERMINALS = 41; +static const uint16_t AIRPORT_TILES_OFFSET_TERMINALS = 33; +static const uint16_t AIRTYPE_SPRITE_COUNT = AIRTYPE_SPRITES * 5 + AIRTYPE_EXTRA_TRACKS_YELLOW; + +/** Airport preview sprites */ +static const SpriteID SPR_AIRTYPE_PREVIEW_BASE = SPR_AIRTYPE_BASE + AIRTYPE_SPRITE_COUNT; +static const SpriteID SPR_AIRTYPE_PREVIEW_SMALL = SPR_AIRTYPE_PREVIEW_BASE; +static const SpriteID SPR_AIRTYPE_PREVIEW_LARGE = SPR_AIRTYPE_PREVIEW_BASE + 1 * 4; +static const SpriteID SPR_AIRTYPE_PREVIEW_HELIPORT = SPR_AIRTYPE_PREVIEW_BASE + 2 * 4; +static const SpriteID SPR_AIRTYPE_PREVIEW_METROPOLITAN = SPR_AIRTYPE_PREVIEW_BASE + 3 * 4; +static const SpriteID SPR_AIRTYPE_PREVIEW_INTERNATIONAL = SPR_AIRTYPE_PREVIEW_BASE + 4 * 4; +static const SpriteID SPR_AIRTYPE_PREVIEW_COMMUTER = SPR_AIRTYPE_PREVIEW_BASE + 5 * 4; +static const SpriteID SPR_AIRTYPE_PREVIEW_HELIDEPOT = SPR_AIRTYPE_PREVIEW_BASE + 6 * 4; +static const SpriteID SPR_AIRTYPE_PREVIEW_INTERCONTINENTAL = SPR_AIRTYPE_PREVIEW_BASE + 7 * 4; +static const SpriteID SPR_AIRTYPE_PREVIEW_HELISTATION = SPR_AIRTYPE_PREVIEW_BASE + 8 * 4; +static const uint16_t SPR_AIRTYPE_PREVIEW_COUNT = 9 * 4; +static const uint16_t AIRTYPE_SPRITE_TOTAL_COUNT = AIRTYPE_SPRITE_COUNT + SPR_AIRTYPE_PREVIEW_COUNT; + /* From where can we start putting NewGRFs? */ -static const SpriteID SPR_NEWGRFS_BASE = SPR_ROAD_WAYPOINTS_BASE + ROAD_WAYPOINTS_SPRITE_COUNT; +static const SpriteID SPR_NEWGRFS_BASE = SPR_AIRPORT_PREVIEW_BASE + SPR_AIRPORT_PREVIEW_COUNT; /* Manager face sprites */ static const SpriteID SPR_GRADIENT = 874; // background gradient behind manager face diff --git a/src/table/station_land.h b/src/table/station_land.h index fc47de8d77810..9d5d62668e76e 100644 --- a/src/table/station_land.h +++ b/src/table/station_land.h @@ -71,13 +71,13 @@ static const DrawTileSeqStruct _station_display_datas_1[] = { }; static const DrawTileSeqStruct _station_display_datas_2[] = { - TILE_SEQ_LINE( 0, 0, 0, 16, 5, 15, SPR_RAIL_PLATFORM_BUILDING_X | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_LINE( 0, 0, 0, 16, 5, 2, SPR_RAIL_PLATFORM_BUILDING_X | (1U << PALETTE_MODIFIER_COLOUR)) TILE_SEQ_LINE( 0, 11, 0, 16, 5, 2, SPR_RAIL_PLATFORM_X_FRONT | (1U << PALETTE_MODIFIER_COLOUR)) TILE_SEQ_END() }; static const DrawTileSeqStruct _station_display_datas_3[] = { - TILE_SEQ_LINE( 0, 0, 0, 5, 16, 15, SPR_RAIL_PLATFORM_BUILDING_Y | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_LINE( 0, 0, 0, 5, 16, 2, SPR_RAIL_PLATFORM_BUILDING_Y | (1U << PALETTE_MODIFIER_COLOUR)) TILE_SEQ_LINE(11, 0, 0, 5, 16, 2, SPR_RAIL_PLATFORM_Y_FRONT | (1U << PALETTE_MODIFIER_COLOUR)) TILE_SEQ_END() }; @@ -789,6 +789,400 @@ static const DrawTileSeqStruct _station_display_datas_waypoint_Y[] = { TILE_SEQ_END() }; +static const DrawTileSeqStruct _airtype_display_datas_terminal_c_2[] = { + TILE_SEQ_LINE( 0, 0, 0, 15, 15, 30, SPR_AIRFIELD_TERM_C_BUILD | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_flag_1[] = { + TILE_SEQ_LINE( 4, 11, 0, 1, 1, 20, SPR_AIRFIELD_WIND_1 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_flag_2[] = { + TILE_SEQ_LINE( 4, 11, 0, 1, 1, 20, SPR_AIRFIELD_WIND_2 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_flag_3[] = { + TILE_SEQ_LINE( 4, 11, 0, 1, 1, 20, SPR_AIRFIELD_WIND_3 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_flag_4[] = { + TILE_SEQ_LINE( 4, 11, 0, 1, 1, 20, SPR_AIRFIELD_WIND_4 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_flag_1_NE[] = { + TILE_SEQ_LINE( 4, 11, 0, 1, 1, 20, SPR_AIRFIELD_WIND_1 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_flag_2_NE[] = { + TILE_SEQ_LINE( 4, 11, 0, 1, 1, 20, SPR_AIRFIELD_WIND_2 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_flag_3_NE[] = { + TILE_SEQ_LINE( 4, 11, 0, 1, 1, 20, SPR_AIRFIELD_WIND_3 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_flag_4_NE[] = { + TILE_SEQ_LINE( 4, 11, 0, 1, 1, 20, SPR_AIRFIELD_WIND_4 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_flag_1_SE[] = { + TILE_SEQ_LINE( 14, 12, 0, 1, 1, 20, SPR_AIRFIELD_WIND_1 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_flag_2_SE[] = { + TILE_SEQ_LINE( 14, 12, 0, 1, 1, 20, SPR_AIRFIELD_WIND_2 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_flag_3_SE[] = { + TILE_SEQ_LINE( 14, 12, 0, 1, 1, 20, SPR_AIRFIELD_WIND_3 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_flag_4_SE[] = { + TILE_SEQ_LINE( 14, 12, 0, 1, 1, 20, SPR_AIRFIELD_WIND_4 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_flag_1_SW[] = { + TILE_SEQ_LINE( 14, 5, 0, 1, 1, 20, SPR_AIRFIELD_WIND_1 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_flag_2_SW[] = { + TILE_SEQ_LINE( 14, 5, 0, 1, 1, 20, SPR_AIRFIELD_WIND_2 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_flag_3_SW[] = { + TILE_SEQ_LINE( 14, 5, 0, 1, 1, 20, SPR_AIRFIELD_WIND_3 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_flag_4_SW[] = { + TILE_SEQ_LINE( 14, 5, 0, 1, 1, 20, SPR_AIRFIELD_WIND_4 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_flag_1_NW[] = { + TILE_SEQ_LINE( 6, 3, 0, 1, 1, 20, SPR_AIRFIELD_WIND_1 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_flag_2_NW[] = { + TILE_SEQ_LINE( 6, 3, 0, 1, 1, 20, SPR_AIRFIELD_WIND_2 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_flag_3_NW[] = { + TILE_SEQ_LINE( 6, 3, 0, 1, 1, 20, SPR_AIRFIELD_WIND_3 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_flag_4_NW[] = { + TILE_SEQ_LINE( 6, 3, 0, 1, 1, 20, SPR_AIRFIELD_WIND_4 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_small_depot_se[] = { + TILE_SEQ_LINE(14, 0, 0, 2, 17, 28, SPR_AIRFIELD_HANGAR_FRONT | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_LINE( 0, 0, 0, 2, 17, 28, SPR_AIRFIELD_HANGAR_REAR | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_heliport[] = { + TILE_SEQ_LINE( 0, 0, 0, 16, 16, 60, 43 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +/* control tower without fence */ +static const DrawTileSeqStruct _airtype_display_tower_1[] = { + TILE_SEQ_LINE( 7, 7, 0, 2, 2, 70, 59 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +/* control tower without fence */ +static const DrawTileSeqStruct _airtype_display_tower_2[] = { + TILE_SEQ_LINE( 7, 7, 0, 2, 2, 70, 60 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +/* control tower without fence */ +static const DrawTileSeqStruct _airtype_display_tower_3[] = { + TILE_SEQ_LINE( 7, 7, 0, 2, 2, 70, 61 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +/* control tower without fence */ +static const DrawTileSeqStruct _airtype_display_tower_4[] = { + TILE_SEQ_LINE( 7, 7, 0, 2, 2, 70, 62 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +/* control tower without fence */ +static const DrawTileSeqStruct _airtype_display_transmitter_1[] = { + TILE_SEQ_LINE( 7, 7, 0, 2, 2, 70, 51) + TILE_SEQ_END() +}; + +/* control tower without fence */ +static const DrawTileSeqStruct _airtype_display_transmitter_2[] = { + TILE_SEQ_LINE( 7, 7, 0, 2, 2, 70, 52) + TILE_SEQ_END() +}; + +/* control tower without fence */ +static const DrawTileSeqStruct _airtype_display_transmitter_3[] = { + TILE_SEQ_LINE( 7, 7, 0, 2, 2, 70, 53) + TILE_SEQ_END() +}; + +/* control tower without fence */ +static const DrawTileSeqStruct _airtype_display_transmitter_4[] = { + TILE_SEQ_LINE( 7, 7, 0, 2, 2, 70, 54) + TILE_SEQ_END() +}; + +/* turning radar -- needs 12 tiles + *BEGIN */ +static const DrawTileSeqStruct _airtype_display_radar_1[] = { + TILE_SEQ_LINE(7, 7, 0, 2, 2, 8, SPR_AIRPORT_RADAR_1) // turning radar + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_radar_2[] = { + TILE_SEQ_LINE(7, 7, 0, 2, 2, 8, SPR_AIRPORT_RADAR_2) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_radar_3[] = { + TILE_SEQ_LINE(7, 7, 0, 2, 2, 8, SPR_AIRPORT_RADAR_3) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_radar_4[] = { + TILE_SEQ_LINE(7, 7, 0, 2, 2, 8, SPR_AIRPORT_RADAR_4) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_radar_5[] = { + TILE_SEQ_LINE(7, 7, 0, 2, 2, 8, SPR_AIRPORT_RADAR_5) + TILE_SEQ_END() +}; + +/* END */ +static const DrawTileSeqStruct _airtype_display_radar_6[] = { + TILE_SEQ_LINE(7, 7, 0, 2, 2, 8, SPR_AIRPORT_RADAR_6) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_radar_7[] = { + TILE_SEQ_LINE(7, 7, 0, 2, 2, 8, SPR_AIRPORT_RADAR_7) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_radar_8[] = { + TILE_SEQ_LINE(7, 7, 0, 2, 2, 8, SPR_AIRPORT_RADAR_8) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_radar_9[] = { + TILE_SEQ_LINE(7, 7, 0, 2, 2, 8, SPR_AIRPORT_RADAR_9) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_radar_10[] = { + TILE_SEQ_LINE(7, 7, 0, 2, 2, 8, SPR_AIRPORT_RADAR_A) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_radar_11[] = { + TILE_SEQ_LINE(7, 7, 0, 2, 2, 8, SPR_AIRPORT_RADAR_B) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_radar_12[] = { + TILE_SEQ_LINE(7, 7, 0, 2, 2, 8, SPR_AIRPORT_RADAR_C) + TILE_SEQ_END() +}; + +/* END */ +/* plane apron */ +static const DrawTileSeqStruct _airtype_display_apron[] = { + TILE_SEQ_GROUND(0, 0, 0, 41) + TILE_SEQ_END() +}; + +/* helipad for continental airport */ +static const DrawTileSeqStruct _airtype_display_helipad[] = { + TILE_SEQ_GROUND(0, 0, 0, 42) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_building_1_ne[] = { + TILE_SEQ_LINE( 0, 0, 0, 15, 15, 30, 67 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_building_1_se[] = { + TILE_SEQ_LINE( 0, 0, 0, 15, 15, 30, 68 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_building_1_sw[] = { + TILE_SEQ_LINE( 0, 0, 0, 15, 15, 30, 69 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_building_1_nw[] = { + TILE_SEQ_LINE( 0, 0, 0, 15, 15, 30, 70 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_building_2_ne[] = { + TILE_SEQ_LINE( 0, 0, 0, 15, 15, 30, 71 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_building_2_se[] = { + TILE_SEQ_LINE( 0, 0, 0, 15, 15, 30, 72 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_building_2_sw[] = { + TILE_SEQ_LINE( 0, 0, 0, 15, 15, 30, 73 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_building_2_nw[] = { + TILE_SEQ_LINE( 0, 0, 0, 15, 15, 30, 74 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_building_3_ne[] = { + TILE_SEQ_LINE( 0, 0, 0, 15, 15, 30, 75 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_building_3_se[] = { + TILE_SEQ_LINE( 0, 0, 0, 15, 15, 30, 76 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_building_3_sw[] = { + TILE_SEQ_LINE( 0, 0, 0, 15, 15, 30, 77 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_building_3_nw[] = { + TILE_SEQ_LINE( 0, 0, 0, 15, 15, 30, 78 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_building_flat_ne[] = { + TILE_SEQ_LINE( 0, 0, 0, 15, 15, 30, 79 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_building_flat_se[] = { + TILE_SEQ_LINE( 0, 0, 0, 15, 15, 30, 80 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_building_flat_sw[] = { + TILE_SEQ_LINE( 0, 0, 0, 15, 15, 30, 81 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_building_flat_nw[] = { + TILE_SEQ_LINE( 0, 0, 0, 15, 15, 30, 82 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_building_terminal_ne[] = { + TILE_SEQ_LINE( 0, 0, 0, 15, 15, 30, 83 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_building_terminal_se[] = { + TILE_SEQ_LINE( 0, 0, 0, 15, 15, 30, 84 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_building_terminal_sw[] = { + TILE_SEQ_LINE( 0, 0, 0, 15, 15, 30, 85 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_building_terminal_nw[] = { + TILE_SEQ_LINE( 0, 0, 0, 15, 15, 30, 86 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_hangar_se[] = { + TILE_SEQ_GROUND( 0, 0, 0, 37) + TILE_SEQ_LINE(14, 0, 0, 2, 17, 28, 29 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_LINE( 0, 0, 0, 2, 17, 28, 39 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +/* West facing hangar */ +static const DrawTileSeqStruct _airtype_display_hangar_sw[] = { + TILE_SEQ_GROUND( 0, 0, 0, 38) + TILE_SEQ_LINE( 0, 14, 0, 16, 2, 28, 30 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_LINE( 0, 0, 0, 16, 2, 28, 40 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +/* North facing hangar */ +static const DrawTileSeqStruct _airtype_display_hangar_nw[] = { + TILE_SEQ_LINE(0, 0, 0, 16, 16, 28, 31 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +/* East facing hangar */ +static const DrawTileSeqStruct _airtype_display_hangar_ne[] = { + TILE_SEQ_LINE(0, 0, 0, 16, 16, 28, 32 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +static const DrawTileSeqStruct _airtype_display_hangar_se_snow[] = { + TILE_SEQ_GROUND( 0, 0, 0, 37) + TILE_SEQ_LINE(14, 0, 0, 2, 17, 28, 33 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_LINE( 0, 0, 0, 2, 17, 28, 39 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +/* West facing hangar */ +static const DrawTileSeqStruct _airtype_display_hangar_sw_snow[] = { + TILE_SEQ_GROUND( 0, 0, 0, 38) + TILE_SEQ_LINE( 0, 14, 0, 16, 2, 28, 34 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_LINE( 0, 0, 0, 16, 2, 28, 40 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +/* North facing hangar */ +static const DrawTileSeqStruct _airtype_display_hangar_nw_snow[] = { + TILE_SEQ_LINE(0, 0, 0, 16, 16, 28, 35 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +/* East facing hangar */ +static const DrawTileSeqStruct _airtype_display_hangar_ne_snow[] = { + TILE_SEQ_LINE(0, 0, 0, 16, 16, 28, 36 | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + #undef TILE_SEQ_END #undef TILE_SEQ_LINE #undef TILE_SEQ_LINE_PAL @@ -1006,6 +1400,205 @@ static const DrawTileSprites _station_display_datas_waypoint[] = { TILE_SPRITE_LINE(SPR_RAIL_TRACK_Y, _station_display_datas_waypoint_Y) }; +static const DrawTileSprites _airtype_display_datas_hangars[] = { + TILE_SPRITE_LINE(0, _airtype_display_hangar_ne) // DEPOT_NE + TILE_SPRITE_LINE(0, _airtype_display_hangar_se) // DEPOT_SE + TILE_SPRITE_LINE(0, _airtype_display_hangar_sw) // DEPOT_SW + TILE_SPRITE_LINE(0, _airtype_display_hangar_nw) // DEPOT_NW + TILE_SPRITE_LINE(0, _airtype_display_hangar_ne_snow) // DEPOT_NE + TILE_SPRITE_LINE(0, _airtype_display_hangar_se_snow) // DEPOT_SE + TILE_SPRITE_LINE(0, _airtype_display_hangar_sw_snow) // DEPOT_SW + TILE_SPRITE_LINE(0, _airtype_display_hangar_nw_snow) // DEPOT_NW +}; + +static const DrawTileSprites _airtype_display_datas_oilrig[] = { + TILE_SPRITE_LINE(SPR_FLAT_WATER_TILE, _station_display_nothing) +}; + +static const DrawTileSprites _airtype_display_datas_aprons[] = { + TILE_SPRITE_LINE(0, _airtype_display_apron) // plane apron + TILE_SPRITE_LINE(0, _airtype_display_helipad) // helipad + TILE_SPRITE_LINE(0, _airtype_display_heliport) // heliport + TILE_SPRITE_LINE(SPR_FLAT_WATER_TILE, _station_display_nothing) // built-in heliport unused +}; + +extern const DrawTileSprites _airtype_display_datas_transmitter[] = { + TILE_SPRITE_LINE(0, _airtype_display_transmitter_1) + TILE_SPRITE_LINE(0, _airtype_display_transmitter_2) + TILE_SPRITE_LINE(0, _airtype_display_transmitter_3) + TILE_SPRITE_LINE(0, _airtype_display_transmitter_4) +}; + +extern const DrawTileSprites _airtype_display_datas_tower[] = { + TILE_SPRITE_LINE(0, _airtype_display_tower_1) + TILE_SPRITE_LINE(0, _airtype_display_tower_2) + TILE_SPRITE_LINE(0, _airtype_display_tower_3) + TILE_SPRITE_LINE(0, _airtype_display_tower_4) +}; + +extern const DrawTileSprites _airtype_display_datas_airport[] = { + TILE_SPRITE_LINE(0, _airtype_display_building_1_ne) // building 1 + TILE_SPRITE_LINE(0, _airtype_display_building_1_se) // + TILE_SPRITE_LINE(0, _airtype_display_building_1_sw) // + TILE_SPRITE_LINE(0, _airtype_display_building_1_nw) // + TILE_SPRITE_LINE(0, _airtype_display_building_2_ne) // building 2 + TILE_SPRITE_LINE(0, _airtype_display_building_2_se) // + TILE_SPRITE_LINE(0, _airtype_display_building_2_sw) // + TILE_SPRITE_LINE(0, _airtype_display_building_2_nw) // + TILE_SPRITE_LINE(0, _airtype_display_building_3_ne) // building 3 + TILE_SPRITE_LINE(0, _airtype_display_building_3_se) // + TILE_SPRITE_LINE(0, _airtype_display_building_3_sw) // + TILE_SPRITE_LINE(0, _airtype_display_building_3_nw) // + TILE_SPRITE_LINE(0, _airtype_display_building_flat_ne) // flat building + TILE_SPRITE_LINE(0, _airtype_display_building_flat_se) // + TILE_SPRITE_LINE(0, _airtype_display_building_flat_sw) // + TILE_SPRITE_LINE(0, _airtype_display_building_flat_nw) // + TILE_SPRITE_LINE(0, _airtype_display_building_terminal_ne) // terminal building + TILE_SPRITE_LINE(0, _airtype_display_building_terminal_se) // + TILE_SPRITE_LINE(0, _airtype_display_building_terminal_sw) // + TILE_SPRITE_LINE(0, _airtype_display_building_terminal_nw) // + TILE_SPRITE_NULL() // flags + TILE_SPRITE_NULL() // + TILE_SPRITE_NULL() // + TILE_SPRITE_NULL() // + TILE_SPRITE_LINE(0, _station_display_nothing) // transmitter + TILE_SPRITE_LINE(0, _station_display_nothing) // + TILE_SPRITE_LINE(0, _station_display_nothing) // + TILE_SPRITE_LINE(0, _station_display_nothing) // + TILE_SPRITE_LINE(0, _station_display_nothing) // tower + TILE_SPRITE_LINE(0, _station_display_nothing) // + TILE_SPRITE_LINE(0, _station_display_nothing) // + TILE_SPRITE_LINE(0, _station_display_nothing) // + TILE_SPRITE_NULL() // radar + TILE_SPRITE_NULL() // + TILE_SPRITE_NULL() // + TILE_SPRITE_NULL() // + TILE_SPRITE_LINE(0, _station_display_nothing) // pier + TILE_SPRITE_LINE(0, _station_display_nothing) // + TILE_SPRITE_LINE(0, _station_display_nothing) // + TILE_SPRITE_LINE(0, _station_display_nothing) // + TILE_SPRITE_LINE(0, _station_display_nothing) // empty + TILE_SPRITE_LINE(0, _station_display_nothing) // + TILE_SPRITE_LINE(0, _station_display_nothing) // + TILE_SPRITE_LINE(0, _station_display_nothing) // +}; + +extern const DrawTileSprites _airtype_display_datas_radar[] = { + TILE_SPRITE_LINE(SPR_AIRPORT_APRON, _airtype_display_radar_1) // APT_RADAR_FENCE_SW + TILE_SPRITE_LINE(SPR_AIRPORT_APRON, _airtype_display_radar_2) + TILE_SPRITE_LINE(SPR_AIRPORT_APRON, _airtype_display_radar_3) + TILE_SPRITE_LINE(SPR_AIRPORT_APRON, _airtype_display_radar_4) + TILE_SPRITE_LINE(SPR_AIRPORT_APRON, _airtype_display_radar_5) + TILE_SPRITE_LINE(SPR_AIRPORT_APRON, _airtype_display_radar_6) + TILE_SPRITE_LINE(SPR_AIRPORT_APRON, _airtype_display_radar_7) + TILE_SPRITE_LINE(SPR_AIRPORT_APRON, _airtype_display_radar_8) + TILE_SPRITE_LINE(SPR_AIRPORT_APRON, _airtype_display_radar_9) + TILE_SPRITE_LINE(SPR_AIRPORT_APRON, _airtype_display_radar_10) + TILE_SPRITE_LINE(SPR_AIRPORT_APRON, _airtype_display_radar_11) + TILE_SPRITE_LINE(SPR_AIRPORT_APRON, _airtype_display_radar_12) +}; + +extern const DrawTileSprites _airtype_display_datas_flag_NE[] = { + TILE_SPRITE_LINE(SPR_FLAT_GRASS_TILE, _airtype_display_flag_1_NE) // FLAG_2 + TILE_SPRITE_LINE(SPR_FLAT_GRASS_TILE, _airtype_display_flag_2_NE) + TILE_SPRITE_LINE(SPR_FLAT_GRASS_TILE, _airtype_display_flag_3_NE) + TILE_SPRITE_LINE(SPR_FLAT_GRASS_TILE, _airtype_display_flag_4_NE) +}; + +extern const DrawTileSprites _airtype_display_datas_flag_SE[] = { + TILE_SPRITE_LINE(SPR_FLAT_GRASS_TILE, _airtype_display_flag_1_SE) // FLAG_2 + TILE_SPRITE_LINE(SPR_FLAT_GRASS_TILE, _airtype_display_flag_2_SE) + TILE_SPRITE_LINE(SPR_FLAT_GRASS_TILE, _airtype_display_flag_3_SE) + TILE_SPRITE_LINE(SPR_FLAT_GRASS_TILE, _airtype_display_flag_4_SE) +}; + +extern const DrawTileSprites _airtype_display_datas_flag_SW[] = { + TILE_SPRITE_LINE(SPR_FLAT_GRASS_TILE, _airtype_display_flag_1_SW) // FLAG_2 + TILE_SPRITE_LINE(SPR_FLAT_GRASS_TILE, _airtype_display_flag_2_SW) + TILE_SPRITE_LINE(SPR_FLAT_GRASS_TILE, _airtype_display_flag_3_SW) + TILE_SPRITE_LINE(SPR_FLAT_GRASS_TILE, _airtype_display_flag_4_SW) +}; + +extern const DrawTileSprites _airtype_display_datas_flag_NW[] = { + TILE_SPRITE_LINE(SPR_FLAT_GRASS_TILE, _airtype_display_flag_1_NW) // FLAG_2 + TILE_SPRITE_LINE(SPR_FLAT_GRASS_TILE, _airtype_display_flag_2_NW) + TILE_SPRITE_LINE(SPR_FLAT_GRASS_TILE, _airtype_display_flag_3_NW) + TILE_SPRITE_LINE(SPR_FLAT_GRASS_TILE, _airtype_display_flag_4_NW) +}; + +static const DrawTileSprites *_airtype_display_datas_flags[] = { + _airtype_display_datas_flag_NE, + _airtype_display_datas_flag_SE, + _airtype_display_datas_flag_SW, + _airtype_display_datas_flag_NW +}; + +extern const DrawTileSprites _airport_hangars[] = { + TILE_SPRITE_LINE(38, _airtype_display_hangar_ne) + TILE_SPRITE_LINE(37, _airtype_display_hangar_se) + TILE_SPRITE_LINE(38, _airtype_display_hangar_sw) + TILE_SPRITE_LINE(37, _airtype_display_hangar_nw) +}; + +extern const DrawTileSprites _airport_heliports[] = { + TILE_SPRITE_LINE(0, _airtype_display_heliport) +}; + +extern const DrawTileSprites _airport_infra_no_catchment[] = { + TILE_SPRITE_LINE(0, _airtype_display_heliport) +}; + +extern const DrawTileSprites _airport_infra_with_catchment[] = { + TILE_SPRITE_LINE(0, _airtype_display_heliport) +}; + +extern const DrawTileSprites _airtype_display_datas[] = { + TILE_SPRITE_LINE(0, _airtype_display_building_1_ne) // building 1 + TILE_SPRITE_LINE(0, _airtype_display_building_1_se) // + TILE_SPRITE_LINE(0, _airtype_display_building_1_sw) // + TILE_SPRITE_LINE(0, _airtype_display_building_1_nw) // + TILE_SPRITE_LINE(0, _airtype_display_building_2_ne) // building 2 + TILE_SPRITE_LINE(0, _airtype_display_building_2_se) // + TILE_SPRITE_LINE(0, _airtype_display_building_2_sw) // + TILE_SPRITE_LINE(0, _airtype_display_building_2_nw) // + TILE_SPRITE_LINE(0, _airtype_display_building_3_ne) // building 3 + TILE_SPRITE_LINE(0, _airtype_display_building_3_se) // + TILE_SPRITE_LINE(0, _airtype_display_building_3_sw) // + TILE_SPRITE_LINE(0, _airtype_display_building_3_nw) // + TILE_SPRITE_LINE(0, _airtype_display_building_flat_ne) // flat building + TILE_SPRITE_LINE(0, _airtype_display_building_flat_se) // + TILE_SPRITE_LINE(0, _airtype_display_building_flat_sw) // + TILE_SPRITE_LINE(0, _airtype_display_building_flat_nw) // + TILE_SPRITE_LINE(0, _airtype_display_building_terminal_ne) // terminal building + TILE_SPRITE_LINE(0, _airtype_display_building_terminal_se) // + TILE_SPRITE_LINE(0, _airtype_display_building_terminal_sw) // + TILE_SPRITE_LINE(0, _airtype_display_building_terminal_nw) // + TILE_SPRITE_NULL() // flags + TILE_SPRITE_NULL() // + TILE_SPRITE_NULL() // + TILE_SPRITE_NULL() // + TILE_SPRITE_LINE(0, _station_display_nothing) // transmitter + TILE_SPRITE_LINE(0, _station_display_nothing) // + TILE_SPRITE_LINE(0, _station_display_nothing) // + TILE_SPRITE_LINE(0, _station_display_nothing) // + TILE_SPRITE_LINE(0, _station_display_nothing) // tower + TILE_SPRITE_LINE(0, _station_display_nothing) // + TILE_SPRITE_LINE(0, _station_display_nothing) // + TILE_SPRITE_LINE(0, _station_display_nothing) // + TILE_SPRITE_NULL() // radar + TILE_SPRITE_NULL() // + TILE_SPRITE_NULL() // + TILE_SPRITE_NULL() // + TILE_SPRITE_LINE(0, _station_display_nothing) // pier + TILE_SPRITE_LINE(0, _station_display_nothing) // + TILE_SPRITE_LINE(0, _station_display_nothing) // + TILE_SPRITE_LINE(0, _station_display_nothing) // + TILE_SPRITE_LINE(0, _station_display_nothing) // empty + TILE_SPRITE_LINE(0, _station_display_nothing) // + TILE_SPRITE_LINE(0, _station_display_nothing) // + TILE_SPRITE_LINE(0, _station_display_nothing) // +}; + #undef TILE_SPRITE_LINE #undef TILE_SPRITE_NULL @@ -1024,3 +1617,4 @@ static const DrawTileSprites * const _station_display_datas[] = { _station_display_datas_waypoint, _station_display_datas_road_waypoint, }; + diff --git a/src/table/track_land.h b/src/table/track_land.h index 70ea770bfed4e..d8275743ed2cf 100644 --- a/src/table/track_land.h +++ b/src/table/track_land.h @@ -33,18 +33,18 @@ static const DrawTileSeqStruct _depot_gfx_NW[] = { TILE_SEQ_END() }; -static const DrawTileSprites _depot_gfx_table[] = { +static const DrawTileSprites _depot_gfx_gui_table[] = { { {SPR_FLAT_GRASS_TILE, PAL_NONE}, _depot_gfx_NE }, { {SPR_RAIL_TRACK_Y, PAL_NONE}, _depot_gfx_SE }, { {SPR_RAIL_TRACK_X, PAL_NONE}, _depot_gfx_SW }, { {SPR_FLAT_GRASS_TILE, PAL_NONE}, _depot_gfx_NW } }; -static const DrawTileSprites _depot_invisible_gfx_table[] = { - { {SPR_RAIL_TRACK_X, PAL_NONE}, _depot_gfx_NE }, - { {SPR_RAIL_TRACK_Y, PAL_NONE}, _depot_gfx_SE }, - { {SPR_RAIL_TRACK_X, PAL_NONE}, _depot_gfx_SW }, - { {SPR_RAIL_TRACK_Y, PAL_NONE}, _depot_gfx_NW } +static const DrawTileSprites _depot_gfx_table[] = { + { {SPR_FLAT_GRASS_TILE, PAL_NONE}, _depot_gfx_NE }, + { {SPR_FLAT_GRASS_TILE, PAL_NONE}, _depot_gfx_SE }, + { {SPR_FLAT_GRASS_TILE, PAL_NONE}, _depot_gfx_SW }, + { {SPR_FLAT_GRASS_TILE, PAL_NONE}, _depot_gfx_NW } }; #undef TILE_SEQ_LINE diff --git a/src/tile_cmd.h b/src/tile_cmd.h index 88f36fef86643..d81c031f6a718 100644 --- a/src/tile_cmd.h +++ b/src/tile_cmd.h @@ -19,9 +19,10 @@ /** The returned bits of VehicleEnterTile. */ enum VehicleEnterTileStatus { - VETS_ENTERED_STATION = 1, ///< The vehicle entered a station - VETS_ENTERED_WORMHOLE = 2, ///< The vehicle either entered a bridge, tunnel or depot tile (this includes the last tile of the bridge/tunnel) - VETS_CANNOT_ENTER = 3, ///< The vehicle cannot enter the tile + VETS_ENTERED_STATION = 1, ///< The vehicle entered a station. + VETS_ENTERED_WORMHOLE = 2, ///< The vehicle either entered a bridge, tunnel or depot tile (this includes the last tile of the bridge/tunnel). + VETS_CANNOT_ENTER = 3, ///< The vehicle cannot enter the tile. + VETS_ENTERED_DEPOT_PLATFORM = 4, ///< The vehicle entered a depot platform. /** * Shift the VehicleEnterTileStatus this many bits @@ -32,10 +33,11 @@ enum VehicleEnterTileStatus { VETS_STATION_MASK = 0xFFFF << VETS_STATION_ID_OFFSET, /** Bit sets of the above specified bits */ - VETSB_CONTINUE = 0, ///< The vehicle can continue normally - VETSB_ENTERED_STATION = 1 << VETS_ENTERED_STATION, ///< The vehicle entered a station - VETSB_ENTERED_WORMHOLE = 1 << VETS_ENTERED_WORMHOLE, ///< The vehicle either entered a bridge, tunnel or depot tile (this includes the last tile of the bridge/tunnel) - VETSB_CANNOT_ENTER = 1 << VETS_CANNOT_ENTER, ///< The vehicle cannot enter the tile + VETSB_CONTINUE = 0, ///< The vehicle can continue normally. + VETSB_ENTERED_STATION = 1 << VETS_ENTERED_STATION, ///< The vehicle entered a station. + VETSB_ENTERED_WORMHOLE = 1 << VETS_ENTERED_WORMHOLE, ///< The vehicle either entered a bridge, tunnel or depot tile (this includes the last tile of the bridge/tunnel). + VETSB_CANNOT_ENTER = 1 << VETS_CANNOT_ENTER, ///< The vehicle cannot enter the tile. + VETSB_ENTERED_DEPOT_PLATFORM = 1 << VETS_ENTERED_DEPOT_PLATFORM, ///< The vehicle entered a depot platform. }; DECLARE_ENUM_AS_BIT_SET(VehicleEnterTileStatus) @@ -57,6 +59,7 @@ struct TileDesc { TimerGameCalendar::Date build_date; ///< Date of construction of tile contents StringID station_class; ///< Class of station StringID station_name; ///< Type of station within the class + StringID airtype; ///< Type of airport on the tile. StringID airport_class; ///< Name of the airport class StringID airport_name; ///< Name of the airport StringID airport_tile_name; ///< Name of the airport tile diff --git a/src/tilehighlight_func.h b/src/tilehighlight_func.h index 572c5bd43ec6a..770013bc59ed1 100644 --- a/src/tilehighlight_func.h +++ b/src/tilehighlight_func.h @@ -26,6 +26,8 @@ void VpStartDragging(ViewportDragDropSelectionProcess process); void VpStartPlaceSizing(TileIndex tile, ViewportPlaceMethod method, ViewportDragDropSelectionProcess process); void VpSetPresizeRange(TileIndex from, TileIndex to); void VpSetPlaceSizingLimit(int limit); +void VpSetPlaceFixedSize(uint8_t fixed_size); +void VpResetFixedSize(); void UpdateTileSelection(); diff --git a/src/tilehighlight_type.h b/src/tilehighlight_type.h index a19eef5aacb9b..a7a43d179e853 100644 --- a/src/tilehighlight_type.h +++ b/src/tilehighlight_type.h @@ -55,11 +55,12 @@ struct TileHighlightData { Point new_pos; ///< New value for \a pos; used to determine whether to redraw the selection. Point new_size; ///< New value for \a size; used to determine whether to redraw the selection. Point new_outersize; ///< New value for \a outersize; used to determine whether to redraw the selection. - uint8_t dirty; ///< Whether the build station window needs to redraw due to the changed selection. + uint8_t dirty; ///< Whether the build station window needs to redraw due to the changed selection. Point selstart; ///< The location where the dragging started. Point selend; ///< The location where the drag currently ends. - uint8_t sizelimit; ///< Whether the selection is limited in length, and what the maximum length is. + uint8_t sizelimit; ///< Whether the selection is limited in length, and what the maximum length is. + uint8_t fixed_size; ///< The fixed length for one of the sides. HighLightStyle drawstyle; ///< Lower bits 0-3 are reserved for detailed highlight information. HighLightStyle next_drawstyle; ///< Queued, but not yet drawn style. diff --git a/src/timer/timer_game_tick.h b/src/timer/timer_game_tick.h index 02ae2b16ff95b..7af0024bd4465 100644 --- a/src/timer/timer_game_tick.h +++ b/src/timer/timer_game_tick.h @@ -78,6 +78,7 @@ class Ticks { static constexpr TimerGameTick::Ticks STATION_RATING_TICKS = 185; ///< Cycle duration for updating station rating. static constexpr TimerGameTick::Ticks STATION_ACCEPTANCE_TICKS = 250; ///< Cycle duration for updating station acceptance. static constexpr TimerGameTick::Ticks STATION_LINKGRAPH_TICKS = 504; ///< Cycle duration for cleaning dead links. + static constexpr TimerGameTick::Ticks DEPOT_REMOVAL_TICKS = 250; ///< Cycle duration for cleaning demolished depots. static constexpr TimerGameTick::Ticks CARGO_AGING_TICKS = 185; ///< Cycle duration for aging cargo. static constexpr TimerGameTick::Ticks INDUSTRY_PRODUCE_TICKS = 256; ///< Cycle duration for industry production. static constexpr TimerGameTick::Ticks TOWN_GROWTH_TICKS = 70; ///< Cycle duration for towns trying to grow (this originates from the size of the town array in TTD). diff --git a/src/toolbar_gui.cpp b/src/toolbar_gui.cpp index c1aa2ff2673e4..23a1f2c2f75fd 100644 --- a/src/toolbar_gui.cpp +++ b/src/toolbar_gui.cpp @@ -18,6 +18,7 @@ #include "dropdown_common_type.h" #include "house.h" #include "vehicle_gui.h" +#include "airport_gui.h" #include "rail_gui.h" #include "road.h" #include "road_gui.h" @@ -73,6 +74,7 @@ /** Width of the toolbar, shared by statusbar. */ uint _toolbar_width = 0; +AirType _last_built_airtype; RailType _last_built_railtype; RoadType _last_built_roadtype; RoadType _last_built_tramtype; @@ -938,9 +940,13 @@ static CallBackFunction MenuClickBuildWater(int) static CallBackFunction ToolbarBuildAirClick(Window *w) { - DropDownList list; - list.push_back(MakeDropDownListIconItem(SPR_IMG_AIRPORT, PAL_NONE, STR_AIRCRAFT_MENU_AIRPORT_CONSTRUCTION, 0)); - ShowDropDownList(w, std::move(list), 0, WID_TN_AIR, 140, true); + if (_settings_game.station.allow_modify_airports) { + DropDownList list = GetAirTypeDropDownList(); + ShowDropDownList(w, std::move(list), _last_built_airtype, WID_TN_AIR, 140, true); + } else { + ShowBuildAirToolbar(INVALID_AIRTYPE); + } + if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP); return CBF_NONE; } @@ -950,9 +956,10 @@ static CallBackFunction ToolbarBuildAirClick(Window *w) * * @return #CBF_NONE */ -static CallBackFunction MenuClickBuildAir(int) +static CallBackFunction MenuClickBuildAir(int index) { - ShowBuildAirToolbar(); + _last_built_airtype = (AirType)index; + ShowBuildAirToolbar(_last_built_airtype); return CBF_NONE; } @@ -2028,7 +2035,7 @@ struct MainToolbarWindow : Window { case MTHK_BUILD_ROAD: ShowBuildRoadToolbar(_last_built_roadtype); break; case MTHK_BUILD_TRAM: ShowBuildRoadToolbar(_last_built_tramtype); break; case MTHK_BUILD_DOCKS: ShowBuildDocksToolbar(); break; - case MTHK_BUILD_AIRPORT: ShowBuildAirToolbar(); break; + case MTHK_BUILD_AIRPORT: if (CanBuildVehicleInfrastructure(VEH_AIRCRAFT)) ShowBuildAirToolbar(_last_built_airtype); break; case MTHK_BUILD_TREES: ShowBuildTreesToolbar(); break; case MTHK_MUSIC: ShowMusicWindow(); break; case MTHK_SCRIPT_DEBUG: ShowScriptDebugWindow(); break; diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index 79e995079129a..e50b2044cee7d 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -44,6 +44,7 @@ #include "core/random_func.hpp" #include "core/backup_type.hpp" #include "depot_base.h" +#include "depot_func.h" #include "object_map.h" #include "object_base.h" #include "ai/ai.hpp" @@ -1254,7 +1255,7 @@ static bool CanRoadContinueIntoNextTile(const Town *t, const TileIndex tile, con /* If the next tile is a road depot, allow if it's facing the right way. */ if (IsTileType(next_tile, MP_ROAD)) { - return IsRoadDepot(next_tile) && GetRoadDepotDirection(next_tile) == ReverseDiagDir(road_dir); + return IsRoadDepot(next_tile) && (GetRoadBits(next_tile, RTT_ROAD) & DiagDirToRoadBits(ReverseDiagDir(road_dir))) != ROAD_NONE; } /* If the next tile is a railroad track, check if towns are allowed to build level crossings. @@ -3005,6 +3006,8 @@ CommandCost CmdRenameTown(DoCommandFlag flags, TownID town_id, const std::string ClearAllStationCachedNames(); ClearAllIndustryCachedNames(); UpdateAllStationVirtCoords(); + UpdateAllDepotVirtCoords(); + RebuildViewportKdtree(); } return CommandCost(); } diff --git a/src/track_func.h b/src/track_func.h index bc325d8928043..74b4def341397 100644 --- a/src/track_func.h +++ b/src/track_func.h @@ -424,6 +424,20 @@ inline TrackBits TrackCrossesTracks(Track track) return _track_crosses_tracks[track]; } +/** + * Maps a trackdir to the (4-way) direction used for entering this tile + * when following that trackdir. + * + * @param trackdir The given track direction + * @return The entry direction to a tile in order to follow the Trackdir + */ +static inline DiagDirection TrackdirToEntrydir(Trackdir trackdir) +{ + assert(IsValidTrackdirForRoadVehicle(trackdir)); + extern const DiagDirection _trackdir_to_entrydir[TRACKDIR_END]; + return _trackdir_to_entrydir[trackdir]; +} + /** * Maps a trackdir to the (4-way) direction the tile is exited when following * that trackdir. @@ -443,6 +457,13 @@ inline DiagDirection TrackdirToExitdir(Trackdir trackdir) return _trackdir_to_exitdir[trackdir]; } +static inline Direction TrackdirToDir(Trackdir trackdir) +{ + assert(IsValidTrackdir(trackdir)); + extern const Direction _trackdir_to_direction[TRACKDIR_END]; + return _trackdir_to_direction[trackdir]; +} + /** * Maps a track and an (4-way) dir to the trackdir that represents the track * with the exit in the given direction. diff --git a/src/track_type.h b/src/track_type.h index e3c3f22b6792f..89a30cd5d2284 100644 --- a/src/track_type.h +++ b/src/track_type.h @@ -14,7 +14,8 @@ /** * These are used to specify a single track. - * Can be translated to a trackbit with TrackToTrackbit + * Can be translated to a trackbit with TrackToTrackbit. + * TRACK_WORMHOLE and TRACK_DEPOT do not represent single tracks but states; they cannot be translated to trackbits. */ enum Track : uint8_t { TRACK_BEGIN = 0, ///< Used for iterations @@ -24,7 +25,9 @@ enum Track : uint8_t { TRACK_LOWER = 3, ///< Track in the lower corner of the tile (south) TRACK_LEFT = 4, ///< Track in the left corner of the tile (west) TRACK_RIGHT = 5, ///< Track in the right corner of the tile (east) - TRACK_END, ///< Used for iterations + TRACK_END = 6, ///< Used for iterations + TRACK_WORMHOLE = TRACK_END, + TRACK_DEPOT = 7, INVALID_TRACK = 0xFF, ///< Flag for an invalid track }; @@ -49,8 +52,8 @@ enum TrackBits : uint8_t { TRACK_BIT_3WAY_NW = TRACK_BIT_Y | TRACK_BIT_UPPER | TRACK_BIT_LEFT, ///< "Arrow" to the north-west TRACK_BIT_ALL = TRACK_BIT_CROSS | TRACK_BIT_HORZ | TRACK_BIT_VERT, ///< All possible tracks TRACK_BIT_MASK = 0x3FU, ///< Bitmask for the first 6 bits - TRACK_BIT_WORMHOLE = 0x40U, ///< Bitflag for a wormhole (used for tunnels) - TRACK_BIT_DEPOT = 0x80U, ///< Bitflag for a depot + TRACK_BIT_WORMHOLE = 1U << TRACK_WORMHOLE, ///< Bitflag for a wormhole (used for tunnels) + TRACK_BIT_DEPOT = 1U << TRACK_DEPOT, ///< Bitflag for a depot INVALID_TRACK_BIT = 0xFF, ///< Flag for an invalid trackbits value }; DECLARE_ENUM_AS_BIT_SET(TrackBits) diff --git a/src/train.h b/src/train.h index bbf1e04365e0b..9fcd925c82fce 100644 --- a/src/train.h +++ b/src/train.h @@ -21,6 +21,9 @@ struct Train; +static const uint8_t _vehicle_initial_x_fract[4] = {10, 8, 4, 8}; +static const uint8_t _vehicle_initial_y_fract[4] = { 8, 4, 8, 10}; + /** Rail vehicle flags. */ enum VehicleRailFlags { VRF_REVERSING = 0, @@ -66,7 +69,7 @@ int GetTrainStopLocation(StationID station_id, TileIndex tile, const Train *v, i void GetTrainSpriteSize(EngineID engine, uint &width, uint &height, int &xoffs, int &yoffs, EngineImageType image_type); bool TrainOnCrossing(TileIndex tile); -void NormalizeTrainVehInDepot(const Train *u); +void NormalizeTrainVehInDepot(const Train *u, DoCommandFlag flags = DC_EXEC); /** Variables that are cached to improve performance and such */ struct TrainCache { @@ -120,7 +123,7 @@ struct Train final : public GroundVehicle { Money GetRunningCost() const override; int GetCursorImageOffset() const; int GetDisplayImageWidth(Point *offset = nullptr) const; - bool IsInDepot() const override { return this->track == TRACK_BIT_DEPOT; } + bool IsInDepot() const override { return HasBit((uint8_t)this->track, TRACK_DEPOT); } bool Tick() override; void OnNewCalendarDay() override; void OnNewEconomyDay() override; @@ -353,4 +356,8 @@ struct Train final : public GroundVehicle { } }; +bool HasCompatibleDepotTile(TileIndex tile, const Train *t); +bool HandleTrainEnterDepot(Train *v); +bool CheckReverseTrain(const Train *v); + #endif /* TRAIN_H */ diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index a9fb14ee54923..684e77fab0b31 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -7,6 +7,7 @@ /** @file train_cmd.cpp Handling of trains. */ +#include "depot_map.h" #include "stdafx.h" #include "error.h" #include "articulated_vehicles.h" @@ -38,6 +39,10 @@ #include "misc_cmd.h" #include "timer/timer_game_calendar.h" #include "timer/timer_game_economy.h" +#include "depot_base.h" +#include "platform_func.h" +#include "depot_map.h" +#include "train_placement.h" #include "table/strings.h" #include "table/train_sprites.h" @@ -51,9 +56,6 @@ static TileIndex TrainApproachingCrossingTile(const Train *v); static void CheckIfTrainNeedsService(Train *v); static void CheckNextTrainTile(Train *v); -static const uint8_t _vehicle_initial_x_fract[4] = {10, 8, 4, 8}; -static const uint8_t _vehicle_initial_y_fract[4] = { 8, 4, 8, 10}; - template <> bool IsValidImageIndex(uint8_t image_index) { @@ -262,9 +264,9 @@ void Train::ConsistChanged(ConsistChangeFlags allowed_changes) */ int GetTrainStopLocation(StationID station_id, TileIndex tile, const Train *v, int *station_ahead, int *station_length) { - const Station *st = Station::Get(station_id); - *station_ahead = st->GetPlatformLength(tile, DirToDiagDir(v->direction)) * TILE_SIZE; - *station_length = st->GetPlatformLength(tile) * TILE_SIZE; + assert(IsRailStationTile(tile)); + *station_ahead = GetPlatformLength(tile, DirToDiagDir(v->direction)) * TILE_SIZE; + *station_length = GetPlatformLength(tile) * TILE_SIZE; /* Default to the middle of the station for stations stops that are not in * the order list like intermediate stations when non-stop is disabled */ @@ -604,6 +606,65 @@ void GetTrainSpriteSize(EngineID engine, uint &width, uint &height, int &xoffs, } } + +/** + * Check if a train chain is compatible with a depot tile. + * @param tile Tile to check. + * @param t Train chain to check. + * @return Whether the full train chain is compatible with this tile. + */ +bool IsVehicleCompatibleWithDepotTile(TileIndex tile, const Train *t) +{ + assert(IsRailDepotTile(tile)); + for (const Train *u = t; u != nullptr; u = u->Next()) { + RailType rail_type = Engine::Get(u->engine_type)->u.rail.railtype; + if (!IsCompatibleRail(rail_type, GetRailType(tile))) return false; + } + + return true; +} + +/** + * Check if a depot has a tile where a train chain can be stored. + * @param tile A tile of the depot. + * @param t The train to check. + * @return True iff the depot has a tile compatible with the chain. + */ +bool HasCompatibleDepotTile(TileIndex tile, const Train *t) +{ + assert(IsRailDepotTile(tile)); + Depot *dep = Depot::GetByTile(tile); + + for (auto &depot_tile : dep->depot_tiles) { + if (IsVehicleCompatibleWithDepotTile(depot_tile, t)) return true; + } + + return false; +} + +/** + * Find a tile of a depot compatible with the rail type of a rail vehicle. + * @param depot_id Index of the depot. + * @param rail_type Rail type of the new vehicle. + * @param is_engine Whether the vehicle is an engine. + * @return A compatible tile of the depot or INVALID_TILE if no compatible tile is found. + */ +TileIndex FindCompatibleDepotTile(DepotID depot_id, RailType rail_type, bool is_engine) +{ + assert(Depot::IsValidID(depot_id)); + Depot *depot = Depot::Get(depot_id); + + for (auto &dep_tile : depot->depot_tiles) { + if (is_engine) { + if (HasPowerOnRail(rail_type, GetRailType(dep_tile))) return dep_tile; + } else { + if (IsCompatibleRail(rail_type, GetRailType(dep_tile))) return dep_tile; + } + } + + return INVALID_TILE; +} + /** * Build a railroad wagon. * @param flags type of operation. @@ -615,9 +676,12 @@ void GetTrainSpriteSize(EngineID engine, uint &width, uint &height, int &xoffs, static CommandCost CmdBuildRailWagon(DoCommandFlag flags, TileIndex tile, const Engine *e, Vehicle **ret) { const RailVehicleInfo *rvi = &e->u.rail; + assert(IsRailDepotTile(tile)); + DepotID depot_id = GetDepotIndex(tile); - /* Check that the wagon can drive on the track in question */ - if (!IsCompatibleRail(rvi->railtype, GetRailType(tile))) return CMD_ERROR; + /* Find a good tile to place the wagon. */ + tile = FindCompatibleDepotTile(depot_id, rvi->railtype, false); + if (tile == INVALID_TILE) return CMD_ERROR; if (flags & DC_EXEC) { Train *v = new Train(); @@ -645,7 +709,7 @@ static CommandCost CmdBuildRailWagon(DoCommandFlag flags, TileIndex tile, const v->SetWagon(); v->SetFreeWagon(); - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, depot_id); v->cargo_type = e->GetDefaultCargoType(); assert(IsValidCargoID(v->cargo_type)); @@ -673,12 +737,13 @@ static CommandCost CmdBuildRailWagon(DoCommandFlag flags, TileIndex tile, const /* Try to connect the vehicle to one of free chains of wagons. */ for (Train *w : Train::Iterate()) { - if (w->tile == tile && ///< Same depot + if (!IsRailDepotTile(w->tile)) continue; + if (GetDepotIndex(w->tile) == depot_id && ///< Same depot w->IsFreeWagon() && ///< A free wagon chain w->engine_type == e->index && ///< Same type w->First() != v && ///< Don't connect to ourself !(w->vehstatus & VS_CRASHED)) { ///< Not crashed/flooded - if (Command::Do(DC_EXEC, v->index, w->Last()->index, true).Succeeded()) { + if (Command::Do(flags | DC_EXEC, v->index, w->Last()->index, true).Succeeded()) { break; } } @@ -689,13 +754,16 @@ static CommandCost CmdBuildRailWagon(DoCommandFlag flags, TileIndex tile, const } /** Move all free vehicles in the depot to the train */ -void NormalizeTrainVehInDepot(const Train *u) +void NormalizeTrainVehInDepot(const Train *u, DoCommandFlag flags) { assert(u->IsEngine()); + assert(flags & DC_EXEC); + + DepotID dep_id = GetDepotIndex(u->tile); for (const Train *v : Train::Iterate()) { - if (v->IsFreeWagon() && v->tile == u->tile && - v->track == TRACK_BIT_DEPOT) { - if (Command::Do(DC_EXEC, v->index, u->index, true).Failed()) { + if (v->IsFreeWagon() && v->IsInDepot() && + GetDepotIndex(v->tile) == dep_id) { + if (Command::Do(flags, v->index, u->index, true).Failed()) { break; } } @@ -748,13 +816,14 @@ static void AddRearEngineToMultiheadedTrain(Train *v) */ CommandCost CmdBuildRailVehicle(DoCommandFlag flags, TileIndex tile, const Engine *e, Vehicle **ret) { + assert(IsRailDepotTile(tile)); const RailVehicleInfo *rvi = &e->u.rail; if (rvi->railveh_type == RAILVEH_WAGON) return CmdBuildRailWagon(flags, tile, e, ret); - /* Check if depot and new engine uses the same kind of tracks * - * We need to see if the engine got power on the tile to avoid electric engines in non-electric depots */ - if (!HasPowerOnRail(rvi->railtype, GetRailType(tile))) return CMD_ERROR; + /* Find a good tile to place the engine and get power on it. */ + tile = FindCompatibleDepotTile(GetDepotIndex(tile), rvi->railtype, true); + if (tile == INVALID_TILE) return CMD_ERROR; if (flags & DC_EXEC) { DiagDirection dir = GetRailDepotDirection(tile); @@ -816,6 +885,10 @@ CommandCost CmdBuildRailVehicle(DoCommandFlag flags, TileIndex tile, const Engin UpdateTrainGroupID(v); CheckConsistencyOfArticulatedVehicle(v); + + TrainPlacement train_placement; + train_placement.LiftTrain(v, flags); + train_placement.PlaceTrain(v, flags); } return CommandCost(); @@ -824,10 +897,10 @@ CommandCost CmdBuildRailVehicle(DoCommandFlag flags, TileIndex tile, const Engin static Train *FindGoodVehiclePos(const Train *src) { EngineID eng = src->engine_type; - TileIndex tile = src->tile; + DepotID dep_id = GetDepotIndex(src->tile); for (Train *dst : Train::Iterate()) { - if (dst->IsFreeWagon() && dst->tile == tile && !(dst->vehstatus & VS_CRASHED)) { + if (dst->IsFreeWagon() && !(dst->vehstatus & VS_CRASHED) && GetDepotIndex(dst->tile) == dep_id) { /* check so all vehicles in the line have the same engine. */ Train *t = dst; while (t->engine_type == eng) { @@ -994,6 +1067,10 @@ static CommandCost CheckTrainAttachment(Train *t) /* No multi-part train, no need to check. */ if (t == nullptr || t->Next() == nullptr) return CommandCost(); + TrainPlacement tp; + tp.LookForPlaceInDepot(t, false); + if (tp.info == PI_FAILED_PLATFORM_TYPE) return_cmd_error(STR_ERROR_INCOMPATIBLE_RAILTYPES_WITH_DEPOT); + /* The maximum length for a train. For each part we decrease this by one * and if the result is negative the train is simply too long. */ int allowed_len = _settings_game.vehicle.max_train_length * TILE_SIZE - t->gcache.cached_veh_length; @@ -1229,7 +1306,7 @@ CommandCost CmdMoveRailVehicle(DoCommandFlag flags, VehicleID src_veh, VehicleID Train *dst_head; if (dst != nullptr) { dst_head = dst->First(); - if (dst_head->tile != src_head->tile) return CMD_ERROR; + if (GetDepotIndex(dst_head->tile) != GetDepotIndex(src_head->tile)) return CMD_ERROR; /* Now deal with articulated part of destination wagon */ dst = dst->GetLastEnginePart(); } else { @@ -1270,6 +1347,13 @@ CommandCost CmdMoveRailVehicle(DoCommandFlag flags, VehicleID src_veh, VehicleID bool original_src_head_front_engine = original_src_head->IsFrontEngine(); bool original_dst_head_front_engine = original_dst_head != nullptr && original_dst_head->IsFrontEngine(); + TrainPlacement train_placement_src; + TrainPlacement train_placement_dst; + train_placement_src.LiftTrain(src_head, flags); + train_placement_dst.LiftTrain(dst_head, flags); + + assert(src_head != nullptr); + /* (Re)arrange the trains in the wanted arrangement. */ ArrangeTrains(&dst_head, dst, &src_head, src, move_chain); @@ -1282,6 +1366,8 @@ CommandCost CmdMoveRailVehicle(DoCommandFlag flags, VehicleID src_veh, VehicleID /* Restore the train we had. */ RestoreTrainBackup(original_src); RestoreTrainBackup(original_dst); + train_placement_src.PlaceTrain(original_src_head, flags & ~DC_EXEC); + if (src_head != dst_head) train_placement_dst.PlaceTrain(original_dst_head, flags & ~DC_EXEC); return ret; } } @@ -1362,13 +1448,21 @@ CommandCost CmdMoveRailVehicle(DoCommandFlag flags, VehicleID src_veh, VehicleID if (src_head != nullptr) src_head->First()->MarkDirty(); if (dst_head != nullptr) dst_head->First()->MarkDirty(); + bool reverse_emplacement_order = !train_placement_src.placed && train_placement_dst.placed; + + if (!reverse_emplacement_order) train_placement_src.PlaceTrain(src_head, flags); + if (src_head != dst_head) train_placement_dst.PlaceTrain(dst_head, flags); + if (reverse_emplacement_order) train_placement_src.PlaceTrain(src_head, flags); + /* We are undoubtedly changing something in the depot and train list. */ - InvalidateWindowData(WC_VEHICLE_DEPOT, src->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(src->tile)); InvalidateWindowClassesData(WC_TRAINS_LIST, 0); } else { /* We don't want to execute what we're just tried. */ RestoreTrainBackup(original_src); RestoreTrainBackup(original_dst); + train_placement_src.PlaceTrain(original_src_head, flags); + if (src_head != dst_head) train_placement_dst.PlaceTrain(original_dst_head, flags); } return CommandCost(); @@ -1393,6 +1487,9 @@ CommandCost CmdSellRailWagon(DoCommandFlag flags, Vehicle *t, bool sell_chain, b if (v->IsRearDualheaded()) return_cmd_error(STR_ERROR_REAR_ENGINE_FOLLOW_FRONT); + TrainPlacement train_placement; + train_placement.LiftTrain(first, flags); + /* First make a backup of the order of the train. That way we can do * whatever we want with the order and later on easily revert. */ TrainList original; @@ -1410,12 +1507,14 @@ CommandCost CmdSellRailWagon(DoCommandFlag flags, Vehicle *t, bool sell_chain, b if (ret.Failed()) { /* Restore the train we had. */ RestoreTrainBackup(original); + train_placement.PlaceTrain(first, flags); return ret; } if (first->orders == nullptr && !OrderList::CanAllocateItem()) { /* Restore the train we had. */ RestoreTrainBackup(original); + train_placement.PlaceTrain(first, flags); return_cmd_error(STR_ERROR_NO_MORE_SPACE_FOR_ORDERS); } @@ -1445,16 +1544,18 @@ CommandCost CmdSellRailWagon(DoCommandFlag flags, Vehicle *t, bool sell_chain, b /* We need to update the information about the train. */ NormaliseTrainHead(new_head); + train_placement.PlaceTrain(new_head, flags); /* We are undoubtedly changing something in the depot and train list. */ - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); InvalidateWindowClassesData(WC_TRAINS_LIST, 0); /* Actually delete the sold 'goods' */ delete sell_head; } else { - /* We don't want to execute what we're just tried. */ + /* We don't want to execute what we have just tried. */ RestoreTrainBackup(original); + train_placement.PlaceTrain(first, flags); } return cost; @@ -1965,8 +2066,12 @@ static bool IsWholeTrainInsideDepot(const Train *v) void ReverseTrainDirection(Train *v) { if (IsRailDepotTile(v->tile)) { - if (IsWholeTrainInsideDepot(v)) return; - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + if (IsExtendedDepot(v->tile)) { + if ((v->track & TRACK_BIT_DEPOT) != 0 && (v->track & ~TRACK_BIT_DEPOT) != 0) return; + } else { + if (IsWholeTrainInsideDepot(v)) return; + } + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); } /* Clear path reservation in front if train is not stuck. */ @@ -1989,7 +2094,7 @@ void ReverseTrainDirection(Train *v) AdvanceWagonsAfterSwap(v); if (IsRailDepotTile(v->tile)) { - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); } ToggleBit(v->flags, VRF_TOGGLE_REVERSE); @@ -2030,9 +2135,9 @@ void ReverseTrainDirection(Train *v) !IsPbsSignal(GetSignalType(v->tile, FindFirstTrack(v->track)))); /* If we are on a depot tile facing outwards, do not treat the current tile as safe. */ - if (IsRailDepotTile(v->tile) && TrackdirToExitdir(v->GetVehicleTrackdir()) == GetRailDepotDirection(v->tile)) first_tile_okay = false; + if (IsStandardRailDepotTile(v->tile) && TrackdirToExitdir(v->GetVehicleTrackdir()) == GetRailDepotDirection(v->tile)) first_tile_okay = false; - if (IsRailStationTile(v->tile)) SetRailStationPlatformReservation(v->tile, TrackdirToExitdir(v->GetVehicleTrackdir()), true); + if (IsPlatformTile(v->tile)) SetPlatformReservation(v->tile, TrackdirToExitdir(v->GetVehicleTrackdir()), true); if (TryPathReserve(v, false, first_tile_okay)) { /* Do a look-ahead now in case our current tile was already a safe tile. */ CheckNextTrainTile(v); @@ -2079,7 +2184,14 @@ CommandCost CmdReverseTrainDirection(DoCommandFlag flags, VehicleID veh_id, bool ToggleBit(v->flags, VRF_REVERSE_DIRECTION); front->ConsistChanged(CCF_ARRANGE); - SetWindowDirty(WC_VEHICLE_DEPOT, front->tile); + if (IsRailDepotTile(front->tile)) { + if (IsExtendedDepot(front->tile)) { + TrainPlacement tp; + tp.LiftTrain(front, flags); + tp.PlaceTrain(front, flags & ~DC_EXEC); + } + SetWindowDirty(WC_VEHICLE_DEPOT, GetDepotIndex(front->tile)); + } SetWindowDirty(WC_VEHICLE_DETAILS, front->index); SetWindowDirty(WC_VEHICLE_VIEW, front->index); SetWindowClassesDirty(WC_TRAINS_LIST); @@ -2089,6 +2201,9 @@ CommandCost CmdReverseTrainDirection(DoCommandFlag flags, VehicleID veh_id, bool if (!v->IsPrimaryVehicle()) return CMD_ERROR; if ((v->vehstatus & VS_CRASHED) || v->breakdown_ctr != 0) return CMD_ERROR; + /* Do not reverse while servicing. */ + if (IsExtendedRailDepotTile(v->tile) && (v->track & TRACK_BIT_DEPOT) != 0 && (v->track & ~TRACK_BIT_DEPOT) != 0) return CMD_ERROR; + if (flags & DC_EXEC) { /* Properly leave the station if we are loading and won't be loading anymore */ if (v->current_order.IsType(OT_LOADING)) { @@ -2212,7 +2327,7 @@ static void CheckNextTrainTile(Train *v) switch (v->current_order.GetType()) { /* Exit if we reached our destination depot. */ case OT_GOTO_DEPOT: - if (v->tile == v->dest_tile) return; + if (IsRailDepotTile(v->tile) && v->current_order.ShouldStopAtDepot(GetDepotIndex(v->tile))) return; break; case OT_GOTO_WAYPOINT: @@ -2258,6 +2373,53 @@ static void CheckNextTrainTile(Train *v) } } +bool HandleTrainEnterDepot(Train *v) +{ + assert(IsRailDepotTile(v->tile)); + + if (IsExtendedRailDepot(v->tile)) { + v->cur_speed = 0; + Train *t = Train::From(v); + for (Train *u = t; u != nullptr; u = u->Next()) { + if (!IsCompatibleTrainDepotTile(u->tile, t->tile)) { + SetDParam(0, v->index); + AddVehicleAdviceNewsItem(STR_NEWS_VEHICLE_TOO_LONG_FOR_SERVICING, v->index); + return false; + } + } + + for (Train *u = t; u != nullptr; u = u->Next()) u->track |= TRACK_BIT_DEPOT; + t->force_proceed = TFP_NONE; + ClrBit(t->flags, VRF_TOGGLE_REVERSE); + UpdateExtendedDepotReservation(t, true); + v->UpdateViewport(true, true); + SetWindowClassesDirty(WC_TRAINS_LIST); + SetWindowDirty(WC_VEHICLE_VIEW, v->index); + + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); + v->StartService(); + } else { + /* Clear path reservation */ + SetDepotReservation(v->tile, false); + VehicleEnterDepot(v); + } + + return true; +} + +bool CheckReverseTrain(const Train *v) +{ + if (_settings_game.difficulty.line_reverse_mode != 0 || + v->track == TRACK_BIT_DEPOT || v->track == TRACK_BIT_WORMHOLE || + !(v->direction & 1)) { + return false; + } + + assert(v->track != TRACK_BIT_NONE); + + return YapfTrainCheckReverse(v); +} + /** * Will the train stay in the depot the next tick? * @param v %Train to check. @@ -2266,15 +2428,80 @@ static void CheckNextTrainTile(Train *v) static bool CheckTrainStayInDepot(Train *v) { /* bail out if not all wagons are in the same depot or not in a depot at all */ - for (const Train *u = v; u != nullptr; u = u->Next()) { - if (u->track != TRACK_BIT_DEPOT || u->tile != v->tile) return false; - } + if (!v->IsInDepot()) return false; + assert(IsRailDepotTile(v->tile)); + + DepotID depot_id = GetDepotIndex(v->tile); + if (IsExtendedRailDepot(v->tile)) { + /* If not placed, try it. If not possible, exit. */ + if (CheckIfTrainNeedsPlacement(v)) { + /* If stuck, wait a little bit, so we can avoid + * trying placing it as much as we can. */ + bool already_stuck = false; + bool send_message = false; + if (HasBit(v->flags, VRF_TRAIN_STUCK)) { + already_stuck = true; + v->wait_counter++; + if (v->wait_counter % (1 << 7) != 0) { + return true; + } else if (v->wait_counter % (1 << 11) == 0) { + send_message = true; + } + ClrBit(v->flags, VRF_TRAIN_STUCK); + } - /* if the train got no power, then keep it in the depot */ - if (v->gcache.cached_power == 0) { - v->vehstatus |= VS_STOPPED; - SetWindowDirty(WC_VEHICLE_DEPOT, v->tile); - return true; + TrainPlacement train_placement; + train_placement.LiftTrain(v, DC_EXEC); + train_placement.LookForPlaceInDepot(v, true); + if (train_placement.info < PI_FAILED_END) { + if (send_message) { + ClrBit(v->flags, VRF_TRAIN_STUCK); + /* Show message to player. */ + if (_settings_client.gui.lost_vehicle_warn && v->owner == _local_company) { + SetDParam(0, v->index); + AddVehicleAdviceNewsItem(STR_ADVICE_PLATFORM_TYPE + train_placement.info - PI_ERROR_BEGIN, v->index); + } + } + if (already_stuck) { + SetBit(v->flags, VRF_TRAIN_STUCK); + } else { + MarkTrainAsStuck(v); + } + return true; + } else { + VehicleServiceInExtendedDepot(v); + train_placement.PlaceTrain(v, DC_EXEC); + if (!IsExtendedDepot(v->tile)) return true; + } + } else { + VehicleServiceInExtendedDepot(v); + } + + for (Train *u = v; u != nullptr; u = u->Next()) u->track &= ~TRACK_BIT_DEPOT; + + v->cur_speed = 0; + v->UpdateAcceleration(); + ProcessOrders(v); + if (CheckReverseTrain(v)) ReverseTrainDirection(v); + UpdateExtendedDepotReservation(v, false); + InvalidateWindowData(WC_VEHICLE_DEPOT, depot_id); + + /* Check whether it is safe to exit the depot. */ + if (UpdateSignalsOnSegment(v->tile, VehicleExitDir(v->direction, v->track), v->owner) == SIGSEG_PBS || _settings_game.pf.reserve_paths) { + if (!TryPathReserve(v, true, true)) return true; + } + return false; + } else { + for (const Train *u = v; u != nullptr; u = u->Next()) { + if (!u->IsInDepot() || u->tile != v->tile) return false; + } + + /* if the train got no power, then keep it in the depot */ + if (v->gcache.cached_power == 0) { + v->vehstatus |= VS_STOPPED; + SetWindowDirty(WC_VEHICLE_DEPOT, depot_id); + return true; + } } /* Check if we should wait here for unbunching. */ @@ -2302,9 +2529,11 @@ static bool CheckTrainStayInDepot(Train *v) } /* We are leaving a depot, but have to go to the exact same one; re-enter. */ - if (v->current_order.IsType(OT_GOTO_DEPOT) && v->tile == v->dest_tile) { + if (v->current_order.IsType(OT_GOTO_DEPOT) && + IsRailDepotTile(v->tile) && + v->current_order.GetDestination() == GetDepotIndex(v->tile)) { /* Service when depot has no reservation. */ - if (!HasDepotReservation(v->tile)) VehicleEnterDepot(v); + if (!HasDepotReservation(v->tile)) HandleTrainEnterDepot(v); return true; } @@ -2334,7 +2563,7 @@ static bool CheckTrainStayInDepot(Train *v) v->UpdatePosition(); UpdateSignalsOnSegment(v->tile, INVALID_DIAGDIR, v->owner); v->UpdateAcceleration(); - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, depot_id); return false; } @@ -2369,12 +2598,12 @@ static void ClearPathReservation(const Train *v, TileIndex tile, Trackdir track_ } } } - } else if (IsRailStationTile(tile)) { + } else if (IsRailStationTile(tile) || IsExtendedRailDepotTile(tile)) { TileIndex new_tile = TileAddByDiagDir(tile, dir); /* If the new tile is not a further tile of the same station, we * clear the reservation for the whole platform. */ - if (!IsCompatibleTrainStationTile(new_tile, tile)) { - SetRailStationPlatformReservation(tile, ReverseDiagDir(dir), false); + if (!IsCompatiblePlatformTile(new_tile, tile)) { + SetPlatformReservation(tile, ReverseDiagDir(dir), false); } } else { /* Any other tile */ @@ -2392,11 +2621,12 @@ void FreeTrainTrackReservation(const Train *v) TileIndex tile = v->tile; Trackdir td = v->GetVehicleTrackdir(); - bool free_tile = !(IsRailStationTile(v->tile) || IsTileType(v->tile, MP_TUNNELBRIDGE)); + bool free_tile = !(IsRailStationTile(v->tile) || IsExtendedRailDepotTile(v->tile) || IsTileType(v->tile, MP_TUNNELBRIDGE)); StationID station_id = IsRailStationTile(v->tile) ? GetStationIndex(v->tile) : INVALID_STATION; + DepotID depot_id = IsExtendedRailDepotTile(v->tile) ? GetDepotIndex(v->tile) : INVALID_DEPOT; /* Can't be holding a reservation if we enter a depot. */ - if (IsRailDepotTile(tile) && TrackdirToExitdir(td) != GetRailDepotDirection(tile)) return; + if (IsStandardRailDepotTile(tile) && TrackdirToExitdir(td) != GetRailDepotDirection(tile)) return; if (v->track == TRACK_BIT_DEPOT) { /* Front engine is in a depot. We enter if some part is not in the depot. */ for (const Train *u = v; u != nullptr; u = u->Next()) { @@ -2436,7 +2666,7 @@ void FreeTrainTrackReservation(const Train *v) } /* Don't free first station/bridge/tunnel if we are on it. */ - if (free_tile || (!(ft.m_is_station && GetStationIndex(ft.m_new_tile) == station_id) && !ft.m_is_tunnel && !ft.m_is_bridge)) ClearPathReservation(v, tile, td); + if (free_tile || (!(ft.m_is_station && GetStationIndex(ft.m_new_tile) == station_id) && !(ft.m_is_extended_depot && GetDepotIndex(ft.m_new_tile) == depot_id) && !ft.m_is_tunnel && !ft.m_is_bridge)) ClearPathReservation(v, tile, td); free_tile = true; } @@ -2495,7 +2725,7 @@ static PBSTileInfo ExtendTrainReservation(const Train *v, TrackBits *new_tracks, } /* Station, depot or waypoint are a possible target. */ - bool target_seen = ft.m_is_station || (IsTileType(ft.m_new_tile, MP_RAILWAY) && !IsPlainRail(ft.m_new_tile)); + bool target_seen = ft.m_is_station || ft.m_is_extended_depot || (IsTileType(ft.m_new_tile, MP_RAILWAY) && !IsPlainRail(ft.m_new_tile)); if (target_seen || KillFirstBit(ft.m_new_td_bits) != TRACKDIR_BIT_NONE) { /* Choice found or possible target encountered. * On finding a possible target, we need to stop and let the pathfinder handle the @@ -2852,6 +3082,7 @@ bool TryPathReserve(Train *v, bool mark_as_stuck, bool first_tile_okay) if (mark_as_stuck) MarkTrainAsStuck(v); return false; } else { + assert(IsStandardRailDepotTile(v->tile)); /* Depot not reserved, but the next tile might be. */ TileIndex next_tile = TileAddByDiagDir(v->tile, GetRailDepotDirection(v->tile)); if (HasReservedTracks(next_tile, DiagdirReachesTracks(GetRailDepotDirection(v->tile)))) return false; @@ -2907,19 +3138,6 @@ bool TryPathReserve(Train *v, bool mark_as_stuck, bool first_tile_okay) } -static bool CheckReverseTrain(const Train *v) -{ - if (_settings_game.difficulty.line_reverse_mode != 0 || - v->track == TRACK_BIT_DEPOT || v->track == TRACK_BIT_WORMHOLE || - !(v->direction & 1)) { - return false; - } - - assert(v->track != TRACK_BIT_NONE); - - return YapfTrainCheckReverse(v); -} - /** * Get the location of the next station to visit. * @param station Next station to visit. @@ -3065,6 +3283,7 @@ static bool TrainMovedChangeSignals(TileIndex tile, DiagDirection dir) void Train::ReserveTrackUnderConsist() const { for (const Train *u = this; u != nullptr; u = u->Next()) { + if (u->vehstatus & VS_HIDDEN) continue; switch (u->track) { case TRACK_BIT_WORMHOLE: TryReserveRailTrack(u->tile, DiagDirToDiagTrack(GetTunnelBridgeDirection(u->tile))); @@ -3093,6 +3312,24 @@ uint Train::Crash(bool flooded) /* Remove the reserved path in front of the train if it is not stuck. * Also clear all reserved tracks the train is currently on. */ if (!HasBit(this->flags, VRF_TRAIN_STUCK)) FreeTrainTrackReservation(this); + + if (IsExtendedRailDepotTile(this->tile)) { + if (this->track & ~TRACK_BIT_DEPOT) { + for (Train *v = this; v != nullptr; v = v->Next()) { + v->track &= ~TRACK_BIT_DEPOT; + } + UpdateExtendedDepotReservation(this, false); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(this->tile)); + } + /* Remove reserved tracks of platform ahead. */ + TileIndexDiff diff = TileOffsByDiagDir(TrackdirToExitdir(this->GetVehicleTrackdir())); + for (TileIndex pt_tile = this->tile + diff; IsCompatiblePlatformTile(pt_tile, this->tile) && HasDepotReservation(pt_tile); pt_tile += diff) { + if (EnsureNoVisibleVehicleOnGround(pt_tile).Failed()) break; + SetDepotReservation(pt_tile, false); + MarkTileDirtyByTile(pt_tile); + } + } + for (const Train *v = this; v != nullptr; v = v->Next()) { ClearPathReservation(v, v->tile, v->GetVehicleTrackdir()); if (IsTileType(v->tile, MP_TUNNELBRIDGE)) { @@ -3100,6 +3337,23 @@ uint Train::Crash(bool flooded) * if the train has just entered the wormhole. */ SetTunnelBridgeReservation(GetOtherTunnelBridgeEnd(v->tile), false); } + + if (v->Next() == nullptr && (IsRailStationTile(v->tile) || IsExtendedRailDepotTile(v->tile))) { + /* Remove reserved tracks of platform tiles behind the train. */ + TileIndexDiff diff = TileOffsByDiagDir(TrackdirToExitdir(ReverseTrackdir(v->GetVehicleTrackdir()))); + for (TileIndex pt_tile = v->tile + diff; IsCompatiblePlatformTile(pt_tile, v->tile); pt_tile += diff) { + if (IsExtendedRailDepotTile(pt_tile)) { + if (!HasDepotReservation(pt_tile)) break; + if (EnsureNoVisibleVehicleOnGround(pt_tile).Failed()) break; + SetDepotReservation(pt_tile, false); + } else { + if (!HasStationReservation(pt_tile)) break; + if (EnsureNoVisibleVehicleOnGround(pt_tile).Failed()) break; + SetRailStationReservation(pt_tile, false); + } + MarkTileDirtyByTile(pt_tile); + } + } } /* we may need to update crossing we were approaching, @@ -3112,6 +3366,7 @@ uint Train::Crash(bool flooded) } victims += this->GroundVehicleBase::Crash(flooded); + this->ReserveTrackUnderConsist(); this->crash_anim_pos = flooded ? 4000 : 1; // max 4440, disappear pretty fast when flooded return victims; @@ -3190,6 +3445,9 @@ static Vehicle *FindTrainCollideEnum(Vehicle *v, void *data) tcc->num += TrainCrashed(tcc->v); tcc->num += TrainCrashed(coll); + /* The crashing of the coll train frees reservation of train v: Reserve again for train v. */ + tcc->v->ReserveTrackUnderConsist(); + return nullptr; // continue searching } @@ -3285,6 +3543,12 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse) if (HasBit(r, VETS_ENTERED_STATION)) { /* The new position is the end of the platform */ TrainEnterStation(v, r >> VETS_STATION_ID_OFFSET); + } else if (HasBit(r, VETS_ENTERED_DEPOT_PLATFORM)) { + if (HandleTrainEnterDepot(first)) { + v->UpdatePosition(); + v->UpdateViewport(true, true); + return false; + } } } } else { @@ -3628,7 +3892,7 @@ static void DeleteLastWagon(Train *v) /* Update the depot window if the first vehicle is in depot - * if v == first, then it is updated in PreDestructor() */ if (first->track == TRACK_BIT_DEPOT) { - SetWindowDirty(WC_VEHICLE_DEPOT, first->tile); + SetWindowDirty(WC_VEHICLE_DEPOT, GetDepotIndex(first->tile)); } v->last_station_visited = first->last_station_visited; // for PreDestructor } @@ -3670,7 +3934,7 @@ static void DeleteLastWagon(Train *v) } /* Update signals */ - if (IsTileType(tile, MP_TUNNELBRIDGE) || IsRailDepotTile(tile)) { + if (IsTileType(tile, MP_TUNNELBRIDGE) || IsStandardRailDepotTile(tile)) { UpdateSignalsOnSegment(tile, INVALID_DIAGDIR, owner); } else { SetSignalsOnBothDir(tile, track, owner); @@ -3821,8 +4085,13 @@ static bool TrainCanLeaveTile(const Train *v) /* entering a depot? */ if (IsRailDepotTile(tile)) { - DiagDirection dir = ReverseDiagDir(GetRailDepotDirection(tile)); - if (DiagDirToDir(dir) == v->direction) return false; + if (IsExtendedRailDepot(tile)) { + Direction dir = DiagDirToDir(GetRailDepotDirection(tile)); + if (dir == v->direction || ReverseDir(dir) == v->direction) return false; + } else { + DiagDirection dir = ReverseDiagDir(GetRailDepotDirection(tile)); + if (DiagDirToDir(dir) == v->direction) return false; + } } return true; @@ -3936,6 +4205,8 @@ static bool TrainLocoHandler(Train *v, bool mode) /* exit if train is stopped */ if ((v->vehstatus & VS_STOPPED) && v->cur_speed == 0) return true; + if (v->ContinueServicing()) return true; + bool valid_order = !v->current_order.IsType(OT_NOTHING) && v->current_order.GetType() != OT_CONDITIONAL; if (ProcessOrders(v) && CheckReverseTrain(v)) { v->wait_counter = 0; @@ -4198,9 +4469,13 @@ Trackdir Train::GetVehicleTrackdir() const { if (this->vehstatus & VS_CRASHED) return INVALID_TRACKDIR; - if (this->track == TRACK_BIT_DEPOT) { + if (this->IsInDepot()) { /* We'll assume the train is facing outwards */ - return DiagDirToDiagTrackdir(GetRailDepotDirection(this->tile)); // Train in depot + if (this->track == TRACK_BIT_DEPOT) + return DiagDirToDiagTrackdir(DirToDiagDir(this->direction)); // Train in depot + Track track = FindFirstTrack(this->track & ~TRACK_BIT_DEPOT); + assert(IsValidTrack(track)); + return TrackDirectionToTrackdir(track, this->direction); } if (this->track == TRACK_BIT_WORMHOLE) { diff --git a/src/train_gui.cpp b/src/train_gui.cpp index d62d3dce55abb..51ad7a9d0069b 100644 --- a/src/train_gui.cpp +++ b/src/train_gui.cpp @@ -15,6 +15,7 @@ #include "vehicle_func.h" #include "zoom_func.h" #include "train_cmd.h" +#include "depot_map.h" #include "table/strings.h" @@ -29,11 +30,12 @@ void CcBuildWagon(Commands, const CommandCost &result, VehicleID new_veh_id, uint, uint16_t, CargoArray, TileIndex tile, EngineID, bool, CargoID, ClientID) { if (result.Failed()) return; + DepotID depot_id = GetDepotIndex(tile); /* find a locomotive in the depot. */ const Vehicle *found = nullptr; for (const Train *t : Train::Iterate()) { - if (t->IsFrontEngine() && t->tile == tile && t->IsStoppedInDepot()) { + if (t->IsFrontEngine() && t->IsStoppedInDepot() && GetDepotIndex(t->tile) == depot_id) { if (found != nullptr) return; // must be exactly one. found = t; } diff --git a/src/train_placement.cpp b/src/train_placement.cpp new file mode 100644 index 0000000000000..0f05da5ebc016 --- /dev/null +++ b/src/train_placement.cpp @@ -0,0 +1,314 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file train_placement.cpp Handling of trains in depot platforms. */ + +#include "stdafx.h" +#include "error.h" +#include "news_func.h" +#include "company_func.h" +#include "strings_func.h" +#include "platform_func.h" +#include "depot_base.h" +#include "depot_map.h" +#include "train_placement.h" +#include "train.h" + +#include "table/strings.h" + +#include "safeguards.h" + + +/** + * Check if a train can be placed in a given tile. + * @param train The train. + * @param check_tile The tile where we want to check whether it is possible to place the train. + * @param executing False if testing and true if the call is being executed. + * @return whether it found a platform to place the train. + */ +bool TrainPlacement::CheckPlacement(const Train *train, TileIndex check_tile, bool executing) +{ + assert(train != nullptr); + assert(IsRailDepotTile(check_tile)); + + RailType rt = GetRailType(check_tile); + PlacementInfo error_info = PI_FAILED_FREE_WAGGON; + bool is_extended_depot = IsExtendedRailDepot(check_tile); + bool succeeded = !train->IsFreeWagon(); + + if (succeeded) { + error_info = PI_FAILED_PLATFORM_TYPE; + for (const Train *t = train; t != nullptr && succeeded; t = t->Next()) { + RailType rail_type = Engine::Get(t->engine_type)->u.rail.railtype; + if (!IsCompatibleRail(rail_type, rt)) succeeded = false; + } + } + + if (succeeded && is_extended_depot) { + error_info = PI_FAILED_LENGTH; + if (train->gcache.cached_total_length > GetPlatformLength(check_tile) * TILE_SIZE) succeeded = false; + } + + if (succeeded) { + error_info = PI_FAILED_POWER; + bool has_power = false; + for (const Train *t = train; t != nullptr && !has_power; t = t->Next()) { + if (HasPowerOnRail(train->railtype, rt)) has_power = true; + } + if (!has_power) succeeded = false; + } + + if (succeeded && is_extended_depot) { + error_info = PI_FAILED_RESERVED; + + /* Check whether any tile of the platform is reserved. Don't assume all platform + * is reserved as a whole: sections of the platform may be reserved by crashed trains. */ + for (TileIndex tile : GetPlatformTileArea(check_tile)) { + if (HasDepotReservation(tile)) { + succeeded = false; + break; + } + } + } + + if (succeeded && executing) { + /* Do not check for signals if really not executing and action. */ + error_info = PI_FAILED_SIGNALS; + SigSegState seg_state = UpdateSignalsOnSegment(check_tile, INVALID_DIAGDIR, train->owner); + if (train->force_proceed == TFP_NONE && seg_state == SIGSEG_FULL) succeeded = false; + } + + if (succeeded) error_info = PI_SUCCESS; + + if (error_info > this->info) { + this->best_tile = check_tile; + this->info = error_info; + + /* A direction for the train must be choosen: the one that allows the longest train in platform. */ + DiagDirection dir = GetRailDepotDirection(check_tile); + if (is_extended_depot && GetPlatformLength(check_tile, dir) > GetPlatformLength(check_tile, ReverseDiagDir(dir))) { + dir = ReverseDiagDir(dir); + } + this->best_dir = DiagDirToDir(dir); + } + + return succeeded; +} + +/** + * Before placing a train in the rails of a depot, a valid platform must + * be found. This function finds a tile for placing the train (and also gets the direction and track). + * If there is no valid tile, it will be returned as best_tile == INVALID_TILE or info == PI_FAILED_PLATFORM_TYPE. + * @param t The train we want to place in rails. + * @param executing False if testing and true if the call is being executed. + * @pre The train must be inside the rail depot as if it where in a standard depot. + * (i.e. the track is TRACK_BIT_DEPOT, vehicles are hidden...). + */ +void TrainPlacement::LookForPlaceInDepot(const Train *train, bool executing) +{ + assert(train != nullptr); + assert(IsRailDepotTile(train->tile)); + + /* Initialitzation. */ + bool is_extended_depot = IsExtendedRailDepot(train->tile); + this->best_tile = (this->placed || !is_extended_depot) ? train->tile : GetPlatformExtremeTile(train->tile, DirToDiagDir(train->direction)); + assert(IsStandardRailDepot(this->best_tile) || IsAnyStartPlatformTile(this->best_tile)); + this->best_dir = train->direction; + this->info = PI_BEGIN; + + /* First candidate is the original position of the train. */ + if (CheckPlacement(train, this->best_tile, executing)) return; + + /* Check all platforms. */ + Depot *depot = Depot::GetByTile(train->tile); + for (auto &depot_tile : depot->depot_tiles) { + if (CheckPlacement(train, depot_tile, executing)) return; + } +} + +/** + * Check if a train can leave now or when other trains + * move away. It returns whether there is a platform long + * enough and with the appropriate rail type. + * @param train The train. + * @param executing False if testing and true if the call is being executed. + * @return true iff there is a compatible platform long enough. + */ +bool TrainPlacement::CanFindAppropriatePlatform(const Train *train, bool executing) +{ + this->LookForPlaceInDepot(train, executing); + return this->info >= PI_WONT_LEAVE; +} + + +/** + * Lift a train in a depot: keep the positions of the elements of the chain if needed, + * and keep also the original tile, direction and track. + * @param train The train we want to lift. + * @pre The train must be inside a rail depot. + * (i.e. the track is 'valid track | TRACK_BIT_DEPOT' or just 'TRACK_BIT_DEPOT'). + */ +void TrainPlacement::LiftTrain(Train *train, DoCommandFlag flags) +{ + assert(train == nullptr || train->IsInDepot()); + assert(train == nullptr || IsRailDepotTile(train->tile)); + assert(this->placed == false); + + /* Lift the train only if we have a train in an extended depot. */ + if (train == nullptr || !IsExtendedRailDepot(train->tile)) return; + + /* Do not lift in recursive commands of autoreplace. */ + if (flags & DC_AUTOREPLACE) return; + + /* If train is not placed... return, because train is already lifted. */ + if ((train->track & ~TRACK_BIT_DEPOT) == 0) return; + + /* Train is placed in rails: lift it. */ + this->placed = true; + if (flags & DC_EXEC) FreeTrainTrackReservation(train); + + for (Train *t = train; t != nullptr; t = t->Next()) { + // Lift. + t->track = TRACK_BIT_DEPOT; + t->tile = train->tile; + t->x_pos = train->x_pos; + t->y_pos = train->y_pos; + t->UpdatePosition(); + t->UpdateViewport(true, true); + + } + + if ((flags & DC_EXEC) == 0) return; + + SetPlatformReservation(train->tile, false); + UpdateExtendedDepotReservation(train, false); + + UpdateSignalsOnSegment(train->tile, INVALID_DIAGDIR, train->owner); +} + +/** + * When a train is lifted inside a depot, before starting its way again, + * must be placed in rails if in an extended rail depot; this function does all necessary things to do so. + * In general, it's the opposite of #LiftTrain + * @param train The train we want to place in rails. + * @param flags Associated command flags + * @pre The train must be inside the extended rail depot as if in a standard depot. + * (i.e. the track is TRACK_BIT_DEPOT, vehicles are hidden...). + */ +void TrainPlacement::PlaceTrain(Train *train, DoCommandFlag flags) +{ + if (train == nullptr) return; + if (train != train->First()) return; + if (!IsRailDepotTile(train->tile)) return; + if (flags & DC_AUTOREPLACE) return; + + bool executing = (flags & DC_EXEC) != 0; + + /* Look for an appropriate platform. */ + this->LookForPlaceInDepot(train, executing); + assert(!IsExtendedRailDepot(this->best_tile) || IsAnyStartPlatformTile(this->best_tile)); + + if (this->info < PI_FAILED_END || !executing) { + if (!executing) { + /* Restore the train. */ + this->best_tile = train->tile; + this->best_dir = train->direction; + this->info = PI_SUCCESS; + } + + if (!this->placed || (this->info < PI_FAILED_END && executing)) { + for (Train *t = train; t != nullptr; t = t->Next()) { + t->tile = this->best_tile; + t->vehstatus |= VS_HIDDEN; + t->track = TRACK_BIT_DEPOT; + } + if (!executing) return; + train->PowerChanged(); + } + + if (this->info < PI_FAILED_END && executing) { + /* Train cannot leave until changing the depot. Stop the train and send a message. */ + if (info < PI_WONT_LEAVE) { + train->vehstatus |= VS_STOPPED; + /* If vehicle is not stopped and user is the local company, send a message if needed. */ + if ((train->vehstatus & VS_STOPPED) == 0 && train->owner == _local_company && train->IsFrontEngine()) { + SetDParam(0, train->index); + AddVehicleAdviceNewsItem(STR_ADVICE_PLATFORM_TYPE + info - PI_ERROR_BEGIN, train->index); + } + } + return; + } + } + + assert(this->best_tile != INVALID_TILE); + assert(this->best_dir != INVALID_DIR); + assert(IsRailDepotTile(this->best_tile)); + + if (executing) { + train->tile = this->best_tile; + train->track = TrackToTrackBits(GetRailDepotTrack(this->best_tile)); + train->direction = this->best_dir; + train->PowerChanged(); + } + + if (IsStandardRailDepot(this->best_tile)) { + int x = TileX(this->best_tile) * TILE_SIZE + _vehicle_initial_x_fract[DirToDiagDir(this->best_dir)]; + int y = TileY(this->best_tile) * TILE_SIZE + _vehicle_initial_y_fract[DirToDiagDir(this->best_dir)]; + for (Train *t = train; t != nullptr; t = t->Next()) { + t->tile = this->best_tile; + t->direction = this->best_dir; + t->vehstatus |= VS_HIDDEN; + t->track = TRACK_BIT_DEPOT; + t->x_pos = x; + t->y_pos = y; + t->z_pos = GetSlopePixelZ(x, y); + t->UpdatePosition(); + t->UpdateViewport(true, true); + } + return; + } + + DiagDirection placing_dir = ReverseDiagDir(DirToDiagDir(this->best_dir)); + + static const uint8_t _plat_initial_x_fract[4] = {15, 8, 0, 8}; + static const uint8_t _plat_initial_y_fract[4] = { 8, 0, 8, 15}; + + int x = TileX(this->best_tile) * TILE_SIZE | _plat_initial_x_fract[placing_dir]; + int y = TileY(this->best_tile) * TILE_SIZE | _plat_initial_y_fract[placing_dir]; + + /* Add the offset for the first vehicle. */ + x += TileIndexDiffCByDiagDir(placing_dir).x * (train->gcache.cached_veh_length + 1) / 2; + y += TileIndexDiffCByDiagDir(placing_dir).y * (train->gcache.cached_veh_length + 1) / 2; + + /* Proceed placing the train in the given tile. + * At this point, the first vehicle contains the direction, tile and track. + * We must update positions of all the chain. */ + for (Train *t = train; t != nullptr; t = t->Next()) { + t->vehstatus &= ~VS_HIDDEN; + t->direction = this->best_dir; + t->track = DiagDirToDiagTrackBits(placing_dir) | TRACK_BIT_DEPOT; + t->x_pos = x; + t->y_pos = y; + t->z_pos = GetSlopePixelZ(t->x_pos, t->y_pos); + t->tile = TileVirtXY(t->x_pos,t->y_pos); + + assert(t->z_pos == train->z_pos); + assert(IsExtendedRailDepotTile(t->tile)); + + t->UpdatePosition(); + t->UpdateViewport(true, true); + + int advance = t->CalcNextVehicleOffset(); + x += TileIndexDiffCByDiagDir(placing_dir).x * advance; + y += TileIndexDiffCByDiagDir(placing_dir).y * advance; + } + + SetPlatformReservation(train->tile, true); + UpdateExtendedDepotReservation(train, true); + + UpdateSignalsOnSegment(train->tile, INVALID_DIAGDIR, train->owner); +} diff --git a/src/train_placement.h b/src/train_placement.h new file mode 100644 index 0000000000000..dfa11458941d8 --- /dev/null +++ b/src/train_placement.h @@ -0,0 +1,58 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file train_placement.h Handling of trains in depot platforms. */ + +#ifndef TRAIN_PLA_H +#define TRAIN_PLA_H + +#include "core/enum_type.hpp" +#include "train.h" + + +/* Flags of failure and success when placing a train. */ +enum PlacementInfo { + PI_BEGIN = 0, + PI_FAILED_FREE_WAGGON = PI_BEGIN, // Free waggon: not to be placed. + PI_ERROR_BEGIN, + PI_FAILED_PLATFORM_TYPE = PI_ERROR_BEGIN, // No compatible platforms with train type. + PI_FAILED_LENGTH, // There are compatible platforms but not long enough. + PI_FAILED_POWER, // No engine gets power in the platform. + PI_WONT_LEAVE, + PI_FAILED_RESERVED = PI_WONT_LEAVE, // There are compatible platforms but reserved right now. + PI_FAILED_SIGNALS, // There are compatible platforms not reserved, but signals don't allow placing it now. + PI_FAILED_END, + PI_SUCCESS = PI_FAILED_END, // There is an appropriate platform. + PI_END, +}; + +/* Store position of a train and lift it when necessary. */ +struct TrainPlacement { + bool placed; // True if train is placed in rails. + TileIndex best_tile; // Best tile for the train. + Direction best_dir; // Best direction for the train. + PlacementInfo info; // Info of possible problems in best platform. + + TrainPlacement() : placed(false), + best_tile(INVALID_TILE), + best_dir(INVALID_DIR), + info(PI_FAILED_PLATFORM_TYPE) {} + + bool CheckPlacement(const Train *train, TileIndex tile, bool executing); + void LookForPlaceInDepot(const Train *train, bool executing); + bool CanFindAppropriatePlatform(const Train *train, bool executing); + + void LiftTrain(Train *train, DoCommandFlag flags); + void PlaceTrain(Train *train, DoCommandFlag flags); +}; + +static inline bool CheckIfTrainNeedsPlacement(const Train *train) +{ + return IsExtendedRailDepot(train->tile) && (train->track & ~TRACK_BIT_DEPOT) == 0; +} + +#endif /* TRAIN_PLA_H */ diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 9db7da2fa1c8f..d9f92bc9af2e8 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -18,6 +18,7 @@ #include "command_func.h" #include "company_func.h" #include "train.h" +#include "air.h" #include "aircraft.h" #include "newgrf_debug.h" #include "newgrf_sound.h" @@ -44,6 +45,7 @@ #include "effectvehicle_func.h" #include "effectvehicle_base.h" #include "vehiclelist.h" +#include "air_map.h" #include "bridge_map.h" #include "tunnel_map.h" #include "depot_map.h" @@ -181,6 +183,118 @@ void VehicleServiceInDepot(Vehicle *v) } while (v != nullptr && v->HasEngineType()); } +/** + * List of vehicles that should check for autoreplace this tick. + * Mapping of vehicle -> leave depot immediately after autoreplace. + */ +using AutoreplaceMap = std::map; +static AutoreplaceMap _vehicles_to_autoreplace; + +void VehicleServiceInExtendedDepot(Vehicle *v) +{ + /* Always work with the front of the vehicle */ + assert(v == v->First()); + assert(IsExtendedDepotTile(v->tile)); + + switch (v->type) { + case VEH_TRAIN: { + SetWindowClassesDirty(WC_TRAINS_LIST); + Train *t = Train::From(v); + t->ConsistChanged(CCF_ARRANGE); + t->UpdateViewport(true, true); + break; + } + + case VEH_SHIP: { + SetWindowClassesDirty(WC_SHIPS_LIST); + Ship *ship = Ship::From(v); + ship->UpdateCache(); + ship->UpdateViewport(true, true); + break; + } + + case VEH_ROAD: + SetWindowClassesDirty(WC_ROADVEH_LIST); + break; + + case VEH_AIRCRAFT: + Aircraft::From(v)->dest_tile = 0; + SetWindowClassesDirty(WC_AIRCRAFT_LIST); + break; + + default: NOT_REACHED(); + } + + DepotID depot_id = GetDepotIndex(v->tile); + SetWindowDirty(WC_VEHICLE_DEPOT, depot_id); + SetWindowDirty(WC_VEHICLE_VIEW, v->index); + + InvalidateWindowData(WC_VEHICLE_DEPOT, depot_id); + + VehicleServiceInDepot(v); + + /* After a vehicle trigger, the graphics and properties of the vehicle could change. */ + TriggerVehicle(v, VEHICLE_TRIGGER_DEPOT); + v->MarkDirty(); + + if (v->current_order.IsType(OT_GOTO_DEPOT)) { + SetWindowDirty(WC_VEHICLE_VIEW, v->index); + + const Order *real_order = v->GetOrder(v->cur_real_order_index); + Order t = v->current_order; + v->current_order.MakeDummy(); + + /* Test whether we are heading for this depot. If not, do nothing. + * Note: The target depot for nearest-/manual-depot-orders is only updated on junctions, but we want to accept every depot. */ + if ((t.GetDepotOrderType() & ODTFB_PART_OF_ORDERS) && + real_order != nullptr && !(real_order->GetDepotActionType() & ODATFB_NEAREST_DEPOT) && + t.GetDestination() != GetDepotIndex(v->tile)) { + /* We are heading for another depot, keep driving. */ + return; + } + + if (t.IsRefit()) { + Backup cur_company(_current_company, v->owner); + CommandCost cost = std::get<0>(Command::Do(DC_EXEC, v->index, t.GetRefitCargo(), 0xFF, false, false, 0)); + cur_company.Restore(); + + if (cost.Failed()) { + _vehicles_to_autoreplace[v->index] = false; + if (v->owner == _local_company) { + /* Notify the user that we stopped the vehicle */ + SetDParam(0, v->index); + AddVehicleAdviceNewsItem(STR_NEWS_ORDER_REFIT_FAILED, v->index); + } + } else if (cost.GetCost() != 0) { + v->profit_this_year -= cost.GetCost() << 8; + if (v->owner == _local_company) { + ShowCostOrIncomeAnimation(v->x_pos, v->y_pos, v->z_pos, cost.GetCost()); + } + } + } + + if (t.GetDepotOrderType() & ODTFB_PART_OF_ORDERS) { + /* Part of orders */ + v->DeleteUnreachedImplicitOrders(); + UpdateVehicleTimetable(v, true); + v->IncrementImplicitOrderIndex(); + } + if (t.GetDepotActionType() & ODATFB_HALT) { + /* Vehicles are always stopped on entering depots. Do not restart this one. */ + _vehicles_to_autoreplace[v->index] = false; + /* Invalidate last_loading_station. As the link from the station + * before the stop to the station after the stop can't be predicted + * we shouldn't construct it when the vehicle visits the next stop. */ + v->last_loading_station = INVALID_STATION; + if (v->owner == _local_company) { + SetDParam(0, v->index); + AddVehicleAdviceNewsItem(STR_NEWS_TRAIN_IS_WAITING + v->type, v->index); + } + AI::NewEvent(v->owner, new ScriptEventVehicleWaitingInDepot(v->index)); + } + } +} + /** * Check if the vehicle needs to go to a depot in near future (if a opportunity presents itself) for service or replacement. * @@ -297,7 +411,7 @@ uint Vehicle::Crash(bool) InvalidateWindowClassesData(GetWindowClassForVehicleType(this->type), 0); SetWindowWidgetDirty(WC_VEHICLE_VIEW, this->index, WID_VV_START_STOP); SetWindowDirty(WC_VEHICLE_DETAILS, this->index); - SetWindowDirty(WC_VEHICLE_DEPOT, this->tile); + if (IsDepotTile(this->tile)) SetWindowDirty(WC_VEHICLE_DEPOT, GetDepotIndex(this->tile)); delete this->cargo_payment; assert(this->cargo_payment == nullptr); // cleared by ~CargoPayment @@ -556,6 +670,43 @@ CommandCost EnsureNoVehicleOnGround(TileIndex tile) return CommandCost(); } +/** + * Ensure there is no visible vehicle at the ground at the given position. + * @param tile Position to examine. + * @return Succeeded command (ground is free) or failed command (a visible vehicle is found). + */ +CommandCost EnsureNoVisibleVehicleOnGround(TileIndex tile) +{ + int z = GetTileMaxPixelZ(tile); + + /* Value v is not safe in MP games, however, it is used to generate a local + * error message only (which may be different for different machines). + * Such a message does not affect MP synchronisation. + */ + Vehicle *v = VehicleFromPos(tile, &z, &EnsureNoVehicleProcZ, true); + if (v != nullptr && (v->vehstatus & VS_HIDDEN) == 0) return_cmd_error(STR_ERROR_TRAIN_IN_THE_WAY + v->type); + return CommandCost(); +} + +/** + * Ensure there is no vehicle at the ground at the given position. + * @param tile Position to examine. + * @return Succeeded command (ground is free) or failed command (a vehicle is found). + */ +CommandCost EnsureFreeHangar(TileIndex tile) +{ + int z = GetTileMaxPixelZ(tile) + 1; + + /* Value v is not safe in MP games, however, it is used to generate a local + * error message only (which may be different for different machines). + * Such a message does not affect MP synchronisation. + */ + Vehicle *v = VehicleFromPos(tile, &z, &EnsureNoVehicleProcZ, true); + if (v != nullptr) return_cmd_error(STR_ERROR_AIRCRAFT_IN_THE_WAY); + + return CommandCost(); +} + /** Procedure called for every vehicle found in tunnel/bridge in the hash map */ static Vehicle *GetVehicleTunnelBridgeProc(Vehicle *v, void *data) { @@ -687,13 +838,6 @@ void ResetVehicleColourMap() for (Vehicle *v : Vehicle::Iterate()) { v->colourmap = PAL_NONE; } } -/** - * List of vehicles that should check for autoreplace this tick. - * Mapping of vehicle -> leave depot immediately after autoreplace. - */ -using AutoreplaceMap = std::map; -static AutoreplaceMap _vehicles_to_autoreplace; - void InitializeVehicles() { _vehicles_to_autoreplace.clear(); @@ -790,6 +934,22 @@ void Vehicle::ShiftDates(TimerGameEconomy::Date interval) */ void Vehicle::HandlePathfindingResult(bool path_found) { + if (this->dest_tile != INVALID_TILE && IsDepotTypeTile(this->dest_tile, (TransportType)this->type) && IsDepotFullWithStoppedVehicles(this->dest_tile)) { + /* Vehicle cannot find a free depot. */ + /* Were we already lost? */ + if (HasBit(this->vehicle_flags, VF_PATHFINDER_LOST)) return; + + /* It is first time the problem occurred, set the "lost" flag. */ + SetBit(this->vehicle_flags, VF_PATHFINDER_LOST); + /* Notify user about the event. */ + AI::NewEvent(this->owner, new ScriptEventVehicleLost(this->index)); + if (_settings_client.gui.lost_vehicle_warn && this->owner == _local_company) { + SetDParam(0, this->index); + AddVehicleAdviceNewsItem(STR_NEWS_VEHICLE_CAN_T_FIND_FREE_DEPOT, this->index); + } + return; + } + if (path_found) { /* Route found, is the vehicle marked with "lost" flag? */ if (!HasBit(this->vehicle_flags, VF_PATHFINDER_LOST)) return; @@ -848,16 +1008,6 @@ void Vehicle::PreDestructor() Company::Get(this->owner)->freeunits[this->type].ReleaseID(this->unitnumber); - if (this->type == VEH_AIRCRAFT && this->IsPrimaryVehicle()) { - Aircraft *a = Aircraft::From(this); - Station *st = GetTargetAirportIfValid(a); - if (st != nullptr) { - const AirportFTA *layout = st->airport.GetFTA()->layout; - CLRBITS(st->airport.flags, layout[a->previous_pos].block | layout[a->pos].block); - } - } - - if (this->type == VEH_ROAD && this->IsPrimaryVehicle()) { RoadVehicle *v = RoadVehicle::From(this); if (!(v->vehstatus & VS_CRASHED) && IsInsideMM(v->state, RVSB_IN_DT_ROAD_STOP, RVSB_IN_DT_ROAD_STOP_END)) { @@ -868,8 +1018,8 @@ void Vehicle::PreDestructor() if (v->disaster_vehicle != INVALID_VEHICLE) ReleaseDisasterVehicle(v->disaster_vehicle); } - if (this->Previous() == nullptr) { - InvalidateWindowData(WC_VEHICLE_DEPOT, this->tile); + if (this->Previous() == nullptr && IsDepotTile(this->tile)) { + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(this->tile)); } if (this->IsPrimaryVehicle()) { @@ -1078,7 +1228,12 @@ void CallVehicleTicks() /* Start vehicle if we stopped them in VehicleEnteredDepotThisTick() * We need to stop them between VehicleEnteredDepotThisTick() and here or we risk that * they are already leaving the depot again before being replaced. */ - if (it.second) v->vehstatus &= ~VS_STOPPED; + if (it.second) { + v->vehstatus &= ~VS_STOPPED; + } else if (IsExtendedDepotTile(v->tile)){ + if (v->type == VEH_TRAIN) FreeTrainTrackReservation(Train::From(v)); + UpdateExtendedDepotReservation(v, true); + } /* Store the position of the effect as the vehicle pointer will become invalid later */ int x = v->x_pos; @@ -1554,12 +1709,11 @@ void VehicleEnterDepot(Vehicle *v) /* Always work with the front of the vehicle */ assert(v == v->First()); + DepotID depot_id = GetDepotIndex(v->tile); switch (v->type) { case VEH_TRAIN: { Train *t = Train::From(v); SetWindowClassesDirty(WC_TRAINS_LIST); - /* Clear path reservation */ - SetDepotReservation(t->tile, false); if (_settings_client.gui.show_track_reservation) MarkTileDirtyByTile(t->tile); UpdateSignalsOnSegment(t->tile, INVALID_DIAGDIR, t->owner); @@ -1580,14 +1734,14 @@ void VehicleEnterDepot(Vehicle *v) ship->state = TRACK_BIT_DEPOT; ship->UpdateCache(); ship->UpdateViewport(true, true); - SetWindowDirty(WC_VEHICLE_DEPOT, v->tile); + SetWindowDirty(WC_VEHICLE_DEPOT, depot_id); break; } case VEH_AIRCRAFT: SetWindowClassesDirty(WC_AIRCRAFT_LIST); - HandleAircraftEnterHangar(Aircraft::From(v)); break; + default: NOT_REACHED(); } SetWindowDirty(WC_VEHICLE_VIEW, v->index); @@ -1595,9 +1749,9 @@ void VehicleEnterDepot(Vehicle *v) if (v->type != VEH_TRAIN) { /* Trains update the vehicle list when the first unit enters the depot and calls VehicleEnterDepot() when the last unit enters. * We only increase the number of vehicles when the first one enters, so we will not need to search for more vehicles in the depot */ - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, depot_id); } - SetWindowDirty(WC_VEHICLE_DEPOT, v->tile); + SetWindowDirty(WC_VEHICLE_DEPOT, depot_id); v->vehstatus |= VS_HIDDEN; v->cur_speed = 0; @@ -1619,7 +1773,7 @@ void VehicleEnterDepot(Vehicle *v) * Note: The target depot for nearest-/manual-depot-orders is only updated on junctions, but we want to accept every depot. */ if ((v->current_order.GetDepotOrderType() & ODTFB_PART_OF_ORDERS) && real_order != nullptr && !(real_order->GetDepotActionType() & ODATFB_NEAREST_DEPOT) && - (v->type == VEH_AIRCRAFT ? v->current_order.GetDestination() != GetStationIndex(v->tile) : v->dest_tile != v->tile)) { + v->current_order.GetDestination() != GetDepotIndex(v->tile)) { /* We are heading for another depot, keep driving. */ return; } @@ -2634,27 +2788,18 @@ CommandCost Vehicle::SendToDepot(DoCommandFlag flags, DepotCommand command) SetBit(gv_flags, GVF_SUPPRESS_IMPLICIT_ORDERS); } - this->SetDestTile(closestDepot.location); this->current_order.MakeGoToDepot(closestDepot.destination, ODTF_MANUAL); if ((command & DepotCommand::Service) == DepotCommand::None) this->current_order.SetDepotActionType(ODATFB_HALT); + this->SetDestTile(closestDepot.location); SetWindowWidgetDirty(WC_VEHICLE_VIEW, this->index, WID_VV_START_STOP); /* If there is no depot in front and the train is not already reversing, reverse automatically (trains only) */ if (this->type == VEH_TRAIN && (closestDepot.reverse ^ HasBit(Train::From(this)->flags, VRF_REVERSING))) { Command::Do(DC_EXEC, this->index, false); } - - if (this->type == VEH_AIRCRAFT) { - Aircraft *a = Aircraft::From(this); - if (a->state == FLYING && a->targetairport != closestDepot.destination) { - /* The aircraft is now heading for a different hangar than the next in the orders */ - AircraftNextAirportPos_and_Order(a); - } - } } return CommandCost(); - } /** @@ -3074,8 +3219,14 @@ bool CanVehicleUseStation(EngineID engine_type, const Station *st) return (st->facilities & FACIL_DOCK) != 0; case VEH_AIRCRAFT: - return (st->facilities & FACIL_AIRPORT) != 0 && - (st->airport.GetFTA()->flags & (e->u.air.subtype & AIR_CTOL ? AirportFTAClass::AIRPLANES : AirportFTAClass::HELICOPTERS)) != 0; + if ((st->facilities & FACIL_AIRPORT) == 0) return false; + if (st->airport.type == AT_OILRIG && e->u.air.subtype == AIR_HELI) return true; + if (!IsCompatibleAirType(e->u.air.airtype, st->airport.air_type)) return false; + + if (e->u.air.subtype & AIR_CTOL) return st->airport.HasLandingRunway(); + + return !st->airport.aprons.empty() || + (e->u.air.subtype == AIR_HELI && (!st->airport.helipads.empty() || !st->airport.heliports.empty())); default: return false; diff --git a/src/vehicle_base.h b/src/vehicle_base.h index 65fac6e1035f7..4b27b35cfe09f 100644 --- a/src/vehicle_base.h +++ b/src/vehicle_base.h @@ -52,6 +52,7 @@ enum VehicleFlags { VF_PATHFINDER_LOST, ///< Vehicle's pathfinder is lost. VF_SERVINT_IS_CUSTOM, ///< Service interval is custom. VF_SERVINT_IS_PERCENT, ///< Service interval is percent. + VF_IS_SERVICING, ///< Vehicle is servicing. }; /** Bit numbers used to indicate which of the #NewGRFCache values are valid. */ @@ -230,14 +231,18 @@ struct RefitDesc { struct ClosestDepot { TileIndex location; DestinationID destination; ///< The DestinationID as used for orders. + StationID st_destination; ///< The StationID of the hangar, for aircraft. bool reverse; bool found; ClosestDepot() : - location(INVALID_TILE), destination(0), reverse(false), found(false) {} + location(INVALID_TILE), destination(0), st_destination(INVALID_STATION), reverse(false), found(false) {} ClosestDepot(TileIndex location, DestinationID destination, bool reverse = false) : location(location), destination(destination), reverse(reverse), found(true) {} + + ClosestDepot(TileIndex location, DestinationID destination, StationID st_station) : + location(location), destination(destination), st_destination(st_station), found(true) {} }; /** %Vehicle data structure. */ @@ -313,23 +318,24 @@ struct Vehicle : VehiclePool::PoolItem<&_vehicle_pool>, BaseVehicle, BaseConsist * 0xff == reserved for another custom sprite */ uint8_t spritenum; - uint8_t x_extent; ///< x-extent of vehicle bounding box - uint8_t y_extent; ///< y-extent of vehicle bounding box - uint8_t z_extent; ///< z-extent of vehicle bounding box - int8_t x_bb_offs; ///< x offset of vehicle bounding box - int8_t y_bb_offs; ///< y offset of vehicle bounding box - int8_t x_offs; ///< x offset for vehicle sprite - int8_t y_offs; ///< y offset for vehicle sprite + uint8_t x_extent; ///< x-extent of vehicle bounding box + uint8_t y_extent; ///< y-extent of vehicle bounding box + uint8_t z_extent; ///< z-extent of vehicle bounding box + int8_t x_bb_offs; ///< x offset of vehicle bounding box + int8_t y_bb_offs; ///< y offset of vehicle bounding box + int8_t x_offs; ///< x offset for vehicle sprite + int8_t y_offs; ///< y offset for vehicle sprite EngineID engine_type; ///< The type of engine used for this vehicle. TextEffectID fill_percent_te_id; ///< a text-effect id to a loading indicator object UnitID unitnumber; ///< unit number, for display purposes only - uint16_t cur_speed; ///< current speed - uint8_t subspeed; ///< fractional speed - uint8_t acceleration; ///< used by train & aircraft - uint32_t motion_counter; ///< counter to occasionally play a vehicle sound. - uint8_t progress; ///< The percentage (if divided by 256) this vehicle already crossed the tile unit. + uint16_t cur_speed; ///< current speed + uint8_t subspeed; ///< fractional speed + uint8_t acceleration; ///< used by train & aircraft + uint32_t motion_counter; ///< counter to occasionally play a vehicle sound. + uint16_t wait_counter; ///< waiting ticks (servicing, waiting in front of a signal or forced proceeding) + uint8_t progress; ///< The percentage (if divided by 256) this vehicle already crossed the tile unit. uint8_t waiting_triggers; ///< Triggers to be yet matched before rerandomizing the random bits. uint16_t random_bits; ///< Bits used for randomized variational spritegroups. @@ -781,6 +787,12 @@ struct Vehicle : VehiclePool::PoolItem<&_vehicle_pool>, BaseVehicle, BaseConsist bool HandleBreakdown(); + bool IsServicing() const { return HasBit(this->vehicle_flags, VF_IS_SERVICING); } + + void StartService(); + bool ContinueServicing(); + void StopServicing(); + bool NeedsAutorenewing(const Company *c, bool use_renew_setting = true) const; bool NeedsServicing() const; diff --git a/src/vehicle_cmd.cpp b/src/vehicle_cmd.cpp index 6d22d56b0abf6..23a06c210491f 100644 --- a/src/vehicle_cmd.cpp +++ b/src/vehicle_cmd.cpp @@ -37,6 +37,10 @@ #include "roadveh_cmd.h" #include "train_cmd.h" #include "ship_cmd.h" +#include "depot_base.h" +#include "train_placement.h" +#include "strings_func.h" +#include "air_map.h" #include #include @@ -178,7 +182,7 @@ std::tuple CmdBuildVehicle(D NormalizeTrainVehInDepot(Train::From(v)); } - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); InvalidateWindowClassesData(GetWindowClassForVehicleType(type), 0); SetWindowDirty(WC_COMPANY, _current_company); if (IsLocalCompany()) { @@ -250,6 +254,20 @@ CommandCost CmdSellVehicle(DoCommandFlag flags, VehicleID v_id, bool sell_chain, ret = CommandCost(EXPENSES_NEW_VEHICLES, -front->value); if (flags & DC_EXEC) { + if (IsExtendedDepot(v->tile) && (flags & DC_AUTOREPLACE) == 0) { + switch (front->type) { + case VEH_ROAD: + UpdateExtendedDepotReservation(v, false); + break; + case VEH_AIRCRAFT: + assert(IsHangar(front->tile)); + RemoveAirportTrackReservation(front->tile, DiagDirToDiagTrack(GetHangarDirection(front->tile))); + MarkTileDirtyByTile(front->tile); + break; + default: + break; + } + } if (front->IsPrimaryVehicle() && backup_order) OrderBackup::Backup(front, client_id); delete front; } @@ -522,6 +540,11 @@ std::tuple CmdRefitVehicle(DoCommandFla /* For ships and aircraft there is always only one. */ only_this |= front->type == VEH_SHIP || front->type == VEH_AIRCRAFT; + /* If it is a train on a depot, lift it. New length of the vehicle won't be checked. */ + Train *train = (v->type == VEH_TRAIN && IsDepotTile(front->tile)) ? Train::From(front) : nullptr; + TrainPlacement train_placement; + train_placement.LiftTrain(train, flags); + auto [cost, refit_capacity, mail_capacity, cargo_capacities] = RefitVehicle(v, only_this, num_vehicles, new_cid, new_subtype, flags, auto_refit); if (flags & DC_EXEC) { @@ -553,12 +576,15 @@ std::tuple CmdRefitVehicle(DoCommandFla InvalidateWindowData(WC_VEHICLE_DETAILS, front->index); InvalidateWindowClassesData(GetWindowClassForVehicleType(v->type), 0); } - SetWindowDirty(WC_VEHICLE_DEPOT, front->tile); + if (IsDepotTile(front->tile)) SetWindowDirty(WC_VEHICLE_DEPOT, GetDepotIndex(front->tile)); } else { /* Always invalidate the cache; querycost might have filled it. */ v->InvalidateNewGRFCacheOfChain(); } + /* If it is a train on an extended depot, try placing it. */ + train_placement.PlaceTrain(train, flags); + return { cost, refit_capacity, mail_capacity, cargo_capacities }; } @@ -583,19 +609,38 @@ CommandCost CmdStartStopVehicle(DoCommandFlag flags, VehicleID veh_id, bool eval if (v->vehstatus & VS_CRASHED) return_cmd_error(STR_ERROR_VEHICLE_IS_DESTROYED); switch (v->type) { - case VEH_TRAIN: - if ((v->vehstatus & VS_STOPPED) && Train::From(v)->gcache.cached_power == 0) return_cmd_error(STR_ERROR_TRAIN_START_NO_POWER); + case VEH_TRAIN: { + Train *t = Train::From(v); + if ((v->vehstatus & VS_STOPPED) && t->gcache.cached_power == 0) return_cmd_error(STR_ERROR_TRAIN_START_NO_POWER); + + /* Train cannot leave until changing the depot. Stop the train and send a message. */ + if (!(flags & DC_AUTOREPLACE) && v->IsStoppedInDepot() && CheckIfTrainNeedsPlacement(t)) { + TrainPlacement train_placement; + if (!train_placement.CanFindAppropriatePlatform(t, (flags & DC_EXEC) != 0)) { + SetDParam(0, v->index); + return_cmd_error(STR_ERROR_CAN_T_START_PLATFORM_TYPE + train_placement.info - PI_FAILED_PLATFORM_TYPE); + } + } break; + } case VEH_SHIP: + break; case VEH_ROAD: + if ((v->vehstatus & VS_STOPPED) && !(flags & DC_AUTOREPLACE) && v->IsStoppedInDepot()) { + Depot *dep = Depot:: GetByTile(v->tile); + + /* Check that the vehicle can drive on some tile of the depot */ + RoadType rt = Engine::Get(v->engine_type)->u.road.roadtype; + const RoadTypeInfo *rti = GetRoadTypeInfo(rt); + if ((dep->r_types.road_types & rti->powered_roadtypes) == 0) return_cmd_error(STR_ERROR_ROAD_VEHICLE_START_NO_POWER); + } break; case VEH_AIRCRAFT: { Aircraft *a = Aircraft::From(v); /* cannot stop airplane when in flight, or when taking off / landing */ - if (a->state >= STARTTAKEOFF && a->state < TERM7) return_cmd_error(STR_ERROR_AIRCRAFT_IS_IN_FLIGHT); - if (HasBit(a->flags, VAF_HELI_DIRECT_DESCENT)) return_cmd_error(STR_ERROR_AIRCRAFT_IS_IN_FLIGHT); + if (a->IsAircraftFlying()) return_cmd_error(STR_ERROR_AIRCRAFT_IS_IN_FLIGHT); break; } @@ -637,9 +682,14 @@ CommandCost CmdStartStopVehicle(DoCommandFlag flags, VehicleID veh_id, bool eval /* Unbunching data is no longer valid. */ v->ResetDepotUnbunching(); + if (v->type == VEH_TRAIN && v->IsInDepot() && IsExtendedDepotTile(v->tile)) { + if ((v->vehstatus & VS_STOPPED) != 0) FreeTrainTrackReservation(Train::From(v)); + UpdateExtendedDepotReservation(v, true); + } + v->MarkDirty(); SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); - SetWindowDirty(WC_VEHICLE_DEPOT, v->tile); + if (IsDepotTile(v->tile)) SetWindowDirty(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); SetWindowClassesDirty(GetWindowClassForVehicleType(v->type)); InvalidateWindowData(WC_VEHICLE_VIEW, v->index); } @@ -667,7 +717,7 @@ CommandCost CmdMassStartStopVehicle(DoCommandFlag flags, TileIndex tile, bool do } else { if (!IsDepotTile(tile) || !IsTileOwner(tile, _current_company)) return CMD_ERROR; /* Get the list of vehicles in the depot */ - BuildDepotVehicleList(vli.vtype, tile, &list, nullptr); + BuildDepotVehicleList(vli.vtype, GetDepotIndex(tile), &list, nullptr); } for (const Vehicle *v : list) { @@ -676,7 +726,14 @@ CommandCost CmdMassStartStopVehicle(DoCommandFlag flags, TileIndex tile, bool do if (!vehicle_list_window && !v->IsChainInDepot()) continue; /* Just try and don't care if some vehicle's can't be stopped. */ - Command::Do(flags, v->index, false); + CommandCost ret = Command::Do(flags, v->index, false); + if (ret.Failed()) { + if (ret.GetErrorMessage() == STR_ERROR_CAN_T_START_PLATFORM_TYPE || + ret.GetErrorMessage() == STR_ERROR_CAN_T_START_PLATFORM_LONG) { + SetDParam(0, v->index); + return ret; + } + } } return CommandCost(); @@ -699,7 +756,7 @@ CommandCost CmdDepotSellAllVehicles(DoCommandFlag flags, TileIndex tile, Vehicle if (!IsDepotTile(tile) || !IsTileOwner(tile, _current_company)) return CMD_ERROR; /* Get the list of vehicles in the depot */ - BuildDepotVehicleList(vehicle_type, tile, &list, &list); + BuildDepotVehicleList(vehicle_type, GetDepotIndex(tile), &list, &list); CommandCost last_error = CMD_ERROR; bool had_success = false; @@ -732,7 +789,7 @@ CommandCost CmdDepotMassAutoReplace(DoCommandFlag flags, TileIndex tile, Vehicle if (!IsDepotTile(tile) || !IsTileOwner(tile, _current_company)) return CMD_ERROR; /* Get the list of vehicles in the depot */ - BuildDepotVehicleList(vehicle_type, tile, &list, &list, true); + BuildDepotVehicleList(vehicle_type, GetDepotIndex(tile), &list, &list, true); for (const Vehicle *v : list) { /* Ensure that the vehicle completely in the depot */ @@ -1130,3 +1187,85 @@ CommandCost CmdChangeServiceInt(DoCommandFlag flags, VehicleID veh_id, uint16_t return CommandCost(); } + +const uint16_t DEFAULT_SERVICE_TIME = 1 << 7; +const uint16_t TRAIN_SERVICE_TIME = 1 << 8; + +/** + * A vehicle that entered an extended depot, starts servicing. + */ +void Vehicle::StartService() +{ + assert(IsExtendedDepotTile(this->tile)); + + switch (this->type) { + case VEH_AIRCRAFT: + case VEH_ROAD: + case VEH_SHIP: { + this->wait_counter = DEFAULT_SERVICE_TIME; + break; + } + + case VEH_TRAIN: { + this->wait_counter = TRAIN_SERVICE_TIME; + break; + } + + default: NOT_REACHED(); + } + + this->cur_speed = 0; + this->subspeed = 0; + + SetBit(this->vehicle_flags, VF_IS_SERVICING); + SetWindowWidgetDirty(WC_VEHICLE_VIEW, this->index, WID_VV_START_STOP); + assert(this->fill_percent_te_id == INVALID_TE_ID); + this->fill_percent_te_id = ShowFillingPercent(this->x_pos, this->y_pos, this->z_pos + 20, 0, STR_SERVICING_INDICATOR); +} + +/** + * Check if vehicle is servicing. + * If it is servicing, decrease time till finishing the servicing. + * It services the vehicle when servicing time ends. + * @return true if the vehicle is still servicing, false if it is not servicing. + */ +bool Vehicle::ContinueServicing() +{ + if (!HasBit(this->vehicle_flags, VF_IS_SERVICING)) return false; + + /* Update text effect every 16 ticks. */ + if ((this->wait_counter & 15) == 0) { + uint8_t percent; + uint16_t max = this->type == VEH_TRAIN ? TRAIN_SERVICE_TIME : DEFAULT_SERVICE_TIME; + /* Return the percentage */ + if ((max - this->wait_counter) * 2 < max) { + /* Less than 50%; round up, so that 0% means really empty. */ + percent = (uint8_t)CeilDiv((max - this->wait_counter) * 100, max); + } else { + /* More than 50%; round down, so that 100% means really full. */ + percent = (uint8_t)(((max - this->wait_counter) * 100) / max); + } + + if (this->fill_percent_te_id == INVALID_TE_ID) { + this->fill_percent_te_id = ShowFillingPercent(this->x_pos, this->y_pos, this->z_pos + 20, percent, STR_SERVICING_INDICATOR); + } else { + UpdateFillingPercent(this->fill_percent_te_id, percent, STR_SERVICING_INDICATOR); + } + } + + if (this->wait_counter--) return true; + + VehicleServiceInExtendedDepot(this); + this->StopServicing(); + return false; +} + +void Vehicle::StopServicing() +{ + this->wait_counter = 0; + + /* End servicing. */ + ClrBit(this->vehicle_flags, VF_IS_SERVICING); + HideFillingPercent(&this->fill_percent_te_id); + InvalidateWindowData(WC_VEHICLE_VIEW, this->index); +} diff --git a/src/vehicle_func.h b/src/vehicle_func.h index 8047a1952edbf..9ecdc53913a08 100644 --- a/src/vehicle_func.h +++ b/src/vehicle_func.h @@ -40,6 +40,7 @@ bool IsValidImageIndex(uint8_t image_index); typedef Vehicle *VehicleFromPosProc(Vehicle *v, void *data); void VehicleServiceInDepot(Vehicle *v); +void VehicleServiceInExtendedDepot(Vehicle *v); uint CountVehiclesInChain(const Vehicle *v); void FindVehicleOnPos(TileIndex tile, void *data, VehicleFromPosProc *proc); void FindVehicleOnPosXY(int x, int y, void *data, VehicleFromPosProc *proc); @@ -164,13 +165,16 @@ inline StringID GetCmdSendToDepotMsg(const BaseVehicle *v) } CommandCost EnsureNoVehicleOnGround(TileIndex tile); +CommandCost EnsureNoVisibleVehicleOnGround(TileIndex tile); CommandCost EnsureNoTrainOnTrackBits(TileIndex tile, TrackBits track_bits); +CommandCost EnsureFreeHangar(TileIndex tile); bool CanVehicleUseStation(EngineID engine_type, const struct Station *st); bool CanVehicleUseStation(const Vehicle *v, const struct Station *st); StringID GetVehicleCannotUseStationReason(const Vehicle *v, const Station *st); void ReleaseDisasterVehicle(VehicleID vehicle); +void DeleteCrashedZeppelins(); typedef std::vector VehicleSet; void GetVehicleSet(VehicleSet &set, Vehicle *v, uint8_t num_vehicles); diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp index 2a43b351a39ae..f12b9cc88e6d0 100644 --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -45,6 +45,7 @@ #include "train_cmd.h" #include "hotkeys.h" #include "group_cmd.h" +#include "depot_base.h" #include "safeguards.h" @@ -2122,7 +2123,7 @@ struct VehicleListWindow : public BaseVehicleListWindow { } case WID_VL_AVAILABLE_VEHICLES: - ShowBuildVehicleWindow(INVALID_TILE, this->vli.vtype); + ShowBuildVehicleWindow(INVALID_DEPOT, this->vli.vtype); break; case WID_VL_MANAGE_VEHICLES_DROPDOWN: { @@ -2267,16 +2268,9 @@ void ShowVehicleListWindow(CompanyID company, VehicleType vehicle_type, StationI ShowVehicleListWindowLocal(company, VL_STATION_LIST, vehicle_type, station); } -void ShowVehicleListWindow(CompanyID company, VehicleType vehicle_type, TileIndex depot_tile) +void ShowVehicleListWindowDepot(CompanyID company, VehicleType vehicle_type, DepotID depot_id) { - uint16_t depot_airport_index; - - if (vehicle_type == VEH_AIRCRAFT) { - depot_airport_index = GetStationIndex(depot_tile); - } else { - depot_airport_index = GetDepotIndex(depot_tile); - } - ShowVehicleListWindowLocal(company, VL_DEPOT_LIST, vehicle_type, depot_airport_index); + ShowVehicleListWindowLocal(company, VL_DEPOT_LIST, vehicle_type, depot_id); } @@ -3100,6 +3094,8 @@ struct VehicleViewWindow : Window { TextColour text_colour = TC_FROMSTRING; if (v->vehstatus & VS_CRASHED) { str = STR_VEHICLE_STATUS_CRASHED; + } else if (v->type == VEH_AIRCRAFT && Aircraft::From(v)->IsAircraftFalling()) { + str = STR_VEHICLE_STATUS_FALLING; } else if (v->type != VEH_AIRCRAFT && v->breakdown_ctr == 1) { // check for aircraft necessary? str = STR_VEHICLE_STATUS_BROKEN_DOWN; } else if (v->vehstatus & VS_STOPPED && (!mouse_over_start_stop || v->IsStoppedInDepot())) { @@ -3117,6 +3113,8 @@ struct VehicleViewWindow : Window { } else { // no train str = STR_VEHICLE_STATUS_STOPPED; } + } else if (HasBit(v->vehicle_flags, VF_IS_SERVICING)) { + str = STR_VEHICLE_STATUS_SERVICING; } else if (v->IsInDepot() && v->IsWaitingForUnbunching()) { str = STR_VEHICLE_STATUS_WAITING_UNBUNCHING; } else if (v->type == VEH_TRAIN && HasBit(Train::From(v)->flags, VRF_TRAIN_STUCK) && !v->current_order.IsType(OT_LOADING)) { @@ -3141,7 +3139,7 @@ struct VehicleViewWindow : Window { case OT_GOTO_DEPOT: { SetDParam(0, v->type); - SetDParam(1, v->current_order.GetDestination()); + SetDParam(1, GetTargetDestination(v->current_order, v->type == VEH_AIRCRAFT)); SetDParam(2, PackVelocity(v->GetDisplaySpeed(), v->type)); if (v->current_order.GetDestination() == INVALID_DEPOT) { /* This case *only* happens when multiple nearest depot orders diff --git a/src/vehicle_gui.h b/src/vehicle_gui.h index ebd0d3fde85af..cf5d8a381dc92 100644 --- a/src/vehicle_gui.h +++ b/src/vehicle_gui.h @@ -18,6 +18,7 @@ #include "station_type.h" #include "engine_type.h" #include "company_type.h" +#include "depot_type.h" void ShowVehicleRefitWindow(const Vehicle *v, VehicleOrderID order, Window *parent, bool auto_refit = false); @@ -56,7 +57,7 @@ void DrawRoadVehImage(const Vehicle *v, const Rect &r, VehicleID selection, Engi void DrawShipImage(const Vehicle *v, const Rect &r, VehicleID selection, EngineImageType image_type); void DrawAircraftImage(const Vehicle *v, const Rect &r, VehicleID selection, EngineImageType image_type); -void ShowBuildVehicleWindow(TileIndex tile, VehicleType type); +void ShowBuildVehicleWindow(DepotID depot_id, VehicleType type); uint ShowRefitOptionsList(int left, int right, int y, EngineID engine); StringID GetCargoSubtypeText(const Vehicle *v); @@ -64,7 +65,7 @@ StringID GetCargoSubtypeText(const Vehicle *v); void ShowVehicleListWindow(const Vehicle *v); void ShowVehicleListWindow(CompanyID company, VehicleType vehicle_type); void ShowVehicleListWindow(CompanyID company, VehicleType vehicle_type, StationID station); -void ShowVehicleListWindow(CompanyID company, VehicleType vehicle_type, TileIndex depot_tile); +void ShowVehicleListWindowDepot(CompanyID company, VehicleType vehicle_type, DepotID depot_id); /** * Get the height of a single vehicle in the GUIs. diff --git a/src/vehiclelist.cpp b/src/vehiclelist.cpp index 48214d93843b4..01bbc757df7f8 100644 --- a/src/vehiclelist.cpp +++ b/src/vehiclelist.cpp @@ -13,6 +13,7 @@ #include "vehiclelist.h" #include "vehiclelist_func.h" #include "group.h" +#include "depot_base.h" #include "safeguards.h" @@ -95,19 +96,24 @@ static Vehicle *BuildDepotVehicleListProc(Vehicle *v, void *data) /** * Generate a list of vehicles inside a depot. - * @param type Type of vehicle - * @param tile The tile the depot is located on - * @param engines Pointer to list to add vehicles to - * @param wagons Pointer to list to add wagons to (can be nullptr) + * @param type Type of vehicle + * @param depot_id The id of the depot + * @param engines Pointer to list to add vehicles to + * @param wagons Pointer to list to add wagons to (can be nullptr) * @param individual_wagons If true add every wagon to \a wagons which is not attached to an engine. If false only add the first wagon of every row. */ -void BuildDepotVehicleList(VehicleType type, TileIndex tile, VehicleList *engines, VehicleList *wagons, bool individual_wagons) +void BuildDepotVehicleList(VehicleType type, DepotID depot_id, VehicleList *engines, VehicleList *wagons, bool individual_wagons) { + assert(Depot::IsValidID(depot_id)); + Depot *dep = Depot::Get(depot_id); engines->clear(); if (wagons != nullptr && wagons != engines) wagons->clear(); BuildDepotVehicleListData bdvld{engines, wagons, type, individual_wagons}; - FindVehicleOnPos(tile, &bdvld, BuildDepotVehicleListProc); + + for (TileIndex tile : dep->depot_tiles) { + FindVehicleOnPos(tile, &bdvld, BuildDepotVehicleListProc); + } } /** diff --git a/src/vehiclelist.h b/src/vehiclelist.h index 037953f25db71..9bb3aa6dcfabf 100644 --- a/src/vehiclelist.h +++ b/src/vehiclelist.h @@ -12,6 +12,7 @@ #include "vehicle_type.h" #include "company_type.h" +#include "depot_type.h" #include "tile_type.h" /** Vehicle List type flags */ @@ -54,7 +55,7 @@ struct VehicleListIdentifier { typedef std::vector VehicleList; bool GenerateVehicleSortList(VehicleList *list, const VehicleListIdentifier &identifier); -void BuildDepotVehicleList(VehicleType type, TileIndex tile, VehicleList *engine_list, VehicleList *wagon_list, bool individual_wagons = false); +void BuildDepotVehicleList(VehicleType type, DepotID depot_id, VehicleList *engine_list, VehicleList *wagon_list, bool individual_wagons = false); uint GetUnitNumberDigits(VehicleList &vehicles); #endif /* VEHICLELIST_H */ diff --git a/src/viewport.cpp b/src/viewport.cpp index 1aa848058ee82..c27c67d091872 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -66,6 +66,8 @@ #include "viewport_func.h" #include "station_base.h" #include "waypoint_base.h" +#include "depot_base.h" +#include "depot_func.h" #include "town.h" #include "signs_base.h" #include "signs_func.h" @@ -90,6 +92,7 @@ #include "network/network_func.h" #include "framerate_type.h" #include "viewport_cmd.h" +#include "depot_map.h" #include #include @@ -138,14 +141,6 @@ struct ChildScreenSpriteToDraw { int next; ///< next child to draw (-1 at the end) }; -/** Enumeration of multi-part foundations */ -enum FoundationPart { - FOUNDATION_PART_NONE = 0xFF, ///< Neither foundation nor groundsprite drawn yet. - FOUNDATION_PART_NORMAL = 0, ///< First part (normal foundation or no foundation) - FOUNDATION_PART_HALFTILE = 1, ///< Second part (halftile foundation) - FOUNDATION_PART_END -}; - /** * Mode of "sprite combining" * @see StartSpriteCombine @@ -884,7 +879,7 @@ static void AddStringToDraw(int x, int y, StringID string, Colours colour, uint1 * @param extra_offs_x Pixel X offset for the sprite position. * @param extra_offs_y Pixel Y offset for the sprite position. */ -static void DrawSelectionSprite(SpriteID image, PaletteID pal, const TileInfo *ti, int z_offset, FoundationPart foundation_part, int extra_offs_x = 0, int extra_offs_y = 0) +void DrawSelectionSprite(SpriteID image, PaletteID pal, const TileInfo *ti, int z_offset, FoundationPart foundation_part, int extra_offs_x, int extra_offs_y) { /* FIXME: This is not totally valid for some autorail highlights that extend over the edges of the tile. */ if (_vd.foundation[foundation_part] == -1) { @@ -1002,6 +997,7 @@ enum TileHighlightType { const Station *_viewport_highlight_station; ///< Currently selected station for coverage area highlight const Waypoint *_viewport_highlight_waypoint; ///< Currently selected waypoint for coverage area highlight const Town *_viewport_highlight_town; ///< Currently selected town for coverage area highlight +DepotID _viewport_highlight_depot = INVALID_DEPOT; ///< Currently selected depot for depot highlight /** * Get tile highlight type of coverage area for a given tile. @@ -1010,6 +1006,10 @@ const Town *_viewport_highlight_town; ///< Currently selected town for coverage */ static TileHighlightType GetTileHighlightType(TileIndex t) { + if (_viewport_highlight_depot != INVALID_DEPOT) { + if (IsDepotTile(t) && GetDepotIndex(t) == _viewport_highlight_depot) return THT_BLUE; + } + if (_viewport_highlight_station != nullptr) { if (IsTileType(t, MP_STATION) && GetStationIndex(t) == _viewport_highlight_station->index) return THT_WHITE; if (_viewport_highlight_station->TileIsInCatchment(t)) return THT_BLUE; @@ -1360,12 +1360,14 @@ static void ViewportAddKdtreeSigns(DrawPixelInfo *dpi) bool show_waypoints = HasBit(_display_opt, DO_SHOW_WAYPOINT_NAMES) && _game_mode != GM_MENU; bool show_towns = HasBit(_display_opt, DO_SHOW_TOWN_NAMES) && _game_mode != GM_MENU; bool show_signs = HasBit(_display_opt, DO_SHOW_SIGNS) && !IsInvisibilitySet(TO_SIGNS); + bool show_depotsigns = _game_mode != GM_MENU; bool show_competitors = HasBit(_display_opt, DO_SHOW_COMPETITOR_SIGNS); /* Collect all the items first and draw afterwards, to ensure layering */ std::vector stations; std::vector towns; std::vector signs; + std::vector depots; _viewport_sign_kdtree.FindContained(search_rect.left, search_rect.top, search_rect.right, search_rect.bottom, [&](const ViewportSignKdtreeItem & item) { switch (item.type) { @@ -1409,6 +1411,19 @@ static void ViewportAddKdtreeSigns(DrawPixelInfo *dpi) break; } + case ViewportSignKdtreeItem::VKI_DEPOT: { + if (!show_depotsigns) break; + const Depot *depot = Depot::Get(item.id.depot); + + /* Only show depot name after the depot is removed. */ + if (depot->IsInUse()) break; + /* Don't draw if depot is owned by another company and competitor signs are hidden. */ + if (!show_competitors && _local_company != depot->owner) break; + + depots.push_back(depot); + break; + } + default: NOT_REACHED(); } @@ -1435,6 +1450,12 @@ static void ViewportAddKdtreeSigns(DrawPixelInfo *dpi) (si->owner == OWNER_NONE) ? COLOUR_GREY : (si->owner == OWNER_DEITY ? INVALID_COLOUR : _company_colours[si->owner])); } + for (const auto *d : depots) { + SetDParam(0, d->veh_type); + SetDParam(1, d->veh_type == VEH_AIRCRAFT ? d->station->index : d->index); + ViewportAddString(dpi, ZOOM_LVL_OUT_4X, &d->sign, STR_VIEWPORT_DEPOT, STR_VIEWPORT_DEPOT_TINY, STR_NULL, COLOUR_GREY); + } + for (const auto *st : stations) { SetDParam(0, st->index); SetDParam(1, st->facilities); @@ -2231,6 +2252,7 @@ static bool CheckClickOnViewportSign(const Viewport *vp, int x, int y) BaseStation *st = nullptr, *last_st = nullptr; Town *t = nullptr, *last_t = nullptr; Sign *si = nullptr, *last_si = nullptr; + Depot *dep = nullptr, *last_dep = nullptr; /* See ViewportAddKdtreeSigns() for details on the search logic */ _viewport_sign_kdtree.FindContained(search_rect.left, search_rect.top, search_rect.right, search_rect.bottom, [&](const ViewportSignKdtreeItem & item) { @@ -2262,6 +2284,12 @@ static bool CheckClickOnViewportSign(const Viewport *vp, int x, int y) if (CheckClickOnViewportSign(vp, x, y, &si->sign)) last_si = si; break; + case ViewportSignKdtreeItem::VKI_DEPOT: + dep = Depot::Get(item.id.depot); + if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break; + if (CheckClickOnViewportSign(vp, x, y, &dep->sign)) last_dep = dep; + break; + default: NOT_REACHED(); } @@ -2281,6 +2309,9 @@ static bool CheckClickOnViewportSign(const Viewport *vp, int x, int y) } else if (last_si != nullptr) { HandleClickOnSign(last_si); return true; + } else if (last_dep != nullptr) { + ShowDepotWindow(last_dep->index); + return true; } else { return false; } @@ -2304,6 +2335,23 @@ ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeStation(StationID id) return item; } +ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeDepot(DepotID id) +{ + ViewportSignKdtreeItem item; + item.type = VKI_DEPOT; + item.id.depot = id; + + const Depot *depot = Depot::Get(id); + + item.center = depot->sign.center; + item.top = depot->sign.top; + + /* Assume the sign can be a candidate for drawing, so measure its width */ + _viewport_sign_maxwidth = std::max(_viewport_sign_maxwidth, depot->sign.width_normal); + + return item; +} + ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeWaypoint(StationID id) { ViewportSignKdtreeItem item; @@ -2379,6 +2427,11 @@ void RebuildViewportKdtree() if (sign->sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeSign(sign->index)); } + for (const Depot *dep : Depot::Iterate()) { + if (dep->IsInUse()) continue; + items.push_back(ViewportSignKdtreeItem::MakeDepot(dep->index)); + } + _viewport_sign_kdtree.Build(items.begin(), items.end()); } @@ -2674,6 +2727,8 @@ void UpdateTileSelection() } _thd.new_pos.x = x1 & ~TILE_UNIT_MASK; _thd.new_pos.y = y1 & ~TILE_UNIT_MASK; + if (_thd.select_method == VPM_LIMITED_X_FIXED_Y) _thd.new_size.y = (TILE_SIZE * _thd.fixed_size) & ~TILE_UNIT_MASK; + if (_thd.select_method == VPM_LIMITED_Y_FIXED_X) _thd.new_size.x = (TILE_SIZE * _thd.fixed_size) & ~TILE_UNIT_MASK; } } @@ -2766,6 +2821,15 @@ void VpSetPlaceSizingLimit(int limit) _thd.sizelimit = limit; } +void VpSetPlaceFixedSize(uint8_t fixed) +{ + _thd.fixed_size = fixed; +} + +void VpResetFixedSize() { + VpSetPlaceFixedSize(1); +} + /** * Highlights all tiles between a set of two tiles. Used in dock and tunnel placement * @param from TileIndex of the first tile to highlight @@ -3237,7 +3301,7 @@ void VpSelectTilesWithMethod(int x, int y, ViewportPlaceMethod method) sx = _thd.selstart.x; sy = _thd.selstart.y; - int limit = 0; + int limit = -1; switch (method) { case VPM_X_OR_Y: // drag in X or Y direction @@ -3250,28 +3314,33 @@ void VpSelectTilesWithMethod(int x, int y, ViewportPlaceMethod method) } goto calc_heightdiff_single_direction; + case VPM_LIMITED_Y_FIXED_X: case VPM_X_LIMITED: // Drag in X direction (limited size). limit = (_thd.sizelimit - 1) * TILE_SIZE; [[fallthrough]]; case VPM_FIX_X: // drag in Y direction - x = sx; + x = sx + (method == VPM_LIMITED_Y_FIXED_X ? (TILE_SIZE * (_thd.fixed_size - 1)) : 0) ; style = HT_DIR_Y; goto calc_heightdiff_single_direction; + case VPM_LIMITED_X_FIXED_Y: case VPM_Y_LIMITED: // Drag in Y direction (limited size). limit = (_thd.sizelimit - 1) * TILE_SIZE; [[fallthrough]]; case VPM_FIX_Y: // drag in X direction - y = sy; + y = sy + (method == VPM_LIMITED_X_FIXED_Y ? (TILE_SIZE * (_thd.fixed_size - 1)) : 0) ; style = HT_DIR_X; calc_heightdiff_single_direction:; - if (limit > 0) { - x = sx + Clamp(x - sx, -limit, limit); - y = sy + Clamp(y - sy, -limit, limit); + if (limit >= 0) { + if (method != VPM_LIMITED_X_FIXED_Y) y = sy + Clamp(y - sy, -limit, limit); + if (method != VPM_LIMITED_Y_FIXED_X) x = sx + Clamp(x - sx, -limit, limit); } + + if (method == VPM_LIMITED_Y_FIXED_X || method == VPM_LIMITED_X_FIXED_Y) goto measure_area; + if (_settings_client.gui.measure_tooltip) { TileIndex t0 = TileVirtXY(sx, sy); TileIndex t1 = TileVirtXY(x, y); @@ -3301,6 +3370,7 @@ calc_heightdiff_single_direction:; [[fallthrough]]; case VPM_X_AND_Y: // drag an X by Y area +measure_area: if (_settings_client.gui.measure_tooltip) { static const StringID measure_strings_area[] = { STR_NULL, STR_NULL, STR_MEASURE_AREA, STR_MEASURE_AREA_HEIGHTDIFF @@ -3574,14 +3644,9 @@ void MarkCatchmentTilesDirty() } if (_viewport_highlight_station != nullptr) { if (_viewport_highlight_station->catchment_tiles.tile == INVALID_TILE) { - MarkWholeScreenDirty(); _viewport_highlight_station = nullptr; - } else { - BitmapTileIterator it(_viewport_highlight_station->catchment_tiles); - for (TileIndex tile = it; tile != INVALID_TILE; tile = ++it) { - MarkTileDirtyByTile(tile); - } } + MarkWholeScreenDirty(); } if (_viewport_highlight_waypoint != nullptr) { if (!_viewport_highlight_waypoint->IsInUse()) { @@ -3589,6 +3654,13 @@ void MarkCatchmentTilesDirty() } MarkWholeScreenDirty(); } + if (_viewport_highlight_depot != INVALID_DEPOT) { + Depot *dep = Depot::Get(_viewport_highlight_depot); + if (!dep->IsInUse()) { + _viewport_highlight_depot = INVALID_DEPOT; + } + MarkWholeScreenDirty(); + } } static void SetWindowDirtyForViewportCatchment() @@ -3596,6 +3668,7 @@ static void SetWindowDirtyForViewportCatchment() if (_viewport_highlight_station != nullptr) SetWindowDirty(WC_STATION_VIEW, _viewport_highlight_station->index); if (_viewport_highlight_waypoint != nullptr) SetWindowDirty(WC_WAYPOINT_VIEW, _viewport_highlight_waypoint->index); if (_viewport_highlight_town != nullptr) SetWindowDirty(WC_TOWN_VIEW, _viewport_highlight_town->index); + if (_viewport_highlight_depot != INVALID_DEPOT) SetWindowDirty(WC_VEHICLE_DEPOT, _viewport_highlight_depot); } static void ClearViewportCatchment() @@ -3604,6 +3677,7 @@ static void ClearViewportCatchment() _viewport_highlight_station = nullptr; _viewport_highlight_waypoint = nullptr; _viewport_highlight_town = nullptr; + _viewport_highlight_depot = INVALID_DEPOT; } /** @@ -3665,3 +3739,30 @@ void SetViewportCatchmentTown(const Town *t, bool sel) } if (_viewport_highlight_town != nullptr) SetWindowDirty(WC_TOWN_VIEW, _viewport_highlight_town->index); } + +static void MarkDepotTilesDirty() +{ + if (_viewport_highlight_depot != INVALID_DEPOT) { + MarkWholeScreenDirty(); + return; + } +} + +/** + * Select or deselect depot to highlight. + * @param *dep Depot in question + * @param sel Select or deselect given depot + */ +void SetViewportHighlightDepot(const DepotID dep, bool sel) +{ + SetWindowDirtyForViewportCatchment(); + if (sel && _viewport_highlight_depot != dep) { + ClearViewportCatchment(); + _viewport_highlight_depot = dep; + MarkDepotTilesDirty(); + } else if (!sel && _viewport_highlight_depot == dep) { + MarkDepotTilesDirty(); + _viewport_highlight_depot = INVALID_DEPOT; + } + if (_viewport_highlight_depot != INVALID_DEPOT) SetWindowDirty(WC_VEHICLE_DEPOT, _viewport_highlight_depot); +} diff --git a/src/viewport_func.h b/src/viewport_func.h index 5b1537478c78d..bce1f4bc00b35 100644 --- a/src/viewport_func.h +++ b/src/viewport_func.h @@ -16,6 +16,15 @@ #include "tile_map.h" #include "station_type.h" #include "vehicle_type.h" +#include "depot_type.h" + +/** Enumeration of multi-part foundations */ +enum FoundationPart { + FOUNDATION_PART_NONE = 0xFF, ///< Neither foundation nor groundsprite drawn yet. + FOUNDATION_PART_NORMAL = 0, ///< First part (normal foundation or no foundation) + FOUNDATION_PART_HALFTILE = 1, ///< Second part (halftile foundation) + FOUNDATION_PART_END +}; static const int TILE_HEIGHT_STEP = 50; ///< One Z unit tile height difference is displayed as 50m. @@ -56,6 +65,9 @@ void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int w, void AddChildSpriteScreen(SpriteID image, PaletteID pal, int x, int y, bool transparent = false, const SubSprite *sub = nullptr, bool scale = true, bool relative = true); void ViewportAddString(const DrawPixelInfo *dpi, ZoomLevel small_from, const ViewportSign *sign, StringID string_normal, StringID string_small, StringID string_small_shadow, Colours colour = INVALID_COLOUR); +struct TileInfo; + +void DrawSelectionSprite(SpriteID image, PaletteID pal, const TileInfo *ti, int z_offset, FoundationPart foundation_part, int extra_offs_x = 0, int extra_offs_y = 0); void StartSpriteCombine(); void EndSpriteCombine(); @@ -102,6 +114,8 @@ struct Town; void SetViewportCatchmentStation(const Station *st, bool sel); void SetViewportCatchmentWaypoint(const Waypoint *wp, bool sel); void SetViewportCatchmentTown(const Town *t, bool sel); +void SetViewportHighlightDepot(const DepotID dep, bool sel); + void MarkCatchmentTilesDirty(); template diff --git a/src/viewport_kdtree.h b/src/viewport_kdtree.h index 3c2f49c2e4c17..333c3685fca58 100644 --- a/src/viewport_kdtree.h +++ b/src/viewport_kdtree.h @@ -22,12 +22,14 @@ struct ViewportSignKdtreeItem { VKI_WAYPOINT, VKI_TOWN, VKI_SIGN, + VKI_DEPOT, }; ItemType type; union { StationID station; TownID town; SignID sign; + DepotID depot; } id; int32_t center; int32_t top; @@ -43,6 +45,8 @@ struct ViewportSignKdtreeItem { return this->id.town == other.id.town; case VKI_SIGN: return this->id.sign == other.id.sign; + case VKI_DEPOT: + return this->id.depot == other.id.depot; default: NOT_REACHED(); } @@ -59,6 +63,8 @@ struct ViewportSignKdtreeItem { return this->id.town < other.id.town; case VKI_SIGN: return this->id.sign < other.id.sign; + case VKI_DEPOT: + return this->id.depot < other.id.depot; default: NOT_REACHED(); } @@ -68,6 +74,7 @@ struct ViewportSignKdtreeItem { static ViewportSignKdtreeItem MakeWaypoint(StationID id); static ViewportSignKdtreeItem MakeTown(TownID id); static ViewportSignKdtreeItem MakeSign(SignID id); + static ViewportSignKdtreeItem MakeDepot(DepotID id); }; inline int32_t Kdtree_ViewportSignXYFunc(const ViewportSignKdtreeItem &item, int dim) diff --git a/src/viewport_type.h b/src/viewport_type.h index 0fde051f38db1..e5ef362fa9c9e 100644 --- a/src/viewport_type.h +++ b/src/viewport_type.h @@ -99,6 +99,8 @@ enum ViewportPlaceMethod { VPM_FIX_VERTICAL = 6, ///< drag only in vertical direction VPM_X_LIMITED = 7, ///< Drag only in X axis with limited size VPM_Y_LIMITED = 8, ///< Drag only in Y axis with limited size + VPM_LIMITED_Y_FIXED_X = 9, ///< Drag only in Y axis with limited size and a fixed value for X axis + VPM_LIMITED_X_FIXED_Y = 10, ///< Drag only in X axis with limited size and a fixed value for Y axis VPM_RAILDIRS = 0x40, ///< all rail directions VPM_SIGNALDIRS = 0x80, ///< similar to VMP_RAILDIRS, but with different cursor }; @@ -120,6 +122,8 @@ enum ViewportDragDropSelectionProcess { DDSP_PLANT_TREES, ///< Plant trees DDSP_BUILD_BRIDGE, ///< Bridge placement DDSP_BUILD_OBJECT, ///< Build an object + DDSP_BUILD_DEPOT, ///< Depot placement + DDSP_REMOVE_DEPOT, ///< Depot removal /* Rail specific actions */ DDSP_PLACE_RAIL, ///< Rail placement diff --git a/src/water_cmd.cpp b/src/water_cmd.cpp index c4b01c05edff7..ee2f73c243b26 100644 --- a/src/water_cmd.cpp +++ b/src/water_cmd.cpp @@ -40,6 +40,9 @@ #include "water_cmd.h" #include "landscape_cmd.h" #include "pathfinder/water_regions.h" +#include "train.h" +#include "platform_func.h" +#include "pbs.h" #include "table/strings.h" @@ -94,66 +97,80 @@ static void MarkCanalsAndRiversAroundDirty(TileIndex tile) /** * Build a ship depot. * @param flags type of operation - * @param tile tile where ship depot is built + * @param tile first tile where ship depot is built * @param axis depot orientation (Axis) + * @param adjacent allow adjacent depots + * @param extended whether to build an extended depot + * @param join_to depot to join to + * @param end_tile end tile of area to be built * @return the cost of this operation or an error */ -CommandCost CmdBuildShipDepot(DoCommandFlag flags, TileIndex tile, Axis axis) +CommandCost CmdBuildShipDepot(DoCommandFlag flags, TileIndex tile, Axis axis, bool adjacent, bool extended, DepotID join_to, TileIndex end_tile) { + if (Company::IsValidHumanID(_current_company) && !HasBit(_settings_game.depot.water_depot_types, extended)) { + return_cmd_error(STR_ERROR_DEPOT_TYPE_NOT_AVAILABLE); + } + if (!IsValidAxis(axis)) return CMD_ERROR; TileIndex tile2 = tile + (axis == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1)); - if (!HasTileWaterGround(tile) || !HasTileWaterGround(tile2)) { - return_cmd_error(STR_ERROR_MUST_BE_BUILT_ON_WATER); - } + TileArea complete_area(tile, end_tile); + complete_area.Add(tile2); + assert(complete_area.w == 2 || complete_area.h == 2); - if (IsBridgeAbove(tile) || IsBridgeAbove(tile2)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); + TileArea northern_tiles(complete_area.tile); + northern_tiles.Add(complete_area.tile + (axis == AXIS_X ? TileDiffXY(0, complete_area.h - 1) : TileDiffXY(complete_area.w - 1, 0))); - if (!IsTileFlat(tile) || !IsTileFlat(tile2)) { - /* Prevent depots on rapids */ - return_cmd_error(STR_ERROR_SITE_UNSUITABLE); - } + /* Create a new depot or find a depot to join to. */ + Depot *depot = nullptr; + CommandCost ret = FindJoiningDepot(complete_area, VEH_SHIP, join_to, depot, adjacent, flags); + if (ret.Failed()) return ret; - if (!Depot::CanAllocateItem()) return CMD_ERROR; + /* Get the cost of building all the ship depots. */ + CommandCost cost = CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_DEPOT_SHIP] * northern_tiles.w * northern_tiles.h); - WaterClass wc1 = GetWaterClass(tile); - WaterClass wc2 = GetWaterClass(tile2); - CommandCost cost = CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_DEPOT_SHIP]); + /* Update infrastructure counts after the tile clears earlier. + * Clearing object tiles may result in water tiles which are already accounted for in the water infrastructure total. + * See: MakeWaterKeepingClass() */ + uint new_water_infra = 0; - bool add_cost = !IsWaterTile(tile); - CommandCost ret = Command::Do(flags | DC_AUTO, tile); - if (ret.Failed()) return ret; - if (add_cost) { - cost.AddCost(ret); - } - add_cost = !IsWaterTile(tile2); - ret = Command::Do(flags | DC_AUTO, tile2); - if (ret.Failed()) return ret; - if (add_cost) { - cost.AddCost(ret); - } + for (TileIndex t : complete_area) { + /* Build water depots in water valid tiles... */ + if (!IsValidTile(t) || !HasTileWaterGround(t)) return_cmd_error(STR_ERROR_MUST_BE_BUILT_ON_WATER); - if (flags & DC_EXEC) { - Depot *depot = new Depot(tile); - depot->build_date = TimerGameCalendar::date; + /* ... with no bridges above... */ + if (IsBridgeAbove(t)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); + + /* ... and preventing depots on rapids. */ + if (!IsTileFlat(t)) return_cmd_error(STR_ERROR_SITE_UNSUITABLE); + + /* Keep original water class before clearing tile. */ + WaterClass wc = GetWaterClass(t); + + /* Clear the tile. */ + bool add_cost = !IsWaterTile(t); + CommandCost ret = Command::Do(flags | DC_AUTO, t); + if (ret.Failed()) return ret; + if (add_cost) cost.AddCost(ret); + + if (wc == WATER_CLASS_CANAL && !(HasTileWaterClass(t) && GetWaterClass(t) == WATER_CLASS_CANAL && IsTileOwner(t, _current_company))) new_water_infra++; - uint new_water_infra = 2 * LOCK_DEPOT_TILE_FACTOR; - /* Update infrastructure counts after the tile clears earlier. - * Clearing object tiles may result in water tiles which are already accounted for in the water infrastructure total. - * See: MakeWaterKeepingClass() */ - if (wc1 == WATER_CLASS_CANAL && !(HasTileWaterClass(tile) && GetWaterClass(tile) == WATER_CLASS_CANAL && IsTileOwner(tile, _current_company))) new_water_infra++; - if (wc2 == WATER_CLASS_CANAL && !(HasTileWaterClass(tile2) && GetWaterClass(tile2) == WATER_CLASS_CANAL && IsTileOwner(tile2, _current_company))) new_water_infra++; + if (flags & DC_EXEC) { + DepotPart dp = northern_tiles.Contains(t) ? DEPOT_PART_NORTH : DEPOT_PART_SOUTH; + MakeShipDepot(t, _current_company, depot->index, extended, dp, axis, wc); + CheckForDockingTile(t); + MarkTileDirtyByTile(t); + } + } - Company::Get(_current_company)->infrastructure.water += new_water_infra; + if (flags & DC_EXEC) { + Company::Get(_current_company)->infrastructure.water += new_water_infra + + complete_area.w * complete_area.h * LOCK_DEPOT_TILE_FACTOR; DirtyCompanyInfrastructureWindows(_current_company); - MakeShipDepot(tile, _current_company, depot->index, DEPOT_PART_NORTH, axis, wc1); - MakeShipDepot(tile2, _current_company, depot->index, DEPOT_PART_SOUTH, axis, wc2); - CheckForDockingTile(tile); - CheckForDockingTile(tile2); - MarkTileDirtyByTile(tile); - MarkTileDirtyByTile(tile2); MakeDefaultName(depot); + depot->AfterAddRemove(complete_area, true); + if (join_to == NEW_DEPOT) MakeDefaultName(depot); } return cost; @@ -198,7 +215,7 @@ void CheckForDockingTile(TileIndex t) SetDockingTile(t, true); } } - if (IsTileType(tile, MP_STATION) && IsOilRig(tile)) { + if (IsWateredBuiltInHeliportTile(tile)) { Station::GetByTile(tile)->docking_station.Add(t); SetDockingTile(t, true); } @@ -275,9 +292,8 @@ static CommandCost RemoveShipDepot(TileIndex tile, DoCommandFlag flags) bool do_clear = (flags & DC_FORCE_CLEAR_TILE) != 0; if (flags & DC_EXEC) { - delete Depot::GetByTile(tile); - - Company *c = Company::GetIfValid(GetTileOwner(tile)); + Depot *depot = Depot::GetByTile(tile); + Company *c = Company::GetIfValid(depot->owner); if (c != nullptr) { c->infrastructure.water -= 2 * LOCK_DEPOT_TILE_FACTOR; if (do_clear && GetWaterClass(tile) == WATER_CLASS_CANAL) c->infrastructure.water--; @@ -286,6 +302,8 @@ static CommandCost RemoveShipDepot(TileIndex tile, DoCommandFlag flags) if (!do_clear) MakeWaterKeepingClass(tile, GetTileOwner(tile)); MakeWaterKeepingClass(tile2, GetTileOwner(tile2)); + + depot->AfterAddRemove(TileArea(tile, tile2), false); } return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_SHIP]); @@ -652,22 +670,15 @@ bool IsWateredTile(TileIndex tile, Direction from) return false; case MP_STATION: - if (IsOilRig(tile)) { - /* Do not draw waterborders inside of industries. - * Note: There is no easy way to detect the industry of an oilrig tile. */ - TileIndex src_tile = tile + TileOffsByDir(from); - if ((IsTileType(src_tile, MP_STATION) && IsOilRig(src_tile)) || - (IsTileType(src_tile, MP_INDUSTRY))) return true; - - return IsTileOnWater(tile); - } + if (IsAirportTile(tile)) return IsTileOnWater(tile); + return (IsDock(tile) && IsTileFlat(tile)) || IsBuoy(tile); case MP_INDUSTRY: { /* Do not draw waterborders inside of industries. * Note: There is no easy way to detect the industry of an oilrig tile. */ TileIndex src_tile = tile + TileOffsByDir(from); - if ((IsTileType(src_tile, MP_STATION) && IsOilRig(src_tile)) || + if (IsBuiltInHeliportTile(src_tile) || (IsTileType(src_tile, MP_INDUSTRY) && GetIndustryIndex(src_tile) == GetIndustryIndex(tile))) return true; return IsTileOnWater(tile); @@ -974,7 +985,7 @@ static void GetTileDesc_Water(TileIndex tile, TileDesc *td) case WATER_TILE_COAST: td->str = STR_LAI_WATER_DESCRIPTION_COAST_OR_RIVERBANK; break; case WATER_TILE_LOCK : td->str = STR_LAI_WATER_DESCRIPTION_LOCK; break; case WATER_TILE_DEPOT: - td->str = STR_LAI_WATER_DESCRIPTION_SHIP_DEPOT; + td->str = IsExtendedDepot(tile) ? STR_LAI_WATER_DESCRIPTION_SHIP_DEPOT_EXTENDED : STR_LAI_WATER_DESCRIPTION_SHIP_DEPOT; td->build_date = Depot::GetByTile(tile)->build_date; break; default: NOT_REACHED(); @@ -1013,19 +1024,9 @@ static Vehicle *FloodVehicleProc(Vehicle *v, void *data) switch (v->type) { default: break; - case VEH_AIRCRAFT: { - if (!IsAirportTile(v->tile) || GetTileMaxZ(v->tile) != 0) break; - if (v->subtype == AIR_SHADOW) break; - - /* We compare v->z_pos against delta_z + 1 because the shadow - * is at delta_z and the actual aircraft at delta_z + 1. */ - const Station *st = Station::GetByTile(v->tile); - const AirportFTAClass *airport = st->airport.GetFTA(); - if (v->z_pos != airport->delta_z + 1) break; - - FloodVehicle(v); + case VEH_AIRCRAFT: + /* Aircraft is flooded with FloodAircraftOnAirport(const Station *). */ break; - } case VEH_TRAIN: case VEH_ROAD: { @@ -1050,24 +1051,29 @@ static void FloodVehicles(TileIndex tile) if (IsAirportTile(tile)) { const Station *st = Station::GetByTile(tile); - for (TileIndex airport_tile : st->airport) { - if (st->TileBelongsToAirport(airport_tile)) FindVehicleOnPos(airport_tile, &z, &FloodVehicleProc); - } - - /* No vehicle could be flooded on this airport anymore */ + FloodAircraftOnAirport(st); return; } - if (!IsBridgeTile(tile)) { + if (IsBridgeTile(tile)) { + TileIndex end = GetOtherBridgeEnd(tile); + z = GetBridgePixelHeight(tile); + + FindVehicleOnPos(tile, &z, &FloodVehicleProc); + FindVehicleOnPos(end, &z, &FloodVehicleProc); + } else if (IsExtendedRailDepotTile(tile)) { + /* Free reserved path. */ + if (HasDepotReservation(tile)) { + Train *v = GetTrainForReservation(tile, GetRailDepotTrack(tile)); + if (v != nullptr) FreeTrainTrackReservation(v); + } + /* Crash trains on platform. */ + for (TileIndex t : GetPlatformTileArea(tile)) { + FindVehicleOnPos(t, &z, &FloodVehicleProc); + } + } else { FindVehicleOnPos(tile, &z, &FloodVehicleProc); - return; } - - TileIndex end = GetOtherBridgeEnd(tile); - z = GetBridgePixelHeight(tile); - - FindVehicleOnPos(tile, &z, &FloodVehicleProc); - FindVehicleOnPos(end, &z, &FloodVehicleProc); } /** @@ -1239,6 +1245,7 @@ void TileLoop_Water(TileIndex tile) if (!IsValidTile(dest)) continue; /* do not try to flood water tiles - increases performance a lot */ if (IsTileType(dest, MP_WATER)) continue; + if (IsAirportTile(dest) && IsTileOnWater(dest)) continue; /* TREE_GROUND_SHORE is the sign of a previous flood. */ if (IsTileType(dest, MP_TREES) && GetTreeGround(dest) == TREE_GROUND_SHORE) continue; @@ -1334,7 +1341,7 @@ static TrackStatus GetTileTrackStatus_Water(TileIndex tile, TransportType mode, static bool ClickTile_Water(TileIndex tile) { if (GetWaterTileType(tile) == WATER_TILE_DEPOT) { - ShowDepotWindow(GetShipDepotNorthTile(tile), VEH_SHIP); + ShowDepotWindow(GetDepotIndex(tile)); return true; } return false; diff --git a/src/water_cmd.h b/src/water_cmd.h index 1c56790327dea..02efc043cea9e 100644 --- a/src/water_cmd.h +++ b/src/water_cmd.h @@ -13,7 +13,7 @@ #include "command_type.h" #include "water_map.h" -CommandCost CmdBuildShipDepot(DoCommandFlag flags, TileIndex tile, Axis axis); +CommandCost CmdBuildShipDepot(DoCommandFlag flags, TileIndex tile, Axis axis, bool adjacent, bool extended, DepotID depot_id, TileIndex end_tile); CommandCost CmdBuildCanal(DoCommandFlag flags, TileIndex tile, TileIndex start_tile, WaterClass wc, bool diagonal); CommandCost CmdBuildLock(DoCommandFlag flags, TileIndex tile); diff --git a/src/water_map.h b/src/water_map.h index 116a37f228651..5291a980139c8 100644 --- a/src/water_map.h +++ b/src/water_map.h @@ -17,12 +17,14 @@ * Bit field layout of m5 for water tiles. */ enum WaterTileTypeBitLayout { - WBL_TYPE_BEGIN = 4, ///< Start of the 'type' bitfield. - WBL_TYPE_COUNT = 4, ///< Length of the 'type' bitfield. + WBL_TYPE_BEGIN = 6, ///< Start of the 'type' bitfield. + WBL_TYPE_COUNT = 2, ///< Length of the 'type' bitfield. WBL_TYPE_NORMAL = 0x0, ///< Clear water or coast ('type' bitfield). WBL_TYPE_LOCK = 0x1, ///< Lock ('type' bitfield). - WBL_TYPE_DEPOT = 0x8, ///< Depot ('type' bitfield). + WBL_TYPE_DEPOT = 0x2, ///< Depot ('type' bitfield). + + WBL_DEPOT_EXTENDED = 5, ///< Bit for the standard/extended depot flag. WBL_COAST_FLAG = 0, ///< Flag for coast. @@ -78,6 +80,16 @@ enum LockPart { bool IsPossibleDockingTile(Tile t); +/** + * Get the type of water tile: clear, lock or depot. + * @param t Water tile to query. + * @return WBL_TYPE_NORMAL, WBL_TYPE_LOCK or WBL_TYPE_DEPOT. + */ +static inline WaterTileTypeBitLayout GetWaterTileClass(Tile t) { + assert(IsTileType(t, MP_WATER)); + return static_cast(GB(t.m5(), WBL_TYPE_BEGIN, WBL_TYPE_COUNT)); +} + /** * Get the water tile type at a tile. * @param t Water tile to query. @@ -87,7 +99,7 @@ inline WaterTileType GetWaterTileType(Tile t) { assert(IsTileType(t, MP_WATER)); - switch (GB(t.m5(), WBL_TYPE_BEGIN, WBL_TYPE_COUNT)) { + switch (GetWaterTileClass(t)) { case WBL_TYPE_NORMAL: return HasBit(t.m5(), WBL_COAST_FLAG) ? WATER_TILE_COAST : WATER_TILE_CLEAR; case WBL_TYPE_LOCK: return WATER_TILE_LOCK; case WBL_TYPE_DEPOT: return WATER_TILE_DEPOT; @@ -224,7 +236,8 @@ inline bool IsCoastTile(Tile t) */ inline bool IsShipDepot(Tile t) { - return GetWaterTileType(t) == WATER_TILE_DEPOT; + assert(IsTileType(t, MP_WATER)); + return GetWaterTileClass(t) == WBL_TYPE_DEPOT; } /** @@ -305,7 +318,8 @@ inline TileIndex GetShipDepotNorthTile(Tile t) */ inline bool IsLock(Tile t) { - return GetWaterTileType(t) == WATER_TILE_LOCK; + assert(IsTileType(t, MP_WATER)); + return GetWaterTileClass(t) == WBL_TYPE_LOCK; } /** @@ -452,11 +466,12 @@ inline void MakeCanal(Tile t, Owner o, uint8_t random_bits) * @param t Tile to place the ship depot section. * @param o Owner of the depot. * @param did Depot ID. + * @param extended True if building an extended depot. * @param part Depot part (either #DEPOT_PART_NORTH or #DEPOT_PART_SOUTH). * @param a Axis of the depot. * @param original_water_class Original water class. */ -inline void MakeShipDepot(Tile t, Owner o, DepotID did, DepotPart part, Axis a, WaterClass original_water_class) +inline void MakeShipDepot(Tile t, Owner o, DepotID did, bool extended, DepotPart part, Axis a, WaterClass original_water_class) { SetTileType(t, MP_WATER); SetTileOwner(t, o); @@ -465,7 +480,7 @@ inline void MakeShipDepot(Tile t, Owner o, DepotID did, DepotPart part, Axis a, t.m2() = did; t.m3() = 0; t.m4() = 0; - t.m5() = WBL_TYPE_DEPOT << WBL_TYPE_BEGIN | part << WBL_DEPOT_PART | a << WBL_DEPOT_AXIS; + t.m5() = WBL_TYPE_DEPOT << WBL_TYPE_BEGIN | extended << WBL_DEPOT_EXTENDED | part << WBL_DEPOT_PART | a << WBL_DEPOT_AXIS; SB(t.m6(), 2, 4, 0); t.m7() = 0; } diff --git a/src/waypoint_base.h b/src/waypoint_base.h index cbf2e1e608899..a7017d9acbf74 100644 --- a/src/waypoint_base.h +++ b/src/waypoint_base.h @@ -45,16 +45,6 @@ struct Waypoint final : SpecializedStation { void GetTileArea(TileArea *ta, StationType type) const override; - uint GetPlatformLength(TileIndex, DiagDirection) const override - { - return 1; - } - - uint GetPlatformLength(TileIndex) const override - { - return 1; - } - /** * Is this a single tile waypoint? * @return true if it is. diff --git a/src/widgets/airport_widget.h b/src/widgets/airport_widget.h index 4e16f4596ea9d..b48ee28be1aae 100644 --- a/src/widgets/airport_widget.h +++ b/src/widgets/airport_widget.h @@ -12,26 +12,112 @@ /** Widgets of the #BuildAirToolbarWindow class. */ enum AirportToolbarWidgets : WidgetID { - WID_AT_AIRPORT, ///< Build airport button. - WID_AT_DEMOLISH, ///< Demolish button. + WID_AT_CAPTION, ///< Caption of the window. + WID_AT_BUILD_TILE, ///< Add tiles to an airport. + WID_AT_INFRASTRUCTURE_CATCH, ///< Build a piece of airport infrastructure with catchment. + WID_AT_INFRASTRUCTURE_NO_CATCH, ///< Build a piece of airport infrastructure without catchment. + WID_AT_TRACKS, ///< Add new tracks to a tracktile. + WID_AT_RUNWAY_LANDING, ///< Define a new runway for an airport allowing landing. + WID_AT_RUNWAY_NO_LANDING, ///< Define a new runway for an airport not allowing landing. + WID_AT_APRON, ///< Build a new apron. + WID_AT_HELIPAD, ///< Build a new helipad. + WID_AT_HELIPORT, ///< Build a new heliport (same as helipad but taller). + WID_AT_HANGAR_STANDARD, ///< Build a new standard hangar. + WID_AT_HANGAR_EXTENDED, ///< Build a new extended hangar. + WID_AT_CONVERT, ///< Change the airtype of this airport. + WID_AT_REMOVE, ///< Remove widget. + WID_AT_AIRPORT, ///< Build a predefined airport. + WID_AT_DEMOLISH, ///< Demolish button. + WID_AT_CHANGE_GRAPHICS, ///< Change base graphics, if possible. + WID_AT_TOGGLE_GROUND, ///< Toggle airport ground graphics on a tile. + + WID_AT_REMOVE_FIRST = WID_AT_BUILD_TILE, ///< First an last widgets that work combined with remove widget. + WID_AT_REMOVE_LAST = WID_AT_HANGAR_EXTENDED, INVALID_WID_AT = -1, }; /** Widgets of the #BuildAirportWindow class. */ enum AirportPickerWidgets : WidgetID { - WID_AP_CLASS_DROPDOWN, ///< Dropdown of airport classes. - WID_AP_AIRPORT_LIST, ///< List of airports. - WID_AP_SCROLLBAR, ///< Scrollbar of the list. - WID_AP_LAYOUT_NUM, ///< Current number of the layout. - WID_AP_LAYOUT_DECREASE, ///< Decrease the layout number. - WID_AP_LAYOUT_INCREASE, ///< Increase the layout number. - WID_AP_AIRPORT_SPRITE, ///< A visual display of the airport currently selected. - WID_AP_EXTRA_TEXT, ///< Additional text about the airport. - WID_AP_COVERAGE_LABEL, ///< Label if you want to see the coverage. - WID_AP_BTN_DONTHILIGHT, ///< Don't show the coverage button. - WID_AP_BTN_DOHILIGHT, ///< Show the coverage button. - WID_AP_ACCEPTANCE, ///< Acceptance info. + WID_AP_CLASS_DROPDOWN, ///< Dropdown of airport classes. + WID_AP_AIRPORT_LIST, ///< List of airports. + WID_AP_SCROLLBAR, ///< Scrollbar of the list. + WID_AP_LAYOUT_NUM, ///< Current number of the layout. + WID_AP_LAYOUT_DECREASE, ///< Decrease the layout number. + WID_AP_LAYOUT_INCREASE, ///< Increase the layout number. + WID_AP_ROTATION, ///< Selected angle of rotation of the layout. + WID_AP_ROTATION_DECREASE, ///< Decrease the angle of rotation. + WID_AP_ROTATION_INCREASE, ///< Increase the angle of rotation. + WID_AP_AIRPORT_SPRITE, ///< A visual display of the airport currently selected. + WID_AP_EXTRA_TEXT, ///< Additional text about the airport. + WID_AP_COVERAGE_LABEL, ///< Label if you want to see the coverage. + WID_AP_BTN_DONTHILIGHT, ///< Don't show the coverage button. + WID_AP_BTN_DOHILIGHT, ///< Show the coverage button. + WID_AP_ACCEPTANCE, ///< Acceptance info. +}; + +/** Widgets of the #BuildHangarWindow class. */ +enum BuildHangarHeliportWidgets : WidgetID { + WID_BHW_CAPTION, ///< Caption of the window. + WID_BHW_NE, ///< Hangar/heliport with NE entry. + WID_BHW_SE, ///< Hangar/heliport with SE entry. + WID_BHW_SW, ///< Hangar/heliport with SW entry. + WID_BHW_NW, ///< Hangar/heliport with NW entry. +}; + +/** Widgets of the #BuildAirportInfraNoCatchWindow class. */ +enum BuildAirportInfrastructureNoCatchmentWidgets : WidgetID { + WID_BAINC_CAPTION, ///< Caption of the window. + WID_BAINC_FLAG, + WID_BAINC_TRANSMITTER, + WID_BAINC_TOWER, + WID_BAINC_RADAR, + WID_BAINC_PIER, + WID_BAINC_EMPTY, + WID_BAINC_ROTATION, ///< Selected angle of rotation of the infrastructure tile. + WID_BAINC_ROTATION_DECREASE, ///< Decrease the angle of rotation. + WID_BAINC_ROTATION_INCREASE, ///< Increase the angle of rotation. +}; +DECLARE_ENUM_AS_ADDABLE(BuildAirportInfrastructureNoCatchmentWidgets) + +/** Widgets of the #BuildAirportInfraWithCatchWindow class. */ +enum BuildAirportInfrastructureWithCatchmentWidgets : WidgetID { + WID_BAIWC_CAPTION, ///< Caption of the window. + WID_BAIWC_BUILDING_1, + WID_BAIWC_BUILDING_2, + WID_BAIWC_BUILDING_3, + WID_BAIWC_BUILDING_FLAT, + WID_BAIWC_BUILDING_TERMINAL, + WID_BAIWC_ROTATION, ///< Selected angle of rotation of the infrastructure tile. + WID_BAIWC_ROTATION_DECREASE, ///< Decrease the angle of rotation. + WID_BAIWC_ROTATION_INCREASE, ///< Increase the angle of rotation. +}; + +/** Widgets of the #BuildAirportInfraWithCatchWindow class. */ +enum BuildAirportSelectGfxForAirportTracks : WidgetID { + WID_BASGFAT_CAPTION, ///< Caption of the window. + WID_BASGFAT_AUTO, + WID_BASGFAT_DEFAULT, ///< + WID_BASGFAT_01, ///< + WID_BASGFAT_02, ///< + WID_BASGFAT_03, ///< + WID_BASGFAT_04, ///< + WID_BASGFAT_05, ///< + WID_BASGFAT_06, ///< + WID_BASGFAT_07, ///< + WID_BASGFAT_08, ///< + WID_BASGFAT_09, ///< + WID_BASGFAT_10, ///< + WID_BASGFAT_11, ///< + WID_BASGFAT_12, ///< + WID_BASGFAT_13, ///< + WID_BASGFAT_14, ///< + WID_BASGFAT_15, ///< + WID_BASGFAT_16, ///< + WID_BASGFAT_17, ///< + WID_BASGFAT_18, ///< + WID_BASGFAT_19, ///< + WID_BASGFAT_20, ///< }; #endif /* WIDGETS_AIRPORT_WIDGET_H */ diff --git a/src/widgets/company_widget.h b/src/widgets/company_widget.h index 452632dccbabf..72e32bb24a747 100644 --- a/src/widgets/company_widget.h +++ b/src/widgets/company_widget.h @@ -178,6 +178,8 @@ enum CompanyInfrastructureWidgets : WidgetID { WID_CI_TRAM_COUNT, ///< Count of tram. WID_CI_WATER_DESC, ///< Description of water. WID_CI_WATER_COUNT, ///< Count of water. + WID_CI_AIRPORT_DESC, ///< Description of airport. + WID_CI_AIRPORT_COUNT, ///< Count of airport tiles. WID_CI_STATION_DESC, ///< Description of station. WID_CI_STATION_COUNT, ///< Count of station. WID_CI_TOTAL_DESC, ///< Description of total. diff --git a/src/widgets/depot_widget.h b/src/widgets/depot_widget.h index f94f1263d2436..8797fb36ff811 100644 --- a/src/widgets/depot_widget.h +++ b/src/widgets/depot_widget.h @@ -27,9 +27,17 @@ enum DepotWidgets : WidgetID { WID_D_LOCATION, ///< Location button. WID_D_SHOW_RENAME, ///< Show rename panel. WID_D_RENAME, ///< Rename button. + WID_D_HIGHLIGHT, ///< Highlight button. WID_D_VEHICLE_LIST, ///< List of vehicles. WID_D_STOP_ALL, ///< Stop all button. WID_D_START_ALL, ///< Start all button. }; +/** Widgets of the #SelectDepotWindow class. */ +enum JoinDepotWidgets { + WID_JD_CAPTION, // Caption of the window. + WID_JD_PANEL, // Main panel. + WID_JD_SCROLLBAR, // Scrollbar of the panel. +}; + #endif /* WIDGETS_DEPOT_WIDGET_H */ diff --git a/src/widgets/dock_widget.h b/src/widgets/dock_widget.h index 0a1f480eb6187..84f39589c0861 100644 --- a/src/widgets/dock_widget.h +++ b/src/widgets/dock_widget.h @@ -22,7 +22,8 @@ enum DockToolbarWidgets : WidgetID { WID_DT_CANAL, ///< Build canal button. WID_DT_LOCK, ///< Build lock button. WID_DT_DEMOLISH, ///< Demolish aka dynamite button. - WID_DT_DEPOT, ///< Build depot button. + WID_DT_DEPOT, ///< Build standard depot button. + WID_DT_EXTENDED_DEPOT, ///< Build extended depot button. WID_DT_STATION, ///< Build station button. WID_DT_BUOY, ///< Build buoy button. WID_DT_RIVER, ///< Build river button (in scenario editor). diff --git a/src/widgets/rail_widget.h b/src/widgets/rail_widget.h index f90280c52be87..b7358c515273e 100644 --- a/src/widgets/rail_widget.h +++ b/src/widgets/rail_widget.h @@ -13,21 +13,22 @@ /** Widgets of the #BuildRailToolbarWindow class. */ enum RailToolbarWidgets : WidgetID { /* Name starts with RA instead of R, because of collision with RoadToolbarWidgets */ - WID_RAT_CAPTION, ///< Caption of the window. - WID_RAT_BUILD_NS, ///< Build rail along the game view Y axis. - WID_RAT_BUILD_X, ///< Build rail along the game grid X axis. - WID_RAT_BUILD_EW, ///< Build rail along the game view X axis. - WID_RAT_BUILD_Y, ///< Build rail along the game grid Y axis. - WID_RAT_AUTORAIL, ///< Autorail tool. - WID_RAT_DEMOLISH, ///< Destroy something with dynamite! - WID_RAT_BUILD_DEPOT, ///< Build a depot. - WID_RAT_BUILD_WAYPOINT, ///< Build a waypoint. - WID_RAT_BUILD_STATION, ///< Build a station. - WID_RAT_BUILD_SIGNALS, ///< Build signals. - WID_RAT_BUILD_BRIDGE, ///< Build a bridge. - WID_RAT_BUILD_TUNNEL, ///< Build a tunnel. - WID_RAT_REMOVE, ///< Bulldozer to remove rail. - WID_RAT_CONVERT_RAIL, ///< Convert other rail to this type. + WID_RAT_CAPTION, ///< Caption of the window. + WID_RAT_BUILD_NS, ///< Build rail along the game view Y axis. + WID_RAT_BUILD_X, ///< Build rail along the game grid X axis. + WID_RAT_BUILD_EW, ///< Build rail along the game view X axis. + WID_RAT_BUILD_Y, ///< Build rail along the game grid Y axis. + WID_RAT_AUTORAIL, ///< Autorail tool. + WID_RAT_DEMOLISH, ///< Destroy something with dynamite! + WID_RAT_BUILD_DEPOT, ///< Build a depot. + WID_RAT_BUILD_EXTENDED_DEPOT, ///< Build an extended depot. + WID_RAT_BUILD_WAYPOINT, ///< Build a waypoint. + WID_RAT_BUILD_STATION, ///< Build a station. + WID_RAT_BUILD_SIGNALS, ///< Build signals. + WID_RAT_BUILD_BRIDGE, ///< Build a bridge. + WID_RAT_BUILD_TUNNEL, ///< Build a tunnel. + WID_RAT_REMOVE, ///< Bulldozer to remove rail. + WID_RAT_CONVERT_RAIL, ///< Convert other rail to this type. INVALID_WID_RAT = -1, }; diff --git a/src/widgets/road_widget.h b/src/widgets/road_widget.h index 679545728a2e8..2fbf3c657393f 100644 --- a/src/widgets/road_widget.h +++ b/src/widgets/road_widget.h @@ -19,6 +19,7 @@ enum RoadToolbarWidgets : WidgetID { WID_ROT_AUTOROAD, ///< Autorail. WID_ROT_DEMOLISH, ///< Demolish. WID_ROT_DEPOT, ///< Build depot. + WID_ROT_EXTENDED_DEPOT, ///< Build extended depot. WID_ROT_BUILD_WAYPOINT, ///< Build waypoint. WID_ROT_BUS_STATION, ///< Build bus station. WID_ROT_TRUCK_STATION, ///< Build truck station. diff --git a/src/window_gui.h b/src/window_gui.h index 1c5b7d8d5c84f..a082e141f05d9 100644 --- a/src/window_gui.h +++ b/src/window_gui.h @@ -336,6 +336,11 @@ struct Window : ZeroedMemoryAllocator { template inline NWID *GetWidget(WidgetID widnum); + inline bool HasWidget(WidgetID widnum) const + { + return this->GetWidget(widnum) != nullptr; + } + const Scrollbar *GetScrollbar(WidgetID widnum) const; Scrollbar *GetScrollbar(WidgetID widnum); diff --git a/src/window_type.h b/src/window_type.h index 0896d5ff6fc1e..9a271f53ddcee 100644 --- a/src/window_type.h +++ b/src/window_type.h @@ -241,6 +241,12 @@ enum WindowClass { */ WC_SELECT_STATION, + /** + * Select depot (when joining depots); %Window numbers: + * - #Vehicle type = #JoinDepotWidgets + */ + WC_SELECT_DEPOT, + /** * News window; %Window numbers: * - 0 = #NewsWidgets @@ -346,7 +352,7 @@ enum WindowClass { /** * Depot view; %Window numbers: - * - #TileIndex = #DepotWidgets + * - #DepotID = #DepotWidgets */ WC_VEHICLE_DEPOT, @@ -383,8 +389,8 @@ enum WindowClass { /** * Build vehicle; %Window numbers: - * - #VehicleType = #BuildVehicleWidgets - * - #TileIndex = #BuildVehicleWidgets + * - #DepotID = #BuildVehicleWidgets, for existing depots + * - #MAX_DEPOTS + VehicleType = #BuildVehicleWidgets, for "Available Trains"... */ WC_BUILD_VEHICLE, @@ -419,9 +425,29 @@ enum WindowClass { * - #TRANSPORT_WATER = #BuildDockDepotWidgets * - #TRANSPORT_RAIL = #BuildRailDepotWidgets * - #TRANSPORT_ROAD = #BuildRoadDepotWidgets + * - #TRANSPORT_AIR = #BuildHangarHeliportWidgets */ WC_BUILD_DEPOT, + /** + * Build heliport; %Window numbers: + * - #TRANSPORT_AIR = #BuildHangarHeliportWidgets + */ + WC_BUILD_HELIPORT, + + /** + * Build airport infrastructure; %Window numbers: + * - #TRANSPORT_AIR = #BuildAirportInfrastructureNoCatchmentWidgets + * - #TRANSPORT_AIR = #BuildAirportInfrastructureWithCatchmentWidgets + */ + WC_BUILD_AIRPORT_INFRASTRUCTURE, + + /** + * Select gfx for airport track tiles; %Window numbers: + * - #TRANSPORT_AIR = #BuildHangarHeliportWidgets + */ + WC_SELECT_TRACK_GFX, + /** * Build waypoint; %Window numbers: * - #TRANSPORT_RAIL = #BuildRailWaypointWidgets