Initial implementation of fishing system. A couple bugs fixed, e.g. zombies that are killed by residents actually die.
This commit is contained in:
@@ -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
@@ -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);
|
||||
|
||||
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
+88
-7
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = "";
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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";
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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!");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user