Favor lowered. It should require actual work to maintain. Fixed a bug with pets.

This commit is contained in:
Storm Dragon
2026-02-14 00:16:02 -05:00
parent 6687863fbe
commit dc23ddabb2
12 changed files with 276 additions and 326 deletions

149
README.md
View File

@@ -1,150 +1,3 @@
# Draugnorak # Draugnorak
A survival audio game built with NVGT. Explore, gather, craft, and defend your base as day and night cycle. See files/instructions.md for how to play.
## 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 (010).
- 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. **12 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.

View File

@@ -1,162 +1,191 @@
# Draugnorak # 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 ## Installation
- Unzip the file for your operating system. - Unzip the file for your operating system
- On Linux and Mac, make sure the draugnorak executable is marked executable. - On Linux and Mac mark the draugnorak executable as executable
``` bash ```bash
chmod +x draugnorak chmod +x draugnorak
``` ```
## Quick Start ## Quick Start
- Move with Left/Right, jump with Up. - Move with Left or Right
- Hold Shift, /, or Z for 1 second to search the current area. - Jump with Up
- Craft basic tools in the base with C. - Hold Shift or slash or Z for 2 seconds to start a search results come after 1 second delay
- Set snares and build fires to survive. - 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.
Some of the first things you will want are a stone knife a spear a stone axe and a firepit
## Controls ## Controls
- **Left/Right**: Move. - **Left or Right** move
- **Up**: Jump, climb trees, start rope climbs when prompted. - **Up** jump climb trees start rope climbs when prompted
- **Down**: Climb down trees or descend rope climbs when prompted. - **Down** climb down trees descend rope climbs when prompted
- **Shift, /, or Z (hold)**: Search area (1-second hold, 1-second delay). - **Shift or slash or Z hold** search area
- **Control (hold/release)**: Attack with equipped weapon. Bow and sling use a charge window. - **Control hold then 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. - **Space** call your pet send it out or call it back
- **C**: Crafting menu (base only). - **A** action menu place snare feed fire burn incense also check fire fishing pole or snare if in range
- **I**: Inventory. - **C** crafting menu base only
- **E**: Equipment. - **I** inventory
- **P**: Character info. - **E** equipment
- **B**: Base info (base only). - **P** character info
- **Q**: Quests menu (base only). - **B** base info base only
- **S**: Altar menu (base only). - **Q** quests menu base only
- **Tab**: Adventure menu (outside base, once per day). - **S** altar menu base only
- **H**: Health. - **F** fylgja menu once unlocked
- **T**: Time. - **Tab** adventure menu outside base once per day
- **X**: Coordinates + terrain. - **H** health
- **1-0**: Quick slots (equip bound items). - **T** time
- **Backspace**: Pause/Resume. - **X** coordinates and terrain
- **Escape**: Exit game (or close menus). - **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 ### History Shortcuts
- **\\**: Repeat most recent notification. - **Backslash** repeat most recent notification
- **[ / ]**: Previous/next notification. - **Left bracket** previous notification
- **, / .**: Previous/next speech history message. - **Right bracket** next notification
- **Comma** previous speech history message
- **Period** next speech history message
## Menus ## Menus
- **Up/Down**: Move selection. - **Up or Down** move selection
- **Enter**: Confirm. - **Enter** confirm
- **Escape**: Close. - **Escape** close
- **Tab (in most menus)**: Perform max actions (craft all, deposit all, etc.). - **Tab in most menus** perform max actions craft all deposit all and similar
## Gathering and Resources ## Gathering and Resources
- **Grass/Forest/Deep Forest**: Sticks and vines (search). - **Grass Forest Deep Forest** sticks and vines search
- **Gravel/Stone**: Stones (search). - **Gravel Stone** stones search
- **Streams/Water edges**: Reeds and clay (search). - **Streams and 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. - **Snow** found in high mountains currently yields no resources
- **Trees**: - **World drops** found on the ground after hunting search picks them up such as goose turkey and recoverable arrows
- Search near trees for sticks/vines. - **Trees** search near trees for sticks and vines
- Use an axe to chop down a tree for logs (and extra sticks/vines). - **Trees** use an axe to chop a tree for logs and extra sticks and vines
- Trees regrow over time. - **Trees** regrow over time
- You can climb trees (Up) and climb down (Down). - **Trees** you can climb Up and climb down with Down
## Crafting (Base Only) ## Crafting Base Only
Crafting is only available in the base (C). Crafting is only available in the base with C
### Categories ### Categories
- **Weapons**: Spear, Sling, Bow - **Weapons** spear sling bow
- **Tools**: Knife, Snare, Stone Axe, Fishing Pole, Rope, Quiver, Canoe, Reed Basket, Clay Pot - **Tools** knife snare stone axe fishing pole rope quiver canoe reed basket clay pot
- **Materials**: Butcher Game, Smoke Fish, Bowstring, Arrows, Incense - **Materials** butcher game smoke fish bowstring arrows incense
- **Clothing**: Skin gear, Moccasins, Pouch, Backpack - **Clothing** skin gear moccasins pouch backpack
- **Buildings**: Firepit, Fire, Herb Garden, Storage, Pasture, Stable, Altar - **Buildings** firepit fire herb garden storage pasture stable altar
- **Barricade**: Reinforcement options - **Barricade** reinforcement options
- **Runes**: Unlocks later - **Runes** engrave equipment after unlocking runes in adventures
## Combat and Hunting ## Combat and Hunting
- Equip weapons in the Equipment menu (E) or bind them to quick slots. - Equip weapons in the Equipment menu or bind them to quick slots
- **Spear**: 1-tile range. - **Spear** 1 tile range
- **Axe**: Melee; also chops trees. - **Axe** melee also chops trees
- **Sling**: Ranged (uses stones). Hold Control to charge; release on the in-range audio cue. - **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 (010). - **Bow** ranged uses arrows hold Control to draw release to fire damage scales with draw time 0 to 10
- Enemies include zombies (night), bandits (invasions), and boars (daytime). - Enemies include zombies at night bandits during invasions and boars during daytime
- **25% chance** to recover a fired arrow as a world drop. - **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**. - If your base has an altar each undead kill grants 0.2 favor
## Base and Survival ## Base and Survival
- **Passive healing** in the base when below max health. - Passive healing in the base when below max health
- **Herb Garden** increases healing speed (base only, one per base). - Herb Garden increases healing speed base only one per base
- **Fires** require a nearby firepit and need fuel (sticks, vines, logs). - Fires require a nearby firepit and need fuel sticks vines logs
- **Barricade** starts at 100 health, capped at 500; reinforces via Crafting > Barricade. - Barricade starts at 40 health capped at 500 reinforces via Crafting Barricade
- **Storage** enables base inventory management and helps attract residents. - Storage enables base inventory management and helps attract residents
- **Residents** (if recruited) consume meat, help defend, repair the barricade, and collect resources. - Residents if recruited consume meat help defend repair the barricade and collect resources
## Residents and Automation ## 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 and Care
- **Recruitment**: Residents join automatically after invasions. If you have **Storage** built, the likelihood for survivors to become residents increases. **12 survivors** can join at a time, up to a maximum of **4 residents**. Beware the vampyr, it will eat your residents. - **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** each resident consumes 1 unit of food every 8 hours 3 per day
- Priority: Meat > Smoked Fish > Basket of Fruits and Nuts. - **Food priority** meat then smoked fish then basket of fruits and nuts
- If no food is available, residents will go hungry and stop working. - **Hunger** if no food is available residents 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. - **Blessing** the Blessing of the Resident from the altar doubles efficiency and reduces tool breakage for a short time
### Activities and Requirements ### 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 | Tools used by residents can break keep a stock of spares
| :--- | :--- | :--- |
| **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! ## Pets
Pets are optional companions that can fight and retrieve drops
## Time, Weather, and Events ### Getting a pet
- **1 real minute = 1 in-game hour**. - Pets can appear after a quest score of 10 or more
- Day/night ambience crossfades around sunrise/sunset. - Pets can appear after adventure victories with a small chance
- Weather transitions between clear, windy, rainy, and stormy. - Reaching the top of a tree can attract a hawk with a small chance
- **Invasions** can begin after day 2 and expand the map eastward. - You need food to accept a pet offer
- Expansions can be normal biomes or mountain ranges.
### 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 and Rope Climbing
- Mountains have elevation; steep slopes require a rope. - Mountains have elevation steep slopes require a rope
- When prompted, press Up to climb up or Down to climb down. - When prompted press Up to climb up or Down to climb down
- Moving left/right during a rope climb cancels and causes a fall. - Moving left or right during a rope climb cancels and causes a fall
- Falling from height can cause damage. - Falling from height can cause damage
## Quests, Adventures, and Runes ## Quests Adventures and Runes
- **Quests** become available in the base once you have an altar and enough favor. - 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). - Adventures are accessed with Tab outside the base once per day
- The mountain adventure features the **Unicorn boss**. - The mountain adventure features the Unicorn boss unlocking the Rune of Swiftness
- Defeating the Unicorn unlocks the **Rune of Swiftness**. - The forest adventure features the Bandit Hideout unlocking the Rune of Destruction
- Runes are engraved in the base (Crafting > Runes) and require a knife, clay, and favor. - 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 ## Inventory and Storage
- Personal inventory stacks are capped at 9. - Personal inventory stacks are capped at 9
- Skin pouches add +2 stack capacity, backpacks add +9. - Skin pouches add 2 stack capacity backpacks add 9
- Arrows require quivers. Each quiver adds capacity for 12 arrows. - Arrows require quivers each quiver adds capacity for 12 arrows
- Bowstrings are crafted from **sinew at a fire** and used to craft bows. - 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). - Storage is separate and accessed in the base Inventory menu when storage exists
## Saving and Loading ## Saving and Loading
- The game auto-saves daily at 6 AM (in-game time). - The game auto saves daily at 6 AM in game time
- Load from the main menu. Starting a new game prompts if a save exists. - Load from the main menu starting a new game prompts if a save exists
- In the Load Game menu press Delete to remove the selected save (confirmation required)

View File

@@ -11,9 +11,9 @@ const int BANDIT_HIDEOUT_BASE_SPAWN_RANGE = 10;
const int BANDIT_HIDEOUT_START_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_PER_DAY = 34;
const int BANDIT_HIDEOUT_BARRICADE_HP_MAX = 500; const int BANDIT_HIDEOUT_BARRICADE_HP_MAX = 500;
const double BANDIT_HIDEOUT_FAVOR_PER_KILL = 0.2; const double BANDIT_HIDEOUT_FAVOR_PER_KILL = 0.08;
const double BANDIT_HIDEOUT_BASE_FAVOR = 3.0; const double BANDIT_HIDEOUT_BASE_FAVOR = 0.30;
const double BANDIT_HIDEOUT_FAVOR_MAX = 10.0; const double BANDIT_HIDEOUT_FAVOR_MAX = 1.0;
class HideoutBandit { class HideoutBandit {
int position; int position;

View File

@@ -653,7 +653,7 @@ void give_unicorn_rewards() {
speak_with_history("Victory!", true); speak_with_history("Victory!", true);
// Calculate rewards // 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; favor += favor_reward;
int scrolls_added = add_to_stack(get_personal_count(ITEM_HEAL_SCROLL), 1); int scrolls_added = add_to_stack(get_personal_count(ITEM_HEAL_SCROLL), 1);
add_personal_count(ITEM_HEAL_SCROLL, scrolls_added); add_personal_count(ITEM_HEAL_SCROLL, scrolls_added);
@@ -667,7 +667,7 @@ void give_unicorn_rewards() {
string[] rewards; string[] rewards;
rewards.insert_last("=== Victory Rewards ==="); rewards.insert_last("=== Victory Rewards ===");
rewards.insert_last(""); 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("Heal Scrolls: +" + scrolls_added + ".");
rewards.insert_last(""); rewards.insert_last("");
if (new_rune) { if (new_rune) {

View File

@@ -442,7 +442,14 @@ int add_to_stack(int current, int amount) {
string format_favor(double value) { string format_favor(double value) {
if (value < 0) value = 0; 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() { string get_equipped_weapon_name() {

View File

@@ -110,6 +110,14 @@ bool sort_string_case_insensitive_sounds(const string &in a, const string &in b)
return a.lower() < b.lower(); 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 collapse_spaces(const string&in text) {
string result = text; string result = text;
while (result.find_first(" ") > -1) { 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"); string[]@ mdFiles = find_files("files/*.md");
if (@mdFiles !is null) { if (@mdFiles !is null) {
for (uint i = 0; i < mdFiles.length(); i++) { 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"); string[]@ mdCapsFiles = find_files("files/*.MD");
if (@mdCapsFiles !is null) { if (@mdCapsFiles !is null) {
for (uint i = 0; i < mdCapsFiles.length(); i++) { for (uint i = 0; i < mdCapsFiles.length(); i++) {
docFiles.insert_last(mdCapsFiles[i]); append_unique_case_insensitive(docFiles, mdCapsFiles[i]);
} }
} }

View File

@@ -1,6 +1,11 @@
// Altar/sacrifice menu system // Altar/sacrifice menu system
// Functions for sacrificing items at the altar // 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) { void check_altar_menu(int player_x) {
if (!key_pressed(KEY_S)) return; if (!key_pressed(KEY_S)) return;
@@ -43,7 +48,7 @@ void sacrifice_item(int item_type) {
cleanup_equipment_after_inventory_change(); cleanup_equipment_after_inventory_change();
double favor_gain = get_item_favor_value(item_type); double favor_gain = get_item_favor_value(item_type);
favor += favor_gain; 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) { 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; double total_favor = favor_per_item * available;
favor += total_favor; 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() { 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; int selection = 0;
string[] options; string[] options;

View File

@@ -404,8 +404,10 @@ void check_pet_call_key() {
petOut = true; petOut = true;
petPosition = get_pet_listener_pos(); petPosition = get_pet_listener_pos();
petPositionValid = true; petPositionValid = true;
if (file_exists("sounds/pets/call_pet.ogg")) { if (file_exists("sounds/action/call_pet.ogg")) {
p.play_stationary("sounds/pets/call_pet.ogg", false); /* 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); add_personal_count(ITEM_SMALL_GAME, 1);
personal_small_game_types.insert_last(gameType); 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; return true;
} }
@@ -681,7 +683,7 @@ bool try_pet_pickup_world_drop(WorldDrop@ drop, string &out message) {
return true; return true;
} }
add_personal_count(ITEM_ARROWS, 1); 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; return true;
} }
if (drop.type == "boar carcass") { if (drop.type == "boar carcass") {
@@ -690,7 +692,7 @@ bool try_pet_pickup_world_drop(WorldDrop@ drop, string &out message) {
return true; return true;
} }
add_personal_count(ITEM_BOAR_CARCASSES, 1); 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 true;
} }
return false; 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()) { if (!has_pet_food_available()) {
adjust_pet_loyalty(-PET_LOYALTY_HUNGER_LOSS); adjust_pet_loyalty(-PET_LOYALTY_HUNGER_LOSS);
} }

View File

@@ -45,7 +45,7 @@ string get_quest_description(int quest_type) {
int get_quest_chance_from_favor() { int get_quest_chance_from_favor() {
int chance = int(favor * QUEST_CHANCE_PER_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; if (chance > 100) chance = 100;
return chance; return chance;
} }
@@ -66,7 +66,7 @@ void add_quest(int quest_type) {
void attempt_daily_quest() { void attempt_daily_quest() {
if (quest_roll_done_today) return; if (quest_roll_done_today) return;
if (favor <= 1.0) return; if (favor < 1.0) return;
if (world_altars.length() == 0) return; if (world_altars.length() == 0) return;
if (quest_queue.length() >= QUEST_MAX_ACTIVE) 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) { void apply_quest_reward(int score) {
double favor_gain = score * QUEST_FAVOR_PER_POINT; double favor_gain = score * QUEST_FAVOR_PER_POINT;
if (favor_gain < 0) favor_gain = 0; if (favor_gain < 0) favor_gain = 0;
if (favor_gain > 1.0) favor_gain = 1.0;
favor += favor_gain; favor += favor_gain;
// Determine quantity based on score // Determine quantity based on score
@@ -142,6 +143,7 @@ void apply_quest_reward(int score) {
string message = "Quest Complete!\n\nRewards:\n"; string message = "Quest Complete!\n\nRewards:\n";
message += get_quest_favor_phrase(score) + "\n"; message += get_quest_favor_phrase(score) + "\n";
message += "Favor: +" + format_favor(favor_gain) + "\n";
if (added_amount > 0) { if (added_amount > 0) {
message += get_item_display_name(reward_item) + ": +" + added_amount + "\n"; message += get_item_display_name(reward_item) + ": +" + added_amount + "\n";

View File

@@ -414,47 +414,85 @@ bool setup_new_character() {
} }
bool select_save_file(string &out filename) { 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) { while (true) {
wait(5); string[] files = get_save_files();
if (key_pressed(KEY_DOWN)) { if (files.length() == 0) return false;
play_menu_move_sound();
selection++; string[] options;
if (selection >= options.length()) selection = 0; string[] displayNames;
speak_with_history(options[selection], true); 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(); int selection = 0;
selection--; speak_with_history("Load game. Select character. Press delete to remove a save.", true);
if (selection < 0) selection = int(options.length()) - 1; speak_with_history(options[selection], 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(); if (!refreshList) {
filename = files[selection];
return true;
}
if (key_pressed(KEY_ESCAPE)) {
return false; return false;
} }
} }

View File

@@ -894,7 +894,8 @@ string get_time_string() {
} else if (weather_state == WEATHER_STORMY) { } else if (weather_state == WEATHER_STORMY) {
conditions.insert_last("Storming"); 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"); conditions.insert_last("Moon and stars");
} }
if (conditions.length() > 0) { if (conditions.length() > 0) {