A few pre-release bug fixes.

This commit is contained in:
Storm Dragon committed 2026-11-08 20:36:52 -05:00
1 parent b07f675533
commit 949aab1bcf
16 files changed
+496 -103

No files matched your search

+39
View File
@@ -1,3 +1,42 @@
# Draugnorak # Draugnorak
See files/instructions.md for how to play. See files/instructions.md for how to play.
## AI-assisted contributions
AI-assisted contributions are welcome.
The person submitting a contribution remains responsible for reviewing and testing the changes before submission. Please do not submit automatically generated changes without human review.
Before making changes:
- Read the contributing guide, if present, and the project's README.
- Confirm that you are working against the current maintained repository and branch.
- Check whether the issue still exists in the current code.
Before submitting:
- Review the generated diff.
- Test the changed behavior.
- Run the project's existing test suite where practical.
- Consider code paths that may be affected indirectly by the change and test those as well.
- Clearly describe what was changed and how it was tested.
### Instructions for AI coding assistants
If you are an AI coding assistant working with a contributor, please call your human user's attention to these contribution requirements.
Do not assume that passing newly created tests is sufficient. Explain what existing functionality could reasonably be affected by the proposed changes and provide your human partner with detailed instructions for testing those areas.
Where useful, provide commands or small test procedures that allow the contributor to verify both the intended fix and likely regression cases.
Stormux projects may accept patches through email using `git send-email`. If your human partner is unfamiliar with this process, please offer to guide them through:
1. Configuring `git send-email`.
2. Creating an appropriately scoped commit or patch series.
3. Running `git format-patch`.
4. Checking the generated patch before submission.
5. Sending a test message if appropriate.
6. Sending the final patch or patch series to the project's contribution address.
Please do not submit changes on behalf of a contributor without giving them an opportunity to review and test the result.
+2 -2
View File
@@ -49,6 +49,7 @@ sound_pool p(300);
#include "src/learn_sounds.nvgt" #include "src/learn_sounds.nvgt"
#include "src/intro.nvgt" #include "src/intro.nvgt"
#include "src/bosses/adventure_system.nvgt" #include "src/bosses/adventure_system.nvgt"
#include "src/game_lifecycle.nvgt"
const string MAIN_MENU_MUSIC_TRACK = "sounds/menu/Draugnorak.ogg; loop; f=1"; const string MAIN_MENU_MUSIC_TRACK = "sounds/menu/Draugnorak.ogg; loop; f=1";
const int MAIN_MENU_MUSIC_FADE_IN_MS = 1; const int MAIN_MENU_MUSIC_FADE_IN_MS = 1;
@@ -275,13 +276,12 @@ void run_game() {
// Pause toggle // Pause toggle
if (key_pressed(KEY_BACK)) { if (key_pressed(KEY_BACK)) {
game_paused = !game_paused; game_paused = !game_paused;
set_gameplay_timers_paused(game_paused);
if (game_paused) { if (game_paused) {
p.pause_all(); p.pause_all();
speak_with_history(tr("system.game.paused_prompt"), true); speak_with_history(tr("system.game.paused_prompt"), true);
} else { } else {
p.resume_all(); p.resume_all();
// Restart all timers to prevent time from accumulating while paused
restart_all_timers();
speak_with_history(tr("system.game.resumed"), true); speak_with_history(tr("system.game.resumed"), true);
} }
continue; continue;
+2
View File
@@ -205,5 +205,7 @@ Current categories:
## Saving and Loading ## Saving and Loading
- The game auto saves daily at 6 AM in game time - The game auto saves daily at 6 AM in game time
- If saving fails, an error dialog shows the affected file path
- The previous save is kept as a .bak recovery copy; interrupted replacements are recovered automatically
- Load from the main menu starting a new game prompts if a save exists - Load from the main menu starting a new game prompts if a save exists
- In the Load Game menu press Delete to remove the selected save (confirmation required) - In the Load Game menu press Delete to remove the selected save (confirmation required)
+2
View File
@@ -14,6 +14,8 @@ load_game.delete_save_failed=Kon nie stoorlêer uitvee nie.
load_game.save_deleted=Stoorlêer uitgevee. load_game.save_deleted=Stoorlêer uitgevee.
load_game.no_saves_found=Geen stoorleers gevind nie. load_game.no_saves_found=Geen stoorleers gevind nie.
load_game.unable_to_load=Kon nie stoorleer laai nie. load_game.unable_to_load=Kon nie stoorleer laai nie.
load_game.invalid_data=Die stoorlêer is onvolledig of bevat ongeldige data.
load_game.unsupported_version=Hierdie stoorlêer is met 'n onondersteunde spelweergawe geskep.
load_game.select_prompt=Laai speletjie. Kies karakter. Druk Delete om'n stoorlêer te verwyder. load_game.select_prompt=Laai speletjie. Kies karakter. Druk Delete om'n stoorlêer te verwyder.
main_menu.prompt=Draugnorak. Hoofkieslys. main_menu.prompt=Draugnorak. Hoofkieslys.
game.new_game_started=Nuwe speletjie begin. game.new_game_started=Nuwe speletjie begin.
+27 -24
View File
@@ -33,6 +33,8 @@ load_game.delete_save_failed=Unable to delete save.
load_game.save_deleted=Save deleted. load_game.save_deleted=Save deleted.
load_game.no_saves_found=No saves found. load_game.no_saves_found=No saves found.
load_game.unable_to_load=Unable to load save. load_game.unable_to_load=Unable to load save.
load_game.invalid_data=Save file is incomplete or contains invalid data.
load_game.unsupported_version=This save was created by an unsupported game version.
load_game.select_prompt=Load game. Select character. Press delete to remove a save. load_game.select_prompt=Load game. Select character. Press delete to remove a save.
quick_slot.no_item_bound=No item bound to slot {slot}. quick_slot.no_item_bound=No item bound to slot {slot}.
menu.closed=Closed. menu.closed=Closed.
@@ -708,7 +710,7 @@ msg.041d8413712d=A crash log was written to your save data folder.
msg.04a8916a9494=vampyr1 msg.04a8916a9494=vampyr1
; src/item_registry.nvgt:item_plural:incense ; src/item_registry.nvgt:item_plural:incense
msg.0532db2192fa=incense msg.0532db2192fa=incense
; src/bosses/bandit_hideout.nvgt:787:insert_last[0] ; src/bosses/bandit_hideout.nvgt:786:insert_last[0]
msg.066723a92e94==== Victory Rewards === msg.066723a92e94==== Victory Rewards ===
; seed:learn_sounds_label:sounds/enemies/corpse_worm_dies.ogg ; seed:learn_sounds_label:sounds/enemies/corpse_worm_dies.ogg
msg.06c9fefbd6d7=corpse worm dies msg.06c9fefbd6d7=corpse worm dies
@@ -749,12 +751,13 @@ msg.0f9af12cbb82=skin pouch
; src/item_registry.nvgt:item_display:logs ; src/item_registry.nvgt:item_display:logs
msg.126dd3b70a5c=Logs msg.126dd3b70a5c=Logs
; libstorm-nvgt/file_viewer.nvgt:74:screen_reader_speak[0] ; libstorm-nvgt/file_viewer.nvgt:74:screen_reader_speak[0]
; src/save_system.nvgt:233:ui_info_box[1]
msg.136e4cb03bd5=Failed to save file msg.136e4cb03bd5=Failed to save file
; src/item_registry.nvgt:item_plural:quivers ; src/item_registry.nvgt:item_plural:quivers
msg.145ba8a7a0f2=quivers msg.145ba8a7a0f2=quivers
; src/item_registry.nvgt:item_display:great_spears ; src/item_registry.nvgt:item_display:great_spears
msg.14941e07fc96=Great Spears msg.14941e07fc96=Great Spears
; src/bosses/bandit_hideout.nvgt:860:insert_last[0] ; src/bosses/bandit_hideout.nvgt:859:insert_last[0]
msg.14f5f5ff4f76=No survivors were found. msg.14f5f5ff4f76=No survivors were found.
; seed:learn_sounds_label:sounds/game/game_falls.ogg ; seed:learn_sounds_label:sounds/game/game_falls.ogg
msg.16134a4250f7=game falls msg.16134a4250f7=game falls
@@ -782,7 +785,7 @@ msg.1bfa6bc809d4==== Bandit's Hideout ===
msg.1c7022965d1a=skin glove msg.1c7022965d1a=skin glove
; src/menus/base_info.nvgt:20:insert_last[0] ; src/menus/base_info.nvgt:20:insert_last[0]
msg.1d15d0d5c98d=Storage built. Total items {arg1} msg.1d15d0d5c98d=Storage built. Total items {arg1}
; src/bosses/bandit_hideout.nvgt:806:insert_last[0] ; src/bosses/bandit_hideout.nvgt:805:insert_last[0]
msg.1d61343b9660=Storage not built. No item rewards. msg.1d61343b9660=Storage not built. No item rewards.
; seed:learn_sounds_label:sounds/items/fire.ogg ; seed:learn_sounds_label:sounds/items/fire.ogg
msg.1da840244989=fire msg.1da840244989=fire
@@ -792,7 +795,7 @@ msg.1db76ac85135=sling
msg.237b5017397b=axe msg.237b5017397b=axe
; src/bosses/unicorn/unicorn_boss.nvgt:117:insert_last[0] ; src/bosses/unicorn/unicorn_boss.nvgt:117:insert_last[0]
msg.245c89590526=- When the bridge collapses with the Unicorn on it, you win! msg.245c89590526=- When the bridge collapses with the Unicorn on it, you win!
; src/bosses/bandit_hideout.nvgt:834:insert_last[0] ; src/bosses/bandit_hideout.nvgt:833:insert_last[0]
msg.25db7a4ec692=Livestock were recovered and added to your pasture. msg.25db7a4ec692=Livestock were recovered and added to your pasture.
; seed:learn_sounds_label:sounds/quests/safe_to_jump.ogg ; seed:learn_sounds_label:sounds/quests/safe_to_jump.ogg
msg.2630bd23d7d4=safe to jump msg.2630bd23d7d4=safe to jump
@@ -818,7 +821,7 @@ msg.27e4cdae51e3=vampyr4
msg.27f1dd42841b=- Press Enter to confirm msg.27f1dd42841b=- Press Enter to confirm
; src/menus/base_info.nvgt:30:insert_last[0] ; src/menus/base_info.nvgt:30:insert_last[0]
msg.27f5254de319=Storage not built msg.27f5254de319=Storage not built
; src/bosses/bandit_hideout.nvgt:817:insert_last[0] ; src/bosses/bandit_hideout.nvgt:816:insert_last[0]
msg.27f76f96867d={arg1}: +{arg2}. msg.27f76f96867d={arg1}: +{arg2}.
; src/bosses/unicorn/unicorn_boss.nvgt:115:insert_last[0] ; src/bosses/unicorn/unicorn_boss.nvgt:115:insert_last[0]
msg.282b619d84bc=- Or fight the Unicorn directly (it has massive health) msg.282b619d84bc=- Or fight the Unicorn directly (it has massive health)
@@ -952,7 +955,7 @@ msg.5009c8e190ee=turkey
msg.508e8bc0cc57=- Use Up/Down to change the number msg.508e8bc0cc57=- Use Up/Down to change the number
; seed:learn_sounds_label:sounds/weapons/sling_low_range.ogg ; seed:learn_sounds_label:sounds/weapons/sling_low_range.ogg
msg.5123c22ce6a5=sling low range msg.5123c22ce6a5=sling low range
; draugnorak.nvgt:113:insert_last[0] ; draugnorak.nvgt:114:insert_last[0]
msg.528fb1c4e5fb=New Game msg.528fb1c4e5fb=New Game
; seed:learn_sounds_label:sounds/quests/squish.ogg ; seed:learn_sounds_label:sounds/quests/squish.ogg
msg.5460f2e3c43e=squish msg.5460f2e3c43e=squish
@@ -980,7 +983,7 @@ msg.5b61e4982b59=wight dies
msg.5bdb7fa2223d=Feathers msg.5bdb7fa2223d=Feathers
; src/item_registry.nvgt:item_plural:reeds ; src/item_registry.nvgt:item_plural:reeds
msg.5c2434ecd90f=reeds msg.5c2434ecd90f=reeds
; src/bosses/bandit_hideout.nvgt:829:insert_last[0] ; src/bosses/bandit_hideout.nvgt:828:insert_last[0]
msg.5cac31311668=Pasture is full. No livestock were recovered. msg.5cac31311668=Pasture is full. No livestock were recovered.
; src/bosses/unicorn/unicorn_boss.nvgt:110:insert_last[0] ; src/bosses/unicorn/unicorn_boss.nvgt:110:insert_last[0]
msg.5cc7429297c8=that can be destroyed with an axe. msg.5cc7429297c8=that can be destroyed with an axe.
@@ -993,7 +996,7 @@ msg.5ea84b1201bb={arg1} favor grants you the eyes of an eagle.
msg.5f3f3a85393b=menu select msg.5f3f3a85393b=menu select
; src/menus/base_info.nvgt:12:insert_last[0] ; src/menus/base_info.nvgt:12:insert_last[0]
msg.623f78278373=Barricade health {arg1} of {arg2} msg.623f78278373=Barricade health {arg1} of {arg2}
; src/bosses/bandit_hideout.nvgt:827:insert_last[0] ; src/bosses/bandit_hideout.nvgt:826:insert_last[0]
msg.62583aa87a59=Pasture or storage not built. No livestock were recovered. msg.62583aa87a59=Pasture or storage not built. No livestock were recovered.
; seed:learn_sounds_label:sounds/actions/climb_tree.ogg ; seed:learn_sounds_label:sounds/actions/climb_tree.ogg
msg.627a5b9e6322=climb tree msg.627a5b9e6322=climb tree
@@ -1070,8 +1073,8 @@ msg.7e83de91b4a2=bandit female dies
msg.7f7e3bbf8864=An enemy is no longer in range of your currently wielded weapon. msg.7f7e3bbf8864=An enemy is no longer in range of your currently wielded weapon.
; src/item_registry.nvgt:item_display:backpacks ; src/item_registry.nvgt:item_display:backpacks
msg.7fae743693f4=Backpacks msg.7fae743693f4=Backpacks
; draugnorak.nvgt:232:ui_info_box[1] ; draugnorak.nvgt:233:ui_info_box[1]
; draugnorak.nvgt:246:ui_info_box[1] ; draugnorak.nvgt:247:ui_info_box[1]
msg.802aa6576577=Load Game msg.802aa6576577=Load Game
; src/item_registry.nvgt:item_plural:sticks ; src/item_registry.nvgt:item_plural:sticks
msg.803e4c41bbd2=sticks msg.803e4c41bbd2=sticks
@@ -1129,7 +1132,7 @@ msg.924e0eb6b6f1=player male damage
msg.9281d2fe5f08={arg1} favor shines upon you. You feel swift for a while. msg.9281d2fe5f08={arg1} favor shines upon you. You feel swift for a while.
; seed:learn_sounds_label:sounds/actions/climb_rope.ogg ; seed:learn_sounds_label:sounds/actions/climb_rope.ogg
msg.92abc6d6953f=climb rope msg.92abc6d6953f=climb rope
; src/bosses/bandit_hideout.nvgt:800:insert_last[0] ; src/bosses/bandit_hideout.nvgt:799:insert_last[0]
msg.9379874d592b=Altar not built. No favor awarded. msg.9379874d592b=Altar not built. No favor awarded.
; src/bosses/unicorn/unicorn_boss.nvgt:113:insert_last[0] ; src/bosses/unicorn/unicorn_boss.nvgt:113:insert_last[0]
msg.93894d088f6a=- Use your axe to destroy a bridge support msg.93894d088f6a=- Use your axe to destroy a bridge support
@@ -1138,7 +1141,7 @@ msg.93894d088f6a=- Use your axe to destroy a bridge support
msg.946466f6c963=stick msg.946466f6c963=stick
; seed:learn_sounds_label:sounds/game/boar.ogg ; seed:learn_sounds_label:sounds/game/boar.ogg
msg.957f554c342a=boar msg.957f554c342a=boar
; src/bosses/bandit_hideout.nvgt:857:insert_last[0] ; src/bosses/bandit_hideout.nvgt:856:insert_last[0]
msg.97c59037a63c=A survivor joins your base. msg.97c59037a63c=A survivor joins your base.
; src/item_registry.nvgt:item_plural:smoked_fish ; src/item_registry.nvgt:item_plural:smoked_fish
; src/item_registry.nvgt:item_singular:smoked_fish ; src/item_registry.nvgt:item_singular:smoked_fish
@@ -1147,7 +1150,7 @@ msg.995d4e06ae4e=smoked fish
msg.998b61086fa7=You picked up an item for which there is no specific sound. msg.998b61086fa7=You picked up an item for which there is no specific sound.
; src/quests/catch_the_boomerang_game.nvgt:19:insert_last[0] ; src/quests/catch_the_boomerang_game.nvgt:19:insert_last[0]
msg.9afdb7e61391=- Press Space to throw msg.9afdb7e61391=- Press Space to throw
; src/bosses/bandit_hideout.nvgt:865:text_reader_lines[1] ; src/bosses/bandit_hideout.nvgt:864:text_reader_lines[1]
msg.9b64062b2074=Bandit's Hideout msg.9b64062b2074=Bandit's Hideout
; seed:learn_sounds_label:sounds/enemies/bandit4.ogg ; seed:learn_sounds_label:sounds/enemies/bandit4.ogg
msg.9bbcfc6c8f9d=bandit4 msg.9bbcfc6c8f9d=bandit4
@@ -1182,7 +1185,7 @@ msg.a56be51786dc=bone1
msg.a570b024087d=Ever hungry, they feed upon your residents. msg.a570b024087d=Ever hungry, they feed upon your residents.
; src/quests/escape_from_hel_game.nvgt:15:insert_last[0] ; src/quests/escape_from_hel_game.nvgt:15:insert_last[0]
msg.a641f39a6317=- The run ends when you fall into a grave msg.a641f39a6317=- The run ends when you fall into a grave
; src/bosses/bandit_hideout.nvgt:798:insert_last[0] ; src/bosses/bandit_hideout.nvgt:797:insert_last[0]
msg.a646f0dcb735=Favor awarded: {arg1}. msg.a646f0dcb735=Favor awarded: {arg1}.
; src/item_registry.nvgt:item_plural:slings ; src/item_registry.nvgt:item_plural:slings
msg.a6caf0c3771d=slings msg.a6caf0c3771d=slings
@@ -1233,7 +1236,7 @@ msg.b73a0709297f=call pet
msg.b774102c89b5=Knives msg.b774102c89b5=Knives
; seed:learn_sounds_label:sounds/terrain/snow.ogg ; seed:learn_sounds_label:sounds/terrain/snow.ogg
msg.b94e9f3d7e00=snow msg.b94e9f3d7e00=snow
; src/bosses/bandit_hideout.nvgt:844:insert_last[0] ; src/bosses/bandit_hideout.nvgt:843:insert_last[0]
msg.ba3af68702a3=Learned Rune of Destruction! msg.ba3af68702a3=Learned Rune of Destruction!
; src/item_registry.nvgt:item_display:baskets_of_fruits_and_nuts ; src/item_registry.nvgt:item_display:baskets_of_fruits_and_nuts
msg.ba7d7dfe330b=Baskets of Fruits and Nuts msg.ba7d7dfe330b=Baskets of Fruits and Nuts
@@ -1250,7 +1253,7 @@ msg.bdebda25dad9=arrow hit
msg.bf93e5ce8bc1=Down msg.bf93e5ce8bc1=Down
; src/sound_settings.nvgt:26:learn_sounds_add_description[1] ; src/sound_settings.nvgt:26:learn_sounds_add_description[1]
msg.c01409b3a728=When casting release control when over water. When catching release when sound is over player. msg.c01409b3a728=When casting release control when over water. When catching release when sound is over player.
; src/bosses/bandit_hideout.nvgt:821:insert_last[0] ; src/bosses/bandit_hideout.nvgt:820:insert_last[0]
msg.c05feaca7269=No items were recovered. msg.c05feaca7269=No items were recovered.
; src/item_registry.nvgt:item_singular:snare ; src/item_registry.nvgt:item_singular:snare
msg.c1104d82c0bd=snare msg.c1104d82c0bd=snare
@@ -1266,7 +1269,7 @@ msg.c52888225c69=fall
msg.c65e5dfc5ba7=bandit1 msg.c65e5dfc5ba7=bandit1
; seed:learn_sounds_label:sounds/enemies/corpse_worm.ogg ; seed:learn_sounds_label:sounds/enemies/corpse_worm.ogg
msg.c6d56016b910=corpse worm msg.c6d56016b910=corpse worm
; src/bosses/bandit_hideout.nvgt:789:insert_last[0] ; src/bosses/bandit_hideout.nvgt:788:insert_last[0]
msg.c72b7844929d=Bandits defeated: {arg1}. msg.c72b7844929d=Bandits defeated: {arg1}.
; seed:learn_sounds_label:sounds/terrain/stream.ogg ; seed:learn_sounds_label:sounds/terrain/stream.ogg
msg.c82e3d7279ef=stream msg.c82e3d7279ef=stream
@@ -1278,7 +1281,7 @@ msg.c8da1c4f1714=Lowers in pitch as the fall progresses.
msg.c90399f41b3f=Meat msg.c90399f41b3f=Meat
; seed:learn_sounds_label:sounds/quests/bone7.ogg ; seed:learn_sounds_label:sounds/quests/bone7.ogg
msg.c9532ab465fb=bone7 msg.c9532ab465fb=bone7
; src/bosses/bandit_hideout.nvgt:808:insert_last[0] ; src/bosses/bandit_hideout.nvgt:807:insert_last[0]
msg.cb8b9677952a=Storage rewards: msg.cb8b9677952a=Storage rewards:
; src/item_registry.nvgt:item_plural:boar_carcasses ; src/item_registry.nvgt:item_plural:boar_carcasses
msg.cb8e70ab2bcc=boar carcasses msg.cb8e70ab2bcc=boar carcasses
@@ -1298,7 +1301,7 @@ msg.ce3897c6b3be={arg1}: {arg2}
msg.ce5aeca95fb9=item breaks msg.ce5aeca95fb9=item breaks
; src/i18n.nvgt:677:speak_with_history[0] ; src/i18n.nvgt:677:speak_with_history[0]
; src/pet_system.nvgt:936:speak_with_history[0] ; src/pet_system.nvgt:936:speak_with_history[0]
; src/save_system.nvgt:344:speak_with_history[0] ; src/save_system.nvgt:410:speak_with_history[0]
msg.cea88b214070={arg1} {arg2} msg.cea88b214070={arg1} {arg2}
; seed:manual ; seed:manual
msg.cfe2650b4c6e=Found a stone. msg.cfe2650b4c6e=Found a stone.
@@ -1327,7 +1330,7 @@ msg.d6dc139f529a=invasion
msg.d6fd3467a950=yeti1 msg.d6fd3467a950=yeti1
; src/item_registry.nvgt:item_display:axes ; src/item_registry.nvgt:item_display:axes
msg.d75f36f88797=Axes msg.d75f36f88797=Axes
; src/bosses/bandit_hideout.nvgt:836:insert_last[0] ; src/bosses/bandit_hideout.nvgt:835:insert_last[0]
msg.d7879bd506af=No livestock were recovered. msg.d7879bd506af=No livestock were recovered.
; src/quests/enchanted_melody_game.nvgt:31:insert_last[0] ; src/quests/enchanted_melody_game.nvgt:31:insert_last[0]
msg.da0b5a461d98=F or K - First note (lowest pitch) msg.da0b5a461d98=F or K - First note (lowest pitch)
@@ -1395,7 +1398,7 @@ msg.e5d79aba2048=undead resident1
msg.e5f656d506a7=zombie hit msg.e5f656d506a7=zombie hit
; src/quests/escape_from_hel_game.nvgt:8:insert_last[0] ; src/quests/escape_from_hel_game.nvgt:8:insert_last[0]
msg.e6526216c064=You have plundered the treasure, and the Draugr are displeased.! msg.e6526216c064=You have plundered the treasure, and the Draugr are displeased.!
; src/bosses/bandit_hideout.nvgt:847:insert_last[0] ; src/bosses/bandit_hideout.nvgt:846:insert_last[0]
msg.e65fa8960222=You have already mastered the Rune of Destruction. msg.e65fa8960222=You have already mastered the Rune of Destruction.
; seed:learn_sounds_label:sounds/quests/bone8.ogg ; seed:learn_sounds_label:sounds/quests/bone8.ogg
msg.e6ae58426f13=bone8 msg.e6ae58426f13=bone8
@@ -1405,9 +1408,9 @@ msg.e7409fcb54bc=feed fire
msg.e8002c169040=wood msg.e8002c169040=wood
; src/quests/escape_from_hel_game.nvgt:13:insert_last[0] ; src/quests/escape_from_hel_game.nvgt:13:insert_last[0]
msg.e8216720d30b=- Press SPACE to jump over graves msg.e8216720d30b=- Press SPACE to jump over graves
; draugnorak.nvgt:133:insert_last[0] ; draugnorak.nvgt:134:insert_last[0]
msg.e8db3a42f86f=Learn Sounds msg.e8db3a42f86f=Learn Sounds
; src/bosses/bandit_hideout.nvgt:845:insert_last[0] ; src/bosses/bandit_hideout.nvgt:844:insert_last[0]
msg.e8e8f4ee7a5a=You can now engrave weapons with this rune at the crafting menu. msg.e8e8f4ee7a5a=You can now engrave weapons with this rune at the crafting menu.
; src/bosses/bandit_hideout.nvgt:302:insert_last[0] ; src/bosses/bandit_hideout.nvgt:302:insert_last[0]
msg.e9dd1d268b65=The base lies far to the east, guarded by a barricade. msg.e9dd1d268b65=The base lies far to the east, guarded by a barricade.
@@ -1436,7 +1439,7 @@ msg.f3d964225e83=player female damage
msg.f5cabf873590=reed msg.f5cabf873590=reed
; src/bosses/bandit_hideout.nvgt:301:insert_last[0] ; src/bosses/bandit_hideout.nvgt:301:insert_last[0]
msg.f77fc018cb7b=You find a hidden bandit base deep in the forest. msg.f77fc018cb7b=You find a hidden bandit base deep in the forest.
; draugnorak.nvgt:149:insert_last[0] ; draugnorak.nvgt:150:insert_last[0]
msg.f83b6fe3aebf=Exit msg.f83b6fe3aebf=Exit
; src/inventory_items.nvgt:504:speak_with_history[0] ; src/inventory_items.nvgt:504:speak_with_history[0]
; src/quests/skeletal_bard_game.nvgt:26:speak_with_history[0] ; src/quests/skeletal_bard_game.nvgt:26:speak_with_history[0]
+27 -24
View File
@@ -33,6 +33,8 @@ load_game.delete_save_failed=Unable to delete save.
load_game.save_deleted=Save deleted. load_game.save_deleted=Save deleted.
load_game.no_saves_found=No saves found. load_game.no_saves_found=No saves found.
load_game.unable_to_load=Unable to load save. load_game.unable_to_load=Unable to load save.
load_game.invalid_data=Save file is incomplete or contains invalid data.
load_game.unsupported_version=This save was created by an unsupported game version.
load_game.select_prompt=Load game. Select character. Press delete to remove a save. load_game.select_prompt=Load game. Select character. Press delete to remove a save.
quick_slot.no_item_bound=No item bound to slot {slot}. quick_slot.no_item_bound=No item bound to slot {slot}.
menu.closed=Closed. menu.closed=Closed.
@@ -708,7 +710,7 @@ msg.041d8413712d=A crash log was written to your save data folder.
msg.04a8916a9494=vampyr1 msg.04a8916a9494=vampyr1
; src/item_registry.nvgt:item_plural:incense ; src/item_registry.nvgt:item_plural:incense
msg.0532db2192fa=incense msg.0532db2192fa=incense
; src/bosses/bandit_hideout.nvgt:787:insert_last[0] ; src/bosses/bandit_hideout.nvgt:786:insert_last[0]
msg.066723a92e94==== Victory Rewards === msg.066723a92e94==== Victory Rewards ===
; seed:learn_sounds_label:sounds/enemies/corpse_worm_dies.ogg ; seed:learn_sounds_label:sounds/enemies/corpse_worm_dies.ogg
msg.06c9fefbd6d7=corpse worm dies msg.06c9fefbd6d7=corpse worm dies
@@ -749,12 +751,13 @@ msg.0f9af12cbb82=skin pouch
; src/item_registry.nvgt:item_display:logs ; src/item_registry.nvgt:item_display:logs
msg.126dd3b70a5c=Logs msg.126dd3b70a5c=Logs
; libstorm-nvgt/file_viewer.nvgt:74:screen_reader_speak[0] ; libstorm-nvgt/file_viewer.nvgt:74:screen_reader_speak[0]
; src/save_system.nvgt:233:ui_info_box[1]
msg.136e4cb03bd5=Failed to save file msg.136e4cb03bd5=Failed to save file
; src/item_registry.nvgt:item_plural:quivers ; src/item_registry.nvgt:item_plural:quivers
msg.145ba8a7a0f2=quivers msg.145ba8a7a0f2=quivers
; src/item_registry.nvgt:item_display:great_spears ; src/item_registry.nvgt:item_display:great_spears
msg.14941e07fc96=Great Spears msg.14941e07fc96=Great Spears
; src/bosses/bandit_hideout.nvgt:860:insert_last[0] ; src/bosses/bandit_hideout.nvgt:859:insert_last[0]
msg.14f5f5ff4f76=No survivors were found. msg.14f5f5ff4f76=No survivors were found.
; seed:learn_sounds_label:sounds/game/game_falls.ogg ; seed:learn_sounds_label:sounds/game/game_falls.ogg
msg.16134a4250f7=game falls msg.16134a4250f7=game falls
@@ -782,7 +785,7 @@ msg.1bfa6bc809d4==== Bandit's Hideout ===
msg.1c7022965d1a=skin glove msg.1c7022965d1a=skin glove
; src/menus/base_info.nvgt:20:insert_last[0] ; src/menus/base_info.nvgt:20:insert_last[0]
msg.1d15d0d5c98d=Storage built. Total items {arg1} msg.1d15d0d5c98d=Storage built. Total items {arg1}
; src/bosses/bandit_hideout.nvgt:806:insert_last[0] ; src/bosses/bandit_hideout.nvgt:805:insert_last[0]
msg.1d61343b9660=Storage not built. No item rewards. msg.1d61343b9660=Storage not built. No item rewards.
; seed:learn_sounds_label:sounds/items/fire.ogg ; seed:learn_sounds_label:sounds/items/fire.ogg
msg.1da840244989=fire msg.1da840244989=fire
@@ -792,7 +795,7 @@ msg.1db76ac85135=sling
msg.237b5017397b=axe msg.237b5017397b=axe
; src/bosses/unicorn/unicorn_boss.nvgt:117:insert_last[0] ; src/bosses/unicorn/unicorn_boss.nvgt:117:insert_last[0]
msg.245c89590526=- When the bridge collapses with the Unicorn on it, you win! msg.245c89590526=- When the bridge collapses with the Unicorn on it, you win!
; src/bosses/bandit_hideout.nvgt:834:insert_last[0] ; src/bosses/bandit_hideout.nvgt:833:insert_last[0]
msg.25db7a4ec692=Livestock were recovered and added to your pasture. msg.25db7a4ec692=Livestock were recovered and added to your pasture.
; seed:learn_sounds_label:sounds/quests/safe_to_jump.ogg ; seed:learn_sounds_label:sounds/quests/safe_to_jump.ogg
msg.2630bd23d7d4=safe to jump msg.2630bd23d7d4=safe to jump
@@ -818,7 +821,7 @@ msg.27e4cdae51e3=vampyr4
msg.27f1dd42841b=- Press Enter to confirm msg.27f1dd42841b=- Press Enter to confirm
; src/menus/base_info.nvgt:30:insert_last[0] ; src/menus/base_info.nvgt:30:insert_last[0]
msg.27f5254de319=Storage not built msg.27f5254de319=Storage not built
; src/bosses/bandit_hideout.nvgt:817:insert_last[0] ; src/bosses/bandit_hideout.nvgt:816:insert_last[0]
msg.27f76f96867d={arg1}: +{arg2}. msg.27f76f96867d={arg1}: +{arg2}.
; src/bosses/unicorn/unicorn_boss.nvgt:115:insert_last[0] ; src/bosses/unicorn/unicorn_boss.nvgt:115:insert_last[0]
msg.282b619d84bc=- Or fight the Unicorn directly (it has massive health) msg.282b619d84bc=- Or fight the Unicorn directly (it has massive health)
@@ -952,7 +955,7 @@ msg.5009c8e190ee=turkey
msg.508e8bc0cc57=- Use Up/Down to change the number msg.508e8bc0cc57=- Use Up/Down to change the number
; seed:learn_sounds_label:sounds/weapons/sling_low_range.ogg ; seed:learn_sounds_label:sounds/weapons/sling_low_range.ogg
msg.5123c22ce6a5=sling low range msg.5123c22ce6a5=sling low range
; draugnorak.nvgt:113:insert_last[0] ; draugnorak.nvgt:114:insert_last[0]
msg.528fb1c4e5fb=New Game msg.528fb1c4e5fb=New Game
; seed:learn_sounds_label:sounds/quests/squish.ogg ; seed:learn_sounds_label:sounds/quests/squish.ogg
msg.5460f2e3c43e=squish msg.5460f2e3c43e=squish
@@ -980,7 +983,7 @@ msg.5b61e4982b59=wight dies
msg.5bdb7fa2223d=Feathers msg.5bdb7fa2223d=Feathers
; src/item_registry.nvgt:item_plural:reeds ; src/item_registry.nvgt:item_plural:reeds
msg.5c2434ecd90f=reeds msg.5c2434ecd90f=reeds
; src/bosses/bandit_hideout.nvgt:829:insert_last[0] ; src/bosses/bandit_hideout.nvgt:828:insert_last[0]
msg.5cac31311668=Pasture is full. No livestock were recovered. msg.5cac31311668=Pasture is full. No livestock were recovered.
; src/bosses/unicorn/unicorn_boss.nvgt:110:insert_last[0] ; src/bosses/unicorn/unicorn_boss.nvgt:110:insert_last[0]
msg.5cc7429297c8=that can be destroyed with an axe. msg.5cc7429297c8=that can be destroyed with an axe.
@@ -993,7 +996,7 @@ msg.5ea84b1201bb={arg1} favor grants you the eyes of an eagle.
msg.5f3f3a85393b=menu select msg.5f3f3a85393b=menu select
; src/menus/base_info.nvgt:12:insert_last[0] ; src/menus/base_info.nvgt:12:insert_last[0]
msg.623f78278373=Barricade health {arg1} of {arg2} msg.623f78278373=Barricade health {arg1} of {arg2}
; src/bosses/bandit_hideout.nvgt:827:insert_last[0] ; src/bosses/bandit_hideout.nvgt:826:insert_last[0]
msg.62583aa87a59=Pasture or storage not built. No livestock were recovered. msg.62583aa87a59=Pasture or storage not built. No livestock were recovered.
; seed:learn_sounds_label:sounds/actions/climb_tree.ogg ; seed:learn_sounds_label:sounds/actions/climb_tree.ogg
msg.627a5b9e6322=climb tree msg.627a5b9e6322=climb tree
@@ -1070,8 +1073,8 @@ msg.7e83de91b4a2=bandit female dies
msg.7f7e3bbf8864=An enemy is no longer in range of your currently wielded weapon. msg.7f7e3bbf8864=An enemy is no longer in range of your currently wielded weapon.
; src/item_registry.nvgt:item_display:backpacks ; src/item_registry.nvgt:item_display:backpacks
msg.7fae743693f4=Backpacks msg.7fae743693f4=Backpacks
; draugnorak.nvgt:232:ui_info_box[1] ; draugnorak.nvgt:233:ui_info_box[1]
; draugnorak.nvgt:246:ui_info_box[1] ; draugnorak.nvgt:247:ui_info_box[1]
msg.802aa6576577=Load Game msg.802aa6576577=Load Game
; src/item_registry.nvgt:item_plural:sticks ; src/item_registry.nvgt:item_plural:sticks
msg.803e4c41bbd2=sticks msg.803e4c41bbd2=sticks
@@ -1129,7 +1132,7 @@ msg.924e0eb6b6f1=player male damage
msg.9281d2fe5f08={arg1} favor shines upon you. You feel swift for a while. msg.9281d2fe5f08={arg1} favor shines upon you. You feel swift for a while.
; seed:learn_sounds_label:sounds/actions/climb_rope.ogg ; seed:learn_sounds_label:sounds/actions/climb_rope.ogg
msg.92abc6d6953f=climb rope msg.92abc6d6953f=climb rope
; src/bosses/bandit_hideout.nvgt:800:insert_last[0] ; src/bosses/bandit_hideout.nvgt:799:insert_last[0]
msg.9379874d592b=Altar not built. No favor awarded. msg.9379874d592b=Altar not built. No favor awarded.
; src/bosses/unicorn/unicorn_boss.nvgt:113:insert_last[0] ; src/bosses/unicorn/unicorn_boss.nvgt:113:insert_last[0]
msg.93894d088f6a=- Use your axe to destroy a bridge support msg.93894d088f6a=- Use your axe to destroy a bridge support
@@ -1138,7 +1141,7 @@ msg.93894d088f6a=- Use your axe to destroy a bridge support
msg.946466f6c963=stick msg.946466f6c963=stick
; seed:learn_sounds_label:sounds/game/boar.ogg ; seed:learn_sounds_label:sounds/game/boar.ogg
msg.957f554c342a=boar msg.957f554c342a=boar
; src/bosses/bandit_hideout.nvgt:857:insert_last[0] ; src/bosses/bandit_hideout.nvgt:856:insert_last[0]
msg.97c59037a63c=A survivor joins your base. msg.97c59037a63c=A survivor joins your base.
; src/item_registry.nvgt:item_plural:smoked_fish ; src/item_registry.nvgt:item_plural:smoked_fish
; src/item_registry.nvgt:item_singular:smoked_fish ; src/item_registry.nvgt:item_singular:smoked_fish
@@ -1147,7 +1150,7 @@ msg.995d4e06ae4e=smoked fish
msg.998b61086fa7=You picked up an item for which there is no specific sound. msg.998b61086fa7=You picked up an item for which there is no specific sound.
; src/quests/catch_the_boomerang_game.nvgt:19:insert_last[0] ; src/quests/catch_the_boomerang_game.nvgt:19:insert_last[0]
msg.9afdb7e61391=- Press Space to throw msg.9afdb7e61391=- Press Space to throw
; src/bosses/bandit_hideout.nvgt:865:text_reader_lines[1] ; src/bosses/bandit_hideout.nvgt:864:text_reader_lines[1]
msg.9b64062b2074=Bandit's Hideout msg.9b64062b2074=Bandit's Hideout
; seed:learn_sounds_label:sounds/enemies/bandit4.ogg ; seed:learn_sounds_label:sounds/enemies/bandit4.ogg
msg.9bbcfc6c8f9d=bandit4 msg.9bbcfc6c8f9d=bandit4
@@ -1182,7 +1185,7 @@ msg.a56be51786dc=bone1
msg.a570b024087d=Ever hungry, they feed upon your residents. msg.a570b024087d=Ever hungry, they feed upon your residents.
; src/quests/escape_from_hel_game.nvgt:15:insert_last[0] ; src/quests/escape_from_hel_game.nvgt:15:insert_last[0]
msg.a641f39a6317=- The run ends when you fall into a grave msg.a641f39a6317=- The run ends when you fall into a grave
; src/bosses/bandit_hideout.nvgt:798:insert_last[0] ; src/bosses/bandit_hideout.nvgt:797:insert_last[0]
msg.a646f0dcb735=Favor awarded: {arg1}. msg.a646f0dcb735=Favor awarded: {arg1}.
; src/item_registry.nvgt:item_plural:slings ; src/item_registry.nvgt:item_plural:slings
msg.a6caf0c3771d=slings msg.a6caf0c3771d=slings
@@ -1233,7 +1236,7 @@ msg.b73a0709297f=call pet
msg.b774102c89b5=Knives msg.b774102c89b5=Knives
; seed:learn_sounds_label:sounds/terrain/snow.ogg ; seed:learn_sounds_label:sounds/terrain/snow.ogg
msg.b94e9f3d7e00=snow msg.b94e9f3d7e00=snow
; src/bosses/bandit_hideout.nvgt:844:insert_last[0] ; src/bosses/bandit_hideout.nvgt:843:insert_last[0]
msg.ba3af68702a3=Learned Rune of Destruction! msg.ba3af68702a3=Learned Rune of Destruction!
; src/item_registry.nvgt:item_display:baskets_of_fruits_and_nuts ; src/item_registry.nvgt:item_display:baskets_of_fruits_and_nuts
msg.ba7d7dfe330b=Baskets of Fruits and Nuts msg.ba7d7dfe330b=Baskets of Fruits and Nuts
@@ -1250,7 +1253,7 @@ msg.bdebda25dad9=arrow hit
msg.bf93e5ce8bc1=Down msg.bf93e5ce8bc1=Down
; src/sound_settings.nvgt:26:learn_sounds_add_description[1] ; src/sound_settings.nvgt:26:learn_sounds_add_description[1]
msg.c01409b3a728=When casting release control when over water. When catching release when sound is over player. msg.c01409b3a728=When casting release control when over water. When catching release when sound is over player.
; src/bosses/bandit_hideout.nvgt:821:insert_last[0] ; src/bosses/bandit_hideout.nvgt:820:insert_last[0]
msg.c05feaca7269=No items were recovered. msg.c05feaca7269=No items were recovered.
; src/item_registry.nvgt:item_singular:snare ; src/item_registry.nvgt:item_singular:snare
msg.c1104d82c0bd=snare msg.c1104d82c0bd=snare
@@ -1266,7 +1269,7 @@ msg.c52888225c69=fall
msg.c65e5dfc5ba7=bandit1 msg.c65e5dfc5ba7=bandit1
; seed:learn_sounds_label:sounds/enemies/corpse_worm.ogg ; seed:learn_sounds_label:sounds/enemies/corpse_worm.ogg
msg.c6d56016b910=corpse worm msg.c6d56016b910=corpse worm
; src/bosses/bandit_hideout.nvgt:789:insert_last[0] ; src/bosses/bandit_hideout.nvgt:788:insert_last[0]
msg.c72b7844929d=Bandits defeated: {arg1}. msg.c72b7844929d=Bandits defeated: {arg1}.
; seed:learn_sounds_label:sounds/terrain/stream.ogg ; seed:learn_sounds_label:sounds/terrain/stream.ogg
msg.c82e3d7279ef=stream msg.c82e3d7279ef=stream
@@ -1278,7 +1281,7 @@ msg.c8da1c4f1714=Lowers in pitch as the fall progresses.
msg.c90399f41b3f=Meat msg.c90399f41b3f=Meat
; seed:learn_sounds_label:sounds/quests/bone7.ogg ; seed:learn_sounds_label:sounds/quests/bone7.ogg
msg.c9532ab465fb=bone7 msg.c9532ab465fb=bone7
; src/bosses/bandit_hideout.nvgt:808:insert_last[0] ; src/bosses/bandit_hideout.nvgt:807:insert_last[0]
msg.cb8b9677952a=Storage rewards: msg.cb8b9677952a=Storage rewards:
; src/item_registry.nvgt:item_plural:boar_carcasses ; src/item_registry.nvgt:item_plural:boar_carcasses
msg.cb8e70ab2bcc=boar carcasses msg.cb8e70ab2bcc=boar carcasses
@@ -1298,7 +1301,7 @@ msg.ce3897c6b3be={arg1}: {arg2}
msg.ce5aeca95fb9=item breaks msg.ce5aeca95fb9=item breaks
; src/i18n.nvgt:677:speak_with_history[0] ; src/i18n.nvgt:677:speak_with_history[0]
; src/pet_system.nvgt:936:speak_with_history[0] ; src/pet_system.nvgt:936:speak_with_history[0]
; src/save_system.nvgt:344:speak_with_history[0] ; src/save_system.nvgt:410:speak_with_history[0]
msg.cea88b214070={arg1} {arg2} msg.cea88b214070={arg1} {arg2}
; seed:manual ; seed:manual
msg.cfe2650b4c6e=Found a stone. msg.cfe2650b4c6e=Found a stone.
@@ -1327,7 +1330,7 @@ msg.d6dc139f529a=invasion
msg.d6fd3467a950=yeti1 msg.d6fd3467a950=yeti1
; src/item_registry.nvgt:item_display:axes ; src/item_registry.nvgt:item_display:axes
msg.d75f36f88797=Axes msg.d75f36f88797=Axes
; src/bosses/bandit_hideout.nvgt:836:insert_last[0] ; src/bosses/bandit_hideout.nvgt:835:insert_last[0]
msg.d7879bd506af=No livestock were recovered. msg.d7879bd506af=No livestock were recovered.
; src/quests/enchanted_melody_game.nvgt:31:insert_last[0] ; src/quests/enchanted_melody_game.nvgt:31:insert_last[0]
msg.da0b5a461d98=F or K - First note (lowest pitch) msg.da0b5a461d98=F or K - First note (lowest pitch)
@@ -1395,7 +1398,7 @@ msg.e5d79aba2048=undead resident1
msg.e5f656d506a7=zombie hit msg.e5f656d506a7=zombie hit
; src/quests/escape_from_hel_game.nvgt:8:insert_last[0] ; src/quests/escape_from_hel_game.nvgt:8:insert_last[0]
msg.e6526216c064=You have plundered the treasure, and the Draugr are displeased.! msg.e6526216c064=You have plundered the treasure, and the Draugr are displeased.!
; src/bosses/bandit_hideout.nvgt:847:insert_last[0] ; src/bosses/bandit_hideout.nvgt:846:insert_last[0]
msg.e65fa8960222=You have already mastered the Rune of Destruction. msg.e65fa8960222=You have already mastered the Rune of Destruction.
; seed:learn_sounds_label:sounds/quests/bone8.ogg ; seed:learn_sounds_label:sounds/quests/bone8.ogg
msg.e6ae58426f13=bone8 msg.e6ae58426f13=bone8
@@ -1405,9 +1408,9 @@ msg.e7409fcb54bc=feed fire
msg.e8002c169040=wood msg.e8002c169040=wood
; src/quests/escape_from_hel_game.nvgt:13:insert_last[0] ; src/quests/escape_from_hel_game.nvgt:13:insert_last[0]
msg.e8216720d30b=- Press SPACE to jump over graves msg.e8216720d30b=- Press SPACE to jump over graves
; draugnorak.nvgt:133:insert_last[0] ; draugnorak.nvgt:134:insert_last[0]
msg.e8db3a42f86f=Learn Sounds msg.e8db3a42f86f=Learn Sounds
; src/bosses/bandit_hideout.nvgt:845:insert_last[0] ; src/bosses/bandit_hideout.nvgt:844:insert_last[0]
msg.e8e8f4ee7a5a=You can now engrave weapons with this rune at the crafting menu. msg.e8e8f4ee7a5a=You can now engrave weapons with this rune at the crafting menu.
; src/bosses/bandit_hideout.nvgt:302:insert_last[0] ; src/bosses/bandit_hideout.nvgt:302:insert_last[0]
msg.e9dd1d268b65=The base lies far to the east, guarded by a barricade. msg.e9dd1d268b65=The base lies far to the east, guarded by a barricade.
@@ -1436,7 +1439,7 @@ msg.f3d964225e83=player female damage
msg.f5cabf873590=reed msg.f5cabf873590=reed
; src/bosses/bandit_hideout.nvgt:301:insert_last[0] ; src/bosses/bandit_hideout.nvgt:301:insert_last[0]
msg.f77fc018cb7b=You find a hidden bandit base deep in the forest. msg.f77fc018cb7b=You find a hidden bandit base deep in the forest.
; draugnorak.nvgt:149:insert_last[0] ; draugnorak.nvgt:150:insert_last[0]
msg.f83b6fe3aebf=Exit msg.f83b6fe3aebf=Exit
; src/inventory_items.nvgt:504:speak_with_history[0] ; src/inventory_items.nvgt:504:speak_with_history[0]
; src/quests/skeletal_bard_game.nvgt:26:speak_with_history[0] ; src/quests/skeletal_bard_game.nvgt:26:speak_with_history[0]
+2
View File
@@ -14,6 +14,8 @@ load_game.delete_save_failed=No se pudo eliminar la partida.
load_game.save_deleted=Partida eliminada. load_game.save_deleted=Partida eliminada.
load_game.no_saves_found=No hay partidas guardadas. load_game.no_saves_found=No hay partidas guardadas.
load_game.unable_to_load=No se pudo cargar la partida. load_game.unable_to_load=No se pudo cargar la partida.
load_game.invalid_data=El archivo de guardado está incompleto o contiene datos no válidos.
load_game.unsupported_version=Esta partida se creó con una versión del juego no compatible.
load_game.select_prompt=Cargar partida. Selecciona personaje. Presiona Suprimir para borrar una partida. load_game.select_prompt=Cargar partida. Selecciona personaje. Presiona Suprimir para borrar una partida.
main_menu.prompt=Draugnorak. Menu principal. main_menu.prompt=Draugnorak. Menu principal.
game.new_game_started=Partida nueva iniciada. game.new_game_started=Partida nueva iniciada.
+2
View File
@@ -524,6 +524,8 @@ def write_catalog(entries: Dict[str, Dict[str, object]], output_path: Path) -> N
("system.load_game.save_deleted", "Save deleted."), ("system.load_game.save_deleted", "Save deleted."),
("system.load_game.no_saves_found", "No saves found."), ("system.load_game.no_saves_found", "No saves found."),
("system.load_game.unable_to_load", "Unable to load save."), ("system.load_game.unable_to_load", "Unable to load save."),
("system.load_game.invalid_data", "Save file is incomplete or contains invalid data."),
("system.load_game.unsupported_version", "This save was created by an unsupported game version."),
("system.load_game.select_prompt", "Load game. Select character. Press delete to remove a save."), ("system.load_game.select_prompt", "Load game. Select character. Press delete to remove a save."),
("system.quick_slot.no_item_bound", "No item bound to slot {slot}."), ("system.quick_slot.no_item_bound", "No item bound to slot {slot}."),
("system.menu.closed", "Closed."), ("system.menu.closed", "Closed."),
+1 -2
View File
@@ -79,8 +79,6 @@ void run_adventure_menu(int player_x) {
play_menu_select_sound(); play_menu_select_sound();
start_adventure(adventure_ids[selection]); start_adventure(adventure_ids[selection]);
// Adventure has finished (it blocks until done) // Adventure has finished (it blocks until done)
// Resume main game ambience
update_ambience(true);
return; return;
} }
} }
@@ -95,4 +93,5 @@ void start_adventure(int adventure_id) {
run_bandit_hideout_adventure(); run_bandit_hideout_adventure();
} }
menuBackgroundUpdatesEnabled = true; menuBackgroundUpdatesEnabled = true;
restore_world_audio();
} }
+3 -4
View File
@@ -286,12 +286,12 @@ void cleanup_bandit_hideout_adventure() {
clear_hideout_bandits(); clear_hideout_bandits();
end_pet_adventure(); end_pet_adventure();
reset_adventure_combat_state(); reset_adventure_combat_state();
p.destroy_all(); reset_game_sound_pool();
} }
void run_bandit_hideout_adventure() { void run_bandit_hideout_adventure() {
// Stop main game sounds // Stop main game sounds
p.destroy_all(); reset_game_sound_pool();
init_bandit_hideout_adventure(); init_bandit_hideout_adventure();
@@ -317,13 +317,12 @@ void run_bandit_hideout_adventure() {
if (key_pressed(KEY_BACK)) { if (key_pressed(KEY_BACK)) {
adventurePaused = !adventurePaused; adventurePaused = !adventurePaused;
set_gameplay_timers_paused(adventurePaused);
if (adventurePaused) { if (adventurePaused) {
p.pause_all(); p.pause_all();
speak_with_history(tr("system.game.paused_prompt"), true); speak_with_history(tr("system.game.paused_prompt"), true);
} else { } else {
p.resume_all(); p.resume_all();
restart_hideout_adventure_timers();
restart_all_timers();
speak_with_history(tr("system.game.resumed"), true); speak_with_history(tr("system.game.resumed"), true);
} }
continue; continue;
+3 -4
View File
@@ -84,7 +84,7 @@ void init_unicorn_adventure() {
} }
void cleanup_unicorn_adventure() { void cleanup_unicorn_adventure() {
p.destroy_all(); reset_game_sound_pool();
reset_adventure_combat_state(); reset_adventure_combat_state();
end_pet_adventure(); end_pet_adventure();
if (unicorn.sound_handle != -1) { if (unicorn.sound_handle != -1) {
@@ -95,7 +95,7 @@ void cleanup_unicorn_adventure() {
void run_unicorn_adventure() { void run_unicorn_adventure() {
// Stop main game sounds // Stop main game sounds
p.destroy_all(); reset_game_sound_pool();
init_unicorn_adventure(); init_unicorn_adventure();
@@ -130,13 +130,12 @@ void run_unicorn_adventure() {
if (key_pressed(KEY_BACK)) { if (key_pressed(KEY_BACK)) {
adventurePaused = !adventurePaused; adventurePaused = !adventurePaused;
set_gameplay_timers_paused(adventurePaused);
if (adventurePaused) { if (adventurePaused) {
p.pause_all(); p.pause_all();
speak_with_history(tr("system.game.paused_prompt"), true); speak_with_history(tr("system.game.paused_prompt"), true);
} else { } else {
p.resume_all(); p.resume_all();
restart_unicorn_adventure_timers();
restart_all_timers();
speak_with_history(tr("system.game.resumed"), true); speak_with_history(tr("system.game.resumed"), true);
} }
continue; continue;
+136
View File
@@ -0,0 +1,136 @@
// Keep elapsed gameplay progress when pausing in the world or either adventure.
timer@[] get_gameplay_timers() {
timer@[] timers = {
@walktimer, @jumptimer, @search_timer, @search_delay_timer, @attack_timer,
@fall_timer, @climb_timer, @rope_climb_timer, @fire_damage_timer, @healing_timer,
@sling_charge_timer, @bow_draw_timer, @bow_shot_timer, @fishing_timer, @cast_move_timer,
@blessing_speed_timer, @blessing_resident_timer, @blessing_search_timer,
@hour_timer, @crossfade_timer, @wind_gust_timer, @rain_fade_timer, @thunder_timer,
@corpse_worm_spawn_timer, @resident_combat_timer, @movementBlockTimer, @ammoBlockTimer,
@fylgjaChargeTimer, @petAttackTimer, @petRetrieveTimer, @petTravelTimer,
@notificationsDelayTimer, @arena_jump_timer, @arena_walk_timer, @arena_attack_timer,
@unicorn.move_timer, @hideoutWalkTimer, @hideoutAttackTimer, @hideoutSearchTimer,
@hideoutSearchDelayTimer, @hideoutJumpTimer
};
for (uint i = 0; i < trees.length(); i++)
timers.insert_last(@trees[i].regen_timer);
for (uint i = 0; i < world_fires.length(); i++)
timers.insert_last(@world_fires[i].fuel_timer);
for (uint i = 0; i < world_snares.length(); i++)
timers.insert_last(@world_snares[i].hour_timer);
for (uint i = 0; i < undeads.length(); i++) {
timers.insert_last(@undeads[i].move_timer);
timers.insert_last(@undeads[i].attack_timer);
}
for (uint i = 0; i < bandits.length(); i++) {
timers.insert_last(@bandits[i].move_timer);
timers.insert_last(@bandits[i].attack_timer);
timers.insert_last(@bandits[i].wander_direction_timer);
}
for (uint i = 0; i < ground_games.length(); i++) {
timers.insert_last(@ground_games[i].move_timer);
timers.insert_last(@ground_games[i].attack_timer);
}
for (uint i = 0; i < corpse_worms.length(); i++) {
timers.insert_last(@corpse_worms[i].move_timer);
timers.insert_last(@corpse_worms[i].attack_timer);
}
for (uint i = 0; i < flying_creatures.length(); i++) {
timers.insert_last(@flying_creatures[i].move_timer);
timers.insert_last(@flying_creatures[i].fall_timer);
timers.insert_last(@flying_creatures[i].fade_timer);
}
for (uint i = 0; i < active_thunder.length(); i++)
timers.insert_last(@active_thunder[i].movement_timer);
for (uint i = 0; i < hideoutBandits.length(); i++) {
timers.insert_last(@hideoutBandits[i].moveTimer);
timers.insert_last(@hideoutBandits[i].attackTimer);
}
return timers;
}
void set_gameplay_timers_paused(bool paused) {
timer@[] timers = get_gameplay_timers();
for (uint i = 0; i < timers.length(); i++)
timers[i].set_paused(paused);
}
// Every pool-wide destruction must invalidate the handles retained by owners.
void reset_game_sound_pool() {
p.destroy_all();
day_sound_handle = -1;
night_sound_handle = -1;
fall_sound_handle = -1;
rope_climb_sound_handle = -1;
sling_sound_handle = -1;
bow_shot_sound_handle = -1;
cast_sound_handle = -1;
fylgjaSoundHandle = -1;
currentFylgjaSound = "";
petEventSoundHandle = -1;
petTravelSoundHandle = -1;
playerItemBreakSoundHandle = -1;
wind_sound_handle = -1;
rain_sound_handle = -1;
for (uint i = 0; i < active_thunder.length(); i++)
active_thunder[i].sound_handle = -1;
active_thunder.resize(0);
for (uint i = 0; i < trees.length(); i++)
trees[i].sound_handle = -1;
for (uint i = 0; i < world_fires.length(); i++)
world_fires[i].sound_handle = -1;
for (uint i = 0; i < world_snares.length(); i++)
world_snares[i].sound_handle = -1;
for (uint i = 0; i < world_streams.length(); i++) {
world_streams[i].sound_handle = -1;
world_streams[i].sound_position = -1;
}
for (uint i = 0; i < world_mountains.length(); i++) {
world_mountains[i].stream_sound_handle = -1;
world_mountains[i].stream_sound_position = -1;
}
for (uint i = 0; i < world_drops.length(); i++)
world_drops[i].sound_handle = -1;
for (uint i = 0; i < undeads.length(); i++) {
undeads[i].sound_handle = -1;
undeads[i].in_weapon_range = false;
}
for (uint i = 0; i < bandits.length(); i++) {
bandits[i].sound_handle = -1;
bandits[i].in_weapon_range = false;
}
for (uint i = 0; i < ground_games.length(); i++) {
ground_games[i].sound_handle = -1;
ground_games[i].in_weapon_range = false;
}
for (uint i = 0; i < corpse_worms.length(); i++) {
corpse_worms[i].sound_handle = -1;
corpse_worms[i].in_weapon_range = false;
}
for (uint i = 0; i < flying_creatures.length(); i++) {
flying_creatures[i].sound_handle = -1;
flying_creatures[i].fall_sound_handle = -1;
flying_creatures[i].in_weapon_range = false;
}
unicorn.sound_handle = -1;
current_unicorn_sound = "";
unicorn_in_weapon_range = false;
for (uint i = 0; i < hideoutBandits.length(); i++) {
hideoutBandits[i].soundHandle = -1;
hideoutBandits[i].inWeaponRange = false;
}
}
void restore_world_audio() {
p.update_listener_1d(x);
crossfade_active = false;
is_daytime = is_day_hour(current_hour);
update_ambience(true);
rain_fading = false;
safe_destroy_sound(rain_sound_handle);
if (rain_intensity > INTENSITY_NONE) {
rain_sound_handle = p.play_stationary(RAIN_SOUND, true);
if (rain_sound_handle != -1)
p.update_sound_start_values(rain_sound_handle, 0.0, get_rain_target_volume(rain_intensity), 1.0);
}
}
-17
View File
@@ -99,20 +99,3 @@ int last_adventure_day = -1;
bool game_paused = false; bool game_paused = false;
bool menuBackgroundUpdatesEnabled = true; bool menuBackgroundUpdatesEnabled = true;
bool return_to_main_menu_requested = false; bool return_to_main_menu_requested = false;
// Restart all game timers when resuming from pause
// Prevents time from accumulating while paused
void restart_all_timers() {
// Player timers
fire_damage_timer.restart();
healing_timer.restart();
sling_charge_timer.restart();
bow_draw_timer.restart();
bow_shot_timer.restart();
fylgjaChargeTimer.restart();
// Fire fuel timers
for (uint i = 0; i < world_fires.length(); i++) {
world_fires[i].fuel_timer.restart();
}
}
+90 -25
View File
@@ -3,6 +3,7 @@
#include "libstorm-nvgt/dict_utils.nvgt" #include "libstorm-nvgt/dict_utils.nvgt"
#include "libstorm-nvgt/name_sanitize.nvgt" #include "libstorm-nvgt/name_sanitize.nvgt"
#include "libstorm-nvgt/save_utils.nvgt" #include "libstorm-nvgt/save_utils.nvgt"
#include "src/save_validation.nvgt"
const string SAVE_EXTENSION = ".dat"; const string SAVE_EXTENSION = ".dat";
const string SAVE_ENCRYPTION_KEY = "draugnorak_save_v1"; const string SAVE_ENCRYPTION_KEY = "draugnorak_save_v1";
@@ -112,7 +113,7 @@ void migrate_legacy_save_files_if_needed() {
continue; continue;
string targetPath = resolve_save_path(legacyName); string targetPath = resolve_save_path(legacyName);
if (file_exists(targetPath)) if (file_exists(targetPath) || file_exists(targetPath + ".bak"))
continue; continue;
file legacyFile; file legacyFile;
@@ -130,6 +131,15 @@ void migrate_legacy_save_files_if_needed() {
string[] get_save_files() { string[] get_save_files() {
migrate_legacy_save_files_if_needed(); migrate_legacy_save_files_if_needed();
// A replacement may have stopped after moving the previous save aside.
string[] @backups = glob(resolve_save_path("*" + SAVE_EXTENSION + ".bak"));
if (@backups != null) {
for (uint i = 0; i < backups.length(); i++) {
string backupPath = backups[i];
recover_interrupted_save(backupPath.substr(0, backupPath.length() - 4));
}
}
string[] result; string[] result;
string[] @items = glob(resolve_save_path("*" + SAVE_EXTENSION)); string[] @items = glob(resolve_save_path("*" + SAVE_EXTENSION));
if (@items == null) if (@items == null)
@@ -165,7 +175,63 @@ string decrypt_save_data(const string& in encryptedData) {
} }
bool save_data(const string& in filename, const string& in data) { bool save_data(const string& in filename, const string& in data) {
return save_string_file(resolve_save_path(filename), data); last_save_error = "";
string targetPath = resolve_save_path(filename);
string stagedPath = targetPath + ".tmp";
string backupPath = targetPath + ".bak";
if (filename == "" || data.length() == 0 || directory_exists(targetPath) ||
!recover_interrupted_save(filename)) {
return save_write_failed(targetPath);
}
// The shared writer only touches staging; the previous save stays intact.
string stagedData;
if (!save_string_file(stagedPath, data) || !read_string_file(stagedPath, stagedData) || stagedData != data) {
return save_write_failed(targetPath);
}
bool hadSave = file_exists(targetPath);
if (hadSave) {
if (directory_exists(backupPath) || (file_exists(backupPath) && !file_delete(backupPath)) ||
!file_move(targetPath, backupPath)) {
return save_write_failed(targetPath);
}
}
if (!file_move(stagedPath, targetPath)) {
if (hadSave)
recover_interrupted_save(filename);
return save_write_failed(targetPath);
}
return true;
}
bool save_write_failed(const string& in path) {
last_save_error = i18n_text("Failed to save file") + "\n" + path;
return false;
}
bool recover_interrupted_save(const string& in filename) {
string targetPath = resolve_save_path(filename);
if (file_exists(targetPath))
return true;
string backupPath = targetPath + ".bak";
if (!file_exists(backupPath))
return true;
return file_move(backupPath, targetPath);
}
bool delete_save_file(const string& in filename) {
string targetPath = resolve_save_path(filename);
string backupPath = targetPath + ".bak";
// Remove the recovery copy first so a deleted character cannot reappear.
if (file_exists(backupPath) && !file_delete(backupPath))
return false;
return file_delete(targetPath);
}
bool save_game_with_feedback() {
if (save_game_state())
return true;
ui_info_box(tr("system.ui.window_title"), i18n_text("Failed to save file"), last_save_error);
return false;
} }
bool read_file_string(const string& in filename, string& out data) { bool read_file_string(const string& in filename, string& out data) {
@@ -472,7 +538,7 @@ bool select_save_file(string& out filename) {
} }
int confirm = ui_question("", prompt); int confirm = ui_question("", prompt);
if (confirm == 1) { if (confirm == 1) {
if (file_delete(resolve_save_path(files[selection]))) { if (delete_save_file(files[selection])) {
speak_with_history(tr("system.load_game.save_deleted"), true); speak_with_history(tr("system.load_game.save_deleted"), true);
refreshList = true; refreshList = true;
break; break;
@@ -561,6 +627,8 @@ void clear_world_objects() {
} }
void reset_game_state() { void reset_game_state() {
set_gameplay_timers_paused(false);
game_paused = false;
stop_active_sounds(); stop_active_sounds();
clear_world_objects(); clear_world_objects();
@@ -721,7 +789,7 @@ void start_new_game() {
current_save_file = get_save_filename_for_name(player_name); current_save_file = get_save_filename_for_name(player_name);
} }
queue_current_goal_announcement(false); queue_current_goal_announcement(false);
save_game_state(); save_game_with_feedback();
} }
string serialize_bool(bool value) { string serialize_bool(bool value) {
@@ -830,7 +898,7 @@ void deserialize_inventory_array(const string& in data, int[] @arr, int expected
if (data.length() == 0) if (data.length() == 0)
return; return;
string[] @parts = data.split(","); string[] parts = split_save_record(data, ",");
uint count = parts.length(); uint count = parts.length();
if (count > uint(expected_size)) if (count > uint(expected_size))
count = uint(expected_size); count = uint(expected_size);
@@ -840,6 +908,7 @@ void deserialize_inventory_array(const string& in data, int[] @arr, int expected
} }
bool save_game_state() { bool save_game_state() {
last_save_error = "";
dictionary saveData; dictionary saveData;
saveData.set("version", SAVE_VERSION); saveData.set("version", SAVE_VERSION);
@@ -1092,6 +1161,10 @@ bool save_game_state() {
bool load_game_state_from_file(const string& in filename) { bool load_game_state_from_file(const string& in filename) {
last_save_error = ""; last_save_error = "";
string resolvedFilename = resolve_save_path(filename); string resolvedFilename = resolve_save_path(filename);
if (!recover_interrupted_save(filename)) {
last_save_error = tr("system.load_game.unable_to_load");
return false;
}
if (!file_exists(resolvedFilename)) { if (!file_exists(resolvedFilename)) {
last_save_error = "No save file found."; last_save_error = "No save file found.";
return false; return false;
@@ -1112,16 +1185,8 @@ bool load_game_state_from_file(const string& in filename) {
last_save_error = "Save file is corrupted or unreadable."; last_save_error = "Save file is corrupted or unreadable.";
return false; return false;
} }
double version; if (!validate_save_data(saveData))
bool has_version = saveData.get("version", version); return false;
if (!has_version) {
if (!has_number_key(saveData, "player_x") || !has_number_key(saveData, "player_health") ||
!has_number_key(saveData, "time_current_day")) {
last_save_error = "Save file is missing required data.";
return false;
}
version = 0;
}
reset_game_state(); reset_game_state();
current_save_file = get_path_basename(filename); current_save_file = get_path_basename(filename);
@@ -1656,7 +1721,7 @@ bool load_game_state_from_file(const string& in filename) {
string[] treeData = get_string_list_or_split(saveData, "trees_data"); string[] treeData = get_string_list_or_split(saveData, "trees_data");
for (uint i = 0; i < treeData.length(); i++) { for (uint i = 0; i < treeData.length(); i++) {
string[] @parts = treeData[i].split("|"); string[] parts = split_save_record(treeData[i]);
if (parts.length() < 8) if (parts.length() < 8)
continue; continue;
@@ -1676,7 +1741,7 @@ bool load_game_state_from_file(const string& in filename) {
string[] snareData = get_string_list_or_split(saveData, "snares_data"); string[] snareData = get_string_list_or_split(saveData, "snares_data");
for (uint i = 0; i < snareData.length(); i++) { for (uint i = 0; i < snareData.length(); i++) {
string[] @parts = snareData[i].split("|"); string[] parts = split_save_record(snareData[i]);
if (parts.length() < 6) if (parts.length() < 6)
continue; continue;
@@ -1697,7 +1762,7 @@ bool load_game_state_from_file(const string& in filename) {
string[] fireData = get_string_list_or_split(saveData, "fires_data"); string[] fireData = get_string_list_or_split(saveData, "fires_data");
for (uint i = 0; i < fireData.length(); i++) { for (uint i = 0; i < fireData.length(); i++) {
string[] @parts = fireData[i].split("|"); string[] parts = split_save_record(fireData[i]);
if (parts.length() < 3) if (parts.length() < 3)
continue; continue;
@@ -1763,7 +1828,7 @@ bool load_game_state_from_file(const string& in filename) {
string[] streamData = get_string_list_or_split(saveData, "streams_data"); string[] streamData = get_string_list_or_split(saveData, "streams_data");
for (uint i = 0; i < streamData.length(); i++) { for (uint i = 0; i < streamData.length(); i++) {
string[] @parts = streamData[i].split("|"); string[] parts = split_save_record(streamData[i]);
if (parts.length() < 2) if (parts.length() < 2)
continue; continue;
@@ -1776,7 +1841,7 @@ bool load_game_state_from_file(const string& in filename) {
string[] banditData = get_string_list_or_split(saveData, "bandits_data"); string[] banditData = get_string_list_or_split(saveData, "bandits_data");
for (uint i = 0; i < banditData.length(); i++) { for (uint i = 0; i < banditData.length(); i++) {
string[] @parts = banditData[i].split("|"); string[] parts = split_save_record(banditData[i]);
if (parts.length() < 6) if (parts.length() < 6)
continue; continue;
@@ -1838,7 +1903,7 @@ bool load_game_state_from_file(const string& in filename) {
string[] mountainData = get_string_list_or_split(saveData, "mountains_data"); string[] mountainData = get_string_list_or_split(saveData, "mountains_data");
for (uint i = 0; i < mountainData.length(); i++) { for (uint i = 0; i < mountainData.length(); i++) {
string[] @parts = mountainData[i].split("|"); string[] parts = split_save_record(mountainData[i]);
if (parts.length() < 5) if (parts.length() < 5)
continue; continue;
@@ -1852,14 +1917,14 @@ bool load_game_state_from_file(const string& in filename) {
mountain.end_position = end_pos; mountain.end_position = end_pos;
// Parse elevations // Parse elevations
string[] @elev_parts = parts[2].split(","); string[] elev_parts = split_save_record(parts[2], ",");
mountain.elevations.resize(elev_parts.length()); mountain.elevations.resize(elev_parts.length());
for (uint j = 0; j < elev_parts.length(); j++) { for (uint j = 0; j < elev_parts.length(); j++) {
mountain.elevations[j] = parse_int(elev_parts[j]); mountain.elevations[j] = parse_int(elev_parts[j]);
} }
// Parse terrain types // Parse terrain types
string[] @terrain_parts = parts[3].split(","); string[] terrain_parts = split_save_record(parts[3], ",");
mountain.terrain_types.resize(terrain_parts.length()); mountain.terrain_types.resize(terrain_parts.length());
for (uint j = 0; j < terrain_parts.length(); j++) { for (uint j = 0; j < terrain_parts.length(); j++) {
mountain.terrain_types[j] = terrain_parts[j]; mountain.terrain_types[j] = terrain_parts[j];
@@ -1867,7 +1932,7 @@ bool load_game_state_from_file(const string& in filename) {
// Parse stream positions // Parse stream positions
if (parts[4].length() > 0) { if (parts[4].length() > 0) {
string[] @stream_parts = parts[4].split(","); string[] stream_parts = split_save_record(parts[4], ",");
for (uint j = 0; j < stream_parts.length(); j++) { for (uint j = 0; j < stream_parts.length(); j++) {
mountain.stream_positions.insert_last(parse_int(stream_parts[j])); mountain.stream_positions.insert_last(parse_int(stream_parts[j]));
} }
@@ -1878,7 +1943,7 @@ bool load_game_state_from_file(const string& in filename) {
string[] dropData = get_string_list_or_split(saveData, "drops_data"); string[] dropData = get_string_list_or_split(saveData, "drops_data");
for (uint i = 0; i < dropData.length(); i++) { for (uint i = 0; i < dropData.length(); i++) {
string[] @parts = dropData[i].split("|"); string[] parts = split_save_record(dropData[i]);
if (parts.length() < 2) if (parts.length() < 2)
continue; continue;
add_world_drop(parse_int(parts[0]), parts[1]); add_world_drop(parse_int(parts[0]), parts[1]);
+159
View File
@@ -0,0 +1,159 @@
// Validate before resetting the active character or allocating loaded arrays.
string[] split_save_record(const string& in encoded, const string& in delimiter = "|") {
string[] columns;
uint start = 0;
int separator = encoded.find(delimiter, start);
while (separator >= 0) {
columns.insert_last(encoded.substr(start, uint(separator) - start));
start = uint(separator) + delimiter.length();
separator = encoded.find(delimiter, start);
}
columns.insert_last(encoded.substr(start));
return columns;
}
bool save_integer_in_range(dictionary@ data, const string& in key, int minimum, int maximum, bool required = true) {
if (!data.exists(key))
return !required;
double value;
if (!data.get(key, value) || !(value >= minimum && value <= maximum))
return false;
return value == int(value);
}
bool save_integer_text_in_range(const string& in text, int minimum, int maximum) {
if (text.length() == 0 || text.length() > 11)
return false;
uint start = text.substr(0, 1) == "-" ? 1 : 0;
if (start == text.length())
return false;
for (uint i = start; i < text.length(); i++) {
string ch = text.substr(i, 1);
if (ch < "0" || ch > "9")
return false;
}
int64 value = parse_int(text);
return value >= minimum && value <= maximum;
}
bool invalid_save_data() {
last_save_error = tr("system.load_game.invalid_data");
return false;
}
bool validate_save_data(dictionary@ data) {
if (data.exists("version") && !save_integer_in_range(data, "version", 0, SAVE_VERSION)) {
last_save_error = tr("system.load_game.unsupported_version");
return false;
}
int version = int(get_number(data, "version", 0));
if (!save_integer_in_range(data, "world_map_size", 35, 2147483647, version >= 3))
return invalid_save_data();
int mapSize = int(get_number(data, "world_map_size", 35));
if (!save_integer_in_range(data, "player_x", 0, mapSize - 1) ||
!save_integer_in_range(data, "player_health", 0, 2147483647) ||
!save_integer_in_range(data, "time_current_day", 1, 2147483647) ||
!save_integer_in_range(data, "player_y", 0, 2147483647, version >= 3) ||
!save_integer_in_range(data, "player_facing", 0, 1, version >= 3) ||
!save_integer_in_range(data, "player_base_health", 1, 2147483647, version >= 3) ||
!save_integer_in_range(data, "player_max_health", 1, 2147483647, version >= 3) ||
!save_integer_in_range(data, "time_current_hour", 0, 23, version >= 3)) {
return invalid_save_data();
}
string[] inventoryKeys = {"personal_inventory", "storage_inventory"};
for (uint i = 0; i < inventoryKeys.length(); i++) {
if (!data.exists(inventoryKeys[i]) && version < 3)
continue;
string encoded;
if (!data.get(inventoryKeys[i], encoded))
return invalid_save_data();
string[] counts = split_save_record(encoded, ",");
// Great spears were appended to the version-3 registry after its release.
if (counts.length() < uint(ITEM_BASKET_FOOD + 1) || counts.length() > uint(ITEM_COUNT))
return invalid_save_data();
for (uint j = 0; j < counts.length(); j++) {
if (!save_integer_text_in_range(counts[j], 0, 2147483647))
return invalid_save_data();
}
}
string[] listKeys = {
"world_expanded_terrain_types", "trees_data", "snares_data", "fires_data",
"firepits_positions", "herb_gardens_positions", "storages_positions", "altars_positions",
"streams_data", "bandits_data", "mountains_data", "drops_data"
};
for (uint i = 0; i < listKeys.length(); i++) {
string encoded;
string[] records;
if (!data.get(listKeys[i], encoded) && !data.get(listKeys[i], records) &&
(data.exists(listKeys[i]) || version >= 3)) {
return invalid_save_data();
}
}
string[] terrain = get_string_list_or_split(data, "world_expanded_terrain_types");
if (terrain.length() != uint(mapSize - 35))
return invalid_save_data();
if (!save_integer_in_range(data, "world_expanded_area_start", mapSize == 35 ? -1 : 35,
mapSize == 35 ? -1 : 35, version >= 3) ||
!save_integer_in_range(data, "world_expanded_area_end", mapSize == 35 ? -1 : mapSize - 1,
mapSize == 35 ? -1 : mapSize - 1, version >= 3)) {
return invalid_save_data();
}
string[] positionKeys = {"firepits_positions", "herb_gardens_positions", "storages_positions",
"altars_positions", "pastures_positions", "stables_positions"};
for (uint i = 0; i < positionKeys.length(); i++) {
string[] positions = get_string_list_or_split(data, positionKeys[i]);
for (uint j = 0; j < positions.length(); j++) {
if (!save_integer_text_in_range(positions[j], 0, mapSize - 1))
return invalid_save_data();
}
}
string[] objectKeys = {"trees_data", "snares_data", "fires_data", "streams_data", "bandits_data", "drops_data"};
int[] minimumColumns = {8, 6, 3, 2, 6, 2};
for (uint i = 0; i < objectKeys.length(); i++) {
string[] records = get_string_list_or_split(data, objectKeys[i]);
for (uint j = 0; j < records.length(); j++) {
string[] columns = split_save_record(records[j]);
// Missed arrows in existing saves can have positions beyond map edges.
int minimumPosition = objectKeys[i] == "drops_data" ? -2147483647 : 0;
int maximumPosition = objectKeys[i] == "drops_data" ? 2147483647 : mapSize - 1;
if (columns.length() < uint(minimumColumns[i]) ||
!save_integer_text_in_range(columns[0], minimumPosition, maximumPosition)) {
return invalid_save_data();
}
if (objectKeys[i] == "streams_data" &&
!save_integer_text_in_range(columns[1], 1, mapSize - int(parse_int(columns[0])))) {
return invalid_save_data();
}
}
}
string[] mountains = get_string_list_or_split(data, "mountains_data");
for (uint i = 0; i < mountains.length(); i++) {
string[] columns = split_save_record(mountains[i]);
if (columns.length() < 5 || !save_integer_text_in_range(columns[0], 35, mapSize - 1) ||
!save_integer_text_in_range(columns[1], int(parse_int(columns[0])), mapSize - 1)) {
return invalid_save_data();
}
int size = int(parse_int(columns[1]) - parse_int(columns[0]) + 1);
string[] elevations = split_save_record(columns[2], ",");
string[] terrainTypes = split_save_record(columns[3], ",");
if (elevations.length() != uint(size) || terrainTypes.length() != uint(size))
return invalid_save_data();
for (uint j = 0; j < elevations.length(); j++) {
if (!save_integer_text_in_range(elevations[j], 0, MOUNTAIN_MAX_ELEVATION))
return invalid_save_data();
}
if (columns[4] != "") {
string[] streams = split_save_record(columns[4], ",");
for (uint j = 0; j < streams.length(); j++) {
if (!save_integer_text_in_range(streams[j], 0, size - 1))
return invalid_save_data();
}
}
}
return true;
}
+1 -1
View File
@@ -996,7 +996,7 @@ void update_time() {
attempt_resident_fish_smoking(); attempt_resident_fish_smoking();
attempt_livestock_production(); attempt_livestock_production();
if (current_hour == DAY_START_HOUR) { if (current_hour == DAY_START_HOUR) {
save_game_state(); save_game_with_feedback();
} }
} }