Fixed crash bug with snares and possibly in other places.

This commit is contained in:
Storm Dragon
2026-01-25 15:46:45 -05:00
parent f80e3263a3
commit 815ac97a64
19 changed files with 575 additions and 154 deletions
+163 -110
View File
@@ -31,6 +31,41 @@ bool has_burning_fire_in_base() {
return false;
}
int get_resident_effect_multiplier() {
return blessing_resident_active ? 2 : 1;
}
int get_resident_success_chance(int base_chance) {
int chance = base_chance * get_resident_effect_multiplier();
if (chance > 100) chance = 100;
return chance;
}
int get_resident_break_chance(int base_chance) {
if (!blessing_resident_active) return base_chance;
int reduced = base_chance / get_resident_effect_multiplier();
if (reduced < 1 && base_chance > 0) reduced = 1;
return reduced;
}
int get_resident_escape_chance(int base_chance) {
if (!blessing_resident_active) return base_chance;
int reduced = base_chance / get_resident_effect_multiplier();
if (reduced < 1 && base_chance > 0) reduced = 1;
return reduced;
}
int get_resident_cooldown(int base_cooldown) {
int cooldown = base_cooldown / get_resident_effect_multiplier();
if (cooldown < 1) cooldown = 1;
return cooldown;
}
int apply_resident_damage_bonus(int damage) {
if (!blessing_resident_active) return damage;
return damage * get_resident_effect_multiplier();
}
void consume_food_for_residents() {
int needed = get_food_requirement();
if (needed <= 0) return;
@@ -77,13 +112,15 @@ void attempt_resident_fishing() {
int caught = 0;
int poles_broken = 0;
int break_chance = get_resident_break_chance(RESIDENT_TOOL_BREAK_CHANCE);
int fishing_chance = get_resident_success_chance(RESIDENT_FISHING_CHANCE);
for (int i = 0; i < active_fishers; i++) {
// Check for pole breakage during use
if (random(1, 100) <= RESIDENT_TOOL_BREAK_CHANCE) {
if (random(1, 100) <= break_chance) {
poles_broken++;
continue;
}
if (random(1, 100) > RESIDENT_FISHING_CHANCE) continue;
if (random(1, 100) > fishing_chance) continue;
if (get_storage_count(ITEM_FISH) >= BASE_STORAGE_MAX) break;
add_storage_count(ITEM_FISH, 1);
add_storage_fish_weight(random(FISH_WEIGHT_MIN, FISH_WEIGHT_MAX));
@@ -116,19 +153,26 @@ void attempt_resident_fish_smoking() {
if (get_storage_count(ITEM_FISH) <= 0) return;
if (get_storage_count(ITEM_STICKS) <= 0) return;
if (!has_burning_fire_in_base()) return;
if (random(1, 100) > RESIDENT_SMOKE_FISH_CHANCE) return;
int weight = (storage_fish_weights.length() > 0) ? storage_fish_weights[0] : get_default_fish_weight();
int yield = get_smoked_fish_yield(weight);
if (get_storage_count(ITEM_SMOKED_FISH) + yield > BASE_STORAGE_MAX) return;
int attempts = get_resident_effect_multiplier();
int smoke_chance = get_resident_success_chance(RESIDENT_SMOKE_FISH_CHANCE);
for (int attempt = 0; attempt < attempts; attempt++) {
if (get_storage_count(ITEM_FISH) <= 0) return;
if (get_storage_count(ITEM_STICKS) <= 0) return;
if (random(1, 100) > smoke_chance) continue;
pop_storage_fish_weight();
add_storage_count(ITEM_FISH, -1);
add_storage_count(ITEM_STICKS, -1);
add_storage_count(ITEM_SMOKED_FISH, yield);
int weight = (storage_fish_weights.length() > 0) ? storage_fish_weights[0] : get_default_fish_weight();
int yield = get_smoked_fish_yield(weight);
if (get_storage_count(ITEM_SMOKED_FISH) + yield > BASE_STORAGE_MAX) return;
if (x <= BASE_END) {
speak_with_history("Resident smoked a fish into " + yield + " smoked fish.", true);
pop_storage_fish_weight();
add_storage_count(ITEM_FISH, -1);
add_storage_count(ITEM_STICKS, -1);
add_storage_count(ITEM_SMOKED_FISH, yield);
if (x <= BASE_END) {
speak_with_history("Resident smoked a fish into " + yield + " smoked fish.", true);
}
}
}
@@ -190,12 +234,12 @@ int perform_resident_defense() {
int damage = 0;
if (useSpear && get_storage_count(ITEM_SPEARS) > 0) {
damage = RESIDENT_SPEAR_DAMAGE;
damage = apply_resident_damage_bonus(RESIDENT_SPEAR_DAMAGE);
// Weapons don't get consumed on use - they break via daily breakage check
// Just play the sound
play_1d_with_volume_step("sounds/weapons/spear_swing.ogg", x, BASE_END + 1, false, RESIDENT_DEFENSE_VOLUME_STEP);
} else if (get_storage_count(ITEM_SLINGS) > 0 && get_storage_count(ITEM_STONES) > 0) {
damage = random(RESIDENT_SLING_DAMAGE_MIN, RESIDENT_SLING_DAMAGE_MAX);
damage = apply_resident_damage_bonus(random(RESIDENT_SLING_DAMAGE_MIN, RESIDENT_SLING_DAMAGE_MAX));
// Slings use stones as ammo, so consume a stone
add_storage_count(ITEM_STONES, -1);
play_1d_with_volume_step("sounds/weapons/sling_hit.ogg", x, BASE_END + 1, false, RESIDENT_DEFENSE_VOLUME_STEP);
@@ -213,7 +257,7 @@ void attempt_resident_sling_defense() {
if (get_storage_count(ITEM_SLINGS) <= 0 || get_storage_count(ITEM_STONES) <= 0) return;
// Cooldown between shots
if (resident_sling_timer.elapsed < RESIDENT_SLING_COOLDOWN) return;
if (resident_sling_timer.elapsed < get_resident_cooldown(RESIDENT_SLING_COOLDOWN)) return;
// Find nearest enemy within sling range
int nearestDistance = SLING_RANGE + 1;
@@ -249,7 +293,7 @@ void attempt_resident_sling_defense() {
resident_sling_timer.restart();
add_storage_count(ITEM_STONES, -1);
int damage = random(RESIDENT_SLING_DAMAGE_MIN, RESIDENT_SLING_DAMAGE_MAX);
int damage = apply_resident_damage_bonus(random(RESIDENT_SLING_DAMAGE_MIN, RESIDENT_SLING_DAMAGE_MAX));
play_1d_with_volume_step("sounds/weapons/sling_hit.ogg", x, targetPos, false, RESIDENT_DEFENSE_VOLUME_STEP);
if (targetIsBandit) {
@@ -293,15 +337,16 @@ void process_daily_weapon_breakage() {
// Perform breakage checks
int spearsBroken = 0;
int slingsBroken = 0;
int break_chance = get_resident_break_chance(RESIDENT_WEAPON_BREAK_CHANCE);
for (int i = 0; i < spearChecks; i++) {
if (random(1, 100) <= RESIDENT_WEAPON_BREAK_CHANCE) {
if (random(1, 100) <= break_chance) {
spearsBroken++;
}
}
for (int i = 0; i < slingChecks; i++) {
if (random(1, 100) <= RESIDENT_WEAPON_BREAK_CHANCE) {
if (random(1, 100) <= break_chance) {
slingsBroken++;
}
}
@@ -337,6 +382,9 @@ void attempt_resident_snare_retrieval() {
// Need food in storage (same limitation as other resident tasks)
if (!has_any_storage_food()) return;
int check_chance = get_resident_success_chance(RESIDENT_SNARE_CHECK_CHANCE);
int escape_chance = get_resident_escape_chance(RESIDENT_SNARE_ESCAPE_CHANCE);
// Check each snare that has a catch
for (int i = int(world_snares.length()) - 1; i >= 0; i--) {
WorldSnare@ snare = world_snares[i];
@@ -344,10 +392,10 @@ void attempt_resident_snare_retrieval() {
if (!snare.active) continue;
// Each snare has a chance to be checked by a resident this hour
if (random(1, 100) > RESIDENT_SNARE_CHECK_CHANCE) continue;
if (random(1, 100) > check_chance) continue;
// Small chance the game escapes during retrieval (like normal)
if (random(1, 100) <= RESIDENT_SNARE_ESCAPE_CHANCE) {
if (random(1, 100) <= escape_chance) {
notify("A " + snare.catch_type + " escaped while a resident checked the snare at x " + snare.position + ".");
remove_snare_at(snare.position);
continue;
@@ -376,7 +424,7 @@ void attempt_resident_snare_retrieval() {
}
}
// Resident butchering - processes one game per day from storage
// Resident butchering - processes up to two games per day when blessed
void attempt_resident_butchering() {
// Need residents
if (residents_count <= 0) return;
@@ -391,100 +439,101 @@ void attempt_resident_butchering() {
if (get_storage_count(ITEM_KNIVES) <= 0) return;
// Need a fire in base
bool has_fire = false;
for (uint i = 0; i < world_fires.length(); i++) {
if (world_fires[i].position <= BASE_END && world_fires[i].is_burning()) {
has_fire = true;
break;
if (!has_burning_fire_in_base()) return;
int attempts = get_resident_effect_multiplier();
int break_chance = get_resident_break_chance(RESIDENT_TOOL_BREAK_CHANCE);
for (int attempt = 0; attempt < attempts; attempt++) {
// Need game in storage
if (get_storage_count(ITEM_SMALL_GAME) <= 0 && get_storage_count(ITEM_BOAR_CARCASSES) <= 0) return;
if (get_storage_count(ITEM_KNIVES) <= 0) return;
// Determine what to butcher (prioritize boar carcasses)
string game_type = "";
bool is_boar = false;
if (get_storage_count(ITEM_BOAR_CARCASSES) > 0) {
game_type = "boar carcass";
is_boar = true;
} else if (storage_small_game_types.length() > 0) {
game_type = storage_small_game_types[0];
} else {
game_type = "small game";
}
}
if (!has_fire) return;
// Determine what to butcher (prioritize boar carcasses)
string game_type = "";
bool is_boar = false;
// Calculate outputs and check storage capacity
int meat_yield = 0;
int skins_yield = 0;
int feathers_yield = 0;
int down_yield = 0;
int sinew_yield = 0;
if (get_storage_count(ITEM_BOAR_CARCASSES) > 0) {
game_type = "boar carcass";
is_boar = true;
} else if (storage_small_game_types.length() > 0) {
game_type = storage_small_game_types[0];
} else {
game_type = "small game";
}
// Calculate outputs and check storage capacity
int meat_yield = 0;
int skins_yield = 0;
int feathers_yield = 0;
int down_yield = 0;
int sinew_yield = 0;
if (game_type == "goose") {
meat_yield = 1;
feathers_yield = random(3, 6);
down_yield = random(1, 3);
} else if (is_boar) {
meat_yield = random(2, 3);
skins_yield = 3;
sinew_yield = 2;
} else {
meat_yield = 1;
skins_yield = 1;
}
// Check storage capacity for outputs
if (meat_yield > 0 && get_storage_count(ITEM_MEAT) + meat_yield > BASE_STORAGE_MAX) return;
if (skins_yield > 0 && get_storage_count(ITEM_SKINS) + skins_yield > BASE_STORAGE_MAX) return;
if (feathers_yield > 0 && get_storage_count(ITEM_FEATHERS) + feathers_yield > BASE_STORAGE_MAX) return;
if (down_yield > 0 && get_storage_count(ITEM_DOWN) + down_yield > BASE_STORAGE_MAX) return;
if (sinew_yield > 0 && get_storage_count(ITEM_SINEW) + sinew_yield > BASE_STORAGE_MAX) return;
// Consume the game
if (is_boar) {
add_storage_count(ITEM_BOAR_CARCASSES, -1);
} else {
add_storage_count(ITEM_SMALL_GAME, -1);
if (storage_small_game_types.length() > 0) {
storage_small_game_types.remove_at(0);
if (game_type == "goose") {
meat_yield = 1;
feathers_yield = random(3, 6);
down_yield = random(1, 3);
} else if (is_boar) {
meat_yield = random(2, 3);
skins_yield = 3;
sinew_yield = 2;
} else {
meat_yield = 1;
skins_yield = 1;
}
}
// Check for knife breakage
if (random(1, 100) <= RESIDENT_TOOL_BREAK_CHANCE) {
add_storage_count(ITEM_KNIVES, -1);
notify("A resident's knife broke while butchering.");
}
// Check storage capacity for outputs
if (meat_yield > 0 && get_storage_count(ITEM_MEAT) + meat_yield > BASE_STORAGE_MAX) return;
if (skins_yield > 0 && get_storage_count(ITEM_SKINS) + skins_yield > BASE_STORAGE_MAX) return;
if (feathers_yield > 0 && get_storage_count(ITEM_FEATHERS) + feathers_yield > BASE_STORAGE_MAX) return;
if (down_yield > 0 && get_storage_count(ITEM_DOWN) + down_yield > BASE_STORAGE_MAX) return;
if (sinew_yield > 0 && get_storage_count(ITEM_SINEW) + sinew_yield > BASE_STORAGE_MAX) return;
// Add outputs to storage
if (meat_yield > 0) add_storage_count(ITEM_MEAT, meat_yield);
if (skins_yield > 0) add_storage_count(ITEM_SKINS, skins_yield);
if (feathers_yield > 0) add_storage_count(ITEM_FEATHERS, feathers_yield);
if (down_yield > 0) add_storage_count(ITEM_DOWN, down_yield);
if (sinew_yield > 0) add_storage_count(ITEM_SINEW, sinew_yield);
// Build notification message
string result = "Resident butchered " + game_type + ". Added ";
string[] outputs;
if (meat_yield > 0) outputs.insert_last(meat_yield + " meat");
if (skins_yield > 0) outputs.insert_last(skins_yield + " skins");
if (feathers_yield > 0) outputs.insert_last(feathers_yield + " feathers");
if (down_yield > 0) outputs.insert_last(down_yield + " down");
if (sinew_yield > 0) outputs.insert_last(sinew_yield + " sinew");
for (uint i = 0; i < outputs.length(); i++) {
if (i > 0) {
if (i == outputs.length() - 1) {
result += " and ";
} else {
result += ", ";
// Consume the game
if (is_boar) {
add_storage_count(ITEM_BOAR_CARCASSES, -1);
} else {
add_storage_count(ITEM_SMALL_GAME, -1);
if (storage_small_game_types.length() > 0) {
storage_small_game_types.remove_at(0);
}
}
result += outputs[i];
}
result += " to storage.";
notify(result);
// Check for knife breakage
if (random(1, 100) <= break_chance) {
add_storage_count(ITEM_KNIVES, -1);
notify("A resident's knife broke while butchering.");
}
// Add outputs to storage
if (meat_yield > 0) add_storage_count(ITEM_MEAT, meat_yield);
if (skins_yield > 0) add_storage_count(ITEM_SKINS, skins_yield);
if (feathers_yield > 0) add_storage_count(ITEM_FEATHERS, feathers_yield);
if (down_yield > 0) add_storage_count(ITEM_DOWN, down_yield);
if (sinew_yield > 0) add_storage_count(ITEM_SINEW, sinew_yield);
// Build notification message
string result = "Resident butchered " + game_type + ". Added ";
string[] outputs;
if (meat_yield > 0) outputs.insert_last(meat_yield + " meat");
if (skins_yield > 0) outputs.insert_last(skins_yield + " skins");
if (feathers_yield > 0) outputs.insert_last(feathers_yield + " feathers");
if (down_yield > 0) outputs.insert_last(down_yield + " down");
if (sinew_yield > 0) outputs.insert_last(sinew_yield + " sinew");
for (uint i = 0; i < outputs.length(); i++) {
if (i > 0) {
if (i == outputs.length() - 1) {
result += " and ";
} else {
result += ", ";
}
}
result += outputs[i];
}
result += " to storage.";
notify(result);
}
}
// Resident resource collection
@@ -504,14 +553,16 @@ void attempt_resident_collection() {
// Each active collector has a 10% chance to collect something
int baskets_broken = 0;
int break_chance = get_resident_break_chance(RESIDENT_TOOL_BREAK_CHANCE);
int collection_chance = get_resident_success_chance(RESIDENT_COLLECTION_CHANCE);
for (int i = 0; i < active_collectors; i++) {
// Check for basket breakage during use
if (random(1, 100) <= RESIDENT_TOOL_BREAK_CHANCE) {
if (random(1, 100) <= break_chance) {
baskets_broken++;
continue;
}
if (random(1, 100) > RESIDENT_COLLECTION_CHANCE) continue;
if (random(1, 100) > collection_chance) continue;
// Determine what to collect (weighted random)
// Sticks and vines more common, logs and stones less common
@@ -573,16 +624,18 @@ void attempt_resident_foraging() {
int baskets_produced = 0;
int baskets_broken = 0;
int break_chance = get_resident_break_chance(RESIDENT_TOOL_BREAK_CHANCE);
int forage_chance = get_resident_success_chance(RESIDENT_FORAGING_CHANCE);
for (int i = 0; i < active_foragers; i++) {
// Check for basket breakage during foraging
if (random(1, 100) <= RESIDENT_TOOL_BREAK_CHANCE) {
if (random(1, 100) <= break_chance) {
baskets_broken++;
continue;
}
// Check if foraging succeeds
if (random(1, 100) > RESIDENT_FORAGING_CHANCE) continue;
if (random(1, 100) > forage_chance) continue;
// Check storage capacity
if (get_storage_count(ITEM_BASKET_FOOD) >= BASE_STORAGE_MAX) break;
+1
View File
@@ -23,6 +23,7 @@ const int BASE_STORAGE_MAX = 50;
const int BLESSING_HEAL_AMOUNT = 3;
const int BLESSING_BARRICADE_REPAIR = 20;
const int BLESSING_SPEED_DURATION = 300000;
const int BLESSING_RESIDENT_DURATION = 300000;
const int BLESSING_TRIGGER_CHANCE = 10;
const int BLESSING_WALK_SPEED = 320;
const int FISH_WEIGHT_MIN = 1;
+26 -6
View File
@@ -84,7 +84,12 @@ void spawn_bandit(int expansion_start, int expansion_end) {
Bandit@ b = Bandit(spawn_x, expansion_start, expansion_end);
bandits.insert_last(b);
// 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);
int[] areaStarts;
int[] areaEnds;
get_active_audio_areas(areaStarts, areaEnds);
if (areaStarts.length() == 0 || range_overlaps_active_areas(spawn_x, spawn_x, areaStarts, areaEnds)) {
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) {
@@ -174,9 +179,14 @@ void try_attack_barricade_bandit(Bandit@ bandit) {
}
}
void update_bandit(Bandit@ bandit) {
void update_bandit(Bandit@ bandit, bool audio_active) {
// Update looping sound position
if (bandit.sound_handle != -1 && p.sound_is_active(bandit.sound_handle)) {
if (!audio_active) {
if (bandit.sound_handle != -1) {
p.destroy_sound(bandit.sound_handle);
bandit.sound_handle = -1;
}
} else 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
@@ -227,7 +237,9 @@ void update_bandit(Bandit@ bandit) {
bandit.wander_direction = -bandit.wander_direction;
} else {
bandit.position = target_x;
play_creature_footstep(x, bandit.position, BASE_END, GRASS_END, BANDIT_FOOTSTEP_MAX_DISTANCE, BANDIT_SOUND_VOLUME_STEP);
if (audio_active) {
play_creature_footstep(x, bandit.position, BASE_END, GRASS_END, BANDIT_FOOTSTEP_MAX_DISTANCE, BANDIT_SOUND_VOLUME_STEP);
}
}
} else {
// Hit map boundary, reverse direction
@@ -270,13 +282,21 @@ void update_bandit(Bandit@ bandit) {
}
bandit.position = target_x;
play_creature_footstep(x, bandit.position, BASE_END, GRASS_END, BANDIT_FOOTSTEP_MAX_DISTANCE, BANDIT_SOUND_VOLUME_STEP);
if (audio_active) {
play_creature_footstep(x, bandit.position, BASE_END, GRASS_END, BANDIT_FOOTSTEP_MAX_DISTANCE, BANDIT_SOUND_VOLUME_STEP);
}
}
}
void update_bandits() {
int[] areaStarts;
int[] areaEnds;
get_active_audio_areas(areaStarts, areaEnds);
bool limit_audio = (areaStarts.length() > 0);
for (uint i = 0; i < bandits.length(); i++) {
update_bandit(bandits[i]);
bool audio_active = !limit_audio || range_overlaps_active_areas(bandits[i].position, bandits[i].position, areaStarts, areaEnds);
update_bandit(bandits[i], audio_active);
}
}
+32 -9
View File
@@ -226,11 +226,16 @@ bool spawn_flying_creature(string creature_type) {
FlyingCreature@ c = FlyingCreature(creature_type, spawn_x, area_start, area_end, cfg);
flying_creatures.insert_last(c);
// 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);
int[] areaStarts;
int[] areaEnds;
get_active_audio_areas(areaStarts, areaEnds);
if (areaStarts.length() == 0 || range_overlaps_active_areas(spawn_x, spawn_x, areaStarts, areaEnds)) {
c.sound_handle = play_1d_with_volume_step(c.voice_sound, x, spawn_x, true, cfg.sound_volume_step);
}
return true;
}
void update_flying_creature(FlyingCreature@ creature) {
void update_flying_creature(FlyingCreature@ creature, bool audio_active) {
FlyingCreatureConfig@ cfg = get_flying_creature_config(creature.creature_type);
if (cfg is null) return;
@@ -240,7 +245,11 @@ void update_flying_creature(FlyingCreature@ creature) {
creature.fade_timer.restart();
}
if (creature.sound_handle != -1 && p.sound_is_active(creature.sound_handle)) {
if (!audio_active && creature.sound_handle != -1) {
p.destroy_sound(creature.sound_handle);
creature.sound_handle = -1;
creature.ready_to_remove = true;
} else if (creature.sound_handle != -1 && p.sound_is_active(creature.sound_handle)) {
float progress = float(creature.fade_timer.elapsed) / float(FLYING_CREATURE_FADE_OUT_DURATION);
if (progress < 0.0) progress = 0.0;
if (progress > 1.0) progress = 1.0;
@@ -265,7 +274,12 @@ void update_flying_creature(FlyingCreature@ creature) {
}
// Update looping sound position
if (creature.sound_handle != -1 && p.sound_is_active(creature.sound_handle)) {
if (!audio_active) {
if (creature.sound_handle != -1) {
p.destroy_sound(creature.sound_handle);
creature.sound_handle = -1;
}
} else 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
@@ -301,7 +315,7 @@ void update_flying_creature(FlyingCreature@ creature) {
}
if (target_x >= 0 && target_x < MAP_SIZE) {
creature.position = target_x;
if (creature.sound_handle != -1 && p.sound_is_active(creature.sound_handle)) {
if (audio_active && creature.sound_handle != -1 && p.sound_is_active(creature.sound_handle)) {
p.update_sound_1d(creature.sound_handle, creature.position);
}
}
@@ -314,15 +328,18 @@ void update_flying_creature(FlyingCreature@ creature) {
if (creature.fall_sound_handle != -1) {
p.destroy_sound(creature.fall_sound_handle);
creature.fall_sound_handle = -1;
}
float pitch_percent = 50.0 + (50.0 * (float(creature.height) / float(cfg.max_height)));
if (pitch_percent < 50.0) pitch_percent = 50.0;
if (pitch_percent > 100.0) pitch_percent = 100.0;
creature.fall_sound_handle = p.play_extended_1d(cfg.fall_sound, x, creature.position, 0, 0, true, 0, 0.0, 0.0, pitch_percent);
if (creature.fall_sound_handle != -1) {
p.update_sound_positioning_values(creature.fall_sound_handle, -1.0, cfg.sound_volume_step, true);
if (audio_active) {
creature.fall_sound_handle = p.play_extended_1d(cfg.fall_sound, x, creature.position, 0, 0, true, 0, 0.0, 0.0, pitch_percent);
if (creature.fall_sound_handle != -1) {
p.update_sound_positioning_values(creature.fall_sound_handle, -1.0, cfg.sound_volume_step, true);
}
}
if (creature.height <= 0) {
@@ -340,8 +357,14 @@ void update_flying_creature(FlyingCreature@ creature) {
}
void update_flying_creatures() {
int[] areaStarts;
int[] areaEnds;
get_active_audio_areas(areaStarts, areaEnds);
bool limit_audio = (areaStarts.length() > 0);
for (uint i = 0; i < flying_creatures.length(); i++) {
update_flying_creature(flying_creatures[i]);
bool audio_active = !limit_audio || range_overlaps_active_areas(flying_creatures[i].position, flying_creatures[i].position, areaStarts, areaEnds);
update_flying_creature(flying_creatures[i], audio_active);
if (flying_creatures[i].health <= 0) {
if (flying_creatures[i].state == "falling" && flying_creatures[i].height <= 0) {
+26 -6
View File
@@ -77,7 +77,12 @@ 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);
// 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);
int[] areaStarts;
int[] areaEnds;
get_active_audio_areas(areaStarts, areaEnds);
if (areaStarts.length() == 0 || range_overlaps_active_areas(spawn_x, spawn_x, areaStarts, areaEnds)) {
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) {
@@ -109,9 +114,14 @@ bool try_attack_player_ground_game(GroundGame@ game) {
return true;
}
void update_ground_game(GroundGame@ game) {
void update_ground_game(GroundGame@ game, bool audio_active) {
// Update looping sound position
if (game.sound_handle != -1 && p.sound_is_active(game.sound_handle)) {
if (!audio_active) {
if (game.sound_handle != -1) {
p.destroy_sound(game.sound_handle);
game.sound_handle = -1;
}
} else 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
@@ -153,7 +163,9 @@ void update_ground_game(GroundGame@ game) {
// Don't leave area or enter base
if (target >= game.area_start && target <= game.area_end && target > BASE_END) {
game.position = target;
play_creature_footstep(x, game.position, BASE_END, GRASS_END, BOAR_FOOTSTEP_MAX_DISTANCE, BOAR_SOUND_VOLUME_STEP);
if (audio_active) {
play_creature_footstep(x, game.position, BASE_END, GRASS_END, BOAR_FOOTSTEP_MAX_DISTANCE, BOAR_SOUND_VOLUME_STEP);
}
}
} else {
// Wandering
@@ -166,7 +178,9 @@ void update_ground_game(GroundGame@ game) {
// Don't leave area or enter base
if (target >= game.area_start && target <= game.area_end && target > BASE_END) {
game.position = target;
play_creature_footstep(x, game.position, BASE_END, GRASS_END, BOAR_FOOTSTEP_MAX_DISTANCE, BOAR_SOUND_VOLUME_STEP);
if (audio_active) {
play_creature_footstep(x, game.position, BASE_END, GRASS_END, BOAR_FOOTSTEP_MAX_DISTANCE, BOAR_SOUND_VOLUME_STEP);
}
} else {
game.wander_direction = -game.wander_direction; // Turn around
}
@@ -175,8 +189,14 @@ void update_ground_game(GroundGame@ game) {
}
void update_ground_games() {
int[] areaStarts;
int[] areaEnds;
get_active_audio_areas(areaStarts, areaEnds);
bool limit_audio = (areaStarts.length() > 0);
for (uint i = 0; i < ground_games.length(); i++) {
update_ground_game(ground_games[i]);
bool audio_active = !limit_audio || range_overlaps_active_areas(ground_games[i].position, ground_games[i].position, areaStarts, areaEnds);
update_ground_game(ground_games[i], audio_active);
}
}
+23 -5
View File
@@ -62,7 +62,12 @@ void spawn_undead() {
Undead@ z = Undead(spawn_x, "zombie");
undeads.insert_last(z);
// 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);
int[] areaStarts;
int[] areaEnds;
get_active_audio_areas(areaStarts, areaEnds);
if (areaStarts.length() == 0 || range_overlaps_active_areas(spawn_x, spawn_x, areaStarts, areaEnds)) {
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) {
@@ -128,9 +133,14 @@ bool try_attack_player_undead(Undead@ undead) {
return true;
}
void update_undead(Undead@ undead) {
void update_undead(Undead@ undead, bool audio_active) {
// Update looping sound position
if (undead.sound_handle != -1 && p.sound_is_active(undead.sound_handle)) {
if (!audio_active) {
if (undead.sound_handle != -1) {
p.destroy_sound(undead.sound_handle);
undead.sound_handle = -1;
}
} else 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
@@ -175,7 +185,9 @@ void update_undead(Undead@ undead) {
}
undead.position = target_x;
play_creature_footstep(x, undead.position, BASE_END, GRASS_END, ZOMBIE_FOOTSTEP_MAX_DISTANCE, ZOMBIE_SOUND_VOLUME_STEP);
if (audio_active) {
play_creature_footstep(x, undead.position, BASE_END, GRASS_END, ZOMBIE_FOOTSTEP_MAX_DISTANCE, ZOMBIE_SOUND_VOLUME_STEP);
}
}
void update_undeads() {
@@ -195,8 +207,14 @@ void update_undeads() {
spawn_undead();
}
int[] areaStarts;
int[] areaEnds;
get_active_audio_areas(areaStarts, areaEnds);
bool limit_audio = (areaStarts.length() > 0);
for (uint i = 0; i < undeads.length(); i++) {
update_undead(undeads[i]);
bool audio_active = !limit_audio || range_overlaps_active_areas(undeads[i].position, undeads[i].position, areaStarts, areaEnds);
update_undead(undeads[i], audio_active);
}
}
+14 -1
View File
@@ -481,9 +481,22 @@ void normalize_tree_positions() {
}
void update_environment() {
int[] areaStarts;
int[] areaEnds;
get_active_audio_areas(areaStarts, areaEnds);
bool limit_audio = (areaStarts.length() > 0);
for(uint i = 0; i < trees.length(); i++) {
trees[i].update();
trees[i].try_regen();
if (limit_audio && !range_overlaps_active_areas(trees[i].position, trees[i].position, areaStarts, areaEnds)) {
if (trees[i].sound_handle != -1) {
p.destroy_sound(trees[i].sound_handle);
trees[i].sound_handle = -1;
}
continue;
}
trees[i].update();
}
}
+87
View File
@@ -7,6 +7,65 @@ void check_action_menu(int x) {
}
}
WorldSnare@ get_snare_within_range(int pos, int range) {
WorldSnare@ nearest = null;
int nearest_distance = range + 1;
for (uint i = 0; i < world_snares.length(); i++) {
int dist = abs(world_snares[i].position - pos);
if (dist <= range && dist < nearest_distance) {
nearest_distance = dist;
@nearest = @world_snares[i];
}
}
return nearest;
}
string get_fire_intensity_label(int fuel_remaining) {
int hours_remaining = fuel_remaining / 60000;
if (hours_remaining >= 8) return "Giant blaze";
if (hours_remaining >= 2) return "Small fire";
return "A few glowing coals";
}
string get_fire_time_estimate(int fuel_remaining) {
int approx_hours = (fuel_remaining + 30000) / 60000;
if (approx_hours <= 0) return "less than 1 hour";
if (approx_hours == 1) return "approximately 1 hour";
return "approximately " + approx_hours + " hours";
}
void check_fire_status(WorldFire@ fire) {
if (fire == null) {
speak_with_history("No fire nearby.", true);
return;
}
string label = get_fire_intensity_label(fire.fuel_remaining);
string estimate = get_fire_time_estimate(fire.fuel_remaining);
speak_with_history(label + ". " + estimate + " remaining.", true);
}
void check_snare_status(WorldSnare@ snare) {
if (snare == null) {
speak_with_history("No snare nearby.", true);
return;
}
if (snare.has_catch) {
speak_with_history("Snare holds a " + snare.catch_type + ".", true);
} else if (!snare.active) {
speak_with_history("Snare is set but not active yet.", true);
} else {
speak_with_history("Snare is set and empty.", true);
}
}
void check_fishing_status() {
if (fish_on_line) {
speak_with_history("Fish on the line.", true);
return;
}
speak_with_history("No fish on the line.", true);
}
void try_place_snare(int x) {
if (get_personal_count(ITEM_SNARES) > 0) {
// Prevent placing in base area
@@ -153,10 +212,13 @@ void run_action_menu(int x) {
int selection = 0;
string[] options;
int[] action_types; // Track what action each option corresponds to
const int check_range = 2;
// Check if fire is nearby
WorldFire@ nearby_fire = get_fire_near(x);
bool can_feed_fire = nearby_fire != null;
WorldFire@ check_fire = get_fire_within_range(x, check_range);
WorldSnare@ check_snare = get_snare_within_range(x, check_range);
// Build menu options dynamically
options.insert_last("Place Snare");
@@ -182,6 +244,19 @@ void run_action_menu(int x) {
action_types.insert_last(4);
}
if (check_fire != null) {
options.insert_last("Check fire");
action_types.insert_last(5);
}
if (check_snare != null) {
options.insert_last("Check snare");
action_types.insert_last(6);
}
if (line_in_water || fish_on_line || is_reeling || is_casting) {
options.insert_last("Check fishing pole");
action_types.insert_last(7);
}
while(true) {
wait(5);
menu_background_tick();
@@ -214,6 +289,12 @@ void run_action_menu(int x) {
try_feed_fire_log(nearby_fire);
} else if (action == 4) {
try_burn_incense();
} else if (action == 5) {
check_fire_status(check_fire);
} else if (action == 6) {
check_snare_status(check_snare);
} else if (action == 7) {
check_fishing_status();
}
break;
}
@@ -230,6 +311,12 @@ void run_action_menu(int x) {
try_feed_fire_log_max(nearby_fire);
} else if (action == 4) {
try_burn_incense_max();
} else if (action == 5) {
check_fire_status(check_fire);
} else if (action == 6) {
check_snare_status(check_snare);
} else if (action == 7) {
check_fishing_status();
}
break;
}
+5 -1
View File
@@ -12,7 +12,11 @@ void run_base_info_menu() {
int selection = 0;
string[] options;
options.insert_last("Barricade health " + barricade_health + " of " + BARRICADE_MAX_HEALTH);
options.insert_last("Residents " + residents_count);
string resident_status = "Residents " + residents_count;
if (blessing_resident_active) {
resident_status += " (blessed)";
}
options.insert_last(resident_status);
if (world_storages.length() > 0) {
options.insert_last("Storage built. Total items " + get_storage_total_items());
+3 -1
View File
@@ -26,7 +26,9 @@ void notify(string message) {
void update_notifications() {
if (notification_queue.length() == 0) {
notification_active = false;
if (notification_active && notification_timer.elapsed >= NOTIFICATION_DELAY) {
notification_active = false;
}
notification_sound_handle = -1;
return;
}
+2
View File
@@ -62,6 +62,8 @@ int incense_hours_remaining = 0;
bool incense_burning = false;
bool blessing_speed_active = false;
timer blessing_speed_timer;
bool blessing_resident_active = false;
timer blessing_resident_timer;
// Timers
timer walktimer;
+7 -1
View File
@@ -202,6 +202,7 @@ void reset_game_state() {
incense_hours_remaining = 0;
incense_burning = false;
blessing_speed_active = false;
blessing_resident_active = false;
// Reset inventory using the registry system
reset_inventory();
@@ -978,7 +979,12 @@ bool load_game_state() {
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);
int[] areaStarts;
int[] areaEnds;
get_active_audio_areas(areaStarts, areaEnds);
if (areaStarts.length() == 0 || range_overlaps_active_areas(b.position, b.position, areaStarts, areaEnds)) {
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");
+35 -1
View File
@@ -317,6 +317,10 @@ void update_blessings() {
update_max_health_from_equipment();
speak_with_history("The speed blessing fades.", true);
}
if (blessing_resident_active && blessing_resident_timer.elapsed >= BLESSING_RESIDENT_DURATION) {
blessing_resident_active = false;
speak_with_history("The residents' purpose fades.", true);
}
}
void attempt_blessing() {
@@ -328,6 +332,7 @@ void attempt_blessing() {
if (player_health < max_health) options.insert_last(0);
if (!blessing_speed_active) options.insert_last(1);
if (barricade_health < BARRICADE_MAX_HEALTH) options.insert_last(2);
if (residents_count > 0 && !blessing_resident_active) options.insert_last(3);
if (options.length() == 0) return;
int choice = options[random(0, options.length() - 1)];
@@ -358,6 +363,10 @@ void attempt_blessing() {
? "A divine force repairs the barricade. +" + gained + " health."
: "A divine force surrounds the barricade.";
notify(god_name + " favor shines upon you. " + bonus);
} else if (choice == 3) {
blessing_resident_active = true;
blessing_resident_timer.restart();
notify(god_name + " radiance fills residents with purpose.");
}
}
@@ -401,7 +410,7 @@ void update_time() {
if (is_daytime && residents_count > 0 && barricade_health < BARRICADE_MAX_HEALTH && current_hour % 4 == 0) {
if (has_any_storage_food()) {
int gained = add_barricade_health(residents_count);
int gained = add_barricade_health(residents_count * get_resident_effect_multiplier());
if (gained > 0 && x <= BASE_END) {
speak_with_history("Residents repaired the barricade. +" + gained + " health.", true);
}
@@ -527,6 +536,31 @@ void start_crossfade(bool to_night) {
void update_crossfade() {
if (!crossfade_active) return;
// If a handle went inactive mid-fade, cancel and restart ambience to avoid fading unrelated sounds.
if (crossfade_to_night) {
if (day_sound_handle == -1 || !p.sound_is_active(day_sound_handle)) {
crossfade_active = false;
update_ambience(true);
return;
}
if (night_sound_handle == -1 || !p.sound_is_active(night_sound_handle)) {
crossfade_active = false;
update_ambience(true);
return;
}
} else {
if (night_sound_handle == -1 || !p.sound_is_active(night_sound_handle)) {
crossfade_active = false;
update_ambience(true);
return;
}
if (day_sound_handle == -1 || !p.sound_is_active(day_sound_handle)) {
crossfade_active = false;
update_ambience(true);
return;
}
}
float progress = float(crossfade_timer.elapsed) / float(CROSSFADE_DURATION);
if (progress > 1.0) progress = 1.0;
+13 -1
View File
@@ -360,12 +360,24 @@ bool is_mountain_stream_at(int world_x) {
return mountain.is_stream_at(world_x);
}
void update_mountains() {
void update_mountains_with_audio_areas(int[]@ areaStarts, int[]@ areaEnds) {
bool limit_audio = (areaStarts.length() > 0);
for (uint i = 0; i < world_mountains.length(); i++) {
if (limit_audio && !range_overlaps_active_areas(world_mountains[i].start_position, world_mountains[i].end_position, areaStarts, areaEnds)) {
world_mountains[i].destroy();
continue;
}
world_mountains[i].update();
}
}
void update_mountains() {
int[] areaStarts;
int[] areaEnds;
get_active_audio_areas(areaStarts, areaEnds);
update_mountains_with_audio_areas(areaStarts, areaEnds);
}
void clear_mountains() {
for (uint i = 0; i < world_mountains.length(); i++) {
world_mountains[i].destroy();
+9
View File
@@ -49,7 +49,16 @@ void add_world_drop(int pos, string type) {
}
void update_world_drops() {
int[] areaStarts;
int[] areaEnds;
get_active_audio_areas(areaStarts, areaEnds);
bool limit_audio = (areaStarts.length() > 0);
for (uint i = 0; i < world_drops.length(); i++) {
if (limit_audio && !range_overlaps_active_areas(world_drops[i].position, world_drops[i].position, areaStarts, areaEnds)) {
world_drops[i].destroy();
continue;
}
world_drops[i].update();
}
}
+11
View File
@@ -87,9 +87,20 @@ void add_world_fire(int pos) {
}
void update_fires() {
int[] areaStarts;
int[] areaEnds;
get_active_audio_areas(areaStarts, areaEnds);
bool limit_audio = (areaStarts.length() > 0);
// Update all fires and remove any that have burned out
for (uint i = 0; i < world_fires.length(); i++) {
world_fires[i].update();
if (limit_audio && !range_overlaps_active_areas(world_fires[i].position, world_fires[i].position, areaStarts, areaEnds)) {
if (world_fires[i].sound_handle != -1) {
p.destroy_sound(world_fires[i].sound_handle);
world_fires[i].sound_handle = -1;
}
}
}
// Remove dead fires
+13 -1
View File
@@ -149,7 +149,19 @@ void check_snare_collision(int player_x) {
}
void update_snares() {
int[] areaStarts;
int[] areaEnds;
get_active_audio_areas(areaStarts, areaEnds);
bool limit_audio = (areaStarts.length() > 0);
for (int i = int(world_snares.length()) - 1; i >= 0; i--) {
world_snares[i].update();
WorldSnare@ snare = world_snares[i];
snare.update();
if (limit_audio && !range_overlaps_active_areas(snare.position, snare.position, areaStarts, areaEnds)) {
if (snare.sound_handle != -1) {
p.destroy_sound(snare.sound_handle);
snare.sound_handle = -1;
}
}
}
}
+105
View File
@@ -66,15 +66,120 @@ class WorldStream {
}
WorldStream@[] world_streams;
string get_expanded_area_type(int index) {
string terrain = expanded_terrain_types[index];
if (terrain.find("mountain:") == 0) {
return "mountain";
}
return terrain;
}
bool get_audio_area_bounds_for_position(int pos, int &out areaStart, int &out areaEnd) {
if (pos <= BASE_END) {
areaStart = 0;
areaEnd = BASE_END;
return true;
}
if (pos <= GRASS_END) {
areaStart = BASE_END + 1;
areaEnd = GRASS_END;
return true;
}
if (pos <= GRAVEL_END) {
areaStart = GRAVEL_START;
areaEnd = GRAVEL_END;
return true;
}
if (expanded_area_start == -1 || pos < expanded_area_start || pos > expanded_area_end) {
return false;
}
int index = pos - expanded_area_start;
int total = int(expanded_terrain_types.length());
if (index < 0 || index >= total) return false;
string areaType = get_expanded_area_type(index);
int left = index;
while (left > 0) {
if (get_expanded_area_type(left - 1) != areaType) break;
left--;
}
int right = index;
while (right + 1 < total) {
if (get_expanded_area_type(right + 1) != areaType) break;
right++;
}
areaStart = expanded_area_start + left;
areaEnd = expanded_area_start + right;
return true;
}
void get_active_audio_areas(int[]@ areaStarts, int[]@ areaEnds) {
areaStarts.resize(0);
areaEnds.resize(0);
int currentStart = 0;
int currentEnd = 0;
if (!get_audio_area_bounds_for_position(x, currentStart, currentEnd)) return;
areaStarts.insert_last(currentStart);
areaEnds.insert_last(currentEnd);
if (currentStart > 0) {
int prevStart = 0;
int prevEnd = 0;
if (get_audio_area_bounds_for_position(currentStart - 1, prevStart, prevEnd)) {
if (prevStart != currentStart || prevEnd != currentEnd) {
areaStarts.insert_last(prevStart);
areaEnds.insert_last(prevEnd);
}
}
}
if (currentEnd + 1 < MAP_SIZE) {
int nextStart = 0;
int nextEnd = 0;
if (get_audio_area_bounds_for_position(currentEnd + 1, nextStart, nextEnd)) {
if (nextStart != currentStart || nextEnd != currentEnd) {
areaStarts.insert_last(nextStart);
areaEnds.insert_last(nextEnd);
}
}
}
}
bool range_overlaps_active_areas(int rangeStart, int rangeEnd, int[]@ areaStarts, int[]@ areaEnds) {
for (uint i = 0; i < areaStarts.length(); i++) {
if (rangeEnd >= areaStarts[i] && rangeStart <= areaEnds[i]) {
return true;
}
}
return false;
}
void add_world_stream(int start_pos, int width) {
WorldStream@ s = WorldStream(start_pos, width);
world_streams.insert_last(s);
}
void update_streams() {
int[] areaStarts;
int[] areaEnds;
get_active_audio_areas(areaStarts, areaEnds);
bool limit_audio = areaStarts.length() > 0;
for (uint i = 0; i < world_streams.length(); i++) {
if (limit_audio && !range_overlaps_active_areas(world_streams[i].start_position, world_streams[i].end_position, areaStarts, areaEnds)) {
world_streams[i].destroy();
continue;
}
world_streams[i].update();
}
update_mountains_with_audio_areas(areaStarts, areaEnds);
}
WorldStream@ get_stream_at(int pos) {
-11
View File
@@ -17,14 +17,3 @@
// - world_streams.nvgt - Streams for fishing and creature spawning
// - mountains.nvgt - Mountain ranges with procedural terrain generation
// - barricade.nvgt - Base defense system
// Legacy update function - retained for compatibility
// Note: This is largely obsolete as individual update functions are called from main loop
void update_world_objects() {
for (uint i = 0; i < world_snares.length(); i++) {
world_snares[i].update();
}
for (uint i = 0; i < world_fires.length(); i++) {
world_fires[i].update();
}
}