Various bug fixes.

This commit is contained in:
Storm Dragon
2026-01-22 23:30:36 -05:00
parent 35e192cb21
commit 9ded17873d
14 changed files with 357 additions and 93 deletions

View File

@@ -10,9 +10,7 @@ class Bandit {
string weapon_type; // "spear" or "axe" string weapon_type; // "spear" or "axe"
int sound_handle; int sound_handle;
timer move_timer; timer move_timer;
timer alert_timer;
timer attack_timer; timer attack_timer;
int next_alert_delay;
int move_interval; int move_interval;
// Wandering behavior properties // Wandering behavior properties
@@ -38,9 +36,7 @@ class Bandit {
move_interval = random(BANDIT_MOVE_INTERVAL_MIN, BANDIT_MOVE_INTERVAL_MAX); move_interval = random(BANDIT_MOVE_INTERVAL_MIN, BANDIT_MOVE_INTERVAL_MAX);
move_timer.restart(); move_timer.restart();
alert_timer.restart();
attack_timer.restart(); attack_timer.restart();
next_alert_delay = random(BANDIT_ALERT_MIN_DELAY, BANDIT_ALERT_MAX_DELAY);
// Initialize wandering behavior (start aggressive during invasion) // Initialize wandering behavior (start aggressive during invasion)
behavior_state = "aggressive"; 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); Bandit@ b = Bandit(spawn_x, expansion_start, expansion_end);
bandits.insert_last(b); 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) { bool can_bandit_attack_player(Bandit@ bandit) {
@@ -177,18 +174,15 @@ void try_attack_barricade_bandit(Bandit@ bandit) {
} }
void update_bandit(Bandit@ bandit) { void update_bandit(Bandit@ bandit) {
// Play alert sound at intervals // Update looping sound position
if (bandit.alert_timer.elapsed > bandit.next_alert_delay) { if (bandit.sound_handle != -1 && p.sound_is_active(bandit.sound_handle)) {
bandit.alert_timer.restart(); p.update_sound_1d(bandit.sound_handle, bandit.position);
bandit.next_alert_delay = random(BANDIT_ALERT_MIN_DELAY, BANDIT_ALERT_MAX_DELAY); } else if (bandit.sound_handle == -1 || !p.sound_is_active(bandit.sound_handle)) {
// Restart looping sound if it stopped
// Destroy old sound handle before playing new one to prevent overlapping
if (bandit.sound_handle != -1) { if (bandit.sound_handle != -1) {
p.destroy_sound(bandit.sound_handle); p.destroy_sound(bandit.sound_handle);
bandit.sound_handle = -1;
} }
bandit.sound_handle = play_1d_with_volume_step(bandit.alert_sound, x, bandit.position, true, BANDIT_SOUND_VOLUME_STEP);
bandit.sound_handle = play_creature_voice(bandit.alert_sound, x, bandit.position, BANDIT_SOUND_VOLUME_STEP);
} }
if (try_attack_player_bandit(bandit)) { if (try_attack_player_bandit(bandit)) {

View File

@@ -38,10 +38,8 @@ class FlyingCreature {
int sound_handle; int sound_handle;
int fall_sound_handle; int fall_sound_handle;
timer move_timer; timer move_timer;
timer sound_timer;
timer fall_timer; timer fall_timer;
int next_move_delay; int next_move_delay;
int next_sound_delay;
string voice_sound; string voice_sound;
bool fading_out; bool fading_out;
bool ready_to_remove; bool ready_to_remove;
@@ -63,10 +61,8 @@ class FlyingCreature {
} }
move_timer.restart(); move_timer.restart();
sound_timer.restart();
next_move_delay = random(cfg.move_interval_min, cfg.move_interval_max); 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; fading_out = false;
ready_to_remove = 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); FlyingCreature@ c = FlyingCreature(creature_type, spawn_x, area_start, area_end, cfg);
flying_creatures.insert_last(c); 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; return true;
} }
@@ -267,10 +264,15 @@ void update_flying_creature(FlyingCreature@ creature) {
return; return;
} }
if (creature.sound_timer.elapsed > creature.next_sound_delay) { // Update looping sound position
creature.sound_timer.restart(); if (creature.sound_handle != -1 && p.sound_is_active(creature.sound_handle)) {
creature.next_sound_delay = random(cfg.sound_delay_min, cfg.sound_delay_max); p.update_sound_1d(creature.sound_handle, creature.position);
creature.sound_handle = play_creature_voice(creature.voice_sound, x, creature.position, cfg.sound_volume_step); } 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) { if (cfg.fly_away_chance > 0 && random(1, 1000) <= cfg.fly_away_chance) {

View File

@@ -8,10 +8,8 @@ class GroundGame {
int health; int health;
int sound_handle; int sound_handle;
timer move_timer; timer move_timer;
timer sound_timer;
timer attack_timer; timer attack_timer;
int next_move_delay; int next_move_delay;
int next_sound_delay;
string voice_sound; string voice_sound;
string state; // "wandering" or "charging" string state; // "wandering" or "charging"
int area_start; int area_start;
@@ -32,11 +30,9 @@ class GroundGame {
voice_sound = ground_game_boar_sounds[random(0, ground_game_boar_sounds.length() - 1)]; voice_sound = ground_game_boar_sounds[random(0, ground_game_boar_sounds.length() - 1)];
move_timer.restart(); move_timer.restart();
sound_timer.restart();
attack_timer.restart(); attack_timer.restart();
next_move_delay = random(BOAR_MOVE_INTERVAL_MIN, BOAR_MOVE_INTERVAL_MAX); 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; 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"); GroundGame@ b = GroundGame(spawn_x, expansion_start, expansion_end, "boar");
ground_games.insert_last(b); 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) { 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) { void update_ground_game(GroundGame@ game) {
// Sound logic // Update looping sound position
if (game.sound_timer.elapsed > game.next_sound_delay) { if (game.sound_handle != -1 && p.sound_is_active(game.sound_handle)) {
game.sound_timer.restart(); p.update_sound_1d(game.sound_handle, game.position);
game.next_sound_delay = random(BOAR_SOUND_MIN_DELAY, BOAR_SOUND_MAX_DELAY); } else if (game.sound_handle == -1 || !p.sound_is_active(game.sound_handle)) {
// Only play if wandering or occasionally while charging // Restart looping sound if it stopped for some reason
game.sound_handle = play_creature_voice(game.voice_sound, x, game.position, BOAR_SOUND_VOLUME_STEP); 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 // Combat logic

View File

@@ -10,9 +10,7 @@ class Undead {
string voice_sound; string voice_sound;
int sound_handle; int sound_handle;
timer move_timer; timer move_timer;
timer groan_timer;
timer attack_timer; timer attack_timer;
int next_groan_delay;
Undead(int pos, string type = "zombie") { Undead(int pos, string type = "zombie") {
position = pos; position = pos;
@@ -22,9 +20,7 @@ class Undead {
voice_sound = undead_zombie_sounds[sound_index]; voice_sound = undead_zombie_sounds[sound_index];
sound_handle = -1; sound_handle = -1;
move_timer.restart(); move_timer.restart();
groan_timer.restart();
attack_timer.restart(); attack_timer.restart();
next_groan_delay = random(ZOMBIE_GROAN_MIN_DELAY, ZOMBIE_GROAN_MAX_DELAY);
} }
} }
Undead@[] undeads; Undead@[] undeads;
@@ -65,7 +61,8 @@ void spawn_undead() {
Undead@ z = Undead(spawn_x, "zombie"); Undead@ z = Undead(spawn_x, "zombie");
undeads.insert_last(z); 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) { void try_attack_barricade_undead(Undead@ undead) {
@@ -131,17 +128,15 @@ bool try_attack_player_undead(Undead@ undead) {
} }
void update_undead(Undead@ undead) { void update_undead(Undead@ undead) {
if (undead.groan_timer.elapsed > undead.next_groan_delay) { // Update looping sound position
undead.groan_timer.restart(); if (undead.sound_handle != -1 && p.sound_is_active(undead.sound_handle)) {
undead.next_groan_delay = random(ZOMBIE_GROAN_MIN_DELAY, ZOMBIE_GROAN_MAX_DELAY); p.update_sound_1d(undead.sound_handle, undead.position);
} else if (undead.sound_handle == -1 || !p.sound_is_active(undead.sound_handle)) {
// Destroy old sound handle before playing new one to prevent overlapping // Restart looping sound if it stopped for some reason
if (undead.sound_handle != -1) { if (undead.sound_handle != -1) {
p.destroy_sound(undead.sound_handle); p.destroy_sound(undead.sound_handle);
undead.sound_handle = -1;
} }
undead.sound_handle = play_1d_with_volume_step(undead.voice_sound, x, undead.position, true, ZOMBIE_SOUND_VOLUME_STEP);
undead.sound_handle = play_creature_voice(undead.voice_sound, x, undead.position, ZOMBIE_SOUND_VOLUME_STEP);
} }
if (try_attack_player_undead(undead)) { if (try_attack_player_undead(undead)) {

View File

@@ -558,23 +558,30 @@ void perform_search(int current_x)
return; 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; Tree@ nearest = null;
int nearest_distance = 999; int nearest_distance = 999;
for(uint i=0; i<trees.length(); i++) if (!skip_tree_search) {
{ for(uint i=0; i<trees.length(); i++)
int distance = trees[i].position - current_x;
if (distance < 0) distance = -distance;
if(distance <= 1 && distance < nearest_distance)
{ {
nearest_distance = distance; int distance = trees[i].position - current_x;
@nearest = @trees[i]; if (distance < 0) distance = -distance;
if (nearest_distance == 0) { if(distance <= 1 && distance < nearest_distance)
break; {
nearest_distance = distance;
@nearest = @trees[i];
if (nearest_distance == 0) {
break;
}
} }
} }
} }
if(@nearest != null) if(@nearest != null)
{ {
if(nearest.is_chopped) { if(nearest.is_chopped) {
@@ -659,7 +666,7 @@ void perform_search(int current_x)
MountainRange@ mountain = get_mountain_at(current_x); MountainRange@ mountain = get_mountain_at(current_x);
if (mountain !is null) { if (mountain !is null) {
string terrain = mountain.get_terrain_at(current_x); string terrain = mountain.get_terrain_at(current_x);
if (terrain == "stone" || terrain == "hard_stone") { if (terrain == "stone" || terrain == "hard_stone" || terrain == "gravel") {
is_stone_terrain = true; is_stone_terrain = true;
} }
} else { } else {
@@ -671,7 +678,7 @@ void perform_search(int current_x)
if (terrain.find("mountain:") == 0) { if (terrain.find("mountain:") == 0) {
terrain = terrain.substr(9); terrain = terrain.substr(9);
} }
if (terrain == "stone" || terrain == "hard_stone") { if (terrain == "stone" || terrain == "hard_stone" || terrain == "gravel") {
is_stone_terrain = true; is_stone_terrain = true;
} }
} }

View File

@@ -177,17 +177,18 @@ string get_equipment_name(int equip_type) {
} }
bool equipment_available(int equip_type) { bool equipment_available(int equip_type) {
if (equip_type == EQUIP_SPEAR) return inv_spears > 0; // Check unruned items first
if (equip_type == EQUIP_AXE) return inv_axes > 0; if (equip_type == EQUIP_SPEAR) return inv_spears > 0 || has_any_runed_version(equip_type);
if (equip_type == EQUIP_SLING) return inv_slings > 0; if (equip_type == EQUIP_AXE) return inv_axes > 0 || has_any_runed_version(equip_type);
if (equip_type == EQUIP_BOW) return inv_bows > 0; if (equip_type == EQUIP_SLING) return inv_slings > 0 || has_any_runed_version(equip_type);
if (equip_type == EQUIP_HAT) return inv_skin_hats > 0; if (equip_type == EQUIP_BOW) return inv_bows > 0 || has_any_runed_version(equip_type);
if (equip_type == EQUIP_GLOVES) return inv_skin_gloves > 0; if (equip_type == EQUIP_HAT) return inv_skin_hats > 0 || has_any_runed_version(equip_type);
if (equip_type == EQUIP_PANTS) return inv_skin_pants > 0; if (equip_type == EQUIP_GLOVES) return inv_skin_gloves > 0 || has_any_runed_version(equip_type);
if (equip_type == EQUIP_TUNIC) return inv_skin_tunics > 0; if (equip_type == EQUIP_PANTS) return inv_skin_pants > 0 || has_any_runed_version(equip_type);
if (equip_type == EQUIP_MOCCASINS) return inv_moccasins > 0; if (equip_type == EQUIP_TUNIC) return inv_skin_tunics > 0 || has_any_runed_version(equip_type);
if (equip_type == EQUIP_POUCH) return inv_skin_pouches > 0; if (equip_type == EQUIP_MOCCASINS) return inv_moccasins > 0 || has_any_runed_version(equip_type);
if (equip_type == EQUIP_BACKPACK) return inv_backpacks > 0; 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; return false;
} }

View File

@@ -20,6 +20,10 @@ void check_altar_menu(int player_x) {
} }
void sacrifice_item(int item_type) { 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); int available = get_personal_count(item_type);
if (available <= 0) { if (available <= 0) {
speak_with_history("Nothing to sacrifice.", true); speak_with_history("Nothing to sacrifice.", true);
@@ -66,6 +70,10 @@ void sacrifice_item(int item_type) {
} }
void sacrifice_item_max(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); int available = get_personal_count(item_type);
if (available <= 0) { if (available <= 0) {
speak_with_history("Nothing to sacrifice.", true); speak_with_history("Nothing to sacrifice.", true);

View File

@@ -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("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("Skin Pouches: " + inv_skin_pouches); item_types.insert_last(ITEM_SKIN_POUCHES);
options.insert_last("Backpacks: " + inv_backpacks); item_types.insert_last(ITEM_BACKPACKS); 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() { void show_inventory() {
@@ -50,6 +63,21 @@ void show_inventory() {
info += inv_incense + " incense. "; 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 += "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."; 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); speak_with_history(info, true);
} }

View File

@@ -31,7 +31,44 @@ int prompt_transfer_amount(const string prompt, int max_amount) {
return 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) { 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); int available = get_personal_count(item_type);
if (available <= 0) { if (available <= 0) {
speak_with_history("Nothing to deposit.", true); speak_with_history("Nothing to deposit.", true);
@@ -80,6 +117,30 @@ void deposit_item(int item_type) {
} }
void deposit_item_max(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); int available = get_personal_count(item_type);
if (available <= 0) { if (available <= 0) {
speak_with_history("Nothing to deposit.", true); speak_with_history("Nothing to deposit.", true);
@@ -128,6 +189,18 @@ void deposit_item_max(int item_type) {
} }
void withdraw_item(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); int available = get_storage_count(item_type);
if (available <= 0) { if (available <= 0) {
speak_with_history("Nothing to withdraw.", true); speak_with_history("Nothing to withdraw.", true);
@@ -175,6 +248,24 @@ void withdraw_item(int item_type) {
} }
void withdraw_item_max(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); int available = get_storage_count(item_type);
if (available <= 0) { if (available <= 0) {
speak_with_history("Nothing to withdraw.", true); 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("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("Skin Pouches: " + storage_skin_pouches); item_types.insert_last(ITEM_SKIN_POUCHES);
options.insert_last("Backpacks: " + storage_backpacks); item_types.insert_last(ITEM_BACKPACKS); 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() { void run_storage_menu() {

View File

@@ -93,16 +93,19 @@ void apply_quest_reward(int score) {
int skins_added = add_to_stack(inv_skins, skins_gain); int skins_added = add_to_stack(inv_skins, skins_gain);
inv_skins += skins_added; inv_skins += skins_added;
string message = "Quest reward: favor +" + format_favor(favor_gain) + "."; string message = "Quest Complete!\n\nRewards:\n";
if (stones_gain > 0) message += " Stones +" + stones_added + "."; message += "Favor: +" + format_favor(favor_gain) + "\n";
if (logs_gain > 0) message += " Logs +" + logs_added + "."; if (stones_gain > 0) message += "Stones: +" + stones_added + "\n";
if (skins_gain > 0) message += " Skins +" + skins_added + "."; if (logs_gain > 0) message += "Logs: +" + logs_added + "\n";
speak_with_history(message, true); if (skins_gain > 0) message += "Skins: +" + skins_added + "\n";
message += "\nScore: " + score;
text_reader(message, "Quest Rewards", true);
} }
void run_quest(int quest_type) { void run_quest(int quest_type) {
speak_with_history(get_quest_description(quest_type), true); string quest_name = get_quest_name(quest_type);
wait(800); string quest_desc = get_quest_description(quest_type);
text_reader(quest_name + "\n\n" + quest_desc, "Quest Instructions", true);
p.pause_all(); p.pause_all();
int score = 0; int score = 0;
if (quest_type == QUEST_BAT_INVASION) score = run_bat_invasion(); if (quest_type == QUEST_BAT_INVASION) score = run_bat_invasion();

View File

@@ -22,6 +22,9 @@ const int RUNE_SWIFTNESS_GATHER_BONUS = 5;
// Value: count of that runed item type // Value: count of that runed item type
dictionary runed_items; dictionary runed_items;
// Dictionary for stored runed items (base storage)
dictionary stored_runed_items;
// Track which rune is on equipped items // Track which rune is on equipped items
int equipped_head_rune = RUNE_NONE; int equipped_head_rune = RUNE_NONE;
int equipped_torso_rune = RUNE_NONE; int equipped_torso_rune = RUNE_NONE;
@@ -91,6 +94,51 @@ bool has_any_runed_version(int equip_type) {
return false; 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 // Get the rune type on equipped item for a given slot
int get_equipped_rune_for_slot(int equip_type) { int get_equipped_rune_for_slot(int equip_type) {
if (equip_type == EQUIP_SPEAR || equip_type == EQUIP_AXE || if (equip_type == EQUIP_SPEAR || equip_type == EQUIP_AXE ||
@@ -148,6 +196,7 @@ void reset_rune_data() {
rune_swiftness_unlocked = false; rune_swiftness_unlocked = false;
unicorn_boss_defeated = false; unicorn_boss_defeated = false;
runed_items.delete_all(); runed_items.delete_all();
stored_runed_items.delete_all();
equipped_head_rune = RUNE_NONE; equipped_head_rune = RUNE_NONE;
equipped_torso_rune = RUNE_NONE; equipped_torso_rune = RUNE_NONE;
equipped_arms_rune = RUNE_NONE; equipped_arms_rune = RUNE_NONE;

View File

@@ -110,6 +110,7 @@ void stop_active_sounds() {
p.destroy_sound(sling_sound_handle); p.destroy_sound(sling_sound_handle);
sling_sound_handle = -1; sling_sound_handle = -1;
} }
stop_all_weather_sounds();
} }
void clear_world_objects() { 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_skin_tunics", value)) inv_skin_tunics = value;
if (get_raw_number(rawData, "inventory_moccasins", value)) inv_moccasins = 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_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_spear_equipped", bool_value)) spear_equipped = bool_value;
if (get_raw_bool(rawData, "equipment_axe_equipped", bool_value)) axe_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_feet", value)) equipped_feet = value;
if (get_raw_number(rawData, "equipment_arms", value)) equipped_arms = 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 (!equipment_available(equipped_head)) equipped_head = EQUIP_NONE;
if (equipped_arms == EQUIP_POUCH && inv_skin_pouches <= 0) equipped_arms = EQUIP_NONE; if (!equipment_available(equipped_torso)) equipped_torso = EQUIP_NONE;
if (equipped_arms == EQUIP_BACKPACK && inv_backpacks <= 0) equipped_arms = 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 (incense_hours_remaining > 0) incense_burning = true;
if (inv_small_game_types.length() == 0 && inv_small_game > 0) { 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)); 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_hour", current_hour);
saveData.set("time_current_day", current_day); saveData.set("time_current_day", current_day);
saveData.set("time_is_daytime", is_daytime); 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_hands = int(get_number(saveData, "equipment_hands", EQUIP_NONE));
equipped_legs = int(get_number(saveData, "equipment_legs", EQUIP_NONE)); equipped_legs = int(get_number(saveData, "equipment_legs", EQUIP_NONE));
equipped_feet = int(get_number(saveData, "equipment_feet", 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; // Note: Equipment validation moved after runed items are loaded (see below)
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;
reset_quick_slots(); reset_quick_slots();
string[] loadedQuickSlots = get_string_list_or_split(saveData, "equipment_quick_slots"); string[] loadedQuickSlots = get_string_list_or_split(saveData, "equipment_quick_slots");
uint slot_count = loadedQuickSlots.length(); uint slot_count = loadedQuickSlots.length();
@@ -941,7 +982,6 @@ bool load_game_state() {
quick_slots[i] = slot_value; quick_slots[i] = slot_value;
} }
} }
update_max_health_from_equipment();
// Load rune system data // Load rune system data
rune_swiftness_unlocked = get_bool(saveData, "rune_swiftness_unlocked", false); 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_hour = int(get_number(saveData, "time_current_hour", 8));
current_day = int(get_number(saveData, "time_current_day", 1)); current_day = int(get_number(saveData, "time_current_day", 1));
sun_setting_warned = get_bool(saveData, "time_sun_setting_warned", false); 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_change_interval = random(BANDIT_WANDER_DIRECTION_CHANGE_MIN, BANDIT_WANDER_DIRECTION_CHANGE_MAX);
b.wander_direction_timer.restart(); b.wander_direction_timer.restart();
b.move_timer.restart(); b.move_timer.restart();
b.alert_timer.restart();
b.attack_timer.restart(); b.attack_timer.restart();
// Restore alert sound based on weapon type // Restore alert sound based on weapon type
int sound_index = random(0, bandit_sounds.length() - 1); int sound_index = random(0, bandit_sounds.length() - 1);
b.alert_sound = bandit_sounds[sound_index]; b.alert_sound = bandit_sounds[sound_index];
b.next_alert_delay = random(BANDIT_ALERT_MIN_DELAY, BANDIT_ALERT_MAX_DELAY);
bandits.insert_last(b); 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"); string[] mountainData = get_string_list_or_split(saveData, "mountains_data");

View File

@@ -391,9 +391,9 @@ void update_time() {
} }
if (current_hour == 6) { if (current_hour == 6) {
save_game_state();
process_daily_weapon_breakage(); process_daily_weapon_breakage();
attempt_daily_quest(); attempt_daily_quest();
save_game_state();
} }
attempt_daily_invasion(); attempt_daily_invasion();
keep_base_fires_fed(); keep_base_fires_fed();

View File

@@ -39,6 +39,13 @@ class ThunderStrike {
movement_timer.restart(); movement_timer.restart();
} }
~ThunderStrike() {
if (sound_handle != -1) {
p.destroy_sound(sound_handle);
sound_handle = -1;
}
}
void update() { void update() {
if (direction == 0) return; // Stationary thunder if (direction == 0) return; // Stationary thunder
@@ -77,6 +84,7 @@ string[] thunder_sounds = {
}; };
void init_weather() { void init_weather() {
stop_all_weather_sounds();
weather_state = WEATHER_CLEAR; weather_state = WEATHER_CLEAR;
wind_intensity = INTENSITY_NONE; wind_intensity = INTENSITY_NONE;
rain_intensity = INTENSITY_NONE; rain_intensity = INTENSITY_NONE;