A few pre-release bug fixes.
This commit is contained in:
1 parent
b07f675533
commit
949aab1bcf
16 files changed
+496
-103
No files matched your search
@@ -1,3 +1,42 @@
|
||||
# Draugnorak
|
||||
|
||||
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
@@ -49,6 +49,7 @@ sound_pool p(300);
|
||||
#include "src/learn_sounds.nvgt"
|
||||
#include "src/intro.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 int MAIN_MENU_MUSIC_FADE_IN_MS = 1;
|
||||
@@ -275,13 +276,12 @@ void run_game() {
|
||||
// Pause toggle
|
||||
if (key_pressed(KEY_BACK)) {
|
||||
game_paused = !game_paused;
|
||||
set_gameplay_timers_paused(game_paused);
|
||||
if (game_paused) {
|
||||
p.pause_all();
|
||||
speak_with_history(tr("system.game.paused_prompt"), true);
|
||||
} else {
|
||||
p.resume_all();
|
||||
// Restart all timers to prevent time from accumulating while paused
|
||||
restart_all_timers();
|
||||
speak_with_history(tr("system.game.resumed"), true);
|
||||
}
|
||||
continue;
|
||||
|
||||
@@ -205,5 +205,7 @@ Current categories:
|
||||
|
||||
## Saving and Loading
|
||||
- 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
|
||||
- In the Load Game menu press Delete to remove the selected save (confirmation required)
|
||||
@@ -14,6 +14,8 @@ load_game.delete_save_failed=Kon nie stoorlêer uitvee nie.
|
||||
load_game.save_deleted=Stoorlêer uitgevee.
|
||||
load_game.no_saves_found=Geen stoorleers gevind 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.
|
||||
main_menu.prompt=Draugnorak. Hoofkieslys.
|
||||
game.new_game_started=Nuwe speletjie begin.
|
||||
|
||||
+27
-24
@@ -33,6 +33,8 @@ load_game.delete_save_failed=Unable to delete save.
|
||||
load_game.save_deleted=Save deleted.
|
||||
load_game.no_saves_found=No saves found.
|
||||
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.
|
||||
quick_slot.no_item_bound=No item bound to slot {slot}.
|
||||
menu.closed=Closed.
|
||||
@@ -708,7 +710,7 @@ msg.041d8413712d=A crash log was written to your save data folder.
|
||||
msg.04a8916a9494=vampyr1
|
||||
; src/item_registry.nvgt:item_plural: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 ===
|
||||
; seed:learn_sounds_label:sounds/enemies/corpse_worm_dies.ogg
|
||||
msg.06c9fefbd6d7=corpse worm dies
|
||||
@@ -749,12 +751,13 @@ msg.0f9af12cbb82=skin pouch
|
||||
; src/item_registry.nvgt:item_display:logs
|
||||
msg.126dd3b70a5c=Logs
|
||||
; 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
|
||||
; src/item_registry.nvgt:item_plural:quivers
|
||||
msg.145ba8a7a0f2=quivers
|
||||
; src/item_registry.nvgt:item_display: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.
|
||||
; seed:learn_sounds_label:sounds/game/game_falls.ogg
|
||||
msg.16134a4250f7=game falls
|
||||
@@ -782,7 +785,7 @@ msg.1bfa6bc809d4==== Bandit's Hideout ===
|
||||
msg.1c7022965d1a=skin glove
|
||||
; src/menus/base_info.nvgt:20:insert_last[0]
|
||||
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.
|
||||
; seed:learn_sounds_label:sounds/items/fire.ogg
|
||||
msg.1da840244989=fire
|
||||
@@ -792,7 +795,7 @@ msg.1db76ac85135=sling
|
||||
msg.237b5017397b=axe
|
||||
; src/bosses/unicorn/unicorn_boss.nvgt:117:insert_last[0]
|
||||
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.
|
||||
; seed:learn_sounds_label:sounds/quests/safe_to_jump.ogg
|
||||
msg.2630bd23d7d4=safe to jump
|
||||
@@ -818,7 +821,7 @@ msg.27e4cdae51e3=vampyr4
|
||||
msg.27f1dd42841b=- Press Enter to confirm
|
||||
; src/menus/base_info.nvgt:30:insert_last[0]
|
||||
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}.
|
||||
; src/bosses/unicorn/unicorn_boss.nvgt:115:insert_last[0]
|
||||
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
|
||||
; seed:learn_sounds_label:sounds/weapons/sling_low_range.ogg
|
||||
msg.5123c22ce6a5=sling low range
|
||||
; draugnorak.nvgt:113:insert_last[0]
|
||||
; draugnorak.nvgt:114:insert_last[0]
|
||||
msg.528fb1c4e5fb=New Game
|
||||
; seed:learn_sounds_label:sounds/quests/squish.ogg
|
||||
msg.5460f2e3c43e=squish
|
||||
@@ -980,7 +983,7 @@ msg.5b61e4982b59=wight dies
|
||||
msg.5bdb7fa2223d=Feathers
|
||||
; src/item_registry.nvgt:item_plural: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.
|
||||
; src/bosses/unicorn/unicorn_boss.nvgt:110:insert_last[0]
|
||||
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
|
||||
; src/menus/base_info.nvgt:12:insert_last[0]
|
||||
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.
|
||||
; seed:learn_sounds_label:sounds/actions/climb_tree.ogg
|
||||
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.
|
||||
; src/item_registry.nvgt:item_display:backpacks
|
||||
msg.7fae743693f4=Backpacks
|
||||
; draugnorak.nvgt:232:ui_info_box[1]
|
||||
; draugnorak.nvgt:246:ui_info_box[1]
|
||||
; draugnorak.nvgt:233:ui_info_box[1]
|
||||
; draugnorak.nvgt:247:ui_info_box[1]
|
||||
msg.802aa6576577=Load Game
|
||||
; src/item_registry.nvgt:item_plural: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.
|
||||
; seed:learn_sounds_label:sounds/actions/climb_rope.ogg
|
||||
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.
|
||||
; src/bosses/unicorn/unicorn_boss.nvgt:113:insert_last[0]
|
||||
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
|
||||
; seed:learn_sounds_label:sounds/game/boar.ogg
|
||||
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.
|
||||
; src/item_registry.nvgt:item_plural: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.
|
||||
; src/quests/catch_the_boomerang_game.nvgt:19:insert_last[0]
|
||||
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
|
||||
; seed:learn_sounds_label:sounds/enemies/bandit4.ogg
|
||||
msg.9bbcfc6c8f9d=bandit4
|
||||
@@ -1182,7 +1185,7 @@ msg.a56be51786dc=bone1
|
||||
msg.a570b024087d=Ever hungry, they feed upon your residents.
|
||||
; src/quests/escape_from_hel_game.nvgt:15:insert_last[0]
|
||||
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}.
|
||||
; src/item_registry.nvgt:item_plural:slings
|
||||
msg.a6caf0c3771d=slings
|
||||
@@ -1233,7 +1236,7 @@ msg.b73a0709297f=call pet
|
||||
msg.b774102c89b5=Knives
|
||||
; seed:learn_sounds_label:sounds/terrain/snow.ogg
|
||||
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!
|
||||
; src/item_registry.nvgt:item_display:baskets_of_fruits_and_nuts
|
||||
msg.ba7d7dfe330b=Baskets of Fruits and Nuts
|
||||
@@ -1250,7 +1253,7 @@ msg.bdebda25dad9=arrow hit
|
||||
msg.bf93e5ce8bc1=Down
|
||||
; 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.
|
||||
; src/bosses/bandit_hideout.nvgt:821:insert_last[0]
|
||||
; src/bosses/bandit_hideout.nvgt:820:insert_last[0]
|
||||
msg.c05feaca7269=No items were recovered.
|
||||
; src/item_registry.nvgt:item_singular:snare
|
||||
msg.c1104d82c0bd=snare
|
||||
@@ -1266,7 +1269,7 @@ msg.c52888225c69=fall
|
||||
msg.c65e5dfc5ba7=bandit1
|
||||
; seed:learn_sounds_label:sounds/enemies/corpse_worm.ogg
|
||||
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}.
|
||||
; seed:learn_sounds_label:sounds/terrain/stream.ogg
|
||||
msg.c82e3d7279ef=stream
|
||||
@@ -1278,7 +1281,7 @@ msg.c8da1c4f1714=Lowers in pitch as the fall progresses.
|
||||
msg.c90399f41b3f=Meat
|
||||
; seed:learn_sounds_label:sounds/quests/bone7.ogg
|
||||
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:
|
||||
; src/item_registry.nvgt:item_plural:boar_carcasses
|
||||
msg.cb8e70ab2bcc=boar carcasses
|
||||
@@ -1298,7 +1301,7 @@ msg.ce3897c6b3be={arg1}: {arg2}
|
||||
msg.ce5aeca95fb9=item breaks
|
||||
; src/i18n.nvgt:677: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}
|
||||
; seed:manual
|
||||
msg.cfe2650b4c6e=Found a stone.
|
||||
@@ -1327,7 +1330,7 @@ msg.d6dc139f529a=invasion
|
||||
msg.d6fd3467a950=yeti1
|
||||
; src/item_registry.nvgt:item_display: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.
|
||||
; src/quests/enchanted_melody_game.nvgt:31:insert_last[0]
|
||||
msg.da0b5a461d98=F or K - First note (lowest pitch)
|
||||
@@ -1395,7 +1398,7 @@ msg.e5d79aba2048=undead resident1
|
||||
msg.e5f656d506a7=zombie hit
|
||||
; src/quests/escape_from_hel_game.nvgt:8:insert_last[0]
|
||||
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.
|
||||
; seed:learn_sounds_label:sounds/quests/bone8.ogg
|
||||
msg.e6ae58426f13=bone8
|
||||
@@ -1405,9 +1408,9 @@ msg.e7409fcb54bc=feed fire
|
||||
msg.e8002c169040=wood
|
||||
; src/quests/escape_from_hel_game.nvgt:13:insert_last[0]
|
||||
msg.e8216720d30b=- Press SPACE to jump over graves
|
||||
; draugnorak.nvgt:133:insert_last[0]
|
||||
; draugnorak.nvgt:134:insert_last[0]
|
||||
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.
|
||||
; src/bosses/bandit_hideout.nvgt:302:insert_last[0]
|
||||
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
|
||||
; src/bosses/bandit_hideout.nvgt:301:insert_last[0]
|
||||
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
|
||||
; src/inventory_items.nvgt:504:speak_with_history[0]
|
||||
; src/quests/skeletal_bard_game.nvgt:26:speak_with_history[0]
|
||||
|
||||
+27
-24
@@ -33,6 +33,8 @@ load_game.delete_save_failed=Unable to delete save.
|
||||
load_game.save_deleted=Save deleted.
|
||||
load_game.no_saves_found=No saves found.
|
||||
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.
|
||||
quick_slot.no_item_bound=No item bound to slot {slot}.
|
||||
menu.closed=Closed.
|
||||
@@ -708,7 +710,7 @@ msg.041d8413712d=A crash log was written to your save data folder.
|
||||
msg.04a8916a9494=vampyr1
|
||||
; src/item_registry.nvgt:item_plural: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 ===
|
||||
; seed:learn_sounds_label:sounds/enemies/corpse_worm_dies.ogg
|
||||
msg.06c9fefbd6d7=corpse worm dies
|
||||
@@ -749,12 +751,13 @@ msg.0f9af12cbb82=skin pouch
|
||||
; src/item_registry.nvgt:item_display:logs
|
||||
msg.126dd3b70a5c=Logs
|
||||
; 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
|
||||
; src/item_registry.nvgt:item_plural:quivers
|
||||
msg.145ba8a7a0f2=quivers
|
||||
; src/item_registry.nvgt:item_display: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.
|
||||
; seed:learn_sounds_label:sounds/game/game_falls.ogg
|
||||
msg.16134a4250f7=game falls
|
||||
@@ -782,7 +785,7 @@ msg.1bfa6bc809d4==== Bandit's Hideout ===
|
||||
msg.1c7022965d1a=skin glove
|
||||
; src/menus/base_info.nvgt:20:insert_last[0]
|
||||
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.
|
||||
; seed:learn_sounds_label:sounds/items/fire.ogg
|
||||
msg.1da840244989=fire
|
||||
@@ -792,7 +795,7 @@ msg.1db76ac85135=sling
|
||||
msg.237b5017397b=axe
|
||||
; src/bosses/unicorn/unicorn_boss.nvgt:117:insert_last[0]
|
||||
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.
|
||||
; seed:learn_sounds_label:sounds/quests/safe_to_jump.ogg
|
||||
msg.2630bd23d7d4=safe to jump
|
||||
@@ -818,7 +821,7 @@ msg.27e4cdae51e3=vampyr4
|
||||
msg.27f1dd42841b=- Press Enter to confirm
|
||||
; src/menus/base_info.nvgt:30:insert_last[0]
|
||||
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}.
|
||||
; src/bosses/unicorn/unicorn_boss.nvgt:115:insert_last[0]
|
||||
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
|
||||
; seed:learn_sounds_label:sounds/weapons/sling_low_range.ogg
|
||||
msg.5123c22ce6a5=sling low range
|
||||
; draugnorak.nvgt:113:insert_last[0]
|
||||
; draugnorak.nvgt:114:insert_last[0]
|
||||
msg.528fb1c4e5fb=New Game
|
||||
; seed:learn_sounds_label:sounds/quests/squish.ogg
|
||||
msg.5460f2e3c43e=squish
|
||||
@@ -980,7 +983,7 @@ msg.5b61e4982b59=wight dies
|
||||
msg.5bdb7fa2223d=Feathers
|
||||
; src/item_registry.nvgt:item_plural: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.
|
||||
; src/bosses/unicorn/unicorn_boss.nvgt:110:insert_last[0]
|
||||
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
|
||||
; src/menus/base_info.nvgt:12:insert_last[0]
|
||||
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.
|
||||
; seed:learn_sounds_label:sounds/actions/climb_tree.ogg
|
||||
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.
|
||||
; src/item_registry.nvgt:item_display:backpacks
|
||||
msg.7fae743693f4=Backpacks
|
||||
; draugnorak.nvgt:232:ui_info_box[1]
|
||||
; draugnorak.nvgt:246:ui_info_box[1]
|
||||
; draugnorak.nvgt:233:ui_info_box[1]
|
||||
; draugnorak.nvgt:247:ui_info_box[1]
|
||||
msg.802aa6576577=Load Game
|
||||
; src/item_registry.nvgt:item_plural: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.
|
||||
; seed:learn_sounds_label:sounds/actions/climb_rope.ogg
|
||||
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.
|
||||
; src/bosses/unicorn/unicorn_boss.nvgt:113:insert_last[0]
|
||||
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
|
||||
; seed:learn_sounds_label:sounds/game/boar.ogg
|
||||
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.
|
||||
; src/item_registry.nvgt:item_plural: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.
|
||||
; src/quests/catch_the_boomerang_game.nvgt:19:insert_last[0]
|
||||
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
|
||||
; seed:learn_sounds_label:sounds/enemies/bandit4.ogg
|
||||
msg.9bbcfc6c8f9d=bandit4
|
||||
@@ -1182,7 +1185,7 @@ msg.a56be51786dc=bone1
|
||||
msg.a570b024087d=Ever hungry, they feed upon your residents.
|
||||
; src/quests/escape_from_hel_game.nvgt:15:insert_last[0]
|
||||
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}.
|
||||
; src/item_registry.nvgt:item_plural:slings
|
||||
msg.a6caf0c3771d=slings
|
||||
@@ -1233,7 +1236,7 @@ msg.b73a0709297f=call pet
|
||||
msg.b774102c89b5=Knives
|
||||
; seed:learn_sounds_label:sounds/terrain/snow.ogg
|
||||
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!
|
||||
; src/item_registry.nvgt:item_display:baskets_of_fruits_and_nuts
|
||||
msg.ba7d7dfe330b=Baskets of Fruits and Nuts
|
||||
@@ -1250,7 +1253,7 @@ msg.bdebda25dad9=arrow hit
|
||||
msg.bf93e5ce8bc1=Down
|
||||
; 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.
|
||||
; src/bosses/bandit_hideout.nvgt:821:insert_last[0]
|
||||
; src/bosses/bandit_hideout.nvgt:820:insert_last[0]
|
||||
msg.c05feaca7269=No items were recovered.
|
||||
; src/item_registry.nvgt:item_singular:snare
|
||||
msg.c1104d82c0bd=snare
|
||||
@@ -1266,7 +1269,7 @@ msg.c52888225c69=fall
|
||||
msg.c65e5dfc5ba7=bandit1
|
||||
; seed:learn_sounds_label:sounds/enemies/corpse_worm.ogg
|
||||
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}.
|
||||
; seed:learn_sounds_label:sounds/terrain/stream.ogg
|
||||
msg.c82e3d7279ef=stream
|
||||
@@ -1278,7 +1281,7 @@ msg.c8da1c4f1714=Lowers in pitch as the fall progresses.
|
||||
msg.c90399f41b3f=Meat
|
||||
; seed:learn_sounds_label:sounds/quests/bone7.ogg
|
||||
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:
|
||||
; src/item_registry.nvgt:item_plural:boar_carcasses
|
||||
msg.cb8e70ab2bcc=boar carcasses
|
||||
@@ -1298,7 +1301,7 @@ msg.ce3897c6b3be={arg1}: {arg2}
|
||||
msg.ce5aeca95fb9=item breaks
|
||||
; src/i18n.nvgt:677: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}
|
||||
; seed:manual
|
||||
msg.cfe2650b4c6e=Found a stone.
|
||||
@@ -1327,7 +1330,7 @@ msg.d6dc139f529a=invasion
|
||||
msg.d6fd3467a950=yeti1
|
||||
; src/item_registry.nvgt:item_display: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.
|
||||
; src/quests/enchanted_melody_game.nvgt:31:insert_last[0]
|
||||
msg.da0b5a461d98=F or K - First note (lowest pitch)
|
||||
@@ -1395,7 +1398,7 @@ msg.e5d79aba2048=undead resident1
|
||||
msg.e5f656d506a7=zombie hit
|
||||
; src/quests/escape_from_hel_game.nvgt:8:insert_last[0]
|
||||
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.
|
||||
; seed:learn_sounds_label:sounds/quests/bone8.ogg
|
||||
msg.e6ae58426f13=bone8
|
||||
@@ -1405,9 +1408,9 @@ msg.e7409fcb54bc=feed fire
|
||||
msg.e8002c169040=wood
|
||||
; src/quests/escape_from_hel_game.nvgt:13:insert_last[0]
|
||||
msg.e8216720d30b=- Press SPACE to jump over graves
|
||||
; draugnorak.nvgt:133:insert_last[0]
|
||||
; draugnorak.nvgt:134:insert_last[0]
|
||||
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.
|
||||
; src/bosses/bandit_hideout.nvgt:302:insert_last[0]
|
||||
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
|
||||
; src/bosses/bandit_hideout.nvgt:301:insert_last[0]
|
||||
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
|
||||
; src/inventory_items.nvgt:504:speak_with_history[0]
|
||||
; src/quests/skeletal_bard_game.nvgt:26:speak_with_history[0]
|
||||
|
||||
@@ -14,6 +14,8 @@ load_game.delete_save_failed=No se pudo eliminar la partida.
|
||||
load_game.save_deleted=Partida eliminada.
|
||||
load_game.no_saves_found=No hay partidas guardadas.
|
||||
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.
|
||||
main_menu.prompt=Draugnorak. Menu principal.
|
||||
game.new_game_started=Partida nueva iniciada.
|
||||
|
||||
@@ -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.no_saves_found", "No saves found."),
|
||||
("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.quick_slot.no_item_bound", "No item bound to slot {slot}."),
|
||||
("system.menu.closed", "Closed."),
|
||||
|
||||
@@ -79,8 +79,6 @@ void run_adventure_menu(int player_x) {
|
||||
play_menu_select_sound();
|
||||
start_adventure(adventure_ids[selection]);
|
||||
// Adventure has finished (it blocks until done)
|
||||
// Resume main game ambience
|
||||
update_ambience(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -95,4 +93,5 @@ void start_adventure(int adventure_id) {
|
||||
run_bandit_hideout_adventure();
|
||||
}
|
||||
menuBackgroundUpdatesEnabled = true;
|
||||
restore_world_audio();
|
||||
}
|
||||
@@ -286,12 +286,12 @@ void cleanup_bandit_hideout_adventure() {
|
||||
clear_hideout_bandits();
|
||||
end_pet_adventure();
|
||||
reset_adventure_combat_state();
|
||||
p.destroy_all();
|
||||
reset_game_sound_pool();
|
||||
}
|
||||
|
||||
void run_bandit_hideout_adventure() {
|
||||
// Stop main game sounds
|
||||
p.destroy_all();
|
||||
reset_game_sound_pool();
|
||||
|
||||
init_bandit_hideout_adventure();
|
||||
|
||||
@@ -317,13 +317,12 @@ void run_bandit_hideout_adventure() {
|
||||
|
||||
if (key_pressed(KEY_BACK)) {
|
||||
adventurePaused = !adventurePaused;
|
||||
set_gameplay_timers_paused(adventurePaused);
|
||||
if (adventurePaused) {
|
||||
p.pause_all();
|
||||
speak_with_history(tr("system.game.paused_prompt"), true);
|
||||
} else {
|
||||
p.resume_all();
|
||||
restart_hideout_adventure_timers();
|
||||
restart_all_timers();
|
||||
speak_with_history(tr("system.game.resumed"), true);
|
||||
}
|
||||
continue;
|
||||
|
||||
@@ -84,7 +84,7 @@ void init_unicorn_adventure() {
|
||||
}
|
||||
|
||||
void cleanup_unicorn_adventure() {
|
||||
p.destroy_all();
|
||||
reset_game_sound_pool();
|
||||
reset_adventure_combat_state();
|
||||
end_pet_adventure();
|
||||
if (unicorn.sound_handle != -1) {
|
||||
@@ -95,7 +95,7 @@ void cleanup_unicorn_adventure() {
|
||||
|
||||
void run_unicorn_adventure() {
|
||||
// Stop main game sounds
|
||||
p.destroy_all();
|
||||
reset_game_sound_pool();
|
||||
|
||||
init_unicorn_adventure();
|
||||
|
||||
@@ -130,13 +130,12 @@ void run_unicorn_adventure() {
|
||||
|
||||
if (key_pressed(KEY_BACK)) {
|
||||
adventurePaused = !adventurePaused;
|
||||
set_gameplay_timers_paused(adventurePaused);
|
||||
if (adventurePaused) {
|
||||
p.pause_all();
|
||||
speak_with_history(tr("system.game.paused_prompt"), true);
|
||||
} else {
|
||||
p.resume_all();
|
||||
restart_unicorn_adventure_timers();
|
||||
restart_all_timers();
|
||||
speak_with_history(tr("system.game.resumed"), true);
|
||||
}
|
||||
continue;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -99,20 +99,3 @@ int last_adventure_day = -1;
|
||||
bool game_paused = false;
|
||||
bool menuBackgroundUpdatesEnabled = true;
|
||||
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
@@ -3,6 +3,7 @@
|
||||
#include "libstorm-nvgt/dict_utils.nvgt"
|
||||
#include "libstorm-nvgt/name_sanitize.nvgt"
|
||||
#include "libstorm-nvgt/save_utils.nvgt"
|
||||
#include "src/save_validation.nvgt"
|
||||
|
||||
const string SAVE_EXTENSION = ".dat";
|
||||
const string SAVE_ENCRYPTION_KEY = "draugnorak_save_v1";
|
||||
@@ -112,7 +113,7 @@ void migrate_legacy_save_files_if_needed() {
|
||||
continue;
|
||||
|
||||
string targetPath = resolve_save_path(legacyName);
|
||||
if (file_exists(targetPath))
|
||||
if (file_exists(targetPath) || file_exists(targetPath + ".bak"))
|
||||
continue;
|
||||
|
||||
file legacyFile;
|
||||
@@ -130,6 +131,15 @@ void migrate_legacy_save_files_if_needed() {
|
||||
string[] get_save_files() {
|
||||
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[] @items = glob(resolve_save_path("*" + SAVE_EXTENSION));
|
||||
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) {
|
||||
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) {
|
||||
@@ -472,7 +538,7 @@ bool select_save_file(string& out filename) {
|
||||
}
|
||||
int confirm = ui_question("", prompt);
|
||||
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);
|
||||
refreshList = true;
|
||||
break;
|
||||
@@ -561,6 +627,8 @@ void clear_world_objects() {
|
||||
}
|
||||
|
||||
void reset_game_state() {
|
||||
set_gameplay_timers_paused(false);
|
||||
game_paused = false;
|
||||
stop_active_sounds();
|
||||
clear_world_objects();
|
||||
|
||||
@@ -721,7 +789,7 @@ void start_new_game() {
|
||||
current_save_file = get_save_filename_for_name(player_name);
|
||||
}
|
||||
queue_current_goal_announcement(false);
|
||||
save_game_state();
|
||||
save_game_with_feedback();
|
||||
}
|
||||
|
||||
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)
|
||||
return;
|
||||
|
||||
string[] @parts = data.split(",");
|
||||
string[] parts = split_save_record(data, ",");
|
||||
uint count = parts.length();
|
||||
if (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() {
|
||||
last_save_error = "";
|
||||
dictionary saveData;
|
||||
|
||||
saveData.set("version", SAVE_VERSION);
|
||||
@@ -1092,6 +1161,10 @@ bool save_game_state() {
|
||||
bool load_game_state_from_file(const string& in filename) {
|
||||
last_save_error = "";
|
||||
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)) {
|
||||
last_save_error = "No save file found.";
|
||||
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.";
|
||||
return false;
|
||||
}
|
||||
double version;
|
||||
bool has_version = saveData.get("version", version);
|
||||
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;
|
||||
}
|
||||
if (!validate_save_data(saveData))
|
||||
return false;
|
||||
|
||||
reset_game_state();
|
||||
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");
|
||||
for (uint i = 0; i < treeData.length(); i++) {
|
||||
string[] @parts = treeData[i].split("|");
|
||||
string[] parts = split_save_record(treeData[i]);
|
||||
if (parts.length() < 8)
|
||||
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");
|
||||
for (uint i = 0; i < snareData.length(); i++) {
|
||||
string[] @parts = snareData[i].split("|");
|
||||
string[] parts = split_save_record(snareData[i]);
|
||||
if (parts.length() < 6)
|
||||
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");
|
||||
for (uint i = 0; i < fireData.length(); i++) {
|
||||
string[] @parts = fireData[i].split("|");
|
||||
string[] parts = split_save_record(fireData[i]);
|
||||
if (parts.length() < 3)
|
||||
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");
|
||||
for (uint i = 0; i < streamData.length(); i++) {
|
||||
string[] @parts = streamData[i].split("|");
|
||||
string[] parts = split_save_record(streamData[i]);
|
||||
if (parts.length() < 2)
|
||||
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");
|
||||
for (uint i = 0; i < banditData.length(); i++) {
|
||||
string[] @parts = banditData[i].split("|");
|
||||
string[] parts = split_save_record(banditData[i]);
|
||||
if (parts.length() < 6)
|
||||
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");
|
||||
for (uint i = 0; i < mountainData.length(); i++) {
|
||||
string[] @parts = mountainData[i].split("|");
|
||||
string[] parts = split_save_record(mountainData[i]);
|
||||
if (parts.length() < 5)
|
||||
continue;
|
||||
|
||||
@@ -1852,14 +1917,14 @@ bool load_game_state_from_file(const string& in filename) {
|
||||
mountain.end_position = end_pos;
|
||||
|
||||
// Parse elevations
|
||||
string[] @elev_parts = parts[2].split(",");
|
||||
string[] elev_parts = split_save_record(parts[2], ",");
|
||||
mountain.elevations.resize(elev_parts.length());
|
||||
for (uint j = 0; j < elev_parts.length(); j++) {
|
||||
mountain.elevations[j] = parse_int(elev_parts[j]);
|
||||
}
|
||||
|
||||
// Parse terrain types
|
||||
string[] @terrain_parts = parts[3].split(",");
|
||||
string[] terrain_parts = split_save_record(parts[3], ",");
|
||||
mountain.terrain_types.resize(terrain_parts.length());
|
||||
for (uint j = 0; j < terrain_parts.length(); 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
|
||||
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++) {
|
||||
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");
|
||||
for (uint i = 0; i < dropData.length(); i++) {
|
||||
string[] @parts = dropData[i].split("|");
|
||||
string[] parts = split_save_record(dropData[i]);
|
||||
if (parts.length() < 2)
|
||||
continue;
|
||||
add_world_drop(parse_int(parts[0]), parts[1]);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -996,7 +996,7 @@ void update_time() {
|
||||
attempt_resident_fish_smoking();
|
||||
attempt_livestock_production();
|
||||
if (current_hour == DAY_START_HOUR) {
|
||||
save_game_state();
|
||||
save_game_with_feedback();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user