Continue refactor splitting into smaller files.
This commit is contained in:
@@ -10,6 +10,8 @@ sound_pool p(100);
|
|||||||
#include "src/creature_death.nvgt"
|
#include "src/creature_death.nvgt"
|
||||||
#include "src/world_object_base.nvgt"
|
#include "src/world_object_base.nvgt"
|
||||||
#include "src/enemies/undead.nvgt"
|
#include "src/enemies/undead.nvgt"
|
||||||
|
#include "src/enemies/bandit.nvgt"
|
||||||
|
#include "src/enemies/ground_game.nvgt"
|
||||||
#include "src/world_state.nvgt"
|
#include "src/world_state.nvgt"
|
||||||
#include "src/ui.nvgt"
|
#include "src/ui.nvgt"
|
||||||
#include "src/inventory.nvgt"
|
#include "src/inventory.nvgt"
|
||||||
|
|||||||
+1
-1
@@ -159,7 +159,7 @@ void release_sling_attack(int player_x) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Then check for boar
|
// Then check for boar
|
||||||
GameBoar@ boar = get_boar_at(check_x);
|
GroundGame@ boar = get_boar_at(check_x);
|
||||||
if (boar != null) {
|
if (boar != null) {
|
||||||
target_x = check_x;
|
target_x = check_x;
|
||||||
hit_boar = true;
|
hit_boar = true;
|
||||||
|
|||||||
@@ -0,0 +1,297 @@
|
|||||||
|
// Bandit enemies
|
||||||
|
// Hostile humans that spawn during invasions and wander in expanded areas
|
||||||
|
|
||||||
|
string[] bandit_sounds = {"sounds/enemies/bandit1.ogg", "sounds/enemies/bandit2.ogg"};
|
||||||
|
|
||||||
|
class Bandit {
|
||||||
|
int position;
|
||||||
|
int health;
|
||||||
|
string alert_sound;
|
||||||
|
string weapon_type; // "spear" or "axe"
|
||||||
|
int sound_handle;
|
||||||
|
timer move_timer;
|
||||||
|
timer alert_timer;
|
||||||
|
timer attack_timer;
|
||||||
|
int next_alert_delay;
|
||||||
|
int move_interval;
|
||||||
|
|
||||||
|
// Wandering behavior properties
|
||||||
|
string behavior_state; // "aggressive" or "wandering"
|
||||||
|
int wander_direction; // -1, 0, or 1
|
||||||
|
timer wander_direction_timer;
|
||||||
|
int wander_direction_change_interval;
|
||||||
|
|
||||||
|
Bandit(int pos, int expansion_start, int expansion_end) {
|
||||||
|
// Spawn somewhere in the expanded area
|
||||||
|
position = random(expansion_start, expansion_end);
|
||||||
|
health = BANDIT_HEALTH;
|
||||||
|
sound_handle = -1;
|
||||||
|
|
||||||
|
// Choose random alert sound
|
||||||
|
int sound_index = random(0, bandit_sounds.length() - 1);
|
||||||
|
alert_sound = bandit_sounds[sound_index];
|
||||||
|
|
||||||
|
// Choose random weapon
|
||||||
|
weapon_type = (random(0, 1) == 0) ? "spear" : "axe";
|
||||||
|
|
||||||
|
// Random movement speed within range
|
||||||
|
move_interval = random(BANDIT_MOVE_INTERVAL_MIN, BANDIT_MOVE_INTERVAL_MAX);
|
||||||
|
|
||||||
|
move_timer.restart();
|
||||||
|
alert_timer.restart();
|
||||||
|
attack_timer.restart();
|
||||||
|
next_alert_delay = random(BANDIT_ALERT_MIN_DELAY, BANDIT_ALERT_MAX_DELAY);
|
||||||
|
|
||||||
|
// Initialize wandering behavior (start aggressive during invasion)
|
||||||
|
behavior_state = "aggressive";
|
||||||
|
wander_direction = 0;
|
||||||
|
wander_direction_change_interval = random(BANDIT_WANDER_DIRECTION_CHANGE_MIN, BANDIT_WANDER_DIRECTION_CHANGE_MAX);
|
||||||
|
wander_direction_timer.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Bandit@[] bandits;
|
||||||
|
|
||||||
|
void clear_bandits() {
|
||||||
|
if (bandits.length() == 0) return;
|
||||||
|
|
||||||
|
for (uint i = 0; i < bandits.length(); i++) {
|
||||||
|
if (bandits[i].sound_handle != -1) {
|
||||||
|
p.destroy_sound(bandits[i].sound_handle);
|
||||||
|
bandits[i].sound_handle = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bandits.resize(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bandit@ get_bandit_at(int pos) {
|
||||||
|
for (uint i = 0; i < bandits.length(); i++) {
|
||||||
|
if (bandits[i].position == pos) {
|
||||||
|
return @bandits[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void spawn_bandit(int expansion_start, int expansion_end) {
|
||||||
|
int spawn_x = -1;
|
||||||
|
for (int attempts = 0; attempts < 20; attempts++) {
|
||||||
|
int candidate = random(expansion_start, expansion_end);
|
||||||
|
if (get_bandit_at(candidate) == null) {
|
||||||
|
spawn_x = candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (spawn_x == -1) {
|
||||||
|
spawn_x = random(expansion_start, expansion_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bandit@ b = Bandit(spawn_x, expansion_start, expansion_end);
|
||||||
|
bandits.insert_last(b);
|
||||||
|
b.sound_handle = play_creature_voice(b.alert_sound, x, spawn_x, BANDIT_SOUND_VOLUME_STEP);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool can_bandit_attack_player(Bandit@ bandit) {
|
||||||
|
if (player_health <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cannot attack player if barricade is up and player is in base
|
||||||
|
if (barricade_health > 0 && x <= BASE_END) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (abs(bandit.position - x) > 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return y <= BANDIT_ATTACK_MAX_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool try_attack_player_bandit(Bandit@ bandit) {
|
||||||
|
if (!can_bandit_attack_player(bandit)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (bandit.attack_timer.elapsed < BANDIT_ATTACK_INTERVAL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bandit.attack_timer.restart();
|
||||||
|
|
||||||
|
// Play weapon swing sound based on bandit's weapon
|
||||||
|
if (bandit.weapon_type == "spear") {
|
||||||
|
play_creature_attack_sound("sounds/weapons/spear_swing.ogg", x, bandit.position, BANDIT_SOUND_VOLUME_STEP);
|
||||||
|
} else if (bandit.weapon_type == "axe") {
|
||||||
|
play_creature_attack_sound("sounds/weapons/axe_swing.ogg", x, bandit.position, BANDIT_SOUND_VOLUME_STEP);
|
||||||
|
}
|
||||||
|
|
||||||
|
int damage = random(BANDIT_DAMAGE_MIN, BANDIT_DAMAGE_MAX);
|
||||||
|
player_health -= damage;
|
||||||
|
if (player_health < 0) {
|
||||||
|
player_health = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Play hit sound
|
||||||
|
if (bandit.weapon_type == "spear") {
|
||||||
|
play_creature_attack_sound("sounds/weapons/spear_hit.ogg", x, bandit.position, BANDIT_SOUND_VOLUME_STEP);
|
||||||
|
} else if (bandit.weapon_type == "axe") {
|
||||||
|
play_creature_attack_sound("sounds/weapons/axe_hit.ogg", x, bandit.position, BANDIT_SOUND_VOLUME_STEP);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void try_attack_barricade_bandit(Bandit@ bandit) {
|
||||||
|
if (barricade_health <= 0) return;
|
||||||
|
if (bandit.attack_timer.elapsed < BANDIT_ATTACK_INTERVAL) return;
|
||||||
|
|
||||||
|
bandit.attack_timer.restart();
|
||||||
|
|
||||||
|
// Bandits do 1-2 damage to barricade
|
||||||
|
int damage = random(BANDIT_DAMAGE_MIN, BANDIT_DAMAGE_MAX);
|
||||||
|
barricade_health -= damage;
|
||||||
|
if (barricade_health < 0) barricade_health = 0;
|
||||||
|
|
||||||
|
// Play weapon swing sound
|
||||||
|
if (bandit.weapon_type == "spear") {
|
||||||
|
play_creature_attack_sound("sounds/weapons/spear_swing.ogg", x, bandit.position, BANDIT_SOUND_VOLUME_STEP);
|
||||||
|
play_creature_attack_sound("sounds/weapons/spear_hit.ogg", x, bandit.position, BANDIT_SOUND_VOLUME_STEP);
|
||||||
|
} else if (bandit.weapon_type == "axe") {
|
||||||
|
play_creature_attack_sound("sounds/weapons/axe_swing.ogg", x, bandit.position, BANDIT_SOUND_VOLUME_STEP);
|
||||||
|
play_creature_attack_sound("sounds/weapons/axe_hit.ogg", x, bandit.position, BANDIT_SOUND_VOLUME_STEP);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resident defense counter-attack
|
||||||
|
if (can_residents_defend()) {
|
||||||
|
int counterDamage = perform_resident_defense();
|
||||||
|
if (counterDamage > 0) {
|
||||||
|
bandit.health -= counterDamage;
|
||||||
|
if (bandit.health <= 0 && x <= BASE_END) {
|
||||||
|
speak_with_history("Residents killed an attacking bandit.", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (barricade_health == 0) {
|
||||||
|
notify("The barricade has fallen!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_bandit(Bandit@ bandit) {
|
||||||
|
// Play alert sound at intervals
|
||||||
|
if (bandit.alert_timer.elapsed > bandit.next_alert_delay) {
|
||||||
|
bandit.alert_timer.restart();
|
||||||
|
bandit.next_alert_delay = random(BANDIT_ALERT_MIN_DELAY, BANDIT_ALERT_MAX_DELAY);
|
||||||
|
bandit.sound_handle = play_creature_voice(bandit.alert_sound, x, bandit.position, BANDIT_SOUND_VOLUME_STEP);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (try_attack_player_bandit(bandit)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bandit.move_timer.elapsed < bandit.move_interval) return;
|
||||||
|
bandit.move_timer.restart();
|
||||||
|
|
||||||
|
// If barricade is up and bandit is at the edge of base, attack barricade
|
||||||
|
if (barricade_health > 0 && bandit.position == BASE_END + 1) {
|
||||||
|
try_attack_barricade_bandit(bandit);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// State-based behavior
|
||||||
|
if (bandit.behavior_state == "wandering") {
|
||||||
|
// Check if player is within detection radius
|
||||||
|
int distance = abs(bandit.position - x);
|
||||||
|
if (distance <= BANDIT_DETECTION_RADIUS) {
|
||||||
|
// Player detected! Switch to aggressive
|
||||||
|
bandit.behavior_state = "aggressive";
|
||||||
|
} else {
|
||||||
|
// Continue wandering
|
||||||
|
if (bandit.wander_direction_timer.elapsed > bandit.wander_direction_change_interval) {
|
||||||
|
// Time to change direction
|
||||||
|
bandit.wander_direction = random(-1, 1);
|
||||||
|
bandit.wander_direction_change_interval = random(BANDIT_WANDER_DIRECTION_CHANGE_MIN, BANDIT_WANDER_DIRECTION_CHANGE_MAX);
|
||||||
|
bandit.wander_direction_timer.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move in wander direction (if not 0)
|
||||||
|
if (bandit.wander_direction != 0) {
|
||||||
|
int target_x = bandit.position + bandit.wander_direction;
|
||||||
|
|
||||||
|
// Check bounds
|
||||||
|
if (target_x >= 0 && target_x < MAP_SIZE) {
|
||||||
|
// Don't wander into base if barricade is up
|
||||||
|
if (target_x <= BASE_END && barricade_health > 0) {
|
||||||
|
// Change direction instead
|
||||||
|
bandit.wander_direction = -bandit.wander_direction;
|
||||||
|
} else {
|
||||||
|
bandit.position = target_x;
|
||||||
|
play_creature_footstep(x, bandit.position, BASE_END, GRASS_END, BANDIT_FOOTSTEP_MAX_DISTANCE, BANDIT_SOUND_VOLUME_STEP);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Hit map boundary, reverse direction
|
||||||
|
bandit.wander_direction = -bandit.wander_direction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggressive behavior (original logic)
|
||||||
|
if (bandit.behavior_state == "aggressive") {
|
||||||
|
// Move toward player
|
||||||
|
int direction = 0;
|
||||||
|
if (x > BASE_END) {
|
||||||
|
// Player is outside base, move toward them
|
||||||
|
if (x > bandit.position) {
|
||||||
|
direction = 1;
|
||||||
|
} else if (x < bandit.position) {
|
||||||
|
direction = -1;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Player is in base, move toward base edge
|
||||||
|
if (bandit.position > BASE_END + 1) {
|
||||||
|
direction = -1;
|
||||||
|
} else {
|
||||||
|
return; // Already at base edge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int target_x = bandit.position + direction;
|
||||||
|
if (target_x < 0 || target_x >= MAP_SIZE) return;
|
||||||
|
|
||||||
|
// Don't enter base if barricade is up
|
||||||
|
if (target_x <= BASE_END && barricade_health > 0) {
|
||||||
|
try_attack_barricade_bandit(bandit);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bandit.position = target_x;
|
||||||
|
play_creature_footstep(x, bandit.position, BASE_END, GRASS_END, BANDIT_FOOTSTEP_MAX_DISTANCE, BANDIT_SOUND_VOLUME_STEP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_bandits() {
|
||||||
|
for (uint i = 0; i < bandits.length(); i++) {
|
||||||
|
update_bandit(bandits[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool damage_bandit_at(int pos, int damage) {
|
||||||
|
for (uint i = 0; i < bandits.length(); i++) {
|
||||||
|
if (bandits[i].position == pos) {
|
||||||
|
bandits[i].health -= damage;
|
||||||
|
if (bandits[i].health <= 0) {
|
||||||
|
if (bandits[i].sound_handle != -1) {
|
||||||
|
p.destroy_sound(bandits[i].sound_handle);
|
||||||
|
bandits[i].sound_handle = -1;
|
||||||
|
}
|
||||||
|
play_creature_death_sound("sounds/enemies/enemy_falls.ogg", x, pos, BANDIT_SOUND_VOLUME_STEP);
|
||||||
|
bandits.remove_at(i);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
// Ground game animals (boars, mountain goats, rams, etc.)
|
||||||
|
// Currently only boars are implemented
|
||||||
|
|
||||||
|
string[] ground_game_boar_sounds = {"sounds/game/boar.ogg"};
|
||||||
|
|
||||||
|
class GroundGame {
|
||||||
|
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
|
||||||
|
string animal_type; // "boar", future: "mountain_goat", "ram", etc.
|
||||||
|
|
||||||
|
GroundGame(int pos, int start, int end, string type = "boar") {
|
||||||
|
position = pos;
|
||||||
|
area_start = start;
|
||||||
|
area_end = end;
|
||||||
|
animal_type = type;
|
||||||
|
health = BOAR_HEALTH;
|
||||||
|
sound_handle = -1;
|
||||||
|
state = "wandering";
|
||||||
|
wander_direction = 0;
|
||||||
|
|
||||||
|
voice_sound = ground_game_boar_sounds[random(0, ground_game_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GroundGame@[] ground_games;
|
||||||
|
|
||||||
|
void clear_ground_games() {
|
||||||
|
if (ground_games.length() == 0) return;
|
||||||
|
|
||||||
|
for (uint i = 0; i < ground_games.length(); i++) {
|
||||||
|
if (ground_games[i].sound_handle != -1) {
|
||||||
|
p.destroy_sound(ground_games[i].sound_handle);
|
||||||
|
ground_games[i].sound_handle = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ground_games.resize(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
GroundGame@ get_ground_game_at(int pos) {
|
||||||
|
for (uint i = 0; i < ground_games.length(); i++) {
|
||||||
|
if (ground_games[i].position == pos) {
|
||||||
|
return @ground_games[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void spawn_ground_game(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_ground_game_at(candidate) == null) {
|
||||||
|
spawn_x = candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (spawn_x == -1) return; // Failed to find spot
|
||||||
|
|
||||||
|
GroundGame@ b = GroundGame(spawn_x, expansion_start, expansion_end, "boar");
|
||||||
|
ground_games.insert_last(b);
|
||||||
|
b.sound_handle = play_creature_voice(b.voice_sound, x, spawn_x, BOAR_SOUND_VOLUME_STEP);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool can_ground_game_attack_player(GroundGame@ game) {
|
||||||
|
if (player_health <= 0) return false;
|
||||||
|
|
||||||
|
// Check if player is on ground (ground game can't fly/climb)
|
||||||
|
if (y > 0) return false;
|
||||||
|
|
||||||
|
if (abs(game.position - x) > 1) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool try_attack_player_ground_game(GroundGame@ game) {
|
||||||
|
if (!can_ground_game_attack_player(game)) return false;
|
||||||
|
|
||||||
|
if (game.attack_timer.elapsed < BOAR_ATTACK_INTERVAL) return false;
|
||||||
|
|
||||||
|
game.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, game.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_ground_game(GroundGame@ game) {
|
||||||
|
// Sound logic
|
||||||
|
if (game.sound_timer.elapsed > game.next_sound_delay) {
|
||||||
|
game.sound_timer.restart();
|
||||||
|
game.next_sound_delay = random(BOAR_SOUND_MIN_DELAY, BOAR_SOUND_MAX_DELAY);
|
||||||
|
// Only play if wandering or occasionally while charging
|
||||||
|
game.sound_handle = play_creature_voice(game.voice_sound, x, game.position, BOAR_SOUND_VOLUME_STEP);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combat logic
|
||||||
|
if (try_attack_player_ground_game(game)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Movement logic
|
||||||
|
int move_speed = (game.state == "charging") ? BOAR_CHARGE_SPEED : game.next_move_delay;
|
||||||
|
|
||||||
|
if (game.move_timer.elapsed < move_speed) return;
|
||||||
|
game.move_timer.restart();
|
||||||
|
if (game.state == "wandering") {
|
||||||
|
game.next_move_delay = random(BOAR_MOVE_INTERVAL_MIN, BOAR_MOVE_INTERVAL_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detection Logic
|
||||||
|
int dist_to_player = x - game.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) {
|
||||||
|
game.state = "charging";
|
||||||
|
} else {
|
||||||
|
game.state = "wandering";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (game.state == "charging") {
|
||||||
|
int dir = (dist_to_player > 0) ? 1 : -1;
|
||||||
|
int target = game.position + dir;
|
||||||
|
|
||||||
|
// Don't leave area or enter base
|
||||||
|
if (target >= game.area_start && target <= game.area_end && target > BASE_END) {
|
||||||
|
game.position = target;
|
||||||
|
play_creature_footstep(x, game.position, BASE_END, GRASS_END, BOAR_FOOTSTEP_MAX_DISTANCE, BOAR_SOUND_VOLUME_STEP);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Wandering
|
||||||
|
if (random(1, 100) <= 20) {
|
||||||
|
game.wander_direction = random(-1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (game.wander_direction != 0) {
|
||||||
|
int target = game.position + game.wander_direction;
|
||||||
|
// Don't leave area or enter base
|
||||||
|
if (target >= game.area_start && target <= game.area_end && target > BASE_END) {
|
||||||
|
game.position = target;
|
||||||
|
play_creature_footstep(x, game.position, BASE_END, GRASS_END, BOAR_FOOTSTEP_MAX_DISTANCE, BOAR_SOUND_VOLUME_STEP);
|
||||||
|
} else {
|
||||||
|
game.wander_direction = -game.wander_direction; // Turn around
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_ground_games() {
|
||||||
|
for (uint i = 0; i < ground_games.length(); i++) {
|
||||||
|
update_ground_game(ground_games[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void attempt_hourly_ground_game_spawn() {
|
||||||
|
if (expanded_area_start == -1) return;
|
||||||
|
if (ground_games.length() >= BOAR_MAX_COUNT) return;
|
||||||
|
|
||||||
|
if (random(1, 100) <= BOAR_SPAWN_CHANCE_PER_HOUR) {
|
||||||
|
spawn_ground_game(expanded_area_start, expanded_area_end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool damage_ground_game_at(int pos, int damage) {
|
||||||
|
for (uint i = 0; i < ground_games.length(); i++) {
|
||||||
|
if (ground_games[i].position == pos) {
|
||||||
|
ground_games[i].health -= damage;
|
||||||
|
if (ground_games[i].health <= 0) {
|
||||||
|
if (ground_games[i].sound_handle != -1) {
|
||||||
|
p.destroy_sound(ground_games[i].sound_handle);
|
||||||
|
ground_games[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");
|
||||||
|
|
||||||
|
ground_games.remove_at(i);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backward compatibility aliases (to be removed after refactoring)
|
||||||
|
GroundGame@[]@ boars = @ground_games;
|
||||||
|
GroundGame@ get_boar_at(int pos) { return get_ground_game_at(pos); }
|
||||||
|
void clear_boars() { clear_ground_games(); }
|
||||||
|
void spawn_boar(int expansion_start, int expansion_end) { spawn_ground_game(expansion_start, expansion_end); }
|
||||||
|
void update_boars() { update_ground_games(); }
|
||||||
|
void attempt_hourly_boar_spawn() { attempt_hourly_ground_game_spawn(); }
|
||||||
|
bool damage_boar_at(int pos, int damage) { return damage_ground_game_at(pos, damage); }
|
||||||
+2
-502
@@ -7,93 +7,7 @@ int barricade_health = 0;
|
|||||||
bool barricade_initialized = false;
|
bool barricade_initialized = false;
|
||||||
int residents_count = 0;
|
int residents_count = 0;
|
||||||
|
|
||||||
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 Bandit {
|
|
||||||
int position;
|
|
||||||
int health;
|
|
||||||
string alert_sound;
|
|
||||||
string weapon_type; // "spear" or "axe"
|
|
||||||
int sound_handle;
|
|
||||||
timer move_timer;
|
|
||||||
timer alert_timer;
|
|
||||||
timer attack_timer;
|
|
||||||
int next_alert_delay;
|
|
||||||
int move_interval;
|
|
||||||
|
|
||||||
// Wandering behavior properties
|
|
||||||
string behavior_state; // "aggressive" or "wandering"
|
|
||||||
int wander_direction; // -1, 0, or 1
|
|
||||||
timer wander_direction_timer;
|
|
||||||
int wander_direction_change_interval;
|
|
||||||
|
|
||||||
Bandit(int pos, int expansion_start, int expansion_end) {
|
|
||||||
// Spawn somewhere in the expanded area
|
|
||||||
position = random(expansion_start, expansion_end);
|
|
||||||
health = BANDIT_HEALTH;
|
|
||||||
sound_handle = -1;
|
|
||||||
|
|
||||||
// Choose random alert sound
|
|
||||||
int sound_index = random(0, bandit_sounds.length() - 1);
|
|
||||||
alert_sound = bandit_sounds[sound_index];
|
|
||||||
|
|
||||||
// Choose random weapon
|
|
||||||
weapon_type = (random(0, 1) == 0) ? "spear" : "axe";
|
|
||||||
|
|
||||||
// Random movement speed within range
|
|
||||||
move_interval = random(BANDIT_MOVE_INTERVAL_MIN, BANDIT_MOVE_INTERVAL_MAX);
|
|
||||||
|
|
||||||
move_timer.restart();
|
|
||||||
alert_timer.restart();
|
|
||||||
attack_timer.restart();
|
|
||||||
next_alert_delay = random(BANDIT_ALERT_MIN_DELAY, BANDIT_ALERT_MAX_DELAY);
|
|
||||||
|
|
||||||
// Initialize wandering behavior (start aggressive during invasion)
|
|
||||||
behavior_state = "aggressive";
|
|
||||||
wander_direction = 0;
|
|
||||||
wander_direction_change_interval = random(BANDIT_WANDER_DIRECTION_CHANGE_MIN, BANDIT_WANDER_DIRECTION_CHANGE_MAX);
|
|
||||||
wander_direction_timer.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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;
|
||||||
@@ -788,422 +702,8 @@ WorldHerbGarden@ get_herb_garden_at_base() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bandit Functions
|
// Bandit functions moved to src/enemies/bandit.nvgt
|
||||||
void clear_bandits() {
|
// Boar/GroundGame functions moved to src/enemies/ground_game.nvgt
|
||||||
if (bandits.length() == 0) return;
|
|
||||||
|
|
||||||
for (uint i = 0; i < bandits.length(); i++) {
|
|
||||||
if (bandits[i].sound_handle != -1) {
|
|
||||||
p.destroy_sound(bandits[i].sound_handle);
|
|
||||||
bandits[i].sound_handle = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bandits.resize(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Bandit@ get_bandit_at(int pos) {
|
|
||||||
for (uint i = 0; i < bandits.length(); i++) {
|
|
||||||
if (bandits[i].position == pos) {
|
|
||||||
return @bandits[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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() {
|
|
||||||
for (uint i = 0; i < boars.length(); i++) {
|
|
||||||
update_boar(boars[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void attempt_hourly_boar_spawn() {
|
|
||||||
if (expanded_area_start == -1) return;
|
|
||||||
if (boars.length() >= BOAR_MAX_COUNT) return;
|
|
||||||
|
|
||||||
if (random(1, 100) <= BOAR_SPAWN_CHANCE_PER_HOUR) {
|
|
||||||
spawn_boar(expanded_area_start, expanded_area_end);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
int spawn_x = -1;
|
|
||||||
for (int attempts = 0; attempts < 20; attempts++) {
|
|
||||||
int candidate = random(expansion_start, expansion_end);
|
|
||||||
if (get_bandit_at(candidate) == null) {
|
|
||||||
spawn_x = candidate;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (spawn_x == -1) {
|
|
||||||
spawn_x = random(expansion_start, expansion_end);
|
|
||||||
}
|
|
||||||
|
|
||||||
Bandit@ b = Bandit(spawn_x, expansion_start, expansion_end);
|
|
||||||
bandits.insert_last(b);
|
|
||||||
b.sound_handle = play_creature_voice(b.alert_sound, x, spawn_x, BANDIT_SOUND_VOLUME_STEP);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool can_bandit_attack_player(Bandit@ bandit) {
|
|
||||||
if (player_health <= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cannot attack player if barricade is up and player is in base
|
|
||||||
if (barricade_health > 0 && x <= BASE_END) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (abs(bandit.position - x) > 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return y <= BANDIT_ATTACK_MAX_HEIGHT;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool try_attack_player_bandit(Bandit@ bandit) {
|
|
||||||
if (!can_bandit_attack_player(bandit)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (bandit.attack_timer.elapsed < BANDIT_ATTACK_INTERVAL) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bandit.attack_timer.restart();
|
|
||||||
|
|
||||||
// Play weapon swing sound based on bandit's weapon
|
|
||||||
if (bandit.weapon_type == "spear") {
|
|
||||||
play_creature_attack_sound("sounds/weapons/spear_swing.ogg", x, bandit.position, BANDIT_SOUND_VOLUME_STEP);
|
|
||||||
} else if (bandit.weapon_type == "axe") {
|
|
||||||
play_creature_attack_sound("sounds/weapons/axe_swing.ogg", x, bandit.position, BANDIT_SOUND_VOLUME_STEP);
|
|
||||||
}
|
|
||||||
|
|
||||||
int damage = random(BANDIT_DAMAGE_MIN, BANDIT_DAMAGE_MAX);
|
|
||||||
player_health -= damage;
|
|
||||||
if (player_health < 0) {
|
|
||||||
player_health = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Play hit sound
|
|
||||||
if (bandit.weapon_type == "spear") {
|
|
||||||
play_creature_attack_sound("sounds/weapons/spear_hit.ogg", x, bandit.position, BANDIT_SOUND_VOLUME_STEP);
|
|
||||||
} else if (bandit.weapon_type == "axe") {
|
|
||||||
play_creature_attack_sound("sounds/weapons/axe_hit.ogg", x, bandit.position, BANDIT_SOUND_VOLUME_STEP);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void try_attack_barricade_bandit(Bandit@ bandit) {
|
|
||||||
if (barricade_health <= 0) return;
|
|
||||||
if (bandit.attack_timer.elapsed < BANDIT_ATTACK_INTERVAL) return;
|
|
||||||
|
|
||||||
bandit.attack_timer.restart();
|
|
||||||
|
|
||||||
// Bandits do 1-2 damage to barricade
|
|
||||||
int damage = random(BANDIT_DAMAGE_MIN, BANDIT_DAMAGE_MAX);
|
|
||||||
barricade_health -= damage;
|
|
||||||
if (barricade_health < 0) barricade_health = 0;
|
|
||||||
|
|
||||||
// Play weapon swing sound
|
|
||||||
if (bandit.weapon_type == "spear") {
|
|
||||||
play_creature_attack_sound("sounds/weapons/spear_swing.ogg", x, bandit.position, BANDIT_SOUND_VOLUME_STEP);
|
|
||||||
play_creature_attack_sound("sounds/weapons/spear_hit.ogg", x, bandit.position, BANDIT_SOUND_VOLUME_STEP);
|
|
||||||
} else if (bandit.weapon_type == "axe") {
|
|
||||||
play_creature_attack_sound("sounds/weapons/axe_swing.ogg", x, bandit.position, BANDIT_SOUND_VOLUME_STEP);
|
|
||||||
play_creature_attack_sound("sounds/weapons/axe_hit.ogg", x, bandit.position, BANDIT_SOUND_VOLUME_STEP);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resident defense counter-attack
|
|
||||||
if (can_residents_defend()) {
|
|
||||||
int counterDamage = perform_resident_defense();
|
|
||||||
if (counterDamage > 0) {
|
|
||||||
bandit.health -= counterDamage;
|
|
||||||
if (bandit.health <= 0 && x <= BASE_END) {
|
|
||||||
speak_with_history("Residents killed an attacking bandit.", true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (barricade_health == 0) {
|
|
||||||
notify("The barricade has fallen!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void update_bandit(Bandit@ bandit) {
|
|
||||||
// Play alert sound at intervals
|
|
||||||
if (bandit.alert_timer.elapsed > bandit.next_alert_delay) {
|
|
||||||
bandit.alert_timer.restart();
|
|
||||||
bandit.next_alert_delay = random(BANDIT_ALERT_MIN_DELAY, BANDIT_ALERT_MAX_DELAY);
|
|
||||||
bandit.sound_handle = play_creature_voice(bandit.alert_sound, x, bandit.position, BANDIT_SOUND_VOLUME_STEP);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (try_attack_player_bandit(bandit)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bandit.move_timer.elapsed < bandit.move_interval) return;
|
|
||||||
bandit.move_timer.restart();
|
|
||||||
|
|
||||||
// If barricade is up and bandit is at the edge of base, attack barricade
|
|
||||||
if (barricade_health > 0 && bandit.position == BASE_END + 1) {
|
|
||||||
try_attack_barricade_bandit(bandit);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// State-based behavior
|
|
||||||
if (bandit.behavior_state == "wandering") {
|
|
||||||
// Check if player is within detection radius
|
|
||||||
int distance = abs(bandit.position - x);
|
|
||||||
if (distance <= BANDIT_DETECTION_RADIUS) {
|
|
||||||
// Player detected! Switch to aggressive
|
|
||||||
bandit.behavior_state = "aggressive";
|
|
||||||
} else {
|
|
||||||
// Continue wandering
|
|
||||||
if (bandit.wander_direction_timer.elapsed > bandit.wander_direction_change_interval) {
|
|
||||||
// Time to change direction
|
|
||||||
bandit.wander_direction = random(-1, 1);
|
|
||||||
bandit.wander_direction_change_interval = random(BANDIT_WANDER_DIRECTION_CHANGE_MIN, BANDIT_WANDER_DIRECTION_CHANGE_MAX);
|
|
||||||
bandit.wander_direction_timer.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move in wander direction (if not 0)
|
|
||||||
if (bandit.wander_direction != 0) {
|
|
||||||
int target_x = bandit.position + bandit.wander_direction;
|
|
||||||
|
|
||||||
// Check bounds
|
|
||||||
if (target_x >= 0 && target_x < MAP_SIZE) {
|
|
||||||
// Don't wander into base if barricade is up
|
|
||||||
if (target_x <= BASE_END && barricade_health > 0) {
|
|
||||||
// Change direction instead
|
|
||||||
bandit.wander_direction = -bandit.wander_direction;
|
|
||||||
} else {
|
|
||||||
bandit.position = target_x;
|
|
||||||
play_creature_footstep(x, bandit.position, BASE_END, GRASS_END, BANDIT_FOOTSTEP_MAX_DISTANCE, BANDIT_SOUND_VOLUME_STEP);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Hit map boundary, reverse direction
|
|
||||||
bandit.wander_direction = -bandit.wander_direction;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Aggressive behavior (original logic)
|
|
||||||
if (bandit.behavior_state == "aggressive") {
|
|
||||||
// Move toward player
|
|
||||||
int direction = 0;
|
|
||||||
if (x > BASE_END) {
|
|
||||||
// Player is outside base, move toward them
|
|
||||||
if (x > bandit.position) {
|
|
||||||
direction = 1;
|
|
||||||
} else if (x < bandit.position) {
|
|
||||||
direction = -1;
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Player is in base, move toward base edge
|
|
||||||
if (bandit.position > BASE_END + 1) {
|
|
||||||
direction = -1;
|
|
||||||
} else {
|
|
||||||
return; // Already at base edge
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int target_x = bandit.position + direction;
|
|
||||||
if (target_x < 0 || target_x >= MAP_SIZE) return;
|
|
||||||
|
|
||||||
// Don't enter base if barricade is up
|
|
||||||
if (target_x <= BASE_END && barricade_health > 0) {
|
|
||||||
try_attack_barricade_bandit(bandit);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bandit.position = target_x;
|
|
||||||
play_creature_footstep(x, bandit.position, BASE_END, GRASS_END, BANDIT_FOOTSTEP_MAX_DISTANCE, BANDIT_SOUND_VOLUME_STEP);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void update_bandits() {
|
|
||||||
for (uint i = 0; i < bandits.length(); i++) {
|
|
||||||
update_bandit(bandits[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool damage_bandit_at(int pos, int damage) {
|
|
||||||
for (uint i = 0; i < bandits.length(); i++) {
|
|
||||||
if (bandits[i].position == pos) {
|
|
||||||
bandits[i].health -= damage;
|
|
||||||
if (bandits[i].health <= 0) {
|
|
||||||
if (bandits[i].sound_handle != -1) {
|
|
||||||
p.destroy_sound(bandits[i].sound_handle);
|
|
||||||
bandits[i].sound_handle = -1;
|
|
||||||
}
|
|
||||||
play_creature_death_sound("sounds/enemies/enemy_falls.ogg", x, pos, BANDIT_SOUND_VOLUME_STEP);
|
|
||||||
bandits.remove_at(i);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stream Functions
|
// Stream Functions
|
||||||
void add_world_stream(int start_pos, int width) {
|
void add_world_stream(int start_pos, int width) {
|
||||||
|
|||||||
Reference in New Issue
Block a user