From dc23ddabb2ffaba48fd2d996929a91f6885e6901 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Sat, 14 Feb 2026 00:16:02 -0500 Subject: [PATCH] Favor lowered. It should require actual work to maintain. Fixed a bug with pets. --- README.md | 149 +------------- files/instructions.md | 271 ++++++++++++++------------ sounds/{pets => actions}/call_pet.ogg | 0 src/bosses/bandit_hideout.nvgt | 6 +- src/bosses/unicorn/unicorn_boss.nvgt | 4 +- src/inventory_items.nvgt | 9 +- src/learn_sounds.nvgt | 12 +- src/menus/altar_menu.nvgt | 11 +- src/pet_system.nvgt | 17 +- src/quest_system.nvgt | 6 +- src/save_system.nvgt | 114 +++++++---- src/time_system.nvgt | 3 +- 12 files changed, 276 insertions(+), 326 deletions(-) rename sounds/{pets => actions}/call_pet.ogg (100%) diff --git a/README.md b/README.md index 59ae10e..36f292d 100644 --- a/README.md +++ b/README.md @@ -1,150 +1,3 @@ # Draugnorak -A survival audio game built with NVGT. Explore, gather, craft, and defend your base as day and night cycle. - -## Installation -- Unzip the file for your operating system. -- On Linux and Mac, make sure the draugnorak executable is marked executable. -``` bash -chmod +x draugnorak -``` - -## Quick Start -- Move with Left/Right, jump with Up. -- Hold Shift, /, or Z for 1 second to search the current area. -- Craft basic tools in the base with C. -- Set snares and build fires to survive. - -Some of the first things you will want are a stone knife, spear, stone axe, and firepit. -## Controls -- **Left/Right**: Move. -- **Up**: Jump, climb trees, start rope climbs when prompted. -- **Down**: Climb down trees or descend rope climbs when prompted. -- **Shift, /, or Z (hold)**: Search area (1-second hold, 1-second delay). -- **Control (hold/release)**: Attack with equipped weapon. Sling uses a charge window. -- **A**: Action menu (place snare, feed fire, burn incense). -- **C**: Crafting menu (base only). -- **I**: Inventory. -- **E**: Equipment. -- **P**: Character info. -- **B**: Base info (base only). -- **Q**: Quests menu (base only). -- **S**: Altar menu (base only). -- **Tab**: Adventure menu (outside base, once per day). -- **H**: Health. -- **T**: Time. -- **X**: Coordinates + terrain. -- **1-0**: Quick slots (equip bound items). -- **Backspace**: Pause/Resume. -- **Escape**: Exit game (or close menus). - -### History Shortcuts -- **\\**: Repeat most recent notification. -- **[ / ]**: Previous/next notification. -- **, / .**: Previous/next speech history message. - -## Menus -- **Up/Down**: Move selection. -- **Enter**: Confirm. -- **Escape**: Close. -- **Tab (in most menus)**: Perform “max” actions (craft all, deposit all, etc.). - -## Gathering and Resources -- **Grass/Forest/Deep Forest**: Sticks and vines (search). -- **Gravel/Stone**: Stones (search). -- **Streams/Water edges**: Reeds and clay (search). -- **World drops**: Found on the ground after hunting; search picks them up. -- **Trees**: - - Search near trees for sticks/vines. - - Use an axe to chop down a tree for logs (and extra sticks/vines). - - Trees regrow over time. - - You can climb trees (Up) and climb down (Down). - -## Crafting (Base Only) -Crafting is only available in the base (C). - -### Categories -- **Weapons**: Spear, Sling, Bow -- **Tools**: Knife, Snare, Stone Axe, Fishing Pole, Rope, Quiver, Canoe, Reed Basket, Clay Pot -- **Materials**: Butcher Game, Smoke Fish, Bowstring, Arrows, Incense -- **Clothing**: Skin gear, Moccasins, Pouch, Backpack -- **Buildings**: Firepit, Fire, Herb Garden, Storage, Pasture, Stable, Altar -- **Barricade**: Reinforcement options -- **Runes**: Unlocks later - -## Combat and Hunting -- Equip weapons in the Equipment menu (E) or bind them to quick slots. -- **Spear**: 1-tile range. -- **Axe**: Melee; also chops trees. -- **Sling**: Ranged (uses stones). Hold Control to charge; release on the “in-range” audio cue. -- **Bow**: Ranged (uses arrows). Hold Control to draw; release to fire. Damage scales with draw time (0–10). -- Enemies include zombies (night), bandits (invasions), and boars (daytime). -- **25% chance** to recover a fired arrow as a world drop. -- If your base has an altar, each undead kill grants **+0.2 favor**. - -## Base and Survival -- **Passive healing** in the base when below max health. -- **Herb Garden** increases healing speed (base only, one per base). -- **Fires** require a nearby firepit and need fuel (sticks, vines, logs). -- **Barricade** starts at 100 health, capped at 500; reinforces via Crafting > Barricade. -- **Storage** enables base inventory management and helps attract residents. -- **Residents** (if recruited) consume meat, help defend, repair the barricade, and collect resources. - -## Residents and Automation -Residents can automate survival tasks, defend your base, and process resources. To get the most out of them, ensure you have the necessary tools and facilities in your base storage. - -### Recruitment and Care -- **Recruitment**: Residents join automatically after invasions. If you have **Storage** built, the likelihood for survivors to become residents increases. **1–2 survivors** can join at a time, up to a maximum of **4 residents**. -- **Food**: Each resident consumes **1 unit of food every 8 hours** (3 per day). - - Priority: Meat > Smoked Fish > Basket of Fruits and Nuts. - - If no food is available, residents will go hungry and stop working. -- **Blessing**: The "Blessing of the Resident" (from the Altar) doubles their efficiency and reduces tool breakage for a short time. - -### Activities and Requirements -Residents automatically perform these tasks if the requirements are met in your base storage. - -| Activity | Requires (in Storage) | Details | -| :--- | :--- | :--- | -| **Defense (Melee)** | **Spear** | Deals damage to attackers. Spears may break. | -| **Defense (Ranged - Bow)** | **Bow** + **Arrows** | Preferred ranged defense. Consumes arrows. Bows have a chance to break. | -| **Defense (Ranged - Sling)** | **Sling** + **Stones** | Attacks enemies from a distance. Consumes stones. Slings have a chance to break. | -| **Repairs** | N/A | Repairs the **Barricade** during the day. | -| **Gathering** | **Reed Basket** | Collects Sticks, Vines, Stones, and Logs. | -| **Foraging** | **Reed Basket** | Occurs at 6 AM and 12 PM. Produces **Basket of Fruits and Nuts**. | -| **Fishing** | **Fishing Pole** | Daytime only. Requires a nearby stream. Catches **Raw Fish**. | -| **Smoking Fish** | **Raw Fish** + **Sticks** | Requires a **burning fire**. Converts fish to **Smoked Fish**. | -| **Butchering** | **Knife** + **Game** | Requires a **burning fire**. Processes Small Game or Boar Carcasses into Meat, Skins, etc. | -| **Snare Check** | **Placed Snares** | Checks active snares, retrieves **Small Game**, and resets the snare. | - -> **Note**: Tools (Spears, Slings, Bows, Baskets, Knives, Fishing Poles) have a chance to break when used by residents. Keep a stock of spares! - -## Time, Weather, and Events -- **1 real minute = 1 in-game hour**. -- Day/night ambience crossfades around sunrise/sunset. -- Weather transitions between clear, windy, rainy, and stormy. -- **Invasions** can begin after day 2 and expand the map eastward. -- Expansions can be normal biomes or mountain ranges. - -## Mountains and Rope Climbing -- Mountains have elevation; steep slopes require a rope. -- When prompted, press Up to climb up or Down to climb down. -- Moving left/right during a rope climb cancels and causes a fall. -- Falling from height can cause damage. - -## Quests, Adventures, and Runes -- **Quests** become available in the base once you have an altar and enough favor. -- **Adventures** are accessed with Tab outside the base (once per day). -- The mountain adventure features the **Unicorn boss**. -- Defeating the Unicorn unlocks the **Rune of Swiftness**. -- Runes are engraved in the base (Crafting > Runes) and require a knife, clay, and favor. - -## Inventory and Storage -- Personal inventory stacks are capped at 9. -- Skin pouches add +2 stack capacity, backpacks add +9. -- Arrows require quivers. Each quiver adds capacity for 12 arrows. -- 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). - -## 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. +See files/instructions.md for how to play. diff --git a/files/instructions.md b/files/instructions.md index 023c7e4..95ee9d0 100644 --- a/files/instructions.md +++ b/files/instructions.md @@ -1,162 +1,191 @@ # Draugnorak -A survival audio game built with NVGT. Explore, gather, craft, and defend your base as day and night cycle. +A survival audio game built with NVGT +Explore gather craft defend your base through the day and night cycle ## Installation -- Unzip the file for your operating system. -- On Linux and Mac, make sure the draugnorak executable is marked executable. +- Unzip the file for your operating system +- On Linux and Mac mark the draugnorak executable as executable -``` bash +```bash chmod +x draugnorak ``` - ## Quick Start -- Move with Left/Right, jump with Up. -- Hold Shift, /, or Z for 1 second to search the current area. -- Craft basic tools in the base with C. -- Set snares and build fires to survive. - -Some of the first things you will want are a stone knife, spear, stone axe, and firepit. +- Move with Left or Right +- Jump with Up +- Hold Shift or slash or Z for 2 seconds to start a search results come after 1 second delay +- Craft basic tools in the base with C +- Set snares and build fires to survive +Some of the first things you will want are a stone knife a spear a stone axe and a firepit ## Controls -- **Left/Right**: Move. -- **Up**: Jump, climb trees, start rope climbs when prompted. -- **Down**: Climb down trees or descend rope climbs when prompted. -- **Shift, /, or Z (hold)**: Search area (1-second hold, 1-second delay). -- **Control (hold/release)**: Attack with equipped weapon. Bow and sling use a charge window. -- **A**: Action menu (place snare, feed fire, burn incense). Also check fire, fishing pole, or snare if in range. -- **C**: Crafting menu (base only). -- **I**: Inventory. -- **E**: Equipment. -- **P**: Character info. -- **B**: Base info (base only). -- **Q**: Quests menu (base only). -- **S**: Altar menu (base only). -- **Tab**: Adventure menu (outside base, once per day). -- **H**: Health. -- **T**: Time. -- **X**: Coordinates + terrain. -- **1-0**: Quick slots (equip bound items). -- **Backspace**: Pause/Resume. -- **Escape**: Exit game (or close menus). - +- **Left or Right** move +- **Up** jump climb trees start rope climbs when prompted +- **Down** climb down trees descend rope climbs when prompted +- **Shift or slash or Z hold** search area +- **Control hold then release** attack with equipped weapon bow and sling use a charge window +- **Space** call your pet send it out or call it back +- **A** action menu place snare feed fire burn incense also check fire fishing pole or snare if in range +- **C** crafting menu base only +- **I** inventory +- **E** equipment +- **P** character info +- **B** base info base only +- **Q** quests menu base only +- **S** altar menu base only +- **F** fylgja menu once unlocked +- **Tab** adventure menu outside base once per day +- **H** health +- **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 +- **Backspace** pause or resume +- **Escape** exit game or close menus ### History Shortcuts -- **\\**: Repeat most recent notification. -- **[ / ]**: Previous/next notification. -- **, / .**: Previous/next speech history message. - +- **Backslash** repeat most recent notification +- **Left bracket** previous notification +- **Right bracket** next notification +- **Comma** previous speech history message +- **Period** next speech history message ## Menus -- **Up/Down**: Move selection. -- **Enter**: Confirm. -- **Escape**: Close. -- **Tab (in most menus)**: Perform “max” actions (craft all, deposit all, etc.). - +- **Up or Down** move selection +- **Enter** confirm +- **Escape** close +- **Tab in most menus** perform max actions craft all deposit all and similar ## Gathering and Resources -- **Grass/Forest/Deep Forest**: Sticks and vines (search). -- **Gravel/Stone**: Stones (search). -- **Streams/Water edges**: Reeds and clay (search). -- **World drops**: Found on the ground after hunting; search picks them up e.g. goose, turkey, and recoverable arrows. -- **Trees**: - - Search near trees for sticks/vines. - - Use an axe to chop down a tree for logs (and extra sticks/vines). - - Trees regrow over time. - - You can climb trees (Up) and climb down (Down). +- **Grass Forest Deep Forest** sticks and vines search +- **Gravel Stone** stones search +- **Streams and water edges** reeds and clay search +- **Snow** found in high mountains currently yields no resources +- **World drops** found on the ground after hunting search picks them up such as goose turkey and recoverable arrows +- **Trees** search near trees for sticks and vines +- **Trees** use an axe to chop a tree for logs and extra sticks and vines +- **Trees** regrow over time +- **Trees** you can climb Up and climb down with Down -## Crafting (Base Only) -Crafting is only available in the base (C). +## Crafting Base Only +Crafting is only available in the base with C ### Categories -- **Weapons**: Spear, Sling, Bow -- **Tools**: Knife, Snare, Stone Axe, Fishing Pole, Rope, Quiver, Canoe, Reed Basket, Clay Pot -- **Materials**: Butcher Game, Smoke Fish, Bowstring, Arrows, Incense -- **Clothing**: Skin gear, Moccasins, Pouch, Backpack -- **Buildings**: Firepit, Fire, Herb Garden, Storage, Pasture, Stable, Altar -- **Barricade**: Reinforcement options -- **Runes**: Unlocks later - +- **Weapons** spear sling bow +- **Tools** knife snare stone axe fishing pole rope quiver canoe reed basket clay pot +- **Materials** butcher game smoke fish bowstring arrows incense +- **Clothing** skin gear moccasins pouch backpack +- **Buildings** firepit fire herb garden storage pasture stable altar +- **Barricade** reinforcement options +- **Runes** engrave equipment after unlocking runes in adventures ## Combat and Hunting -- Equip weapons in the Equipment menu (E) or bind them to quick slots. -- **Spear**: 1-tile range. -- **Axe**: Melee; also chops trees. -- **Sling**: Ranged (uses stones). Hold Control to charge; release on the “in-range” audio cue. -- **Bow**: Ranged (uses arrows). Hold Control to draw; release to fire. Damage scales with draw time (0–10). -- Enemies include zombies (night), bandits (invasions), and boars (daytime). -- **25% chance** to recover a fired arrow as a world drop. -- If your base has an altar, each undead kill grants **+0.2 favor**. - +- Equip weapons in the Equipment menu or bind them to quick slots +- **Spear** 1 tile range +- **Axe** melee also chops trees +- **Sling** ranged uses stones hold Control to charge release on the in range audio cue +- **Bow** ranged uses arrows hold Control to draw release to fire damage scales with draw time 0 to 10 +- Enemies include zombies at night bandits during invasions and boars during daytime +- **25 percent chance** to recover a fired arrow as a world drop +- If your base has an altar each undead kill grants 0.2 favor ## Base and Survival -- **Passive healing** in the base when below max health. -- **Herb Garden** increases healing speed (base only, one per base). -- **Fires** require a nearby firepit and need fuel (sticks, vines, logs). -- **Barricade** starts at 100 health, capped at 500; reinforces via Crafting > Barricade. -- **Storage** enables base inventory management and helps attract residents. -- **Residents** (if recruited) consume meat, help defend, repair the barricade, and collect resources. - +- Passive healing in the base when below max health +- Herb Garden increases healing speed base only one per base +- Fires require a nearby firepit and need fuel sticks vines logs +- Barricade starts at 40 health capped at 500 reinforces via Crafting Barricade +- Storage enables base inventory management and helps attract residents +- Residents if recruited consume meat help defend repair the barricade and collect resources ## Residents and Automation -Residents can automate survival tasks, defend your base, and process resources. To get the most out of them, ensure you have the necessary tools and facilities in your base storage. - +Residents can automate survival tasks defend your base and process resources +To get the most out of them ensure you have the necessary tools and facilities in your base storage ### Recruitment and Care -- **Recruitment**: Residents join automatically after invasions. If you have **Storage** built, the likelihood for survivors to become residents increases. **1–2 survivors** can join at a time, up to a maximum of **4 residents**. Beware the vampyr, it will eat your residents. -- **Food**: Each resident consumes **1 unit of food every 8 hours** (3 per day). - - Priority: Meat > Smoked Fish > Basket of Fruits and Nuts. - - If no food is available, residents will go hungry and stop working. -- **Blessing**: The "Blessing of the Resident" (from the Altar) doubles their efficiency and reduces tool breakage for a short time. - +- **Recruitment** residents can join after invasions end if your barricade is standing storage increases the chance 1 to 2 survivors can join at a time up to a maximum of 4 residents beware the vampyr it will eat your residents +- **Food** each resident consumes 1 unit of food every 8 hours 3 per day +- **Food priority** meat then smoked fish then basket of fruits and nuts +- **Hunger** if no food is available residents go hungry and stop working +- **Blessing** the Blessing of the Resident from the altar doubles efficiency and reduces tool breakage for a short time ### Activities and Requirements -Residents automatically perform these tasks if the requirements are met in your base storage. +- **Defense melee** requires spear deals damage to attackers spears can break +- **Defense ranged bow** requires bow and arrows preferred ranged defense consumes arrows bows can break +- **Defense ranged sling** requires sling and stones attacks from a distance consumes stones slings can break +- **Repairs** no tools required repairs the barricade during the day +- **Gathering** requires reed basket collects sticks vines stones and logs +- **Foraging** requires reed basket occurs during daytime and scales with resident count produces basket of fruits and nuts +- **Fishing** requires fishing pole daytime only requires a nearby stream catches raw fish +- **Smoking fish** requires raw fish and sticks requires a burning fire converts fish to smoked fish +- **Butchering** requires knife and game requires a burning fire processes small game or boar carcasses into meat skins and more +- **Snare check** requires placed snares checks active snares retrieves small game and resets the snare -| Activity | Requires (in Storage) | Details | -| :--- | :--- | :--- | -| **Defense (Melee)** | **Spear** | Deals damage to attackers. Spears may break. | -| **Defense (Ranged - Bow)** | **Bow** + **Arrows** | Preferred ranged defense. Consumes arrows. Bows have a chance to break. | -| **Defense (Ranged - Sling)** | **Sling** + **Stones** | Attacks enemies from a distance. Consumes stones. Slings have a chance to break. | -| **Repairs** | N/A | Repairs the **Barricade** during the day. | -| **Gathering** | **Reed Basket** | Collects Sticks, Vines, Stones, and Logs. | -| **Foraging** | **Reed Basket** | Occurs at 6 AM and 12 PM. Produces **Basket of Fruits and Nuts**. | -| **Fishing** | **Fishing Pole** | Daytime only. Requires a nearby stream. Catches **Raw Fish**. | -| **Smoking Fish** | **Raw Fish** + **Sticks** | Requires a **burning fire**. Converts fish to **Smoked Fish**. | -| **Butchering** | **Knife** + **Game** | Requires a **burning fire**. Processes Small Game or Boar Carcasses into Meat, Skins, etc. | -| **Snare Check** | **Placed Snares** | Checks active snares, retrieves **Small Game**, and resets the snare. | +Tools used by residents can break keep a stock of spares -> **Note**: Tools (Spears, Slings, Bows, Baskets, Knives, Fishing Poles) have a chance to break when used by residents. Keep a stock of spares! +## Pets +Pets are optional companions that can fight and retrieve drops -## Time, Weather, and Events -- **1 real minute = 1 in-game hour**. -- Day/night ambience crossfades around sunrise/sunset. -- Weather transitions between clear, windy, rainy, and stormy. -- **Invasions** can begin after day 2 and expand the map eastward. -- Expansions can be normal biomes or mountain ranges. +### Getting a pet +- Pets can appear after a quest score of 10 or more +- Pets can appear after adventure victories with a small chance +- Reaching the top of a tree can attract a hawk with a small chance +- You need food to accept a pet offer + +### Calling and status +- **Space** call your pet +- If your pet is out Space calls it back +- If your pet is with you Space sends it out and costs 1 loyalty +- If your pet is knocked out Space reports remaining recovery time +- Press H to hear pet health and loyalty when you have a pet + +### Loyalty and feeding +- Loyalty starts at 5 and max is 10 +- If no food is available loyalty drops by 2 each hour +- Every 8 hours your pet eats 1 food and gains 2 loyalty if food is available +- Food can be meat smoked fish raw fish or basket of fruits and nuts +- Food is taken from your inventory first then storage +- If loyalty reaches 0 your pet leaves + +### Help in the field +- When out your pet attacks bandits undead and boars within range +- Each successful hit costs the pet 1 health +- If health reaches 0 the pet is knocked out for 3 hours and then recovers +- When out your pet can retrieve world drops like small game boar carcasses and arrows +- If you cannot carry the item the pet keeps it and it is lost + +### Random finds +- If loyalty is at least 5 your pet can bring back sticks vines stones or clay + +## Time Weather and Events +- 1 real minute equals 1 in game hour +- Day night ambience crossfades around sunrise and sunset +- Weather transitions between clear windy rainy and stormy +- Invasions begin on or after day 2 and expand the map east +- Expansions can be normal biomes or mountain ranges ## Mountains and Rope Climbing -- Mountains have elevation; steep slopes require a rope. -- When prompted, press Up to climb up or Down to climb down. -- Moving left/right during a rope climb cancels and causes a fall. -- Falling from height can cause damage. +- Mountains have elevation steep slopes require a rope +- When prompted press Up to climb up or Down to climb down +- Moving left or right during a rope climb cancels and causes a fall +- Falling from height can cause damage -## Quests, Adventures, and Runes -- **Quests** become available in the base once you have an altar and enough favor. -- **Adventures** are accessed with Tab outside the base (once per day). -- The mountain adventure features the **Unicorn boss**. -- Defeating the Unicorn unlocks the **Rune of Swiftness**. -- Runes are engraved in the base (Crafting > Runes) and require a knife, clay, and favor. +## Quests Adventures and Runes +- Quests become available in the base once you have an altar and enough favor +- Adventures are accessed with Tab outside the base once per day +- The mountain adventure features the Unicorn boss unlocking the Rune of Swiftness +- The forest adventure features the Bandit Hideout unlocking the Rune of Destruction +- Fylgja unlocks after 9 wins in its adventure press F to activate once per day for example Unicorn Charge +- Runes are engraved in the base Crafting Runes and require a knife clay and favor ## Inventory and Storage -- Personal inventory stacks are capped at 9. -- Skin pouches add +2 stack capacity, backpacks add +9. -- Arrows require quivers. Each quiver adds capacity for 12 arrows. -- 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). +- Personal inventory stacks are capped at 9 +- Skin pouches add 2 stack capacity backpacks add 9 +- Arrows require quivers each quiver adds capacity for 12 arrows +- 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 ## 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. +- 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 +- In the Load Game menu press Delete to remove the selected save (confirmation required) diff --git a/sounds/pets/call_pet.ogg b/sounds/actions/call_pet.ogg similarity index 100% rename from sounds/pets/call_pet.ogg rename to sounds/actions/call_pet.ogg diff --git a/src/bosses/bandit_hideout.nvgt b/src/bosses/bandit_hideout.nvgt index fe0dee1..0ec16b7 100644 --- a/src/bosses/bandit_hideout.nvgt +++ b/src/bosses/bandit_hideout.nvgt @@ -11,9 +11,9 @@ const int BANDIT_HIDEOUT_BASE_SPAWN_RANGE = 10; const int BANDIT_HIDEOUT_START_SPAWN_RANGE = 10; const int BANDIT_HIDEOUT_BARRICADE_HP_PER_DAY = 34; const int BANDIT_HIDEOUT_BARRICADE_HP_MAX = 500; -const double BANDIT_HIDEOUT_FAVOR_PER_KILL = 0.2; -const double BANDIT_HIDEOUT_BASE_FAVOR = 3.0; -const double BANDIT_HIDEOUT_FAVOR_MAX = 10.0; +const double BANDIT_HIDEOUT_FAVOR_PER_KILL = 0.08; +const double BANDIT_HIDEOUT_BASE_FAVOR = 0.30; +const double BANDIT_HIDEOUT_FAVOR_MAX = 1.0; class HideoutBandit { int position; diff --git a/src/bosses/unicorn/unicorn_boss.nvgt b/src/bosses/unicorn/unicorn_boss.nvgt index be434d2..06a3e16 100644 --- a/src/bosses/unicorn/unicorn_boss.nvgt +++ b/src/bosses/unicorn/unicorn_boss.nvgt @@ -653,7 +653,7 @@ void give_unicorn_rewards() { speak_with_history("Victory!", true); // Calculate rewards - int favor_reward = 4 + random(1, 4); // 4 + 1d4 = 5-8 favor + double favor_reward = double(random(7, 10)) / 10.0; // 0.7-1.0 favor favor += favor_reward; int scrolls_added = add_to_stack(get_personal_count(ITEM_HEAL_SCROLL), 1); add_personal_count(ITEM_HEAL_SCROLL, scrolls_added); @@ -667,7 +667,7 @@ void give_unicorn_rewards() { string[] rewards; rewards.insert_last("=== Victory Rewards ==="); rewards.insert_last(""); - rewards.insert_last("The gods are pleased with your victory! " + favor_reward + " favor awarded."); + rewards.insert_last("The gods are pleased with your victory! " + format_favor(favor_reward) + " favor awarded."); rewards.insert_last("Heal Scrolls: +" + scrolls_added + "."); rewards.insert_last(""); if (new_rune) { diff --git a/src/inventory_items.nvgt b/src/inventory_items.nvgt index 9d68d5e..a6b09f4 100644 --- a/src/inventory_items.nvgt +++ b/src/inventory_items.nvgt @@ -442,7 +442,14 @@ int add_to_stack(int current, int amount) { string format_favor(double value) { if (value < 0) value = 0; - return "" + int(value); + int scaled = int((value * 100.0) + 0.5); + int wholePart = scaled / 100; + int fractionalPart = scaled % 100; + + if (fractionalPart <= 0) return "" + wholePart; + if (fractionalPart < 10) return wholePart + ".0" + fractionalPart; + if (fractionalPart % 10 == 0) return wholePart + "." + (fractionalPart / 10); + return wholePart + "." + fractionalPart; } string get_equipped_weapon_name() { diff --git a/src/learn_sounds.nvgt b/src/learn_sounds.nvgt index 681560a..2fd03a3 100644 --- a/src/learn_sounds.nvgt +++ b/src/learn_sounds.nvgt @@ -110,6 +110,14 @@ bool sort_string_case_insensitive_sounds(const string &in a, const string &in b) return a.lower() < b.lower(); } +void append_unique_case_insensitive(string[]@ items, const string&in value) { + string lowerValue = value.lower(); + for (uint i = 0; i < items.length(); i++) { + if (items[i].lower() == lowerValue) return; + } + items.insert_last(value); +} + string collapse_spaces(const string&in text) { string result = text; while (result.find_first(" ") > -1) { @@ -159,13 +167,13 @@ void add_doc_entries(string[]@ labels, string[]@ paths, int[]@ types) { string[]@ mdFiles = find_files("files/*.md"); if (@mdFiles !is null) { for (uint i = 0; i < mdFiles.length(); i++) { - docFiles.insert_last(mdFiles[i]); + append_unique_case_insensitive(docFiles, mdFiles[i]); } } string[]@ mdCapsFiles = find_files("files/*.MD"); if (@mdCapsFiles !is null) { for (uint i = 0; i < mdCapsFiles.length(); i++) { - docFiles.insert_last(mdCapsFiles[i]); + append_unique_case_insensitive(docFiles, mdCapsFiles[i]); } } diff --git a/src/menus/altar_menu.nvgt b/src/menus/altar_menu.nvgt index 645e272..fc08af8 100644 --- a/src/menus/altar_menu.nvgt +++ b/src/menus/altar_menu.nvgt @@ -1,6 +1,11 @@ // Altar/sacrifice menu system // Functions for sacrificing items at the altar +string format_altar_favor(double value) { + if (value < 0) value = 0; + return "" + int(value); +} + void check_altar_menu(int player_x) { if (!key_pressed(KEY_S)) return; @@ -43,7 +48,7 @@ void sacrifice_item(int item_type) { cleanup_equipment_after_inventory_change(); double favor_gain = get_item_favor_value(item_type); favor += favor_gain; - speak_with_history("Sacrificed 1 " + get_item_label_singular(item_type) + ". Favor +" + format_favor(favor_gain) + ". Total " + format_favor(favor) + ".", true); + speak_with_history("Sacrificed 1 " + get_item_label_singular(item_type) + ". Favor +" + format_altar_favor(favor_gain) + ". Total " + format_altar_favor(favor) + ".", true); } void sacrifice_item_max(int item_type) { @@ -71,11 +76,11 @@ void sacrifice_item_max(int item_type) { double total_favor = favor_per_item * available; favor += total_favor; - speak_with_history("Sacrificed " + available + " " + get_item_label(item_type) + ". Favor +" + format_favor(total_favor) + ". Total " + format_favor(favor) + ".", true); + speak_with_history("Sacrificed " + available + " " + get_item_label(item_type) + ". Favor +" + format_altar_favor(total_favor) + ". Total " + format_altar_favor(favor) + ".", true); } void run_altar_menu() { - speak_with_history("Altar. Favor " + format_favor(favor) + ".", true); + speak_with_history("Altar. Favor " + format_altar_favor(favor) + ".", true); int selection = 0; string[] options; diff --git a/src/pet_system.nvgt b/src/pet_system.nvgt index 6663697..d3bbb6a 100644 --- a/src/pet_system.nvgt +++ b/src/pet_system.nvgt @@ -404,8 +404,10 @@ void check_pet_call_key() { petOut = true; petPosition = get_pet_listener_pos(); petPositionValid = true; - if (file_exists("sounds/pets/call_pet.ogg")) { - p.play_stationary("sounds/pets/call_pet.ogg", false); + if (file_exists("sounds/action/call_pet.ogg")) { + /* But I can feel it, black water washes over me. + As it soothes I call to you with my control. */ + p.play_stationary("sounds/action/call_pet.ogg", false); } } @@ -661,7 +663,7 @@ bool try_pet_pickup_small_game(const string&in gameType, string &out message) { } add_personal_count(ITEM_SMALL_GAME, 1); personal_small_game_types.insert_last(gameType); - message = "Your " + get_pet_display_name() + " retrieved " + gameType + ". A " + get_pet_display_name() + " returns to you."; + message = "Your " + get_pet_display_name() + " retrieved " + gameType + "."; return true; } @@ -681,7 +683,7 @@ bool try_pet_pickup_world_drop(WorldDrop@ drop, string &out message) { return true; } add_personal_count(ITEM_ARROWS, 1); - message = "Your " + get_pet_display_name() + " retrieved an arrow. A " + get_pet_display_name() + " returns to you."; + message = "Your " + get_pet_display_name() + " retrieved an arrow."; return true; } if (drop.type == "boar carcass") { @@ -690,7 +692,7 @@ bool try_pet_pickup_world_drop(WorldDrop@ drop, string &out message) { return true; } add_personal_count(ITEM_BOAR_CARCASSES, 1); - message = "Your " + get_pet_display_name() + " retrieved a boar carcass. A " + get_pet_display_name() + " returns to you."; + message = "Your " + get_pet_display_name() + " retrieved a boar carcass."; return true; } return false; @@ -868,6 +870,11 @@ void handle_pet_hourly_update(int hour) { } } + if (petKnockoutHoursRemaining <= 0 && petHealth > 0 && petHealth < PET_HEALTH_MAX) { + petHealth += 1; + if (petHealth > PET_HEALTH_MAX) petHealth = PET_HEALTH_MAX; + } + if (!has_pet_food_available()) { adjust_pet_loyalty(-PET_LOYALTY_HUNGER_LOSS); } diff --git a/src/quest_system.nvgt b/src/quest_system.nvgt index 0620af0..0f02626 100644 --- a/src/quest_system.nvgt +++ b/src/quest_system.nvgt @@ -45,7 +45,7 @@ string get_quest_description(int quest_type) { int get_quest_chance_from_favor() { int chance = int(favor * QUEST_CHANCE_PER_FAVOR); - if (chance < QUEST_MIN_CHANCE && favor > 1.0) chance = QUEST_MIN_CHANCE; + if (chance < QUEST_MIN_CHANCE && favor >= 1.0) chance = QUEST_MIN_CHANCE; if (chance > 100) chance = 100; return chance; } @@ -66,7 +66,7 @@ void add_quest(int quest_type) { void attempt_daily_quest() { if (quest_roll_done_today) return; - if (favor <= 1.0) return; + if (favor < 1.0) return; if (world_altars.length() == 0) return; if (quest_queue.length() >= QUEST_MAX_ACTIVE) return; @@ -105,6 +105,7 @@ string get_quest_favor_phrase(int score) { void apply_quest_reward(int score) { double favor_gain = score * QUEST_FAVOR_PER_POINT; if (favor_gain < 0) favor_gain = 0; + if (favor_gain > 1.0) favor_gain = 1.0; favor += favor_gain; // Determine quantity based on score @@ -142,6 +143,7 @@ void apply_quest_reward(int score) { string message = "Quest Complete!\n\nRewards:\n"; message += get_quest_favor_phrase(score) + "\n"; + message += "Favor: +" + format_favor(favor_gain) + "\n"; if (added_amount > 0) { message += get_item_display_name(reward_item) + ": +" + added_amount + "\n"; diff --git a/src/save_system.nvgt b/src/save_system.nvgt index 75af86f..142553f 100644 --- a/src/save_system.nvgt +++ b/src/save_system.nvgt @@ -414,47 +414,85 @@ bool setup_new_character() { } bool select_save_file(string &out filename) { - string[] files = get_save_files(); - if (files.length() == 0) return false; - - string[] options; - for (uint i = 0; i < files.length(); i++) { - string displayName; - int sex = SEX_MALE; - int day = 1; - if (!read_save_metadata(files[i], displayName, sex, day)) { - displayName = strip_save_extension(files[i]); - options.insert_last(displayName); - } else { - string sex_label = (sex == SEX_FEMALE) ? "female" : "male"; - options.insert_last(displayName + ", " + sex_label + ", day " + day); - } - } - - int selection = 0; - speak_with_history("Load game. Select character.", true); - speak_with_history(options[selection], true); - while (true) { - wait(5); - if (key_pressed(KEY_DOWN)) { - play_menu_move_sound(); - selection++; - if (selection >= options.length()) selection = 0; - speak_with_history(options[selection], true); + string[] files = get_save_files(); + if (files.length() == 0) return false; + + string[] options; + string[] displayNames; + int[] sexValues; + int[] dayValues; + bool[] hasMetadata; + for (uint i = 0; i < files.length(); i++) { + string displayName; + int sex = SEX_MALE; + int day = 1; + bool gotMeta = read_save_metadata(files[i], displayName, sex, day); + if (!gotMeta) { + displayName = strip_save_extension(files[i]); + options.insert_last(displayName); + } else { + string sex_label = (sex == SEX_FEMALE) ? "female" : "male"; + options.insert_last(displayName + ", " + sex_label + ", day " + day); + } + displayNames.insert_last(displayName); + sexValues.insert_last(sex); + dayValues.insert_last(day); + hasMetadata.insert_last(gotMeta); } - if (key_pressed(KEY_UP)) { - play_menu_move_sound(); - selection--; - if (selection < 0) selection = int(options.length()) - 1; - speak_with_history(options[selection], true); + + int selection = 0; + speak_with_history("Load game. Select character. Press delete to remove a save.", true); + speak_with_history(options[selection], true); + + bool refreshList = false; + while (true) { + wait(5); + if (key_pressed(KEY_DOWN)) { + play_menu_move_sound(); + selection++; + if (selection >= 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(); + filename = files[selection]; + return true; + } + if (key_pressed(KEY_DELETE)) { + string prompt = "Are you sure you want to delete the character " + displayNames[selection]; + if (hasMetadata[selection]) { + string sex_label = (sexValues[selection] == SEX_FEMALE) ? "female" : "male"; + prompt += " gender " + sex_label + " days " + dayValues[selection] + "?"; + } else { + prompt += "?"; + } + int confirm = ui_question("", prompt); + if (confirm == 1) { + if (file_delete(files[selection])) { + speak_with_history("Save deleted.", true); + refreshList = true; + break; + } else { + ui_info_box("Draugnorak", "Delete Save", "Unable to delete save."); + speak_with_history(options[selection], true); + } + } else { + speak_with_history(options[selection], true); + } + } + if (key_pressed(KEY_ESCAPE)) { + return false; + } } - if (key_pressed(KEY_RETURN)) { - play_menu_select_sound(); - filename = files[selection]; - return true; - } - if (key_pressed(KEY_ESCAPE)) { + + if (!refreshList) { return false; } } diff --git a/src/time_system.nvgt b/src/time_system.nvgt index 7345b68..5b1755f 100644 --- a/src/time_system.nvgt +++ b/src/time_system.nvgt @@ -894,7 +894,8 @@ string get_time_string() { } else if (weather_state == WEATHER_STORMY) { conditions.insert_last("Storming"); } - if (!is_daytime) { + bool can_see_night_sky = (weather_state == WEATHER_CLEAR || weather_state == WEATHER_WINDY); + if (!is_daytime && can_see_night_sky) { conditions.insert_last("Moon and stars"); } if (conditions.length() > 0) {