diff --git a/src/enemies/bandit.nvgt b/src/enemies/bandit.nvgt index 0e0419b..efa762e 100644 --- a/src/enemies/bandit.nvgt +++ b/src/enemies/bandit.nvgt @@ -10,9 +10,7 @@ class Bandit { string weapon_type; // "spear" or "axe" int sound_handle; timer move_timer; - timer alert_timer; timer attack_timer; - int next_alert_delay; int move_interval; // Wandering behavior properties @@ -38,9 +36,7 @@ class Bandit { move_interval = random(BANDIT_MOVE_INTERVAL_MIN, BANDIT_MOVE_INTERVAL_MAX); move_timer.restart(); - alert_timer.restart(); attack_timer.restart(); - next_alert_delay = random(BANDIT_ALERT_MIN_DELAY, BANDIT_ALERT_MAX_DELAY); // Initialize wandering behavior (start aggressive during invasion) behavior_state = "aggressive"; @@ -87,7 +83,8 @@ void spawn_bandit(int expansion_start, int expansion_end) { Bandit@ b = Bandit(spawn_x, expansion_start, expansion_end); bandits.insert_last(b); - b.sound_handle = play_creature_voice(b.alert_sound, x, spawn_x, BANDIT_SOUND_VOLUME_STEP); + // Play looping sound that follows the bandit + b.sound_handle = play_1d_with_volume_step(b.alert_sound, x, spawn_x, true, BANDIT_SOUND_VOLUME_STEP); } bool can_bandit_attack_player(Bandit@ bandit) { @@ -177,18 +174,15 @@ void try_attack_barricade_bandit(Bandit@ bandit) { } void update_bandit(Bandit@ bandit) { - // Play alert sound at intervals - if (bandit.alert_timer.elapsed > bandit.next_alert_delay) { - bandit.alert_timer.restart(); - bandit.next_alert_delay = random(BANDIT_ALERT_MIN_DELAY, BANDIT_ALERT_MAX_DELAY); - - // Destroy old sound handle before playing new one to prevent overlapping + // Update looping sound position + if (bandit.sound_handle != -1 && p.sound_is_active(bandit.sound_handle)) { + p.update_sound_1d(bandit.sound_handle, bandit.position); + } else if (bandit.sound_handle == -1 || !p.sound_is_active(bandit.sound_handle)) { + // Restart looping sound if it stopped if (bandit.sound_handle != -1) { p.destroy_sound(bandit.sound_handle); - bandit.sound_handle = -1; } - - bandit.sound_handle = play_creature_voice(bandit.alert_sound, x, bandit.position, BANDIT_SOUND_VOLUME_STEP); + bandit.sound_handle = play_1d_with_volume_step(bandit.alert_sound, x, bandit.position, true, BANDIT_SOUND_VOLUME_STEP); } if (try_attack_player_bandit(bandit)) { diff --git a/src/enemies/flying_creatures.nvgt b/src/enemies/flying_creatures.nvgt index da0ce12..1f69d4e 100644 --- a/src/enemies/flying_creatures.nvgt +++ b/src/enemies/flying_creatures.nvgt @@ -38,10 +38,8 @@ class FlyingCreature { int sound_handle; int fall_sound_handle; timer move_timer; - timer sound_timer; timer fall_timer; int next_move_delay; - int next_sound_delay; string voice_sound; bool fading_out; bool ready_to_remove; @@ -63,10 +61,8 @@ class FlyingCreature { } move_timer.restart(); - sound_timer.restart(); next_move_delay = random(cfg.move_interval_min, cfg.move_interval_max); - next_sound_delay = random(cfg.sound_delay_min, cfg.sound_delay_max); fading_out = false; ready_to_remove = false; } @@ -229,7 +225,8 @@ bool spawn_flying_creature(string creature_type) { FlyingCreature@ c = FlyingCreature(creature_type, spawn_x, area_start, area_end, cfg); flying_creatures.insert_last(c); - c.sound_handle = play_creature_voice(c.voice_sound, x, spawn_x, cfg.sound_volume_step); + // Play looping sound that follows the flying creature + c.sound_handle = play_1d_with_volume_step(c.voice_sound, x, spawn_x, true, cfg.sound_volume_step); return true; } @@ -267,10 +264,15 @@ void update_flying_creature(FlyingCreature@ creature) { return; } - if (creature.sound_timer.elapsed > creature.next_sound_delay) { - creature.sound_timer.restart(); - creature.next_sound_delay = random(cfg.sound_delay_min, cfg.sound_delay_max); - creature.sound_handle = play_creature_voice(creature.voice_sound, x, creature.position, cfg.sound_volume_step); + // Update looping sound position + if (creature.sound_handle != -1 && p.sound_is_active(creature.sound_handle)) { + p.update_sound_1d(creature.sound_handle, creature.position); + } else if (creature.sound_handle == -1 || !p.sound_is_active(creature.sound_handle)) { + // Restart looping sound if it stopped + if (creature.sound_handle != -1) { + p.destroy_sound(creature.sound_handle); + } + creature.sound_handle = play_1d_with_volume_step(creature.voice_sound, x, creature.position, true, cfg.sound_volume_step); } if (cfg.fly_away_chance > 0 && random(1, 1000) <= cfg.fly_away_chance) { diff --git a/src/enemies/ground_game.nvgt b/src/enemies/ground_game.nvgt index b58f3ec..b0d691b 100644 --- a/src/enemies/ground_game.nvgt +++ b/src/enemies/ground_game.nvgt @@ -8,10 +8,8 @@ class GroundGame { int health; int sound_handle; timer move_timer; - timer sound_timer; timer attack_timer; int next_move_delay; - int next_sound_delay; string voice_sound; string state; // "wandering" or "charging" int area_start; @@ -32,11 +30,9 @@ class GroundGame { voice_sound = ground_game_boar_sounds[random(0, ground_game_boar_sounds.length() - 1)]; move_timer.restart(); - sound_timer.restart(); attack_timer.restart(); next_move_delay = random(BOAR_MOVE_INTERVAL_MIN, BOAR_MOVE_INTERVAL_MAX); - next_sound_delay = random(BOAR_SOUND_MIN_DELAY, BOAR_SOUND_MAX_DELAY); } } GroundGame@[] ground_games; @@ -80,7 +76,8 @@ void spawn_ground_game(int expansion_start, int expansion_end) { GroundGame@ b = GroundGame(spawn_x, expansion_start, expansion_end, "boar"); ground_games.insert_last(b); - b.sound_handle = play_creature_voice(b.voice_sound, x, spawn_x, BOAR_SOUND_VOLUME_STEP); + // Play looping sound that follows the boar + b.sound_handle = play_1d_with_volume_step(b.voice_sound, x, spawn_x, true, BOAR_SOUND_VOLUME_STEP); } bool can_ground_game_attack_player(GroundGame@ game) { @@ -113,12 +110,15 @@ bool try_attack_player_ground_game(GroundGame@ game) { } void update_ground_game(GroundGame@ game) { - // Sound logic - if (game.sound_timer.elapsed > game.next_sound_delay) { - game.sound_timer.restart(); - game.next_sound_delay = random(BOAR_SOUND_MIN_DELAY, BOAR_SOUND_MAX_DELAY); - // Only play if wandering or occasionally while charging - game.sound_handle = play_creature_voice(game.voice_sound, x, game.position, BOAR_SOUND_VOLUME_STEP); + // Update looping sound position + if (game.sound_handle != -1 && p.sound_is_active(game.sound_handle)) { + p.update_sound_1d(game.sound_handle, game.position); + } else if (game.sound_handle == -1 || !p.sound_is_active(game.sound_handle)) { + // Restart looping sound if it stopped for some reason + if (game.sound_handle != -1) { + p.destroy_sound(game.sound_handle); + } + game.sound_handle = play_1d_with_volume_step(game.voice_sound, x, game.position, true, BOAR_SOUND_VOLUME_STEP); } // Combat logic diff --git a/src/enemies/undead.nvgt b/src/enemies/undead.nvgt index f2f78e3..790fbca 100644 --- a/src/enemies/undead.nvgt +++ b/src/enemies/undead.nvgt @@ -10,9 +10,7 @@ class Undead { string voice_sound; int sound_handle; timer move_timer; - timer groan_timer; timer attack_timer; - int next_groan_delay; Undead(int pos, string type = "zombie") { position = pos; @@ -22,9 +20,7 @@ class Undead { voice_sound = undead_zombie_sounds[sound_index]; sound_handle = -1; move_timer.restart(); - groan_timer.restart(); attack_timer.restart(); - next_groan_delay = random(ZOMBIE_GROAN_MIN_DELAY, ZOMBIE_GROAN_MAX_DELAY); } } Undead@[] undeads; @@ -65,7 +61,8 @@ void spawn_undead() { Undead@ z = Undead(spawn_x, "zombie"); undeads.insert_last(z); - z.sound_handle = play_creature_voice(z.voice_sound, x, spawn_x, ZOMBIE_SOUND_VOLUME_STEP); + // Play looping sound that follows the zombie + z.sound_handle = play_1d_with_volume_step(z.voice_sound, x, spawn_x, true, ZOMBIE_SOUND_VOLUME_STEP); } void try_attack_barricade_undead(Undead@ undead) { @@ -131,17 +128,15 @@ bool try_attack_player_undead(Undead@ undead) { } void update_undead(Undead@ undead) { - if (undead.groan_timer.elapsed > undead.next_groan_delay) { - undead.groan_timer.restart(); - undead.next_groan_delay = random(ZOMBIE_GROAN_MIN_DELAY, ZOMBIE_GROAN_MAX_DELAY); - - // Destroy old sound handle before playing new one to prevent overlapping + // Update looping sound position + if (undead.sound_handle != -1 && p.sound_is_active(undead.sound_handle)) { + p.update_sound_1d(undead.sound_handle, undead.position); + } else if (undead.sound_handle == -1 || !p.sound_is_active(undead.sound_handle)) { + // Restart looping sound if it stopped for some reason if (undead.sound_handle != -1) { p.destroy_sound(undead.sound_handle); - undead.sound_handle = -1; } - - undead.sound_handle = play_creature_voice(undead.voice_sound, x, undead.position, ZOMBIE_SOUND_VOLUME_STEP); + undead.sound_handle = play_1d_with_volume_step(undead.voice_sound, x, undead.position, true, ZOMBIE_SOUND_VOLUME_STEP); } if (try_attack_player_undead(undead)) { diff --git a/src/environment.nvgt b/src/environment.nvgt index c315844..3cf13f8 100644 --- a/src/environment.nvgt +++ b/src/environment.nvgt @@ -558,23 +558,30 @@ void perform_search(int current_x) return; } - // Trees (Sticks/Vines) - Check for nearby tree anywhere + // Trees (Sticks/Vines) - Check for nearby tree, but only on non-stone terrain + // Skip tree search if player is on stone terrain (prioritize stone finding) + string search_terrain = get_terrain_at_position(current_x); + bool skip_tree_search = (search_terrain == "stone" || search_terrain == "gravel" || + search_terrain == "snow" || search_terrain == "hard_stone"); + Tree@ nearest = null; int nearest_distance = 999; - for(uint i=0; i 0; - if (equip_type == EQUIP_AXE) return inv_axes > 0; - if (equip_type == EQUIP_SLING) return inv_slings > 0; - if (equip_type == EQUIP_BOW) return inv_bows > 0; - if (equip_type == EQUIP_HAT) return inv_skin_hats > 0; - if (equip_type == EQUIP_GLOVES) return inv_skin_gloves > 0; - if (equip_type == EQUIP_PANTS) return inv_skin_pants > 0; - if (equip_type == EQUIP_TUNIC) return inv_skin_tunics > 0; - if (equip_type == EQUIP_MOCCASINS) return inv_moccasins > 0; - if (equip_type == EQUIP_POUCH) return inv_skin_pouches > 0; - if (equip_type == EQUIP_BACKPACK) return inv_backpacks > 0; + // Check unruned items first + if (equip_type == EQUIP_SPEAR) return inv_spears > 0 || has_any_runed_version(equip_type); + if (equip_type == EQUIP_AXE) return inv_axes > 0 || has_any_runed_version(equip_type); + if (equip_type == EQUIP_SLING) return inv_slings > 0 || has_any_runed_version(equip_type); + if (equip_type == EQUIP_BOW) return inv_bows > 0 || has_any_runed_version(equip_type); + if (equip_type == EQUIP_HAT) return inv_skin_hats > 0 || has_any_runed_version(equip_type); + if (equip_type == EQUIP_GLOVES) return inv_skin_gloves > 0 || has_any_runed_version(equip_type); + if (equip_type == EQUIP_PANTS) return inv_skin_pants > 0 || has_any_runed_version(equip_type); + if (equip_type == EQUIP_TUNIC) return inv_skin_tunics > 0 || has_any_runed_version(equip_type); + if (equip_type == EQUIP_MOCCASINS) return inv_moccasins > 0 || has_any_runed_version(equip_type); + if (equip_type == EQUIP_POUCH) return inv_skin_pouches > 0 || has_any_runed_version(equip_type); + if (equip_type == EQUIP_BACKPACK) return inv_backpacks > 0 || has_any_runed_version(equip_type); return false; } diff --git a/src/menus/altar_menu.nvgt b/src/menus/altar_menu.nvgt index b7928d7..0303f63 100644 --- a/src/menus/altar_menu.nvgt +++ b/src/menus/altar_menu.nvgt @@ -20,6 +20,10 @@ void check_altar_menu(int player_x) { } void sacrifice_item(int item_type) { + if (item_type == -2 || is_runed_item_type(item_type)) { + speak_with_history("Runed items cannot be sacrificed.", true); + return; + } int available = get_personal_count(item_type); if (available <= 0) { speak_with_history("Nothing to sacrifice.", true); @@ -66,6 +70,10 @@ void sacrifice_item(int item_type) { } void sacrifice_item_max(int item_type) { + if (item_type == -2 || is_runed_item_type(item_type)) { + speak_with_history("Runed items cannot be sacrificed.", true); + return; + } int available = get_personal_count(item_type); if (available <= 0) { speak_with_history("Nothing to sacrifice.", true); diff --git a/src/menus/inventory_core.nvgt b/src/menus/inventory_core.nvgt index c78e841..00635e8 100644 --- a/src/menus/inventory_core.nvgt +++ b/src/menus/inventory_core.nvgt @@ -32,6 +32,19 @@ void build_personal_inventory_options(string[]@ options, int[]@ item_types) { options.insert_last("Moccasins: " + inv_moccasins); item_types.insert_last(ITEM_MOCCASINS); options.insert_last("Skin Pouches: " + inv_skin_pouches); item_types.insert_last(ITEM_SKIN_POUCHES); options.insert_last("Backpacks: " + inv_backpacks); item_types.insert_last(ITEM_BACKPACKS); + + // Add runed items + int[] runeable = get_runeable_equipment_types(); + for (uint i = 0; i < runeable.length(); i++) { + int equip_type = runeable[i]; + // For now, only check for Swiftness rune + int count = get_runed_item_count(equip_type, RUNE_SWIFTNESS); + if (count > 0) { + string name = "Runed " + get_base_equipment_name(equip_type) + " of Quickness"; + options.insert_last(name + ": " + count); + item_types.insert_last(encode_runed_item_type(equip_type, RUNE_SWIFTNESS)); + } + } } void show_inventory() { @@ -50,6 +63,21 @@ void show_inventory() { info += inv_incense + " incense. "; info += "Tools: " + inv_spears + " spears, " + inv_slings + " slings, " + inv_axes + " axes, " + inv_snares + " snares, " + inv_knives + " knives, " + inv_fishing_poles + " fishing poles, " + inv_ropes + " ropes, " + inv_reed_baskets + " reed baskets, " + inv_clay_pots + " clay pots. "; info += "Clothing: " + inv_skin_hats + " hats, " + inv_skin_gloves + " gloves, " + inv_skin_pants + " pants, " + inv_skin_tunics + " tunics, " + inv_moccasins + " moccasins, " + inv_skin_pouches + " skin pouches."; + + // Add runed items summary + string runed_info = ""; + int[] runeable = get_runeable_equipment_types(); + for (uint i = 0; i < runeable.length(); i++) { + int count = get_runed_item_count(runeable[i], RUNE_SWIFTNESS); + if (count > 0) { + if (runed_info.length() > 0) runed_info += ", "; + runed_info += count + " Runed " + get_base_equipment_name(runeable[i]) + " of Quickness"; + } + } + if (runed_info.length() > 0) { + info += " Runed items: " + runed_info + "."; + } + speak_with_history(info, true); } diff --git a/src/menus/storage_menu.nvgt b/src/menus/storage_menu.nvgt index 5cfcf2b..7e5d0e3 100644 --- a/src/menus/storage_menu.nvgt +++ b/src/menus/storage_menu.nvgt @@ -31,7 +31,44 @@ int prompt_transfer_amount(const string prompt, int max_amount) { return amount; } +// Encode runed item info into a negative item type +// Format: -1000 - (equip_type * 10) - rune_type +// This allows us to extract both equip_type and rune_type from a single int +int encode_runed_item_type(int equip_type, int rune_type) { + return -1000 - (equip_type * 10) - rune_type; +} + +// Decode a runed item type back to equip_type and rune_type +void decode_runed_item_type(int encoded, int& out equip_type, int& out rune_type) { + int decoded = -(encoded + 1000); + equip_type = decoded / 10; + rune_type = decoded % 10; +} + +// Check if an item_type represents a runed item +bool is_runed_item_type(int item_type) { + return item_type <= -1000; +} + void deposit_item(int item_type) { + // Handle runed items + if (is_runed_item_type(item_type)) { + int equip_type, rune_type; + decode_runed_item_type(item_type, equip_type, rune_type); + if (deposit_runed_item(equip_type, rune_type)) { + string name = "Runed " + get_base_equipment_name(equip_type) + " of " + get_rune_effect_name(rune_type); + cleanup_equipment_after_inventory_change(); + speak_with_history("Deposited " + name + ".", true); + } else { + speak_with_history("Nothing to deposit.", true); + } + return; + } + // Handle legacy -2 marker (shouldn't happen anymore but keep for safety) + if (item_type == -2) { + speak_with_history("Runed items cannot be deposited into storage.", true); + return; + } int available = get_personal_count(item_type); if (available <= 0) { speak_with_history("Nothing to deposit.", true); @@ -80,6 +117,30 @@ void deposit_item(int item_type) { } void deposit_item_max(int item_type) { + // Handle runed items + if (is_runed_item_type(item_type)) { + int equip_type, rune_type; + decode_runed_item_type(item_type, equip_type, rune_type); + int personal = get_runed_item_count(equip_type, rune_type); + if (personal <= 0) { + speak_with_history("Nothing to deposit.", true); + return; + } + int count = 0; + while (get_runed_item_count(equip_type, rune_type) > 0) { + deposit_runed_item(equip_type, rune_type); + count++; + } + string name = "Runed " + get_base_equipment_name(equip_type) + " of " + get_rune_effect_name(rune_type); + cleanup_equipment_after_inventory_change(); + speak_with_history("Deposited " + count + " " + name + ".", true); + return; + } + // Handle legacy -2 marker + if (item_type == -2) { + speak_with_history("Runed items cannot be deposited into storage.", true); + return; + } int available = get_personal_count(item_type); if (available <= 0) { speak_with_history("Nothing to deposit.", true); @@ -128,6 +189,18 @@ void deposit_item_max(int item_type) { } void withdraw_item(int item_type) { + // Handle runed items + if (is_runed_item_type(item_type)) { + int equip_type, rune_type; + decode_runed_item_type(item_type, equip_type, rune_type); + if (withdraw_runed_item(equip_type, rune_type)) { + string name = "Runed " + get_base_equipment_name(equip_type) + " of " + get_rune_effect_name(rune_type); + speak_with_history("Withdrew " + name + ".", true); + } else { + speak_with_history("Nothing to withdraw.", true); + } + return; + } int available = get_storage_count(item_type); if (available <= 0) { speak_with_history("Nothing to withdraw.", true); @@ -175,6 +248,24 @@ void withdraw_item(int item_type) { } void withdraw_item_max(int item_type) { + // Handle runed items + if (is_runed_item_type(item_type)) { + int equip_type, rune_type; + decode_runed_item_type(item_type, equip_type, rune_type); + int stored = get_stored_runed_item_count(equip_type, rune_type); + if (stored <= 0) { + speak_with_history("Nothing to withdraw.", true); + return; + } + int count = 0; + while (get_stored_runed_item_count(equip_type, rune_type) > 0) { + withdraw_runed_item(equip_type, rune_type); + count++; + } + string name = "Runed " + get_base_equipment_name(equip_type) + " of " + get_rune_effect_name(rune_type); + speak_with_history("Withdrew " + count + " " + name + ".", true); + return; + } int available = get_storage_count(item_type); if (available <= 0) { speak_with_history("Nothing to withdraw.", true); @@ -255,6 +346,18 @@ void build_storage_inventory_options(string[]@ options, int[]@ item_types) { options.insert_last("Moccasins: " + storage_moccasins); item_types.insert_last(ITEM_MOCCASINS); options.insert_last("Skin Pouches: " + storage_skin_pouches); item_types.insert_last(ITEM_SKIN_POUCHES); options.insert_last("Backpacks: " + storage_backpacks); item_types.insert_last(ITEM_BACKPACKS); + + // Add stored runed items + int[] runeable = get_runeable_equipment_types(); + for (uint i = 0; i < runeable.length(); i++) { + int equip_type = runeable[i]; + int count = get_stored_runed_item_count(equip_type, RUNE_SWIFTNESS); + if (count > 0) { + string name = "Runed " + get_base_equipment_name(equip_type) + " of Quickness"; + options.insert_last(name + ": " + count); + item_types.insert_last(encode_runed_item_type(equip_type, RUNE_SWIFTNESS)); + } + } } void run_storage_menu() { diff --git a/src/quest_system.nvgt b/src/quest_system.nvgt index 174238e..5010d2a 100644 --- a/src/quest_system.nvgt +++ b/src/quest_system.nvgt @@ -93,16 +93,19 @@ void apply_quest_reward(int score) { int skins_added = add_to_stack(inv_skins, skins_gain); inv_skins += skins_added; - string message = "Quest reward: favor +" + format_favor(favor_gain) + "."; - if (stones_gain > 0) message += " Stones +" + stones_added + "."; - if (logs_gain > 0) message += " Logs +" + logs_added + "."; - if (skins_gain > 0) message += " Skins +" + skins_added + "."; - speak_with_history(message, true); + string message = "Quest Complete!\n\nRewards:\n"; + message += "Favor: +" + format_favor(favor_gain) + "\n"; + if (stones_gain > 0) message += "Stones: +" + stones_added + "\n"; + if (logs_gain > 0) message += "Logs: +" + logs_added + "\n"; + if (skins_gain > 0) message += "Skins: +" + skins_added + "\n"; + message += "\nScore: " + score; + text_reader(message, "Quest Rewards", true); } void run_quest(int quest_type) { - speak_with_history(get_quest_description(quest_type), true); - wait(800); + string quest_name = get_quest_name(quest_type); + string quest_desc = get_quest_description(quest_type); + text_reader(quest_name + "\n\n" + quest_desc, "Quest Instructions", true); p.pause_all(); int score = 0; if (quest_type == QUEST_BAT_INVASION) score = run_bat_invasion(); diff --git a/src/runes/rune_data.nvgt b/src/runes/rune_data.nvgt index 03f34b8..24547c1 100644 --- a/src/runes/rune_data.nvgt +++ b/src/runes/rune_data.nvgt @@ -22,6 +22,9 @@ const int RUNE_SWIFTNESS_GATHER_BONUS = 5; // Value: count of that runed item type dictionary runed_items; +// Dictionary for stored runed items (base storage) +dictionary stored_runed_items; + // Track which rune is on equipped items int equipped_head_rune = RUNE_NONE; int equipped_torso_rune = RUNE_NONE; @@ -91,6 +94,51 @@ bool has_any_runed_version(int equip_type) { return false; } +// Get count of stored runed items +int get_stored_runed_item_count(int equip_type, int rune_type) { + string key = make_runed_key(equip_type, rune_type); + if (stored_runed_items.exists(key)) { + return int(stored_runed_items[key]); + } + return 0; +} + +// Add a runed item to storage +void add_stored_runed_item(int equip_type, int rune_type) { + string key = make_runed_key(equip_type, rune_type); + int current = get_stored_runed_item_count(equip_type, rune_type); + stored_runed_items.set(key, current + 1); +} + +// Remove a runed item from storage +void remove_stored_runed_item(int equip_type, int rune_type) { + string key = make_runed_key(equip_type, rune_type); + int current = get_stored_runed_item_count(equip_type, rune_type); + if (current > 0) { + stored_runed_items.set(key, current - 1); + } +} + +// Deposit runed item from personal inventory to storage +bool deposit_runed_item(int equip_type, int rune_type) { + int personal = get_runed_item_count(equip_type, rune_type); + if (personal <= 0) return false; + + remove_runed_item(equip_type, rune_type); + add_stored_runed_item(equip_type, rune_type); + return true; +} + +// Withdraw runed item from storage to personal inventory +bool withdraw_runed_item(int equip_type, int rune_type) { + int stored = get_stored_runed_item_count(equip_type, rune_type); + if (stored <= 0) return false; + + remove_stored_runed_item(equip_type, rune_type); + add_runed_item(equip_type, rune_type); + return true; +} + // Get the rune type on equipped item for a given slot int get_equipped_rune_for_slot(int equip_type) { if (equip_type == EQUIP_SPEAR || equip_type == EQUIP_AXE || @@ -148,6 +196,7 @@ void reset_rune_data() { rune_swiftness_unlocked = false; unicorn_boss_defeated = false; runed_items.delete_all(); + stored_runed_items.delete_all(); equipped_head_rune = RUNE_NONE; equipped_torso_rune = RUNE_NONE; equipped_arms_rune = RUNE_NONE; diff --git a/src/save_system.nvgt b/src/save_system.nvgt index 2d4b348..10078b3 100644 --- a/src/save_system.nvgt +++ b/src/save_system.nvgt @@ -110,6 +110,7 @@ void stop_active_sounds() { p.destroy_sound(sling_sound_handle); sling_sound_handle = -1; } + stop_all_weather_sounds(); } void clear_world_objects() { @@ -494,6 +495,36 @@ bool load_game_state_from_raw(const string&in rawData) { if (get_raw_number(rawData, "inventory_skin_tunics", value)) inv_skin_tunics = value; if (get_raw_number(rawData, "inventory_moccasins", value)) inv_moccasins = value; if (get_raw_number(rawData, "inventory_skin_pouches", value)) inv_skin_pouches = value; + if (get_raw_number(rawData, "inventory_backpacks", value)) inv_backpacks = value; + + if (get_raw_number(rawData, "storage_stones", value)) storage_stones = value; + if (get_raw_number(rawData, "storage_sticks", value)) storage_sticks = value; + if (get_raw_number(rawData, "storage_vines", value)) storage_vines = value; + if (get_raw_number(rawData, "storage_reeds", value)) storage_reeds = value; + if (get_raw_number(rawData, "storage_logs", value)) storage_logs = value; + if (get_raw_number(rawData, "storage_clay", value)) storage_clay = value; + if (get_raw_number(rawData, "storage_small_game", value)) storage_small_game = value; + if (get_raw_number(rawData, "storage_meat", value)) storage_meat = value; + if (get_raw_number(rawData, "storage_skins", value)) storage_skins = value; + if (get_raw_number(rawData, "storage_feathers", value)) storage_feathers = value; + if (get_raw_number(rawData, "storage_down", value)) storage_down = value; + if (get_raw_number(rawData, "storage_incense", value)) storage_incense = value; + if (get_raw_number(rawData, "storage_spears", value)) storage_spears = value; + if (get_raw_number(rawData, "storage_snares", value)) storage_snares = value; + if (get_raw_number(rawData, "storage_axes", value)) storage_axes = value; + if (get_raw_number(rawData, "storage_knives", value)) storage_knives = value; + if (get_raw_number(rawData, "storage_fishing_poles", value)) storage_fishing_poles = value; + if (get_raw_number(rawData, "storage_slings", value)) storage_slings = value; + if (get_raw_number(rawData, "storage_ropes", value)) storage_ropes = value; + if (get_raw_number(rawData, "storage_reed_baskets", value)) storage_reed_baskets = value; + if (get_raw_number(rawData, "storage_clay_pots", value)) storage_clay_pots = value; + if (get_raw_number(rawData, "storage_skin_hats", value)) storage_skin_hats = value; + if (get_raw_number(rawData, "storage_skin_gloves", value)) storage_skin_gloves = value; + if (get_raw_number(rawData, "storage_skin_pants", value)) storage_skin_pants = value; + if (get_raw_number(rawData, "storage_skin_tunics", value)) storage_skin_tunics = value; + if (get_raw_number(rawData, "storage_moccasins", value)) storage_moccasins = value; + if (get_raw_number(rawData, "storage_skin_pouches", value)) storage_skin_pouches = value; + if (get_raw_number(rawData, "storage_backpacks", value)) storage_backpacks = value; if (get_raw_bool(rawData, "equipment_spear_equipped", bool_value)) spear_equipped = bool_value; if (get_raw_bool(rawData, "equipment_axe_equipped", bool_value)) axe_equipped = bool_value; @@ -505,9 +536,12 @@ bool load_game_state_from_raw(const string&in rawData) { if (get_raw_number(rawData, "equipment_feet", value)) equipped_feet = value; if (get_raw_number(rawData, "equipment_arms", value)) equipped_arms = value; - if (equipped_arms != EQUIP_POUCH && equipped_arms != EQUIP_BACKPACK) equipped_arms = EQUIP_NONE; - if (equipped_arms == EQUIP_POUCH && inv_skin_pouches <= 0) equipped_arms = EQUIP_NONE; - if (equipped_arms == EQUIP_BACKPACK && inv_backpacks <= 0) equipped_arms = EQUIP_NONE; + if (!equipment_available(equipped_head)) equipped_head = EQUIP_NONE; + if (!equipment_available(equipped_torso)) equipped_torso = EQUIP_NONE; + if (!equipment_available(equipped_hands)) equipped_hands = EQUIP_NONE; + if (!equipment_available(equipped_legs)) equipped_legs = EQUIP_NONE; + if (!equipment_available(equipped_feet)) equipped_feet = EQUIP_NONE; + if (!equipment_available(equipped_arms)) equipped_arms = EQUIP_NONE; if (incense_hours_remaining > 0) incense_burning = true; if (inv_small_game_types.length() == 0 && inv_small_game > 0) { @@ -642,6 +676,18 @@ bool save_game_state() { } saveData.set("runed_items", join_string_array(runed_items_data)); + // Save stored runed items dictionary + string[] stored_runed_items_data; + string[]@ stored_keys = stored_runed_items.get_keys(); + for (uint i = 0; i < stored_keys.length(); i++) { + string key = stored_keys[i]; + int count = int(stored_runed_items[key]); + if (count > 0) { + stored_runed_items_data.insert_last(key + "=" + count); + } + } + saveData.set("stored_runed_items", join_string_array(stored_runed_items_data)); + saveData.set("time_current_hour", current_hour); saveData.set("time_current_day", current_day); saveData.set("time_is_daytime", is_daytime); @@ -923,14 +969,9 @@ bool load_game_state() { equipped_hands = int(get_number(saveData, "equipment_hands", EQUIP_NONE)); equipped_legs = int(get_number(saveData, "equipment_legs", EQUIP_NONE)); equipped_feet = int(get_number(saveData, "equipment_feet", EQUIP_NONE)); - if (equipped_head != EQUIP_HAT) equipped_head = EQUIP_NONE; - if (equipped_torso != EQUIP_TUNIC) equipped_torso = EQUIP_NONE; - if (equipped_hands != EQUIP_GLOVES) equipped_hands = EQUIP_NONE; - if (equipped_legs != EQUIP_PANTS) equipped_legs = EQUIP_NONE; - if (equipped_feet != EQUIP_MOCCASINS) equipped_feet = EQUIP_NONE; - if (equipped_arms != EQUIP_POUCH && equipped_arms != EQUIP_BACKPACK) equipped_arms = EQUIP_NONE; - if (equipped_arms == EQUIP_POUCH && inv_skin_pouches <= 0) equipped_arms = EQUIP_NONE; - if (equipped_arms == EQUIP_BACKPACK && inv_backpacks <= 0) equipped_arms = EQUIP_NONE; + + // Note: Equipment validation moved after runed items are loaded (see below) + reset_quick_slots(); string[] loadedQuickSlots = get_string_list_or_split(saveData, "equipment_quick_slots"); uint slot_count = loadedQuickSlots.length(); @@ -941,7 +982,6 @@ bool load_game_state() { quick_slots[i] = slot_value; } } - update_max_health_from_equipment(); // Load rune system data rune_swiftness_unlocked = get_bool(saveData, "rune_swiftness_unlocked", false); @@ -969,6 +1009,32 @@ bool load_game_state() { } } + // Load stored runed items dictionary + stored_runed_items.delete_all(); + string[] loaded_stored_runed_items = get_string_list_or_split(saveData, "stored_runed_items"); + for (uint i = 0; i < loaded_stored_runed_items.length(); i++) { + string entry = loaded_stored_runed_items[i]; + int eq_pos = entry.find("="); + if (eq_pos > 0) { + string key = entry.substr(0, eq_pos); + int count = parse_int(entry.substr(eq_pos + 1)); + if (count > 0) { + stored_runed_items.set(key, count); + } + } + } + + // Validate equipped items now that runed items are loaded + if (!equipment_available(equipped_head)) equipped_head = EQUIP_NONE; + if (!equipment_available(equipped_torso)) equipped_torso = EQUIP_NONE; + if (!equipment_available(equipped_hands)) equipped_hands = EQUIP_NONE; + if (!equipment_available(equipped_legs)) equipped_legs = EQUIP_NONE; + if (!equipment_available(equipped_feet)) equipped_feet = EQUIP_NONE; + if (!equipment_available(equipped_arms)) equipped_arms = EQUIP_NONE; + + // Now that both equipment and runes are loaded, update stats + update_max_health_from_equipment(); + current_hour = int(get_number(saveData, "time_current_hour", 8)); current_day = int(get_number(saveData, "time_current_day", 1)); sun_setting_warned = get_bool(saveData, "time_sun_setting_warned", false); @@ -1122,15 +1188,15 @@ bool load_game_state() { b.wander_direction_change_interval = random(BANDIT_WANDER_DIRECTION_CHANGE_MIN, BANDIT_WANDER_DIRECTION_CHANGE_MAX); b.wander_direction_timer.restart(); b.move_timer.restart(); - b.alert_timer.restart(); b.attack_timer.restart(); // Restore alert sound based on weapon type int sound_index = random(0, bandit_sounds.length() - 1); b.alert_sound = bandit_sounds[sound_index]; - b.next_alert_delay = random(BANDIT_ALERT_MIN_DELAY, BANDIT_ALERT_MAX_DELAY); bandits.insert_last(b); + // Start looping sound for loaded bandit + b.sound_handle = play_1d_with_volume_step(b.alert_sound, x, b.position, true, BANDIT_SOUND_VOLUME_STEP); } string[] mountainData = get_string_list_or_split(saveData, "mountains_data"); diff --git a/src/time_system.nvgt b/src/time_system.nvgt index ca218db..579d886 100644 --- a/src/time_system.nvgt +++ b/src/time_system.nvgt @@ -391,9 +391,9 @@ void update_time() { } if (current_hour == 6) { - save_game_state(); process_daily_weapon_breakage(); attempt_daily_quest(); + save_game_state(); } attempt_daily_invasion(); keep_base_fires_fed(); diff --git a/src/weather.nvgt b/src/weather.nvgt index 8a95093..2017c48 100644 --- a/src/weather.nvgt +++ b/src/weather.nvgt @@ -39,6 +39,13 @@ class ThunderStrike { movement_timer.restart(); } + ~ThunderStrike() { + if (sound_handle != -1) { + p.destroy_sound(sound_handle); + sound_handle = -1; + } + } + void update() { if (direction == 0) return; // Stationary thunder @@ -77,6 +84,7 @@ string[] thunder_sounds = { }; void init_weather() { + stop_all_weather_sounds(); weather_state = WEATHER_CLEAR; wind_intensity = INTENSITY_NONE; rain_intensity = INTENSITY_NONE;