// 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(); } } }