From 11c64625ccf3cb201108abe5dede75cd55199ba5 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Thu, 5 Feb 2026 03:06:25 -0500 Subject: [PATCH] A couple more quests added. --- sounds/quests/boomerang.ogg | 3 + src/constants.nvgt | 2 +- src/crafting/craft_runes.nvgt | 67 +++++++++++++++++ src/quest_system.nvgt | 28 ++++++- src/quests/catch_the_boomerang_game.nvgt | 89 +++++++++++++++++++++++ src/quests/skeletal_bard_game.nvgt | 93 ++++++++++++++++++++++++ 6 files changed, 278 insertions(+), 4 deletions(-) create mode 100644 sounds/quests/boomerang.ogg create mode 100644 src/quests/catch_the_boomerang_game.nvgt create mode 100644 src/quests/skeletal_bard_game.nvgt diff --git a/sounds/quests/boomerang.ogg b/sounds/quests/boomerang.ogg new file mode 100644 index 0000000..c199a5a --- /dev/null +++ b/sounds/quests/boomerang.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e0ce93a20815637a15f117e0e03768436c146f9ddf9a61b6239f20c67147a035 +size 4542 diff --git a/src/constants.nvgt b/src/constants.nvgt index 83ea370..b762017 100644 --- a/src/constants.nvgt +++ b/src/constants.nvgt @@ -222,7 +222,7 @@ const int GOOSE_FALL_SPEED = 100; // ms per foot const int GOOSE_FLY_AWAY_CHANCE = 0; // Chance out of 1000 per tick to fly away const int GOOSE_MAX_DIST_FROM_WATER = 4; // How far they can wander from water const int GOOSE_MAX_COUNT = 3; -const int GOOSE_HOURLY_SPAWN_CHANCE = 35; // Percent chance per hour to spawn a goose +const int GOOSE_HOURLY_SPAWN_CHANCE = 40; // Percent chance per hour to spawn a goose const int GOOSE_SIGHT_RANGE = 0; // Weather settings diff --git a/src/crafting/craft_runes.nvgt b/src/crafting/craft_runes.nvgt index 969bdb2..85765bf 100644 --- a/src/crafting/craft_runes.nvgt +++ b/src/crafting/craft_runes.nvgt @@ -19,6 +19,22 @@ string get_base_equipment_name(int equip_type) { return "Unknown"; } +string get_base_equipment_name_plural(int equip_type) { + if (equip_type == EQUIP_SPEAR) return "Spears"; + if (equip_type == EQUIP_AXE) return "Stone Axes"; + if (equip_type == EQUIP_SLING) return "Slings"; + if (equip_type == EQUIP_BOW) return "Bows"; + if (equip_type == EQUIP_HAT) return "Skin Hats"; + if (equip_type == EQUIP_GLOVES) return "Skin Gloves"; + if (equip_type == EQUIP_PANTS) return "Skin Pants"; + if (equip_type == EQUIP_TUNIC) return "Skin Tunics"; + if (equip_type == EQUIP_MOCCASINS) return "Moccasins"; + if (equip_type == EQUIP_POUCH) return "Skin Pouches"; + if (equip_type == EQUIP_BACKPACK) return "Backpacks"; + if (equip_type == EQUIP_FISHING_POLE) return "Fishing Poles"; + return "Items"; +} + // Get inventory count for an equipment type int get_unruned_equipment_count(int equip_type) { if (equip_type == EQUIP_SPEAR) return get_personal_count(ITEM_SPEARS); @@ -158,6 +174,12 @@ void run_rune_equipment_menu(int rune_type) { engrave_rune(equip_type, rune_type); break; } + + if (key_pressed(KEY_TAB)) { + int equip_type = equipment_types[selection]; + engrave_rune_max(equip_type, rune_type); + break; + } } } @@ -194,3 +216,48 @@ void engrave_rune(int equip_type, int rune_type) { speak_with_history("Missing: " + missing, true); } } + +void engrave_rune_max(int equip_type, int rune_type) { + // Validate requirements + string missing = ""; + if (get_personal_count(ITEM_KNIVES) < 1) missing += "Stone Knife "; + + int unruned_count = get_unruned_equipment_count(equip_type); + if (unruned_count < 1) { + speak_with_history("No " + get_base_equipment_name(equip_type) + " available.", true); + return; + } + + int clay_count = get_personal_count(ITEM_CLAY); + int favor_count = int(favor); + int max_craft = unruned_count; + if (clay_count < max_craft) max_craft = clay_count; + if (favor_count < max_craft) max_craft = favor_count; + + if (missing != "") { + speak_with_history("Missing: " + missing, true); + return; + } + + if (max_craft <= 0) { + if (clay_count < 1) missing += "1 clay "; + if (favor < 1.0) missing += "1 favor "; + if (missing == "") missing = "resources"; + speak_with_history("Missing: " + missing, true); + return; + } + + // Consume materials (knife is not consumed, it's a tool) + add_personal_count(ITEM_CLAY, -max_craft); + favor -= double(max_craft); + + for (int i = 0; i < max_craft; i++) { + decrement_unruned_equipment(equip_type); + add_runed_item(equip_type, rune_type); + } + + simulate_crafting(6 * max_craft); + + string item_name = (max_craft == 1) ? get_base_equipment_name(equip_type) : get_base_equipment_name_plural(equip_type); + speak_with_history("Engraved " + max_craft + " " + item_name + " with " + get_rune_name(rune_type) + ".", true); +} diff --git a/src/quest_system.nvgt b/src/quest_system.nvgt index 38ec1bb..d07e983 100644 --- a/src/quest_system.nvgt +++ b/src/quest_system.nvgt @@ -1,20 +1,26 @@ // Quest system #include "src/quests/bat_invasion_game.nvgt" +#include "src/quests/catch_the_boomerang_game.nvgt" #include "src/quests/enchanted_melody_game.nvgt" #include "src/quests/escape_from_hel_game.nvgt" +#include "src/quests/skeletal_bard_game.nvgt" const int QUEST_BAT_INVASION = 0; -const int QUEST_ENCHANTED_MELODY = 1; -const int QUEST_ESCAPE_FROM_HEL = 2; -const int QUEST_TYPE_COUNT = 3; +const int QUEST_CATCH_BOOMERANG = 1; +const int QUEST_ENCHANTED_MELODY = 2; +const int QUEST_ESCAPE_FROM_HEL = 3; +const int QUEST_SKELETAL_BARD = 4; +const int QUEST_TYPE_COUNT = 5; int[] quest_queue; bool quest_roll_done_today = false; string get_quest_name(int quest_type) { if (quest_type == QUEST_BAT_INVASION) return "Bat Invasion"; + if (quest_type == QUEST_CATCH_BOOMERANG) return "Catch the Boomerang"; if (quest_type == QUEST_ENCHANTED_MELODY) return "Enchanted Melody"; if (quest_type == QUEST_ESCAPE_FROM_HEL) return "Escape from Hel"; + if (quest_type == QUEST_SKELETAL_BARD) return "Skeletal Bard"; return "Unknown Quest"; } @@ -22,12 +28,18 @@ string get_quest_description(int quest_type) { if (quest_type == QUEST_BAT_INVASION) { return "Bat Invasion. Giant killer bats are attacking. Press space to throw when the bat is centered."; } + if (quest_type == QUEST_CATCH_BOOMERANG) { + return "Catch the Boomerang. Throw and catch the boomerang as it returns for up to 4 points."; + } if (quest_type == QUEST_ENCHANTED_MELODY) { return "Enchanted Melody. Repeat the pattern using E R D F or U I J K. Lowest to highest pitch."; } if (quest_type == QUEST_ESCAPE_FROM_HEL) { return "Escape from Hel. Press space to jump over open graves. The pace quickens."; } + if (quest_type == QUEST_SKELETAL_BARD) { + return "Skeletal Bard. A skeleton named Billy Bones is practicing to become a bard. Count the notes."; + } return "Unknown quest."; } @@ -38,6 +50,14 @@ int get_quest_chance_from_favor() { return chance; } +void quest_menu_background_tick() { + menu_background_tick(); +} + +void quest_boomerang_hit_sound() { + play_player_damage_sound(); +} + void add_quest(int quest_type) { if (quest_queue.length() >= QUEST_MAX_ACTIVE) return; quest_queue.insert_last(quest_type); @@ -139,8 +159,10 @@ void run_quest(int quest_type) { p.pause_all(); int score = 0; if (quest_type == QUEST_BAT_INVASION) score = run_bat_invasion(); + else if (quest_type == QUEST_CATCH_BOOMERANG) score = run_catch_the_boomerang(); else if (quest_type == QUEST_ENCHANTED_MELODY) score = run_enchanted_melody(); else if (quest_type == QUEST_ESCAPE_FROM_HEL) score = run_escape_from_hel(); + else if (quest_type == QUEST_SKELETAL_BARD) score = run_skeletal_bard(); apply_quest_reward(score); p.resume_all(); } diff --git a/src/quests/catch_the_boomerang_game.nvgt b/src/quests/catch_the_boomerang_game.nvgt new file mode 100644 index 0000000..5bf27d5 --- /dev/null +++ b/src/quests/catch_the_boomerang_game.nvgt @@ -0,0 +1,89 @@ +// Catch the Boomerang quest game +int run_catch_the_boomerang() { + string[] instructions; + instructions.insert_last("=== Catch the Boomerang ==="); + instructions.insert_last(""); + instructions.insert_last("Throw a boomerang and catch it on the return."); + instructions.insert_last(""); + instructions.insert_last("How to play:"); + instructions.insert_last(" - Press Space to throw"); + instructions.insert_last(" - Press Space again when it sounds close"); + instructions.insert_last(" - There are 5 turns"); + instructions.insert_last(" - Better timing earns more points (up to 4)"); + instructions.insert_last(""); + instructions.insert_last("Close this screen to begin."); + text_reader_lines(instructions, "Quest Instructions", true); + + wait(500); + + const string boomerangSound = "sounds/quests/boomerang.ogg"; + const int turnsTotal = 5; + int score = 0; + int caughtCount = 0; + string[] skillLevel = {"", "almost dropped", "managed to catch", "caught", "expertly caught"}; + + bool firstTurnPrompt = true; + + for (int turn = 0; turn < turnsTotal; turn++) { + if (firstTurnPrompt) { + speak_with_history("Press Space to throw.", true); + firstTurnPrompt = false; + } + + while (true) { + wait(5); + if (key_pressed(KEY_SPACE)) { + break; + } + } + + int maxDistance = random(40, 70); + int boomerangY = -2; + bool returning = false; + bool resolved = false; + int stepDelay = random(180, 280); + int stepSize = 2; + + while (!resolved) { + wait(stepDelay); + + p.play_2d(boomerangSound, 0.0, 0.0, 0.0, boomerangY, false); + + if (key_pressed(KEY_SPACE)) { + if (returning && boomerangY > -5) { + int points = 5 + boomerangY; + if (points < 1) points = 1; + if (points > 4) points = 4; + score += points; + caughtCount++; + string promptSuffix = (turn < turnsTotal - 1) ? " Press Space to throw." : ""; + speak_with_history("You " + skillLevel[points] + " the boomerang. " + points + " points." + promptSuffix, true); + } else { + string promptSuffix = (turn < turnsTotal - 1) ? " Press Space to throw." : ""; + speak_with_history("You missed the boomerang." + promptSuffix, true); + } + resolved = true; + } + + if (!resolved) { + if (!returning && boomerangY <= -maxDistance) { + returning = true; + } + + boomerangY += returning ? stepSize : -stepSize; + + if (boomerangY >= 0) { + string promptSuffix = (turn < turnsTotal - 1) ? " Press Space to throw." : ""; + speak_with_history("The boomerang hit you." + promptSuffix, true); + quest_boomerang_hit_sound(); + resolved = true; + } + } + } + + wait(400); + } + + speak_with_history("You caught " + caughtCount + " boomerangs for a total of " + score + " points.", true); + return score; +} diff --git a/src/quests/skeletal_bard_game.nvgt b/src/quests/skeletal_bard_game.nvgt new file mode 100644 index 0000000..9d4b1c7 --- /dev/null +++ b/src/quests/skeletal_bard_game.nvgt @@ -0,0 +1,93 @@ +// Skeletal Bard quest game +string[] skeletalBardNotes = { + "sounds/quests/bone1.ogg", + "sounds/quests/bone2.ogg", + "sounds/quests/bone3.ogg", + "sounds/quests/bone4.ogg", + "sounds/quests/bone5.ogg", + "sounds/quests/bone6.ogg", + "sounds/quests/bone7.ogg", + "sounds/quests/bone8.ogg" +}; + +void play_skeletal_bard_note() { + int noteIndex = random(0, skeletalBardNotes.length() - 1); + p.play_stationary(skeletalBardNotes[noteIndex], false); +} + +int select_note_count(int minNotes, int maxNotes, int startValue) { + int selection = startValue; + speak_with_history("Select number of notes. " + selection + ".", true); + + while (true) { + wait(5); + quest_menu_background_tick(); + + if (key_pressed(KEY_UP)) { + if (selection < maxNotes) selection++; + speak_with_history("" + selection + ".", true); + } + + if (key_pressed(KEY_DOWN)) { + if (selection > minNotes) selection--; + speak_with_history("" + selection + ".", true); + } + + if (key_pressed(KEY_RETURN)) { + return selection; + } + } + return selection; +} + +int run_skeletal_bard() { + string[] instructions; + instructions.insert_last("=== Skeletal Bard ==="); + instructions.insert_last(""); + instructions.insert_last("A skeleton named Billy Bones is practicing to become a bard."); + instructions.insert_last("Listen to the melody and count the notes."); + instructions.insert_last(""); + instructions.insert_last("How to play:"); + instructions.insert_last(" - After the tune, choose the number of notes"); + instructions.insert_last(" - Use Up/Down to change the number"); + instructions.insert_last(" - Press Enter to confirm"); + instructions.insert_last(" - There are 5 rounds, up to 4 points each"); + instructions.insert_last(""); + instructions.insert_last("Close this screen to begin."); + text_reader_lines(instructions, "Quest Instructions", true); + + wait(500); + + int score = 0; + const int rounds = 5; + + for (int round = 0; round < rounds; round++) { + int length = random(5, 20); + + for (int i = 0; i < length; i++) { + play_skeletal_bard_note(); + wait(random(200, 400)); + } + + wait(400); + + int guess = select_note_count(5, 20, 5); + int diff = length - guess; + if (diff < 0) diff = -diff; + + int points = 4 - diff; + if (points < 0) points = 0; + score += points; + + speak_with_history("You entered " + guess + " notes and the actual number was " + length + ". " + points + " points. Press Enter to continue.", true); + while (true) { + wait(5); + if (key_pressed(KEY_RETURN)) { + break; + } + } + } + + speak_with_history("Skeletal Bard complete. Your score for all five tunes is " + score + ".", true); + return score; +}