diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index c4bf40139b..8af0245668 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -50,7 +50,6 @@ Describe here Some things are not automated, and forgotten often. This list is a reminder for the reviewers. * The bug fix is important enough to be backported? (label: 'backport requested') * This PR touches english.txt or translations? Check the [guidelines](https://github.com/OpenTTD/OpenTTD/blob/master/docs/eints.md) -* This PR affects the save game format? (label 'savegame upgrade') * This PR affects the GS/AI API? (label 'needs review: Script API') * ai_changelog.hpp, game_changelog.hpp need updating. * The compatibility wrappers (compat_*.nut) need updating. diff --git a/.github/unused-strings.py b/.github/unused-strings.py index 1ebb652982..55e8ec1bfd 100644 --- a/.github/unused-strings.py +++ b/.github/unused-strings.py @@ -158,7 +158,7 @@ def scan_source_files(path, strings_found): # Most files we can just open, but some use magic, that requires the # G++ preprocessor before we can make sense out of it. if new_path == "src/table/cargo_const.h": - p = subprocess.run(["g++", "-E", new_path], stdout=subprocess.PIPE) + p = subprocess.run(["g++", "-E", "-DCHECK_UNUSED_STRINGS", new_path], stdout=subprocess.PIPE) output = p.stdout.decode() else: with open(new_path) as fp: diff --git a/README.md b/README.md index 96a3a6eb93..a0e50bdef8 100644 --- a/README.md +++ b/README.md @@ -37,12 +37,10 @@ Both 'stable' and 'nightly' versions are available for download: OpenTTD is also available for free on [Steam](https://store.steampowered.com/app/1536610/OpenTTD/), [GOG.com](https://www.gog.com/game/openttd), and the [Microsoft Store](https://www.microsoft.com/p/openttd-official/9ncjg5rvrr1c). On some platforms OpenTTD will be available via your OS package manager or a similar service. - ## 1.2) OpenTTD gameplay manual OpenTTD has a [community-maintained wiki](https://wiki.openttd.org/), including a gameplay manual and tips. - ## 1.3) Supported platforms OpenTTD has been ported to several platforms and operating systems. @@ -56,6 +54,7 @@ The currently supported platforms are: Other platforms may also work (in particular various BSD systems), but we don't actively test or maintain these. ### 1.3.1) Legacy support + Platforms, languages and compilers change. We'll keep support going on old platforms as long as someone is interested in supporting them, except where it means the project can't move forward to keep up with language and compiler features. @@ -72,7 +71,6 @@ For some platforms these will be downloaded during the installation process if r For some platforms, you will need to refer to [the installation guide](https://wiki.openttd.org/en/Manual/Installation). - ### 1.4.1) Free graphics and sound files The free data files, split into OpenGFX for graphics, OpenSFX for sounds and @@ -85,7 +83,6 @@ OpenMSX for music can be found at: Please follow the readme of these packages about the installation procedure. The Windows installer can optionally download and install these packages. - ### 1.4.2) Original Transport Tycoon Deluxe graphics and sound files If you want to play with the original Transport Tycoon Deluxe data files you have to copy the data files from the CD-ROM into the baseset/ directory. @@ -100,7 +97,6 @@ You need to copy the following files: - trgir.grf or TRGI.GRF - trgtr.grf or TRGT.GRF - ### 1.4.3) Original Transport Tycoon Deluxe music If you want the Transport Tycoon Deluxe music, copy the appropriate files from the original game into the baseset folder. @@ -108,7 +104,6 @@ If you want the Transport Tycoon Deluxe music, copy the appropriate files from t - TTD for DOS: The GM.CAT file - Transport Tycoon Original: The GM.CAT file, but rename it to GM-TTO.CAT - ## 1.5) Add-on content / mods OpenTTD features multiple types of add-on content, which modify gameplay in different ways. @@ -117,7 +112,6 @@ Most types of add-on content can be downloaded within OpenTTD via the 'Check Onl Add-on content can also be installed manually, but that's more complicated; the [OpenTTD wiki](https://wiki.openttd.org/) may offer help with that, or the [OpenTTD directory structure guide](./docs/directory_structure.md). - ### 1.5.1) Social Integration OpenTTD has the ability to load plugins to integrate with Social Platforms like Steam, Discord, etc. @@ -126,7 +120,6 @@ To enable such integration, the plugin for the specific platform has to be downl See [OpenTTD's website](https://www.openttd.org), under Downloads, for what plugins are available. - ### 1.6) OpenTTD directories OpenTTD uses its own directory structure to store game data, add-on content etc. @@ -137,7 +130,6 @@ For more information, see the [directory structure guide](./docs/directory_struc If you want to compile OpenTTD from source, instructions can be found in [COMPILING.md](./COMPILING.md). - ## 2.0) Contact and Community 'Official' channels @@ -160,12 +152,10 @@ You can play OpenTTD with others, either cooperatively or competitively. See the [multiplayer documentation](./docs/multiplayer.md) for more details. - ### 2.2) Contributing to OpenTTD We welcome contributors to OpenTTD. More information for contributors can be found in [CONTRIBUTING.md](./CONTRIBUTING.md) - ### 2.3) Reporting bugs Good bug reports are very helpful. We have a [guide to reporting bugs](./CONTRIBUTING.md#bug-reports) to help with this. @@ -173,12 +163,10 @@ Good bug reports are very helpful. We have a [guide to reporting bugs](./CONTRI Desyncs in multiplayer are complex to debug and report (some software development skils are required). Instructions can be found in [debugging and reporting desyncs](./docs/debugging_desyncs.md). - ### 2.4) Translating OpenTTD is translated into many languages. Translations are added and updated via the [online translation tool](https://translator.openttd.org). - ## 3.0) Licensing OpenTTD is licensed under the GNU General Public License version 2.0. @@ -215,6 +203,6 @@ See `src/3rdparty/openttd_social_integration_api/LICENSE` for the complete licen The atomic datatype support detection in `cmake/3rdparty/llvm/CheckAtomic.cmake` is licensed under the Apache 2.0 license. See `cmake/3rdparty/llvm/LICENSE.txt` for the complete license text. -## 4.0 Credits +## 4.0) Credits See [CREDITS.md](./CREDITS.md) diff --git a/bin/game/compat_14.nut b/bin/game/compat_14.nut index 00efae0b65..757e2496f4 100644 --- a/bin/game/compat_14.nut +++ b/bin/game/compat_14.nut @@ -9,6 +9,9 @@ GSBridge.GetBridgeID <- GSBridge.GetBridgeType; +/* Emulate old GSText parameter padding behaviour */ +GSText.SCRIPT_TEXT_MAX_PARAMETERS <- 20; + class GSCompat14 { function Text(text) { diff --git a/changelog.md b/changelog.md index 8ed27438a8..5dbd8bf0a4 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,108 @@ ## 15.x +### 15.0-beta2 (2025-04-13) + +- Feature: Snow-covered rocks are now visible (#13627) +- Feature: Generate more rocks on steep slopes during map generation or heightmap import (#13462) +- Feature: Prevent towns from upgrading individually-placed houses (#13270) +- Feature: [Win32] Touchpad two-finger map scrolling (#13172) +- Feature: NewGRF Badges (#13073) +- Add: [NewGRF] Add road-/tram-/rail-type variable 0x45 to get mutual road-/tram-/rail-type on same tile (#13934) +- Add: [Script] Newer Cargo Classes (#13779) +- Add: Show hyperlink destination tooltips in text window (#13742) +- Add: [Script] Saving/loading ScriptList (#13556) +- Add: Press Ctrl to build diagonal canals in game mode (#13432) +- Add: Sandbox settings to Sandbox Options window (#13268) +- Add: Setting to allow placing houses manually in-game (#13266) +- Add: [Script] Event for when a company's president name changes (#13208) +- Add: Ability to toggle visibility of station signs by facility (#13207) +- Add: [Script] ScriptEventCompanyRename (#12878) +- Add: Ukrainian Hryvnia currency (#12877) +- Add: Convert 32bpp-only sprites to 8bpp when needed (#11602) +- Change: [Script] Start GS (but don't run it) when generating world in scenario editor (#13961) +- Change: [Script] Add vehicle owner to crash event (#13878) +- Change: Make tree placement at world generation look more organic (#13515) +- Change: [MacOS] Put the icon in a rounded rectangle (#13446) +- Change: [Script] GetWaypointID to return the StationID of any waypoint (#13407) +- Change: Draw company manager face jacket after collar (#13390) +- Change: Don't distinguish between bus and truck stops when removing them (#13384) +- Change: [Script] Rename BridgeID to BridgeType in the script API (#13352) +- Change: Add fonts document to help window (#13305) +- Change: Log changes to sandbox settings (#13267) +- Change: When player joins network company, use its name instead of number in chat (#13263) +- Change: [Win32] Draw window title bar according to current Windows light/dark theme (#13196) +- Change: Restore wider spacers in main toolbars (#12039) +- Fix: NewGRF Global variables 0D, 0E and 1E refer to wrong GRFFile (#13986) +- Fix #13980: Allow diagonal selection for road convert (#13983) +- Fix: Validate raw strings from game-scripts, and strip invalid and control characters (#13976) +- Fix: Capitalise "Disabled" for the "maximum non-sticky open windows" setting (#13975) +- Fix: Frame widget with label had incorrect spacing (#13967) +- Fix: StringFilter included quotes in the search and failed (#13965) +- Fix #13955: Make graphs respect RTL (#13957) +- Fix: Numbers were left-aligned for RTL languages in several windows (#13959) +- Fix: MayHaveRoad claimed rail station tiles had road, so the custom stationspec index would be read as roadtype (#13949) +- Fix: [Script] Prevent cloning of API instances (#13947) +- Fix: Reference to the correct section of the README, if a graphics or a sound set is incomplete (#13946) +- Fix: Draw the bevel around the music track name as inset (#13935) +- Fix #13923: Padding in music GUI was asymmetric, so it looked different for LTR and RTL languages (#13933) +- Fix #13928: BuildOilRig did not properly set airport rotation (#13929) +- Fix: SDL2 application name hint was not effective (#13926) +- Fix #13921: [Win32] Don't try close an already closed event handle during destruction (#13924) +- Fix #13921: Don't reject MIDI files with a valid file magic value (#13924) +- Fix #13912: Multitile buildings break apart in house picker (#13914) +- Fix #13908: Require double click on order to change stop location (#13913) +- Fix #13910: Invalidate content of house picker window if language is changed (#13911) +- Fix: [Script] Reset instance when changing running scripts in scenario editor (#13906) +- Fix: [Script] Only run the gamescript GameLoop() in-game (#13896) +- Fix #13893: Reversed all x-axis labels for company related and industry production graphs in wallclock mode (#13894) +- Fix #13842: Close industry production graph if industry is removed (#13890) +- Fix #11528: Starting autorail dragging from existing track tiles resulted in adding non-continuous tracks (#13885) +- Fix: Autoreplace rail/road list only listed buildable types (#13887) +- Fix: [NewGRF] Display an error, if NewGRF reference out-of-bounds string parameters in gender/plural choices (#13881) +- Fix #13849: Settings in old saves could be overridden by defaults (#13874) +- Fix #13562: Removed cost estimation message from money cheat (#13857) +- Fix: [NewGRF] Plurals and genders did not work in strings with cases or substrings (#13853, #13852) +- Fix: [NewGRF] String parameter stack and case selection were not processed for control code 0x81 (#13851) +- Fix #13839: Incorrect colour of first company legend in smallmap window (#13841) +- Fix: i circumflex width in TrueType small font (#13836) +- Fix: Don't show owner of non-existent road (#13824) +- Fix: Error message window timeout doesn't match setting (#13812) +- Fix #13795: Crash in vehicle list of 32-bit platforms (#13796) +- Fix: [Script] Company rename event sometimes had the wrong name (#13794) +- Fix: Improve manager face randomisation (#13776) +- Fix #13740: [Script] Handle implicit orders for jump orders (#13753) +- Fix #13749: Default service intervals were not updated when changing timekeeping unit (#13751) +- Fix #13725: Use proper query strings for changing timetable values (#13737) +- Fix #11226: Don't draw story page elements that won't be visible (#13736) +- Fix: More AI than max_no_competitors could start with competitors_interval=0 (#13670) +- Fix: League table window ignored the minimal size in its widget description (#13629) +- Fix: Incorrect snow density when making rocks snowy (#13626) +- Fix: NewGRF vehicles display loading sprites when not actually loading or unloading (#13554) +- Fix #12925: Prevent cost estimates for settings changes (#13550) +- Fix: [Script] Report errors happening during 'Load()' (#13537) +- Fix: [Script] Improve type checking of parameters (#13522) +- Fix: [Script] Don't set CommandCallback for asynchronous commands (#13501) +- Fix: Missing error messages with sell- and autoreplace-all commands (#13469) +- Fix: Too many trees when generating trees at same height (#13460) +- Fix #12912: Company inaugurated year in wallclock mode was not saved (#13448) +- Fix: [Script] Wrong return value for failed preconditions Vehicle::CloneVehicle (#13445) +- Fix #13140: Scale initial industry production estimate by cargo scale (#13427) +- Fix #13384: Crash when remove bus/truck stop tool used on road waypoints (#13391) +- Fix #12987: Historical houses now always spawn completed (#13332) +- Fix: [Win32] Font detection didn't work for locales not supporting code pages (#13306) +- Fix: Restore ability to disable service interval (#13281) +- Fix: Hide company settings from console commands (#13269) +- Fix: Disable service interval widgets for non-owned vehicles (#13260) +- Fix #13225: Cargo payment graph key toggled wrong data sets (#13226) +- Fix: Rail station tile flags were not set early enough (#13203) +- Fix #13199: -f command line parameter does not need a value (#13200) +- Fix: Missing water region invalidation after flooding a half tile with rail in the highest corner (#13047) +- Fix: Strip control codes before sorting NewGRF names (#13034) +- Fix #12968: Added back ability to create unremovable houses (#12989) +- Remove: Drop support for UCS2/UTF-16 encoded scripts (#13992) +- Remove: Support for SDL1.2 (#13298) + + ### 15.0-beta1 (2024-12-24) - Feature: Town, industry and vehicle window zoom with mouse wheel (#12810, #12809, #12797) diff --git a/cmake/scripts/SquirrelExport.cmake b/cmake/scripts/SquirrelExport.cmake index 6549078844..8b9ca55621 100644 --- a/cmake/scripts/SquirrelExport.cmake +++ b/cmake/scripts/SquirrelExport.cmake @@ -17,41 +17,48 @@ if(NOT APILC) endif() macro(dump_fileheader) - get_filename_component(SCRIPT_API_FILE_NAME "${SCRIPT_API_FILE}" NAME) - string(APPEND SQUIRREL_EXPORT "\n#include \"../${SCRIPT_API_FILE_NAME}\"") + get_filename_component(SCRIPT_API_FILE_NAME "${SCRIPT_API_FILE}" NAME_WE) + string(APPEND SQUIRREL_EXPORT "\n#include \"../${SCRIPT_API_FILE_NAME}.hpp\"") if(NOT "${APIUC}" STREQUAL "Template") string(REPLACE "script_" "template_" SCRIPT_API_FILE_NAME "${SCRIPT_API_FILE_NAME}") - string(APPEND SQUIRREL_EXPORT "\n#include \"../template/${SCRIPT_API_FILE_NAME}.sq\"") + string(APPEND SQUIRREL_EXPORT "\n#include \"../template/${SCRIPT_API_FILE_NAME}.sq.hpp\"") + endif() +endmacro() + +macro(open_namespace) + if(NOT NAMESPACE_OPENED) + string(APPEND SQUIRREL_EXPORT "\nnamespace SQConvert {") + set(NAMESPACE_OPENED TRUE) endif() endmacro() macro(dump_class_templates NAME) string(REGEX REPLACE "^Script" "" REALNAME ${NAME}) - string(APPEND SQUIRREL_EXPORT "\n template <> struct Param<${NAME} *> { static inline ${NAME} *Get(HSQUIRRELVM vm, int index) { return static_cast<${NAME} *>(Squirrel::GetRealInstance(vm, index, \"${REALNAME}\")); } };") - string(APPEND SQUIRREL_EXPORT "\n template <> struct Param<${NAME} &> { static inline ${NAME} &Get(HSQUIRRELVM vm, int index) { return *static_cast<${NAME} *>(Squirrel::GetRealInstance(vm, index, \"${REALNAME}\")); } };") - string(APPEND SQUIRREL_EXPORT "\n template <> struct Param { static inline const ${NAME} *Get(HSQUIRRELVM vm, int index) { return static_cast<${NAME} *>(Squirrel::GetRealInstance(vm, index, \"${REALNAME}\")); } };") - string(APPEND SQUIRREL_EXPORT "\n template <> struct Param { static inline const ${NAME} &Get(HSQUIRRELVM vm, int index) { return *static_cast<${NAME} *>(Squirrel::GetRealInstance(vm, index, \"${REALNAME}\")); } };") + string(APPEND SQUIRREL_EXPORT "\n\ttemplate <> struct Param<${NAME} *> { static inline ${NAME} *Get(HSQUIRRELVM vm, int index) { return static_cast<${NAME} *>(Squirrel::GetRealInstance(vm, index, \"${REALNAME}\")); } };") + string(APPEND SQUIRREL_EXPORT "\n\ttemplate <> struct Param<${NAME} &> { static inline ${NAME} &Get(HSQUIRRELVM vm, int index) { return *static_cast<${NAME} *>(Squirrel::GetRealInstance(vm, index, \"${REALNAME}\")); } };") + string(APPEND SQUIRREL_EXPORT "\n\ttemplate <> struct Param { static inline const ${NAME} *Get(HSQUIRRELVM vm, int index) { return static_cast<${NAME} *>(Squirrel::GetRealInstance(vm, index, \"${REALNAME}\")); } };") + string(APPEND SQUIRREL_EXPORT "\n\ttemplate <> struct Param { static inline const ${NAME} &Get(HSQUIRRELVM vm, int index) { return *static_cast<${NAME} *>(Squirrel::GetRealInstance(vm, index, \"${REALNAME}\")); } };") if("${NAME}" STREQUAL "ScriptEvent") - string(APPEND SQUIRREL_EXPORT "\n template <> struct Return<${NAME} *> { static inline int Set(HSQUIRRELVM vm, ${NAME} *res) { if (res == nullptr) { sq_pushnull(vm); return 1; } Squirrel::CreateClassInstanceVM(vm, \"${REALNAME}\", res, nullptr, DefSQDestructorCallback<${NAME}>, true); return 1; } };") + string(APPEND SQUIRREL_EXPORT "\n\ttemplate <> struct Return<${NAME} *> { static inline int Set(HSQUIRRELVM vm, ${NAME} *res) { if (res == nullptr) { sq_pushnull(vm); return 1; } Squirrel::CreateClassInstanceVM(vm, \"${REALNAME}\", res, nullptr, DefSQDestructorCallback<${NAME}>, true); return 1; } };") elseif("${NAME}" STREQUAL "ScriptText") string(APPEND SQUIRREL_EXPORT "\n") - string(APPEND SQUIRREL_EXPORT "\n template <> struct Param {") - string(APPEND SQUIRREL_EXPORT "\n static inline Text *Get(HSQUIRRELVM vm, int index) {") - string(APPEND SQUIRREL_EXPORT "\n if (sq_gettype(vm, index) == OT_INSTANCE) {") - string(APPEND SQUIRREL_EXPORT "\n return Param::Get(vm, index);") - string(APPEND SQUIRREL_EXPORT "\n }") - string(APPEND SQUIRREL_EXPORT "\n if (sq_gettype(vm, index) == OT_STRING) {") - string(APPEND SQUIRREL_EXPORT "\n return new RawText(Param::Get(vm, index));") - string(APPEND SQUIRREL_EXPORT "\n }") - string(APPEND SQUIRREL_EXPORT "\n if (sq_gettype(vm, index) == OT_NULL) {") - string(APPEND SQUIRREL_EXPORT "\n return nullptr;") - string(APPEND SQUIRREL_EXPORT "\n }") - string(APPEND SQUIRREL_EXPORT "\n throw sq_throwerror(vm, fmt::format(\"parameter {} has an invalid type ; expected: 'Text'\", index - 1));") - string(APPEND SQUIRREL_EXPORT "\n }") - string(APPEND SQUIRREL_EXPORT "\n };") + string(APPEND SQUIRREL_EXPORT "\n\ttemplate <> struct Param {") + string(APPEND SQUIRREL_EXPORT "\n\t\tstatic inline Text *Get(HSQUIRRELVM vm, int index) {") + string(APPEND SQUIRREL_EXPORT "\n\t\t\tif (sq_gettype(vm, index) == OT_INSTANCE) {") + string(APPEND SQUIRREL_EXPORT "\n\t\t\t\treturn Param::Get(vm, index);") + string(APPEND SQUIRREL_EXPORT "\n\t\t\t}") + string(APPEND SQUIRREL_EXPORT "\n\t\t\tif (sq_gettype(vm, index) == OT_STRING) {") + string(APPEND SQUIRREL_EXPORT "\n\t\t\t\treturn new RawText(Param::Get(vm, index));") + string(APPEND SQUIRREL_EXPORT "\n\t\t\t}") + string(APPEND SQUIRREL_EXPORT "\n\t\t\tif (sq_gettype(vm, index) == OT_NULL) {") + string(APPEND SQUIRREL_EXPORT "\n\t\t\t\treturn nullptr;") + string(APPEND SQUIRREL_EXPORT "\n\t\t\t}") + string(APPEND SQUIRREL_EXPORT "\n\t\t\tthrow sq_throwerror(vm, fmt::format(\"parameter {} has an invalid type ; expected: 'Text'\", index - 1));") + string(APPEND SQUIRREL_EXPORT "\n\t\t}") + string(APPEND SQUIRREL_EXPORT "\n\t};") else() - string(APPEND SQUIRREL_EXPORT "\n template <> struct Return<${NAME} *> { static inline int Set(HSQUIRRELVM vm, ${NAME} *res) { if (res == nullptr) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, \"${REALNAME}\", res, nullptr, DefSQDestructorCallback<${NAME}>, true); return 1; } };") + string(APPEND SQUIRREL_EXPORT "\n\ttemplate <> struct Return<${NAME} *> { static inline int Set(HSQUIRRELVM vm, ${NAME} *res) { if (res == nullptr) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, \"${REALNAME}\", res, nullptr, DefSQDestructorCallback<${NAME}>, true); return 1; } };") endif() endmacro() @@ -66,7 +73,6 @@ macro(reset_reader) unset(STATIC_METHODS) unset(CLS) unset(START_SQUIRREL_DEFINE_ON_NEXT_LINE) - set(CLS_LEVEL 0) unset(CLS_IN_API) endmacro() @@ -75,6 +81,9 @@ reset_reader() file(STRINGS "${SCRIPT_API_FILE}" SOURCE_LINES) set(NUM_LINE 0) +set(CLS_LEVEL 0) +set(BRACE_LEVEL 0) + macro(doxygen_check) if(NOT "${DOXYGEN_SKIP}" STREQUAL "") message(FATAL_ERROR "${SCRIPT_API_FILE}:${NUM_LINE}: a DOXYGEN_API block was not properly closed") @@ -110,7 +119,7 @@ foreach(LINE IN LISTS SOURCE_LINES) continue() endif() - if("${LINE}" MATCHES "^([ ]*)\\* @api (.*)$") + if("${LINE}" MATCHES "^([\t ]*)\\* @api (.*)$") set(LINE ${CMAKE_MATCH_2}) # By default, classes are not selected if(NOT CLS_LEVEL) @@ -148,22 +157,28 @@ foreach(LINE IN LISTS SOURCE_LINES) continue() endif() + # Count braces to skip function bodies + string(REGEX REPLACE "[^{]" "" OPENING_BRACES "${LINE}") + string(LENGTH "${OPENING_BRACES}" OPENING_BRACES) + string(REGEX REPLACE "[^}]" "" CLOSING_BRACES "${LINE}") + string(LENGTH "${CLOSING_BRACES}" CLOSING_BRACES) + math(EXPR BRACE_LEVEL "${BRACE_LEVEL} + ${OPENING_BRACES} - ${CLOSING_BRACES}") + # Ignore forward declarations of classes - if("${LINE}" MATCHES "^( *)class(.*);") + if("${LINE}" MATCHES "^(\t*)class(.*);") continue() endif() # We only want to have public functions exported for now - if("${LINE}" MATCHES "^( *)class (.*) (: public|: protected|: private|:) ([^ ]*)") + if("${LINE}" MATCHES "^(\t*)class (.*) (: public|: protected|: private|:) ([^ ]*)") if(NOT CLS_LEVEL) if(NOT DEFINED API_SELECTED) - message(WARNING "Class '${CMAKE_MATCH_2}' has no @api. It won't be published to any API.") + message(WARNING "${SCRIPT_API_FILE}:${NUM_LINE}: Class '${CMAKE_MATCH_2}' has no @api. It won't be published to any API.") set(API_SELECTED FALSE) endif() unset(IS_PUBLIC) - unset(CLS_PARAM_0) - set(CLS_PARAM_1 1) - set(CLS_PARAM_2 "x") + unset(CLS_PARAMS) + set(CLS_TYPES "x") set(CLS_IN_API ${API_SELECTED}) unset(API_SELECTED) set(CLS "${CMAKE_MATCH_2}") @@ -181,19 +196,19 @@ foreach(LINE IN LISTS SOURCE_LINES) math(EXPR CLS_LEVEL "${CLS_LEVEL} + 1") continue() endif() - if("${LINE}" MATCHES "^( *)public") + if("${LINE}" MATCHES "^(\t*)public") if(CLS_LEVEL EQUAL 1) set(IS_PUBLIC TRUE) endif() continue() endif() - if("${LINE}" MATCHES "^( *)protected") + if("${LINE}" MATCHES "^(\t*)protected") if(CLS_LEVEL EQUAL 1) unset(IS_PUBLIC) endif() continue() endif() - if("${LINE}" MATCHES "^( *)private") + if("${LINE}" MATCHES "^(\t*)private") if(CLS_LEVEL EQUAL 1) unset(IS_PUBLIC) endif() @@ -221,7 +236,7 @@ foreach(LINE IN LISTS SOURCE_LINES) endif() # We need to make specialized conversions for structs - if("${LINE}" MATCHES "^( *)struct ([^ ]*)") + if("${LINE}" MATCHES "^(\t*)struct ([^ ]*)") math(EXPR CLS_LEVEL "${CLS_LEVEL} + 1") # Check if we want to publish this struct @@ -243,7 +258,7 @@ foreach(LINE IN LISTS SOURCE_LINES) endif() # We need to make specialized conversions for enums - if("${LINE}" MATCHES "^( *)enum ([^ ]*)") + if("${LINE}" MATCHES "^(\t*)enum ([^ ]*)") math(EXPR CLS_LEVEL "${CLS_LEVEL} + 1") # Check if we want to publish this enum @@ -266,7 +281,7 @@ foreach(LINE IN LISTS SOURCE_LINES) endif() # Maybe the end of the class, if so we can start with the Squirrel export pretty soon - if("${LINE}" MATCHES "};") + if(BRACE_LEVEL LESS CLS_LEVEL) math(EXPR CLS_LEVEL "${CLS_LEVEL} - 1") if(CLS_LEVEL) unset(IN_ENUM) @@ -280,7 +295,7 @@ foreach(LINE IN LISTS SOURCE_LINES) endif() # Empty/white lines. When we may do the Squirrel export, do that export. - if("${LINE}" MATCHES "^([ ]*)$") + if("${LINE}" MATCHES "^([ \t]*)$") if(NOT START_SQUIRREL_DEFINE_ON_NEXT_LINE) continue() endif() @@ -304,33 +319,17 @@ foreach(LINE IN LISTS SOURCE_LINES) string(APPEND SQUIRREL_EXPORT "\n") if("${APIUC}" STREQUAL "Template") - # First check whether we have enums to print - if(DEFINED ENUMS) - if(NOT NAMESPACE_OPENED) - string(APPEND SQUIRREL_EXPORT "\nnamespace SQConvert {") - set(NAMESPACE_OPENED TRUE) - endif() - endif() - # Then check whether we have structs/classes to print if(DEFINED STRUCTS) - if(NOT NAMESPACE_OPENED) - string(APPEND SQUIRREL_EXPORT "\nnamespace SQConvert {") - set(NAMESPACE_OPENED TRUE) - endif() - string(APPEND SQUIRREL_EXPORT "\n /* Allow inner classes/structs to be used as Squirrel parameters */") + open_namespace() + string(APPEND SQUIRREL_EXPORT "\n\t/* Allow inner classes/structs to be used as Squirrel parameters */") foreach(STRUCT IN LISTS STRUCTS) dump_class_templates(${STRUCT}) endforeach() endif() - if(NOT NAMESPACE_OPENED) - string(APPEND SQUIRREL_EXPORT "\nnamespace SQConvert {") - set(NAMESPACE_OPENED TRUE) - else() - string(APPEND SQUIRREL_EXPORT "\n") - endif() - string(APPEND SQUIRREL_EXPORT "\n /* Allow ${CLS} to be used as Squirrel parameter */") + open_namespace() + string(APPEND SQUIRREL_EXPORT "\n\t/* Allow ${CLS} to be used as Squirrel parameter */") dump_class_templates(${CLS}) string(APPEND SQUIRREL_EXPORT "\n} // namespace SQConvert") @@ -340,23 +339,23 @@ foreach(LINE IN LISTS SOURCE_LINES) endif() string(APPEND SQUIRREL_EXPORT "\n") - string(APPEND SQUIRREL_EXPORT "\ntemplate <> SQInteger PushClassName<${CLS}, ScriptType::${APIUC}>(HSQUIRRELVM vm) { sq_pushstring(vm, \"${API_CLS}\", -1); return 1; }") + string(APPEND SQUIRREL_EXPORT "\ntemplate <> SQInteger PushClassName<${CLS}, ScriptType::${APIUC}>(HSQUIRRELVM vm) { sq_pushstring(vm, \"${API_CLS}\"); return 1; }") string(APPEND SQUIRREL_EXPORT "\n") # Then do the registration functions of the class. - string(APPEND SQUIRREL_EXPORT "\nvoid SQ${API_CLS}_Register(Squirrel *engine)") + string(APPEND SQUIRREL_EXPORT "\nvoid SQ${API_CLS}_Register(Squirrel &engine)") string(APPEND SQUIRREL_EXPORT "\n{") - string(APPEND SQUIRREL_EXPORT "\n DefSQClass<${CLS}, ScriptType::${APIUC}> SQ${API_CLS}(\"${API_CLS}\");") + string(APPEND SQUIRREL_EXPORT "\n\tDefSQClass<${CLS}, ScriptType::${APIUC}> SQ${API_CLS}(\"${API_CLS}\");") if("${SUPER_CLS}" STREQUAL "Text") - string(APPEND SQUIRREL_EXPORT "\n SQ${API_CLS}.PreRegister(engine);") + string(APPEND SQUIRREL_EXPORT "\n\tSQ${API_CLS}.PreRegister(engine);") else() - string(APPEND SQUIRREL_EXPORT "\n SQ${API_CLS}.PreRegister(engine, \"${API_SUPER_CLS}\");") + string(APPEND SQUIRREL_EXPORT "\n\tSQ${API_CLS}.PreRegister(engine, \"${API_SUPER_CLS}\");") endif() - if(NOT "${SUPER_CLS}" MATCHES "^ScriptEvent") - if("${CLS_PARAM_2}" STREQUAL "v") - string(APPEND SQUIRREL_EXPORT "\n SQ${API_CLS}.AddSQAdvancedConstructor(engine);") + if((DEFINED CLS_PARAMS OR DEFINED METHODS) AND NOT "${SUPER_CLS}" MATCHES "^ScriptEvent" AND NOT "${CLS}" STREQUAL "ScriptEvent") + if("${CLS_TYPES}" STREQUAL "v") + string(APPEND SQUIRREL_EXPORT "\n\tSQ${API_CLS}.AddSQAdvancedConstructor(engine);") else() - string(APPEND SQUIRREL_EXPORT "\n SQ${API_CLS}.AddConstructor(engine, \"${CLS_PARAM_2}\");") + string(APPEND SQUIRREL_EXPORT "\n\tSQ${API_CLS}.AddConstructor(engine, \"${CLS_TYPES}\");") endif() endif() string(APPEND SQUIRREL_EXPORT "\n") @@ -376,7 +375,7 @@ foreach(LINE IN LISTS SOURCE_LINES) foreach(i RANGE ${LEN}) string(APPEND SPACES " ") endforeach() - string(APPEND SQUIRREL_EXPORT "\n SQ${API_CLS}.DefSQConst(engine, ${CLS}::${ENUM_VALUE},${SPACES}\"${ENUM_VALUE}\");") + string(APPEND SQUIRREL_EXPORT "\n\tSQ${API_CLS}.DefSQConst(engine, ${CLS}::${ENUM_VALUE},${SPACES}\"${ENUM_VALUE}\");") endforeach() if(MLEN) string(APPEND SQUIRREL_EXPORT "\n") @@ -397,7 +396,7 @@ foreach(LINE IN LISTS SOURCE_LINES) foreach(i RANGE ${LEN}) string(APPEND SPACES " ") endforeach() - string(APPEND SQUIRREL_EXPORT "\n SQ${API_CLS}.DefSQConst(engine, ${CLS}::${CONST_VALUE},${SPACES}\"${CONST_VALUE}\");") + string(APPEND SQUIRREL_EXPORT "\n\tSQ${API_CLS}.DefSQConst(engine, ${CLS}::${CONST_VALUE},${SPACES}\"${CONST_VALUE}\");") endforeach() if(MLEN) string(APPEND SQUIRREL_EXPORT "\n") @@ -423,7 +422,7 @@ foreach(LINE IN LISTS SOURCE_LINES) foreach(i RANGE ${LEN}) string(APPEND SPACES " ") endforeach() - string(APPEND SQUIRREL_EXPORT "\n ScriptError::RegisterErrorMap(${ENUM_STRING},${SPACES}${CLS}::${ENUM_ERROR});") + string(APPEND SQUIRREL_EXPORT "\n\tScriptError::RegisterErrorMap(${ENUM_STRING},${SPACES}${CLS}::${ENUM_ERROR});") endforeach() if(MLEN) string(APPEND SQUIRREL_EXPORT "\n") @@ -444,7 +443,7 @@ foreach(LINE IN LISTS SOURCE_LINES) foreach(i RANGE ${LEN}) string(APPEND SPACES " ") endforeach() - string(APPEND SQUIRREL_EXPORT "\n ScriptError::RegisterErrorMapString(${CLS}::${ENUM_ERROR_TO_STRING},${SPACES}\"${ENUM_ERROR_TO_STRING}\");") + string(APPEND SQUIRREL_EXPORT "\n\tScriptError::RegisterErrorMapString(${CLS}::${ENUM_ERROR_TO_STRING},${SPACES}\"${ENUM_ERROR_TO_STRING}\");") endforeach() if(MLEN) string(APPEND SQUIRREL_EXPORT "\n") @@ -463,8 +462,7 @@ foreach(LINE IN LISTS SOURCE_LINES) foreach(STATIC_METHOD IN LISTS STATIC_METHODS) string(REPLACE ":" ";" STATIC_METHOD "${STATIC_METHOD}") list(GET STATIC_METHOD 0 FUNCNAME) - list(GET STATIC_METHOD 1 ARGC) - list(GET STATIC_METHOD 2 TYPES) + list(GET STATIC_METHOD 1 TYPES) string(LENGTH "${FUNCNAME}" LEN) math(EXPR LEN "${MLEN} - ${LEN}") if("${TYPES}" STREQUAL "v") @@ -479,9 +477,9 @@ foreach(LINE IN LISTS SOURCE_LINES) string(APPEND SPACES " ") endforeach() if("${TYPES}" STREQUAL "v") - string(APPEND SQUIRREL_EXPORT "\n SQ${API_CLS}.DefSQAdvancedStaticMethod(engine, &${CLS}::${FUNCNAME},${SPACES}\"${FUNCNAME}\");") + string(APPEND SQUIRREL_EXPORT "\n\tSQ${API_CLS}.DefSQAdvancedStaticMethod(engine, &${CLS}::${FUNCNAME},${SPACES}\"${FUNCNAME}\");") else() - string(APPEND SQUIRREL_EXPORT "\n SQ${API_CLS}.DefSQStaticMethod(engine, &${CLS}::${FUNCNAME},${SPACES}\"${FUNCNAME}\",${SPACES}${ARGC}, \"${TYPES}\");") + string(APPEND SQUIRREL_EXPORT "\n\tSQ${API_CLS}.DefSQStaticMethod(engine, &${CLS}::${FUNCNAME},${SPACES}\"${FUNCNAME}\",${SPACES}\"${TYPES}\");") endif() endforeach() if(MLEN) @@ -501,8 +499,7 @@ foreach(LINE IN LISTS SOURCE_LINES) foreach(METHOD IN LISTS METHODS) string(REPLACE ":" ";" METHOD "${METHOD}") list(GET METHOD 0 FUNCNAME) - list(GET METHOD 1 ARGC) - list(GET METHOD 2 TYPES) + list(GET METHOD 1 TYPES) string(LENGTH "${FUNCNAME}" LEN) math(EXPR LEN "${MLEN} - ${LEN}") if("${TYPES}" STREQUAL "v") @@ -517,16 +514,16 @@ foreach(LINE IN LISTS SOURCE_LINES) string(APPEND SPACES " ") endforeach() if("${TYPES}" STREQUAL "v") - string(APPEND SQUIRREL_EXPORT "\n SQ${API_CLS}.DefSQAdvancedMethod(engine, &${CLS}::${FUNCNAME},${SPACES}\"${FUNCNAME}\");") + string(APPEND SQUIRREL_EXPORT "\n\tSQ${API_CLS}.DefSQAdvancedMethod(engine, &${CLS}::${FUNCNAME},${SPACES}\"${FUNCNAME}\");") else() - string(APPEND SQUIRREL_EXPORT "\n SQ${API_CLS}.DefSQMethod(engine, &${CLS}::${FUNCNAME},${SPACES}\"${FUNCNAME}\",${SPACES}${ARGC}, \"${TYPES}\");") + string(APPEND SQUIRREL_EXPORT "\n\tSQ${API_CLS}.DefSQMethod(engine, &${CLS}::${FUNCNAME},${SPACES}\"${FUNCNAME}\",${SPACES}\"${TYPES}\");") endif() endforeach() if(MLEN) string(APPEND SQUIRREL_EXPORT "\n") endif() - string(APPEND SQUIRREL_EXPORT "\n SQ${API_CLS}.PostRegister(engine);") + string(APPEND SQUIRREL_EXPORT "\n\tSQ${API_CLS}.PostRegister(engine);") string(APPEND SQUIRREL_EXPORT "\n}") reset_reader() @@ -539,9 +536,13 @@ foreach(LINE IN LISTS SOURCE_LINES) continue() endif() + if(NOT BRACE_LEVEL EQUAL CLS_LEVEL) + continue() + endif() + # Add enums if(IN_ENUM) - string(REGEX MATCH "([^, ]+)" ENUM_VALUE "${LINE}") + string(REGEX MATCH "([^,\t ]+)" ENUM_VALUE "${LINE}") list(APPEND ENUM_VALUES "${ENUM_VALUE}") # Check if this a special error enum @@ -549,12 +550,12 @@ foreach(LINE IN LISTS SOURCE_LINES) if("${ENUM}" MATCHES ".*::ErrorMessages") # syntax: # enum ErrorMessages { - # ERR_SOME_ERROR, // [STR_ITEM1, STR_ITEM2, ...] + #\tERR_SOME_ERROR,\t// [STR_ITEM1, STR_ITEM2, ...] # } # Set the mappings if("${LINE}" MATCHES "\\[(.*)\\]") - string(REGEX REPLACE "[ ]" "" MAPPINGS "${CMAKE_MATCH_1}") + string(REGEX REPLACE "[ \t]" "" MAPPINGS "${CMAKE_MATCH_1}") string(REPLACE "," ";" MAPPINGS "${MAPPINGS}") foreach(MAPPING IN LISTS MAPPINGS) @@ -568,11 +569,11 @@ foreach(LINE IN LISTS SOURCE_LINES) endif() # Add a const (non-enum) value - if("${LINE}" MATCHES "^[ ]*static const [^ ]+ ([^ ]+) = -?\\(?[^ ]*\\)?[^ ]+;") + if("${LINE}" MATCHES "^[ \t]*static const [^ ]+ ([^ ]+) = -?\\(?[^ ]*\\)?[^ ]+;") list(APPEND CONST_VALUES "${CMAKE_MATCH_1}") continue() endif() - if("${LINE}" MATCHES "^[ ]*static constexpr [^ ]+ ([^ ]+) = -?\\(?[^ ]*\\)?[^ ]+;") + if("${LINE}" MATCHES "^[ \t]*static constexpr [^ ]+ ([^ ]+) = -?\\(?[^ ]*\\)?[^ ]+;") list(APPEND CONST_VALUES "${CMAKE_MATCH_1}") continue() endif() @@ -584,41 +585,43 @@ foreach(LINE IN LISTS SOURCE_LINES) endif() if("${LINE}" MATCHES "~") if(DEFINED API_SELECTED) - message(WARNING "Destructor for '${CLS}' has @api. Tag ignored.") + message(WARNING "${SCRIPT_API_FILE}:${NUM_LINE}: Destructor for '${CLS}' has @api. Tag ignored.") unset(API_SELECTED) endif() continue() endif() unset(IS_STATIC) - if("${LINE}" MATCHES "static") + if("${LINE}" MATCHES "static ") set(IS_STATIC TRUE) endif() - string(REGEX REPLACE "(virtual|static|const)[ ]+" "" LINE "${LINE}") + string(REGEX REPLACE "(virtual|static|const)[ \t]+" "" LINE "${LINE}") string(REGEX REPLACE "{.*" "" LINE "${LINE}") set(PARAM_S "${LINE}") string(REGEX REPLACE "\\*" "" LINE "${LINE}") string(REGEX REPLACE "\\(.*" "" LINE "${LINE}") - string(REGEX REPLACE ".*\\(" "" PARAM_S "${PARAM_S}") + # Parameters start at first "(". Further "(" will appear in ctor lists. + string(REGEX MATCH "\\(.*" PARAM_S "${PARAM_S}") string(REGEX REPLACE "\\).*" "" PARAM_S "${PARAM_S}") + string(REGEX REPLACE "^\\(" "" PARAM_S "${PARAM_S}") - string(REGEX MATCH "([^ ]+)( ([^ ]+))?" RESULT "${LINE}") + string(REGEX MATCH "([^ \t]+)( ([^ ]+))?" RESULT "${LINE}") set(FUNCTYPE "${CMAKE_MATCH_1}") set(FUNCNAME "${CMAKE_MATCH_3}") if("${FUNCTYPE}" STREQUAL "${CLS}" AND NOT FUNCNAME) if(DEFINED API_SELECTED) - message(WARNING "Constructor for '${CLS}' has @api. Tag ignored.") + message(WARNING "${SCRIPT_API_FILE}:${NUM_LINE}: Constructor for '${CLS}' has @api. Tag ignored.") unset(API_SELECTED) endif() - set(CLS_PARAM_0 "${PARAM_S}") + set(CLS_PARAMS "${PARAM_S}") if(NOT PARAM_S) continue() endif() elseif(NOT FUNCNAME) continue() - endif() + endif() string(REPLACE "," ";" PARAMS "${PARAM_S}") if(IS_STATIC) @@ -627,9 +630,7 @@ foreach(LINE IN LISTS SOURCE_LINES) set(TYPES "x") endif() - set(LEN 1) foreach(PARAM IN LISTS PARAMS) - math(EXPR LEN "${LEN} + 1") string(STRIP "${PARAM}" PARAM) if("${PARAM}" MATCHES "\\*|&") if("${PARAM}" MATCHES "^char") @@ -668,13 +669,12 @@ foreach(LINE IN LISTS SOURCE_LINES) unset(API_SELECTED) if("${FUNCTYPE}" STREQUAL "${CLS}" AND NOT FUNCNAME) - set(CLS_PARAM_1 ${LEN}) - set(CLS_PARAM_2 "${TYPES}") + set(CLS_TYPES "${TYPES}") elseif("${FUNCNAME}" MATCHES "^_" AND NOT "${TYPES}" STREQUAL "v") elseif(IS_STATIC) - list(APPEND STATIC_METHODS "${FUNCNAME}:${LEN}:${TYPES}") + list(APPEND STATIC_METHODS "${FUNCNAME}:${TYPES}") else() - list(APPEND METHODS "${FUNCNAME}:${LEN}:${TYPES}") + list(APPEND METHODS "${FUNCNAME}:${TYPES}") endif() continue() endif() diff --git a/cmake/scripts/SquirrelIncludes.cmake b/cmake/scripts/SquirrelIncludes.cmake index d6022f73dd..5e1cab355b 100644 --- a/cmake/scripts/SquirrelIncludes.cmake +++ b/cmake/scripts/SquirrelIncludes.cmake @@ -19,7 +19,7 @@ endif() file(READ "${API_FILES}" SCRIPT_API_BINARY_FILES) foreach(FILE IN LISTS SCRIPT_API_BINARY_FILES) - file(STRINGS "${FILE}" LINES REGEX "^void SQ${APIUC}.*_Register\\(Squirrel \\*engine\\)$") + file(STRINGS "${FILE}" LINES REGEX "^void SQ${APIUC}.*_Register\\(Squirrel &engine\\)$") if(LINES) string(REGEX REPLACE ".*api/${APILC}/(.*)" "#include \"\\1\"" FILE "${FILE}") list(APPEND SQUIRREL_INCLUDES "${FILE}") @@ -28,7 +28,7 @@ foreach(FILE IN LISTS SCRIPT_API_BINARY_FILES) continue() endif() string(REGEX REPLACE "^.*void " " " LINE "${LINE}") - string(REGEX REPLACE "Squirrel \\*" "" LINE "${LINE}") + string(REGEX REPLACE "Squirrel &" "" LINE "${LINE}") list(APPEND SQUIRREL_REGISTER "${LINE}") endforeach() endif() diff --git a/docs/obg_format.txt b/docs/obg_format.txt index f43ea7edd7..2aa9bec0d2 100644 --- a/docs/obg_format.txt +++ b/docs/obg_format.txt @@ -16,8 +16,11 @@ ; - `openttd -I ` starts OpenTTD with the given set (case sensitive) ; - adding `graphicsset = ` to the misc section of openttd.cfg makes ; OpenTTD start with that graphics set by default -; - there is a command line tool for all platforms called md5sum that can -; create the MD5 checksum you need. +; - `grfid -m` can give the GRF file MD5 checksums that you need +; - The `--md5` output option for `nmlc` can also give the MD5 if you are +; encoding from an nml source +; - Simple file MD5 checksums, eg. using `md5sum` are not correct for grf +; container versions other than 1 ; - all files specified in this file are search relatively to the path where ; this file is found, i.e. if the graphics files are in a subdir you have ; to add that subdir to the names in this file to! It will NOT search for @@ -44,6 +47,8 @@ description.en_US = howdie palette = DOS ; preferred blitter, optional; either 8bpp (default) or 32bpp. blitter = 8bpp +; url, optional +url = https://github.com/my/baseset ; The files section lists the files that replace sprites. ; The file names are case sensitive. diff --git a/known-bugs.md b/known-bugs.md index b50fff766d..64c9628025 100644 --- a/known-bugs.md +++ b/known-bugs.md @@ -2,8 +2,8 @@ ## Table of contents -- 1.0) About -- 2.0) Known bugs +- 1.0) [About](#10-about) +- 2.0) [Known bugs](#20-known-bugs) ## 1.0) About @@ -12,7 +12,7 @@ that are the same as these. If you do, do not act surprised, because we WILL flame you! The current list of known bugs that we intend to fix can be found in our -bug tracking system at https://github.com/OpenTTD/OpenTTD/issues +bug tracking system at [https://github.com/OpenTTD/OpenTTD/issues](https://github.com/OpenTTD/OpenTTD/issues) Also check the closed bugs when searching for your bug in this system as we might have fixed the bug in the mean time. diff --git a/media/baseset/openttd.grf b/media/baseset/openttd.grf index 540ddf3fe9..bca7c61448 100644 Binary files a/media/baseset/openttd.grf and b/media/baseset/openttd.grf differ diff --git a/media/baseset/openttd.grf.hash b/media/baseset/openttd.grf.hash index b0a772e8d0..90176c9546 100644 --- a/media/baseset/openttd.grf.hash +++ b/media/baseset/openttd.grf.hash @@ -1 +1 @@ -019dba4830a64ee4345d3d647633e1da +a4a727b03a7cd07ee0499231f7f233f4 diff --git a/media/baseset/openttd/chars.nfo b/media/baseset/openttd/chars.nfo index 8d1acb814f..dbacd2934c 100644 --- a/media/baseset/openttd/chars.nfo +++ b/media/baseset/openttd/chars.nfo @@ -840,3 +840,7 @@ -1 sprites/chars.png 8bpp 630 400 6 21 0 -2 normal -1 sprites/mono.png 8bpp 325 270 7 13 0 0 normal -1 sprites/mono.png 8bpp 340 270 7 13 0 0 normal + +// U+E29D: Small left arrow + -1 * 6 12 01 01 01 9D E2 + -1 sprites/chars.png 8bpp 10 430 5 5 0 1 normal diff --git a/media/baseset/openttd/chars.png b/media/baseset/openttd/chars.png index 34a4a67c5d..bb1484729e 100644 Binary files a/media/baseset/openttd/chars.png and b/media/baseset/openttd/chars.png differ diff --git a/media/baseset/openttd/openttdgui.nfo b/media/baseset/openttd/openttdgui.nfo index 2fd5a5bb4c..b6466f98b6 100644 --- a/media/baseset/openttd/openttdgui.nfo +++ b/media/baseset/openttd/openttdgui.nfo @@ -4,7 +4,7 @@ // See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . // -1 * 0 0C "OpenTTD GUI graphics" - -1 * 3 05 15 \b 191 // OPENTTD_SPRITE_COUNT + -1 * 3 05 15 \b 192 // OPENTTD_SPRITE_COUNT -1 sprites/openttdgui.png 8bpp 66 8 64 31 -31 7 normal -1 sprites/openttdgui.png 8bpp 146 8 64 31 -31 7 normal -1 sprites/openttdgui.png 8bpp 226 8 64 31 -31 7 normal @@ -196,3 +196,4 @@ -1 sprites/openttdgui.png 8bpp 567 440 12 10 0 0 normal -1 sprites/openttdgui.png 8bpp 581 440 10 10 0 0 normal -1 sprites/openttdgui.png 8bpp 593 440 10 10 0 0 normal + -1 sprites/openttdgui.png 8bpp 605 440 8 10 0 0 normal diff --git a/media/baseset/openttd/openttdgui.png b/media/baseset/openttd/openttdgui.png index 5b80c33260..e9fea8b976 100644 Binary files a/media/baseset/openttd/openttdgui.png and b/media/baseset/openttd/openttdgui.png differ diff --git a/os/windows/sign_azure.ps1 b/os/windows/sign_azure.ps1 index e19476fa6d..a9fc93f427 100644 --- a/os/windows/sign_azure.ps1 +++ b/os/windows/sign_azure.ps1 @@ -24,7 +24,7 @@ if (!$Env:AZURE_CODESIGN_ENDPOINT -or !$Env:AZURE_CODESIGN_ACCOUNT_NAME -or !$En exit } -Install-Module -Name AzureCodeSigning -Scope CurrentUser -RequiredVersion 0.3.0 -Force -Repository PSGallery +Install-Module -Name TrustedSigning -Scope CurrentUser -RequiredVersion 0.5.3 -Force -Repository PSGallery $params = @{} @@ -37,4 +37,4 @@ $params["FileDigest"] = "SHA256" $params["TimestampRfc3161"] = "http://timestamp.acs.microsoft.com" $params["TimestampDigest"] = "SHA256" -Invoke-AzureCodeSigning @params +Invoke-TrustedSigning @params diff --git a/regression/regression/main.nut b/regression/regression/main.nut index c0fe80ea4b..951c9d4598 100644 --- a/regression/regression/main.nut +++ b/regression/regression/main.nut @@ -17,6 +17,10 @@ function Regression::TestInit() print(" IsValid(vehicle.plane_speed): " + AIGameSettings.IsValid("vehicle.plane_speed")); print(" vehicle.plane_speed: " + AIGameSettings.GetValue("vehicle.plane_speed")); require("require.nut"); + print(" TestEnum.value1: " + ::TestEnum.value1); + print(" test_constant: " + ::test_constant); + print(" TestEnum.value2: " + TestEnum.value2); + print(" test_constant: " + test_constant); print(" min(6, 3): " + min(6, 3)); print(" min(3, 6): " + min(3, 6)); print(" max(6, 3): " + max(6, 3)); @@ -824,6 +828,13 @@ function Regression::List() print(" []:"); print(" 4000 => " + list[4000]); + print(" clone:"); + local list3 = clone list; + print(" Clone ListDump:"); + foreach (idx, val in list3) { + print(" " + idx + " => " + val); + } + list.Clear(); print(" IsEmpty(): " + list.IsEmpty()); @@ -856,6 +867,12 @@ function Regression::List() it = list.Next(); print(" " + it + " => " + list.GetValue(it)); } + + print(" Clone ListDump:"); + foreach (idx, val in list3) { + print(" " + idx + " => " + val); + } + } function Regression::Map() @@ -1017,6 +1034,28 @@ function Regression::Order() foreach (idx, val in list) { print(" " + idx + " => " + val); } + list = AIVehicleList_Station(3, AIVehicle.VT_ROAD); + print(" Count(): " + list.Count()); + list.Valuate(AIVehicle.GetLocation); + print(" Location ListDump:"); + for (local i = list.Begin(); !list.IsEnd(); i = list.Next()) { + print(" " + i + " => " + list.GetValue(i)); + } + print(" foreach():"); + foreach (idx, val in list) { + print(" " + idx + " => " + val); + } + list = AIVehicleList_Station(3, AIVehicle.VT_RAIL); + print(" Count(): " + list.Count()); + list.Valuate(AIVehicle.GetLocation); + print(" Location ListDump:"); + for (local i = list.Begin(); !list.IsEnd(); i = list.Next()) { + print(" " + i + " => " + list.GetValue(i)); + } + print(" foreach():"); + foreach (idx, val in list) { + print(" " + idx + " => " + val); + } } function Regression::RailTypeList() @@ -1675,13 +1714,22 @@ function Regression::TownList() } print(" HasStatue(): " + AITown.HasStatue(list.Begin())); - print(" GetRoadReworkDuration(): " + AITown.GetRoadReworkDuration(list.Begin())); - print(" GetExclusiveRightsCompany(): " + AITown.GetExclusiveRightsCompany(list.Begin())); - print(" GetExclusiveRightsDuration(): " + AITown.GetExclusiveRightsDuration(list.Begin())); print(" IsActionAvailable(BUILD_STATUE): " + AITown.IsActionAvailable(list.Begin(), AITown.TOWN_ACTION_BUILD_STATUE)); print(" PerformTownAction(BUILD_STATUE): " + AITown.PerformTownAction(list.Begin(), AITown.TOWN_ACTION_BUILD_STATUE)); print(" IsActionAvailable(BUILD_STATUE): " + AITown.IsActionAvailable(list.Begin(), AITown.TOWN_ACTION_BUILD_STATUE)); print(" HasStatue(): " + AITown.HasStatue(list.Begin())); + print(" GetRoadReworkDuration(): " + AITown.GetRoadReworkDuration(list.Begin())); + print(" IsActionAvailable(ROAD_REBUILD): " + AITown.IsActionAvailable(list.Begin(), AITown.TOWN_ACTION_ROAD_REBUILD)); + print(" PerformTownAction(ROAD_REBUILD): " + AITown.PerformTownAction(list.Begin(), AITown.TOWN_ACTION_ROAD_REBUILD)); + print(" IsActionAvailable(ROAD_REBUILD): " + AITown.IsActionAvailable(list.Begin(), AITown.TOWN_ACTION_ROAD_REBUILD)); + print(" GetRoadReworkDuration(): " + AITown.GetRoadReworkDuration(list.Begin())); + print(" GetExclusiveRightsCompany(): " + AITown.GetExclusiveRightsCompany(list.Begin())); + print(" GetExclusiveRightsDuration(): " + AITown.GetExclusiveRightsDuration(list.Begin())); + print(" IsActionAvailable(BUY_RIGHTS): " + AITown.IsActionAvailable(list.Begin(), AITown.TOWN_ACTION_BUY_RIGHTS)); + print(" PerformTownAction(BUY_RIGHTS): " + AITown.PerformTownAction(list.Begin(), AITown.TOWN_ACTION_BUY_RIGHTS)); + print(" IsActionAvailable(BUY_RIGHTS): " + AITown.IsActionAvailable(list.Begin(), AITown.TOWN_ACTION_BUY_RIGHTS)); + print(" GetExclusiveRightsCompany(): " + AITown.GetExclusiveRightsCompany(list.Begin())); + print(" GetExclusiveRightsDuration(): " + AITown.GetExclusiveRightsDuration(list.Begin())); } function Regression::Tunnel() @@ -1975,6 +2023,33 @@ function Regression::Math() print(" 13725 > -2147483648: " + ( 13725 > -2147483648)); } +function Regression::PriorityQueue() +{ + print(""); + print("--PriorityQueue--"); + local queue = AIPriorityQueue(); + print(" IsEmpty(): " + queue.IsEmpty()); + print(" Count(): " + queue.Count()); + print(" --Insert--") + for (local i = 0; i < 10; i++) { + print(" Insert(" + i + ", " + i + "): " + queue.Insert(i, i)); + } + print(" Exists(5): " + queue.Exists(5)); + print(" Insert(5, 5): "+ queue.Insert(5, 5)); + print(" IsEmpty(): " + queue.IsEmpty()); + print(" Count(): " + queue.Count()); + local item = queue.Peek(); + print(" Peek(): " + item); + print(" Count(): " + queue.Count()); + local item2 = queue.Pop(); + print(" Pop(): " + item2); + print(" Count(): " + queue.Count()); + print(" " + item + " == " + item2 + " : " + (item == item2)); + print(" Clear(): " + queue.Clear()); + print(" IsEmpty(): " + queue.IsEmpty()); + print(" Count(): " + queue.Count()); +} + function Regression::Start() { this.TestInit(); @@ -2049,6 +2124,20 @@ function Regression::Start() print(" PresidentName: " + c.GetNewName()); } break; + case AIEvent.ET_EXCLUSIVE_TRANSPORT_RIGHTS: { + local c = AIEventExclusiveTransportRights.Convert(e); + print(" EventName: ExclusiveTransportRights"); + print(" CompanyID: " + c.GetCompanyID()); + print(" TownID: " + c.GetTownID()); + } break; + + case AIEvent.ET_ROAD_RECONSTRUCTION: { + local c = AIEventRoadReconstruction.Convert(e); + print(" EventName: RoadReconstruction"); + print(" CompanyID: " + c.GetCompanyID()); + print(" TownID: " + c.GetTownID()); + } break; + default: print(" Unknown Event"); break; @@ -2057,12 +2146,18 @@ function Regression::Start() print(" IsEventWaiting: false"); this.Math(); + this.PriorityQueue(); /* Check Valuate() is actually limited, MUST BE THE LAST TEST. */ print("--Valuate() with excessive CPU usage--") local list = AIList(); list.AddItem(0, 0); local Infinite = function(id) { while(true); } + try { + list = AIIndustryList(Infinite); + } catch (e) { + print("constructor failed with: " + e); + } list.Valuate(Infinite); } diff --git a/regression/regression/require.nut b/regression/regression/require.nut index d8dc4baa7d..872a929a5f 100644 --- a/regression/regression/require.nut +++ b/regression/regression/require.nut @@ -1,2 +1,9 @@ print(" Required this file"); +const test_constant = 1; + +enum TestEnum { + value0, + value1, + value2 +}; diff --git a/regression/regression/result.txt b/regression/regression/result.txt index f00c9e3813..18afcb5f2f 100644 --- a/regression/regression/result.txt +++ b/regression/regression/result.txt @@ -8,6 +8,10 @@ IsValid(vehicle.plane_speed): true vehicle.plane_speed: 2 Required this file + TestEnum.value1: 1 + test_constant: 1 + TestEnum.value2: 2 + test_constant: 1 min(6, 3): 3 min(3, 6): 3 max(6, 3): 6 @@ -81,7 +85,7 @@ 20 30 40 - Ops: 8673 + Ops: 8649 --Std-- abs(-21): 21 @@ -571,6 +575,13 @@ 4006 => 12 []: 4000 => 50 + clone: + Clone ListDump: + 1005 => 1005 + 4000 => 50 + 4001 => 8002 + 4002 => 8004 + 4006 => 12 IsEmpty(): true 0 => 5 (true) ERROR: Next() is invalid as Begin() is never called @@ -580,6 +591,12 @@ ERROR: IsEnd() is invalid as Begin() is never called 2 => 6 (true) 3 => 6 (true) 9 => 0 (false) + Clone ListDump: + 1005 => 1005 + 4000 => 50 + 4001 => 8002 + 4002 => 8004 + 4006 => 12 --Company-- SetName(): true @@ -9423,13 +9440,22 @@ ERROR: IsEnd() is invalid as Begin() is never called 23 => 652 25 => 563 HasStatue(): false - GetRoadReworkDuration(): 0 - GetExclusiveRightsCompany(): -1 - GetExclusiveRightsDuration(): 0 IsActionAvailable(BUILD_STATUE): true PerformTownAction(BUILD_STATUE): true IsActionAvailable(BUILD_STATUE): false HasStatue(): true + GetRoadReworkDuration(): 0 + IsActionAvailable(ROAD_REBUILD): true + PerformTownAction(ROAD_REBUILD): true + IsActionAvailable(ROAD_REBUILD): true + GetRoadReworkDuration(): 6 + GetExclusiveRightsCompany(): -1 + GetExclusiveRightsDuration(): 0 + IsActionAvailable(BUY_RIGHTS): true + PerformTownAction(BUY_RIGHTS): true + IsActionAvailable(BUY_RIGHTS): false + GetExclusiveRightsCompany(): 1 + GetExclusiveRightsDuration(): 12 --Tunnel-- IsTunnelTile(): false @@ -9694,6 +9720,14 @@ ERROR: IsEnd() is invalid as Begin() is never called GetStopLocation(): 1 --VehicleList_Station-- + Count(): 1 + Location ListDump: + 20 => 23596 + foreach(): + 20 => 23596 + Count(): 0 + Location ListDump: + foreach(): Count(): 1 Location ListDump: 20 => 23596 @@ -9726,6 +9760,16 @@ ERROR: IsEnd() is invalid as Begin() is never called EventName: CompanyRenamed CompanyID: 1 CompanyName: Little Frutford Transport + GetNextEvent: instance + GetEventType: 28 + EventName: RoadReconstruction + CompanyID: 1 + TownID: 12 + GetNextEvent: instance + GetEventType: 27 + EventName: ExclusiveTransportRights + CompanyID: 1 + TownID: 12 IsEventWaiting: false --Math-- @@ -9760,21 +9804,38 @@ ERROR: IsEnd() is invalid as Begin() is never called -1 > 2147483647: false -2147483648 > 2147483647: false 13725 > -2147483648: true + +--PriorityQueue-- + IsEmpty(): true + Count(): 0 + --Insert-- + Insert(0, 0): true + Insert(1, 1): true + Insert(2, 2): true + Insert(3, 3): true + Insert(4, 4): true + Insert(5, 5): true + Insert(6, 6): true + Insert(7, 7): true + Insert(8, 8): true + Insert(9, 9): true + Exists(5): true + Insert(5, 5): true + IsEmpty(): false + Count(): 11 + Peek(): 0 + Count(): 11 + Pop(): 0 + Count(): 10 + 0 == 0 : true + Clear(): (null : 0x00000000) + IsEmpty(): true + Count(): 0 --Valuate() with excessive CPU usage-- +constructor failed with: excessive CPU usage in list filter function Your script made an error: excessive CPU usage in valuator function -*FUNCTION [unknown()] regression/main.nut line [2065] -*FUNCTION [Valuate()] NATIVE line [-1] -*FUNCTION [Start()] regression/main.nut line [2066] - -[id] 0 -[this] TABLE -[Infinite] CLOSURE -[list] INSTANCE -[this] INSTANCE -Your script made an error: excessive CPU usage in valuator function - -*FUNCTION [Start()] regression/main.nut line [2066] +*FUNCTION [Start()] regression/main.nut line [2161] [Infinite] CLOSURE [list] INSTANCE diff --git a/src/3rdparty/icu/scriptrun.h b/src/3rdparty/icu/scriptrun.h index 792d5c55c9..21d97645b0 100644 --- a/src/3rdparty/icu/scriptrun.h +++ b/src/3rdparty/icu/scriptrun.h @@ -19,6 +19,7 @@ #include #include #include +#include U_NAMESPACE_BEGIN diff --git a/src/3rdparty/md5/md5.cpp b/src/3rdparty/md5/md5.cpp index 0e58d989b6..d758f0e974 100644 --- a/src/3rdparty/md5/md5.cpp +++ b/src/3rdparty/md5/md5.cpp @@ -281,7 +281,7 @@ void Md5::Append(const void *data, const size_t nbytes) if (offset) { size_t copy = (offset + nbytes > 64 ? 64 - offset : nbytes); - memcpy(this->buf + offset, p, copy); + std::copy_n(p, copy, this->buf + offset); if (offset + copy < 64) return; @@ -294,7 +294,7 @@ void Md5::Append(const void *data, const size_t nbytes) for (; left >= 64; p += 64, left -= 64) this->Process(p); /* Process a final partial block. */ - if (left) memcpy(this->buf, p, left); + if (left) std::copy_n(p, left, this->buf); } void Md5::Finish(MD5Hash &digest) diff --git a/src/3rdparty/squirrel/include/CMakeLists.txt b/src/3rdparty/squirrel/include/CMakeLists.txt index 5237360d31..249e97f326 100644 --- a/src/3rdparty/squirrel/include/CMakeLists.txt +++ b/src/3rdparty/squirrel/include/CMakeLists.txt @@ -1,6 +1,5 @@ add_files( sqstdaux.h sqstdmath.h - sqstdstring.h squirrel.h ) diff --git a/src/3rdparty/squirrel/include/sqstdstring.h b/src/3rdparty/squirrel/include/sqstdstring.h deleted file mode 100644 index 65dae348b3..0000000000 --- a/src/3rdparty/squirrel/include/sqstdstring.h +++ /dev/null @@ -1,23 +0,0 @@ -/* see copyright notice in squirrel.h */ -#ifndef _SQSTD_STRING_H_ -#define _SQSTD_STRING_H_ - -typedef unsigned int SQRexBool; -typedef struct SQRex SQRex; - -typedef struct { - const SQChar *begin; - SQInteger len; -} SQRexMatch; - -SQRex *sqstd_rex_compile(const SQChar *pattern,const SQChar **error); -void sqstd_rex_free(SQRex *exp); -SQBool sqstd_rex_match(SQRex* exp,const SQChar* text); -SQBool sqstd_rex_search(SQRex* exp,const SQChar* text, const SQChar** out_begin, const SQChar** out_end); -SQBool sqstd_rex_searchrange(SQRex* exp,const SQChar* text_begin,const SQChar* text_end,const SQChar** out_begin, const SQChar** out_end); -SQInteger sqstd_rex_getsubexpcount(SQRex* exp); -SQBool sqstd_rex_getsubexp(SQRex* exp, SQInteger n, SQRexMatch *subexp); - -SQRESULT sqstd_register_stringlib(HSQUIRRELVM v); - -#endif /*_SQSTD_STRING_H_*/ diff --git a/src/3rdparty/squirrel/include/squirrel.h b/src/3rdparty/squirrel/include/squirrel.h index bf0b6fec49..eb65c69c50 100644 --- a/src/3rdparty/squirrel/include/squirrel.h +++ b/src/3rdparty/squirrel/include/squirrel.h @@ -68,7 +68,6 @@ struct SQClass; struct SQInstance; struct SQDelegable; -typedef char SQChar; #define MAX_CHAR 0xFFFF #define SQUIRREL_VERSION "Squirrel 2.2.5 stable - With custom OpenTTD modifications" @@ -164,17 +163,17 @@ typedef struct tagSQObject }SQObject; typedef struct tagSQStackInfos{ - const SQChar* funcname; - const SQChar* source; - SQInteger line; + std::string_view funcname; + std::string_view source; + SQInteger line = -1; }SQStackInfos; typedef struct SQVM* HSQUIRRELVM; typedef SQObject HSQOBJECT; typedef SQInteger (*SQFUNCTION)(HSQUIRRELVM); typedef SQInteger (*SQRELEASEHOOK)(SQUserPointer,SQInteger size); -typedef void (*SQCOMPILERERROR)(HSQUIRRELVM,const SQChar * /*desc*/,const SQChar * /*source*/,SQInteger /*line*/,SQInteger /*column*/); -typedef void (*SQPRINTFUNCTION)(HSQUIRRELVM,const std::string &); +typedef void (*SQCOMPILERERROR)(HSQUIRRELVM,std::string_view /*desc*/,std::string_view /*source*/,SQInteger /*line*/,SQInteger /*column*/); +typedef void (*SQPRINTFUNCTION)(HSQUIRRELVM,std::string_view); typedef SQInteger (*SQWRITEFUNC)(SQUserPointer,SQUserPointer,SQInteger); typedef SQInteger (*SQREADFUNC)(SQUserPointer,SQUserPointer,SQInteger); @@ -182,16 +181,16 @@ typedef SQInteger (*SQREADFUNC)(SQUserPointer,SQUserPointer,SQInteger); typedef char32_t (*SQLEXREADFUNC)(SQUserPointer); typedef struct tagSQRegFunction{ - const SQChar *name; + std::string_view name; SQFUNCTION f; SQInteger nparamscheck; - const SQChar *typemask; + std::optional typemask; }SQRegFunction; typedef struct tagSQFunctionInfo { SQUserPointer funcid; - const SQChar *name; - const SQChar *source; + std::string_view name; + std::string_view source; }SQFunctionInfo; @@ -213,8 +212,8 @@ SQInteger sq_getvmstate(HSQUIRRELVM v); void sq_decreaseops(HSQUIRRELVM v, int amount); /*compiler*/ -SQRESULT sq_compile(HSQUIRRELVM v,SQLEXREADFUNC read,SQUserPointer p,const SQChar *sourcename,SQBool raiseerror); -SQRESULT sq_compilebuffer(HSQUIRRELVM v,const SQChar *s,SQInteger size,const SQChar *sourcename,SQBool raiseerror); +SQRESULT sq_compile(HSQUIRRELVM v,SQLEXREADFUNC read,SQUserPointer p,std::string_view sourcename,SQBool raiseerror); +SQRESULT sq_compilebuffer(HSQUIRRELVM v,std::string_view buffer,std::string_view sourcename,SQBool raiseerror); void sq_enabledebuginfo(HSQUIRRELVM v, SQBool enable); void sq_notifyallexceptions(HSQUIRRELVM v, SQBool enable); void sq_setcompilererrorhandler(HSQUIRRELVM v,SQCOMPILERERROR f); @@ -235,10 +234,9 @@ SQUserPointer sq_newuserdata(HSQUIRRELVM v,SQUnsignedInteger size); void sq_newtable(HSQUIRRELVM v); void sq_newarray(HSQUIRRELVM v,SQInteger size); void sq_newclosure(HSQUIRRELVM v,SQFUNCTION func,SQUnsignedInteger nfreevars); -SQRESULT sq_setparamscheck(HSQUIRRELVM v,SQInteger nparamscheck,const SQChar *typemask); +SQRESULT sq_setparamscheck(HSQUIRRELVM v,SQInteger nparamscheck,std::optional typemask); SQRESULT sq_bindenv(HSQUIRRELVM v,SQInteger idx); -void sq_pushstring(HSQUIRRELVM v,const SQChar *s,SQInteger len); -inline void sq_pushstring(HSQUIRRELVM v, const std::string &str, SQInteger len = -1) { sq_pushstring(v, str.data(), len == -1 ? str.size() : len); } +void sq_pushstring(HSQUIRRELVM v, std::string_view str); void sq_pushfloat(HSQUIRRELVM v,SQFloat f); void sq_pushinteger(HSQUIRRELVM v,SQInteger n); void sq_pushbool(HSQUIRRELVM v,SQBool b); @@ -250,7 +248,7 @@ SQRESULT sq_getbase(HSQUIRRELVM v,SQInteger idx); SQBool sq_instanceof(HSQUIRRELVM v); void sq_tostring(HSQUIRRELVM v,SQInteger idx); void sq_tobool(HSQUIRRELVM v, SQInteger idx, SQBool *b); -SQRESULT sq_getstring(HSQUIRRELVM v,SQInteger idx,const SQChar **c); +SQRESULT sq_getstring(HSQUIRRELVM v,SQInteger idx,std::string_view &str); SQRESULT sq_getinteger(HSQUIRRELVM v,SQInteger idx,SQInteger *i); SQRESULT sq_getfloat(HSQUIRRELVM v,SQInteger idx,SQFloat *f); SQRESULT sq_getbool(HSQUIRRELVM v,SQInteger idx,SQBool *b); @@ -260,10 +258,10 @@ SQRESULT sq_getuserdata(HSQUIRRELVM v,SQInteger idx,SQUserPointer *p,SQUserPoint SQRESULT sq_settypetag(HSQUIRRELVM v,SQInteger idx,SQUserPointer typetag); SQRESULT sq_gettypetag(HSQUIRRELVM v,SQInteger idx,SQUserPointer *typetag); void sq_setreleasehook(HSQUIRRELVM v,SQInteger idx,SQRELEASEHOOK hook); -SQChar *sq_getscratchpad(HSQUIRRELVM v,SQInteger minsize); +std::span sq_getscratchpad(HSQUIRRELVM v,SQInteger minsize); SQRESULT sq_getfunctioninfo(HSQUIRRELVM v,SQInteger idx,SQFunctionInfo *fi); SQRESULT sq_getclosureinfo(HSQUIRRELVM v,SQInteger idx,SQUnsignedInteger *nparams,SQUnsignedInteger *nfreevars); -SQRESULT sq_setnativeclosurename(HSQUIRRELVM v,SQInteger idx,const SQChar *name); +SQRESULT sq_setnativeclosurename(HSQUIRRELVM v,SQInteger idx,std::string_view name); SQRESULT sq_setinstanceup(HSQUIRRELVM v, SQInteger idx, SQUserPointer p); SQRESULT sq_getinstanceup(HSQUIRRELVM v, SQInteger idx, SQUserPointer *p,SQUserPointer typetag); SQRESULT sq_setclassudsize(HSQUIRRELVM v, SQInteger idx, SQInteger udsize); @@ -305,10 +303,9 @@ SQRESULT sq_clear(HSQUIRRELVM v,SQInteger idx); /*calls*/ SQRESULT sq_call(HSQUIRRELVM v,SQInteger params,SQBool retval,SQBool raiseerror, int suspend = -1); SQRESULT sq_resume(HSQUIRRELVM v,SQBool retval,SQBool raiseerror); -const SQChar *sq_getlocal(HSQUIRRELVM v,SQUnsignedInteger level,SQUnsignedInteger idx); -const SQChar *sq_getfreevariable(HSQUIRRELVM v,SQInteger idx,SQUnsignedInteger nval); -SQRESULT sq_throwerror(HSQUIRRELVM v,const SQChar *err, SQInteger len = -1); -inline SQRESULT sq_throwerror(HSQUIRRELVM v, const std::string_view err) { return sq_throwerror(v, err.data(), err.size()); } +std::optional sq_getlocal(HSQUIRRELVM v,SQUnsignedInteger level,SQUnsignedInteger idx); +std::optional sq_getfreevariable(HSQUIRRELVM v,SQInteger idx,SQUnsignedInteger nval); +SQRESULT sq_throwerror(HSQUIRRELVM v,std::string_view err); void sq_reseterror(HSQUIRRELVM v); void sq_getlasterror(HSQUIRRELVM v); @@ -318,7 +315,7 @@ void sq_pushobject(HSQUIRRELVM v,HSQOBJECT obj); void sq_addref(HSQUIRRELVM v,HSQOBJECT *po); SQBool sq_release(HSQUIRRELVM v,HSQOBJECT *po); void sq_resetobject(HSQOBJECT *po); -const SQChar *sq_objtostring(HSQOBJECT *o); +std::optional sq_objtostring(HSQOBJECT *o); SQBool sq_objtobool(HSQOBJECT *o); SQInteger sq_objtointeger(HSQOBJECT *o); SQFloat sq_objtofloat(HSQOBJECT *o); @@ -363,7 +360,7 @@ void sq_setdebughook(HSQUIRRELVM v); /* Limit the total number of ops that can be consumed by an operation */ struct SQOpsLimiter { - SQOpsLimiter(HSQUIRRELVM v, SQInteger ops, const char *label); + SQOpsLimiter(HSQUIRRELVM v, SQInteger ops, std::string_view label); ~SQOpsLimiter(); private: HSQUIRRELVM _v; diff --git a/src/3rdparty/squirrel/sqstdlib/sqstdaux.cpp b/src/3rdparty/squirrel/sqstdlib/sqstdaux.cpp index 1bdee97e69..f5763589c2 100644 --- a/src/3rdparty/squirrel/sqstdlib/sqstdaux.cpp +++ b/src/3rdparty/squirrel/sqstdlib/sqstdaux.cpp @@ -16,28 +16,22 @@ void sqstd_printcallstack(HSQUIRRELVM v) SQInteger i; SQBool b; SQFloat f; - const SQChar *s; SQInteger level=1; //1 is to skip this function that is level 0 - const SQChar *name=nullptr; SQInteger seq=0; pf(v,"\nCALLSTACK\n"); while(SQ_SUCCEEDED(sq_stackinfos(v,level,&si))) { - const SQChar *fn="unknown"; - const SQChar *src="unknown"; - if(si.funcname)fn=si.funcname; - if(si.source) { + std::string_view fn="unknown"; + std::string_view src="unknown"; + if(!si.funcname.empty())fn=si.funcname; + if(!si.source.empty()) { /* We don't want to bother users with absolute paths to all AI files. * Since the path only reaches NoAI code in a formatted string we have * to strip it here. Let's hope nobody installs openttd in a subdirectory * of a directory named /ai/. */ - src = strstr(si.source, "\\ai\\"); - if (!src) src = strstr(si.source, "/ai/"); - if (src) { - src += 4; - } else { - src = si.source; - } + auto p = si.source.find("\\ai\\"); + if (p == std::string_view::npos) p = si.source.find("/ai/"); + src = (p == std::string_view::npos) ? si.source : si.source.substr(p + 4); } pf(v,fmt::format("*FUNCTION [{}()] {} line [{}]\n",fn,src,si.line)); level++; @@ -47,8 +41,9 @@ void sqstd_printcallstack(HSQUIRRELVM v) for(level=0;level<10;level++){ seq=0; - while((name = sq_getlocal(v,level,seq))) - { + std::optional opt; + while ((opt = sq_getlocal(v,level,seq)).has_value()) { + std::string_view name = *opt; seq++; switch(sq_gettype(v,-1)) { @@ -66,10 +61,12 @@ void sqstd_printcallstack(HSQUIRRELVM v) case OT_USERPOINTER: pf(v,fmt::format("[{}] USERPOINTER\n",name)); break; - case OT_STRING: - sq_getstring(v,-1,&s); - pf(v,fmt::format("[{}] \"{}\"\n",name,s)); + case OT_STRING: { + std::string_view view; + sq_getstring(v,-1,view); + pf(v,fmt::format("[{}] \"{}\"\n",name,view)); break; + } case OT_TABLE: pf(v,fmt::format("[{}] TABLE\n",name)); break; @@ -117,10 +114,10 @@ static SQInteger _sqstd_aux_printerror(HSQUIRRELVM v) { SQPRINTFUNCTION pf = sq_getprintfunc(v); if(pf) { - const SQChar *sErr = nullptr; + std::string_view error; if(sq_gettop(v)>=1) { - if(SQ_SUCCEEDED(sq_getstring(v,2,&sErr))) { - pf(v,fmt::format("\nAN ERROR HAS OCCURRED [{}]\n",sErr)); + if(SQ_SUCCEEDED(sq_getstring(v,2,error))) { + pf(v,fmt::format("\nAN ERROR HAS OCCURRED [{}]\n",error)); } else{ pf(v,"\nAN ERROR HAS OCCURRED [unknown]\n"); @@ -131,7 +128,7 @@ static SQInteger _sqstd_aux_printerror(HSQUIRRELVM v) return 0; } -void _sqstd_compiler_error(HSQUIRRELVM v,const SQChar *sErr,const SQChar *sSource,SQInteger line,SQInteger column) +void _sqstd_compiler_error(HSQUIRRELVM v,std::string_view sErr,std::string_view sSource,SQInteger line,SQInteger column) { SQPRINTFUNCTION pf = sq_getprintfunc(v); if(pf) { diff --git a/src/3rdparty/squirrel/sqstdlib/sqstdmath.cpp b/src/3rdparty/squirrel/sqstdlib/sqstdmath.cpp index 7f07271f20..5345f9b21b 100644 --- a/src/3rdparty/squirrel/sqstdlib/sqstdmath.cpp +++ b/src/3rdparty/squirrel/sqstdlib/sqstdmath.cpp @@ -67,7 +67,7 @@ SINGLE_ARG_FUNC(ceil, 1) SINGLE_ARG_FUNC(exp, 100) #define _DECL_FUNC(name,nparams,tycheck) {#name,math_##name,nparams,tycheck} -static SQRegFunction mathlib_funcs[] = { +static const std::initializer_list mathlib_funcs = { _DECL_FUNC(sqrt,2,".n"), _DECL_FUNC(sin,2,".n"), _DECL_FUNC(cos,2,".n"), @@ -84,11 +84,10 @@ static SQRegFunction mathlib_funcs[] = { _DECL_FUNC(exp,2,".n"), #ifdef EXPORT_DEFAULT_SQUIRREL_FUNCTIONS _DECL_FUNC(srand,2,".n"), - _DECL_FUNC(rand,1,nullptr), + _DECL_FUNC(rand,1,std::nullopt), #endif /* EXPORT_DEFAULT_SQUIRREL_FUNCTIONS */ _DECL_FUNC(fabs,2,".n"), _DECL_FUNC(abs,2,".n"), - {nullptr,nullptr,0,nullptr}, }; #ifndef M_PI @@ -97,21 +96,19 @@ static SQRegFunction mathlib_funcs[] = { SQRESULT sqstd_register_mathlib(HSQUIRRELVM v) { - SQInteger i=0; - while(mathlib_funcs[i].name!=nullptr) { - sq_pushstring(v,mathlib_funcs[i].name,-1); - sq_newclosure(v,mathlib_funcs[i].f,0); - sq_setparamscheck(v,mathlib_funcs[i].nparamscheck,mathlib_funcs[i].typemask); - sq_setnativeclosurename(v,-1,mathlib_funcs[i].name); + for(auto &func : mathlib_funcs) { + sq_pushstring(v,func.name); + sq_newclosure(v,func.f,0); + sq_setparamscheck(v,func.nparamscheck,func.typemask); + sq_setnativeclosurename(v,-1,func.name); sq_createslot(v,-3); - i++; } #ifdef EXPORT_DEFAULT_SQUIRREL_FUNCTIONS - sq_pushstring(v,"RAND_MAX",-1); + sq_pushstring(v,"RAND_MAX"); sq_pushinteger(v,RAND_MAX); sq_createslot(v,-3); #endif /* EXPORT_DEFAULT_SQUIRREL_FUNCTIONS */ - sq_pushstring(v,"PI",-1); + sq_pushstring(v,"PI"); sq_pushfloat(v,(SQFloat)M_PI); sq_createslot(v,-3); return SQ_OK; diff --git a/src/3rdparty/squirrel/sqstdlib/sqstdrex.cpp b/src/3rdparty/squirrel/sqstdlib/sqstdrex.cpp deleted file mode 100644 index 16e87b3d24..0000000000 --- a/src/3rdparty/squirrel/sqstdlib/sqstdrex.cpp +++ /dev/null @@ -1,632 +0,0 @@ -/* see copyright notice in squirrel.h */ -#include -#include -#include "sqstdstring.h" - -#ifdef _UNICODE -#define scisprint iswprint -#else -#define scisprint isprint -#endif - -#ifdef _DEBUG - -static const SQChar *g_nnames[] = -{ - "NONE","OP_GREEDY", "OP_OR", - "OP_EXPR","OP_NOCAPEXPR","OP_DOT", "OP_CLASS", - "OP_CCLASS","OP_NCLASS","OP_RANGE","OP_CHAR", - "OP_EOL","OP_BOL","OP_WB" -}; - -#endif - -#define OP_GREEDY (MAX_CHAR+1) // * + ? {n} -#define OP_OR (MAX_CHAR+2) -#define OP_EXPR (MAX_CHAR+3) //parentesis () -#define OP_NOCAPEXPR (MAX_CHAR+4) //parentesis (?:) -#define OP_DOT (MAX_CHAR+5) -#define OP_CLASS (MAX_CHAR+6) -#define OP_CCLASS (MAX_CHAR+7) -#define OP_NCLASS (MAX_CHAR+8) //negates class the [^ -#define OP_RANGE (MAX_CHAR+9) -#define OP_CHAR (MAX_CHAR+10) -#define OP_EOL (MAX_CHAR+11) -#define OP_BOL (MAX_CHAR+12) -#define OP_WB (MAX_CHAR+13) - -#define SQREX_SYMBOL_ANY_CHAR ('.') -#define SQREX_SYMBOL_GREEDY_ONE_OR_MORE ('+') -#define SQREX_SYMBOL_GREEDY_ZERO_OR_MORE ('*') -#define SQREX_SYMBOL_GREEDY_ZERO_OR_ONE ('?') -#define SQREX_SYMBOL_BRANCH ('|') -#define SQREX_SYMBOL_END_OF_STRING ('$') -#define SQREX_SYMBOL_BEGINNING_OF_STRING ('^') -#define SQREX_SYMBOL_ESCAPE_CHAR ('\\') - - -typedef int SQRexNodeType; - -typedef struct tagSQRexNode{ - SQRexNodeType type; - SQInteger left; - SQInteger right; - SQInteger next; -}SQRexNode; - -struct SQRex{ - const SQChar *_eol; - const SQChar *_bol; - const SQChar *_p; - SQInteger _first; - SQInteger _op; - SQRexNode *_nodes; - SQInteger _nallocated; - SQInteger _nsize; - SQInteger _nsubexpr; - SQRexMatch *_matches; - SQInteger _currsubexp; - const SQChar **_error; -}; - -static SQInteger sqstd_rex_list(SQRex *exp); - -static SQInteger sqstd_rex_newnode(SQRex *exp, SQRexNodeType type) -{ - SQRexNode n; - n.type = type; - n.next = n.right = n.left = -1; - if(type == OP_EXPR) - n.right = exp->_nsubexpr++; - if(exp->_nallocated < (exp->_nsize + 1)) { - SQInteger oldsize = exp->_nallocated; - exp->_nallocated *= 2; - exp->_nodes = (SQRexNode *)sq_realloc(exp->_nodes, oldsize * sizeof(SQRexNode) ,exp->_nallocated * sizeof(SQRexNode)); - } - exp->_nodes[exp->_nsize++] = n; - SQInteger newid = exp->_nsize - 1; - return (SQInteger)newid; -} - -static void sqstd_rex_error(SQRex *exp,const SQChar *error) -{ - if(exp->_error) *exp->_error = error; - throw std::exception(); -} - -static void sqstd_rex_expect(SQRex *exp, SQChar n){ - if((*exp->_p) != n) - sqstd_rex_error(exp, "expected paren"); - exp->_p++; -} - -static SQChar sqstd_rex_escapechar(SQRex *exp) -{ - if(*exp->_p == SQREX_SYMBOL_ESCAPE_CHAR){ - exp->_p++; - switch(*exp->_p) { - case 'v': exp->_p++; return '\v'; - case 'n': exp->_p++; return '\n'; - case 't': exp->_p++; return '\t'; - case 'r': exp->_p++; return '\r'; - case 'f': exp->_p++; return '\f'; - default: return (*exp->_p++); - } - } else if(!scisprint(*exp->_p)) sqstd_rex_error(exp,"letter expected"); - return (*exp->_p++); -} - -static SQInteger sqstd_rex_charclass(SQRex *exp,SQInteger classid) -{ - SQInteger n = sqstd_rex_newnode(exp,OP_CCLASS); - exp->_nodes[n].left = classid; - return n; -} - -static SQInteger sqstd_rex_charnode(SQRex *exp,SQBool isclass) -{ - SQChar t; - if(*exp->_p == SQREX_SYMBOL_ESCAPE_CHAR) { - exp->_p++; - switch(*exp->_p) { - case 'n': exp->_p++; return sqstd_rex_newnode(exp,'\n'); - case 't': exp->_p++; return sqstd_rex_newnode(exp,'\t'); - case 'r': exp->_p++; return sqstd_rex_newnode(exp,'\r'); - case 'f': exp->_p++; return sqstd_rex_newnode(exp,'\f'); - case 'v': exp->_p++; return sqstd_rex_newnode(exp,'\v'); - case 'a': case 'A': case 'w': case 'W': case 's': case 'S': - case 'd': case 'D': case 'x': case 'X': case 'c': case 'C': - case 'p': case 'P': case 'l': case 'u': - { - t = *exp->_p; exp->_p++; - return sqstd_rex_charclass(exp,t); - } - case 'b': - case 'B': - if(!isclass) { - SQInteger node = sqstd_rex_newnode(exp,OP_WB); - exp->_nodes[node].left = *exp->_p; - exp->_p++; - return node; - } //else default - default: - t = *exp->_p; exp->_p++; - return sqstd_rex_newnode(exp,t); - } - } - else if(!scisprint(*exp->_p)) { - - sqstd_rex_error(exp,"letter expected"); - } - t = *exp->_p; exp->_p++; - return sqstd_rex_newnode(exp,t); -} -static SQInteger sqstd_rex_class(SQRex *exp) -{ - SQInteger ret = -1; - SQInteger first = -1,chain; - if(*exp->_p == SQREX_SYMBOL_BEGINNING_OF_STRING){ - ret = sqstd_rex_newnode(exp,OP_NCLASS); - exp->_p++; - }else ret = sqstd_rex_newnode(exp,OP_CLASS); - - if(*exp->_p == ']') sqstd_rex_error(exp,"empty class"); - chain = ret; - while(*exp->_p != ']' && exp->_p != exp->_eol) { - if(*exp->_p == '-' && first != -1){ - SQInteger r; - if(*exp->_p++ == ']') sqstd_rex_error(exp,"unfinished range"); - r = sqstd_rex_newnode(exp,OP_RANGE); - if(exp->_nodes[first].type>*exp->_p) sqstd_rex_error(exp,"invalid range"); - if(exp->_nodes[first].type == OP_CCLASS) sqstd_rex_error(exp,"cannot use character classes in ranges"); - exp->_nodes[r].left = exp->_nodes[first].type; - SQInteger t = sqstd_rex_escapechar(exp); - exp->_nodes[r].right = t; - exp->_nodes[chain].next = r; - chain = r; - first = -1; - } - else{ - if(first!=-1){ - SQInteger c = first; - exp->_nodes[chain].next = c; - chain = c; - first = sqstd_rex_charnode(exp,SQTrue); - } - else{ - first = sqstd_rex_charnode(exp,SQTrue); - } - } - } - if(first!=-1){ - SQInteger c = first; - exp->_nodes[chain].next = c; - chain = c; - first = -1; - } - /* hack? */ - exp->_nodes[ret].left = exp->_nodes[ret].next; - exp->_nodes[ret].next = -1; - return ret; -} - -static SQInteger sqstd_rex_parsenumber(SQRex *exp) -{ - SQInteger ret = *exp->_p-'0'; - SQInteger positions = 10; - exp->_p++; - while(isdigit(*exp->_p)) { - ret = ret*10+(*exp->_p++-'0'); - if(positions==1000000000) sqstd_rex_error(exp,"overflow in numeric constant"); - positions *= 10; - }; - return ret; -} - -static SQInteger sqstd_rex_element(SQRex *exp) -{ - SQInteger ret = -1; - switch(*exp->_p) - { - case '(': { - SQInteger expr; - exp->_p++; - - - if(*exp->_p =='?') { - exp->_p++; - sqstd_rex_expect(exp,':'); - expr = sqstd_rex_newnode(exp,OP_NOCAPEXPR); - } - else - expr = sqstd_rex_newnode(exp,OP_EXPR); - SQInteger newn = sqstd_rex_list(exp); - exp->_nodes[expr].left = newn; - ret = expr; - sqstd_rex_expect(exp,')'); - } - break; - case '[': - exp->_p++; - ret = sqstd_rex_class(exp); - sqstd_rex_expect(exp,']'); - break; - case SQREX_SYMBOL_END_OF_STRING: exp->_p++; ret = sqstd_rex_newnode(exp,OP_EOL);break; - case SQREX_SYMBOL_ANY_CHAR: exp->_p++; ret = sqstd_rex_newnode(exp,OP_DOT);break; - default: - ret = sqstd_rex_charnode(exp,SQFalse); - break; - } - - - SQInteger op; - SQBool isgreedy = SQFalse; - unsigned short p0 = 0, p1 = 0; - switch(*exp->_p){ - case SQREX_SYMBOL_GREEDY_ZERO_OR_MORE: p0 = 0; p1 = 0xFFFF; exp->_p++; isgreedy = SQTrue; break; - case SQREX_SYMBOL_GREEDY_ONE_OR_MORE: p0 = 1; p1 = 0xFFFF; exp->_p++; isgreedy = SQTrue; break; - case SQREX_SYMBOL_GREEDY_ZERO_OR_ONE: p0 = 0; p1 = 1; exp->_p++; isgreedy = SQTrue; break; - case '{': - exp->_p++; - if(!isdigit(*exp->_p)) sqstd_rex_error(exp,"number expected"); - p0 = (unsigned short)sqstd_rex_parsenumber(exp); - /*******************************/ - switch(*exp->_p) { - case '}': - p1 = p0; exp->_p++; - break; - case ',': - exp->_p++; - p1 = 0xFFFF; - if(isdigit(*exp->_p)){ - p1 = (unsigned short)sqstd_rex_parsenumber(exp); - } - sqstd_rex_expect(exp,'}'); - break; - default: - sqstd_rex_error(exp,", or } expected"); - } - /*******************************/ - isgreedy = SQTrue; - break; - - } - if(isgreedy) { - SQInteger nnode = sqstd_rex_newnode(exp,OP_GREEDY); - op = OP_GREEDY; - exp->_nodes[nnode].left = ret; - exp->_nodes[nnode].right = ((p0)<<16)|p1; - ret = nnode; - } - - if((*exp->_p != SQREX_SYMBOL_BRANCH) && (*exp->_p != ')') && (*exp->_p != SQREX_SYMBOL_GREEDY_ZERO_OR_MORE) && (*exp->_p != SQREX_SYMBOL_GREEDY_ONE_OR_MORE) && (*exp->_p != '\0')) { - SQInteger nnode = sqstd_rex_element(exp); - exp->_nodes[ret].next = nnode; - } - - return ret; -} - -static SQInteger sqstd_rex_list(SQRex *exp) -{ - SQInteger ret=-1,e; - if(*exp->_p == SQREX_SYMBOL_BEGINNING_OF_STRING) { - exp->_p++; - ret = sqstd_rex_newnode(exp,OP_BOL); - } - e = sqstd_rex_element(exp); - if(ret != -1) { - exp->_nodes[ret].next = e; - } - else ret = e; - - if(*exp->_p == SQREX_SYMBOL_BRANCH) { - SQInteger temp,tright; - exp->_p++; - temp = sqstd_rex_newnode(exp,OP_OR); - exp->_nodes[temp].left = ret; - tright = sqstd_rex_list(exp); - exp->_nodes[temp].right = tright; - ret = temp; - } - return ret; -} - -static SQBool sqstd_rex_matchcclass(SQInteger cclass,SQChar c) -{ - switch(cclass) { - case 'a': return isalpha(c)?SQTrue:SQFalse; - case 'A': return !isalpha(c)?SQTrue:SQFalse; - case 'w': return (isalnum(c) || c == '_')?SQTrue:SQFalse; - case 'W': return (!isalnum(c) && c != '_')?SQTrue:SQFalse; - case 's': return isspace(c)?SQTrue:SQFalse; - case 'S': return !isspace(c)?SQTrue:SQFalse; - case 'd': return isdigit(c)?SQTrue:SQFalse; - case 'D': return !isdigit(c)?SQTrue:SQFalse; - case 'x': return isxdigit(c)?SQTrue:SQFalse; - case 'X': return !isxdigit(c)?SQTrue:SQFalse; - case 'c': return iscntrl(c)?SQTrue:SQFalse; - case 'C': return !iscntrl(c)?SQTrue:SQFalse; - case 'p': return ispunct(c)?SQTrue:SQFalse; - case 'P': return !ispunct(c)?SQTrue:SQFalse; - case 'l': return islower(c)?SQTrue:SQFalse; - case 'u': return isupper(c)?SQTrue:SQFalse; - } - return SQFalse; /*cannot happen*/ -} - -static SQBool sqstd_rex_matchclass(SQRex* exp,SQRexNode *node,SQInteger c) -{ - do { - switch(node->type) { - case OP_RANGE: - if(c >= node->left && c <= node->right) return SQTrue; - break; - case OP_CCLASS: - if(sqstd_rex_matchcclass(node->left,c)) return SQTrue; - break; - default: - if(c == node->type)return SQTrue; - } - } while((node->next != -1) && (node = &exp->_nodes[node->next])); - return SQFalse; -} - -static const SQChar *sqstd_rex_matchnode(SQRex* exp,SQRexNode *node,const SQChar *str,SQRexNode *next) -{ - - SQRexNodeType type = node->type; - switch(type) { - case OP_GREEDY: { - //SQRexNode *greedystop = (node->next != -1) ? &exp->_nodes[node->next] : nullptr; - SQRexNode *greedystop = nullptr; - SQInteger p0 = (node->right >> 16)&0x0000FFFF, p1 = node->right&0x0000FFFF, nmaches = 0; - const SQChar *s=str, *good = str; - - if(node->next != -1) { - greedystop = &exp->_nodes[node->next]; - } - else { - greedystop = next; - } - - while((nmaches == 0xFFFF || nmaches < p1)) { - - const SQChar *stop; - if(!(s = sqstd_rex_matchnode(exp,&exp->_nodes[node->left],s,greedystop))) - break; - nmaches++; - good=s; - if(greedystop) { - //checks that 0 matches satisfy the expression(if so skips) - //if not would always stop(for instance if is a '?') - if(greedystop->type != OP_GREEDY || - (greedystop->type == OP_GREEDY && ((greedystop->right >> 16)&0x0000FFFF) != 0)) - { - SQRexNode *gnext = nullptr; - if(greedystop->next != -1) { - gnext = &exp->_nodes[greedystop->next]; - }else if(next && next->next != -1){ - gnext = &exp->_nodes[next->next]; - } - stop = sqstd_rex_matchnode(exp,greedystop,s,gnext); - if(stop) { - //if satisfied stop it - if(p0 == p1 && p0 == nmaches) break; - else if(nmaches >= p0 && p1 == 0xFFFF) break; - else if(nmaches >= p0 && nmaches <= p1) break; - } - } - } - - if(s >= exp->_eol) - break; - } - if(p0 == p1 && p0 == nmaches) return good; - else if(nmaches >= p0 && p1 == 0xFFFF) return good; - else if(nmaches >= p0 && nmaches <= p1) return good; - return nullptr; - } - case OP_OR: { - const SQChar *asd = str; - SQRexNode *temp=&exp->_nodes[node->left]; - while( (asd = sqstd_rex_matchnode(exp,temp,asd,nullptr)) ) { - if(temp->next != -1) - temp = &exp->_nodes[temp->next]; - else - return asd; - } - asd = str; - temp = &exp->_nodes[node->right]; - while( (asd = sqstd_rex_matchnode(exp,temp,asd,nullptr)) ) { - if(temp->next != -1) - temp = &exp->_nodes[temp->next]; - else - return asd; - } - return nullptr; - break; - } - case OP_EXPR: - case OP_NOCAPEXPR:{ - SQRexNode *n = &exp->_nodes[node->left]; - const SQChar *cur = str; - SQInteger capture = -1; - if(node->type != OP_NOCAPEXPR && node->right == exp->_currsubexp) { - capture = exp->_currsubexp; - exp->_matches[capture].begin = cur; - exp->_currsubexp++; - } - - do { - SQRexNode *subnext = nullptr; - if(n->next != -1) { - subnext = &exp->_nodes[n->next]; - }else { - subnext = next; - } - if(!(cur = sqstd_rex_matchnode(exp,n,cur,subnext))) { - if(capture != -1){ - exp->_matches[capture].begin = 0; - exp->_matches[capture].len = 0; - } - return nullptr; - } - } while((n->next != -1) && (n = &exp->_nodes[n->next])); - - if(capture != -1) - exp->_matches[capture].len = cur - exp->_matches[capture].begin; - return cur; - } - case OP_WB: - if((str == exp->_bol && !isspace(*str)) - || (str == exp->_eol && !isspace(*(str-1))) - || (!isspace(*str) && isspace(*(str+1))) - || (isspace(*str) && !isspace(*(str+1))) ) { - return (node->left == 'b')?str:nullptr; - } - return (node->left == 'b')?nullptr:str; - case OP_BOL: - if(str == exp->_bol) return str; - return nullptr; - case OP_EOL: - if(str == exp->_eol) return str; - return nullptr; - case OP_DOT:{ - *str++; - } - return str; - case OP_NCLASS: - case OP_CLASS: - if(sqstd_rex_matchclass(exp,&exp->_nodes[node->left],*str)?(type == OP_CLASS?SQTrue:SQFalse):(type == OP_NCLASS?SQTrue:SQFalse)) { - *str++; - return str; - } - return nullptr; - case OP_CCLASS: - if(sqstd_rex_matchcclass(node->left,*str)) { - *str++; - return str; - } - return nullptr; - default: /* char */ - if(*str != (SQChar)node->type) return nullptr; - *str++; - return str; - } - return nullptr; -} - -/* public api */ -SQRex *sqstd_rex_compile(const SQChar *pattern,const SQChar **error) -{ - SQRex *exp = (SQRex *)sq_malloc(sizeof(SQRex)); - exp->_eol = exp->_bol = nullptr; - exp->_p = pattern; - exp->_nallocated = (SQInteger)strlen(pattern) * sizeof(SQChar); - exp->_nodes = (SQRexNode *)sq_malloc(exp->_nallocated * sizeof(SQRexNode)); - exp->_nsize = 0; - exp->_matches = 0; - exp->_nsubexpr = 0; - exp->_first = sqstd_rex_newnode(exp,OP_EXPR); - exp->_error = error; - try { - SQInteger res = sqstd_rex_list(exp); - exp->_nodes[exp->_first].left = res; - if(*exp->_p!='\0') - sqstd_rex_error(exp,"unexpected character"); -#ifdef _DEBUG - { - SQInteger nsize,i; - SQRexNode *t; - nsize = exp->_nsize; - t = &exp->_nodes[0]; - printf("\n"); - /* XXX -- The (int) casts are needed to silent warnings on 64bit systems (SQInteger is 64bit, %d assumes 32bit, (int) is 32bit) */ - for(i = 0;i < nsize; i++) { - if(exp->_nodes[i].type>MAX_CHAR) - printf("[%02d] %10s ",(int)i,g_nnames[exp->_nodes[i].type-MAX_CHAR]); - else - printf("[%02d] %10c ",(int)i,exp->_nodes[i].type); - printf("left %02d right %02d next %02d\n",(int)exp->_nodes[i].left,(int)exp->_nodes[i].right,(int)exp->_nodes[i].next); - } - printf("\n"); - } -#endif - exp->_matches = (SQRexMatch *) sq_malloc(exp->_nsubexpr * sizeof(SQRexMatch)); - memset(exp->_matches,0,exp->_nsubexpr * sizeof(SQRexMatch)); - } - catch (...) { - sqstd_rex_free(exp); - return nullptr; - } - return exp; -} - -void sqstd_rex_free(SQRex *exp) -{ - if(exp) { - if(exp->_nodes) sq_free(exp->_nodes,exp->_nallocated * sizeof(SQRexNode)); - if(exp->_matches) sq_free(exp->_matches,exp->_nsubexpr * sizeof(SQRexMatch)); - sq_free(exp,sizeof(SQRex)); - } -} - -SQBool sqstd_rex_match(SQRex* exp,const SQChar* text) -{ - const SQChar* res = nullptr; - exp->_bol = text; - exp->_eol = text + strlen(text); - exp->_currsubexp = 0; - res = sqstd_rex_matchnode(exp,exp->_nodes,text,nullptr); - if(res == nullptr || res != exp->_eol) - return SQFalse; - return SQTrue; -} - -SQBool sqstd_rex_searchrange(SQRex* exp,const SQChar* text_begin,const SQChar* text_end,const SQChar** out_begin, const SQChar** out_end) -{ - const SQChar *cur = nullptr; - SQInteger node = exp->_first; - if(text_begin >= text_end) return SQFalse; - exp->_bol = text_begin; - exp->_eol = text_end; - do { - cur = text_begin; - while(node != -1) { - exp->_currsubexp = 0; - cur = sqstd_rex_matchnode(exp,&exp->_nodes[node],cur,nullptr); - if(!cur) - break; - node = exp->_nodes[node].next; - } - *text_begin++; - } while(cur == nullptr && text_begin != text_end); - - if(cur == nullptr) - return SQFalse; - - --text_begin; - - if(out_begin) *out_begin = text_begin; - if(out_end) *out_end = cur; - return SQTrue; -} - -SQBool sqstd_rex_search(SQRex* exp,const SQChar* text, const SQChar** out_begin, const SQChar** out_end) -{ - return sqstd_rex_searchrange(exp,text,text + strlen(text),out_begin,out_end); -} - -SQInteger sqstd_rex_getsubexpcount(SQRex* exp) -{ - return exp->_nsubexpr; -} - -SQBool sqstd_rex_getsubexp(SQRex* exp, SQInteger n, SQRexMatch *subexp) -{ - if( n<0 || n >= exp->_nsubexpr) return SQFalse; - *subexp = exp->_matches[n]; - return SQTrue; -} - diff --git a/src/3rdparty/squirrel/sqstdlib/sqstdstring.cpp b/src/3rdparty/squirrel/sqstdlib/sqstdstring.cpp deleted file mode 100644 index f042ac7df8..0000000000 --- a/src/3rdparty/squirrel/sqstdlib/sqstdstring.cpp +++ /dev/null @@ -1,270 +0,0 @@ -/* see copyright notice in squirrel.h */ -#include -#include - -#define scstrchr strchr -#define scatoi atoi -#define scstrtok strtok -#define MAX_FORMAT_LEN 20 -#define MAX_WFORMAT_LEN 3 -#define ADDITIONAL_FORMAT_SPACE (100*sizeof(SQChar)) - -static SQInteger validate_format(HSQUIRRELVM v, SQChar *fmt, const SQChar *src, SQInteger n,SQInteger &width) -{ - SQChar swidth[MAX_WFORMAT_LEN]; - SQInteger wc = 0; - SQInteger start = n; - fmt[0] = '%'; - while (scstrchr("-+ #0", src[n])) n++; - while (isdigit(src[n])) { - swidth[wc] = src[n]; - n++; - wc++; - if(wc>=MAX_WFORMAT_LEN) - return sq_throwerror(v,"width format too long"); - } - swidth[wc] = '\0'; - if(wc > 0) { - width = atoi(swidth); - } - else - width = 0; - if (src[n] == '.') { - n++; - - wc = 0; - while (isdigit(src[n])) { - swidth[wc] = src[n]; - n++; - wc++; - if(wc>=MAX_WFORMAT_LEN) - return sq_throwerror(v,"precision format too long"); - } - swidth[wc] = '\0'; - if(wc > 0) { - width += atoi(swidth); - } - } - if (n-start > MAX_FORMAT_LEN ) - return sq_throwerror(v,"format too long"); - memcpy(&fmt[1],&src[start],((n-start)+1)*sizeof(SQChar)); - fmt[(n-start)+2] = '\0'; - return n; -} - -static void __strip_l(const SQChar *str,const SQChar **start) -{ - const SQChar *t = str; - while(((*t) != '\0') && isspace(*t)){ t++; } - *start = t; -} - -static void __strip_r(const SQChar *str,SQInteger len,const SQChar **end) -{ - if(len == 0) { - *end = str; - return; - } - const SQChar *t = &str[len-1]; - while(t != str && isspace(*t)) { t--; } - *end = t+1; -} - -static SQInteger _string_strip(HSQUIRRELVM v) -{ - const SQChar *str,*start,*end; - sq_getstring(v,2,&str); - SQInteger len = sq_getsize(v,2); - __strip_l(str,&start); - __strip_r(str,len,&end); - sq_pushstring(v,start,end - start); - return 1; -} - -static SQInteger _string_lstrip(HSQUIRRELVM v) -{ - const SQChar *str,*start; - sq_getstring(v,2,&str); - __strip_l(str,&start); - sq_pushstring(v,start,-1); - return 1; -} - -static SQInteger _string_rstrip(HSQUIRRELVM v) -{ - const SQChar *str,*end; - sq_getstring(v,2,&str); - SQInteger len = sq_getsize(v,2); - __strip_r(str,len,&end); - sq_pushstring(v,str,end - str); - return 1; -} - -static SQInteger _string_split(HSQUIRRELVM v) -{ - const SQChar *str,*seps; - SQChar *stemp,*tok; - sq_getstring(v,2,&str); - sq_getstring(v,3,&seps); - if(sq_getsize(v,3) == 0) return sq_throwerror(v,"empty separators string"); - SQInteger memsize = (sq_getsize(v,2)+1)*sizeof(SQChar); - stemp = sq_getscratchpad(v,memsize); - memcpy(stemp,str,memsize); - tok = scstrtok(stemp,seps); - sq_newarray(v,0); - while( tok != nullptr ) { - sq_pushstring(v,tok,-1); - sq_arrayappend(v,-2); - tok = scstrtok( nullptr, seps ); - } - return 1; -} - -#define SETUP_REX(v) \ - SQRex *self = nullptr; \ - sq_getinstanceup(v,1,(SQUserPointer *)&self,0); - -static SQInteger _rexobj_releasehook(SQUserPointer p, SQInteger size) -{ - SQRex *self = ((SQRex *)p); - sqstd_rex_free(self); - return 1; -} - -static SQInteger _regexp_match(HSQUIRRELVM v) -{ - SETUP_REX(v); - const SQChar *str; - sq_getstring(v,2,&str); - if(sqstd_rex_match(self,str) == SQTrue) - { - sq_pushbool(v,SQTrue); - return 1; - } - sq_pushbool(v,SQFalse); - return 1; -} - -static void _addrexmatch(HSQUIRRELVM v,const SQChar *str,const SQChar *begin,const SQChar *end) -{ - sq_newtable(v); - sq_pushstring(v,"begin",-1); - sq_pushinteger(v,begin - str); - sq_rawset(v,-3); - sq_pushstring(v,"end",-1); - sq_pushinteger(v,end - str); - sq_rawset(v,-3); -} - -static SQInteger _regexp_search(HSQUIRRELVM v) -{ - SETUP_REX(v); - const SQChar *str,*begin,*end; - SQInteger start = 0; - sq_getstring(v,2,&str); - if(sq_gettop(v) > 2) sq_getinteger(v,3,&start); - if(sqstd_rex_search(self,str+start,&begin,&end) == SQTrue) { - _addrexmatch(v,str,begin,end); - return 1; - } - return 0; -} - -static SQInteger _regexp_capture(HSQUIRRELVM v) -{ - SETUP_REX(v); - const SQChar *str,*begin,*end; - SQInteger start = 0; - sq_getstring(v,2,&str); - if(sq_gettop(v) > 2) sq_getinteger(v,3,&start); - if(sqstd_rex_search(self,str+start,&begin,&end) == SQTrue) { - SQInteger n = sqstd_rex_getsubexpcount(self); - SQRexMatch match; - sq_newarray(v,0); - for(SQInteger i = 0;i < n; i++) { - sqstd_rex_getsubexp(self,i,&match); - if(match.len > 0) - _addrexmatch(v,str,match.begin,match.begin+match.len); - else - _addrexmatch(v,str,str,str); //empty match - sq_arrayappend(v,-2); - } - return 1; - } - return 0; -} - -static SQInteger _regexp_subexpcount(HSQUIRRELVM v) -{ - SETUP_REX(v); - sq_pushinteger(v,sqstd_rex_getsubexpcount(self)); - return 1; -} - -static SQInteger _regexp_constructor(HSQUIRRELVM v) -{ - const SQChar *error,*pattern; - sq_getstring(v,2,&pattern); - SQRex *rex = sqstd_rex_compile(pattern,&error); - if(!rex) return sq_throwerror(v,error); - sq_setinstanceup(v,1,rex); - sq_setreleasehook(v,1,_rexobj_releasehook); - return 0; -} - -static SQInteger _regexp__typeof(HSQUIRRELVM v) -{ - sq_pushstring(v,"regexp",-1); - return 1; -} - -#define _DECL_REX_FUNC(name,nparams,pmask) {#name,_regexp_##name,nparams,pmask} -static SQRegFunction rexobj_funcs[]={ - _DECL_REX_FUNC(constructor,2,".s"), - _DECL_REX_FUNC(search,-2,"xsn"), - _DECL_REX_FUNC(match,2,"xs"), - _DECL_REX_FUNC(capture,-2,"xsn"), - _DECL_REX_FUNC(subexpcount,1,"x"), - _DECL_REX_FUNC(_typeof,1,"x"), - {0,0,0,0} -}; - -#define _DECL_FUNC(name,nparams,pmask) {#name,_string_##name,nparams,pmask} -static SQRegFunction stringlib_funcs[]={ - _DECL_FUNC(format,-2,".s"), - _DECL_FUNC(strip,2,".s"), - _DECL_FUNC(lstrip,2,".s"), - _DECL_FUNC(rstrip,2,".s"), - _DECL_FUNC(split,3,".ss"), - {0,0,0,0} -}; - - -SQInteger sqstd_register_stringlib(HSQUIRRELVM v) -{ - sq_pushstring(v,"regexp",-1); - sq_newclass(v,SQFalse); - SQInteger i = 0; - while(rexobj_funcs[i].name != 0) { - SQRegFunction &f = rexobj_funcs[i]; - sq_pushstring(v,f.name,-1); - sq_newclosure(v,f.f,0); - sq_setparamscheck(v,f.nparamscheck,f.typemask); - sq_setnativeclosurename(v,-1,f.name); - sq_createslot(v,-3); - i++; - } - sq_createslot(v,-3); - - i = 0; - while(stringlib_funcs[i].name!=0) - { - sq_pushstring(v,stringlib_funcs[i].name,-1); - sq_newclosure(v,stringlib_funcs[i].f,0); - sq_setparamscheck(v,stringlib_funcs[i].nparamscheck,stringlib_funcs[i].typemask); - sq_setnativeclosurename(v,-1,stringlib_funcs[i].name); - sq_createslot(v,-3); - i++; - } - return 1; -} diff --git a/src/3rdparty/squirrel/squirrel/sqapi.cpp b/src/3rdparty/squirrel/squirrel/sqapi.cpp index 189fe67329..18b31ad990 100644 --- a/src/3rdparty/squirrel/squirrel/sqapi.cpp +++ b/src/3rdparty/squirrel/squirrel/sqapi.cpp @@ -18,6 +18,7 @@ #include "sqfuncstate.h" #include "sqclass.h" +#include "../../../core/string_consumer.hpp" #include "../../../string_func.h" #include "../../../safeguards.h" @@ -131,7 +132,7 @@ void sq_close(HSQUIRRELVM v) sq_delete(ss, SQSharedState); } -SQRESULT sq_compile(HSQUIRRELVM v,SQLEXREADFUNC read,SQUserPointer p,const SQChar *sourcename,SQBool raiseerror) +SQRESULT sq_compile(HSQUIRRELVM v,SQLEXREADFUNC read,SQUserPointer p,std::string_view sourcename,SQBool raiseerror) { SQObjectPtr o; if(Compile(v, read, p, sourcename, o, raiseerror != 0, _ss(v)->_debuginfo)) { @@ -172,12 +173,12 @@ SQBool sq_release(HSQUIRRELVM v,HSQOBJECT *po) #endif } -const SQChar *sq_objtostring(HSQOBJECT *o) +std::optional sq_objtostring(HSQOBJECT *o) { if(sq_type(*o) == OT_STRING) { return _stringval(*o); } - return nullptr; + return std::nullopt; } SQInteger sq_objtointeger(HSQOBJECT *o) @@ -209,11 +210,9 @@ void sq_pushnull(HSQUIRRELVM v) v->Push(_null_); } -void sq_pushstring(HSQUIRRELVM v,const SQChar *s,SQInteger len) +void sq_pushstring(HSQUIRRELVM v,std::string_view s) { - if(s) - v->Push(SQObjectPtr(SQString::Create(_ss(v), s, len))); - else v->Push(_null_); + v->Push(SQObjectPtr(SQString::Create(_ss(v), s))); } void sq_pushinteger(HSQUIRRELVM v,SQInteger n) @@ -375,7 +374,7 @@ SQRESULT sq_getclosureinfo(HSQUIRRELVM v,SQInteger idx,SQUnsignedInteger *nparam return sq_throwerror(v,"the object is not a closure"); } -SQRESULT sq_setnativeclosurename(HSQUIRRELVM v,SQInteger idx,const SQChar *name) +SQRESULT sq_setnativeclosurename(HSQUIRRELVM v,SQInteger idx,std::string_view name) { SQObject o = stack_get(v, idx); if(sq_isnativeclosure(o)) { @@ -386,16 +385,16 @@ SQRESULT sq_setnativeclosurename(HSQUIRRELVM v,SQInteger idx,const SQChar *name) return sq_throwerror(v,"the object is not a nativeclosure"); } -SQRESULT sq_setparamscheck(HSQUIRRELVM v,SQInteger nparamscheck,const SQChar *typemask) +SQRESULT sq_setparamscheck(HSQUIRRELVM v,SQInteger nparamscheck,std::optional typemask) { SQObject o = stack_get(v, -1); if(!sq_isnativeclosure(o)) return sq_throwerror(v, "native closure expected"); SQNativeClosure *nc = _nativeclosure(o); nc->_nparamscheck = nparamscheck; - if(typemask) { + if(typemask.has_value()) { SQIntVec res; - if(!CompileTypemask(res, typemask)) + if(!CompileTypemask(res, *typemask)) return sq_throwerror(v, "invalid typemask"); nc->_typecheck.copy(res); } @@ -552,11 +551,11 @@ SQRESULT sq_getbool(HSQUIRRELVM v,SQInteger idx,SQBool *b) return SQ_ERROR; } -SQRESULT sq_getstring(HSQUIRRELVM v,SQInteger idx,const SQChar **c) +SQRESULT sq_getstring(HSQUIRRELVM v,SQInteger idx,std::string_view &str) { SQObjectPtr *o = nullptr; _GETSAFE_OBJ(v, idx, OT_STRING,o); - *c = _stringval(*o); + str = _stringval(*o); return SQ_OK; } @@ -584,7 +583,7 @@ SQInteger sq_getsize(HSQUIRRELVM v, SQInteger idx) SQObjectPtr &o = stack_get(v, idx); SQObjectType type = type(o); switch(type) { - case OT_STRING: return _string(o)->_len; + case OT_STRING: return _string(o)->View().size(); case OT_TABLE: return _table(o)->CountUsed(); case OT_ARRAY: return _array(o)->Size(); case OT_USERDATA: return _userdata(o)->_size; @@ -896,7 +895,7 @@ SQRESULT sq_getstackobj(HSQUIRRELVM v,SQInteger idx,HSQOBJECT *po) return SQ_OK; } -const SQChar *sq_getlocal(HSQUIRRELVM v,SQUnsignedInteger level,SQUnsignedInteger idx) +std::optional sq_getlocal(HSQUIRRELVM v,SQUnsignedInteger level,SQUnsignedInteger idx) { SQUnsignedInteger cstksize=v->_callsstacksize; SQUnsignedInteger lvl=(cstksize-level)-1; @@ -908,7 +907,7 @@ const SQChar *sq_getlocal(HSQUIRRELVM v,SQUnsignedInteger level,SQUnsignedIntege } SQVM::CallInfo &ci=v->_callsstack[lvl]; if(type(ci._closure)!=OT_CLOSURE) - return nullptr; + return std::nullopt; SQClosure *c=_closure(ci._closure); SQFunctionProto *func=_funcproto(c->_function); if(func->_noutervalues > (SQInteger)idx) { @@ -918,7 +917,7 @@ const SQChar *sq_getlocal(HSQUIRRELVM v,SQUnsignedInteger level,SQUnsignedIntege idx -= func->_noutervalues; return func->GetLocal(v,stackbase,idx,(SQInteger)(ci._ip-func->_instructions)-1); } - return nullptr; + return std::nullopt; } void sq_pushobject(HSQUIRRELVM v,HSQOBJECT obj) @@ -931,9 +930,9 @@ void sq_resetobject(HSQOBJECT *po) po->_unVal.pUserPointer=nullptr;po->_type=OT_NULL; } -SQRESULT sq_throwerror(HSQUIRRELVM v,const SQChar *err, SQInteger len) +SQRESULT sq_throwerror(HSQUIRRELVM v,std::string_view error) { - v->_lasterror=SQString::Create(_ss(v),err, len); + v->_lasterror=SQString::Create(_ss(v),error); return -1; } @@ -978,6 +977,10 @@ SQRESULT sq_call(HSQUIRRELVM v,SQInteger params,SQBool retval,SQBool raiseerror, if(!v->_suspended) { v->Pop(params);//pop closure and args } + if (!v->_can_suspend && v->IsOpsTillSuspendError()) { + v->Raise_Error(fmt::format("excessive CPU usage in {}", v->_ops_till_suspend_error_label)); + return SQ_ERROR; + } if(retval){ v->Push(res); return SQ_OK; } @@ -1075,7 +1078,7 @@ SQRESULT sq_readclosure(HSQUIRRELVM v,SQREADFUNC r,SQUserPointer up) return SQ_OK; } -SQChar *sq_getscratchpad(HSQUIRRELVM v,SQInteger minsize) +std::span sq_getscratchpad(HSQUIRRELVM v,SQInteger minsize) { return _ss(v)->GetScratchPad(minsize); } @@ -1089,19 +1092,18 @@ SQInteger sq_collectgarbage(HSQUIRRELVM v) #endif } -const SQChar *sq_getfreevariable(HSQUIRRELVM v,SQInteger idx,SQUnsignedInteger nval) +std::optional sq_getfreevariable(HSQUIRRELVM v,SQInteger idx,SQUnsignedInteger nval) { SQObjectPtr &self = stack_get(v,idx); - const SQChar *name = nullptr; if(type(self) == OT_CLOSURE) { if(_closure(self)->_outervalues.size()>nval) { v->Push(_closure(self)->_outervalues[nval]); SQFunctionProto *fp = _funcproto(_closure(self)->_function); SQOuterVar &ov = fp->_outervalues[nval]; - name = _stringval(ov._name); + return _stringval(ov._name); } } - return name; + return std::nullopt; } SQRESULT sq_setfreevariable(HSQUIRRELVM v,SQInteger idx,SQUnsignedInteger nval) @@ -1253,44 +1255,16 @@ SQRESULT sq_next(HSQUIRRELVM v,SQInteger idx) return SQ_ERROR; } -struct BufState{ - const SQChar *buf; - SQInteger ptr; - SQInteger size; -}; - char32_t buf_lexfeed(SQUserPointer file) { /* Convert an UTF-8 character into a char32_t */ - BufState *buf = (BufState *)file; - const char *p = &buf->buf[buf->ptr]; - - if (buf->size < buf->ptr + 1) return 0; - - /* Read the first character, and get the length based on UTF-8 specs. If invalid, bail out. */ - uint len = Utf8EncodedCharLen(*p); - if (len == 0) { - buf->ptr++; - return -1; - } - - /* Read the remaining bits. */ - if (buf->size < buf->ptr + len) return 0; - buf->ptr += len; - - /* Convert the character, and when definitely invalid, bail out as well. */ - char32_t c; - if (Utf8Decode(&c, p) != len) return -1; - - return c; + StringConsumer &consumer = *reinterpret_cast(file); + return consumer.AnyBytesLeft() ? consumer.ReadUtf8(-1) : 0; } -SQRESULT sq_compilebuffer(HSQUIRRELVM v,const SQChar *s,SQInteger size,const SQChar *sourcename,SQBool raiseerror) { - BufState buf; - buf.buf = s; - buf.size = size; - buf.ptr = 0; - return sq_compile(v, buf_lexfeed, &buf, sourcename, raiseerror); +SQRESULT sq_compilebuffer(HSQUIRRELVM v,std::string_view buffer,std::string_view sourcename,SQBool raiseerror) { + StringConsumer consumer{buffer}; + return sq_compile(v, buf_lexfeed, &consumer, sourcename, raiseerror); } void sq_move(HSQUIRRELVM dest,HSQUIRRELVM src,SQInteger idx) @@ -1323,7 +1297,7 @@ void sq_free(void *p,SQUnsignedInteger size) SQ_FREE(p,size); } -SQOpsLimiter::SQOpsLimiter(HSQUIRRELVM v, SQInteger ops, const char *label) : _v(v) +SQOpsLimiter::SQOpsLimiter(HSQUIRRELVM v, SQInteger ops, std::string_view label) : _v(v) { this->_ops = v->_ops_till_suspend_error_threshold; if (this->_ops == INT64_MIN) { diff --git a/src/3rdparty/squirrel/squirrel/sqbaselib.cpp b/src/3rdparty/squirrel/squirrel/sqbaselib.cpp index 26c02510dd..ba00fde96b 100644 --- a/src/3rdparty/squirrel/squirrel/sqbaselib.cpp +++ b/src/3rdparty/squirrel/squirrel/sqbaselib.cpp @@ -5,6 +5,7 @@ #include "../../../stdafx.h" #include "../../fmt/format.h" +#include "../../../core/string_consumer.hpp" #include "sqpcheader.h" #include "sqvm.h" #include "sqstring.h" @@ -17,19 +18,20 @@ #include "../../../safeguards.h" -bool str2num(const SQChar *s,SQObjectPtr &res) +bool str2num(std::string_view s,SQObjectPtr &res) { - SQChar *end; - if(strstr(s,".")){ - SQFloat r = SQFloat(strtod(s,&end)); - if(s == end) return false; + if(s.find('.') != std::string_view::npos){ + char *end; + std::string str{s}; + SQFloat r = SQFloat(strtod(str.c_str(),&end)); + if(str.c_str() == end) return false; res = r; return true; } else{ - SQInteger r = SQInteger(strtol(s,&end,10)); - if(s == end) return false; - res = r; + auto val = ParseInteger(s); + if (!val.has_value()) return false; + res = *val; return true; } } @@ -101,29 +103,29 @@ static SQInteger base_getstackinfos(HSQUIRRELVM v) SQInteger level; SQStackInfos si; SQInteger seq = 0; - const SQChar *name = nullptr; sq_getinteger(v, -1, &level); if (SQ_SUCCEEDED(sq_stackinfos(v, level, &si))) { - const SQChar *fn = "unknown"; - const SQChar *src = "unknown"; + std::string_view fn = "unknown"; + std::string_view src = "unknown"; if(si.funcname)fn = si.funcname; if(si.source)src = si.source; sq_newtable(v); - sq_pushstring(v, "func", -1); - sq_pushstring(v, fn, -1); + sq_pushstring(v, "func"); + sq_pushstring(v, fn); sq_createslot(v, -3); - sq_pushstring(v, "src", -1); - sq_pushstring(v, src, -1); + sq_pushstring(v, "src"); + sq_pushstring(v, src); sq_createslot(v, -3); - sq_pushstring(v, "line", -1); + sq_pushstring(v, "line"); sq_pushinteger(v, si.line); sq_createslot(v, -3); - sq_pushstring(v, "locals", -1); + sq_pushstring(v, "locals"); sq_newtable(v); seq=0; - while ((name = sq_getlocal(v, level, seq))) { - sq_pushstring(v, name, -1); + std::optional name; + while ((name = sq_getlocal(v, level, seq)).has_value()) { + sq_pushstring(v, *name); sq_push(v, -2); sq_createslot(v, -4); sq_pop(v, 1); @@ -169,9 +171,9 @@ static SQInteger get_slice_params(HSQUIRRELVM v,SQInteger &sidx,SQInteger &eidx, static SQInteger base_print(HSQUIRRELVM v) { - const SQChar *str; + std::string_view str; sq_tostring(v,2); - sq_getstring(v,-1,&str); + sq_getstring(v,-1,str); if(_ss(v)->_printfunc) _ss(v)->_printfunc(v,str); return 0; } @@ -180,14 +182,14 @@ static SQInteger base_print(HSQUIRRELVM v) static SQInteger base_compilestring(HSQUIRRELVM v) { SQInteger nargs=sq_gettop(v); - const SQChar *src=nullptr,*name="unnamedbuffer"; + std::string_view src, name="unnamedbuffer"; SQInteger size; - sq_getstring(v,2,&src); + sq_getstring(v,2,src); size=sq_getsize(v,2); if(nargs>2){ - sq_getstring(v,3,&name); + sq_getstring(v,3,name); } - if(SQ_SUCCEEDED(sq_compilebuffer(v,src,size,name,SQFalse))) + if(SQ_SUCCEEDED(sq_compilebuffer(v,src.substr(0, size),name,SQFalse))) return 1; else return SQ_ERROR; @@ -232,62 +234,59 @@ static SQInteger base_array(HSQUIRRELVM v) static SQInteger base_type(HSQUIRRELVM v) { SQObjectPtr &o = stack_get(v,2); - v->Push(SQString::Create(_ss(v),GetTypeName(o),-1)); + v->Push(SQString::Create(_ss(v),GetTypeName(o))); return 1; } -static SQRegFunction base_funcs[]={ +static const std::initializer_list base_funcs={ //generic #ifdef EXPORT_DEFAULT_SQUIRREL_FUNCTIONS - {"seterrorhandler",base_seterrorhandler,2, nullptr}, - {"setdebughook",base_setdebughook,2, nullptr}, - {"enabledebuginfo",base_enabledebuginfo,2, nullptr}, + {"seterrorhandler",base_seterrorhandler,2, std::nullopt}, + {"setdebughook",base_setdebughook,2, std::nullopt}, + {"enabledebuginfo",base_enabledebuginfo,2, std::nullopt}, {"getstackinfos",base_getstackinfos,2, ".n"}, - {"getroottable",base_getroottable,1, nullptr}, - {"setroottable",base_setroottable,2, nullptr}, - {"getconsttable",base_getconsttable,1, nullptr}, - {"setconsttable",base_setconsttable,2, nullptr}, + {"getroottable",base_getroottable,1, std::nullopt}, + {"setroottable",base_setroottable,2, std::nullopt}, + {"getconsttable",base_getconsttable,1, std::nullopt}, + {"setconsttable",base_setconsttable,2, std::nullopt}, #endif - {"assert",base_assert,2, nullptr}, - {"print",base_print,2, nullptr}, + {"assert",base_assert,2, std::nullopt}, + {"print",base_print,2, std::nullopt}, #ifdef EXPORT_DEFAULT_SQUIRREL_FUNCTIONS {"compilestring",base_compilestring,-2, ".ss"}, {"newthread",base_newthread,2, ".c"}, - {"suspend",base_suspend,-1, nullptr}, + {"suspend",base_suspend,-1, std::nullopt}, #endif {"array",base_array,-2, ".n"}, - {"type",base_type,2, nullptr}, + {"type",base_type,2, std::nullopt}, #ifdef EXPORT_DEFAULT_SQUIRREL_FUNCTIONS - {"dummy",base_dummy,0,nullptr}, + {"dummy",base_dummy,0,std::nullopt}, #ifndef NO_GARBAGE_COLLECTOR {"collectgarbage",base_collectgarbage,1, "t"}, #endif #endif - {nullptr,nullptr,0,nullptr} }; void sq_base_register(HSQUIRRELVM v) { - SQInteger i=0; sq_pushroottable(v); - while(base_funcs[i].name!=nullptr) { - sq_pushstring(v,base_funcs[i].name,-1); - sq_newclosure(v,base_funcs[i].f,0); - sq_setnativeclosurename(v,-1,base_funcs[i].name); - sq_setparamscheck(v,base_funcs[i].nparamscheck,base_funcs[i].typemask); + for(auto &func : base_funcs) { + sq_pushstring(v,func.name); + sq_newclosure(v,func.f,0); + sq_setnativeclosurename(v,-1,func.name); + sq_setparamscheck(v,func.nparamscheck,func.typemask); sq_createslot(v,-3); - i++; } - sq_pushstring(v,"_version_",-1); - sq_pushstring(v,SQUIRREL_VERSION,-1); + sq_pushstring(v,"_version_"); + sq_pushstring(v,SQUIRREL_VERSION); sq_createslot(v,-3); - sq_pushstring(v,"_charsize_",-1); - sq_pushinteger(v,sizeof(SQChar)); + sq_pushstring(v,"_charsize_"); + sq_pushinteger(v,sizeof(char)); sq_createslot(v,-3); - sq_pushstring(v,"_intsize_",-1); + sq_pushstring(v,"_intsize_"); sq_pushinteger(v,sizeof(SQInteger)); sq_createslot(v,-3); - sq_pushstring(v,"_floatsize_",-1); + sq_pushstring(v,"_floatsize_"); sq_pushinteger(v,sizeof(SQFloat)); sq_createslot(v,-3); sq_pop(v,1); @@ -370,8 +369,8 @@ static SQInteger obj_clear(HSQUIRRELVM v) static SQInteger number_delegate_tochar(HSQUIRRELVM v) { SQObject &o=stack_get(v,1); - SQChar c = (SQChar)tointeger(o); - v->Push(SQString::Create(_ss(v),(const SQChar *)&c,1)); + char c = static_cast(tointeger(o)); + v->Push(SQString::Create(_ss(v),std::string_view(&c, 1))); return 1; } @@ -409,16 +408,15 @@ static SQInteger table_rawget(HSQUIRRELVM v) } -SQRegFunction SQSharedState::_table_default_delegate_funcz[]={ +/* static */ const std::initializer_list SQSharedState::_table_default_delegate_funcz={ {"len",default_delegate_len,1, "t"}, {"rawget",table_rawget,2, "t"}, {"rawset",table_rawset,3, "t"}, {"rawdelete",table_rawdelete,2, "t"}, {"rawin",container_rawexists,2, "t"}, - {"weakref",obj_delegate_weakref,1, nullptr }, + {"weakref",obj_delegate_weakref,1, std::nullopt }, {"tostring",default_delegate_tostring,1, "."}, {"clear",obj_clear,1, "."}, - {nullptr,nullptr,0,nullptr} }; //ARRAY DEFAULT DELEGATE/////////////////////////////////////// @@ -611,7 +609,7 @@ static SQInteger array_slice(HSQUIRRELVM v) } -SQRegFunction SQSharedState::_array_default_delegate_funcz[]={ +/* static */ const std::initializer_list SQSharedState::_array_default_delegate_funcz={ {"len",default_delegate_len,1, "a"}, {"append",array_append,2, "a"}, {"extend",array_extend,2, "aa"}, @@ -624,10 +622,9 @@ SQRegFunction SQSharedState::_array_default_delegate_funcz[]={ {"reverse",array_reverse,1, "a"}, {"sort",array_sort,-1, "ac"}, {"slice",array_slice,-1, "ann"}, - {"weakref",obj_delegate_weakref,1, nullptr }, + {"weakref",obj_delegate_weakref,1, std::nullopt }, {"tostring",default_delegate_tostring,1, "."}, {"clear",obj_clear,1, "."}, - {nullptr,nullptr,0,nullptr} }; //STRING DEFAULT DELEGATE////////////////////////// @@ -636,25 +633,25 @@ static SQInteger string_slice(HSQUIRRELVM v) SQInteger sidx,eidx; SQObjectPtr o; if(SQ_FAILED(get_slice_params(v,sidx,eidx,o)))return -1; - SQInteger slen = _string(o)->_len; + SQInteger slen = _string(o)->View().size(); if(sidx < 0)sidx = slen + sidx; if(eidx < 0)eidx = slen + eidx; if(eidx < sidx) return sq_throwerror(v,"wrong indexes"); if(eidx > slen) return sq_throwerror(v,"slice out of range"); - v->Push(SQString::Create(_ss(v),&_stringval(o)[sidx],eidx-sidx)); + v->Push(SQString::Create(_ss(v),_stringval(o).substr(sidx,eidx-sidx))); return 1; } static SQInteger string_find(HSQUIRRELVM v) { SQInteger top,start_idx=0; - const SQChar *str,*substr,*ret; - if(((top=sq_gettop(v))>1) && SQ_SUCCEEDED(sq_getstring(v,1,&str)) && SQ_SUCCEEDED(sq_getstring(v,2,&substr))){ + std::string_view str,substr; + if(((top=sq_gettop(v))>1) && SQ_SUCCEEDED(sq_getstring(v,1,str)) && SQ_SUCCEEDED(sq_getstring(v,2,substr))){ if(top>2)sq_getinteger(v,3,&start_idx); if((sq_getsize(v,1)>start_idx) && (start_idx>=0)){ - ret=strstr(&str[start_idx],substr); - if(ret){ - sq_pushinteger(v,(SQInteger)(ret-str)); + auto ret = str.find(substr, start_idx); + if(ret != std::string_view::npos){ + sq_pushinteger(v,static_cast(ret)); return 1; } } @@ -666,11 +663,11 @@ static SQInteger string_find(HSQUIRRELVM v) #define STRING_TOFUNCZ(func) static SQInteger string_##func(HSQUIRRELVM v) \ { \ SQObject str=stack_get(v,1); \ - SQInteger len=_string(str)->_len; \ - const SQChar *sThis=_stringval(str); \ - SQChar *sNew=(_ss(v)->GetScratchPad(len)); \ - for(SQInteger i=0;iPush(SQString::Create(_ss(v),sNew,len)); \ + std::string_view sThis=_stringval(str); \ + size_t len=sThis.size(); \ + std::span sNew=(_ss(v)->GetScratchPad(len)); \ + for(size_t i=0;iPush(SQString::Create(_ss(v),std::string_view(sNew.data(), len))); \ return 1; \ } @@ -678,7 +675,7 @@ static SQInteger string_find(HSQUIRRELVM v) STRING_TOFUNCZ(tolower) STRING_TOFUNCZ(toupper) -SQRegFunction SQSharedState::_string_default_delegate_funcz[]={ +/* static */ const std::initializer_list SQSharedState::_string_default_delegate_funcz={ {"len",default_delegate_len,1, "s"}, {"tointeger",default_delegate_tointeger,1, "s"}, {"tofloat",default_delegate_tofloat,1, "s"}, @@ -687,18 +684,16 @@ SQRegFunction SQSharedState::_string_default_delegate_funcz[]={ {"find",string_find,-2, "s s n "}, {"tolower",string_tolower,1, "s"}, {"toupper",string_toupper,1, "s"}, - {"weakref",obj_delegate_weakref,1, nullptr }, - {nullptr,nullptr,0,nullptr} + {"weakref",obj_delegate_weakref,1, std::nullopt }, }; //INTEGER DEFAULT DELEGATE////////////////////////// -SQRegFunction SQSharedState::_number_default_delegate_funcz[]={ +/* static */ const std::initializer_list SQSharedState::_number_default_delegate_funcz={ {"tointeger",default_delegate_tointeger,1, "n|b"}, {"tofloat",default_delegate_tofloat,1, "n|b"}, {"tostring",default_delegate_tostring,1, "."}, {"tochar",number_delegate_tochar,1, "n|b"}, - {"weakref",obj_delegate_weakref,1, nullptr }, - {nullptr,nullptr,0,nullptr} + {"weakref",obj_delegate_weakref,1, std::nullopt }, }; //CLOSURE DEFAULT DELEGATE////////////////////////// @@ -749,19 +744,19 @@ static SQInteger closure_getinfos(HSQUIRRELVM v) { _array(params)->Set((SQInteger)n,f->_parameters[n]); } if(f->_varparams) { - _array(params)->Set(nparams-1,SQString::Create(_ss(v),"...",-1)); + _array(params)->Set(nparams-1,SQString::Create(_ss(v),"...")); } - res->NewSlot(SQString::Create(_ss(v),"native",-1),false); - res->NewSlot(SQString::Create(_ss(v),"name",-1),f->_name); - res->NewSlot(SQString::Create(_ss(v),"src",-1),f->_sourcename); - res->NewSlot(SQString::Create(_ss(v),"parameters",-1),params); - res->NewSlot(SQString::Create(_ss(v),"varargs",-1),f->_varparams); + res->NewSlot(SQString::Create(_ss(v),"native"),false); + res->NewSlot(SQString::Create(_ss(v),"name"),f->_name); + res->NewSlot(SQString::Create(_ss(v),"src"),f->_sourcename); + res->NewSlot(SQString::Create(_ss(v),"parameters"),params); + res->NewSlot(SQString::Create(_ss(v),"varargs"),f->_varparams); } else { //OT_NATIVECLOSURE SQNativeClosure *nc = _nativeclosure(o); - res->NewSlot(SQString::Create(_ss(v),"native",-1),true); - res->NewSlot(SQString::Create(_ss(v),"name",-1),nc->_name); - res->NewSlot(SQString::Create(_ss(v),"paramscheck",-1),nc->_nparamscheck); + res->NewSlot(SQString::Create(_ss(v),"native"),true); + res->NewSlot(SQString::Create(_ss(v),"name"),nc->_name); + res->NewSlot(SQString::Create(_ss(v),"paramscheck"),nc->_nparamscheck); SQObjectPtr typecheck; if(!nc->_typecheck.empty()) { typecheck = @@ -770,23 +765,22 @@ static SQInteger closure_getinfos(HSQUIRRELVM v) { _array(typecheck)->Set((SQInteger)n,nc->_typecheck[n]); } } - res->NewSlot(SQString::Create(_ss(v),"typecheck",-1),typecheck); + res->NewSlot(SQString::Create(_ss(v),"typecheck"),typecheck); } v->Push(res); return 1; } -SQRegFunction SQSharedState::_closure_default_delegate_funcz[]={ +/* static */ const std::initializer_list SQSharedState::_closure_default_delegate_funcz={ {"call",closure_call,-1, "c"}, {"pcall",closure_pcall,-1, "c"}, {"acall",closure_acall,2, "ca"}, {"pacall",closure_pacall,2, "ca"}, - {"weakref",obj_delegate_weakref,1, nullptr }, + {"weakref",obj_delegate_weakref,1, std::nullopt }, {"tostring",default_delegate_tostring,1, "."}, {"bindenv",closure_bindenv,2, "c x|y|t"}, {"getinfos",closure_getinfos,1, "c"}, - {nullptr,nullptr,0,nullptr} }; //GENERATOR DEFAULT DELEGATE @@ -801,11 +795,10 @@ static SQInteger generator_getstatus(HSQUIRRELVM v) return 1; } -SQRegFunction SQSharedState::_generator_default_delegate_funcz[]={ +/* static */ const std::initializer_list SQSharedState::_generator_default_delegate_funcz={ {"getstatus",generator_getstatus,1, "g"}, - {"weakref",obj_delegate_weakref,1, nullptr }, + {"weakref",obj_delegate_weakref,1, std::nullopt }, {"tostring",default_delegate_tostring,1, "."}, - {nullptr,nullptr,0,nullptr} }; //THREAD DEFAULT DELEGATE @@ -871,13 +864,13 @@ static SQInteger thread_getstatus(HSQUIRRELVM v) SQObjectPtr &o = stack_get(v,1); switch(sq_getvmstate(_thread(o))) { case SQ_VMSTATE_IDLE: - sq_pushstring(v,"idle",-1); + sq_pushstring(v,"idle"); break; case SQ_VMSTATE_RUNNING: - sq_pushstring(v,"running",-1); + sq_pushstring(v,"running"); break; case SQ_VMSTATE_SUSPENDED: - sq_pushstring(v,"suspended",-1); + sq_pushstring(v,"suspended"); break; default: return sq_throwerror(v,"internal VM error"); @@ -885,13 +878,12 @@ static SQInteger thread_getstatus(HSQUIRRELVM v) return 1; } -SQRegFunction SQSharedState::_thread_default_delegate_funcz[] = { +/* static */ const std::initializer_list SQSharedState::_thread_default_delegate_funcz = { {"call", thread_call, -1, "v"}, {"wakeup", thread_wakeup, -1, "v"}, {"getstatus", thread_getstatus, 1, "v"}, - {"weakref",obj_delegate_weakref,1, nullptr }, + {"weakref",obj_delegate_weakref,1, std::nullopt }, {"tostring",default_delegate_tostring,1, "."}, - {nullptr,nullptr,0,nullptr}, }; static SQInteger class_getattributes(HSQUIRRELVM v) @@ -915,14 +907,13 @@ static SQInteger class_instance(HSQUIRRELVM v) return SQ_ERROR; } -SQRegFunction SQSharedState::_class_default_delegate_funcz[] = { +/* static */ const std::initializer_list SQSharedState::_class_default_delegate_funcz = { {"getattributes", class_getattributes, 2, "y."}, {"setattributes", class_setattributes, 3, "y.."}, {"rawin",container_rawexists,2, "y"}, - {"weakref",obj_delegate_weakref,1, nullptr }, + {"weakref",obj_delegate_weakref,1, std::nullopt }, {"tostring",default_delegate_tostring,1, "."}, {"instance",class_instance,1, "y"}, - {nullptr,nullptr,0,nullptr} }; static SQInteger instance_getclass(HSQUIRRELVM v) @@ -932,12 +923,11 @@ static SQInteger instance_getclass(HSQUIRRELVM v) return SQ_ERROR; } -SQRegFunction SQSharedState::_instance_default_delegate_funcz[] = { +/* static */ const std::initializer_list SQSharedState::_instance_default_delegate_funcz = { {"getclass", instance_getclass, 1, "x"}, {"rawin",container_rawexists,2, "x"}, - {"weakref",obj_delegate_weakref,1, nullptr }, + {"weakref",obj_delegate_weakref,1, std::nullopt }, {"tostring",default_delegate_tostring,1, "."}, - {nullptr,nullptr,0,nullptr} }; static SQInteger weakref_ref(HSQUIRRELVM v) @@ -947,11 +937,10 @@ static SQInteger weakref_ref(HSQUIRRELVM v) return 1; } -SQRegFunction SQSharedState::_weakref_default_delegate_funcz[] = { +/* static */ const std::initializer_list SQSharedState::_weakref_default_delegate_funcz = { {"ref",weakref_ref,1, "r"}, - {"weakref",obj_delegate_weakref,1, nullptr }, + {"weakref",obj_delegate_weakref,1, std::nullopt }, {"tostring",default_delegate_tostring,1, "."}, - {nullptr,nullptr,0,nullptr} }; diff --git a/src/3rdparty/squirrel/squirrel/sqcompiler.cpp b/src/3rdparty/squirrel/squirrel/sqcompiler.cpp index 560edfc66b..2d7c7d5703 100644 --- a/src/3rdparty/squirrel/squirrel/sqcompiler.cpp +++ b/src/3rdparty/squirrel/squirrel/sqcompiler.cpp @@ -57,16 +57,12 @@ typedef sqvector ExpStateVec; class SQCompiler { public: - SQCompiler(SQVM *v, SQLEXREADFUNC rg, SQUserPointer up, const SQChar* sourcename, bool raiseerror, bool lineinfo) : _token(0), _fs(nullptr), _lex(_ss(v), rg, up), _debugline(0), _debugop(0) + SQCompiler(SQVM *v, SQLEXREADFUNC rg, SQUserPointer up, std::string_view sourcename, bool raiseerror, bool lineinfo) : _token(0), _fs(nullptr), _lex(_ss(v), rg, up), _debugline(0), _debugop(0) { _vm=v; _sourcename = SQString::Create(_ss(v), sourcename); _lineinfo = lineinfo;_raiseerror = raiseerror; } - [[noreturn]] void Error(const std::string &msg) - { - throw CompileException(msg); - } void Lex(){ _token = _lex.Lex();} void PushExpState(){ _expstates.push_back(ExpState()); } bool IsDerefToken(SQInteger tok) @@ -92,7 +88,7 @@ public: //do nothing } else { - const SQChar *etypename; + std::string_view etypename; if(tok > 255) { switch(tok) { @@ -109,21 +105,21 @@ public: etypename = "FLOAT"; break; default: - etypename = _lex.Tok2Str(tok); + etypename = _lex.Tok2Str(tok).value_or(""); } - Error(fmt::format("expected '{}'", etypename)); + throw CompileException(fmt::format("expected '{}'", etypename)); } - Error(fmt::format("expected '{:c}'", tok)); + throw CompileException(fmt::format("expected '{:c}'", tok)); } } SQObjectPtr ret; switch(tok) { case TK_IDENTIFIER: - ret = _fs->CreateString(_lex._svalue); + ret = _fs->CreateString(_lex.View()); break; case TK_STRING_LITERAL: - ret = _fs->CreateString(_lex._svalue,_lex._longstr.size()-1); + ret = _fs->CreateString(_lex.View()); break; case TK_INTEGER: ret = SQObjectPtr(_lex._nvalue); @@ -140,7 +136,7 @@ public: { if(_token == ';') { Lex(); return; } if(!IsEndOfStatement()) { - Error("end of statement expected (; or lf)"); + throw CompileException("end of statement expected (; or lf)"); } } void MoveIfCurrentTargetIsLocal() { @@ -233,7 +229,7 @@ public: } break;} case TK_BREAK: - if(_fs->_breaktargets.size() <= 0)Error("'break' has to be in a loop block"); + if(_fs->_breaktargets.size() <= 0)throw CompileException("'break' has to be in a loop block"); if(_fs->_breaktargets.top() > 0){ _fs->AddInstruction(_OP_POPTRAP, _fs->_breaktargets.top(), 0); } @@ -243,7 +239,7 @@ public: Lex(); break; case TK_CONTINUE: - if(_fs->_continuetargets.size() <= 0)Error("'continue' has to be in a loop block"); + if(_fs->_continuetargets.size() <= 0)throw CompileException("'continue' has to be in a loop block"); if(_fs->_continuetargets.top() > 0) { _fs->AddInstruction(_OP_POPTRAP, _fs->_continuetargets.top(), 0); } @@ -356,19 +352,19 @@ public: SQInteger op = _token; SQInteger ds = _exst._deref; bool freevar = _exst._freevar; - if(ds == DEREF_NO_DEREF) Error("can't assign expression"); + if(ds == DEREF_NO_DEREF) throw CompileException("can't assign expression"); Lex(); Expression(); switch(op){ case TK_NEWSLOT: - if(freevar) Error("free variables cannot be modified"); + if(freevar) throw CompileException("free variables cannot be modified"); if(ds == DEREF_FIELD) EmitDerefOp(_OP_NEWSLOT); else //if _derefstate != DEREF_NO_DEREF && DEREF_FIELD so is the index of a local - Error("can't 'create' a local slot"); + throw CompileException("can't 'create' a local slot"); break; case '=': //ASSIGN - if(freevar) Error("free variables cannot be modified"); + if(freevar) throw CompileException("free variables cannot be modified"); if(ds == DEREF_FIELD) EmitDerefOp(_OP_SET); else {//if _derefstate != DEREF_NO_DEREF && DEREF_FIELD so is the index of a local @@ -533,7 +529,7 @@ public: if(_token == TK_PARENT) { Lex(); if(!NeedGet()) - Error("parent cannot be set"); + throw CompileException("parent cannot be set"); SQInteger src = _fs->PopTarget(); _fs->AddInstruction(_OP_GETPARENT, _fs->PushTarget(), src); } @@ -546,7 +542,7 @@ public: } break; case '[': - if(_lex._prevtoken == '\n') Error("cannot brake deref/or comma needed after [exp]=exp slot declaration"); + if(_lex._prevtoken == '\n') throw CompileException("cannot brake deref/or comma needed after [exp]=exp slot declaration"); Lex(); Expression(); Expect(']'); pos = -1; if(NeedGet()) Emit2ArgsOP(_OP_GET); @@ -598,7 +594,7 @@ public: switch(_token) { case TK_STRING_LITERAL: { - _fs->AddInstruction(_OP_LOAD, _fs->PushTarget(), _fs->GetConstant(_fs->CreateString(_lex._svalue,_lex._longstr.size()-1))); + _fs->AddInstruction(_OP_LOAD, _fs->PushTarget(), _fs->GetConstant(_fs->CreateString(_lex.View()))); Lex(); } break; @@ -618,7 +614,7 @@ public: SQObject id; SQObject constant; switch(_token) { - case TK_IDENTIFIER: id = _fs->CreateString(_lex._svalue); break; + case TK_IDENTIFIER: id = _fs->CreateString(_lex.View()); break; case TK_THIS: id = _fs->CreateString("this"); break; case TK_CONSTRUCTOR: id = _fs->CreateString("constructor"); break; } @@ -638,7 +634,7 @@ public: Expect('.'); constid = Expect(TK_IDENTIFIER); if(!_table(constant)->Get(constid,constval)) { constval.Null(); - Error(fmt::format("invalid constant [{}.{}]", _stringval(id),_stringval(constid))); + throw CompileException(fmt::format("invalid constant [{}.{}]", _stringval(id),_stringval(constid))); } } else { @@ -742,7 +738,7 @@ public: case TK_DELEGATE : DelegateExpr(); break; case '(': Lex(); CommaExpr(); Expect(')'); break; - default: Error("expression expected"); + default: throw CompileException("expression expected"); } return -1; } @@ -771,7 +767,7 @@ public: nargs++; if(_token == ','){ Lex(); - if(_token == ')') Error("expression expected, found ')'"); + if(_token == ')') throw CompileException("expression expected, found ')'"); } } Lex(); @@ -1082,13 +1078,13 @@ public: _exst._funcarg = false; PrefixedExpr(); es = PopExpState(); - if(es._deref == DEREF_NO_DEREF) Error("invalid class name"); + if(es._deref == DEREF_NO_DEREF) throw CompileException("invalid class name"); if(es._deref == DEREF_FIELD) { ClassExp(); EmitDerefOp(_OP_NEWSLOT); _fs->PopTarget(); } - else Error("cannot create a class in a local with the syntax(class )"); + else throw CompileException("cannot create a class in a local with the syntax(class )"); } SQObject ExpectScalar() { @@ -1103,7 +1099,7 @@ public: val._unVal.fFloat = _lex._fvalue; break; case TK_STRING_LITERAL: - val = _fs->CreateString(_lex._svalue,_lex._longstr.size()-1); + val = _fs->CreateString(_lex.View()); break; case '-': Lex(); @@ -1118,12 +1114,12 @@ public: val._unVal.fFloat = -_lex._fvalue; break; default: - Error("scalar expected : integer,float"); + throw CompileException("scalar expected : integer,float"); val._type = OT_NULL; // Silent compile-warning } break; default: - Error("scalar expected : integer,float or string"); + throw CompileException("scalar expected : integer,float or string"); val._type = OT_NULL; // Silent compile-warning } Lex(); @@ -1226,9 +1222,9 @@ public: _exst._funcarg = false; PrefixedExpr(); es = PopExpState(); - if(es._deref == DEREF_NO_DEREF) Error("can't delete an expression"); + if(es._deref == DEREF_NO_DEREF) throw CompileException("can't delete an expression"); if(es._deref == DEREF_FIELD) Emit2ArgsOP(_OP_DELETE); - else Error("cannot delete a local"); + else throw CompileException("cannot delete a local"); } void PrefixIncDec(SQInteger token) { @@ -1255,10 +1251,10 @@ public: SQInteger defparams = 0; while(_token!=')') { if(_token == TK_VARPARAMS) { - if(defparams > 0) Error("function with default parameters cannot have variable number of parameters"); + if(defparams > 0) throw CompileException("function with default parameters cannot have variable number of parameters"); funcstate->_varparams = true; Lex(); - if(_token != ')') Error("expected ')'"); + if(_token != ')') throw CompileException("expected ')'"); break; } else { @@ -1271,10 +1267,10 @@ public: defparams++; } else { - if(defparams > 0) Error("expected '='"); + if(defparams > 0) throw CompileException("expected '='"); } if(_token == ',') Lex(); - else if(_token != ')') Error("expected ')' or ','"); + else if(_token != ')') throw CompileException("expected ')' or ','"); } } Expect(')'); @@ -1289,7 +1285,7 @@ public: //outers are treated as implicit local variables funcstate->AddOuterValue(paramname); if(_token == ',') Lex(); - else if(_token != ')') Error("expected ')' or ','"); + else if(_token != ')') throw CompileException("expected ')' or ','"); } Lex(); } @@ -1346,7 +1342,7 @@ private: SQVM *_vm; }; -bool Compile(SQVM *vm,SQLEXREADFUNC rg, SQUserPointer up, const SQChar *sourcename, SQObjectPtr &out, bool raiseerror, bool lineinfo) +bool Compile(SQVM *vm,SQLEXREADFUNC rg, SQUserPointer up, std::string_view sourcename, SQObjectPtr &out, bool raiseerror, bool lineinfo) { SQCompiler p(vm, rg, up, sourcename, raiseerror, lineinfo); return p.Compile(out); diff --git a/src/3rdparty/squirrel/squirrel/sqcompiler.h b/src/3rdparty/squirrel/squirrel/sqcompiler.h index cdb6b1d65c..de3f2eb215 100644 --- a/src/3rdparty/squirrel/squirrel/sqcompiler.h +++ b/src/3rdparty/squirrel/squirrel/sqcompiler.h @@ -73,5 +73,5 @@ struct SQVM; using CompileException = std::runtime_error; -bool Compile(SQVM *vm, SQLEXREADFUNC rg, SQUserPointer up, const SQChar *sourcename, SQObjectPtr &out, bool raiseerror, bool lineinfo); +bool Compile(SQVM *vm, SQLEXREADFUNC rg, SQUserPointer up, std::string_view sourcename, SQObjectPtr &out, bool raiseerror, bool lineinfo); #endif //_SQCOMPILER_H_ diff --git a/src/3rdparty/squirrel/squirrel/sqdebug.cpp b/src/3rdparty/squirrel/squirrel/sqdebug.cpp index 8a6f15121a..cc5c21316c 100644 --- a/src/3rdparty/squirrel/squirrel/sqdebug.cpp +++ b/src/3rdparty/squirrel/squirrel/sqdebug.cpp @@ -37,7 +37,7 @@ SQRESULT sq_stackinfos(HSQUIRRELVM v, SQInteger level, SQStackInfos *si) { SQInteger cssize = v->_callsstacksize; if (cssize > level) { - memset(si, 0, sizeof(SQStackInfos)); + *si = {}; SQVM::CallInfo &ci = v->_callsstack[cssize-level-1]; switch (type(ci._closure)) { case OT_CLOSURE:{ @@ -101,15 +101,15 @@ void SQVM::Raise_CompareError(const SQObject &o1, const SQObject &o2) void SQVM::Raise_ParamTypeError(SQInteger nparam,SQInteger typemask,SQInteger type) { - SQObjectPtr exptypes = SQString::Create(_ss(this), "", -1); + SQObjectPtr exptypes = SQString::Create(_ss(this), ""); SQInteger found = 0; for(SQInteger i=0; i<16; i++) { SQInteger mask = 0x00000001LL << i; if(typemask & (mask)) { - if(found>0) StringCat(exptypes,SQString::Create(_ss(this), "|", -1), exptypes); + if(found>0) StringCat(exptypes,SQString::Create(_ss(this), "|"), exptypes); found ++; - StringCat(exptypes,SQString::Create(_ss(this), IdType2Name((SQObjectType)mask), -1), exptypes); + StringCat(exptypes,SQString::Create(_ss(this), IdType2Name((SQObjectType)mask)), exptypes); } } Raise_Error(fmt::format("parameter {} has an invalid type '{}' ; expected: '{}'", nparam, IdType2Name((SQObjectType)type), _stringval(exptypes))); diff --git a/src/3rdparty/squirrel/squirrel/sqfuncproto.h b/src/3rdparty/squirrel/squirrel/sqfuncproto.h index b65b040b49..a2cdb94fd1 100644 --- a/src/3rdparty/squirrel/squirrel/sqfuncproto.h +++ b/src/3rdparty/squirrel/squirrel/sqfuncproto.h @@ -114,7 +114,7 @@ public: this->~SQFunctionProto(); sq_vm_free(this,size); } - const SQChar* GetLocal(SQVM *v,SQUnsignedInteger stackbase,SQUnsignedInteger nseq,SQUnsignedInteger nop); + std::optional GetLocal(SQVM *v,SQUnsignedInteger stackbase,SQUnsignedInteger nseq,SQUnsignedInteger nop); SQInteger GetLine(SQInstruction *curr); bool Save(SQVM *v,SQUserPointer up,SQWRITEFUNC write); static bool Load(SQVM *v,SQUserPointer up,SQREADFUNC read,SQObjectPtr &ret); diff --git a/src/3rdparty/squirrel/squirrel/sqfuncstate.cpp b/src/3rdparty/squirrel/squirrel/sqfuncstate.cpp index 54744292a9..790805a3b5 100644 --- a/src/3rdparty/squirrel/squirrel/sqfuncstate.cpp +++ b/src/3rdparty/squirrel/squirrel/sqfuncstate.cpp @@ -110,11 +110,6 @@ SQFuncState::SQFuncState(SQSharedState *ss,SQFuncState *parent) } -void SQFuncState::Error(const SQChar *err) -{ - throw CompileException(err); -} - #ifdef _DEBUG_DUMP void SQFuncState::Dump(SQFunctionProto *func) { @@ -234,7 +229,7 @@ SQInteger SQFuncState::GetConstant(const SQObject &cons) _nliterals++; if(_nliterals > MAX_LITERALS) { val.Null(); - Error("internal compiler error: too many literals"); + throw CompileException("internal compiler error: too many literals"); } } return _integer(val); @@ -264,7 +259,7 @@ SQInteger SQFuncState::AllocStackPos() SQInteger npos=_vlocals.size(); _vlocals.push_back(SQLocalVarInfo()); if(_vlocals.size()>((SQUnsignedInteger)_stacksize)) { - if(_stacksize>MAX_FUNC_STACKSIZE) Error("internal compiler error: too many locals"); + if(_stacksize>MAX_FUNC_STACKSIZE) throw CompileException("internal compiler error: too many locals"); _stacksize=_vlocals.size(); } return npos; @@ -499,9 +494,9 @@ void SQFuncState::AddInstruction(SQInstruction &i) _instructions.push_back(i); } -SQObject SQFuncState::CreateString(const SQChar *s,SQInteger len) +SQObject SQFuncState::CreateString(std::string_view s) { - SQObjectPtr ns(SQString::Create(_sharedstate,s,len)); + SQObjectPtr ns(SQString::Create(_sharedstate,s)); _table(_strings)->NewSlot(ns,(SQInteger)1); return std::move(ns); } @@ -539,7 +534,7 @@ SQFunctionProto *SQFuncState::BuildProto() for(SQUnsignedInteger no = 0; no < _lineinfos.size(); no++) f->_lineinfos[no] = _lineinfos[no]; for(SQUnsignedInteger no = 0; no < _defaultparams.size(); no++) f->_defaultparams[no] = _defaultparams[no]; - memcpy(f->_instructions,&_instructions[0],(size_t)_instructions.size()*sizeof(SQInstruction)); + std::copy_n(&_instructions[0], _instructions.size(), f->_instructions); f->_varparams = _varparams; diff --git a/src/3rdparty/squirrel/squirrel/sqfuncstate.h b/src/3rdparty/squirrel/squirrel/sqfuncstate.h index 739eb16261..d67f6dcc91 100644 --- a/src/3rdparty/squirrel/squirrel/sqfuncstate.h +++ b/src/3rdparty/squirrel/squirrel/sqfuncstate.h @@ -11,7 +11,6 @@ struct SQFuncState #ifdef _DEBUG_DUMP void Dump(SQFunctionProto *func); #endif - [[noreturn]] void Error(const SQChar *err); SQFuncState *PushChildState(SQSharedState *ss); void PopChildState(); void AddInstruction(SQOpcode _op,SQInteger arg0=0,SQInteger arg1=0,SQInteger arg2=0,SQInteger arg3=0){SQInstruction i(_op,arg0,arg1,arg2,arg3);AddInstruction(i);} @@ -43,7 +42,7 @@ struct SQFuncState SQInteger TopTarget(); SQInteger GetUpTarget(SQInteger n); bool IsLocal(SQUnsignedInteger stkpos); - SQObject CreateString(const SQChar *s,SQInteger len = -1); + SQObject CreateString(std::string_view s); SQObject CreateTable(); bool IsConstant(const SQObject &name,SQObject &e); SQInteger _returnexp; diff --git a/src/3rdparty/squirrel/squirrel/sqlexer.cpp b/src/3rdparty/squirrel/squirrel/sqlexer.cpp index 89eae0026e..6dadfa36d1 100644 --- a/src/3rdparty/squirrel/squirrel/sqlexer.cpp +++ b/src/3rdparty/squirrel/squirrel/sqlexer.cpp @@ -12,6 +12,7 @@ #include "sqlexer.h" #include "../../../core/utf8.hpp" +#include "../../../core/string_consumer.hpp" #include "../../../safeguards.h" @@ -84,22 +85,16 @@ SQLexer::SQLexer(SQSharedState *ss, SQLEXREADFUNC rg, SQUserPointer up) _prevtoken = -1; _curtoken = -1; - _svalue = nullptr; _nvalue = 0; _fvalue = 0; Next(); } -[[noreturn]] void SQLexer::Error(const SQChar *err) -{ - throw CompileException(err); -} - void SQLexer::Next() { char32_t t = _readf(_up); - if(t > MAX_CHAR) Error("Invalid character"); + if(t > MAX_CHAR) throw CompileException("Invalid character"); if(t != 0) { _currdata = t; return; @@ -107,7 +102,7 @@ void SQLexer::Next() _currdata = SQUIRREL_EOB; } -const SQChar *SQLexer::Tok2Str(SQInteger tok) +std::optional SQLexer::Tok2Str(SQInteger tok) { SQObjectPtr itr, key, val; SQInteger nitr; @@ -116,7 +111,7 @@ const SQChar *SQLexer::Tok2Str(SQInteger tok) if(((SQInteger)_integer(val)) == tok) return _stringval(key); } - return nullptr; + return std::nullopt; } void SQLexer::LexBlockComment() @@ -126,7 +121,7 @@ void SQLexer::LexBlockComment() switch(CUR_CHAR) { case '*': { NEXT(); if(CUR_CHAR == '/') { done = true; NEXT(); }}; continue; case '\n': _currentline++; NEXT(); continue; - case SQUIRREL_EOB: Error("missing \"*/\" in comment"); + case SQUIRREL_EOB: throw CompileException("missing \"*/\" in comment"); default: NEXT(); } } @@ -195,11 +190,11 @@ SQInteger SQLexer::Lex() SQInteger stype; NEXT(); if(CUR_CHAR != '"') - Error("string expected"); + throw CompileException("string expected"); if((stype=ReadString('"',true))!=-1) { RETURN_TOKEN(stype); } - Error("error parsing the string"); + throw CompileException("error parsing the string"); } case '"': case '\'': { @@ -207,7 +202,7 @@ SQInteger SQLexer::Lex() if((stype=ReadString(CUR_CHAR,false))!=-1){ RETURN_TOKEN(stype); } - Error("error parsing the string"); + throw CompileException("error parsing the string"); } case '{': case '}': case '(': case ')': case '[': case ']': case ';': case ',': case '?': case '^': case '~': @@ -217,7 +212,7 @@ SQInteger SQLexer::Lex() NEXT(); if (CUR_CHAR != '.'){ RETURN_TOKEN('.') } NEXT(); - if (CUR_CHAR != '.'){ Error("invalid token '..'"); } + if (CUR_CHAR != '.'){ throw CompileException("invalid token '..'"); } NEXT(); RETURN_TOKEN(TK_VARPARAMS); case '&': @@ -263,7 +258,7 @@ SQInteger SQLexer::Lex() } else { SQInteger c = CUR_CHAR; - if (iscntrl((int)c)) Error("unexpected character(control)"); + if (iscntrl((int)c)) throw CompileException("unexpected character(control)"); NEXT(); RETURN_TOKEN(c); } @@ -274,7 +269,7 @@ SQInteger SQLexer::Lex() return 0; } -SQInteger SQLexer::GetIDType(SQChar *s) +SQInteger SQLexer::GetIDType(std::string_view s) { SQObjectPtr t; if(_keywords->Get(SQString::Create(_sharedstate, s), t)) { @@ -293,10 +288,10 @@ SQInteger SQLexer::ReadString(char32_t ndelim,bool verbatim) while(CUR_CHAR != ndelim) { switch(CUR_CHAR) { case SQUIRREL_EOB: - Error("unfinished string"); + throw CompileException("unfinished string"); return -1; case '\n': - if(!verbatim) Error("newline in a constant"); + if(!verbatim) throw CompileException("newline in a constant"); APPEND_CHAR(CUR_CHAR); NEXT(); _currentline++; break; @@ -308,18 +303,18 @@ SQInteger SQLexer::ReadString(char32_t ndelim,bool verbatim) NEXT(); switch(CUR_CHAR) { case 'x': NEXT(); { - if(!isxdigit(CUR_CHAR)) Error("hexadecimal number expected"); + if(!isxdigit(CUR_CHAR)) throw CompileException("hexadecimal number expected"); const SQInteger maxdigits = 4; - SQChar temp[maxdigits+1]; - SQInteger n = 0; + char temp[maxdigits]; + size_t n = 0; while(isxdigit(CUR_CHAR) && n < maxdigits) { temp[n] = CUR_CHAR; n++; NEXT(); } - temp[n] = 0; - SQChar *sTemp; - APPEND_CHAR((SQChar)strtoul(temp,&sTemp,16)); + auto val = ParseInteger(std::string_view{temp, n}, 16); + if (!val.has_value()) throw CompileException("hexadecimal number expected"); + APPEND_CHAR(static_cast(*val)); } break; case 't': APPEND_CHAR('\t'); NEXT(); break; @@ -334,7 +329,7 @@ SQInteger SQLexer::ReadString(char32_t ndelim,bool verbatim) case '"': APPEND_CHAR('"'); NEXT(); break; case '\'': APPEND_CHAR('\''); NEXT(); break; default: - Error("unrecognised escaper char"); + throw CompileException("unrecognised escaper char"); break; } } @@ -353,49 +348,16 @@ SQInteger SQLexer::ReadString(char32_t ndelim,bool verbatim) break; } } - TERMINATE_BUFFER(); - SQInteger len = _longstr.size()-1; if(ndelim == '\'') { - if(len == 0) Error("empty constant"); - if(len > 1) Error("constant too long"); + if(_longstr.empty()) throw CompileException("empty constant"); + if(_longstr.size() > 1) throw CompileException("constant too long"); _nvalue = _longstr[0]; return TK_INTEGER; } - _svalue = &_longstr[0]; return TK_STRING_LITERAL; } -void LexHexadecimal(const SQChar *s,SQUnsignedInteger *res) -{ - *res = 0; - while(*s != 0) - { - if(isdigit(*s)) *res = (*res)*16+((*s++)-'0'); - else if(isxdigit(*s)) *res = (*res)*16+(toupper(*s++)-'A'+10); - else { assert(0); } - } -} - -void LexInteger(const SQChar *s,SQUnsignedInteger *res) -{ - *res = 0; - while(*s != 0) - { - *res = (*res)*10+((*s++)-'0'); - } -} - -SQInteger scisodigit(SQChar c) { return c >= '0' && c <= '7'; } - -void LexOctal(const SQChar *s,SQUnsignedInteger *res) -{ - *res = 0; - while(*s != 0) - { - if(scisodigit(*s)) *res = (*res)*8+((*s++)-'0'); - else { assert(0); } - } -} +SQInteger scisodigit(char c) { return c >= '0' && c <= '7'; } SQInteger isexponent(SQInteger c) { return c == 'e' || c=='E'; } @@ -409,7 +371,6 @@ SQInteger SQLexer::ReadNumber() #define TSCIENTIFIC 4 #define TOCTAL 5 SQInteger type = TINT, firstchar = CUR_CHAR; - SQChar *sTemp; INIT_TEMP_STRING(); NEXT(); if(firstchar == '0' && (toupper(CUR_CHAR) == 'X' || scisodigit(CUR_CHAR)) ) { @@ -419,7 +380,7 @@ SQInteger SQLexer::ReadNumber() APPEND_CHAR(CUR_CHAR); NEXT(); } - if(isdigit(CUR_CHAR)) Error("invalid octal number"); + if(isdigit(CUR_CHAR)) throw CompileException("invalid octal number"); } else { NEXT(); @@ -428,7 +389,7 @@ SQInteger SQLexer::ReadNumber() APPEND_CHAR(CUR_CHAR); NEXT(); } - if(_longstr.size() > MAX_HEX_DIGITS) Error("too many digits for an Hex number"); + if(_longstr.size() > MAX_HEX_DIGITS) throw CompileException("too many digits for an Hex number"); } } else { @@ -436,7 +397,7 @@ SQInteger SQLexer::ReadNumber() while (CUR_CHAR == '.' || isdigit(CUR_CHAR) || isexponent(CUR_CHAR)) { if(CUR_CHAR == '.' || isexponent(CUR_CHAR)) type = TFLOAT; if(isexponent(CUR_CHAR)) { - if(type != TFLOAT) Error("invalid numeric format"); + if(type != TFLOAT) throw CompileException("invalid numeric format"); type = TSCIENTIFIC; APPEND_CHAR(CUR_CHAR); NEXT(); @@ -444,27 +405,29 @@ SQInteger SQLexer::ReadNumber() APPEND_CHAR(CUR_CHAR); NEXT(); } - if(!isdigit(CUR_CHAR)) Error("exponent expected"); + if(!isdigit(CUR_CHAR)) throw CompileException("exponent expected"); } APPEND_CHAR(CUR_CHAR); NEXT(); } } - TERMINATE_BUFFER(); switch(type) { case TSCIENTIFIC: - case TFLOAT: - _fvalue = (SQFloat)strtod(&_longstr[0],&sTemp); + case TFLOAT: { + std::string str{View()}; + char *sTemp; + _fvalue = (SQFloat)strtod(str.c_str(),&sTemp); return TK_FLOAT; + } case TINT: - LexInteger(&_longstr[0],(SQUnsignedInteger *)&_nvalue); + _nvalue = ParseInteger(View(), 10).value_or(0); return TK_INTEGER; case THEX: - LexHexadecimal(&_longstr[0],(SQUnsignedInteger *)&_nvalue); + _nvalue = ParseInteger(View(), 16).value_or(0); return TK_INTEGER; case TOCTAL: - LexOctal(&_longstr[0],(SQUnsignedInteger *)&_nvalue); + _nvalue = ParseInteger(View(), 8).value_or(0); return TK_INTEGER; } return 0; @@ -478,10 +441,6 @@ SQInteger SQLexer::ReadID() APPEND_CHAR(CUR_CHAR); NEXT(); } while(isalnum(CUR_CHAR) || CUR_CHAR == '_'); - TERMINATE_BUFFER(); - res = GetIDType(&_longstr[0]); - if(res == TK_IDENTIFIER || res == TK_CONSTRUCTOR) { - _svalue = &_longstr[0]; - } + res = GetIDType(View()); return res; } diff --git a/src/3rdparty/squirrel/squirrel/sqlexer.h b/src/3rdparty/squirrel/squirrel/sqlexer.h index 9ce9469b24..0281e9dff8 100644 --- a/src/3rdparty/squirrel/squirrel/sqlexer.h +++ b/src/3rdparty/squirrel/squirrel/sqlexer.h @@ -6,11 +6,10 @@ struct SQLexer { ~SQLexer(); SQLexer(SQSharedState *ss,SQLEXREADFUNC rg,SQUserPointer up); - [[noreturn]] void Error(const SQChar *err); SQInteger Lex(); - const SQChar *Tok2Str(SQInteger tok); + std::optional Tok2Str(SQInteger tok); private: - SQInteger GetIDType(SQChar *s); + SQInteger GetIDType(std::string_view s); SQInteger ReadString(char32_t ndelim,bool verbatim); SQInteger ReadNumber(); void LexBlockComment(); @@ -20,21 +19,21 @@ private: SQTable *_keywords; void INIT_TEMP_STRING() { _longstr.resize(0); } void APPEND_CHAR(char32_t c); - void TERMINATE_BUFFER() { _longstr.push_back('\0'); } public: SQInteger _prevtoken; SQInteger _currentline; SQInteger _lasttokenline; SQInteger _currentcolumn; - const SQChar *_svalue; SQInteger _nvalue; SQFloat _fvalue; SQLEXREADFUNC _readf; SQUserPointer _up; char32_t _currdata; SQSharedState *_sharedstate; - sqvector _longstr; + sqvector _longstr; + + std::string_view View() const { return std::string_view(_longstr._vals, _longstr.size()); } }; #endif diff --git a/src/3rdparty/squirrel/squirrel/sqobject.cpp b/src/3rdparty/squirrel/squirrel/sqobject.cpp index a0f9c75363..9fc3ae3545 100644 --- a/src/3rdparty/squirrel/squirrel/sqobject.cpp +++ b/src/3rdparty/squirrel/squirrel/sqobject.cpp @@ -18,7 +18,7 @@ #include "../../../safeguards.h" -const SQChar *IdType2Name(SQObjectType type) +std::string_view IdType2Name(SQObjectType type) { switch(_RAW_TYPE(type)) { @@ -42,25 +42,25 @@ const SQChar *IdType2Name(SQObjectType type) case _RT_INSTANCE: return "instance"; case _RT_WEAKREF: return "weakref"; default: - return nullptr; + NOT_REACHED(); } } -const SQChar *GetTypeName(const SQObjectPtr &obj1) +std::string_view GetTypeName(const SQObjectPtr &obj1) { return IdType2Name(type(obj1)); } -SQString *SQString::Create(SQSharedState *ss,const SQChar *s,SQInteger len) +SQString *SQString::Create(SQSharedState *ss,std::string_view s) { - SQString *str=ADD_STRING(ss,s,len); + SQString *str=ss->_stringtable->Add(s); str->_sharedstate=ss; return str; } void SQString::Release() { - REMOVE_STRING(_sharedstate,this); + _sharedstate->_stringtable->Remove(this); } SQInteger SQString::Next(const SQObjectPtr &refpos, SQObjectPtr &outkey, SQObjectPtr &outval) @@ -202,24 +202,21 @@ void SQArray::Extend(const SQArray *a){ Append(a->_values[i]); } -const SQChar* SQFunctionProto::GetLocal(SQVM *vm,SQUnsignedInteger stackbase,SQUnsignedInteger nseq,SQUnsignedInteger nop) +std::optional SQFunctionProto::GetLocal(SQVM *vm,SQUnsignedInteger stackbase,SQUnsignedInteger nseq,SQUnsignedInteger nop) { SQUnsignedInteger nvars=_nlocalvarinfos; - const SQChar *res=nullptr; if(nvars>=nseq){ for(SQUnsignedInteger i=0;i=nop) - { + if(_localvarinfos[i]._start_op<=nop && _localvarinfos[i]._end_op>=nop) { if(nseq==0){ vm->Push(vm->_stack[stackbase+_localvarinfos[i]._pos]); - res=_stringval(_localvarinfos[i]._name); - break; + return _stringval(_localvarinfos[i]._name); } nseq--; } } } - return res; + return std::nullopt; } SQInteger SQFunctionProto::GetLine(SQInstruction *curr) @@ -273,10 +270,13 @@ bool WriteObject(HSQUIRRELVM v,SQUserPointer up,SQWRITEFUNC write,SQObjectPtr &o { _CHECK_IO(SafeWrite(v,write,up,&type(o),sizeof(SQObjectType))); switch(type(o)){ - case OT_STRING: - _CHECK_IO(SafeWrite(v,write,up,&_string(o)->_len,sizeof(SQInteger))); - _CHECK_IO(SafeWrite(v,write,up,_stringval(o),_string(o)->_len)); + case OT_STRING: { + auto str = _string(o)->Span(); + SQInteger len = str.size(); + _CHECK_IO(SafeWrite(v,write,up,&len,sizeof(len))); + _CHECK_IO(SafeWrite(v,write,up,str.data(),len)); break; + } case OT_INTEGER: _CHECK_IO(SafeWrite(v,write,up,&_integer(o),sizeof(SQInteger)));break; case OT_FLOAT: @@ -297,11 +297,11 @@ bool ReadObject(HSQUIRRELVM v,SQUserPointer up,SQREADFUNC read,SQObjectPtr &o) switch(t){ case OT_STRING:{ SQInteger len; - _CHECK_IO(SafeRead(v,read,up,&len,sizeof(SQInteger))); - _CHECK_IO(SafeRead(v,read,up,_ss(v)->GetScratchPad(len),len)); - o=SQString::Create(_ss(v),_ss(v)->GetScratchPad(-1),len); - } + _CHECK_IO(SafeRead(v,read,up,&len,sizeof(len))); + _CHECK_IO(SafeRead(v,read,up,_ss(v)->GetScratchPad(len).data(),len)); + o=SQString::Create(_ss(v),std::string_view(_ss(v)->GetScratchPad(-1).data(),len)); break; + } case OT_INTEGER:{ SQInteger i; _CHECK_IO(SafeRead(v,read,up,&i,sizeof(SQInteger))); o = i; break; @@ -323,7 +323,7 @@ bool ReadObject(HSQUIRRELVM v,SQUserPointer up,SQREADFUNC read,SQObjectPtr &o) bool SQClosure::Save(SQVM *v,SQUserPointer up,SQWRITEFUNC write) { _CHECK_IO(WriteTag(v,write,up,SQ_CLOSURESTREAM_HEAD)); - _CHECK_IO(WriteTag(v,write,up,sizeof(SQChar))); + _CHECK_IO(WriteTag(v,write,up,sizeof(char))); _CHECK_IO(_funcproto(_function)->Save(v,up,write)); _CHECK_IO(WriteTag(v,write,up,SQ_CLOSURESTREAM_TAIL)); return true; @@ -332,7 +332,7 @@ bool SQClosure::Save(SQVM *v,SQUserPointer up,SQWRITEFUNC write) bool SQClosure::Load(SQVM *v,SQUserPointer up,SQREADFUNC read,SQObjectPtr &ret) { _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_HEAD)); - _CHECK_IO(CheckTag(v,read,up,sizeof(SQChar))); + _CHECK_IO(CheckTag(v,read,up,sizeof(char))); SQObjectPtr func; _CHECK_IO(SQFunctionProto::Load(v,up,read,func)); _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_TAIL)); diff --git a/src/3rdparty/squirrel/squirrel/sqobject.h b/src/3rdparty/squirrel/squirrel/sqobject.h index 3b3c516067..d9bce1732d 100644 --- a/src/3rdparty/squirrel/squirrel/sqobject.h +++ b/src/3rdparty/squirrel/squirrel/sqobject.h @@ -138,7 +138,7 @@ struct SQObjectPtr; #define _refcounted(obj) ((obj)._unVal.pRefCounted) #define _rawval(obj) ((obj)._unVal.raw) -#define _stringval(obj) (obj)._unVal.pString->_val +#define _stringval(obj) (obj)._unVal.pString->View() #define _userdataval(obj) (obj)._unVal.pUserData->_val #define tofloat(num) ((type(num)==OT_INTEGER)?(SQFloat)_integer(num):_float(num)) @@ -357,7 +357,7 @@ struct SQObjectPtr : public SQObject return *this; } private: - SQObjectPtr(const SQChar *){} //safety + SQObjectPtr(const char *) = delete; //safety }; inline void _Swap(SQObject &a,SQObject &b) @@ -449,8 +449,8 @@ struct SQDelegable : public CHAINABLE_OBJ { SQUnsignedInteger TranslateIndex(const SQObjectPtr &idx); typedef sqvector SQObjectPtrVec; typedef sqvector SQIntVec; -const SQChar *GetTypeName(const SQObjectPtr &obj1); -const SQChar *IdType2Name(SQObjectType type); +std::string_view GetTypeName(const SQObjectPtr &obj1); +std::string_view IdType2Name(SQObjectType type); diff --git a/src/3rdparty/squirrel/squirrel/sqopcodes.h b/src/3rdparty/squirrel/squirrel/sqopcodes.h index a9e4a8743c..47d0e5197a 100644 --- a/src/3rdparty/squirrel/squirrel/sqopcodes.h +++ b/src/3rdparty/squirrel/squirrel/sqopcodes.h @@ -87,7 +87,7 @@ enum SQOpcode }; struct SQInstructionDesc { - const SQChar *name; + std::string_view name; }; struct SQInstruction diff --git a/src/3rdparty/squirrel/squirrel/sqstate.cpp b/src/3rdparty/squirrel/squirrel/sqstate.cpp index 3adb4d8f98..f75c466da0 100644 --- a/src/3rdparty/squirrel/squirrel/sqstate.cpp +++ b/src/3rdparty/squirrel/squirrel/sqstate.cpp @@ -14,6 +14,7 @@ #include "sqarray.h" #include "squserdata.h" #include "sqclass.h" +#include "../../../core/string_consumer.hpp" #include "../../../safeguards.h" @@ -32,14 +33,12 @@ SQObjectPtr _minusone_((SQInteger)-1); _table(_metamethodsmap)->NewSlot(_metamethods->back(),(SQInteger)(_metamethods->size()-1)); \ } -bool CompileTypemask(SQIntVec &res,const SQChar *typemask) +bool CompileTypemask(SQIntVec &res,std::string_view typemask) { - SQInteger i = 0; - SQInteger mask = 0; - while(typemask[i] != 0) { - - switch(typemask[i]){ + StringConsumer consumer{typemask}; + while (consumer.AnyBytesLeft()) { + switch(consumer.ReadChar()){ case 'o': mask |= _RT_NULL; break; case 'i': mask |= _RT_INTEGER; break; case 'f': mask |= _RT_FLOAT; break; @@ -56,37 +55,32 @@ bool CompileTypemask(SQIntVec &res,const SQChar *typemask) case 'x': mask |= _RT_INSTANCE; break; case 'y': mask |= _RT_CLASS; break; case 'r': mask |= _RT_WEAKREF; break; - case '.': mask = -1; res.push_back(mask); i++; mask = 0; continue; - case ' ': i++; continue; //ignores spaces + case '.': mask = -1; res.push_back(mask); mask = 0; continue; + case ' ': continue; //ignores spaces default: return false; } - i++; - if(typemask[i] == '|') { - i++; - if(typemask[i] == 0) - return false; + + if(consumer.ReadCharIf('|')) { + if(!consumer.AnyBytesLeft()) return false; continue; } res.push_back(mask); mask = 0; - } return true; } -SQTable *CreateDefaultDelegate(SQSharedState *ss,SQRegFunction *funcz) +SQTable *CreateDefaultDelegate(SQSharedState *ss,const std::initializer_list &funcz) { - SQInteger i=0; SQTable *t=SQTable::Create(ss,0); - while(funcz[i].name!=nullptr){ - SQNativeClosure *nc = SQNativeClosure::Create(ss,funcz[i].f); - nc->_nparamscheck = funcz[i].nparamscheck; - nc->_name = SQString::Create(ss,funcz[i].name); - if(funcz[i].typemask && !CompileTypemask(nc->_typecheck,funcz[i].typemask)) + for (auto &func : funcz) { + SQNativeClosure *nc = SQNativeClosure::Create(ss,func.f); + nc->_nparamscheck = func.nparamscheck; + nc->_name = SQString::Create(ss,func.name); + if(func.typemask.has_value() && !CompileTypemask(nc->_typecheck,*func.typemask)) return nullptr; - t->NewSlot(SQString::Create(ss,funcz[i].name),nc); - i++; + t->NewSlot(SQString::Create(ss,func.name),nc); } return t; } @@ -97,8 +91,6 @@ SQSharedState::SQSharedState() _printfunc = nullptr; _debuginfo = false; _notifyallexceptions = false; - _scratchpad=nullptr; - _scratchpadsize=0; _collectable_free_processing = false; #ifndef NO_GARBAGE_COLLECTOR _gc_chain=nullptr; @@ -212,7 +204,6 @@ SQSharedState::~SQSharedState() sq_delete(_systemstrings,SQObjectPtrVec); sq_delete(_metamethods,SQObjectPtrVec); sq_delete(_stringtable,SQStringTable); - if(_scratchpad)SQ_FREE(_scratchpad,_scratchpadsize); } @@ -357,19 +348,16 @@ void SQCollectable::RemoveFromChain(SQCollectable **chain,SQCollectable *c) } #endif -SQChar* SQSharedState::GetScratchPad(SQInteger size) +std::span SQSharedState::GetScratchPad(SQInteger size) { SQInteger newsize; if(size>0) { - if(_scratchpadsize < size) { + if(_scratchpad.size() < static_cast(size)) { newsize = size + (size>>1); - _scratchpad = (SQChar *)SQ_REALLOC(_scratchpad,_scratchpadsize,newsize); - _scratchpadsize = newsize; - - }else if(_scratchpadsize >= (size<<5)) { - newsize = _scratchpadsize >> 1; - _scratchpad = (SQChar *)SQ_REALLOC(_scratchpad,_scratchpadsize,newsize); - _scratchpadsize = newsize; + _scratchpad.resize(newsize); + }else if(_scratchpad.size() >= static_cast(size<<5)) { + newsize = _scratchpad.size() >> 1; + _scratchpad.resize(newsize); } } return _scratchpad; @@ -550,36 +538,36 @@ void SQStringTable::AllocNodes(SQInteger size) { _numofslots = size; _strings = (SQString**)SQ_MALLOC(sizeof(SQString*)*_numofslots); - memset(_strings,0,sizeof(SQString*)*(size_t)_numofslots); + std::fill_n(_strings, _numofslots, nullptr); } -SQString *SQStringTable::Add(const SQChar *news,SQInteger len) +static const std::hash string_table_hash{}; + +SQString *SQStringTable::Add(std::string_view new_string) { - if(len<0) - len = (SQInteger)strlen(news); - SQHash h = ::_hashstr(news,(size_t)len)&(_numofslots-1); + size_t len = new_string.size(); + auto slot = string_table_hash(new_string) & (_numofslots-1); SQString *s; - for (s = _strings[h]; s; s = s->_next){ - if(s->_len == len && (!memcmp(news,s->_val,(size_t)len))) - return s; //found + for (s = _strings[slot]; s; s = s->_next){ + if(s->View() == new_string) return s; //found } SQString *t=(SQString *)SQ_MALLOC(len+sizeof(SQString)); - new (t) SQString(news, len); - t->_next = _strings[h]; - _strings[h] = t; + new (t) SQString(new_string); + t->_next = _strings[slot]; + _strings[slot] = t; _slotused++; if (_slotused > _numofslots) /* too crowded? */ Resize(_numofslots*2); return t; } -SQString::SQString(const SQChar *news, SQInteger len) +SQString::SQString(std::string_view new_string) { - memcpy(_val,news,(size_t)len); - _val[len] = '\0'; - _len = len; - _hash = ::_hashstr(news,(size_t)len); + std::ranges::copy(new_string, _val); + _val[new_string.size()] = '\0'; + _len = new_string.size(); + _hash = string_table_hash(new_string); _next = nullptr; _sharedstate = nullptr; } @@ -615,7 +603,7 @@ void SQStringTable::Remove(SQString *bs) else _strings[h] = s->_next; _slotused--; - SQInteger slen = s->_len; + size_t slen = s->View().size(); s->~SQString(); SQ_FREE(s,sizeof(SQString) + slen); return; diff --git a/src/3rdparty/squirrel/squirrel/sqstate.h b/src/3rdparty/squirrel/squirrel/sqstate.h index 547e6de47d..d98b903b05 100644 --- a/src/3rdparty/squirrel/squirrel/sqstate.h +++ b/src/3rdparty/squirrel/squirrel/sqstate.h @@ -13,7 +13,7 @@ struct SQStringTable { SQStringTable(); ~SQStringTable(); - SQString *Add(const SQChar *,SQInteger len); + SQString *Add(std::string_view str); void Remove(SQString *); private: void Resize(SQInteger size); @@ -49,9 +49,6 @@ private: RefNode **_buckets; }; -#define ADD_STRING(ss,str,len) ss->_stringtable->Add(str,len) -#define REMOVE_STRING(ss,bstr) ss->_stringtable->Remove(bstr) - struct SQObjectPtr; struct SQSharedState @@ -59,7 +56,7 @@ struct SQSharedState SQSharedState(); ~SQSharedState(); public: - SQChar* GetScratchPad(SQInteger size); + std::span GetScratchPad(SQInteger size); SQInteger GetMetaMethodIdxByName(const SQObjectPtr &name); void DelayFinalFree(SQCollectable *collectable); #ifndef NO_GARBAGE_COLLECTOR @@ -84,33 +81,32 @@ public: #endif SQObjectPtr _root_vm; SQObjectPtr _table_default_delegate; - static SQRegFunction _table_default_delegate_funcz[]; + static const std::initializer_list _table_default_delegate_funcz; SQObjectPtr _array_default_delegate; - static SQRegFunction _array_default_delegate_funcz[]; + static const std::initializer_list _array_default_delegate_funcz; SQObjectPtr _string_default_delegate; - static SQRegFunction _string_default_delegate_funcz[]; + static const std::initializer_list _string_default_delegate_funcz; SQObjectPtr _number_default_delegate; - static SQRegFunction _number_default_delegate_funcz[]; + static const std::initializer_list _number_default_delegate_funcz; SQObjectPtr _generator_default_delegate; - static SQRegFunction _generator_default_delegate_funcz[]; + static const std::initializer_list _generator_default_delegate_funcz; SQObjectPtr _closure_default_delegate; - static SQRegFunction _closure_default_delegate_funcz[]; + static const std::initializer_list _closure_default_delegate_funcz; SQObjectPtr _thread_default_delegate; - static SQRegFunction _thread_default_delegate_funcz[]; + static const std::initializer_list _thread_default_delegate_funcz; SQObjectPtr _class_default_delegate; - static SQRegFunction _class_default_delegate_funcz[]; + static const std::initializer_list _class_default_delegate_funcz; SQObjectPtr _instance_default_delegate; - static SQRegFunction _instance_default_delegate_funcz[]; + static const std::initializer_list _instance_default_delegate_funcz; SQObjectPtr _weakref_default_delegate; - static SQRegFunction _weakref_default_delegate_funcz[]; + static const std::initializer_list _weakref_default_delegate_funcz; SQCOMPILERERROR _compilererrorhandler; SQPRINTFUNCTION _printfunc; bool _debuginfo; bool _notifyallexceptions; private: - SQChar *_scratchpad; - SQInteger _scratchpadsize; + std::vector _scratchpad; }; #define _sp(s) (_sharedstate->GetScratchPad(s)) @@ -133,6 +129,6 @@ extern SQObjectPtr _false_; extern SQObjectPtr _one_; extern SQObjectPtr _minusone_; -bool CompileTypemask(SQIntVec &res,const SQChar *typemask); +bool CompileTypemask(SQIntVec &res,std::string_view typemask); #endif //_SQSTATE_H_ diff --git a/src/3rdparty/squirrel/squirrel/sqstring.h b/src/3rdparty/squirrel/squirrel/sqstring.h index 4d14e4adb4..830a71b8fa 100644 --- a/src/3rdparty/squirrel/squirrel/sqstring.h +++ b/src/3rdparty/squirrel/squirrel/sqstring.h @@ -2,29 +2,23 @@ #ifndef _SQSTRING_H_ #define _SQSTRING_H_ -inline SQHash _hashstr (const SQChar *s, size_t l) -{ - SQHash h = (SQHash)l; /* seed */ - size_t step = (l>>5)|1; /* if string is too long, don't hash all its chars */ - for (; l>=step; l-=step) - h = h ^ ((h<<5)+(h>>2)+(unsigned short)*(s++)); - return h; -} - struct SQString : public SQRefCounted { - SQString(const SQChar *news, SQInteger len); + SQString(std::string_view str); ~SQString(){} public: - static SQString *Create(SQSharedState *ss, const SQChar *, SQInteger len = -1 ); - static SQString *Create(SQSharedState *ss, const std::string &str) { return Create(ss, str.data(), str.size()); } + static SQString *Create(SQSharedState *ss, std::string_view str); SQInteger Next(const SQObjectPtr &refpos, SQObjectPtr &outkey, SQObjectPtr &outval); void Release() override; SQSharedState *_sharedstate; SQString *_next; //chain for the string table + std::size_t _hash; + + std::string_view View() const { return std::string_view(this->_val, this->_len); } + std::span Span() { return std::span(this->_val, this->_len); } +private: SQInteger _len; - SQHash _hash; - SQChar _val[1]; + char _val[1]; }; diff --git a/src/3rdparty/squirrel/squirrel/squserdata.h b/src/3rdparty/squirrel/squirrel/squserdata.h index de75ca3c37..108c0f8f1e 100644 --- a/src/3rdparty/squirrel/squirrel/squserdata.h +++ b/src/3rdparty/squirrel/squirrel/squserdata.h @@ -31,7 +31,7 @@ struct SQUserData : SQDelegable SQInteger _size; SQRELEASEHOOK _hook; SQUserPointer _typetag; - SQChar _val[1]; + char _val[1]; }; #endif //_SQUSERDATA_H_ diff --git a/src/3rdparty/squirrel/squirrel/squtils.h b/src/3rdparty/squirrel/squirrel/squtils.h index 2c7a343638..87d0473a26 100644 --- a/src/3rdparty/squirrel/squirrel/squtils.h +++ b/src/3rdparty/squirrel/squirrel/squtils.h @@ -93,7 +93,7 @@ public: { _vals[idx].~T(); if(idx < (_size - 1)) { - memmove(static_cast(&_vals[idx]), &_vals[idx+1], sizeof(T) * (_size - (size_t)idx - 1)); + std::move(&_vals[idx + 1], &_vals[_size], &_vals[idx]); } _size--; } diff --git a/src/3rdparty/squirrel/squirrel/sqvm.cpp b/src/3rdparty/squirrel/squirrel/sqvm.cpp index bb50dc2244..1637c359f3 100644 --- a/src/3rdparty/squirrel/squirrel/sqvm.cpp +++ b/src/3rdparty/squirrel/squirrel/sqvm.cpp @@ -117,7 +117,6 @@ SQVM::SQVM(SQSharedState *ss) _in_stackoverflow = false; _ops_till_suspend = 0; _ops_till_suspend_error_threshold = INT64_MIN; - _ops_till_suspend_error_label = nullptr; _callsstack = nullptr; _callsstacksize = 0; _alloccallsstacksize = 0; @@ -198,7 +197,7 @@ bool SQVM::ObjCmp(const SQObjectPtr &o1,const SQObjectPtr &o2,SQInteger &result) SQObjectPtr res; switch(type(o1)){ case OT_STRING: - _RET_SUCCEED(strcmp(_stringval(o1),_stringval(o2))); + _RET_SUCCEED(_stringval(o1).compare(_stringval(o2))); case OT_INTEGER: /* FS#3954: wrong integer comparison */ _RET_SUCCEED((_integer(o1)<_integer(o2))?-1:(_integer(o1)==_integer(o2))?0:1); @@ -302,11 +301,7 @@ bool SQVM::StringCat(const SQObjectPtr &str,const SQObjectPtr &obj,SQObjectPtr & SQObjectPtr a, b; ToString(str, a); ToString(obj, b); - SQInteger l = _string(a)->_len , ol = _string(b)->_len; - SQChar *s = _sp(l + ol + 1); - memcpy(s, _stringval(a), (size_t)l); - memcpy(s + l, _stringval(b), (size_t)ol); - dest = SQString::Create(_ss(this), _spval, l + ol); + dest = SQString::Create(_ss(this), fmt::format("{}{}", _stringval(a), _stringval(b))); return true; } @@ -1253,7 +1248,8 @@ bool SQVM::Get(const SQObjectPtr &self,const SQObjectPtr &key,SQObjectPtr &dest, if(fetchroot) { if(_rawval(STK(0)) == _rawval(self) && type(STK(0)) == type(self)) { - return _table(_roottable)->Get(key,dest); + if (_table(_roottable)->Get(key,dest)) return true; + return _table(_ss(this)->_consts)->Get(key,dest); } } return false; @@ -1288,9 +1284,10 @@ bool SQVM::FallBackGet(const SQObjectPtr &self,const SQObjectPtr &key,SQObjectPt case OT_STRING: if(sq_isnumeric(key)){ SQInteger n=tointeger(key); - if(abs((int)n)<_string(self)->_len){ - if(n<0)n=_string(self)->_len-n; - dest=SQInteger(_stringval(self)[n]); + std::string_view str = _stringval(self); + if(std::abs(n) < static_cast(str.size())){ + if(n<0)n=str.size()+n; + dest=SQInteger(str[n]); return true; } return false; diff --git a/src/3rdparty/squirrel/squirrel/sqvm.h b/src/3rdparty/squirrel/squirrel/sqvm.h index b083cf3bdd..34554678a5 100644 --- a/src/3rdparty/squirrel/squirrel/sqvm.h +++ b/src/3rdparty/squirrel/squirrel/sqvm.h @@ -169,7 +169,7 @@ public: SQBool _can_suspend; SQInteger _ops_till_suspend; SQInteger _ops_till_suspend_error_threshold; - const char *_ops_till_suspend_error_label; + std::string_view _ops_till_suspend_error_label; SQBool _in_stackoverflow; bool ShouldSuspend() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3e7160c4e2..266e4e577c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -279,6 +279,10 @@ add_files( newgrf_animation_type.h newgrf_badge.cpp newgrf_badge.h + newgrf_badge_config.cpp + newgrf_badge_config.h + newgrf_badge_gui.cpp + newgrf_badge_gui.h newgrf_badge_type.h newgrf_callbacks.h newgrf_canal.cpp diff --git a/src/ai/ai.hpp b/src/ai/ai.hpp index 286c78f0e9..7a2d9fcb3d 100644 --- a/src/ai/ai.hpp +++ b/src/ai/ai.hpp @@ -135,8 +135,8 @@ public: static AIScannerLibrary *GetScannerLibrary(); /** Wrapper function for AIScanner::HasAI */ - static bool HasAI(const struct ContentInfo *ci, bool md5sum); - static bool HasAILibrary(const ContentInfo *ci, bool md5sum); + static bool HasAI(const ContentInfo &ci, bool md5sum); + static bool HasAILibrary(const ContentInfo &ci, bool md5sum); private: static uint frame_counter; ///< Tick counter for the AI code static std::unique_ptr scanner_info; ///< ScriptScanner instance that is used to find AIs diff --git a/src/ai/ai_core.cpp b/src/ai/ai_core.cpp index f6fc28c68b..0283b38f66 100644 --- a/src/ai/ai_core.cpp +++ b/src/ai/ai_core.cpp @@ -334,12 +334,12 @@ * @param md5sum whether to check the MD5 checksum * @return true iff we have an AI (library) matching. */ -/* static */ bool AI::HasAI(const ContentInfo *ci, bool md5sum) +/* static */ bool AI::HasAI(const ContentInfo &ci, bool md5sum) { return AI::scanner_info->HasScript(ci, md5sum); } -/* static */ bool AI::HasAILibrary(const ContentInfo *ci, bool md5sum) +/* static */ bool AI::HasAILibrary(const ContentInfo &ci, bool md5sum) { return AI::scanner_library->HasScript(ci, md5sum); } diff --git a/src/ai/ai_gui.cpp b/src/ai/ai_gui.cpp index 711441c564..c13e313130 100644 --- a/src/ai/ai_gui.cpp +++ b/src/ai/ai_gui.cpp @@ -84,7 +84,7 @@ static constexpr NWidgetPart _nested_ai_config_widgets[] = { /** Window definition for the configure AI window. */ static WindowDesc _ai_config_desc( - WDP_CENTER, nullptr, 0, 0, + WDP_CENTER, {}, 0, 0, WC_GAME_OPTIONS, WC_NONE, {}, _nested_ai_config_widgets @@ -142,7 +142,7 @@ struct AIConfigWindow : public Window { case WID_AIC_LIST: this->line_height = GetCharacterHeight(FS_NORMAL) + padding.height; - resize.height = this->line_height; + fill.height = resize.height = this->line_height; size.height = 8 * this->line_height; break; } @@ -218,7 +218,7 @@ struct AIConfigWindow : public Window { if (widget >= WID_AIC_TEXTFILE && widget < WID_AIC_TEXTFILE + TFT_CONTENT_END) { if (this->selected_slot == CompanyID::Invalid() || AIConfig::GetConfig(this->selected_slot) == nullptr) return; - ShowScriptTextfileWindow((TextfileType)(widget - WID_AIC_TEXTFILE), this->selected_slot); + ShowScriptTextfileWindow(this, static_cast(widget - WID_AIC_TEXTFILE), this->selected_slot); return; } @@ -250,23 +250,23 @@ struct AIConfigWindow : public Window { } case WID_AIC_LIST: { // Select a slot - this->selected_slot = (CompanyID)this->vscroll->GetScrolledRowFromWidget(pt.y, this, widget); + this->selected_slot = static_cast(this->vscroll->GetScrolledRowFromWidget(pt.y, this, widget)); this->InvalidateData(); - if (click_count > 1 && IsEditable(this->selected_slot)) ShowScriptListWindow((CompanyID)this->selected_slot, _ctrl_pressed); + if (click_count > 1 && IsEditable(this->selected_slot)) ShowScriptListWindow(this->selected_slot, _ctrl_pressed); break; } case WID_AIC_MOVE_UP: - if (IsEditable(this->selected_slot) && IsEditable((CompanyID)(this->selected_slot - 1))) { + if (IsEditable(this->selected_slot) && IsEditable(static_cast(this->selected_slot - 1))) { std::swap(GetGameSettings().script_config.ai[this->selected_slot], GetGameSettings().script_config.ai[this->selected_slot - 1]); - this->selected_slot = CompanyID(this->selected_slot - 1); + this->selected_slot = static_cast(this->selected_slot - 1); this->vscroll->ScrollTowards(this->selected_slot.base()); this->InvalidateData(); } break; case WID_AIC_MOVE_DOWN: - if (IsEditable(this->selected_slot) && IsEditable((CompanyID)(this->selected_slot + 1))) { + if (IsEditable(this->selected_slot) && IsEditable(static_cast(this->selected_slot + 1))) { std::swap(GetGameSettings().script_config.ai[this->selected_slot], GetGameSettings().script_config.ai[this->selected_slot + 1]); ++this->selected_slot; this->vscroll->ScrollTowards(this->selected_slot.base()); @@ -282,11 +282,11 @@ struct AIConfigWindow : public Window { } case WID_AIC_CHANGE: // choose other AI - if (IsEditable(this->selected_slot)) ShowScriptListWindow((CompanyID)this->selected_slot, _ctrl_pressed); + if (IsEditable(this->selected_slot)) ShowScriptListWindow(this->selected_slot, _ctrl_pressed); break; case WID_AIC_CONFIGURE: // change the settings for an AI - ShowScriptSettingsWindow((CompanyID)this->selected_slot); + ShowScriptSettingsWindow(this->selected_slot); break; case WID_AIC_CONTENT_DOWNLOAD: @@ -320,8 +320,8 @@ struct AIConfigWindow : public Window { this->SetWidgetDisabledState(WID_AIC_INCREASE_INTERVAL, GetGameSettings().difficulty.competitors_interval == MAX_COMPETITORS_INTERVAL); this->SetWidgetDisabledState(WID_AIC_CHANGE, !IsEditable(this->selected_slot)); this->SetWidgetDisabledState(WID_AIC_CONFIGURE, this->selected_slot == CompanyID::Invalid() || config->GetConfigList()->empty()); - this->SetWidgetDisabledState(WID_AIC_MOVE_UP, !IsEditable(this->selected_slot) || !IsEditable((CompanyID)(this->selected_slot - 1))); - this->SetWidgetDisabledState(WID_AIC_MOVE_DOWN, !IsEditable(this->selected_slot) || !IsEditable((CompanyID)(this->selected_slot + 1))); + this->SetWidgetDisabledState(WID_AIC_MOVE_UP, !IsEditable(this->selected_slot) || !IsEditable(static_cast(this->selected_slot - 1))); + this->SetWidgetDisabledState(WID_AIC_MOVE_DOWN, !IsEditable(this->selected_slot) || !IsEditable(static_cast(this->selected_slot + 1))); this->SetWidgetDisabledState(WID_AIC_OPEN_URL, this->selected_slot == CompanyID::Invalid() || config->GetInfo() == nullptr || config->GetInfo()->GetURL().empty()); for (TextfileType tft = TFT_CONTENT_BEGIN; tft < TFT_CONTENT_END; tft++) { diff --git a/src/ai/ai_info.cpp b/src/ai/ai_info.cpp index 79d5ca96dd..8e3de91a1f 100644 --- a/src/ai/ai_info.cpp +++ b/src/ai/ai_info.cpp @@ -27,14 +27,14 @@ static bool CheckAPIVersion(const std::string &api_version) return std::ranges::find(AIInfo::ApiVersions, api_version) != std::end(AIInfo::ApiVersions); } -template <> SQInteger PushClassName(HSQUIRRELVM vm) { sq_pushstring(vm, "AIInfo", -1); return 1; } +template <> SQInteger PushClassName(HSQUIRRELVM vm) { sq_pushstring(vm, "AIInfo"); return 1; } -/* static */ void AIInfo::RegisterAPI(Squirrel *engine) +/* static */ void AIInfo::RegisterAPI(Squirrel &engine) { /* Create the AIInfo class, and add the RegisterAI function */ DefSQClass SQAIInfo("AIInfo"); SQAIInfo.PreRegister(engine); - SQAIInfo.AddConstructor(engine, "x"); + SQAIInfo.AddConstructor(engine, "x"); SQAIInfo.DefSQAdvancedMethod(engine, &AIInfo::AddSetting, "AddSetting"); SQAIInfo.DefSQAdvancedMethod(engine, &AIInfo::AddLabels, "AddLabels"); SQAIInfo.DefSQConst(engine, ScriptConfigFlags{}.base(), "CONFIG_NONE"); @@ -50,8 +50,8 @@ template <> SQInteger PushClassName(HSQUIRRELVM vm) { sq SQAIInfo.DefSQConst(engine, ScriptConfigFlags{ScriptConfigFlag::InGame}.base(), "AICONFIG_INGAME"); SQAIInfo.PostRegister(engine); - engine->AddMethod("RegisterAI", &AIInfo::Constructor, 2, "tx"); - engine->AddMethod("RegisterDummyAI", &AIInfo::DummyConstructor, 2, "tx"); + engine.AddMethod("RegisterAI", &AIInfo::Constructor, "tx"); + engine.AddMethod("RegisterDummyAI", &AIInfo::DummyConstructor, "tx"); } /* static */ SQInteger AIInfo::Constructor(HSQUIRRELVM vm) @@ -61,11 +61,12 @@ template <> SQInteger PushClassName(HSQUIRRELVM vm) { sq if (SQ_FAILED(sq_getinstanceup(vm, 2, &instance, nullptr)) || instance == nullptr) return sq_throwerror(vm, "Pass an instance of a child class of AIInfo to RegisterAI"); AIInfo *info = (AIInfo *)instance; - SQInteger res = ScriptInfo::Constructor(vm, info); + SQInteger res = ScriptInfo::Constructor(vm, *info); if (res != 0) return res; if (info->engine->MethodExists(info->SQ_instance, "MinVersionToLoad")) { if (!info->engine->CallIntegerMethod(info->SQ_instance, "MinVersionToLoad", &info->min_loadable_version, MAX_GET_OPS)) return SQ_ERROR; + if (info->min_loadable_version < 0) return SQ_ERROR; } else { info->min_loadable_version = info->GetVersion(); } @@ -101,13 +102,13 @@ template <> SQInteger PushClassName(HSQUIRRELVM vm) { sq AIInfo *info = (AIInfo *)instance; info->api_version = *std::rbegin(AIInfo::ApiVersions); - SQInteger res = ScriptInfo::Constructor(vm, info); + SQInteger res = ScriptInfo::Constructor(vm, *info); if (res != 0) return res; /* Remove the link to the real instance, else it might get deleted by RegisterAI() */ sq_setinstanceup(vm, 2, nullptr); /* Register the AI to the base system */ - static_cast(info->GetScanner())->SetDummyAI(info); + static_cast(info->GetScanner())->SetDummyAI(std::unique_ptr(info)); return 0; } @@ -124,12 +125,12 @@ bool AIInfo::CanLoadFromVersion(int version) const } -/* static */ void AILibrary::RegisterAPI(Squirrel *engine) +/* static */ void AILibrary::RegisterAPI(Squirrel &engine) { /* Create the AILibrary class, and add the RegisterLibrary function */ - engine->AddClassBegin("AILibrary"); - engine->AddClassEnd(); - engine->AddMethod("RegisterLibrary", &AILibrary::Constructor, 2, "tx"); + engine.AddClassBegin("AILibrary"); + engine.AddClassEnd(); + engine.AddMethod("RegisterLibrary", &AILibrary::Constructor, "tx"); } /* static */ SQInteger AILibrary::Constructor(HSQUIRRELVM vm) @@ -137,7 +138,7 @@ bool AIInfo::CanLoadFromVersion(int version) const /* Create a new library */ AILibrary *library = new AILibrary(); - SQInteger res = ScriptInfo::Constructor(vm, library); + SQInteger res = ScriptInfo::Constructor(vm, *library); if (res != 0) { delete library; return res; diff --git a/src/ai/ai_info.hpp b/src/ai/ai_info.hpp index dc2e0de500..3928588114 100644 --- a/src/ai/ai_info.hpp +++ b/src/ai/ai_info.hpp @@ -23,7 +23,7 @@ public: /** * Register the functions of this class. */ - static void RegisterAPI(Squirrel *engine); + static void RegisterAPI(Squirrel &engine); /** * Create an AI, using this AIInfo as start-template. @@ -64,7 +64,7 @@ public: /** * Register the functions of this class. */ - static void RegisterAPI(Squirrel *engine); + static void RegisterAPI(Squirrel &engine); /** * Create an AI, using this AIInfo as start-template. diff --git a/src/ai/ai_instance.cpp b/src/ai/ai_instance.cpp index 07dcd40023..cb8a4a00b4 100644 --- a/src/ai/ai_instance.cpp +++ b/src/ai/ai_instance.cpp @@ -27,7 +27,7 @@ #include "table/strings.h" /* Manually include the Text glue. */ -#include "../script/api/template/template_text.hpp.sq" +#include "../script/api/template/template_text.sq.hpp" /* Convert all AI related classes to Squirrel data. */ #include "../script/api/ai/ai_includes.hpp" @@ -43,7 +43,7 @@ void AIInstance::Initialize(AIInfo *info) this->api_version = info->GetAPIVersion(); /* Register the AIController (including the "import" command) */ - SQAIController_Register(this->engine); + SQAIController_Register(*this->engine); ScriptInstance::Initialize(info->GetMainScript(), info->GetInstanceName(), _current_company); } @@ -53,7 +53,7 @@ void AIInstance::RegisterAPI() ScriptInstance::RegisterAPI(); /* Register all classes */ - SQAI_RegisterAll(this->engine); + SQAI_RegisterAll(*this->engine); if (!this->LoadCompatibilityScripts(AI_DIR, AIInfo::ApiVersions)) this->Died(); } diff --git a/src/ai/ai_scanner.cpp b/src/ai/ai_scanner.cpp index 843229e699..827dc4cc3c 100644 --- a/src/ai/ai_scanner.cpp +++ b/src/ai/ai_scanner.cpp @@ -22,11 +22,8 @@ #include "../safeguards.h" -AIScannerInfo::AIScannerInfo() : - ScriptScanner(), - info_dummy(nullptr) -{ -} +AIScannerInfo::AIScannerInfo() = default; +AIScannerInfo::~AIScannerInfo() = default; void AIScannerInfo::Initialize() { @@ -39,22 +36,17 @@ void AIScannerInfo::Initialize() Script_CreateDummyInfo(this->engine->GetVM(), "AI", "ai"); } -void AIScannerInfo::SetDummyAI(class AIInfo *info) +void AIScannerInfo::SetDummyAI(std::unique_ptr &&info) { - this->info_dummy = info; + this->info_dummy = std::move(info); } -AIScannerInfo::~AIScannerInfo() +std::string AIScannerInfo::GetScriptName(ScriptInfo &info) { - delete this->info_dummy; + return info.GetName(); } -std::string AIScannerInfo::GetScriptName(ScriptInfo *info) -{ - return info->GetName(); -} - -void AIScannerInfo::RegisterAPI(class Squirrel *engine) +void AIScannerInfo::RegisterAPI(class Squirrel &engine) { AIInfo::RegisterAPI(engine); } @@ -63,7 +55,7 @@ AIInfo *AIScannerInfo::SelectRandomAI() const { if (_game_mode == GM_MENU) { Debug(script, 0, "The intro game should not use AI, loading 'dummy' AI."); - return this->info_dummy; + return this->info_dummy.get(); } /* Filter for AIs suitable as Random AI. */ @@ -72,7 +64,7 @@ AIInfo *AIScannerInfo::SelectRandomAI() const uint num_random_ais = std::ranges::distance(random_ais); if (num_random_ais == 0) { Debug(script, 0, "No suitable AI found, loading 'dummy' AI."); - return this->info_dummy; + return this->info_dummy.get(); } /* Pick a random AI */ @@ -125,13 +117,13 @@ void AIScannerLibrary::Initialize() ScriptScanner::Initialize("AIScanner"); } -std::string AIScannerLibrary::GetScriptName(ScriptInfo *info) +std::string AIScannerLibrary::GetScriptName(ScriptInfo &info) { - AILibrary *library = static_cast(info); - return fmt::format("{}.{}", library->GetCategory(), library->GetInstanceName()); + AILibrary &library = static_cast(info); + return fmt::format("{}.{}", library.GetCategory(), library.GetInstanceName()); } -void AIScannerLibrary::RegisterAPI(class Squirrel *engine) +void AIScannerLibrary::RegisterAPI(class Squirrel &engine) { AILibrary::RegisterAPI(engine); } diff --git a/src/ai/ai_scanner.hpp b/src/ai/ai_scanner.hpp index a2c8a7280e..364e5642a9 100644 --- a/src/ai/ai_scanner.hpp +++ b/src/ai/ai_scanner.hpp @@ -37,17 +37,17 @@ public: /** * Set the Dummy AI. */ - void SetDummyAI(class AIInfo *info); + void SetDummyAI(std::unique_ptr &&info); protected: - std::string GetScriptName(ScriptInfo *info) override; - const char *GetFileName() const override { return PATHSEP "info.nut"; } + std::string GetScriptName(ScriptInfo &info) override; + std::string_view GetFileName() const override { return PATHSEP "info.nut"; } Subdirectory GetDirectory() const override { return AI_DIR; } - const char *GetScannerName() const override { return "AIs"; } - void RegisterAPI(class Squirrel *engine) override; + std::string_view GetScannerName() const override { return "AIs"; } + void RegisterAPI(class Squirrel &engine) override; private: - AIInfo *info_dummy; ///< The dummy AI. + std::unique_ptr info_dummy; ///< The dummy AI. }; class AIScannerLibrary : public ScriptScanner { @@ -63,11 +63,11 @@ public: class AILibrary *FindLibrary(const std::string &library, int version); protected: - std::string GetScriptName(ScriptInfo *info) override; - const char *GetFileName() const override { return PATHSEP "library.nut"; } + std::string GetScriptName(ScriptInfo &info) override; + std::string_view GetFileName() const override { return PATHSEP "library.nut"; } Subdirectory GetDirectory() const override { return AI_LIBRARY_DIR; } - const char *GetScannerName() const override { return "AI Libraries"; } - void RegisterAPI(class Squirrel *engine) override; + std::string_view GetScannerName() const override { return "AI Libraries"; } + void RegisterAPI(class Squirrel &engine) override; }; #endif /* AI_SCANNER_HPP */ diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp index 78597e3a30..aac97c84c9 100644 --- a/src/aircraft_cmd.cpp +++ b/src/aircraft_cmd.cpp @@ -175,7 +175,7 @@ void Aircraft::GetImage(Direction direction, EngineImageType image_type, Vehicle { uint8_t spritenum = this->spritenum; - if (is_custom_sprite(spritenum)) { + if (IsCustomVehicleSpriteNum(spritenum)) { GetCustomVehicleSprite(this, direction, image_type, result); if (result->IsValid()) return; @@ -191,7 +191,7 @@ void GetRotorImage(const Aircraft *v, EngineImageType image_type, VehicleSpriteS assert(v->subtype == AIR_HELICOPTER); const Aircraft *w = v->Next()->Next(); - if (is_custom_sprite(v->spritenum)) { + if (IsCustomVehicleSpriteNum(v->spritenum)) { GetCustomRotorSprite(v, image_type, result); if (result->IsValid()) return; } @@ -205,7 +205,7 @@ static void GetAircraftIcon(EngineID engine, EngineImageType image_type, Vehicle const Engine *e = Engine::Get(engine); uint8_t spritenum = e->u.air.image_index; - if (is_custom_sprite(spritenum)) { + if (IsCustomVehicleSpriteNum(spritenum)) { GetCustomVehicleIcon(engine, DIR_W, image_type, result); if (result->IsValid()) return; @@ -1430,7 +1430,7 @@ static void AircraftLandAirplane(Aircraft *v) v->UpdateDeltaXY(); - AirportTileAnimationTrigger(st, vt, AAT_STATION_AIRPLANE_LAND); + TriggerAirportTileAnimation(st, vt, AirportAnimationTrigger::AirplaneTouchdown); if (!PlayVehicleSound(v, VSE_TOUCHDOWN)) { SndPlayVehicleFx(SND_17_SKID_PLANE, v); diff --git a/src/aircraft_gui.cpp b/src/aircraft_gui.cpp index 1ba0a311d5..5b7d67582b 100644 --- a/src/aircraft_gui.cpp +++ b/src/aircraft_gui.cpp @@ -82,7 +82,7 @@ void DrawAircraftImage(const Vehicle *v, const Rect &r, VehicleID selection, Eng int x_offs = UnScaleGUI(rect.left); int x = rtl ? r.right - width - x_offs : r.left - x_offs; /* This magic -1 offset is related to the sprite_y_offsets in build_vehicle_gui.cpp */ - int y = ScaleSpriteTrad(-1) + CenterBounds(r.top, r.bottom, 0); + int y = ScaleSpriteTrad(-1) + CentreBounds(r.top, r.bottom, 0); bool helicopter = v->subtype == AIR_HELICOPTER; int heli_offs = 0; diff --git a/src/airport_gui.cpp b/src/airport_gui.cpp index 6954317887..de467e0a5c 100644 --- a/src/airport_gui.cpp +++ b/src/airport_gui.cpp @@ -21,7 +21,7 @@ #include "company_base.h" #include "station_type.h" #include "newgrf_airport.h" -#include "newgrf_badge.h" +#include "newgrf_badge_gui.h" #include "newgrf_callbacks.h" #include "dropdown_type.h" #include "dropdown_func.h" @@ -204,9 +204,9 @@ static constexpr NWidgetPart _nested_air_toolbar_widgets[] = { NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN), EndContainer(), NWidget(NWID_HORIZONTAL), - NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_AIRPORT), SetFill(0, 1), SetMinimalSize(42, 22), SetSpriteTip(SPR_IMG_AIRPORT, STR_TOOLBAR_AIRCRAFT_BUILD_AIRPORT_TOOLTIP), - NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(4, 22), SetFill(1, 1), EndContainer(), - NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_DEMOLISH), SetFill(0, 1), SetMinimalSize(22, 22), SetSpriteTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_AIRPORT), SetFill(0, 1), SetToolbarMinimalSize(2), SetSpriteTip(SPR_IMG_AIRPORT, STR_TOOLBAR_AIRCRAFT_BUILD_AIRPORT_TOOLTIP), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetToolbarSpacerMinimalSize(), SetFill(1, 1), EndContainer(), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_DEMOLISH), SetFill(0, 1), SetToolbarMinimalSize(1), SetSpriteTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC), EndContainer(), }; @@ -401,7 +401,7 @@ public: case WID_AP_AIRPORT_SPRITE: if (this->preview_sprite != 0) { Dimension d = GetSpriteSize(this->preview_sprite); - DrawSprite(this->preview_sprite, COMPANY_SPRITE_COLOUR(_local_company), CenterBounds(r.left, r.right, d.width), CenterBounds(r.top, r.bottom, d.height)); + DrawSprite(this->preview_sprite, GetCompanyPalette(_local_company), CentreBounds(r.left, r.right, d.width), CentreBounds(r.top, r.bottom, d.height)); } break; @@ -561,7 +561,7 @@ public: this->SelectOtherAirport(-1); } - void OnDropdownSelect(WidgetID widget, int index) override + void OnDropdownSelect(WidgetID widget, int index, int) override { if (widget == WID_AP_CLASS_DROPDOWN) { _selected_airport_class = (AirportClassID)index; @@ -575,7 +575,7 @@ public: CheckRedrawStationCoverage(this); } - IntervalTimer yearly_interval = {{TimerGameCalendar::YEAR, TimerGameCalendar::Priority::NONE}, [this](auto) { + const IntervalTimer yearly_interval = {{TimerGameCalendar::YEAR, TimerGameCalendar::Priority::NONE}, [this](auto) { this->InvalidateData(); }}; }; @@ -618,7 +618,7 @@ static constexpr NWidgetPart _nested_build_airport_widgets[] = { }; static WindowDesc _build_airport_desc( - WDP_AUTO, nullptr, 0, 0, + WDP_AUTO, {}, 0, 0, WC_BUILD_STATION, WC_BUILD_TOOLBAR, WindowDefaultFlag::Construction, _nested_build_airport_widgets diff --git a/src/articulated_vehicles.cpp b/src/articulated_vehicles.cpp index 1f547b2615..ffb2fc052b 100644 --- a/src/articulated_vehicles.cpp +++ b/src/articulated_vehicles.cpp @@ -80,20 +80,18 @@ uint CountArticulatedParts(EngineID engine_type, bool purchase_window) * either, so it doesn't matter how many articulated parts there are. */ if (!Vehicle::CanAllocateItem()) return 0; - Vehicle *v = nullptr; + std::unique_ptr v; if (!purchase_window) { - v = new Vehicle(); + v = std::make_unique(); v->engine_type = engine_type; v->owner = _current_company; } uint i; for (i = 1; i < MAX_ARTICULATED_PARTS; i++) { - if (GetNextArticulatedPart(i, engine_type, v) == EngineID::Invalid()) break; + if (GetNextArticulatedPart(i, engine_type, v.get()) == EngineID::Invalid()) break; } - delete v; - return i - 1; } @@ -432,7 +430,10 @@ void AddArticulatedParts(Vehicle *first) if (flip_image) v->spritenum++; - if (v->type == VEH_TRAIN && TestVehicleBuildProbability(v, v->engine_type, BuildProbabilityType::Reversed)) SetBit(Train::From(v)->flags, VRF_REVERSE_DIRECTION); + if (v->type == VEH_TRAIN) { + auto prob = TestVehicleBuildProbability(v, v->engine_type, BuildProbabilityType::Reversed); + if (prob.has_value()) Train::From(v)->flags.Set(VehicleRailFlag::Flipped, prob.value()); + } v->UpdatePosition(); } } diff --git a/src/autocompletion.cpp b/src/autocompletion.cpp index 1a643f8c05..e8d36406f3 100644 --- a/src/autocompletion.cpp +++ b/src/autocompletion.cpp @@ -17,9 +17,12 @@ #include "safeguards.h" +/** + * @return true, if the textbuf was updated. + */ bool AutoCompletion::AutoComplete() { - // We are pressing TAB for the first time after reset. + /* We are pressing TAB for the first time after reset. */ if (this->suggestions.empty()) { this->InitSuggestions(this->textbuf->GetText()); if (this->suggestions.empty()) { @@ -29,11 +32,11 @@ bool AutoCompletion::AutoComplete() return true; } - // We are pressing TAB again on the same text. + /* We are pressing TAB again on the same text. */ if (this->current_suggestion_index + 1 < this->suggestions.size()) { this->ApplySuggestion(prefix, this->suggestions[++this->current_suggestion_index]); } else { - // We are out of options, restore original text. + /* We are out of options, restore original text. */ this->textbuf->Assign(initial_buf); this->Reset(); } diff --git a/src/autocompletion.h b/src/autocompletion.h index 452c23e87a..03c08f5c77 100644 --- a/src/autocompletion.h +++ b/src/autocompletion.h @@ -32,7 +32,6 @@ public: } virtual ~AutoCompletion() = default; - // Returns true the textbuf was updated. bool AutoComplete(); void Reset(); diff --git a/src/autoreplace_cmd.cpp b/src/autoreplace_cmd.cpp index 668360f6e7..df64561cf3 100644 --- a/src/autoreplace_cmd.cpp +++ b/src/autoreplace_cmd.cpp @@ -185,9 +185,9 @@ static bool VerifyAutoreplaceRefitForOrders(const Vehicle *v, EngineID engine_ty CargoTypes union_refit_mask_b = GetUnionOfArticulatedRefitMasks(engine_type, false); const Vehicle *u = (v->type == VEH_TRAIN) ? v->First() : v; - for (const Order *o : u->Orders()) { - if (!o->IsRefit() || o->IsAutoRefit()) continue; - CargoType cargo_type = o->GetRefitCargo(); + for (const Order &o : u->Orders()) { + if (!o.IsRefit() || o.IsAutoRefit()) continue; + CargoType cargo_type = o.GetRefitCargo(); if (!HasBit(union_refit_mask_a, cargo_type)) continue; if (!HasBit(union_refit_mask_b, cargo_type)) return false; @@ -206,13 +206,12 @@ static int GetIncompatibleRefitOrderIdForAutoreplace(const Vehicle *v, EngineID { CargoTypes union_refit_mask = GetUnionOfArticulatedRefitMasks(engine_type, false); - const Order *o; const Vehicle *u = (v->type == VEH_TRAIN) ? v->First() : v; const OrderList *orders = u->orders; if (orders == nullptr) return -1; for (VehicleOrderID i = 0; i < orders->GetNumOrders(); i++) { - o = orders->GetOrderAt(i); + const Order *o = orders->GetOrderAt(i); if (!o->IsRefit()) continue; if (!HasBit(union_refit_mask, o->GetRefitCargo())) return i; } @@ -373,8 +372,12 @@ static CommandCost BuildReplacementVehicle(Vehicle *old_veh, Vehicle **new_vehic } /* Try to reverse the vehicle, but do not care if it fails as the new type might not be reversible */ - if (new_veh->type == VEH_TRAIN && HasBit(Train::From(old_veh)->flags, VRF_REVERSE_DIRECTION)) { - Command::Do(DoCommandFlag::Execute, new_veh->index, true); + if (new_veh->type == VEH_TRAIN && Train::From(old_veh)->flags.Test(VehicleRailFlag::Flipped)) { + /* Only copy the reverse state if neither old or new vehicle implements reverse-on-build probability callback. */ + if (!TestVehicleBuildProbability(old_veh, old_veh->engine_type, BuildProbabilityType::Reversed).has_value() && + !TestVehicleBuildProbability(new_veh, new_veh->engine_type, BuildProbabilityType::Reversed).has_value()) { + Command::Do(DoCommandFlag::Execute, new_veh->index, true); + } } return cost; diff --git a/src/autoreplace_gui.cpp b/src/autoreplace_gui.cpp index 3632415472..804dd92a9d 100644 --- a/src/autoreplace_gui.cpp +++ b/src/autoreplace_gui.cpp @@ -311,7 +311,7 @@ public: case WID_RV_LEFT_MATRIX: case WID_RV_RIGHT_MATRIX: - resize.height = GetEngineListHeight(this->window_number); + fill.height = resize.height = GetEngineListHeight(this->window_number); size.height = (this->window_number <= VEH_ROAD ? 8 : 4) * resize.height; break; @@ -406,11 +406,11 @@ public: case WID_RV_TRAIN_WAGONREMOVE_TOGGLE: if (const Group *g = Group::GetIfValid(this->sel_group); g != nullptr) { bool remove_wagon = g->flags.Test(GroupFlag::ReplaceWagonRemoval); - return GetString(STR_GROUP_NAME, sel_group, remove_wagon ? STR_CONFIG_SETTING_ON : STR_CONFIG_SETTING_OFF); + return GetString(STR_REPLACE_REMOVE_WAGON, STR_GROUP_NAME, sel_group, remove_wagon ? STR_CONFIG_SETTING_ON : STR_CONFIG_SETTING_OFF); } else { const Company *c = Company::Get(_local_company); bool remove_wagon = c->settings.renew_keep_length; - return GetString(STR_GROUP_DEFAULT_TRAINS + this->window_number, std::monostate{}, remove_wagon ? STR_CONFIG_SETTING_ON : STR_CONFIG_SETTING_OFF); + return GetString(STR_REPLACE_REMOVE_WAGON, STR_GROUP_DEFAULT_TRAINS + this->window_number, std::monostate{}, remove_wagon ? STR_CONFIG_SETTING_ON : STR_CONFIG_SETTING_OFF); } break; @@ -552,7 +552,7 @@ public: if (g != nullptr) { Command::Post(this->sel_group, GroupFlag::ReplaceWagonRemoval, !g->flags.Test(GroupFlag::ReplaceWagonRemoval), _ctrl_pressed); } else { - // toggle renew_keep_length + /* toggle renew_keep_length */ Command::Post("company.renew_keep_length", Company::Get(_local_company)->settings.renew_keep_length ? 0 : 1); } break; @@ -623,7 +623,7 @@ public: } } - void OnDropdownSelect(WidgetID widget, int index) override + void OnDropdownSelect(WidgetID widget, int index, int) override { switch (widget) { case WID_RV_SORT_DROPDOWN: @@ -744,7 +744,7 @@ static constexpr NWidgetPart _nested_replace_rail_vehicle_widgets[] = { NWidget(WWT_PANEL, COLOUR_GREY, WID_RV_LEFT_DETAILS), SetMinimalSize(240, 122), SetResize(1, 0), EndContainer(), NWidget(NWID_VERTICAL), NWidget(WWT_PANEL, COLOUR_GREY, WID_RV_RIGHT_DETAILS), SetMinimalSize(240, 122), SetResize(1, 0), EndContainer(), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_RV_TRAIN_WAGONREMOVE_TOGGLE), SetMinimalSize(138, 12), SetStringTip(STR_REPLACE_REMOVE_WAGON, STR_REPLACE_REMOVE_WAGON_TOOLTIP), SetFill(1, 0), SetResize(1, 0), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_RV_TRAIN_WAGONREMOVE_TOGGLE), SetMinimalSize(138, 12), SetToolTip(STR_REPLACE_REMOVE_WAGON_TOOLTIP), SetFill(1, 0), SetResize(1, 0), EndContainer(), EndContainer(), NWidget(NWID_HORIZONTAL), diff --git a/src/base_media_base.h b/src/base_media_base.h index cd77cef4df..6286455b2d 100644 --- a/src/base_media_base.h +++ b/src/base_media_base.h @@ -16,8 +16,9 @@ #include "3rdparty/md5/md5.h" #include -/* Forward declare these; can't do 'struct X' in functions as older GCCs barf on that */ struct IniFile; +struct IniGroup; +struct IniItem; struct ContentInfo; /** Structure holding filename and MD5 information about a single file */ @@ -47,7 +48,7 @@ template struct BaseSetTraits; */ template struct BaseSet { - typedef std::unordered_map TranslatedStrings; + typedef std::unordered_map> TranslatedStrings; /** Number of files in this set */ static constexpr size_t NUM_FILES = BaseSetTraits::num_files; @@ -62,21 +63,13 @@ struct BaseSet { std::string url; ///< URL for information about the base set TranslatedStrings description; ///< Description of the base set uint32_t shortname = 0; ///< Four letter short variant of the name - uint32_t version = 0; ///< The version of this base set + std::vector version; ///< The version of this base set bool fallback = false; ///< This set is a fallback set, i.e. it should be used only as last resort std::array::NUM_FILES> files{}; ///< All files part of this set uint found_files = 0; ///< Number of the files that could be found uint valid_files = 0; ///< Number of the files that could be found and are valid - T *next = nullptr; ///< The next base set in this list - - /** Free everything we allocated */ - ~BaseSet() - { - delete this->next; - } - /** * Get the number of missing files. * @return the number @@ -96,6 +89,9 @@ struct BaseSet { return BaseSet::NUM_FILES - this->valid_files; } + void LogError(std::string_view full_filename, std::string_view detail, int level = 0) const; + const IniItem *GetMandatoryItem(std::string_view full_filename, const IniGroup &group, std::string_view name) const; + bool FillSetDetails(const IniFile &ini, const std::string &path, const std::string &full_filename, bool allow_empty_filename = true); void CopyCompatibleConfig([[maybe_unused]] const T &src) {} @@ -107,7 +103,7 @@ struct BaseSet { * @param isocode the isocode to search for * @return the description */ - const std::string &GetDescription(const std::string &isocode) const + const std::string &GetDescription(std::string_view isocode) const { if (!isocode.empty()) { /* First the full ISO code */ @@ -166,9 +162,9 @@ struct BaseSet { template class BaseMedia : FileScanner { protected: - static inline Tbase_set *available_sets = nullptr; ///< All available sets - static inline Tbase_set *duplicate_sets = nullptr; ///< All sets that aren't available, but needed for not downloading base sets when a newer version than the one on BaNaNaS is loaded. - static inline const Tbase_set *used_set = nullptr; ///< The currently used set + static inline std::vector> available_sets; ///< All available sets + static inline std::vector> duplicate_sets; ///< All sets that aren't available, but needed for not downloading base sets when a newer version than the one on BaNaNaS is loaded. + static inline const Tbase_set *used_set; ///< The currently used set bool AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename) override; @@ -176,7 +172,13 @@ protected: * Get the extension that is used to identify this set. * @return the extension */ - static const char *GetExtension(); + static std::string_view GetExtension(); + + /** + * Return the duplicate sets. + * @return The duplicate sets. + */ + static std::span> GetDuplicateSets() { return BaseMedia::duplicate_sets; } public: /** * Determine the graphics pack that has to be used. @@ -194,7 +196,11 @@ public: return num + fs.Scan(GetExtension(), BASESET_DIR, Tbase_set::SEARCH_IN_TARS); } - static Tbase_set *GetAvailableSets(); + /** + * Return the available sets. + * @return The available sets. + */ + static std::span> GetAvailableSets() { return BaseMedia::available_sets; } static bool SetSet(const Tbase_set *set); static bool SetSetByName(const std::string &name); @@ -211,7 +217,7 @@ public: * @param md5sum whether to check the MD5 checksum * @return true iff we have an set matching. */ - static bool HasSet(const ContentInfo *ci, bool md5sum); + static bool HasSet(const ContentInfo &ci, bool md5sum); }; /** @@ -219,9 +225,9 @@ public: * @param ci The content info to compare it to. * @param md5sum Should the MD5 checksum be tested as well? * @param s The list with sets. - * @return The filename of the first file of the base set, or \c nullptr if there is no match. + * @return The filename of the first file of the base set, or \c std::nullopt if there is no match. */ template -const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const Tbase_set *s); +std::optional TryGetBaseSetFile(const ContentInfo &ci, bool md5sum, std::span> sets); #endif /* BASE_MEDIA_BASE_H */ diff --git a/src/base_media_func.h b/src/base_media_func.h index 4e32a12e94..8f1fac4c26 100644 --- a/src/base_media_func.h +++ b/src/base_media_func.h @@ -14,20 +14,39 @@ #include "ini_type.h" #include "string_func.h" #include "error_func.h" +#include "core/string_consumer.hpp" +#include "3rdparty/fmt/ranges.h" extern void CheckExternalFiles(); /** - * Try to read a single piece of metadata and return false if it doesn't exist. + * Log error from reading basesets. + * @param full_filename the full filename of the loaded file + * @param detail detail log message + * @param level debug level + */ +template +void BaseSet::LogError(std::string_view full_filename, std::string_view detail, int level) const +{ + Debug(misc, level, "Loading base {}set details failed: {}", BaseSet::SET_TYPE, full_filename); + Debug(misc, level, " {}", detail); +} + +/** + * Try to read a single piece of metadata and return nullptr if it doesn't exist. + * Log error, if the data is missing. + * @param full_filename the full filename of the loaded file (for error reporting purposes) + * @param group ini group to read from * @param name the name of the item to fetch. */ -#define fetch_metadata(name) \ - item = metadata->GetItem(name); \ - if (item == nullptr || !item->value.has_value() || item->value->empty()) { \ - Debug(grf, 0, "Base {}set detail loading: {} field missing.", BaseSet::SET_TYPE, name); \ - Debug(grf, 0, " Is {} readable for the user running OpenTTD?", full_filename); \ - return false; \ - } +template +const IniItem *BaseSet::GetMandatoryItem(std::string_view full_filename, const IniGroup &group, std::string_view name) const +{ + auto *item = group.GetItem(name); + if (item != nullptr && item->value.has_value() && !item->value->empty()) return item; + this->LogError(full_filename, fmt::format("{}.{} field missing.", group.name, name)); + return nullptr; +} /** * Read the set information from a loaded ini. @@ -42,16 +61,17 @@ bool BaseSet::FillSetDetails(const IniFile &ini, const std::string &path, con { const IniGroup *metadata = ini.GetGroup("metadata"); if (metadata == nullptr) { - Debug(grf, 0, "Base {}set detail loading: metadata missing.", BaseSet::SET_TYPE); - Debug(grf, 0, " Is {} readable for the user running OpenTTD?", full_filename); + this->LogError(full_filename, "Is the file readable for the user running OpenTTD?"); return false; } const IniItem *item; - fetch_metadata("name"); + item = this->GetMandatoryItem(full_filename, *metadata, "name"); + if (item == nullptr) return false; this->name = *item->value; - fetch_metadata("description"); + item = this->GetMandatoryItem(full_filename, *metadata, "description"); + if (item == nullptr) return false; this->description[std::string{}] = *item->value; item = metadata->GetItem("url"); @@ -64,13 +84,24 @@ bool BaseSet::FillSetDetails(const IniFile &ini, const std::string &path, con this->description[titem.name.substr(12)] = titem.value.value_or(""); } - fetch_metadata("shortname"); + item = this->GetMandatoryItem(full_filename, *metadata, "shortname"); + if (item == nullptr) return false; for (uint i = 0; (*item->value)[i] != '\0' && i < 4; i++) { this->shortname |= ((uint8_t)(*item->value)[i]) << (i * 8); } - fetch_metadata("version"); - this->version = atoi(item->value->c_str()); + item = this->GetMandatoryItem(full_filename, *metadata, "version"); + if (item == nullptr) return false; + for (StringConsumer consumer{*item->value};;) { + auto value = consumer.TryReadIntegerBase(10); + bool valid = value.has_value(); + if (valid) this->version.push_back(*value); + if (valid && !consumer.AnyBytesLeft()) break; + if (!valid || !consumer.ReadIf(".")) { + this->LogError(full_filename, fmt::format("metadata.version field is invalid: {}", *item->value)); + return false; + } + } item = metadata->GetItem("fallback"); this->fallback = (item != nullptr && item->value && *item->value != "0" && *item->value != "false"); @@ -80,13 +111,18 @@ bool BaseSet::FillSetDetails(const IniFile &ini, const std::string &path, con const IniGroup *md5s = ini.GetGroup("md5s"); const IniGroup *origin = ini.GetGroup("origin"); auto file_names = BaseSet::GetFilenames(); + bool original_set = + std::byteswap(this->shortname) == 'TTDD' || // TTD DOS graphics, TTD DOS music + std::byteswap(this->shortname) == 'TTDW' || // TTD WIN graphics, TTD WIN music + std::byteswap(this->shortname) == 'TTDO' || // TTD sound + std::byteswap(this->shortname) == 'TTOD'; // TTO music for (uint i = 0; i < BaseSet::NUM_FILES; i++) { MD5File *file = &this->files[i]; /* Find the filename first. */ item = files != nullptr ? files->GetItem(file_names[i]) : nullptr; if (item == nullptr || (!item->value.has_value() && !allow_empty_filename)) { - Debug(grf, 0, "No {} file for: {} (in {})", BaseSet::SET_TYPE, file_names[i], full_filename); + this->LogError(full_filename, fmt::format("files.{} field missing", file_names[i])); return false; } @@ -104,34 +140,19 @@ bool BaseSet::FillSetDetails(const IniFile &ini, const std::string &path, con /* Then find the MD5 checksum */ item = md5s != nullptr ? md5s->GetItem(filename) : nullptr; if (item == nullptr || !item->value.has_value()) { - Debug(grf, 0, "No MD5 checksum specified for: {} (in {})", filename, full_filename); + this->LogError(full_filename, fmt::format("md5s.{} field missing", filename)); return false; } - const char *c = item->value->c_str(); - for (size_t i = 0; i < file->hash.size() * 2; i++, c++) { - uint j; - if ('0' <= *c && *c <= '9') { - j = *c - '0'; - } else if ('a' <= *c && *c <= 'f') { - j = *c - 'a' + 10; - } else if ('A' <= *c && *c <= 'F') { - j = *c - 'A' + 10; - } else { - Debug(grf, 0, "Malformed MD5 checksum specified for: {} (in {})", filename, full_filename); - return false; - } - if (i % 2 == 0) { - file->hash[i / 2] = j << 4; - } else { - file->hash[i / 2] |= j; - } + if (!ConvertHexToBytes(*item->value, file->hash)) { + this->LogError(full_filename, fmt::format("md5s.{} is malformed: {}", filename, *item->value)); + return false; } /* Then find the warning message when the file's missing */ item = origin != nullptr ? origin->GetItem(filename) : nullptr; if (item == nullptr) item = origin != nullptr ? origin->GetItem("default") : nullptr; if (item == nullptr || !item->value.has_value()) { - Debug(grf, 1, "No origin warning message specified for: {}", filename); + this->LogError(full_filename, fmt::format("origin.{} field missing", filename), 1); file->missing_warning.clear(); } else { file->missing_warning = item->value.value(); @@ -148,12 +169,14 @@ bool BaseSet::FillSetDetails(const IniFile &ini, const std::string &path, con break; case MD5File::CR_MISMATCH: - Debug(grf, 1, "MD5 checksum mismatch for: {} (in {})", filename, full_filename); + /* This is normal for original sample.cat, which either matches with orig_dos or orig_win. */ + this->LogError(full_filename, fmt::format("MD5 checksum mismatch for: {}", filename), original_set ? 1 : 0); this->found_files++; break; case MD5File::CR_NO_FILE: - Debug(grf, 1, "The file {} specified in {} is missing", filename, full_filename); + /* Missing files is normal for the original basesets. Use lower debug level */ + this->LogError(full_filename, fmt::format("File is missing: {}", filename), original_set ? 1 : 0); break; } } @@ -164,10 +187,9 @@ bool BaseSet::FillSetDetails(const IniFile &ini, const std::string &path, con template bool BaseMedia::AddFile(const std::string &filename, size_t basepath_length, const std::string &) { - bool ret = false; - Debug(grf, 1, "Checking {} for base {} set", filename, BaseSet::SET_TYPE); + Debug(misc, 1, "Checking {} for base {} set", filename, BaseSet::SET_TYPE); - Tbase_set *set = new Tbase_set(); + auto set = std::make_unique(); IniFile ini{}; std::string path{ filename, basepath_length }; ini.LoadFromDisk(path, BASESET_DIR); @@ -179,60 +201,44 @@ bool BaseMedia::AddFile(const std::string &filename, size_t basepath_ path.clear(); } - if (set->FillSetDetails(ini, path, filename)) { - Tbase_set *duplicate = nullptr; - for (Tbase_set *c = BaseMedia::available_sets; c != nullptr; c = c->next) { - if (c->name == set->name || c->shortname == set->shortname) { - duplicate = c; - break; - } + if (!set->FillSetDetails(ini, path, filename)) return false; + + auto existing = std::ranges::find_if(BaseMedia::available_sets, [&set](const auto &c) { return c->name == set->name || c->shortname == set->shortname; }); + if (existing != std::end(BaseMedia::available_sets)) { + /* The more complete set takes precedence over the version number. */ + if (((*existing)->valid_files == set->valid_files && (*existing)->version >= set->version) || + (*existing)->valid_files > set->valid_files) { + + Debug(misc, 1, "Not adding {} ({}) as base {} set (duplicate, {})", set->name, fmt::join(set->version, "."), + BaseSet::SET_TYPE, + (*existing)->valid_files > set->valid_files ? "fewer valid files" : "lower version"); + + duplicate_sets.push_back(std::move(set)); + return false; } - if (duplicate != nullptr) { - /* The more complete set takes precedence over the version number. */ - if ((duplicate->valid_files == set->valid_files && duplicate->version >= set->version) || - duplicate->valid_files > set->valid_files) { - Debug(grf, 1, "Not adding {} ({}) as base {} set (duplicate, {})", set->name, set->version, - BaseSet::SET_TYPE, - duplicate->valid_files > set->valid_files ? "less valid files" : "lower version"); - set->next = BaseMedia::duplicate_sets; - BaseMedia::duplicate_sets = set; - } else { - Tbase_set **prev = &BaseMedia::available_sets; - while (*prev != duplicate) prev = &(*prev)->next; - *prev = set; - set->next = duplicate->next; + /* If the duplicate set is currently used (due to rescanning this can happen) + * update the currently used set to the new one. This will 'lie' about the + * version number until a new game is started which isn't a big problem */ + if (BaseMedia::used_set == existing->get()) BaseMedia::used_set = set.get(); - /* Keep baseset configuration, if compatible */ - set->CopyCompatibleConfig(*duplicate); + /* Keep baseset configuration, if compatible */ + set->CopyCompatibleConfig(**existing); - /* If the duplicate set is currently used (due to rescanning this can happen) - * update the currently used set to the new one. This will 'lie' about the - * version number until a new game is started which isn't a big problem */ - if (BaseMedia::used_set == duplicate) BaseMedia::used_set = set; + Debug(misc, 1, "Removing {} ({}) as base {} set (duplicate, {})", (*existing)->name, fmt::join((*existing)->version, "."), BaseSet::SET_TYPE, + (*existing)->valid_files < set->valid_files ? "fewer valid files" : "lower version"); - Debug(grf, 1, "Removing {} ({}) as base {} set (duplicate, {})", duplicate->name, duplicate->version, - BaseSet::SET_TYPE, - duplicate->valid_files < set->valid_files ? "less valid files" : "lower version"); - duplicate->next = BaseMedia::duplicate_sets; - BaseMedia::duplicate_sets = duplicate; - ret = true; - } - } else { - Tbase_set **last = &BaseMedia::available_sets; - while (*last != nullptr) last = &(*last)->next; + /* Existing set is worse, move it to duplicates and replace with the current set. */ + duplicate_sets.push_back(std::move(*existing)); - *last = set; - ret = true; - } - if (ret) { - Debug(grf, 1, "Adding {} ({}) as base {} set", set->name, set->version, BaseSet::SET_TYPE); - } + Debug(misc, 1, "Adding {} ({}) as base {} set", set->name, fmt::join(set->version, "."), BaseSet::SET_TYPE); + *existing = std::move(set); } else { - delete set; + Debug(grf, 1, "Adding {} ({}) as base {} set", set->name, set->version, BaseSet::SET_TYPE); + available_sets.push_back(std::move(set)); } - return ret; + return true; } /** @@ -264,9 +270,9 @@ template return SetSet(nullptr); } - for (const Tbase_set *s = BaseMedia::available_sets; s != nullptr; s = s->next) { + for (const auto &s : BaseMedia::available_sets) { if (name == s->name) { - return SetSet(s); + return SetSet(s.get()); } } return false; @@ -284,9 +290,9 @@ template return SetSet(nullptr); } - for (const Tbase_set *s = BaseMedia::available_sets; s != nullptr; s = s->next) { + for (const auto &s : BaseMedia::available_sets) { if (shortname == s->shortname) { - return SetSet(s); + return SetSet(s.get()); } } return false; @@ -300,7 +306,7 @@ template /* static */ void BaseMedia::GetSetsList(std::back_insert_iterator &output_iterator) { fmt::format_to(output_iterator, "List of {} sets:\n", BaseSet::SET_TYPE); - for (const Tbase_set *s = BaseMedia::available_sets; s != nullptr; s = s->next) { + for (const auto &s : BaseMedia::available_sets) { fmt::format_to(output_iterator, "{:>18}: {}", s->name, s->GetDescription({})); int invalid = s->GetNumInvalid(); if (invalid != 0) { @@ -319,28 +325,28 @@ template #include "network/core/tcp_content_type.h" -template const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const Tbase_set *s) +template std::optional TryGetBaseSetFile(const ContentInfo &ci, bool md5sum, std::span> sets) { - for (; s != nullptr; s = s->next) { + for (const auto &s : sets) { if (s->GetNumMissing() != 0) continue; - if (s->shortname != ci->unique_id) continue; - if (!md5sum) return s->files[0].filename.c_str(); + if (s->shortname != ci.unique_id) continue; + if (!md5sum) return s->files[0].filename; MD5Hash md5; for (const auto &file : s->files) { md5 ^= file.hash; } - if (md5 == ci->md5sum) return s->files[0].filename.c_str(); + if (md5 == ci.md5sum) return s->files[0].filename; } - return nullptr; + return std::nullopt; } template -/* static */ bool BaseMedia::HasSet(const ContentInfo *ci, bool md5sum) +/* static */ bool BaseMedia::HasSet(const ContentInfo &ci, bool md5sum) { - return (TryGetBaseSetFile(ci, md5sum, BaseMedia::available_sets) != nullptr) || - (TryGetBaseSetFile(ci, md5sum, BaseMedia::duplicate_sets) != nullptr); + return TryGetBaseSetFile(ci, md5sum, BaseMedia::GetAvailableSets()).has_value() || + TryGetBaseSetFile(ci, md5sum, BaseMedia::GetDuplicateSets()).has_value(); } /** @@ -350,12 +356,9 @@ template template /* static */ int BaseMedia::GetNumSets() { - int n = 0; - for (const Tbase_set *s = BaseMedia::available_sets; s != nullptr; s = s->next) { - if (s != BaseMedia::used_set && s->GetNumMissing() != 0) continue; - n++; - } - return n; + return std::ranges::count_if(BaseMedia::GetAvailableSets(), [](const auto &set) { + return set.get() == BaseMedia::used_set || set->GetNumMissing() == 0; + }); } /** @@ -366,8 +369,8 @@ template /* static */ int BaseMedia::GetIndexOfUsedSet() { int n = 0; - for (const Tbase_set *s = BaseMedia::available_sets; s != nullptr; s = s->next) { - if (s == BaseMedia::used_set) return n; + for (const auto &s : BaseMedia::available_sets) { + if (s.get() == BaseMedia::used_set) return n; if (s->GetNumMissing() != 0) continue; n++; } @@ -381,9 +384,9 @@ template template /* static */ const Tbase_set *BaseMedia::GetSet(int index) { - for (const Tbase_set *s = BaseMedia::available_sets; s != nullptr; s = s->next) { - if (s != BaseMedia::used_set && s->GetNumMissing() != 0) continue; - if (index == 0) return s; + for (const auto &s : BaseMedia::available_sets) { + if (s.get() != BaseMedia::used_set && s->GetNumMissing() != 0) continue; + if (index == 0) return s.get(); index--; } FatalError("Base{}::GetSet(): index {} out of range", BaseSet::SET_TYPE, index); @@ -398,13 +401,3 @@ template { return BaseMedia::used_set; } - -/** - * Return the available sets. - * @return The available sets. - */ -template -/* static */ Tbase_set *BaseMedia::GetAvailableSets() -{ - return BaseMedia::available_sets; -} diff --git a/src/base_station_base.h b/src/base_station_base.h index dd6d3a5976..fd493d7853 100644 --- a/src/base_station_base.h +++ b/src/base_station_base.h @@ -74,9 +74,9 @@ struct BaseStation : StationPool::PoolItem<&_station_pool> { TimerGameCalendar::Date build_date{}; ///< Date of construction uint16_t random_bits = 0; ///< Random bits assigned to this station - uint8_t waiting_triggers = 0; ///< Waiting triggers (NewGRF) for this station - uint8_t cached_anim_triggers = 0; ///< NOSAVE: Combined animation trigger bitmask, used to determine if trigger processing should happen. - uint8_t cached_roadstop_anim_triggers = 0; ///< NOSAVE: Combined animation trigger bitmask for road stops, used to determine if trigger processing should happen. + StationRandomTriggers waiting_random_triggers; ///< Waiting triggers (NewGRF), shared by all station parts/tiles, road stops, ... essentially useless and broken by design. + StationAnimationTriggers cached_anim_triggers; ///< NOSAVE: Combined animation trigger bitmask, used to determine if trigger processing should happen. + StationAnimationTriggers cached_roadstop_anim_triggers; ///< NOSAVE: Combined animation trigger bitmask for road stops, used to determine if trigger processing should happen. CargoTypes cached_cargo_triggers{}; ///< NOSAVE: Combined cargo trigger bitmask CargoTypes cached_roadstop_cargo_triggers{}; ///< NOSAVE: Combined cargo trigger bitmask for road stops diff --git a/src/blitter/32bpp_anim.cpp b/src/blitter/32bpp_anim.cpp index 6635d34d0f..5006305789 100644 --- a/src/blitter/32bpp_anim.cpp +++ b/src/blitter/32bpp_anim.cpp @@ -25,8 +25,8 @@ inline void Blitter_32bppAnim::Draw(const Blitter::BlitterParams *bp, ZoomLevel { const SpriteData *src = (const SpriteData *)bp->sprite; - const Colour *src_px = (const Colour *)(src->data + src->offset[zoom][0]); - const uint16_t *src_n = (const uint16_t *)(src->data + src->offset[zoom][1]); + const Colour *src_px = reinterpret_cast(src->data + src->offset[0][zoom]); + const uint16_t *src_n = reinterpret_cast(src->data + src->offset[1][zoom]); for (uint i = bp->skip_top; i != 0; i--) { src_px = (const Colour *)((const uint8_t *)src_px + *(const uint32_t *)src_px); @@ -389,11 +389,11 @@ void Blitter_32bppAnim::CopyFromBuffer(void *video, const void *src, int width, Colour *dst_pal = dst; uint16_t *anim_pal = anim_line; - memcpy(static_cast(dst), usrc, width * sizeof(uint32_t)); + std::copy_n(usrc, width, reinterpret_cast(dst)); usrc += width; dst += _screen.pitch; /* Copy back the anim-buffer */ - memcpy(anim_line, usrc, width * sizeof(uint16_t)); + std::copy_n(reinterpret_cast(usrc), width, anim_line); usrc = (const uint32_t *)&((const uint16_t *)usrc)[width]; anim_line += this->anim_buf_pitch; @@ -428,11 +428,11 @@ void Blitter_32bppAnim::CopyToBuffer(const void *video, void *dst, int width, in const uint16_t *anim_line = this->ScreenToAnimOffset((const uint32_t *)video) + this->anim_buf; for (; height > 0; height--) { - memcpy(udst, src, width * sizeof(uint32_t)); + std::copy_n(src, width, udst); src += _screen.pitch; udst += width; /* Copy the anim-buffer */ - memcpy(udst, anim_line, width * sizeof(uint16_t)); + std::copy_n(anim_line, width, reinterpret_cast(udst)); udst = (uint32_t *)&((uint16_t *)udst)[width]; anim_line += this->anim_buf_pitch; } @@ -458,11 +458,7 @@ void Blitter_32bppAnim::ScrollBuffer(void *video, int &left, int &top, int &widt uint tw = width + (scroll_x >= 0 ? -scroll_x : scroll_x); uint th = height - scroll_y; - for (; th > 0; th--) { - memcpy(dst, src, tw * sizeof(uint16_t)); - src -= this->anim_buf_pitch; - dst -= this->anim_buf_pitch; - } + Blitter::MovePixels(src, dst, tw, th, -this->anim_buf_pitch); } else { /* Calculate pointers */ dst = this->anim_buf + left + top * this->anim_buf_pitch; @@ -475,15 +471,9 @@ void Blitter_32bppAnim::ScrollBuffer(void *video, int &left, int &top, int &widt src -= scroll_x; } - /* the y-displacement may be 0 therefore we have to use memmove, - * because source and destination may overlap */ uint tw = width + (scroll_x >= 0 ? -scroll_x : scroll_x); uint th = height + scroll_y; - for (; th > 0; th--) { - memmove(dst, src, tw * sizeof(uint16_t)); - src += this->anim_buf_pitch; - dst += this->anim_buf_pitch; - } + Blitter::MovePixels(src, dst, tw, th, this->anim_buf_pitch); } Blitter_32bppBase::ScrollBuffer(video, left, top, width, height, scroll_x, scroll_y); diff --git a/src/blitter/32bpp_anim_sse4.cpp b/src/blitter/32bpp_anim_sse4.cpp index 37da12f71c..4ed29b33c1 100644 --- a/src/blitter/32bpp_anim_sse4.cpp +++ b/src/blitter/32bpp_anim_sse4.cpp @@ -190,8 +190,8 @@ bmno_full_transparency: const uint m1 = (uint8_t) (mvX2 >> 16); const uint r1 = remap[m1]; if (mvX2 & 0x00FF00FF) { + /* Written so the compiler uses CMOV. */ #define CMOV_REMAP(m_colour, m_colour_init, m_src, m_m) \ - /* Written so the compiler uses CMOV. */ \ Colour m_colour = m_colour_init; \ { \ const Colour srcm = (Colour) (m_src); \ diff --git a/src/blitter/32bpp_anim_sse4.hpp b/src/blitter/32bpp_anim_sse4.hpp index cad4295a9b..c325fb806c 100644 --- a/src/blitter/32bpp_anim_sse4.hpp +++ b/src/blitter/32bpp_anim_sse4.hpp @@ -39,8 +39,10 @@ public: template void Draw(const Blitter::BlitterParams *bp, ZoomLevel zoom); void Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom) override; - Sprite *Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override { - return Blitter_32bppSSE_Base::Encode(sprite, allocator); + + Sprite *Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override + { + return Blitter_32bppSSE_Base::Encode(sprite_type, sprite, allocator); } std::string_view GetName() override { return "32bpp-sse4-anim"; } using Blitter_32bppSSE2_Anim::LookupColourInPalette; diff --git a/src/blitter/32bpp_base.cpp b/src/blitter/32bpp_base.cpp index 58acafc35a..5c25d4185f 100644 --- a/src/blitter/32bpp_base.cpp +++ b/src/blitter/32bpp_base.cpp @@ -51,7 +51,7 @@ void Blitter_32bppBase::CopyFromBuffer(void *video, const void *src, int width, const uint32_t *usrc = (const uint32_t *)src; for (; height > 0; height--) { - memcpy(dst, usrc, width * sizeof(uint32_t)); + std::copy_n(usrc, width, dst); usrc += width; dst += _screen.pitch; } @@ -63,7 +63,7 @@ void Blitter_32bppBase::CopyToBuffer(const void *video, void *dst, int width, in const uint32_t *src = (const uint32_t *)video; for (; height > 0; height--) { - memcpy(udst, src, width * sizeof(uint32_t)); + std::copy_n(src, width, udst); src += _screen.pitch; udst += width; } @@ -75,7 +75,7 @@ void Blitter_32bppBase::CopyImageToBuffer(const void *video, void *dst, int widt const uint32_t *src = (const uint32_t *)video; for (; height > 0; height--) { - memcpy(udst, src, width * sizeof(uint32_t)); + std::copy_n(src, width, udst); src += _screen.pitch; udst += dst_pitch; } @@ -106,11 +106,7 @@ void Blitter_32bppBase::ScrollBuffer(void *video, int &left, int &top, int &widt width += scroll_x; } - for (int h = height; h > 0; h--) { - memcpy(dst, src, width * sizeof(uint32_t)); - src -= _screen.pitch; - dst -= _screen.pitch; - } + Blitter::MovePixels(src, dst, width, height, -_screen.pitch); } else { /* Calculate pointers */ dst = (uint32_t *)video + left + top * _screen.pitch; @@ -130,13 +126,7 @@ void Blitter_32bppBase::ScrollBuffer(void *video, int &left, int &top, int &widt width += scroll_x; } - /* the y-displacement may be 0 therefore we have to use memmove, - * because source and destination may overlap */ - for (int h = height; h > 0; h--) { - memmove(dst, src, width * sizeof(uint32_t)); - src += _screen.pitch; - dst += _screen.pitch; - } + Blitter::MovePixels(src, dst, width, height, _screen.pitch); } } diff --git a/src/blitter/32bpp_optimized.cpp b/src/blitter/32bpp_optimized.cpp index eebdece030..7667597636 100644 --- a/src/blitter/32bpp_optimized.cpp +++ b/src/blitter/32bpp_optimized.cpp @@ -32,11 +32,11 @@ inline void Blitter_32bppOptimized::Draw(const Blitter::BlitterParams *bp, ZoomL /* src_px : each line begins with uint32_t n = 'number of bytes in this line', * then n times is the Colour struct for this line */ - const Colour *src_px = (const Colour *)(src->data + src->offset[zoom][0]); + const Colour *src_px = reinterpret_cast(src->data + src->offset[0][zoom]); /* src_n : each line begins with uint32_t n = 'number of bytes in this line', * then interleaved stream of 'm' and 'n' channels. 'm' is remap, * 'n' is number of bytes with the same alpha channel class */ - const uint16_t *src_n = (const uint16_t *)(src->data + src->offset[zoom][1]); + const uint16_t *src_n = reinterpret_cast(src->data + src->offset[1][zoom]); /* skip upper lines in src_px and src_n */ for (uint i = bp->skip_top; i != 0; i--) { @@ -285,12 +285,13 @@ void Blitter_32bppOptimized::Draw(Blitter::BlitterParams *bp, BlitterMode mode, this->Draw(bp, mode, zoom); } -template Sprite *Blitter_32bppOptimized::EncodeInternal(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) +template +Sprite *Blitter_32bppOptimized::EncodeInternal(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) { /* streams of pixels (a, r, g, b channels) * * stored in separated stream so data are always aligned on 4B boundary */ - std::array, ZOOM_LVL_END> dst_px_orig; + SpriteCollMap> dst_px_orig; /* interleaved stream of 'm' channel and 'n' channel * 'n' is number of following pixels with the same alpha channel class @@ -298,21 +299,21 @@ template Sprite *Blitter_32bppOptimized::EncodeInternal(const * * it has to be stored in one stream so fewer registers are used - * x86 has problems with register allocation even with this solution */ - std::array, ZOOM_LVL_END> dst_n_orig; + SpriteCollMap> dst_n_orig; /* lengths of streams */ - uint32_t lengths[ZOOM_LVL_END][2]; + SpriteCollMap lengths[2]; ZoomLevel zoom_min; ZoomLevel zoom_max; - if (sprite[ZOOM_LVL_MIN].type == SpriteType::Font) { - zoom_min = ZOOM_LVL_MIN; - zoom_max = ZOOM_LVL_MIN; + if (sprite_type == SpriteType::Font) { + zoom_min = ZoomLevel::Min; + zoom_max = ZoomLevel::Min; } else { zoom_min = _settings_client.gui.zoom_min; zoom_max = _settings_client.gui.zoom_max; - if (zoom_max == zoom_min) zoom_max = ZOOM_LVL_MAX; + if (zoom_max == zoom_min) zoom_max = ZoomLevel::Max; } for (ZoomLevel z = zoom_min; z <= zoom_max; z++) { @@ -405,40 +406,43 @@ template Sprite *Blitter_32bppOptimized::EncodeInternal(const dst_n_ln = (uint32_t *)dst_n; } - lengths[z][0] = reinterpret_cast(dst_px_ln) - reinterpret_cast(dst_px_orig[z].get()); // all are aligned to 4B boundary - lengths[z][1] = reinterpret_cast(dst_n_ln) - reinterpret_cast(dst_n_orig[z].get()); + lengths[0][z] = reinterpret_cast(dst_px_ln) - reinterpret_cast(dst_px_orig[z].get()); // all are aligned to 4B boundary + lengths[1][z] = reinterpret_cast(dst_n_ln) - reinterpret_cast(dst_n_orig[z].get()); } uint len = 0; // total length of data for (ZoomLevel z = zoom_min; z <= zoom_max; z++) { - len += lengths[z][0] + lengths[z][1]; + len += lengths[0][z] + lengths[1][z]; } Sprite *dest_sprite = allocator.Allocate(sizeof(*dest_sprite) + sizeof(SpriteData) + len); - dest_sprite->height = sprite[ZOOM_LVL_MIN].height; - dest_sprite->width = sprite[ZOOM_LVL_MIN].width; - dest_sprite->x_offs = sprite[ZOOM_LVL_MIN].x_offs; - dest_sprite->y_offs = sprite[ZOOM_LVL_MIN].y_offs; + const auto &root_sprite = sprite.Root(); + dest_sprite->height = root_sprite.height; + dest_sprite->width = root_sprite.width; + dest_sprite->x_offs = root_sprite.x_offs; + dest_sprite->y_offs = root_sprite.y_offs; SpriteData *dst = (SpriteData *)dest_sprite->data; - memset(dst, 0, sizeof(*dst)); + uint32_t offset = 0; for (ZoomLevel z = zoom_min; z <= zoom_max; z++) { - dst->offset[z][0] = z == zoom_min ? 0 : lengths[z - 1][1] + dst->offset[z - 1][1]; - dst->offset[z][1] = lengths[z][0] + dst->offset[z][0]; + dst->offset[0][z] = offset; + offset += lengths[0][z]; + dst->offset[1][z] = offset; + offset += lengths[1][z]; - memcpy(dst->data + dst->offset[z][0], dst_px_orig[z].get(), lengths[z][0]); - memcpy(dst->data + dst->offset[z][1], dst_n_orig[z].get(), lengths[z][1]); + std::copy_n(reinterpret_cast(dst_px_orig[z].get()), lengths[0][z], dst->data + dst->offset[0][z]); + std::copy_n(reinterpret_cast(dst_n_orig[z].get()), lengths[1][z], dst->data + dst->offset[1][z]); } return dest_sprite; } -template Sprite *Blitter_32bppOptimized::EncodeInternal(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator); -template Sprite *Blitter_32bppOptimized::EncodeInternal(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator); +template Sprite *Blitter_32bppOptimized::EncodeInternal(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator); +template Sprite *Blitter_32bppOptimized::EncodeInternal(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator); -Sprite *Blitter_32bppOptimized::Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) +Sprite *Blitter_32bppOptimized::Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) { - return this->EncodeInternal(sprite, allocator); + return this->EncodeInternal(sprite_type, sprite, allocator); } diff --git a/src/blitter/32bpp_optimized.hpp b/src/blitter/32bpp_optimized.hpp index ccd7d9ac2e..c1a8a37cd2 100644 --- a/src/blitter/32bpp_optimized.hpp +++ b/src/blitter/32bpp_optimized.hpp @@ -17,12 +17,12 @@ class Blitter_32bppOptimized : public Blitter_32bppSimple { public: /** Data stored about a (single) sprite. */ struct SpriteData { - uint32_t offset[ZOOM_LVL_END][2]; ///< Offsets (from .data) to streams for different zoom levels, and the normal and remap image information. + SpriteCollMap offset[2]; ///< Offsets (from .data) to streams for different zoom levels, and the normal and remap image information. uint8_t data[]; ///< Data, all zoomlevels. }; void Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom) override; - Sprite *Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override; + Sprite *Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override; std::string_view GetName() override { return "32bpp-optimized"; } @@ -30,7 +30,7 @@ public: protected: template void Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom); - template Sprite *EncodeInternal(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator); + template Sprite *EncodeInternal(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator); }; /** Factory for the optimised 32 bpp blitter (without palette animation). */ diff --git a/src/blitter/32bpp_simple.cpp b/src/blitter/32bpp_simple.cpp index e55aadb6dc..d3fe2f98ca 100644 --- a/src/blitter/32bpp_simple.cpp +++ b/src/blitter/32bpp_simple.cpp @@ -115,20 +115,21 @@ void Blitter_32bppSimple::DrawColourMappingRect(void *dst, int width, int height Debug(misc, 0, "32bpp blitter doesn't know how to draw this colour table ('{}')", pal); } -Sprite *Blitter_32bppSimple::Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) +Sprite *Blitter_32bppSimple::Encode(SpriteType, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) { + const auto &root_sprite = sprite.Root(); Blitter_32bppSimple::Pixel *dst; - Sprite *dest_sprite = allocator.Allocate(sizeof(*dest_sprite) + static_cast(sprite[ZOOM_LVL_MIN].height) * static_cast(sprite[ZOOM_LVL_MIN].width) * sizeof(*dst)); + Sprite *dest_sprite = allocator.Allocate(sizeof(*dest_sprite) + static_cast(root_sprite.height) * static_cast(root_sprite.width) * sizeof(*dst)); - dest_sprite->height = sprite[ZOOM_LVL_MIN].height; - dest_sprite->width = sprite[ZOOM_LVL_MIN].width; - dest_sprite->x_offs = sprite[ZOOM_LVL_MIN].x_offs; - dest_sprite->y_offs = sprite[ZOOM_LVL_MIN].y_offs; + dest_sprite->height = root_sprite.height; + dest_sprite->width = root_sprite.width; + dest_sprite->x_offs = root_sprite.x_offs; + dest_sprite->y_offs = root_sprite.y_offs; - dst = (Blitter_32bppSimple::Pixel *)dest_sprite->data; - SpriteLoader::CommonPixel *src = (SpriteLoader::CommonPixel *)sprite[ZOOM_LVL_MIN].data; + dst = reinterpret_cast(dest_sprite->data); + SpriteLoader::CommonPixel *src = reinterpret_cast(root_sprite.data); - for (int i = 0; i < sprite[ZOOM_LVL_MIN].height * sprite[ZOOM_LVL_MIN].width; i++) { + for (int i = 0; i < root_sprite.height * root_sprite.width; i++) { if (src->m == 0) { dst[i].r = src->r; dst[i].g = src->g; diff --git a/src/blitter/32bpp_simple.hpp b/src/blitter/32bpp_simple.hpp index cbecf3c44b..4bacba345f 100644 --- a/src/blitter/32bpp_simple.hpp +++ b/src/blitter/32bpp_simple.hpp @@ -26,7 +26,7 @@ class Blitter_32bppSimple : public Blitter_32bppBase { public: void Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom) override; void DrawColourMappingRect(void *dst, int width, int height, PaletteID pal) override; - Sprite *Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override; + Sprite *Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override; std::string_view GetName() override { return "32bpp-simple"; } }; diff --git a/src/blitter/32bpp_sse2.cpp b/src/blitter/32bpp_sse2.cpp index c0ccb269c3..6fc08ca6cd 100644 --- a/src/blitter/32bpp_sse2.cpp +++ b/src/blitter/32bpp_sse2.cpp @@ -20,18 +20,18 @@ /** Instantiation of the SSE2 32bpp blitter factory. */ static FBlitter_32bppSSE2 iFBlitter_32bppSSE2; -Sprite *Blitter_32bppSSE_Base::Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) +Sprite *Blitter_32bppSSE_Base::Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) { /* First uint32_t of a line = the number of transparent pixels from the left. * Second uint32_t of a line = the number of transparent pixels from the right. * Then all RGBA then all MV. */ - ZoomLevel zoom_min = ZOOM_LVL_MIN; - ZoomLevel zoom_max = ZOOM_LVL_MIN; - if (sprite[ZOOM_LVL_MIN].type != SpriteType::Font) { + ZoomLevel zoom_min = ZoomLevel::Min; + ZoomLevel zoom_max = ZoomLevel::Min; + if (sprite_type != SpriteType::Font) { zoom_min = _settings_client.gui.zoom_min; zoom_max = _settings_client.gui.zoom_max; - if (zoom_max == zoom_min) zoom_max = ZOOM_LVL_MAX; + if (zoom_max == zoom_min) zoom_max = ZoomLevel::Max; } /* Calculate sizes and allocate. */ @@ -39,23 +39,25 @@ Sprite *Blitter_32bppSSE_Base::Encode(const SpriteLoader::SpriteCollection &spri uint all_sprites_size = 0; for (ZoomLevel z = zoom_min; z <= zoom_max; z++) { const SpriteLoader::Sprite *src_sprite = &sprite[z]; - sd.infos[z].sprite_width = src_sprite->width; - sd.infos[z].sprite_offset = all_sprites_size; - sd.infos[z].sprite_line_size = sizeof(Colour) * src_sprite->width + sizeof(uint32_t) * META_LENGTH; + auto &info = sd.infos[z]; + info.sprite_width = src_sprite->width; + info.sprite_offset = all_sprites_size; + info.sprite_line_size = sizeof(Colour) * src_sprite->width + sizeof(uint32_t) * META_LENGTH; - const uint rgba_size = sd.infos[z].sprite_line_size * src_sprite->height; - sd.infos[z].mv_offset = all_sprites_size + rgba_size; + const uint rgba_size = info.sprite_line_size * src_sprite->height; + info.mv_offset = all_sprites_size + rgba_size; const uint mv_size = sizeof(MapValue) * src_sprite->width * src_sprite->height; all_sprites_size += rgba_size + mv_size; } Sprite *dst_sprite = allocator.Allocate(sizeof(Sprite) + sizeof(SpriteData) + all_sprites_size); - dst_sprite->height = sprite[ZOOM_LVL_MIN].height; - dst_sprite->width = sprite[ZOOM_LVL_MIN].width; - dst_sprite->x_offs = sprite[ZOOM_LVL_MIN].x_offs; - dst_sprite->y_offs = sprite[ZOOM_LVL_MIN].y_offs; - memcpy(dst_sprite->data, &sd, sizeof(SpriteData)); + const auto &root_sprite = sprite.Root(); + dst_sprite->height = root_sprite.height; + dst_sprite->width = root_sprite.width; + dst_sprite->x_offs = root_sprite.x_offs; + dst_sprite->y_offs = root_sprite.y_offs; + std::copy_n(reinterpret_cast(&sd), sizeof(SpriteData), dst_sprite->data); /* Copy colours and determine flags. */ bool has_remap = false; @@ -64,8 +66,9 @@ Sprite *Blitter_32bppSSE_Base::Encode(const SpriteLoader::SpriteCollection &spri for (ZoomLevel z = zoom_min; z <= zoom_max; z++) { const SpriteLoader::Sprite *src_sprite = &sprite[z]; const SpriteLoader::CommonPixel *src = (const SpriteLoader::CommonPixel *) src_sprite->data; - Colour *dst_rgba_line = (Colour *) &dst_sprite->data[sizeof(SpriteData) + sd.infos[z].sprite_offset]; - MapValue *dst_mv = (MapValue *) &dst_sprite->data[sizeof(SpriteData) + sd.infos[z].mv_offset]; + const auto &info = sd.infos[z]; + Colour *dst_rgba_line = reinterpret_cast(&dst_sprite->data[sizeof(SpriteData) + info.sprite_offset]); + MapValue *dst_mv = reinterpret_cast(&dst_sprite->data[sizeof(SpriteData) + info.mv_offset]); for (uint y = src_sprite->height; y != 0; y--) { Colour *dst_rgba = dst_rgba_line + META_LENGTH; for (uint x = src_sprite->width; x != 0; x--) { @@ -113,7 +116,7 @@ Sprite *Blitter_32bppSSE_Base::Encode(const SpriteLoader::SpriteCollection &spri (*dst_rgba_line).data = nb_pix_transp; Colour *nb_right = dst_rgba_line + 1; - dst_rgba_line = (Colour*) ((uint8_t*) dst_rgba_line + sd.infos[z].sprite_line_size); + dst_rgba_line = reinterpret_cast(reinterpret_cast(dst_rgba_line) + info.sprite_line_size); /* Count the number of transparent pixels from the right. */ dst_rgba = dst_rgba_line - 1; @@ -132,7 +135,7 @@ Sprite *Blitter_32bppSSE_Base::Encode(const SpriteLoader::SpriteCollection &spri if (has_translucency) sd.flags.Set(SpriteFlag::Translucent); if (!has_remap) sd.flags.Set(SpriteFlag::NoRemap); if (!has_anim) sd.flags.Set(SpriteFlag::NoAnim); - memcpy(dst_sprite->data, &sd, sizeof(SpriteData)); + std::copy_n(reinterpret_cast(&sd), sizeof(SpriteData), dst_sprite->data); return dst_sprite; } diff --git a/src/blitter/32bpp_sse2.hpp b/src/blitter/32bpp_sse2.hpp index 4d2c1f10bc..383ef41c39 100644 --- a/src/blitter/32bpp_sse2.hpp +++ b/src/blitter/32bpp_sse2.hpp @@ -73,11 +73,11 @@ public: }; struct SpriteData { SpriteFlags flags{}; - std::array infos{}; + SpriteCollMap infos{}; uint8_t data[]; ///< Data, all zoomlevels. }; - Sprite *Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator); + Sprite *Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator); }; /** The SSE2 32 bpp blitter (without palette animation). */ @@ -87,8 +87,9 @@ public: template void Draw(const Blitter::BlitterParams *bp, ZoomLevel zoom); - Sprite *Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override { - return Blitter_32bppSSE_Base::Encode(sprite, allocator); + Sprite *Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override + { + return Blitter_32bppSSE_Base::Encode(sprite_type, sprite, allocator); } std::string_view GetName() override { return "32bpp-sse2"; } diff --git a/src/blitter/32bpp_sse_func.hpp b/src/blitter/32bpp_sse_func.hpp index f931ba3077..bed92686cc 100644 --- a/src/blitter/32bpp_sse_func.hpp +++ b/src/blitter/32bpp_sse_func.hpp @@ -309,8 +309,8 @@ inline void Blitter_32bppSSE4::Draw(const Blitter::BlitterParams *bp, ZoomLevel /* Remap colours. */ if (mvX2 & 0x00FF00FF) { + /* Written so the compiler uses CMOV. */ #define CMOV_REMAP(m_colour, m_colour_init, m_src, m_m) \ - /* Written so the compiler uses CMOV. */ \ Colour m_colour = m_colour_init; \ { \ const Colour srcm = (Colour) (m_src); \ diff --git a/src/blitter/32bpp_sse_type.h b/src/blitter/32bpp_sse_type.h index c9a05e5c08..78d05e67e3 100644 --- a/src/blitter/32bpp_sse_type.h +++ b/src/blitter/32bpp_sse_type.h @@ -28,18 +28,10 @@ #endif #define META_LENGTH 2 ///< Number of uint32_t inserted before each line of pixels in a sprite. -#define MARGIN_NORMAL_THRESHOLD (zoom == ZOOM_LVL_OUT_8X ? 8 : 4) ///< Minimum width to use margins with BlitterMode::Normal. +#define MARGIN_NORMAL_THRESHOLD (zoom == ZoomLevel::Out8x ? 8 : 4) ///< Minimum width to use margins with BlitterMode::Normal. #define MARGIN_REMAP_THRESHOLD 4 ///< Minimum width to use margins with BlitterMode::ColourRemap. -#undef ALIGN - -#ifdef _MSC_VER - #define ALIGN(n) __declspec(align(n)) -#else - #define ALIGN(n) __attribute__ ((aligned (n))) -#endif - -typedef union ALIGN(16) um128i { +typedef union alignas(16) um128i { __m128i m128i; uint8_t m128i_u8[16]; uint16_t m128i_u16[8]; diff --git a/src/blitter/40bpp_anim.cpp b/src/blitter/40bpp_anim.cpp index 866389644a..7ef8cd4ba8 100644 --- a/src/blitter/40bpp_anim.cpp +++ b/src/blitter/40bpp_anim.cpp @@ -96,11 +96,11 @@ inline void Blitter_40bppAnim::Draw(const Blitter::BlitterParams *bp, ZoomLevel /* src_px : each line begins with uint32_t n = 'number of bytes in this line', * then n times is the Colour struct for this line */ - const Colour *src_px = (const Colour *)(src->data + src->offset[zoom][0]); + const Colour *src_px = reinterpret_cast(src->data + src->offset[0][zoom]); /* src_n : each line begins with uint32_t n = 'number of bytes in this line', * then interleaved stream of 'm' and 'n' channels. 'm' is remap, * 'n' is number of bytes with the same alpha channel class */ - const uint16_t *src_n = (const uint16_t *)(src->data + src->offset[zoom][1]); + const uint16_t *src_n = reinterpret_cast(src->data + src->offset[1][zoom]); /* skip upper lines in src_px and src_n */ for (uint i = bp->skip_top; i != 0; i--) { @@ -377,8 +377,11 @@ void Blitter_40bppAnim::DrawColourMappingRect(void *dst, int width, int height, const uint8_t *remap = GetNonSprite(pal, SpriteType::Recolour) + 1; do { for (int i = 0; i != width; i++) { - if (*anim == 0) *udst = MakeGrey(*udst); - *anim = remap[*anim]; + if (*anim == 0) { + *udst = MakeGrey(*udst); + } else { + *anim = remap[*anim]; + } udst++; anim++; } @@ -389,7 +392,7 @@ void Blitter_40bppAnim::DrawColourMappingRect(void *dst, int width, int height, const uint8_t *remap = GetNonSprite(pal, SpriteType::Recolour) + 1; do { for (int i = 0; i != width; i++) { - *anim = remap[*anim]; + if (*anim != 0) *anim = remap[*anim]; anim++; } anim = anim - width + _screen.pitch; @@ -397,9 +400,9 @@ void Blitter_40bppAnim::DrawColourMappingRect(void *dst, int width, int height, } } -Sprite *Blitter_40bppAnim::Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) +Sprite *Blitter_40bppAnim::Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) { - return this->EncodeInternal(sprite, allocator); + return this->EncodeInternal(sprite_type, sprite, allocator); } @@ -415,11 +418,11 @@ void Blitter_40bppAnim::CopyFromBuffer(void *video, const void *src, int width, uint8_t *anim_line = ((uint32_t *)video - (uint32_t *)_screen.dst_ptr) + anim_buf; for (; height > 0; height--) { - memcpy(dst, usrc, width * sizeof(uint32_t)); + std::copy_n(usrc, width, dst); usrc += width; dst += _screen.pitch; /* Copy back the anim-buffer */ - memcpy(anim_line, usrc, width * sizeof(uint8_t)); + std::copy_n(reinterpret_cast(usrc), width, anim_line); usrc = (const uint32_t *)((const uint8_t *)usrc + width); anim_line += _screen.pitch; } @@ -437,11 +440,11 @@ void Blitter_40bppAnim::CopyToBuffer(const void *video, void *dst, int width, in const uint8_t *anim_line = ((const uint32_t *)video - (uint32_t *)_screen.dst_ptr) + anim_buf; for (; height > 0; height--) { - memcpy(udst, src, width * sizeof(uint32_t)); + std::copy_n(src, width, udst); src += _screen.pitch; udst += width; /* Copy the anim-buffer */ - memcpy(udst, anim_line, width * sizeof(uint8_t)); + std::copy_n(anim_line, width, reinterpret_cast(udst)); udst = (uint32_t *)((uint8_t *)udst + width); anim_line += _screen.pitch; } @@ -490,11 +493,7 @@ void Blitter_40bppAnim::ScrollBuffer(void *video, int &left, int &top, int &widt uint tw = width + (scroll_x >= 0 ? -scroll_x : scroll_x); uint th = height - scroll_y; - for (; th > 0; th--) { - memcpy(dst, src, tw * sizeof(uint8_t)); - src -= _screen.pitch; - dst -= _screen.pitch; - } + Blitter::MovePixels(src, dst, tw, th, -_screen.pitch); } else { /* Calculate pointers */ dst = anim_buf + left + top * _screen.pitch; @@ -507,15 +506,9 @@ void Blitter_40bppAnim::ScrollBuffer(void *video, int &left, int &top, int &widt src -= scroll_x; } - /* the y-displacement may be 0 therefore we have to use memmove, - * because source and destination may overlap */ uint tw = width + (scroll_x >= 0 ? -scroll_x : scroll_x); uint th = height + scroll_y; - for (; th > 0; th--) { - memmove(dst, src, tw * sizeof(uint8_t)); - src += _screen.pitch; - dst += _screen.pitch; - } + Blitter::MovePixels(src, dst, tw, th, _screen.pitch); } Blitter_32bppBase::ScrollBuffer(video, left, top, width, height, scroll_x, scroll_y); diff --git a/src/blitter/40bpp_anim.hpp b/src/blitter/40bpp_anim.hpp index 385a4fe555..66990bae4e 100644 --- a/src/blitter/40bpp_anim.hpp +++ b/src/blitter/40bpp_anim.hpp @@ -27,7 +27,7 @@ public: void ScrollBuffer(void *video, int &left, int &top, int &width, int &height, int scroll_x, int scroll_y) override; void Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom) override; void DrawColourMappingRect(void *dst, int width, int height, PaletteID pal) override; - Sprite *Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override; + Sprite *Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override; size_t BufferSize(uint width, uint height) override; Blitter::PaletteAnimation UsePaletteAnimation() override; bool NeedsAnimationBuffer() override; diff --git a/src/blitter/8bpp_base.cpp b/src/blitter/8bpp_base.cpp index a936138a1a..db4e18b3fe 100644 --- a/src/blitter/8bpp_base.cpp +++ b/src/blitter/8bpp_base.cpp @@ -43,9 +43,10 @@ void Blitter_8bppBase::DrawLine(void *video, int x, int y, int x2, int y2, int s void Blitter_8bppBase::DrawRect(void *video, int width, int height, uint8_t colour) { + std::byte *p = static_cast(video); do { - memset(video, colour, width); - video = (uint8_t *)video + _screen.pitch; + std::fill_n(p, width, static_cast(colour)); + p += _screen.pitch; } while (--height); } @@ -55,7 +56,7 @@ void Blitter_8bppBase::CopyFromBuffer(void *video, const void *src, int width, i const uint8_t *usrc = (const uint8_t *)src; for (; height > 0; height--) { - memcpy(dst, usrc, width * sizeof(uint8_t)); + std::copy_n(usrc, width, dst); usrc += width; dst += _screen.pitch; } @@ -67,7 +68,7 @@ void Blitter_8bppBase::CopyToBuffer(const void *video, void *dst, int width, int const uint8_t *src = (const uint8_t *)video; for (; height > 0; height--) { - memcpy(udst, src, width * sizeof(uint8_t)); + std::copy_n(src, width, udst); src += _screen.pitch; udst += width; } @@ -79,7 +80,7 @@ void Blitter_8bppBase::CopyImageToBuffer(const void *video, void *dst, int width const uint8_t *src = (const uint8_t *)video; for (; height > 0; height--) { - memcpy(udst, src, width * sizeof(uint8_t)); + std::copy_n(src, width, udst); src += _screen.pitch; udst += dst_pitch; } @@ -110,11 +111,7 @@ void Blitter_8bppBase::ScrollBuffer(void *video, int &left, int &top, int &width width += scroll_x; } - for (int h = height; h > 0; h--) { - memcpy(dst, src, width * sizeof(uint8_t)); - src -= _screen.pitch; - dst -= _screen.pitch; - } + Blitter::MovePixels(src, dst, width, height, -_screen.pitch); } else { /* Calculate pointers */ dst = (uint8_t *)video + left + top * _screen.pitch; @@ -134,13 +131,7 @@ void Blitter_8bppBase::ScrollBuffer(void *video, int &left, int &top, int &width width += scroll_x; } - /* the y-displacement may be 0 therefore we have to use memmove, - * because source and destination may overlap */ - for (int h = height; h > 0; h--) { - memmove(dst, src, width * sizeof(uint8_t)); - src += _screen.pitch; - dst += _screen.pitch; - } + Blitter::MovePixels(src, dst, width, height, _screen.pitch); } } diff --git a/src/blitter/8bpp_optimized.cpp b/src/blitter/8bpp_optimized.cpp index 775fad8253..5cc16d975a 100644 --- a/src/blitter/8bpp_optimized.cpp +++ b/src/blitter/8bpp_optimized.cpp @@ -11,7 +11,6 @@ #include "../zoom_func.h" #include "../settings_type.h" #include "../core/math_func.hpp" -#include "../core/mem_func.hpp" #include "8bpp_optimized.hpp" #include "../safeguards.h" @@ -96,7 +95,7 @@ void Blitter_8bppOptimized::Draw(Blitter::BlitterParams *bp, BlitterMode mode, Z } case BlitterMode::BlackRemap: - MemSetT(dst, 0, pixels); + std::fill_n(dst, pixels, 0); dst += pixels; break; @@ -112,7 +111,7 @@ void Blitter_8bppOptimized::Draw(Blitter::BlitterParams *bp, BlitterMode mode, Z } default: - MemCpyT(dst, src, pixels); + std::copy_n(src, pixels, dst); dst += pixels; src += pixels; break; } @@ -120,7 +119,7 @@ void Blitter_8bppOptimized::Draw(Blitter::BlitterParams *bp, BlitterMode mode, Z } } -Sprite *Blitter_8bppOptimized::Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) +Sprite *Blitter_8bppOptimized::Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) { /* Make memory for all zoom-levels */ uint memory = sizeof(SpriteData); @@ -128,13 +127,13 @@ Sprite *Blitter_8bppOptimized::Encode(const SpriteLoader::SpriteCollection &spri ZoomLevel zoom_min; ZoomLevel zoom_max; - if (sprite[ZOOM_LVL_MIN].type == SpriteType::Font) { - zoom_min = ZOOM_LVL_MIN; - zoom_max = ZOOM_LVL_MIN; + if (sprite_type == SpriteType::Font) { + zoom_min = ZoomLevel::Min; + zoom_max = ZoomLevel::Min; } else { zoom_min = _settings_client.gui.zoom_min; zoom_max = _settings_client.gui.zoom_max; - if (zoom_max == zoom_min) zoom_max = ZOOM_LVL_MAX; + if (zoom_max == zoom_min) zoom_max = ZoomLevel::Max; } for (ZoomLevel i = zoom_min; i <= zoom_max; i++) { @@ -153,13 +152,14 @@ Sprite *Blitter_8bppOptimized::Encode(const SpriteLoader::SpriteCollection &spri /* Make the sprites per zoom-level */ for (ZoomLevel i = zoom_min; i <= zoom_max; i++) { + const SpriteLoader::Sprite &src_orig = sprite[i]; /* Store the index table */ uint offset = dst - temp_dst->data; temp_dst->offset[i] = offset; /* cache values, because compiler can't cache it */ - int scaled_height = sprite[i].height; - int scaled_width = sprite[i].width; + int scaled_height = src_orig.height; + int scaled_width = src_orig.width; for (int y = 0; y < scaled_height; y++) { uint trans = 0; @@ -168,7 +168,7 @@ Sprite *Blitter_8bppOptimized::Encode(const SpriteLoader::SpriteCollection &spri uint8_t *count_dst = nullptr; /* Store the scaled image */ - const SpriteLoader::CommonPixel *src = &sprite[i].data[y * sprite[i].width]; + const SpriteLoader::CommonPixel *src = &src_orig.data[y * src_orig.width]; for (int x = 0; x < scaled_width; x++) { uint colour = src++->m; @@ -220,11 +220,12 @@ Sprite *Blitter_8bppOptimized::Encode(const SpriteLoader::SpriteCollection &spri /* Allocate the exact amount of memory we need */ Sprite *dest_sprite = allocator.Allocate(sizeof(*dest_sprite) + size); - dest_sprite->height = sprite[ZOOM_LVL_MIN].height; - dest_sprite->width = sprite[ZOOM_LVL_MIN].width; - dest_sprite->x_offs = sprite[ZOOM_LVL_MIN].x_offs; - dest_sprite->y_offs = sprite[ZOOM_LVL_MIN].y_offs; - memcpy(dest_sprite->data, temp_dst, size); + const auto &root_sprite = sprite.Root(); + dest_sprite->height = root_sprite.height; + dest_sprite->width = root_sprite.width; + dest_sprite->x_offs = root_sprite.x_offs; + dest_sprite->y_offs = root_sprite.y_offs; + std::copy_n(reinterpret_cast(temp_dst), size, dest_sprite->data); return dest_sprite; } diff --git a/src/blitter/8bpp_optimized.hpp b/src/blitter/8bpp_optimized.hpp index 8452111d73..240b6dbdd9 100644 --- a/src/blitter/8bpp_optimized.hpp +++ b/src/blitter/8bpp_optimized.hpp @@ -18,12 +18,12 @@ class Blitter_8bppOptimized final : public Blitter_8bppBase { public: /** Data stored about a (single) sprite. */ struct SpriteData { - uint32_t offset[ZOOM_LVL_END]; ///< Offsets (from .data) to streams for different zoom levels. + SpriteCollMap offset; ///< Offsets (from .data) to streams for different zoom levels. uint8_t data[]; ///< Data, all zoomlevels. }; void Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom) override; - Sprite *Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override; + Sprite *Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override; std::string_view GetName() override { return "8bpp-optimized"; } }; diff --git a/src/blitter/8bpp_simple.cpp b/src/blitter/8bpp_simple.cpp index 9451dcd944..54cb9ee404 100644 --- a/src/blitter/8bpp_simple.cpp +++ b/src/blitter/8bpp_simple.cpp @@ -61,19 +61,21 @@ void Blitter_8bppSimple::Draw(Blitter::BlitterParams *bp, BlitterMode mode, Zoom } } -Sprite *Blitter_8bppSimple::Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) +Sprite *Blitter_8bppSimple::Encode(SpriteType, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) { + const auto &root_sprite = sprite.Root(); Sprite *dest_sprite; - dest_sprite = allocator.Allocate(sizeof(*dest_sprite) + static_cast(sprite[ZOOM_LVL_MIN].height) * static_cast(sprite[ZOOM_LVL_MIN].width)); + dest_sprite = allocator.Allocate(sizeof(*dest_sprite) + static_cast(root_sprite.height) * static_cast(root_sprite.width)); - dest_sprite->height = sprite[ZOOM_LVL_MIN].height; - dest_sprite->width = sprite[ZOOM_LVL_MIN].width; - dest_sprite->x_offs = sprite[ZOOM_LVL_MIN].x_offs; - dest_sprite->y_offs = sprite[ZOOM_LVL_MIN].y_offs; + dest_sprite->height = root_sprite.height; + dest_sprite->width = root_sprite.width; + dest_sprite->x_offs = root_sprite.x_offs; + dest_sprite->y_offs = root_sprite.y_offs; /* Copy over only the 'remap' channel, as that is what we care about in 8bpp */ - for (int i = 0; i < sprite[ZOOM_LVL_MIN].height * sprite[ZOOM_LVL_MIN].width; i++) { - dest_sprite->data[i] = sprite[ZOOM_LVL_MIN].data[i].m; + uint8_t *dst = reinterpret_cast(dest_sprite->data); + for (int i = 0; i < root_sprite.height * root_sprite.width; i++) { + dst[i] = root_sprite.data[i].m; } return dest_sprite; diff --git a/src/blitter/8bpp_simple.hpp b/src/blitter/8bpp_simple.hpp index 77dcf16a84..9398456737 100644 --- a/src/blitter/8bpp_simple.hpp +++ b/src/blitter/8bpp_simple.hpp @@ -17,7 +17,7 @@ class Blitter_8bppSimple final : public Blitter_8bppBase { public: void Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom) override; - Sprite *Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override; + Sprite *Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override; std::string_view GetName() override { return "8bpp-simple"; } }; diff --git a/src/blitter/base.hpp b/src/blitter/base.hpp index 7ee97a6c20..2a31d1d0b0 100644 --- a/src/blitter/base.hpp +++ b/src/blitter/base.hpp @@ -207,6 +207,8 @@ public: virtual ~Blitter() = default; template void DrawLineGeneric(int x, int y, int x2, int y2, int screen_width, int screen_height, int width, int dash, SetPixelT set_pixel); + + template static void MovePixels(const T *src, T *dst, size_t width, size_t height, ptrdiff_t pitch); }; #endif /* BLITTER_BASE_HPP */ diff --git a/src/blitter/common.hpp b/src/blitter/common.hpp index eebdfc05e7..6b36feb980 100644 --- a/src/blitter/common.hpp +++ b/src/blitter/common.hpp @@ -192,4 +192,24 @@ void Blitter::DrawLineGeneric(int x1, int y1, int x2, int y2, int screen_width, } } +template +/* static */ void Blitter::MovePixels(const T *src, T *dst, size_t width, size_t height, ptrdiff_t pitch) +{ + if (src == dst) return; + + if (src < dst) { + for (size_t i = 0; i < height; ++i) { + std::move_backward(src, src + width, dst + width); + src += pitch; + dst += pitch; + } + } else { + for (size_t i = 0; i < height; ++i) { + std::move(src, src + width, dst); + src += pitch; + dst += pitch; + } + } +} + #endif /* BLITTER_COMMON_HPP */ diff --git a/src/blitter/factory.hpp b/src/blitter/factory.hpp index 0dceea90f2..a1dffdcd4a 100644 --- a/src/blitter/factory.hpp +++ b/src/blitter/factory.hpp @@ -55,7 +55,7 @@ protected: * @pre description != nullptr. * @pre There is no blitter registered with this name. */ - BlitterFactory(const char *name, const char *description, bool usable = true) : + BlitterFactory(std::string_view name, std::string_view description, bool usable = true) : name(name), description(description) { if (usable) { @@ -93,7 +93,7 @@ public: * @param name the blitter to select. * @post Sets the blitter so GetCurrentBlitter() returns it too. */ - static Blitter *SelectBlitter(const std::string_view name) + static Blitter *SelectBlitter(std::string_view name) { BlitterFactory *b = GetBlitterFactory(name); if (b == nullptr) return nullptr; @@ -109,17 +109,17 @@ public: * @param name the blitter factory to select. * @return The blitter factory, or nullptr when there isn't one with the wanted name. */ - static BlitterFactory *GetBlitterFactory(const std::string_view name) + static BlitterFactory *GetBlitterFactory(std::string_view name) { #if defined(DEDICATED) - const std::string_view default_blitter = "null"; + static const std::string_view default_blitter = "null"; #elif defined(WITH_COCOA) - const std::string_view default_blitter = "32bpp-anim"; + static const std::string_view default_blitter = "32bpp-anim"; #else - const std::string_view default_blitter = "8bpp-optimized"; + static const std::string_view default_blitter = "8bpp-optimized"; #endif if (GetBlitters().empty()) return nullptr; - const std::string_view bname = name.empty() ? default_blitter : name; + std::string_view bname = name.empty() ? default_blitter : name; for (auto &it : GetBlitters()) { BlitterFactory *b = it.second; diff --git a/src/blitter/null.cpp b/src/blitter/null.cpp index dc8b1c0ea2..41b8a719b1 100644 --- a/src/blitter/null.cpp +++ b/src/blitter/null.cpp @@ -15,15 +15,16 @@ /** Instantiation of the null blitter factory. */ static FBlitter_Null iFBlitter_Null; -Sprite *Blitter_Null::Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) +Sprite *Blitter_Null::Encode(SpriteType, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) { Sprite *dest_sprite; dest_sprite = allocator.Allocate(sizeof(*dest_sprite)); - dest_sprite->height = sprite[ZOOM_LVL_MIN].height; - dest_sprite->width = sprite[ZOOM_LVL_MIN].width; - dest_sprite->x_offs = sprite[ZOOM_LVL_MIN].x_offs; - dest_sprite->y_offs = sprite[ZOOM_LVL_MIN].y_offs; + const auto &root_sprite = sprite.Root(); + dest_sprite->height = root_sprite.height; + dest_sprite->width = root_sprite.width; + dest_sprite->x_offs = root_sprite.x_offs; + dest_sprite->y_offs = root_sprite.y_offs; return dest_sprite; } diff --git a/src/blitter/null.hpp b/src/blitter/null.hpp index 15dc722929..8e90ca9af8 100644 --- a/src/blitter/null.hpp +++ b/src/blitter/null.hpp @@ -18,7 +18,7 @@ public: uint8_t GetScreenDepth() override { return 0; } void Draw(Blitter::BlitterParams *, BlitterMode, ZoomLevel) override {}; void DrawColourMappingRect(void *, int, int, PaletteID) override {}; - Sprite *Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override; + Sprite *Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override; void *MoveTo(void *, int, int) override { return nullptr; }; void SetPixel(void *, int, int, uint8_t) override {}; void DrawRect(void *, int, int, uint8_t) override {}; diff --git a/src/bootstrap_gui.cpp b/src/bootstrap_gui.cpp index b5d03fb506..8d9897b51d 100644 --- a/src/bootstrap_gui.cpp +++ b/src/bootstrap_gui.cpp @@ -42,7 +42,7 @@ static constexpr NWidgetPart _background_widgets[] = { * Window description for the background window to prevent smearing. */ static WindowDesc _background_desc( - WDP_MANUAL, nullptr, 0, 0, + WDP_MANUAL, {}, 0, 0, WC_BOOTSTRAP, WC_NONE, WindowDefaultFlag::NoClose, _background_widgets @@ -78,7 +78,7 @@ static constexpr NWidgetPart _nested_bootstrap_errmsg_widgets[] = { /** Window description for the error window. */ static WindowDesc _bootstrap_errmsg_desc( - WDP_CENTER, nullptr, 0, 0, + WDP_CENTER, {}, 0, 0, WC_BOOTSTRAP, WC_NONE, {WindowDefaultFlag::Modal, WindowDefaultFlag::NoClose}, _nested_bootstrap_errmsg_widgets @@ -135,7 +135,7 @@ static constexpr NWidgetPart _nested_bootstrap_download_status_window_widgets[] /** Window description for the download window */ static WindowDesc _bootstrap_download_status_window_desc( - WDP_CENTER, nullptr, 0, 0, + WDP_CENTER, {}, 0, 0, WC_NETWORK_STATUS_WINDOW, WC_NONE, {WindowDefaultFlag::Modal, WindowDefaultFlag::NoClose}, _nested_bootstrap_download_status_window_widgets @@ -187,7 +187,7 @@ static constexpr NWidgetPart _bootstrap_query_widgets[] = { /** The window description for the query. */ static WindowDesc _bootstrap_query_desc( - WDP_CENTER, nullptr, 0, 0, + WDP_CENTER, {}, 0, 0, WC_CONFIRM_POPUP_QUERY, WC_NONE, WindowDefaultFlag::NoClose, _bootstrap_query_widgets @@ -273,10 +273,10 @@ public: _network_content_client.RequestContentList(CONTENT_TYPE_BASE_GRAPHICS); } - void OnReceiveContentInfo(const ContentInfo *ci) override + void OnReceiveContentInfo(const ContentInfo &ci) override { /* And once the meta data is received, start downloading it. */ - _network_content_client.Select(ci->id); + _network_content_client.Select(ci.id); new BootstrapContentDownloadStatusWindow(); this->Close(); } @@ -320,19 +320,19 @@ public: _network_content_client.RequestContentList(CONTENT_TYPE_BASE_GRAPHICS); } - void OnReceiveContentInfo(const ContentInfo *ci) override + void OnReceiveContentInfo(const ContentInfo &ci) override { if (this->downloading) return; /* And once the metadata is received, start downloading it. */ - _network_content_client.Select(ci->id); + _network_content_client.Select(ci.id); _network_content_client.DownloadSelectedContent(this->total_files, this->total_bytes); this->downloading = true; EM_ASM({ if (window["openttd_bootstrap"]) openttd_bootstrap($0, $1); }, this->downloaded_bytes, this->total_bytes); } - void OnDownloadProgress(const ContentInfo *, int bytes) override + void OnDownloadProgress(const ContentInfo &, int bytes) override { /* A negative value means we are resetting; for example, when retrying or using a fallback. */ if (bytes < 0) { diff --git a/src/bridge_gui.cpp b/src/bridge_gui.cpp index acfda71049..f77337014e 100644 --- a/src/bridge_gui.cpp +++ b/src/bridge_gui.cpp @@ -205,7 +205,7 @@ public: sprite_dim = maxdim(sprite_dim, GetScaledSpriteSize(bridge_data.spec->sprite)); text_dim = maxdim(text_dim, GetStringBoundingBox(GetBridgeSelectString(bridge_data))); } - resize.height = std::max(sprite_dim.height, text_dim.height) + padding.height; // Max of both sizes + account for matrix edges. + fill.height = resize.height = std::max(sprite_dim.height, text_dim.height) + padding.height; // Max of both sizes + account for matrix edges. this->icon_width = sprite_dim.width; // Width of bridge icon. size.width = this->icon_width + WidgetDimensions::scaled.hsep_normal + text_dim.width + padding.width; @@ -239,7 +239,7 @@ public: for (auto it = first; it != last; ++it) { const BridgeSpec *b = it->spec; DrawSpriteIgnorePadding(b->sprite, b->pal, tr.WithWidth(this->icon_width, rtl), SA_HOR_CENTER | SA_BOTTOM); - DrawStringMultiLine(tr.Indent(this->icon_width + WidgetDimensions::scaled.hsep_normal, rtl), GetBridgeSelectString(*it)); + DrawStringMultiLineWithClipping(tr.Indent(this->icon_width + WidgetDimensions::scaled.hsep_normal, rtl), GetBridgeSelectString(*it)); tr = tr.Translate(0, this->resize.step_height); } break; @@ -283,7 +283,7 @@ public: } } - void OnDropdownSelect(WidgetID widget, int index) override + void OnDropdownSelect(WidgetID widget, int index, int) override { if (widget == WID_BBS_DROPDOWN_CRITERIA && this->bridges.SortType() != index) { this->bridges.SetSortType(index); diff --git a/src/build_vehicle_gui.cpp b/src/build_vehicle_gui.cpp index ad0f55141e..e713a7b5ec 100644 --- a/src/build_vehicle_gui.cpp +++ b/src/build_vehicle_gui.cpp @@ -18,6 +18,8 @@ #include "company_func.h" #include "vehicle_gui.h" #include "newgrf_badge.h" +#include "newgrf_badge_config.h" +#include "newgrf_badge_gui.h" #include "newgrf_engine.h" #include "newgrf_text.h" #include "group.h" @@ -72,10 +74,13 @@ static constexpr NWidgetPart _nested_build_vehicle_widgets[] = { NWidget(NWID_HORIZONTAL), NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BV_SHOW_HIDDEN_ENGINES), NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_BV_CARGO_FILTER_DROPDOWN), SetResize(1, 0), SetFill(1, 0), SetToolTip(STR_TOOLTIP_FILTER_CRITERIA), + NWidget(WWT_IMGBTN, COLOUR_GREY, WID_BV_CONFIGURE_BADGES), SetAspect(WidgetDimensions::ASPECT_UP_DOWN_BUTTON), SetResize(0, 0), SetFill(0, 1), SetSpriteTip(SPR_EXTRA_MENU, STR_BADGE_CONFIG_MENU_TOOLTIP), EndContainer(), NWidget(WWT_PANEL, COLOUR_GREY), NWidget(WWT_EDITBOX, COLOUR_GREY, WID_BV_FILTER), SetResize(1, 0), SetFill(1, 0), SetPadding(2), SetStringTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP), EndContainer(), + NWidget(NWID_VERTICAL, NWidContainerFlag{}, WID_BV_BADGE_FILTER), + EndContainer(), EndContainer(), /* Vehicle list. */ NWidget(NWID_HORIZONTAL), @@ -795,16 +800,20 @@ static int DrawAircraftPurchaseInfo(int left, int right, int y, EngineID engine_ */ static std::optional GetNewGRFAdditionalText(EngineID engine) { - uint16_t callback = GetVehicleCallback(CBID_VEHICLE_ADDITIONAL_TEXT, 0, 0, engine, nullptr); + std::array regs100; + uint16_t callback = GetVehicleCallback(CBID_VEHICLE_ADDITIONAL_TEXT, 0, 0, engine, nullptr, regs100); if (callback == CALLBACK_FAILED || callback == 0x400) return std::nullopt; const GRFFile *grffile = Engine::Get(engine)->GetGRF(); assert(grffile != nullptr); + if (callback == 0x40F) { + return GetGRFStringWithTextStack(grffile, static_cast(regs100[0]), std::span{regs100}.subspan(1)); + } if (callback > 0x400) { ErrorUnknownCallbackResult(grffile->grfid, CBID_VEHICLE_ADDITIONAL_TEXT, callback); return std::nullopt; } - return GetGRFStringWithTextStack(grffile, GRFSTR_MISC_GRF_TEXT + callback, 6); + return GetGRFStringWithTextStack(grffile, GRFSTR_MISC_GRF_TEXT + callback, regs100); } /** @@ -995,7 +1004,7 @@ void DrawEngineList(VehicleType type, const Rect &r, const GUIEngineList &eng_li if (lvl < item.indent) tx += level_width; } /* Draw our node in the tree. */ - int ycentre = CenterBounds(textr.top, textr.bottom, WidgetDimensions::scaled.fullbevel.top); + int ycentre = CentreBounds(textr.top, textr.bottom, WidgetDimensions::scaled.fullbevel.top); if (!HasBit(item.level_mask, item.indent)) GfxDrawLine(tx, ir.top, tx, ycentre, linecolour, WidgetDimensions::scaled.fullbevel.top); GfxDrawLine(tx, ycentre, tx + offset - (rtl ? -1 : 1), ycentre, linecolour, WidgetDimensions::scaled.fullbevel.top); } @@ -1122,11 +1131,6 @@ void GUIEngineListAddChildren(GUIEngineList &dst, const GUIEngineList &src, Engi } } -/** Enum referring to the Hotkeys in the build vehicle window */ -enum BuildVehicleHotkeys : int32_t { - BVHK_FOCUS_FILTER_BOX, ///< Focus the edit box for editing the filter string -}; - /** GUI for building vehicles. */ struct BuildVehicleWindow : Window { VehicleType vehicle_type = VEH_INVALID; ///< Type of vehicles shown in the window. @@ -1147,9 +1151,14 @@ struct BuildVehicleWindow : Window { TestedEngineDetails te{}; ///< Tested cost and capacity after refit. GUIBadgeClasses badge_classes{}; + static constexpr int BADGE_COLUMNS = 3; ///< Number of columns available for badges (0 = left of image, 1 = between image and name, 2 = after name) + StringFilter string_filter{}; ///< Filter for vehicle name QueryString vehicle_editbox; ///< Filter editbox + std::pair badge_filters{}; ///< First and last widgets IDs of badge filters. + BadgeFilterChoices badge_filter_choices{}; + void SetBuyVehicleText() { NWidgetCore *widget = this->GetWidget(WID_BV_BUILD); @@ -1305,6 +1314,12 @@ struct BuildVehicleWindow : Window { { this->badge_classes = GUIBadgeClasses(static_cast(GSF_TRAINS + this->vehicle_type)); this->SetCargoFilterArray(); + + auto container = this->GetWidget(WID_BV_BADGE_FILTER); + this->badge_filters = AddBadgeDropdownFilters(*container, WID_BV_BADGE_FILTER, COLOUR_GREY, static_cast(GSF_TRAINS + this->vehicle_type)); + + this->widget_lookup.clear(); + this->nested_root->FillWidgetLookup(this->widget_lookup); } /** Filter the engine list against the currently selected cargo filter */ @@ -1352,6 +1367,7 @@ struct BuildVehicleWindow : Window { list.clear(); BadgeTextFilter btf(this->string_filter, GSF_TRAINS); + BadgeDropdownFilter bdf(this->badge_filter_choices); /* Make list of all available train engines and wagons. * Also check to see if the previously selected engine is still available, @@ -1368,6 +1384,8 @@ struct BuildVehicleWindow : Window { /* Filter now! So num_engines and num_wagons is valid */ if (!FilterSingleEngine(eid)) continue; + if (!bdf.Filter(e->badges)) continue; + /* Filter by name or NewGRF extra text */ if (!FilterByText(e) && !btf.Filter(e->badges)) continue; @@ -1573,6 +1591,12 @@ struct BuildVehicleWindow : Window { return list; } + DropDownList BuildBadgeConfigurationList() const + { + static const auto separators = {STR_BADGE_CONFIG_PREVIEW, STR_BADGE_CONFIG_NAME}; + return BuildBadgeClassConfigurationList(this->badge_classes, BADGE_COLUMNS, separators); + } + void BuildVehicle() { EngineID sel_eng = this->sel_engine; @@ -1656,6 +1680,11 @@ struct BuildVehicleWindow : Window { ShowDropDownList(this, this->BuildCargoDropDownList(), this->cargo_filter_criteria, widget); break; + case WID_BV_CONFIGURE_BADGES: + if (this->badge_classes.GetClasses().empty()) break; + ShowDropDownList(this, this->BuildBadgeConfigurationList(), -1, widget, 0, false, true); + break; + case WID_BV_SHOW_HIDE: { const Engine *e = (this->sel_engine == EngineID::Invalid()) ? nullptr : Engine::Get(this->sel_engine); if (e != nullptr) { @@ -1676,6 +1705,12 @@ struct BuildVehicleWindow : Window { } break; } + + default: + if (IsInsideMM(widget, this->badge_filters.first, this->badge_filters.second)) { + ShowDropDownList(this, this->GetWidget(widget)->GetDropDownList(), -1, widget, 0, false); + } + break; } } @@ -1726,6 +1761,10 @@ struct BuildVehicleWindow : Window { } default: + if (IsInsideMM(widget, this->badge_filters.first, this->badge_filters.second)) { + return this->GetWidget(widget)->GetStringParameter(this->badge_filter_choices); + } + return this->Window::GetWidgetString(widget, stringid); } } @@ -1734,7 +1773,7 @@ struct BuildVehicleWindow : Window { { switch (widget) { case WID_BV_LIST: - resize.height = GetEngineListHeight(this->vehicle_type); + fill.height = resize.height = GetEngineListHeight(this->vehicle_type); size.height = 3 * resize.height; size.width = std::max(size.width, this->badge_classes.GetTotalColumnsWidth() + GetVehicleImageCellSize(this->vehicle_type, EIT_PURCHASE).extend_left + GetVehicleImageCellSize(this->vehicle_type, EIT_PURCHASE).extend_right + 165) + padding.width; break; @@ -1755,6 +1794,11 @@ struct BuildVehicleWindow : Window { size.width = std::max(size.width, GetDropDownListDimension(this->BuildCargoDropDownList()).width + padding.width); break; + case WID_BV_CONFIGURE_BADGES: + /* Hide the configuration button if no configurable badges are present. */ + if (this->badge_classes.GetClasses().empty()) size = {0, 0}; + break; + case WID_BV_BUILD: size = GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_BUTTON + this->vehicle_type); size = maxdim(size, GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_BUY_REFIT_VEHICLE_BUTTON + this->vehicle_type)); @@ -1829,7 +1873,7 @@ struct BuildVehicleWindow : Window { Command::Post(STR_ERROR_CAN_T_RENAME_TRAIN_TYPE + this->vehicle_type, this->rename_engine, *str); } - void OnDropdownSelect(WidgetID widget, int index) override + void OnDropdownSelect(WidgetID widget, int index, int click_result) override { switch (widget) { case WID_BV_SORT_DROPDOWN: @@ -1850,6 +1894,31 @@ struct BuildVehicleWindow : Window { this->SelectEngine(this->sel_engine); } break; + + case WID_BV_CONFIGURE_BADGES: { + bool reopen = HandleBadgeConfigurationDropDownClick(static_cast(GSF_TRAINS + this->vehicle_type), BADGE_COLUMNS, index, click_result); + + this->ReInit(); + + if (reopen) { + ReplaceDropDownList(this, this->BuildBadgeConfigurationList(), -1); + } else { + this->CloseChildWindows(WC_DROPDOWN_MENU); + } + break; + } + + default: + if (IsInsideMM(widget, this->badge_filters.first, this->badge_filters.second)) { + if (index < 0) { + ResetBadgeFilter(this->badge_filter_choices, this->GetWidget(widget)->GetBadgeClassID()); + } else { + SetBadgeFilter(this->badge_filter_choices, BadgeID(index)); + } + this->eng_list.ForceRebuild(); + this->SetDirty(); + } + break; } this->SetDirty(); } @@ -1867,23 +1936,8 @@ struct BuildVehicleWindow : Window { } } - EventState OnHotkey(int hotkey) override - { - switch (hotkey) { - case BVHK_FOCUS_FILTER_BOX: - this->SetFocusedWidget(WID_BV_FILTER); - SetFocusedWindow(this); // The user has asked to give focus to the text box, so make sure this window is focused. - return ES_HANDLED; - - default: - return ES_NOT_HANDLED; - } - - return ES_HANDLED; - } - static inline HotkeyList hotkeys{"buildvehicle", { - Hotkey('F', "focus_filter_box", BVHK_FOCUS_FILTER_BOX), + Hotkey('F', "focus_filter_box", WID_BV_FILTER), }}; }; diff --git a/src/cachecheck.cpp b/src/cachecheck.cpp index 8f39f8d5ed..fb0872802b 100644 --- a/src/cachecheck.cpp +++ b/src/cachecheck.cpp @@ -74,9 +74,8 @@ void CheckCaches() for (const RoadStop *rs : RoadStop::Iterate()) { if (IsBayRoadStopTile(rs->xy)) continue; - assert(rs->GetEntry(DIAGDIR_NE) != rs->GetEntry(DIAGDIR_NW)); - rs->GetEntry(DIAGDIR_NE)->CheckIntegrity(rs); - rs->GetEntry(DIAGDIR_NW)->CheckIntegrity(rs); + rs->GetEntry(DIAGDIR_NE).CheckIntegrity(rs); + rs->GetEntry(DIAGDIR_NW).CheckIntegrity(rs); } std::vector grf_cache; diff --git a/src/cheat.cpp b/src/cheat.cpp index 30adc36fbe..71546382de 100644 --- a/src/cheat.cpp +++ b/src/cheat.cpp @@ -18,5 +18,5 @@ Cheats _cheats; /** Reinitialise all the cheats. */ void InitializeCheats() { - memset(&_cheats, 0, sizeof(Cheats)); + _cheats = {}; } diff --git a/src/cheat_func.h b/src/cheat_func.h index 5c205b04bd..8585a589e2 100644 --- a/src/cheat_func.h +++ b/src/cheat_func.h @@ -12,8 +12,6 @@ #include "cheat_type.h" -extern Cheats _cheats; - void ShowCheatWindow(); diff --git a/src/cheat_gui.cpp b/src/cheat_gui.cpp index b2994d5eee..5cbdb1f78f 100644 --- a/src/cheat_gui.cpp +++ b/src/cheat_gui.cpp @@ -35,6 +35,7 @@ #include "timer/timer.h" #include "timer/timer_game_calendar.h" #include "timer/timer_game_economy.h" +#include "core/string_consumer.hpp" #include "widgets/cheat_widget.h" @@ -286,7 +287,7 @@ struct CheatWindow : Window { case SLE_BOOL: { bool on = (*(bool*)ce->variable); - DrawBoolButton(button_left, y + button_y_offset, on, true); + DrawBoolButton(button_left, y + button_y_offset, COLOUR_YELLOW, COLOUR_GREY, on, true); str = GetString(ce->str, on ? STR_CONFIG_SETTING_ON : STR_CONFIG_SETTING_OFF); break; } @@ -354,7 +355,7 @@ struct CheatWindow : Window { int32_t value = sd->Read(&GetGameSettings()); if (sd->IsBoolSetting()) { /* Draw checkbox for boolean-value either on/off */ - DrawBoolButton(buttons.left, buttons.top, value != 0, editable); + DrawBoolButton(buttons.left, buttons.top, COLOUR_YELLOW, COLOUR_GREY, value != 0, editable); } else if (sd->flags.Test(SettingFlag::GuiDropdown)) { /* Draw [v] button for settings of an enum-type */ DrawDropDownButton(buttons.left, buttons.top, COLOUR_YELLOW, state != 0, editable); @@ -607,12 +608,13 @@ struct CheatWindow : Window { int32_t value; if (!str->empty()) { - long long llvalue = atoll(str->c_str()); + auto llvalue = ParseInteger(*str, 10, true); + if (!llvalue.has_value()) return; /* Save the correct currency-translated value */ - if (sd->flags.Test(SettingFlag::GuiCurrency)) llvalue /= GetCurrency().rate; + if (sd->flags.Test(SettingFlag::GuiCurrency)) llvalue = *llvalue / GetCurrency().rate; - value = ClampTo(llvalue); + value = ClampTo(*llvalue); } else { value = sd->GetDefaultValue(); } @@ -621,18 +623,19 @@ struct CheatWindow : Window { } else { const CheatEntry *ce = &_cheats_ui[clicked_cheat]; int oldvalue = static_cast(ReadValue(ce->variable, ce->type)); - int value = atoi(str->c_str()); + auto value = ParseInteger(*str, 10, true); + if (!value.has_value()) return; *ce->been_used = true; - value = ce->proc(value, value - oldvalue); + value = ce->proc(*value, *value - oldvalue); - if (value != oldvalue) WriteValue(ce->variable, ce->type, static_cast(value)); + if (*value != oldvalue) WriteValue(ce->variable, ce->type, static_cast(*value)); } this->valuewindow_entry = nullptr; this->SetDirty(); } - IntervalTimer daily_interval = {{TimerGameCalendar::MONTH, TimerGameCalendar::Priority::NONE}, [this](auto) { + const IntervalTimer daily_interval = {{TimerGameCalendar::MONTH, TimerGameCalendar::Priority::NONE}, [this](auto) { this->SetDirty(); }}; }; diff --git a/src/cheat_type.h b/src/cheat_type.h index 8c5dc981b1..a13cab7eb2 100644 --- a/src/cheat_type.h +++ b/src/cheat_type.h @@ -14,8 +14,8 @@ * Info about each of the cheats. */ struct Cheat { - bool been_used; ///< has this cheat been used before? - bool value; ///< tells if the bool cheat is active or not + bool been_used = false; ///< has this cheat been used before? + bool value = false; ///< tells if the bool cheat is active or not }; /** @@ -24,15 +24,15 @@ struct Cheat { * Only add new entries at the end of the struct! */ struct Cheats { - Cheat magic_bulldozer; ///< dynamite industries, objects - Cheat switch_company; ///< change to another company - Cheat money; ///< get rich or poor - Cheat crossing_tunnels; ///< allow tunnels that cross each other - Cheat no_jetcrash; ///< no jet will crash on small airports anymore - Cheat change_date; ///< changes date ingame - Cheat setup_prod; ///< setup raw-material production in game - Cheat edit_max_hl; ///< edit the maximum heightlevel; this is a cheat because of the fact that it needs to reset NewGRF game state and doing so as a simple configuration breaks the expectation of many - Cheat station_rating; ///< Fix station ratings at 100% + Cheat magic_bulldozer{}; ///< dynamite industries, objects + Cheat switch_company{}; ///< change to another company + Cheat money{}; ///< get rich or poor + Cheat crossing_tunnels{}; ///< allow tunnels that cross each other + Cheat no_jetcrash{}; ///< no jet will crash on small airports anymore + Cheat change_date{}; ///< changes date ingame + Cheat setup_prod{}; ///< setup raw-material production in game + Cheat edit_max_hl{}; ///< edit the maximum heightlevel; this is a cheat because of the fact that it needs to reset NewGRF game state and doing so as a simple configuration breaks the expectation of many + Cheat station_rating{}; ///< Fix station ratings at 100% }; extern Cheats _cheats; diff --git a/src/command.cpp b/src/command.cpp index 281588e89a..c11289902e 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -74,7 +74,7 @@ int RecursiveCommandCounter::_counter = 0; * the #CommandFlag::Auto, #CommandFlag::Offline and #CommandFlag::Server values. */ struct CommandInfo { - const char *name; ///< A human readable name for the procedure + std::string_view name; ///< A human readable name for the procedure CommandFlags flags; ///< The (command) flags to that apply to this command CommandType type; ///< The type of command. }; @@ -129,7 +129,7 @@ CommandFlags GetCommandFlags(Commands cmd) * @param cmd The integer value of the command * @return The name for this command */ -const char *GetCommandName(Commands cmd) +std::string_view GetCommandName(Commands cmd) { assert(IsValidCommand(cmd)); diff --git a/src/command_func.h b/src/command_func.h index 52c70f6aab..cb3ba95610 100644 --- a/src/command_func.h +++ b/src/command_func.h @@ -31,7 +31,7 @@ void NetworkSendCommand(Commands cmd, StringID err_message, CommandCallback *cal bool IsValidCommand(Commands cmd); CommandFlags GetCommandFlags(Commands cmd); -const char *GetCommandName(Commands cmd); +std::string_view GetCommandName(Commands cmd); bool IsCommandAllowedWhilePaused(Commands cmd); template diff --git a/src/command_type.h b/src/command_type.h index 12a81a685b..e491677871 100644 --- a/src/command_type.h +++ b/src/command_type.h @@ -470,7 +470,7 @@ template struct CommandTraits; static constexpr auto &proc = proc_; \ static constexpr CommandFlags flags = flags_; \ static constexpr CommandType type = type_; \ - static inline constexpr const char *name = #proc_; \ + static inline constexpr std::string_view name = #proc_; \ }; /** Storage buffer for serialized command data. */ diff --git a/src/company_cmd.cpp b/src/company_cmd.cpp index 91025c7d01..6399a34cc1 100644 --- a/src/company_cmd.cpp +++ b/src/company_cmd.cpp @@ -39,6 +39,7 @@ #include "timer/timer.h" #include "timer/timer_game_economy.h" #include "timer/timer_game_tick.h" +#include "timer/timer_window.h" #include "widgets/statusbar_widget.h" @@ -51,7 +52,7 @@ void UpdateObjectColours(const Company *c); CompanyID _local_company; ///< Company controlled by the human player at this client. Can also be #COMPANY_SPECTATOR. CompanyID _current_company; ///< Company currently doing an action. -ReferenceThroughBaseContainer> _company_colours; ///< NOSAVE: can be determined from company structs. +TypedIndexContainer, CompanyID> _company_colours; ///< NOSAVE: can be determined from company structs. CompanyManagerFace _company_manager_face; ///< for company manager face storage in openttd.cfg uint _cur_company_tick_index; ///< used to generate a name for one company that doesn't have a name yet per tick @@ -151,6 +152,16 @@ TextColour GetDrawStringCompanyColour(CompanyID company) return (TextColour)GetColourGradient(_company_colours[company], SHADE_NORMAL) | TC_IS_PALETTE_COLOUR; } +/** + * Get the palette for recolouring with a company colour. + * @param company Company to get the colour of. + * @return Palette for recolouring. + */ +PaletteID GetCompanyPalette(CompanyID company) +{ + return GetColourPalette(_company_colours[company]); +} + /** * Draw the icon of a company. * @param c Company that needs its icon drawn. @@ -159,7 +170,7 @@ TextColour GetDrawStringCompanyColour(CompanyID company) */ void DrawCompanyIcon(CompanyID c, int x, int y) { - DrawSprite(SPR_COMPANY_ICON, COMPANY_SPRITE_COLOUR(c), x, y); + DrawSprite(SPR_COMPANY_ICON, GetCompanyPalette(c), x, y); } /** @@ -193,18 +204,38 @@ static bool IsValidCompanyManagerFace(CompanyManagerFace cmf) return true; } +static CompanyMask _dirty_company_finances{}; ///< Bitmask of compamy finances that should be marked dirty. + /** - * Refresh all windows owned by a company. + * Mark all finance windows owned by a company as needing a refresh. + * The actual refresh is deferred until the end of the gameloop to reduce duplicated work. * @param company Company that changed, and needs its windows refreshed. */ void InvalidateCompanyWindows(const Company *company) { CompanyID cid = company->index; - - if (cid == _local_company) SetWindowWidgetDirty(WC_STATUS_BAR, 0, WID_S_RIGHT); - SetWindowDirty(WC_FINANCES, cid); + _dirty_company_finances.Set(cid); } +/** + * Refresh all company finance windows previously marked dirty. + */ +static const IntervalTimer invalidate_company_windows_interval(std::chrono::milliseconds(1), [](auto) { + for (CompanyID cid : _dirty_company_finances) { + if (cid == _local_company) SetWindowWidgetDirty(WC_STATUS_BAR, 0, WID_S_RIGHT); + Window *w = FindWindowById(WC_FINANCES, cid); + if (w != nullptr) { + w->SetWidgetDirty(WID_CF_EXPS_PRICE3); + w->SetWidgetDirty(WID_CF_OWN_VALUE); + w->SetWidgetDirty(WID_CF_LOAN_VALUE); + w->SetWidgetDirty(WID_CF_BALANCE_VALUE); + w->SetWidgetDirty(WID_CF_MAXLOAN_VALUE); + } + SetWindowWidgetDirty(WC_COMPANY, cid, WID_C_DESC_COMPANY_VALUE); + } + _dirty_company_finances.Reset(); +}); + /** * Get the amount of money that a company has available, or INT64_MAX * if there is no such valid company. @@ -781,7 +812,7 @@ void OnTick_Companies() * A year has passed, update the economic data of all companies, and perhaps show the * financial overview window of the local company. */ -static IntervalTimer _economy_companies_yearly({TimerGameEconomy::YEAR, TimerGameEconomy::Priority::COMPANY}, [](auto) +static const IntervalTimer _economy_companies_yearly({TimerGameEconomy::YEAR, TimerGameEconomy::Priority::COMPANY}, [](auto) { /* Copy statistics */ for (Company *c : Company::Iterate()) { diff --git a/src/company_func.h b/src/company_func.h index 870a292424..deddcd4b60 100644 --- a/src/company_func.h +++ b/src/company_func.h @@ -36,8 +36,9 @@ CommandCost CheckTileOwnership(TileIndex tile); extern CompanyID _local_company; extern CompanyID _current_company; -extern ReferenceThroughBaseContainer> _company_colours; +extern TypedIndexContainer, CompanyID> _company_colours; extern CompanyManagerFace _company_manager_face; +PaletteID GetCompanyPalette(CompanyID company); /** * Is the current company the local company? diff --git a/src/company_gui.cpp b/src/company_gui.cpp index 0c171330e2..29f48a8957 100644 --- a/src/company_gui.cpp +++ b/src/company_gui.cpp @@ -45,6 +45,7 @@ #include "object_cmd.h" #include "timer/timer.h" #include "timer/timer_window.h" +#include "core/string_consumer.hpp" #include "widgets/company_widget.h" @@ -539,7 +540,7 @@ struct CompanyFinancesWindow : Window { * Check on a regular interval if the maximum amount of money has changed. * If it has, rescale the window to fit the new amount. */ - IntervalTimer rescale_interval = {std::chrono::seconds(3), [this](auto) { + const IntervalTimer rescale_interval = {std::chrono::seconds(3), [this](auto) { const Company *c = Company::Get(this->window_number); if (c->money > CompanyFinancesWindow::max_money) { CompanyFinancesWindow::max_money = std::max(c->money * 2, CompanyFinancesWindow::max_money * 4); @@ -589,7 +590,8 @@ static const LiveryClass _livery_class[LS_END] = { template class DropDownListColourItem : public DropDownIcon> { public: - DropDownListColourItem(int colour, bool masked) : DropDownIcon>(TSprite, GENERAL_SPRITE_COLOUR(colour % COLOUR_END), GetString(colour < COLOUR_END ? (STR_COLOUR_DARK_BLUE + colour) : STR_COLOUR_DEFAULT), colour, masked) + DropDownListColourItem(int colour, bool masked) : + DropDownIcon>(TSprite, GetColourPalette(static_cast(colour % COLOUR_END)), GetString(colour < COLOUR_END ? (STR_COLOUR_DARK_BLUE + colour) : STR_COLOUR_DEFAULT), colour, masked) { } }; @@ -748,13 +750,6 @@ public: d = maxdim(d, GetStringBoundingBox(STR_LIVERY_DEFAULT + scheme)); } - /* And group names */ - for (const Group *g : Group::Iterate()) { - if (g->owner == this->window_number) { - d = maxdim(d, GetStringBoundingBox(GetString(STR_GROUP_NAME, g->index))); - } - } - size.width = std::max(size.width, 5 + d.width + padding.width); break; } @@ -766,7 +761,7 @@ public: size.height = 5 * this->line_height; resize.width = 1; - resize.height = this->line_height; + fill.height = resize.height = this->line_height; break; } @@ -878,12 +873,12 @@ public: DrawString(sch.left + (rtl ? 0 : indent), sch.right - (rtl ? indent : 0), y + text_offs, str, is_selected ? TC_WHITE : TC_BLACK); /* Text below the first dropdown. */ - DrawSprite(SPR_SQUARE, GENERAL_SPRITE_COLOUR(livery.colour1), pri_squ.left, y + square_offs); + DrawSprite(SPR_SQUARE, GetColourPalette(livery.colour1), pri_squ.left, y + square_offs); DrawString(pri.left, pri.right, y + text_offs, (is_default_scheme || HasBit(livery.in_use, 0)) ? STR_COLOUR_DARK_BLUE + livery.colour1 : STR_COLOUR_DEFAULT, is_selected ? TC_WHITE : TC_GOLD); /* Text below the second dropdown. */ if (sec.right > sec.left) { // Second dropdown has non-zero size. - DrawSprite(SPR_SQUARE, GENERAL_SPRITE_COLOUR(livery.colour2), sec_squ.left, y + square_offs); + DrawSprite(SPR_SQUARE, GetColourPalette(livery.colour2), sec_squ.left, y + square_offs); DrawString(sec.left, sec.right, y + text_offs, (is_default_scheme || HasBit(livery.in_use, 1)) ? STR_COLOUR_DARK_BLUE + livery.colour2 : STR_COLOUR_DEFAULT, is_selected ? TC_WHITE : TC_GOLD); } @@ -997,7 +992,7 @@ public: this->vscroll->SetCapacityFromWidget(this, WID_SCL_MATRIX); } - void OnDropdownSelect(WidgetID widget, int index) override + void OnDropdownSelect(WidgetID widget, int index, int) override { bool local = this->window_number == _local_company; if (!local) return; @@ -1075,15 +1070,15 @@ static constexpr NWidgetPart _nested_select_company_livery_widgets[] = { NWidget(WWT_STICKYBOX, COLOUR_GREY), EndContainer(), NWidget(NWID_HORIZONTAL), - NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_CLASS_GENERAL), SetMinimalSize(22, 22), SetFill(0, 1), SetSpriteTip(SPR_IMG_COMPANY_GENERAL, STR_LIVERY_GENERAL_TOOLTIP), - NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_CLASS_RAIL), SetMinimalSize(22, 22), SetFill(0, 1), SetSpriteTip(SPR_IMG_TRAINLIST, STR_LIVERY_TRAIN_TOOLTIP), - NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_CLASS_ROAD), SetMinimalSize(22, 22), SetFill(0, 1), SetSpriteTip(SPR_IMG_TRUCKLIST, STR_LIVERY_ROAD_VEHICLE_TOOLTIP), - NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_CLASS_SHIP), SetMinimalSize(22, 22), SetFill(0, 1), SetSpriteTip(SPR_IMG_SHIPLIST, STR_LIVERY_SHIP_TOOLTIP), - NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_CLASS_AIRCRAFT), SetMinimalSize(22, 22), SetFill(0, 1), SetSpriteTip(SPR_IMG_AIRPLANESLIST, STR_LIVERY_AIRCRAFT_TOOLTIP), - NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_GROUPS_RAIL), SetMinimalSize(22, 22), SetFill(0, 1), SetSpriteTip(SPR_GROUP_LIVERY_TRAIN, STR_LIVERY_TRAIN_GROUP_TOOLTIP), - NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_GROUPS_ROAD), SetMinimalSize(22, 22), SetFill(0, 1), SetSpriteTip(SPR_GROUP_LIVERY_ROADVEH, STR_LIVERY_ROAD_VEHICLE_GROUP_TOOLTIP), - NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_GROUPS_SHIP), SetMinimalSize(22, 22), SetFill(0, 1), SetSpriteTip(SPR_GROUP_LIVERY_SHIP, STR_LIVERY_SHIP_GROUP_TOOLTIP), - NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_GROUPS_AIRCRAFT), SetMinimalSize(22, 22), SetFill(0, 1), SetSpriteTip(SPR_GROUP_LIVERY_AIRCRAFT, STR_LIVERY_AIRCRAFT_GROUP_TOOLTIP), + NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_CLASS_GENERAL), SetToolbarMinimalSize(1), SetFill(0, 1), SetSpriteTip(SPR_IMG_COMPANY_GENERAL, STR_LIVERY_GENERAL_TOOLTIP), + NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_CLASS_RAIL), SetToolbarMinimalSize(1), SetFill(0, 1), SetSpriteTip(SPR_IMG_TRAINLIST, STR_LIVERY_TRAIN_TOOLTIP), + NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_CLASS_ROAD), SetToolbarMinimalSize(1), SetFill(0, 1), SetSpriteTip(SPR_IMG_TRUCKLIST, STR_LIVERY_ROAD_VEHICLE_TOOLTIP), + NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_CLASS_SHIP), SetToolbarMinimalSize(1), SetFill(0, 1), SetSpriteTip(SPR_IMG_SHIPLIST, STR_LIVERY_SHIP_TOOLTIP), + NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_CLASS_AIRCRAFT), SetToolbarMinimalSize(1), SetFill(0, 1), SetSpriteTip(SPR_IMG_AIRPLANESLIST, STR_LIVERY_AIRCRAFT_TOOLTIP), + NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_GROUPS_RAIL), SetToolbarMinimalSize(1), SetFill(0, 1), SetSpriteTip(SPR_GROUP_LIVERY_TRAIN, STR_LIVERY_TRAIN_GROUP_TOOLTIP), + NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_GROUPS_ROAD), SetToolbarMinimalSize(1), SetFill(0, 1), SetSpriteTip(SPR_GROUP_LIVERY_ROADVEH, STR_LIVERY_ROAD_VEHICLE_GROUP_TOOLTIP), + NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_GROUPS_SHIP), SetToolbarMinimalSize(1), SetFill(0, 1), SetSpriteTip(SPR_GROUP_LIVERY_SHIP, STR_LIVERY_SHIP_GROUP_TOOLTIP), + NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_GROUPS_AIRCRAFT), SetToolbarMinimalSize(1), SetFill(0, 1), SetSpriteTip(SPR_GROUP_LIVERY_AIRCRAFT, STR_LIVERY_AIRCRAFT_GROUP_TOOLTIP), NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), SetResize(1, 0), EndContainer(), EndContainer(), NWidget(NWID_HORIZONTAL), @@ -1127,8 +1122,8 @@ void DrawCompanyManagerFace(CompanyManagerFace cmf, Colours colour, const Rect & /* Determine offset from centre of drawing rect. */ Dimension d = GetSpriteSize(SPR_GRADIENT); - int x = CenterBounds(r.left, r.right, d.width); - int y = CenterBounds(r.top, r.bottom, d.height); + int x = CentreBounds(r.left, r.right, d.width); + int y = CentreBounds(r.top, r.bottom, d.height); bool has_moustache = !HasBit(ge, GENDER_FEMALE) && GetCompanyManagerFaceBits(cmf, CMFV_HAS_MOUSTACHE, ge) != 0; bool has_tie_earring = !HasBit(ge, GENDER_FEMALE) || GetCompanyManagerFaceBits(cmf, CMFV_HAS_TIE_EARRING, ge) != 0; @@ -1148,7 +1143,7 @@ void DrawCompanyManagerFace(CompanyManagerFace cmf, Colours colour, const Rect & } /* Draw the gradient (background) */ - DrawSprite(SPR_GRADIENT, GENERAL_SPRITE_COLOUR(colour), x, y); + DrawSprite(SPR_GRADIENT, GetColourPalette(colour), x, y); for (CompanyManagerFaceVariable cmfv = CMFV_CHEEKS; cmfv < CMFV_END; cmfv++) { switch (cmfv) { @@ -1691,7 +1686,9 @@ public: if (!str.has_value()) return; /* Set a new company manager face number */ if (!str->empty()) { - this->face = std::strtoul(str->c_str(), nullptr, 10); + auto val = ParseInteger(*str, 10, true); + if (!val.has_value()) return; + this->face = *val; ScaleAllCompanyManagerFaceBits(this->face); ShowErrorMessage(GetEncodedString(STR_FACE_FACECODE_SET), {}, WL_INFO); this->UpdateData(); @@ -1704,7 +1701,7 @@ public: /** Company manager face selection window description */ static WindowDesc _select_company_manager_face_desc( - WDP_AUTO, nullptr, 0, 0, + WDP_AUTO, {}, 0, 0, WC_COMPANY_MANAGER_FACE, WC_NONE, WindowDefaultFlag::Construction, _nested_select_company_manager_face_widgets @@ -1728,34 +1725,16 @@ static constexpr NWidgetPart _nested_company_infrastructure_widgets[] = { NWidget(WWT_CLOSEBOX, COLOUR_GREY), NWidget(WWT_CAPTION, COLOUR_GREY, WID_CI_CAPTION), NWidget(WWT_SHADEBOX, COLOUR_GREY), + NWidget(WWT_DEFSIZEBOX, COLOUR_GREY), NWidget(WWT_STICKYBOX, COLOUR_GREY), EndContainer(), - NWidget(WWT_PANEL, COLOUR_GREY), - NWidget(NWID_VERTICAL), SetPadding(WidgetDimensions::unscaled.framerect), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0), - NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0), - NWidget(WWT_EMPTY, INVALID_COLOUR, WID_CI_RAIL_DESC), SetMinimalTextLines(2, 0), SetFill(1, 0), - NWidget(WWT_EMPTY, INVALID_COLOUR, WID_CI_RAIL_COUNT), SetMinimalTextLines(2, 0), SetFill(0, 1), - EndContainer(), - NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0), - NWidget(WWT_EMPTY, INVALID_COLOUR, WID_CI_ROAD_DESC), SetMinimalTextLines(2, 0), SetFill(1, 0), - NWidget(WWT_EMPTY, INVALID_COLOUR, WID_CI_ROAD_COUNT), SetMinimalTextLines(2, 0), SetFill(0, 1), - EndContainer(), - NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0), - NWidget(WWT_EMPTY, INVALID_COLOUR, WID_CI_TRAM_DESC), SetMinimalTextLines(2, 0), SetFill(1, 0), - NWidget(WWT_EMPTY, INVALID_COLOUR, WID_CI_TRAM_COUNT), SetMinimalTextLines(2, 0), SetFill(0, 1), - EndContainer(), - NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0), - NWidget(WWT_EMPTY, INVALID_COLOUR, WID_CI_WATER_DESC), SetMinimalTextLines(2, 0), SetFill(1, 0), - NWidget(WWT_EMPTY, INVALID_COLOUR, WID_CI_WATER_COUNT), SetMinimalTextLines(2, 0), SetFill(0, 1), - EndContainer(), - NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0), - NWidget(WWT_EMPTY, INVALID_COLOUR, WID_CI_STATION_DESC), SetMinimalTextLines(3, 0), SetFill(1, 0), - NWidget(WWT_EMPTY, INVALID_COLOUR, WID_CI_STATION_COUNT), SetMinimalTextLines(3, 0), SetFill(0, 1), - EndContainer(), - NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0), - NWidget(WWT_EMPTY, INVALID_COLOUR, WID_CI_TOTAL_DESC), SetFill(1, 0), - NWidget(WWT_EMPTY, INVALID_COLOUR, WID_CI_TOTAL), SetFill(0, 1), - EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PANEL, COLOUR_GREY, WID_CI_LIST), SetFill(1, 1), SetResize(0, 1), + SetMinimalTextLines(5, WidgetDimensions::unscaled.framerect.Vertical()), SetScrollbar(WID_CI_SCROLLBAR), + EndContainer(), + NWidget(NWID_VERTICAL), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_CI_SCROLLBAR), + NWidget(WWT_RESIZEBOX, COLOUR_GREY), EndContainer(), EndContainer(), }; @@ -1765,70 +1744,127 @@ static constexpr NWidgetPart _nested_company_infrastructure_widgets[] = { */ struct CompanyInfrastructureWindow : Window { - RailTypes railtypes{}; ///< Valid railtypes. - RoadTypes roadtypes{}; ///< Valid roadtypes. + enum class InfrastructureItemType : uint8_t { + Header, ///< Section header. + Spacer, ///< Spacer + Value, ///< Label with values. + Total, ///< Total cost. + }; - uint total_width = 0; ///< String width of the total cost line. + struct InfrastructureItem { + InfrastructureItemType type; + StringID label; + uint count; + Money cost; + }; + + uint count_width = 0; + uint cost_width = 0; + + mutable std::vector list; CompanyInfrastructureWindow(WindowDesc &desc, WindowNumber window_number) : Window(desc) { - this->UpdateRailRoadTypes(); - this->InitNested(window_number); this->owner = this->window_number; } - void UpdateRailRoadTypes() + void OnInit() override { - this->railtypes = {}; - this->roadtypes = {}; - - /* Find the used railtypes. */ - for (const Engine *e : Engine::IterateType(VEH_TRAIN)) { - if (!e->info.climates.Test(_settings_game.game_creation.landscape)) continue; - - this->railtypes.Set(GetRailTypeInfo(e->u.rail.railtype)->introduces_railtypes); - } - - /* Get the date introduced railtypes as well. */ - this->railtypes = AddDateIntroducedRailTypes(this->railtypes, CalendarTime::MAX_DATE); - this->railtypes.Reset(_railtypes_hidden_mask); - - /* Find the used roadtypes. */ - for (const Engine *e : Engine::IterateType(VEH_ROAD)) { - if (!e->info.climates.Test(_settings_game.game_creation.landscape)) continue; - - this->roadtypes.Set(GetRoadTypeInfo(e->u.road.roadtype)->introduces_roadtypes); - } - - /* Get the date introduced roadtypes as well. */ - this->roadtypes = AddDateIntroducedRoadTypes(this->roadtypes, CalendarTime::MAX_DATE); - this->roadtypes.Reset(_roadtypes_hidden_mask); + this->UpdateInfrastructureList(); } - /** Get total infrastructure maintenance cost. */ - Money GetTotalMaintenanceCost() const + void UpdateInfrastructureList() { - const Company *c = Company::Get(this->window_number); - Money total; + this->list.clear(); - uint32_t rail_total = c->infrastructure.GetRailTotal(); - for (RailType rt = RAILTYPE_BEGIN; rt != RAILTYPE_END; rt++) { - if (this->railtypes.Test(rt)) total += RailMaintenanceCost(rt, c->infrastructure.rail[rt], rail_total); - } - total += SignalMaintenanceCost(c->infrastructure.signal); + const Company *c = Company::GetIfValid(this->window_number); + if (c == nullptr) return; - uint32_t road_total = c->infrastructure.GetRoadTotal(); - uint32_t tram_total = c->infrastructure.GetTramTotal(); - for (RoadType rt = ROADTYPE_BEGIN; rt != ROADTYPE_END; rt++) { - if (this->roadtypes.Test(rt)) total += RoadMaintenanceCost(rt, c->infrastructure.road[rt], RoadTypeIsRoad(rt) ? road_total : tram_total); + Money total_monthly_cost = 0; + + if (uint32_t rail_total = c->infrastructure.GetRailTotal(); rail_total > 0) { + /* Rail types and signals. */ + this->list.emplace_back(InfrastructureItemType::Header, STR_COMPANY_INFRASTRUCTURE_VIEW_RAIL_SECT); + + for (const RailType &rt : _sorted_railtypes) { + if (c->infrastructure.rail[rt] == 0) continue; + Money monthly_cost = RailMaintenanceCost(rt, c->infrastructure.rail[rt], rail_total); + total_monthly_cost += monthly_cost; + this->list.emplace_back(InfrastructureItemType::Value, GetRailTypeInfo(rt)->strings.name, c->infrastructure.rail[rt], monthly_cost); + } + + if (c->infrastructure.signal > 0) { + Money monthly_cost = SignalMaintenanceCost(c->infrastructure.signal); + total_monthly_cost += monthly_cost; + this->list.emplace_back(InfrastructureItemType::Value, STR_COMPANY_INFRASTRUCTURE_VIEW_SIGNALS, c->infrastructure.signal, monthly_cost); + } } - total += CanalMaintenanceCost(c->infrastructure.water); - total += StationMaintenanceCost(c->infrastructure.station); - total += AirportMaintenanceCost(c->index); + if (uint32_t road_total = c->infrastructure.GetRoadTotal(); road_total > 0) { + /* Road types. */ + if (!this->list.empty()) this->list.emplace_back(InfrastructureItemType::Spacer); + this->list.emplace_back(InfrastructureItemType::Header, STR_COMPANY_INFRASTRUCTURE_VIEW_ROAD_SECT); - return total; + for (const RoadType &rt : _sorted_roadtypes) { + if (!RoadTypeIsRoad(rt)) continue; + if (c->infrastructure.road[rt] == 0) continue; + Money monthly_cost = RoadMaintenanceCost(rt, c->infrastructure.road[rt], road_total); + total_monthly_cost += monthly_cost; + this->list.emplace_back(InfrastructureItemType::Value, GetRoadTypeInfo(rt)->strings.name, c->infrastructure.road[rt], monthly_cost); + } + } + + if (uint32_t tram_total = c->infrastructure.GetTramTotal(); tram_total > 0) { + /* Tram types. */ + if (!this->list.empty()) this->list.emplace_back(InfrastructureItemType::Spacer); + this->list.emplace_back(InfrastructureItemType::Header, STR_COMPANY_INFRASTRUCTURE_VIEW_TRAM_SECT); + + for (const RoadType &rt : _sorted_roadtypes) { + if (!RoadTypeIsTram(rt)) continue; + if (c->infrastructure.road[rt] == 0) continue; + Money monthly_cost = RoadMaintenanceCost(rt, c->infrastructure.road[rt], tram_total); + total_monthly_cost += monthly_cost; + this->list.emplace_back(InfrastructureItemType::Value, GetRoadTypeInfo(rt)->strings.name, c->infrastructure.road[rt], monthly_cost); + } + } + + if (c->infrastructure.water > 0) { + /* Canals, locks, and ship depots (docks are counted as stations). */ + if (!this->list.empty()) this->list.emplace_back(InfrastructureItemType::Spacer); + this->list.emplace_back(InfrastructureItemType::Header, STR_COMPANY_INFRASTRUCTURE_VIEW_WATER_SECT); + + Money monthly_cost = CanalMaintenanceCost(c->infrastructure.water); + total_monthly_cost += monthly_cost; + this->list.emplace_back(InfrastructureItemType::Value, STR_COMPANY_INFRASTRUCTURE_VIEW_CANALS, c->infrastructure.water, monthly_cost); + } + + if (Money airport_cost = AirportMaintenanceCost(c->index); airport_cost > 0 || c->infrastructure.station > 0) { + /* Stations and airports. */ + if (!this->list.empty()) this->list.emplace_back(InfrastructureItemType::Spacer); + this->list.emplace_back(InfrastructureItemType::Header, STR_COMPANY_INFRASTRUCTURE_VIEW_STATION_SECT); + + if (c->infrastructure.station > 0) { + Money monthly_cost = StationMaintenanceCost(c->infrastructure.station); + total_monthly_cost += monthly_cost; + this->list.emplace_back(InfrastructureItemType::Value, STR_COMPANY_INFRASTRUCTURE_VIEW_STATIONS, c->infrastructure.station, monthly_cost); + } + + if (airport_cost > 0) { + Money monthly_cost = airport_cost; + total_monthly_cost += monthly_cost; + this->list.emplace_back(InfrastructureItemType::Value, STR_COMPANY_INFRASTRUCTURE_VIEW_AIRPORTS, c->infrastructure.airport, monthly_cost); + } + } + + if (_settings_game.economy.infrastructure_maintenance) { + /* Total monthly maintenance cost. */ + this->list.emplace_back(InfrastructureItemType::Spacer); + this->list.emplace_back(InfrastructureItemType::Total, STR_NULL, 0, total_monthly_cost); + } + + /* Update scrollbar. */ + this->GetScrollbar(WID_CI_SCROLLBAR)->SetCount(std::size(list)); } std::string GetWidgetString(WidgetID widget, StringID stringid) const override @@ -1842,230 +1878,122 @@ struct CompanyInfrastructureWindow : Window } } - void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override + void FindWindowPlacementAndResize(int def_width, int def_height, bool allow_resize) override { - const Company *c = Company::Get(this->window_number); - - switch (widget) { - case WID_CI_RAIL_DESC: { - uint lines = 1; // Starts at 1 because a line is also required for the section title - - size.width = std::max(size.width, GetStringBoundingBox(STR_COMPANY_INFRASTRUCTURE_VIEW_RAIL_SECT).width + padding.width); - - for (const auto &rt : _sorted_railtypes) { - if (this->railtypes.Test(rt)) { - lines++; - size.width = std::max(size.width, GetStringBoundingBox(GetRailTypeInfo(rt)->strings.name).width + padding.width + WidgetDimensions::scaled.hsep_indent); - } - } - if (this->railtypes.Any()) { - lines++; - size.width = std::max(size.width, GetStringBoundingBox(STR_COMPANY_INFRASTRUCTURE_VIEW_SIGNALS).width + padding.width + WidgetDimensions::scaled.hsep_indent); - } - - size.height = std::max(size.height, lines * GetCharacterHeight(FS_NORMAL)); - break; - } - - case WID_CI_ROAD_DESC: - case WID_CI_TRAM_DESC: { - uint lines = 1; // Starts at 1 because a line is also required for the section title - - size.width = std::max(size.width, GetStringBoundingBox(widget == WID_CI_ROAD_DESC ? STR_COMPANY_INFRASTRUCTURE_VIEW_ROAD_SECT : STR_COMPANY_INFRASTRUCTURE_VIEW_TRAM_SECT).width + padding.width); - - for (const auto &rt : _sorted_roadtypes) { - if (this->roadtypes.Test(rt) && RoadTypeIsRoad(rt) == (widget == WID_CI_ROAD_DESC)) { - lines++; - size.width = std::max(size.width, GetStringBoundingBox(GetRoadTypeInfo(rt)->strings.name).width + padding.width + WidgetDimensions::scaled.hsep_indent); - } - } - - size.height = std::max(size.height, lines * GetCharacterHeight(FS_NORMAL)); - break; - } - - case WID_CI_WATER_DESC: - size.width = std::max(size.width, GetStringBoundingBox(STR_COMPANY_INFRASTRUCTURE_VIEW_WATER_SECT).width + padding.width); - size.width = std::max(size.width, GetStringBoundingBox(STR_COMPANY_INFRASTRUCTURE_VIEW_CANALS).width + padding.width + WidgetDimensions::scaled.hsep_indent); - break; - - case WID_CI_STATION_DESC: - size.width = std::max(size.width, GetStringBoundingBox(STR_COMPANY_INFRASTRUCTURE_VIEW_STATION_SECT).width + padding.width); - size.width = std::max(size.width, GetStringBoundingBox(STR_COMPANY_INFRASTRUCTURE_VIEW_STATIONS).width + padding.width + WidgetDimensions::scaled.hsep_indent); - size.width = std::max(size.width, GetStringBoundingBox(STR_COMPANY_INFRASTRUCTURE_VIEW_AIRPORTS).width + padding.width + WidgetDimensions::scaled.hsep_indent); - break; - - case WID_CI_RAIL_COUNT: - case WID_CI_ROAD_COUNT: - case WID_CI_TRAM_COUNT: - case WID_CI_WATER_COUNT: - case WID_CI_STATION_COUNT: - case WID_CI_TOTAL: { - /* Find the maximum count that is displayed. */ - uint32_t max_val = 1000; // Some random number to reserve enough space. - Money max_cost = 10000; // Some random number to reserve enough space. - uint32_t rail_total = c->infrastructure.GetRailTotal(); - for (RailType rt = RAILTYPE_BEGIN; rt < RAILTYPE_END; rt++) { - max_val = std::max(max_val, c->infrastructure.rail[rt]); - max_cost = std::max(max_cost, RailMaintenanceCost(rt, c->infrastructure.rail[rt], rail_total)); - } - max_val = std::max(max_val, c->infrastructure.signal); - max_cost = std::max(max_cost, SignalMaintenanceCost(c->infrastructure.signal)); - uint32_t road_total = c->infrastructure.GetRoadTotal(); - uint32_t tram_total = c->infrastructure.GetTramTotal(); - for (RoadType rt = ROADTYPE_BEGIN; rt < ROADTYPE_END; rt++) { - max_val = std::max(max_val, c->infrastructure.road[rt]); - max_cost = std::max(max_cost, RoadMaintenanceCost(rt, c->infrastructure.road[rt], RoadTypeIsRoad(rt) ? road_total : tram_total)); - - } - max_val = std::max(max_val, c->infrastructure.water); - max_cost = std::max(max_cost, CanalMaintenanceCost(c->infrastructure.water)); - max_val = std::max(max_val, c->infrastructure.station); - max_cost = std::max(max_cost, StationMaintenanceCost(c->infrastructure.station)); - max_val = std::max(max_val, c->infrastructure.airport); - max_cost = std::max(max_cost, AirportMaintenanceCost(c->index)); - - uint count_width = GetStringBoundingBox(GetString(STR_JUST_COMMA, GetParamMaxValue(max_val))).width + WidgetDimensions::scaled.hsep_indent; // Reserve some wiggle room - - if (_settings_game.economy.infrastructure_maintenance) { - StringID str_total = TimerGameEconomy::UsingWallclockUnits() ? STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_PERIOD : STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_YEAR; - /* Convert to per year */ - this->total_width = GetStringBoundingBox(GetString(str_total, GetParamMaxValue(this->GetTotalMaintenanceCost() * 12))).width + WidgetDimensions::scaled.hsep_indent * 2; - size.width = std::max(size.width, this->total_width); - - /* Convert to per year */ - count_width += std::max(this->total_width, GetStringBoundingBox(GetString(str_total, GetParamMaxValue(max_cost * 12))).width); - } - - size.width = std::max(size.width, count_width); - - /* Set height of the total line. */ - if (widget == WID_CI_TOTAL) { - size.height = _settings_game.economy.infrastructure_maintenance ? std::max(size.height, WidgetDimensions::scaled.vsep_normal + GetCharacterHeight(FS_NORMAL)) : 0; - } - break; - } + if (def_height == 0) { + /* Try to open the window with the exact required rows, but clamp to a reasonable limit. */ + int rows = (this->GetWidget(WID_CI_LIST)->current_y - WidgetDimensions::scaled.framerect.Vertical()) / GetCharacterHeight(FS_NORMAL); + int delta = std::min(20, static_cast(std::size(this->list))) - rows; + def_height = this->height + delta * GetCharacterHeight(FS_NORMAL); } + + this->Window::FindWindowPlacementAndResize(def_width, def_height, allow_resize); } - /** - * Helper for drawing the counts line. - * @param r The bounds to draw in. - * @param y The y position to draw at. - * @param count The count to show on this line. - * @param monthly_cost The monthly costs. - */ - void DrawCountLine(const Rect &r, int &y, int count, Money monthly_cost) const + void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override { - Rect cr = r.Indent(this->total_width, _current_text_dir == TD_RTL); - DrawString(cr.left, cr.right, y += GetCharacterHeight(FS_NORMAL), GetString(STR_JUST_COMMA, count), TC_WHITE, SA_RIGHT | SA_FORCE); + if (widget != WID_CI_LIST) return; + + uint max_count = 1000; // Some random number to reserve minimum space. + Money max_cost = 1000000; // Some random number to reserve minimum space. + + /* List of headers that might be used. */ + static constexpr StringID header_strings[] = { + STR_COMPANY_INFRASTRUCTURE_VIEW_RAIL_SECT, + STR_COMPANY_INFRASTRUCTURE_VIEW_ROAD_SECT, + STR_COMPANY_INFRASTRUCTURE_VIEW_TRAM_SECT, + STR_COMPANY_INFRASTRUCTURE_VIEW_WATER_SECT, + STR_COMPANY_INFRASTRUCTURE_VIEW_STATION_SECT, + }; + /* List of labels that might be used. */ + static constexpr StringID label_strings[] = { + STR_COMPANY_INFRASTRUCTURE_VIEW_SIGNALS, + STR_COMPANY_INFRASTRUCTURE_VIEW_CANALS, + STR_COMPANY_INFRASTRUCTURE_VIEW_STATIONS, + STR_COMPANY_INFRASTRUCTURE_VIEW_AIRPORTS, + }; + + uint max_header_width = GetStringListWidth(header_strings); + uint max_label_width = GetStringListWidth(label_strings); + + /* Include width of all possible rail and road types. */ + for (const RailType &rt : _sorted_railtypes) max_label_width = std::max(max_label_width, GetStringBoundingBox(GetRailTypeInfo(rt)->strings.name).width); + for (const RoadType &rt : _sorted_roadtypes) max_label_width = std::max(max_label_width, GetStringBoundingBox(GetRoadTypeInfo(rt)->strings.name).width); + + for (const InfrastructureItem &entry : this->list) { + max_count = std::max(max_count, entry.count); + max_cost = std::max(max_cost, entry.cost * 12); + } + + max_label_width += WidgetDimensions::scaled.hsep_indent; + this->count_width = GetStringBoundingBox(GetString(STR_JUST_COMMA, max_count)).width; if (_settings_game.economy.infrastructure_maintenance) { - Rect tr = r.WithWidth(this->total_width, _current_text_dir == TD_RTL); - DrawString(tr.left, tr.right, y, - GetString(TimerGameEconomy::UsingWallclockUnits() ? STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_PERIOD : STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_YEAR, monthly_cost * 12), - TC_FROMSTRING, SA_RIGHT | SA_FORCE); + this->cost_width = GetStringBoundingBox(GetString(TimerGameEconomy::UsingWallclockUnits() ? STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_PERIOD : STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_YEAR, max_cost)).width; + } else { + this->cost_width = 0; } + + size.width = max_label_width + WidgetDimensions::scaled.hsep_wide + this->count_width + WidgetDimensions::scaled.hsep_wide + this->cost_width; + size.width = std::max(size.width, max_header_width) + WidgetDimensions::scaled.framerect.Horizontal(); + + fill.height = resize.height = GetCharacterHeight(FS_NORMAL); } void DrawWidget(const Rect &r, WidgetID widget) const override { - const Company *c = Company::Get(this->window_number); + if (widget != WID_CI_LIST) return; - int y = r.top; + bool rtl = _current_text_dir == TD_RTL; // We allocate space from end-to-start so the label fills. + int line_height = GetCharacterHeight(FS_NORMAL); - Rect ir = r.Indent(WidgetDimensions::scaled.hsep_indent, _current_text_dir == TD_RTL); - switch (widget) { - case WID_CI_RAIL_DESC: - DrawString(r.left, r.right, y, STR_COMPANY_INFRASTRUCTURE_VIEW_RAIL_SECT); + Rect ir = r.Shrink(WidgetDimensions::scaled.framerect); + Rect countr = ir.WithWidth(this->count_width, !rtl); + Rect costr = ir.Indent(this->count_width + WidgetDimensions::scaled.hsep_wide, !rtl).WithWidth(this->cost_width, !rtl); + Rect labelr = ir.Indent(this->count_width + WidgetDimensions::scaled.hsep_wide + this->cost_width + WidgetDimensions::scaled.hsep_wide, !rtl); - if (this->railtypes.Any()) { - /* Draw name of each valid railtype. */ - for (const auto &rt : _sorted_railtypes) { - if (this->railtypes.Test(rt)) { - DrawString(ir.left, ir.right, y += GetCharacterHeight(FS_NORMAL), GetRailTypeInfo(rt)->strings.name, TC_WHITE); - } + auto [first, last] = this->GetScrollbar(WID_CI_SCROLLBAR)->GetVisibleRangeIterators(this->list); + for (auto it = first; it != last; ++it) { + switch (it->type) { + case InfrastructureItemType::Header: + /* Header is allowed to fill the window's width. */ + DrawString(ir.left, ir.right, labelr.top, GetString(it->label), TC_ORANGE); + break; + + case InfrastructureItemType::Spacer: + break; + + case InfrastructureItemType::Total: + /* Draw line in the spacer above the total. */ + GfxFillRect(costr.Translate(0, -WidgetDimensions::scaled.vsep_normal).WithHeight(WidgetDimensions::scaled.fullbevel.top), PC_WHITE); + DrawString(costr, GetString(TimerGameEconomy::UsingWallclockUnits() ? STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_PERIOD : STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_YEAR, it->cost * 12), TC_BLACK, SA_RIGHT | SA_FORCE); + break; + + case InfrastructureItemType::Value: + DrawString(labelr.Indent(WidgetDimensions::scaled.hsep_indent, rtl), GetString(it->label), TC_WHITE); + DrawString(countr, GetString(STR_JUST_COMMA, it->count), TC_WHITE, SA_RIGHT | SA_FORCE); + if (_settings_game.economy.infrastructure_maintenance) { + DrawString(costr, GetString(TimerGameEconomy::UsingWallclockUnits() ? STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_PERIOD : STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_YEAR, it->cost * 12), TC_BLACK, SA_RIGHT | SA_FORCE); } - DrawString(ir.left, ir.right, y += GetCharacterHeight(FS_NORMAL), STR_COMPANY_INFRASTRUCTURE_VIEW_SIGNALS); - } else { - /* No valid railtype. */ - DrawString(ir.left, ir.right, y += GetCharacterHeight(FS_NORMAL), STR_COMPANY_VIEW_INFRASTRUCTURE_NONE); - } - - break; - - case WID_CI_RAIL_COUNT: { - /* Draw infrastructure count for each valid railtype. */ - uint32_t rail_total = c->infrastructure.GetRailTotal(); - for (const auto &rt : _sorted_railtypes) { - if (this->railtypes.Test(rt)) { - this->DrawCountLine(r, y, c->infrastructure.rail[rt], RailMaintenanceCost(rt, c->infrastructure.rail[rt], rail_total)); - } - } - if (this->railtypes.Any()) { - this->DrawCountLine(r, y, c->infrastructure.signal, SignalMaintenanceCost(c->infrastructure.signal)); - } - break; + break; } - case WID_CI_ROAD_DESC: - case WID_CI_TRAM_DESC: { - DrawString(r.left, r.right, y, widget == WID_CI_ROAD_DESC ? STR_COMPANY_INFRASTRUCTURE_VIEW_ROAD_SECT : STR_COMPANY_INFRASTRUCTURE_VIEW_TRAM_SECT); - - /* Draw name of each valid roadtype. */ - for (const auto &rt : _sorted_roadtypes) { - if (this->roadtypes.Test(rt) && RoadTypeIsRoad(rt) == (widget == WID_CI_ROAD_DESC)) { - DrawString(ir.left, ir.right, y += GetCharacterHeight(FS_NORMAL), GetRoadTypeInfo(rt)->strings.name, TC_WHITE); - } - } - - break; - } - - case WID_CI_ROAD_COUNT: - case WID_CI_TRAM_COUNT: { - uint32_t road_tram_total = widget == WID_CI_ROAD_COUNT ? c->infrastructure.GetRoadTotal() : c->infrastructure.GetTramTotal(); - for (const auto &rt : _sorted_roadtypes) { - if (this->roadtypes.Test(rt) && RoadTypeIsRoad(rt) == (widget == WID_CI_ROAD_COUNT)) { - this->DrawCountLine(r, y, c->infrastructure.road[rt], RoadMaintenanceCost(rt, c->infrastructure.road[rt], road_tram_total)); - } - } - break; - } - - case WID_CI_WATER_DESC: - DrawString(r.left, r.right, y, STR_COMPANY_INFRASTRUCTURE_VIEW_WATER_SECT); - DrawString(ir.left, ir.right, y += GetCharacterHeight(FS_NORMAL), STR_COMPANY_INFRASTRUCTURE_VIEW_CANALS); - break; - - case WID_CI_WATER_COUNT: - this->DrawCountLine(r, y, c->infrastructure.water, CanalMaintenanceCost(c->infrastructure.water)); - break; - - case WID_CI_TOTAL: - if (_settings_game.economy.infrastructure_maintenance) { - Rect tr = r.WithWidth(this->total_width, _current_text_dir == TD_RTL); - GfxFillRect(tr.left, y, tr.right, y + WidgetDimensions::scaled.bevel.top - 1, PC_WHITE); - y += WidgetDimensions::scaled.vsep_normal; - DrawString(tr.left, tr.right, y, - GetString(TimerGameEconomy::UsingWallclockUnits() ? STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_PERIOD : STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_YEAR, this->GetTotalMaintenanceCost() * 12), - TC_FROMSTRING, SA_RIGHT | SA_FORCE); - } - break; - - case WID_CI_STATION_DESC: - DrawString(r.left, r.right, y, STR_COMPANY_INFRASTRUCTURE_VIEW_STATION_SECT); - DrawString(ir.left, ir.right, y += GetCharacterHeight(FS_NORMAL), STR_COMPANY_INFRASTRUCTURE_VIEW_STATIONS); - DrawString(ir.left, ir.right, y += GetCharacterHeight(FS_NORMAL), STR_COMPANY_INFRASTRUCTURE_VIEW_AIRPORTS); - break; - - case WID_CI_STATION_COUNT: - this->DrawCountLine(r, y, c->infrastructure.station, StationMaintenanceCost(c->infrastructure.station)); - this->DrawCountLine(r, y, c->infrastructure.airport, AirportMaintenanceCost(c->index)); - break; + labelr.top += line_height; + countr.top += line_height; + costr.top += line_height; } } + const IntervalTimer redraw_interval = {std::chrono::seconds(1), [this](auto) { + this->UpdateInfrastructureList(); + this->SetWidgetDirty(WID_CI_LIST); + }}; + + void OnResize() override + { + this->GetScrollbar(WID_CI_SCROLLBAR)->SetCapacityFromWidget(this, WID_CI_LIST, WidgetDimensions::scaled.framerect.top); + } + /** * Some data on this window has become invalid. * @param data Information about the changed data. @@ -2075,7 +2003,6 @@ struct CompanyInfrastructureWindow : Window { if (!gui_scope) return; - this->UpdateRailRoadTypes(); this->ReInit(); } }; @@ -2373,7 +2300,7 @@ struct CompanyWindow : Window Point offset; Dimension d = GetSpriteSize(SPR_VEH_BUS_SW_VIEW, &offset); d.height -= offset.y; - DrawSprite(SPR_VEH_BUS_SW_VIEW, COMPANY_SPRITE_COLOUR(c->index), r.left - offset.x, CenterBounds(r.top, r.bottom, d.height) - offset.y); + DrawSprite(SPR_VEH_BUS_SW_VIEW, GetCompanyPalette(c->index), r.left - offset.x, CentreBounds(r.top, r.bottom, d.height) - offset.y); break; } @@ -2499,7 +2426,7 @@ struct CompanyWindow : Window } /** Redraw the window on a regular interval. */ - IntervalTimer redraw_interval = {std::chrono::seconds(3), [this](auto) { + const IntervalTimer redraw_interval = {std::chrono::seconds(3), [this](auto) { this->SetDirty(); }}; @@ -2524,7 +2451,9 @@ struct CompanyWindow : Window default: NOT_REACHED(); case WID_C_GIVE_MONEY: { - Money money = std::strtoull(str->c_str(), nullptr, 10) / GetCurrency().rate; + auto value = ParseInteger(*str, 10, true); + if (!value.has_value()) return; + Money money = *value / GetCurrency().rate; Command::Post(STR_ERROR_CAN_T_GIVE_MONEY, money, this->window_number); break; } @@ -2643,7 +2572,7 @@ struct BuyCompanyWindow : Window { /** * Check on a regular interval if the company value has changed. */ - IntervalTimer rescale_interval = {std::chrono::seconds(3), [this](auto) { + const IntervalTimer rescale_interval = {std::chrono::seconds(3), [this](auto) { /* Value can't change when in bankruptcy. */ if (!this->hostile_takeover) return; @@ -2680,7 +2609,7 @@ static constexpr NWidgetPart _nested_buy_company_widgets[] = { }; static WindowDesc _buy_company_desc( - WDP_AUTO, nullptr, 0, 0, + WDP_AUTO, {}, 0, 0, WC_BUY_COMPANY, WC_NONE, WindowDefaultFlag::Construction, _nested_buy_company_widgets diff --git a/src/console.cpp b/src/console.cpp index 675b8bbcb5..e94f2acb9d 100644 --- a/src/console.cpp +++ b/src/console.cpp @@ -8,6 +8,8 @@ /** @file console.cpp Handling of the in-game console. */ #include "stdafx.h" +#include "core/string_builder.hpp" +#include "core/string_consumer.hpp" #include "console_internal.h" #include "network/network.h" #include "network/network_func.h" @@ -18,7 +20,6 @@ #include "safeguards.h" -static const uint ICON_TOKEN_COUNT = 20; ///< Maximum number of tokens in one command static const uint ICON_MAX_RECURSE = 10; ///< Maximum number of recursion /* console parser */ @@ -117,30 +118,6 @@ void IConsolePrint(TextColour colour_code, const std::string &string) IConsoleGUIPrint(colour_code, str); } -/** - * Change a string into its number representation. Supports - * decimal and hexadecimal numbers as well as 'on'/'off' 'true'/'false' - * @param *value the variable a successful conversion will be put in - * @param *arg the string to be converted - * @return Return true on success or false on failure - */ -bool GetArgumentInteger(uint32_t *value, const char *arg) -{ - char *endptr; - - if (strcmp(arg, "on") == 0 || strcmp(arg, "true") == 0) { - *value = 1; - return true; - } - if (strcmp(arg, "off") == 0 || strcmp(arg, "false") == 0) { - *value = 0; - return true; - } - - *value = std::strtoul(arg, &endptr, 0); - return arg != endptr; -} - /** * Creates a copy of a string with underscores removed from it * @param name String to remove the underscores from. @@ -179,7 +156,7 @@ static std::string RemoveUnderscores(std::string name) * @param name name of the alias that will be used * @param cmd name of the command that 'name' will be alias of */ -/* static */ void IConsole::AliasRegister(const std::string &name, const std::string &cmd) +/* static */ void IConsole::AliasRegister(const std::string &name, std::string_view cmd) { auto result = IConsole::Aliases().try_emplace(RemoveUnderscores(name), name, cmd); if (!result.second) IConsolePrint(CC_ERROR, "An alias with the name '{}' already exists.", name); @@ -200,14 +177,12 @@ static std::string RemoveUnderscores(std::string name) /** * An alias is just another name for a command, or for more commands * Execute it as well. - * @param *alias is the alias of the command - * @param tokencount the number of parameters passed - * @param *tokens are the parameters given to the original command (0 is the first param) + * @param alias is the alias of the command + * @param tokens are the parameters given to the original command (0 is the first param) + * @param recurse_count the number of re-entrant calls to this function */ -static void IConsoleAliasExec(const IConsoleAlias *alias, uint8_t tokencount, char *tokens[ICON_TOKEN_COUNT], const uint recurse_count) +static void IConsoleAliasExec(const IConsoleAlias *alias, std::span tokens, uint recurse_count) { - std::string alias_buffer; - Debug(console, 6, "Requested command is an alias; parsing..."); if (recurse_count > ICON_MAX_RECURSE) { @@ -215,72 +190,75 @@ static void IConsoleAliasExec(const IConsoleAlias *alias, uint8_t tokencount, ch return; } - for (const char *cmdptr = alias->cmdline.c_str(); *cmdptr != '\0'; cmdptr++) { - switch (*cmdptr) { + std::string buffer; + StringBuilder builder{buffer}; + + StringConsumer consumer{alias->cmdline}; + while (consumer.AnyBytesLeft()) { + auto c = consumer.TryReadUtf8(); + if (!c.has_value()) { + IConsolePrint(CC_ERROR, "Alias '{}' ('{}') contains malformed characters.", alias->name, alias->cmdline); + return; + } + + switch (*c) { case '\'': // ' will double for "" - alias_buffer += '\"'; + builder.PutChar('\"'); break; case ';': // Cmd separator; execute previous and start new command - IConsoleCmdExec(alias_buffer, recurse_count); + IConsoleCmdExec(builder.GetString(), recurse_count); - alias_buffer.clear(); - - cmdptr++; + buffer.clear(); break; case '%': // Some or all parameters - cmdptr++; - switch (*cmdptr) { + c = consumer.ReadUtf8(); + switch (*c) { case '+': { // All parameters separated: "[param 1]" "[param 2]" - for (uint i = 0; i != tokencount; i++) { - if (i != 0) alias_buffer += ' '; - alias_buffer += '\"'; - alias_buffer += tokens[i]; - alias_buffer += '\"'; + for (size_t i = 0; i < tokens.size(); ++i) { + if (i != 0) builder.PutChar(' '); + builder.PutChar('\"'); + builder += tokens[i]; + builder.PutChar('\"'); } break; } case '!': { // Merge the parameters to one: "[param 1] [param 2] [param 3...]" - alias_buffer += '\"'; - for (uint i = 0; i != tokencount; i++) { - if (i != 0) alias_buffer += " "; - alias_buffer += tokens[i]; + builder.PutChar('\"'); + for (size_t i = 0; i < tokens.size(); ++i) { + if (i != 0) builder.PutChar(' '); + builder += tokens[i]; } - alias_buffer += '\"'; + builder.PutChar('\"'); break; } default: { // One specific parameter: %A = [param 1] %B = [param 2] ... - int param = *cmdptr - 'A'; + size_t param = *c - 'A'; - if (param < 0 || param >= tokencount) { + if (param >= tokens.size()) { IConsolePrint(CC_ERROR, "Too many or wrong amount of parameters passed to alias."); IConsolePrint(CC_HELP, "Usage of alias '{}': '{}'.", alias->name, alias->cmdline); return; } - alias_buffer += '\"'; - alias_buffer += tokens[param]; - alias_buffer += '\"'; + builder.PutChar('\"'); + builder += tokens[param]; + builder.PutChar('\"'); break; } } break; default: - alias_buffer += *cmdptr; + builder.PutUtf8(*c); break; } - - if (alias_buffer.size() >= ICON_MAX_STREAMSIZE - 1) { - IConsolePrint(CC_ERROR, "Requested alias execution would overflow execution buffer."); - return; - } } - IConsoleCmdExec(alias_buffer, recurse_count); + IConsoleCmdExec(builder.GetString(), recurse_count); } /** @@ -288,88 +266,73 @@ static void IConsoleAliasExec(const IConsoleAlias *alias, uint8_t tokencount, ch * individual tokens (separated by spaces), then execute it if possible * @param command_string string to be parsed and executed */ -void IConsoleCmdExec(const std::string &command_string, const uint recurse_count) +void IConsoleCmdExec(std::string_view command_string, const uint recurse_count) { - const char *cmdptr; - char *tokens[ICON_TOKEN_COUNT], tokenstream[ICON_MAX_STREAMSIZE]; - uint t_index, tstream_i; - - bool longtoken = false; - bool foundtoken = false; - if (command_string[0] == '#') return; // comments - for (cmdptr = command_string.c_str(); *cmdptr != '\0'; cmdptr++) { - if (!IsValidChar(*cmdptr, CS_ALPHANUMERAL)) { - IConsolePrint(CC_ERROR, "Command '{}' contains malformed characters.", command_string); - return; - } - } - Debug(console, 4, "Executing cmdline: '{}'", command_string); - memset(&tokens, 0, sizeof(tokens)); - memset(&tokenstream, 0, sizeof(tokenstream)); + std::string buffer; + StringBuilder builder{buffer}; + StringConsumer consumer{command_string}; + + std::vector tokens; + bool found_token = false; + bool in_quotes = false; /* 1. Split up commandline into tokens, separated by spaces, commands * enclosed in "" are taken as one token. We can only go as far as the amount * of characters in our stream or the max amount of tokens we can handle */ - for (cmdptr = command_string.c_str(), t_index = 0, tstream_i = 0; *cmdptr != '\0'; cmdptr++) { - if (tstream_i >= lengthof(tokenstream)) { - IConsolePrint(CC_ERROR, "Command line too long."); + while (consumer.AnyBytesLeft()) { + auto c = consumer.TryReadUtf8(); + if (!c.has_value()) { + IConsolePrint(CC_ERROR, "Command '{}' contains malformed characters.", command_string); return; } - switch (*cmdptr) { - case ' ': // Token separator - if (!foundtoken) break; + switch (*c) { + case ' ': // Token separator + if (!found_token) break; - if (longtoken) { - tokenstream[tstream_i] = *cmdptr; - } else { - tokenstream[tstream_i] = '\0'; - foundtoken = false; - } - - tstream_i++; - break; - case '"': // Tokens enclosed in "" are one token - longtoken = !longtoken; - if (!foundtoken) { - if (t_index >= lengthof(tokens)) { - IConsolePrint(CC_ERROR, "Command line too long."); - return; + if (in_quotes) { + builder.PutUtf8(*c); + break; } - tokens[t_index++] = &tokenstream[tstream_i]; - foundtoken = true; - } - break; - case '\\': // Escape character for "" - if (cmdptr[1] == '"' && tstream_i + 1 < lengthof(tokenstream)) { - tokenstream[tstream_i++] = *++cmdptr; + + tokens.emplace_back(std::move(buffer)); + buffer.clear(); + found_token = false; break; - } - [[fallthrough]]; - default: // Normal character - tokenstream[tstream_i++] = *cmdptr; - if (!foundtoken) { - if (t_index >= lengthof(tokens)) { - IConsolePrint(CC_ERROR, "Command line too long."); - return; + case '"': // Tokens enclosed in "" are one token + in_quotes = !in_quotes; + found_token = true; + break; + + case '\\': // Escape character for "" + if (consumer.ReadUtf8If('"')) { + builder.PutUtf8('"'); + break; } - tokens[t_index++] = &tokenstream[tstream_i - 1]; - foundtoken = true; - } - break; + [[fallthrough]]; + + default: // Normal character + builder.PutUtf8(*c); + found_token = true; + break; } } - for (uint i = 0; i < lengthof(tokens) && tokens[i] != nullptr; i++) { + if (found_token) { + tokens.emplace_back(std::move(buffer)); + buffer.clear(); + } + + for (size_t i = 0; i < tokens.size(); i++) { Debug(console, 8, "Token {} is: '{}'", i, tokens[i]); } - if (StrEmpty(tokens[0])) return; // don't execute empty commands + if (tokens.empty() || tokens[0].empty()) return; // don't execute empty commands /* 2. Determine type of command (cmd or alias) and execute * First try commands, then aliases. Execute * the found action taking into account its hooking code @@ -378,21 +341,23 @@ void IConsoleCmdExec(const std::string &command_string, const uint recurse_count if (cmd != nullptr) { ConsoleHookResult chr = (cmd->hook == nullptr ? CHR_ALLOW : cmd->hook(true)); switch (chr) { - case CHR_ALLOW: - if (!cmd->proc(t_index, tokens)) { // index started with 0 - cmd->proc(0, nullptr); // if command failed, give help + case CHR_ALLOW: { + std::vector views; + for (auto &token : tokens) views.emplace_back(token); + if (!cmd->proc(views)) { // index started with 0 + cmd->proc({}); // if command failed, give help } return; + } case CHR_DISALLOW: return; case CHR_HIDE: break; } } - t_index--; IConsoleAlias *alias = IConsole::AliasGet(tokens[0]); if (alias != nullptr) { - IConsoleAliasExec(alias, t_index, &tokens[1], recurse_count + 1); + IConsoleAliasExec(alias, std::span(tokens).subspan(1), recurse_count + 1); return; } diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index 93151f364b..3293c4704f 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -8,6 +8,7 @@ /** @file console_cmds.cpp Implementation of the console hooks. */ #include "stdafx.h" +#include "core/string_consumer.hpp" #include "console_internal.h" #include "debug.h" #include "engine_func.h" @@ -56,7 +57,7 @@ static uint _script_current_depth; ///< Depth of scripts running (used to abort static std::string _scheduled_monthly_script; ///< Script scheduled to execute by the 'schedule' console command (empty if no script is scheduled). /** Timer that runs every month of game time for the 'schedule' console command. */ -static IntervalTimer _scheduled_monthly_timer = {{TimerGameCalendar::MONTH, TimerGameCalendar::Priority::NONE}, [](auto) { +static const IntervalTimer _scheduled_monthly_timer = {{TimerGameCalendar::MONTH, TimerGameCalendar::Priority::NONE}, [](auto) { if (_scheduled_monthly_script.empty()) { return; } @@ -67,9 +68,23 @@ static IntervalTimer _scheduled_monthly_timer = {{TimerGameCa _scheduled_monthly_script.clear(); IConsolePrint(CC_DEFAULT, "Executing scheduled script file '{}'...", filename); - IConsoleCmdExec(std::string("exec") + " " + filename); + IConsoleCmdExec(fmt::format("exec {}", filename)); }}; +/** + * Parse an integer using #ParseInteger and convert it to the requested type. + * @param arg The string to be converted. + * @tparam T The type to return. + * @return The number in the given type, or std::nullopt when it could not be parsed. + */ +template +static std::optional ParseType(std::string_view arg) +{ + auto i = ParseInteger(arg); + if (i.has_value()) return static_cast(*i); + return std::nullopt; +} + /** File list storage for the console, for caching the last 'ls' command. */ class ConsoleFileList : public FileList { public: @@ -105,11 +120,6 @@ static ConsoleFileList _console_file_list_savegame{FT_SAVEGAME, true}; ///< File static ConsoleFileList _console_file_list_scenario{FT_SCENARIO, false}; ///< File storage cache for scenarios. static ConsoleFileList _console_file_list_heightmap{FT_HEIGHTMAP, false}; ///< File storage cache for heightmaps. -/* console command defines */ -#define DEF_CONSOLE_CMD(function) static bool function([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) -#define DEF_CONSOLE_HOOK(function) static ConsoleHookResult function(bool echo) - - /**************** * command hooks ****************/ @@ -131,7 +141,7 @@ static inline bool NetworkAvailable(bool echo) * Check whether we are a server. * @return Are we a server? True when yes, false otherwise. */ -DEF_CONSOLE_HOOK(ConHookServerOnly) +static ConsoleHookResult ConHookServerOnly(bool echo) { if (!NetworkAvailable(echo)) return CHR_DISALLOW; @@ -146,7 +156,7 @@ DEF_CONSOLE_HOOK(ConHookServerOnly) * Check whether we are a client in a network game. * @return Are we a client in a network game? True when yes, false otherwise. */ -DEF_CONSOLE_HOOK(ConHookClientOnly) +static ConsoleHookResult ConHookClientOnly(bool echo) { if (!NetworkAvailable(echo)) return CHR_DISALLOW; @@ -161,7 +171,7 @@ DEF_CONSOLE_HOOK(ConHookClientOnly) * Check whether we are in a multiplayer game. * @return True when we are client or server in a network game. */ -DEF_CONSOLE_HOOK(ConHookNeedNetwork) +static ConsoleHookResult ConHookNeedNetwork(bool echo) { if (!NetworkAvailable(echo)) return CHR_DISALLOW; @@ -176,7 +186,7 @@ DEF_CONSOLE_HOOK(ConHookNeedNetwork) * Check whether we are in a multiplayer game and are playing, i.e. we are not the dedicated server. * @return Are we a client or non-dedicated server in a network game? True when yes, false otherwise. */ -DEF_CONSOLE_HOOK(ConHookNeedNonDedicatedNetwork) +static ConsoleHookResult ConHookNeedNonDedicatedNetwork(bool echo) { if (!NetworkAvailable(echo)) return CHR_DISALLOW; @@ -191,7 +201,7 @@ DEF_CONSOLE_HOOK(ConHookNeedNonDedicatedNetwork) * Check whether we are in singleplayer mode. * @return True when no network is active. */ -DEF_CONSOLE_HOOK(ConHookNoNetwork) +static ConsoleHookResult ConHookNoNetwork(bool echo) { if (_networking) { if (echo) IConsolePrint(CC_ERROR, "This command is forbidden in multiplayer."); @@ -204,7 +214,7 @@ DEF_CONSOLE_HOOK(ConHookNoNetwork) * Check if are either in singleplayer or a server. * @return True iff we are either in singleplayer or a server. */ -DEF_CONSOLE_HOOK(ConHookServerOrNoNetwork) +static ConsoleHookResult ConHookServerOrNoNetwork(bool echo) { if (_networking && !_network_server) { if (echo) IConsolePrint(CC_ERROR, "This command is only available to a network server."); @@ -213,7 +223,7 @@ DEF_CONSOLE_HOOK(ConHookServerOrNoNetwork) return CHR_ALLOW; } -DEF_CONSOLE_HOOK(ConHookNewGRFDeveloperTool) +static ConsoleHookResult ConHookNewGRFDeveloperTool(bool echo) { if (_settings_client.gui.newgrf_developer_tools) { if (_game_mode == GM_MENU) { @@ -229,9 +239,9 @@ DEF_CONSOLE_HOOK(ConHookNewGRFDeveloperTool) * Reset status of all engines. * @return Will always succeed. */ -DEF_CONSOLE_CMD(ConResetEngines) +static bool ConResetEngines(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Reset status data of all engines. This might solve some issues with 'lost' engines. Usage: 'resetengines'."); return true; } @@ -245,9 +255,9 @@ DEF_CONSOLE_CMD(ConResetEngines) * @return Will always return true. * @note Resetting the pool only succeeds when there are no vehicles ingame. */ -DEF_CONSOLE_CMD(ConResetEnginePool) +static bool ConResetEnginePool(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Reset NewGRF allocations of engine slots. This will remove invalid engine definitions, and might make default engines available again."); return true; } @@ -271,18 +281,18 @@ DEF_CONSOLE_CMD(ConResetEnginePool) * param tile number. * @return True when the tile is reset or the help on usage was printed (0 or two parameters). */ -DEF_CONSOLE_CMD(ConResetTile) +static bool ConResetTile(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Reset a tile to bare land. Usage: 'resettile '."); IConsolePrint(CC_HELP, "Tile can be either decimal (34161) or hexadecimal (0x4a5B)."); return true; } - if (argc == 2) { - uint32_t result; - if (GetArgumentInteger(&result, argv[1])) { - DoClearSquare((TileIndex)result); + if (argv.size() == 2) { + auto result = ParseInteger(argv[1], 0); + if (result.has_value() && IsValidTile(*result)) { + DoClearSquare(TileIndex{*result}); return true; } } @@ -296,46 +306,39 @@ DEF_CONSOLE_CMD(ConResetTile) * param level As defined by ZoomLevel and as limited by zoom_min/zoom_max from GUISettings. * @return True when either console help was shown or a proper amount of parameters given. */ -DEF_CONSOLE_CMD(ConZoomToLevel) +static bool ConZoomToLevel(std::span argv) { - switch (argc) { + switch (argv.size()) { case 0: IConsolePrint(CC_HELP, "Set the current zoom level of the main viewport."); IConsolePrint(CC_HELP, "Usage: 'zoomto '."); - if (ZOOM_LVL_MIN < _settings_client.gui.zoom_min) { - IConsolePrint(CC_HELP, "The lowest zoom-in level allowed by current client settings is {}.", std::max(ZOOM_LVL_MIN, _settings_client.gui.zoom_min)); + if (ZoomLevel::Min < _settings_client.gui.zoom_min) { + IConsolePrint(CC_HELP, "The lowest zoom-in level allowed by current client settings is {}.", std::max(ZoomLevel::Min, _settings_client.gui.zoom_min)); } else { - IConsolePrint(CC_HELP, "The lowest supported zoom-in level is {}.", std::max(ZOOM_LVL_MIN, _settings_client.gui.zoom_min)); + IConsolePrint(CC_HELP, "The lowest supported zoom-in level is {}.", std::max(ZoomLevel::Min, _settings_client.gui.zoom_min)); } - if (_settings_client.gui.zoom_max < ZOOM_LVL_MAX) { - IConsolePrint(CC_HELP, "The highest zoom-out level allowed by current client settings is {}.", std::min(_settings_client.gui.zoom_max, ZOOM_LVL_MAX)); + if (_settings_client.gui.zoom_max < ZoomLevel::Max) { + IConsolePrint(CC_HELP, "The highest zoom-out level allowed by current client settings is {}.", std::min(_settings_client.gui.zoom_max, ZoomLevel::Max)); } else { - IConsolePrint(CC_HELP, "The highest supported zoom-out level is {}.", std::min(_settings_client.gui.zoom_max, ZOOM_LVL_MAX)); + IConsolePrint(CC_HELP, "The highest supported zoom-out level is {}.", std::min(_settings_client.gui.zoom_max, ZoomLevel::Max)); } return true; case 2: { - uint32_t level; - if (GetArgumentInteger(&level, argv[1])) { - /* In case ZOOM_LVL_MIN is more than 0, the next if statement needs to be amended. - * A simple check for less than ZOOM_LVL_MIN does not work here because we are - * reading an unsigned integer from the console, so just check for a '-' char. */ - static_assert(ZOOM_LVL_MIN == 0); - if (argv[1][0] == '-') { - IConsolePrint(CC_ERROR, "Zoom-in levels below {} are not supported.", ZOOM_LVL_MIN); - } else if (level < _settings_client.gui.zoom_min) { - IConsolePrint(CC_ERROR, "Current client settings do not allow zooming in below level {}.", _settings_client.gui.zoom_min); - } else if (level > ZOOM_LVL_MAX) { - IConsolePrint(CC_ERROR, "Zoom-in levels above {} are not supported.", ZOOM_LVL_MAX); - } else if (level > _settings_client.gui.zoom_max) { - IConsolePrint(CC_ERROR, "Current client settings do not allow zooming out beyond level {}.", _settings_client.gui.zoom_max); + auto level = ParseInteger>(argv[1]); + if (level.has_value()) { + auto zoom_lvl = static_cast(*level); + if (!IsInsideMM(zoom_lvl, ZoomLevel::Begin, ZoomLevel::End)) { + IConsolePrint(CC_ERROR, "Invalid zoom level. Valid range is {} to {}.", ZoomLevel::Min, ZoomLevel::Max); + } else if (!IsInsideMM(zoom_lvl, _settings_client.gui.zoom_min, _settings_client.gui.zoom_max + 1)) { + IConsolePrint(CC_ERROR, "Current client settings limit zoom levels to range {} to {}.", _settings_client.gui.zoom_min, _settings_client.gui.zoom_max); } else { Window *w = GetMainWindow(); Viewport &vp = *w->viewport; - while (vp.zoom > level) DoZoomInOutWindow(ZOOM_IN, w); - while (vp.zoom < level) DoZoomInOutWindow(ZOOM_OUT, w); + while (vp.zoom > zoom_lvl) DoZoomInOutWindow(ZOOM_IN, w); + while (vp.zoom < zoom_lvl) DoZoomInOutWindow(ZOOM_OUT, w); } return true; } @@ -355,46 +358,47 @@ DEF_CONSOLE_CMD(ConZoomToLevel) * and y coordinates. * @return True when either console help was shown or a proper amount of parameters given. */ -DEF_CONSOLE_CMD(ConScrollToTile) +static bool ConScrollToTile(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Center the screen on a given tile."); IConsolePrint(CC_HELP, "Usage: 'scrollto [instant] ' or 'scrollto [instant] '."); IConsolePrint(CC_HELP, "Numbers can be either decimal (34161) or hexadecimal (0x4a5B)."); IConsolePrint(CC_HELP, "'instant' will immediately move and redraw viewport without smooth scrolling."); return true; } - if (argc < 2) return false; + if (argv.size() < 2) return false; uint32_t arg_index = 1; bool instant = false; - if (strcmp(argv[arg_index], "instant") == 0) { + if (argv[arg_index] == "instant") { ++arg_index; instant = true; } - switch (argc - arg_index) { + switch (argv.size() - arg_index) { case 1: { - uint32_t result; - if (GetArgumentInteger(&result, argv[arg_index])) { - if (result >= Map::Size()) { + auto result = ParseInteger(argv[arg_index], 0); + if (result.has_value()) { + if (*result >= Map::Size()) { IConsolePrint(CC_ERROR, "Tile does not exist."); return true; } - ScrollMainWindowToTile((TileIndex)result, instant); + ScrollMainWindowToTile(TileIndex{*result}, instant); return true; } break; } case 2: { - uint32_t x, y; - if (GetArgumentInteger(&x, argv[arg_index]) && GetArgumentInteger(&y, argv[arg_index + 1])) { - if (x >= Map::SizeX() || y >= Map::SizeY()) { + auto x = ParseInteger(argv[arg_index], 0); + auto y = ParseInteger(argv[arg_index + 1], 0); + if (x.has_value() && y.has_value()) { + if (*x >= Map::SizeX() || *y >= Map::SizeY()) { IConsolePrint(CC_ERROR, "Tile does not exist."); return true; } - ScrollMainWindowToTile(TileXY(x, y), instant); + ScrollMainWindowToTile(TileXY(*x, *y), instant); return true; } break; @@ -409,16 +413,15 @@ DEF_CONSOLE_CMD(ConScrollToTile) * param filename the filename to save the map to. * @return True when help was displayed or the file attempted to be saved. */ -DEF_CONSOLE_CMD(ConSave) +static bool ConSave(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Save the current game. Usage: 'save '."); return true; } - if (argc == 2) { - std::string filename = argv[1]; - filename += ".sav"; + if (argv.size() == 2) { + std::string filename = fmt::format("{}.sav", argv[1]); IConsolePrint(CC_DEFAULT, "Saving map..."); if (SaveOrLoad(filename, SLO_SAVE, DFT_GAME_FILE, SAVE_DIR) != SL_OK) { @@ -436,9 +439,9 @@ DEF_CONSOLE_CMD(ConSave) * Explicitly save the configuration. * @return True. */ -DEF_CONSOLE_CMD(ConSaveConfig) +static bool ConSaveConfig(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Saves the configuration for new games to the configuration file, typically 'openttd.cfg'."); IConsolePrint(CC_HELP, "It does not save the configuration of the current game to the configuration file."); return true; @@ -449,20 +452,20 @@ DEF_CONSOLE_CMD(ConSaveConfig) return true; } -DEF_CONSOLE_CMD(ConLoad) +static bool ConLoad(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Load a game by name or index. Usage: 'load '."); return true; } - if (argc != 2) return false; + if (argv.size() != 2) return false; - const char *file = argv[1]; + std::string_view file = argv[1]; _console_file_list_savegame.ValidateFileList(); const FiosItem *item = _console_file_list_savegame.FindItem(file); if (item != nullptr) { - if (GetAbstractFileType(item->type) == FT_SAVEGAME) { + if (item->type.abstract == FT_SAVEGAME) { _switch_mode = SM_LOAD_GAME; _file_to_saveload.Set(*item); } else { @@ -475,20 +478,20 @@ DEF_CONSOLE_CMD(ConLoad) return true; } -DEF_CONSOLE_CMD(ConLoadScenario) +static bool ConLoadScenario(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Load a scenario by name or index. Usage: 'load_scenario '."); return true; } - if (argc != 2) return false; + if (argv.size() != 2) return false; - const char *file = argv[1]; + std::string_view file = argv[1]; _console_file_list_scenario.ValidateFileList(); const FiosItem *item = _console_file_list_scenario.FindItem(file); if (item != nullptr) { - if (GetAbstractFileType(item->type) == FT_SCENARIO) { + if (item->type.abstract == FT_SCENARIO) { _switch_mode = SM_LOAD_GAME; _file_to_saveload.Set(*item); } else { @@ -501,20 +504,20 @@ DEF_CONSOLE_CMD(ConLoadScenario) return true; } -DEF_CONSOLE_CMD(ConLoadHeightmap) +static bool ConLoadHeightmap(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Load a heightmap by name or index. Usage: 'load_heightmap '."); return true; } - if (argc != 2) return false; + if (argv.size() != 2) return false; - const char *file = argv[1]; + std::string_view file = argv[1]; _console_file_list_heightmap.ValidateFileList(); const FiosItem *item = _console_file_list_heightmap.FindItem(file); if (item != nullptr) { - if (GetAbstractFileType(item->type) == FT_HEIGHTMAP) { + if (item->type.abstract == FT_HEIGHTMAP) { _switch_mode = SM_START_HEIGHTMAP; _file_to_saveload.Set(*item); } else { @@ -527,21 +530,25 @@ DEF_CONSOLE_CMD(ConLoadHeightmap) return true; } -DEF_CONSOLE_CMD(ConRemove) +static bool ConRemove(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Remove a savegame by name or index. Usage: 'rm '."); return true; } - if (argc != 2) return false; + if (argv.size() != 2) return false; - const char *file = argv[1]; + std::string_view file = argv[1]; _console_file_list_savegame.ValidateFileList(); const FiosItem *item = _console_file_list_savegame.FindItem(file); if (item != nullptr) { - if (!FioRemove(item->name)) { - IConsolePrint(CC_ERROR, "Failed to delete '{}'.", item->name); + if (item->type.abstract == FT_SAVEGAME) { + if (!FioRemove(item->name)) { + IConsolePrint(CC_ERROR, "Failed to delete '{}'.", item->name); + } + } else { + IConsolePrint(CC_ERROR, "'{}' is not a savegame.", file); } } else { IConsolePrint(CC_ERROR, "'{}' could not be found.", file); @@ -553,9 +560,9 @@ DEF_CONSOLE_CMD(ConRemove) /* List all the files in the current dir via console */ -DEF_CONSOLE_CMD(ConListFiles) +static bool ConListFiles(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "List all loadable savegames and directories in the current dir via console. Usage: 'ls | dir'."); return true; } @@ -569,9 +576,9 @@ DEF_CONSOLE_CMD(ConListFiles) } /* List all the scenarios */ -DEF_CONSOLE_CMD(ConListScenarios) +static bool ConListScenarios(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "List all loadable scenarios. Usage: 'list_scenarios'."); return true; } @@ -585,9 +592,9 @@ DEF_CONSOLE_CMD(ConListScenarios) } /* List all the heightmaps */ -DEF_CONSOLE_CMD(ConListHeightmaps) +static bool ConListHeightmaps(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "List all loadable heightmaps. Usage: 'list_heightmaps'."); return true; } @@ -601,21 +608,23 @@ DEF_CONSOLE_CMD(ConListHeightmaps) } /* Change the dir via console */ -DEF_CONSOLE_CMD(ConChangeDirectory) +static bool ConChangeDirectory(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Change the dir via console. Usage: 'cd '."); return true; } - if (argc != 2) return false; + if (argv.size() != 2) return false; - const char *file = argv[1]; + std::string_view file = argv[1]; _console_file_list_savegame.ValidateFileList(true); const FiosItem *item = _console_file_list_savegame.FindItem(file); if (item != nullptr) { - switch (item->type) { - case FIOS_TYPE_DIR: case FIOS_TYPE_DRIVE: case FIOS_TYPE_PARENT: + switch (item->type.detailed) { + case DFT_FIOS_DIR: + case DFT_FIOS_DRIVE: + case DFT_FIOS_PARENT: FiosBrowseTo(item); break; default: IConsolePrint(CC_ERROR, "{}: Not a directory.", file); @@ -628,9 +637,9 @@ DEF_CONSOLE_CMD(ConChangeDirectory) return true; } -DEF_CONSOLE_CMD(ConPrintWorkingDirectory) +static bool ConPrintWorkingDirectory(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Print out the current working directory. Usage: 'pwd'."); return true; } @@ -643,9 +652,9 @@ DEF_CONSOLE_CMD(ConPrintWorkingDirectory) return true; } -DEF_CONSOLE_CMD(ConClearBuffer) +static bool ConClearBuffer(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Clear the console buffer. Usage: 'clear'."); return true; } @@ -660,38 +669,42 @@ DEF_CONSOLE_CMD(ConClearBuffer) * Network Core Console Commands **********************************/ -static bool ConKickOrBan(const char *argv, bool ban, const std::string &reason) +static bool ConKickOrBan(std::string_view arg, bool ban, std::string_view reason) { uint n; - if (strchr(argv, '.') == nullptr && strchr(argv, ':') == nullptr) { // banning with ID - ClientID client_id = (ClientID)atoi(argv); + if (arg.find_first_of(".:") == std::string::npos) { // banning with ID + auto client_id = ParseType(arg); + if (!client_id.has_value()) { + IConsolePrint(CC_ERROR, "The given client-id is not a valid number."); + return true; + } /* Don't kill the server, or the client doing the rcon. The latter can't be kicked because * kicking frees closes and subsequently free the connection related instances, which we * would be reading from and writing to after returning. So we would read or write data * from freed memory up till the segfault triggers. */ - if (client_id == CLIENT_ID_SERVER || client_id == _redirect_console_to_client) { + if (*client_id == CLIENT_ID_SERVER || *client_id == _redirect_console_to_client) { IConsolePrint(CC_ERROR, "You can not {} yourself!", ban ? "ban" : "kick"); return true; } - NetworkClientInfo *ci = NetworkClientInfo::GetByClientID(client_id); + NetworkClientInfo *ci = NetworkClientInfo::GetByClientID(*client_id); if (ci == nullptr) { - IConsolePrint(CC_ERROR, "Invalid client ID."); + IConsolePrint(CC_ERROR, "Invalid client-id."); return true; } if (!ban) { /* Kick only this client, not all clients with that IP */ - NetworkServerKickClient(client_id, reason); + NetworkServerKickClient(*client_id, reason); return true; } /* When banning, kick+ban all clients with that IP */ - n = NetworkServerKickOrBanIP(client_id, ban, reason); + n = NetworkServerKickOrBanIP(*client_id, ban, reason); } else { - n = NetworkServerKickOrBanIP(argv, ban, reason); + n = NetworkServerKickOrBanIP(arg, ban, reason); } if (n == 0) { @@ -703,21 +716,21 @@ static bool ConKickOrBan(const char *argv, bool ban, const std::string &reason) return true; } -DEF_CONSOLE_CMD(ConKick) +static bool ConKick(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Kick a client from a network game. Usage: 'kick []'."); IConsolePrint(CC_HELP, "For client-id's, see the command 'clients'."); return true; } - if (argc != 2 && argc != 3) return false; + if (argv.size() != 2 && argv.size() != 3) return false; /* No reason supplied for kicking */ - if (argc == 2) return ConKickOrBan(argv[1], false, {}); + if (argv.size() == 2) return ConKickOrBan(argv[1], false, {}); /* Reason for kicking supplied */ - size_t kick_message_length = strlen(argv[2]); + size_t kick_message_length = argv[2].size(); if (kick_message_length >= 255) { IConsolePrint(CC_ERROR, "Maximum kick message length is 254 characters. You entered {} characters.", kick_message_length); return false; @@ -726,22 +739,22 @@ DEF_CONSOLE_CMD(ConKick) } } -DEF_CONSOLE_CMD(ConBan) +static bool ConBan(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Ban a client from a network game. Usage: 'ban []'."); IConsolePrint(CC_HELP, "For client-id's, see the command 'clients'."); IConsolePrint(CC_HELP, "If the client is no longer online, you can still ban their IP."); return true; } - if (argc != 2 && argc != 3) return false; + if (argv.size() != 2 && argv.size() != 3) return false; /* No reason supplied for kicking */ - if (argc == 2) return ConKickOrBan(argv[1], true, {}); + if (argv.size() == 2) return ConKickOrBan(argv[1], true, {}); /* Reason for kicking supplied */ - size_t kick_message_length = strlen(argv[2]); + size_t kick_message_length = argv[2].size(); if (kick_message_length >= 255) { IConsolePrint(CC_ERROR, "Maximum kick message length is 254 characters. You entered {} characters.", kick_message_length); return false; @@ -750,15 +763,15 @@ DEF_CONSOLE_CMD(ConBan) } } -DEF_CONSOLE_CMD(ConUnBan) +static bool ConUnBan(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Unban a client from a network game. Usage: 'unban '."); IConsolePrint(CC_HELP, "For a list of banned IP's, see the command 'banlist'."); return true; } - if (argc != 2) return false; + if (argv.size() != 2) return false; /* Try by IP. */ uint index; @@ -768,7 +781,7 @@ DEF_CONSOLE_CMD(ConUnBan) /* Try by index. */ if (index >= _network_ban_list.size()) { - index = atoi(argv[1]) - 1U; // let it wrap + index = ParseInteger(argv[1]).value_or(0) - 1U; // let it wrap } if (index < _network_ban_list.size()) { @@ -782,9 +795,9 @@ DEF_CONSOLE_CMD(ConUnBan) return true; } -DEF_CONSOLE_CMD(ConBanList) +static bool ConBanList(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "List the IP's of banned clients: Usage 'banlist'."); return true; } @@ -800,9 +813,9 @@ DEF_CONSOLE_CMD(ConBanList) return true; } -DEF_CONSOLE_CMD(ConPauseGame) +static bool ConPauseGame(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Pause a network game. Usage: 'pause'."); return true; } @@ -822,9 +835,9 @@ DEF_CONSOLE_CMD(ConPauseGame) return true; } -DEF_CONSOLE_CMD(ConUnpauseGame) +static bool ConUnpauseGame(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Unpause a network game. Usage: 'unpause'."); return true; } @@ -848,16 +861,16 @@ DEF_CONSOLE_CMD(ConUnpauseGame) return true; } -DEF_CONSOLE_CMD(ConRcon) +static bool ConRcon(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Remote control the server from another client. Usage: 'rcon '."); IConsolePrint(CC_HELP, "Remember to enclose the command in quotes, otherwise only the first parameter is sent."); IConsolePrint(CC_HELP, "When your client's public key is in the 'authorized keys' for 'rcon', the password is not checked and may be '*'."); return true; } - if (argc < 3) return false; + if (argv.size() < 3) return false; if (_network_server) { IConsoleCmdExec(argv[2]); @@ -867,9 +880,9 @@ DEF_CONSOLE_CMD(ConRcon) return true; } -DEF_CONSOLE_CMD(ConStatus) +static bool ConStatus(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "List the status of all clients connected to the server. Usage 'status'."); return true; } @@ -878,9 +891,9 @@ DEF_CONSOLE_CMD(ConStatus) return true; } -DEF_CONSOLE_CMD(ConServerInfo) +static bool ConServerInfo(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "List current and maximum client/company limits. Usage 'server_info'."); IConsolePrint(CC_HELP, "You can change these values by modifying settings 'network.max_clients' and 'network.max_companies'."); return true; @@ -894,49 +907,63 @@ DEF_CONSOLE_CMD(ConServerInfo) return true; } -DEF_CONSOLE_CMD(ConClientNickChange) +static bool ConClientNickChange(std::span argv) { - if (argc != 3) { + if (argv.size() != 3) { IConsolePrint(CC_HELP, "Change the nickname of a connected client. Usage: 'client_name '."); IConsolePrint(CC_HELP, "For client-id's, see the command 'clients'."); return true; } - ClientID client_id = (ClientID)atoi(argv[1]); + auto client_id = ParseType(argv[1]); + if (!client_id.has_value()) { + IConsolePrint(CC_ERROR, "The given client-id is not a valid number."); + return true; + } - if (client_id == CLIENT_ID_SERVER) { + if (*client_id == CLIENT_ID_SERVER) { IConsolePrint(CC_ERROR, "Please use the command 'name' to change your own name!"); return true; } - if (NetworkClientInfo::GetByClientID(client_id) == nullptr) { - IConsolePrint(CC_ERROR, "Invalid client ID."); + if (NetworkClientInfo::GetByClientID(*client_id) == nullptr) { + IConsolePrint(CC_ERROR, "Invalid client-id."); return true; } - std::string client_name(argv[2]); - StrTrimInPlace(client_name); + std::string client_name{StrTrimView(argv[2], StringConsumer::WHITESPACE_NO_NEWLINE)}; if (!NetworkIsValidClientName(client_name)) { IConsolePrint(CC_ERROR, "Cannot give a client an empty name."); return true; } - if (!NetworkServerChangeClientName(client_id, client_name)) { + if (!NetworkServerChangeClientName(*client_id, client_name)) { IConsolePrint(CC_ERROR, "Cannot give a client a duplicate name."); } return true; } -DEF_CONSOLE_CMD(ConJoinCompany) +static std::optional ParseCompanyID(std::string_view arg) { - if (argc < 2) { + auto company_id = ParseType(arg); + if (company_id.has_value() && *company_id <= MAX_COMPANIES) return static_cast(*company_id - 1); + return company_id; +} + +static bool ConJoinCompany(std::span argv) +{ + if (argv.size() < 2) { IConsolePrint(CC_HELP, "Request joining another company. Usage: 'join '."); IConsolePrint(CC_HELP, "For valid company-id see company list, use 255 for spectator."); return true; } - CompanyID company_id = (CompanyID)(atoi(argv[1]) <= MAX_COMPANIES ? atoi(argv[1]) - 1 : atoi(argv[1])); + auto company_id = ParseCompanyID(argv[1]); + if (!company_id.has_value()) { + IConsolePrint(CC_ERROR, "The given company-id is not a valid number."); + return true; + } const NetworkClientInfo *info = NetworkClientInfo::GetByClientID(_network_own_client_id); if (info == nullptr) { @@ -945,46 +972,56 @@ DEF_CONSOLE_CMD(ConJoinCompany) } /* Check we have a valid company id! */ - if (!Company::IsValidID(company_id) && company_id != COMPANY_SPECTATOR) { + if (!Company::IsValidID(*company_id) && *company_id != COMPANY_SPECTATOR) { IConsolePrint(CC_ERROR, "Company does not exist. Company-id must be between 1 and {}.", MAX_COMPANIES); return true; } - if (info->client_playas == company_id) { + if (info->client_playas == *company_id) { IConsolePrint(CC_ERROR, "You are already there!"); return true; } - if (company_id != COMPANY_SPECTATOR && !Company::IsHumanID(company_id)) { + if (*company_id != COMPANY_SPECTATOR && !Company::IsHumanID(*company_id)) { IConsolePrint(CC_ERROR, "Cannot join AI company."); return true; } - if (!info->CanJoinCompany(company_id)) { + if (!info->CanJoinCompany(*company_id)) { IConsolePrint(CC_ERROR, "You are not allowed to join this company."); return true; } /* non-dedicated server may just do the move! */ if (_network_server) { - NetworkServerDoMove(CLIENT_ID_SERVER, company_id); + NetworkServerDoMove(CLIENT_ID_SERVER, *company_id); } else { - NetworkClientRequestMove(company_id); + NetworkClientRequestMove(*company_id); } return true; } -DEF_CONSOLE_CMD(ConMoveClient) +static bool ConMoveClient(std::span argv) { - if (argc < 3) { + if (argv.size() < 3) { IConsolePrint(CC_HELP, "Move a client to another company. Usage: 'move '."); IConsolePrint(CC_HELP, "For valid client-id see 'clients', for valid company-id see 'companies', use 255 for moving to spectators."); return true; } - const NetworkClientInfo *ci = NetworkClientInfo::GetByClientID((ClientID)atoi(argv[1])); - CompanyID company_id = (CompanyID)(atoi(argv[2]) <= MAX_COMPANIES ? atoi(argv[2]) - 1 : atoi(argv[2])); + auto client_id = ParseType(argv[1]); + if (!client_id.has_value()) { + IConsolePrint(CC_ERROR, "The given client-id is not a valid number."); + return true; + } + const NetworkClientInfo *ci = NetworkClientInfo::GetByClientID(*client_id); + + auto company_id = ParseCompanyID(argv[2]); + if (!company_id.has_value()) { + IConsolePrint(CC_ERROR, "The given company-id is not a valid number."); + return true; + } /* check the client exists */ if (ci == nullptr) { @@ -992,12 +1029,12 @@ DEF_CONSOLE_CMD(ConMoveClient) return true; } - if (!Company::IsValidID(company_id) && company_id != COMPANY_SPECTATOR) { + if (!Company::IsValidID(*company_id) && *company_id != COMPANY_SPECTATOR) { IConsolePrint(CC_ERROR, "Company does not exist. Company-id must be between 1 and {}.", MAX_COMPANIES); return true; } - if (company_id != COMPANY_SPECTATOR && !Company::IsHumanID(company_id)) { + if (*company_id != COMPANY_SPECTATOR && !Company::IsHumanID(*company_id)) { IConsolePrint(CC_ERROR, "You cannot move clients to AI companies."); return true; } @@ -1007,61 +1044,65 @@ DEF_CONSOLE_CMD(ConMoveClient) return true; } - if (ci->client_playas == company_id) { + if (ci->client_playas == *company_id) { IConsolePrint(CC_ERROR, "You cannot move someone to where they already are!"); return true; } /* we are the server, so force the update */ - NetworkServerDoMove(ci->client_id, company_id); + NetworkServerDoMove(ci->client_id, *company_id); return true; } -DEF_CONSOLE_CMD(ConResetCompany) +static bool ConResetCompany(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Remove an idle company from the game. Usage: 'reset_company '."); IConsolePrint(CC_HELP, "For company-id's, see the list of companies from the dropdown menu. Company 1 is 1, etc."); return true; } - if (argc != 2) return false; + if (argv.size() != 2) return false; - CompanyID index = (CompanyID)(atoi(argv[1]) - 1); - - /* Check valid range */ - if (!Company::IsValidID(index)) { - IConsolePrint(CC_ERROR, "Company does not exist. Company-id must be between 1 and {}.", MAX_COMPANIES); + auto index = ParseCompanyID(argv[1]); + if (!index.has_value()) { + IConsolePrint(CC_ERROR, "The given company-id is not a valid number."); return true; } - if (!Company::IsHumanID(index)) { + /* Check valid range */ + if (!Company::IsValidID(*index)) { + IConsolePrint(CC_ERROR, "Company does not exist. company-id must be between 1 and {}.", MAX_COMPANIES); + return true; + } + + if (!Company::IsHumanID(*index)) { IConsolePrint(CC_ERROR, "Company is owned by an AI."); return true; } - if (NetworkCompanyHasClients(index)) { + if (NetworkCompanyHasClients(*index)) { IConsolePrint(CC_ERROR, "Cannot remove company: a client is connected to that company."); return false; } const NetworkClientInfo *ci = NetworkClientInfo::GetByClientID(CLIENT_ID_SERVER); assert(ci != nullptr); - if (ci->client_playas == index) { + if (ci->client_playas == *index) { IConsolePrint(CC_ERROR, "Cannot remove company: the server is connected to that company."); return true; } /* It is safe to remove this company */ - Command::Post(CCA_DELETE, index, CRR_MANUAL, INVALID_CLIENT_ID); + Command::Post(CCA_DELETE, *index, CRR_MANUAL, INVALID_CLIENT_ID); IConsolePrint(CC_DEFAULT, "Company deleted."); return true; } -DEF_CONSOLE_CMD(ConNetworkClients) +static bool ConNetworkClients(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Get a list of connected clients including their ID, name, company-id, and IP. Usage: 'clients'."); return true; } @@ -1071,24 +1112,24 @@ DEF_CONSOLE_CMD(ConNetworkClients) return true; } -DEF_CONSOLE_CMD(ConNetworkReconnect) +static bool ConNetworkReconnect(std::span argv) { - if (argc == 0) { - IConsolePrint(CC_HELP, "Reconnect to server to which you were connected last time. Usage: 'reconnect []'."); - IConsolePrint(CC_HELP, "Company 255 is spectator (default, if not specified), 0 means creating new company."); + if (argv.empty()) { + IConsolePrint(CC_HELP, "Reconnect to server to which you were connected last time. Usage: 'reconnect []'."); + IConsolePrint(CC_HELP, "Company 255 is spectator (default, if not specified), 254 means creating new company."); IConsolePrint(CC_HELP, "All others are a certain company with Company 1 being #1."); return true; } - CompanyID playas = (argc >= 2) ? (CompanyID)atoi(argv[1]) : COMPANY_SPECTATOR; - switch (playas.base()) { - case 0: playas = COMPANY_NEW_COMPANY; break; - case COMPANY_SPECTATOR.base(): /* nothing to do */ break; - default: - /* From a user pov 0 is a new company, internally it's different and all - * companies are offset by one to ease up on users (eg companies 1-8 not 0-7) */ - if (playas < CompanyID::Begin() + 1 || playas > MAX_COMPANIES + 1) return false; - break; + CompanyID playas = COMPANY_SPECTATOR; + if (argv.size() >= 2) { + auto company_id = ParseCompanyID(argv[1]); + if (!company_id.has_value()) { + IConsolePrint(CC_ERROR, "The given company-id is not a valid number."); + return true; + } + if (*company_id >= MAX_COMPANIES && *company_id != COMPANY_NEW_COMPANY && *company_id != COMPANY_SPECTATOR) return false; + playas = *company_id; } if (_settings_client.network.last_joined.empty()) { @@ -1102,16 +1143,16 @@ DEF_CONSOLE_CMD(ConNetworkReconnect) return NetworkClientConnectGame(_settings_client.network.last_joined, playas); } -DEF_CONSOLE_CMD(ConNetworkConnect) +static bool ConNetworkConnect(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Connect to a remote OTTD server and join the game. Usage: 'connect '."); IConsolePrint(CC_HELP, "IP can contain port and company: 'IP[:Port][#Company]', eg: 'server.ottd.org:443#2'."); IConsolePrint(CC_HELP, "Company #255 is spectator all others are a certain company with Company 1 being #1."); return true; } - if (argc < 2) return false; + if (argv.size() < 2) return false; return NetworkClientConnectGame(argv[1], COMPANY_NEW_COMPANY); } @@ -1120,19 +1161,20 @@ DEF_CONSOLE_CMD(ConNetworkConnect) * script file console commands *********************************/ -DEF_CONSOLE_CMD(ConExec) +static bool ConExec(std::span argv) { - if (argc == 0) { - IConsolePrint(CC_HELP, "Execute a local script file. Usage: 'exec