Sound has been updated. As a result snares can be collected from a bit further away as it is now harder to tell when you're about to step on it. A few string updates. Mountain terrain added. May require rope for traversal.
This commit is contained in:
+79
-28
@@ -1,4 +1,3 @@
|
||||
#include "bgt_compat.nvgt"
|
||||
#include "sound_pool.nvgt"
|
||||
#include "virtual_dialogs.nvgt"
|
||||
|
||||
@@ -57,6 +56,10 @@ int run_main_menu() {
|
||||
|
||||
void main()
|
||||
{
|
||||
// Configure sound pool for better spatial audio
|
||||
p.volume_step = AUDIO_VOLUME_STEP / float(AUDIO_TILE_SCALE); // Default falloff in audio units
|
||||
p.pan_step = AUDIO_PAN_STEP; // Panning strength for scaled tile distances
|
||||
|
||||
show_window("Draugnorak");
|
||||
|
||||
bool game_started = false;
|
||||
@@ -154,7 +157,12 @@ void main()
|
||||
// Coordinates Key
|
||||
if (key_pressed(KEY_X)) {
|
||||
string direction_label = (facing == 1) ? "east" : "west";
|
||||
screen_reader_speak(direction_label + ", x " + x + ", y " + y, true);
|
||||
string terrain_info = "";
|
||||
MountainRange@ mountain = get_mountain_at(x);
|
||||
if (mountain !is null) {
|
||||
terrain_info = ", elevation " + y + ", " + mountain.get_terrain_at(x);
|
||||
}
|
||||
screen_reader_speak(direction_label + ", x " + x + ", y " + y + terrain_info, true);
|
||||
}
|
||||
|
||||
// Base Info Key (base only)
|
||||
@@ -165,19 +173,41 @@ void main()
|
||||
// Climbing and Falling Updates
|
||||
update_climbing();
|
||||
update_falling();
|
||||
update_rope_climbing();
|
||||
check_rope_climb_fall();
|
||||
update_mountains();
|
||||
|
||||
// Down arrow to climb down from tree
|
||||
// Down arrow to climb down from tree or start rope climb down
|
||||
if (key_pressed(KEY_DOWN)) {
|
||||
Tree@ tree = get_tree_at(x);
|
||||
if (tree != null && !tree.is_chopped && y > 0 && !jumping && !climbing && !falling) {
|
||||
climb_down_tree();
|
||||
// Check for pending rope climb (going down)
|
||||
if (pending_rope_climb_x != -1 && !rope_climbing && !climbing && !falling && !jumping) {
|
||||
int elevation_change = pending_rope_climb_elevation - y;
|
||||
if (elevation_change < 0) {
|
||||
start_rope_climb(false, pending_rope_climb_x, pending_rope_climb_elevation);
|
||||
pending_rope_climb_x = -1;
|
||||
}
|
||||
}
|
||||
// Tree climbing down
|
||||
else {
|
||||
Tree@ tree = get_tree_at(x);
|
||||
if (tree != null && !tree.is_chopped && y > 0 && !jumping && !climbing && !falling && !rope_climbing) {
|
||||
climb_down_tree();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Jumping Logic
|
||||
if(key_pressed(KEY_UP))
|
||||
{
|
||||
if(!jumping && !climbing && !falling)
|
||||
// Check for pending rope climb (going up)
|
||||
if (pending_rope_climb_x != -1 && !rope_climbing && !climbing && !falling && !jumping) {
|
||||
int elevation_change = pending_rope_climb_elevation - y;
|
||||
if (elevation_change > 0) {
|
||||
start_rope_climb(true, pending_rope_climb_x, pending_rope_climb_elevation);
|
||||
pending_rope_climb_x = -1;
|
||||
}
|
||||
}
|
||||
else if(!jumping && !climbing && !falling && !rope_climbing)
|
||||
{
|
||||
// Check if on tree tile
|
||||
Tree@ tree = get_tree_at(x);
|
||||
@@ -210,46 +240,67 @@ void main()
|
||||
movetime = jumping ? jump_speed : walk_speed;
|
||||
|
||||
// Movement Logic
|
||||
if (key_pressed(KEY_LEFT) && facing != 0 && !climbing && !falling) {
|
||||
if (key_pressed(KEY_LEFT) && facing != 0 && !climbing && !falling && !rope_climbing) {
|
||||
facing = 0;
|
||||
screen_reader_speak("west", true);
|
||||
walktimer.restart();
|
||||
// Cancel pending rope climb when changing direction
|
||||
pending_rope_climb_x = -1;
|
||||
}
|
||||
if (key_pressed(KEY_RIGHT) && facing != 1 && !climbing && !falling) {
|
||||
if (key_pressed(KEY_RIGHT) && facing != 1 && !climbing && !falling && !rope_climbing) {
|
||||
facing = 1;
|
||||
screen_reader_speak("east", true);
|
||||
walktimer.restart();
|
||||
// Cancel pending rope climb when changing direction
|
||||
pending_rope_climb_x = -1;
|
||||
}
|
||||
|
||||
if(walktimer.elapsed > movetime)
|
||||
{
|
||||
int old_x = x;
|
||||
|
||||
// Check if trying to move left/right while in tree
|
||||
if((key_down(KEY_LEFT) || key_down(KEY_RIGHT)) && y > 0 && !jumping && !falling) {
|
||||
// Check if trying to move left/right while in tree (not in mountain)
|
||||
MountainRange@ current_mountain = get_mountain_at(x);
|
||||
if((key_down(KEY_LEFT) || key_down(KEY_RIGHT)) && y > 0 && !jumping && !falling && !rope_climbing && current_mountain is null) {
|
||||
// Fall out of tree
|
||||
climbing = false;
|
||||
start_falling();
|
||||
}
|
||||
|
||||
if(key_down(KEY_LEFT) && x > 0 && !climbing && !falling)
|
||||
if(key_down(KEY_LEFT) && x > 0 && !climbing && !falling && !rope_climbing)
|
||||
{
|
||||
facing = 0;
|
||||
x--;
|
||||
walktimer.restart();
|
||||
if(!jumping) {
|
||||
play_footstep(x, BASE_END, GRASS_END);
|
||||
check_snare_collision(x); // Check when moving onto a tile
|
||||
// Check mountain movement
|
||||
if (can_move_mountain(x, x - 1)) {
|
||||
x--;
|
||||
// Update elevation if in mountain
|
||||
int new_elevation = get_mountain_elevation_at(x);
|
||||
if (new_elevation != y && get_mountain_at(x) !is null) {
|
||||
y = new_elevation;
|
||||
}
|
||||
walktimer.restart();
|
||||
if(!jumping) {
|
||||
play_footstep(x, BASE_END, GRASS_END);
|
||||
check_snare_collision(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(key_down(KEY_RIGHT) && x < MAP_SIZE - 1 && !climbing && !falling)
|
||||
else if(key_down(KEY_RIGHT) && x < MAP_SIZE - 1 && !climbing && !falling && !rope_climbing)
|
||||
{
|
||||
facing = 1;
|
||||
x++;
|
||||
walktimer.restart();
|
||||
if(!jumping) {
|
||||
play_footstep(x, BASE_END, GRASS_END);
|
||||
check_snare_collision(x); // Check when moving onto a tile
|
||||
// Check mountain movement
|
||||
if (can_move_mountain(x, x + 1)) {
|
||||
x++;
|
||||
// Update elevation if in mountain
|
||||
int new_elevation = get_mountain_elevation_at(x);
|
||||
if (new_elevation != y && get_mountain_at(x) !is null) {
|
||||
y = new_elevation;
|
||||
}
|
||||
walktimer.restart();
|
||||
if(!jumping) {
|
||||
play_footstep(x, BASE_END, GRASS_END);
|
||||
check_snare_collision(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -285,7 +336,7 @@ void main()
|
||||
}
|
||||
|
||||
// Sling charge detection
|
||||
if (sling_equipped && (key_down(KEY_LCONTROL) || key_down(KEY_RCONTROL)) && !sling_charging) {
|
||||
if (sling_equipped && (key_down(KEY_LCTRL) || key_down(KEY_RCTRL)) && !sling_charging) {
|
||||
if (inv_stones > 0) {
|
||||
sling_charging = true;
|
||||
sling_charge_timer.restart();
|
||||
@@ -297,12 +348,12 @@ void main()
|
||||
}
|
||||
|
||||
// Update sling charge state while holding
|
||||
if (sling_charging && (key_down(KEY_LCONTROL) || key_down(KEY_RCONTROL))) {
|
||||
if (sling_charging && (key_down(KEY_LCTRL) || key_down(KEY_RCTRL))) {
|
||||
update_sling_charge();
|
||||
}
|
||||
|
||||
// Sling release detection
|
||||
if (sling_charging && (!key_down(KEY_LCONTROL) && !key_down(KEY_RCONTROL))) {
|
||||
if (sling_charging && (!key_down(KEY_LCTRL) && !key_down(KEY_RCTRL))) {
|
||||
release_sling_attack(x);
|
||||
sling_charging = false;
|
||||
if (sling_sound_handle != -1) {
|
||||
@@ -317,7 +368,7 @@ void main()
|
||||
if (spear_equipped) attack_cooldown = 800;
|
||||
if (axe_equipped) attack_cooldown = 1600;
|
||||
|
||||
if((key_down(KEY_LCONTROL) || key_down(KEY_RCONTROL)) && attack_timer.elapsed > attack_cooldown)
|
||||
if((key_down(KEY_LCTRL) || key_down(KEY_RCTRL)) && attack_timer.elapsed > attack_cooldown)
|
||||
{
|
||||
attack_timer.restart();
|
||||
perform_attack(x);
|
||||
@@ -325,6 +376,6 @@ void main()
|
||||
}
|
||||
|
||||
// Audio Listener Update
|
||||
p.update_listener_1d(x);
|
||||
update_listener_tile(x);
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
+55
-6
@@ -1,7 +1,7 @@
|
||||
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)) {
|
||||
// Check if in water first (regular streams or mountain streams)
|
||||
if (is_position_in_water(current_x) || is_mountain_stream_at(current_x)) {
|
||||
return "sounds/terrain/shallow_water.ogg";
|
||||
}
|
||||
|
||||
@@ -27,17 +27,36 @@ string get_footstep_sound(int current_x, int base_end, int grass_end)
|
||||
}
|
||||
else if (expanded_area_start != -1 && current_x >= expanded_area_start && current_x <= expanded_area_end)
|
||||
{
|
||||
// Expanded area - check terrain type
|
||||
// Check for mountain terrain first
|
||||
MountainRange@ mountain = get_mountain_at(current_x);
|
||||
if (mountain !is null) {
|
||||
string terrain = mountain.get_terrain_at(current_x);
|
||||
if (terrain == "stone") {
|
||||
return "sounds/terrain/stone.ogg";
|
||||
} else if (terrain == "gravel") {
|
||||
return "sounds/terrain/gravel.ogg";
|
||||
} else if (terrain == "snow") {
|
||||
return "sounds/terrain/snow.ogg";
|
||||
}
|
||||
}
|
||||
|
||||
// Regular expanded area - check terrain type
|
||||
int index = current_x - expanded_area_start;
|
||||
if (index >= 0 && index < expanded_terrain_types.length())
|
||||
if (index >= 0 && index < int(expanded_terrain_types.length()))
|
||||
{
|
||||
string terrain = expanded_terrain_types[index];
|
||||
// Handle "mountain:terrain" format from older saves
|
||||
if (terrain.find("mountain:") == 0) {
|
||||
terrain = terrain.substr(9);
|
||||
}
|
||||
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";
|
||||
} else if (terrain == "gravel") {
|
||||
return "sounds/terrain/gravel.ogg";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,11 +74,41 @@ void play_footstep(int current_x, int base_end, int grass_end)
|
||||
}
|
||||
}
|
||||
|
||||
int to_audio_position(int tile_x)
|
||||
{
|
||||
return tile_x * AUDIO_TILE_SCALE;
|
||||
}
|
||||
|
||||
float to_audio_volume_step(float volume_step)
|
||||
{
|
||||
return volume_step / float(AUDIO_TILE_SCALE);
|
||||
}
|
||||
|
||||
int play_1d_tile(string sound_file, int listener_x, int sound_x, bool looping, bool persistent = false)
|
||||
{
|
||||
return p.play_1d(sound_file, to_audio_position(listener_x), to_audio_position(sound_x), looping, persistent);
|
||||
}
|
||||
|
||||
bool update_sound_1d_tile(int slot, int sound_x)
|
||||
{
|
||||
return p.update_sound_1d(slot, to_audio_position(sound_x));
|
||||
}
|
||||
|
||||
void update_listener_tile(int listener_x)
|
||||
{
|
||||
p.update_listener_1d(to_audio_position(listener_x));
|
||||
}
|
||||
|
||||
void update_sound_range_1d_tile(int slot, int range_tiles)
|
||||
{
|
||||
p.update_sound_range_1d(slot, range_tiles * AUDIO_TILE_SCALE, range_tiles * AUDIO_TILE_SCALE);
|
||||
}
|
||||
|
||||
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);
|
||||
int slot = p.play_1d(sound_file, to_audio_position(listener_x), to_audio_position(sound_x), looping);
|
||||
if (slot != -1) {
|
||||
p.update_sound_positioning_values(slot, -1.0, volume_step, true);
|
||||
p.update_sound_positioning_values(slot, -1.0, to_audio_volume_step(volume_step), true);
|
||||
}
|
||||
return slot;
|
||||
}
|
||||
|
||||
+3
-3
@@ -161,7 +161,7 @@ void release_sling_attack(int player_x) {
|
||||
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);
|
||||
play_1d_tile("sounds/weapons/sling_hit.ogg", player_x, check_x, false);
|
||||
screen_reader_speak("Stone hit tree at " + check_x + ".", true);
|
||||
return;
|
||||
}
|
||||
@@ -179,11 +179,11 @@ void release_sling_attack(int player_x) {
|
||||
// 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_tile("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_tile("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);
|
||||
}
|
||||
}
|
||||
|
||||
+29
-3
@@ -87,9 +87,35 @@ const int BANDIT_DETECTION_RADIUS = 5;
|
||||
const int BANDIT_WANDER_DIRECTION_CHANGE_MIN = 3000;
|
||||
const int BANDIT_WANDER_DIRECTION_CHANGE_MAX = 8000;
|
||||
|
||||
// Stream audio
|
||||
const int STREAM_SOUND_RANGE = 5;
|
||||
const float STREAM_SOUND_VOLUME_STEP = 0.6;
|
||||
// Audio ranges and volume falloff
|
||||
// Formula: final_volume = start_volume - (volume_step × distance)
|
||||
// Using 30 dB fade over range for gradual but noticeable falloff
|
||||
const int AUDIO_TILE_SCALE = 10;
|
||||
const float AUDIO_PAN_STEP = 2.0;
|
||||
const float AUDIO_VOLUME_STEP = 3.0;
|
||||
const int SNARE_SOUND_RANGE = 5;
|
||||
const float SNARE_SOUND_VOLUME_STEP = 4.0; // More audible for locating snares
|
||||
const float SNARE_SOUND_PAN_STEP = 4.0; // Stronger pan for direction
|
||||
const int SNARE_COLLECT_RANGE = 2;
|
||||
|
||||
const int FIRE_SOUND_RANGE = 6;
|
||||
const float FIRE_SOUND_VOLUME_STEP = 5.0; // 30 dB over 6 tiles
|
||||
|
||||
const int FIREPIT_SOUND_RANGE = 5;
|
||||
const float FIREPIT_SOUND_VOLUME_STEP = 6.0; // 30 dB over 5 tiles
|
||||
|
||||
const int STREAM_SOUND_RANGE = 7;
|
||||
const float STREAM_SOUND_VOLUME_STEP = 4.3; // 30 dB over 7 tiles
|
||||
|
||||
// Mountain configuration
|
||||
const int MOUNTAIN_SIZE = 60;
|
||||
const int MOUNTAIN_MIN_ELEVATION = 0;
|
||||
const int MOUNTAIN_MAX_ELEVATION = 40;
|
||||
const int MOUNTAIN_STEEP_THRESHOLD = 6;
|
||||
const int MOUNTAIN_MAX_SLOPE = 20;
|
||||
const int ROPE_CLIMB_SPEED = 1000;
|
||||
const int MOUNTAIN_STREAM_SOUND_RANGE = 7;
|
||||
const float MOUNTAIN_STREAM_VOLUME_STEP = 4.3; // 30 dB over 7 tiles
|
||||
const int QUEST_MAX_ACTIVE = 4;
|
||||
const int QUEST_CHANCE_PER_FAVOR = 10;
|
||||
const int QUEST_MIN_CHANCE = 5;
|
||||
|
||||
+40
-39
@@ -294,20 +294,21 @@ void run_barricade_menu() {
|
||||
}
|
||||
}
|
||||
|
||||
void simulate_crafting() {
|
||||
void simulate_crafting(int item_count) {
|
||||
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;
|
||||
// Nothing should take less than 4.
|
||||
if(item_count < 4) {
|
||||
item_count = 4;
|
||||
}
|
||||
for(int i = 0; i < item_count; i++) {
|
||||
float pitch = random(85, 115);
|
||||
p.play_stationary_extended("sounds/crafting.ogg", false, 0, 0, 0, pitch);
|
||||
|
||||
timer t;
|
||||
while(t.elapsed < 800) {
|
||||
wait(5);
|
||||
menu_background_tick();
|
||||
}
|
||||
wait(5);
|
||||
menu_background_tick();
|
||||
}
|
||||
p.play_stationary("sounds/crafting_complete.ogg", false);
|
||||
}
|
||||
@@ -321,7 +322,7 @@ void craft_knife() {
|
||||
screen_reader_speak("You can't carry any more stone knives.", true);
|
||||
return;
|
||||
}
|
||||
simulate_crafting();
|
||||
simulate_crafting(2);
|
||||
inv_stones -= 2;
|
||||
inv_knives++;
|
||||
screen_reader_speak("Crafted a Stone Knife.", true);
|
||||
@@ -342,7 +343,7 @@ void craft_spear() {
|
||||
screen_reader_speak("You can't carry any more spears.", true);
|
||||
return;
|
||||
}
|
||||
simulate_crafting();
|
||||
simulate_crafting(3);
|
||||
inv_sticks--;
|
||||
inv_vines--;
|
||||
inv_stones--;
|
||||
@@ -363,7 +364,7 @@ void craft_sling() {
|
||||
screen_reader_speak("You can't carry any more slings.", true);
|
||||
return;
|
||||
}
|
||||
simulate_crafting();
|
||||
simulate_crafting(3);
|
||||
inv_skins--;
|
||||
inv_vines -= 2;
|
||||
inv_slings++;
|
||||
@@ -383,7 +384,7 @@ void craft_skin_hat() {
|
||||
screen_reader_speak("You can't carry any more skin hats.", true);
|
||||
return;
|
||||
}
|
||||
simulate_crafting();
|
||||
simulate_crafting(2);
|
||||
inv_skins--;
|
||||
inv_vines--;
|
||||
inv_skin_hats++;
|
||||
@@ -403,7 +404,7 @@ void craft_skin_gloves() {
|
||||
screen_reader_speak("You can't carry any more skin gloves.", true);
|
||||
return;
|
||||
}
|
||||
simulate_crafting();
|
||||
simulate_crafting(2);
|
||||
inv_skins--;
|
||||
inv_vines--;
|
||||
inv_skin_gloves++;
|
||||
@@ -423,7 +424,7 @@ void craft_skin_pants() {
|
||||
screen_reader_speak("You can't carry any more skin pants.", true);
|
||||
return;
|
||||
}
|
||||
simulate_crafting();
|
||||
simulate_crafting(9);
|
||||
inv_skins -= 6;
|
||||
inv_vines -= 3;
|
||||
inv_skin_pants++;
|
||||
@@ -443,7 +444,7 @@ void craft_skin_tunic() {
|
||||
screen_reader_speak("You can't carry any more skin tunics.", true);
|
||||
return;
|
||||
}
|
||||
simulate_crafting();
|
||||
simulate_crafting(6);
|
||||
inv_skins -= 4;
|
||||
inv_vines -= 2;
|
||||
inv_skin_tunics++;
|
||||
@@ -463,7 +464,7 @@ void craft_moccasins() {
|
||||
screen_reader_speak("You can't carry any more moccasins.", true);
|
||||
return;
|
||||
}
|
||||
simulate_crafting();
|
||||
simulate_crafting(3);
|
||||
inv_skins -= 2;
|
||||
inv_vines--;
|
||||
inv_moccasins++;
|
||||
@@ -483,7 +484,7 @@ void craft_skin_pouch() {
|
||||
screen_reader_speak("You can't carry any more skin pouches.", true);
|
||||
return;
|
||||
}
|
||||
simulate_crafting();
|
||||
simulate_crafting(3);
|
||||
inv_skins -= 2;
|
||||
inv_vines--;
|
||||
inv_skin_pouches++;
|
||||
@@ -503,7 +504,7 @@ void craft_snare() {
|
||||
screen_reader_speak("You can't carry any more snares.", true);
|
||||
return;
|
||||
}
|
||||
simulate_crafting();
|
||||
simulate_crafting(3);
|
||||
inv_sticks--;
|
||||
inv_vines -= 2;
|
||||
inv_snares++;
|
||||
@@ -525,7 +526,7 @@ void craft_axe() {
|
||||
screen_reader_speak("You can't carry any more stone axes.", true);
|
||||
return;
|
||||
}
|
||||
simulate_crafting();
|
||||
simulate_crafting(4);
|
||||
inv_sticks--;
|
||||
inv_vines--;
|
||||
inv_stones -= 2;
|
||||
@@ -547,7 +548,7 @@ void craft_firepit() {
|
||||
if (inv_stones < 9) missing += "9 stones ";
|
||||
|
||||
if (missing == "") {
|
||||
simulate_crafting();
|
||||
simulate_crafting(9);
|
||||
inv_stones -= 9;
|
||||
add_world_firepit(x);
|
||||
screen_reader_speak("Firepit built here.", true);
|
||||
@@ -569,7 +570,7 @@ void craft_campfire() {
|
||||
if (inv_sticks < 2) missing += "2 sticks ";
|
||||
|
||||
if (missing == "") {
|
||||
simulate_crafting();
|
||||
simulate_crafting(3);
|
||||
inv_logs--;
|
||||
inv_sticks -= 2;
|
||||
// Build the fire at the firepit location, not player location
|
||||
@@ -599,7 +600,7 @@ void craft_herb_garden() {
|
||||
if (inv_logs < 2) missing += "2 logs ";
|
||||
|
||||
if (missing == "") {
|
||||
simulate_crafting();
|
||||
simulate_crafting(14);
|
||||
inv_stones -= 9;
|
||||
inv_vines -= 3;
|
||||
inv_logs -= 2;
|
||||
@@ -625,7 +626,7 @@ void craft_storage() {
|
||||
if (inv_vines < STORAGE_VINE_COST) missing += STORAGE_VINE_COST + " vines ";
|
||||
|
||||
if (missing == "") {
|
||||
simulate_crafting();
|
||||
simulate_crafting(23);
|
||||
inv_logs -= STORAGE_LOG_COST;
|
||||
inv_stones -= STORAGE_STONE_COST;
|
||||
inv_vines -= STORAGE_VINE_COST;
|
||||
@@ -650,7 +651,7 @@ void craft_pasture() {
|
||||
if (inv_vines < PASTURE_VINE_COST) missing += PASTURE_VINE_COST + " vines ";
|
||||
|
||||
if (missing == "") {
|
||||
simulate_crafting();
|
||||
simulate_crafting(28);
|
||||
inv_logs -= PASTURE_LOG_COST;
|
||||
inv_vines -= PASTURE_VINE_COST;
|
||||
add_world_pasture(x);
|
||||
@@ -675,7 +676,7 @@ void craft_stable() {
|
||||
if (inv_vines < STABLE_VINE_COST) missing += STABLE_VINE_COST + " vines ";
|
||||
|
||||
if (missing == "") {
|
||||
simulate_crafting();
|
||||
simulate_crafting(35);
|
||||
inv_logs -= STABLE_LOG_COST;
|
||||
inv_stones -= STABLE_STONE_COST;
|
||||
inv_vines -= STABLE_VINE_COST;
|
||||
@@ -700,7 +701,7 @@ void craft_altar() {
|
||||
if (inv_sticks < ALTAR_STICK_COST) missing += ALTAR_STICK_COST + " sticks ";
|
||||
|
||||
if (missing == "") {
|
||||
simulate_crafting();
|
||||
simulate_crafting(12);
|
||||
inv_stones -= ALTAR_STONE_COST;
|
||||
inv_sticks -= ALTAR_STICK_COST;
|
||||
add_world_altar(x);
|
||||
@@ -720,7 +721,7 @@ void reinforce_barricade_with_sticks() {
|
||||
return;
|
||||
}
|
||||
|
||||
simulate_crafting();
|
||||
simulate_crafting(BARRICADE_STICK_COST);
|
||||
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);
|
||||
@@ -736,7 +737,7 @@ void reinforce_barricade_with_vines() {
|
||||
return;
|
||||
}
|
||||
|
||||
simulate_crafting();
|
||||
simulate_crafting(BARRICADE_VINE_COST);
|
||||
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);
|
||||
@@ -752,7 +753,7 @@ void reinforce_barricade_with_log() {
|
||||
return;
|
||||
}
|
||||
|
||||
simulate_crafting();
|
||||
simulate_crafting(BARRICADE_LOG_COST);
|
||||
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);
|
||||
@@ -768,7 +769,7 @@ void reinforce_barricade_with_stones() {
|
||||
return;
|
||||
}
|
||||
|
||||
simulate_crafting();
|
||||
simulate_crafting(BARRICADE_STONE_COST);
|
||||
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);
|
||||
@@ -784,7 +785,7 @@ void craft_fishing_pole() {
|
||||
screen_reader_speak("You can't carry any more fishing poles.", true);
|
||||
return;
|
||||
}
|
||||
simulate_crafting();
|
||||
simulate_crafting(3);
|
||||
inv_sticks--;
|
||||
inv_vines -= 2;
|
||||
inv_fishing_poles++;
|
||||
@@ -803,7 +804,7 @@ void craft_rope() {
|
||||
screen_reader_speak("You can't carry any more rope.", true);
|
||||
return;
|
||||
}
|
||||
simulate_crafting();
|
||||
simulate_crafting(3);
|
||||
inv_vines -= 3;
|
||||
inv_ropes++;
|
||||
screen_reader_speak("Crafted rope.", true);
|
||||
@@ -821,7 +822,7 @@ void craft_reed_basket() {
|
||||
screen_reader_speak("You can't carry any more reed baskets.", true);
|
||||
return;
|
||||
}
|
||||
simulate_crafting();
|
||||
simulate_crafting(3);
|
||||
inv_reeds -= 3;
|
||||
inv_reed_baskets++;
|
||||
screen_reader_speak("Crafted a reed basket.", true);
|
||||
@@ -846,7 +847,7 @@ void craft_clay_pot() {
|
||||
screen_reader_speak("You can't carry any more clay pots.", true);
|
||||
return;
|
||||
}
|
||||
simulate_crafting();
|
||||
simulate_crafting(3);
|
||||
inv_clay -= 3;
|
||||
inv_clay_pots++;
|
||||
screen_reader_speak("Crafted a clay pot.", true);
|
||||
@@ -880,7 +881,7 @@ void butcher_small_game() {
|
||||
screen_reader_speak("You can't carry any more skins.", true);
|
||||
return;
|
||||
}
|
||||
simulate_crafting();
|
||||
simulate_crafting(1);
|
||||
|
||||
// Get the type of game we're butchering (first in the list)
|
||||
string game_type = inv_small_game_types[0];
|
||||
|
||||
+157
-43
@@ -40,18 +40,14 @@ class Tree {
|
||||
}
|
||||
|
||||
void update() {
|
||||
// Only play tree sound if not chopped and within 3 tiles distance (2 tiles on either side)
|
||||
// Keep tree sound active so distance-based fade can work.
|
||||
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;
|
||||
}
|
||||
if (sound_handle == -1 || !p.sound_is_active(sound_handle)) {
|
||||
sound_handle = play_1d_tile("sounds/environment/tree.ogg", x, position, true);
|
||||
}
|
||||
} else if (sound_handle != -1) {
|
||||
p.destroy_sound(sound_handle);
|
||||
sound_handle = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +195,7 @@ void damage_tree(int target_x, int damage) {
|
||||
}
|
||||
|
||||
// Play the falling sound at the tree's position
|
||||
p.play_1d("sounds/items/tree.ogg", x, target.position, false);
|
||||
play_1d_tile("sounds/items/tree.ogg", x, target.position, false);
|
||||
|
||||
int sticks_dropped = random(1, 3);
|
||||
int vines_dropped = random(1, 2);
|
||||
@@ -228,10 +224,8 @@ void damage_tree(int target_x, int damage) {
|
||||
|
||||
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++) {
|
||||
// Check for snares nearby (adjacent within range)
|
||||
for (int check_x = current_x - SNARE_COLLECT_RANGE; check_x <= current_x + SNARE_COLLECT_RANGE; 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)
|
||||
@@ -496,11 +490,14 @@ void start_falling() {
|
||||
void update_falling() {
|
||||
if (!falling) return;
|
||||
|
||||
// Get ground level (mountain elevation or 0)
|
||||
int ground_level = get_mountain_elevation_at(x);
|
||||
|
||||
// Fall faster than climbing - 1 foot per 100ms
|
||||
if (fall_timer.elapsed > 100) {
|
||||
fall_timer.restart();
|
||||
|
||||
if (y > 0) {
|
||||
if (y > ground_level) {
|
||||
y--;
|
||||
|
||||
// Restart falling sound with decreasing pitch each foot
|
||||
@@ -509,38 +506,155 @@ void update_falling() {
|
||||
}
|
||||
|
||||
// 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));
|
||||
float height_above_ground = float(y - ground_level);
|
||||
float pitch_percent = 50.0 + (50.0 * (height_above_ground / 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);
|
||||
|
||||
// Check if we've reached ground level
|
||||
if (y <= ground_level) {
|
||||
land_on_ground(ground_level);
|
||||
}
|
||||
} 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;
|
||||
land_on_ground(ground_level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void land_on_ground(int ground_level) {
|
||||
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 - ground_level;
|
||||
y = ground_level;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Mountain movement check
|
||||
bool can_move_mountain(int from_x, int to_x) {
|
||||
MountainRange@ mountain = get_mountain_at(to_x);
|
||||
if (mountain is null) {
|
||||
// Not entering a mountain
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if from_x is also in same mountain
|
||||
if (!mountain.contains_position(from_x)) {
|
||||
// Entering mountain from edge - always allowed
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check elevation change
|
||||
if (mountain.is_steep_section(from_x, to_x)) {
|
||||
// Need rope
|
||||
if (inv_ropes < 1) {
|
||||
screen_reader_speak("You'll need a rope to climb there.", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prompt for rope climb
|
||||
int elevation_change = mountain.get_elevation_change(from_x, to_x);
|
||||
if (elevation_change > 0) {
|
||||
screen_reader_speak("Press up to climb up.", true);
|
||||
} else {
|
||||
screen_reader_speak("Press down to climb down.", true);
|
||||
}
|
||||
|
||||
// Store pending rope climb info
|
||||
pending_rope_climb_x = to_x;
|
||||
pending_rope_climb_elevation = mountain.get_elevation_at(to_x);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Rope climbing functions
|
||||
void start_rope_climb(bool climbing_up, int target_x, int target_elevation) {
|
||||
rope_climbing = true;
|
||||
rope_climb_up = climbing_up;
|
||||
rope_climb_target_x = target_x;
|
||||
rope_climb_target_y = target_elevation;
|
||||
rope_climb_start_y = y;
|
||||
rope_climb_timer.restart();
|
||||
|
||||
int distance = rope_climb_target_y - y;
|
||||
if (distance < 0) distance = -distance;
|
||||
|
||||
string direction = climbing_up ? "up" : "down";
|
||||
screen_reader_speak("Climbing " + direction + ". " + distance + " feet.", true);
|
||||
}
|
||||
|
||||
void update_rope_climbing() {
|
||||
if (!rope_climbing) return;
|
||||
|
||||
// Climb at ROPE_CLIMB_SPEED ms per foot
|
||||
if (rope_climb_timer.elapsed > ROPE_CLIMB_SPEED) {
|
||||
rope_climb_timer.restart();
|
||||
|
||||
if (rope_climb_up) {
|
||||
// Climbing up
|
||||
if (y < rope_climb_target_y) {
|
||||
y++;
|
||||
p.play_stationary("sounds/actions/climb_rope.ogg", false);
|
||||
|
||||
if (y >= rope_climb_target_y) {
|
||||
complete_rope_climb();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Climbing down
|
||||
if (y > rope_climb_target_y) {
|
||||
y--;
|
||||
p.play_stationary("sounds/actions/climb_rope.ogg", false);
|
||||
|
||||
if (y <= rope_climb_target_y) {
|
||||
complete_rope_climb();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void complete_rope_climb() {
|
||||
rope_climbing = false;
|
||||
x = rope_climb_target_x;
|
||||
y = rope_climb_target_y;
|
||||
|
||||
// Play footstep for new terrain
|
||||
play_footstep(x, BASE_END, GRASS_END);
|
||||
|
||||
screen_reader_speak("Reached elevation " + y + ".", true);
|
||||
}
|
||||
|
||||
void check_rope_climb_fall() {
|
||||
if (!rope_climbing) return;
|
||||
|
||||
if (key_down(KEY_LEFT) || key_down(KEY_RIGHT)) {
|
||||
// Fall from rope!
|
||||
rope_climbing = false;
|
||||
start_falling();
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -40,7 +40,7 @@ void update_notifications() {
|
||||
|
||||
void check_notification_keys() {
|
||||
// [ for previous notification (older) with position
|
||||
if (key_pressed(KEY_LBRACKET)) {
|
||||
if (key_pressed(KEY_LEFTBRACKET)) {
|
||||
if (notification_history.length() == 0) {
|
||||
screen_reader_speak("No notifications.", true);
|
||||
return;
|
||||
@@ -58,7 +58,7 @@ void check_notification_keys() {
|
||||
}
|
||||
|
||||
// ] for next notification (newer) with position
|
||||
if (key_pressed(KEY_RBRACKET)) {
|
||||
if (key_pressed(KEY_RIGHTBRACKET)) {
|
||||
if (notification_history.length() == 0) {
|
||||
screen_reader_speak("No notifications.", true);
|
||||
return;
|
||||
|
||||
@@ -11,6 +11,16 @@ int fall_sound_handle = -1; // Handle for looping fall sound
|
||||
timer fall_timer; // For fall sound pitch
|
||||
timer climb_timer; // For climb speed
|
||||
|
||||
// Rope climbing state
|
||||
bool rope_climbing = false;
|
||||
bool rope_climb_up = true;
|
||||
int rope_climb_target_x = 0;
|
||||
int rope_climb_target_y = 0;
|
||||
int rope_climb_start_y = 0;
|
||||
timer rope_climb_timer;
|
||||
int pending_rope_climb_x = -1;
|
||||
int pending_rope_climb_elevation = 0;
|
||||
|
||||
// Health System
|
||||
int player_health = 10;
|
||||
int base_max_health = 10;
|
||||
|
||||
@@ -145,6 +145,7 @@ void clear_world_objects() {
|
||||
|
||||
clear_zombies();
|
||||
clear_bandits();
|
||||
clear_mountains();
|
||||
}
|
||||
|
||||
void reset_game_state() {
|
||||
@@ -161,6 +162,13 @@ void reset_game_state() {
|
||||
fall_start_y = 0;
|
||||
sling_charging = false;
|
||||
searching = false;
|
||||
rope_climbing = false;
|
||||
rope_climb_up = true;
|
||||
rope_climb_target_x = 0;
|
||||
rope_climb_target_y = 0;
|
||||
rope_climb_start_y = 0;
|
||||
pending_rope_climb_x = -1;
|
||||
pending_rope_climb_elevation = 0;
|
||||
|
||||
player_health = 10;
|
||||
base_max_health = 10;
|
||||
@@ -302,6 +310,32 @@ string serialize_bandit(Bandit@ bandit) {
|
||||
return bandit.position + "|" + bandit.health + "|" + bandit.weapon_type + "|" + bandit.behavior_state + "|" + bandit.wander_direction + "|" + bandit.move_interval;
|
||||
}
|
||||
|
||||
string serialize_mountain(MountainRange@ mountain) {
|
||||
string result = mountain.start_position + "|" + mountain.end_position + "|";
|
||||
|
||||
// Serialize elevations
|
||||
for (int i = 0; i < int(mountain.elevations.length()); i++) {
|
||||
if (i > 0) result += ",";
|
||||
result += mountain.elevations[i];
|
||||
}
|
||||
result += "|";
|
||||
|
||||
// Serialize terrain types
|
||||
for (int i = 0; i < int(mountain.terrain_types.length()); i++) {
|
||||
if (i > 0) result += ",";
|
||||
result += mountain.terrain_types[i];
|
||||
}
|
||||
result += "|";
|
||||
|
||||
// Serialize stream positions
|
||||
for (uint i = 0; i < mountain.stream_positions.length(); i++) {
|
||||
if (i > 0) result += ",";
|
||||
result += mountain.stream_positions[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
string join_string_array(const string[]@ arr) {
|
||||
if (@arr == null || arr.length() == 0) return "";
|
||||
string result = arr[0];
|
||||
@@ -645,6 +679,12 @@ bool save_game_state() {
|
||||
}
|
||||
saveData.set("bandits_data", join_string_array(banditData));
|
||||
|
||||
string[] mountainData;
|
||||
for (uint i = 0; i < world_mountains.length(); i++) {
|
||||
mountainData.insert_last(serialize_mountain(world_mountains[i]));
|
||||
}
|
||||
saveData.set("mountains_data", join_string_array(mountainData));
|
||||
|
||||
string rawData = saveData.serialize();
|
||||
string encryptedData = encrypt_save_data(rawData);
|
||||
return save_data(SAVE_FILE_PATH, encryptedData);
|
||||
@@ -982,6 +1022,45 @@ bool load_game_state() {
|
||||
bandits.insert_last(b);
|
||||
}
|
||||
|
||||
string[] mountainData = get_string_list_or_split(saveData, "mountains_data");
|
||||
for (uint i = 0; i < mountainData.length(); i++) {
|
||||
string[]@ parts = mountainData[i].split("|");
|
||||
if (parts.length() < 5) continue;
|
||||
|
||||
int start_pos = parse_int(parts[0]);
|
||||
int end_pos = parse_int(parts[1]);
|
||||
int size = end_pos - start_pos + 1;
|
||||
|
||||
// Create mountain with minimal init (we'll override everything)
|
||||
MountainRange@ mountain = MountainRange(start_pos, 1);
|
||||
mountain.start_position = start_pos;
|
||||
mountain.end_position = end_pos;
|
||||
|
||||
// Parse elevations
|
||||
string[]@ elev_parts = parts[2].split(",");
|
||||
mountain.elevations.resize(elev_parts.length());
|
||||
for (uint j = 0; j < elev_parts.length(); j++) {
|
||||
mountain.elevations[j] = parse_int(elev_parts[j]);
|
||||
}
|
||||
|
||||
// Parse terrain types
|
||||
string[]@ terrain_parts = parts[3].split(",");
|
||||
mountain.terrain_types.resize(terrain_parts.length());
|
||||
for (uint j = 0; j < terrain_parts.length(); j++) {
|
||||
mountain.terrain_types[j] = terrain_parts[j];
|
||||
}
|
||||
|
||||
// Parse stream positions
|
||||
if (parts[4].length() > 0) {
|
||||
string[]@ stream_parts = parts[4].split(",");
|
||||
for (uint j = 0; j < stream_parts.length(); j++) {
|
||||
mountain.stream_positions.insert_last(parse_int(stream_parts[j]));
|
||||
}
|
||||
}
|
||||
|
||||
world_mountains.insert_last(mountain);
|
||||
}
|
||||
|
||||
update_ambience(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
+66
-17
@@ -17,7 +17,7 @@ bool crossfade_active = false;
|
||||
bool crossfade_to_night = false; // true = fading to night, false = fading to day
|
||||
timer crossfade_timer;
|
||||
const int CROSSFADE_DURATION = 60000; // 1 minute (1 game hour)
|
||||
const float CROSSFADE_MIN_VOLUME = -40.0; // dB, effectively silent but not extreme
|
||||
const float CROSSFADE_MIN_VOLUME = -25.0; // dB, keep overlap audible during crossfade
|
||||
const float CROSSFADE_MAX_VOLUME = 0.0; // dB, full volume
|
||||
|
||||
// Expansion and invasion tracking
|
||||
@@ -53,6 +53,16 @@ void expand_area() {
|
||||
// Play invasion sound
|
||||
p.play_stationary("sounds/enemies/invasion.ogg", false);
|
||||
|
||||
// 25% chance for mountain, 75% for regular expansion
|
||||
int type_roll = random(0, 3);
|
||||
if (type_roll == 0) {
|
||||
expand_mountain();
|
||||
} else {
|
||||
expand_regular_area();
|
||||
}
|
||||
}
|
||||
|
||||
void expand_regular_area() {
|
||||
// Calculate new area
|
||||
int new_start = MAP_SIZE;
|
||||
int new_end = MAP_SIZE + EXPANSION_SIZE - 1;
|
||||
@@ -103,6 +113,30 @@ void expand_area() {
|
||||
notify("The area has expanded! New territory discovered to the east.");
|
||||
}
|
||||
|
||||
void expand_mountain() {
|
||||
int new_start = MAP_SIZE;
|
||||
int size = MOUNTAIN_SIZE;
|
||||
int new_end = new_start + size - 1;
|
||||
|
||||
if (expanded_area_start == -1) {
|
||||
expanded_area_start = new_start;
|
||||
}
|
||||
expanded_area_end = new_end;
|
||||
MAP_SIZE += size;
|
||||
|
||||
// Generate mountain range
|
||||
MountainRange@ mountain = MountainRange(new_start, size);
|
||||
world_mountains.insert_last(mountain);
|
||||
|
||||
// Fill terrain types array for compatibility with save system
|
||||
for (int i = 0; i < size; i++) {
|
||||
expanded_terrain_types.insert_last("mountain:" + mountain.terrain_types[i]);
|
||||
}
|
||||
|
||||
area_expanded_today = true;
|
||||
notify("A mountain range has been discovered to the east!");
|
||||
}
|
||||
|
||||
void start_invasion() {
|
||||
expand_area();
|
||||
invasion_active = true;
|
||||
@@ -139,26 +173,34 @@ void schedule_invasion() {
|
||||
}
|
||||
|
||||
void check_scheduled_invasion() {
|
||||
if (invasion_active || invasion_triggered_today) return;
|
||||
if (invasion_scheduled_hour == -1) return;
|
||||
if (current_hour == invasion_scheduled_hour) {
|
||||
invasion_scheduled_hour = -1;
|
||||
invasion_triggered_today = true;
|
||||
start_invasion();
|
||||
} else if (current_hour > 11) {
|
||||
invasion_scheduled_hour = -1;
|
||||
if (invasion_active) return;
|
||||
|
||||
// Check scheduled invasion regardless of triggered flag (fixes bug where flag was set early in old saves)
|
||||
if (invasion_scheduled_hour != -1) {
|
||||
if (current_hour == invasion_scheduled_hour) {
|
||||
invasion_scheduled_hour = -1;
|
||||
invasion_triggered_today = true;
|
||||
start_invasion();
|
||||
} else if (current_hour > 11) {
|
||||
invasion_scheduled_hour = -1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (invasion_triggered_today) return;
|
||||
}
|
||||
|
||||
void attempt_daily_invasion() {
|
||||
if (current_day < 2) return;
|
||||
if (invasion_triggered_today || invasion_active) return;
|
||||
if (invasion_roll_done_today) return;
|
||||
if (current_hour < 6 || current_hour > 12) return;
|
||||
|
||||
invasion_roll_done_today = true;
|
||||
|
||||
int roll = random(1, 100);
|
||||
if (roll > invasion_chance) return;
|
||||
|
||||
invasion_triggered_today = true;
|
||||
schedule_invasion();
|
||||
check_scheduled_invasion();
|
||||
}
|
||||
@@ -250,24 +292,30 @@ void attempt_blessing() {
|
||||
favor -= 1.0;
|
||||
if (favor < 0) favor = 0;
|
||||
|
||||
string[] god_names = {
|
||||
"Odin's", "Thor's", "Freyja's", "Loki's", "Tyr's", "Baldur's",
|
||||
"Frigg's", "Heimdall's", "Hel's", "Fenrir's", "Freyr's", "The gods'"
|
||||
};
|
||||
string god_name = god_names[random(0, god_names.length() - 1)];
|
||||
|
||||
if (choice == 0) {
|
||||
int before = player_health;
|
||||
player_health += BLESSING_HEAL_AMOUNT;
|
||||
if (player_health > max_health) player_health = max_health;
|
||||
int healed = player_health - before;
|
||||
string bonus = (healed > 0) ? "You feel restored. +" + healed + " health." : "You feel restored.";
|
||||
notify("The gods' favor shines upon you. " + bonus);
|
||||
notify(god_name + " favor shines upon you. " + bonus);
|
||||
} else if (choice == 1) {
|
||||
blessing_speed_active = true;
|
||||
blessing_speed_timer.restart();
|
||||
update_max_health_from_equipment();
|
||||
notify("The gods' favor shines upon you. You feel swift for a while.");
|
||||
notify(god_name + " favor shines upon you. You feel swift for a while.");
|
||||
} else if (choice == 2) {
|
||||
int gained = add_barricade_health(BLESSING_BARRICADE_REPAIR);
|
||||
string bonus = (gained > 0)
|
||||
? "A divine force repairs the barricade. +" + gained + " health."
|
||||
: "A divine force surrounds the barricade.";
|
||||
notify("The gods' favor shines upon you. " + bonus);
|
||||
notify(god_name + " favor shines upon you. " + bonus);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,10 +441,11 @@ void update_crossfade() {
|
||||
float progress = float(crossfade_timer.elapsed) / float(CROSSFADE_DURATION);
|
||||
if (progress > 1.0) progress = 1.0;
|
||||
|
||||
// Volume interpolation: fade out goes 0 -> -40, fade in goes -40 -> 0
|
||||
float volume_range = CROSSFADE_MAX_VOLUME - CROSSFADE_MIN_VOLUME; // 40 dB range
|
||||
float fade_out_vol = CROSSFADE_MAX_VOLUME - (volume_range * progress); // 0 -> -40
|
||||
float fade_in_vol = CROSSFADE_MIN_VOLUME + (volume_range * progress); // -40 -> 0
|
||||
// Volume interpolation: use a slow-start curve to make fade-outs more gradual
|
||||
float volume_range = CROSSFADE_MAX_VOLUME - CROSSFADE_MIN_VOLUME; // dB range
|
||||
float eased_progress = progress * progress;
|
||||
float fade_out_vol = CROSSFADE_MAX_VOLUME - (volume_range * eased_progress); // 0 -> min
|
||||
float fade_in_vol = CROSSFADE_MIN_VOLUME + (volume_range * eased_progress); // min -> 0
|
||||
|
||||
if (crossfade_to_night) {
|
||||
// Fading day out, night in
|
||||
|
||||
+251
-31
@@ -114,15 +114,11 @@ class WorldSnare {
|
||||
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 {
|
||||
// Keep snare sound active so distance-based fade can work.
|
||||
if (sound_handle == -1 || !p.sound_is_active(sound_handle)) {
|
||||
sound_handle = play_1d_tile("sounds/actions/set_snare.ogg", x, position, true);
|
||||
if (sound_handle != -1) {
|
||||
p.destroy_sound(sound_handle);
|
||||
sound_handle = -1;
|
||||
p.update_sound_positioning_values(sound_handle, SNARE_SOUND_PAN_STEP, to_audio_volume_step(SNARE_SOUND_VOLUME_STEP), true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,13 +196,13 @@ class WorldFire {
|
||||
// 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!");
|
||||
notify("Fire at x " + position + " y " + y + " is getting low!");
|
||||
}
|
||||
|
||||
// Fire went out
|
||||
if (fuel_remaining <= 0) {
|
||||
fuel_remaining = 0;
|
||||
notify("Fire at " + position + " has gone out.");
|
||||
notify("Fire at x " + position + " y " + y + " has gone out.");
|
||||
if (sound_handle != -1) {
|
||||
p.destroy_sound(sound_handle);
|
||||
sound_handle = -1;
|
||||
@@ -215,16 +211,12 @@ class WorldFire {
|
||||
}
|
||||
}
|
||||
|
||||
// Limit fire sound to 2 tiles distance (only if burning)
|
||||
// Keep fire sound active while burning so distance-based fade can work.
|
||||
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.sound_is_active(sound_handle)) {
|
||||
sound_handle = play_1d_tile("sounds/items/fire.ogg", x, position, true);
|
||||
if (sound_handle != -1) {
|
||||
p.destroy_sound(sound_handle);
|
||||
sound_handle = -1;
|
||||
p.update_sound_positioning_values(sound_handle, -1.0, to_audio_volume_step(FIRE_SOUND_VOLUME_STEP), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -297,11 +289,13 @@ class WorldStream {
|
||||
int start_position;
|
||||
int end_position;
|
||||
int sound_handle;
|
||||
int sound_position;
|
||||
|
||||
WorldStream(int start_pos, int width) {
|
||||
start_position = start_pos;
|
||||
end_position = start_pos + width - 1;
|
||||
sound_handle = -1;
|
||||
sound_position = -1;
|
||||
}
|
||||
|
||||
bool contains_position(int pos) {
|
||||
@@ -317,22 +311,25 @@ class WorldStream {
|
||||
}
|
||||
|
||||
void update() {
|
||||
int center = get_center_position();
|
||||
|
||||
// Play stream sound within range of center
|
||||
if (abs(x - center) <= STREAM_SOUND_RANGE) {
|
||||
if (sound_handle == -1 || !p.sound_is_active(sound_handle)) {
|
||||
sound_handle = p.play_1d("sounds/terrain/stream.ogg", x, center, true);
|
||||
if (sound_handle != -1) {
|
||||
p.update_sound_positioning_values(sound_handle, -1.0, STREAM_SOUND_VOLUME_STEP, true);
|
||||
p.update_sound_range_1d(sound_handle, STREAM_SOUND_RANGE, STREAM_SOUND_RANGE);
|
||||
}
|
||||
}
|
||||
int sound_pos = 0;
|
||||
if (x < start_position) {
|
||||
sound_pos = start_position;
|
||||
} else if (x > end_position) {
|
||||
sound_pos = end_position;
|
||||
} else {
|
||||
sound_pos = x;
|
||||
}
|
||||
|
||||
// Keep stream sound active so distance-based fade can work.
|
||||
if (sound_handle == -1 || !p.sound_is_active(sound_handle)) {
|
||||
sound_handle = play_1d_tile("sounds/terrain/stream.ogg", x, sound_pos, true);
|
||||
sound_position = sound_pos;
|
||||
if (sound_handle != -1) {
|
||||
p.destroy_sound(sound_handle);
|
||||
sound_handle = -1;
|
||||
p.update_sound_positioning_values(sound_handle, -1.0, to_audio_volume_step(STREAM_SOUND_VOLUME_STEP), true);
|
||||
}
|
||||
} else if (sound_position != sound_pos) {
|
||||
update_sound_1d_tile(sound_handle, sound_pos);
|
||||
sound_position = sound_pos;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,6 +338,7 @@ class WorldStream {
|
||||
p.destroy_sound(sound_handle);
|
||||
sound_handle = -1;
|
||||
}
|
||||
sound_position = -1;
|
||||
}
|
||||
}
|
||||
WorldStream@[] world_streams;
|
||||
@@ -1021,3 +1019,225 @@ WorldStream@ get_stream_at(int pos) {
|
||||
bool is_position_in_water(int pos) {
|
||||
return get_stream_at(pos) != null;
|
||||
}
|
||||
|
||||
// Mountain Range Class
|
||||
class MountainRange {
|
||||
int start_position;
|
||||
int end_position;
|
||||
int[] elevations;
|
||||
string[] terrain_types;
|
||||
int[] stream_positions;
|
||||
int stream_sound_handle;
|
||||
int stream_sound_position;
|
||||
|
||||
MountainRange(int start_pos, int size) {
|
||||
start_position = start_pos;
|
||||
end_position = start_pos + size - 1;
|
||||
elevations.resize(size);
|
||||
terrain_types.resize(size);
|
||||
stream_sound_handle = -1;
|
||||
stream_sound_position = -1;
|
||||
generate_terrain();
|
||||
}
|
||||
|
||||
void generate_terrain() {
|
||||
int size = int(elevations.length());
|
||||
|
||||
// Initialize endpoints at moderate elevations
|
||||
elevations[0] = random(5, 15);
|
||||
elevations[size - 1] = random(5, 15);
|
||||
|
||||
// Use midpoint displacement for natural terrain
|
||||
midpoint_displace(0, size - 1, 25);
|
||||
|
||||
// Clamp values to valid range
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (elevations[i] < MOUNTAIN_MIN_ELEVATION) elevations[i] = MOUNTAIN_MIN_ELEVATION;
|
||||
if (elevations[i] > MOUNTAIN_MAX_ELEVATION) elevations[i] = MOUNTAIN_MAX_ELEVATION;
|
||||
}
|
||||
|
||||
// Smooth to enforce max slope constraint
|
||||
smooth_slopes();
|
||||
|
||||
// Assign terrain types based on elevation
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (elevations[i] > 30) {
|
||||
terrain_types[i] = "snow";
|
||||
} else if (elevations[i] > 15) {
|
||||
terrain_types[i] = "stone";
|
||||
} else {
|
||||
terrain_types[i] = "gravel";
|
||||
}
|
||||
}
|
||||
|
||||
// Place streams in valley areas
|
||||
place_streams();
|
||||
}
|
||||
|
||||
void midpoint_displace(int left, int right, int roughness) {
|
||||
if (right - left <= 1) return;
|
||||
|
||||
int mid = (left + right) / 2;
|
||||
int avg = (elevations[left] + elevations[right]) / 2;
|
||||
int displacement = random(-roughness, roughness);
|
||||
elevations[mid] = avg + displacement;
|
||||
|
||||
int new_roughness = roughness * 7 / 10;
|
||||
if (new_roughness < 1) new_roughness = 1;
|
||||
|
||||
midpoint_displace(left, mid, new_roughness);
|
||||
midpoint_displace(mid, right, new_roughness);
|
||||
}
|
||||
|
||||
void smooth_slopes() {
|
||||
bool changed = true;
|
||||
int iterations = 0;
|
||||
while (changed && iterations < 100) {
|
||||
changed = false;
|
||||
iterations++;
|
||||
|
||||
for (int i = 1; i < int(elevations.length()); i++) {
|
||||
int diff = elevations[i] - elevations[i-1];
|
||||
if (diff > MOUNTAIN_MAX_SLOPE) {
|
||||
elevations[i] = elevations[i-1] + MOUNTAIN_MAX_SLOPE;
|
||||
changed = true;
|
||||
} else if (diff < -MOUNTAIN_MAX_SLOPE) {
|
||||
elevations[i] = elevations[i-1] - MOUNTAIN_MAX_SLOPE;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void place_streams() {
|
||||
// Find valley bottoms (local minima)
|
||||
int[] valleys;
|
||||
for (int i = 2; i < int(elevations.length()) - 2; i++) {
|
||||
if (elevations[i] < elevations[i-1] && elevations[i] < elevations[i+1] &&
|
||||
elevations[i] < elevations[i-2] && elevations[i] < elevations[i+2]) {
|
||||
valleys.insert_last(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Place 1-3 streams in valleys
|
||||
int num_streams = random(1, 3);
|
||||
if (num_streams > int(valleys.length())) num_streams = int(valleys.length());
|
||||
|
||||
for (int i = 0; i < num_streams && valleys.length() > 0; i++) {
|
||||
int idx = random(0, int(valleys.length()) - 1);
|
||||
stream_positions.insert_last(valleys[idx]);
|
||||
valleys.remove_at(idx);
|
||||
}
|
||||
}
|
||||
|
||||
int get_elevation_at(int world_x) {
|
||||
if (world_x < start_position || world_x > end_position) return 0;
|
||||
int index = world_x - start_position;
|
||||
return elevations[index];
|
||||
}
|
||||
|
||||
string get_terrain_at(int world_x) {
|
||||
if (world_x < start_position || world_x > end_position) return "stone";
|
||||
int index = world_x - start_position;
|
||||
return terrain_types[index];
|
||||
}
|
||||
|
||||
int get_elevation_change(int from_x, int to_x) {
|
||||
int from_elev = get_elevation_at(from_x);
|
||||
int to_elev = get_elevation_at(to_x);
|
||||
return to_elev - from_elev;
|
||||
}
|
||||
|
||||
bool is_steep_section(int from_x, int to_x) {
|
||||
int change = get_elevation_change(from_x, to_x);
|
||||
if (change < 0) change = -change;
|
||||
return change >= MOUNTAIN_STEEP_THRESHOLD;
|
||||
}
|
||||
|
||||
bool contains_position(int world_x) {
|
||||
return world_x >= start_position && world_x <= end_position;
|
||||
}
|
||||
|
||||
bool is_stream_at(int world_x) {
|
||||
if (!contains_position(world_x)) return false;
|
||||
int index = world_x - start_position;
|
||||
for (uint i = 0; i < stream_positions.length(); i++) {
|
||||
if (stream_positions[i] == index) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void update() {
|
||||
if (stream_positions.length() == 0) return;
|
||||
|
||||
// Find nearest stream to player
|
||||
int nearest_stream = -1;
|
||||
int nearest_distance = 999;
|
||||
|
||||
for (uint i = 0; i < stream_positions.length(); i++) {
|
||||
int stream_world_x = start_position + stream_positions[i];
|
||||
int distance = abs(x - stream_world_x);
|
||||
if (distance < nearest_distance) {
|
||||
nearest_distance = distance;
|
||||
nearest_stream = stream_world_x;
|
||||
}
|
||||
}
|
||||
|
||||
// Keep nearest stream sound active so distance-based fade can work.
|
||||
if (nearest_stream != -1) {
|
||||
if (stream_sound_handle == -1 || !p.sound_is_active(stream_sound_handle)) {
|
||||
stream_sound_handle = play_1d_tile("sounds/terrain/stream.ogg", x, nearest_stream, true);
|
||||
stream_sound_position = nearest_stream;
|
||||
if (stream_sound_handle != -1) {
|
||||
p.update_sound_positioning_values(stream_sound_handle, -1.0, to_audio_volume_step(MOUNTAIN_STREAM_VOLUME_STEP), true);
|
||||
}
|
||||
} else if (stream_sound_position != nearest_stream) {
|
||||
update_sound_1d_tile(stream_sound_handle, nearest_stream);
|
||||
stream_sound_position = nearest_stream;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
if (stream_sound_handle != -1) {
|
||||
p.destroy_sound(stream_sound_handle);
|
||||
stream_sound_handle = -1;
|
||||
}
|
||||
stream_sound_position = -1;
|
||||
}
|
||||
}
|
||||
MountainRange@[] world_mountains;
|
||||
|
||||
MountainRange@ get_mountain_at(int world_x) {
|
||||
for (uint i = 0; i < world_mountains.length(); i++) {
|
||||
if (world_mountains[i].contains_position(world_x)) {
|
||||
return @world_mountains[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
int get_mountain_elevation_at(int world_x) {
|
||||
MountainRange@ mountain = get_mountain_at(world_x);
|
||||
if (mountain is null) return 0;
|
||||
return mountain.get_elevation_at(world_x);
|
||||
}
|
||||
|
||||
bool is_mountain_stream_at(int world_x) {
|
||||
MountainRange@ mountain = get_mountain_at(world_x);
|
||||
if (mountain is null) return false;
|
||||
return mountain.is_stream_at(world_x);
|
||||
}
|
||||
|
||||
void update_mountains() {
|
||||
for (uint i = 0; i < world_mountains.length(); i++) {
|
||||
world_mountains[i].update();
|
||||
}
|
||||
}
|
||||
|
||||
void clear_mountains() {
|
||||
for (uint i = 0; i < world_mountains.length(); i++) {
|
||||
world_mountains[i].destroy();
|
||||
}
|
||||
world_mountains.resize(0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user