Initial attempt at boars added. Needs work, lots of work.

This commit is contained in:
Storm Dragon
2026-01-21 07:53:03 -05:00
parent 5c25ba9a19
commit bcc0c7db01
9 changed files with 399 additions and 60 deletions

BIN
sounds/game/boar.ogg LFS Normal file

Binary file not shown.

View File

@@ -91,7 +91,6 @@ int perform_resident_defense() {
// Proactive resident sling defense // Proactive resident sling defense
timer resident_sling_timer; timer resident_sling_timer;
const int RESIDENT_SLING_COOLDOWN = 4000; // 4 seconds between shots
void attempt_resident_sling_defense() { void attempt_resident_sling_defense() {
// Only if residents exist and have slings with stones // Only if residents exist and have slings with stones
@@ -213,7 +212,6 @@ void process_daily_weapon_breakage() {
} }
// Resident resource collection // Resident resource collection
const int RESIDENT_COLLECTION_CHANCE = 10; // 10% chance per basket per hour
void attempt_resident_collection() { void attempt_resident_collection() {
// Only during daytime // Only during daytime

View File

@@ -21,6 +21,10 @@ int attack_enemy_ranged(int start_x, int end_x, int damage) {
if (damage_zombie_at(check_x, damage)) { if (damage_zombie_at(check_x, damage)) {
return check_x; return check_x;
} }
// Then check boars
if (damage_boar_at(check_x, damage)) {
return check_x;
}
} }
return -1; return -1;
} }
@@ -30,6 +34,10 @@ bool attack_enemy(int target_x, int damage) {
if (damage_bandit_at(target_x, damage)) { if (damage_bandit_at(target_x, damage)) {
return true; return true;
} }
// Check boars
if (damage_boar_at(target_x, damage)) {
return true;
}
// Then check zombies // Then check zombies
return damage_zombie_at(target_x, damage); return damage_zombie_at(target_x, damage);
} }
@@ -43,6 +51,8 @@ void perform_spear_attack(int current_x) {
// Play hit sound based on enemy type (both use same hit sound for now) // Play hit sound based on enemy type (both use same hit sound for now)
if (get_bandit_at(hit_pos) != null) { if (get_bandit_at(hit_pos) != null) {
play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, hit_pos, BANDIT_SOUND_VOLUME_STEP); play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, hit_pos, BANDIT_SOUND_VOLUME_STEP);
} else if (get_boar_at(hit_pos) != null) {
play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, hit_pos, BOAR_SOUND_VOLUME_STEP);
} else { } else {
play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, hit_pos, ZOMBIE_SOUND_VOLUME_STEP); play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, hit_pos, ZOMBIE_SOUND_VOLUME_STEP);
} }
@@ -61,6 +71,8 @@ void perform_axe_attack(int current_x) {
// Play hit sound based on enemy type // Play hit sound based on enemy type
if (get_bandit_at(current_x) != null) { if (get_bandit_at(current_x) != null) {
play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, current_x, BANDIT_SOUND_VOLUME_STEP); play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, current_x, BANDIT_SOUND_VOLUME_STEP);
} else if (get_boar_at(current_x) != null) {
play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, current_x, BOAR_SOUND_VOLUME_STEP);
} else { } else {
play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, current_x, ZOMBIE_SOUND_VOLUME_STEP); play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, current_x, ZOMBIE_SOUND_VOLUME_STEP);
} }
@@ -131,6 +143,7 @@ void release_sling_attack(int player_x) {
int target_x = -1; int target_x = -1;
bool hit_bandit = false; bool hit_bandit = false;
bool hit_flying_creature = false; bool hit_flying_creature = false;
bool hit_boar = false;
// Priority: Find nearest enemy (bandit or zombie) first // Priority: Find nearest enemy (bandit or zombie) first
for (int dist = 1; dist <= SLING_RANGE; dist++) { for (int dist = 1; dist <= SLING_RANGE; dist++) {
@@ -144,6 +157,14 @@ void release_sling_attack(int player_x) {
hit_bandit = true; hit_bandit = true;
break; break;
} }
// Then check for boar
GameBoar@ boar = get_boar_at(check_x);
if (boar != null) {
target_x = check_x;
hit_boar = true;
break;
}
// Then check for zombie // Then check for zombie
Zombie@ zombie = get_zombie_at(check_x); Zombie@ zombie = get_zombie_at(check_x);
@@ -190,6 +211,10 @@ void release_sling_attack(int player_x) {
damage_bandit_at(target_x, damage); damage_bandit_at(target_x, damage);
play_1d_with_volume_step("sounds/weapons/sling_hit.ogg", player_x, target_x, false, PLAYER_WEAPON_SOUND_VOLUME_STEP); play_1d_with_volume_step("sounds/weapons/sling_hit.ogg", player_x, target_x, false, PLAYER_WEAPON_SOUND_VOLUME_STEP);
play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", player_x, target_x, BANDIT_SOUND_VOLUME_STEP); play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", player_x, target_x, BANDIT_SOUND_VOLUME_STEP);
} else if (hit_boar) {
damage_boar_at(target_x, damage);
play_1d_with_volume_step("sounds/weapons/sling_hit.ogg", player_x, target_x, false, PLAYER_WEAPON_SOUND_VOLUME_STEP);
play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", player_x, target_x, BOAR_SOUND_VOLUME_STEP);
} else if (hit_flying_creature) { } else if (hit_flying_creature) {
damage_flying_creature_at(target_x, damage); damage_flying_creature_at(target_x, damage);
play_1d_with_volume_step("sounds/weapons/sling_hit.ogg", player_x, target_x, false, PLAYER_WEAPON_SOUND_VOLUME_STEP); play_1d_with_volume_step("sounds/weapons/sling_hit.ogg", player_x, target_x, false, PLAYER_WEAPON_SOUND_VOLUME_STEP);

View File

@@ -32,20 +32,12 @@ const int SLING_DAMAGE_MIN = 5;
const int SLING_DAMAGE_MAX = 8; const int SLING_DAMAGE_MAX = 8;
const int SLING_RANGE = 8; const int SLING_RANGE = 8;
// Bow settings (for future implementation) // Bow settings
// Option 1: Longer range, similar damage const int BOW_DAMAGE_MIN = 6;
// const int BOW_DAMAGE_MIN = 6; const int BOW_DAMAGE_MAX = 9;
// const int BOW_DAMAGE_MAX = 9; const int BOW_RANGE = 12;
// const int BOW_RANGE = 12; // 50% more range than sling const int ARROWS_PER_CRAFT = 12;
// const int ARROW_CAPACITY_PER_QUIVER = 12;
// Option 2: Much longer range, slightly more damage
// const int BOW_DAMAGE_MIN = 7;
// const int BOW_DAMAGE_MAX = 10;
// const int BOW_RANGE = 15; // Nearly double sling range
//
// Recommendation: Bows should have BOTH more range AND more damage than slings
// to justify the likely higher resource cost and complexity to craft.
// Suggested balance: BOW_RANGE = 12, damage 6-9 (average 7.5 vs sling's 6.5)
// Zombie settings // Zombie settings
const int ZOMBIE_HEALTH = 12; const int ZOMBIE_HEALTH = 12;
@@ -60,6 +52,21 @@ const int ZOMBIE_FOOTSTEP_MAX_DISTANCE = 5;
const float ZOMBIE_SOUND_VOLUME_STEP = 3.0; const float ZOMBIE_SOUND_VOLUME_STEP = 3.0;
const int ZOMBIE_ATTACK_MAX_HEIGHT = 6; const int ZOMBIE_ATTACK_MAX_HEIGHT = 6;
// Boar settings
const int BOAR_HEALTH = 4;
const int BOAR_MAX_COUNT = 3;
const int BOAR_MOVE_INTERVAL_MIN = 800;
const int BOAR_MOVE_INTERVAL_MAX = 1500;
const int BOAR_ATTACK_INTERVAL = 1500;
const int BOAR_DAMAGE_MIN = 1;
const int BOAR_DAMAGE_MAX = 3;
const int BOAR_SOUND_MIN_DELAY = 3000;
const int BOAR_SOUND_MAX_DELAY = 6000;
const int BOAR_FOOTSTEP_MAX_DISTANCE = 5;
const float BOAR_SOUND_VOLUME_STEP = 3.0;
const int BOAR_SIGHT_RANGE = 4;
const int BOAR_CHARGE_SPEED = 500; // ms per tile when charging
// Barricade configuration // Barricade configuration
const int BARRICADE_BASE_HEALTH = 100; const int BARRICADE_BASE_HEALTH = 100;
const int BARRICADE_MAX_HEALTH = 500; const int BARRICADE_MAX_HEALTH = 500;
@@ -188,10 +195,10 @@ const int WIND_GUST_MIN_DELAY = 30000; // Min 30 seconds between gusts
const int WIND_GUST_MAX_DELAY = 60000; // Max 60 seconds between gusts const int WIND_GUST_MAX_DELAY = 60000; // Max 60 seconds between gusts
const int THUNDER_MIN_INTERVAL = 8000; // Min 8 seconds between thunder const int THUNDER_MIN_INTERVAL = 8000; // Min 8 seconds between thunder
const int THUNDER_MAX_INTERVAL = 35000; // Max 35 seconds between thunder const int THUNDER_MAX_INTERVAL = 35000; // Max 35 seconds between thunder
const int THUNDER_MOVEMENT_SPEED = 2000; // ms per tile movement (slow roll across sky) const int THUNDER_MOVEMENT_SPEED = 250; // ms per tile movement (faster roll across sky)
const float THUNDER_SOUND_VOLUME_STEP = 2.0; // Gentler volume falloff const float THUNDER_SOUND_VOLUME_STEP = 0.5; // Gentler volume falloff
const int THUNDER_SPAWN_DISTANCE_MIN = 20; // Min distance from player const int THUNDER_SPAWN_DISTANCE_MIN = 0; // Min distance from player
const int THUNDER_SPAWN_DISTANCE_MAX = 40; // Max distance from player const int THUNDER_SPAWN_DISTANCE_MAX = 20; // Max distance from player
const int CHANCE_CLEAR_TO_WINDY = 15; const int CHANCE_CLEAR_TO_WINDY = 15;
const int CHANCE_CLEAR_TO_RAINY = 6; const int CHANCE_CLEAR_TO_RAINY = 6;
const int CHANCE_CLEAR_TO_STORMY = 5; const int CHANCE_CLEAR_TO_STORMY = 5;
@@ -202,3 +209,27 @@ const int CHANCE_RAINY_STAY = 40;
const int CHANCE_RAINY_TO_STORMY = 35; const int CHANCE_RAINY_TO_STORMY = 35;
const int CHANCE_STORMY_STAY = 40; const int CHANCE_STORMY_STAY = 40;
const int CHANCE_STORMY_TO_RAINY = 35; const int CHANCE_STORMY_TO_RAINY = 35;
// Weather States
const int WEATHER_CLEAR = 0;
const int WEATHER_WINDY = 1;
const int WEATHER_RAINY = 2;
const int WEATHER_STORMY = 3;
// Weather Intensity levels (0 = none, 1-3 = low/medium/high)
const int INTENSITY_NONE = 0;
const int INTENSITY_LOW = 1;
const int INTENSITY_MEDIUM = 2;
const int INTENSITY_HIGH = 3;
const string RAIN_SOUND = "sounds/nature/rain.ogg";
// Environment / Fall Damage
const int SAFE_FALL_HEIGHT = 10;
const int FALL_DAMAGE_MIN = 0;
const int FALL_DAMAGE_MAX = 4;
// Base Automation
const int RESIDENT_SLING_COOLDOWN = 4000; // 4 seconds between shots
const int RESIDENT_COLLECTION_CHANCE = 10; // 10% chance per basket per hour

View File

@@ -949,8 +949,8 @@ void butcher_small_game() {
// Check for knife // Check for knife
if (inv_knives < 1) missing += "Stone Knife "; if (inv_knives < 1) missing += "Stone Knife ";
// Check for small game // Check for small game or boar
if (inv_small_game < 1) missing += "Small Game "; if (inv_small_game < 1 && inv_boar_carcasses < 1) missing += "Game ";
// Check for fire within 3 tiles (can hear it) // Check for fire within 3 tiles (can hear it)
WorldFire@ fire = get_fire_within_range(x, 3); WorldFire@ fire = get_fire_within_range(x, 3);
@@ -970,15 +970,26 @@ void butcher_small_game() {
} }
simulate_crafting(1); simulate_crafting(1);
string game_type = inv_small_game_types[0]; string game_type = "";
inv_small_game_types.remove_at(0); if (inv_boar_carcasses > 0) {
inv_small_game--; game_type = "boar carcass";
inv_boar_carcasses--;
} else {
game_type = inv_small_game_types[0];
inv_small_game_types.remove_at(0);
inv_small_game--;
}
if (game_type == "goose") { if (game_type == "goose") {
inv_meat++; inv_meat++;
inv_feathers += random(3, 6); inv_feathers += random(3, 6);
inv_down += random(1, 3); inv_down += random(1, 3);
screen_reader_speak("Butchered goose. Got 1 meat, feathers, and down.", true); screen_reader_speak("Butchered goose. Got 1 meat, feathers, and down.", true);
} else if (game_type == "boar carcass") {
inv_meat += random(2, 3);
inv_skins += 3;
inv_sinew += 2;
screen_reader_speak("Butchered boar. Got meat, 3 skins, and 2 sinew.", true);
} else { } else {
inv_meat++; inv_meat++;
inv_skins++; inv_skins++;

View File

@@ -2,9 +2,6 @@
// Safe fall height is 10 feet or less // Safe fall height is 10 feet or less
// Each foot above 10 has a chance to deal 0-4 damage // Each foot above 10 has a chance to deal 0-4 damage
// This means falling from great heights is VERY dangerous but not guaranteed fatal // This means falling from great heights is VERY dangerous but not guaranteed fatal
const int SAFE_FALL_HEIGHT = 10;
const int FALL_DAMAGE_MIN = 0;
const int FALL_DAMAGE_MAX = 4;
void apply_falling_damage(int fall_height) { void apply_falling_damage(int fall_height) {
// Always play the hit ground sound // Always play the hit ground sound

View File

@@ -13,6 +13,12 @@ int inv_skins = 0;
int inv_feathers = 0; int inv_feathers = 0;
int inv_down = 0; int inv_down = 0;
int inv_incense = 0; int inv_incense = 0;
int inv_bows = 0;
int inv_arrows = 0;
int inv_quivers = 0;
int inv_bowstrings = 0;
int inv_sinew = 0;
int inv_boar_carcasses = 0;
int inv_spears = 0; int inv_spears = 0;
int inv_snares = 0; int inv_snares = 0;
@@ -43,6 +49,12 @@ int storage_skins = 0;
int storage_feathers = 0; int storage_feathers = 0;
int storage_down = 0; int storage_down = 0;
int storage_incense = 0; int storage_incense = 0;
int storage_bows = 0;
int storage_arrows = 0;
int storage_quivers = 0;
int storage_bowstrings = 0;
int storage_sinew = 0;
int storage_boar_carcasses = 0;
int storage_spears = 0; int storage_spears = 0;
int storage_snares = 0; int storage_snares = 0;
@@ -63,11 +75,13 @@ int storage_skin_pouches = 0;
bool spear_equipped = false; bool spear_equipped = false;
bool axe_equipped = false; bool axe_equipped = false;
bool sling_equipped = false; bool sling_equipped = false;
bool bow_equipped = false;
int[] quick_slots; int[] quick_slots;
const int EQUIP_NONE = -1; const int EQUIP_NONE = -1;
const int EQUIP_SPEAR = 0; const int EQUIP_SPEAR = 0;
const int EQUIP_AXE = 1; const int EQUIP_AXE = 1;
const int EQUIP_SLING = 2; const int EQUIP_SLING = 2;
const int EQUIP_BOW = 9; // Next available ID
const int EQUIP_HAT = 3; const int EQUIP_HAT = 3;
const int EQUIP_GLOVES = 4; const int EQUIP_GLOVES = 4;
const int EQUIP_PANTS = 5; const int EQUIP_PANTS = 5;
@@ -101,6 +115,12 @@ const int ITEM_CLAY_POTS = 23;
const int ITEM_FEATHERS = 24; const int ITEM_FEATHERS = 24;
const int ITEM_DOWN = 25; const int ITEM_DOWN = 25;
const int ITEM_INCENSE = 26; const int ITEM_INCENSE = 26;
const int ITEM_BOWS = 27;
const int ITEM_ARROWS = 28;
const int ITEM_QUIVERS = 29;
const int ITEM_BOWSTRINGS = 30;
const int ITEM_SINEW = 31;
const int ITEM_BOAR_CARCASSES = 32;
const int HAT_MAX_HEALTH_BONUS = 1; const int HAT_MAX_HEALTH_BONUS = 1;
const int GLOVES_MAX_HEALTH_BONUS = 1; const int GLOVES_MAX_HEALTH_BONUS = 1;
const int PANTS_MAX_HEALTH_BONUS = 3; const int PANTS_MAX_HEALTH_BONUS = 3;
@@ -128,10 +148,18 @@ int get_personal_stack_limit() {
return limit; return limit;
} }
int get_arrow_limit() {
// Quiver required to hold arrows
// Each quiver holds 12 arrows
if (inv_quivers == 0) return 0;
return inv_quivers * ARROW_CAPACITY_PER_QUIVER;
}
string get_equipment_name(int equip_type) { string get_equipment_name(int equip_type) {
if (equip_type == EQUIP_SPEAR) return "Spear"; if (equip_type == EQUIP_SPEAR) return "Spear";
if (equip_type == EQUIP_AXE) return "Stone Axe"; if (equip_type == EQUIP_AXE) return "Stone Axe";
if (equip_type == EQUIP_SLING) return "Sling"; if (equip_type == EQUIP_SLING) return "Sling";
if (equip_type == EQUIP_BOW) return "Bow";
if (equip_type == EQUIP_HAT) return "Skin Hat"; if (equip_type == EQUIP_HAT) return "Skin Hat";
if (equip_type == EQUIP_GLOVES) return "Skin Gloves"; if (equip_type == EQUIP_GLOVES) return "Skin Gloves";
if (equip_type == EQUIP_PANTS) return "Skin Pants"; if (equip_type == EQUIP_PANTS) return "Skin Pants";
@@ -145,6 +173,7 @@ bool equipment_available(int equip_type) {
if (equip_type == EQUIP_SPEAR) return inv_spears > 0; if (equip_type == EQUIP_SPEAR) return inv_spears > 0;
if (equip_type == EQUIP_AXE) return inv_axes > 0; if (equip_type == EQUIP_AXE) return inv_axes > 0;
if (equip_type == EQUIP_SLING) return inv_slings > 0; if (equip_type == EQUIP_SLING) return inv_slings > 0;
if (equip_type == EQUIP_BOW) return inv_bows > 0;
if (equip_type == EQUIP_HAT) return inv_skin_hats > 0; if (equip_type == EQUIP_HAT) return inv_skin_hats > 0;
if (equip_type == EQUIP_GLOVES) return inv_skin_gloves > 0; if (equip_type == EQUIP_GLOVES) return inv_skin_gloves > 0;
if (equip_type == EQUIP_PANTS) return inv_skin_pants > 0; if (equip_type == EQUIP_PANTS) return inv_skin_pants > 0;
@@ -155,10 +184,11 @@ bool equipment_available(int equip_type) {
} }
void equip_equipment_type(int equip_type) { void equip_equipment_type(int equip_type) {
if (equip_type == EQUIP_SPEAR || equip_type == EQUIP_AXE || equip_type == EQUIP_SLING) { if (equip_type == EQUIP_SPEAR || equip_type == EQUIP_AXE || equip_type == EQUIP_SLING || equip_type == EQUIP_BOW) {
spear_equipped = (equip_type == EQUIP_SPEAR); spear_equipped = (equip_type == EQUIP_SPEAR);
axe_equipped = (equip_type == EQUIP_AXE); axe_equipped = (equip_type == EQUIP_AXE);
sling_equipped = (equip_type == EQUIP_SLING); sling_equipped = (equip_type == EQUIP_SLING);
bow_equipped = (equip_type == EQUIP_BOW);
return; return;
} }
@@ -174,6 +204,7 @@ bool equipment_is_equipped(int equip_type) {
if (equip_type == EQUIP_SPEAR) return spear_equipped; if (equip_type == EQUIP_SPEAR) return spear_equipped;
if (equip_type == EQUIP_AXE) return axe_equipped; if (equip_type == EQUIP_AXE) return axe_equipped;
if (equip_type == EQUIP_SLING) return sling_equipped; if (equip_type == EQUIP_SLING) return sling_equipped;
if (equip_type == EQUIP_BOW) return bow_equipped;
if (equip_type == EQUIP_HAT) return equipped_head == EQUIP_HAT; if (equip_type == EQUIP_HAT) return equipped_head == EQUIP_HAT;
if (equip_type == EQUIP_TUNIC) return equipped_torso == EQUIP_TUNIC; if (equip_type == EQUIP_TUNIC) return equipped_torso == EQUIP_TUNIC;
if (equip_type == EQUIP_GLOVES) return equipped_hands == EQUIP_GLOVES; if (equip_type == EQUIP_GLOVES) return equipped_hands == EQUIP_GLOVES;
@@ -190,6 +221,8 @@ void unequip_equipment_type(int equip_type) {
axe_equipped = false; axe_equipped = false;
} else if (equip_type == EQUIP_SLING) { } else if (equip_type == EQUIP_SLING) {
sling_equipped = false; sling_equipped = false;
} else if (equip_type == EQUIP_BOW) {
bow_equipped = false;
} else if (equip_type == EQUIP_HAT && equipped_head == EQUIP_HAT) { } else if (equip_type == EQUIP_HAT && equipped_head == EQUIP_HAT) {
equipped_head = EQUIP_NONE; equipped_head = EQUIP_NONE;
} else if (equip_type == EQUIP_TUNIC && equipped_torso == EQUIP_TUNIC) { } else if (equip_type == EQUIP_TUNIC && equipped_torso == EQUIP_TUNIC) {
@@ -286,6 +319,12 @@ int get_personal_count(int item_type) {
if (item_type == ITEM_FEATHERS) return inv_feathers; if (item_type == ITEM_FEATHERS) return inv_feathers;
if (item_type == ITEM_DOWN) return inv_down; if (item_type == ITEM_DOWN) return inv_down;
if (item_type == ITEM_INCENSE) return inv_incense; if (item_type == ITEM_INCENSE) return inv_incense;
if (item_type == ITEM_BOWS) return inv_bows;
if (item_type == ITEM_ARROWS) return inv_arrows;
if (item_type == ITEM_QUIVERS) return inv_quivers;
if (item_type == ITEM_BOWSTRINGS) return inv_bowstrings;
if (item_type == ITEM_SINEW) return inv_sinew;
if (item_type == ITEM_BOAR_CARCASSES) return inv_boar_carcasses;
if (item_type == ITEM_SPEARS) return inv_spears; if (item_type == ITEM_SPEARS) return inv_spears;
if (item_type == ITEM_SLINGS) return inv_slings; if (item_type == ITEM_SLINGS) return inv_slings;
if (item_type == ITEM_AXES) return inv_axes; if (item_type == ITEM_AXES) return inv_axes;
@@ -317,6 +356,12 @@ int get_storage_count(int item_type) {
if (item_type == ITEM_FEATHERS) return storage_feathers; if (item_type == ITEM_FEATHERS) return storage_feathers;
if (item_type == ITEM_DOWN) return storage_down; if (item_type == ITEM_DOWN) return storage_down;
if (item_type == ITEM_INCENSE) return storage_incense; if (item_type == ITEM_INCENSE) return storage_incense;
if (item_type == ITEM_BOWS) return storage_bows;
if (item_type == ITEM_ARROWS) return storage_arrows;
if (item_type == ITEM_QUIVERS) return storage_quivers;
if (item_type == ITEM_BOWSTRINGS) return storage_bowstrings;
if (item_type == ITEM_SINEW) return storage_sinew;
if (item_type == ITEM_BOAR_CARCASSES) return storage_boar_carcasses;
if (item_type == ITEM_SPEARS) return storage_spears; if (item_type == ITEM_SPEARS) return storage_spears;
if (item_type == ITEM_SLINGS) return storage_slings; if (item_type == ITEM_SLINGS) return storage_slings;
if (item_type == ITEM_AXES) return storage_axes; if (item_type == ITEM_AXES) return storage_axes;
@@ -348,6 +393,12 @@ string get_item_label(int item_type) {
if (item_type == ITEM_FEATHERS) return "feathers"; if (item_type == ITEM_FEATHERS) return "feathers";
if (item_type == ITEM_DOWN) return "down"; if (item_type == ITEM_DOWN) return "down";
if (item_type == ITEM_INCENSE) return "incense"; if (item_type == ITEM_INCENSE) return "incense";
if (item_type == ITEM_BOWS) return "bows";
if (item_type == ITEM_ARROWS) return "arrows";
if (item_type == ITEM_QUIVERS) return "quivers";
if (item_type == ITEM_BOWSTRINGS) return "bowstrings";
if (item_type == ITEM_SINEW) return "sinew";
if (item_type == ITEM_BOAR_CARCASSES) return "boar carcasses";
if (item_type == ITEM_SPEARS) return "spears"; if (item_type == ITEM_SPEARS) return "spears";
if (item_type == ITEM_SLINGS) return "slings"; if (item_type == ITEM_SLINGS) return "slings";
if (item_type == ITEM_AXES) return "axes"; if (item_type == ITEM_AXES) return "axes";
@@ -368,11 +419,7 @@ string get_item_label(int item_type) {
string format_favor(double value) { string format_favor(double value) {
if (value < 0) value = 0; if (value < 0) value = 0;
int scaled = int(value * 100 + 0.5); return "" + int(value);
int whole = scaled / 100;
int frac = scaled % 100;
string frac_text = (frac < 10) ? "0" + frac : "" + frac;
return "" + whole + "." + frac_text;
} }
string get_item_label_singular(int item_type) { string get_item_label_singular(int item_type) {
@@ -388,6 +435,12 @@ string get_item_label_singular(int item_type) {
if (item_type == ITEM_FEATHERS) return "feather"; if (item_type == ITEM_FEATHERS) return "feather";
if (item_type == ITEM_DOWN) return "down"; if (item_type == ITEM_DOWN) return "down";
if (item_type == ITEM_INCENSE) return "incense stick"; if (item_type == ITEM_INCENSE) return "incense stick";
if (item_type == ITEM_BOWS) return "bow";
if (item_type == ITEM_ARROWS) return "arrow";
if (item_type == ITEM_QUIVERS) return "quiver";
if (item_type == ITEM_BOWSTRINGS) return "bowstring";
if (item_type == ITEM_SINEW) return "piece of sinew";
if (item_type == ITEM_BOAR_CARCASSES) return "boar carcass";
if (item_type == ITEM_SPEARS) return "spear"; if (item_type == ITEM_SPEARS) return "spear";
if (item_type == ITEM_SLINGS) return "sling"; if (item_type == ITEM_SLINGS) return "sling";
if (item_type == ITEM_AXES) return "axe"; if (item_type == ITEM_AXES) return "axe";
@@ -419,6 +472,12 @@ double get_item_favor_value(int item_type) {
if (item_type == ITEM_FEATHERS) return 0.05; if (item_type == ITEM_FEATHERS) return 0.05;
if (item_type == ITEM_DOWN) return 0.05; if (item_type == ITEM_DOWN) return 0.05;
if (item_type == ITEM_INCENSE) return 0.10; if (item_type == ITEM_INCENSE) return 0.10;
if (item_type == ITEM_BOWS) return 2.50;
if (item_type == ITEM_ARROWS) return 0.05;
if (item_type == ITEM_QUIVERS) return 1.50;
if (item_type == ITEM_BOWSTRINGS) return 0.20;
if (item_type == ITEM_SINEW) return 0.10;
if (item_type == ITEM_BOAR_CARCASSES) return 1.50;
if (item_type == ITEM_SPEARS) return 1.00; if (item_type == ITEM_SPEARS) return 1.00;
if (item_type == ITEM_SLINGS) return 2.00; if (item_type == ITEM_SLINGS) return 2.00;
if (item_type == ITEM_AXES) return 1.50; if (item_type == ITEM_AXES) return 1.50;
@@ -441,6 +500,7 @@ string get_equipped_weapon_name() {
if (spear_equipped) return "Spear"; if (spear_equipped) return "Spear";
if (axe_equipped) return "Stone Axe"; if (axe_equipped) return "Stone Axe";
if (sling_equipped) return "Sling"; if (sling_equipped) return "Sling";
if (bow_equipped) return "Bow";
return "None"; return "None";
} }
@@ -454,6 +514,7 @@ void cleanup_equipment_after_inventory_change() {
if (inv_spears <= 0) spear_equipped = false; if (inv_spears <= 0) spear_equipped = false;
if (inv_axes <= 0) axe_equipped = false; if (inv_axes <= 0) axe_equipped = false;
if (inv_slings <= 0) sling_equipped = false; if (inv_slings <= 0) sling_equipped = false;
if (inv_bows <= 0) bow_equipped = false;
if (inv_skin_hats <= 0) equipped_head = EQUIP_NONE; if (inv_skin_hats <= 0) equipped_head = EQUIP_NONE;
if (inv_skin_gloves <= 0) equipped_hands = EQUIP_NONE; if (inv_skin_gloves <= 0) equipped_hands = EQUIP_NONE;
if (inv_skin_pants <= 0) equipped_legs = EQUIP_NONE; if (inv_skin_pants <= 0) equipped_legs = EQUIP_NONE;

View File

@@ -2,18 +2,6 @@
// Provides ambient wind, rain, and thunder effects // Provides ambient wind, rain, and thunder effects
// Tunable constants are in src/constants.nvgt // Tunable constants are in src/constants.nvgt
// Weather states
const int WEATHER_CLEAR = 0;
const int WEATHER_WINDY = 1;
const int WEATHER_RAINY = 2;
const int WEATHER_STORMY = 3;
// Intensity levels (0 = none, 1-3 = low/medium/high)
const int INTENSITY_NONE = 0;
const int INTENSITY_LOW = 1;
const int INTENSITY_MEDIUM = 2;
const int INTENSITY_HIGH = 3;
// State variables // State variables
int weather_state = WEATHER_CLEAR; int weather_state = WEATHER_CLEAR;
int wind_intensity = INTENSITY_NONE; int wind_intensity = INTENSITY_NONE;
@@ -37,26 +25,30 @@ timer rain_fade_timer;
// Thunder object state // Thunder object state
class ThunderStrike { class ThunderStrike {
int position; int position;
int direction; // -1 = moving west, 1 = moving east int direction; // -1 = moving west, 1 = moving east, 0 = stationary
int sound_handle; int sound_handle;
timer movement_timer; timer movement_timer;
int movement_speed;
ThunderStrike(int pos, int dir, int handle) { ThunderStrike(int pos, int dir, int handle, int speed) {
position = pos; position = pos;
direction = dir; direction = dir;
sound_handle = handle; sound_handle = handle;
movement_speed = speed;
movement_timer.restart(); movement_timer.restart();
} }
void update() { void update() {
if (movement_timer.elapsed >= THUNDER_MOVEMENT_SPEED) { if (direction == 0) return; // Stationary thunder
if (movement_timer.elapsed >= movement_speed) {
position += direction; position += direction;
movement_timer.restart(); movement_timer.restart();
}
// Update sound position
// Update sound position if (sound_handle != -1 && p.sound_is_active(sound_handle)) {
if (sound_handle != -1 && p.sound_is_active(sound_handle)) { p.update_sound_1d(sound_handle, position);
p.update_sound_1d(sound_handle, position); }
} }
} }
@@ -83,8 +75,6 @@ string[] thunder_sounds = {
"sounds/nature/thunder_high.ogg" "sounds/nature/thunder_high.ogg"
}; };
const string RAIN_SOUND = "sounds/nature/rain.ogg";
void init_weather() { void init_weather() {
weather_state = WEATHER_CLEAR; weather_state = WEATHER_CLEAR;
wind_intensity = INTENSITY_NONE; wind_intensity = INTENSITY_NONE;
@@ -385,15 +375,23 @@ void spawn_thunder() {
// Spawn thunder at random distance from player // Spawn thunder at random distance from player
int distance = random(THUNDER_SPAWN_DISTANCE_MIN, THUNDER_SPAWN_DISTANCE_MAX); int distance = random(THUNDER_SPAWN_DISTANCE_MIN, THUNDER_SPAWN_DISTANCE_MAX);
// Randomly place to left or right of player
int direction = random(0, 1) == 0 ? -1 : 1; // Determine movement: 20% stationary, 80% moving
int thunder_pos = x + (distance * direction); int direction = 0;
if (random(1, 100) > 20) {
direction = random(0, 1) == 0 ? -1 : 1;
}
// Random speed: 50ms (fast rip) to 600ms (slow roll)
int speed = random(50, 600);
int thunder_pos = x + (distance * ((direction == 0) ? (random(0, 1) == 0 ? -1 : 1) : direction));
// Play sound at position with custom volume step // Play sound at position with custom volume step
int handle = play_1d_with_volume_step(thunder_file, x, thunder_pos, false, THUNDER_SOUND_VOLUME_STEP); int handle = play_1d_with_volume_step(thunder_file, x, thunder_pos, false, THUNDER_SOUND_VOLUME_STEP);
if (handle != -1) { if (handle != -1) {
ThunderStrike@ strike = ThunderStrike(thunder_pos, direction, handle); ThunderStrike@ strike = ThunderStrike(thunder_pos, direction, handle, speed);
active_thunder.insert_last(strike); active_thunder.insert_last(strike);
} }
} }

View File

@@ -10,6 +10,7 @@ int residents_count = 0;
string[] zombie_sounds = {"sounds/enemies/zombie1.ogg"}; string[] zombie_sounds = {"sounds/enemies/zombie1.ogg"};
string[] bandit_sounds = {"sounds/enemies/bandit1.ogg", "sounds/enemies/bandit2.ogg"}; string[] bandit_sounds = {"sounds/enemies/bandit1.ogg", "sounds/enemies/bandit2.ogg"};
string[] goose_sounds = {"sounds/game/goose.ogg"}; string[] goose_sounds = {"sounds/game/goose.ogg"};
string[] boar_sounds = {"sounds/game/boar.ogg"};
class Zombie { class Zombie {
int position; int position;
@@ -83,6 +84,42 @@ class Bandit {
} }
Bandit@[] bandits; Bandit@[] bandits;
class GameBoar {
int position;
int health;
int sound_handle;
timer move_timer;
timer sound_timer;
timer attack_timer;
int next_move_delay;
int next_sound_delay;
string voice_sound;
string state; // "wandering" or "charging"
int area_start;
int area_end;
int wander_direction; // -1, 0, 1
GameBoar(int pos, int start, int end) {
position = pos;
area_start = start;
area_end = end;
health = BOAR_HEALTH;
sound_handle = -1;
state = "wandering";
wander_direction = 0;
voice_sound = boar_sounds[random(0, boar_sounds.length() - 1)];
move_timer.restart();
sound_timer.restart();
attack_timer.restart();
next_move_delay = random(BOAR_MOVE_INTERVAL_MIN, BOAR_MOVE_INTERVAL_MAX);
next_sound_delay = random(BOAR_SOUND_MIN_DELAY, BOAR_SOUND_MAX_DELAY);
}
}
GameBoar@[] boars;
class FlyingCreatureConfig { class FlyingCreatureConfig {
string id; string id;
string drop_type; string drop_type;
@@ -247,6 +284,15 @@ bool try_pickup_world_drop(WorldDrop@ drop) {
if (get_flying_creature_config_by_drop_type(drop.type) !is null) { if (get_flying_creature_config_by_drop_type(drop.type) !is null) {
return try_pickup_small_game(drop.type); return try_pickup_small_game(drop.type);
} }
if (drop.type == "boar carcass") {
if (inv_boar_carcasses >= get_personal_stack_limit()) {
screen_reader_speak("You can't carry any more boar carcasses.", true);
return false;
}
inv_boar_carcasses++;
screen_reader_speak("Picked up boar carcass.", true);
return true;
}
screen_reader_speak("Picked up " + drop.type + ".", true); screen_reader_speak("Picked up " + drop.type + ".", true);
return true; return true;
} }
@@ -960,6 +1006,175 @@ Bandit@ get_bandit_at(int pos) {
return null; return null;
} }
// Boar Functions
void clear_boars() {
if (boars.length() == 0) return;
for (uint i = 0; i < boars.length(); i++) {
if (boars[i].sound_handle != -1) {
p.destroy_sound(boars[i].sound_handle);
boars[i].sound_handle = -1;
}
}
boars.resize(0);
}
GameBoar@ get_boar_at(int pos) {
for (uint i = 0; i < boars.length(); i++) {
if (boars[i].position == pos) {
return @boars[i];
}
}
return null;
}
void spawn_boar(int expansion_start, int expansion_end) {
int spawn_x = -1;
// Try to find a valid spawn position in grass/snow (expanded area)
for (int attempts = 0; attempts < 20; attempts++) {
int candidate = random(expansion_start, expansion_end);
// Don't spawn too close to base (keep away from BASE_END)
if (candidate <= BASE_END + 5) continue;
if (get_boar_at(candidate) == null) {
spawn_x = candidate;
break;
}
}
if (spawn_x == -1) return; // Failed to find spot
GameBoar@ b = GameBoar(spawn_x, expansion_start, expansion_end);
boars.insert_last(b);
b.sound_handle = play_creature_voice(b.voice_sound, x, spawn_x, BOAR_SOUND_VOLUME_STEP);
}
bool can_boar_attack_player(GameBoar@ boar) {
if (player_health <= 0) return false;
// Check if player is on ground (boars can't fly/climb)
if (y > 0) return false;
if (abs(boar.position - x) > 1) return false;
return true;
}
bool try_attack_player_boar(GameBoar@ boar) {
if (!can_boar_attack_player(boar)) return false;
if (boar.attack_timer.elapsed < BOAR_ATTACK_INTERVAL) return false;
boar.attack_timer.restart();
// Attack!
// TODO: Add specific boar attack sound? For now re-use zombie hit as generic impact
play_creature_attack_sound("sounds/enemies/zombie_hits_player.ogg", x, boar.position, BOAR_SOUND_VOLUME_STEP);
int damage = random(BOAR_DAMAGE_MIN, BOAR_DAMAGE_MAX);
player_health -= damage;
if (player_health < 0) player_health = 0;
return true;
}
void update_boar(GameBoar@ boar) {
// Sound logic
if (boar.sound_timer.elapsed > boar.next_sound_delay) {
boar.sound_timer.restart();
boar.next_sound_delay = random(BOAR_SOUND_MIN_DELAY, BOAR_SOUND_MAX_DELAY);
// Only play if wandering or occasionally while charging
boar.sound_handle = play_creature_voice(boar.voice_sound, x, boar.position, BOAR_SOUND_VOLUME_STEP);
}
// Combat logic
if (try_attack_player_boar(boar)) {
return;
}
// Movement logic
int move_speed = (boar.state == "charging") ? BOAR_CHARGE_SPEED : boar.next_move_delay;
if (boar.move_timer.elapsed < move_speed) return;
boar.move_timer.restart();
if (boar.state == "wandering") {
boar.next_move_delay = random(BOAR_MOVE_INTERVAL_MIN, BOAR_MOVE_INTERVAL_MAX);
}
// Detection Logic
int dist_to_player = x - boar.position;
int abs_dist = abs(dist_to_player);
// If player is close, on ground, and boar can see them -> Charge
if (y == 0 && abs_dist <= BOAR_SIGHT_RANGE && x > BASE_END) {
boar.state = "charging";
} else {
boar.state = "wandering";
}
if (boar.state == "charging") {
int dir = (dist_to_player > 0) ? 1 : -1;
int target = boar.position + dir;
// Don't leave area or enter base
if (target >= boar.area_start && target <= boar.area_end && target > BASE_END) {
boar.position = target;
play_creature_footstep(x, boar.position, BASE_END, GRASS_END, BOAR_FOOTSTEP_MAX_DISTANCE, BOAR_SOUND_VOLUME_STEP);
}
} else {
// Wandering
if (random(1, 100) <= 20) {
boar.wander_direction = random(-1, 1);
}
if (boar.wander_direction != 0) {
int target = boar.position + boar.wander_direction;
// Don't leave area or enter base
if (target >= boar.area_start && target <= boar.area_end && target > BASE_END) {
boar.position = target;
play_creature_footstep(x, boar.position, BASE_END, GRASS_END, BOAR_FOOTSTEP_MAX_DISTANCE, BOAR_SOUND_VOLUME_STEP);
} else {
boar.wander_direction = -boar.wander_direction; // Turn around
}
}
}
}
void update_boars() {
// Only spawn if map is expanded
if (expanded_area_start != -1) {
while (boars.length() < BOAR_MAX_COUNT) {
spawn_boar(expanded_area_start, expanded_area_end);
}
}
for (uint i = 0; i < boars.length(); i++) {
update_boar(boars[i]);
}
}
bool damage_boar_at(int pos, int damage) {
for (uint i = 0; i < boars.length(); i++) {
if (boars[i].position == pos) {
boars[i].health -= damage;
if (boars[i].health <= 0) {
if (boars[i].sound_handle != -1) {
p.destroy_sound(boars[i].sound_handle);
boars[i].sound_handle = -1;
}
play_creature_death_sound("sounds/game/game_falls.ogg", x, pos, BOAR_SOUND_VOLUME_STEP);
// Drop carcass
add_world_drop(pos, "boar carcass");
boars.remove_at(i);
}
return true;
}
}
return false;
}
void spawn_bandit(int expansion_start, int expansion_end) { void spawn_bandit(int expansion_start, int expansion_end) {
int spawn_x = -1; int spawn_x = -1;
for (int attempts = 0; attempts < 20; attempts++) { for (int attempts = 0; attempts < 20; attempts++) {