// Base automation helpers int get_daily_food_requirement() { if (residents_count <= 0) return 0; return (residents_count + 1) / 2; } void consume_food_for_residents() { int needed = get_daily_food_requirement(); if (needed <= 0) return; if (storage_meat >= needed) { storage_meat -= needed; } else { storage_meat = 0; if (x <= BASE_END) { notify("No food, residents are hungry."); } } } void keep_base_fires_fed() { if (residents_count <= 0) return; if (storage_meat <= 0) return; if (storage_sticks <= 0 && storage_logs <= 0) return; for (uint i = 0; i < world_fires.length(); i++) { if (world_fires[i].position > BASE_END) continue; if (!world_fires[i].is_burning()) continue; if (world_fires[i].fuel_remaining > 300000) continue; if (storage_sticks > 0) { storage_sticks--; world_fires[i].add_fuel(300000); // 5 minutes } else if (storage_logs > 0) { storage_logs--; world_fires[i].add_fuel(720000); // 12 minutes } 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; // 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 (storage_slings > 0 && storage_stones > 0) { damage = random(RESIDENT_SLING_DAMAGE_MIN, RESIDENT_SLING_DAMAGE_MAX); // Slings use stones as ammo, so consume a stone storage_stones--; play_1d_with_volume_step("sounds/weapons/sling_hit.ogg", x, BASE_END + 1, false, RESIDENT_DEFENSE_VOLUME_STEP); } return damage; } // Proactive resident sling defense timer resident_sling_timer; const int RESIDENT_SLING_COOLDOWN = 4000; // 4 seconds between shots void attempt_resident_sling_defense() { // Only if residents exist and have slings with stones if (residents_count <= 0) return; if (storage_slings <= 0 || storage_stones <= 0) return; // Cooldown between shots if (resident_sling_timer.elapsed < RESIDENT_SLING_COOLDOWN) return; // Find nearest enemy within sling range int nearestDistance = SLING_RANGE + 1; int targetPos = -1; bool targetIsBandit = false; int sling_origin = BASE_END; // Check zombies for (uint i = 0; i < zombies.length(); i++) { int dist = abs(zombies[i].position - sling_origin); if (dist > 0 && dist <= SLING_RANGE && dist < nearestDistance) { nearestDistance = dist; targetPos = zombies[i].position; targetIsBandit = false; } } // Check bandits for (uint i = 0; i < bandits.length(); i++) { int dist = abs(bandits[i].position - sling_origin); if (dist > 0 && dist <= SLING_RANGE && dist < nearestDistance) { nearestDistance = dist; targetPos = bandits[i].position; targetIsBandit = true; } } // No targets in range if (targetPos == -1) return; // Shoot! resident_sling_timer.restart(); storage_stones--; int damage = 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) { damage_bandit_at(targetPos, damage); } else { damage_zombie_at(targetPos, damage); } // Play hit sound on enemy play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, targetPos, ZOMBIE_SOUND_VOLUME_STEP); } 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); } } // Resident resource collection const int RESIDENT_COLLECTION_CHANCE = 10; // 10% chance per basket per hour void attempt_resident_collection() { // Only during daytime if (!is_daytime) return; // Need residents if (residents_count <= 0) return; // Need baskets in storage to enable collection if (storage_reed_baskets <= 0) return; // Number of residents who can collect = min(residents, baskets) int active_collectors = (residents_count < storage_reed_baskets) ? residents_count : storage_reed_baskets; // Each active collector has a 10% chance to collect something for (int i = 0; i < active_collectors; i++) { if (random(1, 100) > RESIDENT_COLLECTION_CHANCE) continue; // Determine what to collect (weighted random) // Sticks and vines more common, logs and stones less common int roll = random(1, 100); string item_name = ""; if (roll <= 40) { // 40% chance - stick storage_sticks++; item_name = "stick"; } else if (roll <= 70) { // 30% chance - vine storage_vines++; item_name = "vine"; } else if (roll <= 85) { // 15% chance - stone storage_stones++; item_name = "stone"; } else { // 15% chance - log storage_logs++; item_name = "log"; } // Announce only if player is in base if (x <= BASE_END) { screen_reader_speak("Resident added " + item_name + " to storage.", true); } } }