Compare commits

...

505 Commits

Author SHA1 Message Date
Duncan a01bd32132
Construction Window ToolUpdate (#1119)
* Start Implementing track tool update

* Further work

* Finish function

* Fix bugs

* Fix view issue

* Extract out common function

* Implement road tool update
2021-08-26 21:42:41 +01:00
Aaron van Geffen 070ec04888
Fix caption 22 widget text rendering (#1124) 2021-08-23 10:47:48 +02:00
Marijn van der Werf d793467167
Remove calls to already implemented functions (#1116)
* Remove calls to already implemented functions

* Fix location comment for getClosestTownAndUnk

* Move buildDeliveredCargoPaymentsTable (Maybe just keep in UI?)

* Remove terraform hooks
2021-08-22 18:48:18 +02:00
Aaron van Geffen 2d1b9944b7
Fix crash on save game open (#1121)
My previous PR, #1118, removed the hooks to the prompt browse window. This was working fine for all except saving games! 
Turns out the toolbar menu was still calling the vanilla function, thereby relying on the hook.

Replacing this call revealed a bug in hiding with the save prompt being initialised to the wrong save path.
This has been addressed as well.
2021-08-22 18:37:43 +02:00
rdrdrdrd95 ce8f6bca9f
Implement exception message box popups (#1104) 2021-08-20 14:53:10 +02:00
Aaron van Geffen 7d0f7f9dba
Finish PromptBrowseWindow's prepare draw function (#1118)
* Implement loadFileDetails (0x00446E87)

* Implement the rest of prepareDraw (0x00445C8F)

* Rename file_entry to FileEntry; apply code style

* Remove superfluous hooks for the browse window
2021-08-20 12:31:03 +02:00
Duncan 65895bcab1
Implement Tree Paint (#1115)
* Implement tree drawing

Co-authored-by: Marijn van der Werf <marijn.vanderwerf@gmail.com>
2021-08-18 22:09:34 +01:00
Duncan 18422de137
Implement tile element paint (#1113) 2021-08-18 12:19:54 +01:00
Aaron van Geffen f4d0d9215c
Handle missing strings more gracefully (#1114) 2021-08-17 13:20:30 +02:00
Duncan a48d0c7c74
Further Point refactoring (#1111)
* Split up viewport transforms

* Make naming more concistent

* Move into namespace, make constexpr
2021-08-17 11:59:41 +01:00
Duncan 055def4215
TrackAndDirection (#1017)
* Create new helper struct for trackdirection

* Split up into clear road or track

* Try name offsets where possible

* Update with knowledge from track branch
2021-08-17 07:15:40 +01:00
Duncan 6e74345b08
Remove track Construction Window (#1110)
* Implement track removal

* Add road equivalent connections

* Implement removeRoad
2021-08-17 07:14:50 +01:00
Duncan ef420ec6e6
Construction Window StationTab toolDown (#1105)
* Implement airport toolDown

* Implement dock toolDown

* Tidy up

* Refactor and use alternative simpler search

* Implement roadStation toolDown

* Implement trainStation toolDown

* Revert vehicle body changes as no longer required

* Include correct headers

* Fix off by one error
2021-08-16 21:11:15 +01:00
Duncan 191e6d567c
Implement constructTrack (#1109) 2021-08-16 19:47:02 +01:00
Duncan f4f3d0d72c
Implement various manager resets (#1112) 2021-08-16 14:19:53 +01:00
Aaron van Geffen 51789bd329
Implement RoadElement::update (#1106) 2021-08-16 15:19:02 +02:00
Margen67 14edc842b6
CI improvements (#1107)
Add workflow_dispatch: https://github.blog/changelog/2020-07-06-github-actions-manual-triggers-with-workflow_dispatch/
Remove unneeded brackets and shell.
Windows:
 Opt out of PowerShell telemetry.
 Use default ErrorView.
 Simplify vswhere commands.
 Add -m to msbuild.
 Fix typos in artifact steps.
 upload-artifact:
  Use v2.
  Use runner.os in name.
Use -B instead of mkdir where applicable.
upload-artifact:
 Remove unneeded quotation marks.
 Add if-no-files-found: error
mac:
 Add name.
 Require check-code-formatting like other jobs.
 Remove unneeded shell.
 Add names to steps.
 Make dependency version into variable.
 Don't hardcode job number.
 Use tar instead of zip.
2021-08-15 13:18:19 +01:00
Duncan 3c4937e7ca
Fix road dropdown not remembering selection (#1108)
* Fix road dropdown not remembering selection

Incorrectly signed type was causing comparisons of equality to fail

* Update changelog
2021-08-14 14:20:48 +01:00
duncanspumpkin 29b405007e Start v21.08+ 2021-08-12 22:14:56 +01:00
duncanspumpkin 7aecfa55ef Update OpenLoco version to v21.08 2021-08-12 22:11:49 +01:00
Marijn van der Werf 9c24c21227
Compile macOS builds (32-bit) using osxcross (#924)
* Compile macOS release using osxcross

* Symlink system

* Remove modules bit

* Include YAML dir

* Conditionally build icns

* Merge yaml-cpp branches in cmakelists

* Move toolchain file
2021-08-12 23:03:25 +02:00
Duncan 22cf266773
Introduce doCommand template (#1082)
* Introduce doCommand template

* Continue using template

* Swap around arguments

* Add remaining structs

* Start next batch

* Fix rebase issues and mistake
2021-08-12 22:00:35 +01:00
Duncan f2f9719b75
AnimationManager and WaveManager split (#1097)
* Move wave and animations into seperate manager

* Add game state knowledge

* Move update functions to managers

* Move reset to manager

* Cleanup and implement reset

* Implement Wave Manager update

* Hook create wave earlier to improve interface
2021-08-12 13:35:25 +01:00
Duncan cba3bd06d1
Fix: #366. Implement deliver cargo to nearby stations (#1101)
* Fix: #366. Implement deliver cargo to nearby stations

Original game had a bug where it would write to bad memory and end up
delivering cargo to stations that it shouldn't

* Add note and break

* Update changelog

* Adjust variable name
2021-08-12 11:52:41 +01:00
Aaron van Geffen 44f5210c2f
Prevent invalid filename due to off-by-one in file browser (#1102) 2021-08-11 23:16:49 +02:00
Duncan 8026daa22c
Fix #1099: Unable to place signal properly (#1103)
Actually pass the mouse position to the side calculator.
2021-08-11 23:15:09 +02:00
Duncan 904abdecce
Building tile element update (#1100)
Co-authored-by: Marijn van der Werf <marijn.vanderwerf@gmail.com>
2021-08-11 20:52:21 +01:00
Duncan 498911fd81
Implement base of TileManager::update (#1098)
Co-authored-by: marijnvdwerf
2021-08-10 20:45:57 +01:00
Aaron van Geffen f7fcba0e6a
Implement more company manager functions (#1096)
* Minor refactor to use isPlayerCompany

* Implement CompanyManager::reset (add hook?)

* Replace instances of second getOwnerStatus signature

* Implement CompanyManager::getOwnerStatus
2021-08-10 21:35:01 +02:00
Aaron van Geffen ae294ad80e
Only use red text colour for expenditure sums (#1095) 2021-08-10 15:14:01 +02:00
Duncan 8461ad721b
Implement construction open At functions (#1094)
* Implement openAtTrack

* Split up functions for reuse

* Implement openAtRoad

* Minor refactorings

* Update changelog
2021-08-10 11:53:41 +01:00
Duncan 80952a1c99
Implement Signal toolUpdate/Down (#1093)
* Start implementing toolUpdate signal

* Implement getSide and finish signal tool update

* Implement signal toolDown

* Actually name the game command

* Use correct global address
2021-08-10 05:57:25 +01:00
Duncan 99049e5fab
Track/Road Mods Tool Update/Down (#1092)
* Implement road overhead tool down

* Implement track mod tool down

* Implement overhead tool update

* Apply review comments
2021-08-09 13:04:41 +01:00
Duncan b08815aeaf
Pass by ref Gfx::rect functions (#1091)
* fillRect pass context by ref

* drawRect pass context by ref

* draw/fillRect pass context by ref

* drawLine pass context by ref
2021-08-08 20:43:23 +01:00
Duncan 8333ece44e
Interaction rightOver (#1071)
* Start implementing interaction rightOver

* Implement rightOverTrackExtra

* Implement rightOverSignal and TrackStation

* Implement rightOver road extra station

* Implement rightOver airport dock

* Implement rightOver Tree Wall

* Implement rightOver Headquarters BuildingConstruct

* Implement rightOverBuilding remove call

* Minor refactor and naming

* Make gcc happy

* Fix conditional

* Use ternary
2021-08-08 20:42:53 +01:00
Duncan bd794b21dc
Implement the last company window function (#1077)
* Implement the last company window function

* Rename

* Remove pointless loop

* Fix off by 2 error
2021-08-08 20:42:28 +01:00
Duncan 4e3f77a1d9
Construction Window Tab1 Refactor (#1086)
* Split up two functions

* Rename and split off duplications

* Reorg and label

* Extract out function

* Reintroduce code back from function

* Split out function

* Remove dead code

* Integrate function into base code

* Split out into function

* Refactor track similarly

* Consolidate into one templated function

* Pass by ref

* Fix compile error

* Add missing static
2021-08-08 13:39:57 +01:00
Duncan e698c13357
Implement Clip Context (#1072)
* Implement clipContext

* Switch over all callers to use new version
2021-08-08 13:33:58 +01:00
Aaron van Geffen 139eebf1c0
Make tree clusters cost money outside editor (#1089)
* Change: tree clusters now cost money outside editor

* Query viability before executing placement command

* Fix error title for cluster tools

* Only play sound if tree planting was successful

* Refactor the tree cluster tool to use one templated func

Co-authored-by: duncanspumpkin <duncans_pumpkin@hotmail.co.uk>
2021-08-08 13:56:35 +02:00
Duncan f5494630c6
Ui::Point (#1088)
* Introduce Ui::Point and Ui::Point32

* Remove xy32 replace with Point / Point32

* Remove empty file

* Fix compiler error

* Rename UiSize to Size
2021-08-08 12:46:27 +01:00
Duncan b427523621
Townlist (#1090)
* Close #1039. Implement removeBuildingGhost

* Implement placeBuildingGhost

* Close #1040. Implement town toolUpdate

* Close #1041. Implement town toolDown

* Fix bug

* Add missing title
2021-08-08 11:09:48 +01:00
Duncan 33c28eff12
Pass context by ref for all string functions (#1087) 2021-08-06 19:54:52 +01:00
Duncan 43b670507d
Vehicle Tool Down (#1084)
* Implement air tool down

* Implement pickup tool down water

* Implement pickup tool down land

* Fix broken land placement
2021-08-06 09:24:52 +01:00
Aaron van Geffen a57af0e1f1
Implement removeTree game command (#1083) 2021-08-05 23:44:18 +02:00
Duncan 4c54152737
Prevent implicit conversions from Pos2 and xy32 (#1051) 2021-08-05 22:17:54 +01:00
Duncan 3616ec0f7d
Modify drawScroll event to pass by reference (#1085) 2021-08-05 22:17:31 +01:00
Duncan da39ecf082
Terraform2: Clear and Water tools (#1081)
* Implement terraform clear area tool update

* Implement terraform water update

* Implement missing function, fix compile issue

* Fix rebase issues
2021-08-05 18:52:39 +01:00
Duncan b756c2e440
Implement Tree and Wall toolUpdate Down (#1078)
* Implement Wall toolUpdate Down

* Start implementation of tree tool Update Down

* Continue

* Finish scatter down

* Refactor trig function into Math file

* Implement second cluster tool function

* Name tree flags where possible

* Add land object flags

* Try alternative constant

* Add missing error titles and widget checks

* Add missing error title from industrylist

* Clang format

* Simplify sine wave
2021-08-05 16:06:58 +01:00
Duncan c82281540f
VehicleHead::updateLoadCargo (#983)
* Implement cargo load function

* Fix loop

Fix minor issues

Fix cargo rating mistake
2021-08-05 12:55:31 +01:00
Duncan 17bd0e605c
Implement bringToFront (#1068) 2021-08-05 10:37:00 +01:00
Duncan 375d8571cc
Further coords work (#1066)
* Refactor centre2dCoordinates

* Refactor getCursorPos
2021-08-05 10:36:28 +01:00
Duncan 63b8813db8
Cleanup duplicated call instances and add notes (#1069) 2021-08-05 10:32:27 +01:00
Aaron van Geffen 06e6c34671
Rename cursor and tool constants (#1080)
* Name unnamed cursor constants

* Use cursor constants for Input::toolSet

* Consolidate access to current tool cursor variable
2021-08-04 20:06:37 +02:00
Aaron van Geffen 4baeabc5d2
Use system cursors for pointer and hand cursors (#1076)
Recently, #674 introduced support for Locomotion's custom cursors on Linux and macOS. As a result of that PR,
however, the pointer and hand cursors regressed to their Windows 9x/2000 counterparts. This PR changes only
these specific cursors to use the appropriate system cursors.
2021-08-04 20:06:10 +02:00
Aaron van Geffen ecae932561
Allow rotating buildings in town list by keyboard shortcut (#1079) 2021-08-03 21:11:22 +02:00
Marijn van der Werf f6f1bd7b2e
Unify pointer to x86-register casts (#1062)
* Use X86Pointer for passing pointers

* Use assert_struct_size macro

* Add conversion support to X86Pointer
2021-08-03 03:07:34 +02:00
Duncan a3221a8df2
Some viewport functions (#1067)
* Implement getCentreMapPosition

* Implement screenGetMapXY

* Use function

* Add helper function and rename members

* Implement and rework getQuadrantFromPos

* Split up further for future use

* Move function location and rename further

* Further tweaks

* Rebase fixes

* Use ternary to simplify code
2021-08-02 12:29:47 +01:00
Duncan 33dd39e760
Implement processMessagesMini (#1073) 2021-08-02 11:24:48 +01:00
Duncan 3b062132c8
Vehicle Tool Update (#1064)
* Start vehicle tool update

* Add remaining water tool update code

* Rework and make work

* Start implementing vehicle air placement update

* Further work

* Finish air pickupToolUpdate

* Start land vehicle tool update

* Start rail update function

* Fix rebase mistake

* Move subposition to seperate data file

* Further work

* Fix placement command

* Handle curves

* Implement road tool update

* Name unk variable

* Clang format

* Finish rename

* Apply review comments
2021-08-01 19:14:16 +01:00
Duncan f85b375063
Townlist ToolUpdate/Down (#1035)
* Start work

* Further work

* Finish function

* Implement toolDown

* Remove unused type

* Fix colour. Improve interfaces

* Fix dropdown colour selection

* Update changelog
2021-08-01 18:59:20 +01:00
Duncan 7e46462f87
Industry Tool Update/Down (#1063)
* Implement ghost removal

* Implement tool update

* Implement toolDown

* Fix mistakes

* Fix compile issue
2021-08-01 18:58:39 +01:00
Duncan d4c49e39e4
Company Window: Tool Update (#1061)
* Finish remaining company tool functions

* Fix compile issues. Add comments
2021-08-01 18:57:20 +01:00
Duncan 26256545ba
Fix station naming crashing after exahusting realNames (#1070) 2021-08-01 09:57:46 +02:00
Ted John 0ee44427e9
Fix bitScanReverse for GCC (#1065) 2021-07-26 21:54:31 +01:00
Duncan 1d5b041df3
Remove duplicated function and rename (#1052)
* Remove duplicated function and rename

* Avoid narrow error

* Actually do transform correctly
2021-07-26 12:16:39 +01:00
Duncan d70a3dd7fc
Provide further notes for string control codes (#1056)
* Provide further notes for string control codes

* Apply review comments
2021-07-21 15:04:09 +01:00
Duncan 82adf56d87
Implement getMaxStringWidth (#1060) 2021-07-21 13:02:40 +01:00
Duncan 92b781cf48
Implement alternative clip string (#1054)
* Implement alternative clip string

* Add explicit newlineXY entry
2021-07-21 13:02:16 +01:00
Duncan cbdbded197
Right Click Interact (#1053)
* Start work

* Headquarter and Tree right click interact

* Implement wall and building removal interaction

* Begin airportInteract

* Implement airport and dock interaction

* Implement track and road station interaction

* Implement signal interact

* refactor for consistent uses
2021-07-21 13:01:39 +01:00
Duncan 4186081736
Partial fix #1048. Utf8 path issues (#1057)
* Partial fix #1048

* Further u8path fixes

* Even further path fixes

* Reduce conversion madness in platform files
2021-07-21 12:54:39 +01:00
Duncan 8a0c0fa68a
Implement getTileStartAtCursor (#1034)
* Implement getTileStartAtCursor

* Use implemented function

* Fix narrowing

* Remove duplicated function
2021-07-21 12:43:16 +01:00
Duncan b7ede286a2
Make accessor functions for mapselection points (#1036)
* Make accessor functions for mapselection points

* Clang format
2021-07-21 05:10:54 +01:00
Marijn van der Werf 0a0d9f985b
Dump cursors from original binary (#674)
* Dump cursors from original binary

* Add routines to convert cursors from ASCII art.

* [tmp] Redump cursor

* Load dumped cursors

* Empty out Cursor.cpp

* Move cursors

* Add Cursor.cpp to vcxproj

* Add header file to vcxproj

Co-authored-by: Aaron van Geffen <aaron@aaronweb.net>
Co-authored-by: duncanspumpkin <duncans_pumpkin@hotmail.co.uk>
2021-07-19 13:01:08 +01:00
duncanspumpkin 70c9d8635c Start v21.07+ 2021-07-18 07:40:47 +01:00
duncanspumpkin 7ec445f7f7 Update OpenLoco version to v21.07 2021-07-18 07:14:25 +01:00
Duncan f280941bc5
Readd window_colour_4 control code (#1055) 2021-07-16 21:45:49 +01:00
Duncan f57a13a7da
Refactor and fix headquarter placement (#1044)
* Refactor and fix headquarter placement

* Add changelog entry
2021-07-14 17:45:02 +01:00
Duncan 29938544e2
Implement TownManager::reset (#1050) 2021-07-14 10:14:13 +01:00
Duncan 46637fb5c2
Implement populateTownSizeSelect (#1038)
* Implement populateTownSizeSelect

* Remove extra size entry
2021-07-14 10:13:57 +01:00
Aaron van Geffen 57166baefd
Mention code style in readme (#1047)
* Add note on code style to readme

* Fix readme section numbering and rebuild table of contents
2021-07-10 08:49:54 +02:00
Duncan ee83e9f53c
Remove redundant rotate function (#1046) 2021-07-09 13:54:06 +01:00
Duncan 6a4e022af8
Link up implemented function (#1043) 2021-07-06 07:22:47 +01:00
Duncan 65d905d2c1
Fix use of constants for coord limits (#1037) 2021-07-05 22:48:04 +02:00
Duncan 79f1d00674
validCoords: remove template parameter and name type (#1033)
There is no requirement for this function to take anything other than Pos2 so best make it an explicit type.
2021-07-04 12:04:52 +02:00
Aaron van Geffen 1444685591
Add coordinate validation to tile loops with offsets (#1031) 2021-07-03 22:42:54 +02:00
Aaron van Geffen 6b0c50f77f
Implement ChangeCompanyColour game command (#1029)
* Implement ChangeCompanyColour game command

* Implement updateHeadquartersColour

* Implement updateHeadquartersColourAtTile

* Implement updateVehicleColours
2021-07-03 19:22:53 +02:00
Aaron van Geffen 2f3b9ef696
Implement colour picker dropdowns (#1028)
* Implement Dropdown::showColour and company colour pickers

* Apply competing colour mask to company primary colour selection

* Colour selections for buildings in TownList

* Tree colour picker (untested)
2021-07-03 18:51:56 +02:00
Aaron van Geffen e9fd958b23
Implement StationManager::generateNewStationName (#1020)
* Start implementing StationManager::generateNewStationName

* Use functions to convert from and to town names

* Implement IndustryManager::industryNearPosition for 'Oilfield' and 'Mines'

* Implement TileManager::countSurroundingWaterTiles for 'Lakeside'

* Implement TileManager::countSurroundingTrees for 'Forest' and 'Woods'

* Implement station heights/valley

* Register hook to allow testing by means of creating stations

* Implement central/north/south/east/west names

* Add enum for town names; use bitsets to keep track

* Remove unnecessary surface loops
2021-07-03 11:58:22 +02:00
Aaron van Geffen fbc145a5bf
Station rename command (#984)
This re-introduces the possibility of resetting/regenerating station names automatically
by using an empty station name. Previously, our re-implementation (C++ code) inadvertently
removed this option.
2021-07-01 19:59:37 +02:00
Michał Janiszewski d10b16e404
Fix access to embedded object name (#1022) 2021-07-01 10:59:38 +02:00
rdrdrdrd95 17f1d1f5b1
Reduce inclusion of StringIds.h in header files (#985) 2021-07-01 10:49:30 +02:00
Michał Janiszewski cace3b9148
Merge pull request #1026 from AaronVanGeffen/animation-hook
Force alignment on TileManager::createAnimation hook
2021-06-30 22:24:00 +02:00
Duncan 4863d0e617
Implement the station name background paint (#1023) 2021-06-30 19:49:51 +01:00
Aaron van Geffen f1adeb7568 Force alignment on TileManager::createAnimation hook 2021-06-30 19:07:22 +02:00
Duncan 004f0c520e
Merge duplicated flags and use accessors (#1025) 2021-06-30 10:41:18 +01:00
Duncan b7c0cf5f9e
Remove use of global stringformatbuffer where not required (#1024) 2021-06-30 10:40:25 +01:00
Duncan 7bdc32d8b2
Refactor calls to tryCreateInitialMovementSound (#1018)
Forgot to do this after implementing all of the land update functions.
2021-06-28 15:39:47 +02:00
Alexander 641898d977
Turn widget draw functions into Widget struct member functions (#1012) 2021-06-26 12:56:42 +02:00
Aaron van Geffen 6a10da15ca
Allow filtering the vehicle list by station or cargo (#997) 2021-06-26 11:12:20 +02:00
Aaron van Geffen d6a0b7a0ff Restore game command table alignment 2021-06-26 11:09:14 +02:00
Duncan 6eaf015902
Fix incorrect comparison and naming (#1016)
* Fix incorrect comparison and naming

* Remove function as it is incorrect

* Correct notes

* Add changelog entry
2021-06-25 11:22:02 +01:00
Duncan 9a5e879960
Yank out refactors from cargo load pr (#1015) 2021-06-23 22:42:15 +01:00
Duncan 62f4fe2cfb
Create a safe itererable wrapper for loco arrays (#533)
* Create fixed vector type

* Use fixed vector for Companies

* Use fixed vector for Industries

* Use new type for Towns

* Use FixedVector for Stations

* Move and rename file to LocoFixedVector.hpp
2021-06-22 21:00:13 +01:00
Duncan 3e33a46600
Start implementing the map tooltip window (#1011)
* Start implementing the map tooltip window

* Finish paint functions

* Fix company draw location

* Light refactor

* Implement review comments
2021-06-22 17:43:11 +01:00
Michał Janiszewski 475e9075b9
Merge pull request #1006 from janisozaur/breakpad
Add breakpad to MSVC builds
2021-06-21 22:58:11 +02:00
Michał Janiszewski 31e800c58c Address review comments 2021-06-21 22:09:39 +02:00
Duncan bfd99e849d
Fix 32bit overflow warnings (#1008)
* Fix 32bit overflow warnings

* Fix ui bug
2021-06-21 17:58:46 +01:00
Duncan dfdc1c5d9f
Fix sizeof widget (#1009) 2021-06-21 17:58:29 +01:00
Michał Janiszewski 69318d9a10 Disable breakpad on wine
On wine symbols are not handled the expected way and installing our own
crash handler prevents wine's own from kicking in - which actually
provides useful info.
2021-06-20 21:00:10 +02:00
Michał Janiszewski 0dd240a503 Add changelog entry for #1006: breakpad for MSVC 2021-06-19 23:45:50 +02:00
Michał Janiszewski 155845d299 Obtain version info for dump by querying OpenLoco 2021-06-19 23:45:44 +02:00
Michał Janiszewski 324b97b6fb Rename dump files to include commits 2021-06-19 23:45:44 +02:00
Michał Janiszewski 228e12a6bd Add pdb symbols to MSVC build artifacts 2021-06-19 23:45:44 +02:00
Michał Janiszewski 4c06a785f1 Add breakpad to MSVC builds 2021-06-19 23:45:44 +02:00
Duncan 74995014e6
Make widget::colour safe (#1001)
* Initial start of making widget::colour safe

* Switch all makeWidgets to use enum class

* Modify name to WindowColour

* Use accessors for window colours

* Add assert
2021-06-18 13:15:17 +01:00
Michał Janiszewski 68938e075e
Update dependencies to v1.4.0 (#1004)
This update adds Google's Test, Benchmark and Breakpad libraries
2021-06-16 23:29:31 +02:00
Aaron van Geffen e836285789
Clarify availability of release builds and need for compilation (#996)
* Clarify availability of release builds and need for compilation

* Add instruction for linking/coping openloco.exe
2021-06-16 18:28:41 +02:00
Michał Janiszewski a15e1421f1
Fix challenge tab being disabled twice in ScenarioOptions (#1000) 2021-06-16 10:17:19 +02:00
Duncan 121d0be7b8
Enforce coding style on misc Ui files (#990)
* Enforce coding style on misc Ui files

* Move widget draw functions private

* Make review changes
2021-06-16 08:13:24 +01:00
Michał Janiszewski 936a379d91
Only write town history when using valid index (#992)
GCC10-based mingw complained about possibly writing to index -1
2021-06-15 15:16:40 +01:00
Michał Janiszewski 18b24c7716
Exit cleanly in all conditions through common path (#994)
* Exit cleanly in all conditions through common path

* Free allocated strings upon exit
2021-06-15 14:48:08 +01:00
Michał Janiszewski 4d58d913fd
Retry hook installation (#993)
* Add more logging in case of errors

* Retry hook installation, as it can fail in some circumstances

This can happen on some versions of wine which would fail on
WriteProcessMemory calls with specific addresses. Skipping to next entry
works just fine.

* Add change log entry for hook reinstallation
2021-06-15 14:47:48 +01:00
Michał Janiszewski 4e03c48680
Fix memcpy size in Rename{Town,Industry} (#995)
This would cause reading one more byte than present in source buffer
2021-06-15 15:47:22 +02:00
Michał Janiszewski 46aa213cca
Config: reduce packing to vanilla config struct only (#991) 2021-06-10 23:17:10 +02:00
Michał Janiszewski e2be89f114
Add cstdlib include to TrackData.h for size_t (#989) 2021-06-10 16:31:46 +01:00
Duncan 92543a09a7
Update changelog for #986 (#988) 2021-06-10 11:58:22 +02:00
Duncan c762c82caa
Enforce coding style on object files (#987) 2021-06-10 09:35:58 +01:00
Duncan 31fb6a5f83
Merge pull request #921 from IntelOrca/implement/load
Implement/load
2021-06-10 07:55:45 +01:00
duncanspumpkin 4c48a82c4f Add helper function to object_repository_items 2021-06-10 07:50:50 +01:00
duncanspumpkin 31cbe380c0 fix rebase issues 2021-06-09 16:13:33 +01:00
duncanspumpkin 7bb91b9b0a Fix rebase mistakes 2021-06-09 16:13:33 +01:00
duncanspumpkin 9c9f2ae579 Tidy up remaining loose ends
fix rebase issues

use memcpy

make rotation a static global

Fix ci issues

revert memcpy
2021-06-09 16:13:33 +01:00
duncanspumpkin 29a5d155b5 Implement object unpacking
Fix rebase issues

Cleanup of unpack object code

keep ci happy

Fix hook
2021-06-09 16:13:33 +01:00
duncanspumpkin e0e4e79559 Fix rebase issues and minor corrections
Refactor to follow codebase style
2021-06-09 16:13:30 +01:00
Ted John 00cdd64fef Implement most of loading S5 2021-06-09 16:12:33 +01:00
Duncan 1a3e1902f4
Merge pull request #986 from duncanspumpkin/align
Add force align attributes to all hooks
2021-06-09 16:05:36 +01:00
Michał Janiszewski 6dfa662a43 Add GCC to compilers requiring stack alignment 2021-06-09 16:45:42 +02:00
duncanspumpkin 2ff86cbf0f Add force align attributes to all hooks 2021-06-09 12:38:01 +01:00
Duncan c1cc552b4b
Fix incorrect rating calc for cargo (#982)
* Fix incorrect rating calc for cargo

* Update changelog
2021-06-01 14:10:10 +01:00
Aaron van Geffen f9e8e460a4
Add missing includes for gcc 11.1 compilation (#980) 2021-06-01 15:08:05 +02:00
Hielke Morsink 65b03b9588
Add a default installation path to searchPaths (#978)
This is the default path used by the offline installer from GOG.
2021-06-01 12:55:41 +01:00
TransshipmentEnvoy c483a03dbd
zh-CN: add Simplified Chinese translation (#979) 2021-05-21 23:02:18 +02:00
TELK 1acfbd9a11
ko-KR: Update translation for v21.05 (#977) 2021-05-20 20:06:45 +02:00
Pedro Ortiz Bledow f9358b25bc
Update pt-BR translation (#976)
- General typo fixes
- Changed some instances of 'Locomotion' to 'OpenLoco'
- Changed instances of 'Empresa' to 'Companhia'
- Added translation for 'Disable vehicle breakdowns'
2021-05-15 15:55:30 +02:00
Duncan 8607b871b8
Further coding style enforcement (#972)
* Further coding style enforcement

* Reduce the reptitiveness of some names
2021-05-12 13:27:29 +01:00
Aaron van Geffen cacb426f6f Update OpenLoco version to v21.05 2021-05-11 22:28:26 +02:00
Aaron van Geffen 8e54f42a41
Temporarily hide multiplayer functionality (#975)
Occasionally, new players assume OpenLoco has working multiplayer functionality,
while it's been broken since the first release. This change hides the toggle button
from the title menu, while still making it easy to re-enable.
2021-05-11 22:23:38 +02:00
Duncan 7b5bc50fad
Implement helper function for inflation calcs (#974) 2021-05-10 21:12:33 +01:00
Duncan bb5cccd7e8
Implement update quarterly (#965)
* Implement update quarterly

* Move function locaton

* Fix formatting

* Fix comments
2021-05-09 07:24:57 +01:00
Duncan b2273923ef
Rename of drawpixelinfo to Context (#971)
* Start rename of drawpixelinfo to Context

* Rename of variable name

* Further renames

* Fix formatting

* Catch accidentally missed renames
2021-05-07 19:00:15 +01:00
Duncan bd6936c9f6
Config coding style (#970)
* Enforce coding style on config

* Further work

* Enforce coding style
2021-05-07 09:33:14 +01:00
guKing 3ff62d5d46
Update pt-BR.yml (#966)
Added strings 2131 to 2208 (cheats, debugging, some options and controls in the menus)
Localized: Company names; Station names;
Refined: Lines 145, 374, 473,
Corrected: Lines 131
2021-05-07 10:32:40 +02:00
Duncan a7d62ed539
Enforce coding style on gfx (#968) 2021-05-06 12:50:12 +01:00
Duncan 33b087fc3d
Enforce coding style on language (#967) 2021-05-06 12:50:01 +01:00
Aaron van Geffen 4914cbd2e3
Fix dropdown assertions (#964) 2021-05-05 16:34:32 +02:00
Aaron van Geffen 1726df1351
CMake: Remove reference to non-existing lib directory (#962)
* Remove reference to non-existing lib directory

* Fix clang-specific headers check
2021-05-05 14:32:47 +02:00
Duncan b3bdd47c04
UI Coding Style (#963)
* Enforce coding style and minor Pos2 refactor

* Further cleanup
2021-05-05 13:11:57 +01:00
Duncan 30ff8376f2
Tile Coding Style Enforcement (#961)
* Enforce coding style on tile.h

* Minor refactor and coding style enforce
2021-05-05 10:20:40 +01:00
Aaron van Geffen 0dc126f9d6
Merge pull request #958 from AaronVanGeffen/refactor/game
Move common functions from LoadSaveQuit to new Game file.
Implement Game::{load,save}{Landscape,Scenario,SaveGame}Open functions.
2021-05-03 14:41:28 +02:00
Michał Janiszewski df7f69c25e
Fix interop compilation flags (#960) 2021-05-02 23:18:32 +02:00
Aaron van Geffen fe4fd1be42 Implement Game::{load,save}{Landscape,Scenario,SaveGame}Open functions.
Refactor out common code into openBrowsePrompt function
2021-05-01 19:55:03 +02:00
Aaron van Geffen 36589cef44 Move common functions from LoadSaveQuit to new Game file 2021-05-01 18:25:17 +02:00
Aaron van Geffen b0b6ac8325
Fix element name not showing when inspecting track elements (#957)
Co-authored-by: Duncan <duncans_pumpkin@hotmail.co.uk>
2021-05-01 14:01:34 +02:00
Aaron van Geffen 8d7f1fce54
Name several constants in the construction window (#951)
* Name known RoadStation::ImageIds and TrainStation::ImageIds

* Use PaletteIndex constants

* Reduce whitespace
2021-04-30 09:50:33 +02:00
Aaron van Geffen 86268bd3d2
Implement updateSnowLine (#954)
* Implement initialiseSnowLine and updateSnowLine

* Name ClimateObject variables

* Refactor out common code
2021-04-30 09:15:45 +02:00
Aaron van Geffen a026d75b13
Fix #955: Last landscape path is used as last save game path (#956) 2021-04-30 09:13:26 +02:00
ζeh Matt 8ebeea350b
Use formatter at more places (#952)
* Remove static keyword, this may lock

* Use universal references and cleanup

* Use the argument formatter in GameCommands.cpp

* Use the argument formatter in KeyboardShortcuts.cpp
2021-04-26 20:20:58 +01:00
ζeh Matt 2bfbb3208c
Entity Tweening (#923)
* Add entity tweening

* Add hook to remove entities from tweening

* Rebase

* Decouple window rendering from updating

* Tween only bodies and bogies in the vehicle list

* Adhere code style

* Update CHANGELOG.md

* Move tick interrupt into a function

* Get around null dereference warning
2021-04-26 12:52:32 +01:00
Aaron van Geffen 2613e9b15a
Move all windows into Ui::Windows namespace (#950)
* Move all windows into Ui::Windows namespace

* Remove resolving into Windows:: when not ambiguous

* Resolve Map <> MapWindow ambiguity
2021-04-24 21:29:38 +02:00
Aaron van Geffen 96c87a57b4
Fix #945: Station construction preview image is using wrong colours (#946) 2021-04-24 14:09:40 +02:00
Aaron van Geffen 99dcaa9900
Move economy-related files into Economy folder (#948) 2021-04-24 10:09:14 +02:00
Aaron van Geffen 30c29061a7
Fix panel heights in several windows (#947)
* Fix map window panel height off by two pixels

* Fix tile inspector window panel widget heights being off

* Fix construction window panel widget heights being off

This affected the station, signal, and overhead tabs. The main track/road construction tab was unaffected.
2021-04-24 09:56:23 +02:00
Aaron van Geffen 74b0669d2e
Add polyfill for std::variant and std::visit (#944) 2021-04-23 16:45:09 +02:00
Duncan 61b14fc78a
Replace EntityBase::x,y,z with Map::Pos3 (#876)
* Change entity base to x,y,z

* Simplification

* Fix rebase issues

* Further simplification

* Fix mistake in midpoint calc

* Use less ugly syntax that allows narrowing conversions

* use parameters correctly

* Use correct manhattan calc
2021-04-23 13:03:04 +01:00
Duncan a432bfcc1a
Fix #942: Incorrect upper bound (#943) 2021-04-23 10:21:02 +01:00
Duncan 6b83fa40d9
Implement updateCargoSprite for vehicleBodies (#941) 2021-04-22 15:09:07 +01:00
Duncan 0425ebf524
Vehicle cargo2: sub_4BA7C7 (#918)
* Implement sub4BA7C7

* Refactor adding names

* Further refactor splitting up function

* Adjust name of variable
2021-04-22 13:04:12 +01:00
Duncan 49610c3121
Enforce coding style on gamecommand header (#940) 2021-04-22 09:54:23 +01:00
Peter Gaal 392a81551e
Feature #857: Remember last save directory in configuration variable (#907) 2021-04-22 10:10:04 +02:00
Duncan c8b3232eb4
Enforce coding style on audio headers (#939) 2021-04-21 22:34:41 +01:00
Duncan b85cf7c254
Vehicle Update: Cargo Unload (#846)
* Implement vehicle unload

* Use bounds

* Remove unneeded comments

* Move cargo into seperate struct

This will mean the template function can be removed

* Remove template

* Add station finding helper function

* Fix roadEnd flag mistake

* Use helper function

* Fix clamp

* Copy unused data to hopefully prevent desyncs

* Pass the age variable correctly
2021-04-21 13:17:22 +01:00
Duncan 893f552956
Fix #914. Actually fix correctly this time. (#925)
* Fix #914. Actually fix correctly this time.

Wrong size of height variable caused truncation at high water levels
which caused docks to not be marked as occupied. Which caused a crash
when trying to unoccupy the dock when removing a boat.

* Update changelog
2021-04-20 18:42:13 +01:00
Duncan 061c60d6c7
Fix #931. Odd pathing at certain speeds due to variable overflow (#935)
This was also causing some ship pathing issues
2021-04-19 16:11:37 +01:00
Aaron van Geffen 115a785c55
Vehicle pickup game command (#926)
* Relocate canBeModified and liftUpVehicle to VehicleHead

* Implement vehicle pickup game command

* Use prng for pickup sound frequency to prevent chorus effect
2021-04-19 09:52:01 +02:00
Duncan 8702c86e3e
Fix #932: Incorrect size of array caused overwriting of other memory (#934) 2021-04-19 09:48:40 +02:00
Duncan 9b3b434e31
Fix openloco.vcxproj file (#933) 2021-04-19 09:31:39 +02:00
ζeh Matt 7c0ff0116b
Rename coords types (#929)
* Rename map_pos to Pos2 map_pos3 to Pos3

* Rename TilePos to TilePos2

* Pass Pos3 as const ref in Audio

* Pass Pos3 as const ref in Entity
2021-04-18 20:20:57 +02:00
Aaron van Geffen a1ac80f8d8
Implement cheats window (#910)
* Add skeleton for cheats window

* Implement clearLoan, switchCompany cheats

* Implement tab switching

* Improve company cheats tab layout

* Implement toggle bankruptcy and toggle jail cheats

* Implement acquire company assets cheat

* Implement addCash cheat

* Implement vehicleReliability cheat

* Implement companyRatings cheat

* Move cheats and tile inspector to top of cheats menu

* Amend changelog
2021-04-18 20:12:46 +02:00
Aaron van Geffen 55aff692a2
Cleanup game command expenditure type access (#930)
* Fix typo: getPostion -> getPosition

* Refactor game command expenditure types
2021-04-18 19:07:26 +02:00
Duncan 4b2757bd3b
Implement refresh displayed order labels (#793)
* Implement refresh displayed order labels

* Add casts for null value

* Apply review comments

* Fix address

* Fix rebase issues

* Fix label position
2021-04-18 17:49:16 +01:00
Aaron van Geffen c774323ce3
Fix not all industries showing up in fund industries tab (#927) 2021-04-17 15:35:52 +02:00
Aaron van Geffen 75d95b7917
Merge pull request #908 from duncanspumpkin/strings
Implement a couple string manager functions
2021-04-15 22:45:18 +02:00
duncanspumpkin a92668a329 Add accessors to gamecommand error set 2021-04-15 08:02:31 +01:00
duncanspumpkin aed6fff2a4 Implement a couple string manager functions 2021-04-15 07:40:16 +01:00
Duncan 596f38b7ff
Add base of trig class (#879)
* Add base of trig class

* Use new trig functions

* Actually make compile

* Add notes

* Fis spelling

* Apply review suggestions

* Add further clarification
2021-04-14 21:59:44 +01:00
Duncan f5924dc177
Entity2: Entity manager functions (#920)
* Implement misc entity create

* Implement remaining entity create functions

* Implement zeroUnused

* Fix rebase issues

* Extract common logic into function

* Implement EntityManager::reset

* Implement free entity

* Implement update misc

* Fix ci error

* Add comments and constants

* Nullptr check
2021-04-14 21:47:36 +01:00
Duncan 292e2bf4fc
Move owner and name into entity base to reflect use (#919) 2021-04-14 20:54:53 +01:00
Duncan b20bdd2fef
Implement entity move to list (#906)
* Implement entity move to list

* Add nullptr checks and output warnings

* Add notes and change enum
2021-04-14 20:49:29 +01:00
duncanspumpkin 7e9528eb6c Start v21.04.1+ 2021-04-14 20:27:53 +01:00
duncanspumpkin 753b60ea0f Update OpenLoco version to v21.04.1 2021-04-14 20:07:37 +01:00
Duncan f59a373168
Fix #914. Ships not marking stations as occupied (#917)
This would cause ships to never leave the approaching dock mode
2021-04-13 08:09:08 +01:00
Duncan d668605fda
Fix #915: Money subtractions with large values incorrectly calculated (#916)
Mistake in the currency struct caused large values to be interpreted as negatives
2021-04-12 12:30:26 +02:00
Aaron van Geffen dfdb813f67 Start v21.04+ 2021-04-10 22:32:46 +02:00
Aaron van Geffen 84090672d7 Update OpenLoco version to v21.04 2021-04-10 21:40:06 +02:00
Duncan 4b551cff23
Update changelog for #391 2021-04-10 21:35:24 +02:00
Duncan 329e60f1fb
Stop looking for order station after finding one (#909)
This was causing the incorrect order station to show as where a vehicle was travelling to
2021-04-09 08:39:22 +01:00
Duncan 7b279f3be5
Move label frame out into standalone file (#902) 2021-04-07 21:34:42 +01:00
Aaron van Geffen 999c37e1b6
Use logical string comparison for vehicle list order (#900)
* Use logical string comparison for vehicle list order

* Convert to a string_view version of function

* Implement our own from_chars for unsuported platforms

Co-authored-by: Ted John <ted@brambles.org>
Co-authored-by: duncanspumpkin <duncans_pumpkin@hotmail.co.uk>
2021-04-07 22:24:08 +02:00
Duncan 2bf5d70c78
Enforce coding style for messages (#905)
* Enforce coding style for messages

* Readd c header for size_t
2021-04-07 12:43:02 +01:00
Duncan 4e56aa84c7
Enforce coding style on Station (#903) 2021-04-07 11:00:11 +01:00
Duncan 963cb9d785
Enforce coding style on company names (#901) 2021-04-06 22:12:02 +01:00
Aaron van Geffen dc86bddbf4
Fix vehicle list offsetting rows being off by 2px per row (#899) 2021-04-05 22:03:40 +02:00
Duncan d170de22f0
Fix off-by-one in vehicle list bounds check (#898) 2021-04-05 21:53:04 +02:00
Aaron van Geffen 8b642c07a4
Fix vehicle pickup tool abort event (#897)
The condition is slightly different from an inverted isPlaced.
Regression from #889.
2021-04-05 21:14:40 +02:00
Duncan 2545e8050a
Industry coding style (#896)
* Rename industry to Industry and rework header

* Rename industry_id_t to IndusrtyId_t
2021-04-05 13:14:16 +01:00
Duncan e8f001f3db
Name Vehicle Status fields 2 and 3 (#895) 2021-04-05 12:56:55 +01:00
Duncan 9d180574e5
Town coding style (#893)
* Rename town to Town

* Rename town_id_t to TownId_t

* Rename town_size to TownSize
2021-04-05 12:34:46 +01:00
Peter Gaal 3e23caad85
Fix #884: UTF8 problems with path in Windows (#885) 2021-04-05 11:16:51 +02:00
Duncan e6843199bc
VehicleHead::getStatus (#890)
* Start work on getStatus

* Finished implementing getStatus
2021-04-05 07:48:39 +01:00
Aaron van Geffen c9b5d5148f
Implement the vehicle list window (#878)
* Implementing open function

* Implement widgets, stub events.

* Implement company switching (mouse down, dropdown events)

* Implement update and tooltip events.

* Implement remaining events

* Implement tab and sort mode switching.

* Implement prepareDraw and draw events.

* Implement VehicleList::drawTabs

* Implement refreshVehicleList

* WIP updateVehicleList

* Fix vehicle type checking

* Implement orderByProfit

* Implement orderByAge

* Implement orderByReliability

* Implement onScrollMouseDown

* Implement drawScroll event

* WIP onScrollMouseOver

* Move generateCargoTotalString into VehicleHead

* Finish onScrollMouseOver

* Use VehicleType type in more places.
2021-04-04 20:13:43 +02:00
Duncan ee3172afae
Merge pull request #889 from AaronVanGeffen/cleanup/isplaced
Cleanup isPlaced instances; move generateCargoTotalString
2021-04-04 18:56:31 +01:00
Duncan 6d3d05a3e5
Fix load save window crashing Windows builds (#888)
Sub_441FA7 in a lot of situations will cause a crash this prevents just one of them which is gauranteed to happen. It is quite likely that there are other crashes here that will still happen. Full implementation of all callers and the function will need to be done before we can be confident that there is no sources of crashes here
2021-04-04 18:55:30 +01:00
Aaron van Geffen 73b504d21b
Set regs.ebx for loadSaveQuit and togglePause game commands. (#887)
Fixes #886
2021-04-03 07:37:56 +01:00
Aaron van Geffen a245793271 Move generateCargoTotalString into VehicleHead 2021-04-02 21:29:43 +02:00
Aaron van Geffen 701b18364a
Name Vehicle1 and Vehicle2 fields; minor cleanup (#881)
* Name Vehicle1::dayCreated

* Name Vehicle2::profit array (var_62 to var_6E)

* Replace _tooltipTimeout globals with Input::setTooltipTimeout

* Remove duplicate vehicle status function (sub_4B671C)
2021-04-02 10:17:01 +02:00
Peter Gaal 2876fc6e2f
Fix grammatical error in string 2099 (#882) 2021-04-01 21:52:41 +01:00
Aaron van Geffen d6e13ceed3 Refactor instances of isPlaced into VehicleHead member function 2021-04-01 17:33:02 +02:00
Aaron van Geffen 6ea518d149
Name VehicleHead var_22 (name) and var_44 (ordinalNumber) (#880) 2021-04-01 16:27:56 +02:00
ζeh Matt 6af0b6e6b1
Rename argswrapper to ArgsWrapper and simplify usage (#875) 2021-03-31 23:36:45 +02:00
Aaron van Geffen e665e9b32d
Merge pull request #828 from AaronVanGeffen/gc/loadsavequit
Implement the PromptSave window and the LoadSaveQuit and TogglePause game commands.
2021-03-31 23:29:35 +02:00
Aaron van Geffen 7ca7e1433c Implement GameCommand::pause_game 2021-03-31 23:23:26 +02:00
Aaron van Geffen c010ef6c7d Address reviewer comments 2021-03-31 23:23:26 +02:00
Aaron van Geffen 7552704846 Expose and use CompanyManager::setControllingId 2021-03-31 23:23:26 +02:00
Aaron van Geffen 7dafa196df Implement confirmSaveGame 2021-03-31 23:23:26 +02:00
Aaron van Geffen b0c1ba2fda Implement the loading branch of the LoadSaveQuit command 2021-03-31 23:23:26 +02:00
Aaron van Geffen 810ffbfeeb Implement quitting (networked) games
Add noreturn attribute to exitCleanly to avoid hitting compiler warnings.
Disable MSVC warning 4702 for related reasons.
2021-03-31 23:22:53 +02:00
ζeh Matt fbcaae5f1a
Move GameCommands.h and cpp into GameCommands folder (#877) 2021-03-31 17:17:46 +02:00
Duncan b44c0921f2
Vehicle update 2 Part 4f: Implements landReverseFromSignal and sub_4AA36A (#847)
* Implement landReverseFromSignal

* Implements sub_4AA36A

* Add further comments and label where possible

* Add note
2021-03-31 15:01:51 +01:00
Duncan a7c0800ec2
Add bounded add/subtract to simplify some field updates (#861)
* Add bounded add/subtract to simplify some field updates

* Make bounds STL compatabile

* Add cstdint

* Add compile time checks to bound check
2021-03-31 14:49:43 +01:00
Aaron van Geffen abaee38a8a
Fix route tab not being displayed as active (#873) 2021-03-31 15:48:15 +02:00
Duncan ded2ea28d4
Fix rebase issue in applyPaymentToCompany (#874) 2021-03-31 15:41:09 +02:00
ζeh Matt 92ca3ac490
Minor refactor work in TileManager.cpp (#852) 2021-03-31 14:23:11 +01:00
Duncan 8abc9679b1
Implement applyPaymentToCompany (#867)
* Implement applyPaymentToCompany

* Refactor to remove global variables
2021-03-31 14:10:24 +01:00
Duncan 839d7f34bf
Use a map_pos3 for game command position and add accesors (#872) 2021-03-31 14:08:56 +01:00
Duncan 83779ba719
Move fastSquareRoot to Math folder (#862)
* Move fastSquareRoot to Math folder

* Take on suggestions
2021-03-31 14:08:33 +01:00
Duncan cbfca1475b
Implement create money effect (#869)
* Implement create money effect

* Move where functions are located

* Fix compile issues

* Remove unused using
2021-03-31 13:57:47 +01:00
ζeh Matt 2b87ecbb34
Use correct declaration for company_id_t[2] at 0x00525E3C (#870)
* Use correct declaration for company_id_t[2] at 0x00525E3C

* Add iterator trait info in loco_global_iterator
2021-03-31 13:50:30 +01:00
ζeh Matt f45c89672a
Use std::vector for height map in map generator (#871) 2021-03-31 13:49:42 +01:00
Duncan d710ee10e5
Remove a thing (#868)
* Move things into entities folder

* Rename files to entity

* Make compilable

* Change namespace to EntityManager

* Rename enums and constants

* Rename class to EntityBase

* Rename classes to EntityList...

* Continue rename

* And more renames

* Further enforcing the style

* Rename thing_id_t to EntityId_t

* More minor renames
2021-03-31 06:26:12 +01:00
Aaron van Geffen 19cf4d082a
Merge pull request #831 from AaronVanGeffen/feature/tile-inspector
Implement a basic tile inspector
2021-03-29 17:37:45 +02:00
Duncan 71805f12fa
Rename objects to follow style guide (#866) 2021-03-29 15:11:15 +01:00
Duncan 3b22b194f9
Conform vector file to style guide. Use manhattanDistance function (#864) 2021-03-29 15:10:47 +01:00
Aaron van Geffen b757d72b0e
Implement tutorial footer window (#849) 2021-03-29 12:36:30 +02:00
Duncan 71f538c0bf
Fix capacity string showing incorrect data (#860)
* Fix capacity string showing incorrect data

If a vehicle does not carry any cargo (e.g. is a train engine) the previous items capacity string (or anything else) would be shown as the capcity. This is because a global variable is being used that has not been correctly intialised.

* Add changelog entry
2021-03-29 11:26:17 +01:00
ζeh Matt 4d1a26937d
Remove assert from ZoomLevel, value is used for other things (#863) 2021-03-29 11:29:05 +02:00
Aaron van Geffen 920ffa942a
Implement title sequence (#805)
* Implement title sequence update function

* Integrate title sequence using std::variant and std::visit

Co-authored-by: Marijn van der Werf <marijn.vanderwerf@gmail.com>
Co-authored-by: ζeh Matt <m.moninger.h@gmail.com>
2021-03-28 13:14:41 +02:00
ζeh Matt 7ae3a54a34
Use map_pos3 over Loc16 (#810)
* Use map_pos3 where appropriate

* Remove commented declarations

* Remove unused loc16 type
2021-03-28 07:22:47 +01:00
Aaron van Geffen c49e71d4da
Merge pull request #853 from ZehMatt/refactor/variable-frame-rate
Implement variable frame updating
2021-03-28 00:52:41 +01:00
Aaron van Geffen fea95bf09d Change layout of the Option window's display tab; amend changelog 2021-03-28 00:42:09 +01:00
Aaron van Geffen 8f26e263a8
Add nuget.config (#855)
https://github.com/actions/virtual-environments/issues/3038
2021-03-27 15:27:17 +00:00
Aaron van Geffen 86de7364ac
Implement an optional FPS counter (#850)
Shows a counter at the top of the screen, indicating the number of frames drawn per second, based on std::chrono.
2021-03-27 01:06:24 +01:00
Aaron van Geffen 6d5b481b61
Implement PromptBrowseWindow::handleInput (#841)
Fixes #804

* Implement PromptBrowseWindow::handleInput

* Refactor PromptBrowseWindow to use TextInput::InputSession
2021-03-27 00:29:08 +01:00
Aaron van Geffen 1fbbad01b0
Split TextInput and TextInputWindow (#843)
* Rename Ui::TextInput namespace to Ui::Windows::TextInput

* Move input handling code into new Ui:TextInput namespace

* Use actual text input widget definition to compute text offset

InputSession::calculateTextOffset is using its own (integrated) variables,
instead of the loco_global ones still used by PromptBrowseWindow. We therefore
temporarily revert to using the original routine in PromptBrowseWindow,
until the latter has been refactored to use an InputSession.
2021-03-26 21:47:19 +01:00
ζeh Matt c6aa340fb8
Refactor some includes (#851)
* Move currency related types into a new header

* Remove unused include to Vector.hpp

* Move the location specific types into a new header and use TVector

* Create a ZoomLevel specific type and move it into a new header
2021-03-26 19:57:27 +00:00
Aaron van Geffen 4f1d6320b2
Implement progress bar window (#848)
* Name screen modes 4 (progressBarActive) and 5 (initialised)

* Implement the internal progress bar window

* Integrate _isInternalWindow
2021-03-26 19:52:26 +01:00
ζeh Matt 7b23ab4da9
Avoid converting map to tile pos when passing tile pos (#830) 2021-03-26 15:22:42 +00:00
Michał Janiszewski 86f104d41e
Use __has_include for <optional> detection (#815)
* Use __has_include for <optional> detection

* Update src/OpenLoco/Core/Optional.hpp

Co-authored-by: Aaron van Geffen <aaron@aaronweb.net>
2021-03-24 07:31:11 +00:00
Duncan f7be97293f
Vehicle Update 2 Part 3b: sub_4A8F22 and isOnExpectedRoadOrTrack (#845)
* Implement sub_4AA464

* Implement isOnExpectedRoadOrTrack
2021-03-23 15:44:29 +00:00
Duncan 138e57e5b3
Fix monthly town update (#844)
* Fix monthly town update

* Add changelog entry
2021-03-22 20:03:47 +00:00
Duncan db898ca8ac
Implements sub_4A8C81 part of manual vehicle control (#837) 2021-03-22 16:40:58 +00:00
Duncan 3205138374
Implements sub_4A8CB6 (#836) 2021-03-22 16:34:49 +00:00
Duncan c646e8a708
Implement landTryBeginUnloading and landLoadingUpdate (#840) 2021-03-22 16:23:58 +00:00
Duncan a75b041a8e
Vehicle Update 2 Part 3a: Start the regular movement code (#834)
* Start the regular movement code

* Split up function further

* Add constants and swap some statements to simplfy

* Fix splleing
2021-03-22 13:49:59 +00:00
Duncan 35fcae711c
Vehicle Update 2 Part 2e: Implement airportGetApronTransitionTarget (#835)
* Implement airportGetApronTransitionTarget

* Rename and reorg functions to use MovementEdge and Node
2021-03-22 13:49:03 +00:00
Aaron van Geffen d831911c4f
Fix #838: Escape key doesn't work in confirmation windows (#839) 2021-03-19 22:57:43 +01:00
Aaron van Geffen 58f70d6942
Use fs::path::replace_extension (#814) 2021-03-18 20:14:36 +01:00
Duncan c8f412feb3
Vehicle Update2 Part 2d: Implement airportGetNextApronArea (#826)
* Implement airportGetNextApronArea

* Fix sign issues
2021-03-18 16:08:46 +00:00
Duncan 7839ebc37a
Vehicle Update2 Part 2c: Implements produceTouchdownAirportSound (#822)
* Implements produceLeavingAirportSound

Very similar to leaving dock sound...

* update name of function

* Fix sound index max
2021-03-18 15:30:18 +00:00
Duncan 4c55ac8af9
Implement cargo distribution (#833) 2021-03-18 15:09:31 +01:00
Duncan a9f48c83d0
Vehicle Update 2 Part 3: Implement updateLand (#832)
* Implement updateLand

* Remove additional nesting
2021-03-18 13:00:23 +00:00
Duncan 0bc0d2a0f2
Implements sub_427122 (#821) 2021-03-18 12:26:41 +00:00
Duncan 3247589ec0
Implements movePlaneTo (#827) 2021-03-18 12:26:06 +00:00
Aaron van Geffen 3e2d49a2d0 Allow viewing raw tile element data from tile inspector; tweak layout. 2021-03-14 22:46:45 +01:00
Aaron van Geffen de1f9e9142 Add support for groupbox widget 2021-03-14 22:18:36 +01:00
Matt 6d97f455be Add "Uncap FPS" option to the options widget 2021-03-27 02:25:29 +02:00
Matt ce79c56a50 Implement variable frame updating 2021-03-27 01:37:01 +02:00
ζeh Matt 223048ac51
Use simplified vector implementations with resolution translation (#809)
* Use simplified vector implementations with resolution translation

* Add compile time check of vector size

* Fix z not being passed for audio

* Apply refactor suggestions

* Narrow conversation from int to T in Vector

* Remove unused operators

* Update CHANGELOG.md

* Fix rebase
2021-03-11 18:43:44 +00:00
Aaron van Geffen b95eb5d78b Show selected coordinates along with stepper widgets for adjustments. 2021-03-11 18:39:30 +01:00
Aaron van Geffen b8d6549084 Use object names for elements listed in the tile inspector. 2021-03-11 17:54:25 +01:00
Aaron van Geffen 7c5863c29b Add initial version of the tile inspector. 2021-03-11 16:29:47 +01:00
Aaron van Geffen 7ab2849696 Move some map selection functions into TileManager. 2021-03-11 14:43:25 +01:00
Aaron van Geffen f41d043d99 Introduce GameException to interrupt game ticks
This is not as clean as we'd like, but will do for now.

Co-authored-by: Matt <m.moninger.h@gmail.com>
2021-03-09 10:06:08 +01:00
Aaron van Geffen 43dfb5e2a0 Implement returnToTitle 2021-03-09 00:26:36 +01:00
Aaron van Geffen e4121f9e8b Implement PromptSaveWindow; beginnings of LoadSaveQuit game command 2021-03-08 21:51:59 +01:00
Aaron van Geffen ea680e574c Refactor paused state setting to use GameCommand 2021-03-08 21:24:13 +01:00
Aaron van Geffen adadb1879a
Merge pull request #825 from AaronVanGeffen/gc/rename-town
Implement the RenameTown game command
2021-03-08 17:49:39 +01:00
Aaron van Geffen 10692610b9
Implement the RenameIndustry game command (#824) 2021-03-08 17:35:44 +01:00
Aaron van Geffen af53b4b20b Fix bug in TownWindow::renameTownPrompt 2021-03-08 17:15:10 +01:00
Aaron van Geffen 3b15883b91 Implement the RenameTown game command
Name create_industry and create_town commands as a bonus
2021-03-08 13:48:04 +01:00
Duncan 2963fd4475
Implements airplaneGetNewStatus, airplaneApproachTarget and sub_4A9348 (#820)
These parts were removed from the main updateAir to reduce the amount of code whilst searching for elusive bugs. The bugs were subsequently squashed and these were found to be all okay.
2021-03-07 17:31:25 +00:00
Duncan d4e72ec04e
Vehicle Update2 Part 2: Implements updateAir (#790)
* Implements updateAir

Signed-off-by: duncanspumpkin <duncans_pumpkin@hotmail.co.uk>

* Stub out airplaneGetNewStatus

* Stub out airplaneApproachTarget

* Stub out sub_4A9348

* Correct graphical bug

* Fix weight calc

* Stub out normal airplane movement code

* Reintroduce removed code
2021-03-07 16:35:09 +00:00
Aaron van Geffen 58edc20a3d Start v21.03+ 2021-03-07 17:27:10 +01:00
Aaron van Geffen 70745c3328
Fix compilation on macOS 10.13 (#819) 2021-03-06 22:23:09 +01:00
Aaron van Geffen 59fa24ca1d Update OpenLoco version to v21.03 2021-03-06 20:02:29 +01:00
Ted John 0853232e19
Fix #802: Scenario save is failing (#818) 2021-03-06 16:52:57 +01:00
Aaron van Geffen 7b8b845f6b
Fix return value for S5::save hook (#812) 2021-03-06 15:12:14 +00:00
Aaron van Geffen 3b9a6e6bf7
Merge pull request #362 from AaronVanGeffen/inflation-subs 2021-03-06 15:49:00 +01:00
Duncan b1371d6ace
Move next position calc to function for airplane code (#817) 2021-03-06 14:41:38 +00:00
Aaron van Geffen 84adf81a5b
Merge pull request #796 from AaronVanGeffen/sandbox-mode 2021-03-06 10:19:29 +01:00
YetiWiz d05b492f83
Disable scenery interaction when hidden and update flag names (#787) 2021-03-01 21:13:41 +01:00
Duncan 67f2c05e0a
Fix mistake in rebase and orderskip (#811) 2021-03-01 21:07:47 +01:00
Aaron van Geffen b3a979db46 Split off logic to commandRequiresUnpausingGame 2021-03-01 18:54:56 +01:00
ζeh Matt a44a9e3d89
Use std::pair for pair in Audio code (#808) 2021-03-01 15:38:16 +00:00
Aaron van Geffen 5c90cdb535
Centralise ownership of gameSpeed variable in OpenLoco.cpp (#806) 2021-02-28 22:47:02 +01:00
Duncan f5fd3c3819
Vehicle update2 Part 1a: Water motion update (#758)
* Implement updateWaterMotion

* Prevent npe's

* Actually use the defined flags

* use surface helper function

* Fix speed bug and rebase issues

* Fix calc

* Fix crash

* Split off move

* Fix targeting of vehicle

* Use correct acceleration and fix rebase issues

* Use reference as we are modifying the tile
2021-02-28 21:46:08 +00:00
Duncan 7518897238
Fix speed based vehicle animations such as hydrofoils (#807)
When the vehicle reaches its top speed an invalid value for the animation would be set.
Mistake was made during implementation.
2021-02-28 22:01:23 +01:00
Duncan b3d68cf491
Vehicle update2 Part 1b: Order functions (#747)
* Implement checkIfAtOrderStation

* Implement advanceNextRoutableOrder

* Allow conversion to station base

* Switch to a cyclic order table

* Keep ci's happy

* Definitions on the move again
2021-02-28 17:50:28 +00:00
Peter Gaal c9f144414b
Implement 0x004B6572 - vehicle rename game command (#792)
* Implement 0x004B6572 - vehicle rename game command

* Rename variables and add lookup table

* Add default string ID map depending on vehicle type

Also use string_id types for string ID variables

* Use 2 arrays and copy data with memcpy to string

Also get rid of memory bound array (the memory location is not accessed in different function)

* Make renameStringBuffer and stringFormatBuffer local

* Rename stringFormatBuffer to existingVehicleName

* Removed address comments, replaced 0 with StringIds::empty

Also added documentation on a function with explanation of registers.

* Add documentation for return value

Co-authored-by: Duncan <duncans_pumpkin@hotmail.co.uk>
2021-02-28 17:48:27 +00:00
Duncan ba54e2fbca
Implement buildDeliveredCargoPaymentsTable (#733) 2021-02-28 17:47:34 +00:00
Duncan 36ada16694
Implement Order Skip game command (#786)
* Implement Order Skip game command

* Make review suggestions

* Use ringview
2021-02-28 17:44:08 +00:00
Duncan 2350fb9aac
Vehicle update2 Part 1c: Sound producing functions (#750)
* Implement sub_42843E

* Implement tryCreateInitialMovementSound

* Label flags

* Hard code mask to satisfy compilers

* Fix rebase issues and ci issues
2021-02-28 17:29:29 +00:00
TELK 2331b5cb7e
ko-KR: Update translations for v21.02 (#803) 2021-02-28 15:17:17 +01:00
Aaron van Geffen 411d5d8a7d
Append trailing slash to initial save path (#801) 2021-02-28 14:08:10 +01:00
Duncan ad70c289a8
Fix waypoint setting function (#797)
* Fix waypoint setting function

Calculation was incorrectly adding the offset to the first tile instead of removeing the offset from the current tile. This meant that it would set the waypoint at the wrong location

* Add changelog entry

Co-authored-by: Aaron van Geffen <aaron@aaronweb.net>

* Don't overwrite direction on waypoint

Co-authored-by: Aaron van Geffen <aaron@aaronweb.net>
2021-02-28 12:19:02 +00:00
Aaron van Geffen f9791aa82e
Fix StationManager::zeroUnused (#800)
The station manager is using 0xFFFF (StringIds::null) to check where to allocate
the next station. However, when saving, these are set to 0x0 (StringIds::empty)
instead. The default station name is already StringIds::null, so removing
the extra assignment fixes the issue.
2021-02-27 19:06:17 +01:00
Aaron van Geffen 8d65a8749a Option to allow building while paused. 2021-02-27 14:55:50 +01:00
Aaron van Geffen ceb74c0b87
More compact syntax for game command table (#783)
* Merge function pointer and paused state override tables into one array of definitions.

* Add placeholders enum items for unknown game commands; correct mistakes in existing enum entries.

* Apply GameCommand enum consistently in all game command functions.
2021-02-27 14:45:43 +01:00
Aaron van Geffen 7301077b3e Make cheats/debugging menu optional. 2021-02-27 11:39:18 +01:00
Aaron van Geffen fe60b59d0d Add a basic cheats menu to allow toggling screen modes. 2021-02-27 11:13:02 +01:00
Aaron van Geffen aff8b2dfe6 Minor refactor to use EditorController 2021-02-27 10:02:30 +01:00
Duncan 80f57924b1
Merge pull request #697 from IntelOrca/implement/save
Implement saving of S5 files
2021-02-27 07:22:56 +00:00
Aaron van Geffen 40b97c7670 Add sandbox mode to the game.
This allows editor commands to be used in-game.
2021-02-26 18:34:36 +01:00
Aaron van Geffen 461647ebd7
Fix #794: Game does not stay paused while in construction mode (#795) 2021-02-26 16:39:23 +01:00
Ted John 66ccc387db Fix packing of objects in saved games 2021-02-24 20:15:40 +00:00
Aaron van Geffen 93a348dd34 Minor cleanup; amend changelog 2021-02-24 19:00:07 +01:00
duncanspumpkin 6150ecd87d Simplify and fix inflation calc 2021-02-24 17:26:26 +00:00
Aaron van Geffen d19059bf01
Merge pull request #791 from AaronVanGeffen/initialise-date 2021-02-24 14:49:53 +01:00
Aaron van Geffen c51192a455 Replace dayCount initialisation with a more readable loop.
Co-authored-by: Marijn van der Werf <marijn.vanderwerf@gmail.com>
2021-02-24 12:26:08 +01:00
Ted John a02b431874 Apply code suggestions 2021-02-23 19:50:20 +00:00
Duncan 9128b897f5
Vehicle update2 Part 1d: Vehicle Driving Sounds Update (#751)
* Implement train sound update

* Update names of functions. Fix logical error

* Use speed functions where available

* Add const to const functions

* Rename structs to make it clearer
2021-02-23 16:40:53 +00:00
Aaron van Geffen c273913ca8 Implement Scenario::initialiseDate 2021-02-23 14:59:49 +01:00
Ted John a6ec81ea89 Fix rebase errors 2021-02-22 23:37:33 +00:00
Ted John ca991b2da0 Fix build error 2021-02-22 23:37:33 +00:00
Ted John b533ee2ab0 Implement map reorganise 2021-02-22 23:37:33 +00:00
Ted John 77b46cb1d7 Remove ghost tile elements when saving 2021-02-22 23:37:33 +00:00
Ted John 76439332cd Rename SType and SFlags 2021-02-22 23:37:33 +00:00
Ted John ccb87b13bb Use game state for generating save details 2021-02-22 23:37:32 +00:00
Ted John d7248868a1 Rename members of SawyerEncoding 2021-02-22 23:37:32 +00:00
Ted John 9ac134a24b Refactor S5 to save a struct 2021-02-22 23:37:32 +00:00
Ted John 1b068db716 Implement packing of objects in S5 2021-02-22 23:37:32 +00:00
Ted John f70cba24cd Refactor ObjectHeader 2021-02-22 23:37:32 +00:00
Ted John 489bb7683c Implement prepareSaveDetails 2021-02-22 23:37:30 +00:00
Ted John 4c5c3de677 Implement save 2021-02-22 23:37:08 +00:00
Duncan f5f75f8210
Change OrderTableView to OrderRingView (#754) 2021-02-22 16:45:15 +00:00
Duncan 9b0b14467e
Merge pull request #763 from duncanspumpkin/speed2
Add types for speeds
2021-02-22 13:21:23 +00:00
Marijn van der Werf 57fd312765
Implement editor bottom toolbar (#575)
* Disassemble editor step buttons

* Add missing switch case

* Rename strings

* Add EditorController.{cpp,h} to .vcxproj

* Clean up constants used

* Rename Step enum members

* Name image constants used

* Make requested changes

Co-authored-by: Aaron van Geffen <aaron@aaronweb.net>
2021-02-21 21:09:51 +01:00
Duncan 69b5e7f207
Fix #294. Crash when setting company name twice (#785)
By not indicating that an input buffer was const, a sub function accidentally wrote to the input buffer and overwrote the memory. Has been addressed by applying const to the input buffer, and fixed by using the correct buffer in the sub function.
2021-02-21 11:13:47 +01:00
ζeh Matt 7042e9a6ac
Add .editorconfig file (#777) 2021-02-20 19:51:53 +01:00
ζeh Matt 596804a75a
Implement Scenario::reset (#776) 2021-02-20 19:49:29 +01:00
duncanspumpkin a5ad3dde0c Add even more functions 2021-02-20 18:12:23 +00:00
duncanspumpkin 3cc862b0ad Move to Speed.hpp add additional functions 2021-02-20 18:02:23 +00:00
duncanspumpkin 7fd45795a7 Rework to use SpeedTemplate 2021-02-20 17:22:08 +00:00
Aaron van Geffen b53bfdbab7 Start v21.02+ 2021-02-20 17:20:34 +01:00
Aaron van Geffen 3605a287da Update OpenLoco version to v21.02 2021-02-20 16:50:07 +01:00
Aaron van Geffen b8174c700f
Fix viewport interaction around station and town labels (#782) 2021-02-20 16:45:45 +01:00
Aaron van Geffen a8ed7b65bd
Fix #765: Landscape loading/saving broken (#780) 2021-02-20 12:37:46 +01:00
Duncan e769ab749a
Merge pull request #779 from AaronVanGeffen/fix/town-list
Fix: Town list could show the wrong number of stations
2021-02-19 22:09:00 +00:00
Aaron van Geffen 7c10816dbe Amend changelog for #779 2021-02-19 22:52:14 +01:00
Aaron van Geffen 6553872549 Fix: Town list could show the wrong number of stations.
An int16 was being pushed to the format arguments, rather than an int32.
2021-02-19 22:48:44 +01:00
Duncan 9b88734361
Merge pull request #778 from duncanspumpkin/height
Move getHeight back into tileManager
2021-02-19 21:29:47 +00:00
Duncan d45977a8e0
Fix #772: Cloned vehicle are missing their orders (#775)
Offset for game command 35 is relative to the start of the vehicles orders not relative to the start of the order table
2021-02-19 21:05:38 +01:00
duncanspumpkin e95ea114e8 Move getHeight back into tileManager 2021-02-19 13:07:30 +00:00
Duncan 475478c4e0
Merge pull request #774 from ZehMatt/fix-audio-crash
Fix crash trying to play vehicle audio
2021-02-19 08:50:07 +00:00
Matt ea5da89344
Fix crash trying to play vehicle audio 2021-02-19 03:36:56 +02:00
Marijn van der Werf 173bab52de
Fix compilation of PaintMiscEntity on older macOS versions (#773) 2021-02-18 23:55:13 +01:00
Duncan 71fc5057a5
Remove duplicated get height and move to Tile member (#768)
* Remove duplicated getHeight function and move to Tile member

* Add helper function for getHeight

* Use truncated tile coord to prevent errors
2021-02-18 14:09:00 +01:00
Duncan cb191561b4
Fix #721: Incorrect catchment area for airports (#771)
Mistake made in implementation. Min/Max are signed and are in TilePos coordinates not MapPos coordinates.
2021-02-18 09:53:23 +01:00
Duncan d0560264c2
Fix #769: Road waypoints cannot be set (#770) 2021-02-18 09:53:10 +01:00
Aaron van Geffen f4c77663b8
Dedicated buttons for local/express mode (#762)
* Vehicle window: add buttons for express/local mode.

* Add toggle functionality to new local/express buttons.

* Remove local/express from route list.

* Drop dashes from local/express buttons.
2021-02-17 17:46:30 +01:00
Aaron van Geffen 913d420356
Feature: allow main viewport to follow an entity (#760)
* Implement basic viewport focus on entities

* Unfocus main view when centring it on a tile or entity.

* Vehicle window: change centre viewport control into dropdown
2021-02-17 17:45:58 +01:00
Aaron van Geffen 2d57920631
Merge pull request #748 from duncanspumpkin/cloneVehicle
Add ability to clone a vehicle
2021-02-17 14:35:05 +01:00
Aaron van Geffen 64f2ccc36e Add changelog entry 2021-02-17 14:29:32 +01:00
Aaron van Geffen 873a20669a
Merge pull request #702 from IntelOrca/implement/mapgen
Implement some of the map generator
2021-02-17 14:09:50 +01:00
Aaron van Geffen 72e93d0c29
Fix #766: Performance index is off by a factor of 10 (#767)
The performance index was off by a factor of 10 in scenario options window.
2021-02-17 10:12:37 +01:00
duncanspumpkin af3588ef6b Move clone vehicle code to game command 2021-02-17 07:53:25 +00:00
duncanspumpkin e9aa64527a Integrate _gameCommandCanBeUsedWhenPaused 2021-02-17 07:52:25 +00:00
Duncan 295b516a4a
Merge pull request #764 from duncanspumpkin/gamecommand
Add game commands table and remove vehicle create hook
2021-02-16 19:56:50 +00:00
duncanspumpkin 8b8fe71c88 Add offsets to table 2021-02-16 12:36:00 +00:00
duncanspumpkin 20bcd4df23 Add game commands table and remove vehicle create hook 2021-02-16 07:56:16 +00:00
Aaron van Geffen 61e11fdc4f Hook map generator for scenario use; changelog entry 2021-02-15 20:49:22 +01:00
Aaron van Geffen ed6056d3fd Implement MapGenerator::generateWater 2021-02-15 20:35:38 +01:00
duncanspumpkin 341c30aebe Add types for speeds 2021-02-15 17:37:19 +00:00
Ted John d8a4458283 Allow selection of land generator 2021-02-15 18:07:46 +01:00
Ted John 3855dee811 Create macro for dropdown widgets 2021-02-15 18:07:46 +01:00
Ted John ffbb700baa Use more settings for new terrain generator 2021-02-15 18:07:46 +01:00
Ted John 9acb72e085 Refactor map generating 2021-02-15 18:07:45 +01:00
Ted John 62da8ad4c1 Implement map generation and play with simplex 2021-02-15 18:07:45 +01:00
duncanspumpkin c5849c08d9 Check total cost before cloning vehicle 2021-02-15 07:58:31 +00:00
Duncan 0ee80c5d2a
Fix #744: Z-fighting issue caused by unhappy renderer (#761) 2021-02-13 20:56:30 +01:00
Aaron van Geffen daaf16b0b8 Move clone functionality to dropdown 2021-02-13 19:25:55 +01:00
Duncan 2f22e1c4a5
Fix station struct offset comment (#759) 2021-02-13 10:43:45 +01:00
Duncan cec3274640
Merge pull request #738 from duncanspumpkin/vehicle_update2
Vehicle update2 Part 1: Water Update
2021-02-12 18:37:55 +00:00
duncanspumpkin cc00654c8a Invert logic to allow other update functions to operate 2021-02-12 16:39:49 +00:00
duncanspumpkin 10543435dd Implement waterUpdate 2021-02-12 16:00:51 +00:00
duncanspumpkin 40633cc455 Implement initial section of headUpdate 2021-02-12 15:57:19 +00:00
Duncan 64478b4295
Make calculation of yaw from vectors a free function (#757)
These functions are used in a number of locations and they should not be part of the vehicle body
2021-02-12 15:57:01 +00:00
Peter Gaal 2e2c4147f7
Solve arithmetic overflow and logic operator warnings (#756) 2021-02-12 15:29:05 +00:00
Peter Gaal 218e4d728f
Fix #737: Use _mph literals instead of numeric constants (#755)
* Fix #737: Use _mph literals instead of numeric constants

Replace numeric constants using literals with _mph defined in Types.hpp
All numeric constants are added to static_assert to check the validity of conversion.

* Remove extra static asserts
2021-02-12 15:27:31 +00:00
Aaron van Geffen c6b1f9c0e4
Implement ToolbarTop::Game::prepareSaveGame (#752) 2021-02-12 13:05:11 +01:00
Peter Gaal 0443a33bf7
Refactor ColourScheme - move to Types.hpp (#753) 2021-02-11 17:56:22 +00:00
Peter Gaal adc0450e3c
Implement 0x00440325 - paintMiscEntity (#719)
* Implement 0x00440325 - paintMiscEntity

Implement cases 0-2

* Implement cases 3 to 8 in the paint function

* Implemented review comments round 1

* paintMiscEntity moved to separate file paintMiscEntity.cpp/h

* Add and use recolour2 function (doesn't compile now)

* Fixed compilation error

* Each case split into a separate function

* Fixed typo in paintExhaustEntity

* Fix formatting problems during conflict resolution

* Remove memory address comments on implemented lines

* Make typed parameters in each drawing function

* Use image ids instead of numeric constant with explosion cloud

* Check boundaries with std::array and assert

* Remove unnecessary condition in assert

* Use image ids for explosionSmoke

* Use image ids for fireball

* Use image ids for smoke

* Use image ids for spark

* Remove spark frames which are probably part of different animations

* Renamed spark to splash and add comments to particle types

* Fix errors with renamed MiscBase struct types after merging

* Replace calculation of vehicle crash with map and array constants

* Remove unnecessary CompanyManager.h header

* Implement wiggleYOffsets (0x004FAAC8) as a C++ array

* Added addresses of paint functions

Note: in the original code it is a branch of cases and not real functions

* Fix exhaust problem - wrong numbers which didn't match assembly code

* Fix condition in exhaust painting code
2021-02-11 17:31:50 +00:00
Aaron van Geffen 3e5acf7430
Fix #694: Selecting a song is guaranteed to not play that song (#705)
* Handle manually selected songs correctly.

* Set _lastSong correctly.

* Name _lastSong and _songProgress globals in Options window.
2021-02-10 23:56:44 +01:00
Aaron van Geffen 9003696e73
Fix compilation on Arch Linux (#749)
Commit 963203743 (#711) changed Numeric.hpp to include <limits> instead of <limits.h>.
This had the side-effect of not PATH_MAX no longer being defined on (Arch) Linux.
2021-02-10 15:32:27 +01:00
duncanspumpkin a2a414c5d6 Add ability to clone a vehicle 2021-02-10 12:13:50 +00:00
Marijn van der Werf e086988be0
Merge pull request #746 from marijnvdwerf/21-02/fix-macos-build
Fix compilation on macOS 10.13
2021-02-09 22:17:42 +01:00
Duncan ba7b9efd2d
Add OrderTableView::atOffset function (#745) 2021-02-09 22:17:31 +01:00
Marijn van der Werf 4ff3e86143 Fix compilation on macOS 10.13 2021-02-09 22:06:04 +01:00
Aaron van Geffen 6cc76039e1
Fix translucent vehicle ghosts when white selected (#743) 2021-02-09 20:57:29 +01:00
Aaron van Geffen 9a30d8e6d5
Merge pull request #711 from IntelOrca/implement/sawyer-coding
Implement the sawyer encoder / decoder
2021-02-09 19:01:37 +01:00
Duncan 1756518b0a
Change the mph literal to allow for 5 decimal places of accuracy (#742)
This fixes a potential future bug with 0.33333_mph
2021-02-09 13:35:18 +00:00
Duncan 53215fcb4f
Implement applyBreakdownToTrain (#735)
* Implement applyBreakdownToTrain

* Add offset address

* Name remaing flags

* Fix rebase issues
2021-02-09 12:06:05 +00:00
Duncan 80fb580ec0
Rework Order Table (#734)
* Add basic orders file

* Implement an order table view and use for orderDelete

* Further work to use order table view

* Further refactoring

* Implement a clone order function

* Finish cloning and add raw order output

* Simplfy code

* Introduce some helper functions

* Further refactoring

* Reduce code duplication

* Fix ci warning

* Add headers

* Rename data to data

* Add further ci fix

* static cast to reduce msvc warnings

* Output end of arguments list

* Set waypoints correctly

* Switch around toolSet success state

* Take into account end of orders when computing size

* Make row hover highlighting work for end of orders

* Fix rebase issues

* Rename namespace to Vehicles

* Move orders to Vehicles folder
2021-02-09 07:45:47 +00:00
Duncan c9d217433a
Merge pull request #740 from duncanspumpkin/reorder
Reorg Vehicles
2021-02-08 21:18:13 +00:00
duncanspumpkin 2cd00005a3 Prune some header includes 2021-02-08 17:20:27 +00:00
duncanspumpkin d6fd2ccbaf Rename vehicle_26 to Vehicle2or6 2021-02-08 17:12:26 +00:00
duncanspumpkin 07f5272cb2 Rename vehicle_2 to Vehicle2 2021-02-08 17:10:03 +00:00
duncanspumpkin bde1b7781f Rename vehicle_1 to Vehicle1 2021-02-08 17:06:43 +00:00
duncanspumpkin 25ef0e70ef Rename vehicle_base to VehicleBase 2021-02-08 17:04:36 +00:00
duncanspumpkin 35194d3ee0 Rename vehicle_body to VehicleBody 2021-02-08 17:02:09 +00:00
duncanspumpkin b395294ea1 Rename vehicle_bogie to VehicleBogie 2021-02-08 16:59:44 +00:00
duncanspumpkin 74ad424104 Rename vehicle_tail to VehicleTail 2021-02-08 16:58:29 +00:00
duncanspumpkin 8b1a097418 Rename vehicle_head to VehicleHead 2021-02-08 16:57:03 +00:00
duncanspumpkin 1e8e3ae973 Move vehicle to vehicle.cpp and rename VehicleCommon 2021-02-08 16:50:33 +00:00
duncanspumpkin 3d68d72fab Further remove uses of struct vehicle 2021-02-08 16:36:12 +00:00
duncanspumpkin 7ad6c61c8d Reduce usage of struct vehicle 2021-02-08 12:41:25 +00:00
duncanspumpkin 6fc63d9039 Move to namespace Vehicles 2021-02-08 12:28:07 +00:00
duncanspumpkin 9516b0f112 Move vehicles into seperate folder 2021-02-08 11:57:47 +00:00
Duncan e8fdae3786
Introduce _mph literal (#736) 2021-02-06 19:01:17 +01:00
Aaron van Geffen 4739db6159
Update OpenLoco Dependencies bundle version to 1.3.0 (#731) 2021-02-06 18:20:50 +01:00
Duncan b5356a4b10
Implement setLegendHover for main company list tabs (#732) 2021-02-06 18:18:25 +01:00
Duncan 8f3edd0006
Paintsetup 7: Vehicle Body (#718)
* Implements vehicle body paint

* Rework brakingImage code
2021-02-03 18:08:04 +00:00
Duncan ee21e784a6
Start implementing viewport interaction item_left (#670)
* Start implementing viewport interaction item_left

* Implement missing functions and hook together remaining code

* Fix headquarters crash

* Refactor to keep ci's happy

* Fix stackpointer mistake

* Rename variable to prevent clash

* Rename functions

* Move map tooltip args to format args file

* Update comments

* Fix mistake with track interaction

* Revert accidental renames

* Fix interaction issues

Co-authored-by: Marijn van der Werf <marijn.vanderwerf@gmail.com>
2021-02-02 19:13:09 +00:00
Aaron van Geffen 94aba0a016
Fix #727: Autosave frequency dropdown glitch (#728)
The value being read is 32-bits ("Every {INT32} months").
However, only 16-bits was being pushed to the dropdown arguments.
Thus, this was relying on 'clean' argument memory.
2021-01-31 22:54:33 +01:00
Aaron van Geffen 7a537915e1
Fix #725: Company value graph does not display correctly (#726)
The wrong data type size was being passed to the graphing routine. The value being passed was 4 bytes (32 bits) instead of 6 bytes (currency48_t).

Renaming `_dword_113DD0C` to `_graphDataTypeSize` makes it obvious what's wrong.
2021-01-30 22:18:47 +01:00
Duncan 1908b6d022
Implement setLegendHover for cargo payment rates (#724) 2021-01-30 19:12:44 +01:00
Duncan b02a324e12
Paintsetup 6: Vehicle Bogie (#710)
* Start implementing paintVehicleEntity

* Finish implementing bogie paint setup

* Update names of pitch variables

* Make ghost a function

* Set interaction type for ghosts
2021-01-30 18:41:19 +01:00
Duncan d1f0f0b24c
Formatter refactor (#662)
* Add parameter pack constructor

* Use new formatter constructor in CompanyWindow

* Specify the formatter explicitly
2021-01-20 17:36:28 +00:00
Duncan 293ec5bc4b
Implement enqueueKey and getNextKey (#717)
* Implement enqueueKey and getNextKey

* Implement review suggestions
2021-01-20 12:54:05 +00:00
Ted John 1508ace99b Remove explicit type on constructor 2021-01-18 18:55:45 +00:00
Duncan 07274cfc37
Paintsetup 5: Implement paintEntities2 (flying and floating) (#700)
* Implement paintEntities2

* Use template for filter to allow for better optimisation
2021-01-15 09:59:40 +00:00
Duncan 9fd780a1f5
Implement viewport render (#714) 2021-01-13 17:15:48 +00:00
Duncan 53050d2290
Paintsetup 4: spriteInteractWith (#687)
* Start implementing sprite interact with

* Further work

* Get somewhat working

* Fix formatting

* Fix narrowing conversion

* Remove unused globals
2021-01-12 19:29:33 +00:00
Duncan b66f81cc24
Implements ScrollView::getPart (#688) 2021-01-12 19:26:37 +00:00
Aaron van Geffen 9d7de77427
Update issue templates (#716)
* Update issue templates

* Remove obsolete feature template
2021-01-11 07:56:51 +00:00
Ted John 9632037436 Implement encoding / decoding of S5 files 2021-01-08 11:06:10 +00:00
Ted John fae40bc22b Add polyfill for std::span
We can replace stdx::span with std::span when we move to C++20.
2021-01-08 11:06:10 +00:00
Ted John 57e4018671
Fix #712: Load / save window tries to show preview for item after last (#713) 2021-01-08 00:32:39 +00:00
Ted John 1646f637c9
Merge pull request #690 from IntelOrca/feature/autosave
Implement #39: Auto save
2021-01-07 12:06:58 +00:00
Ted John 8d641cb8f2
Rename trackUpgrade to isNetworkHost (#698) 2021-01-07 12:06:15 +00:00
Ted John 145774be18 Auto create save directory and reduce warnings 2021-01-06 01:15:49 +00:00
Ted John c652fa97c2 Use constants for file extensions 2021-01-06 01:15:49 +00:00
Ted John 94b1b3d924 Use upper case for SV5 extension
Co-authored-by: Aaron van Geffen <aaron@aaronweb.net>
2021-01-06 01:15:49 +00:00
Ted John 28a0f2c942 Apply suggestions from code review
Co-authored-by: Aaron van Geffen <aaron@aaronweb.net>
2021-01-06 01:15:44 +00:00
Ted John 9f790371fe Improve cleaning of autosaves 2021-01-06 01:15:21 +00:00
Ted John 21b1f9a3eb Implement cleaning of autosaves 2021-01-06 01:15:17 +00:00
Ted John 01d4b5b552 Implement working autosaving 2021-01-06 01:14:10 +00:00
Ted John dfecf42cb4 Prepare autosave stub 2021-01-06 01:14:10 +00:00
Ted John 49865e1495 Add UI and config for autosave 2021-01-06 01:14:10 +00:00
Ted John 0e271b13ea
Fix viewport panning in tutorials (#709) 2021-01-04 13:02:48 +00:00
Aaron van Geffen e7a0eae66e
Remove Travis CI (#704) 2021-01-04 00:12:08 +01:00
Duncan cc4964a2cc
Update changelog for #691 (#707) 2021-01-03 19:35:21 +01:00
Duncan ee151a2781
Move tooltip to use event list (#652)
* Move tooltip to use event list

* Pass arguments back via FormatArgs

* Reorg to allow for disabling of tooltips

* Remove unnecessary brace-initialisers.

Co-authored-by: Aaron van Geffen <aaron@aaronweb.net>
2021-01-03 16:58:19 +00:00
Duncan 582d866daf
Implements Audio::setBgmVolume (#689) 2021-01-03 16:35:55 +00:00
Aaron van Geffen 90cbea50ae
Rename the Error window file (#706)
* Rename error.cpp to Error.cpp

* Remove unused widgets global
2021-01-03 14:16:24 +01:00
Aaron van Geffen 3e36285045
Merge pull request #410 from Gymnasiast/desktop-file
Add .desktop file for Linux, and add CMake definitions for installing it, along with the game icon files.
2021-01-03 13:06:06 +01:00
Aaron van Geffen 93ec0aa2ef Install icon resources through CMake as well 2021-01-03 12:55:30 +01:00
Michael Steenbeek c0a64265e6 Add .desktop file for Linux 2021-01-03 12:52:58 +01:00
Duncan 1e0901f920
Implement arrangeStructs (#686)
Heavily based on OpenRCT2 equivalent
2021-01-03 07:44:02 +00:00
Ted John e06e013c6b
Fix #151: Mouse moves out of window when looking around (#703)
Do not use window messages for viewport drag. Instead use getCursorPos and calculate delta from that. This stops the code from processing the reverse movement messages.
2021-01-02 21:40:36 +01:00
Aaron van Geffen 8c7208782b
Migrate Linux CI jobs to GitHub Actions (#701)
* Add Linux CI jobs to GitHub Actions.

* Remove Linux jobs from Travis CI matrix.

* Require clang-format job to pass before running other CI jobs.

* Merge Ubuntu jobs by using a strategy matrix.

* Mark paint{Vehicle,Misc}Entity functions static.
2021-01-02 19:06:33 +01:00
Duncan 7f4e01ab51
Fix display issue (#699) 2021-01-02 13:31:25 +00:00
Duncan fbb028061f
Paintsetup 2: entityPaintSetup (#685)
* Add new paint entity file and move functions

* Start looking at function

* Further work on entityPaintSetup

* Make review comments
2021-01-01 20:17:19 +00:00
Duncan 95c2386940
Implement PaintSession::generate (#681)
* Implement PaintSession::generate

* Apply review comments

* Templatise the rotation to improve code performance

* Add hooks and fix issues at zoom

* Make review changes

* Split up templated code

* Add comment
2020-12-31 13:07:06 +00:00
Duncan d93bdb9cd2
Fix start stop highlighted selection (#695) 2020-12-31 06:50:06 +00:00
Marijn van der Werf cf6888e0e3
Clean up shortcuts (#693)
* Remove reduntant hook

* Integrate simple shortcuts
2020-12-30 15:14:07 +00:00
Duncan 852d0029ae
Fix orders related crashes and quirks (#692)
Mistake made in implementation caused orders to be inserted in the wrong locations in the order table. This could cause unknown side effects or a crash
2020-12-30 11:58:14 +00:00
Duncan 4b79f67e98
Fix #679: Crash when changing ground texture (#691)
Mistake made in implementation. The dropdown index is not gauranteed to be the same as the texture index therefore it would select invalid objects
2020-12-30 09:41:30 +00:00
Duncan 277908e92e
Add ThingTileList iterator (#684) 2020-12-29 21:29:33 +00:00
Duncan 0d83cf1c13
Get map pos 2 (#669)
* Implement station interaction

* Fix function

* Implement town name interaction info

* Fix town interaction

* Make review suggestions

* Make review changes
2020-12-29 17:26:35 +00:00
Duncan c4d6d5087e
Reorg thing2 (#683)
* Simplify code that doesn't need to be complex anymore

* Reorg ThingList iterator to be more generic

* Throw on nullptr due to bad head

* Move more to .cpp file. Use runtime_error. Fix bug in car code

* Alternative layout
2020-12-29 16:11:13 +00:00
Duncan ae28b292df
Reorg thing so that base has access to more (#682)
* Reorg thing so that base has access to more

* Make same changes to misc entities

* Replace uses of Thing with thing_base

* Make review changes

* Overload get/setSubType
2020-12-26 10:53:42 +00:00
Marijn van der Werf 5e596588cf
Implement construction rotate shortcut (#678)
* Disassemble construction rotate shortcut

* Clean up rotation shortcut code

* Make requested changes
2020-12-23 20:49:55 +00:00
Duncan dd3174ea8c
Vehicle window (#464)
* start vehicle_window

Further work

Implement vehicle cargo tab preparation

Continue with cargo tab

Finish vehicle create

* Continue work on cargo tab

Start implementing main_prepareDraw

Further standardise code

Implement tab switching

Make code runnable

work on main tab

Implement prepare_draw for main tab

Implement main tab drawing

Work on tab rendering

Make compilable

* Implement viewport create

Implement tab repositioning and fix resizeing

Further work on viewport

Start implementing other tabs

Work on finance tab rendering

Implement main mouse up functions

Apply hack to fix bug

Implement mouse down

Update widx name

* Implement drawScroll for vehicle::cargo

Implement start of generateCargoTotalString

Finish generateCargoTotalString

Implement dropdown refit selection

Implement start of scrollHover

Implement more of scroll mouse over

Continue tooltip info

Finish implementing cargo tooltip formatting

Minor cleanup

Implement route draw

Implement route prepareDraw

Implement route drawScroll

Update names and implement more simple route functions

Implement further functions

Implement order delete

Implement orderMovement

Update with vehicle iter

* Implement tool cancel. Reorg event15

Implement event15

Start onMouseDown event

Implement createOrderDropdown

Start onDropdown

Implement further order insertion

Finish dropdown event

Start scrollMouseDown event

Implement order copying

Fix display issue

Continue scroll mouse down

Finish scroll mouse down

* Start implementing onToolDown

Start track tool interaction

Finish tool down

* Review of finances tab

Fix financial window drawing. Continue draw function

Complete finance paint function

* Implement details resize

implement details prepareDraw

Fix bug in display

Finish vehicle details draw

start drawScroll

Fix rebase mistake

Implement scroll draw and get size

Implement mouse up, cursor and tooltip

Implement scrollMouseOver for vehicle details

Implement scroll mouse down and rearrange tool funcs

Implement speed control and reverse

* Fix duplicated function

Fix pickup and implement dropdown

* Implement getNumUnitsForCargo

Implement vehicle rename

Implement tool abort

Implement onPickup

Add missing number circles

Cleanup getCarFromScrollView

* Implement dragVehiclePart open

Implement further drag vehicle window functions

Further experimentation

Make scroll hot areas work

Make scrollDragEnd work correctly

* Refactor top of file

Further refactoring and cleanup

Common up functions where appropriate

Further refactoring and cleanup

Further cleanup

Keep ci's happy

Rejig to fix xcode

Remove helpful code to support ci's

Fix animation of main tab

Name offsets

Add more xcode braces

Fix final xcode issue

Name unk widget

Rename namespaces to conform to coding style

* Remove whitespaces and reorg where appropriate

* Name globals

* Add comments and end lines

* Name flags and refactor keycommands

* Add offset to widx

* Move location of getVehicleType function

* Move break to inside of scope

* Correct typo of command

* Make suggested changes

* Rename cargo object var_12

* Fix incorrect type

Co-authored-by: Marijn van der Werf <marijn.vanderwerf@gmail.com>
2020-12-22 20:14:45 +00:00
Marijn van der Werf 977c1e3114
Merge pull request #676 from marijnvdwerf/20-11/fix-news-shortcut
Fix message window not closing when using shortcut
2020-12-09 21:34:11 +01:00
Marijn van der Werf f1aeef2685 Update changelog 2020-12-09 20:35:57 +01:00
Marijn van der Werf 4178cbeddb Move close call to NewsWindow 2020-12-09 20:20:19 +01:00
Marijn van der Werf 95f9f17664 Fix message window not closing when using shortcut 2020-11-14 20:17:35 +01:00
Aaron van Geffen eb39c1a7f8
Merge pull request #671 from AaronVanGeffen/ccache
Add CMake option to build with CCache
2020-10-25 22:06:12 +01:00
Aaron van Geffen 81cf28d864
Implement events and widgets for PromptOkCancelWindow (#672) 2020-10-25 17:11:27 +01:00
Aaron van Geffen ea1215cd0d Start v20.10+ [ci skip] 2020-10-25 17:10:44 +01:00
Aaron van Geffen 854a8267a3 Remove old reference to Boost 2020-10-25 11:17:02 +01:00
Aaron van Geffen a3edba7481 Don't use CCache on Travis CI 2020-10-25 11:16:28 +01:00
ceeac 6bc1794660 Add CMake option to build with CCache
This speeds up CMake recompilation significantly. The default behaviour is
to search for CCache and use it if available; this can be disabled explicitly
by setting OPENLOCO_USE_CCACHE=OFF in CMake.

Ported from OpenRCT2.
2020-10-25 11:15:05 +01:00
Aaron van Geffen 0b0cdb8dda Change minimum year to 1800; hook existing functions. 2019-06-11 19:57:18 +02:00
Aaron van Geffen ba1339616c Start implementing inflation-related functions. 2019-06-11 11:55:38 +02:00
373 changed files with 54049 additions and 16079 deletions

12
.editorconfig Normal file
View File

@ -0,0 +1,12 @@
root = true
[*]
indent_style = space
indent_size = 4
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
[*.txt]
trim_trailing_whitespace = false

View File

@ -1,23 +0,0 @@
<!--
Please fill out the form below by replacing the placeholders.
Delete any headings and placeholders that you do not fill out.
-->
**OS:** e.g. Windows 10
**Version:** e.g. 20.10
**Commit/Build:** e.g. e6e665a
<!-- Explanation of the issue -->
**Steps to reproduce:**
1.
2.
**Is the issue reproducible in Locomotion (vanilla)?**
<!-- Yes / No, and to what extent? -->
**Screenshots / Video:**
<!-- Drag & drop screenshots here. You can use e.g. YouTube to upload video. -->
**Save game:**
<!-- Change the file extension to .txt or package to a .zip so that it can be drag & dropped here... -->

32
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,32 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Version information:**
- Platform: [e.g. Windows 10]
- Version: [e.g. 21.08]
- Build/commit: [e.g. 9c24c21]
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behaviour:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behaviour**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.

View File

@ -0,0 +1,17 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -1,8 +1,8 @@
name: CI
on: [push, pull_request]
on: [push, pull_request, workflow_dispatch]
env:
OPENLOCO_BUILD_SERVER: GitHub
OPENLOCO_VERSION: 20.10
OPENLOCO_VERSION: 21.08
jobs:
check-code-formatting:
name: Check code formatting
@ -17,17 +17,19 @@ jobs:
windows:
name: Windows
runs-on: windows-latest
needs: check-code-formatting
env:
CONFIGURATION: Release
POWERSHELL_TELEMETRY_OPTOUT: 1
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Build OpenRCT2
shell: pwsh
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Build OpenLoco
run: |
$ErrorView = 'NormalView'
$installPath = &"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -version 16.0 -property installationpath
$instanceId = &"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -version 16.0 -property instanceid
$installPath = vswhere -latest -property installationpath
$instanceId = vswhere -latest -property instanceid
Import-Module "$installPath\Common7\Tools\Microsoft.VisualStudio.DevShell.dll"
Enter-VsDevShell $instanceId
if (-not $env:GITHUB_REF.StartsWith("refs/tags/"))
@ -38,21 +40,89 @@ jobs:
$env:OPENLOCO_SHA1_SHORT=$env:GITHUB_SHA.Substring(0, 7)
$env:GIT_DESCRIBE = (git describe HEAD | sed -E "s/-g.+$//")
Write-Host "%GIT_DESCRIBE% = $env:GIT_DESCRIBE"
msbuild openloco.sln /t:restore
msbuild openloco.sln
- name: Build artefacts
shell: pwsh
msbuild openloco.sln -m -t:restore
msbuild openloco.sln -m
- name: Build artifacts
run: |
$ErrorView = 'NormalView'
New-Item -ItemType Directory artefacts | Out-Null
Copy-Item CHANGELOG.md,CONTRIBUTORS.md,LICENSE,bin\*.dll artefacts
Copy-Item data\language -Destination artefacts\data\language -Recurse
Copy-Item loco.exe artefacts\openloco.exe
Push-Location artefacts
7z a -tzip -mx9 -mtc=off -r openloco-v${env:OPENLOCO_VERSION}-win32.zip *
Pop-Location
- name: Upload artefacts (CI)
uses: actions/upload-artifact@v2-preview
mkdir artifacts | Out-Null
Copy-Item CHANGELOG.md,CONTRIBUTORS.md,LICENSE,loco.exe,bin\*.dll,bin\*.pdb artifacts
Copy-Item data\language artifacts\data\language -Recurse
Rename-Item artifacts\loco.exe openloco.exe
- name: Upload artifacts
uses: actions/upload-artifact@v2
with:
name: "OpenLoco-Windows-Win32"
path: artefacts/openloco-*-win32.zip
name: OpenLoco-${{ runner.os }}-Win32
path: artifacts
if-no-files-found: error
ubuntu:
name: Ubuntu i686
runs-on: ubuntu-latest
needs: check-code-formatting
container: openloco/openloco:ubuntu-i686
strategy:
fail-fast: false
matrix:
compiler: [g++, clang++]
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Build OpenLoco
run: |
mkdir build
cd build
cmake .. -G Ninja -DCMAKE_CXX_COMPILER=${{ matrix.compiler }} -DCMAKE_BUILD_TYPE=release -DOPENLOCO_USE_CCACHE=OFF -DSDL2_DIR=/usr/lib/i386-linux-gnu/cmake/SDL2 -DSDL2_MIXER_PATH=/usr/lib/i386-linux-gnu -Dyaml-cpp_DIR=/usr/lib/i386-linux-gnu/cmake/yaml-cpp -DPNG_LIBRARY=/usr/lib/i386-linux-gnu/libpng16.so -DPNG_PNG_INCLUDE_DIR=/usr/include -DZLIB_LIBRARY=/usr/lib/i386-linux-gnu/libz.so
ninja -k0
fedora:
name: Fedora i686 MinGW32
runs-on: ubuntu-latest
needs: check-code-formatting
container: openloco/openloco:fedora-mingw32
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Build OpenLoco
run: |
cmake -B build -G Ninja -DCMAKE_TOOLCHAIN_FILE=../CMakeLists_mingw.txt -DCMAKE_BUILD_TYPE=release -DOPENLOCO_USE_CCACHE=OFF -DSDL2_DIR=/usr/i686-w64-mingw32/sys-root/mingw/lib/cmake/SDL2/ -DSDL2_MIXER_PATH=/usr/i686-w64-mingw32/sys-root/mingw/ -Dyaml-cpp_DIR=/usr/i686-w64-mingw32/sys-root/mingw/CMake/ -DPNG_LIBRARY=/usr/i686-w64-mingw32/sys-root/mingw/bin/libpng16-16.dll -DPNG_PNG_INCLUDE_DIR=/usr/i686-w64-mingw32/sys-root/mingw/include
cd build
ninja -k0
mac:
name: macOS i686 osxcross
runs-on: ubuntu-latest
needs: check-code-formatting
container: openloco/osxcross:latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Install dependencies
env:
dependency_ver: 1.3.0
run: |
ln -s /usr/osxcross/SDK/MacOSX10.13.sdk/System /System
curl -LfO "https://github.com/OpenLoco/Dependencies/releases/download/v${dependency_ver}/openloco.dependencies.macos.${dependency_ver}.zip"
unzip openloco.dependencies.macos.${dependency_ver}.zip -d vcpkg
- name: Build
env:
OSXCROSS_HOST: i386-apple-darwin17
TOOLCHAIN1: ${{ github.workspace }}/osxcross/tools/toolchain.cmake
TOOLCHAIN2: ${{ github.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake
MACOSX_DEPLOYMENT_TARGET: 10.13
run: |
/usr/osxcross/bin/i386-apple-darwin17-osxcross-conf
eval $(/usr/osxcross/bin/i386-apple-darwin17-osxcross-conf)
mkdir build
cd build
export LD_LIBRARY_PATH=/usr/osxcross/lib:$LD_LIBRARY_PATH
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/osxcross_toolchain.cmake -DVCPKG_TARGET_TRIPLET=x86-osx
make -j$(getconf _NPROCESSORS_ONLN)
tar -cvzf ../openloco.tar.gz openloco.app
- name: Archive production artifacts
uses: actions/upload-artifact@v2
with:
name: OpenLoco-macOS
path: openloco.tar.gz
if-no-files-found: error

View File

@ -1,63 +0,0 @@
language: c
before_install:
- docker pull openloco/openloco:$DOCKERIMG
install:
- git config remote.origin.fetch +refs/heads/*:refs/remotes/origin/*
- git config remote.origin.fetch +refs/tags/*:refs/tags/*
- git fetch --tags
sudo: required
dist: trusty
matrix:
include:
- os: linux
name: Ubuntu i686 GCC
services:
- docker
env:
- OPENLOCO_CMAKE_OPTS="-G Ninja -DCMAKE_BUILD_TYPE=release -DSDL2_DIR=/usr/lib/i386-linux-gnu/cmake/SDL2 -DSDL2_MIXER_PATH=/usr/lib/i386-linux-gnu -Dyaml-cpp_DIR=/usr/lib/i386-linux-gnu/cmake/yaml-cpp -DPNG_LIBRARY=/usr/lib/i386-linux-gnu/libpng16.so -DPNG_PNG_INCLUDE_DIR=/usr/include -DZLIB_LIBRARY=/usr/lib/i386-linux-gnu/libz.so"
- DOCKERIMG=ubuntu-i686
- os: linux
name: Ubuntu i686 Clang
services:
- docker
env:
- OPENLOCO_CMAKE_OPTS="-G Ninja -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=release -DSDL2_DIR=/usr/lib/i386-linux-gnu/cmake/SDL2 -DSDL2_MIXER_PATH=/usr/lib/i386-linux-gnu -Dyaml-cpp_DIR=/usr/lib/i386-linux-gnu/cmake/yaml-cpp -DPNG_LIBRARY=/usr/lib/i386-linux-gnu/libpng16.so -DPNG_PNG_INCLUDE_DIR=/usr/include -DZLIB_LIBRARY=/usr/lib/i386-linux-gnu/libz.so"
- DOCKERIMG=ubuntu-i686
- os: linux
name: Fedora i686 MinGW32
services:
- docker
env:
- OPENLOCO_CMAKE_OPTS="-G Ninja -DCMAKE_TOOLCHAIN_FILE=../CMakeLists_mingw.txt -DCMAKE_BUILD_TYPE=release -DBOOST_ROOT=/usr/i686-w64-mingw32/sys-root/mingw/ -DSDL2_DIR=/usr/i686-w64-mingw32/sys-root/mingw/lib/cmake/SDL2/ -DSDL2_MIXER_PATH=/usr/i686-w64-mingw32/sys-root/mingw/ -Dyaml-cpp_DIR=/usr/i686-w64-mingw32/sys-root/mingw/CMake/ -DPNG_LIBRARY=/usr/i686-w64-mingw32/sys-root/mingw/bin/libpng16-16.dll -DPNG_PNG_INCLUDE_DIR=/usr/i686-w64-mingw32/sys-root/mingw/include"
- DOCKERIMG=fedora-mingw32
- os: osx
name: macOS 10.13 Clang (Xcode 9.3)
osx_image: xcode9.3 # macOS 10.13
language: cpp
before_install:
- export HOMEBREW_NO_AUTO_UPDATE=1
script:
- curl -L https://github.com/OpenLoco/Dependencies/releases/download/v1.2.0/openloco.dependencies.macos.1.2.0.zip -o dependencies.zip
- unzip -q dependencies.zip -d vcpkg/
- mkdir build && cd build
- cmake .. "-DCMAKE_TOOLCHAIN_FILE=../vcpkg/scripts/buildsystems/vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=x86-osx
- make -j2
- zip -r openloco-macos.zip openloco.app
script:
- mkdir build
- docker run -v $(pwd):/openloco -w /openloco/build -i -t openloco/openloco:$DOCKERIMG bash -c "cmake ../ ${OPENLOCO_CMAKE_OPTS} && ninja -k0"
deploy:
provider: releases
api_key:
secure: Kuzqa3+lCSDyGu3HE4k/fRmqoBTs6DYiVnO7olAnvaqJxzE+BNAlgPbZmO+mw83xJY7u6mKrrM2y7chzbsmUqVFgRig9OC2K7aCVTRkxiXwDFGjksedXnYc35kGE+p9wv7sk8JLcoqjrqkhpLAqjdgsT8V+VvlSVWjp4J0DYbR7M4COBSMGxdvmeQwG6VrXjdRy90c4FEffLuWG79J879hqYVkXNW4GnYan6YW7sX/YkUmXTbbrT618Whb90jZwu1njn+qTWRyIb2EQaPdAhjGBBDt9QIhauv6AkyZOgL9C79ltizr1l24pnYexcQVv7QZ5ipBPk4weAzbSeJRaMS15qG7w+qOwwlduBB8YHO3DKKlyvAlXtVdeOj4LeaTIGctGKIO/2320rZeIzAIA59uyveKVyIqJpYlay5AMdoNvy//a4+huSdu9gHnNZJOACMRKUvRW+7T3clW8rBw4d7aO9siabps45Usbu+U1s/XKxVWMR65fXhxQwVh4u+NACUMK5/01L7SNKztw/x3eKf15ekxu+I2yBmWQRNWk0TRl4MV+qHt09hcJUU3rThaeJfHAM+/asgVDUn1e5rzktSGw8xxi1G3vjsBEAKts27+3AdDQRWVWPS9zfr+FNAG9W1C56uDQsnEMHbBXOLQ0Q7b7jxqdnS4kuZIjYPugkYTw=
file: build/openloco-macos.zip
skip_cleanup: true
on:
tags: true
repo: OpenLoco/OpenLoco

View File

@ -1,3 +1,95 @@
21.08+ (???)
------------------------------------------------------------------------
- Fix: [#1108] Road selection not being remembered.
- Fix: [#1124] Confirmation prompt captions are not rendered correctly.
- Change: [#1104] Exceptions now trigger a message box popup, instead of only being written to the console.
21.08 (2021-08-12)
------------------------------------------------------------------------
- Fix: [#366] Original Bug. People and mail cargo incorrectly delivered to far away stations.
- Fix: [#1035] Incorrect colour selection when building buildings.
- Fix: [#1070] Crash when naming stations after exhausting natural names.
- Fix: [#1094] Repeated clicking on construction window not always working.
- Fix: [#1095] Individual expenses are drawn in red, not just the expenditure sums.
- Fix: [#1102] Invalid file error when clicking empty space in file browser.
- Change: [#298] Planting clusters of trees now costs money and influences ratings outside of editor mode.
- Change: [#1079] Allow rotating buildings in town list by keyboard shortcut.
21.07 (2021-07-18)
------------------------------------------------------------------------
- Feature: [#856] Allow filtering the vehicle list by station or cargo type.
- Fix: [#982] Incorrect rating calculation for cargo causing penalty for fast vehicles.
- Fix: [#984] Unable to reset/regenerate station names by using an empty name.
- Fix: [#1008] Inability to decrease max altitude for trees in landscape editor.
- Fix: [#1016] Incorrect detection of station causing incorrect smoke sounds.
- Fix: [#1044] Incorrect rotation of headquarters when placing. No scaffolding when placing headquarters.
- Technical: [#986] Stack misalignment in GCC builds caused unexplained crashes on Linux and Mac during interop hooks with loco.exe.
- Technical: [#993] Retry hook installation to fix incompatibles with older wine versions.
- Technical: [#1006] Add breakpad-based dumping for MSVC builds
21.05 (2021-05-11)
------------------------------------------------------------------------
- Feature: [#184] Implement cheats window with financial, company, vehicle, and town cheats.
- Feature: [#857] Remember last save directory in configuration variable.
- Feature: [#923] Tween (linear interpolate) entities when frame limiter is uncapped for smoother movement.
- Fix: [#914] Boats get stuck in approaching dock mode when water is above a certain height. This was incorrectly fixed in 21.04.1.
- Fix: [#923] Decouple viewport updates from game ticks for smoother panning and zooming.
- Fix: [#927] Some available industries are missing in the 'Fund new industries' tab.
- Fix: [#945] Station construction preview image is using wrong colours.
- Fix: [#957] Element name is not shown when inspecting track elements using the Tile Inspector.
- Change: [#975] The multiplayer toggle button on the title screen has been hidden, as multiplayer has not been reimplemented yet.
21.04.1 (2021-04-14)
------------------------------------------------------------------------
- Fix: [#914] Boats get stuck in approaching dock mode when water is above a certain height.
- Fix: [#915] Money subtractions with large values incorrectly calculated causing negative money.
21.04 (2021-04-10)
------------------------------------------------------------------------
- Feature: [#451] Optionally show an FPS counter at the top of the screen.
- Feature: [#831] Add a tile inspector, allowing inspection of tile element data (read-only).
- Feature: [#853] Allow unlocking FPS by detaching game logic from rendering.
- Fix: [#391] Access violation in windows after exiting games.
- Fix: [#804] Enter key not confirming save prompt.
- Fix: [#809] Audio calculation not using the z axis.
- Fix: [#825] Potential crash when opening town rename prompt.
- Fix: [#838] Escape key doesn't work in confirmation windows.
- Fix: [#845] Town growth incorrectly calculated causing more aggressive growth than should be possible.
- Fix: [#853] The game run slightly, but noticeably, slower than vanilla Locomotion.
- Fix: [#860] Incorrect capacity information for vehicles that do not carry cargo (e.g. is a train engine).
21.03 (2021-03-06)
------------------------------------------------------------------------
- Feature: [#125] Allow construction while paused using a new optional cheats/debugging menu.
- Feature: [#796] Allow users to toggle sandbox mode in-game using the cheats menu.
- Fix: [#294] Crash when setting company name twice.
- Fix: [#697] Ghost elements are not removed in autosaves.
- Fix: [#794] Game does not stay paused while in construction mode.
- Fix: [#798] Setting waypoints on multitile track/road elements corrupts the position.
- Fix: [#801] Initial save path does not contain a trailing slash.
- Fix: [#807] Incorrect vehicle animation for speed based animations like hydrofoils when at max speed.
- Change: [#361] The game now allows scenarios to start from 1800, with adjusted inflation.
- Change: [#787] Scenery and building interaction is now disabled when see-through.
21.02 (2021-02-20)
------------------------------------------------------------------------
- Feature: [#122] Allow vehicles to be cloned from the vehicle window.
- Feature: [#690] Automatically save the game at regular intervals.
- Feature: [#702] Optional new map generator (experimental).
- Fix: [#151] Mouse moves out of window when looking around.
- Fix: [#588] 'Cancel or Show Last Announcement' shortcut doesn't close announcements.
- Fix: [#679] Crash when changing ground texture.
- Fix: [#694] Selecting a song to play is guaranteed to not play it.
- Fix: [#712] Load / save window tries to show preview for item after last.
- Fix: [#721] Incorrect catchment area for airports.
- Fix: [#725] Company value graph does not display correctly.
- Fix: [#744] Rendering issues ('Z-fighting') with vehicles over bridges.
- Fix: [#766] Performance index is off by a factor of 10 in scenario options window.
- Fix: [#769] Waypoints for road vehicles could not be set.
- Fix: [#779] Town list displays the wrong amount of stations.
- Change: [#690] Default saved game directory is now in OpenLoco user directory.
- Change: [#762] The vehicle window now uses buttons for local/express mode.
20.10 (2020-10-25)
------------------------------------------------------------------------
- Feature: [#569] Option/cheat to disable AI companies entirely.

View File

@ -3,6 +3,21 @@ cmake_policy(VERSION 3.9)
set (PROJECT openloco)
# Note: Searching for CCache must be before project() so project() can use CCache too
# if it is available
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
find_package(CCache)
if (CCache_FOUND)
option(OPENLOCO_USE_CCACHE "Use CCache to improve recompilation speed (optional)" ON)
if (OPENLOCO_USE_CCACHE)
# Use e.g. "ccache clang++" instead of "clang++"
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCache_EXECUTABLE}")
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CCache_EXECUTABLE}")
endif (OPENLOCO_USE_CCACHE)
endif (CCache_FOUND)
project(${PROJECT} CXX)
if (APPLE)
@ -148,16 +163,13 @@ set(DEBUG_LEVEL 0 CACHE STRING "Select debug level for compilation. Use value in
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DDEBUG=${DEBUG_LEVEL}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDEBUG=${DEBUG_LEVEL}")
# include lib
include_directories("lib/")
# add source files
file(GLOB_RECURSE OLOCO_SOURCES "src/*.cpp")
file(GLOB_RECURSE OLOCO_HEADERS "src/*.h" "src/*.hpp")
if (APPLE)
file(GLOB_RECURSE OLOCO_MM_SOURCES "src/*.mm")
set_source_files_properties(${OLOCO_MM_SOURCES} PROPERTIES COMPILE_FLAGS "-x objective-c++ -fmodules")
set_source_files_properties(${OLOCO_MM_SOURCES} PROPERTIES COMPILE_FLAGS "-x objective-c++")
endif ()
set(PIE_FLAG "-fno-pie")
@ -205,17 +217,12 @@ find_package(SDL2_mixer REQUIRED)
find_package(PNG REQUIRED)
# The hint provided here is targetting Arch Linux, a distro of choice for many contributors
if ("${CMAKE_SYSTEM_NAME}" MATCHES "(Free|Net|Open|DragonFly)BSD")
find_package(yaml-cpp REQUIRED)
include_directories(${YAML_CPP_INCLUDE_DIR})
else()
find_package(yaml-cpp REQUIRED HINTS /usr/lib32/cmake/yaml-cpp)
endif()
find_package(yaml-cpp REQUIRED HINTS /usr/lib32/cmake/yaml-cpp)
include_directories(${YAML_CPP_INCLUDE_DIR})
# Disable optimizations for interop.cpp for all compilers, to allow optimized
# builds without need for -fno-omit-frame-pointer
set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/src/openloco/interop/interop.cpp" PROPERTIES COMPILE_FLAGS -fno-omit-frame-pointer)
set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/src/OpenLoco/Interop/Interop.cpp" PROPERTIES COMPILE_FLAGS "-fno-omit-frame-pointer -O0")
if (NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 17)
@ -268,26 +275,30 @@ if (APPLE)
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${OPENLOCO_VERSION_TAG} ${OPENLOCO_BRANCH}")
endif()
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/distribution/macos/AppIcon.iconset DESTINATION ${CMAKE_BINARY_DIR})
set(ICON_TARGET "${CMAKE_BINARY_DIR}/AppIcon.iconset")
set(ICON_OUTPUT "${CMAKE_BINARY_DIR}/AppIcon.icns")
set(SOURCE_ICON_DIR "${CMAKE_CURRENT_SOURCE_DIR}/resources/logo")
set(BUNDLE_RESOURCES "")
add_custom_command(OUTPUT ${ICON_OUTPUT}
COMMAND cp icon_x16.png ${ICON_TARGET}/icon_16x16.png
COMMAND cp icon_x32.png ${ICON_TARGET}/icon_16x16@2x.png
COMMAND cp icon_x32.png ${ICON_TARGET}/icon_32x32.png
COMMAND cp icon_x64.png ${ICON_TARGET}/icon_32x32@2x.png
COMMAND cp icon_x128.png ${ICON_TARGET}/icon_128x128.png
COMMAND cp icon_x256.png ${ICON_TARGET}/icon_128x128@2x.png
COMMAND cp icon_x256.png ${ICON_TARGET}/icon_256x256.png
COMMAND cp icon_x512.png ${ICON_TARGET}/icon_256x256@2x.png
COMMAND cp icon_x512.png ${ICON_TARGET}/icon_512x512.png
COMMAND cp icon_x1024.png ${ICON_TARGET}/icon_512x512@2x.png
COMMAND iconutil -c icns ${ICON_TARGET}
WORKING_DIRECTORY ${SOURCE_ICON_DIR})
find_program(ICONUTIL iconutil)
if (ICONUTIL)
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/distribution/macos/AppIcon.iconset DESTINATION ${CMAKE_BINARY_DIR})
set(ICON_TARGET "${CMAKE_BINARY_DIR}/AppIcon.iconset")
set(ICON_OUTPUT "${CMAKE_BINARY_DIR}/AppIcon.icns")
set(SOURCE_ICON_DIR "${CMAKE_CURRENT_SOURCE_DIR}/resources/logo")
add_custom_command(OUTPUT ${ICON_OUTPUT}
COMMAND cp icon_x16.png ${ICON_TARGET}/icon_16x16.png
COMMAND cp icon_x32.png ${ICON_TARGET}/icon_16x16@2x.png
COMMAND cp icon_x32.png ${ICON_TARGET}/icon_32x32.png
COMMAND cp icon_x64.png ${ICON_TARGET}/icon_32x32@2x.png
COMMAND cp icon_x128.png ${ICON_TARGET}/icon_128x128.png
COMMAND cp icon_x256.png ${ICON_TARGET}/icon_128x128@2x.png
COMMAND cp icon_x256.png ${ICON_TARGET}/icon_256x256.png
COMMAND cp icon_x512.png ${ICON_TARGET}/icon_256x256@2x.png
COMMAND cp icon_x512.png ${ICON_TARGET}/icon_512x512.png
COMMAND cp icon_x1024.png ${ICON_TARGET}/icon_512x512@2x.png
COMMAND ${ICONUTIL} --convert icns --output ${ICON_OUTPUT} ${ICON_TARGET}
WORKING_DIRECTORY ${SOURCE_ICON_DIR})
list(APPEND BUNDLE_RESOURCES ${ICON_OUTPUT})
endif ()
set(BUNDLE_RESOURCES ${ICON_OUTPUT})
list(APPEND BUNDLE_RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/CHANGELOG.md)
list(APPEND BUNDLE_RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/CONTRIBUTORS.MD)
set_target_properties(${PROJECT} PROPERTIES RESOURCE "${BUNDLE_RESOURCES}")
@ -310,9 +321,16 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
set_target_properties(${PROJECT}-headers-check PROPERTIES LINKER_LANGUAGE CXX)
set_source_files_properties(${OLOCO_HEADERS} PROPERTIES LANGUAGE CXX)
add_definitions("-x c++ -Wno-pragma-once-outside-header -Wno-unused-const-variable")
get_target_property(OPENLOCO_INCLUDE_DIRS ${PROJECT} INCLUDE_DIRECTORIES)
set_target_properties(${PROJECT}-headers-check PROPERTIES INCLUDE_DIRECTORIES "${OPENLOCO_INCLUDE_DIRS}")
else ()
# Dummy target to ease invocation
add_custom_target(${PROJECT}-headers-check)
endif ()
install(FILES "distribution/linux/openloco.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications")
install(FILES "resources/logo/icon_x16.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps" RENAME "openloco.png")
install(FILES "resources/logo/icon_x32.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32/apps" RENAME "openloco.png")
install(FILES "resources/logo/icon_x64.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/64x64/apps" RENAME "openloco.png")
install(FILES "resources/logo/icon_x128.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps" RENAME "openloco.png")
install(FILES "resources/logo/icon_x256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "openloco.png")
install(FILES "resources/logo/icon_x512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "openloco.png")
install(FILES "resources/logo/icon_steam.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/apps" RENAME "openloco.svg")

View File

@ -27,9 +27,10 @@ Includes all git commit authors. Aliases are GitHub user names.
* Italian - extracted from original game
* Korean - (telk5093)
* Polish - (Zcooger)
* Portuguese (BR) - (Kynake)
* Portuguese (BR) - Pedro Bledow (Kynake), Gustavo Fernandes (guKing)
* Slovak - Peter Gaál (petergaal)
* Spanish - extracted from original game
* Chinese (Simplified) - (TransshipmentEnvoy)
## Graphics
* OpenLoco Logo - Zcooger (zcooger)

View File

@ -8,18 +8,19 @@ An open source re-implementation of Chris Sawyer's Locomotion. A construction an
# Contents
- 1 - [Introduction](#1-introduction)
- 2 - [Downloading the game (pre-built)](#2-downloading-the-game-pre-built)
- 3 - [Building the game](#3-building-the-game)
- 3.1 - [Building prerequisites](#31-building-prerequisites)
- 3.2 - [Compiling and running](#32-compiling-and-running)
- 4 - [Licence](#4-licence)
- 5 - [More information](#5-more-information)
- 3 - [Contributing](#3-contributing)
- 4 - [Compiling the game](#4-compiling-the-game)
- 4.1 - [Building prerequisites](#41-building-prerequisites)
- 4.2 - [Compiling and running](#42-compiling-and-running)
- 5 - [Licence](#5-licence)
- 6 - [More information](#6-more-information)
---
### Build Status
| | Windows | Linux / Mac | Download |
|-------------|---------|-------------|----------|
| **master** | ![CI](https://github.com/OpenLoco/OpenLoco/workflows/CI/badge.svg) | [![Travis CI](https://travis-ci.org/OpenLoco/OpenLoco.svg?branch=master)](https://travis-ci.org/OpenLoco/OpenLoco) | [![GitHub release](https://img.shields.io/github/release/OpenLoco/OpenLoco.svg)](https://github.com/OpenLoco/OpenLoco/releases) |
| | Windows / Linux | Download |
|-------------|-----------------|----------|
| **master** | ![CI](https://github.com/OpenLoco/OpenLoco/workflows/CI/badge.svg) | [![GitHub release](https://img.shields.io/github/release/OpenLoco/OpenLoco.svg)](https://github.com/OpenLoco/OpenLoco/releases) |
### Chat
@ -40,17 +41,33 @@ Recent implementation efforts have focussed on re-implementing the UI, so that t
# 2 Downloading the game (pre-built)
OpenLoco requires original files of Chris Sawyer's Locomotion to play. It can be bought at either [Steam](https://store.steampowered.com/app/356430/) or [GOG.com](https://www.gog.com/game/chris_sawyers_locomotion).
The latest releases can be [downloaded from GitHub](https://github.com/OpenLoco/OpenLoco/releases). Releases are currently provided for Windows and macOS (32-bit only).
For Linux and BSD distributions, we currently do not provide any builds. Please refer to the next section to compile the game manually.
The latest release can be found on [GitHub](https://github.com/OpenLoco/OpenLoco/releases).
Please note that OpenLoco requires the asset files of the original Chris Sawyer's Locomotion to play the game.
It can be bought at e.g. [Steam](https://store.steampowered.com/app/356430/) or [GOG.com](https://www.gog.com/game/chris_sawyers_locomotion).
---
# 3 Building the game
# 3 Contributing
## 3.1 Building prerequisites
We warmly welcome any contributions to the project, e.g. for C++ code (game implementation, bug fixes, features) or localisation (new translations).
Please have a look at our [issues for newcomers](https://github.com/OpenLoco/OpenLoco/labels/good%20first%20issue).
OpenLoco requires original files of Chris Sawyer's Locomotion to play. It can be bought at either [Steam](https://store.steampowered.com/app/356430/) or [GOG.com](https://www.gog.com/game/chris_sawyers_locomotion).
For code contributions, please stick to our [code style](https://github.com/OpenLoco/OpenLoco/wiki/Coding-Style).
You can use `clang-format` to apply these guidelines automatically.
---
# 4 Compiling the game
If you would like to contribute code to OpenLoco, please follow the instructions below to get started compiling the game.
Alternatively, we have platform-specific guides for [Ubuntu](https://github.com/OpenLoco/OpenLoco/wiki/Building-on-Ubuntu) and [macOS](https://github.com/OpenLoco/OpenLoco/wiki/Building-on-macOS).
If you just want to play the game, you can just [download the latest release](https://github.com/OpenLoco/OpenLoco/releases) from GitHub.
Releases are currently provided for Windows and macOS (32-bit only).
## 4.1 Building prerequisites
Regardless of platform, the following libraries/dependencies are required:
- [libpng](http://www.libpng.org/pub/png/libpng.html)
@ -72,13 +89,14 @@ Regardless of platform, the following libraries/dependencies are required:
---
## 3.2 Compiling and running
## 4.2 Compiling and running
### Windows:
1. Check out the repository. This can be done using [GitHub Desktop](https://desktop.github.com) or [other tools](https://help.github.com/articles/which-remote-url-should-i-use).
2. Open a new Developer Command Prompt for VS 2019, then navigate to the repository (e.g. `cd C:\GitHub\OpenLoco`).
3. Run `msbuild openloco.sln /t:restore;build`
4. Run `mklink /D bin\data ..\data` or `xcopy data bin\data /EIY`
5. Run the game, `bin\openloco`
5. Run `mklink openloco.exe bin\` or `copy openloco.exe bin\`
6. Run the game, `bin\openloco`
### Linux / macOS:
The standard CMake build procedure is to install the required libraries, then:
@ -97,12 +115,12 @@ cp -r ../data ./data
```
---
# 4 Licence
# 5 Licence
**OpenLoco** is licensed under the MIT License.
---
# 5 More information
# 6 More information
- [GitHub](https://github.com/OpenLoco/OpenLoco)
- [TT-Forums](https://www.tt-forums.net)
- [Locomotion subreddit](https://www.reddit.com/r/locomotion/)

23
cmake/FindCCache.cmake Normal file
View File

@ -0,0 +1,23 @@
# To find CCache compiler cache
include(FindPackageHandleStandardArgs)
find_program(CCache_EXECUTABLE ccache)
if (CCache_EXECUTABLE)
execute_process(COMMAND "${CCache_EXECUTABLE}" --version
OUTPUT_VARIABLE CCache_VERSION_OUTPUT
)
if (CCache_VERSION_OUTPUT MATCHES "version ([0-9]+\\.[0-9]+\\.[0-9]+)")
set(CCache_VERSION "${CMAKE_MATCH_1}")
endif ()
endif (CCache_EXECUTABLE)
find_package_handle_standard_args(CCache
FOUND_VAR CCache_FOUND
REQUIRED_VARS CCache_EXECUTABLE
VERSION_VAR CCache_VERSION
)
mark_as_advanced(CCache_EXECUTABLE)

View File

@ -0,0 +1,37 @@
message("HELLO FROM TOOLCHAIN")
message("TOOLCHAIN2: $ENV{TOOLCHAIN2}")
macro(osxcross_getconf VAR)
if(NOT ${VAR})
set(${VAR} "$ENV{${VAR}}")
if(${VAR})
set(${VAR} "${${VAR}}" CACHE STRING "${VAR}")
message(STATUS "Found ${VAR}: ${${VAR}}")
else()
message(FATAL_ERROR "Cannot determine \"${VAR}\"")
endif()
endif()
endmacro()
osxcross_getconf(OSXCROSS_HOST)
osxcross_getconf(OSXCROSS_TARGET_DIR)
osxcross_getconf(OSXCROSS_TARGET)
osxcross_getconf(OSXCROSS_SDK)
set(CMAKE_SYSTEM_NAME "Darwin")
string(REGEX REPLACE "-.*" "" CMAKE_SYSTEM_PROCESSOR "${OSXCROSS_HOST}")
# specify the cross compiler
set(CMAKE_C_COMPILER "${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-clang")
set(CMAKE_CXX_COMPILER "${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-clang++")
set(CMAKE_AR "${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-ar" CACHE FILEPATH "ar")
set(CMAKE_RANLIB "${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-ranlib" CACHE FILEPATH "ranlib")
set(CMAKE_INSTALL_NAME_TOOL "${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-install_name_tool" CACHE FILEPATH "install_name_tool")
set(ENV{PKG_CONFIG_LIBDIR} "${OSXCROSS_TARGET_DIR}/macports/pkgs/opt/local/lib/pkgconfig")
set(ENV{PKG_CONFIG_SYSROOT_DIR} "${OSXCROSS_TARGET_DIR}/macports/pkgs")
set(TOOLCHAIN2 "$ENV{TOOLCHAIN2}")
include("${TOOLCHAIN2}")
message("CMAKE_FIND_ROOT_PATH: ${CMAKE_FIND_ROOT_PATH}")

View File

@ -1243,8 +1243,8 @@ strings:
1213: "{COLOUR WINDOW_2}{STRINGID})"
1214: Es können keine Aufträge für Fahrzeuge mehr angenommen werden!
1215: Für dieses Fahrzeug liegen zu viele Aufträge vor!
1216: "- - Lokal - -"
1217: "- - Express - -"
1216: "Lokal"
1217: "Express"
1218: "{COLOUR BLACK}- - Keine Strecke definiert - -"
1219: "{COLOUR BLACK}- - Ende der Streckenliste - -"
1220: Halt bei {STRINGID}

View File

@ -1243,8 +1243,8 @@ strings:
1213: "{COLOUR WINDOW_2}{STRINGID})"
1214: No space for more vehicle orders!
1215: Too many orders for this vehicle!
1216: "- - Local - -"
1217: "- - Express - -"
1216: "Local"
1217: "Express"
1218: "{COLOUR BLACK}- - No route defined - -"
1219: "{COLOUR BLACK}- - End of route list - -"
1220: Stop at {STRINGID}
@ -2151,7 +2151,7 @@ strings:
2096: New objects installed successfully
2097: At least one industry must be selected
2098: At least one town building must be selected
2099: An company headquarters building type must be selected
2099: A company headquarters building type must be selected
2100: Only one company headquarters building type must be selected
2101: An interface type must be selected
2102: At least one vehicle type must be selected
@ -2195,3 +2195,78 @@ strings:
2140: "Exit OpenLoco"
2141: "{COLOUR WINDOW_2}Disable AI companies"
2142: "{SMALLFONT}{COLOUR BLACK}This disables AI from 'thinking', rendering them ineffective.{NEWLINE}In new games, this also prevents new AI companies from forming."
2143: "{COLOUR WINDOW_2}Autosave amount:"
2144: "{COLOUR WINDOW_2}Autosave frequency:"
2145: "Never"
2146: "Every month"
2147: "Every {INT32} months"
2148: "{COLOUR WINDOW_2}Generator:"
2149: Original
2150: Improved
2151: "Modify vehicle"
2152: "Clone vehicle"
2153: "Can't clone vehicle..."
2154: "Locate vehicle on main view"
2155: "Follow vehicle on main view"
2156: "Enable cheats/debugging toolbar menu"
2157: "{SMALLFONT}{COLOUR BLACK}This adds an extra button to the game's toolbar, allowing easy access to certain cheats and debugging options."
2158: "Enable sandbox mode"
2159: "Allow manual driving"
2160: "Allow building while paused"
2161: "Display fps counter"
2162: "{SMALLFONT}{COLOUR BLACK}This shows a counter at the top of the screen, indicating the number of frames drawn per second."
2163: "Uncap frame limiter"
2164: "{SMALLFONT}{COLOUR BLACK}Removes the restriction of rendering at 40Hz."
2165: "Hardware"
2166: "Map rendering"
2167: "Tile inspector"
2168: "{SMALLFONT}{COLOUR BLACK}Activate to select a tile on the map to inspect."
2169: "Surface"
2170: "Track"
2171: "Station"
2172: "Signal"
2173: "Building"
2174: "Tree"
2175: "Wall"
2176: "Road"
2177: "Industry"
2178: "{COLOUR WINDOW_2} {STRINGID} ({STRINGID})"
2179: "{COLOUR WINDOW_2} {STRINGID} - {STRINGID} ({STRINGID})"
2180: "X:"
2181: "Y:"
2182: "{COLOUR WINDOW_2}{INT16}"
2183: "Tile element data"
2184: "Cheats"
2185: "Financial cheats"
2186: "Company cheats"
2187: "Vehicle cheats"
2188: "Town cheats"
2189: "Clear loan"
2190: "Clear"
2191: "{COLOUR BLACK}{CURRENCY32}"
2192: "Select target company"
2193: "Select cheat to apply"
2194: "Switch control to this company"
2195: "Acquire all company assets"
2196: "Toggle bankruptcy"
2197: "Toggle jail status"
2198: "Increase funds"
2199: "{COLOUR WINDOW_2}Amount:"
2200: "Add"
2201: "Vehicle reliability"
2202: "Set all to zero (unlimited)"
2203: "Set all to hundred (full renewal)"
2204: "Set company ratings"
2205: "-10% everywhere"
2206: "+10% everywhere"
2207: "Min everywhere"
2208: "Max everywhere"
2209: "All vehicles"
2210: "Stopping at station"
2211: "Transporting cargo"
2212: "{MOVE_X 10}Carrying {STRINGID} {SPRITE}"
2213: "»{MOVE_X 10}Carrying {STRINGID} {SPRITE}"
2214: "No station selected"
2215: "No cargo type selected"
2216: "{SMALLFONT}{COLOUR BLACK}Open a station window to filter by station"
2217: "{SMALLFONT}{COLOUR BLACK}Select a cargo type from the list of available cargo"

View File

@ -1244,8 +1244,8 @@ strings:
1213: "{COLOUR WINDOW_2}{STRINGID})"
1214: No space for more vehicle orders!
1215: Too many orders for this vehicle!
1216: "- - Local - -"
1217: "- - Express - -"
1216: "Local"
1217: "Express"
1218: "{COLOUR BLACK}- - No route defined - -"
1219: "{COLOUR BLACK}- - End of route list - -"
1220: Stop at {STRINGID}
@ -2152,7 +2152,7 @@ strings:
2096: New objects installed successfully
2097: At least one industry must be selected
2098: At least one town building must be selected
2099: An company headquarters building type must be selected
2099: A company headquarters building type must be selected
2100: Only one company headquarters building type must be selected
2101: An interface type must be selected
2102: At least one vehicle type must be selected

View File

@ -1243,8 +1243,8 @@ strings:
1213: "{COLOUR WINDOW_2}{STRINGID})"
1214: ¡No hay sitio para más pedidos de vehículos!
1215: ¡Hay demasiados pedidos para este vehículo!
1216: "- - Local - -"
1217: "- - Expreso - -"
1216: "Local"
1217: "Expreso"
1218: "{COLOUR BLACK}- - Sin ruta definida - -"
1219: "{COLOUR BLACK}- - Fin de la lista de rutas - -"
1220: Parada en {STRINGID}

View File

@ -1243,8 +1243,8 @@ strings:
1213: "{COLOUR WINDOW_2}{STRINGID})"
1214: Pas assez de place pour d'autres ordres !
1215: Trop d'ordres pour ce véhicule !
1216: "- - Local - -"
1217: "- - Express - -"
1216: "Local"
1217: "Express"
1218: "{COLOUR BLACK}- - Aucun itinéraire défini - -"
1219: "{COLOUR BLACK}- - Fin de la liste des itinéraires - -"
1220: Arrêt à {STRINGID}

View File

@ -1243,8 +1243,8 @@ strings:
1213: "{COLOUR WINDOW_2}{STRINGID})"
1214: Non c'è spazio per altri ordini!
1215: Troppi ordini per questo veicolo!
1216: "- - Locale - -"
1217: "- - Espresso - -"
1216: "Locale"
1217: "Espresso"
1218: "{COLOUR BLACK}- - Percorso non definito - -"
1219: "{COLOUR BLACK}- - Fine del percorso - -"
1220: Ferma a {STRINGID}

View File

@ -1243,8 +1243,8 @@ strings:
1213: "{COLOUR WINDOW_2}{STRINGID})"
1214: No space for more vehicle orders!
1215: Too many orders for this vehicle!
1216: "- - Local - -"
1217: "- - Express - -"
1216: "Local"
1217: "Express"
1218: "{COLOUR BLACK}- - No route defined - -"
1219: "{COLOUR BLACK}- - End of route list - -"
1220: Stop at {STRINGID}

View File

@ -1243,8 +1243,8 @@ strings:
1213: "{COLOUR WINDOW_2}{STRINGID})"
1214: 차량 경로를 지정할 공간이 더 이상 없습니다!
1215: 이 차량에 경로가 너무 많습니다!
1216: "- - 완행 - -"
1217: "- - 급행 - -"
1216: "완행"
1217: "급행"
1218: "{COLOUR BLACK}- - 경로 없음 - -"
1219: "{COLOUR BLACK}- - 경로 목록 끝 - -"
1220: Stop at {STRINGID}
@ -2193,3 +2193,71 @@ strings:
2138: "{COLOUR WINDOW_2}타이틀 스크린 음악 재생"
2139: "메뉴로 나가기"
2140: "OpenLoco 종료"
2141: "{COLOUR WINDOW_2}AI 회사 비활성화"
2142: "{SMALLFONT}{COLOUR BLACK}이 설정을 켜면 AI 회사가 '생각'하는 것을 멈춰서 무력화시킵니다.{NEWLINE}새로운 게임에서는, 새로운 AI 회사가 나타나는 것을 막아주기도 합니다."
2143: "{COLOUR WINDOW_2}자동 저장 개수:"
2144: "{COLOUR WINDOW_2}자동 저장 주기:"
2145: "안 함"
2146: "매월마다"
2147: "매 {INT32}개월마다"
2148: "{COLOUR WINDOW_2}생성 알고리즘:"
2149: 오리지널
2150: 개선
2151: "차량 수정"
2152: "차량 복제"
2153: "차량을 복제할 수 없습니다..."
2154: "이 차량의 위치로 주 화면을 옮깁니다"
2155: "주 화면이 이 차량을 따라가게 만듭니다"
2156: "치트/디버그 툴바 메뉴 켜기"
2157: "{SMALLFONT}{COLOUR BLACK}이 설정을 켜면 치트와 디버그 설정을 할 수 있는 추가 버튼이 게임 툴바에 추가됩니다."
2158: "모래상자 모드 켜기"
2159: "수동 운전 허용"
2160: "일시 정지 상태에서 건설 허용"
2161: "FPS 표시"
2162: "{SMALLFONT}{COLOUR BLACK}1초에 그려지는 프레임의 수를 나타내는 수를 화면 상단에 표시합니다."
2163: "프레임 제한 해제"
2164: "{SMALLFONT}{COLOUR BLACK}40Hz 이상으로 화면을 표시합니다."
2165: "하드웨어"
2166: "지도 렌더링"
2167: "칸 조사 창"
2168: "{SMALLFONT}{COLOUR BLACK}지도 상에서 조사할 칸을 클릭해서 활성화합니다."
2169: "지표면"
2170: "선로"
2171: "역"
2172: "신호기"
2173: "건물"
2174: "나무"
2175: "벽"
2176: "도로"
2177: "산업시설"
2178: "{COLOUR WINDOW_2} {STRINGID} ({STRINGID})"
2179: "{COLOUR WINDOW_2} {STRINGID} - {STRINGID} ({STRINGID})"
2180: "X:"
2181: "Y:"
2182: "{COLOUR WINDOW_2}{INT16}"
2183: "칸 요소 데이터"
2184: "치트"
2185: "재정 치트"
2186: "회사 치트"
2187: "차량 치트"
2188: "도시 치트"
2189: "대출 갚기"
2190: "모두 갚기"
2191: "{COLOUR BLACK}{CURRENCY32}"
2192: "회사를 선택하세요"
2193: "적용할 치트를 선택하세요"
2194: "이 회사로 이동합니다"
2195: "모든 회사 자산 획득"
2196: "파산 켜기/끄기"
2197: "감옥에 가두기"
2198: "돈 추가"
2199: "{COLOUR WINDOW_2}금액:"
2200: "추가"
2201: "차량 신뢰도"
2202: "모두 0으로 (무제한)"
2203: "모두 100으로 (새 것으로)"
2204: "회사 평판 설정"
2205: "모든 곳을 -10%"
2206: "모든 곳을 +10%"
2207: "모든 곳을 최소로"
2208: "모든 곳을 최대로"

View File

@ -1243,8 +1243,8 @@ strings:
1213: "{COLOUR WINDOW_2}{STRINGID})"
1214: No space for more vehicle orders!
1215: Too many orders for this vehicle!
1216: "- - Lokaal - -"
1217: "- - Express - -"
1216: "Lokaal"
1217: "Express"
1218: "{COLOUR BLACK}- - Geen route gedefinieerd - -"
1219: "{COLOUR BLACK}- - Einde van routelijst - -"
1220: Stop op {STRINGID}

View File

@ -1236,8 +1236,8 @@ strings:
1213: '{COLOUR WINDOW_2}{STRINGID})'
1214: Brak miejsca na więcej poleceń pojazdu!
1215: Zbyt wiele poleceń dla tego pojazdu!
1216: '- - Normalny - -'
1217: '- - Pospieszny - -'
1216: 'Normalny'
1217: 'Pospieszny'
1218: '{COLOUR BLACK}- - Brak poleceń - -'
1219: '{COLOUR BLACK}- - Koniec poleceń - -'
1220: Zatrzymaj przy {STRINGID}

View File

@ -6,7 +6,7 @@ header:
strings:
0: ""
1: "{POP16}"
2: Nova Empresa{POP16}
2: Nova Companhia{POP16}
3: Sem Nome{POP16}
4: Trem {INT16}
5: Ônibus {INT16}
@ -60,8 +60,8 @@ strings:
50: Out
51: Nov
52: Dez
53: Impossível acessar o arquivo de gráficos
53: Impossível acessar o arquivo de gráficos
54: Arquivo de dados ausente ou inacessível
55: Impossível alocar memória o suficiente
56: "{COLOUR BLACK}❌"
@ -142,7 +142,7 @@ strings:
128: "{SMALLFONT}{COLOUR BLACK}Inverter Sentido"
129: "{SMALLFONT}{COLOUR BLACK}(Clique na paisagem para começar construção)"
130: "{SMALLFONT}{COLOUR BLACK}Construir esta seção de pista"
131: "{SMALLFONT}{COLOUR BLACK}Construir esta seção de pista"
131: "{SMALLFONT}{COLOUR BLACK}Remover seção de pista anterior"
132: "{SMALLFONT}{COLOUR BLACK}Descida íngreme"
133: "{SMALLFONT}{COLOUR BLACK}Descida"
134: "{SMALLFONT}{COLOUR BLACK}Plano"
@ -156,7 +156,7 @@ strings:
142: Impossível remover {POP16}{POP16}{POP16}{STRINGID}...
143: Impossível construir {POP16}{POP16}{POP16}{STRINGID}...
144: Aumente ou rebaixe o terreno primeiro
145: Subterrâneo/Visão interna
145: Visão Subterrânea
146: Esconder vias e estradas em primeiro plano
147: Esta estação só pode ser contruída no final da estrada
148: Tipo errado de estação para {STRINGID}
@ -184,9 +184,9 @@ strings:
170: Combinação de vias não é possível
171: Muitos objetos em jogo
172: Rotacionar no sentido horário
173: rotacionar no sentido anti-horário
174: "Locomotion: Iniciando pela primeira vez..."
175: "Locomotion: Verificando arquivos de objeto..."
173: Rotacionar no sentido anti-horário
174: "OpenLoco: Iniciando pela primeira vez..."
175: "OpenLoco: Verificando arquivos de objeto..."
176: Carregar Jogo
177: Sair do Jogo
178: Sair do Jogo
@ -302,26 +302,26 @@ strings:
285: "{STRINGID} Oeste"
286: "{STRINGID} Central"
287: "{STRINGID} Transfer"
288: "{STRINGID} Halt"
289: "{STRINGID} Valley"
290: "{STRINGID} Heights"
291: "{STRINGID} Woods"
292: "{STRINGID} Lakeside"
288: "Parada {STRINGID}"
289: "Vale {STRINGID}"
290: "Morros de {STRINGID}"
291: "Bosque {STRINGID}"
292: "Lago de {STRINGID}"
293: "{STRINGID} Exchange"
294: "{STRINGID} Airport"
295: "{STRINGID} Oilfield"
296: "{STRINGID} Mines"
297: "{STRINGID} Docks"
298: "{STRINGID} Annexe"
299: "{STRINGID} Sidings"
300: "{STRINGID} Branch"
301: Upper {STRINGID}
302: Lower {STRINGID}
303: "{STRINGID} Heliport"
304: "{STRINGID} Forest"
305: "{STRINGID} Junction"
306: "{STRINGID} Cross"
307: "{STRINGID} Views"
294: "Aeroporto de {STRINGID}"
295: "Petroleira de {STRINGID}"
296: "Minas de {STRINGID}"
297: "Docas de {STRINGID}"
298: "Anexo de {STRINGID}"
299: "Desvio de {STRINGID}"
300: "Filial de {STRINGID}"
301: Alto {STRINGID}
302: Baixo {STRINGID}
303: "Heliporto de {STRINGID}"
304: "Floresta {STRINGID}"
305: "Junção {STRINGID}"
306: "Cruzamento {STRINGID}"
307: "Vista {STRINGID}"
308: "{STRINGID} 1"
309: "{STRINGID} 2"
310: "{STRINGID} 3"
@ -381,14 +381,14 @@ strings:
364: Salvar Paisagem
365: Salvar Jogo
366: Salvar Cenário
367: Locomotion Jogo Salvo
368: Locomotion Arquivo de Cenário
369: Locomotion Arquivo de Paisagem
367: Arquivo de Jogo Salvo do OpenLoco
368: Arquivo de Cenário do OpenLoco
369: Arquivo de Paisagem do OpenLoco
370: Falha ao salvar jogo!
371: Falha ao carregar jogo salvo...{NEWLINE}Arquivo contém dados inválidos!
372: "Esconder cenário e construções em primeiro plano "
373: Somente possível construir isto na água!
374: Somente possível construir isto se próximo a indústria sobre a água!
374: Somente possível construir isto próximo a indústria sobre a água!
375: Nome {STRINGID}
376: "Escreva o novo nome para este {STRINGID}:"
377: Impossível renomear este veículo...
@ -417,7 +417,7 @@ strings:
400: "{STRINGID} está patinando no lugar num declive"
401: "{STRINGID} agora aceita {STRINGID}"
402: "{STRINGID} não aceita mais {STRINGID}"
403: Nova Empresa de Transportes!{NEWLINE}{STRINGID} comça construção perto de {STRINGID}!
403: Nova Companhia de Transportes!{NEWLINE}{STRINGID} começa construção perto de {STRINGID}!
404: "{STRINGID} não consegue pousar em {STRINGID} devido a inadequação do aeroporto"
405: Cidadãos Celebram!{NEWLINE}Primeiro {STRINGID} chega em {STRINGID}!
406: Cidadãos Celebram!{NEWLINE}Primeira entrega de {STRINGID} chega em {STRINGID}!
@ -425,8 +425,8 @@ strings:
408: Novo Veículo Inventado -{NEWLINE}“{STRINGID}”!
409: "{STRINGID} é promovido de “{STRINGID}” para “{STRINGID}”!"
410: Nova {STRINGID} em construção perto de {STRINGID}!
411: Parabéns!{NEWLINE}Você completou seu desafio com êxito!
412: Fracasso!{NEWLINE}Você falhou seu desafio!
411: Parabéns!{NEWLINE}Você completou o desafio com êxito!
412: Fracasso!{NEWLINE}Você falhou o desafio!
413: Vencido!{NEWLINE}{STRINGID} completou o desafio!
414: Alerta de Falência!{NEWLINE}{STRINGID} será fechado em 6 meses a menos que sua performance melhore!
415: Alerta Final!{NEWLINE}{STRINGID} será fechado em 3 meses a menos que sua performance melhore!
@ -444,7 +444,7 @@ strings:
424: "{MOVE_X 10}{STRING}"
425: »{MOVE_X 10}{STRING}
426: Marcas de altura nas psitas e estradas
426: Marcas de altura nas pistas e estradas
427: Marcas de altura no terreno
428: Setas direcionais de mão única
429: Nomes de municípios exibidos
@ -491,7 +491,7 @@ strings:
470: Taxiando em {STRINGID}
471: Decolando de {STRINGID}
472: Indo para {STRINGID}
473: No position{POP16}{POP16}
473: Sem posição{POP16}{POP16}
474: Viajando{POP16}{POP16}
475: "{STRINGID} - {STRINGID}"
476: "{POP16}{POP16}{STRINGID}"
@ -603,7 +603,7 @@ strings:
582: "{CURRENCY32}"
583: Impossível construir isto aqui...
584: "{DATE MY}"
585: Chris Sawyer's Locomotion
585: OpenLoco
586: Por favor insira seu CD de Locomotion na seguinte unidade de disco:-
587: "{COLOUR WINDOW_2}Despesa/Renda"
588: Renda de Trens
@ -651,7 +651,7 @@ strings:
630: "{COLOUR WINDOW_2}Dinheiro: {MOVE_X 81}{COLOUR RED}Falido!"
631: "{COLOUR WINDOW_2}Dinheiro: {MOVE_X 81}{COLOUR BLACK}{CURRENCY48}"
632: "{COLOUR WINDOW_2}Dinheiro: {MOVE_X 81}{COLOUR RED}{CURRENCY48}"
633: "{COLOUR WINDOW_2}Valor da Empresa: {COLOUR BLACK}{CURRENCY48}"
633: "{COLOUR WINDOW_2}Valor da Companhia: {COLOUR BLACK}{CURRENCY48}"
634: "{COLOUR WINDOW_2}Lucro de Veículos: {COLOUR BLACK}{CURRENCY48} por mês"
635: Janeiro
@ -718,7 +718,7 @@ strings:
695: "{NEWLINE}Desafio fracassado!"
696: "{NEWLINE}Desafio completo!"
697: "{SMALLFONT}{COLOUR BLACK}Índice de Performance: {INT16_1DP}% “{STRINGID}”"
698: "{SMALLFONT}{COLOUR BLACK}Valor da Empresa: {CURRENCY48}{NEWLINE}Lucro de veículos: {CURRENCY48}/mês"
698: "{SMALLFONT}{COLOUR BLACK}Valor da Companhia: {CURRENCY48}{NEWLINE}Lucro de veículos: {CURRENCY48}/mês"
699: "{NEWLINE}Progresso do desafio: {INT16}%{STRINGID}"
700: "{NEWLINE}Tempo restante: {COLOUR BLACK}{INT16} anos {INT16} meses"
701: Customizar Teclas...
@ -753,8 +753,8 @@ strings:
730: Mostrar Lista de Municípios
731: Mostrar Lista de Indústrias
732: Mostrar Mapa
733: Mostrar Lista de Empresas
734: Mostrar Informações da Empresa
733: Mostrar Lista de Companhias
734: Mostrar Informações da Companhia
735: Mostrar Finanças
736: Mostrar Lista de Anúncios
737: Captura de Tela
@ -840,7 +840,7 @@ strings:
815: K
816: L
817: M
818: "N"
818: N
819: O
820: P
821: Q
@ -851,7 +851,7 @@ strings:
826: V
827: W
828: X
829: "Y"
829: Y
830: Z
831: ???
832: ???
@ -1032,7 +1032,7 @@ strings:
1006: Mapa - Veículos
1007: Mapa - Indústrias
1008: Mapa - Rotas de Transporte
1009: Mapa - Empresas
1009: Mapa - Companhias
1010: Forçar Software Buffer Mixing
1011: "{SMALLFONT}{COLOUR BLACK}Selecione esta opção para melhorar a performance se o jogo levemente pausa quando sons começam ou escuta-se interferência"
1012: Cenário instalado com sucesso
@ -1067,15 +1067,15 @@ strings:
1040: "{COLOUR WINDOW_2}Everlasting High-Rise"
1041: "{COLOUR BLACK}Allister Brimble"
1042: "{COLOUR WINDOW_2}Solace"
1043: "{COLOUR BLACK}Scott Joplin (Executado por Peter James Adcock)"
1043: "{COLOUR BLACK}Scott Joplin (Performado por Peter James Adcock)"
1044: "{COLOUR WINDOW_2}Chrysanthemum"
1045: "{COLOUR BLACK}Scott Joplin (Executado por Peter James Adcock)"
1045: "{COLOUR BLACK}Scott Joplin (Performado por Peter James Adcock)"
1046: "{COLOUR WINDOW_2}Eugenia"
1047: "{COLOUR BLACK}Scott Joplin (Executado por Peter James Adcock)"
1047: "{COLOUR BLACK}Scott Joplin (Performado por Peter James Adcock)"
1048: "{COLOUR WINDOW_2}The Ragtime Dance"
1049: "{COLOUR BLACK}Scott Joplin (Executado por Peter James Adcock)"
1049: "{COLOUR BLACK}Scott Joplin (Performado por Peter James Adcock)"
1050: "{COLOUR WINDOW_2}Easy Winners"
1051: "{COLOUR BLACK}Scott Joplin (Executado por Peter James Adcock)"
1051: "{COLOUR BLACK}Scott Joplin (Performado por Peter James Adcock)"
1052: "{COLOUR WINDOW_2}Setting Off"
1053: "{COLOUR BLACK}Allister Brimble (Trombeta tocada por Brian Moore, Clarinete tocado por John Glanfield)"
1054: "{COLOUR WINDOW_2}A Traveller's Seranade"
@ -1114,7 +1114,7 @@ strings:
1086: Por Favor, Aguarde...
1087: Inicializando...
1088: Carregando...
1089: "instalando novos dados: "
1089: "Instalando novos dados: "
1090: Branco
1091: Translúcido
@ -1139,12 +1139,12 @@ strings:
1109: "{COLOUR BLACK}Caminhões:"
1110: "{COLOUR BLACK}Aeronaves:"
1111: "{COLOUR BLACK}Navios:"
1112: "{SMALLFONT}{COLOUR BLACK}Sede e detalhes da empresa"
1113: "{SMALLFONT}{COLOUR BLACK}Proprietário e status da empresa"
1114: "{SMALLFONT}{COLOUR BLACK}Finaças da empresa"
1112: "{SMALLFONT}{COLOUR BLACK}Sede e detalhes da companhia"
1113: "{SMALLFONT}{COLOUR BLACK}Proprietário e status da companhia"
1114: "{SMALLFONT}{COLOUR BLACK}Finaças da companhia"
1115: "{SMALLFONT}{COLOUR BLACK}Carga entregue"
1116: "{SMALLFONT}{COLOUR BLACK}Esquema de cores da empresa"
1117: "{SMALLFONT}{COLOUR BLACK}Desafio da empresa para este jogo"
1116: "{SMALLFONT}{COLOUR BLACK}Esquema de cores da companhia"
1117: "{SMALLFONT}{COLOUR BLACK}Desafio da companhia para este jogo"
1118: "{COLOUR WINDOW_2}Esquema de cores especiais usados para:"
1119: "{SMALLFONT}{COLOUR BLACK}Selecione a cor principal"
1120: "{SMALLFONT}{COLOUR BLACK}Selecione a cor secundária (utilizada apenas em alguns tipos de veículos)"
@ -1209,7 +1209,7 @@ strings:
1179: "{SMALLFONT}{COLOUR BLACK}Mostrar tipos de veículo no mapa"
1180: "{SMALLFONT}{COLOUR BLACK}Mostrar tipos de indústria no mapa"
1181: "{SMALLFONT}{COLOUR BLACK}Mostrar rotas de veículos no mapa"
1182: "{SMALLFONT}{COLOUR BLACK}Mostrar propriedade da empresa no mapa"
1182: "{SMALLFONT}{COLOUR BLACK}Mostrar propriedade da companhia no mapa"
1183: Estação muito espalhada!
1184: Impossível acrescentar {POP16}{POP16}{POP16}{POP16}{POP16}{STRINGID} em {STRINGID}...
1185: Impossível construir {POP16}{POP16}{POP16}{POP16}{POP16}{STRINGID}...
@ -1243,28 +1243,28 @@ strings:
1213: "{COLOUR WINDOW_2}{STRINGID})"
1214: Sem espaço para mais ordens do veículo!
1215: Muitas ordens para esse veículo!
1216: "- - Local - -"
1217: "- - Expresso - -"
1216: "Local"
1217: "Expresso"
1218: "{COLOUR BLACK}- - Sem rota definida - -"
1219: "{COLOUR BLACK}- - Fim da lista de rotas - -"
1220: Parar em {STRINGID}
1221: Passar por {STRINGID}
1222: Passar por waypoint
1222: Passar por marcação
1223: Descarregar quaisquer {STRINGID} {SPRITE}
1224: Esperar por carga total de {STRINGID} {SPRITE}
1225: Descarregar quaisquer {STRINGID} {SPRITE}
1226: Esperar por carga total de {STRINGID} {SPRITE}
1227: "{COLOUR WINDOW_2}▶"
1228: Impossível inserir ordem...
1229: "{COLOUR WINDOW_3}Clique para inserir nova ordem para{NEWLINE}passar pelo waypoint nesta posição"
1229: "{COLOUR WINDOW_3}Clique para inserir nova ordem para{NEWLINE}passar pelo marcação nesta posição"
1230: "{COLOUR WINDOW_3}Clique para inserir nova ordem para{NEWLINE}parar em {STRINGID}"
1231: "{COLOUR WINDOW_3}Clique novamente para mudar a última{NEWLINE}ordem para passar por {STRINGID}"
1232: "{SMALLFONT}{COLOUR BLACK}Inserir uma ordem para esperar por carga total"
1233: "{SMALLFONT}{COLOUR BLACK}Inserir uma ordem para forçar descarregamento de carga, mesmo que não aceito pela estação"
1234: "{SMALLFONT}{COLOUR BLACK}Pular para próxima ordem na lista"
1235: "{SMALLFONT}{COLOUR BLACK}Deletar a última ordem ou a selecionada"
1236: "{COLOUR BLACK}Clique numa estação ou waypoint para determinar a rota"
1237: "{SMALLFONT}{COLOUR BLACK}Clique na ordem para selecioná-la, clique duas vezes para centralizar visão na estação/waypoint"
1236: "{COLOUR BLACK}Clique numa estação ou marcação para determinar a rota"
1237: "{SMALLFONT}{COLOUR BLACK}Clique na ordem para selecioná-la, clique duas vezes para centralizar visão na estação/marcação"
1238: "{SMALLFONT}{COLOUR BLACK}Clique na ordem para copiá-la para o veículo selecionado"
1239: "{STRINGID} {STRINGID}{NEWLINE}{COLOUR WINDOW_3}{STRINGID}"
1240: Construir Trens
@ -1336,7 +1336,7 @@ strings:
1306: "{COLOUR BLACK}Tipo"
1307: "{COLOUR BLACK}Tipo ▼"
1308: Nome do Município
1309: "Escreva o novo nome para {STRINGID}:"
1309: "Escreva um novo nome para {STRINGID}:"
1310: "{COLOUR BLACK}{STRINGID} população {INT32}"
1311: Impossível renomear município...
1312: Nova Estação
@ -1368,19 +1368,19 @@ strings:
1338: Escarlate
1339: "{STRINGID}"
1340: "{POP16}{STRINGID}"
1341: "{STRINGID} Transportes"
1341: "{STRINGID} Transportadora"
1342: "{STRINGID} Express"
1343: "{STRINGID} Lines"
1344: "{STRINGID} Tracks"
1345: "{STRINGID} Coaches"
1343: "{STRINGID} Rodovias"
1344: "{STRINGID} Linhas Férreas"
1345: "{STRINGID} Ônibus"
1346: "{STRINGID} Air"
1347: "{STRINGID} Rail"
1347: "{STRINGID} Ferroviária"
1348: "{STRINGID} Carts"
1349: "{STRINGID} Trains"
1349: "{STRINGID} Trens"
1350: "{STRINGID} Haulage"
1351: "{STRINGID} Shipping"
1352: "{STRINGID} Freight"
1353: "{STRINGID} Trucks"
1351: "{STRINGID} Frota"
1352: "{STRINGID} Frete"
1353: "{STRINGID} Caminhões"
1354: "{COLOUR WINDOW_2}Sede"
1355: "{COLOUR WINDOW_2}Proprietário"
1356: "{COLOUR BLACK}@ {INT16}% juros por ano"
@ -1415,7 +1415,7 @@ strings:
1385: "{COLOUR BLACK}Nenhuma indústria disponível"
1386: "{SMALLFONT}{COLOUR BLACK}Município"
1387: "{SMALLFONT}{COLOUR BLACK}Gráfico populacional"
1388: "{SMALLFONT}{COLOUR BLACK}Classificação de cada empresa no município"
1388: "{SMALLFONT}{COLOUR BLACK}Classificação de cada companhia no município"
1389: "{SMALLFONT}{COLOUR BLACK}Indústria"
1390: "{SMALLFONT}{COLOUR BLACK}Gráfico de produção"
1391: "{COLOUR BLACK}{SMALLFONT}({STRINGID})"
@ -1453,9 +1453,9 @@ strings:
1423: "{COLOUR BLACK}{INT16}%"
1424: "{COLOUR BLACK}{TINYFONT}{DATE DMY}"
1425: Mensagens
1426: "{SMALLFONT}{COLOUR BLACK}Exibir mensangens recentes"
1427: "{SMALLFONT}{COLOUR BLACK}Opções de mensangens"
1428: Opções de mensangens
1426: "{SMALLFONT}{COLOUR BLACK}Exibir mensagens recentes"
1427: "{SMALLFONT}{COLOUR BLACK}Opções de mensagens"
1428: Opções de mensagens
1429: "{TINYFONT}{DATE DMY}"
1430: " + "
1431: " em espera"
@ -1478,27 +1478,27 @@ strings:
1448: "{SMALLFONT}{COLOUR BLACK}Carga em espera e aceita"
1449: "{SMALLFONT}{COLOUR BLACK}Classificações de carga"
1450: Demolição não permitida
1451: Outra empresa está prestes a construir aqui
1451: Outra companhia está prestes a construir aqui
1452: Muito longo!
1453: "{SMALLFONT}{COLOUR BLACK}Construir ou mover sede"
1454: "{SMALLFONT}{COLOUR BLACK}Mudar nome do proprietário"
1455: "{COLOUR BLACK}(ainda não construído)"
1456: Nomear Empresa
1457: "Escreva o novo nome da empresa:"
1458: Impossível renomear empresa...
1456: Nomear Companhia
1457: "Escreva o novo nome da companhia:"
1458: Impossível renomear companhia...
1459: Nomear Proprietário
1460: "Escreva o novo nome do proprietário:"
1461: Impossível renomear proprietário...
1462: Sede
1463: "{STRINGID} Sede"
1464: "{STRINGID} autoridade local não permite a remoção de estradas atualmente em uso"
1465: "{SMALLFONT}{COLOUR BLACK}Selecionar empresa"
1465: "{SMALLFONT}{COLOUR BLACK}Selecionar companhia"
1466: Jogo de Dois Jogadores
1467: Jogo de Dois Jogadores - Opções
1468: "{COLOUR BLACK}para criar um jogo de dois jogadores, um computador deve ser definido como host e o outro deve conectar-se a ele.{NEWLINE}{NEWLINE}Por favor selecione:"
1469: ""
1470: "{COLOUR BLACK}Definindo este computador como host..."
1471: "{COLOUR BLACK}Erro: Falha ao definr este computador como host"
1471: "{COLOUR BLACK}Erro: Falha ao definir este computador como host"
1472: "{COLOUR BLACK}Configuração do host completa - Esperando pela conexão do outro computador..."
1473: ""
1474: ""
@ -1506,9 +1506,9 @@ strings:
1476: "{COLOUR BLACK}Tentando se conectar..."
1477: "{COLOUR BLACK}Erro: Falha ao localizar ou se conectar com host"
1478: "{COLOUR BLACK}Successo!{NEWLINE}Você está conectado a: {STRINGID}"
1479: "{COLOUR BLACK}Atualmente conectado a: {STRINGID}{NEWLINE}{NEWLINE}Clique abaixo para desconectar e retornas ao modo de um jogador"
1479: "{COLOUR BLACK}Atualmente conectado a: {STRINGID}{NEWLINE}{NEWLINE}Clique abaixo para desconectar e retornar ao modo de um jogador"
1480: "{COLOUR WINDOW_2}Configurar este computador como host"
1481: "{COLOUR WINDOW_2}Conectar ao hostar"
1481: "{COLOUR WINDOW_2}Conectar-se ao host"
1482: "{COLOUR WINDOW_2}Desconectar"
1483: Digite o Endereço do Host
1484: "Digite o endereço de IP ou nome do computador do host a se conectar, ou deixe em branco para procurar na rede local:"
@ -1539,10 +1539,10 @@ strings:
1505: "{COLOUR WINDOW_2}Moeda preferida:"
1506: ""
1507: "Notícias principais da minha empresa:"
1508: "Notícias principais de outras empresas:"
1509: "Notícias secundárias da minha empresa:"
1510: "Notícias secundárias de outras empresas:"
1507: "Notícias principais da minha companhia:"
1508: "Notícias principais de outras companhias:"
1509: "Notícias secundárias da minha companhia:"
1510: "Notícias secundárias de outras companhias:"
1511: "Notícias gerais:"
1512: "Conselho:"
1513: Desligado
@ -1560,7 +1560,7 @@ strings:
1525: Porto
1526: Tipo de ordem inválido para aeronaves
1527: Tipo de ordem inválido para navios
1528: Estação pertence a outra empresa
1528: Estação pertence a outra companhia
1529: Sem água!
1530: Hidrovia atualmente em uso por navios
1531: Atualmente em uso por pelo menos um veículos
@ -1572,7 +1572,7 @@ strings:
1536: "{SMALLFONT}{COLOUR BLACK}Parar música"
1537: "{SMALLFONT}{COLOUR BLACK}Tocar música"
1538: "{SMALLFONT}{COLOUR BLACK}Tocar pŕoxima música"
1539: Tocar música somente da era atual
1539: Tocar somente músicas da era atual
1540: Tocar todas as músicas
1541: Tocar seleção customizada de músicas
1542: Editar seleção...
@ -1584,8 +1584,8 @@ strings:
1548: "{SMALLFONT}{COLOUR BLACK}Definir volume da música"
1549: Opções de Música
1550: Selecionar aparência do proprietário de {STRINGID}
1551: "{SMALLFONT}{COLOUR BLACK}Clique para selecionar a aparência do proprietário/empresa"
1552: Já selecionado por outra empresa
1551: "{SMALLFONT}{COLOUR BLACK}Clique para selecionar a aparência do proprietário/companhia"
1552: Já selecionado por outra companhia
1553: Impossível selecionar aparência...
1554: aeroportos
1555: portos
@ -1606,9 +1606,9 @@ strings:
1570: Principiante
1571: Fácil
1572: Médio
1573: Díficil
1573: Desafiador
1574: Especialista
1575: Seleção de Região/Objetos
1575: Seleção de Região / Objetos
1576: Editor de Paisagens
1577: Opções de Cenário
1578: Salvar Cenário
@ -1701,7 +1701,7 @@ strings:
1663: "{POP16}{POP16}{INT16}%"
1664: "{COLOUR WINDOW_2}N° de municípios:"
1665: "{INT16}"
1666: "{COLOUR WINDOW_2}Tamanho máximo de um município:"
1666: "{COLOUR WINDOW_2}Tamanho máximo de município:"
1667: Baixo
1668: Médio
1669: Alto
@ -1711,12 +1711,12 @@ strings:
1673: Impossível salvar o cenário ainda...
1674: "{SMALLFONT}{COLOUR BLACK}Opções do cenário"
1675: "{SMALLFONT}{COLOUR BLACK}Desafio do cenário"
1676: "{SMALLFONT}{COLOUR BLACK}Opções da empresa"
1677: "{SMALLFONT}{COLOUR BLACK}Opções finaceiras"
1676: "{SMALLFONT}{COLOUR BLACK}Opções da companhia"
1677: "{SMALLFONT}{COLOUR BLACK}Opções financeiras"
1678: Opções do cenário
1679: Desafio do cenário
1680: Opções da empresa
1681: Opções finaceiras
1680: Opções da companhia
1681: Opções financeiras
1682: "{COLOUR WINDOW_2}Número máximo de competidores:"
1683: "{INT16}"
1684: "{COLOUR WINDOW_2}Atraso antes da criação dos competidores:"
@ -1752,7 +1752,7 @@ strings:
1714: Por favor espere - O outro jogador está salvando a partida...
1715: Por favor espere - O outro jogador está carregando a partida...
1716: Enviar Mensagem
1717: Envira Mensagem
1717: Enviar Mensagem
1718: "Escreva a mensagem a ser enviada para {STRINGID}:"
1719: ""
1720: "{COLOUR WINDOW_2}Tempo de conexão esgotado:"
@ -1762,7 +1762,7 @@ strings:
1724: "{COLOUR WINDOW_2} Obsoleto desde: {COLOUR BLACK}{UINT16 RAW}"
1725: "{COLOUR WINDOW_2} Potência: {COLOUR BLACK}{POWER}"
1726: "{COLOUR WINDOW_2} Peso: {COLOUR BLACK}{INT16}t"
1727: "{COLOUR WINDOW_2} Vel. máxima: {COLOUR BLACK}{VELOCITY}"
1727: "{COLOUR WINDOW_2} Vel. Máxima: {COLOUR BLACK}{VELOCITY}"
1728: "{COLOUR WINDOW_2} Capacidade: {COLOUR BLACK}{STRINGID}"
1729: Estabeleçendo conexão...
1730: Em todo lugar
@ -1778,30 +1778,30 @@ strings:
1740: "{SMALLFONT}{COLOUR BLACK}Editor de Cenário"
1741: "{STRINGID} Transporte"
1742: Mapa
1743: "{NEWLINE 1 2}{SPRITE}{NEWLINE 33 8}Lista de Empresas"
1743: "{NEWLINE 1 2}{SPRITE}{NEWLINE 33 8}Lista de Companhias"
1744: "{NEWLINE 0 8}{STRINGID}{NEWLINE 25 2}{SPRITE}{NEWLINE 51 2}{STRINGID}{NEWLINE 51 12}{COLOUR WINDOW_1}{INT16_1DP}% “{STRINGID}”"
1745: Empresas
1746: Índices de Performance das Empresas
1745: Companhias
1746: Índices de Performance das Companhias
1747: Unidades de Carga Entregues por Mês
1748: Valores da Empresas
1748: Valores da Companhias
1749: Taxa de Pagamento por Carga
1750: "{SMALLFONT}{COLOUR BLACK}Comparar empresas"
1751: "{SMALLFONT}{COLOUR BLACK}Gráficos de índices de performance das empresas"
1752: "{SMALLFONT}{COLOUR BLACK}Gráficos de carga entregue por cada empresa"
1753: "{SMALLFONT}{COLOUR BLACK}Gráficos de valor das empresas"
1750: "{SMALLFONT}{COLOUR BLACK}Comparar companhias"
1751: "{SMALLFONT}{COLOUR BLACK}Gráficos de índices de performance das companhias"
1752: "{SMALLFONT}{COLOUR BLACK}Gráficos de carga entregue por cada companhia"
1753: "{SMALLFONT}{COLOUR BLACK}Gráficos de valor das companhias"
1754: "{SMALLFONT}{COLOUR BLACK}Taxa de pagamento por carga"
1755: "{SMALLFONT}{COLOUR BLACK}Ordenar lista pelo nome da empresa"
1756: "{SMALLFONT}{COLOUR BLACK}Ordenar lista pelo status da empresa"
1755: "{SMALLFONT}{COLOUR BLACK}Ordenar lista pelo nome da companhia"
1756: "{SMALLFONT}{COLOUR BLACK}Ordenar lista pelo status da companhia"
1757: "{SMALLFONT}{COLOUR BLACK}Ordenar lista pelo índice de performance"
1758: "{SMALLFONT}{COLOUR BLACK}Ordenar lista pelo valor da empresa"
1759: "{COLOUR BLACK}Empresa"
1760: "{COLOUR BLACK}Empresa ▼"
1758: "{SMALLFONT}{COLOUR BLACK}Ordenar lista pelo valor da companhia"
1759: "{COLOUR BLACK}Companhia"
1760: "{COLOUR BLACK}Companhia ▼"
1761: "{COLOUR BLACK}Status"
1762: "{COLOUR BLACK}Status ▼"
1763: "{COLOUR BLACK}Índice de Performance"
1764: "{COLOUR BLACK}Índice de Performance ▼"
1765: "{COLOUR BLACK}Valor da Empresa"
1766: "{COLOUR BLACK}Valor da Empresa ▼"
1765: "{COLOUR BLACK}Valor da Companhia"
1766: "{COLOUR BLACK}Valor da Companhia ▼"
1767: "{NEWLINE 1 2}{SPRITE}{NEWLINE 27 8}{STRINGID}"
1768: "{NEWLINE 0 3}{INT16_1DP}% {NEWLINE 0 13}“{STRINGID}”"
1769: "{NEWLINE 0 3}{INT16_1DP}%{SPRITE 2325}{NEWLINE 0 13}“{STRINGID}”"
@ -1813,12 +1813,12 @@ strings:
1775: Coordenador de Transporte
1776: Supervisor de Rota
1777: Diretor
1778: Diretor Executivo
1779: CEO
1778: Chefe Executivo
1779: Chairman
1780: Presidente
1781: Magnata
1782: "{INT16} empresa"
1783: "{INT16} empresas"
1782: "{INT16} companhia"
1783: "{INT16} companhias"
1784: "{RAWDATE MY SHORT}"
1785: "{INT16_1DP}%"
1786: "{SMALLFONT}{COLOUR BLACK}{STRINGID}"
@ -1861,35 +1861,35 @@ strings:
1823: "{COLOUR WINDOW_2}Competidores: {COLOUR BLACK}Nenhum"
1824: "{COLOUR WINDOW_2}Competidores: {COLOUR BLACK}Até {INT16}"
1825: "{COLOUR BLACK}{MOVE_X 10}(não começam antes de {INT16} mês)"
1825: "{COLOUR BLACK}{MOVE_X 10}(não começam antes de {INT16} meses)"
1826: "{COLOUR BLACK}{MOVE_X 10}(não começam antes de {INT16} meses)"
1827: "{COLOUR WINDOW_2}Valor de venda do veículo: {COLOUR BLACK}{CURRENCY32}"
1828: "{COLOUR WINDOW_2}Última renda: {COLOUR BLACK}N/A"
1829: "{COLOUR WINDOW_2}Última renda em: {COLOUR BLACK}{DATE DMY}"
1830: "{COLOUR BLACK}{STRINGID} transportou {INT16} blocos em {INT16} dias = {CURRENCY32}"
1831: "{COLOUR WINDOW_2} Primeiro ano de construção: {COLOUR BLACK}{UINT16 RAW}"
1832: "{COLOUR WINDOW_2} Último ano de construção: {COLOUR BLACK}{UINT16 RAW}"
1833: "{COLOUR WINDOW_2}Classificação das empresas segundo a autoridade local:"
1833: "{COLOUR WINDOW_2}Classificação das companhias segundo a autoridade local:"
1834: Terrível
1835: Ruim
1836: Normal
1837: Boa
1838: Excelente
1839: "{COLOUR WINDOW_2}{STRINGID}: {COLOUR BLACK}{INT16}% ({STRINGID})"
1840: Alcançe um valor da empresa de {CURRENCY32}{STRINGID}{STRINGID}
1840: Alcançe um valor da companhia de {CURRENCY32}{STRINGID}{STRINGID}
1841: Alcançe uma renda mensal de veículos de {CURRENCY32}{STRINGID}{STRINGID}
1842: Alcançe um índice de performance de {INT16_1DP}% (“{STRINGID}”){STRINGID}{STRINGID}
1843: Entregue {STRINGID}{STRINGID}{STRINGID}
1844: " e seja a empresa de melhor performance"
1845: " e esteja estre uma das três empresas de melhor performance"
1844: " e seja a companhia de melhor performance"
1845: " e esteja estre uma das três companhias de melhor performance"
1846: " em menos {INT16} anos"
1847: " ao final de {UINT16 RAW}"
1848: "...e seja a melhor empresa"
1849: "...e esteja entre as três melhores empresas"
1848: "...e seja a melhor companhia"
1849: "...e esteja entre as três melhores companhias"
1850: "...com um limite de tempo de:"
1851: "{COLOUR WINDOW_2}Desafio:"
1852: "{COLOUR BLACK} {STRINGID}"
1853: Alcançe um certo valor de empresa
1854: Alcançe umacerta renda mensal de veículos
1853: Alcançe um certo valor de companhia
1854: Alcançe uma certa renda mensal de veículos
1855: Alcançe um certo índice de performance
1856: Entregue uma certa quantidade de carga
1857: "{CURRENCY32}"
@ -1907,37 +1907,37 @@ strings:
1869: "{OUTLINE}{COLOUR WINDOW_2}Sair do{NEWLINE}Jogo"
1870: Empresa está falida!
1870: Companhia está falida!
1871: Permitir que indústrias fechem durante a partida
1872: Permitir que indústrias novas sejam criadas durante a partida
1873: Compartilhar aparências das empresas/propritário adicionais
1874: "{SMALLFONT}{COLOUR BLACK}Selecione esta opção para enviar quaisquer arquivos de dados personalizados da aparência de empresas/proprietário para o outro computador durante o processo de conexão"
1873: Compartilhar aparências das companhias/proprietário adicionais
1874: "{SMALLFONT}{COLOUR BLACK}Selecione esta opção para enviar quaisquer arquivos de dados personalizados da aparência de companhias/proprietário para o outro computador durante o processo de conexão"
1875: "{COLOUR WINDOW_2}Proibir que os competidores utilizem:"
1876: "{COLOUR WINDOW_2}Proibir que empresas de jogadores utilizem:"
1876: "{COLOUR WINDOW_2}Proibir que companhias de jogadores utilizem:"
1877: "{NEWLINE}Para em: {STRINGID}"
1878: ", {STRINGID}"
1879: "Tutorial 1: Construindo um serviço de ônibus em um município"
1879: "Tutorial 1: Iniciando um serviço de ônibus em um município"
1880: "Tutorial 2: Construindo um serviço de ônibus entre dois municípios"
1881: "Tutorial 3: Construindo um serviço ferroviário entre dois municípios"
# Tutorial 1
1882: "{SMALLFONT}{COLOUR BLACK}Construiremos um serviço de ônibus para transportar passageiros em um município, o primeiro passo é escolher um município..."
1883: "{SMALLFONT}{COLOUR BLACK}Mova o mouse com o botão direito pressionado to para mover a vista para dar uma olhada nos municípios..."
1883: "{SMALLFONT}{COLOUR BLACK}Mova o mouse com o botão direito pressionado para mover a vista e dar uma olhada nos municípios..."
1884: "{SMALLFONT}{COLOUR BLACK}Boulders Bay é o maior município, portanto produzirá o maior número de passageiros. Utilize a roda do mouse ou o ícone de zoom para aproximar a vista..."
1885: "{SMALLFONT}{COLOUR BLACK}Toda construção relacionada a estradas é feita utilizando a janela de construção de estradas..."
1886: "{SMALLFONT}{COLOUR BLACK}Não precisamos de novas estradas no município para nosso serviço de ônibus, apenas de novas estações de ônibus..."
1887: "{SMALLFONT}{COLOUR BLACK}Nosso serviço de ônibus irá de um a lado ao outro do município..."
1887: "{SMALLFONT}{COLOUR BLACK}Nosso serviço de ônibus irá de um a lado do município a outro..."
1888: "{SMALLFONT}{COLOUR BLACK}A área realçada em azul é a área de captação da estação. Quanto mais edifícios na área de captação mais passageiro utilizarão a estação..."
1889: "{SMALLFONT}{COLOUR BLACK}Verique-se de a posição da estação aceita passageiros, caso contrário você não poderá descarregá-los e não será pago. Observe o texto “Área de Captação” na parte inferior da janela de construção..."
1889: "{SMALLFONT}{COLOUR BLACK}Verique-se de que a posição da estação aceita passageiros, caso contrário você não poderá descarregá-los e não será pago. Observe o texto “Área de Captação” na parte inferior da janela de construção..."
1890: "{SMALLFONT}{COLOUR BLACK}Quando estiver contente com a localização, clique o botão esquerdo do mouse para construir a estação..."
1891: "{SMALLFONT}{COLOUR BLACK}Construiremos a segunda estação no lado oposto do município..."
1892: "{SMALLFONT}{COLOUR BLACK}Agora temos que comprar um ônibus..."
1893: "{SMALLFONT}{COLOUR BLACK}Temos a escolha de dois tipos diferentes de ônibus. Vamos escolher o desigh mais novo que carrega mais passageiros..."
1893: "{SMALLFONT}{COLOUR BLACK}Temos a escolha de dois tipos diferentes de ônibus. Vamos escolher o modelo mais novo que carrega mais passageiros..."
1894: "{SMALLFONT}{COLOUR BLACK}Mova o cursor do mouse para posicionar o ônibus numa estrada e clique o botão esquerdo para colocá-lo..."
1895: "{SMALLFONT}{COLOUR BLACK}Antes de ligarmos o ônibus devemos informá-lo aonde ir. Selecione a aba de detalhes da rota na janela do veículo..."
1896: "{SMALLFONT}{COLOUR BLACK}Para determinar a rota, simplesmente clique na estação na vista..."
1897: "{SMALLFONT}{COLOUR BLACK}Agora podemos ligar o ônibus..."
1895: "{SMALLFONT}{COLOUR BLACK}Antes de colocarmos o ônibus em movimento devemos informá-lo aonde ir. Selecione a aba de detalhes da rota na janela do veículo..."
1896: "{SMALLFONT}{COLOUR BLACK}Para determinar a rota, apenas clique na estação na vista..."
1897: "{SMALLFONT}{COLOUR BLACK}Agora podemos por o ônibus em movimento..."
1898: "{SMALLFONT}{COLOUR BLACK}Vamos observá-lo pegando seus primeiros passageiros..."
# Tutorial 2
@ -1945,28 +1945,28 @@ strings:
1900: "{SMALLFONT}{COLOUR BLACK}Selecione a orientação para a nova seção de estrada, e clique na vista para iniciar a construção..."
1901: "{SMALLFONT}{COLOUR BLACK}Clicar no ícone 'construir isto' irá acrescentar mais seções de estrada do mesmo tipo..."
1902: "{SMALLFONT}{COLOUR BLACK}Antes de prosseguirmos, existe um método mais rápido de iniciar construção de estradas..."
1903: "{SMALLFONT}{COLOUR BLACK}Simplesmente pressione o botão direito do mouse no final da estrada que você quer extender..."
1903: "{SMALLFONT}{COLOUR BLACK}Apenas pressione o botão direito do mouse no final da estrada que você quer extender..."
1904: "{SMALLFONT}{COLOUR BLACK}Precisamos dobrar à esquerda aqui, portanto selecione uma curva à esquerda..."
1905: "{SMALLFONT}{COLOUR BLACK}E agora de volta a construção de retas novamente..."
1906: "{SMALLFONT}{COLOUR BLACK}Com a estrada construída, contruiremos as estações..."
1907: "{SMALLFONT}{COLOUR BLACK}E finalmente, Vamos comprar um ônibus e o colocaremos para rodar..."
1906: "{SMALLFONT}{COLOUR BLACK}Com a estrada construída, construiremos as estações..."
1907: "{SMALLFONT}{COLOUR BLACK}E finalmente, Vamos comprar um ônibus e o colocaremos em movimento..."
1908: "{SMALLFONT}{COLOUR BLACK}Essa rota pode ser bem movimentada, então vamos comprar um segundo ônibus..."
1909: "{SMALLFONT}{COLOUR BLACK}Para copiar rapidamente toda a rota do Ônibus 1 para o Ônibus 2, CLique no texto 'Fim da lista de rotas' do Ônibus 1..."
1909: "{SMALLFONT}{COLOUR BLACK}Para copiar rapidamente toda a rota do Ônibus 1 para o Ônibus 2, Clique no texto 'Fim da lista de rotas' do Ônibus 1..."
# Tutorial 3
1910: "{SMALLFONT}{COLOUR BLACK}Vamos começar construindo um serviço ferroviário de passageiros entre as duas maiores cidades..."
1911: "{SMALLFONT}{COLOUR BLACK}Estações de trem só podem ser construidas em trilhos retos e precisam ser longas o suficiente para nosso trem - Aqui é onde construiremos uma estação em alguns minutos..."
1912: "{SMALLFONT}{COLOUR BLACK}Vamos construir as estações..."
1913: "{SMALLFONT}{COLOUR BLACK}Vamos construir o trem. Precisaremos de uma locomotiva para puxar o trem e vagões para carregar os passageiros..."
1914: "{SMALLFONT}{COLOUR BLACK}Como a via é uma só linha, não precisamos configurar uma rota - Apenas começar o trem..."
1914: "{SMALLFONT}{COLOUR BLACK}Como a via é de uma só linha, não precisamos configurar uma rota - Apenas por o trem para andar..."
1915: "{SMALLFONT}{COLOUR BLACK}Vamos extender a via para criar uma junção e uma extensão para o terceiro município. Clicar com o botão direito na via abre a janela de construção de vias pronta para construir a junção..."
1916: "{SMALLFONT}{COLOUR BLACK}Já que o traçado da via não é mais simples Precisaremos dar ao nosso trem uma rota..."
1917: "{SMALLFONT}{COLOUR BLACK}Poderíamos agora adicionar um segundo trem, mas primeiro precisamos construir semáforos para prevenir colisões entre trens..."
1918: "{SMALLFONT}{COLOUR BLACK}Os semáforos diveidem a via em três diferentes 'Seções de bloco' e apenas um trem por vez poderá entrar em cada seção..."
1916: "{SMALLFONT}{COLOUR BLACK}Já que o traçado da via não é mais simples precisaremos dar ao nosso trem uma rota..."
1917: "{SMALLFONT}{COLOUR BLACK}Poderíamos agora adicionar um segundo trem, mas primeiro precisamos construir semáforos para evitar colisões entre trens..."
1918: "{SMALLFONT}{COLOUR BLACK}Os semáforos dividem a via em três diferentes 'Seções de bloco' e apenas um trem por vez poderá entrar em cada seção..."
# Options/Misc
1919: Usar nome do proprietário preferido no início de cada partida
1920: "{SMALLFONT}{COLOUR BLACK}Selecione esta opção para utilizar o mesmo nome do proprietário toda vez que ocê começar uma nova partida"
1920: "{SMALLFONT}{COLOUR BLACK}Selecione esta opção para utilizar o mesmo nome do proprietário toda vez que você começar uma nova partida"
1921: "{COLOUR WINDOW_2}Nome do proprietário preferido: {COLOUR BLACK}{STRINGID}"
1922: Nome do Proprietário Preferido
1923: "Escreva o nome do proprietário preferido:"
@ -2035,8 +2035,8 @@ strings:
1981: "{COLOUR WINDOW_2}"
1982: "{COLOUR WINDOW_2}"
1983: Distâcia por Unidades de Carga Entregues por Mês
1984: "{SMALLFONT}{COLOUR BLACK}Cráficos de carga por distância entregues por cada empresa"
1983: Distância por Unidades de Carga Entregues por Mês
1984: "{SMALLFONT}{COLOUR BLACK}Cráficos de carga por distância entregues por cada companhia"
1985: "{COLOUR WINDOW_2}Velocidade média do último trajeto: {COLOUR BLACK}{VELOCITY}"
1986: terra
1987: ar
@ -2054,17 +2054,17 @@ strings:
1999: "{COLOUR WINDOW_2}Recorde de velocidade de um serviço aquático: {COLOUR BLACK}{VELOCITY}"
2000: "{COLOUR BLACK}Realizado por {STRINGID} em {DATE DMY}"
2001: Confirmar troca do modo de visualização
2002: "{COLOUR WINDOW_2}Resolução aerada para {UINT16 RAW} x {UINT16 RAW}"
2002: "{COLOUR WINDOW_2}Resolução da tela mudou para {UINT16 RAW} x {UINT16 RAW}"
2003: "{COLOUR WINDOW_2}_"
2004: "{COLOUR WINDOW_2}Nome do arquivo:"
2005: "{COLOUR WINDOW_2}Diretório: {COLOUR BLACK}{STRINGID}"
2006: "{SMALLFONT}{COLOUR BLACK}Subir um nível para a pasta principal"
2007: "{COLOUR WINDOW_2}Empresa: {COLOUR BLACK}{STRINGID}"
2007: "{COLOUR WINDOW_2}Companhia: {COLOUR BLACK}{STRINGID}"
2008: "{COLOUR WINDOW_2}Data: {COLOUR BLACK}{DATE DMY}"
2009: "{COLOUR WINDOW_2}Progresso do Desafio: {COLOUR BLACK}{INT16}%"
2010: "{COLOUR WINDOW_2}Desafio: {COLOUR BLACK}Completed"
2011: "{COLOUR WINDOW_2}Desafio: {COLOUR BLACK}Failed"
2012: "Sobrescrever arquivo existente: “{STRINGID}”?"
2012: "Substituir arquivo existente: “{STRINGID}”?"
2013: Substituir
2014: "Excluir arquivo: “{STRINGID}”?"
2015: Excluir
@ -2072,7 +2072,7 @@ strings:
2017: Nome da Indústria
2018: "Escreva um novo nome para {STRINGID}:"
2019: Impossível renomear indústria...
2020: "{NEWLINE 0 1}{SPRITE}{SMALLFONT}{NEWLINE 45 1}Max. speed: {STRINGID}{NEWLINE 45 11}Height limit: {HEIGHT}"
2020: "{NEWLINE 0 1}{SPRITE}{SMALLFONT}{NEWLINE 45 1}Vel. máxima: {STRINGID}{NEWLINE 45 11}Limite de altura: {HEIGHT}"
2021: Códigos seriais duplicados - Falha na conexão!
2022: "{COLOUR BLACK}{SMALLFONT}(Nome do Computador = {STRINGID})"
2023: "1"
@ -2099,7 +2099,7 @@ strings:
2044: Muitos objetos deste tipo selecionados
2045: "O seguinte objeto deve ser selecionado primeiro: "
2046: Este objeto está atualmente em uso
2047: Este objeto e necessitado por outro objeto
2047: Este objeto e requerido por outro objeto
2048: Este objeto é sempre necessário
2049: Impossível selecionar este objeto
2050: Impossível cancelar a seleção deste objeto
@ -2109,7 +2109,7 @@ strings:
2054: Sons
2055: Moeda
2056: Efeitos de Animação
2057: Peredes Verticais do Terreno
2057: Faces Verticais do Terreno
2058: Água
2059: Terreno
2060: Nomes de Municípios
@ -2133,24 +2133,24 @@ strings:
2078: Neve
2079: Clima
2080: Dados da Geração de Mapas
2081: Edifícios
2082: Andaimes
2081: Construções
2082: Suportes
2083: Indústrias
2084: Região do Mundo
2085: Proprietários da Empresa
2085: Proprietários da Companhia
2086: Descrições dos Cenários
2087: lista de objetos
2088: "Faltam dados de objeto, ID: "
2089: Exportar objetos de plug-in com os jogos salvos
2090: "{SMALLFONT}{COLOUR BLACK}Selecione se deseja salvar os dados de objetos de plug-in adicionais (dados adicionais não proporcionados com o produto principal) nos arquivos de jogos salvos ou cenários, possibilitando que alguém sem os dados de objeto os carregue"
2091: Pelo menos uma estrada de mão dupla deve ser selecionada
2092: Um tipo de andaime deve ser selecionado
2092: Um tipo de suporte deve ser selecionado
2093: Avançado
2094: "{SMALLFONT}{COLOUR BLACK}Permite uma seleção de itens individuais mais avançada"
2095: "{COLOUR WHITE}{BIGFONT}£"
2096: Novos objetos instalados com sucesso
2097: Pelo menos uma indústria deve ser selecionada
2098: Pelo menos um edifício municipal deve ser selecionado
2098: Pelo menos uma construção municipal deve ser selecionada
2099: Um tipo de sede deve ser selecionado
2100: Apenas um tipo de sede deve ser selecionado
2101: Um tipo de interface deve ser selecionado
@ -2167,19 +2167,97 @@ strings:
2112: Um tipo de região deve ser selecionado
2113: mph
2114: kmh⁻¹
2115: "hour:"
2116: "hours:"
2115: "hora:"
2116: "horas:"
2117: mins
2118: "min:"
2119: secs
2120: " units"
2119: segs
2120: " unidades"
2121: ft
2122: m
2123: hp
2124: kW
2125: "{COLOUR WINDOW_2}Idioma:"
2126: "{COLOUR WINDOW_2}Desabilitar quebra de veículos"
2127: "{COLOUR WINDOW_2}Modo de tela:"
2128: Janela
2129: Tela Cheia
2130: Tela Cheia Sem Borda
2131: Dispositivo padrão de aúdio
2132: "{COLOUR WINDOW_2}Fator de escala da janela:"
2133: "{COLOUR BLACK}+"
2134: "{COLOUR BLACK}-"
2135: "{COLOUR BLACK}{INT32_1DP}"
2136: "{COLOUR WINDOW_2}Zoom para posição do cursor"
2137: "{SMALLFONT}{COLOUR BLACK}Quando ativado, o zoom centralizará no cursor ao invés do centro da tela"
2138: "{COLOUR WINDOW_2}Tocar música do menu"
2139: "Sair para o menu"
2140: "Sair do OpenLoco"
2141: "{COLOUR WINDOW_2}Desabilitar companhias de IA"
2142: "{SMALLFONT}{COLOUR BLACK}Isso desabilita o 'pensamento' da IA, tornando-as inefetivas.{NEWLINE}Em novos jogos, isso também previne que novas companhias de IA se formem."
2143: "{COLOUR WINDOW_2}Quantidade de autosaves:"
2144: "{COLOUR WINDOW_2}Frequência de autosaves:"
2145: "Nunca"
2146: "Todo mês"
2147: "Cada {INT32} meses"
2148: "{COLOUR WINDOW_2}Gerador:"
2149: Original
2150: Melhorado
2151: "Modificar veículo"
2152: "Clonar veículo"
2153: "Impossível clonar veículo..."
2154: "Localizar veículo na visão principal"
2155: "Seguir veículo na visão principal"
2156: "Habilitar menu de trapaças/debugging"
2157: "{SMALLFONT}{COLOUR BLACK}Isso adiciona um botão extra na barra de tarefas do jogo, permitindo fácil acesso a certas trapaças e opções de debug."
2158: "Habilitar modo sandbox"
2159: "Permitir direção manual"
2160: "Permitir construir enquanto pausado"
2161: "Mostrar contador de FPS"
2162: "{SMALLFONT}{COLOUR BLACK}Isso mostrará um contato no topo da tela, indicando o número de frames desenhados por segundo."
2163: "Tirar limite de FPS"
2164: "{SMALLFONT}{COLOUR BLACK}Remove a restrição de renderização a 40Hz."
2165: "Hardware"
2166: "Renderização de mapa"
2167: "Inspetor de Tiles"
2168: "{SMALLFONT}{COLOUR BLACK}Ative para selecionar um tile para inspecionar."
2169: "Superfíce"
2170: "Via"
2171: "Estação"
2172: "Semáforo"
2173: "Construção"
2174: "Árvore"
2175: "Parede"
2176: "Estrada"
2177: "Indústria"
2178: "{COLOUR WINDOW_2} {STRINGID} ({STRINGID})"
2179: "{COLOUR WINDOW_2} {STRINGID} - {STRINGID} ({STRINGID})"
2180: "X:"
2181: "Y:"
2182: "{COLOUR WINDOW_2}{INT16}"
2183: "Dados dos elementos do tile"
2184: "Trapaças"
2185: "Trapaças financeiras"
2186: "Trapaças de companhia"
2187: "Trapaças de veículos"
2188: "Trapaças de municípios"
2189: "Limpar empréstimo"
2190: "Limpar"
2191: "{COLOUR BLACK}{CURRENCY32}"
2192: "Selecionar companhia alvo"
2193: "Selecionar trapaça para aplicar"
2194: "Mudar controle para essa companhia"
2195: "Adquirir todas as posses da companhia"
2196: "Ativar falência"
2197: "Ativar status da cadeia"
2198: "Aumentar fundos"
2199: "{COLOUR WINDOW_2}Quantidade:"
2200: "Adicionar"
2201: "Confiabilidade de veículo"
2202: "Mudar todas para 0 (ilimitada)"
2203: "Mudar todas para 100 (renovação completa)"
2204: "Mudar avaliação da companhia"
2205: "-10% em tudo"
2206: "+10% em tudo"
2207: "Min em tudo"
2208: "Max em tudo"

View File

@ -1220,8 +1220,8 @@ strings:
1213: "{COLOUR WINDOW_2}{STRINGID})"
1214: "Нет места для новых распоряжений!"
1215: "Слишком много распоряжений для данного транспортного средства!"
1216: "- - Местный - -"
1217: "- - Экспресс - -"
1216: "Местный"
1217: "Экспресс"
1218: "{COLOUR BLACK}- - Маршрут не определен - -"
1219: "{COLOUR BLACK}- - Конец списка маршрутов - -"
1220: "Остановка: {STRINGID}"

View File

@ -1244,8 +1244,8 @@ strings:
1213: "{COLOUR WINDOW_2}{STRINGID})"
1214: Nie je priestor pre viac príkazov vozidiel!
1215: Príliš veľa príkazov pre toto vozidlo!
1216: "- - Normálny - -"
1217: "- - Rýchlik - -"
1216: "Normálny"
1217: "Rýchlik"
1218: "{COLOUR BLACK}- - Nie je definovaná žiadna trasa - -"
1219: "{COLOUR BLACK}- - Koniec zoznamu trás - -"
1220: "Zastávka v: {STRINGID}"

2263
data/language/zh-CN.yml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
# http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html
[Desktop Entry]
Type=Application
Version=1.0
Exec=openloco %u
Icon=openloco
Name=OpenLoco
GenericName=Transport management game
GenericName[nl]=Transportbedrijfsimulator
Comment=Open Source re-implementation of Chris Sawyer's Locomotion.
Comment[ca]=Reimplementació de codi obert del Chris Sawyer's Locomotion.
Comment[cs]=Open Source remake hry Chris Sawyer's Locomotion.
Comment[de]=Quelloffene Neuimplementierung von Chris Sawyer's Locomotion.
Comment[es]=Re-implementación de código abierto de Chris Sawyer's Locomotion.
Comment[fr]=Ré-implémentation libre (Open Source) de Chris Sawyer's Locomotion.
Comment[it]=Versione Open Source di Chris Sawyer's Locomotion.
Comment[nb]=Open Source re-implementasjon av Chris Sawyer's Locomotion.
Comment[nl]=Een opensource-versie van Chris Sawyer's Locomotion.
Comment[pt]=Reimplementação em código aberto do Chris Sawyer's Locomotion.
Comment[ru]=Ремейк с открытым исходным кодом игры Chris Sawyer's Locomotion.
Comment[sv]=Open Source om-implementation av Chris Sawyer's Locomotion.
Categories=Game;Simulation;

8
nuget.config Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
<clear />
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

View File

@ -50,7 +50,7 @@
C4549: 'operator': operator before comma has no effect; did you intend 'operator'?
C4555: expression has no effect; expected expression with side-effect
-->
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;_USE_MATH_DEFINES;SDL_MAIN_HANDLED;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;_USE_MATH_DEFINES;SDL_MAIN_HANDLED;_WINSOCK_DEPRECATED_NO_WARNINGS;USE_BREAKPAD;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<TreatWarningAsError>true</TreatWarningAsError>
<AdditionalOptions>/utf-8 /std:c++17 /permissive-</AdditionalOptions>

View File

@ -2,16 +2,16 @@
#include "../Config.h"
#include "../Console.h"
#include "../Date.h"
#include "../Entities/EntityManager.h"
#include "../Environment.h"
#include "../Interop/Interop.hpp"
#include "../Localisation/StringIds.h"
#include "../Map/TileManager.h"
#include "../Objects/ObjectManager.h"
#include "../Objects/SoundObject.h"
#include "../Things/ThingManager.h"
#include "../Things/Vehicle.h"
#include "../Ui/WindowManager.h"
#include "../Utility/Stream.hpp"
#include "../Vehicles/Vehicle.h"
#include "Channel.h"
#include "MusicChannel.h"
#include "VehicleChannel.h"
@ -45,7 +45,7 @@ namespace OpenLoco::Audio
int16_t cbSize;
};
struct sound_object_data
struct SoundObjectData
{
int32_t var_00;
int32_t offset;
@ -54,12 +54,12 @@ namespace OpenLoco::Audio
const void* pcm()
{
return (void*)((uintptr_t)this + sizeof(sound_object_data));
return (void*)((uintptr_t)this + sizeof(SoundObjectData));
}
};
#pragma pack(pop)
struct audio_format
struct AudioFormat
{
int32_t frequency{};
int32_t format{};
@ -80,20 +80,20 @@ namespace OpenLoco::Audio
static uint8_t _numActiveVehicleSounds; // 0x0112C666
static std::vector<std::string> _devices;
static audio_format _outputFormat;
static std::array<channel, 4> _channels;
static std::array<vehicle_channel, 10> _vehicle_channels;
static music_channel _music_channel;
static channel_id _music_current_channel = channel_id::bgm;
static AudioFormat _outputFormat;
static std::array<Channel, 4> _channels;
static std::array<VehicleChannel, 10> _vehicle_channels;
static MusicChannel _music_channel;
static ChannelId _music_current_channel = ChannelId::bgm;
static std::vector<sample> _samples;
static std::unordered_map<uint16_t, sample> _object_samples;
static std::vector<Sample> _samples;
static std::unordered_map<uint16_t, Sample> _object_samples;
static void playSound(sound_id id, loc16 loc, int32_t volume, int32_t pan, int32_t frequency);
static void mixSound(sound_id id, bool loop, int32_t volume, int32_t pan, int32_t freq);
static void playSound(SoundId id, const Map::Pos3& loc, int32_t volume, int32_t pan, int32_t frequency);
static void mixSound(SoundId id, bool loop, int32_t volume, int32_t pan, int32_t freq);
// 0x004FE910
static const music_info MusicInfo[] = {
static const MusicInfo _musicInfo[] = {
{ path_id::music_20s1, StringIds::music_chuggin_along, 1925, 1933 },
{ path_id::music_20s2, StringIds::music_long_dusty_road, 1927, 1935 },
{ path_id::music_20s4, StringIds::music_flying_high, 1932, 1940 },
@ -125,9 +125,9 @@ namespace OpenLoco::Audio
{ path_id::music_20s6, StringIds::music_sandy_track_blues, 1921, 1929 }
};
static constexpr bool isMusicChannel(channel_id id)
static constexpr bool isMusicChannel(ChannelId id)
{
return (id == channel_id::bgm || id == channel_id::title);
return (id == ChannelId::bgm || id == ChannelId::title);
}
int32_t volumeLocoToSDL(int32_t loco)
@ -135,7 +135,7 @@ namespace OpenLoco::Audio
return (int)(SDL_MIX_MAXVOLUME * (SDL_pow(10, (float)loco / 2000)));
}
static channel* getChannel(channel_id id)
static Channel* getChannel(ChannelId id)
{
auto index = (size_t)id;
if (index < _channels.size())
@ -145,7 +145,7 @@ namespace OpenLoco::Audio
return nullptr;
}
static sample loadSoundFromWaveMemory(const WAVEFORMATEX& format, const void* pcm, size_t pcmLen)
static Sample loadSoundFromWaveMemory(const WAVEFORMATEX& format, const void* pcm, size_t pcmLen)
{
// Build a CVT to convert the audio
const auto& dstFormat = _outputFormat;
@ -167,7 +167,7 @@ namespace OpenLoco::Audio
else if (cr == 0)
{
// No conversion necessary
sample s;
Sample s;
s.pcm = std::malloc(pcmLen);
if (s.pcm == nullptr)
{
@ -201,7 +201,7 @@ namespace OpenLoco::Audio
throw std::bad_alloc();
}
sample s;
Sample s;
s.pcm = cvt.buf;
s.len = cvt.len_cvt;
s.chunk = Mix_QuickLoad_RAW(cvt.buf, cvt.len_cvt);
@ -209,10 +209,10 @@ namespace OpenLoco::Audio
}
}
static std::vector<sample> loadSoundsFromCSS(const fs::path& path)
static std::vector<Sample> loadSoundsFromCSS(const fs::path& path)
{
Console::logVerbose("loadSoundsFromCSS(%s)", path.string().c_str());
std::vector<sample> results;
std::vector<Sample> results;
std::ifstream fs(path, std::ios::in | std::ios::binary);
if (fs.is_open())
@ -254,8 +254,8 @@ namespace OpenLoco::Audio
static void disposeChannels()
{
std::generate(_channels.begin(), _channels.end(), []() { return channel(); });
std::generate(_vehicle_channels.begin(), _vehicle_channels.end(), []() { return vehicle_channel(); });
std::generate(_channels.begin(), _channels.end(), []() { return Channel(); });
std::generate(_vehicle_channels.begin(), _vehicle_channels.end(), []() { return VehicleChannel(); });
}
static void reinitialise()
@ -330,11 +330,11 @@ namespace OpenLoco::Audio
for (size_t i = 0; i < _channels.size(); i++)
{
_channels[i] = channel(i);
_channels[i] = Channel(i);
}
for (size_t i = 0; i < _vehicle_channels.size(); i++)
{
_vehicle_channels[i] = vehicle_channel(channel(4 + i));
_vehicle_channels[i] = VehicleChannel(Channel(4 + i));
}
auto css1path = Environment::getPath(Environment::path_id::css1);
@ -348,11 +348,17 @@ namespace OpenLoco::Audio
disposeSamples();
disposeChannels();
_music_channel = {};
_music_current_channel = (channel_id)-1;
_music_current_channel = (ChannelId)-1;
Mix_CloseAudio();
_audio_initialised = 0;
}
// 0x00489BA1
void close()
{
call(0x00489BA1);
}
#ifdef __HAS_DEFAULT_DEVICE__
static const char* getDefaultDeviceName()
{
@ -461,13 +467,13 @@ namespace OpenLoco::Audio
_audioIsPaused = false;
}
static sound_object* getSoundObject(sound_id id)
static SoundObject* getSoundObject(SoundId id)
{
auto idx = (int32_t)id & ~0x8000;
return ObjectManager::get<sound_object>(idx);
return ObjectManager::get<SoundObject>(idx);
}
static viewport* findBestViewportForSound(viewport_pos vpos)
static Viewport* findBestViewportForSound(viewport_pos vpos)
{
auto w = WindowManager::find(WindowType::main, 0);
if (w != nullptr)
@ -495,7 +501,7 @@ namespace OpenLoco::Audio
return nullptr;
}
static int32_t getVolumeForSoundId(sound_id id)
static int32_t getVolumeForSoundId(SoundId id)
{
if (isObjectSoundId(id))
{
@ -513,7 +519,7 @@ namespace OpenLoco::Audio
}
}
static int32_t calculateVolumeFromViewport(sound_id id, const Map::map_pos3& mpos, const viewport& viewport)
static int32_t calculateVolumeFromViewport(SoundId id, const Map::Pos3& mpos, const Viewport& viewport)
{
auto volume = 0;
auto zVol = 0;
@ -533,17 +539,17 @@ namespace OpenLoco::Audio
return volume;
}
void playSound(sound_id id, loc16 loc)
void playSound(SoundId id, const Map::Pos3& loc)
{
playSound(id, loc, play_at_location);
}
void playSound(sound_id id, int32_t pan)
void playSound(SoundId id, int32_t pan)
{
playSound(id, {}, pan);
}
static vehicle_channel* getFreeVehicleChannel()
static VehicleChannel* getFreeVehicleChannel()
{
for (auto& vc : _vehicle_channels)
{
@ -555,7 +561,7 @@ namespace OpenLoco::Audio
return nullptr;
}
bool shouldSoundLoop(sound_id id)
bool shouldSoundLoop(SoundId id)
{
loco_global<uint8_t[64], 0x0050D514> unk_50D514;
if (isObjectSoundId(id))
@ -570,7 +576,7 @@ namespace OpenLoco::Audio
}
// 0x0048A4BF
void playSound(vehicle_26* v)
void playSound(Vehicles::Vehicle2or6* v)
{
if (v->var_4A & 1)
{
@ -584,20 +590,20 @@ namespace OpenLoco::Audio
}
// 0x00489F1B
void playSound(sound_id id, loc16 loc, int32_t volume, int32_t frequency)
void playSound(SoundId id, const Map::Pos3& loc, int32_t volume, int32_t frequency)
{
playSound(id, loc, volume, play_at_location, frequency);
}
// 0x00489CB5
void playSound(sound_id id, loc16 loc, int32_t pan)
void playSound(SoundId id, const Map::Pos3& loc, int32_t pan)
{
playSound(id, loc, 0, pan, 0);
}
// 0x00489CB5 / 0x00489F1B
// pan is in UI pixels or known constant
void playSound(sound_id id, loc16 loc, int32_t volume, int32_t pan, int32_t frequency)
void playSound(SoundId id, const Map::Pos3& loc, int32_t volume, int32_t pan, int32_t frequency)
{
loco_global<int32_t, 0x00e3f0b8> current_rotation;
@ -606,15 +612,15 @@ namespace OpenLoco::Audio
volume += getVolumeForSoundId(id);
if (pan == play_at_location)
{
auto vpos = viewport::mapFrom3d(loc, current_rotation);
auto vpos = Map::gameToScreen(loc, current_rotation);
auto viewport = findBestViewportForSound(vpos);
if (viewport == nullptr)
{
return;
}
volume += calculateVolumeFromViewport(id, { loc.x, loc.y }, *viewport);
pan = viewport->mapToUi(vpos).x;
volume += calculateVolumeFromViewport(id, loc, *viewport);
pan = viewport->viewportToScreen(vpos).x;
if (volume < -10000)
{
return;
@ -640,7 +646,7 @@ namespace OpenLoco::Audio
}
}
sample* getSoundSample(sound_id id)
Sample* getSoundSample(SoundId id)
{
if (isObjectSoundId(id))
{
@ -651,7 +657,7 @@ namespace OpenLoco::Audio
auto obj = getSoundObject(id);
if (obj != nullptr)
{
auto data = (sound_object_data*)obj->data;
auto data = (SoundObjectData*)obj->data;
assert(data->offset == 8);
auto sample = loadSoundFromWaveMemory(data->pcm_header, data->pcm(), data->length);
_object_samples[static_cast<size_t>(id)] = sample;
@ -670,7 +676,7 @@ namespace OpenLoco::Audio
return nullptr;
}
static void mixSound(sound_id id, bool loop, int32_t volume, int32_t pan, int32_t freq)
static void mixSound(SoundId id, bool loop, int32_t volume, int32_t pan, int32_t freq)
{
Console::logVerbose("mixSound(%d, %s, %d, %d, %d)", (int32_t)id, loop ? "true" : "false", volume, pan, freq);
auto sample = getSoundSample(id);
@ -691,7 +697,7 @@ namespace OpenLoco::Audio
{
}
static bool loadChannel(channel_id id, const fs::path& path, int32_t c)
static bool loadChannel(ChannelId id, const fs::path& path, int32_t c)
{
Console::logVerbose("loadChannel(%d, %s, %d)", id, path.string().c_str(), c);
if (isMusicChannel(id))
@ -715,13 +721,13 @@ namespace OpenLoco::Audio
}
// 0x0040194E
bool loadChannel(channel_id id, const char* path, int32_t c)
bool loadChannel(ChannelId id, const char* path, int32_t c)
{
return loadChannel(id, fs::path(path), c);
return loadChannel(id, fs::u8path(path), c);
}
// 0x00401999
bool playChannel(channel_id id, int32_t loop, int32_t volume, int32_t d, int32_t freq)
bool playChannel(ChannelId id, int32_t loop, int32_t volume, int32_t d, int32_t freq)
{
Console::logVerbose("playChannel(%d, %d, %d, %d, %d)", id, loop, volume, d, freq);
if (isMusicChannel(id))
@ -745,7 +751,7 @@ namespace OpenLoco::Audio
}
// 0x00401A05
void stopChannel(channel_id id)
void stopChannel(ChannelId id)
{
Console::logVerbose("stopChannel(%d)", id);
if (isMusicChannel(id))
@ -766,7 +772,7 @@ namespace OpenLoco::Audio
}
// 0x00401AD3
void setChannelVolume(channel_id id, int32_t volume)
void setChannelVolume(ChannelId id, int32_t volume)
{
Console::logVerbose("setChannelVolume(%d, %d)", id, volume);
if (isMusicChannel(id))
@ -787,7 +793,7 @@ namespace OpenLoco::Audio
}
// 0x00401B10
bool isChannelPlaying(channel_id id)
bool isChannelPlaying(ChannelId id)
{
if (isMusicChannel(id))
{
@ -804,12 +810,12 @@ namespace OpenLoco::Audio
return false;
}
static void sub_48A274(vehicle_26* v)
static void sub_48A274(Vehicles::Vehicle2or6* v)
{
if (v == nullptr)
return;
if (v->sound_id == SoundObjectId::null)
if (v->drivingSoundId == SoundObjectId::null)
return;
// TODO: left or top?
@ -873,7 +879,7 @@ namespace OpenLoco::Audio
}
}
static void off_4FEB58(vehicle_26* v, int32_t x)
static void off_4FEB58(Vehicles::Vehicle2or6* v, int32_t x)
{
switch (x)
{
@ -905,11 +911,11 @@ namespace OpenLoco::Audio
_numActiveVehicleSounds = 0;
}
for (auto v : ThingManager::VehicleList())
for (auto v : EntityManager::VehicleList())
{
Things::Vehicle::Vehicle train(v);
off_4FEB58(reinterpret_cast<vehicle_26*>(train.veh2), x);
off_4FEB58(reinterpret_cast<vehicle_26*>(train.tail), x);
Vehicles::Vehicle train(v);
off_4FEB58(reinterpret_cast<Vehicles::Vehicle2or6*>(train.veh2), x);
off_4FEB58(reinterpret_cast<Vehicles::Vehicle2or6*>(train.tail), x);
}
}
@ -956,7 +962,7 @@ namespace OpenLoco::Audio
loco_global<uint32_t, 0x0050D5AC> _50D5AC;
if (_audio_initialised && _50D5AC != 1)
{
stopChannel(channel_id::ambient);
stopChannel(ChannelId::ambient);
_50D5AC = 1;
}
}
@ -964,7 +970,7 @@ namespace OpenLoco::Audio
// 0x0048AA0C
void revalidateCurrentTrack()
{
using music_playlist_type = Config::music_playlist_type;
using MusicPlaylistType = Config::MusicPlaylistType;
auto cfg = Config::get();
if (_currentSong == no_song)
@ -973,19 +979,19 @@ namespace OpenLoco::Audio
bool trackStillApplies = true;
switch (cfg.music_playlist)
{
case music_playlist_type::current_era:
case MusicPlaylistType::currentEra:
{
auto currentYear = getCurrentYear();
auto info = MusicInfo[_currentSong];
auto info = _musicInfo[_currentSong];
if (currentYear < info.start_year || currentYear > info.end_year)
trackStillApplies = false;
break;
}
case music_playlist_type::all:
case MusicPlaylistType::all:
return;
case music_playlist_type::custom:
case MusicPlaylistType::custom:
if (!cfg.enabled_music[_currentSong])
trackStillApplies = false;
break;
@ -1001,7 +1007,7 @@ namespace OpenLoco::Audio
static int32_t chooseNextMusicTrack(int32_t excludeTrack)
{
using music_playlist_type = Config::music_playlist_type;
using MusicPlaylistType = Config::MusicPlaylistType;
static std::vector<uint8_t> playlist;
playlist.clear();
@ -1009,12 +1015,12 @@ namespace OpenLoco::Audio
auto cfg = Config::get();
switch (cfg.music_playlist)
{
case music_playlist_type::current_era:
case MusicPlaylistType::currentEra:
{
auto currentYear = getCurrentYear();
for (auto i = 0; i < num_music_tracks; i++)
{
const auto& mi = MusicInfo[i];
const auto& mi = _musicInfo[i];
if (currentYear >= mi.start_year && currentYear <= mi.end_year)
{
if (i != excludeTrack)
@ -1025,7 +1031,7 @@ namespace OpenLoco::Audio
}
break;
}
case music_playlist_type::all:
case MusicPlaylistType::all:
for (auto i = 0; i < num_music_tracks; i++)
{
if (i != excludeTrack)
@ -1034,7 +1040,7 @@ namespace OpenLoco::Audio
}
}
break;
case music_playlist_type::custom:
case MusicPlaylistType::custom:
for (auto i = 0; i < num_music_tracks; i++)
{
if (i != excludeTrack && (cfg.enabled_music[i] & 1))
@ -1059,7 +1065,7 @@ namespace OpenLoco::Audio
}
else
{
const auto& mi = MusicInfo[excludeTrack];
const auto& mi = _musicInfo[excludeTrack];
auto currentYear = getCurrentYear();
if (currentYear >= mi.start_year && currentYear <= mi.end_year)
{
@ -1089,14 +1095,28 @@ namespace OpenLoco::Audio
if (!_music_channel.isPlaying())
{
_currentSong = chooseNextMusicTrack(_lastSong);
_lastSong = _currentSong;
// Not playing, but the 'current song' is last song? It's been requested manually!
bool requestedSong = _lastSong != no_song && _lastSong == _currentSong;
const auto& mi = MusicInfo[_currentSong];
// Choose a track to play, unless we have requested one track in particular.
if (_currentSong == no_song || !requestedSong)
{
auto trackToExclude = _lastSong;
_lastSong = _currentSong;
_currentSong = chooseNextMusicTrack(trackToExclude);
}
else
{
// We're choosing this one, but the next one should be decided automatically again.
_lastSong = no_song;
}
// Load info on the song to play.
const auto& mi = _musicInfo[_currentSong];
auto path = Environment::getPath((path_id)mi.path_id);
if (_music_channel.load(path))
{
_music_current_channel = channel_id::bgm;
_music_current_channel = ChannelId::bgm;
if (!_music_channel.play(false))
{
cfg.music_playing = 0;
@ -1111,6 +1131,14 @@ namespace OpenLoco::Audio
}
}
// 0x0048AAD2
void resetMusic()
{
stopBackgroundMusic();
_currentSong = no_song;
_lastSong = no_song;
}
// 0x0048AAE8
void stopBackgroundMusic()
{
@ -1125,20 +1153,20 @@ namespace OpenLoco::Audio
{
if (isTitleMode() && _audio_initialised && _audioIsEnabled && Config::getNew().audio.play_title_music)
{
if (!isChannelPlaying(channel_id::title))
if (!isChannelPlaying(ChannelId::title))
{
auto path = Environment::getPath(path_id::css5);
if (loadChannel(channel_id::title, path, 0))
if (loadChannel(ChannelId::title, path, 0))
{
playChannel(channel_id::title, 1, -500, 0, 0);
playChannel(ChannelId::title, 1, -500, 0, 0);
}
}
}
else
{
if (isChannelPlaying(channel_id::title))
if (isChannelPlaying(ChannelId::title))
{
stopChannel(channel_id::title);
stopChannel(ChannelId::title);
}
}
}
@ -1146,9 +1174,9 @@ namespace OpenLoco::Audio
// 0x0048AC2B
void stopTitleMusic()
{
if (isChannelPlaying(channel_id::title))
if (isChannelPlaying(ChannelId::title))
{
stopChannel(channel_id::title);
stopChannel(ChannelId::title);
}
}
@ -1157,8 +1185,26 @@ namespace OpenLoco::Audio
return _audioIsEnabled;
}
const music_info* getMusicInfo(music_id track)
const MusicInfo* getMusicInfo(MusicId track)
{
return &MusicInfo[track];
return &_musicInfo[track];
}
// 0x0048AA67
void setBgmVolume(int32_t volume)
{
if (Config::get().volume == volume)
{
return;
}
auto& cfg = Config::get();
cfg.volume = volume;
Config::write();
if (_audio_initialised && _currentSong != no_song && isChannelPlaying(ChannelId::bgm))
{
setChannelVolume(ChannelId::bgm, volume);
}
}
}

View File

@ -1,6 +1,8 @@
#pragma once
#include "../Environment.h"
#include "../Location.hpp"
#include "../Map/Map.hpp"
#include "../Types.hpp"
#include <string>
#include <tuple>
@ -8,14 +10,14 @@
struct Mix_Chunk;
namespace OpenLoco
namespace OpenLoco::Vehicles
{
struct vehicle_26;
struct Vehicle2or6;
}
namespace OpenLoco::Audio
{
struct sample
struct Sample
{
void* pcm{};
size_t len{};
@ -23,45 +25,45 @@ namespace OpenLoco::Audio
};
// TODO: This should only be a byte needs to be split off from sound object
enum class sound_id : uint16_t
enum class SoundId : uint16_t
{
click_down = 0,
click_up = 1,
click_press = 2,
clickDown = 0,
clickUp = 1,
clickPress = 2,
construct = 3,
demolish = 4,
income = 5,
crash = 6,
water = 7,
splash_1 = 8,
splash_2 = 9,
splash1 = 8,
splash2 = 9,
waypoint = 10,
notification = 11,
open_window = 12,
openWindow = 12,
applause_1 = 13,
error = 14,
unk_15 = 15,
unk_16 = 16,
demolish_tree = 17,
demolish_building = 18,
demolishTree = 17,
demolishBuilding = 18,
unk_19 = 19,
unk_20 = 20,
construct_ship = 21,
vehiclePickup = 20,
constructShip = 21,
ticker = 22,
applause_2 = 23,
news_oooh = 24,
news_awww = 25,
breakdown_1 = 26,
breakdown_2 = 27,
breakdown_3 = 28,
breakdown_4 = 29,
breakdown_5 = 30,
breakdown_6 = 31,
applause2 = 23,
newsOooh = 24,
newsAwww = 25,
breakdown1 = 26,
breakdown2 = 27,
breakdown3 = 28,
breakdown4 = 29,
breakdown5 = 30,
breakdown6 = 31,
null = 0xFF
};
enum class channel_id
enum class ChannelId
{
bgm,
unk_1,
@ -71,9 +73,9 @@ namespace OpenLoco::Audio
};
constexpr int32_t num_reserved_channels = 4 + 10;
using music_id = uint8_t;
using MusicId = uint8_t;
struct music_info
struct MusicInfo
{
Environment::path_id path_id;
string_id title_id;
@ -83,30 +85,32 @@ namespace OpenLoco::Audio
void initialiseDSound();
void disposeDSound();
void close();
const std::vector<std::string>& getDevices();
const char* getCurrentDeviceName();
size_t getCurrentDevice();
void setDevice(size_t index);
sample* getSoundSample(sound_id id);
bool shouldSoundLoop(sound_id id);
Sample* getSoundSample(SoundId id);
bool shouldSoundLoop(SoundId id);
void toggleSound();
void pauseSound();
void unpauseSound();
void playSound(vehicle_26* t);
void playSound(sound_id id, loc16 loc);
void playSound(sound_id id, loc16 loc, int32_t pan);
void playSound(sound_id id, int32_t pan);
void playSound(sound_id id, loc16 loc, int32_t volume, int32_t frequency);
void playSound(Vehicles::Vehicle2or6* t);
void playSound(SoundId id, const Map::Pos3& loc);
void playSound(SoundId id, const Map::Pos3& loc, int32_t pan);
void playSound(SoundId id, int32_t pan);
void playSound(SoundId id, const Map::Pos3& loc, int32_t volume, int32_t frequency);
void updateSounds();
bool loadChannel(channel_id id, const char* path, int32_t c);
bool playChannel(channel_id id, int32_t loop, int32_t volume, int32_t d, int32_t freq);
void stopChannel(channel_id id);
void setChannelVolume(channel_id id, int32_t volume);
bool isChannelPlaying(channel_id id);
bool loadChannel(ChannelId id, const char* path, int32_t c);
bool playChannel(ChannelId id, int32_t loop, int32_t volume, int32_t d, int32_t freq);
void stopChannel(ChannelId id);
void setChannelVolume(ChannelId id, int32_t volume);
bool isChannelPlaying(ChannelId id);
void setBgmVolume(int32_t volume);
void updateVehicleNoise();
void stopVehicleNoise();
@ -116,6 +120,7 @@ namespace OpenLoco::Audio
void revalidateCurrentTrack();
void resetMusic();
void playBackgroundMusic();
void stopBackgroundMusic();
void playTitleScreenMusic();
@ -123,7 +128,7 @@ namespace OpenLoco::Audio
bool isAudioEnabled();
const music_info* getMusicInfo(music_id track);
const MusicInfo* getMusicInfo(MusicId track);
constexpr int32_t num_music_tracks = 29;
/**
@ -133,35 +138,35 @@ namespace OpenLoco::Audio
*/
int32_t volumeLocoToSDL(int32_t loco);
constexpr bool isObjectSoundId(sound_id id)
constexpr bool isObjectSoundId(SoundId id)
{
return ((int32_t)id & 0x8000);
}
constexpr sound_id makeObjectSoundId(sound_object_id_t id)
constexpr SoundId makeObjectSoundId(SoundObjectId_t id)
{
return (sound_id)((int32_t)id | 0x8000);
return (SoundId)((int32_t)id | 0x8000);
}
/**
* Converts a Locomotion pan range to a left and right value for SDL2 mixer.
*/
constexpr std::tuple<int32_t, int32_t> panLocoToSDL(int32_t pan)
constexpr std::pair<int32_t, int32_t> panLocoToSDL(int32_t pan)
{
constexpr auto range = 2048.0f;
if (pan == 0)
{
return std::make_tuple(0, 0);
return { 0, 0 };
}
else if (pan < 0)
{
auto r = (int32_t)(255 - ((pan / -range) * 255));
return std::make_tuple(255, r);
return { 255, r };
}
else
{
auto r = (int32_t)(255 - ((pan / range) * 255));
return std::make_tuple(r, 255);
return { r, 255 };
}
}
}

View File

@ -6,19 +6,19 @@
using namespace OpenLoco::Audio;
channel::channel(int32_t cid)
Channel::Channel(int32_t cid)
: _id(cid)
{
}
channel::channel(channel&& c)
Channel::Channel(Channel&& c)
: _id(std::exchange(c._id, undefined_id))
, _chunk(std::exchange(c._chunk, nullptr))
, _chunk_owner(std::exchange(c._chunk_owner, {}))
{
}
channel& channel::operator=(channel&& other)
Channel& Channel::operator=(Channel&& other)
{
std::swap(_id, other._id);
std::swap(_chunk, other._chunk);
@ -26,19 +26,19 @@ channel& channel::operator=(channel&& other)
return *this;
}
channel::~channel()
Channel::~Channel()
{
disposeChunk();
}
bool channel::load(sample& sample)
bool Channel::load(Sample& sample)
{
disposeChunk();
_chunk = sample.chunk;
return true;
}
bool channel::load(const fs::path& path)
bool Channel::load(const fs::path& path)
{
disposeChunk();
_chunk = Mix_LoadWAV(path.u8string().c_str());
@ -46,7 +46,7 @@ bool channel::load(const fs::path& path)
return _chunk != nullptr;
}
bool channel::play(bool loop)
bool Channel::play(bool loop)
{
if (!isUndefined())
{
@ -61,7 +61,7 @@ bool channel::play(bool loop)
return false;
}
void channel::stop()
void Channel::stop()
{
if (!isUndefined())
{
@ -69,7 +69,7 @@ void channel::stop()
}
}
void channel::setVolume(int32_t volume)
void Channel::setVolume(int32_t volume)
{
if (!isUndefined())
{
@ -77,7 +77,7 @@ void channel::setVolume(int32_t volume)
}
}
void channel::setPan(int32_t pan)
void Channel::setPan(int32_t pan)
{
if (!isUndefined())
{
@ -86,17 +86,17 @@ void channel::setPan(int32_t pan)
}
}
void channel::setFrequency(int32_t freq)
void Channel::setFrequency(int32_t freq)
{
// TODO
}
bool channel::isPlaying() const
bool Channel::isPlaying() const
{
return isUndefined() ? false : (Mix_Playing(_id) != 0);
}
void channel::disposeChunk()
void Channel::disposeChunk()
{
if (_chunk_owner)
{

View File

@ -6,9 +6,9 @@ struct Mix_Chunk;
namespace OpenLoco::Audio
{
struct sample;
struct Sample;
class channel
class Channel
{
public:
static constexpr int32_t undefined_id = -1;
@ -19,13 +19,13 @@ namespace OpenLoco::Audio
bool _chunk_owner{};
public:
channel() = default;
channel(int32_t id);
channel(const channel&) = delete;
channel(channel&&);
channel& operator=(channel&& other);
~channel();
bool load(sample& sample);
Channel() = default;
Channel(int32_t id);
Channel(const Channel&) = delete;
Channel(Channel&&);
Channel& operator=(Channel&& other);
~Channel();
bool load(Sample& sample);
bool load(const fs::path& path);
bool play(bool loop);
void stop();

View File

@ -3,17 +3,17 @@
using namespace OpenLoco::Audio;
music_channel::~music_channel()
MusicChannel::~MusicChannel()
{
disposeMusic();
}
bool music_channel::isPlaying() const
bool MusicChannel::isPlaying() const
{
return Mix_PlayingMusic() != 0;
}
bool music_channel::load(const fs::path& path)
bool MusicChannel::load(const fs::path& path)
{
auto paths = path.u8string();
auto music = Mix_LoadMUS(paths.c_str());
@ -26,7 +26,7 @@ bool music_channel::load(const fs::path& path)
return false;
}
bool music_channel::play(bool loop)
bool MusicChannel::play(bool loop)
{
if (_music_track != nullptr)
{
@ -39,17 +39,17 @@ bool music_channel::play(bool loop)
return false;
}
void music_channel::stop()
void MusicChannel::stop()
{
Mix_HaltMusic();
}
void music_channel::setVolume(int32_t volume)
void MusicChannel::setVolume(int32_t volume)
{
Mix_VolumeMusic(volumeLocoToSDL(volume));
}
void music_channel::disposeMusic()
void MusicChannel::disposeMusic()
{
Mix_FreeMusic(_music_track);
_music_track = nullptr;

View File

@ -1,7 +1,7 @@
#pragma once
#include "../Core/FileSystem.hpp"
#include "../Things/Thing.h"
#include "../Entities/Entity.h"
#include "Audio.h"
#include "Channel.h"
@ -10,16 +10,16 @@ typedef struct _Mix_Music Mix_Music;
namespace OpenLoco::Audio
{
class music_channel
class MusicChannel
{
private:
Mix_Music* _music_track;
int32_t _current_music = -1;
public:
music_channel() = default;
music_channel(const music_channel&) = delete;
~music_channel();
MusicChannel() = default;
MusicChannel(const MusicChannel&) = delete;
~MusicChannel();
bool isPlaying() const;

View File

@ -1,34 +1,34 @@
#include "VehicleChannel.h"
#include "../Entities/EntityManager.h"
#include "../Interop/Interop.hpp"
#include "../Things/ThingManager.h"
#include "../Things/Vehicle.h"
#include "../Vehicles/Vehicle.h"
using namespace OpenLoco;
using namespace OpenLoco::Audio;
using namespace OpenLoco::Interop;
static std::tuple<sound_id, channel_attributes> sub_48A590(const vehicle* v)
static std::pair<SoundId, ChannelAttributes> sub_48A590(const Vehicles::Vehicle2or6* v)
{
registers regs;
regs.esi = (int32_t)v;
regs.esi = X86Pointer(v);
call(0x0048A590, regs);
return std::make_tuple<sound_id, channel_attributes>((sound_id)regs.eax, { regs.ecx, regs.edx, regs.ebx });
return { static_cast<SoundId>(regs.eax), { regs.ecx, regs.edx, regs.ebx } };
}
vehicle_channel::vehicle_channel(channel&& c)
VehicleChannel::VehicleChannel(Channel&& c)
: _channel(std::exchange(c, {}))
{
}
vehicle_channel::vehicle_channel(vehicle_channel&& vc)
VehicleChannel::VehicleChannel(VehicleChannel&& vc)
: _channel(std::exchange(vc._channel, {}))
, _vehicle_id(std::exchange(vc._vehicle_id, ThingId::null))
, _vehicle_id(std::exchange(vc._vehicle_id, EntityId::null))
, _sound_id(std::exchange(vc._sound_id, {}))
, _attributes(std::exchange(vc._attributes, {}))
{
}
vehicle_channel& vehicle_channel::operator=(vehicle_channel&& other)
VehicleChannel& VehicleChannel::operator=(VehicleChannel&& other)
{
std::swap(_channel, other._channel);
std::swap(_vehicle_id, other._vehicle_id);
@ -37,62 +37,70 @@ vehicle_channel& vehicle_channel::operator=(vehicle_channel&& other)
return *this;
}
void vehicle_channel::begin(thing_id_t vid)
void VehicleChannel::begin(EntityId_t vid)
{
auto v = ThingManager::get<vehicle>(vid);
if (v != nullptr)
auto v = EntityManager::get<Vehicles::VehicleBase>(vid);
if (v != nullptr && v->isVehicle2Or6())
{
auto [sid, sa] = sub_48A590(v);
auto loop = Audio::shouldSoundLoop(sid);
auto sample = Audio::getSoundSample(sid);
if (sample != nullptr)
auto* veh26 = v->asVehicle2Or6();
if (veh26 != nullptr)
{
_vehicle_id = vid;
_sound_id = sid;
_attributes = sa;
auto [sid, sa] = sub_48A590(veh26);
auto loop = Audio::shouldSoundLoop(sid);
auto sample = Audio::getSoundSample(sid);
if (sample != nullptr)
{
_vehicle_id = vid;
_sound_id = sid;
_attributes = sa;
_channel.load(*sample);
_channel.play(loop);
_channel.setVolume(sa.volume);
_channel.setPan(sa.pan);
_channel.setFrequency(sa.freq);
_channel.load(*sample);
_channel.play(loop);
_channel.setVolume(sa.volume);
_channel.setPan(sa.pan);
_channel.setFrequency(sa.freq);
}
}
}
}
void vehicle_channel::update()
void VehicleChannel::update()
{
if (!isFree())
{
auto v = ThingManager::get<vehicle>(_vehicle_id);
if (v != nullptr && v->base_type == thing_base_type::vehicle && (v->type == VehicleThingType::vehicle_2 || v->type == VehicleThingType::tail) && (v->var_4A & 1))
auto v = EntityManager::get<Vehicles::VehicleBase>(_vehicle_id);
if (v != nullptr && v->isVehicle2Or6())
{
auto [sid, sa] = sub_48A590(v);
if (_sound_id == sid)
auto* veh26 = v->asVehicle2Or6();
if (veh26 != nullptr && (veh26->var_4A & 1))
{
v->var_4A &= ~1;
if (_attributes.volume != sa.volume)
auto [sid, sa] = sub_48A590(veh26);
if (_sound_id == sid)
{
_channel.setVolume(sa.volume);
veh26->var_4A &= ~1;
if (_attributes.volume != sa.volume)
{
_channel.setVolume(sa.volume);
}
if (_attributes.pan != sa.pan)
{
_channel.setPan(sa.pan);
}
if (_attributes.freq != sa.freq)
{
_channel.setFrequency(sa.freq);
}
_attributes = sa;
return;
}
if (_attributes.pan != sa.pan)
{
_channel.setPan(sa.pan);
}
if (_attributes.freq != sa.freq)
{
_channel.setFrequency(sa.freq);
}
_attributes = sa;
return;
}
}
stop();
}
}
void vehicle_channel::stop()
void VehicleChannel::stop()
{
_channel.stop();
_vehicle_id = ThingId::null;
_vehicle_id = EntityId::null;
}

View File

@ -1,36 +1,36 @@
#pragma once
#include "../Things/Thing.h"
#include "../Entities/Entity.h"
#include "Audio.h"
#include "Channel.h"
namespace OpenLoco::Audio
{
struct channel_attributes
struct ChannelAttributes
{
int32_t volume{};
int32_t pan{};
int32_t freq{};
};
class vehicle_channel
class VehicleChannel
{
private:
channel _channel;
thing_id_t _vehicle_id = ThingId::null;
sound_id _sound_id{};
channel_attributes _attributes;
Channel _channel;
EntityId_t _vehicle_id = EntityId::null;
SoundId _sound_id{};
ChannelAttributes _attributes;
public:
vehicle_channel() = default;
vehicle_channel(const vehicle_channel& c) = delete;
explicit vehicle_channel(channel&& c);
vehicle_channel(vehicle_channel&& c);
vehicle_channel& operator=(vehicle_channel&& other);
VehicleChannel() = default;
VehicleChannel(const VehicleChannel& c) = delete;
explicit VehicleChannel(Channel&& c);
VehicleChannel(VehicleChannel&& c);
VehicleChannel& operator=(VehicleChannel&& other);
bool isFree() const { return _vehicle_id == ThingId::null; }
bool isFree() const { return _vehicle_id == EntityId::null; }
void begin(thing_id_t vid);
void begin(EntityId_t vid);
void update();
void stop();
};

View File

@ -1,9 +1,12 @@
#include "Company.h"
#include "Entities/EntityManager.h"
#include "Graphics/Gfx.h"
#include "Interop/Interop.hpp"
#include "Localisation/FormatArguments.hpp"
#include "Localisation/StringIds.h"
#include "Things/ThingManager.h"
#include "Map/TileManager.h"
#include "Ui/WindowManager.h"
#include "Vehicles/Vehicle.h"
#include <algorithm>
#include <array>
#include <map>
@ -12,39 +15,38 @@ using namespace OpenLoco::Interop;
namespace OpenLoco
{
static loco_global<company_id_t[2], 0x00525E3C> _player_company[2];
static loco_global<CompanyId_t[2], 0x00525E3C> _playerCompanies;
bool isPlayerCompany(company_id_t id)
bool isPlayerCompany(CompanyId_t id)
{
auto& player_company = *((std::array<company_id_t, 2>*)_player_company->get());
auto findResult = std::find(
player_company.begin(),
player_company.end(),
_playerCompanies.begin(),
_playerCompanies.end(),
id);
return findResult != player_company.end();
return findResult != _playerCompanies.end();
}
company_id_t company::id() const
CompanyId_t Company::id() const
{
auto first = (company*)0x00531784;
return (company_id_t)(this - first);
auto first = (Company*)0x00531784;
return (CompanyId_t)(this - first);
}
bool company::empty() const
bool Company::empty() const
{
return name == StringIds::empty;
}
// 0x00430762
void company::aiThink()
void Company::aiThink()
{
registers regs;
regs.esi = (int32_t)this;
regs.esi = X86Pointer(this);
call(0x00430762, regs);
}
// 0x00437ED0
void company::recalculateTransportCounts()
void Company::recalculateTransportCounts()
{
// Reset all counts to 0
for (auto& count : transportTypeCount)
@ -53,7 +55,7 @@ namespace OpenLoco
}
auto companyId = id();
for (auto v : ThingManager::VehicleList())
for (auto v : EntityManager::VehicleList())
{
if (v->owner == companyId)
{
@ -104,10 +106,92 @@ namespace OpenLoco
args.push(getCorporateRatingAsStringId(performanceToRating(performanceIndex)));
}
bool company::isVehicleIndexUnlocked(const uint8_t vehicleIndex) const
bool Company::isVehicleIndexUnlocked(const uint8_t vehicleIndex) const
{
auto vehicleTypeIndex = vehicleIndex >> 5;
return (unlocked_vehicles[vehicleTypeIndex] & (1 << (vehicleIndex & 0x1F))) != 0;
}
// 0x00487FCC
void Company::updateQuarterly()
{
for (auto& unk : var_4A8)
{
if (unk.var_00 == 0xFF)
continue;
unk.var_88 = std::min(0xFF, unk.var_88 + 1);
unk.var_84 = unk.var_80;
unk.var_80 = 0;
currency32_t totalRunCost = 0;
for (auto i = 0; i < unk.var_44; ++i)
{
auto* vehHead = EntityManager::get<Vehicles::VehicleHead>(unk.var_66[i]);
totalRunCost += vehHead->calculateRunningCost();
}
unk.var_7C = totalRunCost;
}
}
// 0x004B8ED2
void Company::updateVehicleColours()
{
for (auto v : EntityManager::VehicleList())
{
if (v->owner != id())
{
continue;
}
Vehicles::Vehicle train(v);
for (auto& car : train.cars)
{
auto* vehObject = car.body->object();
auto colour = mainColours;
if (customVehicleColoursSet & (1 << vehObject->colour_type))
{
colour = vehicleColours[vehObject->colour_type - 1];
}
for (auto& carComponent : car)
{
carComponent.front->colour_scheme = colour;
carComponent.back->colour_scheme = colour;
carComponent.body->colour_scheme = colour;
}
}
}
Gfx::invalidateScreen();
}
// 0x0042F0C1
static void updateHeadquartersColourAtTile(const Map::TilePos2& pos, uint8_t zPos, Colour_t newColour)
{
auto tile = Map::TileManager::get(pos);
for (auto& element : tile)
{
if (element.baseZ() != zPos)
continue;
auto building = element.asBuilding();
if (building == nullptr)
continue;
building->setColour(newColour);
return;
}
}
// 0x0042F07B
void Company::updateHeadquartersColour()
{
if (headquarters_x == -1)
return;
Colour_t colour = mainColours.primary;
auto hqPos = Map::TilePos2(Map::Pos2(headquarters_x, headquarters_y));
updateHeadquartersColourAtTile(hqPos + Map::TilePos2(0, 0), headquarters_z, colour);
updateHeadquartersColourAtTile(hqPos + Map::TilePos2(1, 0), headquarters_z, colour);
updateHeadquartersColourAtTile(hqPos + Map::TilePos2(1, 1), headquarters_z, colour);
updateHeadquartersColourAtTile(hqPos + Map::TilePos2(0, 1), headquarters_z, colour);
}
}

View File

@ -1,7 +1,7 @@
#pragma once
#include "Localisation/StringManager.h"
#include "Management/Expenditures.h"
#include "Economy/Currency.h"
#include "Economy/Expenditures.h"
#include "Types.hpp"
#include <cstddef>
#include <cstdint>
@ -12,20 +12,20 @@ namespace OpenLoco
namespace CompanyId
{
constexpr company_id_t neutral = 15;
constexpr company_id_t null = std::numeric_limits<company_id_t>::max();
constexpr CompanyId_t neutral = 15;
constexpr CompanyId_t null = std::numeric_limits<CompanyId_t>::max();
}
enum company_flags
namespace CompanyFlags
{
sorted = (1 << 3), // 0x08
increased_performance = (1 << 4), // 0x10
decreased_performance = (1 << 5), // 0x20
challenge_completed = (1 << 6), // 0x40
challenge_failed = (1 << 7), // 0x80
challenge_beaten_by_opponent = (1 << 8), // 0x100
bankrupt = (1 << 9), // 0x200
};
constexpr uint32_t sorted = (1 << 3); // 0x08
constexpr uint32_t increasedPerformance = (1 << 4); // 0x10
constexpr uint32_t decreasedPerformance = (1 << 5); // 0x20
constexpr uint32_t challengeCompleted = (1 << 6); // 0x40
constexpr uint32_t challengeFailed = (1 << 7); // 0x80
constexpr uint32_t challengeBeatenByOpponent = (1 << 8); // 0x100
constexpr uint32_t bankrupt = (1 << 9); // 0x200
}
enum class CorporateRating
{
@ -41,19 +41,37 @@ namespace OpenLoco
tycoon // 90 - 100%
};
enum ObservationStatus : uint8_t
{
empty,
buildingTrackRoad,
buildingAirport,
buildingDock,
checkingServices,
surveyingLandscape,
};
void formatPerformanceIndex(const int16_t performanceIndex, FormatArguments& args);
constexpr size_t expenditureHistoryCapacity = 16;
struct ColourScheme
{
uint8_t primary; // 0x1A
uint8_t secondary; // 0x1B
};
#pragma pack(push, 1)
struct company
struct Company
{
struct unk4A8
{
uint8_t var_00;
uint8_t pad_01[0x44 - 0x01];
uint8_t var_44; // 0x4EC size of var_66
uint8_t pad_45[0x66 - 0x45];
EntityId_t var_66[11]; // 0x50E unsure on size
currency32_t var_7C; // 0x524
uint32_t var_80; // 0x528
uint32_t var_84; // 0x52C
uint8_t var_88; // 0x530
uint8_t pad_89[3];
};
static_assert(sizeof(unk4A8) == 0x8C);
string_id name;
string_id owner_name;
uint32_t challenge_flags; // 0x04
@ -66,17 +84,22 @@ namespace OpenLoco
ColourScheme mainColours; // 0x1A
ColourScheme vehicleColours[10]; // 0x1C
uint32_t customVehicleColoursSet; // 0x30
uint32_t unlocked_vehicles[7]; // 0x34 (bit field based on vehicle_object index)
uint32_t unlocked_vehicles[7]; // 0x34 (bit field based on VehicleObject index)
uint16_t available_vehicles; // 0x50
uint8_t pad_52[0x57 - 0x52];
uint8_t numExpenditureMonths; // 0x57
currency32_t expenditures[expenditureHistoryCapacity][ExpenditureType::Count]; // 0x58
uint32_t startedDate; // 0x0498
uint8_t pad_49C[0x2579 - 0x49C];
uint32_t var_49C;
uint32_t var_4A0;
uint8_t pad_4A4[0x4A8 - 0x4A4];
unk4A8 var_4A8[60];
uint8_t pad_2578;
uint8_t headquarters_z; // 0x2579
coord_t headquarters_x; // 0x257A -1 on no headquarter placed
coord_t headquarters_y; // 0x257C
uint8_t pad_257E[0x85FC - 0x257E];
uint8_t pad_257E[0x85F8 - 0x257E];
uint32_t cargoUnitsTotalDelivered; // 0x85F8
uint32_t cargo_units_delivered_history[120]; // 0x85FC
int16_t performance_index_history[120]; // 0x87DC
uint16_t history_size; // 0x88CC
@ -84,34 +107,40 @@ namespace OpenLoco
currency48_t vehicleProfit; // 0x8B9E
uint16_t transportTypeCount[6]; // 0x8BA4
uint8_t var_8BB0[9];
uint8_t pad_8BB9[0x8BBC - 0x8BB9];
thing_id_t observation_thing; // 0x8BBC;
int16_t observation_x; // 0x8BBE;
int16_t observation_y; // 0x8BC0;
uint8_t pad_8BC2[0x8BCE - 0x8BC2];
ObservationStatus observationStatus; // 0x8BB9;
TownId_t observationTownId; // 0x8BBA;
EntityId_t observation_thing; // 0x8BBC;
int16_t observation_x; // 0x8BBE;
int16_t observation_y; // 0x8BC0;
uint16_t observationObject; // 0x8BC2;
uint8_t pad_8BC4[0x8BCE - 0x8BC4];
uint32_t cargoDelivered[32]; // 0x8BCE;
uint8_t challengeProgress; // 0x8C4E - percent completed on challenge
uint8_t pad_8C4F[0x8C54 - 0x8C4F];
uint32_t cargo_units_distance_history[120]; // 0x008C54
uint8_t pad_8C4F;
uint32_t cargoUnitsTotalDistance; // 0x8C50
uint32_t cargo_units_distance_history[120]; // 0x8C54
uint16_t jail_status; // 0x8E34
uint8_t pad_8E36[0x8FA8 - 0x8E36];
company_id_t id() const;
CompanyId_t id() const;
bool empty() const;
void aiThink();
bool isVehicleIndexUnlocked(const uint8_t vehicleIndex) const;
void recalculateTransportCounts();
void updateQuarterly();
void updateVehicleColours();
void updateHeadquartersColour();
};
#pragma pack(pop)
static_assert(sizeof(company) == 0x8FA8);
static_assert(sizeof(company::expenditures) == 0x440);
static_assert(offsetof(company, companyValueHistory[0]) == 0x88CE);
static_assert(offsetof(company, vehicleProfit) == 0x8B9E);
static_assert(offsetof(company, challengeProgress) == 0x8C4E);
static_assert(offsetof(company, var_8BB0) == 0x8BB0);
static_assert(sizeof(Company) == 0x8FA8);
static_assert(sizeof(Company::expenditures) == 0x440);
static_assert(offsetof(Company, companyValueHistory[0]) == 0x88CE);
static_assert(offsetof(Company, vehicleProfit) == 0x8B9E);
static_assert(offsetof(Company, challengeProgress) == 0x8C4E);
static_assert(offsetof(Company, var_8BB0) == 0x8BB0);
bool isPlayerCompany(company_id_t id);
bool isPlayerCompany(CompanyId_t id);
constexpr CorporateRating performanceToRating(int16_t performanceIndex);
void formatPerformanceIndex(const int16_t performanceIndex, FormatArguments& args);
}

View File

@ -1,48 +1,85 @@
#include "CompanyManager.h"
#include "Config.h"
#include "GameCommands.h"
#include "Entities/EntityManager.h"
#include "Entities/Misc.h"
#include "GameCommands/GameCommands.h"
#include "Graphics/Colour.h"
#include "Interop/Interop.hpp"
#include "Localisation/FormatArguments.hpp"
#include "Map/Tile.h"
#include "Map/TileManager.h"
#include "Objects/AirportObject.h"
#include "Objects/DockObject.h"
#include "Objects/RoadObject.h"
#include "Objects/TrackObject.h"
#include "OpenLoco.h"
#include "Things/ThingManager.h"
#include "Things/Vehicle.h"
#include "TownManager.h"
#include "Ui/WindowManager.h"
#include "Vehicles/Vehicle.h"
#include "Vehicles/VehicleManager.h"
using namespace OpenLoco::Interop;
using namespace OpenLoco::Ui;
namespace OpenLoco::CompanyManager
{
static loco_global<company_id_t[2], 0x00525E3C> _player_company;
static loco_global<CompanyId_t[2], 0x00525E3C> _player_company;
static loco_global<uint8_t, 0x00525FCB> _byte_525FCB;
static loco_global<uint8_t, 0x00526214> _company_competition_delay;
static loco_global<uint8_t, 0x00525FB7> _company_max_competing;
static loco_global<uint8_t, 0x00525E3C> _byte_525E3C;
static loco_global<uint8_t, 0x00525E3D> _byte_525E3D;
static loco_global<company[max_companies], 0x00531784> _companies;
static loco_global<Company[max_companies], 0x00531784> _companies;
static loco_global<uint8_t[max_companies + 1], 0x009C645C> _company_colours;
static loco_global<company_id_t, 0x009C68EB> _updating_company_id;
static loco_global<CompanyId_t, 0x009C68EB> _updating_company_id;
static void produceCompanies();
company_id_t updatingCompanyId()
// 0x0042F7F8
void reset()
{
// First, empty all non-empty companies.
for (auto& company : companies())
company.name = StringIds::empty;
_byte_525FCB = 0;
// Reset player companies depending on network mode.
if (isNetworkHost())
{
_player_company[0] = 1;
_player_company[1] = 0;
}
else if (isNetworked())
{
_player_company[0] = 0;
_player_company[1] = 1;
}
else
{
_player_company[0] = 0;
_player_company[1] = 0xFF;
}
// Reset primary company colours.
_companies[0].mainColours.primary = Colour::saturated_green;
updateColours();
}
CompanyId_t updatingCompanyId()
{
return _updating_company_id;
}
void updatingCompanyId(company_id_t id)
void updatingCompanyId(CompanyId_t id)
{
_updating_company_id = id;
}
std::array<company, max_companies>& companies()
LocoFixedVector<Company> companies()
{
auto arr = (std::array<company, max_companies>*)_companies.get();
return *arr;
return LocoFixedVector<Company>(_companies);
}
company* get(company_id_t id)
Company* get(CompanyId_t id)
{
auto index = id;
if (index < _companies.size())
@ -52,17 +89,32 @@ namespace OpenLoco::CompanyManager
return nullptr;
}
company_id_t getControllingId()
CompanyId_t getControllingId()
{
return _player_company[0];
}
company* getPlayerCompany()
CompanyId_t getSecondaryPlayerId()
{
return _player_company[1];
}
void setControllingId(CompanyId_t id)
{
_player_company[0] = id;
}
void setSecondaryPlayerId(CompanyId_t id)
{
_player_company[1] = id;
}
Company* getPlayerCompany()
{
return &_companies[_player_company[0]];
}
uint8_t getCompanyColour(company_id_t id)
uint8_t getCompanyColour(CompanyId_t id)
{
return _company_colours[id];
}
@ -77,7 +129,7 @@ namespace OpenLoco::CompanyManager
{
if (!isEditorMode() && !Config::getNew().companyAIDisabled)
{
company_id_t id = scenarioTicks() & 0x0F;
CompanyId_t id = scenarioTicks() & 0x0F;
auto company = get(id);
if (company != nullptr && !isPlayerCompany(id) && !company->empty())
{
@ -94,11 +146,44 @@ namespace OpenLoco::CompanyManager
}
}
// 0x00487FC1
void updateQuarterly()
{
for (auto& company : companies())
{
company.updateQuarterly();
}
}
static void sub_42F9AC()
{
call(0x0042F9AC);
}
// 0x0042F23C
currency32_t calculateDeliveredCargoPayment(uint8_t cargoItem, int32_t numUnits, int32_t distance, uint16_t numDays)
{
registers regs;
regs.eax = cargoItem;
regs.ebx = numUnits;
regs.ecx = distance;
regs.edx = numDays;
call(0x0042F23C, regs);
return regs.eax;
}
// 0x0042FDE2
void determineAvailableVehicles()
{
for (auto& company : _companies)
{
if (company.empty())
continue;
VehicleManager::determineAvailableVehicles(company);
}
}
// 0x004306D1
static void produceCompanies()
{
@ -107,15 +192,11 @@ namespace OpenLoco::CompanyManager
int32_t companies_active = 0;
for (const auto& company : companies())
{
auto id = company.id();
if (!company.empty() && id != _byte_525E3C && id != _byte_525E3D)
{
if (!isPlayerCompany(company.id()))
companies_active++;
}
}
auto& prng = gPrng();
if (prng.randNext(16) == 0)
{
if (prng.randNext(_company_max_competing) + 1 > companies_active)
@ -127,37 +208,73 @@ namespace OpenLoco::CompanyManager
}
}
company* getOpponent()
Company* getOpponent()
{
return &_companies[_player_company[1]];
}
// 0x00438047
// Returns a string between 1810 and 1816 with up to two arguments.
string_id getOwnerStatus(company_id_t id, FormatArguments& args)
string_id getOwnerStatus(CompanyId_t id, FormatArguments& args)
{
registers regs;
regs.esi = (int32_t)get(id);
call(0x00438047, regs);
auto& company = _companies[id];
if (company.challenge_flags & CompanyFlags::bankrupt)
return StringIds::company_status_bankrupt;
args.push(regs.ecx);
args.push(regs.edx);
return regs.bx;
}
const string_id observationStatusStrings[] = {
StringIds::company_status_empty,
StringIds::company_status_building_track_road,
StringIds::company_status_building_airport,
StringIds::company_status_building_dock,
StringIds::company_status_checking_services,
StringIds::company_status_surveying_landscape,
};
owner_status getOwnerStatus(company_id_t id)
{
registers regs;
regs.esi = (int32_t)get(id);
call(0x00438047, regs);
string_id statusString = observationStatusStrings[company.observationStatus];
if (company.observationStatus == ObservationStatus::empty || company.observationTownId == 0xFFFF)
return StringIds::company_status_empty;
owner_status ownerStatus;
switch (company.observationStatus)
{
case ObservationStatus::buildingTrackRoad:
if (company.observationObject & 0x80)
{
auto* obj = ObjectManager::get<RoadObject>(company.observationObject & 0xFF7F);
if (obj != nullptr)
args.push(obj->name);
}
else
{
auto* obj = ObjectManager::get<TrackObject>(company.observationObject);
if (obj != nullptr)
args.push(obj->name);
}
break;
ownerStatus.string = regs.bx;
ownerStatus.argument1 = regs.ecx;
ownerStatus.argument2 = regs.edx;
case ObservationStatus::buildingAirport:
{
auto* obj = ObjectManager::get<AirportObject>(company.observationObject);
if (obj != nullptr)
args.push(obj->name);
break;
}
return ownerStatus;
case ObservationStatus::buildingDock:
{
auto* obj = ObjectManager::get<DockObject>(company.observationObject);
if (obj != nullptr)
args.push(obj->name);
break;
}
default:
break;
}
auto* town = TownManager::get(company.observationTownId);
args.push(town->name);
return statusString;
}
// 0x004383ED
@ -185,8 +302,8 @@ namespace OpenLoco::CompanyManager
if (w->type != WindowType::vehicle)
continue;
auto vehicle = ThingManager::get<OpenLoco::vehicle>(w->number);
if (vehicle->x == Location::null)
auto vehicle = EntityManager::get<Vehicles::VehicleBase>(w->number);
if (vehicle->position.x == Location::null)
continue;
if (vehicle->owner != _updating_company_id)
@ -204,31 +321,139 @@ namespace OpenLoco::CompanyManager
if (viewport == nullptr)
return;
Gfx::point_t screenPosition;
screenPosition.x = viewport->x + viewport->width / 2;
screenPosition.y = viewport->y + viewport->height / 2;
auto screenPosition = viewport->getUiCentre();
registers r1;
r1.ax = screenPosition.x;
r1.bx = screenPosition.y;
call(0x0045F1A7, r1);
Ui::viewport* vp = (Ui::viewport*)r1.edi;
auto mapPosition = Map::map_pos(r1.ax, r1.bx);
auto res = Ui::ViewportInteraction::getSurfaceLocFromUi(screenPosition);
// Happens if center of viewport is obstructed. Probably estimates the centre location
if (mapPosition.x == Location::null || viewport != vp)
Map::Pos2 mapPosition{};
if (!res || res->second != viewport)
{
registers r2;
r2.ax = viewport->view_x + viewport->view_width / 2;
r2.bx = viewport->view_y + viewport->view_height / 2;
r2.edx = viewport->getRotation();
call(0x0045F997, r2);
mapPosition.x = r2.ax;
mapPosition.y = r2.bx;
// Happens if center of viewport is obstructed. Probably estimates the centre location
mapPosition = viewport->getCentreMapPosition();
}
else
{
mapPosition = res->first;
}
GameCommands::do_73(mapPosition);
}
// 0x0046DC9F
// loc : gGameCommandMapX/Y/Z global
// company : updatingCompanyId global
// amount : ebx
void spendMoneyEffect(const Map::Pos3& loc, const CompanyId_t company, const currency32_t amount)
{
if (isEditorMode())
{
return;
}
Map::Pos3 pos = loc;
if (loc.x == Location::null)
{
auto* view = Ui::WindowManager::getMainViewport();
if (view == nullptr)
{
return;
}
auto centre = view->getCentreScreenMapPosition();
if (!centre)
{
return;
}
pos = Map::Pos3(centre->x, centre->y, Map::TileManager::getHeight(*centre).landHeight);
}
pos.z += 10;
MoneyEffect::create(pos, company, -amount);
}
// 0x0046DE2B
// id : updatingCompanyId global var
// payment : ebx (subtracted from company balance)
// type : gGameCommandExpenditureType global var
void applyPaymentToCompany(const CompanyId_t id, const currency32_t payment, const ExpenditureType type)
{
auto* company = get(id);
if (company == nullptr || OpenLoco::isEditorMode())
return;
WindowManager::invalidate(WindowType::company, id);
// Invalidate the company balance if this is the player company
if (getControllingId() == id)
{
Ui::Windows::PlayerInfoPanel::invalidateFrame();
}
auto cost = currency48_t{ payment };
company->cash -= cost;
company->expenditures[0][static_cast<uint8_t>(type)] -= payment;
}
// 0x004302EF
void updateColours()
{
size_t index = 0;
for (auto& company : _companies)
{
_company_colours[index] = company.mainColours.primary;
index++;
}
_company_colours[CompanyId::neutral] = 1;
}
uint32_t competingColourMask(CompanyId_t companyId)
{
const uint32_t similarColourMask[] = {
0b11,
0b11,
0b100,
0b11000,
0b11000,
0b100000,
0b11000000,
0b11000000,
0b1100000000,
0b1100000000,
0b11110000000000,
0b11110000000000,
0b11110000000000,
0b11110000000000,
0b1100000000000000,
0b1100000000000000,
0b10110000000000000000,
0b10110000000000000000,
0b101000000000000000000,
0b10110000000000000000,
0b101000000000000000000,
0b11000000000000000000000,
0b11000000000000000000000,
0b100000000000000000000000,
0b1000000000000000000000000,
0b10000000000000000000000000,
0b1100000000000000000000000000,
0b1100000000000000000000000000,
0b110000000000000000000000000000,
0b110000000000000000000000000000,
0b1000000000000000000000000000000,
};
uint32_t mask = 0;
for (auto& company : companies())
{
if (company.id() == companyId)
continue;
mask |= similarColourMask[company.mainColours.primary];
}
return mask;
}
uint32_t competingColourMask()
{
return competingColourMask(_updating_company_id);
}
}

View File

@ -1,6 +1,8 @@
#pragma once
#include "Company.h"
#include "Core/LocoFixedVector.hpp"
#include "Map/Map.hpp"
#include "Types.hpp"
#include <array>
#include <cstddef>
@ -9,26 +11,31 @@ namespace OpenLoco::CompanyManager
{
constexpr size_t max_companies = 15;
company_id_t updatingCompanyId();
void updatingCompanyId(company_id_t id);
void reset();
CompanyId_t updatingCompanyId();
void updatingCompanyId(CompanyId_t id);
std::array<company, max_companies>& companies();
company* get(company_id_t id);
company_id_t getControllingId();
company* getPlayerCompany();
uint8_t getCompanyColour(company_id_t id);
LocoFixedVector<Company> companies();
Company* get(CompanyId_t id);
CompanyId_t getControllingId();
CompanyId_t getSecondaryPlayerId();
void setControllingId(CompanyId_t id);
void setSecondaryPlayerId(CompanyId_t id);
Company* getPlayerCompany();
uint8_t getCompanyColour(CompanyId_t id);
uint8_t getPlayerCompanyColour();
void update();
void updateQuarterly();
void determineAvailableVehicles();
currency32_t calculateDeliveredCargoPayment(uint8_t cargoItem, int32_t numUnits, int32_t distance, uint16_t numDays);
struct owner_status
{
string_id string;
uint32_t argument1;
uint32_t argument2;
};
company* getOpponent();
string_id getOwnerStatus(company_id_t id, FormatArguments& args);
owner_status getOwnerStatus(company_id_t id);
Company* getOpponent();
string_id getOwnerStatus(CompanyId_t id, FormatArguments& args);
void updateOwnerStatus();
void updateColours();
void spendMoneyEffect(const Map::Pos3& loc, const CompanyId_t company, const currency32_t amount);
void applyPaymentToCompany(const CompanyId_t id, const currency32_t payment, const ExpenditureType type);
uint32_t competingColourMask(CompanyId_t companyId);
uint32_t competingColourMask();
}

View File

@ -1,4 +1,3 @@
#include <fstream>
#ifdef _WIN32
@ -17,22 +16,22 @@ using namespace OpenLoco::Interop;
namespace OpenLoco::Config
{
static loco_global<config_t, 0x0050AEB4> _config;
static new_config _new_config;
static loco_global<LocoConfig, 0x0050AEB4> _config;
static NewConfig _new_config;
static YAML::Node _config_yaml;
config_t& get()
LocoConfig& get()
{
return _config;
}
new_config& getNew()
NewConfig& getNew()
{
return _new_config;
}
// 0x00441A6C
config_t& read()
LocoConfig& read()
{
call(0x00441A6C);
return _config;
@ -45,9 +44,9 @@ namespace OpenLoco::Config
writeNewConfig();
}
new_config& readNewConfig()
NewConfig& readNewConfig()
{
auto configPath = Environment::getPath(Environment::path_id::openloco_yml);
auto configPath = Environment::getPathNoWarning(Environment::path_id::openloco_yml);
if (!fs::exists(configPath))
return _new_config;
@ -60,10 +59,10 @@ namespace OpenLoco::Config
if (displayNode && displayNode.IsMap())
{
auto& displayConfig = _new_config.display;
displayConfig.mode = displayNode["mode"].as<screen_mode>(screen_mode::window);
displayConfig.mode = displayNode["mode"].as<ScreenMode>(ScreenMode::window);
displayConfig.index = displayNode["index"].as<int32_t>(0);
displayConfig.window_resolution = displayNode["window_resolution"].as<resolution_t>();
displayConfig.fullscreen_resolution = displayNode["fullscreen_resolution"].as<resolution_t>();
displayConfig.window_resolution = displayNode["window_resolution"].as<Resolution>();
displayConfig.fullscreen_resolution = displayNode["fullscreen_resolution"].as<Resolution>();
}
auto& audioNode = config["audio"];
@ -77,36 +76,37 @@ namespace OpenLoco::Config
if (config["loco_install_path"])
_new_config.loco_install_path = config["loco_install_path"].as<std::string>();
if (config["last_save_path"])
_new_config.last_save_path = config["last_save_path"].as<std::string>();
if (config["language"])
_new_config.language = config["language"].as<std::string>();
if (config["breakdowns_disabled"])
_new_config.breakdowns_disabled = config["breakdowns_disabled"].as<bool>();
if (config["cheats_menu_enabled"])
_new_config.cheats_menu_enabled = config["cheats_menu_enabled"].as<bool>();
if (config["companyAIDisabled"])
_new_config.companyAIDisabled = config["companyAIDisabled"].as<bool>();
if (config["scale_factor"])
_new_config.scale_factor = config["scale_factor"].as<float>();
if (config["zoom_to_cursor"])
_new_config.zoom_to_cursor = config["zoom_to_cursor"].as<bool>();
if (config["autosave_frequency"])
_new_config.autosave_frequency = config["autosave_frequency"].as<int32_t>();
if (config["autosave_amount"])
_new_config.autosave_amount = config["autosave_amount"].as<int32_t>();
if (config["showFPS"])
_new_config.showFPS = config["showFPS"].as<bool>();
if (config["uncapFPS"])
_new_config.uncapFPS = config["uncapFPS"].as<bool>();
return _new_config;
}
void writeNewConfig()
{
auto configPath = Environment::getPath(Environment::path_id::openloco_yml);
auto configPath = Environment::getPathNoWarning(Environment::path_id::openloco_yml);
auto dir = configPath.parent_path();
if (!fs::is_directory(dir))
{
fs::create_directories(dir);
// clang-format off
fs::permissions(
dir,
fs::perms::owner_all |
fs::perms::group_read | fs::perms::group_exec |
fs::perms::others_read | fs::perms::others_exec
);
// clang-format on
}
Environment::autoCreateDirectory(dir);
auto& node = _config_yaml;
@ -138,11 +138,17 @@ namespace OpenLoco::Config
node["audio"] = audioNode;
node["loco_install_path"] = _new_config.loco_install_path;
node["last_save_path"] = _new_config.last_save_path;
node["language"] = _new_config.language;
node["breakdowns_disabled"] = _new_config.breakdowns_disabled;
node["cheats_menu_enabled"] = _new_config.cheats_menu_enabled;
node["companyAIDisabled"] = _new_config.companyAIDisabled;
node["scale_factor"] = _new_config.scale_factor;
node["zoom_to_cursor"] = _new_config.zoom_to_cursor;
node["autosave_frequency"] = _new_config.autosave_frequency;
node["autosave_amount"] = _new_config.autosave_amount;
node["showFPS"] = _new_config.showFPS;
node["uncapFPS"] = _new_config.uncapFPS;
std::ofstream stream(configPath);
if (stream.is_open())

View File

@ -1,5 +1,6 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
@ -7,32 +8,32 @@ namespace OpenLoco::Config
{
#pragma pack(push, 1)
enum flags
namespace Flags
{
gridlines_on_landscape = (1 << 0),
show_height_as_units = (1 << 1),
landscape_smoothing = (1 << 2),
export_objects_with_saves = (1 << 3),
constexpr uint32_t gridlinesOnLandscape = (1 << 0);
constexpr uint32_t showHeightAsUnits = (1 << 1);
constexpr uint32_t landscapeSmoothing = (1 << 2);
constexpr uint32_t exportObjectsWithSaves = (1 << 3);
preferred_currency_for_new_games = (1 << 6),
preferred_currency_always = (1 << 7),
constexpr uint32_t preferredCurrencyForNewGames = (1 << 6);
constexpr uint32_t preferredCurrencyAlways = (1 << 7);
use_preferred_owner_name = (1 << 9),
};
constexpr uint32_t usePreferredOwnerName = (1 << 9);
}
enum measurement_format
enum class MeasurementFormat : uint8_t
{
imperial = 0,
metric = 1,
};
struct keyboard_shortcut_t
struct KeyboardShortcut
{
uint8_t var_0;
uint8_t var_1;
};
enum class newsType : uint8_t
enum class NewsType : uint8_t
{
none = 0,
ticker,
@ -41,21 +42,21 @@ namespace OpenLoco::Config
constexpr auto newsItemSubTypeCount = 6;
enum class screen_mode
enum class ScreenMode
{
window,
fullscreen,
fullscreen_borderless
fullscreenBorderless
};
enum class music_playlist_type : uint8_t
enum class MusicPlaylistType : uint8_t
{
current_era,
currentEra,
all,
custom,
};
struct config_t
struct LocoConfig
{
uint32_t flags; // 0x50AEB4, 0x00
int16_t resolution_width; // 0x50AEB8, 0x04
@ -72,31 +73,33 @@ namespace OpenLoco::Config
uint8_t max_vehicle_sounds; // 0x25
uint8_t max_sound_instances; // 0x26
uint8_t sound_quality; // 0x27
uint8_t measurement_format; // 0x50AEDC, 0x28
MeasurementFormat measurement_format; // 0x50AEDC, 0x28
uint8_t pad_29; // 0x29
keyboard_shortcut_t keyboard_shortcuts[35]; // 0x2A
KeyboardShortcut keyboard_shortcuts[35]; // 0x2A
uint8_t edge_scrolling; // 0x70
uint8_t vehicles_min_scale; // 0x71
uint8_t vehicles_min_scale; // 0x50AF25, 0x71
uint8_t var_72; // 0x50AF26, 0x72
music_playlist_type music_playlist; // 0x50AF27, 0x73
MusicPlaylistType music_playlist; // 0x50AF27, 0x73
uint16_t height_marker_offset; // 0x50AF28, 0x74
newsType news_settings[newsItemSubTypeCount]; // 0x50AF2A, 0x76
NewsType news_settings[newsItemSubTypeCount]; // 0x50AF2A, 0x76
uint8_t preferred_currency[16]; // 0x7C
uint8_t enabled_music[29]; // 0x50AF40, 0x8C
uint8_t pad_A9[0xCC - 0xA9]; // 0xA9
int32_t volume; // 0xCC
int32_t volume; // 0x50AF80, 0xCC
uint32_t connection_timeout; // 0xD0
char last_host[64]; // 0xD4
uint8_t station_names_min_scale; // 0x114
uint8_t scenario_selected_tab; // 0x115
char preferred_name[256]; // 0x116
};
static_assert(offsetof(config_t, keyboard_shortcuts) == 0x2A);
static_assert(offsetof(config_t, preferred_name) == 0x116);
static_assert(offsetof(config_t, last_host) == 0xD4);
static_assert(sizeof(config_t) == 0x216);
static_assert(offsetof(LocoConfig, keyboard_shortcuts) == 0x2A);
static_assert(offsetof(LocoConfig, preferred_name) == 0x116);
static_assert(offsetof(LocoConfig, last_host) == 0xD4);
static_assert(sizeof(LocoConfig) == 0x216);
struct resolution_t
#pragma pack(pop)
struct Resolution
{
int32_t width{};
int32_t height{};
@ -106,22 +109,22 @@ namespace OpenLoco::Config
return width > 0 && height > 0;
}
bool operator==(const resolution_t& rhs) const
bool operator==(const Resolution& rhs) const
{
return width == rhs.width && height == rhs.height;
}
bool operator!=(const resolution_t& rhs) const
bool operator!=(const Resolution& rhs) const
{
return width != rhs.width || height != rhs.height;
}
bool operator>(const resolution_t& rhs) const
bool operator>(const Resolution& rhs) const
{
return width > rhs.width || height > rhs.height;
}
resolution_t& operator*=(const float scalar)
Resolution& operator*=(const float scalar)
{
width *= scalar;
height *= scalar;
@ -129,39 +132,43 @@ namespace OpenLoco::Config
}
};
struct display_config
struct Display
{
screen_mode mode;
ScreenMode mode;
int32_t index{};
resolution_t window_resolution = { 800, 600 };
resolution_t fullscreen_resolution;
Resolution window_resolution = { 800, 600 };
Resolution fullscreen_resolution;
};
struct audio_config
struct Audio
{
std::string device;
bool play_title_music = true;
};
struct new_config
struct NewConfig
{
display_config display;
audio_config audio;
Display display;
Audio audio;
std::string loco_install_path;
std::string last_save_path;
std::string language = "en-GB";
bool cheats_menu_enabled = false;
bool breakdowns_disabled = false;
bool companyAIDisabled = false;
float scale_factor = 1.0f;
bool zoom_to_cursor = true;
int32_t autosave_frequency = 1;
int32_t autosave_amount = 12;
bool showFPS = false;
bool uncapFPS = false;
};
#pragma pack(pop)
LocoConfig& get();
NewConfig& getNew();
config_t& get();
new_config& getNew();
config_t& read();
new_config& readNewConfig();
LocoConfig& read();
NewConfig& readNewConfig();
void write();
void writeNewConfig();
}

View File

@ -48,11 +48,11 @@ namespace YAML
}
};
// resolution_t
// Resolution
template<>
struct convert<resolution_t>
struct convert<Resolution>
{
static Node encode(const resolution_t& rhs)
static Node encode(const Resolution& rhs)
{
Node node;
node["width"] = rhs.width;
@ -60,7 +60,7 @@ namespace YAML
return node;
}
static bool decode(const Node& node, resolution_t& rhs)
static bool decode(const Node& node, Resolution& rhs)
{
if (node.IsMap())
{
@ -72,16 +72,16 @@ namespace YAML
}
};
// screen_mode
const convert_pair_vector<screen_mode> screen_mode_entries = {
enum_def(screen_mode, window),
enum_def(screen_mode, fullscreen),
enum_def(screen_mode, fullscreen_borderless),
// ScreenMode
const convert_pair_vector<ScreenMode> screen_mode_entries = {
enum_def(ScreenMode, window),
enum_def(ScreenMode, fullscreen),
enum_def(ScreenMode, fullscreenBorderless),
};
template<>
struct convert<screen_mode> : convert_enum_base<screen_mode>
struct convert<ScreenMode> : convert_enum_base<ScreenMode>
{
static const convert_pair_vector<screen_mode>& getEntries() { return screen_mode_entries; }
static const convert_pair_vector<ScreenMode>& getEntries() { return screen_mode_entries; }
};
}

View File

@ -0,0 +1,82 @@
#pragma once
#include <iterator>
namespace OpenLoco
{
template<typename ValueType>
class LocoFixedVector
{
private:
ValueType* startAddress = nullptr;
ValueType* endAddress = nullptr;
class Iter
{
private:
ValueType* arr;
ValueType* endAdd;
public:
constexpr Iter(ValueType* _arr, ValueType* _endAdd)
: arr(_arr)
, endAdd(_endAdd)
{
// finds first valid entry
++(*this);
}
constexpr Iter& operator++()
{
while (arr != endAdd && (++arr)->empty())
{
}
return *this;
}
constexpr Iter operator++(int)
{
Iter retval = *this;
++(*this);
return retval;
}
constexpr bool operator==(Iter other) const
{
return arr == other.arr;
}
constexpr bool operator!=(Iter other) const
{
return !(*this == other);
}
constexpr ValueType& operator*() const
{
return *arr;
}
// iterator traits
using difference_type = std::ptrdiff_t;
using value_type = ValueType;
using pointer = ValueType*;
using reference = ValueType&;
using iterator_category = std::forward_iterator_tag;
};
public:
template<typename T>
LocoFixedVector(T& _arr)
: startAddress(reinterpret_cast<ValueType*>(T::address))
, endAddress(reinterpret_cast<ValueType*>(T::endAddress))
{
}
Iter begin() const
{
return Iter(startAddress - 1, endAddress);
}
Iter end() const
{
return Iter(endAddress, endAddress);
}
};
}

View File

@ -4,14 +4,8 @@
#pragma once
// Find out where std::optional is:
#if defined(__APPLE__) // XCode has the header in experimental namespace
#define NORMAL_OPTIONAL 0
#else // By default assume supported.
#define NORMAL_OPTIONAL 1
#endif
#if NORMAL_OPTIONAL
#if defined(__has_include) // For GCC/Clang check if the header exists.
#if __has_include(<optional>)
#include <optional>
#else
#include <experimental/optional>
@ -21,5 +15,6 @@ namespace std
using std::experimental::optional;
}
#endif
#undef NORMAL_OPTIONAL // Not needed any more, don't make it public.
#else
#error "__has_include operator is unavailable; <optional> header could not be located"
#endif

View File

@ -0,0 +1,7 @@
/// @file
/// This file enables access to std::span as `stdx` namespace
#pragma once
#define TCB_SPAN_NAMESPACE_NAME stdx
#include "../../Thirdparty/span.hpp"

View File

@ -0,0 +1,23 @@
// This file enables access to stdx::variant stdx::visit
#pragma once
// TODO: use a more fine-grained approach to detecting whether <variant>
// contains support for std::visit.
#if !defined(__APPLE__)
#include <variant>
namespace stdx
{
using std::variant;
using std::visit;
}
#else
#include "../../Thirdparty/variant.hpp"
namespace stdx
{
using mpark::variant;
using mpark::visit;
}
#endif

View File

@ -16,11 +16,21 @@ namespace OpenLoco
static std::pair<month_id, uint8_t> getMonthDay(int32_t dayOfYear);
bool isLeapYear(const int year)
{
return year % 4 == 0;
}
uint32_t getCurrentDay()
{
return _current_day;
}
void setCurrentDay(const uint32_t day)
{
_current_day = day;
}
month_id getCurrentMonth()
{
return static_cast<month_id>(*_current_month);
@ -48,6 +58,16 @@ namespace OpenLoco
_current_year = date.year;
}
uint16_t getDayProgression()
{
return _day_progression;
}
void setDayProgression(const uint16_t progression)
{
_day_progression = progression;
}
bool updateDayCounter()
{
bool result = false;

View File

@ -38,7 +38,10 @@ namespace OpenLoco
}
};
bool isLeapYear(const int year);
uint32_t getCurrentDay();
void setCurrentDay(const uint32_t day);
month_id getCurrentMonth();
uint16_t getCurrentYear();
void setCurrentYear(const int16_t year);
@ -46,6 +49,9 @@ namespace OpenLoco
date getCurrentDate();
void setDate(const date& date);
uint16_t getDayProgression();
void setDayProgression(const uint16_t progression);
/**
* Updates the current day counter.
* @returns true if the counter wraps indicating a new day.

View File

@ -0,0 +1,61 @@
#include "FPSCounter.h"
#include "../Graphics/Colour.h"
#include "../Graphics/Gfx.h"
#include "../Localisation/StringManager.h"
#include "../Ui.h"
#include <chrono>
#include <stdio.h>
namespace OpenLoco::Drawing
{
using Clock_t = std::chrono::high_resolution_clock;
using TimePoint_t = Clock_t::time_point;
static TimePoint_t _referenceTime;
static uint32_t _currentFrameCount;
static float _currentFPS;
static float measureFPS()
{
_currentFrameCount++;
auto currentTime = Clock_t::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - _referenceTime).count() / 1000.0;
if (elapsed > 1.0)
{
_currentFPS = _currentFrameCount / elapsed;
_currentFrameCount = 0;
_referenceTime = currentTime;
}
return _currentFPS;
}
void drawFPS()
{
// Measure FPS
const float fps = measureFPS();
// Format string
char buffer[64];
buffer[0] = ControlCodes::font_bold;
buffer[1] = ControlCodes::outline;
buffer[2] = ControlCodes::colour_white;
const char* formatString = (_currentFPS >= 10.0f ? "%.0f" : "%.1f");
snprintf(&buffer[3], std::size(buffer) - 3, formatString, fps);
auto& context = Gfx::screenContext();
// Draw text
const int stringWidth = Gfx::getStringWidth(buffer);
const auto x = Ui::width() / 2 - (stringWidth / 2);
const auto y = 2;
Gfx::drawString(context, x, y, Colour::black, buffer);
// Make area dirty so the text doesn't get drawn over the last
Gfx::setDirtyBlocks(x - 16, y - 4, x + 16, 16);
}
}

View File

@ -0,0 +1,4 @@
namespace OpenLoco::Drawing
{
void drawFPS();
}

View File

@ -10,12 +10,12 @@ using namespace OpenLoco::Ui;
namespace OpenLoco::Drawing
{
static loco_global<Ui::screen_info_t, 0x0050B884> screen_info;
static loco_global<Ui::ScreenInfo, 0x0050B884> screen_info;
static loco_global<uint8_t[1], 0x00E025C4> _E025C4;
static void windowDraw(drawpixelinfo_t* dpi, Ui::window* w, Rect rect);
static void windowDraw(drawpixelinfo_t* dpi, Ui::window* w, int16_t left, int16_t top, int16_t right, int16_t bottom);
static bool windowDrawSplit(Gfx::drawpixelinfo_t* dpi, Ui::window* w, int16_t left, int16_t top, int16_t right, int16_t bottom);
static void windowDraw(Context* context, Ui::Window* w, Rect rect);
static void windowDraw(Context* context, Ui::Window* w, int16_t left, int16_t top, int16_t right, int16_t bottom);
static bool windowDrawSplit(Gfx::Context* context, Ui::Window* w, int16_t left, int16_t top, int16_t right, int16_t bottom);
// T[m][n]
template<typename T>
@ -113,14 +113,8 @@ namespace OpenLoco::Drawing
if (grid[y][x] == 0)
continue;
// Determine columns
size_t xx;
for (xx = x; xx < columns; xx++)
{
if (grid[y][xx] == 0)
break;
}
size_t dX = xx - x;
// Don't determine columns will cause rendering z fighting issues
const size_t dX = 1;
// Check rows
size_t dY = grid.getRows(x, dX, y);
@ -166,14 +160,14 @@ namespace OpenLoco::Drawing
regs.dx = rect.bottom() - 1;
call(0x00451D98, regs);
drawpixelinfo_t windowDPI;
windowDPI.width = rect.width();
windowDPI.height = rect.height();
windowDPI.x = rect.left();
windowDPI.y = rect.top();
windowDPI.bits = screen_info->dpi.bits + rect.left() + ((screen_info->dpi.width + screen_info->dpi.pitch) * rect.top());
windowDPI.pitch = screen_info->dpi.width + screen_info->dpi.pitch - rect.width();
windowDPI.zoom_level = 0;
Context windowContext;
windowContext.width = rect.width();
windowContext.height = rect.height();
windowContext.x = rect.left();
windowContext.y = rect.top();
windowContext.bits = screen_info->context.bits + rect.left() + ((screen_info->context.width + screen_info->context.pitch) * rect.top());
windowContext.pitch = screen_info->context.width + screen_info->context.pitch - rect.width();
windowContext.zoom_level = 0;
for (size_t i = 0; i < Ui::WindowManager::count(); i++)
{
@ -188,13 +182,13 @@ namespace OpenLoco::Drawing
if (rect.left() >= w->x + w->width || rect.top() >= w->y + w->height)
continue;
windowDraw(&windowDPI, w, rect);
windowDraw(&windowContext, w, rect);
}
}
static void windowDraw(drawpixelinfo_t* dpi, Ui::window* w, Rect rect)
static void windowDraw(Context* context, Ui::Window* w, Rect rect)
{
windowDraw(dpi, w, rect.left(), rect.top(), rect.right(), rect.bottom());
windowDraw(context, w, rect.left(), rect.top(), rect.right(), rect.bottom());
}
/**
@ -206,13 +200,13 @@ namespace OpenLoco::Drawing
* @param right @<dx>
* @param bottom @<bp>
*/
static void windowDraw(drawpixelinfo_t* dpi, Ui::window* w, int16_t left, int16_t top, int16_t right, int16_t bottom)
static void windowDraw(Context* context, Ui::Window* w, int16_t left, int16_t top, int16_t right, int16_t bottom)
{
if (!w->isVisible())
return;
// Split window into only the regions that require drawing
if (windowDrawSplit(dpi, w, left, top, right, bottom))
if (windowDrawSplit(context, w, left, top, right, bottom))
return;
// Clamp region
@ -226,7 +220,7 @@ namespace OpenLoco::Drawing
return;
// Draw the window in this region
Ui::WindowManager::drawSingle(dpi, w, left, top, right, bottom);
Ui::WindowManager::drawSingle(context, w, left, top, right, bottom);
for (uint32_t index = Ui::WindowManager::indexOf(w) + 1; index < Ui::WindowManager::count(); index++)
{
@ -236,14 +230,14 @@ namespace OpenLoco::Drawing
if ((v->flags & Ui::WindowFlags::transparent) == 0)
continue;
Ui::WindowManager::drawSingle(dpi, v, left, top, right, bottom);
Ui::WindowManager::drawSingle(context, v, left, top, right, bottom);
}
}
/**
* 0x004C5EA9
*
* @param dpi
* @param context
* @param w @<esi>
* @param left @<ax>
* @param top @<bx>
@ -251,7 +245,7 @@ namespace OpenLoco::Drawing
* @param bottom @<bp>
* @return
*/
static bool windowDrawSplit(Gfx::drawpixelinfo_t* dpi, Ui::window* w, int16_t left, int16_t top, int16_t right, int16_t bottom)
static bool windowDrawSplit(Gfx::Context* context, Ui::Window* w, int16_t left, int16_t top, int16_t right, int16_t bottom)
{
// Divide the draws up for only the visible regions of the window recursively
for (uint32_t index = Ui::WindowManager::indexOf(w) + 1; index < Ui::WindowManager::count(); index++)
@ -270,26 +264,26 @@ namespace OpenLoco::Drawing
if (topwindow->x > left)
{
// Split draw at topwindow.left
windowDraw(dpi, w, left, top, topwindow->x, bottom);
windowDraw(dpi, w, topwindow->x, top, right, bottom);
windowDraw(context, w, left, top, topwindow->x, bottom);
windowDraw(context, w, topwindow->x, top, right, bottom);
}
else if (topwindow->x + topwindow->width < right)
{
// Split draw at topwindow.right
windowDraw(dpi, w, left, top, topwindow->x + topwindow->width, bottom);
windowDraw(dpi, w, topwindow->x + topwindow->width, top, right, bottom);
windowDraw(context, w, left, top, topwindow->x + topwindow->width, bottom);
windowDraw(context, w, topwindow->x + topwindow->width, top, right, bottom);
}
else if (topwindow->y > top)
{
// Split draw at topwindow.top
windowDraw(dpi, w, left, top, right, topwindow->y);
windowDraw(dpi, w, left, topwindow->y, right, bottom);
windowDraw(context, w, left, top, right, topwindow->y);
windowDraw(context, w, left, topwindow->y, right, bottom);
}
else if (topwindow->y + topwindow->height < bottom)
{
// Split draw at topwindow.bottom
windowDraw(dpi, w, left, top, right, topwindow->y + topwindow->height);
windowDraw(dpi, w, left, topwindow->y + topwindow->height, right, bottom);
windowDraw(context, w, left, top, right, topwindow->y + topwindow->height);
windowDraw(context, w, left, topwindow->y + topwindow->height, right, bottom);
}
// Drawing for this region should be done now, exit

View File

@ -0,0 +1,98 @@
#pragma once
#include <cstdint>
namespace OpenLoco
{
using currency32_t = int32_t;
#pragma pack(push, 1)
struct currency48_t
{
uint32_t var_00 = 0;
int16_t var_04 = 0;
currency48_t(int32_t currency)
: currency48_t(static_cast<int64_t>(currency))
{
}
currency48_t(int64_t currency)
{
var_00 = currency & 0xFFFFFFFF;
var_04 = (currency >> 32) & 0xFFFF;
}
int64_t asInt64() const
{
return var_00 | (static_cast<int64_t>(var_04) << 32);
}
bool operator==(const currency48_t rhs) const
{
return var_00 == rhs.var_00 && var_04 == rhs.var_04;
}
bool operator!=(const currency48_t rhs) const
{
return !(var_00 == rhs.var_00 && var_04 == rhs.var_04);
}
currency48_t operator+(currency32_t& rhs)
{
return currency48_t(asInt64() + rhs);
}
currency48_t operator+(currency48_t& rhs)
{
return currency48_t(asInt64() + rhs.asInt64());
}
currency48_t& operator+=(currency32_t& rhs)
{
auto sum = currency48_t(asInt64() + rhs);
return *this = sum;
}
currency48_t& operator+=(currency48_t& rhs)
{
auto sum = currency48_t(asInt64() + rhs.asInt64());
return *this = sum;
}
currency48_t operator-(currency32_t& rhs)
{
return currency48_t(asInt64() - rhs);
}
currency48_t operator-(currency48_t& rhs)
{
return currency48_t(asInt64() - rhs.asInt64());
}
currency48_t& operator-=(currency32_t& rhs)
{
auto sum = currency48_t(asInt64() - rhs);
return *this = sum;
}
currency48_t& operator-=(currency48_t& rhs)
{
auto sum = currency48_t(asInt64() - rhs.asInt64());
return *this = sum;
}
bool operator<(const currency48_t& rhs) const
{
return asInt64() < rhs.asInt64();
}
bool operator<(const int64_t rhs) const
{
return asInt64() < rhs;
}
};
#pragma pack(pop)
static_assert(sizeof(currency48_t) == 6);
}

View File

@ -0,0 +1,112 @@
#include "Economy.h"
#include "../CompanyManager.h"
#include "../Interop/Interop.hpp"
#include "../Ui/WindowManager.h"
#include "../Ui/WindowType.h"
using namespace OpenLoco::Interop;
namespace OpenLoco::Economy
{
static const uint32_t _inflationFactors[32] = {
20,
20,
20,
20,
23,
20,
23,
23,
20,
17,
17,
20,
20,
20,
20,
20,
20,
20,
20,
20,
20,
20,
20,
20,
20,
20,
20,
20,
20,
20,
20,
20,
};
static loco_global<uint32_t[32], 0x00525E5E> currencyMultiplicationFactor;
static loco_global<currency32_t[32][60], 0x009C68F8> _deliveredCargoPayment;
// NB: This is not used for anything due to a mistake in original inflation calculation
// looks as if it was meant to be extra precesion for the currencyMultiplicationFactor
// Always 0.
static loco_global<uint32_t[32], 0x00525EDE> _525EDE;
// 0x004375F7
void buildDeliveredCargoPaymentsTable()
{
for (uint8_t cargoItem = 0; cargoItem < ObjectManager::getMaxObjects(ObjectType::cargo); ++cargoItem)
{
auto* cargoObj = ObjectManager::get<CargoObject>(cargoItem);
if (cargoObj == nullptr)
{
continue;
}
for (uint16_t numDays = 2; numDays <= 122; ++numDays)
{
_deliveredCargoPayment[cargoItem][(numDays / 2) - 1] = CompanyManager::calculateDeliveredCargoPayment(cargoItem, 100, 10, numDays);
}
}
}
// 0x0046E239
// NB: called in sub_46E2C0 below, as well in openloco::date_tick.
void updateMonthly()
{
for (uint8_t i = 0; i < 32; i++)
{
currencyMultiplicationFactor[i] += (static_cast<uint64_t>(_inflationFactors[i]) * currencyMultiplicationFactor[i]) >> 12;
}
buildDeliveredCargoPaymentsTable();
Ui::WindowManager::invalidate(Ui::WindowType::companyList);
Ui::WindowManager::invalidate(Ui::WindowType::buildVehicle);
Ui::WindowManager::invalidate(Ui::WindowType::construction);
Ui::WindowManager::invalidate(Ui::WindowType::terraform);
Ui::WindowManager::invalidate(Ui::WindowType::industryList);
}
// 0x0046E2C0
void sub_46E2C0(uint16_t year)
{
for (uint8_t i = 0; i < 32; i++)
{
currencyMultiplicationFactor[i] = 1024;
_525EDE[i] = 0;
}
// OpenLoco allows 1800 as the minimum year, whereas Locomotion uses 1900.
// Treat years before 1900 as though they were 1900 to not change vanilla scenarios.
const uint32_t baseYear = std::clamp<uint32_t>(year, 1900, 2030) - 1900;
for (uint32_t monthCount = baseYear * 12; monthCount > 0; monthCount--)
{
updateMonthly();
}
}
currency32_t getInflationAdjustedCost(uint16_t costFactor, uint8_t costIndex, uint8_t divisor)
{
return costFactor * currencyMultiplicationFactor[costIndex] / (1 << divisor);
}
}

View File

@ -0,0 +1,12 @@
#pragma once
#include "Currency.h"
#include <cstdint>
namespace OpenLoco::Economy
{
void updateMonthly();
void sub_46E2C0(uint16_t year);
currency32_t getInflationAdjustedCost(uint16_t costFactor, uint8_t costIndex, uint8_t divisor);
void buildDeliveredCargoPaymentsTable();
}

View File

@ -0,0 +1,28 @@
#pragma once
#include "../Types.hpp"
namespace OpenLoco
{
enum ExpenditureType : uint8_t
{
TrainIncome,
TrainRunningCosts,
BusIncome,
BusRunningCosts,
TruckIncome,
TruckRunningCosts,
TramIncome,
TramRunningCosts,
AircraftIncome,
AircraftRunningCosts,
ShipIncome,
ShipRunningCosts,
Construction,
VehiclePurchases,
VehicleDisposals,
LoanInterest,
Miscellaneous,
Count
};
}

View File

@ -0,0 +1,81 @@
#include "EditorController.h"
#include "Ui/WindowManager.h"
using namespace OpenLoco::Interop;
using namespace OpenLoco::Ui;
using namespace OpenLoco::Ui::Windows;
namespace OpenLoco::EditorController
{
static loco_global<Step, 0x009C8714> _editorStep;
// 0x0043D7DC
void init()
{
call(0x0043D7DC);
}
Step getCurrentStep()
{
return _editorStep;
}
Step getPreviousStep()
{
return Step(static_cast<uint8_t>(*_editorStep) - 1);
}
Step getNextStep()
{
return Step(static_cast<uint8_t>(*_editorStep) + 1);
}
bool canGoBack()
{
return getCurrentStep() != Step::objectSelection;
}
// 0x0043D0FA
void goToPreviousStep()
{
call(0x0043D0FA);
}
// 0x0043D15D
void goToNextStep()
{
call(0x0043D15D);
}
// Part of 0x0043D9D4
void tick()
{
switch (getCurrentStep())
{
case Step::null:
break;
case Step::objectSelection:
if (WindowManager::find(WindowType::objectSelection) == nullptr)
Windows::ObjectSelectionWindow::open();
break;
case Step::landscapeEditor:
// Scenario/landscape loaded?
if ((addr<0x00525E28, uint32_t>() & 1) != 0)
return;
if (WindowManager::find(WindowType::landscapeGeneration) == nullptr)
Windows::LandscapeGeneration::open();
break;
case Step::scenarioOptions:
if (WindowManager::find(WindowType::scenarioOptions) == nullptr)
Windows::ScenarioOptions::open();
break;
case Step::saveScenario:
break;
}
}
}

View File

@ -0,0 +1,27 @@
#pragma once
#include <cstdint>
namespace OpenLoco::EditorController
{
enum class Step : int8_t
{
null = -1,
objectSelection = 0,
landscapeEditor = 1,
scenarioOptions = 2,
saveScenario = 3,
};
void init();
Step getCurrentStep();
Step getPreviousStep();
Step getNextStep();
bool canGoBack();
void goToPreviousStep();
void goToNextStep();
void tick();
}

View File

@ -1,4 +1,4 @@
#include "Thing.h"
#include "Entity.h"
#include "../Config.h"
#include "../Graphics/Gfx.h"
#include "../Interop/Interop.hpp"
@ -8,19 +8,24 @@
using namespace OpenLoco;
using namespace OpenLoco::Interop;
bool EntityBase::isEmpty() const
{
return base_type == EntityBaseType::null;
}
// 0x0046FC83
void thing_base::moveTo(loc16 loc)
void EntityBase::moveTo(const Map::Pos3& loc)
{
registers regs;
regs.ax = loc.x;
regs.cx = loc.y;
regs.dx = loc.z;
regs.esi = (int32_t)this;
regs.esi = X86Pointer(this);
call(0x0046FC83, regs);
}
// 0x004CBB01
void OpenLoco::thing_base::invalidateSprite()
void OpenLoco::EntityBase::invalidateSprite()
{
Ui::ViewportManager::invalidate((Thing*)this, ZoomLevel::eighth);
Ui::ViewportManager::invalidate(this, ZoomLevel::eighth);
}

View File

@ -0,0 +1,104 @@
#pragma once
#include "../Location.hpp"
#include "../Map/Map.hpp"
#include "../Types.hpp"
#include <cstdint>
#include <limits>
namespace OpenLoco::Vehicles
{
struct VehicleBase;
}
namespace OpenLoco
{
struct MiscBase;
namespace EntityId
{
constexpr EntityId_t null = std::numeric_limits<EntityId_t>::max();
}
enum class EntityBaseType : uint8_t
{
vehicle = 0,
misc,
null = 0xFF
};
enum class Pitch : uint8_t
{
// actual angle (for trig)
flat = 0,
up6deg = 1, // Transition, 5.75 deg
up12deg = 2, // Gentle, 11.75 deg
up18deg = 3, // Transition, 17 deg
up25deg = 4, // Steep, 22.5 deg
down6deg = 5,
down12deg = 6,
down18deg = 7,
down25deg = 8,
up10deg = 9, // Gentle Curve Up, 10 deg
down10deg = 10,
up20deg = 11, // Steep Curve Up, 19.25 deg
down20deg = 12,
};
#pragma pack(push, 1)
struct EntityBase
{
EntityBaseType base_type;
private:
uint8_t type; // Use type specific getters/setters as this depends on base_type
public:
EntityId_t nextQuadrantId; // 0x02
EntityId_t next_thing_id; // 0x04
EntityId_t llPreviousId; // 0x06
uint8_t linkedListOffset; // 0x8
uint8_t var_09;
EntityId_t id; // 0xA
uint16_t var_0C;
Map::Pos3 position; // 0x0E
uint8_t var_14;
uint8_t var_15;
int16_t sprite_left; // 0x16
int16_t sprite_top; // 0x18
int16_t sprite_right; // 0x1A
int16_t sprite_bottom; // 0x1C
uint8_t sprite_yaw; // 0x1E
Pitch sprite_pitch; // 0x1F
uint8_t pad_20;
CompanyId_t owner; // 0x21
string_id name; // 0x22, combined with ordinalNumber on vehicles
void moveTo(const Map::Pos3& loc);
void invalidateSprite();
Vehicles::VehicleBase* asVehicle() const { return asBase<Vehicles::VehicleBase, EntityBaseType::vehicle>(); }
MiscBase* asMisc() const { return asBase<MiscBase, EntityBaseType::misc>(); }
bool isEmpty() const;
protected:
constexpr uint8_t getSubType() const { return type; }
void setSubType(const uint8_t newType) { type = newType; }
private:
template<typename TType, EntityBaseType TClass>
TType* asBase() const
{
return base_type == TClass ? (TType*)this : nullptr;
}
};
// Max size of a Entity. Use when needing to know Entity size
struct Entity : EntityBase
{
private:
uint8_t pad_24[0x80 - 0x24];
};
static_assert(sizeof(Entity) == 0x80);
#pragma pack(pop)
}

View File

@ -0,0 +1,383 @@
#include "EntityManager.h"
#include "../Console.h"
#include "../Entities/Misc.h"
#include "../GameCommands/GameCommands.h"
#include "../Interop/Interop.hpp"
#include "../Localisation/StringIds.h"
#include "../Map/Tile.h"
#include "../OpenLoco.h"
#include "../Vehicles/Vehicle.h"
#include "EntityTweener.h"
using namespace OpenLoco::Interop;
namespace OpenLoco::EntityManager
{
loco_global<EntityId_t[numEntityLists], 0x00525E3E> _heads;
loco_global<uint16_t[numEntityLists], 0x00525E4C> _listCounts;
loco_global<Entity[maxEntities], 0x006DB6DC> _entities;
loco_global<EntityId_t[0x40001], 0x01025A8C> _entitySpatialIndex;
loco_global<uint32_t, 0x01025A88> _entitySpatialCount;
constexpr size_t _entitySpatialIndexNull = 0x40000;
// 0x0046FDFD
void reset()
{
// Reset all entities to 0
std::fill_n(_entities.get(), maxEntities, Entity{});
// Reset all entity lists
for (auto& count : _listCounts)
{
count = 0;
}
for (auto& head : _heads)
{
head = EntityId::null;
}
// Remake null entities (size maxNormalEntities)
EntityBase* previous = nullptr;
EntityId_t id = 0;
for (; id < maxNormalEntities; ++id)
{
auto& ent = _entities[id];
ent.base_type = EntityBaseType::null;
ent.id = id;
ent.next_thing_id = EntityId::null;
ent.linkedListOffset = static_cast<uint8_t>(EntityListType::null) * 2;
if (previous == nullptr)
{
ent.llPreviousId = EntityId::null;
_heads[static_cast<uint8_t>(EntityListType::null)] = id;
}
else
{
ent.llPreviousId = previous->id;
previous->next_thing_id = id;
}
previous = &ent;
}
_listCounts[static_cast<uint8_t>(EntityListType::null)] = maxNormalEntities;
// Remake null money entities (size maxMoneyEntities)
previous = nullptr;
for (; id < maxEntities; ++id)
{
auto& ent = _entities[id];
ent.base_type = EntityBaseType::null;
ent.id = id;
ent.next_thing_id = EntityId::null;
ent.linkedListOffset = static_cast<uint8_t>(EntityListType::nullMoney) * 2;
if (previous == nullptr)
{
ent.llPreviousId = EntityId::null;
_heads[static_cast<uint8_t>(EntityListType::nullMoney)] = id;
}
else
{
ent.llPreviousId = previous->id;
previous->next_thing_id = id;
}
previous = &ent;
}
_listCounts[static_cast<uint8_t>(EntityListType::nullMoney)] = maxMoneyEntities;
resetSpatialIndex();
EntityTweener::get().reset();
}
EntityId_t firstId(EntityListType list)
{
return _heads[(size_t)list];
}
uint16_t getListCount(const EntityListType list)
{
return _listCounts[static_cast<size_t>(list)];
}
template<>
Vehicles::VehicleHead* first()
{
return get<Vehicles::VehicleHead>(firstId(EntityListType::vehicleHead));
}
template<>
EntityBase* get(EntityId_t id)
{
EntityBase* result = nullptr;
if (id < maxEntities)
{
return &_entities.get()[id];
}
return result;
}
constexpr size_t getSpatialIndexOffset(const Map::Pos2& loc)
{
size_t index = _entitySpatialIndexNull;
if (loc.x != Location::null)
{
uint16_t x = loc.x & 0x3FE0;
uint16_t y = loc.y & 0x3FE0;
index = (x << 4) | (y >> 5);
}
if (index >= 0x40001)
{
return _entitySpatialIndexNull;
}
return index;
}
EntityId_t firstQuadrantId(const Map::Pos2& loc)
{
auto index = getSpatialIndexOffset(loc);
return _entitySpatialIndex[index];
}
// 0x0046FF54
void resetSpatialIndex()
{
call(0x0046FF54);
}
// 0x0046FC57
void updateSpatialIndex()
{
for (auto& ent : _entities)
{
if (!ent.isEmpty())
{
ent.moveTo(ent.position);
}
}
}
static EntityBase* createEntity(EntityId_t id, EntityListType list)
{
auto* newEntity = get<EntityBase>(id);
if (newEntity == nullptr)
{
Console::error("Tried to create invalid entity!");
return nullptr;
}
moveEntityToList(newEntity, list);
newEntity->position = { Location::null, Location::null, 0 };
auto index = getSpatialIndexOffset(newEntity->position);
auto nextSpatialId = _entitySpatialIndex[index];
_entitySpatialIndex[index] = newEntity->id;
newEntity->nextQuadrantId = nextSpatialId;
newEntity->name = StringIds::empty_pop;
newEntity->var_14 = 16;
newEntity->var_09 = 20;
newEntity->var_15 = 8;
newEntity->var_0C = 0;
newEntity->sprite_left = Location::null;
return newEntity;
}
// 0x004700A5
EntityBase* createEntityMisc()
{
if (getListCount(EntityListType::misc) >= maxMiscEntities)
{
return nullptr;
}
if (getListCount(EntityListType::null) <= 0)
{
return nullptr;
}
auto newId = _heads[static_cast<uint8_t>(EntityListType::null)];
return createEntity(newId, EntityListType::misc);
}
// 0x0047011C
EntityBase* createEntityMoney()
{
if (getListCount(EntityListType::nullMoney) <= 0)
{
return nullptr;
}
auto newId = _heads[static_cast<uint8_t>(EntityListType::nullMoney)];
return createEntity(newId, EntityListType::misc);
}
// 0x00470039
EntityBase* createEntityVehicle()
{
if (getListCount(EntityListType::null) <= 0)
{
return nullptr;
}
auto newId = _heads[static_cast<uint8_t>(EntityListType::null)];
return createEntity(newId, EntityListType::vehicle);
}
// 0x0047024A
void freeEntity(EntityBase* const entity)
{
EntityTweener::get().removeEntity(entity);
auto list = entity->id < 19800 ? EntityListType::null : EntityListType::nullMoney;
moveEntityToList(entity, list);
StringManager::emptyUserString(entity->name);
entity->base_type = EntityBaseType::null;
// Remove from spatial lists
auto* quadId = &_entitySpatialIndex[getSpatialIndexOffset(entity->position)];
_entitySpatialCount = 0;
while (*quadId < maxEntities)
{
auto* quadEnt = get<EntityBase>(*quadId);
if (quadEnt == entity)
{
*quadId = entity->nextQuadrantId;
return;
}
_entitySpatialCount++;
if (_entitySpatialCount > maxEntities)
{
break;
}
quadId = &quadEnt->nextQuadrantId;
}
Console::log("Invalid quadrant ids... Reseting spatial index.");
resetSpatialIndex();
}
// 0x004A8826
void updateVehicles()
{
if ((addr<0x00525E28, uint32_t>() & 1) && !isEditorMode())
{
for (auto v : VehicleList())
{
v->updateVehicle();
}
}
}
// 0x004402F4
void updateMiscEntities()
{
if ((addr<0x00525E28, uint32_t>() & 1))
{
for (auto* misc : EntityList<EntityListIterator<MiscBase>, EntityListType::misc>())
{
misc->update();
}
}
}
// 0x0047019F
void moveEntityToList(EntityBase* const entity, const EntityListType list)
{
auto newListOffset = static_cast<uint8_t>(list) * 2;
if (entity->linkedListOffset == newListOffset)
{
return;
}
auto curList = entity->linkedListOffset / 2;
auto nextId = entity->next_thing_id;
auto previousId = entity->llPreviousId;
// Unlink previous entity from this entity
if (previousId == EntityId::null)
{
_heads[curList] = nextId;
}
else
{
auto* previousEntity = get<EntityBase>(previousId);
if (previousEntity == nullptr)
{
Console::error("Invalid previous entity id. Entity linked list corrupted?");
}
else
{
previousEntity->next_thing_id = nextId;
}
}
// Unlink next entity from this entity
if (nextId != EntityId::null)
{
auto* nextEntity = get<EntityBase>(nextId);
if (nextEntity == nullptr)
{
Console::error("Invalid next entity id. Entity linked list corrupted?");
}
else
{
nextEntity->llPreviousId = previousId;
}
}
entity->llPreviousId = EntityId::null;
entity->linkedListOffset = newListOffset;
entity->next_thing_id = _heads[static_cast<uint8_t>(list)];
_heads[static_cast<uint8_t>(list)] = entity->id;
// Link next entity to this entity
if (entity->next_thing_id != EntityId::null)
{
auto* nextEntity = get<EntityBase>(entity->next_thing_id);
if (nextEntity == nullptr)
{
Console::error("Invalid next entity id. Entity linked list corrupted?");
}
else
{
nextEntity->llPreviousId = entity->id;
}
}
_listCounts[curList]--;
_listCounts[static_cast<uint8_t>(list)]++;
}
// 0x00470188
bool checkNumFreeEntities(const size_t numNewEntities)
{
if (EntityManager::getListCount(EntityManager::EntityListType::null) <= numNewEntities)
{
GameCommands::setErrorText(StringIds::too_many_objects_in_game);
return false;
}
return true;
}
static void zeroEntity(EntityBase* ent)
{
auto next = ent->next_thing_id;
auto previous = ent->llPreviousId;
auto id = ent->id;
auto llOffset = ent->linkedListOffset;
std::fill_n(reinterpret_cast<uint8_t*>(ent), sizeof(Entity), 0);
ent->base_type = EntityBaseType::null;
ent->next_thing_id = next;
ent->llPreviousId = previous;
ent->id = id;
ent->linkedListOffset = llOffset;
}
// 0x0046FED5
void zeroUnused()
{
for (auto* ent : EntityList<EntityListIterator<EntityBase>, EntityListType::null>())
{
zeroEntity(ent);
}
for (auto* ent : EntityList<EntityListIterator<EntityBase>, EntityListType::nullMoney>())
{
zeroEntity(ent);
}
}
}

View File

@ -0,0 +1,168 @@
#pragma once
#include "../Map/Map.hpp"
#include "Entity.h"
#include <cstdio>
#include <iterator>
namespace OpenLoco::Vehicles
{
struct VehicleHead;
}
namespace OpenLoco::EntityManager
{
constexpr size_t numEntityLists = 7;
constexpr size_t maxEntities = 20000;
// There is a seperate pool of 200 entities dedicated for money
constexpr size_t maxMoneyEntities = 200;
// This is the main pool for everything that isn't money
constexpr size_t maxNormalEntities = maxEntities - maxMoneyEntities;
// Money is not counted in this limit
constexpr size_t maxMiscEntities = 4000;
enum class EntityListType
{
null, // Used for vehicles and other misc entities (not money)
nullMoney, // For some reason money effects have their own pool of entities to use
vehicleHead,
misc = 4,
vehicle = 6,
};
void reset();
template<typename T>
T* get(EntityId_t id);
template<>
EntityBase* get(EntityId_t id);
template<typename T>
T* get(EntityId_t id)
{
return static_cast<T*>(get<EntityBase>(id));
}
EntityId_t firstId(EntityListType list);
template<typename T>
T* first();
EntityId_t firstQuadrantId(const Map::Pos2& loc);
void resetSpatialIndex();
void updateSpatialIndex();
EntityBase* createEntityMisc();
EntityBase* createEntityMoney();
EntityBase* createEntityVehicle();
void freeEntity(EntityBase* const entity);
void updateVehicles();
void updateMiscEntities();
uint16_t getListCount(const EntityListType list);
void moveEntityToList(EntityBase* const entity, const EntityListType list);
bool checkNumFreeEntities(const size_t numNewEntities);
void zeroUnused();
template<typename TEntityType, EntityId_t EntityBase::*nextList>
class ListIterator
{
private:
TEntityType* entity = nullptr;
EntityId_t nextEntityId = EntityId::null;
public:
ListIterator(const uint16_t _headId)
: nextEntityId(_headId)
{
++(*this);
}
ListIterator& operator++()
{
entity = get<TEntityType>(nextEntityId);
if (entity)
{
nextEntityId = entity->*nextList;
}
return *this;
}
ListIterator operator++(int)
{
ListIterator retval = *this;
++(*this);
return retval;
}
bool operator==(ListIterator& other) const
{
return entity == other.entity;
}
bool operator!=(ListIterator& other) const
{
return !(*this == other);
}
TEntityType* operator*()
{
return entity;
}
// iterator traits
using difference_type = std::ptrdiff_t;
using value_type = TEntityType;
using pointer = TEntityType*;
using reference = TEntityType&;
using iterator_category = std::forward_iterator_tag;
};
template<typename T>
using EntityListIterator = ListIterator<T, &EntityBase::next_thing_id>;
template<typename T, EntityListType list>
class EntityList
{
private:
uint16_t firstId = EntityId::null;
public:
EntityList()
{
firstId = EntityManager::firstId(list);
}
T begin()
{
return T(firstId);
}
T end()
{
return T(EntityId::null);
}
};
using VehicleList = EntityList<EntityListIterator<Vehicles::VehicleHead>, EntityListType::vehicleHead>;
class EntityTileList
{
private:
uint16_t firstId = EntityId::null;
using Iterator = ListIterator<EntityBase, &EntityBase::nextQuadrantId>;
public:
EntityTileList(const Map::Pos2& loc)
{
firstId = EntityManager::firstQuadrantId(loc);
}
Iterator begin()
{
return Iterator(firstId);
}
Iterator end()
{
return Iterator(EntityId::null);
}
};
}

View File

@ -0,0 +1,128 @@
#include "EntityTweener.h"
#include "../OpenLoco.h"
#include "../Vehicles/Vehicle.h"
#include "Entity.h"
#include <cmath>
#include <iostream>
namespace OpenLoco
{
using EntityListType = EntityManager::EntityListType;
using EntityListIterator = EntityManager::ListIterator<EntityBase, &EntityBase::next_thing_id>;
template<EntityListType id, typename Pred>
void PopulateEntities(std::vector<EntityBase*>& list, std::vector<Map::Pos3>& posList, const Pred& pred)
{
auto entsView = EntityManager::EntityList<EntityListIterator, id>();
for (auto* ent : entsView)
{
if (!pred(ent))
continue;
list.push_back(ent);
posList.emplace_back(ent->position);
}
}
static EntityTweener _tweener;
EntityTweener& EntityTweener::get()
{
return _tweener;
}
void EntityTweener::preTick()
{
restore();
reset();
PopulateEntities<EntityListType::misc>(_entities, _prePos, [](auto* ent) { return true; });
PopulateEntities<EntityListType::vehicle>(_entities, _prePos, [](auto* ent) {
const auto* vehicle = ent->asVehicle();
if (vehicle == nullptr)
{
// This can be never null but makes the compiler happy.
return false;
}
return vehicle->isVehicleBody() || vehicle->isVehicleBogie();
});
}
void EntityTweener::postTick()
{
for (auto* ent : _entities)
{
if (ent == nullptr || ent->id == EntityId::null)
{
// Sprite was removed, add a dummy position to keep the index aligned.
_postPos.emplace_back(0, 0, 0);
}
else
{
_postPos.emplace_back(ent->position);
}
}
}
void EntityTweener::removeEntity(const EntityBase* entity)
{
auto it = std::find(_entities.begin(), _entities.end(), entity);
if (it != _entities.end())
{
*it = nullptr;
}
}
void EntityTweener::tween(float alpha)
{
const float inv = (1.0f - alpha);
for (size_t i = 0; i < _entities.size(); ++i)
{
auto* ent = _entities[i];
if (ent == nullptr)
continue;
auto& posA = _prePos[i];
auto& posB = _postPos[i];
if (posA == posB)
continue;
auto newPos = Map::Pos3{ static_cast<int16_t>(std::round(posB.x * alpha + posA.x * inv)),
static_cast<int16_t>(std::round(posB.y * alpha + posA.y * inv)),
static_cast<int16_t>(std::round(posB.z * alpha + posA.z * inv)) };
if (ent->position == newPos)
continue;
ent->moveTo(newPos);
ent->invalidateSprite();
}
}
void EntityTweener::restore()
{
for (size_t i = 0; i < _entities.size(); ++i)
{
auto* ent = _entities[i];
if (ent == nullptr)
continue;
auto& newPos = _postPos[i];
if (ent->position == newPos)
continue;
ent->moveTo(newPos);
ent->invalidateSprite();
}
}
void EntityTweener::reset()
{
_entities.clear();
_prePos.clear();
_postPos.clear();
}
}

View File

@ -0,0 +1,25 @@
#pragma once
#include "../Map/Map.hpp"
#include "EntityManager.h"
#include <vector>
namespace OpenLoco
{
class EntityTweener
{
std::vector<EntityBase*> _entities;
std::vector<Map::Pos3> _prePos;
std::vector<Map::Pos3> _postPos;
public:
static EntityTweener& get();
void preTick();
void postTick();
void removeEntity(const EntityBase* entity);
void tween(float alpha);
void restore();
void reset();
};
}

View File

@ -0,0 +1,114 @@
#include "Misc.h"
#include "../Localisation/FormatArguments.hpp"
#include "../Localisation/StringIds.h"
#include "../Map/TileManager.h"
#include "../Objects/ObjectManager.h"
#include "../Ui/WindowManager.h"
#include "EntityManager.h"
using namespace OpenLoco::Interop;
namespace OpenLoco
{
// 0x004405CD
void MiscBase::update()
{
registers regs;
regs.esi = X86Pointer(this);
call(0x004405CD, regs);
}
SteamObject* Exhaust::object() const
{
return ObjectManager::get<SteamObject>(object_id & 0x7F);
}
// 0x0044080C
Exhaust* Exhaust::create(Map::Pos3 loc, uint8_t type)
{
if ((uint16_t)loc.x > 12287 || (uint16_t)loc.y > 12287)
return nullptr;
auto surface = Map::TileManager::get(loc.x & 0xFFE0, loc.y & 0xFFE0).surface();
if (surface == nullptr)
return nullptr;
if (loc.z <= surface->baseZ() * 4)
return nullptr;
auto _exhaust = static_cast<Exhaust*>(EntityManager::createEntityMisc());
if (_exhaust != nullptr)
{
_exhaust->base_type = EntityBaseType::misc;
_exhaust->moveTo(loc);
_exhaust->object_id = type;
auto obj = _exhaust->object();
_exhaust->var_14 = obj->var_05;
_exhaust->var_09 = obj->var_06;
_exhaust->var_15 = obj->var_07;
_exhaust->setSubType(MiscEntityType::exhaust);
_exhaust->var_26 = 0;
_exhaust->var_28 = 0;
_exhaust->var_32 = 0;
_exhaust->var_34 = 0;
_exhaust->var_36 = 0;
}
return _exhaust;
}
// 0x00440BEB
Smoke* Smoke::create(Map::Pos3 loc)
{
auto t = static_cast<Smoke*>(EntityManager::createEntityMisc());
if (t != nullptr)
{
t->var_14 = 44;
t->var_09 = 32;
t->var_15 = 34;
t->base_type = EntityBaseType::misc;
t->moveTo(loc);
t->setSubType(MiscEntityType::smoke);
t->frame = 0;
}
return t;
}
static loco_global<int32_t, 0x112C876> _currentFontSpriteBase;
// 0x00440A74
// company : updatingCompanyId global
// loc : ax, cx, dx
// amount : ebx
MoneyEffect* MoneyEffect::create(const Map::Pos3& loc, const CompanyId_t company, const currency32_t amount)
{
if (isTitleMode())
{
return nullptr;
}
auto* m = static_cast<MoneyEffect*>(EntityManager::createEntityMoney());
if (m != nullptr)
{
m->amount = amount;
m->var_14 = 64;
m->var_09 = 20;
m->var_15 = 30;
m->base_type = EntityBaseType::misc;
m->var_2E = company;
m->moveTo(loc);
m->setSubType(MiscEntityType::windowCurrency);
m->var_26 = 0;
m->var_28 = 0;
string_id strFormat = (amount < 0) ? StringIds::format_currency_expense_red_negative : StringIds::format_currency_income_green;
char buffer[255] = {};
auto args = FormatArguments::common(amount);
StringManager::formatString(buffer, strFormat, &args);
_currentFontSpriteBase = Font::medium_bold;
m->offsetX = -Gfx::getStringWidth(buffer) / 2;
m->wiggle = 0;
}
return m;
}
}

View File

@ -0,0 +1,137 @@
#pragma once
#include "../Economy/Currency.h"
#include "../Map/Map.hpp"
#include "../Objects/SteamObject.h"
#include "Entity.h"
namespace OpenLoco
{
struct Exhaust;
struct MoneyEffect;
struct VehicleCrashParticle;
struct ExplosionCloud;
struct Splash;
struct Fireball;
struct ExplosionSmoke;
struct Smoke;
enum class MiscEntityType : uint8_t
{
exhaust = 0, // Steam from the exhaust
redGreenCurrency = 1,
windowCurrency = 2, // currency which is created in the company's colour when a transaction is made (for example the train arrives with a passengers into the station)
vehicleCrashParticle = 3, // parts (particles) of vehicle after crash which they fall to the ground after explosion
explosionCloud = 4, // explosion which is created when two trains (or maybe other vehicles) crash to each other
splash = 5, // splash when particles after explosion land to water and creates a splash (exploding train on the bridge)
fireball = 6,
explosionSmoke = 7,
smoke = 8 // Smoke from broken down train
};
#pragma pack(push, 1)
struct MiscBase : EntityBase
{
private:
template<typename TType, MiscEntityType TClass>
TType* as() const
{
return getSubType() == TClass ? (TType*)this : nullptr;
}
public:
MiscEntityType getSubType() const { return MiscEntityType(EntityBase::getSubType()); }
void setSubType(const MiscEntityType newType) { EntityBase::setSubType(static_cast<uint8_t>(newType)); }
Exhaust* asExhaust() const { return as<Exhaust, MiscEntityType::exhaust>(); }
MoneyEffect* asRedGreenCurrency() const { return as<MoneyEffect, MiscEntityType::redGreenCurrency>(); }
MoneyEffect* asWindowCurrency() const { return as<MoneyEffect, MiscEntityType::windowCurrency>(); }
VehicleCrashParticle* asVehicleCrashParticle() const { return as<VehicleCrashParticle, MiscEntityType::vehicleCrashParticle>(); }
ExplosionCloud* asExplosionCloud() const { return as<ExplosionCloud, MiscEntityType::explosionCloud>(); }
Splash* asSplash() const { return as<Splash, MiscEntityType::splash>(); }
Fireball* asFireball() const { return as<Fireball, MiscEntityType::fireball>(); }
ExplosionSmoke* asExplosionSmoke() const { return as<ExplosionSmoke, MiscEntityType::explosionSmoke>(); }
Smoke* asSmoke() const { return as<Smoke, MiscEntityType::smoke>(); }
void update();
};
struct Exhaust : MiscBase
{
uint8_t pad_24[0x26 - 0x24];
int16_t var_26;
int16_t var_28;
uint8_t pad_2A[0x32 - 0x2A];
int16_t var_32;
int16_t var_34;
int16_t var_36;
uint8_t pad_38[0x49 - 0x38];
uint8_t object_id; // 0x49
SteamObject* object() const;
static Exhaust* create(Map::Pos3 loc, uint8_t type);
};
static_assert(sizeof(Exhaust) == 0x4A);
struct MoneyEffect : MiscBase
{
uint8_t pad_24[0x26 - 0x24];
uint16_t var_26;
uint16_t var_28;
int32_t amount; // 0x2A - currency amount in British pounds - different currencies are probably getting recalculated
int8_t var_2E; // company colour?
uint8_t pad_2F[0x44 - 0x2F];
int16_t offsetX; // 0x44
uint16_t wiggle; // 0x46
static MoneyEffect* create(const Map::Pos3& loc, const CompanyId_t company, const currency32_t amount);
};
static_assert(sizeof(MoneyEffect) == 0x48);
struct VehicleCrashParticle : MiscBase
{
uint8_t pad_24[0x28 - 0x24];
uint16_t frame; // 0x28
uint8_t pad_2A[0x2E - 0x2A];
ColourScheme colourScheme; // 0x2E
uint16_t crashedSpriteBase; // 0x30 crashed_sprite_base
};
static_assert(sizeof(VehicleCrashParticle) == 0x32);
struct ExplosionCloud : MiscBase
{
uint8_t pad_24[0x28 - 0x24];
uint16_t frame; // 0x28
};
static_assert(sizeof(ExplosionCloud) == 0x2A);
struct Splash : MiscBase
{
uint8_t pad_24[0x28 - 0x24];
uint16_t frame; // 0x28
};
static_assert(sizeof(Splash) == 0x2A);
struct Fireball : MiscBase
{
uint8_t pad_24[0x28 - 0x24];
uint16_t frame; // 0x28
};
static_assert(sizeof(Fireball) == 0x2A);
struct ExplosionSmoke : MiscBase
{
uint8_t pad_24[0x28 - 0x24];
uint16_t frame; // 0x28
};
static_assert(sizeof(ExplosionSmoke) == 0x2A);
struct Smoke : MiscBase
{
uint8_t pad_24[0x28 - 0x24];
uint16_t frame; // 0x28
static Smoke* create(Map::Pos3 loc);
};
static_assert(sizeof(Smoke) == 0x2A);
#pragma pack(pop)
}

View File

@ -53,6 +53,7 @@ namespace OpenLoco::Environment
static constexpr const char* searchPaths[] = {
"C:/Program Files (x86)/Atari/Locomotion",
"C:/GOG Games/Chris Sawyer's Locomotion",
"C:/GOG Games/Locomotion",
};
std::cout << "Searching for Locomotion install path..." << std::endl;
@ -70,7 +71,7 @@ namespace OpenLoco::Environment
static fs::path resolveLocoInstallPath()
{
auto& cfg = Config::getNew();
auto path = fs::path(cfg.loco_install_path);
auto path = fs::u8path(cfg.loco_install_path);
if (!path.empty())
{
if (validateLocoInstallPath(path))
@ -109,7 +110,7 @@ namespace OpenLoco::Environment
static fs::path getLocoInstallPath()
{
return _path_install.get();
return fs::u8path(_path_install.get());
}
#ifndef _WIN32
@ -146,7 +147,7 @@ namespace OpenLoco::Environment
{
auto basePath = getBasePath(id);
auto subPath = getSubPath(id);
auto result = basePath / subPath;
auto result = (basePath / subPath).lexically_normal();
if (!fs::exists(result))
{
#ifndef _WIN32
@ -166,19 +167,73 @@ namespace OpenLoco::Environment
return result;
}
fs::path getPathNoWarning(path_id id)
{
auto basePath = getBasePath(id);
auto subPath = getSubPath(id);
auto result = (basePath / subPath).lexically_normal();
return result;
}
template<typename T>
static void setDirectory(T& buffer, fs::path path)
{
Utility::strcpy_safe(buffer, path.make_preferred().u8string().c_str());
}
void autoCreateDirectory(const fs::path& path)
{
try
{
if (!fs::is_directory(path))
{
auto path8 = path.u8string();
std::printf("Creating directory: %s\n", path8.c_str());
fs::create_directories(path);
// clang-format off
fs::permissions(
path,
fs::perms::owner_all |
fs::perms::group_read | fs::perms::group_exec |
fs::perms::others_read | fs::perms::others_exec);
// clang-format on
}
}
catch (const std::exception& e)
{
std::fprintf(stderr, "Unable to create directory: %s\n", e.what());
Ui::showMessageBox("Unable to create directory", e.what());
}
}
// 0x004412CE
void resolvePaths()
{
auto basePath = resolveLocoInstallPath();
setDirectory(_path_install, basePath);
setDirectory(_path_saves_single_player, basePath / "Single Player Saved Games/");
setDirectory(_path_saves_two_player, basePath / "Two Player Saved Games/");
// NB: vanilla routines do not use std::filesystem yet, so the trailing slash is still needed.
auto saveDirectory = getPathNoWarning(path_id::save) / "";
auto& configLastSavePath = Config::getNew().last_save_path;
if (!configLastSavePath.empty())
{
// Getting the directory can fail if config is bad.
try
{
auto directory = fs::u8path(configLastSavePath);
if (fs::is_directory(directory))
{
saveDirectory = directory;
}
}
catch (std::system_error&)
{
}
}
setDirectory(_path_saves_single_player, saveDirectory);
setDirectory(_path_saves_two_player, saveDirectory);
autoCreateDirectory(saveDirectory);
setDirectory(_path_scenarios, basePath / "Scenarios/*.SC5");
setDirectory(_path_landscapes, basePath / "Scenarios/Landscapes/*.SC5");
setDirectory(_path_objects, basePath / "ObjData/*.DAT");
@ -193,6 +248,8 @@ namespace OpenLoco::Environment
case path_id::gamecfg:
case path_id::scores:
case path_id::openloco_yml:
case path_id::save:
case path_id::autosave:
return platform::getUserDirectory();
case path_id::language_files:
#if defined(__APPLE__) && defined(__MACH__)
@ -258,6 +315,9 @@ namespace OpenLoco::Environment
"Data/TUT800_3.DAT",
"openloco.yml",
"language",
"save",
"save/autosave",
"1.TMP",
};
size_t index = (size_t)id;

View File

@ -56,8 +56,13 @@ namespace OpenLoco::Environment
tut800_3,
openloco_yml,
language_files,
save,
autosave,
_1tmp,
};
void autoCreateDirectory(const fs::path& path);
fs::path getPath(path_id id);
fs::path getPathNoWarning(path_id id);
void resolvePaths();
}

327
src/OpenLoco/Game.cpp Normal file
View File

@ -0,0 +1,327 @@
#include "Game.h"
#include "Audio/Audio.h"
#include "CompanyManager.h"
#include "Config.h"
#include "GameCommands/GameCommands.h"
#include "GameException.hpp"
#include "Input.h"
#include "Interop/Interop.hpp"
#include "Localisation/StringIds.h"
#include "MultiPlayer.h"
#include "S5/S5.h"
#include "Title.h"
#include "Ui/ProgressBar.h"
#include "Ui/WindowManager.h"
#include "Ui/WindowType.h"
namespace OpenLoco::Game
{
static loco_global<uint8_t, 0x00508F08> _game_command_nest_level;
static loco_global<GameCommands::LoadOrQuitMode, 0x0050A002> _savePromptType;
// TODO: make accessible from Environment
static loco_global<char[257], 0x0050B1CF> _path_saves_single_player;
static loco_global<char[257], 0x0050B2EC> _path_saves_two_player;
static loco_global<char[257], 0x0050B406> _path_scenarios;
static loco_global<char[257], 0x0050B518> _path_landscapes;
static loco_global<char[256], 0x0050B745> _currentScenarioFilename;
static loco_global<uint32_t, 0x00525E28> _525E28;
static loco_global<uint16_t, 0x009C871A> _scenarioFlags;
static loco_global<char[64], 0x009C873E> _scenarioTitle;
static loco_global<char[512], 0x0112CE04> _savePath;
// 0x0046DB4C
static void sub_46DB4C()
{
call(0x0046DB4C);
}
using Ui::Windows::PromptBrowse::browse_type;
static bool openBrowsePrompt(string_id titleId, browse_type type, const char* filter)
{
char titleBuffer[256] = {};
StringManager::formatString(titleBuffer, std::size(titleBuffer), titleId);
Audio::pauseSound();
setPauseFlag(1 << 2);
Gfx::invalidateScreen();
Gfx::render();
bool confirm = Ui::Windows::PromptBrowse::open(type, &_savePath[0], filter, titleBuffer);
Audio::unpauseSound();
Ui::processMessagesMini();
unsetPauseFlag(1 << 2);
Gfx::invalidateScreen();
Gfx::render();
return confirm;
}
// 0x004416FF
bool loadSaveGameOpen()
{
if (!isNetworked())
strncpy(&_savePath[0], &_path_saves_single_player[0], std::size(_savePath));
else
strncpy(&_savePath[0], &_path_saves_two_player[0], std::size(_savePath));
return openBrowsePrompt(StringIds::title_prompt_load_game, browse_type::load, S5::filterSV5);
}
// 0x004417A7
bool loadLandscapeOpen()
{
strncpy(&_savePath[0], &_path_landscapes[0], std::size(_savePath));
return openBrowsePrompt(StringIds::title_prompt_load_landscape, browse_type::load, S5::filterSC5);
}
// 0x00441843
bool saveSaveGameOpen()
{
if (!isNetworked())
strncpy(&_savePath[0], &_path_saves_single_player[0], std::size(_savePath));
else
strncpy(&_savePath[0], &_path_saves_two_player[0], std::size(_savePath));
return openBrowsePrompt(StringIds::title_prompt_save_game, browse_type::save, S5::filterSV5);
}
// 0x004418DB
bool saveScenarioOpen()
{
strncpy(&_savePath[0], &_path_landscapes[0], std::size(_savePath));
strncat(&_savePath[0], &_scenarioTitle[0], std::size(_savePath));
strncat(&_savePath[0], S5::extensionSC5, std::size(_savePath));
return openBrowsePrompt(StringIds::title_prompt_save_game, browse_type::save, S5::filterSC5);
}
// 0x00441993
bool saveLandscapeOpen()
{
*_scenarioFlags &= ~(1 << 0);
if (_525E28 & (1 << 0))
{
*_scenarioFlags |= (1 << 0);
sub_46DB4C();
}
strncpy(&_savePath[0], &_path_landscapes[0], std::size(_savePath));
strncat(&_savePath[0], &_scenarioTitle[0], std::size(_savePath));
strncat(&_savePath[0], S5::extensionSC5, std::size(_savePath));
return openBrowsePrompt(StringIds::title_prompt_save_landscape, browse_type::save, S5::filterSC5);
}
// 0x004424CE
static bool sub_4424CE()
{
registers regs;
call(0x004424CE);
return regs.eax != 0;
}
// 0x0043BFF8
void loadGame()
{
GameCommands::do_21(1, 0);
Input::toolCancel();
if (isEditorMode())
{
if (Game::loadLandscapeOpen())
{
// 0x0043C087
auto path = fs::u8path(&_savePath[0]).replace_extension(S5::extensionSC5);
std::strncpy(&_currentScenarioFilename[0], path.u8string().c_str(), std::size(_currentScenarioFilename));
if (sub_4424CE())
{
resetScreenAge();
throw GameException::Interrupt;
}
}
}
else if (!isNetworked())
{
if (Game::loadSaveGameOpen())
{
// 0x0043C033
auto path = fs::u8path(&_savePath[0]).replace_extension(S5::extensionSV5);
std::strncpy(&_currentScenarioFilename[0], path.u8string().c_str(), std::size(_currentScenarioFilename));
if (S5::load(path, 0))
{
resetScreenAge();
throw GameException::Interrupt;
}
}
}
else if (isNetworked())
{
// 0x0043C0DB
if (CompanyManager::getControllingId() == CompanyManager::updatingCompanyId())
{
MultiPlayer::setFlag(MultiPlayer::flags::flag_4);
MultiPlayer::setFlag(MultiPlayer::flags::flag_3);
}
}
// 0x0043C0D1
Gfx::invalidateScreen();
}
// 0x0043C182
void quitGame()
{
_game_command_nest_level = 0;
// Path for networked games; untested.
if (isNetworked())
{
clearScreenFlag(ScreenFlags::networked);
auto playerCompanyId = CompanyManager::getControllingId();
auto previousUpdatingId = CompanyManager::updatingCompanyId();
CompanyManager::updatingCompanyId(playerCompanyId);
Ui::WindowManager::closeAllFloatingWindows();
CompanyManager::updatingCompanyId(previousUpdatingId);
setScreenFlag(ScreenFlags::networked);
// If the other party is leaving the game, go back to the title screen.
if (playerCompanyId != previousUpdatingId)
{
// 0x0043C1CD
addr<0x00F25428, uint32_t>() = 0;
clearScreenFlag(ScreenFlags::networked);
clearScreenFlag(ScreenFlags::networkHost);
addr<0x00508F0C, uint32_t>() = 0;
CompanyManager::setControllingId(0);
CompanyManager::setSecondaryPlayerId(CompanyId::null);
Gfx::invalidateScreen();
ObjectManager::loadIndex();
Ui::WindowManager::close(Ui::WindowType::options);
Ui::WindowManager::close(Ui::WindowType::companyFaceSelection);
Ui::WindowManager::close(Ui::WindowType::objectSelection);
clearScreenFlag(ScreenFlags::editor);
Audio::pauseSound();
Audio::unpauseSound();
if (Input::hasFlag(Input::Flags::flag5))
{
Input::sub_407231();
Input::resetFlag(Input::Flags::flag5);
}
Title::start();
Ui::Windows::Error::open(StringIds::error_the_other_player_has_exited_the_game, StringIds::null);
throw GameException::Interrupt;
}
}
// 0x0043C1C8
exitCleanly();
}
// 0x0043C0FD
void returnToTitle()
{
if (isNetworked())
{
Ui::WindowManager::closeAllFloatingWindows();
}
Ui::WindowManager::close(Ui::WindowType::options);
Ui::WindowManager::close(Ui::WindowType::companyFaceSelection);
Ui::WindowManager::close(Ui::WindowType::objectSelection);
Ui::WindowManager::close(Ui::WindowType::saveGamePrompt);
clearScreenFlag(ScreenFlags::editor);
Audio::pauseSound();
Audio::unpauseSound();
if (Input::hasFlag(Input::Flags::flag5))
{
Input::sub_407231();
Input::resetFlag(Input::Flags::flag5);
}
Title::start();
throw GameException::Interrupt;
}
// 0x0043C427
void confirmSaveGame()
{
Input::toolCancel();
if (isEditorMode())
{
if (Game::saveLandscapeOpen())
{
// 0x0043C4B3
auto path = fs::u8path(&_savePath[0]).replace_extension(S5::extensionSC5);
std::strncpy(&_currentScenarioFilename[0], path.u8string().c_str(), std::size(_currentScenarioFilename));
if (!S5::save(path, S5::SaveFlags::scenario))
Ui::Windows::Error::open(StringIds::landscape_save_failed, StringIds::null);
else
GameCommands::do_21(2, 0);
}
}
else if (!isNetworked())
{
if (Game::saveSaveGameOpen())
{
// 0x0043C446
auto path = fs::u8path(&_savePath[0]).replace_extension(S5::extensionSV5);
std::strncpy(&_currentScenarioFilename[0], path.u8string().c_str(), std::size(_currentScenarioFilename));
S5::SaveFlags flags = {};
if (Config::get().flags & Config::Flags::exportObjectsWithSaves)
flags = S5::SaveFlags::packCustomObjects;
if (!S5::save(path, flags))
Ui::Windows::Error::open(StringIds::error_game_save_failed, StringIds::null);
else
GameCommands::do_21(2, 0);
}
}
else
{
// 0x0043C511
GameCommands::do_72();
MultiPlayer::setFlag(MultiPlayer::flags::flag_2);
switch (_savePromptType)
{
case GameCommands::LoadOrQuitMode::loadGamePrompt:
MultiPlayer::setFlag(MultiPlayer::flags::flag_13); // intend to load?
break;
case GameCommands::LoadOrQuitMode::returnToTitlePrompt:
MultiPlayer::setFlag(MultiPlayer::flags::flag_14); // intend to return to title?
break;
case GameCommands::LoadOrQuitMode::quitGamePrompt:
MultiPlayer::setFlag(MultiPlayer::flags::flag_15); // intend to quit game?
break;
}
}
// 0x0043C411
Gfx::invalidateScreen();
}
}

14
src/OpenLoco/Game.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
namespace OpenLoco::Game
{
bool loadSaveGameOpen();
bool loadLandscapeOpen();
bool saveSaveGameOpen();
bool saveScenarioOpen();
bool saveLandscapeOpen();
void loadGame();
void quitGame();
void returnToTitle();
void confirmSaveGame();
}

View File

@ -1,326 +0,0 @@
#include "GameCommands.h"
#include "Audio/Audio.h"
#include "Company.h"
#include "CompanyManager.h"
#include "Map/Tile.h"
#include "Objects/ObjectManager.h"
#include "Objects/RoadObject.h"
#include "Objects/TrackObject.h"
#include "StationManager.h"
#include "Things/Vehicle.h"
#include "Ui/WindowManager.h"
#include <cassert>
using namespace OpenLoco::Ui;
using namespace OpenLoco::Map;
namespace OpenLoco::GameCommands
{
static loco_global<company_id_t, 0x009C68EB> _updating_company_id;
static loco_global<uint8_t, 0x00508F08> game_command_nest_level;
static loco_global<company_id_t[2], 0x00525E3C> _player_company;
static loco_global<uint8_t, 0x00508F17> paused_state;
static loco_global<uint8_t, 0x00508F1A> game_speed;
static loco_global<uint16_t, 0x0050A004> _50A004;
static uint16_t _gameCommandFlags;
static loco_global<uintptr_t[80], 0x004F9548> _4F9548;
static loco_global<uint8_t[80], 0x004F9688> _4F9688;
static loco_global<tile_element*, 0x009C68D0> _9C68D0;
static loco_global<coord_t, 0x009C68E4> _game_command_map_z;
static loco_global<string_id, 0x009C68E6> gGameCommandErrorText;
static loco_global<string_id, 0x009C68E8> gGameCommandErrorTitle;
static loco_global<uint8_t, 0x009C68EE> _errorCompanyId;
static loco_global<string_id[8], 0x112C826> _commonFormatArgs;
void registerHooks()
{
registerHook(
0x00431315,
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
registers backup = regs;
auto ebx = doCommand(regs.esi, backup);
regs = backup;
regs.ebx = ebx;
return 0;
});
registerHook(
0x004AE5E4,
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
registers backup = regs;
auto ebx = Things::Vehicle::create(regs.bl, regs.dx, regs.di);
regs = backup;
regs.ebx = ebx;
return 0;
});
}
static uint32_t loc_4314EA();
static uint32_t loc_4313C6(int esi, const registers& regs);
// 0x00431315
uint32_t doCommand(int esi, const registers& regs)
{
uint16_t flags = regs.bx;
_gameCommandFlags = regs.bx;
if (game_command_nest_level != 0)
return loc_4313C6(esi, regs);
if ((flags & GameCommandFlag::apply) == 0)
{
return loc_4313C6(esi, regs);
}
if ((flags & (GameCommandFlag::flag_4 | GameCommandFlag::flag_6)) != 0
&& _4F9688[esi] == 1
&& _updating_company_id == _player_company[0])
{
if (getPauseFlags() & 1)
{
paused_state = paused_state ^ 1;
WindowManager::invalidate(WindowType::timeToolbar);
Audio::unpauseSound();
_50A004 = _50A004 | 1;
}
if (game_speed != 0)
{
game_speed = 0;
WindowManager::invalidate(WindowType::timeToolbar);
}
if (isPaused())
{
gGameCommandErrorText = StringIds::empty;
return 0x80000000;
}
}
if (_updating_company_id == _player_company[0] && isNetworked())
{
assert(false);
registers fnRegs = regs;
call(0x0046E34A, fnRegs); // some network stuff. Untested
}
return loc_4313C6(esi, regs);
}
static uint32_t loc_4313C6(int esi, const registers& regs)
{
uint16_t flags = regs.bx;
gGameCommandErrorText = StringIds::null;
game_command_nest_level++;
auto addr = _4F9548[esi];
uint16_t flagsBackup = _gameCommandFlags;
registers fnRegs1 = regs;
fnRegs1.bl &= ~GameCommandFlag::apply;
call(addr, fnRegs1);
int32_t ebx = fnRegs1.ebx;
_gameCommandFlags = flagsBackup;
if (ebx != static_cast<int32_t>(0x80000000))
{
if (isEditorMode())
ebx = 0;
if (game_command_nest_level == 1)
{
if ((_gameCommandFlags & GameCommandFlag::flag_2) == 0
&& (_gameCommandFlags & GameCommandFlag::flag_6) == 0
&& ebx != 0)
{
registers regs2;
regs2.ebp = ebx;
call(0x0046DD06, regs2);
ebx = regs2.ebp;
}
}
}
if (ebx == static_cast<int32_t>(0x80000000))
{
if (flags & GameCommandFlag::apply)
{
return loc_4314EA();
}
else
{
game_command_nest_level--;
return ebx;
}
}
if ((flags & 1) == 0)
{
game_command_nest_level--;
return ebx;
}
uint16_t flagsBackup2 = _gameCommandFlags;
registers fnRegs2 = regs;
call(addr, fnRegs2);
int32_t ebx2 = fnRegs2.ebx;
_gameCommandFlags = flagsBackup2;
if (ebx2 == static_cast<int32_t>(0x80000000))
{
return loc_4314EA();
}
if (isEditorMode())
{
ebx = 0;
}
if (ebx2 < ebx)
{
ebx = ebx2;
}
game_command_nest_level--;
if (game_command_nest_level != 0)
return ebx;
if ((flagsBackup2 & GameCommandFlag::flag_5) != 0)
return ebx;
{
// Apply to company money
registers fnRegs;
fnRegs.ebx = ebx;
call(0x0046DE2B, fnRegs);
}
if (ebx != 0 && _updating_company_id == _player_company[0])
{
// Add flying cost text
registers fnRegs;
fnRegs.ebx = ebx;
_game_command_map_z = _game_command_map_z + 24;
call(0x0046DC9F, fnRegs);
_game_command_map_z = _game_command_map_z - 24;
}
return ebx;
}
static uint32_t loc_4314EA()
{
game_command_nest_level--;
if (game_command_nest_level != 0)
return 0x80000000;
if (_updating_company_id != _player_company[0])
return 0x80000000;
if (_gameCommandFlags & GameCommandFlag::flag_3)
return 0x80000000;
if (gGameCommandErrorText != 0xFFFE)
{
Windows::showError(gGameCommandErrorTitle, gGameCommandErrorText);
return 0x80000000;
}
// advanced errors
if (_9C68D0 != (void*)-1)
{
auto tile = (tile_element*)_9C68D0;
switch (tile->type())
{
case element_type::track: // 4
{
auto trackElement = tile->asTrack();
if (trackElement == nullptr)
break; // throw exception?
track_object* pObject = ObjectManager::get<track_object>(trackElement->trackObjectId());
if (pObject == nullptr)
break;
_commonFormatArgs[0] = pObject->name;
_commonFormatArgs[1] = CompanyManager::get(_errorCompanyId)->name;
Windows::Error::openWithCompetitor(gGameCommandErrorTitle, StringIds::error_reason_stringid_belongs_to, _errorCompanyId);
return 0x80000000;
}
case element_type::road: //0x1C
{
auto roadElement = tile->asRoad();
if (roadElement == nullptr)
break; // throw exception?
road_object* pObject = ObjectManager::get<road_object>(roadElement->roadObjectId());
if (pObject == nullptr)
break;
_commonFormatArgs[0] = pObject->name;
_commonFormatArgs[1] = CompanyManager::get(_errorCompanyId)->name;
Windows::Error::openWithCompetitor(gGameCommandErrorTitle, StringIds::error_reason_stringid_belongs_to, _errorCompanyId);
return 0x80000000;
}
case element_type::station: // 8
{
auto stationElement = tile->asStation();
if (stationElement == nullptr)
break; // throw exception?
station* pStation = StationManager::get(stationElement->stationId());
if (pStation == nullptr)
break;
_commonFormatArgs[0] = pStation->name;
_commonFormatArgs[1] = pStation->town;
_commonFormatArgs[2] = CompanyManager::get(_errorCompanyId)->name;
Windows::Error::openWithCompetitor(gGameCommandErrorTitle, StringIds::error_reason_stringid_belongs_to, _errorCompanyId);
return 0x80000000;
}
case element_type::signal: // 0x0C
{
_commonFormatArgs[0] = CompanyManager::get(_errorCompanyId)->name;
Windows::Error::openWithCompetitor(gGameCommandErrorTitle, StringIds::error_reason_signal_belongs_to, _errorCompanyId);
return 0x80000000;
}
default:
break;
}
}
// fallback
_commonFormatArgs[0] = CompanyManager::get(_errorCompanyId)->name;
Windows::Error::openWithCompetitor(gGameCommandErrorTitle, StringIds::error_reason_stringid_belongs_to, _errorCompanyId);
return 0x80000000;
}
// 0x00431E6A
// al : company
// esi : tile
bool sub_431E6A(const company_id_t company, Map::tile_element* const tile /*= nullptr*/)
{
if (company == CompanyId::neutral)
{
return true;
}
if (_updating_company_id == company || _updating_company_id == CompanyId::neutral)
{
return true;
}
gGameCommandErrorText = -2;
_errorCompanyId = company;
_9C68D0 = tile == nullptr ? reinterpret_cast<Map::tile_element*>(-1) : tile;
return false;
}
}

View File

@ -1,371 +0,0 @@
#pragma once
#include "Interop/Interop.hpp"
#include "Map/Tile.h"
#include "Objects/ObjectManager.h"
#include "Things/Thing.h"
using namespace OpenLoco::Interop;
namespace OpenLoco::GameCommands
{
enum GameCommandFlag : uint8_t
{
apply = 1 << 0, // 0x01
flag_1 = 1 << 1, // 0x02
flag_2 = 1 << 2, // 0x04
flag_3 = 1 << 3, // 0x08
flag_4 = 1 << 4, // 0x10
flag_5 = 1 << 5, // 0x20
flag_6 = 1 << 6, // 0x40
};
enum class GameCommand : uint8_t
{
vehicle_rearange = 0,
vehicle_place = 1,
vehicle_pickup = 2,
vehicle_create = 5,
vehicle_sell = 6,
build_vehicle = 9,
change_station_name = 11,
change_company_colour_scheme = 19,
pause_game = 20,
load_save_quit_game = 21,
change_land_material = 24,
raise_land = 25,
lower_land = 26,
lower_raise_land_mountain = 27,
raise_water = 28,
lower_water = 29,
change_company_name = 46,
remove_industry = 54,
build_company_headquarters = 55,
change_company_face = 66,
load_multiplayer_map = 67,
send_chat_message = 71,
update_owner_status = 73,
rename_industry = 79,
};
constexpr uint32_t FAILURE = 0x80000000;
void registerHooks();
uint32_t doCommand(int esi, const registers& registers);
bool sub_431E6A(const company_id_t company, Map::tile_element* const tile = nullptr);
// Build vehicle
inline bool do_5(uint16_t vehicle_type, uint16_t vehicle_id = 0xFFFF)
{
registers regs;
regs.bl = GameCommandFlag::apply;
regs.di = vehicle_id;
regs.edx = vehicle_type;
return doCommand(5, regs) != FAILURE;
}
// Change loan
inline void do_9(currency32_t newLoan)
{
registers regs;
regs.bl = GameCommandFlag::apply;
regs.edx = newLoan;
doCommand(9, regs);
}
// Change station name
inline void do_11(uint16_t cx, uint16_t ax, uint32_t edx, uint32_t ebp, uint32_t edi)
{
registers regs;
regs.bl = GameCommandFlag::apply;
regs.cx = cx; // station number or 0
regs.ax = ax; // [ 0, 1, 2]
regs.edx = edx; // part of name buffer
regs.ebp = ebp; // part of name buffer
regs.edi = edi; // part of name buffer
doCommand(11, regs);
}
// Change company colour scheme
inline void do_19(int8_t isPrimary, int8_t value, int8_t colourType, int8_t setColourMode, uint8_t companyId)
{
registers regs;
regs.bl = GameCommandFlag::apply;
regs.cl = colourType; // vehicle type or main
regs.dh = setColourMode; // [ 0, 1 ] -- 0 = set colour, 1 = toggle enabled/disabled;
regs.dl = companyId; // company id
if (setColourMode == 0)
{
// cl is divided by 2 when used
regs.ah = isPrimary; // [ 0, 1 ] -- primary or secondary palette
regs.al = value; // new colour
}
else if (setColourMode == 1)
{
regs.al = value; // [ 0, 1 ] -- off or on
}
doCommand(19, regs);
}
// Pause game
inline void do_20()
{
registers regs;
regs.bl = GameCommandFlag::apply;
doCommand(20, regs);
}
// Load/save/quit game
inline void do_21(uint8_t dl, uint8_t di)
{
registers regs;
regs.bl = GameCommandFlag::apply;
regs.dl = dl; // [ 0, 2 ]
regs.di = di; // [ 0 = load game, 1 = return to title screen, 2 = quit to desktop ]
doCommand(21, regs);
}
// Change Land Material
inline void do_24(Map::map_pos pointA, Map::map_pos pointB, uint8_t landType, uint8_t flags)
{
registers regs;
regs.ax = pointA.x;
regs.cx = pointA.y;
regs.di = pointB.x;
regs.bp = pointB.y;
regs.dl = landType;
regs.bl = flags;
doCommand(24, regs);
}
// Raise Land
inline uint32_t do_25(Map::map_pos centre, Map::map_pos pointA, Map::map_pos pointB, uint16_t di, uint8_t flags)
{
registers regs;
regs.ax = centre.x;
regs.cx = centre.y;
regs.edx = pointB.x << 16 | pointA.x;
regs.ebp = pointB.y << 16 | pointA.y;
regs.bl = flags;
regs.di = di;
return doCommand(25, regs);
}
// Lower Land
inline uint32_t do_26(Map::map_pos centre, Map::map_pos pointA, Map::map_pos pointB, uint16_t di, uint8_t flags)
{
registers regs;
regs.ax = centre.x;
regs.cx = centre.y;
regs.edx = pointB.x << 16 | pointA.x;
regs.ebp = pointB.y << 16 | pointA.y;
regs.bl = flags;
regs.di = di;
return doCommand(26, regs);
}
// Lower/Raise Land Mountain
inline uint32_t do_27(Map::map_pos centre, Map::map_pos pointA, Map::map_pos pointB, uint16_t di, uint8_t flags)
{
registers regs;
regs.ax = centre.x;
regs.cx = centre.y;
regs.edx = pointB.x << 16 | pointA.x;
regs.ebp = pointB.y << 16 | pointA.y;
regs.bl = flags;
regs.di = di;
return doCommand(27, regs);
}
// Raise Water
inline uint32_t do_28(Map::map_pos pointA, Map::map_pos pointB, uint8_t flags)
{
registers regs;
regs.ax = pointA.x;
regs.cx = pointA.y;
regs.di = pointB.x;
regs.bp = pointB.y;
regs.bl = flags;
return doCommand(28, regs);
}
// Lower Water
inline uint32_t do_29(Map::map_pos pointA, Map::map_pos pointB, uint8_t flags)
{
registers regs;
regs.ax = pointA.x;
regs.cx = pointA.y;
regs.di = pointB.x;
regs.bp = pointB.y;
regs.bl = flags;
return doCommand(29, regs);
}
// Change company name
inline bool do_30(uint16_t cx, uint16_t ax, uint32_t edx, uint32_t ebp, uint32_t edi)
{
registers regs;
regs.bl = GameCommandFlag::apply;
regs.cx = cx; // company id
regs.ax = ax; // [ 0, 1, 2]
regs.edx = edx; // part of name buffer
regs.ebp = ebp; // part of name buffer
regs.edi = edi; // part of name buffer
return doCommand(30, regs) != FAILURE;
}
// Change company owner name
inline bool do_31(uint16_t cx, uint16_t ax, uint32_t edx, uint32_t ebp, uint32_t edi)
{
registers regs;
regs.bl = GameCommandFlag::apply;
regs.cx = cx; // company id
regs.ax = ax; // [ 0, 1, 2]
regs.edx = edx; // part of name buffer
regs.ebp = ebp; // part of name buffer
regs.edi = edi; // part of name buffer
return doCommand(31, regs) != FAILURE;
}
// Rename town
inline void do_46(uint16_t cx, uint16_t ax, uint32_t edx, uint32_t ebp, uint32_t edi)
{
registers regs;
regs.bl = GameCommandFlag::apply;
regs.cx = cx; // town number or 0
regs.ax = ax; // [ 0, 1, 2]
regs.edx = edx; // part of name buffer
regs.ebp = ebp; // part of name buffer
regs.edi = edi; // part of name buffer
doCommand(46, regs);
}
// Remove industry
inline bool do_48(uint8_t industryId)
{
registers regs;
regs.bl = GameCommandFlag::apply;
regs.dx = industryId;
return doCommand(48, regs) != FAILURE;
}
// Remove town
inline bool do_50(uint8_t townId)
{
registers regs;
regs.bl = GameCommandFlag::apply;
regs.edi = townId;
return doCommand(50, regs) != FAILURE;
}
// Remove company headquarters (or build - needs to be checked)
// Note: The game seems to call do_55, then do_54 in success case and returns 0x0 code
// if we try to build over the existing only do_54 is called and it returns 0x80000000 (in regs.ebx)
inline uint32_t do_54(uint8_t bl, uint16_t ax, uint16_t cx, uint16_t di, uint16_t dx)
{
registers regs;
regs.bl = bl; // flags
regs.cx = cx; // x
regs.ax = ax; // y
regs.di = di; // z
regs.dx = dx; // company index (value 1 in testing case)
return doCommand(54, regs);
}
// Build company headquarters (or remove - needs to be checked)
// Note: The game seems to call do_55, then do_54 in success case
// if we try to build over the existing one do_55 is not called
inline void do_55(uint8_t bl, uint16_t ax, uint16_t cx, uint16_t di)
{
registers regs;
regs.bl = bl; // flags
regs.cx = cx; // x?
regs.ax = ax; // y?
regs.di = di; // z?
doCommand(55, regs);
}
// Change company face
inline bool do_65(const ObjectManager::header& object, uint8_t company)
{
auto objPtr = reinterpret_cast<const int32_t*>(&object);
registers regs;
regs.bl = GameCommandFlag::apply;
regs.eax = *objPtr++;
regs.ecx = *objPtr++;
regs.edx = *objPtr++;
regs.edi = *objPtr;
regs.bh = company;
return doCommand(65, regs) != FAILURE;
}
// Clear Land
inline void do_66(Map::map_pos centre, Map::map_pos pointA, Map::map_pos pointB, uint8_t flags)
{
registers regs;
regs.ax = centre.x;
regs.cx = centre.y;
regs.edx = pointB.x << 16 | pointA.x;
regs.ebp = pointB.y << 16 | pointA.y;
regs.bl = flags;
doCommand(66, regs);
}
// Load multiplayer map
inline void do_67(const char* filename)
{
registers regs;
regs.bl = GameCommandFlag::apply;
regs.ebp = reinterpret_cast<int32_t>(filename);
doCommand(67, regs);
}
// Send chat message
inline void do_71(int32_t ax, char* string)
{
registers regs;
regs.bl = GameCommandFlag::apply;
regs.ax = ax;
memcpy(&regs.ecx, &string[0], 4);
memcpy(&regs.edx, &string[4], 4);
memcpy(&regs.ebp, &string[8], 4);
memcpy(&regs.edi, &string[12], 4);
doCommand(71, regs);
}
// Update owner status
inline void do_73(thing_id_t id)
{
registers regs;
regs.bl = GameCommandFlag::apply;
regs.ax = -2;
regs.cx = id;
doCommand(73, regs);
}
// Update owner status
inline void do_73(Map::map_pos position)
{
registers regs;
regs.bl = GameCommandFlag::apply;
regs.ax = position.x;
regs.cx = position.y;
doCommand(73, regs);
}
// Rename industry
inline void do_79(uint16_t cx, uint16_t ax, uint32_t edx, uint32_t ebp, uint32_t edi)
{
registers regs;
regs.bl = GameCommandFlag::apply;
regs.cx = cx; // industry number or 0
regs.ax = ax; // [ 0, 1, 2]
regs.edx = edx; // part of name buffer
regs.ebp = ebp; // part of name buffer
regs.edi = edi; // part of name buffer
doCommand(79, regs);
}
}

View File

@ -0,0 +1,77 @@
#include "../Audio/Audio.h"
#include "../CompanyManager.h"
#include "../GameException.hpp"
#include "../Localisation/StringIds.h"
#include "../OpenLoco.h"
#include "../Ui/WindowManager.h"
#include "../Ui/WindowType.h"
#include "GameCommands.h"
using namespace OpenLoco::Interop;
namespace OpenLoco::GameCommands
{
// 0x0043483D
static uint32_t changeCompanyColour(uint8_t flags, uint8_t type, bool toggleMode, CompanyId_t companyId, bool isSecondary, uint8_t value)
{
GameCommands::setExpenditureType(ExpenditureType::Miscellaneous);
GameCommands::setPosition({ static_cast<int16_t>(0x8000), 0, 0 });
auto* company = CompanyManager::get(companyId);
if (flags & Flags::apply)
{
// Toggling vehicle palette
if (toggleMode)
{
if (value)
company->customVehicleColoursSet |= (1 << type);
else
company->customVehicleColoursSet &= ~(1 << type);
}
// Setting a colour
else
{
ColourScheme* colours;
if (type == 0)
colours = &company->mainColours;
else
colours = &company->vehicleColours[type - 1];
if (!isSecondary)
colours->primary = value;
else
colours->secondary = value;
}
company->updateVehicleColours();
CompanyManager::updateColours();
company->updateHeadquartersColour();
Ui::WindowManager::invalidate(Ui::WindowType::company);
return 0;
}
else
{
if (!sub_431E6A(companyId, nullptr))
return GameCommands::FAILURE;
if (toggleMode || type > 0 || isSecondary)
return 0;
// Check whether the requested colour is available
uint32_t unavailableColours = CompanyManager::competingColourMask(companyId);
if (unavailableColours & (1 << value))
{
setErrorText(StringIds::empty);
return GameCommands::FAILURE;
}
}
return 0;
}
void changeCompanyColour(registers& regs)
{
regs.ebx = changeCompanyColour(regs.bl, regs.cl, regs.dh, regs.dl, regs.ah, regs.al);
}
}

View File

@ -0,0 +1,215 @@
#include "Cheat.h"
#include "../CompanyManager.h"
#include "../Economy/Currency.h"
#include "../Entities/EntityManager.h"
#include "../Interop/Interop.hpp"
#include "../Map/TileManager.h"
#include "../StationManager.h"
#include "../TownManager.h"
#include "../Types.hpp"
#include "../Vehicles/Vehicle.h"
#include "GameCommands.h"
using namespace OpenLoco::Interop;
namespace OpenLoco::GameCommands
{
namespace Cheats
{
static uint32_t acquireAssets(CompanyId_t targetCompanyId)
{
auto ourCompanyId = CompanyManager::updatingCompanyId();
// First phase: change ownership of all tile elements that currently belong to the target company.
for (auto& element : TileManager::getElements())
{
auto roadElement = element.asRoad();
if (roadElement != nullptr)
{
roadElement->setOwner(ourCompanyId);
continue;
}
auto trackElement = element.asTrack();
if (trackElement != nullptr)
{
trackElement->setOwner(ourCompanyId);
continue;
}
}
// Second phase: change ownership of all stations that currently belong to the target company.
for (auto& station : StationManager::stations())
{
if (station.owner != targetCompanyId)
continue;
station.owner = ourCompanyId;
}
// Third phase: change ownership of all vehicles that currently belong to the target company.
for (auto vehicle : EntityManager::VehicleList())
{
if (vehicle->owner != targetCompanyId)
continue;
Vehicles::Vehicle train(vehicle);
train.head->owner = ourCompanyId;
train.veh1->owner = ourCompanyId;
train.veh2->owner = ourCompanyId;
train.tail->owner = ourCompanyId;
for (auto& car : train.cars)
{
for (auto& component : car)
{
component.front->owner = ourCompanyId;
component.body->owner = ourCompanyId;
component.back->owner = ourCompanyId;
}
}
}
return 0;
}
static uint32_t addCash(currency32_t amount)
{
auto company = CompanyManager::getPlayerCompany();
company->cash = company->cash + amount;
return 0;
}
static uint32_t clearLoan()
{
auto company = CompanyManager::getPlayerCompany();
company->current_loan = 0;
return 0;
}
static uint32_t companyRatings(bool absolute, int32_t value)
{
auto companyId = CompanyManager::getControllingId();
for (auto& town : TownManager::towns())
{
// Does this town have a rating for our company?
if (!(town.companies_with_rating &= (1 << companyId)))
continue;
int16_t newRanking{};
if (absolute)
{
newRanking = value * max_company_rating;
}
else
{
newRanking = town.company_ratings[companyId] + max_company_rating;
newRanking *= 1.0f + (1.0f / value);
newRanking -= max_company_rating;
}
// Set the new rating.
town.company_ratings[companyId] = std::clamp<int16_t>(newRanking, min_company_rating, max_company_rating);
}
return 0;
}
static uint32_t switchCompany(CompanyId_t targetCompanyId)
{
auto ourId = CompanyManager::getControllingId();
auto otherId = CompanyManager::getSecondaryPlayerId();
// Already controlling the target company?
if (targetCompanyId == ourId)
return 0;
// Is the other player controlling the target company? Swap companies.
if (targetCompanyId == otherId)
{
CompanyManager::setSecondaryPlayerId(ourId);
CompanyManager::setControllingId(otherId);
return 0;
}
// Change control over to the other company.
CompanyManager::setControllingId(targetCompanyId);
return 0;
}
static uint32_t toggleBankruptcy(CompanyId_t targetCompanyId)
{
auto company = CompanyManager::get(targetCompanyId);
company->challenge_flags ^= CompanyFlags::bankrupt;
return 0;
}
static uint32_t toggleJail(CompanyId_t targetCompanyId)
{
auto company = CompanyManager::get(targetCompanyId);
company->jail_status = 30;
return 0;
}
static uint32_t vehicleReliability(int32_t newReliablity)
{
auto ourCompanyId = CompanyManager::updatingCompanyId();
for (auto vehicle : EntityManager::VehicleList())
{
if (vehicle->owner != ourCompanyId)
continue;
Vehicles::Vehicle train(vehicle);
train.veh2->reliability = newReliablity;
// Set reliability for the first car's front bogie component.
auto& car = *(train.cars.begin());
auto& component = *(car.begin());
component.front->reliability = newReliablity * 256;
}
return 0;
}
}
static uint32_t cheat(CheatCommand command, int32_t param1, int32_t param2, int32_t param3)
{
switch (command)
{
case CheatCommand::acquireAssets:
return Cheats::acquireAssets(param1);
case CheatCommand::addCash:
return Cheats::addCash(param1);
case CheatCommand::clearLoan:
return Cheats::clearLoan();
case CheatCommand::companyRatings:
return Cheats::companyRatings(param1, param2);
case CheatCommand::switchCompany:
return Cheats::switchCompany(param1);
case CheatCommand::toggleBankruptcy:
return Cheats::toggleBankruptcy(param1);
case CheatCommand::toggleJail:
return Cheats::toggleJail(param1);
case CheatCommand::vehicleReliability:
return Cheats::vehicleReliability(param1);
default:
break;
}
return 0;
}
void cheat(registers& regs)
{
regs.ebx = cheat(CheatCommand(regs.eax), regs.ebx, regs.ecx, regs.edx);
}
}

View File

@ -0,0 +1,16 @@
#include <cstdint>
namespace OpenLoco::GameCommands
{
enum class CheatCommand : uint8_t
{
acquireAssets,
addCash,
clearLoan,
companyRatings,
switchCompany,
toggleBankruptcy,
toggleJail,
vehicleReliability,
};
}

View File

@ -0,0 +1,466 @@
#include "GameCommands.h"
#include "../Audio/Audio.h"
#include "../Company.h"
#include "../CompanyManager.h"
#include "../Localisation/FormatArguments.hpp"
#include "../Localisation/StringIds.h"
#include "../Map/Tile.h"
#include "../Objects/ObjectManager.h"
#include "../Objects/RoadObject.h"
#include "../Objects/TrackObject.h"
#include "../StationManager.h"
#include "../Ui/WindowManager.h"
#include "../Vehicles/Vehicle.h"
#include <cassert>
using namespace OpenLoco::Ui;
using namespace OpenLoco::Map;
namespace OpenLoco::GameCommands
{
static loco_global<CompanyId_t, 0x009C68EB> _updating_company_id;
static loco_global<uint8_t, 0x00508F08> game_command_nest_level;
static loco_global<CompanyId_t[2], 0x00525E3C> _player_company;
static loco_global<uint8_t, 0x00508F17> paused_state;
static uint16_t _gameCommandFlags;
static loco_global<TileElement*, 0x009C68D0> _9C68D0;
static loco_global<Pos3, 0x009C68E0> _gGameCommandPosition;
static loco_global<string_id, 0x009C68E6> _gGameCommandErrorText;
static loco_global<string_id, 0x009C68E8> _gGameCommandErrorTitle;
static loco_global<uint8_t, 0x009C68EA> _gGameCommandExpenditureType; // premultiplied by 4
static loco_global<uint8_t, 0x009C68EE> _errorCompanyId;
static loco_global<string_id[8], 0x112C826> _commonFormatArgs;
using GameCommandFunc = void (*)(registers& regs);
struct GameCommandInfo
{
GameCommand id;
GameCommandFunc implementation;
uintptr_t originalAddress; // original array: 0x004F9548
bool unpausesGame; // original array: 0x004F9688
};
// clang-format off
static constexpr GameCommandInfo _gameCommandDefinitions[82] = {
{ GameCommand::vehicleRearrange, nullptr, 0x004AF1DF, true },
{ GameCommand::vehiclePlace, nullptr, 0x004B01B6, true },
{ GameCommand::vehiclePickup, vehiclePickup, 0x004B0826, true },
{ GameCommand::vehicleReverse, nullptr, 0x004ADAA8, true },
{ GameCommand::vehiclePassSignal, nullptr, 0x004B0B50, true },
{ GameCommand::vehicleCreate, Vehicles::create, 0x004AE5E4, true },
{ GameCommand::vehicleSell, nullptr, 0x004AED34, true },
{ GameCommand::createTrack, nullptr, 0x0049BB98, true },
{ GameCommand::removeTrack, nullptr, 0x0049C7F2, true },
{ GameCommand::changeLoan, nullptr, 0x0046DE88, false },
{ GameCommand::vehicleRename, Vehicles::rename, 0x004B6572, false },
{ GameCommand::changeStationName, renameStation, 0x00490756, false },
{ GameCommand::vehicleLocalExpress, nullptr, 0x004B694B, true },
{ GameCommand::createSignal, nullptr, 0x00488BDB, true },
{ GameCommand::removeSignal, nullptr, 0x004891E4, true },
{ GameCommand::createTrainStation, nullptr, 0x0048BB20, true },
{ GameCommand::removeTrackStation, nullptr, 0x0048C402, true },
{ GameCommand::createTrackMod, nullptr, 0x004A6479, true },
{ GameCommand::removeTrackMod, nullptr, 0x004A668A, true },
{ GameCommand::changeCompanyColourScheme, changeCompanyColour, 0x0043483D, false },
{ GameCommand::pauseGame, togglePause, 0x00431E32, false },
{ GameCommand::loadSaveQuitGame, loadSaveQuit, 0x0043BFCB, false },
{ GameCommand::removeTree, removeTree, 0x004BB392, true },
{ GameCommand::createTree, nullptr, 0x004BB138, true },
{ GameCommand::changeLandMaterial, nullptr, 0x00468EDD, true },
{ GameCommand::raiseLand, nullptr, 0x00463702, true },
{ GameCommand::lowerLand, nullptr, 0x004638C6, true },
{ GameCommand::lowerRaiseLandMountain, nullptr, 0x00462DCE, true },
{ GameCommand::raiseWater, nullptr, 0x004C4F19, true },
{ GameCommand::lowerWater, nullptr, 0x004C5126, true },
{ GameCommand::changeCompanyName, nullptr, 0x00434914, false },
{ GameCommand::changeCompanyOwnerName, nullptr, 0x00434A58, false },
{ GameCommand::createWall, nullptr, 0x004C436C, true },
{ GameCommand::removeWall, nullptr, 0x004C466C, true },
{ GameCommand::gc_unk_34, nullptr, 0x004C4717, false },
{ GameCommand::vehicleOrderInsert, nullptr, 0x0047036E, false },
{ GameCommand::vehicleOrderDelete, nullptr, 0x0047057A, false },
{ GameCommand::vehicleOrderSkip, Vehicles::orderSkip, 0x0047071A, false },
{ GameCommand::createRoad, nullptr, 0x00475FBC, true },
{ GameCommand::removeRoad, nullptr, 0x004775A5, true },
{ GameCommand::createRoadMod, nullptr, 0x0047A21E, true },
{ GameCommand::removeRoadMod, nullptr, 0x0047A42F, true },
{ GameCommand::createRoadStation, nullptr, 0x0048C708, true },
{ GameCommand::removeRoadStation, nullptr, 0x0048D2AC, true },
{ GameCommand::createBuilding, nullptr, 0x0042D133, true },
{ GameCommand::removeBuilding, nullptr, 0x0042D74E, true },
{ GameCommand::renameTown, renameTown, 0x0049B11E, false },
{ GameCommand::createIndustry, nullptr, 0x0045436B, true },
{ GameCommand::removeIndustry, nullptr, 0x00455943, true },
{ GameCommand::createTown, nullptr, 0x00496C22, true },
{ GameCommand::removeTown, nullptr, 0x0049711F, true },
{ GameCommand::gc_unk_51, nullptr, 0x004A6FDC, true },
{ GameCommand::gc_unk_52, nullptr, 0x004A734F, true },
{ GameCommand::gc_unk_53, nullptr, 0x0047AF0B, true },
{ GameCommand::buildCompanyHeadquarters, nullptr, 0x0042ECFC, true },
{ GameCommand::removeCompanyHeadquarters, nullptr, 0x0042EEAF, true },
{ GameCommand::createAirport, nullptr, 0x00492C41, true },
{ GameCommand::removeAirport, nullptr, 0x00493559, true },
{ GameCommand::vehiclePlaceAir, nullptr, 0x004267BE, true },
{ GameCommand::vehiclePickupAir, nullptr, 0x00426B29, true },
{ GameCommand::createPort, nullptr, 0x00493AA7, true },
{ GameCommand::removePort, nullptr, 0x00494570, true },
{ GameCommand::vehiclePlaceWater, nullptr, 0x0042773C, true },
{ GameCommand::vehiclePickupWater, nullptr, 0x004279CC, true },
{ GameCommand::vehicleRefit, nullptr, 0x0042F6DB, false },
{ GameCommand::changeCompanyFace, nullptr, 0x00435506, false },
{ GameCommand::clearLand, nullptr, 0x00469CCB, true },
{ GameCommand::loadMultiplayerMap, nullptr, 0x00444DA0, false },
{ GameCommand::gc_unk_68, nullptr, 0x0046F8A5, false },
{ GameCommand::gc_unk_69, nullptr, 0x004454BE, false },
{ GameCommand::gc_unk_70, nullptr, 0x004456C8, false },
{ GameCommand::sendChatMessage, nullptr, 0x0046F976, false },
{ GameCommand::multiplayerSave, nullptr, 0x004A0ACD, false },
{ GameCommand::updateOwnerStatus, nullptr, 0x004383CA, false },
{ GameCommand::vehicleSpeedControl, nullptr, 0x004BAB63, true },
{ GameCommand::vehicleOrderUp, nullptr, 0x00470CD2, false },
{ GameCommand::vehicleOrderDown, nullptr, 0x00470E06, false },
{ GameCommand::vehicleApplyShuntCheat, nullptr, 0x004BAC53, false },
{ GameCommand::applyFreeCashCheat, nullptr, 0x00438A08, false },
{ GameCommand::renameIndustry, renameIndustry, 0x00455029, false },
{ GameCommand::vehicleClone, Vehicles::cloneVehicle, 0, true },
{ GameCommand::cheat, cheat, 0, true },
};
// clang-format on
void registerHooks()
{
registerHook(
0x00431315,
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
registers backup = regs;
auto ebx = doCommand(GameCommand(regs.esi), backup);
regs = backup;
regs.ebx = ebx;
return 0;
});
}
static uint32_t loc_4314EA();
static uint32_t loc_4313C6(int esi, const registers& regs);
static bool commandRequiresUnpausingGame(GameCommand command, uint16_t flags)
{
if ((flags & (Flags::flag_4 | Flags::flag_6)) != 0)
return false;
auto& gameCommand = _gameCommandDefinitions[static_cast<uint32_t>(command)];
if (!gameCommand.unpausesGame || isPauseOverrideEnabled())
return false;
return true;
}
// 0x00431315
uint32_t doCommand(GameCommand command, const registers& regs)
{
uint16_t flags = regs.bx;
uint32_t esi = static_cast<uint32_t>(command);
_gameCommandFlags = regs.bx;
if (game_command_nest_level != 0)
return loc_4313C6(esi, regs);
if ((flags & Flags::apply) == 0)
{
return loc_4313C6(esi, regs);
}
if (commandRequiresUnpausingGame(command, flags) && _updating_company_id == _player_company[0])
{
if (getPauseFlags() & 1)
{
paused_state = paused_state ^ 1;
WindowManager::invalidate(WindowType::timeToolbar);
Audio::unpauseSound();
Ui::Windows::PlayerInfoPanel::invalidateFrame();
}
if (getGameSpeed() != 0)
{
setGameSpeed(0);
WindowManager::invalidate(WindowType::timeToolbar);
}
if (isPaused())
{
_gGameCommandErrorText = StringIds::empty;
return 0x80000000;
}
}
if (_updating_company_id == _player_company[0] && isNetworked())
{
assert(false);
registers fnRegs = regs;
call(0x0046E34A, fnRegs); // some network stuff. Untested
}
return loc_4313C6(esi, regs);
}
static void callGameCommandFunction(uint32_t command, registers& regs)
{
auto& gameCommand = _gameCommandDefinitions[command];
if (gameCommand.implementation != nullptr)
{
gameCommand.implementation(regs);
}
else
{
auto addr = gameCommand.originalAddress;
call(addr, regs);
}
}
static uint32_t loc_4313C6(int esi, const registers& regs)
{
uint16_t flags = regs.bx;
_gGameCommandErrorText = StringIds::null;
game_command_nest_level++;
uint16_t flagsBackup = _gameCommandFlags;
registers fnRegs1 = regs;
fnRegs1.bl &= ~Flags::apply;
callGameCommandFunction(esi, fnRegs1);
int32_t ebx = fnRegs1.ebx;
_gameCommandFlags = flagsBackup;
if (ebx != static_cast<int32_t>(0x80000000))
{
if (isEditorMode())
ebx = 0;
if (game_command_nest_level == 1)
{
if ((_gameCommandFlags & Flags::flag_2) == 0
&& (_gameCommandFlags & Flags::flag_6) == 0
&& ebx != 0)
{
registers regs2;
regs2.ebp = ebx;
call(0x0046DD06, regs2);
ebx = regs2.ebp;
}
}
}
if (ebx == static_cast<int32_t>(0x80000000))
{
if (flags & Flags::apply)
{
return loc_4314EA();
}
else
{
game_command_nest_level--;
return ebx;
}
}
if ((flags & 1) == 0)
{
game_command_nest_level--;
return ebx;
}
uint16_t flagsBackup2 = _gameCommandFlags;
registers fnRegs2 = regs;
callGameCommandFunction(esi, fnRegs2);
int32_t ebx2 = fnRegs2.ebx;
_gameCommandFlags = flagsBackup2;
if (ebx2 == static_cast<int32_t>(0x80000000))
{
return loc_4314EA();
}
if (isEditorMode())
{
ebx = 0;
}
if (ebx2 < ebx)
{
ebx = ebx2;
}
game_command_nest_level--;
if (game_command_nest_level != 0)
return ebx;
if ((flagsBackup2 & Flags::flag_5) != 0)
return ebx;
// Apply to company money
CompanyManager::applyPaymentToCompany(CompanyManager::updatingCompanyId(), ebx, getExpenditureType());
if (ebx != 0 && _updating_company_id == _player_company[0])
{
// Add flying cost text
CompanyManager::spendMoneyEffect(getPosition() + Map::Pos3{ 0, 0, 24 }, _updating_company_id, ebx);
}
return ebx;
}
static uint32_t loc_4314EA()
{
game_command_nest_level--;
if (game_command_nest_level != 0)
return 0x80000000;
if (_updating_company_id != _player_company[0])
return 0x80000000;
if (_gameCommandFlags & Flags::flag_3)
return 0x80000000;
if (_gGameCommandErrorText != 0xFFFE)
{
Windows::showError(_gGameCommandErrorTitle, _gGameCommandErrorText);
return 0x80000000;
}
// advanced errors
if (_9C68D0 != (void*)-1)
{
auto tile = (TileElement*)_9C68D0;
switch (tile->type())
{
case ElementType::track: // 4
{
auto trackElement = tile->asTrack();
if (trackElement == nullptr)
break; // throw exception?
TrackObject* pObject = ObjectManager::get<TrackObject>(trackElement->trackObjectId());
if (pObject == nullptr)
break;
auto formatter = FormatArguments::common();
formatter.push(pObject->name);
formatter.push(CompanyManager::get(_errorCompanyId)->name);
Windows::Error::openWithCompetitor(_gGameCommandErrorTitle, StringIds::error_reason_stringid_belongs_to, _errorCompanyId);
return 0x80000000;
}
case ElementType::road: //0x1C
{
auto roadElement = tile->asRoad();
if (roadElement == nullptr)
break; // throw exception?
RoadObject* pObject = ObjectManager::get<RoadObject>(roadElement->roadObjectId());
if (pObject == nullptr)
break;
auto formatter = FormatArguments::common();
formatter.push(pObject->name);
formatter.push(CompanyManager::get(_errorCompanyId)->name);
Windows::Error::openWithCompetitor(_gGameCommandErrorTitle, StringIds::error_reason_stringid_belongs_to, _errorCompanyId);
return 0x80000000;
}
case ElementType::station: // 8
{
auto stationElement = tile->asStation();
if (stationElement == nullptr)
break; // throw exception?
Station* pStation = StationManager::get(stationElement->stationId());
if (pStation == nullptr)
break;
auto formatter = FormatArguments::common();
formatter.push(pStation->name);
formatter.push(pStation->town);
formatter.push(CompanyManager::get(_errorCompanyId)->name);
Windows::Error::openWithCompetitor(_gGameCommandErrorTitle, StringIds::error_reason_stringid_belongs_to, _errorCompanyId);
return 0x80000000;
}
case ElementType::signal: // 0x0C
{
auto formatter = FormatArguments::common();
formatter.push(CompanyManager::get(_errorCompanyId)->name);
Windows::Error::openWithCompetitor(_gGameCommandErrorTitle, StringIds::error_reason_signal_belongs_to, _errorCompanyId);
return 0x80000000;
}
default:
break;
}
}
// fallback
auto formatter = FormatArguments::common();
formatter.push(CompanyManager::get(_errorCompanyId)->name);
Windows::Error::openWithCompetitor(_gGameCommandErrorTitle, StringIds::error_reason_stringid_belongs_to, _errorCompanyId);
return 0x80000000;
}
// 0x00431E6A
// al : company
// esi : tile
bool sub_431E6A(const CompanyId_t company, Map::TileElement* const tile /*= nullptr*/)
{
if (company == CompanyId::neutral)
{
return true;
}
if (_updating_company_id == company || _updating_company_id == CompanyId::neutral)
{
return true;
}
_gGameCommandErrorText = -2;
_errorCompanyId = company;
_9C68D0 = tile == nullptr ? reinterpret_cast<Map::TileElement*>(-1) : tile;
return false;
}
const Map::Pos3& getPosition()
{
return _gGameCommandPosition;
}
void setPosition(const Map::Pos3& pos)
{
_gGameCommandPosition = pos;
}
void setErrorText(const string_id message)
{
_gGameCommandErrorText = message;
}
string_id getErrorText()
{
return _gGameCommandErrorText;
}
void setErrorTitle(const string_id title)
{
_gGameCommandErrorTitle = title;
}
ExpenditureType getExpenditureType()
{
return ExpenditureType(_gGameCommandExpenditureType / 4);
}
void setExpenditureType(const ExpenditureType type)
{
_gGameCommandExpenditureType = static_cast<uint8_t>(type) * 4;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,77 @@
#include "../Audio/Audio.h"
#include "../Game.h"
#include "../Tutorial.h"
#include "../Ui/WindowManager.h"
#include "../Ui/WindowType.h"
#include "GameCommands.h"
#pragma warning(disable : 4702)
using namespace OpenLoco::Interop;
namespace OpenLoco::GameCommands
{
static loco_global<LoadOrQuitMode, 0x0050A002> _savePromptType;
// 0x0043BFCB
static uint32_t loadSaveQuit(const uint8_t flags, uint8_t dl, uint8_t di)
{
if ((flags & Flags::apply) == 0)
return 0;
if (dl == 1)
{
Ui::WindowManager::close(Ui::WindowType::saveGamePrompt);
return 0;
}
if (dl == 0)
{
_savePromptType = static_cast<LoadOrQuitMode>(di);
Ui::Windows::TextInput::cancel();
Ui::Windows::PromptSaveWindow::open(di);
if (!isTitleMode())
{
// 0x0043C369
// NB: tutorial recording has been omitted.
if (Tutorial::state() == Tutorial::State::playing)
{
Tutorial::stop();
}
else if (!isNetworked() || _savePromptType != LoadOrQuitMode::quitGamePrompt)
{
if (getScreenAge() >= 0xF00)
{
auto window = Ui::WindowManager::bringToFront(Ui::WindowType::saveGamePrompt);
Audio::playSound(Audio::SoundId::openWindow, window->x + (window->width / 2));
return 0;
}
}
}
}
// 0x0043BFE3
switch (_savePromptType)
{
case LoadOrQuitMode::loadGamePrompt:
Game::loadGame();
break;
case LoadOrQuitMode::returnToTitlePrompt:
Game::returnToTitle();
break;
case LoadOrQuitMode::quitGamePrompt:
Game::quitGame();
break;
}
return 0;
}
void loadSaveQuit(registers& regs)
{
regs.ebx = loadSaveQuit(regs.bl, regs.dl, regs.di);
}
}

View File

@ -0,0 +1,59 @@
#include "../Economy/Economy.h"
#include "../Economy/Expenditures.h"
#include "../Map/TileManager.h"
#include "../Objects/ObjectManager.h"
#include "../Objects/TreeObject.h"
#include "../OpenLoco.h"
#include "../S5/S5.h"
#include "GameCommands.h"
using namespace OpenLoco::Interop;
namespace OpenLoco::GameCommands
{
// 0x004BB392
static uint32_t removeTree(const Map::Pos3& pos, const uint8_t type, const uint8_t elementType, const uint8_t flags)
{
GameCommands::setExpenditureType(ExpenditureType::Construction);
auto tileHeight = Map::TileManager::getHeight(pos);
GameCommands::setPosition(Map::Pos3(pos.x + Map::tile_size / 2, pos.y + Map::tile_size / 2, tileHeight.landHeight));
auto tile = Map::TileManager::get(pos);
for (auto& element : tile)
{
// TODO: refactor! Figure out what info it actually needs.
if (element.rawData()[0] != elementType)
continue;
if (element.baseZ() * 4 != pos.z)
continue;
auto treeElement = element.asTree();
if (treeElement == nullptr)
continue;
if (treeElement->treeObjectId() != type)
continue;
auto treeObj = ObjectManager::get<TreeObject>(treeElement->treeObjectId());
currency32_t removalCost = Economy::getInflationAdjustedCost(treeObj->clear_cost_factor, treeObj->cost_index, 12);
if (flags & Flags::apply)
Map::TileManager::removeElement(element);
auto& options = S5::getOptions();
options.madeAnyChanges = 1;
return removalCost;
}
return FAILURE;
}
void removeTree(registers& regs)
{
TreeRemovalArgs args(regs);
regs.ebx = removeTree(args.pos, args.type, args.elementType, regs.bl);
}
}

View File

@ -0,0 +1,97 @@
#include "../Economy/Expenditures.h"
#include "../Industry.h"
#include "../IndustryManager.h"
#include "../Interop/Interop.hpp"
#include "../Localisation/FormatArguments.hpp"
#include "../Localisation/StringIds.h"
#include "../Localisation/StringManager.h"
#include "../Types.hpp"
#include "GameCommands.h"
using namespace OpenLoco::Interop;
namespace OpenLoco::GameCommands
{
/**
* 0x00455029
* Renames a particular industry.
*
* This command is called 3 times before the buffer is applied. Each time, 12 chars of the 36 char buffer are provided.
* The resulting industry name has a maximum length of 31 chars; the last bytes are not used.
*
* @param flags @<bl> - game command flags
* @param industryId @<cx> - industry id
* @param index @<ax> - update index (in order of: 1, 2, 0)
* @param buffer0 @<edx> - First part (4 chars) of the 12 update buffer
* @param buffer1 @<dx> - Second part (4 chars) of the 12 update buffer
* @param buffer2 @<bp> - Third part (4 chars) of the 12 update buffer
* @return @<ebx> - returns 0 if rename is successful; otherwise GameCommands::FAILURE
*/
static uint32_t renameIndustry(const uint8_t flags, IndustryId_t industryId, int16_t index, uint32_t buffer0, uint32_t buffer1, uint32_t buffer2)
{
GameCommands::setExpenditureType(ExpenditureType::Miscellaneous);
// Keep track of the industry id over several calls.
static IndustryId_t _industryId{};
if (index == 1)
_industryId = industryId;
static uint32_t renameBuffer[9];
// Fill buffer over calls into the renameBuffer
if ((flags & GameCommands::Flags::apply) != 0)
{
static const std::array<int, 3> transformTable = { 2, 0, 1 };
int arrayIndex = transformTable.at(index);
renameBuffer[arrayIndex * 3] = buffer0;
renameBuffer[arrayIndex * 3 + 1] = buffer1;
renameBuffer[arrayIndex * 3 + 2] = buffer2;
}
// Applying the buffer?
if (index != 0)
return 0;
char renameStringBuffer[37] = "";
memcpy(renameStringBuffer, renameBuffer, sizeof(renameBuffer));
renameStringBuffer[36] = '\0';
// Ensure the new name isn't empty.
if (strlen(renameStringBuffer) == 0)
return 0;
// Figure out the current name for this industry.
char currentIndustryName[256] = "";
auto industry = IndustryManager::get(_industryId);
auto args = FormatArguments::common(industry->town);
StringManager::formatString(currentIndustryName, industry->name, &args);
// Verify the new name actually differs from the old one.
if (strcmp(currentIndustryName, renameStringBuffer) == 0)
return 0;
// Allocate a string id for the new name.
string_id allocatedStringId = StringManager::userStringAllocate(renameStringBuffer, 0);
if (allocatedStringId == StringIds::empty)
return GameCommands::FAILURE;
// Bailing out early?
if ((flags & GameCommands::Flags::apply) == 0)
{
StringManager::emptyUserString(allocatedStringId);
return 0;
}
// Apply the new name to the industry.
string_id oldStringId = industry->name;
industry->name = allocatedStringId;
StringManager::emptyUserString(oldStringId);
Gfx::invalidateScreen();
return 0;
}
void renameIndustry(registers& regs)
{
regs.ebx = renameIndustry(regs.bl, regs.cx, regs.ax, regs.edx, regs.ebp, regs.edi);
}
}

View File

@ -0,0 +1,109 @@
#include "../Economy/Expenditures.h"
#include "../Industry.h"
#include "../Interop/Interop.hpp"
#include "../Localisation/FormatArguments.hpp"
#include "../Localisation/StringIds.h"
#include "../Localisation/StringManager.h"
#include "../StationManager.h"
#include "../TownManager.h"
#include "../Types.hpp"
#include "GameCommands.h"
using namespace OpenLoco::Interop;
namespace OpenLoco::GameCommands
{
/**
* 0x00490756
* Renames a particular station.
*
* This command is called 3 times before the buffer is applied. Each time, 12 chars of the 36 char buffer are provided.
* The resulting station name has a maximum length of 31 chars; the last bytes are not used.
*
* @param flags @<bl> - game command flags
* @param stationId @<cx> - station id
* @param index @<ax> - update index (in order of: 1, 2, 0)
* @param buffer0 @<edx> - First part (4 chars) of the 12 update buffer
* @param buffer1 @<dx> - Second part (4 chars) of the 12 update buffer
* @param buffer2 @<bp> - Third part (4 chars) of the 12 update buffer
* @return @<ebx> - returns 0 if rename is successful; otherwise GameCommands::FAILURE
*/
static uint32_t renameStation(const uint8_t flags, StationId_t stationId, int16_t index, uint32_t buffer0, uint32_t buffer1, uint32_t buffer2)
{
GameCommands::setExpenditureType(ExpenditureType::Miscellaneous);
// Keep track of the station id over several calls.
static StationId_t _stationId{};
if (index == 1)
_stationId = stationId;
static uint32_t renameBuffer[9];
// Fill buffer over calls into the renameBuffer
if ((flags & GameCommands::Flags::apply) != 0)
{
static const std::array<int, 3> transformTable = { 2, 0, 1 };
int arrayIndex = transformTable.at(index);
renameBuffer[arrayIndex * 3] = buffer0;
renameBuffer[arrayIndex * 3 + 1] = buffer1;
renameBuffer[arrayIndex * 3 + 2] = buffer2;
}
// Applying the buffer?
if (index != 0)
return 0;
char renameStringBuffer[37] = "";
memcpy(renameStringBuffer, renameBuffer, sizeof(renameBuffer));
renameStringBuffer[36] = '\0';
// Figure out the current name for this station.
char currentStationName[256] = "";
auto station = StationManager::get(_stationId);
auto args = FormatArguments::common(station->town);
StringManager::formatString(currentStationName, station->name, &args);
// Verify the new name actually differs from the old one.
if (strcmp(currentStationName, renameStringBuffer) == 0)
return 0;
string_id oldStringId = station->name;
// If an empty string is given, generate one instead.
if (strlen(renameStringBuffer) == 0)
{
// Are we bailing out early?
if ((flags & GameCommands::Flags::apply) == 0)
return 0;
station->name = StationManager::generateNewStationName(_stationId, station->town, Map::Pos3(station->x, station->y, station->z), 0);
}
else
{
// Allocate a string id for the new name.
string_id allocatedStringId = StringManager::userStringAllocate(renameStringBuffer, 0);
if (allocatedStringId == StringIds::empty)
return GameCommands::FAILURE;
// Are we bailing out early?
if ((flags & GameCommands::Flags::apply) == 0)
{
StringManager::emptyUserString(allocatedStringId);
return 0;
}
// Apply the new name to the station.
station->name = allocatedStringId;
}
StringManager::emptyUserString(oldStringId);
station->updateLabel();
Gfx::invalidateScreen();
return 0;
}
void renameStation(registers& regs)
{
regs.ebx = renameStation(regs.bl, regs.cx, regs.ax, regs.edx, regs.ebp, regs.edi);
}
}

View File

@ -0,0 +1,102 @@
#include "../Economy/Expenditures.h"
#include "../Industry.h"
#include "../Interop/Interop.hpp"
#include "../Localisation/FormatArguments.hpp"
#include "../Localisation/StringIds.h"
#include "../Localisation/StringManager.h"
#include "../StationManager.h"
#include "../TownManager.h"
#include "../Types.hpp"
#include "GameCommands.h"
using namespace OpenLoco::Interop;
namespace OpenLoco::GameCommands
{
/**
* 0x00455029
* Renames a particular town.
*
* This command is called 3 times before the buffer is applied. Each time, 12 chars of the 36 char buffer are provided.
* The resulting town name has a maximum length of 31 chars; the last bytes are not used.
*
* @param flags @<bl> - game command flags
* @param townId @<cx> - town id
* @param index @<ax> - update index (in order of: 1, 2, 0)
* @param buffer0 @<edx> - First part (4 chars) of the 12 update buffer
* @param buffer1 @<dx> - Second part (4 chars) of the 12 update buffer
* @param buffer2 @<bp> - Third part (4 chars) of the 12 update buffer
* @return @<ebx> - returns 0 if rename is successful; otherwise GameCommands::FAILURE
*/
static uint32_t renameTown(const uint8_t flags, TownId_t townId, int16_t index, uint32_t buffer0, uint32_t buffer1, uint32_t buffer2)
{
GameCommands::setExpenditureType(ExpenditureType::Miscellaneous);
// Keep track of the town id over several calls.
static TownId_t _townId{};
if (index == 1)
_townId = townId;
static uint32_t renameBuffer[9];
// Fill buffer over calls into the renameBuffer
if ((flags & GameCommands::Flags::apply) != 0)
{
static const std::array<int, 3> transformTable = { 2, 0, 1 };
int arrayIndex = transformTable.at(index);
renameBuffer[arrayIndex * 3] = buffer0;
renameBuffer[arrayIndex * 3 + 1] = buffer1;
renameBuffer[arrayIndex * 3 + 2] = buffer2;
}
// Applying the buffer?
if (index != 0)
return 0;
char renameStringBuffer[37] = "";
memcpy(renameStringBuffer, renameBuffer, sizeof(renameBuffer));
renameStringBuffer[36] = '\0';
// Ensure the new name isn't empty.
if (strlen(renameStringBuffer) == 0)
return 0;
// Figure out the current name for this town.
char currentTownName[256] = "";
auto town = TownManager::get(_townId);
StringManager::formatString(currentTownName, town->name);
// Verify the new name actually differs from the old one.
if (strcmp(currentTownName, renameStringBuffer) == 0)
return 0;
// Allocate a string id for the new name.
string_id allocatedStringId = StringManager::userStringAllocate(renameStringBuffer, 0);
if (allocatedStringId == StringIds::empty)
return GameCommands::FAILURE;
// Bailing out early?
if ((flags & GameCommands::Flags::apply) == 0)
{
StringManager::emptyUserString(allocatedStringId);
return 0;
}
// Apply the new name to the town.
string_id oldStringId = town->name;
town->name = allocatedStringId;
StringManager::emptyUserString(oldStringId);
// Recalculate labels for the town and (surrounding) stations.
town->updateLabel();
StationManager::updateLabels();
Gfx::invalidateScreen();
return 0;
}
void renameTown(registers& regs)
{
regs.ebx = renameTown(regs.bl, regs.cx, regs.ax, regs.edx, regs.ebp, regs.edi);
}
}

View File

@ -0,0 +1,39 @@
#include "../Audio/Audio.h"
#include "../GameException.hpp"
#include "../OpenLoco.h"
#include "../Ui/WindowManager.h"
#include "../Ui/WindowType.h"
#include "GameCommands.h"
using namespace OpenLoco::Interop;
namespace OpenLoco::GameCommands
{
// 0x00431E32
uint32_t togglePause(uint8_t flags)
{
if ((flags & Flags::apply) == 0)
return 0;
Ui::WindowManager::invalidate(Ui::WindowType::timeToolbar);
if (isPaused())
{
unsetPauseFlag(1 << 0);
Audio::unpauseSound();
}
else
{
setPauseFlag(1 << 0);
Audio::pauseSound();
Ui::Windows::TimePanel::invalidateFrame();
}
return 0;
}
void togglePause(registers& regs)
{
regs.ebx = togglePause(regs.bl);
}
}

View File

@ -0,0 +1,72 @@
#include "../Audio/Audio.h"
#include "../Economy/Expenditures.h"
#include "../Interop/Interop.hpp"
#include "../Types.hpp"
#include "../Utility/Prng.hpp"
#include "../Vehicles/Vehicle.h"
#include "GameCommands.h"
using namespace OpenLoco::Interop;
namespace OpenLoco::GameCommands
{
static loco_global<Utility::prng, 0x00525E20> _prng;
// 0x0048B15B
static void playPickupSound(Vehicles::Vehicle2* veh2)
{
const auto frequency = _prng->randNext(20003, 24098);
Audio::playSound(Audio::SoundId::vehiclePickup, veh2->position, -1000, frequency);
}
// 0x004B0826
static uint32_t vehiclePickup(const uint8_t flags, EntityId_t headId)
{
GameCommands::setExpenditureType(ExpenditureType::TrainRunningCosts);
auto train = Vehicles::Vehicle(headId);
auto* head = train.head;
auto* veh2 = train.veh2;
GameCommands::setPosition(veh2->position);
if (!GameCommands::sub_431E6A(head->owner))
return FAILURE;
if (!head->canBeModified())
return FAILURE;
if (!(flags & GameCommands::Flags::apply))
return 0;
if (!(flags & GameCommands::Flags::flag_6))
playPickupSound(veh2);
head->liftUpVehicle();
// Clear ghost flag on primary vehicle pieces and all car components.
train.head->var_38 &= ~(Vehicles::Flags38::isGhost);
train.veh1->var_38 &= ~(Vehicles::Flags38::isGhost);
train.veh2->var_38 &= ~(Vehicles::Flags38::isGhost);
train.tail->var_38 &= ~(Vehicles::Flags38::isGhost);
for (auto& car : train.cars)
{
for (auto& component : car)
{
component.front->var_38 &= ~(Vehicles::Flags38::isGhost);
component.back->var_38 &= ~(Vehicles::Flags38::isGhost);
component.body->var_38 &= ~(Vehicles::Flags38::isGhost);
}
}
head->var_0C |= Vehicles::Flags0C::commandStop;
return 0;
}
void vehiclePickup(registers& regs)
{
regs.ebx = vehiclePickup(regs.bl, regs.di);
}
}

View File

@ -0,0 +1,9 @@
#pragma once
namespace OpenLoco
{
enum class GameException
{
Interrupt = 0
};
}

View File

@ -37,7 +37,7 @@ namespace OpenLoco::Colour
}
}
uint8_t getShade(colour_t colour, uint8_t shade)
uint8_t getShade(Colour_t colour, uint8_t shade)
{
colour &= ~Colour::inset_flag;
assert(colour <= 31);

View File

@ -4,8 +4,8 @@
namespace OpenLoco
{
using colour_t = uint8_t;
using palette_index_t = uint8_t;
using Colour_t = uint8_t;
using PaletteIndex_t = uint8_t;
namespace Colour
{
@ -13,108 +13,113 @@ namespace OpenLoco
constexpr uint8_t inset_flag = 1 << 6;
constexpr uint8_t translucent_flag = 1 << 7;
constexpr colour_t black = 0;
constexpr colour_t grey = 1;
constexpr colour_t white = 2;
constexpr colour_t dark_purple = 3;
constexpr colour_t light_purple = 4;
constexpr colour_t bright_purple = 5;
constexpr colour_t dark_blue = 6;
constexpr colour_t light_blue = 7;
constexpr colour_t icy_blue = 8;
constexpr colour_t teal = 9;
constexpr colour_t aquamarine = 10;
constexpr colour_t saturated_green = 11;
constexpr colour_t dark_green = 12;
constexpr colour_t moss_green = 13;
constexpr colour_t bright_green = 14;
constexpr colour_t olive_green = 15;
constexpr colour_t dark_olive_green = 16;
constexpr colour_t bright_yellow = 17;
constexpr colour_t yellow = 18;
constexpr colour_t dark_yellow = 19;
constexpr colour_t light_orange = 20;
constexpr colour_t dark_orange = 21;
constexpr colour_t light_brown = 22;
constexpr colour_t saturated_brown = 23;
constexpr colour_t dark_brown = 24;
constexpr colour_t salmon_pink = 25;
constexpr colour_t bordeaux_red = 26;
constexpr colour_t saturated_red = 27;
constexpr colour_t bright_red = 28;
constexpr colour_t dark_pink = 29;
constexpr colour_t bright_pink = 30;
constexpr colour_t light_pink = 31;
constexpr Colour_t black = 0;
constexpr Colour_t grey = 1;
constexpr Colour_t white = 2;
constexpr Colour_t dark_purple = 3;
constexpr Colour_t light_purple = 4;
constexpr Colour_t bright_purple = 5;
constexpr Colour_t dark_blue = 6;
constexpr Colour_t light_blue = 7;
constexpr Colour_t icy_blue = 8;
constexpr Colour_t teal = 9;
constexpr Colour_t aquamarine = 10;
constexpr Colour_t saturated_green = 11;
constexpr Colour_t dark_green = 12;
constexpr Colour_t moss_green = 13;
constexpr Colour_t bright_green = 14;
constexpr Colour_t olive_green = 15;
constexpr Colour_t dark_olive_green = 16;
constexpr Colour_t bright_yellow = 17;
constexpr Colour_t yellow = 18;
constexpr Colour_t dark_yellow = 19;
constexpr Colour_t light_orange = 20;
constexpr Colour_t dark_orange = 21;
constexpr Colour_t light_brown = 22;
constexpr Colour_t saturated_brown = 23;
constexpr Colour_t dark_brown = 24;
constexpr Colour_t salmon_pink = 25;
constexpr Colour_t bordeaux_red = 26;
constexpr Colour_t saturated_red = 27;
constexpr Colour_t bright_red = 28;
constexpr Colour_t dark_pink = 29;
constexpr Colour_t bright_pink = 30;
constexpr Colour_t light_pink = 31;
constexpr colour_t outline(colour_t c)
constexpr Colour_t outline(Colour_t c)
{
return c | outline_flag;
}
constexpr colour_t inset(colour_t c)
constexpr Colour_t inset(Colour_t c)
{
return c | inset_flag;
}
constexpr colour_t translucent(colour_t c)
constexpr Colour_t translucent(Colour_t c)
{
return c | translucent_flag;
}
constexpr colour_t opaque(colour_t c)
constexpr Colour_t opaque(Colour_t c)
{
return c & ~translucent_flag;
}
void initColourMap();
palette_index_t getShade(colour_t colour, uint8_t shade);
PaletteIndex_t getShade(Colour_t colour, uint8_t shade);
}
namespace PaletteIndex
{
constexpr palette_index_t transparent = 0;
constexpr palette_index_t index_0A = 0x0A;
constexpr palette_index_t index_0C = 0x0C;
constexpr palette_index_t index_0E = 0x0E;
constexpr palette_index_t index_11 = 0x11;
constexpr palette_index_t index_12 = 0x12;
constexpr palette_index_t index_15 = 0x15;
constexpr palette_index_t index_1F = 0x1F;
constexpr palette_index_t index_24 = 0x24;
constexpr palette_index_t index_29 = 0x29;
constexpr palette_index_t index_2E = 0x2E;
constexpr palette_index_t index_30 = 0x30;
constexpr palette_index_t index_35 = 0x35;
constexpr palette_index_t index_38 = 0x38;
constexpr palette_index_t index_3F = 0x3F;
constexpr palette_index_t index_41 = 0x41;
constexpr palette_index_t index_43 = 0x43;
constexpr palette_index_t index_4B = 0x4B;
constexpr palette_index_t index_50 = 0x50;
constexpr palette_index_t index_58 = 0x58;
constexpr palette_index_t index_64 = 0x64;
constexpr palette_index_t index_66 = 0x66;
constexpr palette_index_t index_67 = 0x67;
constexpr palette_index_t index_68 = 0x68;
constexpr palette_index_t index_71 = 0x71;
constexpr palette_index_t index_7D = 0x7D;
constexpr palette_index_t index_85 = 0x85;
constexpr palette_index_t index_89 = 0x89;
constexpr palette_index_t index_9D = 0x9D;
constexpr palette_index_t index_A1 = 0xA1;
constexpr palette_index_t index_A2 = 0xA2;
constexpr palette_index_t index_A3 = 0xA3;
constexpr palette_index_t index_AC = 0xAC;
constexpr palette_index_t index_AD = 0xAD;
constexpr palette_index_t index_B8 = 0xB8;
constexpr palette_index_t index_BA = 0xBA;
constexpr palette_index_t index_BB = 0xBB;
constexpr palette_index_t index_BC = 0xBC;
constexpr palette_index_t index_C3 = 0xC3;
constexpr palette_index_t index_C6 = 0xC6;
constexpr palette_index_t index_D0 = 0xD0;
constexpr palette_index_t index_D3 = 0xD3;
constexpr palette_index_t index_DB = 0xDB;
constexpr palette_index_t index_DE = 0xDE;
constexpr PaletteIndex_t transparent = 0;
constexpr PaletteIndex_t index_0A = 0x0A;
constexpr PaletteIndex_t index_0C = 0x0C;
constexpr PaletteIndex_t index_0E = 0x0E;
constexpr PaletteIndex_t index_11 = 0x11;
constexpr PaletteIndex_t index_12 = 0x12;
constexpr PaletteIndex_t index_15 = 0x15;
constexpr PaletteIndex_t index_1F = 0x1F;
constexpr PaletteIndex_t index_24 = 0x24;
constexpr PaletteIndex_t index_29 = 0x29;
constexpr PaletteIndex_t index_2C = 0x2C;
constexpr PaletteIndex_t index_2E = 0x2E;
constexpr PaletteIndex_t index_30 = 0x30;
constexpr PaletteIndex_t index_31 = 0x31;
constexpr PaletteIndex_t index_32 = 0x32;
constexpr PaletteIndex_t index_35 = 0x35;
constexpr PaletteIndex_t index_38 = 0x38;
constexpr PaletteIndex_t index_3B = 0x3B;
constexpr PaletteIndex_t index_3F = 0x3F;
constexpr PaletteIndex_t index_41 = 0x41;
constexpr PaletteIndex_t index_43 = 0x43;
constexpr PaletteIndex_t index_4B = 0x4B;
constexpr PaletteIndex_t index_50 = 0x50;
constexpr PaletteIndex_t index_58 = 0x58;
constexpr PaletteIndex_t index_64 = 0x64;
constexpr PaletteIndex_t index_66 = 0x66;
constexpr PaletteIndex_t index_67 = 0x67;
constexpr PaletteIndex_t index_68 = 0x68;
constexpr PaletteIndex_t index_71 = 0x71;
constexpr PaletteIndex_t index_74 = 0x74;
constexpr PaletteIndex_t index_7D = 0x7D;
constexpr PaletteIndex_t index_85 = 0x85;
constexpr PaletteIndex_t index_89 = 0x89;
constexpr PaletteIndex_t index_9D = 0x9D;
constexpr PaletteIndex_t index_A1 = 0xA1;
constexpr PaletteIndex_t index_A2 = 0xA2;
constexpr PaletteIndex_t index_A3 = 0xA3;
constexpr PaletteIndex_t index_AC = 0xAC;
constexpr PaletteIndex_t index_AD = 0xAD;
constexpr PaletteIndex_t index_B8 = 0xB8;
constexpr PaletteIndex_t index_BA = 0xBA;
constexpr PaletteIndex_t index_BB = 0xBB;
constexpr PaletteIndex_t index_BC = 0xBC;
constexpr PaletteIndex_t index_C3 = 0xC3;
constexpr PaletteIndex_t index_C6 = 0xC6;
constexpr PaletteIndex_t index_D0 = 0xD0;
constexpr PaletteIndex_t index_D3 = 0xD3;
constexpr PaletteIndex_t index_DB = 0xDB;
constexpr PaletteIndex_t index_DE = 0xDE;
}
}

View File

@ -1,4 +1,5 @@
#include "Gfx.h"
#include "../Config.h"
#include "../Console.h"
#include "../Drawing/SoftwareDrawingEngine.h"
#include "../Environment.h"
@ -15,6 +16,7 @@
#include <fstream>
#include <iostream>
#include <memory>
#include <numeric>
using namespace OpenLoco::Interop;
using namespace OpenLoco::Utility;
@ -40,34 +42,141 @@ namespace OpenLoco::Gfx
constexpr uint32_t g1_count_objects = 0x40000;
constexpr uint32_t g1_count_temporary = 0x1000;
static loco_global<drawpixelinfo_t, 0x0050B884> _screen_dpi;
static loco_global<drawpixelinfo_t, 0x005233B8> _windowDPI;
static loco_global<Context, 0x0050B884> _screenContext;
static loco_global<g1_element[g1_expected_count::disc + g1_count_temporary + g1_count_objects], 0x9E2424> _g1Elements;
static loco_global<G1Element[g1_expected_count::disc + g1_count_temporary + g1_count_objects], 0x9E2424> _g1Elements;
static std::unique_ptr<std::byte[]> _g1Buffer;
static loco_global<uint16_t[147], 0x050B8C8> _paletteToG1Offset;
static loco_global<uint16_t, 0x112C824> _currentFontFlags;
static loco_global<int16_t, 0x112C876> _currentFontSpriteBase;
static loco_global<uint8_t[224 * 4], 0x112C884> _characterWidths;
static loco_global<uint8_t[4], 0x1136594> _windowColours;
static palette_index_t _textColours[8] = { 0 };
static PaletteIndex_t _textColours[8] = { 0 };
drawpixelinfo_t& screenDpi()
Ui::Rect Context::getDrawableRect() const
{
return _screen_dpi;
auto zoom = zoom_level;
auto left = x >> zoom;
auto top = y >> zoom;
auto right = (width >> zoom) + left;
auto bottom = (height >> zoom) + top;
return Ui::Rect::fromLTRB(left, top, right, bottom);
}
static std::vector<g1_element> convertElements(const std::vector<g1_element32_t>& elements32)
Ui::Rect Context::getUiRect() const
{
auto elements = std::vector<g1_element>();
return Ui::Rect::fromLTRB(x, y, x + width, y + height);
}
Context& screenContext()
{
return _screenContext;
}
// 0x004FFAE8
uint32_t applyGhostToImage(uint32_t imageId)
{
if (Config::get().construction_marker)
{
return Gfx::recolourTranslucent(imageId, PaletteIndex::index_31);
}
else
{
return Gfx::recolour(imageId, PaletteIndex::index_2C);
}
}
const PaletteMap& PaletteMap::getDefault()
{
static bool initialised = false;
static uint8_t data[256];
static PaletteMap defaultMap(data);
if (!initialised)
{
std::iota(std::begin(data), std::end(data), 0);
}
return defaultMap;
}
uint8_t& PaletteMap::operator[](size_t index)
{
assert(index < _dataLength);
// Provide safety in release builds
if (index >= _dataLength)
{
static uint8_t dummy;
return dummy;
}
return _data[index];
}
uint8_t PaletteMap::operator[](size_t index) const
{
assert(index < _dataLength);
// Provide safety in release builds
if (index >= _dataLength)
{
return 0;
}
return _data[index];
}
uint8_t PaletteMap::blend(uint8_t src, uint8_t dst) const
{
// src = 0 would be transparent so there is no blend palette for that, hence (src - 1)
assert(src != 0 && (src - 1) < _numMaps);
assert(dst < _mapLength);
auto idx = ((src - 1) * 256) + dst;
return (*this)[idx];
}
void PaletteMap::copy(size_t dstIndex, const PaletteMap& src, size_t srcIndex, size_t length)
{
auto maxLength = std::min(_mapLength - srcIndex, _mapLength - dstIndex);
assert(length <= maxLength);
auto copyLength = std::min(length, maxLength);
std::memcpy(&_data[dstIndex], &src._data[srcIndex], copyLength);
}
std::optional<uint32_t> getPaletteG1Index(Colour_t paletteId)
{
if (paletteId < std::size(_paletteToG1Offset))
{
return _paletteToG1Offset[paletteId];
}
return std::nullopt;
}
std::optional<PaletteMap> getPaletteMapForColour(Colour_t paletteId)
{
auto g1Index = getPaletteG1Index(paletteId);
if (g1Index)
{
auto g1 = getG1Element(*g1Index);
if (g1 != nullptr)
{
return PaletteMap(g1->offset, g1->height, g1->width);
}
}
return std::nullopt;
}
static std::vector<G1Element> convertElements(const std::vector<G1Element32>& elements32)
{
auto elements = std::vector<G1Element>();
elements.reserve(elements32.size());
std::transform(
elements32.begin(),
elements32.end(),
std::back_inserter(elements),
[](g1_element32_t src) { return g1_element(src); });
[](G1Element32 src) { return G1Element(src); });
return elements;
}
@ -81,7 +190,7 @@ namespace OpenLoco::Gfx
throw std::runtime_error("Opening g1 file failed.");
}
g1_header_t header;
G1Header header;
if (!readData(stream, header))
{
throw std::runtime_error("Reading g1 file header failed.");
@ -98,7 +207,7 @@ namespace OpenLoco::Gfx
}
// Read element headers
auto elements32 = std::vector<g1_element32_t>(header.num_entries);
auto elements32 = std::vector<G1Element32>(header.num_entries);
if (!readData(stream, elements32.data(), header.num_entries))
{
throw std::runtime_error("Reading g1 element headers failed.");
@ -139,36 +248,120 @@ namespace OpenLoco::Gfx
}
// 0x00447485
// edi: dpi
// edi: context
// ebp: fill
void clear(drawpixelinfo_t& dpi, uint32_t fill)
void clear(Context& context, uint32_t fill)
{
int32_t w = dpi.width / (1 << dpi.zoom_level);
int32_t h = dpi.height / (1 << dpi.zoom_level);
uint8_t* ptr = dpi.bits;
int32_t w = context.width / (1 << context.zoom_level);
int32_t h = context.height / (1 << context.zoom_level);
uint8_t* ptr = context.bits;
for (int32_t y = 0; y < h; y++)
{
std::fill_n(ptr, w, fill);
ptr += w + dpi.pitch;
ptr += w + context.pitch;
}
}
void clearSingle(drawpixelinfo_t& dpi, uint8_t paletteId)
void clearSingle(Context& context, uint8_t paletteId)
{
auto fill = (paletteId << 24) | (paletteId << 16) | (paletteId << 8) | paletteId;
clear(dpi, fill);
clear(context, fill);
}
// 0x004957C4
int16_t clipString(int16_t width, char* string)
{
if (width < 6)
{
*string = '\0';
return 0;
}
registers regs;
regs.di = width;
regs.esi = (int32_t)string;
call(0x004957C4, regs);
return regs.cx;
// If width of the full string is less than allowed width then we don't need to clip
auto clippedWidth = getStringWidth(string);
if (clippedWidth <= width)
{
return clippedWidth;
}
// Append each character 1 by 1 with an ellipsis on the end until width is exceeded
std::string bestString;
std::string curString;
for (const auto* chr = string; *chr != '\0'; ++chr)
{
curString.push_back(*chr);
switch (*chr)
{
case ControlCodes::move_x:
curString.push_back(*++chr);
break;
case ControlCodes::adjust_palette:
case 3:
case 4:
curString.push_back(*++chr);
break;
case ControlCodes::newline:
case ControlCodes::newline_smaller:
case ControlCodes::font_small:
case ControlCodes::font_large:
case ControlCodes::font_bold:
case ControlCodes::font_regular:
case ControlCodes::outline:
case ControlCodes::outline_off:
case ControlCodes::window_colour_1:
case ControlCodes::window_colour_2:
case ControlCodes::window_colour_3:
case ControlCodes::window_colour_4:
break;
case ControlCodes::inline_sprite_str:
curString.push_back(*++chr);
curString.push_back(*++chr);
curString.push_back(*++chr);
curString.push_back(*++chr);
break;
case ControlCodes::newline_x_y:
curString.push_back(*++chr);
curString.push_back(*++chr);
break;
default:
if (*chr <= 0x16)
{
curString.push_back(*++chr);
curString.push_back(*++chr);
}
else if (*chr < 32)
{
curString.push_back(*++chr);
curString.push_back(*++chr);
curString.push_back(*++chr);
curString.push_back(*++chr);
}
break;
}
auto ellipseString = curString;
ellipseString.append("...");
auto ellipsedWidth = getStringWidth(ellipseString.c_str());
if (ellipsedWidth < width)
{
// Keep best string with ellipse
bestString = ellipseString;
}
else
{
strcpy(string, bestString.c_str());
return getStringWidth(string);
}
}
return getStringWidth(string);
}
/**
@ -232,7 +425,7 @@ namespace OpenLoco::Gfx
case ControlCodes::window_colour_1:
case ControlCodes::window_colour_2:
case ControlCodes::window_colour_3:
case 0x10:
case ControlCodes::window_colour_4:
break;
case ControlCodes::inline_sprite_str:
@ -260,7 +453,104 @@ namespace OpenLoco::Gfx
return width;
}
static void setTextColours(palette_index_t pal1, palette_index_t pal2, palette_index_t pal3)
/**
* 0x004955BC
*
* @param buffer @<esi>
* @return width @<cx>
*/
uint16_t getMaxStringWidth(const char* buffer)
{
uint16_t width = 0;
uint16_t maxWidth = 0;
const uint8_t* str = reinterpret_cast<const uint8_t*>(buffer);
int16_t fontSpriteBase = _currentFontSpriteBase;
while (*str != (uint8_t)0)
{
const uint8_t chr = *str;
str++;
if (chr >= 32)
{
width += _characterWidths[chr - 32 + fontSpriteBase];
continue;
}
switch (chr)
{
case ControlCodes::move_x:
maxWidth = std::max(width, maxWidth);
width = *str;
str++;
break;
case ControlCodes::adjust_palette:
case 3:
case 4:
str++;
break;
case ControlCodes::newline:
case ControlCodes::newline_smaller:
continue;
case ControlCodes::font_small:
fontSpriteBase = Font::small;
break;
case ControlCodes::font_large:
fontSpriteBase = Font::large;
break;
case ControlCodes::font_bold:
fontSpriteBase = Font::medium_bold;
break;
case ControlCodes::font_regular:
fontSpriteBase = Font::medium_normal;
break;
case ControlCodes::outline:
case ControlCodes::outline_off:
case ControlCodes::window_colour_1:
case ControlCodes::window_colour_2:
case ControlCodes::window_colour_3:
case ControlCodes::window_colour_4:
break;
case ControlCodes::newline_x_y:
maxWidth = std::max(width, maxWidth);
width = *str;
str += 2;
break;
case ControlCodes::inline_sprite_str:
{
const uint32_t image = reinterpret_cast<const uint32_t*>(str)[0];
const uint32_t imageId = image & 0x7FFFF;
str += 4;
width += _g1Elements[imageId].width;
break;
}
default:
if (chr <= 0x16)
{
str += 2;
}
else
{
str += 4;
}
break;
}
}
maxWidth = std::max(width, maxWidth);
return maxWidth;
}
static void setTextColours(PaletteIndex_t pal1, PaletteIndex_t pal2, PaletteIndex_t pal3)
{
if ((_currentFontFlags & text_draw_flags::inset) != 0)
return;
@ -282,9 +572,9 @@ namespace OpenLoco::Gfx
}
// 0x00451189
static Gfx::point_t loopNewline(drawpixelinfo_t* context, Gfx::point_t origin, uint8_t* str)
static Ui::Point loopNewline(Context* context, Ui::Point origin, uint8_t* str)
{
Gfx::point_t pos = origin;
Ui::Point pos = origin;
while (true)
{
// When offscreen in y dimension don't draw text
@ -401,6 +691,12 @@ namespace OpenLoco::Gfx
setTextColours(Colour::getShade(hue, 9), PaletteIndex::index_0A, PaletteIndex::index_0A);
break;
}
case ControlCodes::window_colour_4:
{
int hue = _windowColours[3];
setTextColours(Colour::getShade(hue, 9), PaletteIndex::index_0A, PaletteIndex::index_0A);
break;
}
case ControlCodes::inline_sprite_str:
{
@ -509,38 +805,38 @@ namespace OpenLoco::Gfx
* @param context @<edi>
* @param text @<esi>
*/
Gfx::point_t drawString(drawpixelinfo_t* context, int16_t x, int16_t y, uint8_t colour, void* str)
Ui::Point drawString(Context& context, int16_t x, int16_t y, uint8_t colour, void* str)
{
// 0x00E04348, 0x00E0434A
Gfx::point_t origin = { x, y };
Ui::Point origin = { x, y };
if (colour == FormatFlags::fe)
{
return loopNewline(context, origin, (uint8_t*)str);
return loopNewline(&context, origin, (uint8_t*)str);
}
if (colour == FormatFlags::fd)
{
_currentFontFlags = 0;
setTextColour(0);
return loopNewline(context, origin, (uint8_t*)str);
return loopNewline(&context, origin, (uint8_t*)str);
}
if (x >= context->x + context->width)
if (x >= context.x + context.width)
return origin;
if (x < context->x - 1280)
if (x < context.x - 1280)
return origin;
if (y >= context->y + context->height)
if (y >= context.y + context.height)
return origin;
if (y < context->y - 90)
if (y < context.y - 90)
return origin;
if (colour == FormatFlags::ff)
{
return loopNewline(context, origin, (uint8_t*)str);
return loopNewline(&context, origin, (uint8_t*)str);
}
_currentFontFlags = 0;
@ -599,7 +895,7 @@ namespace OpenLoco::Gfx
setTextColours(Colour::getShade(colour, 9), PaletteIndex::index_0A, PaletteIndex::index_0A);
}
return loopNewline(context, origin, (uint8_t*)str);
return loopNewline(&context, origin, (uint8_t*)str);
}
// 0x00495224
@ -609,9 +905,9 @@ namespace OpenLoco::Gfx
// cx: x
// dx: y
// esi: args
// edi: dpi
// edi: context
int16_t drawString_495224(
drawpixelinfo_t& dpi,
Context& context,
int16_t x,
int16_t y,
int16_t width,
@ -625,8 +921,8 @@ namespace OpenLoco::Gfx
regs.bp = width;
regs.cx = x;
regs.dx = y;
regs.esi = (int32_t)args;
regs.edi = (int32_t)&dpi;
regs.esi = X86Pointer(args);
regs.edi = X86Pointer(&context);
call(0x00495224, regs);
return regs.dx;
@ -638,9 +934,9 @@ namespace OpenLoco::Gfx
// cx: x
// dx: y
// esi: args
// edi: dpi
// edi: context
void drawString_494B3F(
drawpixelinfo_t& dpi,
Context& context,
int16_t x,
int16_t y,
uint8_t colour,
@ -652,22 +948,22 @@ namespace OpenLoco::Gfx
regs.bx = stringId;
regs.cx = x;
regs.dx = y;
regs.esi = (int32_t)args;
regs.edi = (int32_t)&dpi;
regs.esi = X86Pointer(args);
regs.edi = X86Pointer(&context);
call(0x00494B3F, regs);
}
/**
*
* @param dpi @<edi>
* @param context @<edi>
* @param origin {x @<cx>, y @<dx>}
* @param colour @<al>
* @param stringId @<bx>
* @param args @<edi>
*/
void drawString_494B3F(
drawpixelinfo_t& dpi,
point_t* origin,
Context& context,
Point* origin,
uint8_t colour,
string_id stringId,
const void* args)
@ -677,8 +973,8 @@ namespace OpenLoco::Gfx
regs.bx = stringId;
regs.cx = origin->x;
regs.dx = origin->y;
regs.esi = (int32_t)args;
regs.edi = (int32_t)&dpi;
regs.esi = X86Pointer(args);
regs.edi = X86Pointer(&context);
call(0x00494B3F, regs);
origin->x = regs.cx;
@ -691,10 +987,10 @@ namespace OpenLoco::Gfx
// cx: x
// dx: y
// esi: args
// edi: dpi
// edi: context
// bp: width
void drawString_494BBF(
drawpixelinfo_t& dpi,
Context& context,
int16_t x,
int16_t y,
int16_t width,
@ -707,8 +1003,8 @@ namespace OpenLoco::Gfx
regs.bx = stringId;
regs.cx = x;
regs.dx = y;
regs.esi = (int32_t)args;
regs.edi = (int32_t)&dpi;
regs.esi = X86Pointer(args);
regs.edi = X86Pointer(&context);
regs.bp = width;
call(0x00494BBF, regs);
}
@ -719,9 +1015,9 @@ namespace OpenLoco::Gfx
// cx: x
// dx: y
// esi: args
// edi: dpi
// edi: context
void drawString_494C78(
drawpixelinfo_t& dpi,
Context& context,
int16_t x,
int16_t y,
uint8_t colour,
@ -733,8 +1029,8 @@ namespace OpenLoco::Gfx
regs.bx = stringId;
regs.cx = x;
regs.dx = y;
regs.esi = (int32_t)args;
regs.edi = (int32_t)&dpi;
regs.esi = X86Pointer(args);
regs.edi = X86Pointer(&context);
call(0x00494C78, regs);
}
@ -744,9 +1040,9 @@ namespace OpenLoco::Gfx
// cx: x
// dx: y
// esi: args
// edi: dpi
// edi: context
void drawStringUnderline(
drawpixelinfo_t& dpi,
Context& context,
int16_t x,
int16_t y,
uint8_t colour,
@ -758,8 +1054,8 @@ namespace OpenLoco::Gfx
regs.bx = stringId;
regs.cx = x;
regs.dx = y;
regs.esi = (int32_t)args;
regs.edi = (int32_t)&dpi;
regs.esi = X86Pointer(args);
regs.edi = X86Pointer(&context);
call(0x00494CB2, regs);
}
@ -769,9 +1065,9 @@ namespace OpenLoco::Gfx
// cx: x
// dx: y
// esi: args
// edi: dpi
// edi: context
void drawStringLeftUnderline(
drawpixelinfo_t& dpi,
Context& context,
int16_t x,
int16_t y,
uint8_t colour,
@ -783,8 +1079,8 @@ namespace OpenLoco::Gfx
regs.bx = stringId;
regs.cx = x;
regs.dx = y;
regs.esi = (int32_t)args;
regs.edi = (int32_t)&dpi;
regs.esi = X86Pointer(args);
regs.edi = X86Pointer(&context);
call(0x00494D78, regs);
}
@ -794,9 +1090,9 @@ namespace OpenLoco::Gfx
// cx: x
// dx: y
// esi: args
// edi: dpi
// edi: context
void drawStringCentred(
drawpixelinfo_t& dpi,
Context& context,
int16_t x,
int16_t y,
uint8_t colour,
@ -808,8 +1104,8 @@ namespace OpenLoco::Gfx
regs.bx = stringId;
regs.cx = x;
regs.dx = y;
regs.esi = (int32_t)args;
regs.edi = (int32_t)&dpi;
regs.esi = X86Pointer(args);
regs.edi = X86Pointer(&context);
call(0x00494DE8, regs);
}
@ -820,9 +1116,9 @@ namespace OpenLoco::Gfx
// cx: x
// dx: y
// esi: args
// edi: dpi
// edi: context
void drawStringCentredClipped(
drawpixelinfo_t& dpi,
Context& context,
int16_t x,
int16_t y,
int16_t width,
@ -831,8 +1127,8 @@ namespace OpenLoco::Gfx
const void* args)
{
registers regs;
regs.edi = (int32_t)&dpi;
regs.esi = (int32_t)args;
regs.edi = X86Pointer(&context);
regs.esi = X86Pointer(args);
regs.ebx = stringId;
regs.cx = x;
regs.dx = y;
@ -850,27 +1146,29 @@ namespace OpenLoco::Gfx
* @param colour @<al>
* @param stringId @<bx>
* @param args @<esi>
* returns width @<ax>
*/
void drawStringCentredWrapped(
drawpixelinfo_t* context,
point_t* origin,
uint16_t drawStringCentredWrapped(
Context& context,
Point& origin,
uint16_t width,
uint8_t colour,
string_id stringId,
const void* args)
{
registers regs;
regs.edi = (uintptr_t)context;
regs.esi = (uintptr_t)args;
regs.cx = origin->x;
regs.dx = origin->y;
regs.edi = X86Pointer(&context);
regs.esi = X86Pointer(args);
regs.cx = origin.x;
regs.dx = origin.y;
regs.bp = width;
regs.al = colour;
regs.bx = stringId;
call(0x00494ECF, regs);
origin->x = regs.cx;
origin->y = regs.dx;
origin.x = regs.cx;
origin.y = regs.dx;
return regs.ax;
}
// 0x00494E33
@ -880,9 +1178,9 @@ namespace OpenLoco::Gfx
// cx: x
// dx: y
// esi: args
// edi: dpi
// edi: context
void drawStringCentredRaw(
drawpixelinfo_t& dpi,
Context& context,
int16_t x,
int16_t y,
int16_t width,
@ -890,8 +1188,8 @@ namespace OpenLoco::Gfx
const void* args)
{
registers regs;
regs.edi = (int32_t)&dpi;
regs.esi = (int32_t)args;
regs.edi = X86Pointer(&context);
regs.esi = X86Pointer(args);
regs.cx = x;
regs.dx = y;
regs.al = colour;
@ -905,7 +1203,7 @@ namespace OpenLoco::Gfx
uint16_t getStringWidthNewLined(const char* buffer)
{
registers regs;
regs.esi = (uintptr_t)buffer;
regs.esi = X86Pointer(buffer);
call(0x00495715, regs);
return regs.cx;
}
@ -914,7 +1212,7 @@ namespace OpenLoco::Gfx
{
// gfx_wrap_string
registers regs;
regs.esi = (uintptr_t)buffer;
regs.esi = X86Pointer(buffer);
regs.di = stringWidth;
call(0x00495301, regs);
@ -922,7 +1220,7 @@ namespace OpenLoco::Gfx
}
// 0x004474BA
static void drawRectImpl(Gfx::drawpixelinfo_t* dpi, int16_t left, int16_t top, int16_t right, int16_t bottom, uint32_t colour)
static void drawRectImpl(Gfx::Context& context, int16_t left, int16_t top, int16_t right, int16_t bottom, uint32_t colour)
{
registers regs;
regs.ax = left;
@ -930,22 +1228,22 @@ namespace OpenLoco::Gfx
regs.cx = top;
regs.dx = bottom;
regs.ebp = colour;
regs.edi = (uint32_t)dpi;
regs.edi = X86Pointer(&context);
call(0x004474BA, regs);
}
void fillRect(Gfx::drawpixelinfo_t* dpi, int16_t left, int16_t top, int16_t right, int16_t bottom, uint32_t colour)
void fillRect(Gfx::Context& context, int16_t left, int16_t top, int16_t right, int16_t bottom, uint32_t colour)
{
drawRectImpl(dpi, left, top, right, bottom, colour);
drawRectImpl(context, left, top, right, bottom, colour);
}
void drawRect(Gfx::drawpixelinfo_t* dpi, int16_t x, int16_t y, uint16_t dx, uint16_t dy, uint32_t colour)
void drawRect(Gfx::Context& context, int16_t x, int16_t y, uint16_t dx, uint16_t dy, uint32_t colour)
{
// This makes the function signature more like a drawing application
drawRectImpl(dpi, x, y, x + dx - 1, y + dy - 1, colour);
drawRectImpl(context, x, y, x + dx - 1, y + dy - 1, colour);
}
void fillRectInset(Gfx::drawpixelinfo_t* dpi, int16_t left, int16_t top, int16_t right, int16_t bottom, uint32_t colour, uint8_t flags)
void fillRectInset(Gfx::Context& context, int16_t left, int16_t top, int16_t right, int16_t bottom, uint32_t colour, uint8_t flags)
{
registers regs;
regs.ax = left;
@ -953,19 +1251,19 @@ namespace OpenLoco::Gfx
regs.cx = top;
regs.dx = bottom;
regs.ebp = colour;
regs.edi = (uint32_t)dpi;
regs.edi = X86Pointer(&context);
regs.si = flags;
call(0x004C58C7, regs);
}
void drawRectInset(Gfx::drawpixelinfo_t* dpi, int16_t x, int16_t y, uint16_t dx, uint16_t dy, uint32_t colour, uint8_t flags)
void drawRectInset(Gfx::Context& context, int16_t x, int16_t y, uint16_t dx, uint16_t dy, uint32_t colour, uint8_t flags)
{
// This makes the function signature more like a drawing application
fillRectInset(dpi, x, y, x + dx - 1, y + dy - 1, colour, flags);
fillRectInset(context, x, y, x + dx - 1, y + dy - 1, colour, flags);
}
// 0x00452DA4
void drawLine(Gfx::drawpixelinfo_t* dpi, int16_t left, int16_t top, int16_t right, int16_t bottom, uint32_t colour)
void drawLine(Gfx::Context& context, int16_t left, int16_t top, int16_t right, int16_t bottom, uint32_t colour)
{
registers regs;
regs.ax = left;
@ -973,7 +1271,7 @@ namespace OpenLoco::Gfx
regs.cx = right;
regs.dx = bottom;
regs.ebp = colour;
regs.edi = (uint32_t)dpi;
regs.edi = X86Pointer(&context);
call(0x00452DA4, regs);
}
@ -1030,9 +1328,9 @@ namespace OpenLoco::Gfx
engine->drawDirtyBlocks();
}
if (Input::hasFlag(Input::input_flags::flag5))
if (Input::hasFlag(Input::Flags::flag5))
{
call(0x004072EC); // NOP on _NO_LOCO_WIN32_
Ui::processMessagesMini();
}
else
{
@ -1067,44 +1365,54 @@ namespace OpenLoco::Gfx
redrawScreenRect(Rect::fromLTRB(left, top, right, bottom));
}
void drawImage(Gfx::drawpixelinfo_t* dpi, int16_t x, int16_t y, uint32_t image)
void drawImage(Gfx::Context* context, int16_t x, int16_t y, uint32_t image)
{
registers regs;
regs.cx = x;
regs.dx = y;
regs.ebx = image;
regs.edi = (uint32_t)dpi;
regs.edi = X86Pointer(context);
call(0x00448C79, regs);
}
uint32_t recolour(uint32_t image)
{
return (1 << 29) | image;
return ImageIdFlags::remap | image;
}
uint32_t recolour(uint32_t image, uint8_t colour)
{
return (1 << 29) | (colour << 19) | image;
return ImageIdFlags::remap | (colour << 19) | image;
}
uint32_t recolour2(uint32_t image, uint8_t colour1, uint8_t colour2)
{
return ImageIdFlags::remap | ImageIdFlags::remap2 | (colour1 << 19) | (colour2 << 24) | image;
}
uint32_t recolour2(uint32_t image, ColourScheme colourScheme)
{
return recolour2(image, colourScheme.primary, colourScheme.secondary);
}
uint32_t recolourTranslucent(uint32_t image, uint8_t colour)
{
return (1 << 30) | (colour << 19) | image;
return ImageIdFlags::translucent | (colour << 19) | image;
}
loco_global<uint8_t*, 0x0050B860> _50B860;
loco_global<uint32_t, 0x00E04324> _E04324;
void drawImageSolid(Gfx::drawpixelinfo_t* dpi, int16_t x, int16_t y, uint32_t image, uint8_t palette_index)
void drawImageSolid(Gfx::Context* context, int16_t x, int16_t y, uint32_t image, uint8_t palette_index)
{
uint8_t palette[256];
memset(palette, palette_index, 256);
palette[0] = 0;
drawImagePaletteSet(dpi, x, y, image, palette);
drawImagePaletteSet(context, x, y, image, palette);
}
void drawImagePaletteSet(Gfx::drawpixelinfo_t* dpi, int16_t x, int16_t y, uint32_t image, uint8_t* palette)
void drawImagePaletteSet(Gfx::Context* context, int16_t x, int16_t y, uint32_t image, uint8_t* palette)
{
_50B860 = palette;
_E04324 = 0x20000000;
@ -1112,30 +1420,30 @@ namespace OpenLoco::Gfx
regs.cx = x;
regs.dx = y;
regs.ebx = image;
regs.edi = (uint32_t)dpi;
regs.edi = X86Pointer(context);
call(0x00448D90, regs);
}
bool clipDrawpixelinfo(Gfx::drawpixelinfo_t** dst, Gfx::drawpixelinfo_t* src, int16_t x, int16_t y, int16_t width, int16_t height)
// 0x004CEC50
std::optional<Gfx::Context> clipContext(const Gfx::Context& src, const Ui::Rect& newRect)
{
registers regs;
regs.ax = x;
regs.bx = width;
regs.edi = (int32_t)src;
regs.dx = height;
regs.cx = y;
call(0x4cec50, regs);
*dst = (Gfx::drawpixelinfo_t*)regs.edi;
const Ui::Rect oldRect = src.getUiRect();
Ui::Rect intersect = oldRect.intersection(newRect);
const auto stride = oldRect.size.width + src.pitch;
const int16_t newPitch = stride - intersect.size.width;
auto* newBits = src.bits + (stride * (intersect.origin.y - oldRect.origin.y) + (intersect.origin.x - oldRect.origin.x));
intersect.origin.x = std::max(0, oldRect.origin.x - newRect.origin.x);
intersect.origin.y = std::max(0, oldRect.origin.y - newRect.origin.y);
Gfx::Context newContext{ newBits, static_cast<int16_t>(intersect.origin.x), static_cast<int16_t>(intersect.origin.y), static_cast<int16_t>(intersect.size.width), static_cast<int16_t>(intersect.size.height), newPitch, 0 };
return *dst != nullptr;
if (newContext.width <= 0 || newContext.height <= 0)
{
return {};
}
return { newContext };
}
bool clipDrawpixelinfo(Gfx::drawpixelinfo_t** dst, Gfx::drawpixelinfo_t* src, point_t pos, Gfx::ui_size_t size)
{
return clipDrawpixelinfo(dst, src, pos.x, pos.y, size.width, size.height);
}
g1_element* getG1Element(uint32_t id)
G1Element* getG1Element(uint32_t id)
{
if (id < _g1Elements.size())
{
@ -1143,4 +1451,9 @@ namespace OpenLoco::Gfx
}
return nullptr;
}
void setCurrentFontSpriteBase(int16_t value)
{
_currentFontSpriteBase = value;
}
}

View File

@ -1,16 +1,22 @@
#pragma once
#include "../Core/Optional.hpp"
#include "../OpenLoco.h"
#include "../Types.hpp"
#include "../Ui/Rect.h"
#include "Types.h"
#include "../Ui/Types.hpp"
#include <cstdint>
namespace OpenLoco
{
using Colour_t = uint8_t;
}
namespace OpenLoco::Gfx
{
#pragma pack(push, 1)
struct drawpixelinfo_t
struct Context
{
uint8_t* bits; // 0x00
int16_t x; // 0x04
@ -19,17 +25,20 @@ namespace OpenLoco::Gfx
int16_t height; // 0x0A
int16_t pitch; // 0x0C note: this is actually (pitch - width)
uint16_t zoom_level; // 0x0E
Ui::Rect getUiRect() const;
Ui::Rect getDrawableRect() const;
};
drawpixelinfo_t& screenDpi();
Context& screenContext();
struct g1_header_t
struct G1Header
{
uint32_t num_entries;
uint32_t total_size;
};
struct g1_element32_t
struct G1Element32
{
uint32_t offset; // 0x00
int16_t width; // 0x04
@ -41,7 +50,7 @@ namespace OpenLoco::Gfx
};
// A version that can be 64-bit when ready...
struct g1_element
struct G1Element
{
uint8_t* offset = nullptr;
int16_t width = 0;
@ -51,8 +60,8 @@ namespace OpenLoco::Gfx
uint16_t flags = 0;
int16_t unused = 0;
g1_element() = default;
g1_element(const g1_element32_t& src)
G1Element() = default;
G1Element(const G1Element32& src)
: offset((uint8_t*)src.offset)
, width(src.width)
, height(src.height)
@ -65,20 +74,73 @@ namespace OpenLoco::Gfx
};
#pragma pack(pop)
namespace ImageIdFlags
{
constexpr uint32_t remap = 1 << 29;
constexpr uint32_t translucent = 1 << 30;
constexpr uint32_t remap2 = 1 << 31;
}
drawpixelinfo_t& screenDpi();
/**
* Represents an 8-bit indexed map that maps from one palette index to another.
*/
struct PaletteMap
{
private:
uint8_t* _data{};
uint32_t _dataLength{};
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-private-field"
uint16_t _numMaps;
#pragma clang diagnostic pop
uint16_t _mapLength;
public:
static const PaletteMap& getDefault();
PaletteMap() = default;
PaletteMap(uint8_t* data, uint16_t numMaps, uint16_t mapLength)
: _data(data)
, _dataLength(numMaps * mapLength)
, _numMaps(numMaps)
, _mapLength(mapLength)
{
}
template<std::size_t TSize>
PaletteMap(uint8_t (&map)[TSize])
: _data(map)
, _dataLength(static_cast<uint32_t>(std::size(map)))
, _numMaps(1)
, _mapLength(static_cast<uint16_t>(std::size(map)))
{
}
uint8_t& operator[](size_t index);
uint8_t operator[](size_t index) const;
uint8_t* data() const { return _data; }
uint8_t blend(uint8_t src, uint8_t dst) const;
void copy(size_t dstIndex, const PaletteMap& src, size_t srcIndex, size_t length);
};
std::optional<uint32_t> getPaletteG1Index(Colour_t paletteId);
std::optional<PaletteMap> getPaletteMapForColour(Colour_t paletteId);
Context& screenContext();
void loadG1();
void clear(drawpixelinfo_t& dpi, uint32_t fill);
void clearSingle(drawpixelinfo_t& dpi, uint8_t paletteId);
void clear(Context& context, uint32_t fill);
void clearSingle(Context& context, uint8_t paletteId);
int16_t clipString(int16_t width, char* string);
uint16_t getStringWidth(const char* buffer);
uint16_t getMaxStringWidth(const char* buffer);
Gfx::point_t drawString(drawpixelinfo_t* context, int16_t x, int16_t y, uint8_t colour, void* str);
Ui::Point drawString(Context& context, int16_t x, int16_t y, uint8_t colour, void* str);
int16_t drawString_495224(
drawpixelinfo_t& dpi,
Context& context,
int16_t x,
int16_t y,
int16_t width,
@ -86,20 +148,20 @@ namespace OpenLoco::Gfx
string_id stringId,
const void* args = nullptr);
void drawString_494B3F(
drawpixelinfo_t& dpi,
Context& context,
int16_t x,
int16_t y,
uint8_t colour,
string_id stringId,
const void* args = nullptr);
void drawString_494B3F(
drawpixelinfo_t& dpi,
point_t* origin,
Context& context,
Ui::Point* origin,
uint8_t colour,
string_id stringId,
const void* args = nullptr);
void drawString_494BBF(
drawpixelinfo_t& dpi,
Context& context,
int16_t x,
int16_t y,
int16_t width,
@ -107,50 +169,50 @@ namespace OpenLoco::Gfx
string_id stringId,
const void* args = nullptr);
void drawString_494C78(
drawpixelinfo_t& dpi,
Context& context,
int16_t x,
int16_t y,
uint8_t colour,
string_id stringId,
const void* args = nullptr);
void drawStringUnderline(
drawpixelinfo_t& dpi,
Context& context,
int16_t x,
int16_t y,
uint8_t colour,
string_id stringId,
const void* args);
void drawStringLeftUnderline(
drawpixelinfo_t& dpi,
Context& context,
int16_t x,
int16_t y,
uint8_t colour,
string_id stringId,
const void* args = nullptr);
void drawStringCentred(
drawpixelinfo_t& dpi,
Context& context,
int16_t x,
int16_t y,
uint8_t colour,
string_id stringId,
const void* args = nullptr);
void drawStringCentredClipped(
drawpixelinfo_t& dpi,
Context& context,
int16_t x,
int16_t y,
int16_t width,
uint8_t colour,
string_id stringId,
const void* args = nullptr);
void drawStringCentredWrapped(
drawpixelinfo_t* context,
point_t* origin,
uint16_t drawStringCentredWrapped(
Context& context,
Ui::Point& origin,
uint16_t width,
uint8_t colour,
string_id stringId,
const void* args = nullptr);
void drawStringCentredRaw(
drawpixelinfo_t& dpi,
Context& context,
int16_t x,
int16_t y,
int16_t width,
@ -159,17 +221,20 @@ namespace OpenLoco::Gfx
uint16_t getStringWidthNewLined(const char* buffer);
std::pair<uint16_t, uint16_t> wrapString(const char* buffer, uint16_t stringWidth);
void fillRect(Gfx::drawpixelinfo_t* dpi, int16_t left, int16_t top, int16_t right, int16_t bottom, uint32_t colour);
void drawRect(Gfx::drawpixelinfo_t* dpi, int16_t x, int16_t y, uint16_t dx, uint16_t dy, uint32_t colour);
void fillRectInset(Gfx::drawpixelinfo_t* dpi, int16_t left, int16_t top, int16_t right, int16_t bottom, uint32_t colour, uint8_t flags);
void drawRectInset(Gfx::drawpixelinfo_t* dpi, int16_t x, int16_t y, uint16_t dx, uint16_t dy, uint32_t colour, uint8_t flags);
void drawLine(Gfx::drawpixelinfo_t* dpi, int16_t left, int16_t top, int16_t right, int16_t bottom, uint32_t colour);
void drawImage(Gfx::drawpixelinfo_t* dpi, int16_t x, int16_t y, uint32_t image);
void drawImageSolid(Gfx::drawpixelinfo_t* dpi, int16_t x, int16_t y, uint32_t image, uint8_t palette_index);
void drawImagePaletteSet(Gfx::drawpixelinfo_t* dpi, int16_t x, int16_t y, uint32_t image, uint8_t* palette);
void fillRect(Gfx::Context& context, int16_t left, int16_t top, int16_t right, int16_t bottom, uint32_t colour);
void drawRect(Gfx::Context& context, int16_t x, int16_t y, uint16_t dx, uint16_t dy, uint32_t colour);
void fillRectInset(Gfx::Context& context, int16_t left, int16_t top, int16_t right, int16_t bottom, uint32_t colour, uint8_t flags);
void drawRectInset(Gfx::Context& context, int16_t x, int16_t y, uint16_t dx, uint16_t dy, uint32_t colour, uint8_t flags);
void drawLine(Gfx::Context& context, int16_t left, int16_t top, int16_t right, int16_t bottom, uint32_t colour);
void drawImage(Gfx::Context* context, int16_t x, int16_t y, uint32_t image);
void drawImageSolid(Gfx::Context* context, int16_t x, int16_t y, uint32_t image, uint8_t palette_index);
void drawImagePaletteSet(Gfx::Context* context, int16_t x, int16_t y, uint32_t image, uint8_t* palette);
uint32_t recolour(uint32_t image);
uint32_t recolour(uint32_t image, uint8_t colour);
uint32_t recolour2(uint32_t image, uint8_t colour1, uint8_t colour2);
uint32_t recolour2(uint32_t image, ColourScheme colourScheme);
uint32_t recolourTranslucent(uint32_t image, uint8_t colour);
uint32_t applyGhostToImage(uint32_t imageId);
void invalidateScreen();
void setDirtyBlocks(int32_t left, int32_t top, int32_t right, int32_t bottom);
@ -179,7 +244,9 @@ namespace OpenLoco::Gfx
void redrawScreenRect(Ui::Rect rect);
void redrawScreenRect(int16_t left, int16_t top, int16_t right, int16_t bottom);
bool clipDrawpixelinfo(Gfx::drawpixelinfo_t** dst, Gfx::drawpixelinfo_t* src, int16_t x, int16_t y, int16_t width, int16_t height);
bool clipDrawpixelinfo(Gfx::drawpixelinfo_t** dst, Gfx::drawpixelinfo_t* src, Gfx::point_t pos, Gfx::ui_size_t size);
g1_element* getG1Element(uint32_t id);
std::optional<Gfx::Context> clipContext(const Gfx::Context& src, const Ui::Rect& newRect);
G1Element* getG1Element(uint32_t id);
void setCurrentFontSpriteBase(int16_t value);
}

View File

@ -8,23 +8,59 @@ namespace OpenLoco::ImageIds
{
constexpr uint32_t null = 0xFFFFFFFF;
constexpr uint32_t construction_arrow_north = 428;
constexpr uint32_t construction_arrow_east = 429;
constexpr uint32_t construction_arrow_south = 430;
constexpr uint32_t construction_arrow_west = 431;
constexpr uint32_t construction_arrow_north2 = 432;
constexpr uint32_t construction_arrow_east2 = 433;
constexpr uint32_t construction_arrow_south2 = 434;
constexpr uint32_t construction_arrow_west2 = 435;
constexpr uint32_t construction_arrow_north3 = 436;
constexpr uint32_t construction_arrow_east3 = 437;
constexpr uint32_t construction_arrow_south3 = 438;
constexpr uint32_t construction_arrow_west3 = 439;
constexpr uint32_t construction_arrow_north_east = 440;
constexpr uint32_t construction_arrow_south_east = 441;
constexpr uint32_t construction_arrow_south_west = 442;
constexpr uint32_t construction_arrow_north_west = 443;
constexpr uint32_t blank_tile = 448;
constexpr uint32_t currency_symbol = 1919;
constexpr uint32_t text_palette = 2169;
constexpr uint32_t window_resize_handle = 2305;
constexpr uint32_t colour_swatch_recolourable = 2306;
constexpr uint32_t colour_swatch_recolourable_raised = 2307;
constexpr uint32_t colour_swatch_recolourable_pressed = 2308;
constexpr uint32_t company_list_dropdown_icon = 2309;
constexpr uint32_t icon_parent_folder = 2310;
constexpr uint32_t icon_folder = 2311;
constexpr uint32_t close_button = 2321;
constexpr uint32_t curved_border_left = 2315;
constexpr uint32_t curved_border_right = 2316;
constexpr uint32_t close_button = 2321;
constexpr uint32_t frame_background_image = 2322;
constexpr uint32_t frame_background_image_alt = 2323;
constexpr uint32_t inline_green_up_arrow = 2324;
constexpr uint32_t inline_red_down_arrow = 2325;
constexpr uint32_t progressbar_style0_frame0 = 2326;
constexpr uint32_t progressbar_style0_frame1 = 2327;
constexpr uint32_t progressbar_style0_frame2 = 2328;
constexpr uint32_t progressbar_style0_frame3 = 2329;
constexpr uint32_t progressbar_track = 2330;
constexpr uint32_t progressbar_style1_frame0 = 2331;
constexpr uint32_t progressbar_style1_frame1 = 2332;
constexpr uint32_t progressbar_style1_frame2 = 2333;
constexpr uint32_t progressbar_style1_frame3 = 2334;
constexpr uint32_t construction_straight = 2335;
constexpr uint32_t step_back = 2336;
constexpr uint32_t step_forward = 2337;
constexpr uint32_t red_arrow_up = 2338;
constexpr uint32_t red_arrow_down = 2339;
constexpr uint32_t construction_left_hand_curve_very_small = 2340;
constexpr uint32_t construction_right_hand_curve_very_small = 2341;
constexpr uint32_t construction_left_hand_curve_small = 2342;
@ -52,6 +88,16 @@ namespace OpenLoco::ImageIds
constexpr uint32_t centre_viewport = 2364;
constexpr uint32_t rotate_object = 2365;
constexpr uint32_t red_flag = 2369;
constexpr uint32_t green_flag = 2370;
constexpr uint32_t yellow_flag = 2371;
constexpr uint32_t airport_pickup = 2372;
constexpr uint32_t airport_place = 2373;
constexpr uint32_t pass_signal = 2374;
constexpr uint32_t route_delete = 2375;
constexpr uint32_t route_skip = 2376;
constexpr uint32_t route_wait = 2377;
constexpr uint32_t route_force_unload = 2378;
constexpr uint32_t show_station_catchment = 2379;
constexpr uint32_t plant_cluster_selected_tree = 2380;
constexpr uint32_t plant_cluster_random_tree = 2381;
@ -59,6 +105,8 @@ namespace OpenLoco::ImageIds
constexpr uint32_t music_controls_stop = 2383;
constexpr uint32_t music_controls_play = 2384;
constexpr uint32_t music_controls_next = 2385;
constexpr uint32_t refit_cargo_button = 2386;
constexpr uint32_t tab_vehicle_background = 2387;
constexpr uint32_t tab_display = 2391;
constexpr uint32_t tab_control = 2392;
@ -132,6 +180,231 @@ namespace OpenLoco::ImageIds
constexpr uint32_t random_map_watermark = 2468;
constexpr uint32_t height_map_compass = 2469;
constexpr uint32_t number_circle_00 = 3238;
constexpr uint32_t number_circle_01 = 3239;
constexpr uint32_t number_circle_02 = 3240;
constexpr uint32_t number_circle_03 = 3241;
constexpr uint32_t number_circle_04 = 3242;
constexpr uint32_t number_circle_05 = 3243;
constexpr uint32_t number_circle_06 = 3244;
constexpr uint32_t number_circle_07 = 3245;
constexpr uint32_t number_circle_08 = 3246;
constexpr uint32_t number_circle_09 = 3247;
constexpr uint32_t number_circle_10 = 3248;
constexpr uint32_t number_circle_11 = 3249;
constexpr uint32_t number_circle_12 = 3250;
constexpr uint32_t number_circle_13 = 3251;
constexpr uint32_t number_circle_14 = 3252;
constexpr uint32_t number_circle_15 = 3253;
constexpr uint32_t number_circle_16 = 3254;
constexpr uint32_t number_circle_17 = 3255;
constexpr uint32_t number_circle_18 = 3256;
constexpr uint32_t number_circle_19 = 3257;
constexpr uint32_t number_circle_20 = 3258;
constexpr uint32_t number_circle_21 = 3259;
constexpr uint32_t number_circle_22 = 3260;
constexpr uint32_t number_circle_23 = 3261;
constexpr uint32_t number_circle_24 = 3262;
constexpr uint32_t number_circle_25 = 3263;
constexpr uint32_t number_circle_26 = 3264;
constexpr uint32_t number_circle_27 = 3265;
constexpr uint32_t number_circle_28 = 3266;
constexpr uint32_t number_circle_29 = 3267;
constexpr uint32_t number_circle_30 = 3268;
constexpr uint32_t number_circle_31 = 3269;
constexpr uint32_t number_circle_32 = 3270;
constexpr uint32_t number_circle_33 = 3271;
constexpr uint32_t number_circle_34 = 3272;
constexpr uint32_t number_circle_35 = 3273;
constexpr uint32_t number_circle_36 = 3274;
constexpr uint32_t number_circle_37 = 3275;
constexpr uint32_t number_circle_38 = 3276;
constexpr uint32_t number_circle_39 = 3277;
constexpr uint32_t number_circle_40 = 3278;
constexpr uint32_t number_circle_41 = 3279;
constexpr uint32_t number_circle_42 = 3280;
constexpr uint32_t number_circle_43 = 3281;
constexpr uint32_t number_circle_44 = 3282;
constexpr uint32_t number_circle_45 = 3283;
constexpr uint32_t number_circle_46 = 3284;
constexpr uint32_t number_circle_47 = 3285;
constexpr uint32_t number_circle_48 = 3286;
constexpr uint32_t number_circle_49 = 3287;
constexpr uint32_t number_circle_50 = 3288;
constexpr uint32_t number_circle_51 = 3289;
constexpr uint32_t number_circle_52 = 3290;
constexpr uint32_t number_circle_53 = 3291;
constexpr uint32_t number_circle_54 = 3292;
constexpr uint32_t number_circle_55 = 3293;
constexpr uint32_t number_circle_56 = 3294;
constexpr uint32_t number_circle_57 = 3295;
constexpr uint32_t number_circle_58 = 3296;
constexpr uint32_t number_circle_59 = 3297;
constexpr uint32_t number_circle_60 = 3298;
constexpr uint32_t number_circle_61 = 3299;
constexpr uint32_t number_circle_62 = 3300;
constexpr uint32_t number_circle_63 = 3301;
constexpr uint32_t vehicle_crash_0_00 = 3302;
constexpr uint32_t vehicle_crash_0_01 = 3303;
constexpr uint32_t vehicle_crash_0_02 = 3304;
constexpr uint32_t vehicle_crash_0_03 = 3305;
constexpr uint32_t vehicle_crash_0_04 = 3306;
constexpr uint32_t vehicle_crash_0_05 = 3307;
constexpr uint32_t vehicle_crash_0_06 = 3308;
constexpr uint32_t vehicle_crash_0_07 = 3309;
constexpr uint32_t vehicle_crash_0_08 = 3310;
constexpr uint32_t vehicle_crash_0_09 = 3311;
constexpr uint32_t vehicle_crash_0_10 = 3312;
constexpr uint32_t vehicle_crash_0_11 = 3313;
constexpr uint32_t vehicle_crash_1_00 = 3314;
constexpr uint32_t vehicle_crash_1_01 = 3315;
constexpr uint32_t vehicle_crash_1_02 = 3316;
constexpr uint32_t vehicle_crash_1_03 = 3317;
constexpr uint32_t vehicle_crash_1_04 = 3318;
constexpr uint32_t vehicle_crash_1_05 = 3319;
constexpr uint32_t vehicle_crash_1_06 = 3320;
constexpr uint32_t vehicle_crash_1_07 = 3321;
constexpr uint32_t vehicle_crash_1_08 = 3322;
constexpr uint32_t vehicle_crash_1_09 = 3323;
constexpr uint32_t vehicle_crash_1_10 = 3324;
constexpr uint32_t vehicle_crash_1_11 = 3325;
constexpr uint32_t vehicle_crash_2_00 = 3326;
constexpr uint32_t vehicle_crash_2_01 = 3327;
constexpr uint32_t vehicle_crash_2_02 = 3328;
constexpr uint32_t vehicle_crash_2_03 = 3329;
constexpr uint32_t vehicle_crash_2_04 = 3330;
constexpr uint32_t vehicle_crash_2_05 = 3331;
constexpr uint32_t vehicle_crash_2_06 = 3332;
constexpr uint32_t vehicle_crash_2_07 = 3333;
constexpr uint32_t vehicle_crash_2_08 = 3334;
constexpr uint32_t vehicle_crash_2_09 = 3335;
constexpr uint32_t vehicle_crash_2_10 = 3336;
constexpr uint32_t vehicle_crash_2_11 = 3337;
constexpr uint32_t vehicle_crash_3_00 = 3338;
constexpr uint32_t vehicle_crash_3_01 = 3339;
constexpr uint32_t vehicle_crash_3_02 = 3340;
constexpr uint32_t vehicle_crash_3_03 = 3341;
constexpr uint32_t vehicle_crash_3_04 = 3342;
constexpr uint32_t vehicle_crash_3_05 = 3343;
constexpr uint32_t vehicle_crash_3_06 = 3344;
constexpr uint32_t vehicle_crash_3_07 = 3345;
constexpr uint32_t vehicle_crash_3_08 = 3346;
constexpr uint32_t vehicle_crash_3_09 = 3347;
constexpr uint32_t vehicle_crash_3_10 = 3348;
constexpr uint32_t vehicle_crash_3_11 = 3349;
constexpr uint32_t vehicle_crash_4_00 = 3350;
constexpr uint32_t vehicle_crash_4_01 = 3351;
constexpr uint32_t vehicle_crash_4_02 = 3352;
constexpr uint32_t vehicle_crash_4_03 = 3353;
constexpr uint32_t vehicle_crash_4_04 = 3354;
constexpr uint32_t vehicle_crash_4_05 = 3355;
constexpr uint32_t vehicle_crash_4_06 = 3356;
constexpr uint32_t vehicle_crash_4_07 = 3357;
constexpr uint32_t vehicle_crash_4_08 = 3358;
constexpr uint32_t vehicle_crash_4_09 = 3359;
constexpr uint32_t vehicle_crash_4_10 = 3360;
constexpr uint32_t vehicle_crash_4_11 = 3361;
constexpr uint32_t explosion_smoke_00 = 3362;
constexpr uint32_t explosion_smoke_01 = 3363;
constexpr uint32_t explosion_smoke_02 = 3364;
constexpr uint32_t explosion_smoke_03 = 3365;
constexpr uint32_t explosion_smoke_04 = 3366;
constexpr uint32_t explosion_smoke_05 = 3367;
constexpr uint32_t explosion_smoke_06 = 3368;
constexpr uint32_t explosion_smoke_07 = 3369;
constexpr uint32_t explosion_smoke_08 = 3370;
constexpr uint32_t explosion_smoke_09 = 3371;
constexpr uint32_t explosion_cloud_00 = 3372;
constexpr uint32_t explosion_cloud_01 = 3373;
constexpr uint32_t explosion_cloud_02 = 3374;
constexpr uint32_t explosion_cloud_03 = 3375;
constexpr uint32_t explosion_cloud_04 = 3376;
constexpr uint32_t explosion_cloud_05 = 3377;
constexpr uint32_t explosion_cloud_06 = 3378;
constexpr uint32_t explosion_cloud_07 = 3379;
constexpr uint32_t explosion_cloud_08 = 3380;
constexpr uint32_t explosion_cloud_09 = 3381;
constexpr uint32_t explosion_cloud_10 = 3382;
constexpr uint32_t explosion_cloud_11 = 3383;
constexpr uint32_t explosion_cloud_12 = 3384;
constexpr uint32_t explosion_cloud_13 = 3385;
constexpr uint32_t explosion_cloud_14 = 3386;
constexpr uint32_t explosion_cloud_15 = 3387;
constexpr uint32_t explosion_cloud_16 = 3388;
constexpr uint32_t explosion_cloud_17 = 3389;
constexpr uint32_t fireball_00 = 3390;
constexpr uint32_t fireball_01 = 3391;
constexpr uint32_t fireball_02 = 3392;
constexpr uint32_t fireball_03 = 3393;
constexpr uint32_t fireball_04 = 3394;
constexpr uint32_t fireball_05 = 3395;
constexpr uint32_t fireball_06 = 3396;
constexpr uint32_t fireball_07 = 3397;
constexpr uint32_t fireball_08 = 3398;
constexpr uint32_t fireball_09 = 3399;
constexpr uint32_t fireball_10 = 3400;
constexpr uint32_t fireball_11 = 3401;
constexpr uint32_t fireball_12 = 3402;
constexpr uint32_t fireball_13 = 3403;
constexpr uint32_t fireball_14 = 3404;
constexpr uint32_t fireball_15 = 3405;
constexpr uint32_t fireball_16 = 3406;
constexpr uint32_t fireball_17 = 3407;
constexpr uint32_t fireball_18 = 3408;
constexpr uint32_t fireball_19 = 3409;
constexpr uint32_t fireball_20 = 3410;
constexpr uint32_t fireball_21 = 3411;
constexpr uint32_t fireball_22 = 3412;
constexpr uint32_t fireball_23 = 3413;
constexpr uint32_t fireball_24 = 3414;
constexpr uint32_t fireball_25 = 3415;
constexpr uint32_t fireball_26 = 3416;
constexpr uint32_t fireball_27 = 3417;
constexpr uint32_t fireball_28 = 3418;
constexpr uint32_t fireball_29 = 3419;
constexpr uint32_t fireball_30 = 3420;
constexpr uint32_t splash_00 = 3421;
constexpr uint32_t splash_01 = 3422;
constexpr uint32_t splash_02 = 3423;
constexpr uint32_t splash_03 = 3424;
constexpr uint32_t splash_04 = 3425;
constexpr uint32_t splash_05 = 3426;
constexpr uint32_t splash_06 = 3427;
constexpr uint32_t splash_07 = 3428;
constexpr uint32_t splash_08 = 3429;
constexpr uint32_t splash_09 = 3430;
constexpr uint32_t splash_10 = 3431;
constexpr uint32_t splash_11 = 3432;
constexpr uint32_t splash_12 = 3433;
constexpr uint32_t splash_13 = 3434;
constexpr uint32_t splash_14 = 3435;
constexpr uint32_t splash_15 = 3436;
constexpr uint32_t splash_16 = 3437;
constexpr uint32_t splash_17 = 3438;
constexpr uint32_t splash_18 = 3439;
constexpr uint32_t splash_19 = 3440;
constexpr uint32_t splash_20 = 3441;
constexpr uint32_t splash_21 = 3442;
constexpr uint32_t splash_22 = 3443;
constexpr uint32_t splash_23 = 3444;
constexpr uint32_t splash_24 = 3445;
constexpr uint32_t splash_25 = 3446;
constexpr uint32_t splash_26 = 3447;
constexpr uint32_t splash_27 = 3448;
constexpr uint32_t smoke_00 = 3465;
constexpr uint32_t smoke_01 = 3466;
constexpr uint32_t smoke_02 = 3467;
constexpr uint32_t smoke_03 = 3468;
constexpr uint32_t smoke_04 = 3469;
constexpr uint32_t smoke_05 = 3470;
constexpr uint32_t smoke_06 = 3471;
constexpr uint32_t smoke_07 = 3472;
constexpr uint32_t smoke_08 = 3473;
constexpr uint32_t smoke_09 = 3474;
constexpr uint32_t smoke_10 = 3475;
constexpr uint32_t smoke_11 = 3476;
constexpr uint32_t tab_object_settings = 3505;
constexpr uint32_t tab_object_audio = 3506;
constexpr uint32_t tab_object_currency = 3507;
@ -172,13 +445,13 @@ namespace OpenLoco::ImageIds
constexpr uint32_t news_background_new_right = 3542;
constexpr uint32_t volume_slider_track = 3543;
constexpr uint32_t volume_slider_thumb = 3544;
constexpr uint32_t speed_control_track = 3545;
constexpr uint32_t speed_control_thumb = 3546;
constexpr uint32_t title_menu_sparkle = 3547;
constexpr uint32_t title_menu_save = 3548;
constexpr uint32_t title_menu_lesson_l = 3549;
constexpr uint32_t title_menu_lesson_a = 3550;
constexpr uint32_t title_menu_lesson_p = 3551;
constexpr uint32_t title_menu_globe_spin_0 = 3552;
constexpr uint32_t title_menu_globe_spin_1 = 3553;
constexpr uint32_t title_menu_globe_spin_2 = 3554;
@ -211,7 +484,6 @@ namespace OpenLoco::ImageIds
constexpr uint32_t title_menu_globe_spin_29 = 3581;
constexpr uint32_t title_menu_globe_spin_30 = 3582;
constexpr uint32_t title_menu_globe_spin_31 = 3583;
constexpr uint32_t title_menu_globe_construct_0 = 3584;
constexpr uint32_t title_menu_globe_construct_1 = 3585;
constexpr uint32_t title_menu_globe_construct_2 = 3586;
@ -244,7 +516,6 @@ namespace OpenLoco::ImageIds
constexpr uint32_t title_menu_globe_construct_29 = 3613;
constexpr uint32_t title_menu_globe_construct_30 = 3614;
constexpr uint32_t title_menu_globe_construct_31 = 3615;
constexpr uint32_t chris_sawyer_logo_small = 3616;
constexpr uint32_t chris_sawyer_logo_intro_left = 3617;
constexpr uint32_t chris_sawyer_logo_intro_right = 3618;
@ -252,10 +523,10 @@ namespace OpenLoco::ImageIds
constexpr uint32_t atari_logo_intro_left = 3620;
constexpr uint32_t atari_logo_intro_right = 3620;
constexpr uint32_t atari_logo_small = UNUSED_IMG(3623);
constexpr uint32_t locomotion_logo = 3624;
constexpr uint32_t wide_tab = 3625;
constexpr uint32_t scenario_completed_tick = 3629;
constexpr uint32_t owner_jailed = 3630;
}

View File

@ -1,68 +0,0 @@
#pragma once
#include <cstdint>
namespace OpenLoco::Gfx
{
struct point_t
{
int16_t x = 0;
int16_t y = 0;
constexpr point_t(){};
constexpr point_t(int16_t x, int16_t y)
: x(x)
, y(y)
{
}
bool operator==(const point_t& rhs)
{
return x == rhs.x && y == rhs.y;
}
bool operator==(const int16_t rhs)
{
return x == rhs && y == rhs;
}
point_t& operator+=(const point_t& rhs)
{
x += rhs.x;
y += rhs.y;
return *this;
}
point_t& operator-=(const point_t& rhs)
{
x -= rhs.x;
y -= rhs.y;
return *this;
}
friend point_t operator+(point_t lhs, const point_t& rhs)
{
lhs += rhs;
return lhs;
}
friend point_t operator-(point_t lhs, const point_t& rhs)
{
lhs -= rhs;
return lhs;
}
};
struct ui_size_t
{
uint16_t width = 0;
uint16_t height = 0;
constexpr ui_size_t(uint16_t width, uint16_t height)
: width(width)
, height(height)
{
}
};
}

View File

@ -2,13 +2,12 @@
#include "Graphics/Colour.h"
#include "Interop/Interop.hpp"
#include "Map/Tile.h"
#include "Objects/InterfaceSkinObject.h"
#include "Objects/ObjectManager.h"
#include "OpenLoco.h"
#include "Tutorial.h"
#include "Ui.h"
#include "Ui/WindowManager.h"
#include "ViewportManager.h"
#include "Widget.h"
#include "Window.h"
using namespace OpenLoco::Interop;
@ -29,37 +28,22 @@ namespace OpenLoco::Gui
if (OpenLoco::isTitleMode())
{
Ui::Windows::openTitleMenu();
Ui::Windows::openTitleExit();
Ui::Windows::openTitleLogo();
Ui::Windows::openTitleVersion();
Ui::TitleOptions::open();
Ui::Windows::TitleMenu::open();
Ui::Windows::TitleExit::open();
Ui::Windows::TitleLogo::open();
Ui::Windows::TitleVersion::open();
Ui::Windows::TitleOptions::open();
}
else
{
Windows::ToolbarTop::Game::open();
Windows::PlayerInfoPanel::open();
TimePanel::open();
Windows::TimePanel::open();
if (OpenLoco::Tutorial::state() != Tutorial::tutorial_state::none)
if (OpenLoco::Tutorial::state() != Tutorial::State::none)
{
auto window = WindowManager::createWindow(
WindowType::tutorial,
Gfx::point_t(140, Ui::height() - 27),
Gfx::ui_size_t(Ui::width() - 280, 27),
Ui::WindowFlags::stick_to_front | Ui::WindowFlags::transparent | Ui::WindowFlags::no_background,
(Ui::window_event_list*)0x4fa10c);
window->widgets = (Ui::widget_t*)0x509de0;
window->initScrollWidgets();
auto skin = OpenLoco::ObjectManager::get<interface_skin_object>();
if (skin != nullptr)
{
window->colours[0] = Colour::translucent(skin->colour_06);
window->colours[1] = Colour::translucent(skin->colour_07);
}
Windows::Tutorial::open();
}
}
@ -72,12 +56,6 @@ namespace OpenLoco::Gui
const int32_t uiWidth = Ui::width();
const int32_t uiHeight = Ui::height();
if (OpenLoco::isEditorMode())
{
call(0x43CD35);
return;
}
auto window = WindowManager::getMainWindow();
if (window)
{
@ -116,6 +94,13 @@ namespace OpenLoco::Gui
window->x = std::max(uiWidth, 640) - window->width;
}
window = WindowManager::find(WindowType::editorToolbar);
if (window)
{
window->y = uiHeight - window->height;
window->width = std::max(uiWidth, 640);
}
window = WindowManager::find(WindowType::titleMenu);
if (window)
{
@ -145,7 +130,7 @@ namespace OpenLoco::Gui
window = WindowManager::find(WindowType::tutorial);
if (window)
{
if (Tutorial::state() == Tutorial::tutorial_state::none)
if (Tutorial::state() == Tutorial::State::none)
{
WindowManager::close(window);
}

View File

@ -1,6 +1,7 @@
#include "Industry.h"
#include "Interop/Interop.hpp"
#include "Localisation/StringIds.h"
#include "Map/AnimationManager.h"
#include "Map/TileManager.h"
#include "Objects/CargoObject.h"
#include "Objects/IndustryObject.h"
@ -13,26 +14,43 @@ using namespace OpenLoco::Map;
namespace OpenLoco
{
industry_id_t industry::id() const
struct Unk4F9274
{
auto first = (industry*)0x005C455C;
return (industry_id_t)(this - first);
Pos2 pos;
uint8_t unk;
};
static const Unk4F9274 word_4F9274[] = {
{ { 0, 0 }, 0 },
{ { Location::null, 0 }, 0 }
};
static const Unk4F9274 word_4F927C[] = {
{ { 0, 0 }, 0 },
{ { 0, 32 }, 1 },
{ { 32, 32 }, 2 },
{ { 32, 0 }, 3 },
{ { Location::null, 0 }, 0 }
};
IndustryId_t Industry::id() const
{
auto first = (Industry*)0x005C455C;
return (IndustryId_t)(this - first);
}
industry_object* industry::object() const
IndustryObject* Industry::object() const
{
return ObjectManager::get<industry_object>(object_id);
return ObjectManager::get<IndustryObject>(object_id);
}
bool industry::empty() const
bool Industry::empty() const
{
return name == StringIds::null;
}
bool industry::canReceiveCargo() const
bool Industry::canReceiveCargo() const
{
auto receiveCargoState = false;
for (const auto& receivedCargo : ObjectManager::get<industry_object>(object_id)->required_cargo_type)
for (const auto& receivedCargo : ObjectManager::get<IndustryObject>(object_id)->required_cargo_type)
{
if (receivedCargo != 0xff)
receiveCargoState = true;
@ -40,10 +58,10 @@ namespace OpenLoco
return receiveCargoState;
}
bool industry::canProduceCargo() const
bool Industry::canProduceCargo() const
{
auto produceCargoState = false;
for (const auto& producedCargo : ObjectManager::get<industry_object>(object_id)->produced_cargo_type)
for (const auto& producedCargo : ObjectManager::get<IndustryObject>(object_id)->produced_cargo_type)
{
if (producedCargo != 0xff)
produceCargoState = true;
@ -51,13 +69,13 @@ namespace OpenLoco
return produceCargoState;
}
static bool findTree(surface_element* surface)
static bool findTree(SurfaceElement* surface)
{
auto element = surface;
while (!element->isLast())
{
element++;
if (element->type() == element_type::tree)
if (element->type() == ElementType::tree)
{
return true;
}
@ -66,14 +84,14 @@ namespace OpenLoco
}
// 0x0045935F
void industry::getStatusString(const char* buffer)
void Industry::getStatusString(const char* buffer)
{
char* ptr = (char*)buffer;
*ptr = '\0';
auto industryObj = object();
// Closing Down
if (flags & IndustryFlags::closing_down)
if (flags & IndustryFlags::closingDown)
{
ptr = StringManager::formatString(ptr, StringIds::industry_closing_down);
return;
@ -114,7 +132,7 @@ namespace OpenLoco
}
// 0x00453275
void industry::update()
void Industry::update()
{
if (!(flags & IndustryFlags::flag_01) && under_construction == 0xFF)
{
@ -124,7 +142,7 @@ namespace OpenLoco
sub_45329B(tile_loop.current());
// loc_453318
if (tile_loop.next() == map_pos())
if (tile_loop.next() == Pos2())
{
sub_453354();
break;
@ -134,7 +152,7 @@ namespace OpenLoco
}
// 0x0045329B
void industry::sub_45329B(const map_pos& pos)
void Industry::sub_45329B(const Pos2& pos)
{
const auto& surface = TileManager::get(pos).surface();
if (surface != nullptr)
@ -159,7 +177,7 @@ namespace OpenLoco
}
}
void industry::sub_453354()
void Industry::sub_453354()
{
// 0x00453366
int16_t tmp_a = var_DB / 16;
@ -187,7 +205,7 @@ namespace OpenLoco
{
if (prng.randBool())
{
Map::map_pos randTile{ static_cast<coord_t>(x + (prng.randNext(-15, 16) * 32)), static_cast<coord_t>(y + (prng.randNext(-15, 16) * 32)) };
Map::Pos2 randTile{ static_cast<coord_t>(x + (prng.randNext(-15, 16) * 32)), static_cast<coord_t>(y + (prng.randNext(-15, 16) * 32)) };
uint8_t bl = obj->var_ED;
uint8_t bh = obj->var_EE;
if (obj->var_EF != 0xFF && prng.randBool())
@ -202,7 +220,7 @@ namespace OpenLoco
}
}
void industry::sub_454A43(map_pos pos, uint8_t bl, uint8_t bh, uint8_t dl)
void Industry::sub_454A43(const Pos2& pos, uint8_t bl, uint8_t bh, uint8_t dl)
{
registers regs;
regs.bl = bl;
@ -213,4 +231,44 @@ namespace OpenLoco
regs.dh = id();
call(0x00454A43, regs);
}
// 0x00459D43
void Industry::createMapAnimations()
{
for (size_t i = 0; i < numTiles; i++)
{
auto& tilePos = tiles[i];
auto baseZ = (tilePos.z & ~Location::null) / 4;
auto tile = TileManager::get(tilePos);
for (auto& el : tile)
{
auto industryEl = el.asIndustry();
if (industryEl == nullptr)
continue;
if (industryEl->baseZ() != baseZ)
continue;
auto tileIndustry = industryEl->industry();
if (tileIndustry != nullptr)
{
auto industryObject = tileIndustry->object();
if (industryObject != nullptr)
{
auto animOffsets = word_4F9274;
if (industryObject->var_C6 & (1 << industryEl->var_6_1F()))
{
animOffsets = word_4F927C;
}
while (animOffsets[0].pos.x != Location::null)
{
AnimationManager::createAnimation(3, animOffsets->pos + tilePos, baseZ);
animOffsets++;
}
}
}
}
}
}
}

View File

@ -1,34 +1,30 @@
#pragma once
#include "Localisation/StringManager.h"
#include "Map/Tile.h"
#include "Map/TileLoop.hpp"
#include "Objects/IndustryObject.h"
#include "Town.h"
#include "Types.hpp"
#include "Utility/Prng.hpp"
#include <cstdint>
#include <limits>
namespace OpenLoco
{
using namespace Map;
using industry_id_t = uint8_t;
struct IndustryObject;
namespace IndustryId
{
constexpr industry_id_t null = std::numeric_limits<industry_id_t>::max();
constexpr IndustryId_t null = std::numeric_limits<IndustryId_t>::max();
}
namespace IndustryFlags
{
constexpr uint16_t flag_01 = 1 << 0;
constexpr uint16_t sorted = 1 << 1;
constexpr uint16_t closing_down = 1 << 2;
constexpr uint16_t closingDown = 1 << 2;
constexpr uint16_t flag_04 = 1 << 3;
}
#pragma pack(push, 1)
struct industry
struct Industry
{
string_id name;
coord_t x; // 0x02
@ -37,18 +33,21 @@ namespace OpenLoco
Utility::prng prng; // 0x08
uint8_t object_id; // 0x10
uint8_t under_construction; // 0x11 (0xFF = Finished)
uint8_t pad_12[0xD5 - 0x12];
town_id_t town; // 0xD5
Map::tile_loop tile_loop; // 0xD7
uint16_t pad_12;
uint8_t numTiles; // 0x14
Map::Pos3 tiles[32]; // 0x15
TownId_t town; // 0xD5
Map::TileLoop tile_loop; // 0xD7
int16_t var_DB;
int16_t var_DD;
uint8_t var_DF;
uint8_t owner; // 0xE0
uint8_t pad_E1[0x189 - 0xE1];
uint16_t produced_cargo_quantity[2]; // 0x189
uint8_t pad_18D[0x193 - 0x18D];
uint16_t var_18D[3];
uint16_t required_cargo_quantity[3]; // 0x193
uint8_t pad_199[0x1A3 - 0x199];
uint16_t var_199[3];
uint8_t pad_19F[0x1A3 - 0x19F];
uint16_t produced_cargo_max[2]; // 0x1A3 (produced_cargo_quantity / 8)
uint8_t produced_cargo_transported[2]; // 0x1A7 (%)
uint8_t history_size[2]; // 0x1A9 (<= 20 * 12)
@ -57,17 +56,20 @@ namespace OpenLoco
int32_t history_min_production[2]; // 0x38B
uint8_t pad_393[0x453 - 0x393];
industry_id_t id() const;
industry_object* object() const;
IndustryId_t id() const;
IndustryObject* object() const;
bool empty() const;
bool canReceiveCargo() const;
bool canProduceCargo() const;
void getStatusString(const char* buffer);
void update();
void sub_45329B(const map_pos& pos);
void sub_45329B(const Map::Pos2& pos);
void sub_453354();
void sub_454A43(map_pos pos, uint8_t bl, uint8_t bh, uint8_t dl);
void sub_454A43(const Map::Pos2& pos, uint8_t bl, uint8_t bh, uint8_t dl);
void createMapAnimations();
};
#pragma pack(pop)
static_assert(sizeof(Industry) == 0x453);
}

View File

@ -1,21 +1,33 @@
#include "IndustryManager.h"
#include "CompanyManager.h"
#include "Interop/Interop.hpp"
#include "Math/Vector.hpp"
#include "Objects/IndustryObject.h"
#include "OpenLoco.h"
#include "Ui/WindowManager.h"
using namespace OpenLoco::Interop;
namespace OpenLoco::IndustryManager
{
static loco_global<industry[max_industries], 0x005C455C> _industries;
static loco_global<Industry[max_industries], 0x005C455C> _industries;
std::array<industry, max_industries>& industries()
// 0x00453214
void reset()
{
auto arr = (std::array<industry, max_industries>*)_industries.get();
return *arr;
for (auto& industry : _industries)
{
industry.name = StringIds::null;
}
Ui::Windows::IndustryList::reset();
}
industry* get(industry_id_t id)
LocoFixedVector<Industry> industries()
{
return LocoFixedVector<Industry>(_industries);
}
Industry* get(IndustryId_t id)
{
if (id >= _industries.size())
{
@ -32,10 +44,7 @@ namespace OpenLoco::IndustryManager
CompanyManager::updatingCompanyId(CompanyId::neutral);
for (auto& industry : industries())
{
if (!industry.empty())
{
industry.update();
}
industry.update();
}
}
}
@ -45,4 +54,33 @@ namespace OpenLoco::IndustryManager
{
call(0x0045383B);
}
// 0x00459D2D
void createAllMapAnimations()
{
if (!(addr<0x00525E28, uint32_t>() & (1 << 0)))
return;
for (auto& industry : industries())
{
industry.createMapAnimations();
}
}
// 0x048FE92
bool industryNearPosition(const Map::Pos2& position, uint32_t flags)
{
for (auto& industry : industries())
{
auto industryObj = industry.object();
if ((industryObj->flags & flags) == 0)
continue;
auto manhattanDistance = Math::Vector::manhattanDistance(Map::Pos2{ industry.x, industry.y }, position);
if (manhattanDistance / Map::tile_size < 11)
return true;
}
return false;
}
}

View File

@ -1,5 +1,6 @@
#pragma once
#include "Core/LocoFixedVector.hpp"
#include "Industry.h"
#include <array>
#include <cstddef>
@ -8,8 +9,11 @@ namespace OpenLoco::IndustryManager
{
constexpr size_t max_industries = 128;
std::array<industry, max_industries>& industries();
industry* get(industry_id_t id);
void reset();
LocoFixedVector<Industry> industries();
Industry* get(IndustryId_t id);
void update();
void updateMonthly();
void createAllMapAnimations();
bool industryNearPosition(const Map::Pos2& position, uint32_t flags);
}

View File

@ -13,40 +13,39 @@ using namespace OpenLoco::Interop;
namespace OpenLoco::Input
{
loco_global<uint32_t, 0x00523368> _flags;
static loco_global<uint8_t, 0x0052336D> _state;
static int32_t _cursor_drag_start_x;
static int32_t _cursor_drag_start_y;
static loco_global<State, 0x0052336D> _state;
static Ui::Point32 _cursor_drag_start;
loco_global<uint32_t, 0x00525374> _cursor_drag_state;
void init()
{
_flags = 0;
_state = 0;
_state = State::reset;
}
bool hasFlag(input_flags value)
bool hasFlag(uint32_t value)
{
return (_flags & (uint32_t)value) != 0;
return (_flags & value) != 0;
}
void setFlag(input_flags value)
void setFlag(uint32_t value)
{
_flags |= (uint32_t)value;
_flags |= value;
}
void resetFlag(input_flags value)
void resetFlag(uint32_t value)
{
_flags &= ~(uint32_t)value;
_flags &= ~value;
}
input_state state()
State state()
{
return (input_state)*_state;
return *_state;
}
void state(input_state state)
void state(State state)
{
_state = (uint8_t)state;
_state = state;
}
// 0x00406FEC
@ -60,7 +59,8 @@ namespace OpenLoco::Input
if (_cursor_drag_state == 0)
{
_cursor_drag_state = 1;
Ui::getCursorPos(_cursor_drag_start_x, _cursor_drag_start_y);
auto cursor = Ui::getCursorPos();
_cursor_drag_start = cursor;
Ui::hideCursor();
}
}
@ -70,8 +70,19 @@ namespace OpenLoco::Input
if (_cursor_drag_state != 0)
{
_cursor_drag_state = 0;
Ui::setCursorPos(_cursor_drag_start_x, _cursor_drag_start_y);
Ui::setCursorPos(_cursor_drag_start.x, _cursor_drag_start.y);
Ui::showCursor();
}
}
Ui::Point getNextDragOffset()
{
auto current = Ui::getCursorPos();
auto delta = current - _cursor_drag_start;
Ui::setCursorPos(_cursor_drag_start.x, _cursor_drag_start.y);
return { static_cast<int16_t>(delta.x), static_cast<int16_t>(delta.y) };
}
}

View File

@ -5,44 +5,50 @@
namespace OpenLoco::Input
{
enum class mouse_button : uint16_t
enum class MouseButton : uint16_t
{
released = 0,
left_pressed = 1,
left_released = 2,
right_pressed = 3,
right_released = 4,
leftPressed = 1,
leftReleased = 2,
rightPressed = 3,
rightReleased = 4,
};
enum class input_state
enum class State : uint8_t
{
reset, // 0
normal, // 1
widget_pressed, // 2
positioning_window, // 3
viewport_right, // 4
dropdown_active, // 5
viewport_left, // 6
scroll_left, // 7
resizing, // 8
scroll_right, // 9
reset, // 0
normal, // 1
widgetPressed, // 2
positioningWindow, // 3
viewportRight, // 4
dropdownActive, // 5
viewportLeft, // 6
scrollLeft, // 7
resizing, // 8
scrollRight, // 9
};
enum class input_flags
namespace Flags
{
widget_pressed = 1 << 0,
flag1 = 1 << 1,
flag2 = 1 << 2,
tool_active = 1 << 3,
flag4 = 1 << 4,
flag5 = 1 << 5,
flag6 = 1 << 6,
viewport_scrolling = 1 << 7,
};
constexpr uint32_t widgetPressed = 1 << 0;
constexpr uint32_t flag1 = 1 << 1;
constexpr uint32_t flag2 = 1 << 2;
constexpr uint32_t toolActive = 1 << 3;
constexpr uint32_t flag4 = 1 << 4;
constexpr uint32_t flag5 = 1 << 5;
constexpr uint32_t flag6 = 1 << 6;
constexpr uint32_t viewportScrolling = 1 << 7;
}
namespace MapSelectionFlags
{
constexpr uint8_t catchment_area = 1 << 5;
constexpr uint8_t enable = 1 << 0;
constexpr uint8_t enableConstruct = (1 << 1);
constexpr uint8_t unk_02 = 1 << 2;
constexpr uint8_t unk_03 = 1 << 3;
constexpr uint8_t unk_04 = 1 << 4; // Vehicle orders?
constexpr uint8_t catchmentArea = 1 << 5;
constexpr uint8_t unk_6 = 1 << 6;
};
namespace KeyModifier
@ -55,31 +61,40 @@ namespace OpenLoco::Input
void init();
void initMouse();
bool hasFlag(input_flags value);
void setFlag(input_flags value);
void resetFlag(input_flags value);
input_state state();
void state(input_state);
bool hasFlag(uint32_t value);
void setFlag(uint32_t value);
void resetFlag(uint32_t value);
State state();
void state(State);
Gfx::point_t getMouseLocation();
Ui::Point getMouseLocation();
Ui::Point getMouseLocation2();
bool isHovering(Ui::WindowType);
bool isHovering(Ui::WindowType, Ui::window_number);
bool isHovering(Ui::WindowType type, Ui::window_number number, Ui::widget_index widgetIndex);
Ui::widget_index getHoveredWidgetIndex();
bool isHovering(Ui::WindowType, Ui::WindowNumber_t);
bool isHovering(Ui::WindowType type, Ui::WindowNumber_t number, Ui::WidgetIndex_t widgetIndex);
Ui::WidgetIndex_t getHoveredWidgetIndex();
bool isDropdownActive(Ui::WindowType type, Ui::widget_index index);
bool isDropdownActive(Ui::WindowType type, Ui::WidgetIndex_t index);
bool isPressed(Ui::WindowType type, Ui::window_number number);
bool isPressed(Ui::WindowType type, Ui::window_number number, Ui::widget_index index);
Ui::widget_index getPressedWidgetIndex();
bool isPressed(Ui::WindowType type, Ui::WindowNumber_t number);
bool isPressed(Ui::WindowType type, Ui::WindowNumber_t number, Ui::WidgetIndex_t index);
Ui::WidgetIndex_t getPressedWidgetIndex();
void setPressedWidgetIndex(Ui::WidgetIndex_t index);
void updateCursorPosition();
bool isToolActive(Ui::WindowType, Ui::window_number);
bool toolSet(Ui::window* w, int16_t widgetIndex, uint8_t tool);
void toolCancel();
void toolCancel(Ui::WindowType, Ui::window_number);
Ui::Window* toolGetActiveWindow();
bool isToolActive(Ui::WindowType);
bool isToolActive(Ui::WindowType, Ui::WindowNumber_t);
bool isToolActive(Ui::WindowType, Ui::WindowNumber_t, int16_t);
bool toolSet(Ui::Window* w, int16_t widgetIndex, Ui::CursorId cursorId);
void toolCancel();
void toolCancel(Ui::WindowType, Ui::WindowNumber_t);
int16_t getToolWidgetIndex();
void enqueueText(const char* text);
void enqueueKey(uint32_t key);
bool hasKeyModifier(uint8_t modifier);
uint16_t getMapSelectionFlags();
bool hasMapSelectionFlag(uint8_t flags);
@ -87,17 +102,24 @@ namespace OpenLoco::Input
void resetMapSelectionFlag(uint8_t flags);
void handleKeyboard();
void handleMouse(int16_t x, int16_t y, mouse_button button);
mouse_button getLastKnownButtonState();
void handleMouse(int16_t x, int16_t y, MouseButton button);
MouseButton getLastKnownButtonState();
void enqueueMouseButton(int32_t button);
void moveMouse(int32_t x, int32_t y, int32_t relX, int32_t relY);
void sub_407218();
void sub_407231();
Ui::Point getNextDragOffset();
void processMouseOver(int16_t x, int16_t y);
void processKeyboardInput();
Gfx::point_t getTooltipMouseLocation();
void setTooltipMouseLocation(const Gfx::point_t& loc);
void windowPositionBegin(int16_t x, int16_t y, Ui::Window* window, Ui::WidgetIndex_t widget_index);
Ui::Point getScrollLastLocation();
Ui::Point getDragLastLocation();
Ui::Point getTooltipMouseLocation();
void setTooltipMouseLocation(const Ui::Point& loc);
uint16_t getTooltipTimeout();
void setTooltipTimeout(uint16_t tooltipTimeout);
void setClickRepeatTicks(uint16_t ticks);
}

View File

@ -2,16 +2,17 @@
#include "../CompanyManager.h"
#include "../Config.h"
#include "../Console.h"
#include "../GameCommands.h"
#include "../Entities/EntityManager.h"
#include "../GameCommands/GameCommands.h"
#include "../Input.h"
#include "../Interop/Interop.hpp"
#include "../Intro.h"
#include "../Localisation/StringIds.h"
#include "../OpenLoco.h"
#include "../Things/ThingManager.h"
#include "../Tutorial.h"
#include "../Ui.h"
#include "../Ui/Screenshot.h"
#include "../Vehicles/Vehicle.h"
#include "../Win32.h"
#include "ShortcutManager.h"
#include <cstdint>
@ -52,6 +53,10 @@ namespace OpenLoco::Input
static loco_global<Ui::WindowType, 0x005233B6> _modalWindowType;
static loco_global<char[16], 0x0112C826> _commonFormatArgs;
static std::string _cheatBuffer; // 0x0011364A5
static loco_global<key[64], 0x0113E300> _keyQueue;
static loco_global<uint32_t, 0x00525388> _keyQueueLastWrite;
static loco_global<uint32_t, 0x00525384> _keyQueueReadIndex;
static loco_global<uint32_t, 0x00525380> _keyQueueWriteIndex;
static loco_global<uint8_t[256], 0x01140740> _keyboardState;
static loco_global<uint8_t, 0x011364A4> _11364A4;
@ -69,14 +74,14 @@ namespace OpenLoco::Input
static void loc_4BECDE()
{
setScreenFlag(ScreenFlags::unknown_6);
setScreenFlag(ScreenFlags::driverCheatEnabled);
Audio::playSound(Audio::sound_id::click_press, Ui::width() / 2);
Audio::playSound(Audio::SoundId::clickPress, Ui::width() / 2);
}
static void loc_4BED04()
{
if ((getScreenFlags() & ScreenFlags::unknown_6) == 0)
if ((getScreenFlags() & ScreenFlags::driverCheatEnabled) == 0)
{
return;
// Only works when DRIVER mode is active
@ -89,18 +94,15 @@ namespace OpenLoco::Input
if (w->type != WindowType::vehicle)
continue;
auto t = ThingManager::get<OpenLoco::vehicle>(w->number);
auto t = EntityManager::get<Vehicles::VehicleBase>(w->number);
if (t->owner != CompanyManager::getControllingId())
continue;
if (t->mode != TransportMode::rail)
if (t->getTransportMode() != TransportMode::rail)
continue;
registers regs;
regs.cx = w->number;
regs.bl = GameCommandFlag::apply;
GameCommands::doCommand(77, regs);
Audio::playSound(Audio::sound_id::click_press, Ui::width() / 2);
GameCommands::do_77(w->number);
Audio::playSound(Audio::SoundId::clickPress, Ui::width() / 2);
return;
}
@ -108,38 +110,36 @@ namespace OpenLoco::Input
static void loc_4BED79()
{
registers regs;
regs.bl = GameCommandFlag::apply;
GameCommands::doCommand(78, regs);
GameCommands::do_78();
Audio::playSound(Audio::sound_id::click_press, Ui::width() / 2);
Audio::playSound(Audio::SoundId::clickPress, Ui::width() / 2);
}
static void loc_4BEFEF()
{
switch (Tutorial::state())
{
case Tutorial::tutorial_state::none:
case Tutorial::State::none:
break;
case Tutorial::tutorial_state::playing:
case Tutorial::State::playing:
{
const uint16_t next = Tutorial::nextInput();
_keyModifier = next;
if ((_keyModifier & KeyModifier::unknown) == 0)
return;
ToolTip::closeAndReset();
Windows::ToolTip::closeAndReset();
auto tutStringId = Tutorial::nextString();
auto main = WindowManager::getMainWindow();
auto cursor = getMouseLocation();
ToolTip::update(main, 0, tutStringId, cursor.x, cursor.y);
Windows::ToolTip::update(main, 0, tutStringId, cursor.x, cursor.y);
break;
}
case Tutorial::tutorial_state::recording:
case Tutorial::State::recording:
{
call(0x004BF005);
break;
@ -147,11 +147,43 @@ namespace OpenLoco::Input
}
}
// 0x00406FBA
void enqueueKey(uint32_t keycode)
{
uint32_t writeIndex = _keyQueueWriteIndex;
auto nextWriteIndex = (writeIndex + 1) % std::size(_keyQueue);
if (nextWriteIndex == _keyQueueReadIndex)
{
return;
}
_keyQueueLastWrite = writeIndex;
_keyQueue[writeIndex] = { keycode, 0 };
_keyQueueWriteIndex = nextWriteIndex;
}
void enqueueText(const char* text)
{
if (text != nullptr && text[0] != '\0')
{
auto index = _keyQueueLastWrite;
_keyQueue[index].charCode = text[0];
}
}
// 0x00407028
static key* getNextKey()
{
registers regs;
call(0x00407028, regs);
return (key*)regs.eax;
uint32_t readIndex = _keyQueueReadIndex;
if (readIndex == _keyQueueWriteIndex)
{
return nullptr;
}
auto* out = &_keyQueue[readIndex];
readIndex++;
// Wrap around at _keyQueue size
readIndex %= std::size(_keyQueue);
_keyQueueReadIndex = readIndex;
return out;
}
static bool tryShortcut(Shortcut sc, uint32_t keyCode, uint8_t modifiers)
@ -249,32 +281,32 @@ namespace OpenLoco::Input
{
while (true)
{
auto eax = getNextKey();
if (eax == 0)
auto* nextKey = getNextKey();
if (nextKey == nullptr)
{
loc_4BEFEF();
break;
}
if (eax->keyCode >= 255)
if (nextKey->keyCode >= 255)
continue;
if (eax->keyCode == 0x10) // VK_SHIFT
if (nextKey->keyCode == 0x10) // VK_SHIFT
continue;
if (eax->keyCode == 0x11) // VK_CONTROL
if (nextKey->keyCode == 0x11) // VK_CONTROL
continue;
if ((_keyModifier & KeyModifier::cheat) != 0)
{
if (eax->charCode >= 'a' && eax->charCode <= 'z')
if (nextKey->charCode >= 'a' && nextKey->charCode <= 'z')
{
eax->charCode = toupper(eax->charCode);
nextKey->charCode = toupper(nextKey->charCode);
}
if (eax->charCode >= 'A' && eax->charCode <= 'Z')
if (nextKey->charCode >= 'A' && nextKey->charCode <= 'Z')
{
_cheatBuffer += eax->charCode;
_cheatBuffer += nextKey->charCode;
}
continue;
@ -283,10 +315,10 @@ namespace OpenLoco::Input
auto ti = WindowManager::find(WindowType::textInput);
if (ti != nullptr)
{
if (tryShortcut(Shortcut::screenshot, eax->keyCode, _keyModifier))
if (tryShortcut(Shortcut::screenshot, nextKey->keyCode, _keyModifier))
continue;
Ui::TextInput::sub_4CE910(eax->charCode, eax->keyCode);
Ui::Windows::TextInput::handleInput(nextKey->charCode, nextKey->keyCode);
continue;
}
@ -295,13 +327,10 @@ namespace OpenLoco::Input
ti = WindowManager::find(WindowType::fileBrowserPrompt);
if (ti != nullptr)
{
if (tryShortcut(Shortcut::screenshot, eax->keyCode, _keyModifier))
if (tryShortcut(Shortcut::screenshot, nextKey->keyCode, _keyModifier))
continue;
registers regs;
regs.eax = eax->charCode;
regs.ebx = eax->keyCode;
call(0x0044685C, regs);
Ui::Windows::PromptBrowse::handleInput(nextKey->charCode, nextKey->keyCode);
continue;
}
}
@ -311,21 +340,19 @@ namespace OpenLoco::Input
ti = WindowManager::find(WindowType::confirmationPrompt);
if (ti != nullptr)
{
registers regs;
regs.eax = eax->charCode;
regs.ebx = eax->keyCode;
call(0x0044685C, regs);
Ui::Windows::PromptOkCancel::handleInput(nextKey->charCode, nextKey->keyCode);
continue;
}
}
ti = WindowManager::find(WindowType::editKeyboardShortcut);
if (ti != nullptr)
{
editShortcut(eax);
editShortcut(nextKey);
continue;
}
if (Tutorial::state() == Tutorial::tutorial_state::playing)
if (Tutorial::state() == Tutorial::State::playing)
{
Tutorial::stop();
continue;
@ -335,46 +362,46 @@ namespace OpenLoco::Input
{
for (int i = 0; i < 35; i++)
{
if (tryShortcut((Shortcut)i, eax->keyCode, _keyModifier))
if (tryShortcut((Shortcut)i, nextKey->keyCode, _keyModifier))
break;
}
continue;
}
if (Intro::state() == (Intro::intro_state)9)
if (Intro::state() == (Intro::State)9)
{
Intro::state(Intro::intro_state::end);
Intro::state(Intro::State::end);
continue;
}
if (Intro::state() != Intro::intro_state::none)
if (Intro::state() != Intro::State::none)
{
Intro::state((Intro::intro_state)8);
Intro::state((Intro::State)8);
}
if (tryShortcut(Shortcut::sendMessage, eax->keyCode, _keyModifier))
if (tryShortcut(Shortcut::sendMessage, nextKey->keyCode, _keyModifier))
continue;
if (tryShortcut(Shortcut::screenshot, eax->keyCode, _keyModifier))
if (tryShortcut(Shortcut::screenshot, nextKey->keyCode, _keyModifier))
continue;
}
}
static void edgeScroll()
{
if (Tutorial::state() != Tutorial::tutorial_state::none)
if (Tutorial::state() != Tutorial::State::none)
return;
if (Config::get().edge_scrolling == 0)
return;
if (Input::state() != input_state::normal && Input::state() != input_state::dropdown_active)
if (Input::state() != State::normal && Input::state() != State::dropdownActive)
return;
if (hasKeyModifier(KeyModifier::shift) || hasKeyModifier(KeyModifier::control))
return;
Gfx::point_t delta = { 0, 0 };
Ui::Point delta = { 0, 0 };
auto cursor = getMouseLocation();
if (cursor.x == 0)
@ -407,12 +434,12 @@ namespace OpenLoco::Input
delta.y *= 1 << viewport->zoom;
main->viewport_configurations[0].saved_view_x += delta.x;
main->viewport_configurations[0].saved_view_y += delta.y;
Input::setFlag(input_flags::viewport_scrolling);
Input::setFlag(Flags::viewportScrolling);
}
static void keyScroll()
{
if (Tutorial::state() != Tutorial::tutorial_state::none)
if (Tutorial::state() != Tutorial::State::none)
return;
if (*_modalWindowType != WindowType::undefined)
@ -421,7 +448,7 @@ namespace OpenLoco::Input
if (WindowManager::find(WindowType::textInput) != nullptr)
return;
Gfx::point_t delta = { 0, 0 };
Ui::Point delta = { 0, 0 };
if (_keyboardState[DIK_LEFT] & 0x80)
delta.x -= 8;
@ -453,7 +480,7 @@ namespace OpenLoco::Input
delta.y *= 1 << viewport->zoom;
main->viewport_configurations[0].saved_view_x += delta.x;
main->viewport_configurations[0].saved_view_y += delta.y;
Input::setFlag(input_flags::viewport_scrolling);
Input::setFlag(Flags::viewportScrolling);
}
// 0x004BE92A

Some files were not shown because too many files have changed in this diff Show More