From feec973e10d565b5dd994c3c55e2d5351f807dc8 Mon Sep 17 00:00:00 2001 From: captainurist <73941350+captainurist@users.noreply.github.com> Date: Sat, 16 Nov 2024 22:07:55 +0000 Subject: [PATCH 1/2] Write out game config nicely --- src/Application/GameConfig.cpp | 5 +- src/Application/GameConfig.h | 448 +++++++++++++----------- src/Application/Startup/GameStarter.cpp | 2 +- src/Library/Config/Config.cpp | 28 +- src/Utility/CMakeLists.txt | 9 +- src/Utility/String/Ascii.h | 4 + src/Utility/String/Tests/Wrap_ut.cpp | 132 +++++++ src/Utility/String/Wrap.cpp | 46 +++ src/Utility/String/Wrap.h | 6 + 9 files changed, 458 insertions(+), 222 deletions(-) create mode 100644 src/Utility/String/Tests/Wrap_ut.cpp create mode 100644 src/Utility/String/Wrap.cpp create mode 100644 src/Utility/String/Wrap.h diff --git a/src/Application/GameConfig.cpp b/src/Application/GameConfig.cpp index b9401204cda3..54e6fd81fd3e 100644 --- a/src/Application/GameConfig.cpp +++ b/src/Application/GameConfig.cpp @@ -72,7 +72,6 @@ GameConfig::CheatCommands::CheatCommands(GameConfig *config) : ConfigSection(con void GameConfig::CheatCommands::_addCommand(int commandIndex, const std::string& defaultValue) { std::string name = fmt::format("command{:02}", commandIndex + 1); - auto item = std::make_unique<String>(this, name, defaultValue, - "Cheat Command. Example: 'xp add 1000|Give 1000 xp to current Character'"); - CommandList.push_back(std::move(item)); + auto item = std::make_unique<String>(this, name, defaultValue, fmt::format("Cheat command #{}.", commandIndex + 1)); + _commandList.push_back(std::move(item)); } diff --git a/src/Application/GameConfig.h b/src/Application/GameConfig.h index cfde91635bf1..9c1d25cad2e5 100644 --- a/src/Application/GameConfig.h +++ b/src/Application/GameConfig.h @@ -44,72 +44,91 @@ class GameConfig : public Config { Bool DisableHRTF = {this, "disable_hrtf", true, "Disable HRTF for headphones."}; }; - Audio audio{ this }; + Audio audio{this}; class Debug : public ConfigSection { public: explicit Debug(GameConfig *config) : ConfigSection(config, "debug") {} Bool AllMagic = {this, "all_magic", false, - "Enable all available spells for each character in spellbook bypassing all class restrictions. " - "Currently also all skills will behave like they are on GM level."}; + "Enable all available spells for each character in spellbook bypassing all class restrictions. " + "Currently also all skills will behave like they are on GM level."}; - Bool InfiniteFood = {this, "infinite_food", false, "Enable unlimited food, using food won't spend it."}; - Bool InfiniteGold = {this, "infinite_gold", false, "Enable unlimited gold, paying in shops won't spend it."}; + Bool InfiniteFood = {this, "infinite_food", false, + "Enable unlimited food, using food won't spend it."}; - Bool LightmapDecals = {this, "lightmap_decals", false, "Draw lightmap and decals outlines."}; + Bool InfiniteGold = {this, "infinite_gold", false, + "Enable unlimited gold, paying in shops won't spend it."}; - Bool PortalOutlines = {this, "portal_outlines", false, "Draw BLV portal outlines."}; - Bool Terrain = {this, "terrain", false, "Draw terrain as wireframe."}; + Bool LightmapDecals = {this, "lightmap_decals", false, + "Draw lightmap and decals outlines."}; + + Bool PortalOutlines = {this, "portal_outlines", false, + "Draw BLV portal outlines."}; + + Bool Terrain = {this, "terrain", false, + "Draw terrain as wireframe."}; Bool TownPortal = {this, "town_portal", false, - "Make all game locations reachable via town portal spell without requiring to visit them first."}; + "Make all game locations reachable via town portal spell without requiring to visit them first."}; Bool TurboSpeed = {this, "turbo_speed", false, - "Increase party movement speed by 12x. Most likely you want to use that option with " - "no_damage option enabled as collision physics often will shoot you in the air."}; + "Increase party movement speed by 12x. Most likely you want to use that option with " + "no_damage option enabled as collision physics often will shoot you in the air."}; - Bool WizardEye = {this, "wizard_eye", false, "Activate wizard eye spell that never expires."}; + Bool WizardEye = {this, "wizard_eye", false, + "Activate wizard eye spell that never expires."}; - Bool ShowFPS = {this, "show_fps", false, "Show debug HUD with FPS and other debug information."}; + Bool ShowFPS = {this, "show_fps", false, + "Show debug HUD with FPS and other debug information."}; Bool ShowPickedFace = {this, "show_picked_face", false, - "Face pointed with mouse will flash with red for buildings or green for dungeons."}; + "Face pointed with mouse will flash with red for buildings or green for dungeons."}; - Bool NoIntro = {this, "no_intro", false, "Skip intro movie on startup."}; + Bool NoIntro = {this, "no_intro", false, + "Skip intro movie on startup."}; - Bool NoLogo = {this, "no_logo", false, "Skip 3do logo on startup."}; + Bool NoLogo = {this, "no_logo", false, + "Skip 3do logo on startup."}; // TODO(captainurist): Move to [audio]? - Bool NoSound = {this, "no_sound", false, "Don't play any sounds. Currently in-house movies are not affected."}; + Bool NoSound = {this, "no_sound", false, + "Don't play any sounds. Currently in-house movies are not affected."}; - Bool NoVideo = {this, "no_video", false, "Don't play any movies."}; + Bool NoVideo = {this, "no_video", false, + "Don't play any movies."}; - Bool NoActors = {this, "no_actors", false, "Disable all actors."}; + Bool NoActors = {this, "no_actors", false, + "Disable all actors."}; - Bool NoDamage = {this, "no_damage", false, "Disable all incoming damage to party."}; + Bool NoDamage = {this, "no_damage", false, + "Disable all incoming damage to party."}; - Bool NoDecorations = {this, "no_decorations", false, "Disable all decorations."}; + Bool NoDecorations = {this, "no_decorations", false, + "Disable all decorations."}; - Bool NoMargaret = {this, "no_margareth", false, "Disable Margaret's tour messages on Emerald Island."}; + Bool NoMargaret = {this, "no_margareth", false, + "Disable Margaret's tour messages on Emerald Island."}; ConfigEntry<::LogLevel> LogLevel = {this, "log_level", LOG_INFO, - "Default log level. One of 'none', 'trace', 'debug', 'info', 'warning', 'error' and 'critical'."}; + "Default log level. One of 'none', 'trace', 'debug', 'info', 'warning', 'error' and 'critical'."}; // TODO(captainurist): move all Trace* options into a separate section. Int TraceFrameTimeMs = {this, "trace_frame_time_ms", 100, &ValidateFrameTime, - "Number of milliseconds per frame when recording game traces."}; + "Number of milliseconds per frame when recording game traces."}; ConfigEntry<RandomEngineType> TraceRandomEngine = {this, "trace_random_engine", RANDOM_ENGINE_MERSENNE_TWISTER, - "Random engine to use for trace recording, 'sequential' or 'mersenne_twister'."}; + "Random engine to use for trace recording, 'sequential' or 'mersenne_twister'."}; - Bool TraceNoVideo = {this, "trace_no_video", true, "Don't play movies when recording traces."}; + Bool TraceNoVideo = {this, "trace_no_video", true, + "Don't play movies when recording traces."}; Bool TraceNoPartyActorCollisions = {this, "trace_no_party_actor_collisions", false, - "Disable collisions between the party and monsters on the map when recording traces."}; + "Disable collisions between the party and monsters on the map when recording traces."}; - Bool FullMonsterID = { this, "full_monster_id", false, "Full monster info on popup." }; + Bool FullMonsterID = {this, "full_monster_id", false, + "Full monster info on popup."}; private: static int ValidateFrameTime(int frameTime) { @@ -117,118 +136,130 @@ class GameConfig : public Config { } }; - Debug debug{ this }; + Debug debug{this}; class Gameplay : public ConfigSection { public: explicit Gameplay(GameConfig *config): ConfigSection(config, "gameplay") {} Bool AlternativeConditionPriorities = {this, "alternative_condition_priorities", true, - "Use condition priorities from Grayface patches (e.g. Zombie has the lowest priority)."}; + "Use condition priorities from Grayface patches (e.g. Zombie has the lowest priority)."}; Int ArtifactLimit = {this, "artifact_limit", 13, &ValidateArtifactLimit, - "Max number of artifacts that the game can generate as loot in any one playthrough. Use 0 for unlimited."}; + "Max number of artifacts that the game can generate as loot in any one playthrough. Use 0 for unlimited."}; Int ChestTryPlaceItems = {this, "chest_try_place_items", 2, - "Fix problems with item loss in high-level chests. " - "Use 0 for vanilla behaviour, items that don't fit will be lost. " - "Use 1 to try to place items that didn't fit every time the chest is opened again. " - "Use 2 to try to place items that didn't fit every time an item is picked up from the chest."}; + "Fix problems with item loss in high-level chests. " + "Use 0 for vanilla behaviour, items that don't fit will be lost. " + "Use 1 to try to place items that didn't fit every time the chest is opened again. " + "Use 2 to try to place items that didn't fit every time an item is picked up from the chest."}; Int FloorChecksEps = {this, "floor_checks_eps", 3, &ValidateFloorChecksEps, // TODO(pskelton): Move to debug - "Maximum allowed slack for point-inside-a-polygon checks when calculating floor z level. " - "This is needed because there are actual holes in level geometry sometimes, up to several units wide."}; + "Maximum allowed slack for point-inside-a-polygon checks when calculating floor z level. " + "This is needed because there are actual holes in level geometry sometimes, up to several units wide."}; - Int Gravity = {this, "gravity", 5, "Gravity strength, the higher the more gravity, 0 disables gravity completely."}; + Int Gravity = {this, "gravity", 5, + "Gravity strength, the higher the more gravity, 0 disables gravity completely."}; Int KeyboardInteractionDepth = {this, "keyboard_interaction_depth", 512, &ValidateInteractionDepth, - "Maximum range for item pickup / opening chests / activating levers / etc " - "with a keyboard (by pressing the interaction key, see keybindings.event_trigger)."}; + "Maximum range for item pickup / opening chests / activating levers / etc " + "with a keyboard (by pressing the interaction key, see keybindings.event_trigger)."}; Int MouseInteractionDepth = {this, "mouse_interaction_depth", 512, &ValidateInteractionDepth, - "Maximum range for item pickup / opening chests / activating levers / etc with a mouse."}; + "Maximum range for item pickup / opening chests / activating levers / etc with a mouse."}; Int MinRecoveryMelee = {this, "minimum_recovery_melee", 30, &ValidateRecovery, - "Minimum recovery time for melee weapons. Was 30 in vanilla."}; + "Minimum recovery time for melee weapons. Was 30 in vanilla."}; Int MinRecoveryRanged = {this, "minimum_recovery_ranged", 5, &ValidateRecovery, - "Minimum recovery time for ranged weapons. Was 0 in vanilla, 5 in GrayFace patches."}; + "Minimum recovery time for ranged weapons. Was 0 in vanilla, 5 in GrayFace patches."}; Int MinRecoveryBlasters = {this, "minimum_recovery_blasters", 5, &ValidateRecovery, - "Minimum recovery time for blasters. Was 0 in vanilla, 5 in Grayface patches"}; + "Minimum recovery time for blasters. Was 0 in vanilla, 5 in Grayface patches."}; Int MaxFlightHeight = {this, "max_flight_height", 4000, &ValidateMaxFlightHeight, - "Maximum height for the fly spell."}; + "Maximum height for the fly spell."}; Int MouseInfoDepthIndoor = {this, "mouse_info_depth_indoor", 16192, &ValidateInteractionDepth, - "Maximum range at which right clicking on a monster produces a popup indoors. " - "Also this is the max range for the souldrinker spell indoors."}; + "Maximum range at which right clicking on a monster produces a popup indoors. " + "Also this is the max range for the souldrinker spell indoors."}; + Int MouseInfoDepthOutdoor = {this, "mouse_info_depth_outdoor", 12800, &ValidateInteractionDepth, - "Maximum range at which right clicking on a monster produces a popup outdoors. " - "Default value is 12800 = 25 * 512, 25 map cells. " - "Also this is the max range for the souldrinker spell outdoors."}; + "Maximum range at which right clicking on a monster produces a popup outdoors. " + "Default value is 12800 = 25 * 512, 25 map cells. " + "Also this is the max range for the souldrinker spell outdoors."}; + + Int NewGameFood = {this, "new_game_food", 7, + "Starting food."}; + + Int NewGameGold = {this, "new_game_gold", 200, + "Starting gold."}; - Int NewGameFood = {this, "new_game_food", 7, "Starting food."}; - Int NewGameGold = {this, "new_game_gold", 200, "Starting gold."}; - String StartingMap = String(this, "starting_map", "out01.odm", "New Game starting map."); + String StartingMap = String(this, "starting_map", "out01.odm", + "New Game starting map."); - Int PartyEyeLevel = {this, "party_eye_level", 160, "Party eye level."}; - Int PartyHeight = {this, "party_height", 192, "Party height."}; - Int PartyWalkSpeed = {this, "party_walk_speed", 384, "Party walk speed."}; + Int PartyEyeLevel = {this, "party_eye_level", 160, + "Party eye level."}; + + Int PartyHeight = {this, "party_height", 192, + "Party height."}; + + Int PartyWalkSpeed = {this, "party_walk_speed", 384, + "Party walk speed."}; Int RangedAttackDepth = {this, "ranged_attack_depth", 5120, &ValidateRangedAttackDepth, - "Max depth for ranged attacks and ranged spells. " - "It's impossible to target monsters that are further away than this value. " - "This is also the depth at which status bar tips are displayed on mouse over."}; + "Max depth for ranged attacks and ranged spells. " + "It's impossible to target monsters that are further away than this value. " + "This is also the depth at which status bar tips are displayed on mouse over."}; Int AoeDamageDistance = {this, "aoe_damage_distance", 512, &ValidateAoeDistance, - "Distance from point of impact of harmful AOE spell. " - "Characters and monsters will suffer damage if they are close to point of impact by this value."}; + "Distance from point of impact of harmful AOE spell. " + "Characters and monsters will suffer damage if they are close to point of impact by this value."}; Int ShrinkRayAoeDistance = {this, "shrink_ray_aoe_distance", 256, &ValidateAoeDistance, - "Distance from point of impact of Shrinking Ray cast at GM mastery. " - "Monsters will be affected by this spell if they are close to point of impact by this value."}; + "Distance from point of impact of Shrinking Ray cast at GM mastery. " + "Monsters will be affected by this spell if they are close to point of impact by this value."}; Bool ShowUndentifiedItem = {this, "show_unidentified_item", true, - "Show unidentified items with a green tint in inventory. " - "If not set, vanilla behavior will be used with green tint applied in shops only."}; + "Show unidentified items with a green tint in inventory. " + "If not set, vanilla behavior will be used with green tint applied in shops only."}; Bool TreatClubAsMace = {this, "treat_club_as_mace", false, - "Treat clubs as maces. " - "In vanilla clubs are using a separate hidden skill and can be equipped without learning the mace skill."}; + "Treat clubs as maces. " + "In vanilla clubs are using a separate hidden skill and can be equipped without learning the mace skill."}; Bool FixWaterWalkManaDrain = {this, "fix_water_walk_mana_drain", true, - "Change water walk mana drain interval to 20 minutes. " - "Spell description says that mana is drained every 20 minutes, but in vanilla, it was every 5 minutes."}; + "Change water walk mana drain interval to 20 minutes. " + "Spell description says that mana is drained every 20 minutes, but in vanilla, it was every 5 minutes."}; Float SpellFailureRecoveryMod = {this, "spell_failure_recovery_mod", 0.5f, &ValidateSpellFailureRecoveryMod, - "Recovery time modifier when spell casting ended in failure for the reason where spell cannot be cast at all in current context. " - "Context include situation where outdoor spell is casted indoor or targeted spell is casted with no characters on screen."}; + "Recovery time modifier when spell casting ended in failure for the reason where spell cannot be cast at all in current context. " + "Context include situation where outdoor spell is casted indoor or targeted spell is casted with no characters on screen."}; - String QuickSaveName = { this, "quick_saves_name", "quicksave", - "What name to use to store the quick saves as." }; + String QuickSaveName = {this, "quick_saves_name", "quicksave", + "What name to use to store the quick saves as."}; - Int QuickSavesCount = { this, "quick_saves_count", 4, &ValidateQuickSaveCount, - "How many quick saves have currently been used." - "This will rotate back to 0 when 5 saves has been reached" }; + Int QuickSavesCount = {this, "quick_saves_count", 4, &ValidateQuickSaveCount, + "How many quick saves have currently been used." + "This will rotate back to 0 when 5 saves has been reached."}; Bool NoPartyActorCollisions = {this, "no_party_actor_collisions", false, // TODO(pskelton): Move to debug - "Disable collisions between the party and monsters on the map. Mainly useful for debugging and tests."}; + "Disable collisions between the party and monsters on the map. Mainly useful for debugging and tests."}; - Bool NoIndoorFallDamage = { this, "no_indoor_fall_damage", false, - "Disable fall damage for indoor maps." }; + Bool NoIndoorFallDamage = {this, "no_indoor_fall_damage", false, + "Disable fall damage for indoor maps."}; - Float SpawnCountMultiplier = { this, "spawn_count_multiplier", 1.0f, - "Multiplication factor for how many enemies are spawned over original." }; + Float SpawnCountMultiplier = {this, "spawn_count_multiplier", 1.0f, + "Multiplication factor for how many enemies are spawned over original."}; - Int MaxActors = { this, "max_actors", 500, &ValidateMaxActors, - "Limit to how many total actors are possible on a map." }; + Int MaxActors = {this, "max_actors", 500, &ValidateMaxActors, + "Limit to how many total actors are possible on a map."}; - Int MaxActiveAIActors = { this, "max_active_ai_actors", 30, &ValidateMaxActiveAIActors, - "Limit to how many actors can be in full AI state at once." }; + Int MaxActiveAIActors = {this, "max_active_ai_actors", 30, &ValidateMaxActiveAIActors, + "Limit to how many actors can be in full AI state at once."}; - Bool RegenStacking = { this, "regen_stacking", true, - "Disable for vanilla like mode where only one item will trigger HP/SP regeneration." }; + Bool RegenStacking = {this, "regen_stacking", true, + "Disable for vanilla like mode where only one item will trigger HP/SP regeneration."}; private: static int ValidateMaxFlightHeight(int max_flight_height) { @@ -275,7 +306,7 @@ class GameConfig : public Config { } }; - Gameplay gameplay{ this }; + Gameplay gameplay{this }; class Gamepad : public ConfigSection { public: @@ -300,9 +331,9 @@ class GameConfig : public Config { Key LookDown = {this, "look_down", PlatformKey::KEY_GAMEPAD_RIGHTSTICK_DOWN, "Look down key."}; Key LookUp = {this, "look_up", PlatformKey::KEY_GAMEPAD_RIGHTSTICK_UP, "Look up key."}; Key MapBook = {this, "map_book", PlatformKey::KEY_GAMEPAD_LEFT, "Open map key."}; - Key Pass = {this, "pass", PlatformKey::KEY_GAMEPAD_GUIDE, "Pass turn key"}; - Key Quest = {this, "quest", PlatformKey::KEY_GAMEPAD_RIGHT, "Open quest book key"}; - Key QuickReference = {this, "quick_reference", PlatformKey::KEY_NONE, "Open quick reference menu"}; + Key Pass = {this, "pass", PlatformKey::KEY_GAMEPAD_GUIDE, "Pass turn key."}; + Key Quest = {this, "quest", PlatformKey::KEY_GAMEPAD_RIGHT, "Open quest book key."}; + Key QuickReference = {this, "quick_reference", PlatformKey::KEY_NONE, "Open quick reference menu key."}; Key Rest = {this, "rest", PlatformKey::KEY_GAMEPAD_BACK, "Rest key."}; Key Right = {this, "right", PlatformKey::KEY_GAMEPAD_RIGHTSTICK_RIGHT, "Turn right key."}; Key StepLeft = {this, "step_left", PlatformKey::KEY_GAMEPAD_LEFTSTICK_LEFT, "Strafe left key."}; @@ -311,54 +342,54 @@ class GameConfig : public Config { Key Yell = {this, "yell", PlatformKey::KEY_GAMEPAD_X, "Yell key."}; Key ZoomIn = {this, "zoom_in", PlatformKey::KEY_NONE, "Zoom in automap key."}; Key ZoomOut = {this, "zoom_out", PlatformKey::KEY_NONE, "Zoom out automap key."}; - Key QuickSave = {this, "quick_save", PlatformKey::KEY_NONE, "Quick save key"}; - Key QuickLoad = {this, "quick_load", PlatformKey::KEY_NONE, "Quick load key"}; - Key History = {this, "history", PlatformKey::KEY_NONE, "History book key"}; - Key Stats = {this, "stats", PlatformKey::KEY_GAMEPAD_A, "Stats tab key"}; - Key Skills = {this, "skills", PlatformKey::KEY_GAMEPAD_X, "Skills tab key"}; - Key Inventory = {this, "inventory", PlatformKey::KEY_GAMEPAD_Y, "Inventory tab key"}; - Key Awards = {this, "awards", PlatformKey::KEY_GAMEPAD_L1, "Stats tab key"}; - Key NewGame = {this, "new_game", PlatformKey::KEY_GAMEPAD_A, "New Game menu key"}; - Key SaveGame = {this, "save_game", PlatformKey::KEY_GAMEPAD_Y, "Save Game menu key"}; - Key LoadGame = {this, "load_game", PlatformKey::KEY_GAMEPAD_X, "Load Game menu key"}; - Key ExitGame = {this, "exit_game", PlatformKey::KEY_GAMEPAD_B, "Exit Game key"}; - Key ReturnToGame = {this, "return_to_game", PlatformKey::KEY_GAMEPAD_B, "Return to Game mode key"}; - Key Controls = {this, "controls", PlatformKey::KEY_GAMEPAD_L1, "Controls change menu key"}; - Key Options = {this, "options", PlatformKey::KEY_GAMEPAD_R1, "Options menu key"}; - Key Credits = {this, "credits", PlatformKey::KEY_GAMEPAD_Y, "Credits menu key"}; - Key Clear = {this, "clear", PlatformKey::KEY_GAMEPAD_Y, "Clear button in New Party Creation menu"}; - Key Return = {this, "return", PlatformKey::KEY_GAMEPAD_A, "Ok button in New Party Creation menu"}; - Key Minus = {this, "minus", PlatformKey::KEY_GAMEPAD_L1, "Minus button in New Party Creation menu"}; - Key Plus = {this, "plus", PlatformKey::KEY_GAMEPAD_R1, "Plus button in New Party Creation menu"}; - Key Yes = {this, "yes", PlatformKey::KEY_GAMEPAD_A, "Yes answer key"}; - Key No = {this, "no", PlatformKey::KEY_GAMEPAD_B, "No answer key"}; - Key Rest8Hours = {this, "rest_8_hours", PlatformKey::KEY_GAMEPAD_BACK, "Rest for 8 hours key in Rest menu"}; - Key WaitTillDawn = {this, "wait_till_dawn", PlatformKey::KEY_GAMEPAD_Y, "Wait till dawn key in Rest menu"}; - Key WaitHour = {this, "wait_hour", PlatformKey::KEY_GAMEPAD_X, "Wait hour in Rest menu"}; - Key Wait5Minutes = {this, "wait_5_minutes", PlatformKey::KEY_GAMEPAD_A, "Wait 5 minutes in Rest menu"}; - Key Screenshot = {this, "screenshot", PlatformKey::KEY_NONE, "Make screenshot key"}; - Key Console = {this, "console", PlatformKey::KEY_NONE, "Show/Hide overlays"}; - Key ToggleMouseGrab = {this, "toggle_mouse_grab", PlatformKey::KEY_NONE, "Toggle mouse grab key"}; - Key ToggleBorderless = {this, "toggle_borderless", PlatformKey::KEY_NONE, "Toggle window borderless key"}; - Key ToggleFullscreen = {this, "toggle_fullscreen", PlatformKey::KEY_NONE, "Toggle window fullscreen key"}; - Key ToggleResizable = {this, "toggle_resizable", PlatformKey::KEY_NONE, "Toggle window resizable key"}; - Key CycleFilter = {this, "cycle_filter", PlatformKey::KEY_NONE, "Cycle rescale filter modes key"}; - Key ReloadShaders = {this, "reload_shaders", PlatformKey::KEY_NONE, "Reload shaders key"}; - Key SelectChar1 = {this, "select_char_1", PlatformKey::KEY_NONE, "Select 1 character key"}; - Key SelectChar2 = {this, "select_char_2", PlatformKey::KEY_NONE, "Select 2 character key"}; - Key SelectChar3 = {this, "select_char_3", PlatformKey::KEY_NONE, "Select 3 character key"}; - Key SelectChar4 = {this, "select_char_4", PlatformKey::KEY_NONE, "Select 4 character key"}; - Key SelectNPC1 = {this, "select_npc_1", PlatformKey::KEY_NONE, "Select 1 hireling key"}; - Key SelectNPC2 = {this, "select_npc_2", PlatformKey::KEY_NONE, "Select 2 hireling key"}; - Key DialogUp = {this, "dialog_up", PlatformKey::KEY_GAMEPAD_UP, "Dialog up key"}; - Key DialogDown = {this, "dialog_down", PlatformKey::KEY_GAMEPAD_DOWN, "Dialog down key"}; - Key DialogLeft = {this, "dialog_left", PlatformKey::KEY_GAMEPAD_LEFT, "Dialog left key"}; - Key DialogRight = {this, "dialog_right", PlatformKey::KEY_GAMEPAD_RIGHT, "Dialog right key"}; - Key DialogSelect = {this, "dialog_select", PlatformKey::KEY_GAMEPAD_A, "Dialog select key"}; - Key Escape = {this, "escape", PlatformKey::KEY_GAMEPAD_B, "Escape key"}; + Key QuickSave = {this, "quick_save", PlatformKey::KEY_NONE, "Quick save key."}; + Key QuickLoad = {this, "quick_load", PlatformKey::KEY_NONE, "Quick load key."}; + Key History = {this, "history", PlatformKey::KEY_NONE, "History book key."}; + Key Stats = {this, "stats", PlatformKey::KEY_GAMEPAD_A, "Stats tab key."}; + Key Skills = {this, "skills", PlatformKey::KEY_GAMEPAD_X, "Skills tab key."}; + Key Inventory = {this, "inventory", PlatformKey::KEY_GAMEPAD_Y, "Inventory tab key."}; + Key Awards = {this, "awards", PlatformKey::KEY_GAMEPAD_L1, "Stats tab key."}; + Key NewGame = {this, "new_game", PlatformKey::KEY_GAMEPAD_A, "New Game menu key."}; + Key SaveGame = {this, "save_game", PlatformKey::KEY_GAMEPAD_Y, "Save Game menu key."}; + Key LoadGame = {this, "load_game", PlatformKey::KEY_GAMEPAD_X, "Load Game menu key."}; + Key ExitGame = {this, "exit_game", PlatformKey::KEY_GAMEPAD_B, "Exit Game key."}; + Key ReturnToGame = {this, "return_to_game", PlatformKey::KEY_GAMEPAD_B, "Return to Game mode key."}; + Key Controls = {this, "controls", PlatformKey::KEY_GAMEPAD_L1, "Controls change menu key."}; + Key Options = {this, "options", PlatformKey::KEY_GAMEPAD_R1, "Options menu key."}; + Key Credits = {this, "credits", PlatformKey::KEY_GAMEPAD_Y, "Credits menu key."}; + Key Clear = {this, "clear", PlatformKey::KEY_GAMEPAD_Y, "Clear button in New Party Creation menu."}; + Key Return = {this, "return", PlatformKey::KEY_GAMEPAD_A, "Ok button in New Party Creation menu."}; + Key Minus = {this, "minus", PlatformKey::KEY_GAMEPAD_L1, "Minus button in New Party Creation menu."}; + Key Plus = {this, "plus", PlatformKey::KEY_GAMEPAD_R1, "Plus button in New Party Creation menu."}; + Key Yes = {this, "yes", PlatformKey::KEY_GAMEPAD_A, "Yes answer key."}; + Key No = {this, "no", PlatformKey::KEY_GAMEPAD_B, "No answer key."}; + Key Rest8Hours = {this, "rest_8_hours", PlatformKey::KEY_GAMEPAD_BACK, "Rest for 8 hours key in Rest menu."}; + Key WaitTillDawn = {this, "wait_till_dawn", PlatformKey::KEY_GAMEPAD_Y, "Wait till dawn key in Rest menu."}; + Key WaitHour = {this, "wait_hour", PlatformKey::KEY_GAMEPAD_X, "Wait one hour key in Rest menu."}; + Key Wait5Minutes = {this, "wait_5_minutes", PlatformKey::KEY_GAMEPAD_A, "Wait 5 minutes key in Rest menu."}; + Key Screenshot = {this, "screenshot", PlatformKey::KEY_NONE, "Make screenshot key."}; + Key Console = {this, "console", PlatformKey::KEY_NONE, "Show/Hide overlays key."}; + Key ToggleMouseGrab = {this, "toggle_mouse_grab", PlatformKey::KEY_NONE, "Toggle mouse grab key."}; + Key ToggleBorderless = {this, "toggle_borderless", PlatformKey::KEY_NONE, "Toggle window borderless key."}; + Key ToggleFullscreen = {this, "toggle_fullscreen", PlatformKey::KEY_NONE, "Toggle window fullscreen key."}; + Key ToggleResizable = {this, "toggle_resizable", PlatformKey::KEY_NONE, "Toggle window resizable key."}; + Key CycleFilter = {this, "cycle_filter", PlatformKey::KEY_NONE, "Cycle rescale filter modes key."}; + Key ReloadShaders = {this, "reload_shaders", PlatformKey::KEY_NONE, "Reload shaders key."}; + Key SelectChar1 = {this, "select_char_1", PlatformKey::KEY_NONE, "Select 1 character key."}; + Key SelectChar2 = {this, "select_char_2", PlatformKey::KEY_NONE, "Select 2 character key."}; + Key SelectChar3 = {this, "select_char_3", PlatformKey::KEY_NONE, "Select 3 character key."}; + Key SelectChar4 = {this, "select_char_4", PlatformKey::KEY_NONE, "Select 4 character key."}; + Key SelectNPC1 = {this, "select_npc_1", PlatformKey::KEY_NONE, "Select 1 hireling key."}; + Key SelectNPC2 = {this, "select_npc_2", PlatformKey::KEY_NONE, "Select 2 hireling key."}; + Key DialogUp = {this, "dialog_up", PlatformKey::KEY_GAMEPAD_UP, "Dialog up key."}; + Key DialogDown = {this, "dialog_down", PlatformKey::KEY_GAMEPAD_DOWN, "Dialog down key."}; + Key DialogLeft = {this, "dialog_left", PlatformKey::KEY_GAMEPAD_LEFT, "Dialog left key."}; + Key DialogRight = {this, "dialog_right", PlatformKey::KEY_GAMEPAD_RIGHT, "Dialog right key."}; + Key DialogSelect = {this, "dialog_select", PlatformKey::KEY_GAMEPAD_A, "Dialog select key."}; + Key Escape = {this, "escape", PlatformKey::KEY_GAMEPAD_B, "Escape key."}; }; - Gamepad gamepad{ this }; + Gamepad gamepad{this}; class Graphics : public ConfigSection { public: @@ -427,8 +458,8 @@ class GameConfig : public Config { "Filtering method when scaling rendered framebuffer to window dimensions if they differ." " 0 - disabled (render dimensions will always match window dimensions), 1 - linear filter, 2 - nearest filter"}; - Float Saturation = { this, "saturation", 0.65f, "Colour saturation multiplier for textures and palettes" }; - Float Lightness = { this, "lightness", 1.1f, "Colour lightness multiplier for textures and palettes" }; + Float Saturation = {this, "saturation", 0.65f, "Colour saturation multiplier for textures and palettes"}; + Float Lightness = {this, "lightness", 1.1f, "Colour lightness multiplier for textures and palettes"}; private: static int ValidateGamma(int level) { @@ -460,7 +491,7 @@ class GameConfig : public Config { } }; - Graphics graphics{ this }; + Graphics graphics{this}; class Keybindings : public ConfigSection { public: @@ -485,9 +516,9 @@ class GameConfig : public Config { Key LookDown = {this, "look_down", PlatformKey::KEY_DELETE, "Look down key."}; Key LookUp = {this, "look_up", PlatformKey::KEY_PAGEDOWN, "Look up key."}; Key MapBook = {this, "map_book", PlatformKey::KEY_M, "Open map key."}; - Key Pass = {this, "pass", PlatformKey::KEY_B, "Pass turn key"}; - Key Quest = {this, "quest", PlatformKey::KEY_Q, "Open quest book key"}; - Key QuickReference = {this, "quick_reference", PlatformKey::KEY_Z, "Open quick reference menu"}; + Key Pass = {this, "pass", PlatformKey::KEY_B, "Pass turn key."}; + Key Quest = {this, "quest", PlatformKey::KEY_Q, "Open quest book key."}; + Key QuickReference = {this, "quick_reference", PlatformKey::KEY_Z, "Open quick reference menu key."}; Key Rest = {this, "rest", PlatformKey::KEY_R, "Rest key."}; Key Right = {this, "right", PlatformKey::KEY_RIGHT, "Turn right key."}; Key StepLeft = {this, "step_left", PlatformKey::KEY_LEFTBRACKET, "Strafe left key."}; @@ -496,54 +527,54 @@ class GameConfig : public Config { Key Yell = {this, "yell", PlatformKey::KEY_Y, "Yell key."}; Key ZoomIn = {this, "zoom_in", PlatformKey::KEY_ADD, "Zoom in automap key."}; Key ZoomOut = {this, "zoom_out", PlatformKey::KEY_SUBTRACT, "Zoom out automap key."}; - Key QuickSave = {this, "quick_save", PlatformKey::KEY_F5, "Quick save key"}; - Key QuickLoad = {this, "quick_load", PlatformKey::KEY_F9, "Quick load key"}; - Key History = {this, "history", PlatformKey::KEY_H, "History book key"}; - Key Stats = {this, "stats", PlatformKey::KEY_C, "Stats tab key"}; - Key Skills = {this, "skills", PlatformKey::KEY_S, "Skills tab key"}; - Key Inventory = {this, "inventory", PlatformKey::KEY_I, "Inventory tab key"}; - Key Awards = {this, "awards", PlatformKey::KEY_A, "Stats tab key"}; - Key NewGame = {this, "new_game", PlatformKey::KEY_N, "New Game menu key"}; - Key SaveGame = {this, "save_game", PlatformKey::KEY_S, "Save Game menu key"}; - Key LoadGame = {this, "load_game", PlatformKey::KEY_L, "Load Game menu key"}; - Key ExitGame = {this, "exit_game", PlatformKey::KEY_Q, "Exit Game key"}; - Key ReturnToGame = {this, "return_to_game", PlatformKey::KEY_R, "Return to Game mode key"}; - Key Controls = {this, "controls", PlatformKey::KEY_C, "Controls change menu key"}; - Key Options = {this, "options", PlatformKey::KEY_O, "Options menu key"}; - Key Credits = {this, "credits", PlatformKey::KEY_C, "Credits menu key"}; - Key Clear = {this, "clear", PlatformKey::KEY_C, "Clear button in New Party Creation menu"}; - Key Return = {this, "return", PlatformKey::KEY_RETURN, "Ok button in New Party Creation menu"}; - Key Minus = {this, "minus", PlatformKey::KEY_SUBTRACT, "Minus button in New Party Creation menu"}; - Key Plus = {this, "plus", PlatformKey::KEY_ADD, "Plus button in New Party Creation menu"}; - Key Yes = {this, "yes", PlatformKey::KEY_Y, "Yes answer key"}; - Key No = {this, "no", PlatformKey::KEY_N, "No answer key"}; - Key Rest8Hours = {this, "rest_8_hours", PlatformKey::KEY_R, "Rest for 8 hours key in Rest menu"}; - Key WaitTillDawn = {this, "wait_till_dawn", PlatformKey::KEY_D, "Wait till dawn key in Rest menu"}; - Key WaitHour = {this, "wait_hour", PlatformKey::KEY_H, "Wait hour in Rest menu"}; - Key Wait5Minutes = {this, "wait_5_minutes", PlatformKey::KEY_M, "Wait 5 minutes in Rest menu"}; - Key Screenshot = {this, "screenshot", PlatformKey::KEY_F2, "Make screenshot key"}; - Key Console = {this, "console", PlatformKey::KEY_TILDE, "Show/Hide overlays"}; - Key ToggleMouseGrab = {this, "toggle_mouse_grab", PlatformKey::KEY_F1, "Toggle mouse grab key"}; - Key ToggleBorderless = {this, "toggle_borderless", PlatformKey::KEY_F3, "Toggle window borderless key"}; - Key ToggleFullscreen = {this, "toggle_fullscreen", PlatformKey::KEY_F4, "Toggle window fullscreen key"}; - Key ToggleResizable = {this, "toggle_resizable", PlatformKey::KEY_F6, "Toggle window resizable key"}; - Key CycleFilter = {this, "cycle_filter", PlatformKey::KEY_F7, "Cycle rescale filter modes key"}; - Key ReloadShaders = {this, "reload_shaders", PlatformKey::KEY_BACKSPACE, "Reload shaders key"}; - Key SelectChar1 = {this, "select_char_1", PlatformKey::KEY_DIGIT_1, "Select 1 character key"}; - Key SelectChar2 = {this, "select_char_2", PlatformKey::KEY_DIGIT_2, "Select 2 character key"}; - Key SelectChar3 = {this, "select_char_3", PlatformKey::KEY_DIGIT_3, "Select 3 character key"}; - Key SelectChar4 = {this, "select_char_4", PlatformKey::KEY_DIGIT_4, "Select 4 character key"}; - Key SelectNPC1 = {this, "select_npc_1", PlatformKey::KEY_DIGIT_5, "Select 1 hireling key"}; - Key SelectNPC2 = {this, "select_npc_2", PlatformKey::KEY_DIGIT_6, "Select 2 hireling key"}; - Key DialogUp = {this, "dialog_up", PlatformKey::KEY_UP, "Dialog up key"}; - Key DialogDown = {this, "dialog_down", PlatformKey::KEY_DOWN, "Dialog down key"}; - Key DialogLeft = {this, "dialog_left", PlatformKey::KEY_LEFT, "Dialog left key"}; - Key DialogRight = {this, "dialog_right", PlatformKey::KEY_RIGHT, "Dialog right key"}; - Key DialogSelect = {this, "dialog_select", PlatformKey::KEY_RETURN, "Dialog select key"}; - Key Escape = {this, "escape", PlatformKey::KEY_ESCAPE, "Escape key"}; + Key QuickSave = {this, "quick_save", PlatformKey::KEY_F5, "Quick save key."}; + Key QuickLoad = {this, "quick_load", PlatformKey::KEY_F9, "Quick load key."}; + Key History = {this, "history", PlatformKey::KEY_H, "History book key."}; + Key Stats = {this, "stats", PlatformKey::KEY_C, "Stats tab key."}; + Key Skills = {this, "skills", PlatformKey::KEY_S, "Skills tab key."}; + Key Inventory = {this, "inventory", PlatformKey::KEY_I, "Inventory tab key."}; + Key Awards = {this, "awards", PlatformKey::KEY_A, "Stats tab key."}; + Key NewGame = {this, "new_game", PlatformKey::KEY_N, "New Game menu key."}; + Key SaveGame = {this, "save_game", PlatformKey::KEY_S, "Save Game menu key."}; + Key LoadGame = {this, "load_game", PlatformKey::KEY_L, "Load Game menu key."}; + Key ExitGame = {this, "exit_game", PlatformKey::KEY_Q, "Exit Game key."}; + Key ReturnToGame = {this, "return_to_game", PlatformKey::KEY_R, "Return to Game mode key."}; + Key Controls = {this, "controls", PlatformKey::KEY_C, "Controls change menu key."}; + Key Options = {this, "options", PlatformKey::KEY_O, "Options menu key."}; + Key Credits = {this, "credits", PlatformKey::KEY_C, "Credits menu key."}; + Key Clear = {this, "clear", PlatformKey::KEY_C, "Clear button in New Party Creation menu."}; + Key Return = {this, "return", PlatformKey::KEY_RETURN, "Ok button in New Party Creation menu."}; + Key Minus = {this, "minus", PlatformKey::KEY_SUBTRACT, "Minus button in New Party Creation menu."}; + Key Plus = {this, "plus", PlatformKey::KEY_ADD, "Plus button in New Party Creation menu."}; + Key Yes = {this, "yes", PlatformKey::KEY_Y, "Yes answer key."}; + Key No = {this, "no", PlatformKey::KEY_N, "No answer key."}; + Key Rest8Hours = {this, "rest_8_hours", PlatformKey::KEY_R, "Rest for 8 hours key in Rest menu."}; + Key WaitTillDawn = {this, "wait_till_dawn", PlatformKey::KEY_D, "Wait till dawn key in Rest menu."}; + Key WaitHour = {this, "wait_hour", PlatformKey::KEY_H, "Wait one hour key in Rest menu."}; + Key Wait5Minutes = {this, "wait_5_minutes", PlatformKey::KEY_M, "Wait 5 minutes key in Rest menu."}; + Key Screenshot = {this, "screenshot", PlatformKey::KEY_F2, "Make screenshot key."}; + Key Console = {this, "console", PlatformKey::KEY_TILDE, "Show/Hide overlays key."}; + Key ToggleMouseGrab = {this, "toggle_mouse_grab", PlatformKey::KEY_F1, "Toggle mouse grab key."}; + Key ToggleBorderless = {this, "toggle_borderless", PlatformKey::KEY_F3, "Toggle window borderless key."}; + Key ToggleFullscreen = {this, "toggle_fullscreen", PlatformKey::KEY_F4, "Toggle window fullscreen key."}; + Key ToggleResizable = {this, "toggle_resizable", PlatformKey::KEY_F6, "Toggle window resizable key."}; + Key CycleFilter = {this, "cycle_filter", PlatformKey::KEY_F7, "Cycle rescale filter modes key."}; + Key ReloadShaders = {this, "reload_shaders", PlatformKey::KEY_BACKSPACE, "Reload shaders key."}; + Key SelectChar1 = {this, "select_char_1", PlatformKey::KEY_DIGIT_1, "Select 1 character key."}; + Key SelectChar2 = {this, "select_char_2", PlatformKey::KEY_DIGIT_2, "Select 2 character key."}; + Key SelectChar3 = {this, "select_char_3", PlatformKey::KEY_DIGIT_3, "Select 3 character key."}; + Key SelectChar4 = {this, "select_char_4", PlatformKey::KEY_DIGIT_4, "Select 4 character key."}; + Key SelectNPC1 = {this, "select_npc_1", PlatformKey::KEY_DIGIT_5, "Select 1 hireling key."}; + Key SelectNPC2 = {this, "select_npc_2", PlatformKey::KEY_DIGIT_6, "Select 2 hireling key."}; + Key DialogUp = {this, "dialog_up", PlatformKey::KEY_UP, "Dialog up key."}; + Key DialogDown = {this, "dialog_down", PlatformKey::KEY_DOWN, "Dialog down key."}; + Key DialogLeft = {this, "dialog_left", PlatformKey::KEY_LEFT, "Dialog left key."}; + Key DialogRight = {this, "dialog_right", PlatformKey::KEY_RIGHT, "Dialog right key."}; + Key DialogSelect = {this, "dialog_select", PlatformKey::KEY_RETURN, "Dialog select key."}; + Key Escape = {this, "escape", PlatformKey::KEY_ESCAPE, "Escape key."}; }; - Keybindings keybindings{ this }; + Keybindings keybindings{this}; class Settings : public ConfigSection { public: @@ -585,34 +616,42 @@ class GameConfig : public Config { } }; - Settings settings{ this }; + Settings settings{this}; class Window : public ConfigSection { public: explicit Window(GameConfig *config): ConfigSection(config, "window") {} - String Title = String(this, "title", "OpenEnroth", &ValidateTitle, "Game window title."); + String Title = String(this, "title", "OpenEnroth", &ValidateTitle, + "Game window title."); Int Display = {this, "display", 0, - "Display number as exposed by SDL. " - "Order is platform-specific, e.g. on windows 0 is main display"}; + "Display number as exposed by SDL. " + "Order is platform-specific, e.g. on windows 0 is main display."}; ConfigEntry<PlatformWindowMode> Mode = {this, "mode", ConfigWindowMode, - "Window mode, one of 'windowed', 'borderless', 'fullscreen' or 'fullscreen_borderless'."}; + "Window mode, one of 'windowed', 'borderless', 'fullscreen' or 'fullscreen_borderless'."}; Int PositionX = {this, "position_x", -1, &ValidatePosition, - "Game window x position in display coordinates. Use -1 for centered."}; + "Game window x position in display coordinates. Use -1 for centered."}; + Int PositionY = {this, "position_y", -1, &ValidatePosition, - "Game window y position in display coordinates. Use -1 for centered."}; + "Game window y position in display coordinates. Use -1 for centered."}; - Int Width = {this, "width", 640, &ValidateWidth, "Window width."}; - Int Height = {this, "height", 480, &ValidateHeight, "Window height."}; + Int Width = {this, "width", 640, &ValidateWidth, + "Window width."}; - Bool MouseGrab = {this, "mouse_grab", false, "Restrict mouse movement to game window when it's in focus."}; + Int Height = {this, "height", 480, &ValidateHeight, + "Window height."}; - Bool Resizable = {this, "resizable", true, "Make window resizable by user or not."}; + Bool MouseGrab = {this, "mouse_grab", false, + "Restrict mouse movement to game window when it's in focus."}; - Bool ReloadTex = {this, "reload_tex", true, "Reload texture assets if window is reinitialised."}; + Bool Resizable = {this, "resizable", true, + "Make window resizable by user or not."}; + + Bool ReloadTex = {this, "reload_tex", true, + "Reload texture assets if window is reinitialised."}; private: static std::string ValidateTitle(std::string title) { @@ -641,8 +680,7 @@ class GameConfig : public Config { } }; - Window window{ this }; - + Window window{this}; class CheatCommands : public ConfigSection { public: @@ -650,8 +688,8 @@ class GameConfig : public Config { private: void _addCommand(int commandIndex, const std::string& defaultValue); - std::vector<std::unique_ptr<String>> CommandList; + std::vector<std::unique_ptr<String>> _commandList; }; - CheatCommands commands{ this }; + CheatCommands commands{this}; }; diff --git a/src/Application/Startup/GameStarter.cpp b/src/Application/Startup/GameStarter.cpp index 3ae485718b50..65429f1ada34 100644 --- a/src/Application/Startup/GameStarter.cpp +++ b/src/Application/Startup/GameStarter.cpp @@ -276,7 +276,7 @@ void GameStarter::run() { _application->component<GameWindowHandler>()->UpdateConfigFromWindow(_config.get()); _config->save(ufs->openForWriting(configName).get()); - logger->info("Configuration file '{}' saved!", configName); + logger->info("Configuration file '{}' saved!", ufs->displayPath(configName)); } catch (const std::exception &e) { // Log the exception so that it goes to all registered loggers. logger->critical("Terminated with exception: {}", e.what()); diff --git a/src/Library/Config/Config.cpp b/src/Library/Config/Config.cpp index 5c0d21ff90af..7d1cdfc1be1a 100644 --- a/src/Library/Config/Config.cpp +++ b/src/Library/Config/Config.cpp @@ -10,6 +10,8 @@ #include "Utility/Streams/FileInputStream.h" #include "Utility/Streams/FileOutputStream.h" #include "Utility/MapAccess.h" +#include "Utility/String/Format.h" +#include "Utility/String/Wrap.h" void Config::load(std::string_view path) { FileInputStream stream(path); // Will throw if file doesn't exist. @@ -26,6 +28,7 @@ void Config::load(InputStream *stream) { std::istringstream stdStream(stream->readAll()); ini::IniFile ini; + ini.setCommentChar(';'); // Use ini comment char, not '#'. ini.decode(stdStream); // This can throw. for (const auto &[sectionName, iniSection] : ini) @@ -36,16 +39,21 @@ void Config::load(InputStream *stream) { } void Config::save(OutputStream *stream) const { - ini::IniFile ini; - for (ConfigSection *section : sections()) - for (AnyConfigEntry *entry : section->entries()) - ini[section->name()][entry->name()] = entry->string(); - - std::ostringstream stdStream; - ini.encode(stdStream); // This can throw. - - // Same here - we'd rather handle FS errors on our side. - stream->write(stdStream.str()); + // ini::IniFile doesn't support comments so we just write things out ourselves. + for (ConfigSection *section : sections()) { + stream->write(fmt::format("[{}]\n", section->name())); + + for (AnyConfigEntry *entry : section->entries()) { + if (!entry->description().empty()) + for (const std::string &line : wrapText(entry->description(), 78)) + stream->write(fmt::format("; {}\n", line)); + stream->write(fmt::format("; Default is '{}'.\n", entry->defaultString())); + stream->write(fmt::format("{} = {}\n", entry->name(), entry->string())); + stream->write("\n"); + } + + stream->write("\n"); + } } void Config::reset() { diff --git a/src/Utility/CMakeLists.txt b/src/Utility/CMakeLists.txt index 212108273416..e2aae1f1d2e3 100644 --- a/src/Utility/CMakeLists.txt +++ b/src/Utility/CMakeLists.txt @@ -14,7 +14,8 @@ set(UTILITY_SOURCES String/Ascii.cpp String/Split.cpp String/Transformations.cpp - UnicodeCrt.cpp) + UnicodeCrt.cpp + String/Wrap.cpp) set(UTILITY_HEADERS Embedded.h @@ -46,7 +47,8 @@ set(UTILITY_HEADERS Unaligned.h UnicodeCrt.h SmallVector.h - ScopedRollback.h) + ScopedRollback.h + String/Wrap.h) if(OE_BUILD_PLATFORM STREQUAL "windows") list(APPEND UTILITY_HEADERS Win/Unicode.h) @@ -78,7 +80,8 @@ if(OE_BUILD_TESTS) String/Tests/TransparentFunctors_ut.cpp String/Tests/Ascii_ut.cpp String/Tests/Split_ut.cpp - String/Tests/Join_ut.cpp) + String/Tests/Join_ut.cpp + String/Tests/Wrap_ut.cpp) if(OE_BUILD_PLATFORM STREQUAL "windows") list(APPEND TEST_UTILITY_SOURCES Win/Tests/Unicode_ut.cpp) diff --git a/src/Utility/String/Ascii.h b/src/Utility/String/Ascii.h index f57fb6a156ca..b769b1b0b894 100644 --- a/src/Utility/String/Ascii.h +++ b/src/Utility/String/Ascii.h @@ -21,6 +21,10 @@ inline char toUpper(char c) { return isLower(c) ? c - 'a' + 'A' : c; } +inline bool isSpace(char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'; +} + std::string toLower(std::string_view text); std::string toUpper(std::string_view text); diff --git a/src/Utility/String/Tests/Wrap_ut.cpp b/src/Utility/String/Tests/Wrap_ut.cpp new file mode 100644 index 000000000000..4b756dce3ff7 --- /dev/null +++ b/src/Utility/String/Tests/Wrap_ut.cpp @@ -0,0 +1,132 @@ +#include <vector> +#include <string> + +#include "Testing/Unit/UnitTest.h" + +#include "Utility/String/Wrap.h" + +UNIT_TEST(StringWrap, BasicSentence) { + std::string text = "This is a simple test"; + size_t width = 10; + std::vector<std::string> expected = {"This is a", "simple", "test"}; + EXPECT_EQ(wrapText(text, width), expected); +} + +UNIT_TEST(StringWrap, EmptyString) { + std::string text = ""; + size_t width = 10; + std::vector<std::string> expected = {""}; + EXPECT_EQ(wrapText(text, width), expected); +} + +UNIT_TEST(StringWrap, SingleShortWord) { + std::string text = "Hello"; + size_t width = 10; + std::vector<std::string> expected = {"Hello"}; + EXPECT_EQ(wrapText(text, width), expected); +} + +UNIT_TEST(StringWrap, SingleLongWord) { + std::string text = "Supercalifragilisticexpialidocious"; + size_t width = 10; + std::vector<std::string> expected = {"Supercalif", "ragilistic", "expialidoc", "ious"}; + EXPECT_EQ(wrapText(text, width), expected); +} + +UNIT_TEST(StringWrap, WordsLongerThanWidth) { + std::string text = "Hello Supercalifragilisticexpialidocious World"; + size_t width = 10; + std::vector<std::string> expected = {"Hello", "Supercalif", "ragilistic", "expialidoc", "ious World"}; + EXPECT_EQ(wrapText(text, width), expected); +} + +UNIT_TEST(StringWrap, ExactFitWidth) { + std::string text = "1234567890 12345"; + size_t width = 10; + std::vector<std::string> expected = {"1234567890", "12345"}; + EXPECT_EQ(wrapText(text, width), expected); +} + +UNIT_TEST(StringWrap, LeadingTrailingSpaces) { + std::string text = " Hello world "; + size_t width = 10; + std::vector<std::string> expected = {" Hello", "world "}; + EXPECT_EQ(wrapText(text, width), expected); +} + +UNIT_TEST(StringWrap, WidthGreaterThanTextLength) { + std::string text = "Hello world"; + size_t width = 20; + std::vector<std::string> expected = {"Hello world"}; + EXPECT_EQ(wrapText(text, width), expected); +} + +UNIT_TEST(StringWrap, WidthEqualToTextLength) { + std::string text = "Hello world"; + size_t width = 11; + std::vector<std::string> expected = {"Hello world"}; + EXPECT_EQ(wrapText(text, width), expected); +} + +UNIT_TEST(StringWrap, WidthOne) { + std::string text = "AB CD EFGH"; + size_t width = 1; + std::vector<std::string> expected = {"A", "B", "C", "D", "E", "F", "G", "H"}; + EXPECT_EQ(wrapText(text, width), expected); +} + +UNIT_TEST(StringWrap, AllSpaces) { + std::string text = " "; + size_t width = 10; + std::vector<std::string> expected = {" "}; + EXPECT_EQ(wrapText(text, width), expected); +} + +UNIT_TEST(StringWrap, ForcedLineBreak) { + std::string text = "Hello\nWorld"; + size_t width = 10; + std::vector<std::string> expected = {"Hello", "World"}; + EXPECT_EQ(wrapText(text, width), expected); +} + +UNIT_TEST(StringWrap, ForcedLineBreakWrap) { + std::string text = "This is a\ntest with forced\nline breaks."; + size_t width = 15; + std::vector<std::string> expected = {"This is a", "test with", "forced", "line breaks."}; + EXPECT_EQ(wrapText(text, width), expected); +} + +UNIT_TEST(StringWrap, ForcedLineBreakRepeated) { + std::string text = "Hello\n\nWorld"; + size_t width = 10; + std::vector<std::string> expected = {"Hello", "", "World"}; + EXPECT_EQ(wrapText(text, width), expected); +} + +UNIT_TEST(StringWrap, NewlineWithLongWordExceedingWidth) { + std::string text = "Hello\nSupercalifragilisticexpialidocious\nWorld"; + size_t width = 10; + std::vector<std::string> expected = {"Hello", "Supercalif", "ragilistic", "expialidoc", "ious", "World"}; + EXPECT_EQ(wrapText(text, width), expected); +} + +UNIT_TEST(StringWrap, NewlineAtEndOfText) { + std::string text = "This is a test\n"; + size_t width = 15; + std::vector<std::string> expected = {"This is a test", ""}; + EXPECT_EQ(wrapText(text, width), expected); +} + +UNIT_TEST(StringWrap, NewlineAtStartOfText) { + std::string text = "\nThis is a test"; + size_t width = 10; + std::vector<std::string> expected = {"", "This is a", "test"}; + EXPECT_EQ(wrapText(text, width), expected); +} + +UNIT_TEST(StringWrap, MultipleNewlinesOnly) { + std::string text = "\n\n\n"; + size_t width = 10; + std::vector<std::string> expected = {"", "", "", ""}; + EXPECT_EQ(wrapText(text, width), expected); +} diff --git a/src/Utility/String/Wrap.cpp b/src/Utility/String/Wrap.cpp new file mode 100644 index 000000000000..49847b63a25e --- /dev/null +++ b/src/Utility/String/Wrap.cpp @@ -0,0 +1,46 @@ +#include "Wrap.h" + +#include <string> +#include <vector> + +#include "Ascii.h" + +std::vector<std::string> wrapText(std::string_view text, size_t size) { + std::vector<std::string> result; + + size_t breakablePos = 0; + size_t lineLength = 0; + size_t lineStart = 0; + for (size_t i = 0; i <= text.size(); ++i) { + lineLength++; + + char c = i == text.size() ? '\n' : text[i]; // Simple hack so that we don't have to do weird ifs after the loop. + if (ascii::isSpace(c)) + breakablePos = i; + + size_t breakPos = static_cast<size_t>(-1); + size_t startPos = 0; + if (lineLength > size && breakablePos != 0) { + /* OK to break! */ + breakPos = breakablePos; + startPos = breakablePos + 1; + } else if (lineLength > size && breakablePos == 0) { + /* Nowhere to break, so break mid-word. */ + breakPos = i; + startPos = i; + } else if (c == '\n') { + /* Forced break. */ + breakPos = i; + startPos = i + 1; + } + + if (breakPos != static_cast<size_t>(-1)) { + result.emplace_back(text.substr(lineStart, breakPos - lineStart)); + breakablePos = 0; + lineLength = i - startPos + 1; + lineStart = startPos; + } + } + + return result; +} diff --git a/src/Utility/String/Wrap.h b/src/Utility/String/Wrap.h new file mode 100644 index 000000000000..6ed09990f135 --- /dev/null +++ b/src/Utility/String/Wrap.h @@ -0,0 +1,6 @@ +#pragma once + +#include <string> +#include <vector> + +std::vector<std::string> wrapText(std::string_view text, size_t size); From 60a0de3583fa9db25c5ed1630a2a3da5d2ef6499 Mon Sep 17 00:00:00 2001 From: captainurist <73941350+captainurist@users.noreply.github.com> Date: Tue, 19 Nov 2024 10:57:42 +0000 Subject: [PATCH 2/2] Don't migrate config --- src/Application/Startup/GameStarter.cpp | 9 --------- src/Library/Config/Config.cpp | 1 - 2 files changed, 10 deletions(-) diff --git a/src/Application/Startup/GameStarter.cpp b/src/Application/Startup/GameStarter.cpp index 65429f1ada34..c0aba5a38a55 100644 --- a/src/Application/Startup/GameStarter.cpp +++ b/src/Application/Startup/GameStarter.cpp @@ -259,15 +259,6 @@ void GameStarter::migrateUserData() { } } } - - if (ufs->exists(configName)) { - logger->info(" Target config exists, skipping config migration."); - } else if (!dfs->exists(configName)) { - logger->info(" No config file to migrate."); - } else { - ufs->write(configName, dfs->read(configName)); - logger->info(" Copied '{}'.", configName); - } } void GameStarter::run() { diff --git a/src/Library/Config/Config.cpp b/src/Library/Config/Config.cpp index 7d1cdfc1be1a..29bce0fc810c 100644 --- a/src/Library/Config/Config.cpp +++ b/src/Library/Config/Config.cpp @@ -24,7 +24,6 @@ void Config::save(std::string_view path) const { } void Config::load(InputStream *stream) { - // We'd rather handle FS errors on our side. std::istringstream stdStream(stream->readAll()); ini::IniFile ini;