264 lines
8.0 KiB
Plaintext
264 lines
8.0 KiB
Plaintext
// 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);
|
|
}
|
|
}
|
|
}
|