Initial implementation of fishing system. A couple bugs fixed, e.g. zombies that are killed by residents actually die.

This commit is contained in:
Storm Dragon
2026-01-24 14:01:38 -05:00
parent 63cf759002
commit 0d711095c1
23 changed files with 837 additions and 47 deletions
+5 -3
View File
@@ -75,10 +75,11 @@
### Snare System
- Snares become active when player moves away
- Check every minute for catching/escaping small game
- Catch chance starts at 5%, increases by 5% per minute (max 75%)
- Escape chance starts at 5%, increases by 5% per minute (max 95%)
- Check every hour for catching/escaping small game
- Catch chance starts at 5%, increases by 5% per hour (max 75%)
- Escape chance: 0% for first hour after catch, then increases by 2% per hour (max 95%)
- Snare sound plays within 2 tiles distance
- Residents can retrieve game from snares (daytime only, requires meat in storage)
### Tree System
- Trees regenerate fully after ~5 minutes (minute-by-minute refill logic)
@@ -208,5 +209,6 @@ Organized into categories:
- Use `notify()` for important game events that should be reviewable
- Use `screen_reader_speak()` for immediate feedback that doesn't need to be stored
- All menus must call `menu_background_tick()` each loop to keep game state updating
- **Time references**: When discussing time (hours, minutes, days), assume game time unless explicitly stated as "real time". Game time runs at 60x speed: 1 real minute = 1 game hour, 24 real minutes = 1 game day.
- See `docs/patterns.md` for module and creature patterns
- See `docs/rune_system.md` for rune data/effects/save requirements
+4 -1
View File
@@ -30,6 +30,7 @@ sound_pool p(300);
#include "src/save_system.nvgt"
#include "src/base_system.nvgt"
#include "src/time_system.nvgt"
#include "src/fishing.nvgt"
#include "src/weather.nvgt"
#include "src/audio_utils.nvgt"
#include "src/creature_audio.nvgt"
@@ -400,6 +401,8 @@ void main()
search_timer.restart();
perform_search(x);
}
update_fishing();
// Sling charge detection
if (sling_equipped && (key_down(KEY_LCTRL) || key_down(KEY_RCTRL)) && !sling_charging) {
@@ -434,7 +437,7 @@ void main()
if (spear_equipped) attack_cooldown = 800;
if (axe_equipped) attack_cooldown = 1600;
if((key_down(KEY_LCTRL) || key_down(KEY_RCTRL)) && attack_timer.elapsed > attack_cooldown)
if(!fishing_pole_equipped && (key_down(KEY_LCTRL) || key_down(KEY_RCTRL)) && attack_timer.elapsed > attack_cooldown)
{
attack_timer.restart();
perform_attack(x);
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+88 -7
View File
@@ -1,22 +1,102 @@
// Base automation helpers
int get_daily_food_requirement() {
int get_food_requirement() {
if (residents_count <= 0) return 0;
return residents_count * 2;
return residents_count; // 1 meat per resident per 8 hours
}
bool has_any_streams() {
if (world_streams.length() > 0) return true;
for (uint i = 0; i < world_mountains.length(); i++) {
if (world_mountains[i].stream_positions.length() > 0) {
return true;
}
}
return false;
}
bool has_burning_fire_in_base() {
for (uint i = 0; i < world_fires.length(); i++) {
if (world_fires[i].position <= BASE_END && world_fires[i].is_burning()) {
return true;
}
}
return false;
}
void consume_food_for_residents() {
int needed = get_daily_food_requirement();
int needed = get_food_requirement();
if (needed <= 0) return;
if (get_storage_count(ITEM_MEAT) >= needed) {
add_storage_count(ITEM_MEAT, -needed);
int meat_available = get_storage_count(ITEM_MEAT);
int smoked_fish_available = get_storage_count(ITEM_SMOKED_FISH);
int total_food = meat_available + smoked_fish_available;
if (total_food >= needed) {
int from_meat = (meat_available >= needed) ? needed : meat_available;
add_storage_count(ITEM_MEAT, -from_meat);
int remaining = needed - from_meat;
if (remaining > 0) {
add_storage_count(ITEM_SMOKED_FISH, -remaining);
}
} else {
set_storage_count(ITEM_MEAT, 0);
set_storage_count(ITEM_SMOKED_FISH, 0);
if (x <= BASE_END) {
notify("No food, residents are hungry.");
}
}
}
void attempt_resident_fishing() {
if (!is_daytime) return;
if (residents_count <= 0) return;
if (get_storage_count(ITEM_FISHING_POLES) <= 0) return;
if (!has_any_streams()) return;
if (get_storage_count(ITEM_FISH) >= BASE_STORAGE_MAX) return;
int active_fishers = residents_count;
int poles = get_storage_count(ITEM_FISHING_POLES);
if (poles < active_fishers) active_fishers = poles;
int caught = 0;
for (int i = 0; i < active_fishers; i++) {
if (random(1, 100) > RESIDENT_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));
caught++;
}
if (caught > 0 && x <= BASE_END) {
if (caught == 1) {
speak_with_history("Resident caught a fish and added it to storage.", true);
} else {
speak_with_history("Residents caught " + caught + " fish and added them to storage.", true);
}
}
}
void attempt_resident_fish_smoking() {
if (!is_daytime) return;
if (residents_count <= 0) return;
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;
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);
}
}
void keep_base_fires_fed() {
if (residents_count <= 0) return;
if (get_storage_count(ITEM_MEAT) <= 0) return;
@@ -253,8 +333,9 @@ void attempt_resident_snare_retrieval() {
snare.has_catch = false;
snare.catch_type = "";
snare.catch_chance = 5;
snare.escape_chance = 5;
snare.minute_timer.restart();
snare.escape_chance = 0;
snare.hours_with_catch = 0;
snare.hour_timer.restart();
// Notify if player is in base
if (x <= BASE_END) {
+4
View File
@@ -25,6 +25,8 @@ const int BLESSING_BARRICADE_REPAIR = 20;
const int BLESSING_SPEED_DURATION = 300000;
const int BLESSING_TRIGGER_CHANCE = 10;
const int BLESSING_WALK_SPEED = 320;
const int FISH_WEIGHT_MIN = 1;
const int FISH_WEIGHT_MAX = 30;
// Weapon damage
const int SPEAR_DAMAGE = 3;
@@ -172,6 +174,8 @@ const int RESIDENT_SLING_DAMAGE_MIN = 3;
const int RESIDENT_SLING_DAMAGE_MAX = 5;
const int RESIDENT_SNARE_ESCAPE_CHANCE = 5; // 5% chance game escapes when resident retrieves
const int RESIDENT_SNARE_CHECK_CHANCE = 15; // 15% chance per hour to check snares
const int RESIDENT_FISHING_CHANCE = 6; // 6% chance per resident per hour to catch a fish
const int RESIDENT_SMOKE_FISH_CHANCE = 10; // 10% chance per hour to smoke a stored fish
// Goose settings
const int GOOSE_HEALTH = 1;
+90 -4
View File
@@ -5,6 +5,7 @@ void run_materials_menu() {
int selection = 0;
string[] options = {
"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]",
"Incense (6 Sticks, 2 Vines, 1 Reed) [Requires Altar]"
};
@@ -31,15 +32,17 @@ void run_materials_menu() {
if (key_pressed(KEY_RETURN)) {
if (selection == 0) butcher_small_game();
else if (selection == 1) craft_arrows();
else if (selection == 2) craft_incense();
else if (selection == 1) craft_smoke_fish();
else if (selection == 2) craft_arrows();
else if (selection == 3) craft_incense();
break;
}
if (key_pressed(KEY_TAB)) {
if (selection == 0) butcher_small_game_max();
else if (selection == 1) craft_arrows_max();
else if (selection == 2) craft_incense_max();
else if (selection == 1) craft_smoke_fish_max();
else if (selection == 2) craft_arrows_max();
else if (selection == 3) craft_incense_max();
break;
}
}
@@ -182,6 +185,89 @@ void craft_incense_max() {
speak_with_history("Crafted " + max_craft + " Incense.", true);
}
void craft_smoke_fish() {
WorldFire@ fire = get_fire_within_range(x, 3);
if (fire == null) {
speak_with_history("You need a fire within 3 tiles to smoke fish.", true);
return;
}
string missing = "";
if (get_personal_count(ITEM_FISH) < 1) missing += "1 fish ";
if (get_personal_count(ITEM_STICKS) < 1) missing += "1 stick ";
if (missing == "") {
int weight = (personal_fish_weights.length() > 0) ? personal_fish_weights[0] : get_default_fish_weight();
int yield = get_smoked_fish_yield(weight);
int space = get_personal_stack_limit() - get_personal_count(ITEM_SMOKED_FISH);
if (space < yield) {
speak_with_history("You can't carry any more smoked fish.", true);
return;
}
simulate_crafting(2);
pop_personal_fish_weight();
add_personal_count(ITEM_FISH, -1);
add_personal_count(ITEM_STICKS, -1);
add_personal_count(ITEM_SMOKED_FISH, yield);
speak_with_history("Smoked a fish into " + yield + " smoked fish.", true);
} else {
speak_with_history("Missing: " + missing, true);
}
}
void craft_smoke_fish_max() {
WorldFire@ fire = get_fire_within_range(x, 3);
if (fire == null) {
speak_with_history("You need a fire within 3 tiles to smoke fish.", true);
return;
}
int max_by_fish = get_personal_count(ITEM_FISH);
int max_by_sticks = get_personal_count(ITEM_STICKS);
int max_craft = max_by_fish;
if (max_by_sticks < max_craft) max_craft = max_by_sticks;
if (max_craft <= 0) {
string missing = "";
if (get_personal_count(ITEM_FISH) < 1) missing += "1 fish ";
if (get_personal_count(ITEM_STICKS) < 1) missing += "1 stick ";
speak_with_history("Missing: " + missing, true);
return;
}
int space = get_personal_stack_limit() - get_personal_count(ITEM_SMOKED_FISH);
if (space <= 0) {
speak_with_history("You can't carry any more smoked fish.", true);
return;
}
int fish_to_smoke = 0;
int total_yield = 0;
for (int i = 0; i < max_craft; i++) {
int weight = (i < int(personal_fish_weights.length())) ? personal_fish_weights[i] : get_default_fish_weight();
int yield = get_smoked_fish_yield(weight);
if (total_yield + yield > space) break;
total_yield += yield;
fish_to_smoke++;
}
if (fish_to_smoke <= 0) {
speak_with_history("You can't carry any more smoked fish.", true);
return;
}
int total_cost = fish_to_smoke * 2;
int craft_time = (total_cost < fish_to_smoke * 4) ? fish_to_smoke * 4 : total_cost;
simulate_crafting(craft_time);
for (int i = 0; i < fish_to_smoke; i++) {
pop_personal_fish_weight();
}
add_personal_count(ITEM_FISH, -fish_to_smoke);
add_personal_count(ITEM_STICKS, -fish_to_smoke);
add_personal_count(ITEM_SMOKED_FISH, total_yield);
speak_with_history("Smoked " + fish_to_smoke + " fish into " + total_yield + " smoked fish.", true);
}
void butcher_small_game() {
string missing = "";
+3
View File
@@ -15,6 +15,7 @@ string get_base_equipment_name(int equip_type) {
if (equip_type == EQUIP_MOCCASINS) return "Moccasins";
if (equip_type == EQUIP_POUCH) return "Skin Pouch";
if (equip_type == EQUIP_BACKPACK) return "Backpack";
if (equip_type == EQUIP_FISHING_POLE) return "Fishing Pole";
return "Unknown";
}
@@ -31,6 +32,7 @@ int get_unruned_equipment_count(int equip_type) {
if (equip_type == EQUIP_MOCCASINS) return get_personal_count(ITEM_MOCCASINS);
if (equip_type == EQUIP_POUCH) return get_personal_count(ITEM_SKIN_POUCHES);
if (equip_type == EQUIP_BACKPACK) return get_personal_count(ITEM_BACKPACKS);
if (equip_type == EQUIP_FISHING_POLE) return get_personal_count(ITEM_FISHING_POLES);
return 0;
}
@@ -47,6 +49,7 @@ void decrement_unruned_equipment(int equip_type) {
if (equip_type == EQUIP_MOCCASINS) { add_personal_count(ITEM_MOCCASINS, -1); return; }
if (equip_type == EQUIP_POUCH) { add_personal_count(ITEM_SKIN_POUCHES, -1); return; }
if (equip_type == EQUIP_BACKPACK) { add_personal_count(ITEM_BACKPACKS, -1); return; }
if (equip_type == EQUIP_FISHING_POLE) { add_personal_count(ITEM_FISHING_POLES, -1); return; }
}
void run_runes_menu() {
+3 -2
View File
@@ -161,8 +161,9 @@ void try_attack_barricade_bandit(Bandit@ bandit) {
if (can_residents_defend()) {
int counterDamage = perform_resident_defense();
if (counterDamage > 0) {
bandit.health -= counterDamage;
if (bandit.health <= 0 && x <= BASE_END) {
int before_health = bandit.health;
damage_bandit_at(bandit.position, counterDamage);
if (before_health - counterDamage <= 0 && x <= BASE_END) {
speak_with_history("Residents killed an attacking bandit.", true);
}
}
+3 -2
View File
@@ -80,8 +80,9 @@ void try_attack_barricade_undead(Undead@ undead) {
if (can_residents_defend()) {
int counterDamage = perform_resident_defense();
if (counterDamage > 0) {
undead.health -= counterDamage;
if (undead.health <= 0 && x <= BASE_END) {
int before_health = undead.health;
damage_undead_at(undead.position, counterDamage);
if (before_health - counterDamage <= 0 && x <= BASE_END) {
speak_with_history("Residents killed an attacking zombie.", true);
}
}
+372
View File
@@ -0,0 +1,372 @@
// Fishing system
const int FISHING_NEAR_STREAM_RANGE = 2;
const int FISHING_CAST_MOVE_MS = 300;
const int FISHING_CATCH_CHANCE_STEP = 15;
const int FISHING_CATCH_CHANCE_MAX = 85;
const int FISHING_BAD_CAST_BREAK_CHANCE = 40;
const int FISHING_EARLY_ESCAPE_CHANCE = 50;
const int FISHING_LATE_ESCAPE_CHANCE = 50;
const int FISHING_CAST_RANGE = 7;
const int FISHING_REEL_RETURN_RANGE = 7;
string[] fish_types = {"trout", "bass", "salmon", "catfish"};
bool is_ctrl_down() {
return key_down(KEY_LCTRL) || key_down(KEY_RCTRL);
}
bool is_ctrl_pressed() {
return key_pressed(KEY_LCTRL) || key_pressed(KEY_RCTRL);
}
void stop_fishing_sound() {
if (cast_sound_handle != -1) {
p.destroy_sound(cast_sound_handle);
cast_sound_handle = -1;
}
}
void reset_fishing_session() {
stop_fishing_sound();
is_casting = false;
line_in_water = false;
fish_on_line = false;
is_reeling = false;
cast_position = -1;
reel_position = -1;
cast_origin_x = -1;
cast_direction = 0;
reel_direction = 0;
reel_start_direction = 0;
target_stream_start = -1;
target_stream_end = -1;
catch_chance = 0;
hooked_fish_type = "";
}
bool get_nearby_stream(int pos, int range, int &out stream_start, int &out stream_end) {
int best_distance = range + 1;
for (uint i = 0; i < world_streams.length(); i++) {
int distance = 0;
if (pos < world_streams[i].start_position) {
distance = world_streams[i].start_position - pos;
} else if (pos > world_streams[i].end_position) {
distance = pos - world_streams[i].end_position;
}
if (distance <= range && distance < best_distance) {
best_distance = distance;
stream_start = world_streams[i].start_position;
stream_end = world_streams[i].end_position;
}
}
return best_distance <= range;
}
int get_cast_target_position(int origin_x, int stream_start, int stream_end) {
if (origin_x < stream_start) return stream_start;
if (origin_x > stream_end) return stream_end;
return (facing == 1) ? stream_end : stream_start;
}
int get_random_stream_tile() {
if (target_stream_start < 0 || target_stream_end < 0) return x;
if (target_stream_end < target_stream_start) return target_stream_start;
return random(target_stream_start, target_stream_end);
}
string get_random_fish_type() {
bool is_night = (current_hour >= 18 || current_hour < 7);
if (is_night) {
int roll = random(0, 99);
if (roll < 40) return "catfish";
else if (roll < 60) return "trout";
else if (roll < 80) return "bass";
else return "salmon";
}
return fish_types[random(0, 3)];
}
string get_fish_size_label(int weight) {
int clamped = clamp_fish_weight(weight);
if (clamped <= 7) return "small";
if (clamped <= 17) return "medium sized";
if (clamped <= 27) return "large";
return "monster";
}
void break_fishing_pole(string message) {
if (get_personal_count(ITEM_FISHING_POLES) > 0) {
add_personal_count(ITEM_FISHING_POLES, -1);
}
fishing_pole_equipped = false;
p.play_stationary("sounds/actions/fishpole_break.ogg", false);
reset_fishing_session();
if (message == "") {
message = "Your fishing pole broke.";
}
speak_with_history(message, true);
}
void lose_fish(string message) {
reset_fishing_session();
if (message != "") {
speak_with_history(message, true);
}
}
void start_casting() {
if (is_casting || line_in_water || fish_on_line || is_reeling) return;
if (!fishing_pole_equipped) {
speak_with_history("You need a fishing pole equipped.", true);
return;
}
int stream_start = -1;
int stream_end = -1;
if (!get_nearby_stream(x, FISHING_NEAR_STREAM_RANGE, stream_start, stream_end)) {
speak_with_history("You need to be within 2 tiles of a stream.", true);
return;
}
target_stream_start = stream_start;
target_stream_end = stream_end;
cast_origin_x = x;
cast_position = x;
cast_direction = 0;
cast_move_timer.restart();
p.play_stationary("sounds/actions/fishpole.ogg", false);
stop_fishing_sound();
is_casting = true;
}
void update_casting() {
if (!is_casting) return;
if (cast_move_timer.elapsed < FISHING_CAST_MOVE_MS) return;
cast_move_timer.restart();
if (cast_direction == 0) cast_direction = (facing == 1) ? 1 : -1;
cast_position += cast_direction;
int offset = cast_position - cast_origin_x;
if (offset > FISHING_CAST_RANGE) {
cast_position = cast_origin_x + FISHING_CAST_RANGE;
cast_direction = -1;
} else if (offset < -FISHING_CAST_RANGE) {
cast_position = cast_origin_x - FISHING_CAST_RANGE;
cast_direction = 1;
}
if (cast_position < 0) {
cast_position = 0;
cast_direction = 1;
} else if (cast_position > MAP_SIZE - 1) {
cast_position = MAP_SIZE - 1;
cast_direction = -1;
}
play_1d_with_volume_step("sounds/actions/cast_strength.ogg", x, cast_position, false, PLAYER_WEAPON_SOUND_VOLUME_STEP);
}
void release_cast() {
if (!is_casting) return;
is_casting = false;
stop_fishing_sound();
if (is_position_in_water(cast_position)) {
line_in_water = true;
catch_chance = 0;
fishing_timer.restart();
p.play_stationary("sounds/actions/start_fishing.ogg", false);
} else {
p.play_stationary("sounds/actions/bad_cast.ogg", false);
if (random(1, 100) <= FISHING_BAD_CAST_BREAK_CHANCE) {
break_fishing_pole("Your fishing pole broke.");
return;
}
}
cast_position = -1;
cast_origin_x = -1;
cast_direction = 0;
}
void update_waiting_for_fish() {
if (!line_in_water) return;
int minutes_waited = fishing_timer.elapsed / 60000;
int new_chance = minutes_waited * FISHING_CATCH_CHANCE_STEP;
if (new_chance > FISHING_CATCH_CHANCE_MAX) new_chance = FISHING_CATCH_CHANCE_MAX;
catch_chance = new_chance;
if (catch_chance > 0 && random(1, 100) <= catch_chance) {
line_in_water = false;
fish_on_line = true;
reel_position = get_random_stream_tile();
reel_direction = 0;
hooked_fish_type = get_random_fish_type();
speak_with_history("A fish is on the line!", true);
}
}
void start_reeling() {
if (!fish_on_line || is_reeling) return;
is_reeling = true;
reel_start_direction = (reel_position < x) ? 1 : -1;
reel_direction = reel_start_direction;
cast_move_timer.restart();
stop_fishing_sound();
play_1d_with_volume_step("sounds/actions/cast_strength.ogg", x, reel_position, false, PLAYER_WEAPON_SOUND_VOLUME_STEP);
}
void update_reeling() {
if (!is_reeling) return;
if (cast_move_timer.elapsed < FISHING_CAST_MOVE_MS) return;
cast_move_timer.restart();
reel_position += reel_direction;
int offset = reel_position - x;
if (offset > FISHING_REEL_RETURN_RANGE) {
reel_position = x + FISHING_REEL_RETURN_RANGE;
reel_direction = -1;
} else if (offset < -FISHING_REEL_RETURN_RANGE) {
reel_position = x - FISHING_REEL_RETURN_RANGE;
reel_direction = 1;
}
if (reel_position < 0) reel_position = 0;
if (reel_position > MAP_SIZE - 1) reel_position = MAP_SIZE - 1;
play_1d_with_volume_step("sounds/actions/cast_strength.ogg", x, reel_position, false, PLAYER_WEAPON_SOUND_VOLUME_STEP);
}
void catch_fish() {
if (get_personal_count(ITEM_FISH) >= get_personal_stack_limit()) {
speak_with_history("You can't carry any more fish.", true);
reset_fishing_session();
return;
}
int weight = random(FISH_WEIGHT_MIN, FISH_WEIGHT_MAX);
string size_label = get_fish_size_label(weight);
add_personal_count(ITEM_FISH, 1);
add_personal_fish_weight(weight);
p.play_stationary("sounds/items/miscellaneous.ogg", false);
string fish_name = hooked_fish_type;
if (fish_name == "") fish_name = "fish";
speak_with_history("Caught a " + size_label + " " + fish_name + ".", true);
reset_fishing_session();
}
void release_reel() {
if (!is_reeling) return;
stop_fishing_sound();
is_reeling = false;
if (!fish_on_line) return;
if (reel_position == x) {
catch_fish();
return;
}
if (is_position_in_water(reel_position)) {
if (random(1, 100) <= FISHING_EARLY_ESCAPE_CHANCE) {
lose_fish("The fish slipped off in the water.");
} else {
speak_with_history("The fish is still on the line.", true);
}
return;
}
if (reel_start_direction == 0) {
reel_start_direction = (reel_position < x) ? 1 : -1;
}
bool before_player = (reel_start_direction == 1 && reel_position < x) ||
(reel_start_direction == -1 && reel_position > x);
bool past_player = (reel_start_direction == 1 && reel_position > x) ||
(reel_start_direction == -1 && reel_position < x);
if (past_player) {
p.play_stationary("sounds/actions/bad_cast.ogg", false);
if (random(1, 100) <= FISHING_LATE_ESCAPE_CHANCE) {
lose_fish("The fish got away.");
} else {
catch_fish();
}
return;
}
if (before_player) {
if (random(1, 100) <= FISHING_EARLY_ESCAPE_CHANCE) {
lose_fish("The fish got away.");
} else {
speak_with_history("The fish is still on the line.", true);
}
return;
}
}
bool handle_fishing_breaks() {
if (!line_in_water && !fish_on_line && !is_reeling) return false;
if (!fishing_pole_equipped) {
break_fishing_pole("You switched weapons and your fishing pole broke.");
return true;
}
if (key_down(KEY_LEFT) || key_down(KEY_RIGHT) || key_pressed(KEY_UP) || jumping) {
if (fish_on_line || is_reeling) {
break_fishing_pole("You moved and the fish got away. Your fishing pole broke.");
} else {
break_fishing_pole("You moved and your fishing pole broke.");
}
return true;
}
return false;
}
void update_fishing() {
bool ctrl_down = is_ctrl_down();
bool ctrl_pressed = is_ctrl_pressed();
if (handle_fishing_breaks()) return;
if (fishing_pole_equipped && ctrl_pressed && !is_casting && !line_in_water && !fish_on_line && !is_reeling) {
start_casting();
}
if (is_casting) {
update_casting();
if (!ctrl_down) {
release_cast();
}
}
if (line_in_water) {
update_waiting_for_fish();
}
if (fish_on_line && ctrl_pressed && !is_reeling) {
start_reeling();
}
if (is_reeling) {
if (ctrl_down) {
update_reeling();
} else {
release_reel();
}
}
}
+19 -1
View File
@@ -14,6 +14,7 @@ const int EQUIP_MOCCASINS = 7;
const int EQUIP_POUCH = 8;
const int EQUIP_BOW = 9;
const int EQUIP_BACKPACK = 10;
const int EQUIP_FISHING_POLE = 11;
// Health bonuses from equipment
const int HAT_MAX_HEALTH_BONUS = 1;
@@ -27,6 +28,7 @@ bool spear_equipped = false;
bool axe_equipped = false;
bool sling_equipped = false;
bool bow_equipped = false;
bool fishing_pole_equipped = false;
int equipped_head = EQUIP_NONE;
int equipped_torso = EQUIP_NONE;
@@ -88,6 +90,7 @@ string get_equipment_name(int equip_type) {
if (equip_type == EQUIP_MOCCASINS) return "Moccasins";
if (equip_type == EQUIP_POUCH) return "Skin Pouch";
if (equip_type == EQUIP_BACKPACK) return "Backpack";
if (equip_type == EQUIP_FISHING_POLE) return "Fishing Pole";
return "Unknown";
}
@@ -104,15 +107,17 @@ bool equipment_available(int equip_type) {
if (equip_type == EQUIP_MOCCASINS) return get_personal_count(ITEM_MOCCASINS) > 0 || has_any_runed_version(equip_type);
if (equip_type == EQUIP_POUCH) return get_personal_count(ITEM_SKIN_POUCHES) > 0 || has_any_runed_version(equip_type);
if (equip_type == EQUIP_BACKPACK) return get_personal_count(ITEM_BACKPACKS) > 0 || has_any_runed_version(equip_type);
if (equip_type == EQUIP_FISHING_POLE) return get_personal_count(ITEM_FISHING_POLES) > 0;
return false;
}
void equip_equipment_type(int equip_type) {
if (equip_type == EQUIP_SPEAR || equip_type == EQUIP_AXE || equip_type == EQUIP_SLING || equip_type == EQUIP_BOW) {
if (equip_type == EQUIP_SPEAR || equip_type == EQUIP_AXE || equip_type == EQUIP_SLING || equip_type == EQUIP_BOW || equip_type == EQUIP_FISHING_POLE) {
spear_equipped = (equip_type == EQUIP_SPEAR);
axe_equipped = (equip_type == EQUIP_AXE);
sling_equipped = (equip_type == EQUIP_SLING);
bow_equipped = (equip_type == EQUIP_BOW);
fishing_pole_equipped = (equip_type == EQUIP_FISHING_POLE);
return;
}
@@ -130,6 +135,7 @@ bool equipment_is_equipped(int equip_type) {
if (equip_type == EQUIP_AXE) return axe_equipped;
if (equip_type == EQUIP_SLING) return sling_equipped;
if (equip_type == EQUIP_BOW) return bow_equipped;
if (equip_type == EQUIP_FISHING_POLE) return fishing_pole_equipped;
if (equip_type == EQUIP_HAT) return equipped_head == EQUIP_HAT;
if (equip_type == EQUIP_TUNIC) return equipped_torso == EQUIP_TUNIC;
if (equip_type == EQUIP_GLOVES) return equipped_hands == EQUIP_GLOVES;
@@ -149,6 +155,8 @@ void unequip_equipment_type(int equip_type) {
sling_equipped = false;
} else if (equip_type == EQUIP_BOW) {
bow_equipped = false;
} else if (equip_type == EQUIP_FISHING_POLE) {
fishing_pole_equipped = false;
} else if (equip_type == EQUIP_HAT && equipped_head == EQUIP_HAT) {
equipped_head = EQUIP_NONE;
} else if (equip_type == EQUIP_TUNIC && equipped_torso == EQUIP_TUNIC) {
@@ -219,6 +227,14 @@ void activate_quick_slot(int slot_index) {
return;
}
if (equipment_is_equipped(equip_type)) {
unequip_equipment_type(equip_type);
clear_equipped_rune_for_slot(equip_type);
update_max_health_from_equipment();
speak_with_history(get_equipment_name(equip_type) + " unequipped.", true);
return;
}
if (!equipment_available(equip_type)) {
speak_with_history("Item not available.", true);
return;
@@ -254,6 +270,7 @@ string get_equipped_weapon_name() {
if (axe_equipped) return "Stone Axe";
if (sling_equipped) return "Sling";
if (bow_equipped) return "Bow";
if (fishing_pole_equipped) return "Fishing Pole";
return "None";
}
@@ -280,6 +297,7 @@ void cleanup_equipment_after_inventory_change() {
if (get_personal_count(ITEM_AXES) <= 0) axe_equipped = false;
if (get_personal_count(ITEM_SLINGS) <= 0) sling_equipped = false;
if (get_personal_count(ITEM_BOWS) <= 0) bow_equipped = false;
if (get_personal_count(ITEM_FISHING_POLES) <= 0) fishing_pole_equipped = false;
if (get_personal_count(ITEM_SKIN_HATS) <= 0) equipped_head = EQUIP_NONE;
if (get_personal_count(ITEM_SKIN_GLOVES) <= 0) equipped_hands = EQUIP_NONE;
if (get_personal_count(ITEM_SKIN_PANTS) <= 0) equipped_legs = EQUIP_NONE;
+64 -1
View File
@@ -37,7 +37,9 @@ const int ITEM_SINEW = 31;
const int ITEM_BOAR_CARCASSES = 32;
const int ITEM_BACKPACKS = 33;
const int ITEM_CANOES = 34;
const int ITEM_COUNT = 35; // Total number of item types
const int ITEM_FISH = 35;
const int ITEM_SMOKED_FISH = 36;
const int ITEM_COUNT = 37; // Total number of item types
// Item definition class
class ItemDefinition {
@@ -77,6 +79,8 @@ int[] storage_inventory;
// Special tracking for small game types (what kind of animals)
string[] personal_small_game_types;
string[] storage_small_game_types;
int[] personal_fish_weights;
int[] storage_fish_weights;
void init_item_registry() {
// Initialize registry array
@@ -118,6 +122,8 @@ void init_item_registry() {
item_registry[ITEM_BOAR_CARCASSES] = ItemDefinition(ITEM_BOAR_CARCASSES, "boar carcasses", "boar carcass", "Boar Carcasses", 1.50);
item_registry[ITEM_BACKPACKS] = ItemDefinition(ITEM_BACKPACKS, "backpacks", "backpack", "Backpacks", 2.50);
item_registry[ITEM_CANOES] = ItemDefinition(ITEM_CANOES, "canoes", "canoe", "Canoes", 4.00);
item_registry[ITEM_FISH] = ItemDefinition(ITEM_FISH, "fish", "fish", "Fish", 0.10);
item_registry[ITEM_SMOKED_FISH] = ItemDefinition(ITEM_SMOKED_FISH, "smoked fish", "smoked fish", "Smoked Fish", 0.20);
// Define display order for inventory menus
// This controls the order items appear in menus
@@ -137,6 +143,9 @@ void init_item_registry() {
ITEM_FEATHERS,
ITEM_DOWN,
ITEM_SINEW,
// Food items
ITEM_FISH,
ITEM_SMOKED_FISH,
// Misc items
ITEM_INCENSE,
// Weapons
@@ -181,6 +190,8 @@ void reset_inventory() {
}
personal_small_game_types.resize(0);
storage_small_game_types.resize(0);
personal_fish_weights.resize(0);
storage_fish_weights.resize(0);
}
// Accessor functions for personal inventory
@@ -217,6 +228,58 @@ void add_storage_count(int item_type, int amount) {
if (storage_inventory[item_type] < 0) storage_inventory[item_type] = 0;
}
int get_default_fish_weight() {
return (FISH_WEIGHT_MIN + FISH_WEIGHT_MAX) / 2;
}
int clamp_fish_weight(int weight) {
if (weight < FISH_WEIGHT_MIN) return FISH_WEIGHT_MIN;
if (weight > FISH_WEIGHT_MAX) return FISH_WEIGHT_MAX;
return weight;
}
void add_personal_fish_weight(int weight) {
personal_fish_weights.insert_last(clamp_fish_weight(weight));
}
void add_storage_fish_weight(int weight) {
storage_fish_weights.insert_last(clamp_fish_weight(weight));
}
int pop_personal_fish_weight() {
if (personal_fish_weights.length() == 0) return get_default_fish_weight();
int weight = personal_fish_weights[0];
personal_fish_weights.remove_at(0);
return clamp_fish_weight(weight);
}
int pop_storage_fish_weight() {
if (storage_fish_weights.length() == 0) return get_default_fish_weight();
int weight = storage_fish_weights[0];
storage_fish_weights.remove_at(0);
return clamp_fish_weight(weight);
}
void move_fish_weights_to_storage(int amount) {
for (int i = 0; i < amount; i++) {
storage_fish_weights.insert_last(pop_personal_fish_weight());
}
}
void move_fish_weights_to_personal(int amount) {
for (int i = 0; i < amount; i++) {
personal_fish_weights.insert_last(pop_storage_fish_weight());
}
}
int get_smoked_fish_yield(int weight) {
int clamped = clamp_fish_weight(weight);
if (clamped <= 7) return 1;
if (clamped <= 17) return 2;
if (clamped <= 27) return 3;
return 4;
}
// Item metadata lookups
string get_item_label(int item_type) {
if (item_type < 0 || item_type >= ITEM_COUNT) return "items";
+1 -1
View File
@@ -16,7 +16,7 @@ void run_base_info_menu() {
if (world_storages.length() > 0) {
options.insert_last("Storage built. Total items " + get_storage_total_items());
int daily_food = get_daily_food_requirement();
int daily_food = residents_count * 3; // 1 per resident per 8 hours = 3 per day
options.insert_last("Food in storage " + get_storage_count(ITEM_MEAT) + " meat. Daily use " + daily_food);
} else {
options.insert_last("Storage not built");
+7
View File
@@ -6,6 +6,7 @@ bool has_any_equipment() {
// Check unruned items
if (get_personal_count(ITEM_SPEARS) > 0 || get_personal_count(ITEM_AXES) > 0 ||
get_personal_count(ITEM_SLINGS) > 0 || get_personal_count(ITEM_BOWS) > 0 ||
get_personal_count(ITEM_FISHING_POLES) > 0 ||
get_personal_count(ITEM_SKIN_HATS) > 0 || get_personal_count(ITEM_SKIN_GLOVES) > 0 ||
get_personal_count(ITEM_SKIN_PANTS) > 0 || get_personal_count(ITEM_SKIN_TUNICS) > 0 ||
get_personal_count(ITEM_MOCCASINS) > 0 || get_personal_count(ITEM_SKIN_POUCHES) > 0 ||
@@ -82,6 +83,12 @@ void run_equipment_menu() {
equipment_types.insert_last(EQUIP_BOW);
rune_types.insert_last(RUNE_NONE);
}
if (get_personal_count(ITEM_FISHING_POLES) > 0) {
string status = is_runed_item_equipped(EQUIP_FISHING_POLE, RUNE_NONE) ? " (equipped)" : "";
options.insert_last("Fishing Pole" + status);
equipment_types.insert_last(EQUIP_FISHING_POLE);
rune_types.insert_last(RUNE_NONE);
}
if (get_personal_count(ITEM_SKIN_HATS) > 0) {
string status = is_runed_item_equipped(EQUIP_HAT, RUNE_NONE) ? " (equipped)" : "";
options.insert_last("Skin Hat" + status);
+20
View File
@@ -23,6 +23,14 @@ void move_small_game_to_personal(int amount) {
}
}
void move_fish_to_storage(int amount) {
move_fish_weights_to_storage(amount);
}
void move_fish_to_personal(int amount) {
move_fish_weights_to_personal(amount);
}
int prompt_transfer_amount(const string prompt, int max_amount) {
string input = ui_input_box("Inventory", prompt + " (max " + max_amount + ")", "");
int amount = parse_int(input);
@@ -91,6 +99,9 @@ void deposit_item(int item_type) {
if (item_type == ITEM_SMALL_GAME) {
move_small_game_to_storage(amount);
}
if (item_type == ITEM_FISH) {
move_fish_to_storage(amount);
}
cleanup_equipment_after_inventory_change();
speak_with_history("Deposited " + amount + " " + get_item_label(item_type) + ".", true);
@@ -143,6 +154,9 @@ void deposit_item_max(int item_type) {
if (item_type == ITEM_SMALL_GAME) {
move_small_game_to_storage(amount);
}
if (item_type == ITEM_FISH) {
move_fish_to_storage(amount);
}
cleanup_equipment_after_inventory_change();
speak_with_history("Deposited " + amount + " " + get_item_label(item_type) + ".", true);
@@ -192,6 +206,9 @@ void withdraw_item(int item_type) {
if (item_type == ITEM_SMALL_GAME) {
move_small_game_to_personal(amount);
}
if (item_type == ITEM_FISH) {
move_fish_to_personal(amount);
}
speak_with_history("Withdrew " + amount + " " + get_item_label(item_type) + ".", true);
}
@@ -251,6 +268,9 @@ void withdraw_item_max(int item_type) {
if (item_type == ITEM_SMALL_GAME) {
move_small_game_to_personal(amount);
}
if (item_type == ITEM_FISH) {
move_fish_to_personal(amount);
}
speak_with_history("Withdrew " + amount + " " + get_item_label(item_type) + ".", true);
}
+19
View File
@@ -35,6 +35,25 @@ timer sling_charge_timer;
int sling_sound_handle = -1;
int last_sling_stage = -1; // Track which stage we're in to avoid duplicate sounds
// Fishing state
bool is_casting = false; // Holding control, cast_strength playing
bool line_in_water = false; // Cast successful, waiting for fish
bool fish_on_line = false; // Fish caught, reeling phase
bool is_reeling = false; // Holding control during reel
int cast_position = -1; // Current position of cast cursor
int reel_position = -1; // Position of fish during reel
int cast_origin_x = -1; // Player position when casting started
int cast_direction = 0; // 1 = moving toward stream, -1 = moving back
int reel_direction = 0; // 1 = moving toward player, -1 = moving past
int reel_start_direction = 0; // Initial reel direction toward player
int target_stream_start = -1; // Start of target stream for casting
int target_stream_end = -1; // End of target stream for casting
timer fishing_timer; // Tracks time line has been in water
timer cast_move_timer; // Controls discrete position movement (150ms per tile)
int catch_chance = 0; // Current catch percentage (starts 0, +15% per minute, max 85%)
int cast_sound_handle = -1; // Handle for cast_strength.ogg
string hooked_fish_type = ""; // Type of fish on the line
// Favor system
double favor = 0.0;
int incense_hours_remaining = 0;
+68 -3
View File
@@ -173,6 +173,24 @@ void reset_game_state() {
rope_climb_sound_handle = -1;
pending_rope_climb_x = -1;
pending_rope_climb_elevation = 0;
is_casting = false;
line_in_water = false;
fish_on_line = false;
is_reeling = false;
cast_position = -1;
reel_position = -1;
cast_origin_x = -1;
cast_direction = 0;
reel_direction = 0;
reel_start_direction = 0;
target_stream_start = -1;
target_stream_end = -1;
catch_chance = 0;
hooked_fish_type = "";
if (cast_sound_handle != -1) {
p.destroy_sound(cast_sound_handle);
cast_sound_handle = -1;
}
player_health = 10;
base_max_health = 10;
@@ -190,6 +208,7 @@ void reset_game_state() {
axe_equipped = false;
sling_equipped = false;
bow_equipped = false;
fishing_pole_equipped = false;
equipped_head = EQUIP_NONE;
equipped_torso = EQUIP_NONE;
equipped_arms = EQUIP_NONE;
@@ -257,7 +276,7 @@ string serialize_tree(Tree@ tree) {
}
string serialize_snare(WorldSnare@ snare) {
return snare.position + "|" + serialize_bool(snare.has_catch) + "|" + snare.catch_type + "|" + snare.catch_chance + "|" + snare.escape_chance + "|" + serialize_bool(snare.active);
return snare.position + "|" + serialize_bool(snare.has_catch) + "|" + snare.catch_type + "|" + snare.catch_chance + "|" + snare.escape_chance + "|" + serialize_bool(snare.active) + "|" + snare.hours_with_catch;
}
string serialize_fire(WorldFire@ fire) {
@@ -370,11 +389,22 @@ bool save_game_state() {
saveData.set("storage_inventory", serialize_inventory_array(storage_inventory));
saveData.set("personal_small_game_types", join_string_array(personal_small_game_types));
saveData.set("storage_small_game_types", join_string_array(storage_small_game_types));
string[] personalFishWeightsData;
for (uint i = 0; i < personal_fish_weights.length(); i++) {
personalFishWeightsData.insert_last("" + personal_fish_weights[i]);
}
saveData.set("personal_fish_weights", join_string_array(personalFishWeightsData));
string[] storageFishWeightsData;
for (uint i = 0; i < storage_fish_weights.length(); i++) {
storageFishWeightsData.insert_last("" + storage_fish_weights[i]);
}
saveData.set("storage_fish_weights", join_string_array(storageFishWeightsData));
saveData.set("equipment_spear_equipped", spear_equipped);
saveData.set("equipment_axe_equipped", axe_equipped);
saveData.set("equipment_sling_equipped", sling_equipped);
saveData.set("equipment_bow_equipped", bow_equipped);
saveData.set("equipment_fishing_pole_equipped", fishing_pole_equipped);
saveData.set("equipment_head", equipped_head);
saveData.set("equipment_torso", equipped_torso);
saveData.set("equipment_arms", equipped_arms);
@@ -649,10 +679,41 @@ bool load_game_state() {
set_storage_count(ITEM_SMALL_GAME, int(storage_small_game_types.length()));
}
string[] loadedPersonalFishWeights = get_string_list_or_split(saveData, "personal_fish_weights");
personal_fish_weights.resize(0);
for (uint i = 0; i < loadedPersonalFishWeights.length(); i++) {
personal_fish_weights.insert_last(parse_int(loadedPersonalFishWeights[i]));
}
int personal_fish_count = get_personal_count(ITEM_FISH);
if (personal_fish_weights.length() < personal_fish_count) {
int missing = personal_fish_count - int(personal_fish_weights.length());
for (int i = 0; i < missing; i++) {
personal_fish_weights.insert_last(get_default_fish_weight());
}
} else if (personal_fish_weights.length() > personal_fish_count) {
personal_fish_weights.resize(personal_fish_count);
}
string[] loadedStorageFishWeights = get_string_list_or_split(saveData, "storage_fish_weights");
storage_fish_weights.resize(0);
for (uint i = 0; i < loadedStorageFishWeights.length(); i++) {
storage_fish_weights.insert_last(parse_int(loadedStorageFishWeights[i]));
}
int storage_fish_count = get_storage_count(ITEM_FISH);
if (storage_fish_weights.length() < storage_fish_count) {
int missing = storage_fish_count - int(storage_fish_weights.length());
for (int i = 0; i < missing; i++) {
storage_fish_weights.insert_last(get_default_fish_weight());
}
} else if (storage_fish_weights.length() > storage_fish_count) {
storage_fish_weights.resize(storage_fish_count);
}
spear_equipped = get_bool(saveData, "equipment_spear_equipped", false);
axe_equipped = get_bool(saveData, "equipment_axe_equipped", false);
sling_equipped = get_bool(saveData, "equipment_sling_equipped", false);
bow_equipped = get_bool(saveData, "equipment_bow_equipped", false);
fishing_pole_equipped = get_bool(saveData, "equipment_fishing_pole_equipped", false);
equipped_head = int(get_number(saveData, "equipment_head", EQUIP_NONE));
equipped_torso = int(get_number(saveData, "equipment_torso", EQUIP_NONE));
equipped_arms = int(get_number(saveData, "equipment_arms", EQUIP_NONE));
@@ -666,7 +727,7 @@ bool load_game_state() {
if (slot_count > quick_slots.length()) slot_count = quick_slots.length();
for (uint i = 0; i < slot_count; i++) {
int slot_value = parse_int(loadedQuickSlots[i]);
if (slot_value >= EQUIP_NONE && slot_value <= EQUIP_BACKPACK) {
if (slot_value >= EQUIP_NONE && slot_value <= EQUIP_FISHING_POLE) {
quick_slots[i] = slot_value;
}
}
@@ -795,7 +856,11 @@ bool load_game_state() {
snare.catch_chance = parse_int(parts[3]);
snare.escape_chance = parse_int(parts[4]);
snare.active = (parse_int(parts[5]) == 1);
snare.minute_timer.restart();
// Load hours_with_catch if available (for backwards compatibility with old saves)
if (parts.length() >= 7) {
snare.hours_with_catch = parse_int(parts[6]);
}
snare.hour_timer.restart();
world_snares.insert_last(snare);
}
+26 -6
View File
@@ -91,17 +91,31 @@ void expand_regular_area() {
expanded_terrain_types.insert_last(terrain_type);
}
// Place exactly one feature: either a stream or a tree
bool place_stream = (terrain_type != "grass") || (random(0, 1) == 0);
// Place features based on terrain type
// Forests get trees, other terrain gets streams or trees
bool place_tree = false;
if (terrain_type == "deep_forest") {
// Deep forest: 80% tree, 20% stream
place_tree = (random(1, 100) <= 80);
} else if (terrain_type == "forest") {
// Forest: 60% tree, 40% stream
place_tree = (random(1, 100) <= 60);
} else if (terrain_type == "grass") {
// Grass: 50% tree, 50% stream
place_tree = (random(0, 1) == 0);
} else {
// Stone/snow: no trees, always stream
place_tree = false;
}
if (place_stream) {
if (place_tree) {
// Try to place a tree with proper spacing and per-area limits
spawn_tree_in_area(new_start, new_end);
} else {
int stream_width = random(1, 5);
int stream_start = random(0, EXPANSION_SIZE - stream_width);
int actual_start = new_start + stream_start;
add_world_stream(actual_start, stream_width);
} else {
// Try to place a tree with proper spacing and per-area limits
spawn_tree_in_area(new_start, new_end);
}
area_expanded_today = true;
@@ -360,6 +374,10 @@ void update_time() {
invasion_roll_done_today = false;
invasion_scheduled_hour = -1;
quest_roll_done_today = false;
}
// Residents consume food every 8 hours (at midnight, 8am, 4pm)
if (current_hour % 8 == 0) {
consume_food_for_residents();
}
@@ -405,6 +423,8 @@ void update_time() {
check_weather_transition();
attempt_resident_collection();
attempt_resident_snare_retrieval();
attempt_resident_fishing();
attempt_resident_fish_smoking();
if (current_hour == 6) {
save_game_state();
}
+26 -16
View File
@@ -16,26 +16,28 @@ class WorldSnare {
int catch_chance;
int escape_chance;
int sound_handle;
timer minute_timer;
timer hour_timer;
bool active; // To prevent immediate breakage on placement
int hours_with_catch; // Track how long animal has been caught (game hours)
WorldSnare(int pos) {
position = pos;
has_catch = false;
catch_type = "";
catch_chance = 5;
escape_chance = 5;
escape_chance = 0;
active = false; // Becomes active when player steps off
sound_handle = -1;
hours_with_catch = 0;
minute_timer.restart();
hour_timer.restart();
}
void update() {
// Activate if player moves away
if (!active && x != position) {
active = true;
minute_timer.restart();
hour_timer.restart();
}
int snare_distance = abs(x - position);
@@ -57,20 +59,27 @@ class WorldSnare {
sound_handle = -1;
}
// Every minute logic (only when active)
if (active && minute_timer.elapsed >= 60000) {
minute_timer.restart();
// Every game hour logic (only when active)
// MS_PER_HOUR = 60000 (1 real minute = 1 game hour)
if (active && hour_timer.elapsed >= 60000) {
hour_timer.restart();
if (has_catch) {
// Animal trying to escape
if (escape_chance < 95) escape_chance += 5;
hours_with_catch++;
int roll = random(1, 100);
if (roll <= escape_chance) {
// Animal escaped!
notify("A " + catch_type + " escaped from your snare at x " + position + " y 0!");
remove_snare_at(position);
return;
// First game hour: no escape chance
// After that: escape chance increases by 2% per game hour
if (hours_with_catch > 1) {
escape_chance = (hours_with_catch - 1) * 2;
if (escape_chance > 95) escape_chance = 95;
int roll = random(1, 100);
if (roll <= escape_chance) {
// Animal escaped!
notify("A " + catch_type + " escaped from your snare at x " + position + " y 0!");
remove_snare_at(position);
return;
}
}
} else {
// Trying to catch small game
@@ -81,7 +90,8 @@ class WorldSnare {
// Caught something!
has_catch = true;
catch_type = get_random_small_game();
escape_chance = 5; // Reset escape chance
escape_chance = 0;
hours_with_catch = 0;
notify(catch_type + " caught in snare at x " + position + " y 0!");
}
}