Initial commit.
This commit is contained in:
845
src/world_state.nvgt
Normal file
845
src/world_state.nvgt
Normal file
@@ -0,0 +1,845 @@
|
||||
// World Objects
|
||||
|
||||
// Small game types that can be caught in snares
|
||||
string[] small_game_types = {"rabbit", "squirrel", "raccoon", "opossum", "groundhog"};
|
||||
|
||||
int barricade_health = 0;
|
||||
bool barricade_initialized = false;
|
||||
|
||||
string[] zombie_sounds = {"sounds/enemies/zombie1.ogg"};
|
||||
string[] bandit_sounds = {"sounds/enemies/bandit1.ogg", "sounds/enemies/bandit2.ogg"};
|
||||
|
||||
class Zombie {
|
||||
int position;
|
||||
int health;
|
||||
string voice_sound;
|
||||
timer move_timer;
|
||||
timer groan_timer;
|
||||
timer attack_timer;
|
||||
int next_groan_delay;
|
||||
|
||||
Zombie(int pos) {
|
||||
position = pos;
|
||||
health = ZOMBIE_HEALTH;
|
||||
int sound_index = random(0, zombie_sounds.length() - 1);
|
||||
voice_sound = zombie_sounds[sound_index];
|
||||
move_timer.restart();
|
||||
groan_timer.restart();
|
||||
attack_timer.restart();
|
||||
next_groan_delay = random(ZOMBIE_GROAN_MIN_DELAY, ZOMBIE_GROAN_MAX_DELAY);
|
||||
}
|
||||
}
|
||||
Zombie@[] zombies;
|
||||
|
||||
class Bandit {
|
||||
int position;
|
||||
int health;
|
||||
string alert_sound;
|
||||
string weapon_type; // "spear" or "axe"
|
||||
timer move_timer;
|
||||
timer alert_timer;
|
||||
timer attack_timer;
|
||||
int next_alert_delay;
|
||||
int move_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;
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
Bandit@[] bandits;
|
||||
|
||||
string get_random_small_game() {
|
||||
int index = random(0, small_game_types.length() - 1);
|
||||
return small_game_types[index];
|
||||
}
|
||||
|
||||
class WorldSnare {
|
||||
int position;
|
||||
bool has_catch;
|
||||
string catch_type; // What type of small game was caught
|
||||
int catch_chance;
|
||||
int escape_chance;
|
||||
int sound_handle;
|
||||
timer minute_timer;
|
||||
bool active; // To prevent immediate breakage on placement
|
||||
|
||||
WorldSnare(int pos) {
|
||||
position = pos;
|
||||
has_catch = false;
|
||||
catch_type = "";
|
||||
catch_chance = 5;
|
||||
escape_chance = 5;
|
||||
active = false; // Becomes active when player steps off
|
||||
sound_handle = -1;
|
||||
|
||||
minute_timer.restart();
|
||||
}
|
||||
|
||||
void update() {
|
||||
// Activate if player moves away
|
||||
if (!active && x != position) {
|
||||
active = true;
|
||||
minute_timer.restart();
|
||||
}
|
||||
|
||||
// Limit snare sound to 2 tiles distance
|
||||
if (abs(x - position) <= 2) {
|
||||
if (sound_handle == -1 || !p.sound_is_active(sound_handle)) {
|
||||
sound_handle = p.play_1d("sounds/actions/set_snare.ogg", x, position, true);
|
||||
}
|
||||
} else {
|
||||
if (sound_handle != -1) {
|
||||
p.destroy_sound(sound_handle);
|
||||
sound_handle = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Every minute logic (only when active)
|
||||
if (active && minute_timer.elapsed >= 60000) {
|
||||
minute_timer.restart();
|
||||
|
||||
if (has_catch) {
|
||||
// Animal trying to escape
|
||||
if (escape_chance < 95) escape_chance += 5;
|
||||
|
||||
int roll = random(1, 100);
|
||||
if (roll <= escape_chance) {
|
||||
// Animal escaped!
|
||||
has_catch = false;
|
||||
notify("A " + catch_type + " escaped from your snare at " + position + "!");
|
||||
catch_type = "";
|
||||
catch_chance = 5;
|
||||
}
|
||||
} else {
|
||||
// Trying to catch small game
|
||||
if (catch_chance < 75) catch_chance += 5;
|
||||
|
||||
int roll = random(1, 100);
|
||||
if (roll <= catch_chance) {
|
||||
// Caught something!
|
||||
has_catch = true;
|
||||
catch_type = get_random_small_game();
|
||||
escape_chance = 5; // Reset escape chance
|
||||
notify(catch_type + " caught in snare at x " + position + " y 0!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
if (sound_handle != -1) {
|
||||
p.destroy_sound(sound_handle);
|
||||
sound_handle = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
WorldSnare@[] world_snares;
|
||||
|
||||
class WorldFire {
|
||||
int position;
|
||||
int sound_handle;
|
||||
timer fuel_timer;
|
||||
int fuel_remaining;
|
||||
bool low_fuel_warned;
|
||||
|
||||
WorldFire(int pos) {
|
||||
position = pos;
|
||||
sound_handle = -1;
|
||||
fuel_remaining = 720000; // Start with 12 minutes (12 hours in-game)
|
||||
low_fuel_warned = false;
|
||||
fuel_timer.restart();
|
||||
}
|
||||
|
||||
void add_fuel(int amount) {
|
||||
fuel_remaining += amount;
|
||||
low_fuel_warned = false;
|
||||
}
|
||||
|
||||
bool is_burning() {
|
||||
return fuel_remaining > 0;
|
||||
}
|
||||
|
||||
void update() {
|
||||
// Update fuel
|
||||
if (fuel_remaining > 0) {
|
||||
int elapsed = fuel_timer.elapsed;
|
||||
fuel_timer.restart();
|
||||
fuel_remaining -= elapsed;
|
||||
|
||||
// Warn when fuel is low (30 seconds remaining)
|
||||
if (!low_fuel_warned && fuel_remaining <= 30000 && fuel_remaining > 0) {
|
||||
low_fuel_warned = true;
|
||||
notify("Fire at " + position + " is getting low!");
|
||||
}
|
||||
|
||||
// Fire went out
|
||||
if (fuel_remaining <= 0) {
|
||||
fuel_remaining = 0;
|
||||
notify("Fire at " + position + " has gone out.");
|
||||
if (sound_handle != -1) {
|
||||
p.destroy_sound(sound_handle);
|
||||
sound_handle = -1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Limit fire sound to 2 tiles distance (only if burning)
|
||||
if (is_burning()) {
|
||||
if (abs(x - position) <= 2) {
|
||||
if (sound_handle == -1 || !p.sound_is_active(sound_handle)) {
|
||||
sound_handle = p.play_1d("sounds/items/fire.ogg", x, position, true);
|
||||
}
|
||||
} else {
|
||||
if (sound_handle != -1) {
|
||||
p.destroy_sound(sound_handle);
|
||||
sound_handle = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
if (sound_handle != -1) {
|
||||
p.destroy_sound(sound_handle);
|
||||
sound_handle = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
WorldFire@[] world_fires;
|
||||
|
||||
class WorldFirepit {
|
||||
int position;
|
||||
|
||||
WorldFirepit(int pos) {
|
||||
position = pos;
|
||||
}
|
||||
}
|
||||
WorldFirepit@[] world_firepits;
|
||||
|
||||
class WorldHerbGarden {
|
||||
int position;
|
||||
|
||||
WorldHerbGarden(int pos) {
|
||||
position = pos;
|
||||
}
|
||||
}
|
||||
WorldHerbGarden@[] world_herb_gardens;
|
||||
|
||||
class WorldStream {
|
||||
int start_position;
|
||||
int end_position;
|
||||
int sound_handle;
|
||||
|
||||
WorldStream(int start_pos, int width) {
|
||||
start_position = start_pos;
|
||||
end_position = start_pos + width - 1;
|
||||
sound_handle = -1;
|
||||
}
|
||||
|
||||
bool contains_position(int pos) {
|
||||
return pos >= start_position && pos <= end_position;
|
||||
}
|
||||
|
||||
int get_width() {
|
||||
return end_position - start_position + 1;
|
||||
}
|
||||
|
||||
int get_center_position() {
|
||||
return (start_position + end_position) / 2;
|
||||
}
|
||||
|
||||
void update() {
|
||||
int center = get_center_position();
|
||||
|
||||
// Play stream sound within 3 tiles distance from center
|
||||
if (abs(x - center) <= 3) {
|
||||
if (sound_handle == -1 || !p.sound_is_active(sound_handle)) {
|
||||
sound_handle = p.play_1d("sounds/terrain/stream.ogg", x, center, true);
|
||||
}
|
||||
} else {
|
||||
if (sound_handle != -1) {
|
||||
p.destroy_sound(sound_handle);
|
||||
sound_handle = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
if (sound_handle != -1) {
|
||||
p.destroy_sound(sound_handle);
|
||||
sound_handle = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
WorldStream@[] world_streams;
|
||||
|
||||
void add_world_snare(int pos) {
|
||||
WorldSnare@ s = WorldSnare(pos);
|
||||
world_snares.insert_last(s);
|
||||
}
|
||||
|
||||
void add_world_fire(int pos) {
|
||||
WorldFire@ f = WorldFire(pos);
|
||||
world_fires.insert_last(f);
|
||||
}
|
||||
|
||||
void add_world_firepit(int pos) {
|
||||
WorldFirepit@ fp = WorldFirepit(pos);
|
||||
world_firepits.insert_last(fp);
|
||||
}
|
||||
|
||||
void update_world_objects() {
|
||||
for (uint i = 0; i < world_snares.length(); i++) {
|
||||
world_snares[i].update();
|
||||
}
|
||||
for (uint i = 0; i < world_fires.length(); i++) {
|
||||
world_fires[i].update();
|
||||
}
|
||||
}
|
||||
|
||||
WorldSnare@ get_snare_at(int pos) {
|
||||
for (uint i = 0; i < world_snares.length(); i++) {
|
||||
if (world_snares[i].position == pos) {
|
||||
return @world_snares[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void remove_snare_at(int pos) {
|
||||
for (uint i = 0; i < world_snares.length(); i++) {
|
||||
if (world_snares[i].position == pos) {
|
||||
world_snares[i].destroy();
|
||||
world_snares.remove_at(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Called when player moves onto a tile
|
||||
void check_snare_collision(int player_x) {
|
||||
WorldSnare@ s = get_snare_at(player_x);
|
||||
if (s != null && s.active) {
|
||||
// Break the snare
|
||||
p.play_stationary("sounds/actions/break_snare.ogg", false);
|
||||
|
||||
if (s.has_catch) {
|
||||
screen_reader_speak("You stepped on your snare! The " + s.catch_type + " escaped.", true);
|
||||
} else {
|
||||
screen_reader_speak("You stepped on your snare and broke it!", true);
|
||||
}
|
||||
|
||||
remove_snare_at(player_x);
|
||||
}
|
||||
}
|
||||
|
||||
void update_snares() {
|
||||
for (uint i = 0; i < world_snares.length(); i++) {
|
||||
world_snares[i].update();
|
||||
}
|
||||
}
|
||||
|
||||
void update_streams() {
|
||||
for (uint i = 0; i < world_streams.length(); i++) {
|
||||
world_streams[i].update();
|
||||
}
|
||||
}
|
||||
|
||||
void update_fires() {
|
||||
// Update all fires and remove any that have burned out
|
||||
for (uint i = 0; i < world_fires.length(); i++) {
|
||||
world_fires[i].update();
|
||||
}
|
||||
|
||||
// Remove dead fires
|
||||
for (uint i = 0; i < world_fires.length(); i++) {
|
||||
if (!world_fires[i].is_burning()) {
|
||||
world_fires[i].destroy();
|
||||
world_fires.remove_at(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WorldFire@ get_fire_at(int pos) {
|
||||
for (uint i = 0; i < world_fires.length(); i++) {
|
||||
if (world_fires[i].position == pos) {
|
||||
return @world_fires[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
WorldFire@ get_fire_near(int pos) {
|
||||
// Check for fire at current position or adjacent tiles
|
||||
for (int check_x = pos - 1; check_x <= pos + 1; check_x++) {
|
||||
WorldFire@ fire = get_fire_at(check_x);
|
||||
if (fire != null && fire.is_burning()) {
|
||||
return @fire;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
WorldFire@ get_fire_within_range(int pos, int range) {
|
||||
// Check for fire within specified range
|
||||
for (int check_x = pos - range; check_x <= pos + range; check_x++) {
|
||||
WorldFire@ fire = get_fire_at(check_x);
|
||||
if (fire != null && fire.is_burning()) {
|
||||
return @fire;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
WorldFirepit@ get_firepit_at(int pos) {
|
||||
for (uint i = 0; i < world_firepits.length(); i++) {
|
||||
if (world_firepits[i].position == pos) {
|
||||
return @world_firepits[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
WorldFirepit@ get_firepit_near(int pos, int range) {
|
||||
// Check for firepit within specified range
|
||||
for (int check_x = pos - range; check_x <= pos + range; check_x++) {
|
||||
WorldFirepit@ firepit = get_firepit_at(check_x);
|
||||
if (firepit != null) {
|
||||
return @firepit;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void add_world_herb_garden(int pos) {
|
||||
WorldHerbGarden@ hg = WorldHerbGarden(pos);
|
||||
world_herb_gardens.insert_last(hg);
|
||||
}
|
||||
|
||||
void init_barricade() {
|
||||
if (barricade_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
barricade_health = BARRICADE_BASE_HEALTH;
|
||||
barricade_initialized = true;
|
||||
}
|
||||
|
||||
int add_barricade_health(int amount) {
|
||||
if (amount <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int before = barricade_health;
|
||||
barricade_health += amount;
|
||||
if (barricade_health > BARRICADE_MAX_HEALTH) {
|
||||
barricade_health = BARRICADE_MAX_HEALTH;
|
||||
}
|
||||
|
||||
return barricade_health - before;
|
||||
}
|
||||
|
||||
void clear_zombies() {
|
||||
if (zombies.length() == 0) return;
|
||||
|
||||
zombies.resize(0);
|
||||
}
|
||||
|
||||
Zombie@ get_zombie_at(int pos) {
|
||||
for (uint i = 0; i < zombies.length(); i++) {
|
||||
if (zombies[i].position == pos) {
|
||||
return @zombies[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void spawn_zombie() {
|
||||
int spawn_x = -1;
|
||||
for (int attempts = 0; attempts < 20; attempts++) {
|
||||
int candidate = random(BASE_END + 1, MAP_SIZE - 1);
|
||||
if (get_zombie_at(candidate) == null) {
|
||||
spawn_x = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (spawn_x == -1) {
|
||||
spawn_x = random(BASE_END + 1, MAP_SIZE - 1);
|
||||
}
|
||||
|
||||
Zombie@ z = Zombie(spawn_x);
|
||||
zombies.insert_last(z);
|
||||
play_1d_with_volume_step(z.voice_sound, x, spawn_x, false, ZOMBIE_SOUND_VOLUME_STEP);
|
||||
}
|
||||
|
||||
void try_attack_barricade(Zombie@ zombie) {
|
||||
if (barricade_health <= 0) return;
|
||||
if (zombie.attack_timer.elapsed < ZOMBIE_ATTACK_INTERVAL) return;
|
||||
|
||||
zombie.attack_timer.restart();
|
||||
int damage = random(ZOMBIE_DAMAGE_MIN, ZOMBIE_DAMAGE_MAX);
|
||||
barricade_health -= damage;
|
||||
if (barricade_health < 0) barricade_health = 0;
|
||||
|
||||
play_1d_with_volume_step("sounds/enemies/zombie_hits_player.ogg", x, zombie.position, false, ZOMBIE_SOUND_VOLUME_STEP);
|
||||
|
||||
if (barricade_health == 0) {
|
||||
notify("The barricade has fallen!");
|
||||
}
|
||||
}
|
||||
|
||||
bool can_zombie_attack_player(Zombie@ zombie) {
|
||||
if (player_health <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (barricade_health > 0 && x <= BASE_END) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (abs(zombie.position - x) > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return y <= ZOMBIE_ATTACK_MAX_HEIGHT;
|
||||
}
|
||||
|
||||
bool try_attack_player(Zombie@ zombie) {
|
||||
if (!can_zombie_attack_player(zombie)) {
|
||||
return false;
|
||||
}
|
||||
if (zombie.attack_timer.elapsed < ZOMBIE_ATTACK_INTERVAL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
zombie.attack_timer.restart();
|
||||
int damage = random(ZOMBIE_DAMAGE_MIN, ZOMBIE_DAMAGE_MAX);
|
||||
player_health -= damage;
|
||||
if (player_health < 0) {
|
||||
player_health = 0;
|
||||
}
|
||||
|
||||
play_1d_with_volume_step("sounds/enemies/zombie_hits_player.ogg", x, zombie.position, false, ZOMBIE_SOUND_VOLUME_STEP);
|
||||
return true;
|
||||
}
|
||||
|
||||
void update_zombie(Zombie@ zombie) {
|
||||
if (zombie.groan_timer.elapsed > zombie.next_groan_delay) {
|
||||
zombie.groan_timer.restart();
|
||||
zombie.next_groan_delay = random(ZOMBIE_GROAN_MIN_DELAY, ZOMBIE_GROAN_MAX_DELAY);
|
||||
play_1d_with_volume_step(zombie.voice_sound, x, zombie.position, false, ZOMBIE_SOUND_VOLUME_STEP);
|
||||
}
|
||||
|
||||
if (try_attack_player(zombie)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (zombie.move_timer.elapsed < ZOMBIE_MOVE_INTERVAL) return;
|
||||
zombie.move_timer.restart();
|
||||
|
||||
if (barricade_health > 0 && zombie.position == BASE_END + 1) {
|
||||
try_attack_barricade(zombie);
|
||||
return;
|
||||
}
|
||||
|
||||
int direction = 0;
|
||||
if (x > BASE_END) {
|
||||
if (x > zombie.position) {
|
||||
direction = 1;
|
||||
} else if (x < zombie.position) {
|
||||
direction = -1;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
direction = random(-1, 1);
|
||||
if (direction == 0) return;
|
||||
}
|
||||
|
||||
int target_x = zombie.position + direction;
|
||||
if (target_x < 0 || target_x >= MAP_SIZE) return;
|
||||
|
||||
if (target_x <= BASE_END && barricade_health > 0) {
|
||||
try_attack_barricade(zombie);
|
||||
return;
|
||||
}
|
||||
|
||||
zombie.position = target_x;
|
||||
play_positional_footstep(x, zombie.position, BASE_END, GRASS_END, ZOMBIE_FOOTSTEP_MAX_DISTANCE, ZOMBIE_SOUND_VOLUME_STEP);
|
||||
}
|
||||
|
||||
void update_zombies() {
|
||||
if (is_daytime) {
|
||||
clear_zombies();
|
||||
return;
|
||||
}
|
||||
|
||||
while (zombies.length() < ZOMBIE_MAX_COUNT) {
|
||||
spawn_zombie();
|
||||
}
|
||||
|
||||
for (uint i = 0; i < zombies.length(); i++) {
|
||||
update_zombie(zombies[i]);
|
||||
}
|
||||
}
|
||||
|
||||
bool damage_zombie_at(int pos, int damage) {
|
||||
for (uint i = 0; i < zombies.length(); i++) {
|
||||
if (zombies[i].position == pos) {
|
||||
zombies[i].health -= damage;
|
||||
if (zombies[i].health <= 0) {
|
||||
play_1d_with_volume_step("sounds/enemies/enemy_falls.ogg", x, pos, false, ZOMBIE_SOUND_VOLUME_STEP);
|
||||
zombies.remove_at(i);
|
||||
screen_reader_speak("Zombie killed.", true);
|
||||
} else {
|
||||
screen_reader_speak("Hit zombie.", true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
WorldHerbGarden@ get_herb_garden_at(int pos) {
|
||||
for (uint i = 0; i < world_herb_gardens.length(); i++) {
|
||||
if (world_herb_gardens[i].position == pos) {
|
||||
return @world_herb_gardens[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
WorldHerbGarden@ get_herb_garden_at_base() {
|
||||
// Check if herb garden exists anywhere in base area (0-4)
|
||||
for (uint i = 0; i < world_herb_gardens.length(); i++) {
|
||||
if (world_herb_gardens[i].position <= BASE_END) {
|
||||
return @world_herb_gardens[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Bandit Functions
|
||||
void clear_bandits() {
|
||||
if (bandits.length() == 0) return;
|
||||
|
||||
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);
|
||||
play_1d_with_volume_step(b.alert_sound, x, spawn_x, false, 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") {
|
||||
p.play_stationary("sounds/weapons/spear_swing.ogg", false);
|
||||
} else if (bandit.weapon_type == "axe") {
|
||||
p.play_stationary("sounds/weapons/axe_swing.ogg", false);
|
||||
}
|
||||
|
||||
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") {
|
||||
p.play_stationary("sounds/weapons/spear_hit.ogg", false);
|
||||
} else if (bandit.weapon_type == "axe") {
|
||||
p.play_stationary("sounds/weapons/axe_hit.ogg", false);
|
||||
}
|
||||
|
||||
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") {
|
||||
p.play_stationary("sounds/weapons/spear_swing.ogg", false);
|
||||
p.play_stationary("sounds/weapons/spear_hit.ogg", false);
|
||||
} else if (bandit.weapon_type == "axe") {
|
||||
p.play_stationary("sounds/weapons/axe_swing.ogg", false);
|
||||
p.play_stationary("sounds/weapons/axe_hit.ogg", false);
|
||||
}
|
||||
|
||||
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);
|
||||
play_1d_with_volume_step(bandit.alert_sound, x, bandit.position, false, 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;
|
||||
}
|
||||
|
||||
// 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_positional_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) {
|
||||
play_1d_with_volume_step("sounds/enemies/enemy_falls.ogg", x, pos, false, BANDIT_SOUND_VOLUME_STEP);
|
||||
bandits.remove_at(i);
|
||||
screen_reader_speak("Bandit killed.", true);
|
||||
} else {
|
||||
screen_reader_speak("Hit bandit.", true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Stream Functions
|
||||
void add_world_stream(int start_pos, int width) {
|
||||
WorldStream@ s = WorldStream(start_pos, width);
|
||||
world_streams.insert_last(s);
|
||||
}
|
||||
|
||||
WorldStream@ get_stream_at(int pos) {
|
||||
for (uint i = 0; i < world_streams.length(); i++) {
|
||||
if (world_streams[i].contains_position(pos)) {
|
||||
return @world_streams[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
bool is_position_in_water(int pos) {
|
||||
return get_stream_at(pos) != null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user