Bow mechanics added, documentation updated.

This commit is contained in:
Storm Dragon
2026-01-26 12:48:14 -05:00
parent 837deacf6c
commit efd90b596c
16 changed files with 484 additions and 100 deletions
+74 -29
View File
@@ -198,12 +198,20 @@ void keep_base_fires_fed() {
}
// Resident defense functions
const int RESIDENT_WEAPON_SPEAR = 0;
const int RESIDENT_WEAPON_SLING = 1;
const int RESIDENT_WEAPON_BOW = 2;
int get_available_defense_weapons() {
int count = get_storage_count(ITEM_SPEARS);
// Slings only count if stones are available
if (get_storage_count(ITEM_SLINGS) > 0 && get_storage_count(ITEM_STONES) > 0) {
count += get_storage_count(ITEM_SLINGS);
}
// Bows only count if arrows are available
if (get_storage_count(ITEM_BOWS) > 0 && get_storage_count(ITEM_ARROWS) > 0) {
count += get_storage_count(ITEM_BOWS);
}
return count;
}
@@ -212,33 +220,42 @@ bool can_residents_defend() {
return get_available_defense_weapons() > 0;
}
bool choose_defense_weapon() {
// Returns true for spear, false for sling
int choose_defense_weapon_type() {
// Prefer bows if available
int bowCount = (get_storage_count(ITEM_BOWS) > 0 && get_storage_count(ITEM_ARROWS) > 0)
? get_storage_count(ITEM_BOWS)
: 0;
if (bowCount > 0) return RESIDENT_WEAPON_BOW;
int spearCount = get_storage_count(ITEM_SPEARS);
int slingCount = (get_storage_count(ITEM_SLINGS) > 0 && get_storage_count(ITEM_STONES) > 0) ? get_storage_count(ITEM_SLINGS) : 0;
int total = spearCount + slingCount;
if (total == 0) return true;
if (slingCount == 0) return true;
if (spearCount == 0) return false;
if (total == 0) return RESIDENT_WEAPON_SPEAR;
if (slingCount == 0) return RESIDENT_WEAPON_SPEAR;
if (spearCount == 0) return RESIDENT_WEAPON_SLING;
int roll = random(1, total);
return roll <= spearCount;
return (roll <= spearCount) ? RESIDENT_WEAPON_SPEAR : RESIDENT_WEAPON_SLING;
}
int perform_resident_defense() {
if (!can_residents_defend()) return 0;
// Choose weapon type randomly weighted by availability
bool useSpear = choose_defense_weapon();
// Choose weapon type (bows preferred, otherwise weighted by availability)
int weapon_type = choose_defense_weapon_type();
int damage = 0;
if (useSpear && get_storage_count(ITEM_SPEARS) > 0) {
if (weapon_type == RESIDENT_WEAPON_BOW && get_storage_count(ITEM_BOWS) > 0 && get_storage_count(ITEM_ARROWS) > 0) {
damage = apply_resident_damage_bonus(random(RESIDENT_SLING_DAMAGE_MIN, RESIDENT_SLING_DAMAGE_MAX));
add_storage_count(ITEM_ARROWS, -1);
play_1d_with_volume_step("sounds/weapons/bow_fire.ogg", x, BASE_END + 1, false, RESIDENT_DEFENSE_VOLUME_STEP);
} else if (weapon_type == RESIDENT_WEAPON_SPEAR && get_storage_count(ITEM_SPEARS) > 0) {
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) {
} else if (weapon_type == RESIDENT_WEAPON_SLING && get_storage_count(ITEM_SLINGS) > 0 && get_storage_count(ITEM_STONES) > 0) {
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);
@@ -248,28 +265,31 @@ int perform_resident_defense() {
return damage;
}
// Proactive resident sling defense
timer resident_sling_timer;
// Proactive resident ranged defense
timer resident_ranged_timer;
void attempt_resident_sling_defense() {
// Only if residents exist and have slings with stones
void attempt_resident_ranged_defense() {
// Only if residents exist and have ranged weapons
if (residents_count <= 0) return;
if (get_storage_count(ITEM_SLINGS) <= 0 || get_storage_count(ITEM_STONES) <= 0) return;
bool has_bow = (get_storage_count(ITEM_BOWS) > 0 && get_storage_count(ITEM_ARROWS) > 0);
bool has_sling = (get_storage_count(ITEM_SLINGS) > 0 && get_storage_count(ITEM_STONES) > 0);
if (!has_bow && !has_sling) return;
// Cooldown between shots
if (resident_sling_timer.elapsed < get_resident_cooldown(RESIDENT_SLING_COOLDOWN)) return;
if (resident_ranged_timer.elapsed < get_resident_cooldown(RESIDENT_SLING_COOLDOWN)) return;
// Find nearest enemy within sling range
int nearestDistance = SLING_RANGE + 1;
int range = has_bow ? BOW_RANGE : SLING_RANGE;
// Find nearest enemy within range
int nearestDistance = range + 1;
int targetPos = -1;
bool targetIsBandit = false;
int sling_origin = BASE_END;
int ranged_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) {
int dist = abs(zombies[i].position - ranged_origin);
if (dist > 0 && dist <= range && dist < nearestDistance) {
nearestDistance = dist;
targetPos = zombies[i].position;
targetIsBandit = false;
@@ -278,8 +298,8 @@ void attempt_resident_sling_defense() {
// 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) {
int dist = abs(bandits[i].position - ranged_origin);
if (dist > 0 && dist <= range && dist < nearestDistance) {
nearestDistance = dist;
targetPos = bandits[i].position;
targetIsBandit = true;
@@ -290,11 +310,16 @@ void attempt_resident_sling_defense() {
if (targetPos == -1) return;
// Shoot!
resident_sling_timer.restart();
add_storage_count(ITEM_STONES, -1);
resident_ranged_timer.restart();
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 (has_bow) {
add_storage_count(ITEM_ARROWS, -1);
play_1d_with_volume_step("sounds/weapons/bow_fire.ogg", x, BASE_END + 1, false, RESIDENT_DEFENSE_VOLUME_STEP);
play_1d_with_volume_step("sounds/weapons/arrow_hit.ogg", x, targetPos, false, RESIDENT_DEFENSE_VOLUME_STEP);
} else {
add_storage_count(ITEM_STONES, -1);
play_1d_with_volume_step("sounds/weapons/sling_hit.ogg", x, targetPos, false, RESIDENT_DEFENSE_VOLUME_STEP);
}
if (targetIsBandit) {
damage_bandit_at(targetPos, damage);
@@ -313,7 +338,7 @@ void attempt_resident_sling_defense() {
void process_daily_weapon_breakage() {
if (residents_count <= 0) return;
int totalWeapons = get_storage_count(ITEM_SPEARS) + get_storage_count(ITEM_SLINGS);
int totalWeapons = get_storage_count(ITEM_SPEARS) + get_storage_count(ITEM_SLINGS) + get_storage_count(ITEM_BOWS);
if (totalWeapons == 0) return;
// Number of breakage checks = min(residents, weapons)
@@ -322,17 +347,21 @@ void process_daily_weapon_breakage() {
// Distribute checks among available weapons
int spearChecks = 0;
int slingChecks = 0;
int bowChecks = 0;
for (int i = 0; i < checksToPerform; i++) {
int remainingSpears = get_storage_count(ITEM_SPEARS) - spearChecks;
int remainingSlings = get_storage_count(ITEM_SLINGS) - slingChecks;
int remaining = remainingSpears + remainingSlings;
int remainingBows = get_storage_count(ITEM_BOWS) - bowChecks;
int remaining = remainingSpears + remainingSlings + remainingBows;
if (remaining <= 0) break;
int roll = random(1, remaining);
if (roll <= remainingSpears && remainingSpears > 0) {
spearChecks++;
} else if (roll <= remainingSpears + remainingBows && remainingBows > 0) {
bowChecks++;
} else if (remainingSlings > 0) {
slingChecks++;
}
@@ -341,6 +370,7 @@ void process_daily_weapon_breakage() {
// Perform breakage checks
int spearsBroken = 0;
int slingsBroken = 0;
int bowsBroken = 0;
int break_chance = get_resident_break_chance(RESIDENT_WEAPON_BREAK_CHANCE);
for (int i = 0; i < spearChecks; i++) {
@@ -355,6 +385,12 @@ void process_daily_weapon_breakage() {
}
}
for (int i = 0; i < bowChecks; i++) {
if (random(1, 100) <= break_chance) {
bowsBroken++;
}
}
// Apply breakage
if (spearsBroken > 0) {
add_storage_count(ITEM_SPEARS, -spearsBroken);
@@ -373,6 +409,15 @@ void process_daily_weapon_breakage() {
: slingsBroken + " slings broke from wear.";
notify(msg);
}
if (bowsBroken > 0) {
add_storage_count(ITEM_BOWS, -bowsBroken);
if (get_storage_count(ITEM_BOWS) < 0) set_storage_count(ITEM_BOWS, 0);
string msg = (bowsBroken == 1)
? "A resident's bow broke from wear."
: bowsBroken + " bows broke from wear.";
notify(msg);
}
}
// Resident snare retrieval
+183 -38
View File
@@ -14,6 +14,11 @@ void perform_attack(int current_x) {
timer ammoBlockTimer;
string lastAmmoBlockMessage = "";
const int AMMO_BLOCK_COOLDOWN_MS = 3000;
const int BOW_HIT_NONE = 0;
const int BOW_HIT_BANDIT = 1;
const int BOW_HIT_BOAR = 2;
const int BOW_HIT_ZOMBIE = 3;
const int BOW_HIT_FLYING = 4;
void speak_ammo_blocked(string message) {
if (message == lastAmmoBlockMessage && ammoBlockTimer.elapsed < AMMO_BLOCK_COOLDOWN_MS) {
@@ -25,6 +30,44 @@ void speak_ammo_blocked(string message) {
speak_with_history(message, true);
}
int find_ranged_enemy(int player_x, int range, int direction, bool allow_flying, bool &out hit_bandit, bool &out hit_boar, bool &out hit_flying_creature) {
hit_bandit = false;
hit_boar = false;
hit_flying_creature = false;
for (int dist = 1; dist <= range; dist++) {
int check_x = player_x + (dist * direction);
if (check_x < 0 || check_x >= MAP_SIZE) break;
Bandit@ bandit = get_bandit_at(check_x);
if (bandit != null) {
hit_bandit = true;
return check_x;
}
GroundGame@ boar = get_boar_at(check_x);
if (boar != null) {
hit_boar = true;
return check_x;
}
Undead@ undead = get_zombie_at(check_x);
if (undead != null) {
return check_x;
}
if (allow_flying) {
FlyingCreature@ creature = get_flying_creature_at(check_x);
if (creature != null && creature.state == "flying") {
hit_flying_creature = true;
return check_x;
}
}
}
return -1;
}
int attack_enemy_ranged(int start_x, int end_x, int damage) {
for (int check_x = start_x; check_x <= end_x; check_x++) {
// Check for bandits first (priority during daytime)
@@ -137,6 +180,145 @@ void update_sling_charge() {
}
}
int get_bow_draw_damage(int elapsed_ms) {
int clamped = elapsed_ms;
if (clamped < 0) clamped = 0;
if (clamped > BOW_DRAW_TIME_MS) clamped = BOW_DRAW_TIME_MS;
float ratio = float(clamped) / float(BOW_DRAW_TIME_MS);
int damage = int(ratio * BOW_DAMAGE_MAX);
if (damage < BOW_DAMAGE_MIN) damage = BOW_DAMAGE_MIN;
if (damage > BOW_DAMAGE_MAX) damage = BOW_DAMAGE_MAX;
return damage;
}
void stop_bow_shot_audio() {
safe_destroy_sound(bow_shot_sound_handle);
bow_shot_active = false;
bow_shot_duration_ms = 0;
bow_shot_hit_x = -1;
bow_shot_hit_type = BOW_HIT_NONE;
}
void start_bow_shot_audio(int start_x, int end_x, int hit_x, int hit_type, int duration_ms) {
stop_bow_shot_audio();
bow_shot_active = true;
bow_shot_timer.restart();
bow_shot_start_x = start_x;
bow_shot_end_x = end_x;
bow_shot_hit_x = hit_x;
bow_shot_hit_type = hit_type;
bow_shot_duration_ms = duration_ms;
bow_shot_drop_pending = false;
bow_shot_drop_pos = -1;
if (bow_shot_duration_ms < 1) bow_shot_duration_ms = 1;
bow_shot_sound_handle = play_1d_with_volume_step(
"sounds/weapons/arrow_flies.ogg",
x,
bow_shot_start_x,
false,
PLAYER_WEAPON_SOUND_VOLUME_STEP
);
}
void update_bow_shot() {
if (!bow_shot_active) return;
if (bow_shot_duration_ms < 1) bow_shot_duration_ms = 1;
int elapsed = bow_shot_timer.elapsed;
float progress = float(elapsed) / float(bow_shot_duration_ms);
if (progress > 1.0f) progress = 1.0f;
int travel = int(float(bow_shot_end_x - bow_shot_start_x) * progress);
int current_pos = bow_shot_start_x + travel;
if (bow_shot_sound_handle != -1) {
p.update_sound_1d(bow_shot_sound_handle, current_pos);
}
if (elapsed >= bow_shot_duration_ms) {
int hit_x = bow_shot_hit_x;
int hit_type = bow_shot_hit_type;
bool drop_pending = bow_shot_drop_pending;
int drop_pos = bow_shot_drop_pos;
stop_bow_shot_audio();
if (hit_x >= 0) {
play_1d_with_volume_step(
"sounds/weapons/arrow_hit.ogg",
x,
hit_x,
false,
PLAYER_WEAPON_SOUND_VOLUME_STEP
);
if (hit_type == BOW_HIT_BANDIT) {
play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, hit_x, BANDIT_SOUND_VOLUME_STEP);
} else if (hit_type == BOW_HIT_BOAR) {
play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, hit_x, BOAR_SOUND_VOLUME_STEP);
} else if (hit_type == BOW_HIT_ZOMBIE) {
play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, hit_x, ZOMBIE_SOUND_VOLUME_STEP);
}
}
if (drop_pending && drop_pos >= 0) {
if (get_drop_at(drop_pos) == null) {
add_world_drop(drop_pos, "arrow");
}
}
}
}
void release_bow_attack(int player_x) {
if (get_personal_count(ITEM_ARROWS) <= 0) {
speak_ammo_blocked("No arrows.");
return;
}
int damage = get_bow_draw_damage(bow_draw_timer.elapsed);
add_personal_count(ITEM_ARROWS, -1);
p.play_stationary("sounds/weapons/bow_fire.ogg", false);
int search_direction = (facing == 1) ? 1 : -1;
bool hit_bandit = false;
bool hit_flying_creature = false;
bool hit_boar = false;
int target_x = find_ranged_enemy(player_x, BOW_RANGE, search_direction, true, hit_bandit, hit_boar, hit_flying_creature);
int hit_type = BOW_HIT_NONE;
if (target_x != -1) {
if (hit_bandit) {
damage_bandit_at(target_x, damage);
hit_type = BOW_HIT_BANDIT;
} else if (hit_boar) {
damage_boar_at(target_x, damage);
hit_type = BOW_HIT_BOAR;
} else if (hit_flying_creature) {
damage_flying_creature_at(target_x, damage);
hit_type = BOW_HIT_FLYING;
} else {
damage_zombie_at(target_x, damage);
hit_type = BOW_HIT_ZOMBIE;
}
}
int end_x = (target_x != -1)
? target_x
: (player_x + (search_direction * (BOW_RANGE + BOW_MISS_EXTRA_TILES)));
int duration_ms = ARROW_FLIES_DURATION_MS;
if (target_x != -1) {
int distance = abs(target_x - player_x);
if (distance < 1) distance = 1;
duration_ms = int(float(ARROW_FLIES_DURATION_MS) * (float(distance) / float(BOW_RANGE)));
if (duration_ms < 1) duration_ms = 1;
}
int hit_x = (target_x != -1) ? target_x : -1;
start_bow_shot_audio(player_x, end_x, hit_x, hit_type, duration_ms);
if (random(1, 100) <= 25) {
bow_shot_drop_pending = true;
bow_shot_drop_pos = (target_x != -1) ? target_x : end_x;
}
}
void release_sling_attack(int player_x) {
// Consume stone
add_personal_count(ITEM_STONES, -1);
@@ -158,44 +340,7 @@ void release_sling_attack(int player_x) {
bool hit_bandit = false;
bool hit_flying_creature = false;
bool hit_boar = false;
// Priority: Find nearest enemy (bandit or zombie) first
for (int dist = 1; dist <= SLING_RANGE; dist++) {
int check_x = player_x + (dist * search_direction);
if (check_x < 0 || check_x >= MAP_SIZE) break;
// Check for bandit first
Bandit@ bandit = get_bandit_at(check_x);
if (bandit != null) {
target_x = check_x;
hit_bandit = true;
break;
}
// Then check for boar
GroundGame@ boar = get_boar_at(check_x);
if (boar != null) {
target_x = check_x;
hit_boar = true;
break;
}
// Then check for undead
Undead@ undead = get_zombie_at(check_x);
if (undead != null) {
target_x = check_x;
hit_bandit = false;
break;
}
// Then check for flying creature (only if flying)
FlyingCreature@ creature = get_flying_creature_at(check_x);
if (creature != null && creature.state == "flying") {
target_x = check_x;
hit_flying_creature = true;
break;
}
}
target_x = find_ranged_enemy(player_x, SLING_RANGE, search_direction, true, hit_bandit, hit_boar, hit_flying_creature);
// If no enemy found, check for trees (but don't damage them)
if (target_x == -1) {
+5 -2
View File
@@ -37,9 +37,12 @@ const int SLING_DAMAGE_MAX = 8;
const int SLING_RANGE = 8;
// Bow settings
const int BOW_DAMAGE_MIN = 6;
const int BOW_DAMAGE_MAX = 9;
const int BOW_DAMAGE_MIN = 0;
const int BOW_DAMAGE_MAX = 10;
const int BOW_RANGE = 12;
const int BOW_DRAW_TIME_MS = 1000;
const int ARROW_FLIES_DURATION_MS = 230;
const int BOW_MISS_EXTRA_TILES = 3;
const int ARROWS_PER_CRAFT = 12;
const int ARROW_CAPACITY_PER_QUIVER = 12;
+60 -2
View File
@@ -7,6 +7,7 @@ void run_materials_menu() {
"Butcher Game [Requires Game, Knife, Fire nearby]",
"Smoke Fish (1 Fish, 1 Stick) [Requires Fire nearby]",
"Arrows (2 Sticks, 4 Feathers, 2 Stones) [Requires Quiver]",
"Bowstring (3 Sinew) [Requires Fire nearby]",
"Incense (6 Sticks, 2 Vines, 1 Reed) [Requires Altar]"
};
@@ -34,7 +35,8 @@ void run_materials_menu() {
if (selection == 0) butcher_small_game();
else if (selection == 1) craft_smoke_fish();
else if (selection == 2) craft_arrows();
else if (selection == 3) craft_incense();
else if (selection == 3) craft_bowstring();
else if (selection == 4) craft_incense();
break;
}
@@ -42,7 +44,8 @@ void run_materials_menu() {
if (selection == 0) butcher_small_game_max();
else if (selection == 1) craft_smoke_fish_max();
else if (selection == 2) craft_arrows_max();
else if (selection == 3) craft_incense_max();
else if (selection == 3) craft_bowstring_max();
else if (selection == 4) craft_incense_max();
break;
}
}
@@ -119,6 +122,61 @@ void craft_arrows_max() {
speak_with_history("Crafted " + (ARROWS_PER_CRAFT * maxCraft) + " arrows.", true);
}
void craft_bowstring() {
WorldFire@ fire = get_fire_within_range(x, 3);
if (fire == null) {
speak_with_history("You need a fire within 3 tiles to make bowstring.", true);
return;
}
string missing = "";
if (get_personal_count(ITEM_SINEW) < 3) missing += "3 sinew ";
if (missing == "") {
if (get_personal_count(ITEM_BOWSTRINGS) >= get_personal_stack_limit()) {
speak_with_history("You can't carry any more bowstrings.", true);
return;
}
simulate_crafting(3);
add_personal_count(ITEM_SINEW, -3);
add_personal_count(ITEM_BOWSTRINGS, 1);
speak_with_history("Crafted a bowstring.", true);
} else {
speak_with_history("Missing: " + missing, true);
}
}
void craft_bowstring_max() {
WorldFire@ fire = get_fire_within_range(x, 3);
if (fire == null) {
speak_with_history("You need a fire within 3 tiles to make bowstring.", true);
return;
}
if (get_personal_count(ITEM_BOWSTRINGS) >= get_personal_stack_limit()) {
speak_with_history("You can't carry any more bowstrings.", true);
return;
}
int max_by_sinew = get_personal_count(ITEM_SINEW) / 3;
int max_craft = max_by_sinew;
int space = get_personal_stack_limit() - get_personal_count(ITEM_BOWSTRINGS);
if (max_craft > space) max_craft = space;
if (max_craft <= 0) {
speak_with_history("Missing: 3 sinew", true);
return;
}
int total_cost = max_craft * 3; // 3 sinew per bowstring
int craft_time = (total_cost < max_craft * 4) ? max_craft * 4 : total_cost;
simulate_crafting(craft_time);
add_personal_count(ITEM_SINEW, -(max_craft * 3));
add_personal_count(ITEM_BOWSTRINGS, max_craft);
speak_with_history("Crafted " + max_craft + " Bowstrings.", true);
}
void craft_incense() {
if (world_altars.length() == 0) {
speak_with_history("You need an altar to craft incense.", true);
+55 -1
View File
@@ -5,7 +5,8 @@ void run_weapons_menu() {
int selection = 0;
string[] options = {
"Spear (1 Stick, 1 Vine, 1 Stone) [Requires Knife]",
"Sling (1 Skin, 2 Vines)"
"Sling (1 Skin, 2 Vines)",
"Bow (1 Stick, 1 Bowstring)"
};
while(true) {
@@ -31,12 +32,14 @@ void run_weapons_menu() {
if (key_pressed(KEY_RETURN)) {
if (selection == 0) craft_spear();
else if (selection == 1) craft_sling();
else if (selection == 2) craft_bow();
break;
}
if (key_pressed(KEY_TAB)) {
if (selection == 0) craft_spear_max();
else if (selection == 1) craft_sling_max();
else if (selection == 2) craft_bow_max();
break;
}
}
@@ -155,6 +158,57 @@ void craft_sling_max() {
speak_with_history("Crafted " + max_craft + " Slings.", true);
}
void craft_bow() {
string missing = "";
if (get_personal_count(ITEM_STICKS) < 1) missing += "1 stick ";
if (get_personal_count(ITEM_BOWSTRINGS) < 1) missing += "1 bowstring ";
if (missing == "") {
if (get_personal_count(ITEM_BOWS) >= get_personal_stack_limit()) {
speak_with_history("You can't carry any more bows.", true);
return;
}
simulate_crafting(3);
add_personal_count(ITEM_STICKS, -1);
add_personal_count(ITEM_BOWSTRINGS, -1);
add_personal_count(ITEM_BOWS, 1);
speak_with_history("Crafted a Bow.", true);
} else {
speak_with_history("Missing: " + missing, true);
}
}
void craft_bow_max() {
if (get_personal_count(ITEM_BOWS) >= get_personal_stack_limit()) {
speak_with_history("You can't carry any more bows.", true);
return;
}
int max_by_sticks = get_personal_count(ITEM_STICKS);
int max_by_bowstrings = get_personal_count(ITEM_BOWSTRINGS);
int max_craft = max_by_sticks;
if (max_by_bowstrings < max_craft) max_craft = max_by_bowstrings;
int space = get_personal_stack_limit() - get_personal_count(ITEM_BOWS);
if (max_craft > space) max_craft = space;
if (max_craft <= 0) {
string missing = "";
if (get_personal_count(ITEM_STICKS) < 1) missing += "1 stick ";
if (get_personal_count(ITEM_BOWSTRINGS) < 1) missing += "1 bowstring ";
speak_with_history("Missing: " + missing, true);
return;
}
int total_cost = max_craft * 2; // 1 stick + 1 bowstring per bow
int craft_time = (total_cost < max_craft * 4) ? max_craft * 4 : total_cost;
simulate_crafting(craft_time);
add_personal_count(ITEM_STICKS, -max_craft);
add_personal_count(ITEM_BOWSTRINGS, -max_craft);
add_personal_count(ITEM_BOWS, max_craft);
speak_with_history("Crafted " + max_craft + " Bows.", true);
}
void craft_axe() {
string missing = "";
if (get_personal_count(ITEM_KNIVES) < 1) missing += "Stone Knife ";
+3
View File
@@ -239,6 +239,9 @@ bool damage_undead_at(int pos, int damage) {
if (undeads[i].position == pos) {
undeads[i].health -= damage;
if (undeads[i].health <= 0) {
if (world_altars.length() > 0) {
favor += 0.2;
}
if (undeads[i].sound_handle != -1) {
p.destroy_sound(undeads[i].sound_handle);
undeads[i].sound_handle = -1;
+16
View File
@@ -35,6 +35,20 @@ timer sling_charge_timer;
int sling_sound_handle = -1;
int last_sling_stage = -1; // Track which stage we're in to avoid duplicate sounds
// Bow state
bool bow_drawing = false;
timer bow_draw_timer;
bool bow_shot_active = false;
timer bow_shot_timer;
int bow_shot_start_x = 0;
int bow_shot_end_x = 0;
int bow_shot_hit_x = -1;
int bow_shot_hit_type = 0;
int bow_shot_sound_handle = -1;
int bow_shot_duration_ms = 0;
bool bow_shot_drop_pending = false;
int bow_shot_drop_pos = -1;
// Fishing state
bool is_casting = false; // Holding control, cast_strength playing
bool line_in_water = false; // Cast successful, waiting for fish
@@ -88,6 +102,8 @@ void restart_all_timers() {
fire_damage_timer.restart();
healing_timer.restart();
sling_charge_timer.restart();
bow_draw_timer.restart();
bow_shot_timer.restart();
// Fire fuel timers
for (uint i = 0; i < world_fires.length(); i++) {
+16
View File
@@ -110,6 +110,10 @@ void stop_active_sounds() {
p.destroy_sound(sling_sound_handle);
sling_sound_handle = -1;
}
if (bow_shot_sound_handle != -1) {
p.destroy_sound(bow_shot_sound_handle);
bow_shot_sound_handle = -1;
}
stop_all_weather_sounds();
}
@@ -164,6 +168,16 @@ void reset_game_state() {
climb_target_y = 0;
fall_start_y = 0;
sling_charging = false;
bow_drawing = false;
bow_shot_active = false;
bow_shot_start_x = 0;
bow_shot_end_x = 0;
bow_shot_hit_x = -1;
bow_shot_hit_type = 0;
bow_shot_duration_ms = 0;
bow_shot_sound_handle = -1;
bow_shot_drop_pending = false;
bow_shot_drop_pos = -1;
searching = false;
rope_climbing = false;
rope_climb_up = true;
@@ -256,6 +270,8 @@ void reset_game_state() {
fire_damage_timer.restart();
healing_timer.restart();
sling_charge_timer.restart();
bow_draw_timer.restart();
bow_shot_timer.restart();
fall_timer.restart();
climb_timer.restart();
}
+3 -3
View File
@@ -284,7 +284,7 @@ void attempt_resident_recruitment() {
return;
}
int added = random(1, 3);
int added = random(1, 2);
// Don't exceed cap
if (residents_count + added > MAX_RESIDENTS) {
added = MAX_RESIDENTS - residents_count;
@@ -483,8 +483,8 @@ void update_time() {
ensure_ambience_running();
// Proactive resident defense with slings
attempt_resident_sling_defense();
// Proactive resident ranged defense
attempt_resident_ranged_defense();
// Manage invasion enemies during active invasion
manage_bandits_during_invasion();
+14
View File
@@ -104,6 +104,20 @@ bool try_pickup_world_drop(WorldDrop@ drop) {
if (get_flying_creature_config_by_drop_type(drop.type) !is null) {
return try_pickup_small_game(drop.type);
}
if (drop.type == "arrow") {
int max_arrows = get_arrow_limit();
if (max_arrows <= 0) {
speak_with_history("You need a quiver to carry arrows.", true);
return false;
}
if (get_personal_count(ITEM_ARROWS) >= max_arrows) {
speak_with_history("You can't carry any more arrows.", true);
return false;
}
add_personal_count(ITEM_ARROWS, 1);
speak_with_history("Picked up arrow.", true);
return true;
}
if (drop.type == "boar carcass") {
if (get_personal_count(ITEM_BOAR_CARCASSES) >= get_personal_stack_limit()) {
speak_with_history("You can't carry any more boar carcasses.", true);