Initial commit.
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
string get_footstep_sound(int current_x, int base_end, int grass_end)
|
||||
{
|
||||
// Check if in water first (overrides all other terrain)
|
||||
if (is_position_in_water(current_x)) {
|
||||
return "sounds/terrain/shallow_water.ogg";
|
||||
}
|
||||
|
||||
if (current_x <= base_end)
|
||||
{
|
||||
// Base area
|
||||
return "sounds/terrain/wood.ogg";
|
||||
}
|
||||
else if (current_x <= grass_end)
|
||||
{
|
||||
// Grass area
|
||||
return "sounds/terrain/grass.ogg";
|
||||
}
|
||||
else if (current_x <= GRAVEL_END)
|
||||
{
|
||||
// Gravel area
|
||||
return "sounds/terrain/gravel.ogg";
|
||||
}
|
||||
else if (expanded_area_start != -1 && current_x >= expanded_area_start && current_x <= expanded_area_end)
|
||||
{
|
||||
// Expanded area - check terrain type
|
||||
int index = current_x - expanded_area_start;
|
||||
if (index >= 0 && index < expanded_terrain_types.length())
|
||||
{
|
||||
string terrain = expanded_terrain_types[index];
|
||||
if (terrain == "stone") {
|
||||
return "sounds/terrain/stone.ogg";
|
||||
} else if (terrain == "grass") {
|
||||
return "sounds/terrain/grass.ogg";
|
||||
} else if (terrain == "snow") {
|
||||
return "sounds/terrain/snow.ogg";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default to gravel
|
||||
return "sounds/terrain/gravel.ogg";
|
||||
}
|
||||
|
||||
void play_footstep(int current_x, int base_end, int grass_end)
|
||||
{
|
||||
string sound_file = get_footstep_sound(current_x, base_end, grass_end);
|
||||
|
||||
if(file_exists(sound_file)) {
|
||||
p.play_stationary(sound_file, false);
|
||||
}
|
||||
}
|
||||
|
||||
int play_1d_with_volume_step(string sound_file, int listener_x, int sound_x, bool looping, float volume_step)
|
||||
{
|
||||
int slot = p.play_1d(sound_file, listener_x, sound_x, looping);
|
||||
if (slot != -1) {
|
||||
p.update_sound_positioning_values(slot, -1.0, volume_step, true);
|
||||
}
|
||||
return slot;
|
||||
}
|
||||
|
||||
void play_positional_footstep(int listener_x, int step_x, int base_end, int grass_end, int max_distance, float volume_step)
|
||||
{
|
||||
int distance = step_x - listener_x;
|
||||
if (distance < 0) {
|
||||
distance = -distance;
|
||||
}
|
||||
if (distance > max_distance) {
|
||||
return;
|
||||
}
|
||||
|
||||
string sound_file = get_footstep_sound(step_x, base_end, grass_end);
|
||||
|
||||
if(file_exists(sound_file)) {
|
||||
play_1d_with_volume_step(sound_file, listener_x, step_x, false, volume_step);
|
||||
}
|
||||
}
|
||||
|
||||
void play_land_sound(int current_x, int base_end, int grass_end)
|
||||
{
|
||||
// Reusing the same logic to play the terrain sound on landing
|
||||
string sound_file = get_footstep_sound(current_x, base_end, grass_end);
|
||||
|
||||
if(file_exists(sound_file)) {
|
||||
p.play_stationary(sound_file, false);
|
||||
}
|
||||
}
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
void perform_attack(int current_x) {
|
||||
if (sling_equipped) {
|
||||
perform_sling_attack(current_x);
|
||||
} else if (spear_equipped) {
|
||||
perform_spear_attack(current_x);
|
||||
} else if (axe_equipped) {
|
||||
perform_axe_attack(current_x);
|
||||
} else {
|
||||
// Optional: Punch logic
|
||||
// p.play_stationary("sounds/weapons/fist_swing.ogg", false);
|
||||
}
|
||||
}
|
||||
|
||||
int attack_enemy_ranged(int start_x, int end_x, int damage) {
|
||||
for (int check_x = start_x; check_x <= end_x; check_x++) {
|
||||
// Check for bandits first (priority during daytime)
|
||||
if (damage_bandit_at(check_x, damage)) {
|
||||
return check_x;
|
||||
}
|
||||
// Then check zombies
|
||||
if (damage_zombie_at(check_x, damage)) {
|
||||
return check_x;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool attack_enemy(int target_x, int damage) {
|
||||
// Check for bandits first
|
||||
if (damage_bandit_at(target_x, damage)) {
|
||||
return true;
|
||||
}
|
||||
// Then check zombies
|
||||
return damage_zombie_at(target_x, damage);
|
||||
}
|
||||
|
||||
void perform_spear_attack(int current_x) {
|
||||
p.play_stationary("sounds/weapons/spear_swing.ogg", false);
|
||||
|
||||
int hit_pos = attack_enemy_ranged(current_x - 1, current_x + 1, SPEAR_DAMAGE);
|
||||
if (hit_pos != -1) {
|
||||
p.play_stationary("sounds/weapons/spear_hit.ogg", false);
|
||||
// Play hit sound based on enemy type (both use same hit sound for now)
|
||||
if (get_bandit_at(hit_pos) != null) {
|
||||
play_1d_with_volume_step("sounds/enemies/zombie_hit.ogg", x, hit_pos, false, BANDIT_SOUND_VOLUME_STEP);
|
||||
} else {
|
||||
play_1d_with_volume_step("sounds/enemies/zombie_hit.ogg", x, hit_pos, false, ZOMBIE_SOUND_VOLUME_STEP);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Hit tree with spear (sound only, 0 damage)
|
||||
hit_tree_with_spear(current_x);
|
||||
}
|
||||
|
||||
void perform_axe_attack(int current_x) {
|
||||
p.play_stationary("sounds/weapons/axe_swing.ogg", false);
|
||||
|
||||
if (attack_enemy(current_x, AXE_DAMAGE)) {
|
||||
p.play_stationary("sounds/weapons/axe_hit.ogg", false);
|
||||
// Play hit sound based on enemy type
|
||||
if (get_bandit_at(current_x) != null) {
|
||||
play_1d_with_volume_step("sounds/enemies/zombie_hit.ogg", x, current_x, false, BANDIT_SOUND_VOLUME_STEP);
|
||||
} else {
|
||||
play_1d_with_volume_step("sounds/enemies/zombie_hit.ogg", x, current_x, false, ZOMBIE_SOUND_VOLUME_STEP);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Axe Attack Logic
|
||||
// Range: current_x (0 distance)
|
||||
// Damage: 4
|
||||
// Target: Trees
|
||||
damage_tree(current_x, 4);
|
||||
}
|
||||
|
||||
void perform_sling_attack(int current_x) {
|
||||
// Sling uses charge/release mechanism, not direct attack
|
||||
// This function is called from main loop when sling is released
|
||||
release_sling_attack(current_x);
|
||||
}
|
||||
|
||||
void hit_tree_with_spear(int target_x) {
|
||||
Tree@ target = get_tree_at(target_x);
|
||||
if (@target != null && !target.is_chopped) {
|
||||
p.play_stationary("sounds/weapons/spear_hit.ogg", false);
|
||||
}
|
||||
}
|
||||
|
||||
void update_sling_charge() {
|
||||
int elapsed = sling_charge_timer.elapsed;
|
||||
int cycle_time = 1500; // 1.5 seconds
|
||||
int stage_duration = 500; // 0.5 seconds per stage
|
||||
|
||||
// Loop the charge cycle
|
||||
int time_in_cycle = elapsed % cycle_time;
|
||||
|
||||
// Determine stage: 0=low, 1=in-range, 2=high
|
||||
int current_stage = time_in_cycle / stage_duration;
|
||||
|
||||
// Play stage indicator sounds (only once per stage change)
|
||||
if (current_stage != last_sling_stage) {
|
||||
if (current_stage == 1) {
|
||||
// Entering in-range window
|
||||
p.play_stationary("sounds/weapons/sling_low_range.ogg", false);
|
||||
} else if (current_stage == 2) {
|
||||
// Entering too-high window
|
||||
p.play_stationary("sounds/weapons/sling_high_range.ogg", false);
|
||||
}
|
||||
last_sling_stage = current_stage;
|
||||
}
|
||||
}
|
||||
|
||||
void release_sling_attack(int player_x) {
|
||||
// Consume stone
|
||||
inv_stones--;
|
||||
|
||||
int elapsed = sling_charge_timer.elapsed;
|
||||
int cycle_time = 1500;
|
||||
int time_in_cycle = elapsed % cycle_time;
|
||||
int stage = time_in_cycle / 500; // 0=low, 1=in-range, 2=high
|
||||
|
||||
// Only hit if released during in-range window (stage 1)
|
||||
if (stage != 1) {
|
||||
screen_reader_speak("Stone missed.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find target in facing direction (5 tiles)
|
||||
int search_direction = (facing == 1) ? 1 : -1;
|
||||
int target_x = -1;
|
||||
bool hit_bandit = false;
|
||||
|
||||
// Priority: Find nearest enemy (bandit or zombie) first
|
||||
for (int dist = 1; dist <= 5; dist++) {
|
||||
int check_x = player_x + (dist * search_direction);
|
||||
if (check_x < 0 || check_x >= MAP_SIZE) break;
|
||||
|
||||
// Check for bandit first
|
||||
Bandit@ bandit = get_bandit_at(check_x);
|
||||
if (bandit != null) {
|
||||
target_x = check_x;
|
||||
hit_bandit = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Then check for zombie
|
||||
Zombie@ zombie = get_zombie_at(check_x);
|
||||
if (zombie != null) {
|
||||
target_x = check_x;
|
||||
hit_bandit = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no enemy found, check for trees (but don't damage them)
|
||||
if (target_x == -1) {
|
||||
for (int dist = 1; dist <= 5; dist++) {
|
||||
int check_x = player_x + (dist * search_direction);
|
||||
if (check_x < 0 || check_x >= MAP_SIZE) break;
|
||||
Tree@ tree = get_tree_at(check_x);
|
||||
if (tree != null && !tree.is_chopped) {
|
||||
// Stone hits tree but doesn't damage it
|
||||
p.play_1d("sounds/weapons/sling_hit.ogg", player_x, check_x, false);
|
||||
screen_reader_speak("Stone hit tree at " + check_x + ".", true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No target found
|
||||
if (target_x == -1) {
|
||||
screen_reader_speak("Stone missed.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
int damage = random(SLING_DAMAGE_MIN, SLING_DAMAGE_MAX);
|
||||
|
||||
// Damage the correct enemy type
|
||||
if (hit_bandit) {
|
||||
damage_bandit_at(target_x, damage);
|
||||
p.play_1d("sounds/weapons/sling_hit.ogg", player_x, target_x, false);
|
||||
play_1d_with_volume_step("sounds/enemies/zombie_hit.ogg", player_x, target_x, false, BANDIT_SOUND_VOLUME_STEP);
|
||||
} else {
|
||||
damage_zombie_at(target_x, damage);
|
||||
p.play_1d("sounds/weapons/sling_hit.ogg", player_x, target_x, false);
|
||||
play_1d_with_volume_step("sounds/enemies/zombie_hit.ogg", player_x, target_x, false, ZOMBIE_SOUND_VOLUME_STEP);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Map configuration
|
||||
int MAP_SIZE = 35;
|
||||
const int BASE_END = 4; // 0-4
|
||||
const int GRASS_END = 19; // 5-19
|
||||
const int GRAVEL_END = 34; // 20-34
|
||||
|
||||
// Expansion configuration
|
||||
const int EXPANSION_SIZE = 30;
|
||||
const int EXPANSION_CHANCE = 30; // 30% chance per hour before noon
|
||||
int expanded_area_start = -1; // -1 means not expanded yet
|
||||
int expanded_area_end = -1;
|
||||
|
||||
// Movement configuration
|
||||
int movetime = 400; // Time between steps/movements
|
||||
int walk_speed = 400;
|
||||
int jump_speed = 170;
|
||||
const int MAX_ITEM_STACK = 9;
|
||||
|
||||
// Weapon damage
|
||||
const int SPEAR_DAMAGE = 3;
|
||||
const int AXE_DAMAGE = 4;
|
||||
const int SLING_DAMAGE_MIN = 5;
|
||||
const int SLING_DAMAGE_MAX = 8;
|
||||
|
||||
// Zombie settings
|
||||
const int ZOMBIE_HEALTH = 12;
|
||||
const int ZOMBIE_MAX_COUNT = 5;
|
||||
const int ZOMBIE_MOVE_INTERVAL = 1000;
|
||||
const int ZOMBIE_ATTACK_INTERVAL = 1600;
|
||||
const int ZOMBIE_DAMAGE_MIN = 4;
|
||||
const int ZOMBIE_DAMAGE_MAX = 6;
|
||||
const int ZOMBIE_GROAN_MIN_DELAY = 2000;
|
||||
const int ZOMBIE_GROAN_MAX_DELAY = 3000;
|
||||
const int ZOMBIE_FOOTSTEP_MAX_DISTANCE = 5;
|
||||
const float ZOMBIE_SOUND_VOLUME_STEP = 3.0;
|
||||
const int ZOMBIE_ATTACK_MAX_HEIGHT = 6;
|
||||
|
||||
// Barricade configuration
|
||||
const int BARRICADE_BASE_HEALTH = 100;
|
||||
const int BARRICADE_MAX_HEALTH = 500;
|
||||
const int BARRICADE_STICK_COST = 3;
|
||||
const int BARRICADE_STICK_HEALTH = 10;
|
||||
const int BARRICADE_VINE_COST = 5;
|
||||
const int BARRICADE_VINE_HEALTH = 15;
|
||||
const int BARRICADE_LOG_COST = 1;
|
||||
const int BARRICADE_LOG_HEALTH = 30;
|
||||
const int BARRICADE_STONE_COST = 5;
|
||||
const int BARRICADE_STONE_HEALTH = 20;
|
||||
|
||||
// Bandit settings
|
||||
const int BANDIT_HEALTH = 4;
|
||||
const int BANDIT_MAX_COUNT = 3;
|
||||
const int BANDIT_MOVE_INTERVAL_MIN = 600;
|
||||
const int BANDIT_MOVE_INTERVAL_MAX = 800;
|
||||
const int BANDIT_ATTACK_INTERVAL = 1200;
|
||||
const int BANDIT_DAMAGE_MIN = 1;
|
||||
const int BANDIT_DAMAGE_MAX = 2;
|
||||
const int BANDIT_ALERT_MIN_DELAY = 3000;
|
||||
const int BANDIT_ALERT_MAX_DELAY = 5000;
|
||||
const int BANDIT_FOOTSTEP_MAX_DISTANCE = 7;
|
||||
const float BANDIT_SOUND_VOLUME_STEP = 3.0;
|
||||
const int BANDIT_ATTACK_MAX_HEIGHT = 6;
|
||||
const int INVASION_DURATION_HOURS = 1;
|
||||
@@ -0,0 +1,456 @@
|
||||
// Tree Object
|
||||
class Tree {
|
||||
int position;
|
||||
int sticks;
|
||||
int vines;
|
||||
int health;
|
||||
int height; // Height in feet
|
||||
int sound_handle;
|
||||
timer regen_timer;
|
||||
bool depleted;
|
||||
bool is_chopped;
|
||||
int minutes_since_depletion; // Track minutes for gradual replenishment
|
||||
|
||||
Tree(int pos) {
|
||||
position = pos;
|
||||
depleted = false;
|
||||
is_chopped = false;
|
||||
sound_handle = -1;
|
||||
minutes_since_depletion = 0;
|
||||
refill();
|
||||
}
|
||||
|
||||
void refill() {
|
||||
sticks = random(2, 3);
|
||||
vines = random(1, 2);
|
||||
health = random(25, 40);
|
||||
height = random(8, 30); // Random height between 8 and 30 feet
|
||||
depleted = false;
|
||||
is_chopped = false;
|
||||
minutes_since_depletion = 0;
|
||||
}
|
||||
|
||||
void respawn(int grass_start, int grass_end) {
|
||||
if (sound_handle != -1) {
|
||||
p.destroy_sound(sound_handle);
|
||||
sound_handle = -1;
|
||||
}
|
||||
position = random(grass_start, grass_end);
|
||||
refill();
|
||||
}
|
||||
|
||||
void update() {
|
||||
// Only play tree sound if not chopped and within 3 tiles distance (2 tiles on either side)
|
||||
if (!is_chopped) {
|
||||
if (abs(x - position) <= 3) {
|
||||
if (sound_handle == -1 || !p.sound_is_active(sound_handle)) {
|
||||
sound_handle = p.play_1d("sounds/environment/tree.ogg", x, position, true);
|
||||
}
|
||||
} else {
|
||||
if (sound_handle != -1) {
|
||||
p.destroy_sound(sound_handle);
|
||||
sound_handle = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void try_regen() {
|
||||
// Skip if tree is fully stocked
|
||||
if (!depleted && !is_chopped) return;
|
||||
|
||||
// Check every minute (60000ms)
|
||||
if (regen_timer.elapsed < 60000) return;
|
||||
|
||||
// Advance to next minute
|
||||
regen_timer.restart();
|
||||
minutes_since_depletion++;
|
||||
|
||||
if (is_chopped) {
|
||||
if (minutes_since_depletion >= 5) {
|
||||
respawn(BASE_END + 1, GRASS_END);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// At minute 5, completely refill
|
||||
if (minutes_since_depletion >= 5) {
|
||||
refill();
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if already fully stocked
|
||||
if (sticks >= 3 && vines >= 2) {
|
||||
depleted = false;
|
||||
is_chopped = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine base chance based on minutes elapsed
|
||||
int base_chance = 0;
|
||||
if (minutes_since_depletion == 1) base_chance = 25;
|
||||
else if (minutes_since_depletion == 2) base_chance = 50;
|
||||
else if (minutes_since_depletion == 3) base_chance = 75;
|
||||
else if (minutes_since_depletion == 4) base_chance = 100;
|
||||
|
||||
// Try to add items with decreasing probability
|
||||
int current_chance = base_chance;
|
||||
while (current_chance >= 25) {
|
||||
// Check if we can add anything
|
||||
if (sticks >= 3 && vines >= 2) break;
|
||||
|
||||
// Roll for success
|
||||
int roll = random(1, 100);
|
||||
if (roll <= current_chance) {
|
||||
// Decide what to add (70% stick, 30% vine)
|
||||
int item_roll = random(1, 100);
|
||||
if (item_roll <= 70 && sticks < 3) {
|
||||
// Add stick
|
||||
sticks++;
|
||||
} else if (vines < 2) {
|
||||
// Add vine
|
||||
vines++;
|
||||
} else if (sticks < 3) {
|
||||
// Vine is full but stick isn't, add stick
|
||||
sticks++;
|
||||
}
|
||||
} else {
|
||||
// Failed roll, stop trying
|
||||
break;
|
||||
}
|
||||
|
||||
// Reduce chance by 25% for next attempt (minimum 25%)
|
||||
current_chance -= 25;
|
||||
}
|
||||
|
||||
// Mark as no longer depleted if we have at least one item
|
||||
if (sticks > 0 || vines > 0) {
|
||||
depleted = false;
|
||||
is_chopped = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Tree@[] trees;
|
||||
|
||||
void spawn_trees(int grass_start, int grass_end) {
|
||||
int pos = random(grass_start, grass_end);
|
||||
Tree@ t = Tree(pos);
|
||||
trees.insert_last(t);
|
||||
}
|
||||
|
||||
void update_environment() {
|
||||
for(uint i = 0; i < trees.length(); i++) {
|
||||
trees[i].update();
|
||||
trees[i].try_regen();
|
||||
}
|
||||
}
|
||||
|
||||
Tree@ get_tree_at(int target_x) {
|
||||
for(uint i=0; i<trees.length(); i++) {
|
||||
if(trees[i].position == target_x) {
|
||||
return @trees[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void damage_tree(int target_x, int damage) {
|
||||
Tree@ target = null;
|
||||
for(uint i=0; i<trees.length(); i++) {
|
||||
if(trees[i].position == target_x) {
|
||||
@target = @trees[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(@target != null) {
|
||||
if(!target.is_chopped) {
|
||||
target.health -= damage;
|
||||
p.play_stationary("sounds/weapons/axe_hit.ogg", false);
|
||||
|
||||
if(target.health <= 0) {
|
||||
target.is_chopped = true;
|
||||
target.depleted = true;
|
||||
target.regen_timer.restart();
|
||||
target.minutes_since_depletion = 0;
|
||||
|
||||
// Stop the looping sound
|
||||
if (target.sound_handle != -1) {
|
||||
p.destroy_sound(target.sound_handle);
|
||||
target.sound_handle = -1;
|
||||
}
|
||||
|
||||
// Play the falling sound at the tree's position
|
||||
p.play_1d("sounds/items/tree.ogg", x, target.position, false);
|
||||
|
||||
int sticks_dropped = random(1, 3);
|
||||
int vines_dropped = random(1, 2);
|
||||
int sticks_added = add_to_stack(inv_sticks, sticks_dropped);
|
||||
int vines_added = add_to_stack(inv_vines, vines_dropped);
|
||||
int logs_added = add_to_stack(inv_logs, 1);
|
||||
inv_sticks += sticks_added;
|
||||
inv_vines += vines_added;
|
||||
inv_logs += logs_added;
|
||||
|
||||
string drop_message = "Tree fell!";
|
||||
if (sticks_added > 0 || vines_added > 0 || logs_added > 0) {
|
||||
string log_label = (logs_added == 1) ? " log" : " logs";
|
||||
drop_message += " Got " + sticks_added + " sticks, " + vines_added + " vines, and " + logs_added + log_label + ".";
|
||||
}
|
||||
if (sticks_added < sticks_dropped || vines_added < vines_dropped || logs_added < 1) {
|
||||
drop_message += " Inventory full.";
|
||||
}
|
||||
|
||||
p.play_stationary("sounds/items/stick.ogg", false);
|
||||
screen_reader_speak(drop_message, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void perform_search(int current_x)
|
||||
{
|
||||
// Check for Snares nearby (Current or Adjacent)
|
||||
// "Shift beside the snare will collect the snare" -> adjacent
|
||||
// We check current and +/- 1
|
||||
for (int check_x = current_x - 1; check_x <= current_x + 1; check_x++) {
|
||||
// Skip current x? User said "beside". If on top, it breaks.
|
||||
// But if I stand adjacent and shift...
|
||||
if (check_x == current_x) continue; // Safety against collecting own snare you stand on? (Collision happens on move)
|
||||
// Actually, collision happens when *moving onto* it. If you placed it, you are on it.
|
||||
// If active is false (just placed), you can pick it up.
|
||||
// If active is true (you moved away), moving back breaks it.
|
||||
// So checking adjacent is correct.
|
||||
|
||||
WorldSnare@ s = get_snare_at(check_x);
|
||||
if (s != null) {
|
||||
if (s.has_catch) {
|
||||
if (inv_small_game >= MAX_ITEM_STACK) {
|
||||
screen_reader_speak("You can't carry any more small game.", true);
|
||||
return;
|
||||
}
|
||||
if (inv_snares >= MAX_ITEM_STACK) {
|
||||
screen_reader_speak("You can't carry any more snares.", true);
|
||||
return;
|
||||
}
|
||||
inv_small_game++;
|
||||
inv_small_game_types.insert_last(s.catch_type);
|
||||
inv_snares++; // Recover snare
|
||||
screen_reader_speak("Collected " + s.catch_type + " and snare.", true);
|
||||
} else {
|
||||
if (inv_snares >= MAX_ITEM_STACK) {
|
||||
screen_reader_speak("You can't carry any more snares.", true);
|
||||
return;
|
||||
}
|
||||
inv_snares++; // Recover snare
|
||||
screen_reader_speak("Collected snare.", true);
|
||||
}
|
||||
p.play_stationary("sounds/items/miscellaneous.ogg", false);
|
||||
remove_snare_at(check_x);
|
||||
return; // Action taken, stop searching
|
||||
}
|
||||
}
|
||||
|
||||
// Gravel Area - Stones (20-34)
|
||||
if (current_x >= 20 && current_x <= 34)
|
||||
{
|
||||
if (inv_stones < MAX_ITEM_STACK)
|
||||
{
|
||||
inv_stones++;
|
||||
p.play_stationary("sounds/items/stone.ogg", false);
|
||||
screen_reader_speak("Found a stone.", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
screen_reader_speak("You can't carry any more stones.", true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Grass Area - Trees (Sticks/Vines) (5-19)
|
||||
if (current_x >= 5 && current_x <= 19)
|
||||
{
|
||||
Tree@ nearest = null;
|
||||
for(uint i=0; i<trees.length(); i++)
|
||||
{
|
||||
if(trees[i].position >= current_x - 1 && trees[i].position <= current_x + 1)
|
||||
{
|
||||
@nearest = @trees[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(@nearest != null)
|
||||
{
|
||||
if(nearest.is_chopped) {
|
||||
screen_reader_speak("This tree has been cut down.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (nearest.depleted) {
|
||||
screen_reader_speak("This tree is empty.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
if(nearest.sticks > 0 || nearest.vines > 0)
|
||||
{
|
||||
bool find_stick = (nearest.vines <= 0) || (nearest.sticks > 0 && random(0, 1) == 0);
|
||||
|
||||
if(find_stick)
|
||||
{
|
||||
if (inv_sticks >= MAX_ITEM_STACK) {
|
||||
screen_reader_speak("You can't carry any more sticks.", true);
|
||||
return;
|
||||
}
|
||||
nearest.sticks--;
|
||||
inv_sticks++;
|
||||
p.play_stationary("sounds/items/stick.ogg", false);
|
||||
screen_reader_speak("Found a stick.", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (inv_vines >= MAX_ITEM_STACK) {
|
||||
screen_reader_speak("You can't carry any more vines.", true);
|
||||
return;
|
||||
}
|
||||
nearest.vines--;
|
||||
inv_vines++;
|
||||
p.play_stationary("sounds/items/vine.ogg", false);
|
||||
screen_reader_speak("Found a vine.", true);
|
||||
}
|
||||
|
||||
if(nearest.sticks == 0 && nearest.vines == 0) {
|
||||
nearest.depleted = true;
|
||||
nearest.regen_timer.restart();
|
||||
nearest.minutes_since_depletion = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
screen_reader_speak("This area has nothing left.", true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
screen_reader_speak("Found nothing.", true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
screen_reader_speak("Found nothing.", true);
|
||||
}
|
||||
|
||||
// Climbing functions
|
||||
void start_climbing_tree(int target_x) {
|
||||
Tree@ tree = get_tree_at(target_x);
|
||||
if (tree == null || tree.is_chopped) {
|
||||
return;
|
||||
}
|
||||
|
||||
climbing = true;
|
||||
climb_target_y = tree.height;
|
||||
climb_timer.restart();
|
||||
screen_reader_speak("Started climbing tree. Height is " + tree.height + " feet.", true);
|
||||
}
|
||||
|
||||
void update_climbing() {
|
||||
if (!climbing) return;
|
||||
|
||||
// Climb at 1 foot per 500ms
|
||||
if (climb_timer.elapsed > 500) {
|
||||
climb_timer.restart();
|
||||
|
||||
// Climbing up
|
||||
if (y < climb_target_y) {
|
||||
y++;
|
||||
p.play_stationary("sounds/actions/climb_tree.ogg", false);
|
||||
|
||||
if (y >= climb_target_y) {
|
||||
climbing = false;
|
||||
screen_reader_speak("Reached the top at " + y + " feet.", true);
|
||||
}
|
||||
}
|
||||
// Climbing down
|
||||
else if (y > climb_target_y) {
|
||||
y--;
|
||||
p.play_stationary("sounds/actions/climb_tree.ogg", false);
|
||||
|
||||
if (y <= 0) {
|
||||
climbing = false;
|
||||
y = 0;
|
||||
screen_reader_speak("Safely reached the ground.", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void climb_down_tree() {
|
||||
if (y == 0 || climbing) return;
|
||||
|
||||
climbing = true;
|
||||
climb_target_y = 0;
|
||||
climb_timer.restart();
|
||||
screen_reader_speak("Climbing down.", true);
|
||||
}
|
||||
|
||||
void start_falling() {
|
||||
if (y <= 0 || falling) return;
|
||||
|
||||
falling = true;
|
||||
fall_start_y = y; // Remember where we started falling from
|
||||
fall_timer.restart();
|
||||
|
||||
// Start looping falling sound
|
||||
fall_sound_handle = p.play_stationary("sounds/actions/falling.ogg", true);
|
||||
}
|
||||
|
||||
void update_falling() {
|
||||
if (!falling) return;
|
||||
|
||||
// Fall faster than climbing - 1 foot per 100ms
|
||||
if (fall_timer.elapsed > 100) {
|
||||
fall_timer.restart();
|
||||
|
||||
if (y > 0) {
|
||||
y--;
|
||||
|
||||
// Restart falling sound with decreasing pitch each foot
|
||||
if (fall_sound_handle != -1) {
|
||||
p.destroy_sound(fall_sound_handle);
|
||||
}
|
||||
|
||||
// Pitch ranges from 100 (high up) to 50 (near ground)
|
||||
// Calculate based on current y position
|
||||
float pitch_percent = 50.0 + (50.0 * (y / 30.0));
|
||||
if (pitch_percent < 50.0) pitch_percent = 50.0;
|
||||
if (pitch_percent > 100.0) pitch_percent = 100.0;
|
||||
|
||||
fall_sound_handle = p.play_stationary_extended("sounds/actions/falling.ogg", true, 0, 0, 0, pitch_percent);
|
||||
} else {
|
||||
// Hit the ground
|
||||
falling = false;
|
||||
|
||||
// Stop falling sound
|
||||
if (fall_sound_handle != -1) {
|
||||
p.destroy_sound(fall_sound_handle);
|
||||
fall_sound_handle = -1;
|
||||
}
|
||||
|
||||
p.play_stationary("sounds/actions/hit_ground.ogg", false);
|
||||
|
||||
// Calculate fall damage
|
||||
int fall_height = fall_start_y;
|
||||
if (fall_height > 10) {
|
||||
int damage = 0;
|
||||
for (int i = 10; i < fall_height; i++) {
|
||||
damage += random(1, 3);
|
||||
}
|
||||
player_health -= damage;
|
||||
screen_reader_speak("Fell " + fall_height + " feet! Took " + damage + " damage. " + player_health + " health remaining.", true);
|
||||
} else {
|
||||
screen_reader_speak("Landed safely.", true);
|
||||
}
|
||||
|
||||
fall_start_y = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,886 @@
|
||||
// Inventory
|
||||
int inv_stones = 0;
|
||||
int inv_sticks = 0;
|
||||
int inv_vines = 0;
|
||||
int inv_logs = 0;
|
||||
int inv_small_game = 0; // Total small game caught (any type)
|
||||
string[] inv_small_game_types; // Array to track what types of small game we have
|
||||
|
||||
int inv_meat = 0;
|
||||
int inv_skins = 0;
|
||||
|
||||
int inv_spears = 0;
|
||||
int inv_snares = 0;
|
||||
int inv_axes = 0;
|
||||
int inv_knives = 0;
|
||||
int inv_fishing_poles = 0;
|
||||
int inv_slings = 0;
|
||||
|
||||
bool spear_equipped = false;
|
||||
bool axe_equipped = false;
|
||||
bool sling_equipped = false;
|
||||
|
||||
int add_to_stack(int current, int amount) {
|
||||
if (amount <= 0) return 0;
|
||||
int space = MAX_ITEM_STACK - current;
|
||||
if (space <= 0) return 0;
|
||||
if (amount > space) return space;
|
||||
return amount;
|
||||
}
|
||||
|
||||
void check_crafting_menu(int x, int base_end_tile) {
|
||||
if (x <= base_end_tile) {
|
||||
if (key_pressed(KEY_C)) {
|
||||
run_crafting_menu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void check_inventory_keys(int x) {
|
||||
if (key_pressed(KEY_I)) {
|
||||
run_inventory_menu();
|
||||
}
|
||||
}
|
||||
|
||||
void check_action_menu(int x) {
|
||||
if (key_pressed(KEY_A)) {
|
||||
run_action_menu(x);
|
||||
}
|
||||
}
|
||||
|
||||
void menu_background_tick() {
|
||||
update_time();
|
||||
update_environment();
|
||||
update_snares();
|
||||
update_fires();
|
||||
update_zombies();
|
||||
|
||||
// Fire damage check (only if not jumping)
|
||||
WorldFire@ fire_on_tile = get_fire_at(x);
|
||||
if (fire_on_tile != null && !jumping && fire_damage_timer.elapsed > 1000) {
|
||||
player_health--;
|
||||
fire_damage_timer.restart();
|
||||
screen_reader_speak("Burning! " + player_health + " health remaining.", true);
|
||||
}
|
||||
|
||||
// Healing in base area
|
||||
if (x <= BASE_END && player_health < max_health) {
|
||||
WorldHerbGarden@ herb_garden = get_herb_garden_at_base();
|
||||
int heal_interval = (herb_garden != null) ? 30000 : 150000; // 30 seconds with garden, 2.5 minutes without
|
||||
|
||||
if (healing_timer.elapsed > heal_interval) {
|
||||
player_health++;
|
||||
healing_timer.restart();
|
||||
screen_reader_speak(player_health + " health.", true);
|
||||
}
|
||||
}
|
||||
|
||||
// Death check
|
||||
if (player_health <= 0) {
|
||||
screen_reader_speak("You have died.", true);
|
||||
wait(2000);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
void show_inventory() {
|
||||
string info = "Inventory: ";
|
||||
info += inv_sticks + " sticks, ";
|
||||
info += inv_vines + " vines, ";
|
||||
info += inv_stones + " stones, ";
|
||||
info += inv_logs + " logs, ";
|
||||
info += inv_small_game + " small game, ";
|
||||
info += inv_meat + " meat, ";
|
||||
info += inv_skins + " skins. ";
|
||||
info += "Tools: " + inv_spears + " spears, " + inv_slings + " slings, " + inv_axes + " axes, " + inv_snares + " snares, " + inv_knives + " knives, " + inv_fishing_poles + " fishing poles.";
|
||||
screen_reader_speak(info, true);
|
||||
}
|
||||
|
||||
void run_inventory_menu() {
|
||||
screen_reader_speak("Inventory menu.", true);
|
||||
|
||||
int selection = 0;
|
||||
string[] options = {
|
||||
"Sticks: " + inv_sticks,
|
||||
"Vines: " + inv_vines,
|
||||
"Stones: " + inv_stones,
|
||||
"Logs: " + inv_logs,
|
||||
"Small Game: " + inv_small_game,
|
||||
"Meat: " + inv_meat,
|
||||
"Skins: " + inv_skins,
|
||||
"Spears: " + inv_spears,
|
||||
"Slings: " + inv_slings,
|
||||
"Axes: " + inv_axes,
|
||||
"Snares: " + inv_snares,
|
||||
"Knives: " + inv_knives,
|
||||
"Fishing Poles: " + inv_fishing_poles
|
||||
};
|
||||
|
||||
while(true) {
|
||||
wait(5);
|
||||
menu_background_tick();
|
||||
if (key_pressed(KEY_ESCAPE)) {
|
||||
screen_reader_speak("Closed.", true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_DOWN)) {
|
||||
selection++;
|
||||
if (selection >= options.length()) selection = 0;
|
||||
screen_reader_speak(options[selection], true);
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_UP)) {
|
||||
selection--;
|
||||
if (selection < 0) selection = options.length() - 1;
|
||||
screen_reader_speak(options[selection], true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void try_place_snare(int x) {
|
||||
if (inv_snares > 0) {
|
||||
// Prevent placing if one already exists here
|
||||
if (get_snare_at(x) != null) {
|
||||
screen_reader_speak("There is already a snare here.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
inv_snares--;
|
||||
add_world_snare(x);
|
||||
screen_reader_speak("Snare set.", true);
|
||||
} else {
|
||||
screen_reader_speak("No snares to place.", true);
|
||||
}
|
||||
}
|
||||
|
||||
void try_feed_fire_stick(WorldFire@ fire) {
|
||||
if (inv_sticks > 0 && fire != null) {
|
||||
inv_sticks--;
|
||||
fire.add_fuel(300000); // 5 minutes
|
||||
screen_reader_speak("You dump an arm load of sticks into the fire.", true);
|
||||
p.play_stationary("sounds/actions/fed_fire.ogg", false);
|
||||
}
|
||||
}
|
||||
|
||||
void try_feed_fire_vine(WorldFire@ fire) {
|
||||
if (inv_vines > 0 && fire != null) {
|
||||
inv_vines--;
|
||||
fire.add_fuel(60000); // 1 minute
|
||||
screen_reader_speak("You toss a fiew vines and leaves into the fire.", true);
|
||||
p.play_stationary("sounds/actions/fed_fire.ogg", false);
|
||||
}
|
||||
}
|
||||
|
||||
void try_feed_fire_log(WorldFire@ fire) {
|
||||
if (inv_logs > 0 && fire != null) {
|
||||
inv_logs--;
|
||||
fire.add_fuel(720000); // 12 minutes
|
||||
screen_reader_speak("You heave a log into the fire.", true);
|
||||
p.play_stationary("sounds/actions/fed_fire.ogg", false);
|
||||
}
|
||||
}
|
||||
|
||||
void check_equipment_menu() {
|
||||
if (key_pressed(KEY_E)) {
|
||||
// Check if player has any equipment
|
||||
if (inv_spears == 0 && inv_axes == 0 && inv_slings == 0) {
|
||||
screen_reader_speak("Nothing to equip.", true);
|
||||
} else {
|
||||
run_equipment_menu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void run_action_menu(int x) {
|
||||
screen_reader_speak("Action menu.", true);
|
||||
|
||||
int selection = 0;
|
||||
string[] options;
|
||||
int[] action_types; // Track what action each option corresponds to
|
||||
|
||||
// Check if fire is nearby
|
||||
WorldFire@ nearby_fire = get_fire_near(x);
|
||||
bool can_feed_fire = nearby_fire != null;
|
||||
|
||||
// Build menu options dynamically
|
||||
options.insert_last("Place Snare");
|
||||
action_types.insert_last(0);
|
||||
|
||||
if (can_feed_fire) {
|
||||
if (inv_sticks > 0) {
|
||||
options.insert_last("Feed fire with stick");
|
||||
action_types.insert_last(1);
|
||||
}
|
||||
if (inv_vines > 0) {
|
||||
options.insert_last("Feed fire with vine");
|
||||
action_types.insert_last(2);
|
||||
}
|
||||
if (inv_logs > 0) {
|
||||
options.insert_last("Feed fire with log");
|
||||
action_types.insert_last(3);
|
||||
}
|
||||
}
|
||||
|
||||
while(true) {
|
||||
wait(5);
|
||||
menu_background_tick();
|
||||
if (key_pressed(KEY_ESCAPE)) {
|
||||
screen_reader_speak("Closed.", true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_DOWN)) {
|
||||
selection++;
|
||||
if (selection >= options.length()) selection = 0;
|
||||
screen_reader_speak(options[selection], true);
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_UP)) {
|
||||
selection--;
|
||||
if (selection < 0) selection = options.length() - 1;
|
||||
screen_reader_speak(options[selection], true);
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_RETURN)) {
|
||||
int action = action_types[selection];
|
||||
if (action == 0) {
|
||||
try_place_snare(x);
|
||||
} else if (action == 1) {
|
||||
try_feed_fire_stick(nearby_fire);
|
||||
} else if (action == 2) {
|
||||
try_feed_fire_vine(nearby_fire);
|
||||
} else if (action == 3) {
|
||||
try_feed_fire_log(nearby_fire);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void run_crafting_menu() {
|
||||
screen_reader_speak("Crafting menu.", true);
|
||||
|
||||
int selection = 0;
|
||||
string[] categories = {"Weapons", "Tools", "Buildings", "Barricade"};
|
||||
|
||||
while(true) {
|
||||
wait(5);
|
||||
menu_background_tick();
|
||||
if (key_pressed(KEY_ESCAPE)) {
|
||||
screen_reader_speak("Closed.", true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_DOWN)) {
|
||||
selection++;
|
||||
if (selection >= categories.length()) selection = 0;
|
||||
screen_reader_speak(categories[selection], true);
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_UP)) {
|
||||
selection--;
|
||||
if (selection < 0) selection = categories.length() - 1;
|
||||
screen_reader_speak(categories[selection], true);
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_RETURN)) {
|
||||
if (selection == 0) run_weapons_menu();
|
||||
else if (selection == 1) run_tools_menu();
|
||||
else if (selection == 2) run_buildings_menu();
|
||||
else if (selection == 3) run_barricade_menu();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void run_weapons_menu() {
|
||||
screen_reader_speak("Weapons.", true);
|
||||
|
||||
int selection = 0;
|
||||
string[] options = {
|
||||
"Spear (1 Stick, 1 Vine, 1 Stone) [Requires Knife]",
|
||||
"Sling (1 Skin, 2 Vines)"
|
||||
};
|
||||
|
||||
while(true) {
|
||||
wait(5);
|
||||
menu_background_tick();
|
||||
if (key_pressed(KEY_ESCAPE)) {
|
||||
screen_reader_speak("Closed.", true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_DOWN)) {
|
||||
selection++;
|
||||
if (selection >= options.length()) selection = 0;
|
||||
screen_reader_speak(options[selection], true);
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_UP)) {
|
||||
selection--;
|
||||
if (selection < 0) selection = options.length() - 1;
|
||||
screen_reader_speak(options[selection], true);
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_RETURN)) {
|
||||
if (selection == 0) craft_spear();
|
||||
else if (selection == 1) craft_sling();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void run_tools_menu() {
|
||||
screen_reader_speak("Tools.", true);
|
||||
|
||||
int selection = 0;
|
||||
string[] options = {
|
||||
"Stone Knife (2 Stones)",
|
||||
"Snare (1 Stick, 2 Vines)",
|
||||
"Stone Axe (1 Stick, 1 Vine, 2 Stones) [Requires Knife]",
|
||||
"Fishing Pole (1 Stick, 2 Vines)",
|
||||
"Butcher Small Game (1 Small Game) [Requires Knife and Fire nearby]"
|
||||
};
|
||||
|
||||
while(true) {
|
||||
wait(5);
|
||||
menu_background_tick();
|
||||
if (key_pressed(KEY_ESCAPE)) {
|
||||
screen_reader_speak("Closed.", true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_DOWN)) {
|
||||
selection++;
|
||||
if (selection >= options.length()) selection = 0;
|
||||
screen_reader_speak(options[selection], true);
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_UP)) {
|
||||
selection--;
|
||||
if (selection < 0) selection = options.length() - 1;
|
||||
screen_reader_speak(options[selection], true);
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_RETURN)) {
|
||||
if (selection == 0) craft_knife();
|
||||
else if (selection == 1) craft_snare();
|
||||
else if (selection == 2) craft_axe();
|
||||
else if (selection == 3) craft_fishing_pole();
|
||||
else if (selection == 4) butcher_small_game();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void run_buildings_menu() {
|
||||
screen_reader_speak("Buildings.", true);
|
||||
|
||||
int selection = 0;
|
||||
string[] options = {
|
||||
"Firepit (9 Stones)",
|
||||
"Fire (2 Sticks, 1 Log) [Requires Firepit]",
|
||||
"Herb Garden (9 Stones, 3 Vines, 2 Logs) [Base Only]"
|
||||
};
|
||||
|
||||
while(true) {
|
||||
wait(5);
|
||||
menu_background_tick();
|
||||
if (key_pressed(KEY_ESCAPE)) {
|
||||
screen_reader_speak("Closed.", true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_DOWN)) {
|
||||
selection++;
|
||||
if (selection >= options.length()) selection = 0;
|
||||
screen_reader_speak(options[selection], true);
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_UP)) {
|
||||
selection--;
|
||||
if (selection < 0) selection = options.length() - 1;
|
||||
screen_reader_speak(options[selection], true);
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_RETURN)) {
|
||||
if (selection == 0) craft_firepit();
|
||||
else if (selection == 1) craft_campfire();
|
||||
else if (selection == 2) craft_herb_garden();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void run_barricade_menu() {
|
||||
if (barricade_health >= BARRICADE_MAX_HEALTH) {
|
||||
screen_reader_speak("Barricade is already at full health.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
screen_reader_speak("Barricade.", true);
|
||||
|
||||
int selection = 0;
|
||||
string[] options;
|
||||
int[] action_types; // 0 = sticks, 1 = vines, 2 = log, 3 = stones
|
||||
|
||||
if (inv_sticks >= BARRICADE_STICK_COST) {
|
||||
options.insert_last("Reinforce with sticks (" + BARRICADE_STICK_COST + " sticks, +" + BARRICADE_STICK_HEALTH + " health)");
|
||||
action_types.insert_last(0);
|
||||
}
|
||||
if (inv_vines >= BARRICADE_VINE_COST) {
|
||||
options.insert_last("Reinforce with vines (" + BARRICADE_VINE_COST + " vines, +" + BARRICADE_VINE_HEALTH + " health)");
|
||||
action_types.insert_last(1);
|
||||
}
|
||||
if (inv_logs >= BARRICADE_LOG_COST) {
|
||||
options.insert_last("Reinforce with log (" + BARRICADE_LOG_COST + " log, +" + BARRICADE_LOG_HEALTH + " health)");
|
||||
action_types.insert_last(2);
|
||||
}
|
||||
if (inv_stones >= BARRICADE_STONE_COST) {
|
||||
options.insert_last("Reinforce with stones (" + BARRICADE_STONE_COST + " stones, +" + BARRICADE_STONE_HEALTH + " health)");
|
||||
action_types.insert_last(3);
|
||||
}
|
||||
|
||||
if (options.length() == 0) {
|
||||
screen_reader_speak("No materials to reinforce the barricade.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
while(true) {
|
||||
wait(5);
|
||||
menu_background_tick();
|
||||
if (key_pressed(KEY_ESCAPE)) {
|
||||
screen_reader_speak("Closed.", true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_DOWN)) {
|
||||
selection++;
|
||||
if (selection >= options.length()) selection = 0;
|
||||
screen_reader_speak(options[selection], true);
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_UP)) {
|
||||
selection--;
|
||||
if (selection < 0) selection = options.length() - 1;
|
||||
screen_reader_speak(options[selection], true);
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_RETURN)) {
|
||||
int action = action_types[selection];
|
||||
if (action == 0) reinforce_barricade_with_sticks();
|
||||
else if (action == 1) reinforce_barricade_with_vines();
|
||||
else if (action == 2) reinforce_barricade_with_log();
|
||||
else if (action == 3) reinforce_barricade_with_stones();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void run_equipment_menu() {
|
||||
screen_reader_speak("Equipment menu.", true);
|
||||
|
||||
int selection = 0;
|
||||
string[] options;
|
||||
int[] equipment_types; // 0 = spear, 1 = axe, 2 = sling
|
||||
|
||||
// Build menu dynamically based on what player has
|
||||
if (inv_spears > 0) {
|
||||
string status = spear_equipped ? " (equipped)" : "";
|
||||
options.insert_last("Spear" + status);
|
||||
equipment_types.insert_last(0);
|
||||
}
|
||||
if (inv_slings > 0) {
|
||||
string status = sling_equipped ? " (equipped)" : "";
|
||||
options.insert_last("Sling" + status);
|
||||
equipment_types.insert_last(2);
|
||||
}
|
||||
if (inv_axes > 0) {
|
||||
string status = axe_equipped ? " (equipped)" : "";
|
||||
options.insert_last("Stone Axe" + status);
|
||||
equipment_types.insert_last(1);
|
||||
}
|
||||
|
||||
while(true) {
|
||||
wait(5);
|
||||
menu_background_tick();
|
||||
if (key_pressed(KEY_ESCAPE)) {
|
||||
screen_reader_speak("Closed.", true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_DOWN)) {
|
||||
selection++;
|
||||
if (selection >= options.length()) selection = 0;
|
||||
screen_reader_speak(options[selection], true);
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_UP)) {
|
||||
selection--;
|
||||
if (selection < 0) selection = options.length() - 1;
|
||||
screen_reader_speak(options[selection], true);
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_RETURN)) {
|
||||
int equip_type = equipment_types[selection];
|
||||
|
||||
if (equip_type == 0) {
|
||||
// Spear
|
||||
if (!spear_equipped) {
|
||||
spear_equipped = true;
|
||||
axe_equipped = false;
|
||||
sling_equipped = false;
|
||||
screen_reader_speak("Spear equipped.", true);
|
||||
} else {
|
||||
spear_equipped = false;
|
||||
screen_reader_speak("Spear unequipped.", true);
|
||||
}
|
||||
} else if (equip_type == 1) {
|
||||
// Axe
|
||||
if (!axe_equipped) {
|
||||
axe_equipped = true;
|
||||
spear_equipped = false;
|
||||
sling_equipped = false;
|
||||
screen_reader_speak("Stone Axe equipped.", true);
|
||||
} else {
|
||||
axe_equipped = false;
|
||||
screen_reader_speak("Stone Axe unequipped.", true);
|
||||
}
|
||||
} else if (equip_type == 2) {
|
||||
// Sling
|
||||
if (!sling_equipped) {
|
||||
sling_equipped = true;
|
||||
spear_equipped = false;
|
||||
axe_equipped = false;
|
||||
screen_reader_speak("Sling equipped.", true);
|
||||
} else {
|
||||
sling_equipped = false;
|
||||
screen_reader_speak("Sling unequipped.", true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void simulate_crafting() {
|
||||
screen_reader_speak("Crafting...", true);
|
||||
timer t;
|
||||
int duration = 4000;
|
||||
int next_sound = 0;
|
||||
|
||||
while(t.elapsed < duration) {
|
||||
if(t.elapsed > next_sound) {
|
||||
float pitch = random(85, 115);
|
||||
p.play_stationary_extended("sounds/crafting.ogg", false, 0, 0, 0, pitch);
|
||||
next_sound = t.elapsed + 800;
|
||||
}
|
||||
wait(5);
|
||||
menu_background_tick();
|
||||
}
|
||||
p.play_stationary("sounds/crafting_complete.ogg", false);
|
||||
}
|
||||
|
||||
void craft_knife() {
|
||||
string missing = "";
|
||||
if (inv_stones < 2) missing += "2 stones ";
|
||||
|
||||
if (missing == "") {
|
||||
if (inv_knives >= MAX_ITEM_STACK) {
|
||||
screen_reader_speak("You can't carry any more stone knives.", true);
|
||||
return;
|
||||
}
|
||||
simulate_crafting();
|
||||
inv_stones -= 2;
|
||||
inv_knives++;
|
||||
screen_reader_speak("Crafted a Stone Knife.", true);
|
||||
} else {
|
||||
screen_reader_speak("Missing: " + missing, true);
|
||||
}
|
||||
}
|
||||
|
||||
void craft_spear() {
|
||||
string missing = "";
|
||||
if (inv_knives < 1) missing += "Stone Knife ";
|
||||
if (inv_sticks < 1) missing += "1 stick ";
|
||||
if (inv_vines < 1) missing += "1 vine ";
|
||||
if (inv_stones < 1) missing += "1 stone ";
|
||||
|
||||
if (missing == "") {
|
||||
if (inv_spears >= MAX_ITEM_STACK) {
|
||||
screen_reader_speak("You can't carry any more spears.", true);
|
||||
return;
|
||||
}
|
||||
simulate_crafting();
|
||||
inv_sticks--;
|
||||
inv_vines--;
|
||||
inv_stones--;
|
||||
inv_spears++;
|
||||
screen_reader_speak("Crafted a Spear.", true);
|
||||
} else {
|
||||
screen_reader_speak("Missing: " + missing, true);
|
||||
}
|
||||
}
|
||||
|
||||
void craft_sling() {
|
||||
string missing = "";
|
||||
if (inv_skins < 1) missing += "1 skin ";
|
||||
if (inv_vines < 2) missing += "2 vines ";
|
||||
|
||||
if (missing == "") {
|
||||
if (inv_slings >= MAX_ITEM_STACK) {
|
||||
screen_reader_speak("You can't carry any more slings.", true);
|
||||
return;
|
||||
}
|
||||
simulate_crafting();
|
||||
inv_skins--;
|
||||
inv_vines -= 2;
|
||||
inv_slings++;
|
||||
screen_reader_speak("Crafted a Sling.", true);
|
||||
} else {
|
||||
screen_reader_speak("Missing: " + missing, true);
|
||||
}
|
||||
}
|
||||
|
||||
void craft_snare() {
|
||||
string missing = "";
|
||||
if (inv_sticks < 1) missing += "1 stick ";
|
||||
if (inv_vines < 2) missing += "2 vines ";
|
||||
|
||||
if (missing == "") {
|
||||
if (inv_snares >= MAX_ITEM_STACK) {
|
||||
screen_reader_speak("You can't carry any more snares.", true);
|
||||
return;
|
||||
}
|
||||
simulate_crafting();
|
||||
inv_sticks--;
|
||||
inv_vines -= 2;
|
||||
inv_snares++;
|
||||
screen_reader_speak("Crafted a Snare.", true);
|
||||
} else {
|
||||
screen_reader_speak("Missing: " + missing, true);
|
||||
}
|
||||
}
|
||||
|
||||
void craft_axe() {
|
||||
string missing = "";
|
||||
if (inv_knives < 1) missing += "Stone Knife ";
|
||||
if (inv_sticks < 1) missing += "1 stick ";
|
||||
if (inv_vines < 1) missing += "1 vine ";
|
||||
if (inv_stones < 2) missing += "2 stones ";
|
||||
|
||||
if (missing == "") {
|
||||
if (inv_axes >= MAX_ITEM_STACK) {
|
||||
screen_reader_speak("You can't carry any more stone axes.", true);
|
||||
return;
|
||||
}
|
||||
simulate_crafting();
|
||||
inv_sticks--;
|
||||
inv_vines--;
|
||||
inv_stones -= 2;
|
||||
inv_axes++;
|
||||
screen_reader_speak("Crafted a Stone Axe.", true);
|
||||
} else {
|
||||
screen_reader_speak("Missing: " + missing, true);
|
||||
}
|
||||
}
|
||||
|
||||
void craft_firepit() {
|
||||
// Check if there's already a firepit here
|
||||
if (get_firepit_at(x) != null) {
|
||||
screen_reader_speak("There is already a firepit here.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
string missing = "";
|
||||
if (inv_stones < 9) missing += "9 stones ";
|
||||
|
||||
if (missing == "") {
|
||||
simulate_crafting();
|
||||
inv_stones -= 9;
|
||||
add_world_firepit(x);
|
||||
screen_reader_speak("Firepit built here.", true);
|
||||
} else {
|
||||
screen_reader_speak("Missing: " + missing, true);
|
||||
}
|
||||
}
|
||||
|
||||
void craft_campfire() {
|
||||
// Check if there's a firepit within 2 tiles
|
||||
WorldFirepit@ firepit = get_firepit_near(x, 2);
|
||||
if (firepit == null) {
|
||||
screen_reader_speak("You need a firepit within 2 tiles to build a fire.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
string missing = "";
|
||||
if (inv_logs < 1) missing += "1 log ";
|
||||
if (inv_sticks < 2) missing += "2 sticks ";
|
||||
|
||||
if (missing == "") {
|
||||
simulate_crafting();
|
||||
inv_logs--;
|
||||
inv_sticks -= 2;
|
||||
// Build the fire at the firepit location, not player location
|
||||
add_world_fire(firepit.position);
|
||||
screen_reader_speak("Fire built at firepit.", true);
|
||||
} else {
|
||||
screen_reader_speak("Missing: " + missing, true);
|
||||
}
|
||||
}
|
||||
|
||||
void craft_herb_garden() {
|
||||
// Can only build in base area
|
||||
if (x > BASE_END) {
|
||||
screen_reader_speak("Herb garden can only be built in the base area.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if there's already an herb garden in the base
|
||||
if (get_herb_garden_at_base() != null) {
|
||||
screen_reader_speak("There is already an herb garden in the base.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
string missing = "";
|
||||
if (inv_stones < 9) missing += "9 stones ";
|
||||
if (inv_vines < 3) missing += "3 vines ";
|
||||
if (inv_logs < 2) missing += "2 logs ";
|
||||
|
||||
if (missing == "") {
|
||||
simulate_crafting();
|
||||
inv_stones -= 9;
|
||||
inv_vines -= 3;
|
||||
inv_logs -= 2;
|
||||
add_world_herb_garden(x);
|
||||
screen_reader_speak("Herb garden built. The base now heals faster.", true);
|
||||
} else {
|
||||
screen_reader_speak("Missing: " + missing, true);
|
||||
}
|
||||
}
|
||||
|
||||
void reinforce_barricade_with_sticks() {
|
||||
if (barricade_health >= BARRICADE_MAX_HEALTH) {
|
||||
screen_reader_speak("Barricade is already at full health.", true);
|
||||
return;
|
||||
}
|
||||
if (inv_sticks < BARRICADE_STICK_COST) {
|
||||
screen_reader_speak("Not enough sticks.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
simulate_crafting();
|
||||
inv_sticks -= BARRICADE_STICK_COST;
|
||||
int gained = add_barricade_health(BARRICADE_STICK_HEALTH);
|
||||
screen_reader_speak("Reinforced barricade with sticks. +" + gained + " health. Now " + barricade_health + " of " + BARRICADE_MAX_HEALTH + ".", true);
|
||||
}
|
||||
|
||||
void reinforce_barricade_with_vines() {
|
||||
if (barricade_health >= BARRICADE_MAX_HEALTH) {
|
||||
screen_reader_speak("Barricade is already at full health.", true);
|
||||
return;
|
||||
}
|
||||
if (inv_vines < BARRICADE_VINE_COST) {
|
||||
screen_reader_speak("Not enough vines.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
simulate_crafting();
|
||||
inv_vines -= BARRICADE_VINE_COST;
|
||||
int gained = add_barricade_health(BARRICADE_VINE_HEALTH);
|
||||
screen_reader_speak("Reinforced barricade with vines. +" + gained + " health. Now " + barricade_health + " of " + BARRICADE_MAX_HEALTH + ".", true);
|
||||
}
|
||||
|
||||
void reinforce_barricade_with_log() {
|
||||
if (barricade_health >= BARRICADE_MAX_HEALTH) {
|
||||
screen_reader_speak("Barricade is already at full health.", true);
|
||||
return;
|
||||
}
|
||||
if (inv_logs < BARRICADE_LOG_COST) {
|
||||
screen_reader_speak("Not enough logs.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
simulate_crafting();
|
||||
inv_logs -= BARRICADE_LOG_COST;
|
||||
int gained = add_barricade_health(BARRICADE_LOG_HEALTH);
|
||||
screen_reader_speak("Reinforced barricade with log. +" + gained + " health. Now " + barricade_health + " of " + BARRICADE_MAX_HEALTH + ".", true);
|
||||
}
|
||||
|
||||
void reinforce_barricade_with_stones() {
|
||||
if (barricade_health >= BARRICADE_MAX_HEALTH) {
|
||||
screen_reader_speak("Barricade is already at full health.", true);
|
||||
return;
|
||||
}
|
||||
if (inv_stones < BARRICADE_STONE_COST) {
|
||||
screen_reader_speak("Not enough stones.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
simulate_crafting();
|
||||
inv_stones -= BARRICADE_STONE_COST;
|
||||
int gained = add_barricade_health(BARRICADE_STONE_HEALTH);
|
||||
screen_reader_speak("Reinforced barricade with stones. +" + gained + " health. Now " + barricade_health + " of " + BARRICADE_MAX_HEALTH + ".", true);
|
||||
}
|
||||
|
||||
void craft_fishing_pole() {
|
||||
string missing = "";
|
||||
if (inv_sticks < 1) missing += "1 stick ";
|
||||
if (inv_vines < 2) missing += "2 vines ";
|
||||
|
||||
if (missing == "") {
|
||||
if (inv_fishing_poles >= MAX_ITEM_STACK) {
|
||||
screen_reader_speak("You can't carry any more fishing poles.", true);
|
||||
return;
|
||||
}
|
||||
simulate_crafting();
|
||||
inv_sticks--;
|
||||
inv_vines -= 2;
|
||||
inv_fishing_poles++;
|
||||
screen_reader_speak("Crafted a Fishing Pole.", true);
|
||||
} else {
|
||||
screen_reader_speak("Missing: " + missing, true);
|
||||
}
|
||||
}
|
||||
|
||||
void butcher_small_game() {
|
||||
string missing = "";
|
||||
|
||||
// Check for knife
|
||||
if (inv_knives < 1) missing += "Stone Knife ";
|
||||
|
||||
// Check for small game
|
||||
if (inv_small_game < 1) missing += "Small Game ";
|
||||
|
||||
// Check for fire within 3 tiles (can hear it)
|
||||
WorldFire@ fire = get_fire_within_range(x, 3);
|
||||
if (fire == null) {
|
||||
screen_reader_speak("You need a fire within 3 tiles to butcher.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (missing == "") {
|
||||
if (inv_meat >= MAX_ITEM_STACK) {
|
||||
screen_reader_speak("You can't carry any more meat.", true);
|
||||
return;
|
||||
}
|
||||
if (inv_skins >= MAX_ITEM_STACK) {
|
||||
screen_reader_speak("You can't carry any more skins.", true);
|
||||
return;
|
||||
}
|
||||
simulate_crafting();
|
||||
|
||||
// Get the type of game we're butchering (first in the list)
|
||||
string game_type = inv_small_game_types[0];
|
||||
inv_small_game_types.remove_at(0);
|
||||
|
||||
inv_small_game--;
|
||||
inv_meat++;
|
||||
inv_skins++;
|
||||
|
||||
screen_reader_speak("Butchered " + game_type + ". Got 1 meat and 1 skin.", true);
|
||||
} else {
|
||||
screen_reader_speak("Missing: " + missing, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Notification System
|
||||
string[] notification_history;
|
||||
const int MAX_NOTIFICATIONS = 10;
|
||||
int current_notification_index = -1;
|
||||
|
||||
void notify(string message) {
|
||||
// Play notification sound
|
||||
p.play_stationary("sounds/notify.ogg", false);
|
||||
|
||||
// Speak the message
|
||||
screen_reader_speak(message, true);
|
||||
|
||||
// Add to history
|
||||
notification_history.insert_last(message);
|
||||
|
||||
// Keep only last 10 notifications
|
||||
if (notification_history.length() > MAX_NOTIFICATIONS) {
|
||||
notification_history.remove_at(0);
|
||||
}
|
||||
|
||||
// Reset index to most recent
|
||||
current_notification_index = notification_history.length() - 1;
|
||||
}
|
||||
|
||||
void check_notification_keys() {
|
||||
// [ for previous notification (older) with position
|
||||
if (key_pressed(KEY_LBRACKET)) {
|
||||
if (notification_history.length() == 0) {
|
||||
screen_reader_speak("No notifications.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
current_notification_index--;
|
||||
if (current_notification_index < 0) {
|
||||
current_notification_index = 0;
|
||||
screen_reader_speak("Oldest notification.", true);
|
||||
return;
|
||||
}
|
||||
int position = current_notification_index + 1;
|
||||
screen_reader_speak(notification_history[current_notification_index] + " " + position + " of " + notification_history.length(), true);
|
||||
return;
|
||||
}
|
||||
|
||||
// ] for next notification (newer) with position
|
||||
if (key_pressed(KEY_RBRACKET)) {
|
||||
if (notification_history.length() == 0) {
|
||||
screen_reader_speak("No notifications.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
current_notification_index++;
|
||||
if (current_notification_index >= notification_history.length()) {
|
||||
current_notification_index = notification_history.length() - 1;
|
||||
screen_reader_speak("Most recent notification.", true);
|
||||
return;
|
||||
}
|
||||
int position = current_notification_index + 1;
|
||||
screen_reader_speak(notification_history[current_notification_index] + " " + position + " of " + notification_history.length(), true);
|
||||
return;
|
||||
}
|
||||
|
||||
// \ for most recent notification (without position)
|
||||
if (key_pressed(KEY_BACKSLASH)) {
|
||||
if (notification_history.length() == 0) {
|
||||
screen_reader_speak("No notifications.", true);
|
||||
return;
|
||||
}
|
||||
current_notification_index = notification_history.length() - 1;
|
||||
screen_reader_speak(notification_history[current_notification_index], true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// Player state
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int facing = 1; // 0: left, 1: right
|
||||
bool jumping = false;
|
||||
bool climbing = false;
|
||||
bool falling = false;
|
||||
int climb_target_y = 0; // Target height when climbing
|
||||
int fall_start_y = 0; // Height where fall started
|
||||
int fall_sound_handle = -1; // Handle for looping fall sound
|
||||
timer fall_timer; // For fall sound pitch
|
||||
timer climb_timer; // For climb speed
|
||||
|
||||
// Health System
|
||||
int player_health = 10;
|
||||
int max_health = 10;
|
||||
timer fire_damage_timer;
|
||||
timer healing_timer;
|
||||
|
||||
// Sling state
|
||||
bool sling_charging = false;
|
||||
timer sling_charge_timer;
|
||||
int sling_sound_handle = -1;
|
||||
int last_sling_stage = -1; // Track which stage we're in to avoid duplicate sounds
|
||||
|
||||
// Timers
|
||||
timer walktimer;
|
||||
timer jumptimer;
|
||||
timer search_timer;
|
||||
timer search_delay_timer;
|
||||
timer attack_timer;
|
||||
|
||||
// Search state
|
||||
bool searching = false;
|
||||
@@ -0,0 +1,485 @@
|
||||
// Save system
|
||||
|
||||
const string SAVE_FILE_PATH = "save.dat";
|
||||
const string SAVE_BACKUP_SUFFIX = "_robust";
|
||||
const string SAVE_ENCRYPTION_KEY = "draugnorak_save_v1";
|
||||
const int SAVE_VERSION = 1;
|
||||
|
||||
bool has_save_game() {
|
||||
return file_exists(SAVE_FILE_PATH);
|
||||
}
|
||||
|
||||
string encrypt_save_data(const string&in rawData) {
|
||||
return string_aes_encrypt(rawData, SAVE_ENCRYPTION_KEY);
|
||||
}
|
||||
|
||||
string decrypt_save_data(const string&in encryptedData) {
|
||||
return string_aes_decrypt(encryptedData, SAVE_ENCRYPTION_KEY);
|
||||
}
|
||||
|
||||
bool save_robust_data(const string&in filename, const string&in data) {
|
||||
if (data.length() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string backupPath = filename + SAVE_BACKUP_SUFFIX;
|
||||
if (file_exists(filename)) {
|
||||
if (!file_copy(filename, backupPath, true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
file tmp;
|
||||
if (!tmp.open(filename, "wb")) {
|
||||
if (file_exists(backupPath)) {
|
||||
file_copy(backupPath, filename, true);
|
||||
file_delete(backupPath);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tmp.write(data) < data.length()) {
|
||||
tmp.close();
|
||||
if (file_exists(backupPath)) {
|
||||
file_copy(backupPath, filename, true);
|
||||
file_delete(backupPath);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
tmp.close();
|
||||
file_delete(backupPath);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool read_file_string(const string&in filename, string&out data) {
|
||||
file tmp;
|
||||
if (!tmp.open(filename, "rb")) {
|
||||
return false;
|
||||
}
|
||||
data = tmp.read();
|
||||
tmp.close();
|
||||
return data.length() > 0;
|
||||
}
|
||||
|
||||
double get_number(dictionary@ data, const string&in key, double defaultValue) {
|
||||
double value;
|
||||
if (@data == null) return defaultValue;
|
||||
if (!data.get(key, value)) return defaultValue;
|
||||
return value;
|
||||
}
|
||||
|
||||
bool get_bool(dictionary@ data, const string&in key, bool defaultValue) {
|
||||
bool value;
|
||||
if (@data == null) return defaultValue;
|
||||
if (!data.get(key, value)) return defaultValue;
|
||||
return value;
|
||||
}
|
||||
|
||||
string[] get_string_list(dictionary@ data, const string&in key) {
|
||||
string[] result;
|
||||
if (@data == null) return result;
|
||||
if (!data.get(key, result)) return result;
|
||||
return result;
|
||||
}
|
||||
|
||||
void stop_active_sounds() {
|
||||
if (day_sound_handle != -1) {
|
||||
p.destroy_sound(day_sound_handle);
|
||||
day_sound_handle = -1;
|
||||
}
|
||||
if (night_sound_handle != -1) {
|
||||
p.destroy_sound(night_sound_handle);
|
||||
night_sound_handle = -1;
|
||||
}
|
||||
if (fall_sound_handle != -1) {
|
||||
p.destroy_sound(fall_sound_handle);
|
||||
fall_sound_handle = -1;
|
||||
}
|
||||
if (sling_sound_handle != -1) {
|
||||
p.destroy_sound(sling_sound_handle);
|
||||
sling_sound_handle = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void clear_world_objects() {
|
||||
for (uint i = 0; i < world_snares.length(); i++) {
|
||||
world_snares[i].destroy();
|
||||
}
|
||||
world_snares.resize(0);
|
||||
|
||||
for (uint i = 0; i < world_fires.length(); i++) {
|
||||
world_fires[i].destroy();
|
||||
}
|
||||
world_fires.resize(0);
|
||||
|
||||
for (uint i = 0; i < world_streams.length(); i++) {
|
||||
world_streams[i].destroy();
|
||||
}
|
||||
world_streams.resize(0);
|
||||
|
||||
world_firepits.resize(0);
|
||||
world_herb_gardens.resize(0);
|
||||
|
||||
for (uint i = 0; i < trees.length(); i++) {
|
||||
if (trees[i].sound_handle != -1) {
|
||||
p.destroy_sound(trees[i].sound_handle);
|
||||
trees[i].sound_handle = -1;
|
||||
}
|
||||
}
|
||||
trees.resize(0);
|
||||
|
||||
clear_zombies();
|
||||
clear_bandits();
|
||||
}
|
||||
|
||||
void reset_game_state() {
|
||||
stop_active_sounds();
|
||||
clear_world_objects();
|
||||
|
||||
x = 0;
|
||||
y = 0;
|
||||
facing = 1;
|
||||
jumping = false;
|
||||
climbing = false;
|
||||
falling = false;
|
||||
climb_target_y = 0;
|
||||
fall_start_y = 0;
|
||||
sling_charging = false;
|
||||
searching = false;
|
||||
|
||||
player_health = 10;
|
||||
max_health = 10;
|
||||
|
||||
inv_stones = 0;
|
||||
inv_sticks = 0;
|
||||
inv_vines = 0;
|
||||
inv_logs = 0;
|
||||
inv_small_game = 0;
|
||||
inv_small_game_types.resize(0);
|
||||
inv_meat = 0;
|
||||
inv_skins = 0;
|
||||
inv_spears = 0;
|
||||
inv_snares = 0;
|
||||
inv_axes = 0;
|
||||
inv_knives = 0;
|
||||
inv_fishing_poles = 0;
|
||||
inv_slings = 0;
|
||||
|
||||
spear_equipped = false;
|
||||
axe_equipped = false;
|
||||
sling_equipped = false;
|
||||
|
||||
MAP_SIZE = 35;
|
||||
expanded_area_start = -1;
|
||||
expanded_area_end = -1;
|
||||
expanded_terrain_types.resize(0);
|
||||
|
||||
barricade_health = 0;
|
||||
barricade_initialized = false;
|
||||
|
||||
current_hour = 8;
|
||||
current_day = 1;
|
||||
is_daytime = true;
|
||||
sun_setting_warned = false;
|
||||
sunrise_warned = false;
|
||||
area_expanded_today = false;
|
||||
invasion_active = false;
|
||||
invasion_start_hour = -1;
|
||||
|
||||
walktimer.restart();
|
||||
jumptimer.restart();
|
||||
search_timer.restart();
|
||||
search_delay_timer.restart();
|
||||
attack_timer.restart();
|
||||
hour_timer.restart();
|
||||
fire_damage_timer.restart();
|
||||
healing_timer.restart();
|
||||
sling_charge_timer.restart();
|
||||
fall_timer.restart();
|
||||
climb_timer.restart();
|
||||
}
|
||||
|
||||
void start_new_game() {
|
||||
reset_game_state();
|
||||
spawn_trees(5, 19);
|
||||
init_barricade();
|
||||
init_time();
|
||||
}
|
||||
|
||||
string serialize_bool(bool value) {
|
||||
return value ? "1" : "0";
|
||||
}
|
||||
|
||||
string serialize_tree(Tree@ tree) {
|
||||
return tree.position + "|" + tree.sticks + "|" + tree.vines + "|" + tree.health + "|" + tree.height + "|" + serialize_bool(tree.depleted) + "|" + serialize_bool(tree.is_chopped) + "|" + tree.minutes_since_depletion;
|
||||
}
|
||||
|
||||
string serialize_snare(WorldSnare@ snare) {
|
||||
return snare.position + "|" + serialize_bool(snare.has_catch) + "|" + snare.catch_type + "|" + snare.catch_chance + "|" + snare.escape_chance + "|" + serialize_bool(snare.active);
|
||||
}
|
||||
|
||||
string serialize_fire(WorldFire@ fire) {
|
||||
return fire.position + "|" + fire.fuel_remaining + "|" + serialize_bool(fire.low_fuel_warned);
|
||||
}
|
||||
|
||||
string serialize_stream(WorldStream@ stream) {
|
||||
return stream.start_position + "|" + stream.get_width();
|
||||
}
|
||||
|
||||
bool save_game_state() {
|
||||
dictionary saveData;
|
||||
|
||||
saveData.set("version", SAVE_VERSION);
|
||||
saveData.set("player_x", x);
|
||||
saveData.set("player_y", y);
|
||||
saveData.set("player_facing", facing);
|
||||
saveData.set("player_health", player_health);
|
||||
saveData.set("player_max_health", max_health);
|
||||
|
||||
saveData.set("inventory_stones", inv_stones);
|
||||
saveData.set("inventory_sticks", inv_sticks);
|
||||
saveData.set("inventory_vines", inv_vines);
|
||||
saveData.set("inventory_logs", inv_logs);
|
||||
saveData.set("inventory_small_game", inv_small_game);
|
||||
saveData.set("inventory_meat", inv_meat);
|
||||
saveData.set("inventory_skins", inv_skins);
|
||||
saveData.set("inventory_spears", inv_spears);
|
||||
saveData.set("inventory_snares", inv_snares);
|
||||
saveData.set("inventory_axes", inv_axes);
|
||||
saveData.set("inventory_knives", inv_knives);
|
||||
saveData.set("inventory_fishing_poles", inv_fishing_poles);
|
||||
saveData.set("inventory_slings", inv_slings);
|
||||
saveData.set("inventory_small_game_types", inv_small_game_types);
|
||||
|
||||
saveData.set("equipment_spear_equipped", spear_equipped);
|
||||
saveData.set("equipment_axe_equipped", axe_equipped);
|
||||
saveData.set("equipment_sling_equipped", sling_equipped);
|
||||
|
||||
saveData.set("time_current_hour", current_hour);
|
||||
saveData.set("time_current_day", current_day);
|
||||
saveData.set("time_is_daytime", is_daytime);
|
||||
saveData.set("time_sun_setting_warned", sun_setting_warned);
|
||||
saveData.set("time_sunrise_warned", sunrise_warned);
|
||||
saveData.set("time_area_expanded_today", area_expanded_today);
|
||||
saveData.set("time_invasion_active", invasion_active);
|
||||
saveData.set("time_invasion_start_hour", invasion_start_hour);
|
||||
|
||||
saveData.set("world_map_size", MAP_SIZE);
|
||||
saveData.set("world_expanded_area_start", expanded_area_start);
|
||||
saveData.set("world_expanded_area_end", expanded_area_end);
|
||||
saveData.set("world_barricade_initialized", barricade_initialized);
|
||||
saveData.set("world_barricade_health", barricade_health);
|
||||
saveData.set("world_expanded_terrain_types", expanded_terrain_types);
|
||||
|
||||
string[] treeData;
|
||||
for (uint i = 0; i < trees.length(); i++) {
|
||||
treeData.insert_last(serialize_tree(trees[i]));
|
||||
}
|
||||
saveData.set("trees_data", treeData);
|
||||
|
||||
string[] snareData;
|
||||
for (uint i = 0; i < world_snares.length(); i++) {
|
||||
snareData.insert_last(serialize_snare(world_snares[i]));
|
||||
}
|
||||
saveData.set("snares_data", snareData);
|
||||
|
||||
string[] fireData;
|
||||
for (uint i = 0; i < world_fires.length(); i++) {
|
||||
fireData.insert_last(serialize_fire(world_fires[i]));
|
||||
}
|
||||
saveData.set("fires_data", fireData);
|
||||
|
||||
string[] firepitPositions;
|
||||
for (uint i = 0; i < world_firepits.length(); i++) {
|
||||
firepitPositions.insert_last("" + world_firepits[i].position);
|
||||
}
|
||||
saveData.set("firepits_positions", firepitPositions);
|
||||
|
||||
string[] herbPositions;
|
||||
for (uint i = 0; i < world_herb_gardens.length(); i++) {
|
||||
herbPositions.insert_last("" + world_herb_gardens[i].position);
|
||||
}
|
||||
saveData.set("herb_gardens_positions", herbPositions);
|
||||
|
||||
string[] streamData;
|
||||
for (uint i = 0; i < world_streams.length(); i++) {
|
||||
streamData.insert_last(serialize_stream(world_streams[i]));
|
||||
}
|
||||
saveData.set("streams_data", streamData);
|
||||
|
||||
string rawData = saveData.serialize();
|
||||
string encryptedData = encrypt_save_data(rawData);
|
||||
return save_robust_data(SAVE_FILE_PATH, encryptedData);
|
||||
}
|
||||
|
||||
bool load_game_state() {
|
||||
if (!file_exists(SAVE_FILE_PATH)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string encryptedData;
|
||||
if (!read_file_string(SAVE_FILE_PATH, encryptedData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string rawData = decrypt_save_data(encryptedData);
|
||||
if (rawData.length() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dictionary@ saveData = deserialize(rawData);
|
||||
if (@saveData == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
reset_game_state();
|
||||
|
||||
MAP_SIZE = int(get_number(saveData, "world_map_size", 35));
|
||||
expanded_area_start = int(get_number(saveData, "world_expanded_area_start", -1));
|
||||
expanded_area_end = int(get_number(saveData, "world_expanded_area_end", -1));
|
||||
|
||||
string[] loadedTerrain = get_string_list(saveData, "world_expanded_terrain_types");
|
||||
expanded_terrain_types.resize(0);
|
||||
for (uint i = 0; i < loadedTerrain.length(); i++) {
|
||||
expanded_terrain_types.insert_last(loadedTerrain[i]);
|
||||
}
|
||||
|
||||
barricade_initialized = get_bool(saveData, "world_barricade_initialized", true);
|
||||
barricade_health = int(get_number(saveData, "world_barricade_health", BARRICADE_BASE_HEALTH));
|
||||
if (!barricade_initialized) {
|
||||
init_barricade();
|
||||
} else {
|
||||
if (barricade_health < 0) barricade_health = 0;
|
||||
if (barricade_health > BARRICADE_MAX_HEALTH) barricade_health = BARRICADE_MAX_HEALTH;
|
||||
}
|
||||
|
||||
x = int(get_number(saveData, "player_x", 0));
|
||||
y = int(get_number(saveData, "player_y", 0));
|
||||
facing = int(get_number(saveData, "player_facing", 1));
|
||||
player_health = int(get_number(saveData, "player_health", 10));
|
||||
max_health = int(get_number(saveData, "player_max_health", 10));
|
||||
|
||||
if (x < 0) x = 0;
|
||||
if (x >= MAP_SIZE) x = MAP_SIZE - 1;
|
||||
if (y < 0) y = 0;
|
||||
if (facing != 0 && facing != 1) facing = 1;
|
||||
|
||||
inv_stones = int(get_number(saveData, "inventory_stones", 0));
|
||||
inv_sticks = int(get_number(saveData, "inventory_sticks", 0));
|
||||
inv_vines = int(get_number(saveData, "inventory_vines", 0));
|
||||
inv_logs = int(get_number(saveData, "inventory_logs", 0));
|
||||
inv_small_game = int(get_number(saveData, "inventory_small_game", 0));
|
||||
inv_meat = int(get_number(saveData, "inventory_meat", 0));
|
||||
inv_skins = int(get_number(saveData, "inventory_skins", 0));
|
||||
inv_spears = int(get_number(saveData, "inventory_spears", 0));
|
||||
inv_snares = int(get_number(saveData, "inventory_snares", 0));
|
||||
inv_axes = int(get_number(saveData, "inventory_axes", 0));
|
||||
inv_knives = int(get_number(saveData, "inventory_knives", 0));
|
||||
inv_fishing_poles = int(get_number(saveData, "inventory_fishing_poles", 0));
|
||||
inv_slings = int(get_number(saveData, "inventory_slings", 0));
|
||||
|
||||
string[] loadedSmallGameTypes = get_string_list(saveData, "inventory_small_game_types");
|
||||
inv_small_game_types.resize(0);
|
||||
for (uint i = 0; i < loadedSmallGameTypes.length(); i++) {
|
||||
inv_small_game_types.insert_last(loadedSmallGameTypes[i]);
|
||||
}
|
||||
|
||||
if (inv_small_game_types.length() == 0 && inv_small_game > 0) {
|
||||
for (int i = 0; i < inv_small_game; i++) {
|
||||
inv_small_game_types.insert_last("small game");
|
||||
}
|
||||
} else {
|
||||
inv_small_game = inv_small_game_types.length();
|
||||
}
|
||||
|
||||
spear_equipped = get_bool(saveData, "equipment_spear_equipped", false);
|
||||
axe_equipped = get_bool(saveData, "equipment_axe_equipped", false);
|
||||
sling_equipped = get_bool(saveData, "equipment_sling_equipped", false);
|
||||
|
||||
current_hour = int(get_number(saveData, "time_current_hour", 8));
|
||||
current_day = int(get_number(saveData, "time_current_day", 1));
|
||||
sun_setting_warned = get_bool(saveData, "time_sun_setting_warned", false);
|
||||
sunrise_warned = get_bool(saveData, "time_sunrise_warned", false);
|
||||
area_expanded_today = get_bool(saveData, "time_area_expanded_today", false);
|
||||
invasion_active = get_bool(saveData, "time_invasion_active", false);
|
||||
invasion_start_hour = int(get_number(saveData, "time_invasion_start_hour", -1));
|
||||
|
||||
if (current_hour < 0) current_hour = 0;
|
||||
if (current_hour > 23) current_hour = 23;
|
||||
if (current_day < 1) current_day = 1;
|
||||
|
||||
is_daytime = (current_hour >= 6 && current_hour < 19);
|
||||
hour_timer.restart();
|
||||
|
||||
string[] treeData = get_string_list(saveData, "trees_data");
|
||||
for (uint i = 0; i < treeData.length(); i++) {
|
||||
string[]@ parts = treeData[i].split("|");
|
||||
if (parts.length() < 8) continue;
|
||||
|
||||
int pos = parse_int(parts[0]);
|
||||
Tree@ tree = Tree(pos);
|
||||
tree.sticks = parse_int(parts[1]);
|
||||
tree.vines = parse_int(parts[2]);
|
||||
tree.health = parse_int(parts[3]);
|
||||
tree.height = parse_int(parts[4]);
|
||||
tree.depleted = (parse_int(parts[5]) == 1);
|
||||
tree.is_chopped = (parse_int(parts[6]) == 1);
|
||||
tree.minutes_since_depletion = parse_int(parts[7]);
|
||||
tree.regen_timer.restart();
|
||||
trees.insert_last(tree);
|
||||
}
|
||||
|
||||
string[] snareData = get_string_list(saveData, "snares_data");
|
||||
for (uint i = 0; i < snareData.length(); i++) {
|
||||
string[]@ parts = snareData[i].split("|");
|
||||
if (parts.length() < 6) continue;
|
||||
|
||||
int pos = parse_int(parts[0]);
|
||||
WorldSnare@ snare = WorldSnare(pos);
|
||||
snare.has_catch = (parse_int(parts[1]) == 1);
|
||||
snare.catch_type = parts[2];
|
||||
snare.catch_chance = parse_int(parts[3]);
|
||||
snare.escape_chance = parse_int(parts[4]);
|
||||
snare.active = (parse_int(parts[5]) == 1);
|
||||
snare.minute_timer.restart();
|
||||
world_snares.insert_last(snare);
|
||||
}
|
||||
|
||||
string[] fireData = get_string_list(saveData, "fires_data");
|
||||
for (uint i = 0; i < fireData.length(); i++) {
|
||||
string[]@ parts = fireData[i].split("|");
|
||||
if (parts.length() < 3) continue;
|
||||
|
||||
int pos = parse_int(parts[0]);
|
||||
WorldFire@ fire = WorldFire(pos);
|
||||
fire.fuel_remaining = parse_int(parts[1]);
|
||||
fire.low_fuel_warned = (parse_int(parts[2]) == 1);
|
||||
fire.fuel_timer.restart();
|
||||
world_fires.insert_last(fire);
|
||||
}
|
||||
|
||||
string[] firepitPositions = get_string_list(saveData, "firepits_positions");
|
||||
for (uint i = 0; i < firepitPositions.length(); i++) {
|
||||
add_world_firepit(parse_int(firepitPositions[i]));
|
||||
}
|
||||
|
||||
string[] herbPositions = get_string_list(saveData, "herb_gardens_positions");
|
||||
for (uint i = 0; i < herbPositions.length(); i++) {
|
||||
add_world_herb_garden(parse_int(herbPositions[i]));
|
||||
}
|
||||
|
||||
string[] streamData = get_string_list(saveData, "streams_data");
|
||||
for (uint i = 0; i < streamData.length(); i++) {
|
||||
string[]@ parts = streamData[i].split("|");
|
||||
if (parts.length() < 2) continue;
|
||||
|
||||
int startPos = parse_int(parts[0]);
|
||||
int width = parse_int(parts[1]);
|
||||
if (width < 1) width = 1;
|
||||
add_world_stream(startPos, width);
|
||||
}
|
||||
|
||||
update_ambience(true);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
// Time System
|
||||
// 1 real minute = 1 in-game hour
|
||||
const int MS_PER_HOUR = 60000;
|
||||
|
||||
int current_hour = 8; // Start at 8 AM
|
||||
int current_day = 1; // Track current day
|
||||
timer hour_timer;
|
||||
|
||||
int day_sound_handle = -1;
|
||||
int night_sound_handle = -1;
|
||||
bool is_daytime = true;
|
||||
bool sun_setting_warned = false;
|
||||
bool sunrise_warned = false;
|
||||
|
||||
// Expansion and invasion tracking
|
||||
bool area_expanded_today = false;
|
||||
bool invasion_active = false;
|
||||
int invasion_start_hour = -1;
|
||||
string[] expanded_terrain_types;
|
||||
|
||||
void init_time() {
|
||||
current_hour = 8;
|
||||
current_day = 1;
|
||||
hour_timer.restart();
|
||||
is_daytime = true;
|
||||
sun_setting_warned = false;
|
||||
sunrise_warned = false;
|
||||
area_expanded_today = false;
|
||||
invasion_active = false;
|
||||
invasion_start_hour = -1;
|
||||
update_ambience(true); // Force start
|
||||
}
|
||||
|
||||
void expand_area() {
|
||||
if (expanded_area_start != -1) {
|
||||
return; // Already expanded
|
||||
}
|
||||
|
||||
// Play invasion sound
|
||||
p.play_stationary("sounds/enemies/invasion.ogg", false);
|
||||
|
||||
// Calculate new area
|
||||
expanded_area_start = MAP_SIZE;
|
||||
expanded_area_end = MAP_SIZE + EXPANSION_SIZE - 1;
|
||||
MAP_SIZE += EXPANSION_SIZE;
|
||||
|
||||
// Generate random terrain for the 30 new tiles
|
||||
expanded_terrain_types.resize(EXPANSION_SIZE);
|
||||
for (int i = 0; i < EXPANSION_SIZE; i++) {
|
||||
int terrain_roll = random(0, 2);
|
||||
if (terrain_roll == 0) {
|
||||
expanded_terrain_types[i] = "stone";
|
||||
} else if (terrain_roll == 1) {
|
||||
expanded_terrain_types[i] = "grass";
|
||||
} else {
|
||||
expanded_terrain_types[i] = "snow";
|
||||
}
|
||||
}
|
||||
|
||||
// Generate streams (30% chance for a stream)
|
||||
int stream_roll = random(1, 100);
|
||||
if (stream_roll <= 30) {
|
||||
// Determine stream width (1-5 tiles)
|
||||
int stream_width = random(1, 5);
|
||||
|
||||
// Find a valid starting position for the stream
|
||||
// Stream can only be in grass, stone, or snow areas
|
||||
int attempts = 0;
|
||||
int stream_start = -1;
|
||||
while (attempts < 50) {
|
||||
int candidate_start = random(0, EXPANSION_SIZE - stream_width);
|
||||
bool valid_position = true;
|
||||
|
||||
// Check if all tiles in this range are valid for a stream
|
||||
for (int i = 0; i < stream_width; i++) {
|
||||
string terrain = expanded_terrain_types[candidate_start + i];
|
||||
// Streams can be in any terrain type
|
||||
if (terrain != "grass" && terrain != "stone" && terrain != "snow") {
|
||||
valid_position = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (valid_position) {
|
||||
stream_start = candidate_start;
|
||||
break;
|
||||
}
|
||||
attempts++;
|
||||
}
|
||||
|
||||
// Create the stream if we found a valid position
|
||||
if (stream_start != -1) {
|
||||
int actual_start = expanded_area_start + stream_start;
|
||||
add_world_stream(actual_start, stream_width);
|
||||
|
||||
string width_desc = "very small";
|
||||
if (stream_width == 2) width_desc = "small";
|
||||
else if (stream_width == 3) width_desc = "medium";
|
||||
else if (stream_width == 4) width_desc = "wide";
|
||||
else if (stream_width == 5) width_desc = "very wide";
|
||||
|
||||
notify("A " + width_desc + " stream flows through the new area at x " + actual_start + ".");
|
||||
}
|
||||
}
|
||||
|
||||
// Generate trees in grass areas (50% chance for each grass tile)
|
||||
for (int i = 0; i < EXPANSION_SIZE; i++) {
|
||||
if (expanded_terrain_types[i] == "grass") {
|
||||
int tile_pos = expanded_area_start + i;
|
||||
|
||||
// Skip if this position has a stream
|
||||
if (is_position_in_water(tile_pos)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 50% chance to spawn a tree
|
||||
int tree_roll = random(1, 100);
|
||||
if (tree_roll <= 50) {
|
||||
Tree@ t = Tree(tile_pos);
|
||||
trees.insert_last(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
area_expanded_today = true;
|
||||
notify("The area has expanded! New territory discovered to the east.");
|
||||
}
|
||||
|
||||
void start_invasion() {
|
||||
invasion_active = true;
|
||||
invasion_start_hour = current_hour;
|
||||
notify("Bandits are invading from the new area!");
|
||||
}
|
||||
|
||||
void end_invasion() {
|
||||
invasion_active = false;
|
||||
invasion_start_hour = -1;
|
||||
clear_bandits();
|
||||
notify("The bandit invasion has ended.");
|
||||
}
|
||||
|
||||
void check_invasion_status() {
|
||||
if (!invasion_active) return;
|
||||
|
||||
// Check if invasion duration has elapsed
|
||||
int hours_elapsed = current_hour - invasion_start_hour;
|
||||
if (hours_elapsed < 0) {
|
||||
hours_elapsed += 24; // Handle day wrap
|
||||
}
|
||||
|
||||
if (hours_elapsed >= INVASION_DURATION_HOURS) {
|
||||
end_invasion();
|
||||
}
|
||||
}
|
||||
|
||||
void manage_bandits_during_invasion() {
|
||||
if (!invasion_active) return;
|
||||
if (expanded_area_start == -1) return;
|
||||
|
||||
// Bandits only appear during daytime (6 AM to 7 PM)
|
||||
if (!is_daytime) {
|
||||
clear_bandits();
|
||||
return;
|
||||
}
|
||||
|
||||
// Maintain BANDIT_MAX_COUNT bandits during invasion
|
||||
while (bandits.length() < BANDIT_MAX_COUNT) {
|
||||
spawn_bandit(expanded_area_start, expanded_area_end);
|
||||
}
|
||||
}
|
||||
|
||||
void update_time() {
|
||||
if (hour_timer.elapsed >= MS_PER_HOUR) {
|
||||
hour_timer.restart();
|
||||
current_hour++;
|
||||
if (current_hour >= 24) {
|
||||
current_hour = 0;
|
||||
current_day++;
|
||||
area_expanded_today = false; // Reset for new day
|
||||
}
|
||||
|
||||
if (current_hour == 18 && !sun_setting_warned) {
|
||||
notify("The sun is setting.");
|
||||
sun_setting_warned = true;
|
||||
} else if (current_hour == 19) {
|
||||
sun_setting_warned = false;
|
||||
}
|
||||
if (current_hour == 5 && !sunrise_warned) {
|
||||
notify("Sunrise isn't far away.");
|
||||
sunrise_warned = true;
|
||||
} else if (current_hour == 6) {
|
||||
sunrise_warned = false;
|
||||
}
|
||||
|
||||
// Check for area expansion (day 2+, daytime morning before noon, not yet expanded today)
|
||||
if (current_day >= 2 && current_hour >= 6 && current_hour < 12 && !area_expanded_today && expanded_area_start == -1) {
|
||||
int roll = random(1, 100);
|
||||
if (roll <= EXPANSION_CHANCE) {
|
||||
expand_area();
|
||||
// Start invasion immediately after expansion (morning, during daytime)
|
||||
start_invasion();
|
||||
}
|
||||
}
|
||||
|
||||
// Check invasion status
|
||||
check_invasion_status();
|
||||
|
||||
check_ambience_transition();
|
||||
|
||||
if (current_hour == 6) {
|
||||
save_game_state();
|
||||
}
|
||||
}
|
||||
|
||||
// Manage bandits during active invasion
|
||||
manage_bandits_during_invasion();
|
||||
}
|
||||
|
||||
void check_time_input() {
|
||||
if (key_pressed(KEY_T)) {
|
||||
screen_reader_speak(get_time_string(), true);
|
||||
}
|
||||
}
|
||||
|
||||
string get_time_string() {
|
||||
int display_hour = current_hour;
|
||||
string period = "am";
|
||||
|
||||
if (display_hour == 0) {
|
||||
display_hour = 12;
|
||||
} else if (display_hour == 12) {
|
||||
period = "pm";
|
||||
} else if (display_hour > 12) {
|
||||
display_hour -= 12;
|
||||
period = "pm";
|
||||
}
|
||||
|
||||
return display_hour + " oclock " + period;
|
||||
}
|
||||
|
||||
void check_ambience_transition() {
|
||||
// Definition of Day: 6 AM to 7 PM (19:00) ?
|
||||
// Let's say Day is 6 (6AM) to 19 (7PM). Night is 20 (8PM) to 5 (5AM).
|
||||
// Or simpler: 6 to 18 (6PM).
|
||||
|
||||
bool now_day = (current_hour >= 6 && current_hour < 19);
|
||||
|
||||
if (now_day != is_daytime) {
|
||||
is_daytime = now_day;
|
||||
update_ambience(false);
|
||||
}
|
||||
}
|
||||
|
||||
void update_ambience(bool force_restart) {
|
||||
if (is_daytime) {
|
||||
// Transition to Day
|
||||
if (night_sound_handle != -1) {
|
||||
p.destroy_sound(night_sound_handle);
|
||||
night_sound_handle = -1;
|
||||
}
|
||||
if (day_sound_handle == -1 || !p.sound_is_active(day_sound_handle)) {
|
||||
// Play looped, stationary (or relative to player x?)
|
||||
// Usually ambience is 2D/Global. play_stationary_extended allows looping.
|
||||
// Or play_1d at player position if we want panning?
|
||||
// "sounds/nature/day.ogg"
|
||||
day_sound_handle = p.play_stationary("sounds/nature/day.ogg", true);
|
||||
}
|
||||
} else {
|
||||
// Transition to Night
|
||||
if (day_sound_handle != -1) {
|
||||
p.destroy_sound(day_sound_handle);
|
||||
day_sound_handle = -1;
|
||||
}
|
||||
if (night_sound_handle == -1 || !p.sound_is_active(night_sound_handle)) {
|
||||
night_sound_handle = p.play_stationary("sounds/nature/night.ogg", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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