From 8219ab793f69dd69a434496843ee7aa43bf01b56 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Tue, 23 Jun 2026 12:16:01 -0400 Subject: [PATCH] Work on inital game experience mostly stable now. Juswt need to flesh out the goals system. --- draugnorak.nvgt | 7 + files/instructions.md | 12 + lang/af.ini | 57 ++++ lang/en.ini | 82 ++++- lang/en.template.ini | 82 ++++- lang/es.ini | 57 ++++ scripts/generate_i18n_catalog.py | 48 +++ src/bosses/bandit_hideout.nvgt | 1 + src/bosses/unicorn/unicorn_boss.nvgt | 1 + src/combat.nvgt | 24 ++ src/crafting/craft_buildings.nvgt | 4 + src/crafting/craft_runes.nvgt | 14 + src/crafting/craft_tools.nvgt | 1 + src/crafting/craft_weapons.nvgt | 42 +++ src/goals.nvgt | 481 +++++++++++++++++++++++++++ src/inventory_items.nvgt | 52 ++- src/item_registry.nvgt | 6 +- src/menus/character_info.nvgt | 13 + src/menus/equipment_menu.nvgt | 9 +- src/quest_system.nvgt | 1 + src/runes/rune_data.nvgt | 11 +- src/save_system.nvgt | 33 +- src/time_system.nvgt | 3 +- src/weapon_range_audio.nvgt | 2 + 24 files changed, 1004 insertions(+), 39 deletions(-) create mode 100644 src/goals.nvgt diff --git a/draugnorak.nvgt b/draugnorak.nvgt index d47bab9..65f6bad 100644 --- a/draugnorak.nvgt +++ b/draugnorak.nvgt @@ -31,6 +31,7 @@ sound_pool p(300); #include "src/inventory.nvgt" #include "src/pet_system.nvgt" #include "src/quest_system.nvgt" +#include "src/goals.nvgt" #include "src/environment.nvgt" #include "src/combat.nvgt" #include "src/fylgja_system.nvgt" @@ -211,6 +212,7 @@ void run_game() { init_item_registry(); init_pet_sounds(); init_search_pools(); + init_goal_state(); while (true) { bool game_started = false; @@ -376,6 +378,9 @@ void run_game() { speak_with_history(get_health_report(), true); } + // Current goal key (G) + check_goal_key(); + // Coordinates Key if (key_pressed(KEY_X)) { string direction_label = (facing == 1) ? tr("system.direction.east") : tr("system.direction.west"); @@ -623,6 +628,8 @@ void run_game() { int attack_cooldown = 1000; if (spear_equipped) attack_cooldown = 800; + if (great_spear_equipped) + attack_cooldown = GREAT_SPEAR_ATTACK_COOLDOWN_MS; if (axe_equipped) attack_cooldown = 1600; diff --git a/files/instructions.md b/files/instructions.md index 540dafa..0162628 100644 --- a/files/instructions.md +++ b/files/instructions.md @@ -42,6 +42,7 @@ Some of the first things you will want are a stone knife a spear a stone axe and - **F** fylgja menu once unlocked - **Tab** adventure menu outside base once per day - **H** health +- **G** current goal speaks the next incomplete goal and unspent goal points - **T** time - **X** coordinates and terrain - **1 to 0** quick slots press in Equipment to bind an item to equip or in Inventory to bind a count check @@ -195,6 +196,17 @@ Pets are optional companions that can fight and retrieve drops - Bowstrings are crafted from sinew at a fire and used to craft bows - Storage is separate and accessed in the base Inventory menu when storage exists +## Goals and Mastery +Completing one-time character milestones awards goal points. +- Press **G** to hear your current goal and unspent points. +- Open **P** (Character info) and choose **Goals** for the full menu. +- Spend goal points in **Mastery** to unlock concrete benefits. + +Current categories: +- **Crafting Mastery 1** (2 points) crafted basic tools are sturdier. +- **Crafting Mastery 2** (3 points) unlocks the great spear recipe. +- **Crafting Mastery 3** (4 points) great spear reaches 2 tiles but has a slower attack window. + ## Saving and Loading - The game auto saves daily at 6 AM in game time - Load from the main menu starting a new game prompts if a save exists diff --git a/lang/af.ini b/lang/af.ini index a4e715d..bc1af96 100644 --- a/lang/af.ini +++ b/lang/af.ini @@ -462,6 +462,9 @@ crafting.weapons.prompt=Wapens. {option} crafting.weapons.option.spear=Speer (1 Stok, 1 Rankplant, 1 Klip) [Vereis Mes] crafting.weapons.option.sling=Slinger (1 Vel, 2 Rankplante) crafting.weapons.option.bow=Boog (1 Stok, 1 Boogstring) +system.crafting.weapons.option.great_spear=Groot Speer (1 Speer, 1 Blok, 2 Rankplante, 2 Senuwee) [Vereis Handwerk Bemeestering 2] +system.crafting.weapons.crafted.great_spear='n Groot Speer gemaak. +system.crafting.weapons.great_spear.requires_mastery=Groot speer vereis Handwerk Bemeestering 2. crafting.weapons.crafted.spear='n Speer gemaak. crafting.weapons.crafted.sling='n Slinger gemaak. crafting.weapons.crafted.bow='n Boog gemaak. @@ -968,3 +971,57 @@ msg.fe2a9d6606e0=draugnorak msg.feea4a3b0a84==== Eenhoringjag === msg.fefc084f67de=D of J - Tweede nota msg.ff5ac0b6df7b=boë + +msg.14941e07fc96=Groot Spere +msg.4fd74919913c=groot speer +msg.f03527f80533=groot spere + +system.character.info.goals=Doelwitte: {unspent} onbestee, {earned} verdien +system.character.info.goals_menu=Doelwitte +system.goals.all_complete=Alle huidige doelwitte voltooi. Onbestede doelpunte: {unspent}. +system.goals.completed.entry_many={goal}. {points} doelpunte toegeken. +system.goals.completed.entry_one={goal}. 1 doelpunt toegeken. +system.goals.completed.none=Nog geen doelwitte voltooi nie. +system.goals.completed.prompt=Voltooide doelwitte. {option} +system.goals.current=Huidige doelwit: {goal}. Onbestede doelpunte: {unspent}. +system.goals.goal.build_altar.name=Bou 'n altaar. +system.goals.goal.build_firepit.name=Bou 'n vuurput. +system.goals.goal.build_storage.name=Bou berging. +system.goals.goal.complete_quest.name=Voltooi 'n soektog. +system.goals.goal.completed=Doelwit voltooi: {goal}. {points} doelpunte toegeken. +system.goals.goal.craft_knife.name=Maak 'n mes. +system.goals.goal.craft_spear.name=Maak 'n speer. +system.goals.goal.engrave_rune.name=Grafeer 'n rune. +system.goals.goal.light_fire.name=Steek 'n vuur aan. +system.goals.goal.unknown.name=Onbekende doelwit. +system.goals.goal.win_adventure.name=Wen 'n avontuur. +system.goals.menu.completed=Voltooide doelwitte +system.goals.menu.current=Huidige doelwit: {goal} +system.goals.menu.current_done=Huidige doelwit: alles voltooi +system.goals.menu.earned=Totale doelpunte verdien: {earned} +system.goals.menu.earned_solo=Totale doelpunte verdien: {earned} +system.goals.menu.mastery=Bemeestering +system.goals.menu.prompt=Doelwitte. {option} +system.goals.menu.unspent=Onbestede doelpunte: {unspent} +system.goals.menu.unspent_solo=Onbestede doelpunte: {unspent} +system.goals.reward.many=Beloning: {count} doelpunte. +system.goals.reward.one=Beloning: 1 doelpunt. + +system.goals.mastery.unknown.name=Onbekende bemeestering +system.goals.mastery.unlocked=Ontgrendel {unlock}. + +system.goals.mastery.prompt=Bemeestering. {option} +system.goals.mastery.crafting.level1=Gemaakte basiese gereedskap is stewiger. +system.goals.mastery.crafting.level2=Ontsluit groot speer resep. +system.goals.mastery.crafting.level3=Groot speer bereik 2 teëls maar het 'n stadiger aanvalsvenster. +system.goals.mastery.crafting.name=Handwerk Bemeestering +system.goals.mastery.unknown.name=Onbekende bemeestering +system.goals.mastery.unknown_level=Onbekende ontgrendeling +system.goals.mastery.maxed=Handwerk Bemeestering: maks bereik +system.goals.mastery.summary={category} vlak {level} van {max}. Volgende koste: {cost} doelpunte. +system.goals.mastery.unlocked=Ontgrendel {unlock}. +system.goals.mastery.confirm=Spandeer {cost} doelpunte om {unlock} te ontgrendel? +system.goals.mastery.already_maxed=Handwerk Bemeestering is reeds maks. +system.goals.mastery.not_enough_points=Benodig {cost} doelpunte. Jy het {unspent}. +system.goals.mastery.spend_failed=Kon nie die doelpunt spandeer nie. +system.goals.menu.back=Terug diff --git a/lang/en.ini b/lang/en.ini index ba5cca2..0c1edfb 100644 --- a/lang/en.ini +++ b/lang/en.ini @@ -229,8 +229,56 @@ character.info.speed=Speed {status} character.info.pet=Pet {pet}, health {health} of {max_health}, loyalty {loyalty} of {max_loyalty} character.info.pet_knocked_out=Knocked out for {hours} {hour_label}. character.info.pet_none=No pet. +character.info.goals=Goals: {unspent} unspent, {earned} earned +character.info.goals_menu=Goals character.word.hour=hour character.word.hours=hours +goals.current=Current goal: {goal}. Unspent goal points: {unspent}. +goals.all_complete=All current goals complete. Unspent goal points: {unspent}. +goals.goal.completed=Goal complete: {goal}. Awarded {points} goal points. +goals.reward.one=Reward: 1 goal point. +goals.reward.many=Reward: {count} goal points. +goals.goal.craft_knife.name=Craft a knife. +goals.goal.craft_spear.name=Craft a spear. +goals.goal.build_firepit.name=Build a firepit. +goals.goal.light_fire.name=Light a fire. +goals.goal.build_storage.name=Build storage. +goals.goal.build_altar.name=Build an altar. +goals.goal.complete_quest.name=Complete a quest. +goals.goal.win_adventure.name=Win an adventure. +goals.goal.engrave_rune.name=Engrave a rune. +goals.goal.unknown.name=Unknown goal. +goals.menu.prompt=Goals. {option} +goals.menu.current=Current goal: {goal} +goals.menu.current_done=Current goal: all complete +goals.menu.completed=Completed goals +goals.menu.mastery=Mastery +goals.menu.unspent=Unspent goal points: {unspent} +goals.menu.unspent_solo=Unspent goal points: {unspent} +goals.menu.earned=Total goal points earned: {earned} +goals.menu.earned_solo=Total goal points earned: {earned} +goals.menu.back=Back +goals.completed.prompt=Completed goals. {option} +goals.completed.none=No goals completed yet. +goals.completed.entry_one={goal}. Awarded 1 goal point. +goals.completed.entry_many={goal}. Awarded {points} goal points. +goals.mastery.prompt=Mastery. {option} +goals.mastery.crafting.name=Crafting Mastery +goals.mastery.unknown.name=Unknown mastery +goals.mastery.unknown_level=Unknown unlock +goals.mastery.maxed=Crafting Mastery: maxed out +goals.mastery.summary={category} level {level} of {max}. Next cost: {cost} goal points. +goals.mastery.unlocked=Unlocked {unlock}. +goals.mastery.confirm=Spend {cost} goal points to unlock {unlock}? +goals.mastery.already_maxed=Crafting Mastery is already maxed out. +goals.mastery.not_enough_points=Need {cost} goal points. You have {unspent}. +goals.mastery.spend_failed=Could not spend the goal point. +goals.mastery.crafting.level1=Crafted basic tools are sturdier. +goals.mastery.crafting.level2=Unlock great spear recipe. +goals.mastery.crafting.level3=Great spear reaches 2 tiles but has a slower attack window. +crafting.weapons.option.great_spear=Great Spear (1 Spear, 1 Log, 2 Vines, 2 Sinew) [Requires Crafting Mastery 2] +crafting.weapons.crafted.great_spear=Crafted a Great Spear. +crafting.weapons.great_spear.requires_mastery=Great spear requires Crafting Mastery 2. pet.default_name=pet pet.returned=A {pet} returns to you. pet.knocked_out=Your {pet} has been knocked out. @@ -622,7 +670,7 @@ msg.03268c7d7239=backpacks msg.03976920927e=rope ; src/item_registry.nvgt:item_display:small_game msg.0406529811c7=Small Game -; draugnorak.nvgt:608:ui_info_box[2] +; draugnorak.nvgt:653:ui_info_box[2] msg.041d8413712d=A crash log was written to your save data folder. ; seed:learn_sounds_label:sounds/enemies/vampyr1.ogg msg.04a8916a9494=vampyr1 @@ -668,6 +716,8 @@ msg.126dd3b70a5c=Logs 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] msg.14f5f5ff4f76=No survivors were found. ; seed:learn_sounds_label:sounds/game/game_falls.ogg @@ -858,19 +908,21 @@ msg.4f773e9d5bf0=Bows msg.4f7f358cd341=goose ; src/item_registry.nvgt:item_display:clay_pots msg.4f99d3d5b638=Clay Pots +; src/item_registry.nvgt:item_singular:great_spear +msg.4fd74919913c=great spear ; seed:learn_sounds_label:sounds/game/turkey.ogg msg.5009c8e190ee=turkey ; src/quests/skeletal_bard_game.nvgt:51:insert_last[0] 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:81:insert_last[0] +; draugnorak.nvgt:112:insert_last[0] msg.528fb1c4e5fb=New Game ; seed:learn_sounds_label:sounds/quests/squish.ogg msg.5460f2e3c43e=squish ; seed:learn_sounds_label:sounds/actions/fishpole_break.ogg msg.546330ee64db=fishpole break -; draugnorak.nvgt:608:ui_info_box[0] +; draugnorak.nvgt:653:ui_info_box[0] msg.54fc2d657ed6=Draugnorak ; seed:learn_sounds_label:sounds/enemies/vampyr3.ogg msg.55748cf43121=vampyr3 @@ -898,7 +950,7 @@ msg.5cac31311668=Pasture is full. No livestock were recovered. msg.5cc7429297c8=that can be destroyed with an axe. ; seed:learn_sounds_label:sounds/actions/cast_strength.ogg msg.5dfc869b7588=cast strength -; src/time_system.nvgt:793:notify[0] +; src/time_system.nvgt:794:notify[0] msg.5ea84b1201bb={arg1} favor grants you the eyes of an eagle. ; seed:learn_sounds_label:sounds/menu/menu_select.ogg ; seed:learn_sounds_label:sounds/menu.bak/menu_select.ogg @@ -982,8 +1034,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:194:ui_info_box[1] -; draugnorak.nvgt:208:ui_info_box[1] +; draugnorak.nvgt:231:ui_info_box[1] +; draugnorak.nvgt:245:ui_info_box[1] msg.802aa6576577=Load Game ; src/item_registry.nvgt:item_plural:sticks msg.803e4c41bbd2=sticks @@ -992,8 +1044,8 @@ msg.803e4c41bbd2=sticks msg.80655da8d80a=tree ; src/item_registry.nvgt:item_plural:canoes msg.806baaf8e039=canoes -; src/time_system.nvgt:775:notify[0] -; src/time_system.nvgt:785:notify[0] +; src/time_system.nvgt:776:notify[0] +; src/time_system.nvgt:786:notify[0] msg.81df04a81e24={arg1} favor shines upon you. {arg2} ; src/quests/catch_the_boomerang_game.nvgt:22:insert_last[0] msg.823be948275f=- Better timing earns more points (up to 4) @@ -1019,7 +1071,7 @@ msg.86e257c3efcb=Oldest notification. {arg1} msg.87383ce4344d=Bowstrings ; src/item_registry.nvgt:item_plural:ropes msg.87cdb5c91437=ropes -; src/time_system.nvgt:789:notify[0] +; src/time_system.nvgt:790:notify[0] msg.8818f1d55166={arg1} radiance fills residents with purpose. ; src/quests/bat_invasion_game.nvgt:7:insert_last[0] msg.88749dbbbf09==== Bat Invasion === @@ -1037,7 +1089,7 @@ msg.8f993ac20023=You enter a narrow mountain pass. A massive Unicorn blocks your msg.917ee46db0cd=bee ; seed:learn_sounds_label:sounds/player_male_damage.ogg msg.924e0eb6b6f1=player male damage -; src/time_system.nvgt:780:notify[0] +; src/time_system.nvgt:781:notify[0] 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 @@ -1220,7 +1272,7 @@ msg.d0cb2acd0739=skin msg.d34b6217ea75=enemy falls ; src/item_registry.nvgt:item_display:skin_gloves msg.d373510877fd=Skin Gloves -; draugnorak.nvgt:608:ui_info_box[1] +; draugnorak.nvgt:653:ui_info_box[1] ; seed:manual msg.d39008c4a392=Unhandled exception ; src/item_registry.nvgt:item_singular:backpack @@ -1313,7 +1365,7 @@ 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:96:insert_last[0] +; draugnorak.nvgt:132:insert_last[0] msg.e8db3a42f86f=Learn Sounds ; src/bosses/bandit_hideout.nvgt:845:insert_last[0] msg.e8e8f4ee7a5a=You can now engrave weapons with this rune at the crafting menu. @@ -1330,6 +1382,8 @@ msg.ee200b3d8ae2=Spears msg.eff5a5f34d9a=unicorn on bridge ; seed:learn_sounds_label:sounds/weapons/bow_draw.ogg msg.f015f25c409b=bow draw +; src/item_registry.nvgt:item_plural:great_spears +msg.f03527f80533=great spears ; src/item_registry.nvgt:item_display:ropes msg.f12b2d219fe8=Ropes ; src/quests/catch_the_boomerang_game.nvgt:14:insert_last[0] @@ -1342,9 +1396,9 @@ 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:112:insert_last[0] +; draugnorak.nvgt:148:insert_last[0] msg.f83b6fe3aebf=Exit -; src/inventory_items.nvgt:460: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:32:speak_with_history[0] msg.f85b4b604c9b={arg1}. diff --git a/lang/en.template.ini b/lang/en.template.ini index ba5cca2..0c1edfb 100644 --- a/lang/en.template.ini +++ b/lang/en.template.ini @@ -229,8 +229,56 @@ character.info.speed=Speed {status} character.info.pet=Pet {pet}, health {health} of {max_health}, loyalty {loyalty} of {max_loyalty} character.info.pet_knocked_out=Knocked out for {hours} {hour_label}. character.info.pet_none=No pet. +character.info.goals=Goals: {unspent} unspent, {earned} earned +character.info.goals_menu=Goals character.word.hour=hour character.word.hours=hours +goals.current=Current goal: {goal}. Unspent goal points: {unspent}. +goals.all_complete=All current goals complete. Unspent goal points: {unspent}. +goals.goal.completed=Goal complete: {goal}. Awarded {points} goal points. +goals.reward.one=Reward: 1 goal point. +goals.reward.many=Reward: {count} goal points. +goals.goal.craft_knife.name=Craft a knife. +goals.goal.craft_spear.name=Craft a spear. +goals.goal.build_firepit.name=Build a firepit. +goals.goal.light_fire.name=Light a fire. +goals.goal.build_storage.name=Build storage. +goals.goal.build_altar.name=Build an altar. +goals.goal.complete_quest.name=Complete a quest. +goals.goal.win_adventure.name=Win an adventure. +goals.goal.engrave_rune.name=Engrave a rune. +goals.goal.unknown.name=Unknown goal. +goals.menu.prompt=Goals. {option} +goals.menu.current=Current goal: {goal} +goals.menu.current_done=Current goal: all complete +goals.menu.completed=Completed goals +goals.menu.mastery=Mastery +goals.menu.unspent=Unspent goal points: {unspent} +goals.menu.unspent_solo=Unspent goal points: {unspent} +goals.menu.earned=Total goal points earned: {earned} +goals.menu.earned_solo=Total goal points earned: {earned} +goals.menu.back=Back +goals.completed.prompt=Completed goals. {option} +goals.completed.none=No goals completed yet. +goals.completed.entry_one={goal}. Awarded 1 goal point. +goals.completed.entry_many={goal}. Awarded {points} goal points. +goals.mastery.prompt=Mastery. {option} +goals.mastery.crafting.name=Crafting Mastery +goals.mastery.unknown.name=Unknown mastery +goals.mastery.unknown_level=Unknown unlock +goals.mastery.maxed=Crafting Mastery: maxed out +goals.mastery.summary={category} level {level} of {max}. Next cost: {cost} goal points. +goals.mastery.unlocked=Unlocked {unlock}. +goals.mastery.confirm=Spend {cost} goal points to unlock {unlock}? +goals.mastery.already_maxed=Crafting Mastery is already maxed out. +goals.mastery.not_enough_points=Need {cost} goal points. You have {unspent}. +goals.mastery.spend_failed=Could not spend the goal point. +goals.mastery.crafting.level1=Crafted basic tools are sturdier. +goals.mastery.crafting.level2=Unlock great spear recipe. +goals.mastery.crafting.level3=Great spear reaches 2 tiles but has a slower attack window. +crafting.weapons.option.great_spear=Great Spear (1 Spear, 1 Log, 2 Vines, 2 Sinew) [Requires Crafting Mastery 2] +crafting.weapons.crafted.great_spear=Crafted a Great Spear. +crafting.weapons.great_spear.requires_mastery=Great spear requires Crafting Mastery 2. pet.default_name=pet pet.returned=A {pet} returns to you. pet.knocked_out=Your {pet} has been knocked out. @@ -622,7 +670,7 @@ msg.03268c7d7239=backpacks msg.03976920927e=rope ; src/item_registry.nvgt:item_display:small_game msg.0406529811c7=Small Game -; draugnorak.nvgt:608:ui_info_box[2] +; draugnorak.nvgt:653:ui_info_box[2] msg.041d8413712d=A crash log was written to your save data folder. ; seed:learn_sounds_label:sounds/enemies/vampyr1.ogg msg.04a8916a9494=vampyr1 @@ -668,6 +716,8 @@ msg.126dd3b70a5c=Logs 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] msg.14f5f5ff4f76=No survivors were found. ; seed:learn_sounds_label:sounds/game/game_falls.ogg @@ -858,19 +908,21 @@ msg.4f773e9d5bf0=Bows msg.4f7f358cd341=goose ; src/item_registry.nvgt:item_display:clay_pots msg.4f99d3d5b638=Clay Pots +; src/item_registry.nvgt:item_singular:great_spear +msg.4fd74919913c=great spear ; seed:learn_sounds_label:sounds/game/turkey.ogg msg.5009c8e190ee=turkey ; src/quests/skeletal_bard_game.nvgt:51:insert_last[0] 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:81:insert_last[0] +; draugnorak.nvgt:112:insert_last[0] msg.528fb1c4e5fb=New Game ; seed:learn_sounds_label:sounds/quests/squish.ogg msg.5460f2e3c43e=squish ; seed:learn_sounds_label:sounds/actions/fishpole_break.ogg msg.546330ee64db=fishpole break -; draugnorak.nvgt:608:ui_info_box[0] +; draugnorak.nvgt:653:ui_info_box[0] msg.54fc2d657ed6=Draugnorak ; seed:learn_sounds_label:sounds/enemies/vampyr3.ogg msg.55748cf43121=vampyr3 @@ -898,7 +950,7 @@ msg.5cac31311668=Pasture is full. No livestock were recovered. msg.5cc7429297c8=that can be destroyed with an axe. ; seed:learn_sounds_label:sounds/actions/cast_strength.ogg msg.5dfc869b7588=cast strength -; src/time_system.nvgt:793:notify[0] +; src/time_system.nvgt:794:notify[0] msg.5ea84b1201bb={arg1} favor grants you the eyes of an eagle. ; seed:learn_sounds_label:sounds/menu/menu_select.ogg ; seed:learn_sounds_label:sounds/menu.bak/menu_select.ogg @@ -982,8 +1034,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:194:ui_info_box[1] -; draugnorak.nvgt:208:ui_info_box[1] +; draugnorak.nvgt:231:ui_info_box[1] +; draugnorak.nvgt:245:ui_info_box[1] msg.802aa6576577=Load Game ; src/item_registry.nvgt:item_plural:sticks msg.803e4c41bbd2=sticks @@ -992,8 +1044,8 @@ msg.803e4c41bbd2=sticks msg.80655da8d80a=tree ; src/item_registry.nvgt:item_plural:canoes msg.806baaf8e039=canoes -; src/time_system.nvgt:775:notify[0] -; src/time_system.nvgt:785:notify[0] +; src/time_system.nvgt:776:notify[0] +; src/time_system.nvgt:786:notify[0] msg.81df04a81e24={arg1} favor shines upon you. {arg2} ; src/quests/catch_the_boomerang_game.nvgt:22:insert_last[0] msg.823be948275f=- Better timing earns more points (up to 4) @@ -1019,7 +1071,7 @@ msg.86e257c3efcb=Oldest notification. {arg1} msg.87383ce4344d=Bowstrings ; src/item_registry.nvgt:item_plural:ropes msg.87cdb5c91437=ropes -; src/time_system.nvgt:789:notify[0] +; src/time_system.nvgt:790:notify[0] msg.8818f1d55166={arg1} radiance fills residents with purpose. ; src/quests/bat_invasion_game.nvgt:7:insert_last[0] msg.88749dbbbf09==== Bat Invasion === @@ -1037,7 +1089,7 @@ msg.8f993ac20023=You enter a narrow mountain pass. A massive Unicorn blocks your msg.917ee46db0cd=bee ; seed:learn_sounds_label:sounds/player_male_damage.ogg msg.924e0eb6b6f1=player male damage -; src/time_system.nvgt:780:notify[0] +; src/time_system.nvgt:781:notify[0] 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 @@ -1220,7 +1272,7 @@ msg.d0cb2acd0739=skin msg.d34b6217ea75=enemy falls ; src/item_registry.nvgt:item_display:skin_gloves msg.d373510877fd=Skin Gloves -; draugnorak.nvgt:608:ui_info_box[1] +; draugnorak.nvgt:653:ui_info_box[1] ; seed:manual msg.d39008c4a392=Unhandled exception ; src/item_registry.nvgt:item_singular:backpack @@ -1313,7 +1365,7 @@ 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:96:insert_last[0] +; draugnorak.nvgt:132:insert_last[0] msg.e8db3a42f86f=Learn Sounds ; src/bosses/bandit_hideout.nvgt:845:insert_last[0] msg.e8e8f4ee7a5a=You can now engrave weapons with this rune at the crafting menu. @@ -1330,6 +1382,8 @@ msg.ee200b3d8ae2=Spears msg.eff5a5f34d9a=unicorn on bridge ; seed:learn_sounds_label:sounds/weapons/bow_draw.ogg msg.f015f25c409b=bow draw +; src/item_registry.nvgt:item_plural:great_spears +msg.f03527f80533=great spears ; src/item_registry.nvgt:item_display:ropes msg.f12b2d219fe8=Ropes ; src/quests/catch_the_boomerang_game.nvgt:14:insert_last[0] @@ -1342,9 +1396,9 @@ 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:112:insert_last[0] +; draugnorak.nvgt:148:insert_last[0] msg.f83b6fe3aebf=Exit -; src/inventory_items.nvgt:460: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:32:speak_with_history[0] msg.f85b4b604c9b={arg1}. diff --git a/lang/es.ini b/lang/es.ini index b0089a7..89dba4b 100644 --- a/lang/es.ini +++ b/lang/es.ini @@ -462,6 +462,9 @@ crafting.weapons.prompt=Armas. {option} crafting.weapons.option.spear=Lanza (1 Palo, 1 Liana, 1 Piedra) [Requiere Cuchillo] crafting.weapons.option.sling=Honda (1 Piel, 2 Lianas) crafting.weapons.option.bow=Arco (1 Palo, 1 Cuerda de arco) +system.crafting.weapons.option.great_spear=Lanza Grande (1 Lanza, 1 Tronco, 2 Lianas, 2 Tendones) [Requiere Maestría de Fabricación 2] +system.crafting.weapons.crafted.great_spear=Fabricaste una lanza grande. +system.crafting.weapons.great_spear.requires_mastery=La lanza grande requiere Maestría de Fabricación 2. crafting.weapons.crafted.spear=Fabricaste una lanza. crafting.weapons.crafted.sling=Fabricaste una honda. crafting.weapons.crafted.bow=Fabricaste un arco. @@ -968,3 +971,57 @@ msg.fe2a9d6606e0=draugnorak msg.feea4a3b0a84==== Unicornio Hunt === msg.fefc084f67de=D o J - Segunda nota msg.ff5ac0b6df7b=arcos + +msg.14941e07fc96=Grandes Lanzas +msg.4fd74919913c=lanza grande +msg.f03527f80533=grandes lanzas + +system.character.info.goals=Objetivos: {unspent} sin gastar, {earned} ganados +system.character.info.goals_menu=Objetivos +system.goals.all_complete=Todos los objetivos actuales completados. Puntos sin gastar: {unspent}. +system.goals.completed.entry_many={goal}. {points} puntos otorgados. +system.goals.completed.entry_one={goal}. 1 punto otorgado. +system.goals.completed.none=Aún no se han completado objetivos. +system.goals.completed.prompt=Objetivos completados. {option} +system.goals.current=Objetivo actual: {goal}. Puntos sin gastar: {unspent}. +system.goals.goal.build_altar.name=Construye un altar. +system.goals.goal.build_firepit.name=Construye un fogón. +system.goals.goal.build_storage.name=Construye almacenamiento. +system.goals.goal.complete_quest.name=Completa una misión. +system.goals.goal.completed=Objetivo completado: {goal}. {points} puntos otorgados. +system.goals.goal.craft_knife.name=Fabrica un cuchillo. +system.goals.goal.craft_spear.name=Fabrica una lanza. +system.goals.goal.engrave_rune.name=Graba una runa. +system.goals.goal.light_fire.name=Enciende un fuego. +system.goals.goal.unknown.name=Objetivo desconocido. +system.goals.goal.win_adventure.name=Gana una aventura. +system.goals.menu.completed=Objetivos completados +system.goals.menu.current=Objetivo actual: {goal} +system.goals.menu.current_done=Objetivo actual: todo completado +system.goals.menu.earned=Puntos totales ganados: {earned} +system.goals.menu.earned_solo=Puntos totales ganados: {earned} +system.goals.menu.mastery=Maestría +system.goals.menu.prompt=Objetivos. {option} +system.goals.menu.unspent=Puntos sin gastar: {unspent} +system.goals.menu.unspent_solo=Puntos sin gastar: {unspent} +system.goals.reward.many=Recompensa: {count} puntos. +system.goals.reward.one=Recompensa: 1 punto. + +system.goals.mastery.unknown.name=Maestría desconocida +system.goals.mastery.unlocked=Desbloqueado {unlock}. + +system.goals.mastery.prompt=Maestría. {option} +system.goals.mastery.crafting.level1=Las herramientas básicas fabricadas son más resistentes. +system.goals.mastery.crafting.level2=Desbloquea la receta de la lanza grande. +system.goals.mastery.crafting.level3=La lanza grande alcanza 2 casillas pero tiene una ventana de ataque más lenta. +system.goals.mastery.crafting.name=Maestría de Fabricación +system.goals.mastery.unknown.name=Maestría desconocida +system.goals.mastery.unknown_level=Desbloqueo desconocido +system.goals.mastery.maxed=Maestría de Fabricación: al máximo +system.goals.mastery.summary={category} nivel {level} de {max}. Coste siguiente: {cost} puntos. +system.goals.mastery.unlocked=Desbloqueado {unlock}. +system.goals.mastery.confirm=¿Gastar {cost} puntos para desbloquear {unlock}? +system.goals.mastery.already_maxed=La Maestría de Fabricación ya está al máximo. +system.goals.mastery.not_enough_points=Necesitas {cost} puntos. Tienes {unspent}. +system.goals.mastery.spend_failed=No se pudo gastar el punto. +system.goals.menu.back=Atrás diff --git a/scripts/generate_i18n_catalog.py b/scripts/generate_i18n_catalog.py index e500661..6036071 100644 --- a/scripts/generate_i18n_catalog.py +++ b/scripts/generate_i18n_catalog.py @@ -732,8 +732,56 @@ def write_catalog(entries: Dict[str, Dict[str, object]], output_path: Path) -> N ("system.character.info.pet", "Pet {pet}, health {health} of {max_health}, loyalty {loyalty} of {max_loyalty}"), ("system.character.info.pet_knocked_out", "Knocked out for {hours} {hour_label}."), ("system.character.info.pet_none", "No pet."), + ("system.character.info.goals", "Goals: {unspent} unspent, {earned} earned"), + ("system.character.info.goals_menu", "Goals"), ("system.character.word.hour", "hour"), ("system.character.word.hours", "hours"), + ("system.goals.current", "Current goal: {goal}. Unspent goal points: {unspent}."), + ("system.goals.all_complete", "All current goals complete. Unspent goal points: {unspent}."), + ("system.goals.goal.completed", "Goal complete: {goal}. Awarded {points} goal points."), + ("system.goals.reward.one", "Reward: 1 goal point."), + ("system.goals.reward.many", "Reward: {count} goal points."), + ("system.goals.goal.craft_knife.name", "Craft a knife."), + ("system.goals.goal.craft_spear.name", "Craft a spear."), + ("system.goals.goal.build_firepit.name", "Build a firepit."), + ("system.goals.goal.light_fire.name", "Light a fire."), + ("system.goals.goal.build_storage.name", "Build storage."), + ("system.goals.goal.build_altar.name", "Build an altar."), + ("system.goals.goal.complete_quest.name", "Complete a quest."), + ("system.goals.goal.win_adventure.name", "Win an adventure."), + ("system.goals.goal.engrave_rune.name", "Engrave a rune."), + ("system.goals.goal.unknown.name", "Unknown goal."), + ("system.goals.menu.prompt", "Goals. {option}"), + ("system.goals.menu.current", "Current goal: {goal}"), + ("system.goals.menu.current_done", "Current goal: all complete"), + ("system.goals.menu.completed", "Completed goals"), + ("system.goals.menu.mastery", "Mastery"), + ("system.goals.menu.unspent", "Unspent goal points: {unspent}"), + ("system.goals.menu.unspent_solo", "Unspent goal points: {unspent}"), + ("system.goals.menu.earned", "Total goal points earned: {earned}"), + ("system.goals.menu.earned_solo", "Total goal points earned: {earned}"), + ("system.goals.menu.back", "Back"), + ("system.goals.completed.prompt", "Completed goals. {option}"), + ("system.goals.completed.none", "No goals completed yet."), + ("system.goals.completed.entry_one", "{goal}. Awarded 1 goal point."), + ("system.goals.completed.entry_many", "{goal}. Awarded {points} goal points."), + ("system.goals.mastery.prompt", "Mastery. {option}"), + ("system.goals.mastery.crafting.name", "Crafting Mastery"), + ("system.goals.mastery.unknown.name", "Unknown mastery"), + ("system.goals.mastery.unknown_level", "Unknown unlock"), + ("system.goals.mastery.maxed", "Crafting Mastery: maxed out"), + ("system.goals.mastery.summary", "{category} level {level} of {max}. Next cost: {cost} goal points."), + ("system.goals.mastery.unlocked", "Unlocked {unlock}."), + ("system.goals.mastery.confirm", "Spend {cost} goal points to unlock {unlock}?"), + ("system.goals.mastery.already_maxed", "Crafting Mastery is already maxed out."), + ("system.goals.mastery.not_enough_points", "Need {cost} goal points. You have {unspent}."), + ("system.goals.mastery.spend_failed", "Could not spend the goal point."), + ("system.goals.mastery.crafting.level1", "Crafted basic tools are sturdier."), + ("system.goals.mastery.crafting.level2", "Unlock great spear recipe."), + ("system.goals.mastery.crafting.level3", "Great spear reaches 2 tiles but has a slower attack window."), + ("system.crafting.weapons.option.great_spear", "Great Spear (1 Spear, 1 Log, 2 Vines, 2 Sinew) [Requires Crafting Mastery 2]"), + ("system.crafting.weapons.crafted.great_spear", "Crafted a Great Spear."), + ("system.crafting.weapons.great_spear.requires_mastery", "Great spear requires Crafting Mastery 2."), ("system.pet.default_name", "pet"), ("system.pet.returned", "A {pet} returns to you."), ("system.pet.knocked_out", "Your {pet} has been knocked out."), diff --git a/src/bosses/bandit_hideout.nvgt b/src/bosses/bandit_hideout.nvgt index f2ca65c..a40c488 100644 --- a/src/bosses/bandit_hideout.nvgt +++ b/src/bosses/bandit_hideout.nvgt @@ -863,5 +863,6 @@ void give_bandit_hideout_rewards() { i18n_translate_string_array_in_place(rewards); text_reader_lines(rewards, i18n_text("Bandit's Hideout"), true); + complete_goal(GOAL_WIN_ADVENTURE); attempt_pet_offer_from_adventure(); } diff --git a/src/bosses/unicorn/unicorn_boss.nvgt b/src/bosses/unicorn/unicorn_boss.nvgt index 301e4eb..cb038c5 100644 --- a/src/bosses/unicorn/unicorn_boss.nvgt +++ b/src/bosses/unicorn/unicorn_boss.nvgt @@ -733,5 +733,6 @@ void give_unicorn_rewards() { // Display rewards in text reader text_reader_lines(rewards, tr("system.adventure.unicorn.victory_title"), true); + complete_goal(GOAL_WIN_ADVENTURE); attempt_pet_offer_from_adventure(); } diff --git a/src/combat.nvgt b/src/combat.nvgt index 637bd62..392cdd2 100644 --- a/src/combat.nvgt +++ b/src/combat.nvgt @@ -3,6 +3,8 @@ void perform_attack(int current_x) { perform_sling_attack(current_x); } else if (spear_equipped) { perform_spear_attack(current_x); + } else if (great_spear_equipped) { + perform_great_spear_attack(current_x); } else if (axe_equipped) { perform_axe_attack(current_x); } else { @@ -409,3 +411,25 @@ void release_sling_attack(int player_x) { play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", player_x, target_x, ZOMBIE_SOUND_VOLUME_STEP); } } + +void perform_great_spear_attack(int current_x) { + p.play_stationary("sounds/weapons/spear_swing.ogg", false); + + int damage = apply_weapon_rune_damage(GREAT_SPEAR_DAMAGE); + int reach = get_great_spear_range(); + int hit_pos = attack_enemy_ranged(current_x - reach, current_x + reach, damage); + if (hit_pos != -1) { + p.play_stationary("sounds/weapons/spear_hit.ogg", false); + if (get_bandit_at(hit_pos) != null) { + play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, hit_pos, BANDIT_SOUND_VOLUME_STEP); + } else if (get_boar_at(hit_pos) != null) { + play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, hit_pos, BOAR_SOUND_VOLUME_STEP); + } else { + play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, hit_pos, ZOMBIE_SOUND_VOLUME_STEP); + } + return; + } + + // Hit tree with great spear (sound only, 0 damage) + hit_tree_with_spear(current_x); +} diff --git a/src/crafting/craft_buildings.nvgt b/src/crafting/craft_buildings.nvgt index a57c606..3469f9d 100644 --- a/src/crafting/craft_buildings.nvgt +++ b/src/crafting/craft_buildings.nvgt @@ -175,6 +175,7 @@ void craft_firepit() { simulate_crafting(9); add_personal_count(ITEM_STONES, -9); add_world_firepit(x); + complete_goal(GOAL_BUILD_FIREPIT); speak_with_history(tr("system.crafting.buildings.firepit.built"), true); } else { speak_crafting_missing(missing); @@ -211,6 +212,7 @@ void craft_campfire() { add_personal_count(ITEM_STICKS, -2); // Build the fire at the firepit location, not player location add_world_fire(firepit.position); + complete_goal(GOAL_LIGHT_FIRE); speak_with_history(tr("system.crafting.buildings.fire.built"), true); } else { speak_crafting_missing(missing); @@ -288,6 +290,7 @@ void craft_storage() { add_personal_count(ITEM_VINES, -vineCost); if (world_storages.length() == 0) { add_world_storage(x); + complete_goal(GOAL_BUILD_STORAGE); } storage_level = targetLevel; dictionary storageArgs; @@ -381,6 +384,7 @@ void craft_altar() { add_personal_count(ITEM_STONES, -ALTAR_STONE_COST); add_personal_count(ITEM_STICKS, -ALTAR_STICK_COST); add_world_altar(x); + complete_goal(GOAL_BUILD_ALTAR); speak_with_history(tr("system.crafting.buildings.altar.built"), true); } else { speak_crafting_missing(missing); diff --git a/src/crafting/craft_runes.nvgt b/src/crafting/craft_runes.nvgt index 324e4cf..26f4fa4 100644 --- a/src/crafting/craft_runes.nvgt +++ b/src/crafting/craft_runes.nvgt @@ -4,6 +4,8 @@ // Get the base equipment name without any rune prefix string get_base_equipment_name(int equip_type) { + if (equip_type == EQUIP_GREAT_SPEAR) + return i18n_lookup_key_with_fallback("system.equipment.name.great_spear", "Great Spear"); if (equip_type == EQUIP_SPEAR) return i18n_lookup_key_with_fallback("system.equipment.name.spear", "Spear"); if (equip_type == EQUIP_AXE) @@ -32,6 +34,8 @@ string get_base_equipment_name(int equip_type) { } string get_base_equipment_name_plural(int equip_type) { + if (equip_type == EQUIP_GREAT_SPEAR) + return i18n_lookup_key_with_fallback("system.equipment.name_plural.great_spears", "Great Spears"); if (equip_type == EQUIP_SPEAR) return i18n_lookup_key_with_fallback("system.equipment.name_plural.spears", "Spears"); if (equip_type == EQUIP_AXE) @@ -61,6 +65,8 @@ string get_base_equipment_name_plural(int equip_type) { // Get inventory count for an equipment type int get_unruned_equipment_count(int equip_type) { + if (equip_type == EQUIP_GREAT_SPEAR) + return get_personal_count(ITEM_GREAT_SPEARS); if (equip_type == EQUIP_SPEAR) return get_personal_count(ITEM_SPEARS); if (equip_type == EQUIP_AXE) @@ -90,6 +96,10 @@ int get_unruned_equipment_count(int equip_type) { // Decrement inventory for an equipment type void decrement_unruned_equipment(int equip_type) { + if (equip_type == EQUIP_GREAT_SPEAR) { + add_personal_count(ITEM_GREAT_SPEARS, -1); + return; + } if (equip_type == EQUIP_SPEAR) { add_personal_count(ITEM_SPEARS, -1); return; @@ -316,6 +326,7 @@ void engrave_rune(int equip_type, int rune_type) { dictionary engravedArgs; engravedArgs.set("item", runed_name); speak_with_history(trf("system.crafting.runes.engraved", engravedArgs), true); + complete_goal(GOAL_ENGRAVE_RUNE); } else { speak_crafting_missing(missing); } @@ -377,4 +388,7 @@ void engrave_rune_max(int equip_type, int rune_type) { engravedMaxArgs.set("item", item_name); engravedMaxArgs.set("rune", get_rune_name(rune_type)); speak_with_history(trf("system.crafting.runes.engraved_max", engravedMaxArgs), true); + if (max_craft > 0) { + complete_goal(GOAL_ENGRAVE_RUNE); + } } diff --git a/src/crafting/craft_tools.nvgt b/src/crafting/craft_tools.nvgt index f51a62b..54d8882 100644 --- a/src/crafting/craft_tools.nvgt +++ b/src/crafting/craft_tools.nvgt @@ -99,6 +99,7 @@ void craft_knife() { simulate_crafting(2); add_personal_count(ITEM_STONES, -2); add_personal_count(ITEM_KNIVES, 1); + complete_goal(GOAL_CRAFT_KNIFE); speak_with_history(tr("system.crafting.tools.crafted.stone_knife"), true); } else { speak_crafting_missing(missing); diff --git a/src/crafting/craft_weapons.nvgt b/src/crafting/craft_weapons.nvgt index 10d5aa7..d15708f 100644 --- a/src/crafting/craft_weapons.nvgt +++ b/src/crafting/craft_weapons.nvgt @@ -3,6 +3,9 @@ void run_weapons_menu() { int selection = 0; string[] options = {tr("system.crafting.weapons.option.spear"), tr("system.crafting.weapons.option.sling"), tr("system.crafting.weapons.option.bow")}; + if (get_crafting_mastery_level() >= 2) { + options.insert_last(tr("system.crafting.weapons.option.great_spear")); + } speak_menu_prompt("system.crafting.weapons.prompt", options[selection]); while (true) { @@ -39,6 +42,8 @@ void run_weapons_menu() { craft_sling(); else if (selection == 2) craft_bow(); + else if (selection == 3) + craft_great_spear(); break; } @@ -50,6 +55,8 @@ void run_weapons_menu() { craft_sling_max(); else if (selection == 2) craft_bow_max(); + else if (selection == 3) + craft_great_spear(); break; } } @@ -76,6 +83,7 @@ void craft_spear() { add_personal_count(ITEM_VINES, -1); add_personal_count(ITEM_STONES, -1); add_personal_count(ITEM_SPEARS, 1); + complete_goal(GOAL_CRAFT_SPEAR); speak_with_history(tr("system.crafting.weapons.crafted.spear"), true); } else { speak_crafting_missing(missing); @@ -319,3 +327,37 @@ void craft_axe_max() { axeArgs.set("count", max_craft); speak_with_history(trf("system.crafting.weapons.crafted_max.stone_axes", axeArgs), true); } + +// Great spear recipe: requires a base spear, extra materials, and Crafting +// Mastery 2. Range 2 with a slower attack/recovery to balance. +void craft_great_spear() { + if (get_crafting_mastery_level() < 2) { + speak_with_history(tr("system.crafting.weapons.great_spear.requires_mastery"), true); + return; + } + string missing = ""; + if (get_personal_count(ITEM_SPEARS) < 1) + missing += "1 spear "; + if (get_personal_count(ITEM_LOGS) < 1) + missing += "1 log "; + if (get_personal_count(ITEM_VINES) < 2) + missing += "2 vines "; + if (get_personal_count(ITEM_SINEW) < 2) + missing += "2 sinew "; + + if (missing == "") { + if (get_personal_count(ITEM_GREAT_SPEARS) >= get_personal_stack_limit()) { + speak_cant_carry_any_more_item(ITEM_GREAT_SPEARS); + return; + } + simulate_crafting(8); + add_personal_count(ITEM_SPEARS, -1); + add_personal_count(ITEM_LOGS, -1); + add_personal_count(ITEM_VINES, -2); + add_personal_count(ITEM_SINEW, -2); + add_personal_count(ITEM_GREAT_SPEARS, 1); + speak_with_history(tr("system.crafting.weapons.crafted.great_spear"), true); + } else { + speak_crafting_missing(missing); + } +} diff --git a/src/goals.nvgt b/src/goals.nvgt new file mode 100644 index 0000000..fb52820 --- /dev/null +++ b/src/goals.nvgt @@ -0,0 +1,481 @@ +// Goals and Mastery system +// Tracks one-time character milestones, goal points, and the +// initial Crafting Mastery spend path. See goal.md for design notes. + +const int GOAL_CRAFT_KNIFE = 0; +const int GOAL_CRAFT_SPEAR = 1; +const int GOAL_BUILD_FIREPIT = 2; +const int GOAL_LIGHT_FIRE = 3; +const int GOAL_BUILD_STORAGE = 4; +const int GOAL_BUILD_ALTAR = 5; +const int GOAL_COMPLETE_QUEST = 6; +const int GOAL_WIN_ADVENTURE = 7; +const int GOAL_ENGRAVE_RUNE = 8; +const int GOAL_COUNT = 9; + +// Mastery spend categories. +const int MASTERY_CRAFTING = 0; +const int MASTERY_CATEGORY_COUNT = 1; + +// Maximum spendable Crafting Mastery level in the first pass. +const int CRAFTING_MASTERY_MAX_LEVEL = 3; + +bool[] goalsCompleted; +int goalPointsUnspent = 0; +int goalPointsEarned = 0; +int craftingMasteryLevel = 0; + +int goal_reward_points(int goalId) { + if (goalId == GOAL_CRAFT_KNIFE) return 1; + if (goalId == GOAL_CRAFT_SPEAR) return 1; + if (goalId == GOAL_BUILD_FIREPIT) return 1; + if (goalId == GOAL_LIGHT_FIRE) return 1; + if (goalId == GOAL_BUILD_STORAGE) return 2; + if (goalId == GOAL_BUILD_ALTAR) return 2; + if (goalId == GOAL_COMPLETE_QUEST) return 2; + if (goalId == GOAL_WIN_ADVENTURE) return 3; + if (goalId == GOAL_ENGRAVE_RUNE) return 3; + return 0; +} + +string goal_name_key(int goalId) { + if (goalId == GOAL_CRAFT_KNIFE) return "system.goals.goal.craft_knife.name"; + if (goalId == GOAL_CRAFT_SPEAR) return "system.goals.goal.craft_spear.name"; + if (goalId == GOAL_BUILD_FIREPIT) return "system.goals.goal.build_firepit.name"; + if (goalId == GOAL_LIGHT_FIRE) return "system.goals.goal.light_fire.name"; + if (goalId == GOAL_BUILD_STORAGE) return "system.goals.goal.build_storage.name"; + if (goalId == GOAL_BUILD_ALTAR) return "system.goals.goal.build_altar.name"; + if (goalId == GOAL_COMPLETE_QUEST) return "system.goals.goal.complete_quest.name"; + if (goalId == GOAL_WIN_ADVENTURE) return "system.goals.goal.win_adventure.name"; + if (goalId == GOAL_ENGRAVE_RUNE) return "system.goals.goal.engrave_rune.name"; + return "system.goals.goal.unknown.name"; +} + +string goal_name(int goalId) { + return tr(goal_name_key(goalId)); +} + +string goal_summary(int goalId) { + string name = goal_name(goalId); + if (name == goal_name_key(goalId)) { + if (goalId == GOAL_CRAFT_KNIFE) name = "Craft a knife."; + else if (goalId == GOAL_CRAFT_SPEAR) name = "Craft a spear."; + else if (goalId == GOAL_BUILD_FIREPIT) name = "Build a firepit."; + else if (goalId == GOAL_LIGHT_FIRE) name = "Light a fire."; + else if (goalId == GOAL_BUILD_STORAGE) name = "Build storage."; + else if (goalId == GOAL_BUILD_ALTAR) name = "Build an altar."; + else if (goalId == GOAL_COMPLETE_QUEST) name = "Complete a quest."; + else if (goalId == GOAL_WIN_ADVENTURE) name = "Win an adventure."; + else if (goalId == GOAL_ENGRAVE_RUNE) name = "Engrave a rune."; + } + int points = goal_reward_points(goalId); + if (points == 1) { + return name + " " + tr("system.goals.reward.one"); + } + dictionary args; + args.set("count", points); + return name + " " + trf("system.goals.reward.many", args); +} + +void init_goal_state() { + goalsCompleted.resize(GOAL_COUNT); + for (int i = 0; i < GOAL_COUNT; i++) { + goalsCompleted[i] = false; + } + goalPointsUnspent = 0; + goalPointsEarned = 0; + craftingMasteryLevel = 0; +} + +void reset_goal_state() { + init_goal_state(); +} + +bool is_valid_goal_id(int goalId) { + return goalId >= 0 && goalId < GOAL_COUNT; +} + +bool is_goal_completed(int goalId) { + if (!is_valid_goal_id(goalId)) + return false; + return goalsCompleted[goalId]; +} + +int get_current_goal_id() { + for (int i = 0; i < GOAL_COUNT; i++) { + if (!goalsCompleted[i]) { + return i; + } + } + return -1; +} + +bool complete_goal(int goalId) { + if (!is_valid_goal_id(goalId)) { + return false; + } + if (goalsCompleted[goalId]) { + return false; + } + goalsCompleted[goalId] = true; + int points = goal_reward_points(goalId); + goalPointsUnspent += points; + goalPointsEarned += points; + + dictionary args; + args.set("goal", goal_name(goalId)); + args.set("points", points); + notify(trf("system.goals.goal.completed", args)); + return true; +} + +string speak_current_goal() { + int currentId = get_current_goal_id(); + dictionary unspentArgs; + unspentArgs.set("unspent", goalPointsUnspent); + if (currentId == -1) { + return trf("system.goals.all_complete", unspentArgs); + } + dictionary args; + args.set("goal", goal_summary(currentId)); + args.set("unspent", goalPointsUnspent); + return trf("system.goals.current", args); +} + +void check_goal_key() { + if (key_pressed(KEY_G)) { + speak_with_history(speak_current_goal(), true); + } +} + +string mastery_category_name(int categoryId) { + if (categoryId == MASTERY_CRAFTING) return tr("system.goals.mastery.crafting.name"); + return tr("system.goals.mastery.unknown.name"); +} + +int mastery_current_level(int categoryId) { + if (categoryId == MASTERY_CRAFTING) return craftingMasteryLevel; + return 0; +} + +int mastery_max_level(int categoryId) { + if (categoryId == MASTERY_CRAFTING) return CRAFTING_MASTERY_MAX_LEVEL; + return 0; +} + +int mastery_cost_for_next_level(int categoryId) { + int level = mastery_current_level(categoryId); + int maxLevel = mastery_max_level(categoryId); + if (level >= maxLevel) + return -1; + if (categoryId == MASTERY_CRAFTING) { + if (level == 0) return 2; + if (level == 1) return 3; + if (level == 2) return 4; + } + return -1; +} + +string mastery_unlock_key(int categoryId, int level) { + if (categoryId == MASTERY_CRAFTING) { + if (level == 1) return "system.goals.mastery.crafting.level1"; + if (level == 2) return "system.goals.mastery.crafting.level2"; + if (level == 3) return "system.goals.mastery.crafting.level3"; + } + return "system.goals.mastery.unknown_level"; +} + +string mastery_unlock_label(int categoryId, int level) { + return tr(mastery_unlock_key(categoryId, level)); +} + +string mastery_level_summary(int categoryId) { + int level = mastery_current_level(categoryId); + int maxLevel = mastery_max_level(categoryId); + int cost = mastery_cost_for_next_level(categoryId); + if (level >= maxLevel) { + return tr("system.goals.mastery.maxed"); + } + dictionary args; + args.set("category", mastery_category_name(categoryId)); + args.set("level", level); + args.set("max", maxLevel); + args.set("cost", cost); + return trf("system.goals.mastery.summary", args); +} + +bool can_spend_mastery_point(int categoryId) { + if (mastery_current_level(categoryId) >= mastery_max_level(categoryId)) + return false; + int cost = mastery_cost_for_next_level(categoryId); + if (cost < 0) + return false; + return goalPointsUnspent >= cost; +} + +bool spend_mastery_point(int categoryId) { + int cost = mastery_cost_for_next_level(categoryId); + if (cost < 0) + return false; + if (goalPointsUnspent < cost) + return false; + if (categoryId == MASTERY_CRAFTING) { + if (craftingMasteryLevel >= CRAFTING_MASTERY_MAX_LEVEL) + return false; + goalPointsUnspent -= cost; + craftingMasteryLevel++; + dictionary args; + args.set("unlock", mastery_unlock_label(categoryId, craftingMasteryLevel)); + notify(trf("system.goals.mastery.unlocked", args)); + return true; + } + return false; +} + +float get_crafting_mastery_break_multiplier() { + if (craftingMasteryLevel >= 1) + return 0.5; + return 1.0; +} + +int get_goal_points_unspent() { + return goalPointsUnspent; +} + +int get_goal_points_earned() { + return goalPointsEarned; +} + +int get_crafting_mastery_level() { + return craftingMasteryLevel; +} + +void run_goals_menu() { + string[] options; + int currentId = get_current_goal_id(); + if (currentId == -1) { + options.insert_last(tr("system.goals.menu.current_done")); + } else { + dictionary args; + args.set("goal", goal_summary(currentId)); + options.insert_last(trf("system.goals.menu.current", args)); + } + options.insert_last(tr("system.goals.menu.completed")); + options.insert_last(tr("system.goals.menu.mastery")); + dictionary unspentArgs; + unspentArgs.set("unspent", goalPointsUnspent); + options.insert_last(trf("system.goals.menu.unspent", unspentArgs)); + dictionary earnedArgs; + earnedArgs.set("earned", goalPointsEarned); + options.insert_last(trf("system.goals.menu.earned", earnedArgs)); + + speak_menu_prompt("system.goals.menu.prompt", options[0]); + int selection = 0; + speak_with_history(options[selection], true); + + while (true) { + wait(5); + if (menu_background_tick()) { + return; + } + if (key_pressed(KEY_ESCAPE)) { + speak_with_history(tr("system.menu.closed"), true); + return; + } + if (key_pressed(KEY_DOWN)) { + play_menu_move_sound(); + selection++; + if (selection >= int(options.length())) + selection = 0; + speak_with_history(options[selection], true); + } + if (key_pressed(KEY_UP)) { + play_menu_move_sound(); + selection--; + if (selection < 0) + selection = int(options.length()) - 1; + speak_with_history(options[selection], true); + } + if (key_pressed(KEY_RETURN)) { + play_menu_select_sound(); + if (selection == 0) { + speak_with_history(speak_current_goal(), true); + } else if (selection == 1) { + run_completed_goals_menu(); + } else if (selection == 2) { + run_mastery_menu(); + } else if (selection == 3) { + dictionary args; + args.set("unspent", goalPointsUnspent); + speak_with_history(trf("system.goals.menu.unspent_solo", args), true); + } else if (selection == 4) { + dictionary args; + args.set("earned", goalPointsEarned); + speak_with_history(trf("system.goals.menu.earned_solo", args), true); + } + } + } +} + +void run_completed_goals_menu() { + int completedCount = 0; + for (int i = 0; i < GOAL_COUNT; i++) { + if (goalsCompleted[i]) { + completedCount++; + } + } + if (completedCount == 0) { + speak_with_history(tr("system.goals.completed.none"), true); + return; + } + + string[] options; + for (int i = 0; i < GOAL_COUNT; i++) { + if (!goalsCompleted[i]) + continue; + int points = goal_reward_points(i); + dictionary args; + args.set("goal", goal_name(i)); + if (points == 1) { + options.insert_last(trf("system.goals.completed.entry_one", args)); + } else { + args.set("points", points); + options.insert_last(trf("system.goals.completed.entry_many", args)); + } + } + + speak_menu_prompt("system.goals.completed.prompt", options[0]); + int selection = 0; + speak_with_history(options[selection], true); + + while (true) { + wait(5); + if (menu_background_tick()) { + return; + } + if (key_pressed(KEY_ESCAPE)) { + speak_with_history(tr("system.menu.closed"), true); + return; + } + if (key_pressed(KEY_DOWN)) { + play_menu_move_sound(); + selection++; + if (selection >= int(options.length())) + selection = 0; + speak_with_history(options[selection], true); + } + if (key_pressed(KEY_UP)) { + play_menu_move_sound(); + selection--; + if (selection < 0) + selection = int(options.length()) - 1; + speak_with_history(options[selection], true); + } + if (key_pressed(KEY_RETURN)) { + play_menu_select_sound(); + speak_with_history(options[selection], true); + } + } +} + +void run_mastery_menu() { + string[] options; + int[] optionCategories; + for (int i = 0; i < MASTERY_CATEGORY_COUNT; i++) { + options.insert_last(mastery_level_summary(i)); + optionCategories.insert_last(i); + } + options.insert_last(tr("system.goals.menu.back")); + int backIndex = int(options.length()) - 1; + + speak_menu_prompt("system.goals.mastery.prompt", options[0]); + int selection = 0; + speak_with_history(options[selection], true); + + while (true) { + wait(5); + if (menu_background_tick()) { + return; + } + if (key_pressed(KEY_ESCAPE)) { + speak_with_history(tr("system.menu.closed"), true); + return; + } + if (key_pressed(KEY_DOWN)) { + play_menu_move_sound(); + selection++; + if (selection >= int(options.length())) + selection = 0; + speak_with_history(options[selection], true); + } + if (key_pressed(KEY_UP)) { + play_menu_move_sound(); + selection--; + if (selection < 0) + selection = int(options.length()) - 1; + speak_with_history(options[selection], true); + } + if (key_pressed(KEY_RETURN)) { + play_menu_select_sound(); + if (selection == backIndex) { + return; + } + int categoryId = optionCategories[selection]; + if (mastery_current_level(categoryId) >= mastery_max_level(categoryId)) { + speak_with_history(tr("system.goals.mastery.already_maxed"), true); + continue; + } + int cost = mastery_cost_for_next_level(categoryId); + if (goalPointsUnspent < cost) { + dictionary args; + args.set("cost", cost); + args.set("unspent", goalPointsUnspent); + speak_with_history(trf("system.goals.mastery.not_enough_points", args), true); + continue; + } + dictionary promptArgs; + promptArgs.set("cost", cost); + promptArgs.set("unlock", mastery_unlock_label(categoryId, mastery_current_level(categoryId) + 1)); + int confirm = ui_question("", trf("system.goals.mastery.confirm", promptArgs)); + if (confirm != 1) { + speak_with_history(tr("system.menu.canceled"), true); + continue; + } + if (spend_mastery_point(categoryId)) { + options[selection] = mastery_level_summary(categoryId); + speak_with_history(options[selection], true); + } else { + speak_with_history(tr("system.goals.mastery.spend_failed"), true); + } + } + } +} + +string goals_serialize_completed() { + string result = ""; + for (int i = 0; i < GOAL_COUNT; i++) { + if (i > 0) + result += ","; + result += goalsCompleted[i] ? "1" : "0"; + } + return result; +} + +void goals_deserialize_completed(const string& in data) { + if (goalsCompleted.length() != GOAL_COUNT) { + goalsCompleted.resize(GOAL_COUNT); + } + for (int i = 0; i < GOAL_COUNT; i++) { + goalsCompleted[i] = false; + } + if (data.length() == 0) + return; + string[] @parts = data.split(","); + uint count = parts.length(); + if (count > uint(GOAL_COUNT)) + count = uint(GOAL_COUNT); + for (uint i = 0; i < count; i++) { + if (parts[i] == "1") + goalsCompleted[i] = true; + } +} diff --git a/src/inventory_items.nvgt b/src/inventory_items.nvgt index f4725c3..e7f56a9 100644 --- a/src/inventory_items.nvgt +++ b/src/inventory_items.nvgt @@ -15,6 +15,14 @@ const int EQUIP_POUCH = 8; const int EQUIP_BOW = 9; const int EQUIP_BACKPACK = 10; const int EQUIP_FISHING_POLE = 11; +const int EQUIP_GREAT_SPEAR = 12; + +// Great spear equipment: long reach only after Crafting Mastery 3, with slower +// attack recovery than a base spear at all levels. +const int GREAT_SPEAR_BASE_RANGE = 1; +const int GREAT_SPEAR_MASTERY_RANGE = 2; +const int GREAT_SPEAR_DAMAGE = 5; +const int GREAT_SPEAR_ATTACK_COOLDOWN_MS = 1100; // Health bonuses from equipment const int HAT_MAX_HEALTH_BONUS = 1; @@ -25,6 +33,7 @@ const int MOCCASINS_MAX_HEALTH_BONUS = 2; // Equipment state bool spear_equipped = false; +bool great_spear_equipped = false; bool axe_equipped = false; bool sling_equipped = false; bool bow_equipped = false; @@ -136,6 +145,8 @@ string get_equipment_name(int equip_type) { bool equipment_available(int equip_type) { // Check unruned items first, then runed versions + if (equip_type == EQUIP_GREAT_SPEAR) + return get_personal_count(ITEM_GREAT_SPEARS) > 0 || has_any_runed_version(equip_type); if (equip_type == EQUIP_SPEAR) return get_personal_count(ITEM_SPEARS) > 0 || has_any_runed_version(equip_type); if (equip_type == EQUIP_AXE) @@ -164,9 +175,10 @@ bool equipment_available(int equip_type) { } void equip_equipment_type(int equip_type) { - if (equip_type == EQUIP_SPEAR || equip_type == EQUIP_AXE || equip_type == EQUIP_SLING || equip_type == EQUIP_BOW || - equip_type == EQUIP_FISHING_POLE) { + if (equip_type == EQUIP_SPEAR || equip_type == EQUIP_GREAT_SPEAR || equip_type == EQUIP_AXE || + equip_type == EQUIP_SLING || equip_type == EQUIP_BOW || equip_type == EQUIP_FISHING_POLE) { spear_equipped = (equip_type == EQUIP_SPEAR); + great_spear_equipped = (equip_type == EQUIP_GREAT_SPEAR); axe_equipped = (equip_type == EQUIP_AXE); sling_equipped = (equip_type == EQUIP_SLING); bow_equipped = (equip_type == EQUIP_BOW); @@ -193,6 +205,8 @@ void equip_equipment_type(int equip_type) { bool equipment_is_equipped(int equip_type) { if (equip_type == EQUIP_SPEAR) return spear_equipped; + if (equip_type == EQUIP_GREAT_SPEAR) + return great_spear_equipped; if (equip_type == EQUIP_AXE) return axe_equipped; if (equip_type == EQUIP_SLING) @@ -221,6 +235,8 @@ bool equipment_is_equipped(int equip_type) { void unequip_equipment_type(int equip_type) { if (equip_type == EQUIP_SPEAR) { spear_equipped = false; + } else if (equip_type == EQUIP_GREAT_SPEAR) { + great_spear_equipped = false; } else if (equip_type == EQUIP_AXE) { axe_equipped = false; } else if (equip_type == EQUIP_SLING) { @@ -571,6 +587,8 @@ string format_favor(double value) { string get_equipped_weapon_name() { if (spear_equipped) return get_base_equipment_name(EQUIP_SPEAR); + if (great_spear_equipped) + return get_base_equipment_name(EQUIP_GREAT_SPEAR); if (axe_equipped) return get_base_equipment_name(EQUIP_AXE); if (sling_equipped) @@ -601,9 +619,17 @@ string get_speed_status() { return i18n_lookup_key_with_fallback("system.character.speed.normal", "normal"); } +int get_great_spear_range() { + if (get_crafting_mastery_level() >= 3) + return GREAT_SPEAR_MASTERY_RANGE; + return GREAT_SPEAR_BASE_RANGE; +} + void cleanup_equipment_after_inventory_change() { if (!equipment_available(EQUIP_SPEAR)) spear_equipped = false; + if (!equipment_available(EQUIP_GREAT_SPEAR)) + great_spear_equipped = false; if (!equipment_available(EQUIP_AXE)) axe_equipped = false; if (!equipment_available(EQUIP_SLING)) @@ -614,7 +640,8 @@ void cleanup_equipment_after_inventory_change() { fishing_pole_equipped = false; bool any_weapon_equipped = - spear_equipped || axe_equipped || sling_equipped || bow_equipped || fishing_pole_equipped; + spear_equipped || great_spear_equipped || axe_equipped || sling_equipped || bow_equipped || + fishing_pole_equipped; if (!any_weapon_equipped) equipped_weapon_rune = RUNE_NONE; @@ -649,3 +676,22 @@ void cleanup_equipment_after_inventory_change() { clamp_arrows_to_quiver_limit(); update_max_health_from_equipment(); } + +int get_equipped_weapon_equip_type() { + if (spear_equipped) return EQUIP_SPEAR; + if (great_spear_equipped) return EQUIP_GREAT_SPEAR; + if (axe_equipped) return EQUIP_AXE; + if (sling_equipped) return EQUIP_SLING; + if (bow_equipped) return EQUIP_BOW; + if (fishing_pole_equipped) return EQUIP_FISHING_POLE; + return EQUIP_NONE; +} + +void unequip_all_weapons() { + spear_equipped = false; + great_spear_equipped = false; + axe_equipped = false; + sling_equipped = false; + bow_equipped = false; + fishing_pole_equipped = false; +} diff --git a/src/item_registry.nvgt b/src/item_registry.nvgt index 33b43d7..821e825 100644 --- a/src/item_registry.nvgt +++ b/src/item_registry.nvgt @@ -41,7 +41,8 @@ const int ITEM_FISH = 35; const int ITEM_SMOKED_FISH = 36; const int ITEM_HEAL_SCROLL = 37; const int ITEM_BASKET_FOOD = 38; -const int ITEM_COUNT = 39; // Total number of item types +const int ITEM_GREAT_SPEARS = 39; +const int ITEM_COUNT = 40; // Total number of item types // Item definition class class ItemDefinition { @@ -137,6 +138,7 @@ void init_item_registry() { ItemDefinition(ITEM_HEAL_SCROLL, "heal scrolls", "heal scroll", "Heal Scrolls", 0.50); item_registry[ITEM_BASKET_FOOD] = ItemDefinition(ITEM_BASKET_FOOD, "baskets of fruits and nuts", "basket of fruits and nuts", "Baskets of Fruits and Nuts", 0.15); + item_registry[ITEM_GREAT_SPEARS] = ItemDefinition(ITEM_GREAT_SPEARS, "great spears", "great spear", "Great Spears", 1.20); // Define display order for inventory menus // This controls the order items appear in menus @@ -150,7 +152,7 @@ void init_item_registry() { // Misc items ITEM_INCENSE, ITEM_HEAL_SCROLL, // Weapons - ITEM_SPEARS, ITEM_SLINGS, ITEM_AXES, ITEM_BOWS, ITEM_ARROWS, ITEM_QUIVERS, ITEM_BOWSTRINGS, + ITEM_SPEARS, ITEM_GREAT_SPEARS, ITEM_SLINGS, ITEM_AXES, ITEM_BOWS, ITEM_ARROWS, ITEM_QUIVERS, ITEM_BOWSTRINGS, // Tools ITEM_SNARES, ITEM_KNIVES, ITEM_FISHING_POLES, ITEM_ROPES, ITEM_REED_BASKETS, ITEM_CLAY_POTS, ITEM_CANOES, // Clothing diff --git a/src/menus/character_info.nvgt b/src/menus/character_info.nvgt index a47243b..e8103d5 100644 --- a/src/menus/character_info.nvgt +++ b/src/menus/character_info.nvgt @@ -122,10 +122,18 @@ void run_character_info_menu() { favorArgs.set("favor", format_favor(favor)); options.insert_last(trf("system.character.info.favor", favorArgs)); + dictionary pointsArgs; + pointsArgs.set("unspent", goalPointsUnspent); + pointsArgs.set("earned", goalPointsEarned); + options.insert_last(trf("system.character.info.goals", pointsArgs)); + dictionary speedArgs; speedArgs.set("status", get_speed_status()); options.insert_last(trf("system.character.info.speed", speedArgs)); + int goalsOptionIndex = options.length(); + options.insert_last(tr("system.character.info.goals_menu")); + if (petActive) { dictionary petArgs; petArgs.set("pet", get_pet_display_name()); @@ -215,6 +223,11 @@ void run_character_info_menu() { continue; int optionIndex = filtered_indices[selection]; + if (optionIndex == goalsOptionIndex) { + play_menu_select_sound(); + run_goals_menu(); + return; + } if (optionIndex == petOptionIndex && petActive) { if (is_pet_knocked_out()) { dictionary args; diff --git a/src/menus/equipment_menu.nvgt b/src/menus/equipment_menu.nvgt index ef6851a..4f474cf 100644 --- a/src/menus/equipment_menu.nvgt +++ b/src/menus/equipment_menu.nvgt @@ -4,7 +4,8 @@ // Check if player has any equipment (including runed items) bool has_any_equipment() { // Check unruned items - if (get_personal_count(ITEM_SPEARS) > 0 || get_personal_count(ITEM_AXES) > 0 || + if (get_personal_count(ITEM_SPEARS) > 0 || get_personal_count(ITEM_GREAT_SPEARS) > 0 || + get_personal_count(ITEM_AXES) > 0 || get_personal_count(ITEM_SLINGS) > 0 || get_personal_count(ITEM_BOWS) > 0 || get_personal_count(ITEM_FISHING_POLES) > 0 || get_personal_count(ITEM_SKIN_HATS) > 0 || get_personal_count(ITEM_SKIN_GLOVES) > 0 || get_personal_count(ITEM_SKIN_PANTS) > 0 || @@ -73,6 +74,12 @@ void run_equipment_menu() { equipment_types.insert_last(EQUIP_SPEAR); rune_types.insert_last(RUNE_NONE); } + if (get_personal_count(ITEM_GREAT_SPEARS) > 0) { + string status = get_equipment_menu_equipped_suffix(is_runed_item_equipped(EQUIP_GREAT_SPEAR, RUNE_NONE)); + options.insert_last(get_full_equipment_name(EQUIP_GREAT_SPEAR, RUNE_NONE) + status); + equipment_types.insert_last(EQUIP_GREAT_SPEAR); + rune_types.insert_last(RUNE_NONE); + } if (get_personal_count(ITEM_SLINGS) > 0) { string status = get_equipment_menu_equipped_suffix(is_runed_item_equipped(EQUIP_SLING, RUNE_NONE)); options.insert_last(get_full_equipment_name(EQUIP_SLING, RUNE_NONE) + status); diff --git a/src/quest_system.nvgt b/src/quest_system.nvgt index 5000412..900fc9d 100644 --- a/src/quest_system.nvgt +++ b/src/quest_system.nvgt @@ -187,6 +187,7 @@ void apply_quest_reward(int score) { scoreArgs.set("score", score); message += "\n" + trf("system.quest.rewards.score_line", scoreArgs); text_reader(message, tr("system.quest.rewards.title"), true); + complete_goal(GOAL_COMPLETE_QUEST); attempt_pet_offer_from_quest(score); } diff --git a/src/runes/rune_data.nvgt b/src/runes/rune_data.nvgt index 56c92a1..707c30a 100644 --- a/src/runes/rune_data.nvgt +++ b/src/runes/rune_data.nvgt @@ -175,8 +175,13 @@ bool withdraw_runed_item(int equip_type, int rune_type) { } // Get the rune type on equipped item for a given slot +bool is_weapon_equipment_type(int equip_type) { + return equip_type == EQUIP_SPEAR || equip_type == EQUIP_GREAT_SPEAR || equip_type == EQUIP_AXE || + equip_type == EQUIP_SLING || equip_type == EQUIP_BOW; +} + int get_equipped_rune_for_slot(int equip_type) { - if (equip_type == EQUIP_SPEAR || equip_type == EQUIP_AXE || equip_type == EQUIP_SLING || equip_type == EQUIP_BOW) { + if (is_weapon_equipment_type(equip_type)) { return equipped_weapon_rune; } if (equip_type == EQUIP_HAT) @@ -196,7 +201,7 @@ int get_equipped_rune_for_slot(int equip_type) { // Set the rune on an equipped item slot void set_equipped_rune_for_slot(int equip_type, int rune_type) { - if (equip_type == EQUIP_SPEAR || equip_type == EQUIP_AXE || equip_type == EQUIP_SLING || equip_type == EQUIP_BOW) { + if (is_weapon_equipment_type(equip_type)) { equipped_weapon_rune = rune_type; return; } @@ -235,6 +240,7 @@ void clear_equipped_rune_for_slot(int equip_type) { int[] get_runeable_equipment_types() { int[] types; types.insert_last(EQUIP_SPEAR); + types.insert_last(EQUIP_GREAT_SPEAR); types.insert_last(EQUIP_AXE); types.insert_last(EQUIP_SLING); types.insert_last(EQUIP_BOW); @@ -252,6 +258,7 @@ int[] get_runeable_equipment_types_for_rune(int rune_type) { if (rune_type == RUNE_DESTRUCTION) { int[] types; types.insert_last(EQUIP_SPEAR); + types.insert_last(EQUIP_GREAT_SPEAR); types.insert_last(EQUIP_AXE); types.insert_last(EQUIP_SLING); types.insert_last(EQUIP_BOW); diff --git a/src/save_system.nvgt b/src/save_system.nvgt index 4a76079..78196c3 100644 --- a/src/save_system.nvgt +++ b/src/save_system.nvgt @@ -624,11 +624,13 @@ void reset_game_state() { blessing_search_active = false; reset_fylgja_state(); reset_pet_state(); + reset_goal_state(); // Reset inventory using the registry system reset_inventory(); spear_equipped = false; + great_spear_equipped = false; axe_equipped = false; sling_equipped = false; bow_equipped = false; @@ -876,6 +878,7 @@ bool save_game_state() { saveData.set("storage_fish_weights", join_string_array(storageFishWeightsData)); saveData.set("equipment_spear_equipped", spear_equipped); + saveData.set("equipment_great_spear_equipped", great_spear_equipped); saveData.set("equipment_axe_equipped", axe_equipped); saveData.set("equipment_sling_equipped", sling_equipped); saveData.set("equipment_bow_equipped", bow_equipped); @@ -974,6 +977,11 @@ bool save_game_state() { } saveData.set("quest_queue", join_string_array(questData)); + saveData.set("goals_completed", goals_serialize_completed()); + saveData.set("goal_points_unspent", goalPointsUnspent); + saveData.set("goal_points_earned", goalPointsEarned); + saveData.set("crafting_mastery_level", craftingMasteryLevel); + saveData.set("weather_data", serialize_weather()); saveData.set("world_map_size", MAP_SIZE); @@ -1314,6 +1322,7 @@ bool load_game_state_from_file(const string& in filename) { } spear_equipped = get_bool(saveData, "equipment_spear_equipped", false); + great_spear_equipped = get_bool(saveData, "equipment_great_spear_equipped", false); axe_equipped = get_bool(saveData, "equipment_axe_equipped", false); sling_equipped = get_bool(saveData, "equipment_sling_equipped", false); bow_equipped = get_bool(saveData, "equipment_bow_equipped", false); @@ -1332,7 +1341,7 @@ bool load_game_state_from_file(const string& in filename) { slot_count = quick_slots.length(); for (uint i = 0; i < slot_count; i++) { int slot_value = parse_int(loadedQuickSlots[i]); - if (slot_value >= EQUIP_NONE && slot_value <= EQUIP_FISHING_POLE) { + if (slot_value >= EQUIP_NONE && slot_value <= EQUIP_GREAT_SPEAR) { quick_slots[i] = slot_value; } } @@ -1405,6 +1414,8 @@ bool load_game_state_from_file(const string& in filename) { // Validate equipped items now that runed items are loaded if (!equipment_available(EQUIP_SPEAR)) spear_equipped = false; + if (!equipment_available(EQUIP_GREAT_SPEAR)) + great_spear_equipped = false; if (!equipment_available(EQUIP_AXE)) axe_equipped = false; if (!equipment_available(EQUIP_SLING)) @@ -1415,7 +1426,8 @@ bool load_game_state_from_file(const string& in filename) { fishing_pole_equipped = false; bool any_weapon_equipped = - spear_equipped || axe_equipped || sling_equipped || bow_equipped || fishing_pole_equipped; + spear_equipped || great_spear_equipped || axe_equipped || sling_equipped || bow_equipped || + fishing_pole_equipped; if (!any_weapon_equipped) equipped_weapon_rune = RUNE_NONE; @@ -1560,6 +1572,23 @@ bool load_game_state_from_file(const string& in filename) { } } + init_goal_state(); + string loadedGoals; + if (saveData.get("goals_completed", loadedGoals)) { + goals_deserialize_completed(loadedGoals); + } + goalPointsUnspent = int(get_number(saveData, "goal_points_unspent", 0)); + goalPointsEarned = int(get_number(saveData, "goal_points_earned", 0)); + craftingMasteryLevel = int(get_number(saveData, "crafting_mastery_level", 0)); + if (goalPointsUnspent < 0) + goalPointsUnspent = 0; + if (goalPointsEarned < goalPointsUnspent) + goalPointsEarned = goalPointsUnspent; + if (craftingMasteryLevel < 0) + craftingMasteryLevel = 0; + if (craftingMasteryLevel > CRAFTING_MASTERY_MAX_LEVEL) + craftingMasteryLevel = CRAFTING_MASTERY_MAX_LEVEL; + if (current_hour < 0) current_hour = 0; if (current_hour > 23) diff --git a/src/time_system.nvgt b/src/time_system.nvgt index 231fe98..438e7e9 100644 --- a/src/time_system.nvgt +++ b/src/time_system.nvgt @@ -147,7 +147,8 @@ void attempt_player_item_break_check() { return; int roll = random(1, PLAYER_ITEM_BREAK_ROLL_MAX); - int checkChance = int(playerItemBreakChance); // Floor for comparison + float masteryMultiplier = get_crafting_mastery_break_multiplier(); + int checkChance = int(playerItemBreakChance * masteryMultiplier); // Floor for comparison if (roll <= checkChance) { int pickIndex = random(0, int(breakableItems.length()) - 1); int itemType = breakableItems[pickIndex]; diff --git a/src/weapon_range_audio.nvgt b/src/weapon_range_audio.nvgt index 62a0ec8..7ddc57a 100644 --- a/src/weapon_range_audio.nvgt +++ b/src/weapon_range_audio.nvgt @@ -28,6 +28,8 @@ int get_current_weapon_range() { return BOW_RANGE; if (spear_equipped) return 1; + if (great_spear_equipped) + return get_great_spear_range(); if (axe_equipped) return 0; return -1;