diff --git a/src/base_system.nvgt b/src/base_system.nvgt index fdc9cfd..a401899 100644 --- a/src/base_system.nvgt +++ b/src/base_system.nvgt @@ -37,3 +37,115 @@ void keep_base_fires_fed() { break; } } + +// Resident defense functions +int get_available_defense_weapons() { + int count = storage_spears; + // Slings only count if stones are available + if (storage_slings > 0 && storage_stones > 0) { + count += storage_slings; + } + return count; +} + +bool can_residents_defend() { + if (residents_count <= 0) return false; + return get_available_defense_weapons() > 0; +} + +bool choose_defense_weapon() { + // Returns true for spear, false for sling + int spearCount = storage_spears; + int slingCount = (storage_slings > 0 && storage_stones > 0) ? storage_slings : 0; + int total = spearCount + slingCount; + + if (total == 0) return true; + if (slingCount == 0) return true; + if (spearCount == 0) return false; + + int roll = random(1, total); + return roll <= spearCount; +} + +int perform_resident_defense() { + if (!can_residents_defend()) return 0; + + // Choose weapon type randomly weighted by availability + bool useSpear = choose_defense_weapon(); + + int damage = 0; + if (useSpear && storage_spears > 0) { + damage = RESIDENT_SPEAR_DAMAGE; + play_1d_with_volume_step("sounds/weapons/spear_swing.ogg", x, BASE_END + 1, false, 3.0); + } else if (storage_slings > 0 && storage_stones > 0) { + damage = random(RESIDENT_SLING_DAMAGE_MIN, RESIDENT_SLING_DAMAGE_MAX); + storage_stones--; + play_1d_with_volume_step("sounds/weapons/sling_hit.ogg", x, BASE_END + 1, false, 3.0); + } + + return damage; +} + +void process_daily_weapon_breakage() { + if (residents_count <= 0) return; + + int totalWeapons = storage_spears + storage_slings; + if (totalWeapons == 0) return; + + // Number of breakage checks = min(residents, weapons) + int checksToPerform = (residents_count < totalWeapons) ? residents_count : totalWeapons; + + // Distribute checks among available weapons + int spearChecks = 0; + int slingChecks = 0; + + for (int i = 0; i < checksToPerform; i++) { + int remainingSpears = storage_spears - spearChecks; + int remainingSlings = storage_slings - slingChecks; + int remaining = remainingSpears + remainingSlings; + + if (remaining <= 0) break; + + int roll = random(1, remaining); + if (roll <= remainingSpears && remainingSpears > 0) { + spearChecks++; + } else if (remainingSlings > 0) { + slingChecks++; + } + } + + // Perform breakage checks + int spearsBroken = 0; + int slingsBroken = 0; + + for (int i = 0; i < spearChecks; i++) { + if (random(1, 100) <= RESIDENT_WEAPON_BREAK_CHANCE) { + spearsBroken++; + } + } + + for (int i = 0; i < slingChecks; i++) { + if (random(1, 100) <= RESIDENT_WEAPON_BREAK_CHANCE) { + slingsBroken++; + } + } + + // Apply breakage + if (spearsBroken > 0) { + storage_spears -= spearsBroken; + if (storage_spears < 0) storage_spears = 0; + string msg = (spearsBroken == 1) + ? "A resident's spear broke from wear." + : spearsBroken + " spears broke from wear."; + notify(msg); + } + + if (slingsBroken > 0) { + storage_slings -= slingsBroken; + if (storage_slings < 0) storage_slings = 0; + string msg = (slingsBroken == 1) + ? "A resident's sling broke from wear." + : slingsBroken + " slings broke from wear."; + notify(msg); + } +} diff --git a/src/constants.nvgt b/src/constants.nvgt index 67c00f5..18bc0e8 100644 --- a/src/constants.nvgt +++ b/src/constants.nvgt @@ -97,3 +97,9 @@ const double QUEST_FAVOR_PER_POINT = 0.05; const int QUEST_STONE_SCORE = 6; const int QUEST_LOG_SCORE = 10; const int QUEST_SKIN_SCORE = 14; + +// Resident defense settings +const int RESIDENT_WEAPON_BREAK_CHANCE = 10; +const int RESIDENT_SPEAR_DAMAGE = 2; +const int RESIDENT_SLING_DAMAGE_MIN = 3; +const int RESIDENT_SLING_DAMAGE_MAX = 5; diff --git a/src/time_system.nvgt b/src/time_system.nvgt index 411de9a..a035a6f 100644 --- a/src/time_system.nvgt +++ b/src/time_system.nvgt @@ -153,12 +153,12 @@ void check_scheduled_invasion() { void attempt_daily_invasion() { if (current_day < 2) return; if (invasion_triggered_today || invasion_active) return; - if (invasion_roll_done_today) return; - invasion_roll_done_today = true; + if (current_hour < 6 || current_hour > 12) return; int roll = random(1, 100); if (roll > invasion_chance) return; + invasion_triggered_today = true; schedule_invasion(); check_scheduled_invasion(); } @@ -305,7 +305,6 @@ void update_time() { check_ambience_transition(); - // TODO: add resident defense using stored weapons once storage exists. if (is_daytime && residents_count > 0 && barricade_health < BARRICADE_MAX_HEALTH && current_hour % 4 == 0) { if (storage_meat > 0) { int gained = add_barricade_health(residents_count); @@ -317,12 +316,10 @@ void update_time() { if (current_hour == 6) { save_game_state(); - } - - if (current_hour == 6) { - attempt_daily_invasion(); + process_daily_weapon_breakage(); attempt_daily_quest(); } + attempt_daily_invasion(); keep_base_fires_fed(); check_scheduled_invasion(); attempt_blessing(); diff --git a/src/world_state.nvgt b/src/world_state.nvgt index 0a7d874..492ba4d 100644 --- a/src/world_state.nvgt +++ b/src/world_state.nvgt @@ -618,6 +618,17 @@ void try_attack_barricade(Zombie@ zombie) { play_1d_with_volume_step("sounds/enemies/zombie_hits_player.ogg", x, zombie.position, false, ZOMBIE_SOUND_VOLUME_STEP); + // Resident defense counter-attack + if (can_residents_defend()) { + int counterDamage = perform_resident_defense(); + if (counterDamage > 0) { + zombie.health -= counterDamage; + if (zombie.health <= 0 && x <= BASE_END) { + screen_reader_speak("Residents killed an attacking zombie.", true); + } + } + } + if (barricade_health == 0) { notify("The barricade has fallen!"); } @@ -859,6 +870,17 @@ void try_attack_barricade_bandit(Bandit@ bandit) { p.play_stationary("sounds/weapons/axe_hit.ogg", false); } + // Resident defense counter-attack + if (can_residents_defend()) { + int counterDamage = perform_resident_defense(); + if (counterDamage > 0) { + bandit.health -= counterDamage; + if (bandit.health <= 0 && x <= BASE_END) { + screen_reader_speak("Residents killed an attacking bandit.", true); + } + } + } + if (barricade_health == 0) { notify("The barricade has fallen!"); }