A couple of terrain types added. First adventure added, incomplete.
This commit is contained in:
@@ -34,6 +34,7 @@ sound_pool p(100);
|
||||
#include "src/creature_audio.nvgt"
|
||||
#include "src/notify.nvgt"
|
||||
#include "src/speech_history.nvgt"
|
||||
#include "src/bosses/adventure_system.nvgt"
|
||||
|
||||
int run_main_menu() {
|
||||
speak_with_history("Draugnorak. Main menu.", true);
|
||||
@@ -192,6 +193,7 @@ void main()
|
||||
// Inventory & Actions
|
||||
check_inventory_keys(x);
|
||||
check_action_menu(x);
|
||||
check_adventure_menu(x);
|
||||
check_crafting_menu(x, BASE_END);
|
||||
check_altar_menu(x);
|
||||
check_equipment_menu();
|
||||
@@ -209,7 +211,11 @@ void main()
|
||||
// Coordinates Key
|
||||
if (key_pressed(KEY_X)) {
|
||||
string direction_label = (facing == 1) ? "east" : "west";
|
||||
speak_with_history(direction_label + ", x " + x + ", y " + y, true);
|
||||
string terrain = get_terrain_at_position(x);
|
||||
if (get_mountain_at(x) !is null) {
|
||||
terrain += ". Mountains";
|
||||
}
|
||||
speak_with_history(direction_label + ", x " + x + ", y " + y + ", terrain " + terrain, true);
|
||||
}
|
||||
|
||||
// Base Info Key (base only)
|
||||
|
||||
Binary file not shown.
BIN
sounds/bosses/unicorn/unicorn_falls.ogg
LFS
Normal file
BIN
sounds/bosses/unicorn/unicorn_falls.ogg
LFS
Normal file
Binary file not shown.
BIN
sounds/bosses/unicorn/unicorn_galloping.ogg
LFS
Normal file
BIN
sounds/bosses/unicorn/unicorn_galloping.ogg
LFS
Normal file
Binary file not shown.
BIN
sounds/bosses/unicorn/unicorn_on_bridge.ogg
LFS
Normal file
BIN
sounds/bosses/unicorn/unicorn_on_bridge.ogg
LFS
Normal file
Binary file not shown.
BIN
sounds/terrain/deep_forest.ogg
LFS
Normal file
BIN
sounds/terrain/deep_forest.ogg
LFS
Normal file
Binary file not shown.
BIN
sounds/terrain/forest.ogg
LFS
Normal file
BIN
sounds/terrain/forest.ogg
LFS
Normal file
Binary file not shown.
@@ -37,6 +37,10 @@ string get_footstep_sound(int current_x, int base_end, int grass_end)
|
||||
return "sounds/terrain/gravel.ogg";
|
||||
} else if (terrain == "snow") {
|
||||
return "sounds/terrain/snow.ogg";
|
||||
} else if (terrain == "forest") {
|
||||
return "sounds/terrain/forest.ogg";
|
||||
} else if (terrain == "deep_forest") {
|
||||
return "sounds/terrain/deep_forest.ogg";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +61,10 @@ string get_footstep_sound(int current_x, int base_end, int grass_end)
|
||||
return "sounds/terrain/snow.ogg";
|
||||
} else if (terrain == "gravel") {
|
||||
return "sounds/terrain/gravel.ogg";
|
||||
} else if (terrain == "forest") {
|
||||
return "sounds/terrain/forest.ogg";
|
||||
} else if (terrain == "deep_forest") {
|
||||
return "sounds/terrain/deep_forest.ogg";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
81
src/bosses/adventure_system.nvgt
Normal file
81
src/bosses/adventure_system.nvgt
Normal file
@@ -0,0 +1,81 @@
|
||||
// Adventure System
|
||||
// Handles triggering and managing terrain-specific adventures
|
||||
|
||||
#include "src/bosses/unicorn/unicorn_boss.nvgt"
|
||||
|
||||
void check_adventure_menu(int player_x) {
|
||||
if (key_pressed(KEY_TAB)) {
|
||||
run_adventure_menu(player_x);
|
||||
}
|
||||
}
|
||||
|
||||
void run_adventure_menu(int player_x) {
|
||||
if (player_x <= BASE_END) {
|
||||
speak_with_history("No adventures available in the base.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (last_adventure_day == current_day) {
|
||||
speak_with_history("You have already attempted an adventure today.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
string terrain = get_terrain_at_position(player_x);
|
||||
MountainRange@ mountain = get_mountain_at(player_x);
|
||||
|
||||
// Check available adventures based on terrain
|
||||
string[] options;
|
||||
int[] adventure_ids; // 1 = Unicorn
|
||||
|
||||
if (mountain !is null) {
|
||||
// Mountain terrain
|
||||
options.insert_last("Unicorn Hunt (Mountain Boss)");
|
||||
adventure_ids.insert_last(1);
|
||||
}
|
||||
|
||||
if (options.length() == 0) {
|
||||
speak_with_history("No adventures found in this area.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Show Menu
|
||||
speak_with_history("Adventure Menu.", true);
|
||||
int selection = 0;
|
||||
speak_with_history(options[selection], true);
|
||||
|
||||
while (true) {
|
||||
wait(5);
|
||||
|
||||
if (key_pressed(KEY_ESCAPE)) {
|
||||
speak_with_history("Closed.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_DOWN)) {
|
||||
selection++;
|
||||
if (selection >= options.length()) selection = 0;
|
||||
speak_with_history(options[selection], true);
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_UP)) {
|
||||
selection--;
|
||||
if (selection < 0) selection = options.length() - 1;
|
||||
speak_with_history(options[selection], true);
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_RETURN)) {
|
||||
start_adventure(adventure_ids[selection]);
|
||||
// Adventure has finished (it blocks until done)
|
||||
// Resume main game ambience
|
||||
update_ambience(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void start_adventure(int adventure_id) {
|
||||
last_adventure_day = current_day;
|
||||
if (adventure_id == 1) {
|
||||
run_unicorn_adventure();
|
||||
}
|
||||
}
|
||||
400
src/bosses/unicorn/unicorn_boss.nvgt
Normal file
400
src/bosses/unicorn/unicorn_boss.nvgt
Normal file
@@ -0,0 +1,400 @@
|
||||
// Unicorn Boss Adventure logic
|
||||
// Terrain: Mountain
|
||||
// Objective: Destroy the bridge supports to defeat the charging unicorn.
|
||||
|
||||
class UnicornBoss {
|
||||
int x;
|
||||
int facing; // 0 = left, 1 = right
|
||||
int health;
|
||||
int speed;
|
||||
timer move_timer;
|
||||
int sound_handle;
|
||||
bool on_bridge;
|
||||
|
||||
UnicornBoss() {
|
||||
reset();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
health = 10000;
|
||||
speed = UNICORN_SPEED;
|
||||
facing = 0; // Start facing west (toward player)
|
||||
x = 0;
|
||||
on_bridge = false;
|
||||
sound_handle = -1;
|
||||
move_timer.restart();
|
||||
}
|
||||
}
|
||||
|
||||
// Adventure Arena Constants
|
||||
const int UNICORN_ARENA_SIZE = 100;
|
||||
const int BRIDGE_START = 45;
|
||||
const int BRIDGE_END = 54;
|
||||
const int BRIDGE_SUPPORT_MAX_HEALTH = 100;
|
||||
const float UNICORN_SOUND_VOLUME_STEP = 2.5; // Lower = audible from further away
|
||||
const int UNICORN_SPEED = 80; // ms per tile, 100 tiles * 80ms = 8 seconds per charge
|
||||
|
||||
// State
|
||||
UnicornBoss unicorn;
|
||||
int player_arena_x = 0;
|
||||
int player_arena_y = 0; // 0 = ground, >0 = jump
|
||||
bool player_arena_jumping = false;
|
||||
timer arena_jump_timer;
|
||||
timer arena_walk_timer;
|
||||
timer arena_attack_timer;
|
||||
int player_arena_facing = 1; // 0 = west, 1 = east
|
||||
int[] bridge_supports_health; // 2 supports: Left (start) and Right (end)
|
||||
bool bridge_collapsed = false;
|
||||
string current_unicorn_sound = "";
|
||||
|
||||
void init_unicorn_adventure() {
|
||||
unicorn.reset();
|
||||
unicorn.x = UNICORN_ARENA_SIZE - 1; // Start at east end
|
||||
|
||||
player_arena_x = 0; // Start player at west end
|
||||
player_arena_y = 0;
|
||||
player_arena_jumping = false;
|
||||
bridge_collapsed = false;
|
||||
current_unicorn_sound = "";
|
||||
|
||||
// Initialize supports
|
||||
bridge_supports_health.resize(2);
|
||||
bridge_supports_health[0] = BRIDGE_SUPPORT_MAX_HEALTH; // Left support at BRIDGE_START
|
||||
bridge_supports_health[1] = BRIDGE_SUPPORT_MAX_HEALTH; // Right support at BRIDGE_END
|
||||
}
|
||||
|
||||
void cleanup_unicorn_adventure() {
|
||||
p.destroy_all();
|
||||
if (unicorn.sound_handle != -1) {
|
||||
p.destroy_sound(unicorn.sound_handle);
|
||||
unicorn.sound_handle = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void run_unicorn_adventure() {
|
||||
// Stop main game sounds
|
||||
p.destroy_all();
|
||||
|
||||
init_unicorn_adventure();
|
||||
|
||||
speak_with_history("You enter a mountain pass. A massive Unicorn blocks the path! A wooden bridge spans a chasm ahead.", true);
|
||||
|
||||
// Adventure Loop
|
||||
while (true) {
|
||||
wait(5);
|
||||
|
||||
// Input Handling
|
||||
if (key_pressed(KEY_ESCAPE)) {
|
||||
cleanup_unicorn_adventure();
|
||||
speak_with_history("You flee the encounter.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Standard game keys
|
||||
check_quick_slot_keys();
|
||||
check_notification_keys();
|
||||
check_speech_history_keys();
|
||||
|
||||
// Health
|
||||
if (key_pressed(KEY_H)) {
|
||||
speak_with_history(player_health + " health of " + max_health, true);
|
||||
}
|
||||
|
||||
// Coordinates
|
||||
if (key_pressed(KEY_X)) {
|
||||
string facing_dir = (unicorn.facing == 1) ? "east" : "west";
|
||||
string terrain = (player_arena_x >= BRIDGE_START && player_arena_x <= BRIDGE_END && !bridge_collapsed) ? "wood" : "grass";
|
||||
speak_with_history("x " + player_arena_x + ", terrain " + terrain + ". Unicorn facing " + facing_dir, true);
|
||||
}
|
||||
|
||||
handle_player_movement();
|
||||
handle_player_actions();
|
||||
|
||||
// Updates
|
||||
update_player_jump();
|
||||
update_unicorn();
|
||||
|
||||
// Check Conditions - unicorn falls when on collapsed bridge
|
||||
if (bridge_collapsed && unicorn.x >= BRIDGE_START && unicorn.x <= BRIDGE_END) {
|
||||
play_unicorn_death_sequence();
|
||||
cleanup_unicorn_adventure();
|
||||
give_unicorn_rewards();
|
||||
return;
|
||||
}
|
||||
|
||||
if (player_health <= 0) {
|
||||
cleanup_unicorn_adventure();
|
||||
speak_with_history("The Unicorn trampled you.", true);
|
||||
// Player death will be handled by main game loop checking player_health <= 0
|
||||
return;
|
||||
}
|
||||
|
||||
// Audio
|
||||
p.update_listener_1d(player_arena_x);
|
||||
update_unicorn_audio();
|
||||
}
|
||||
}
|
||||
|
||||
void handle_player_movement() {
|
||||
// Direction change announces
|
||||
if (key_pressed(KEY_LEFT) && player_arena_facing != 0) {
|
||||
player_arena_facing = 0;
|
||||
speak_with_history("west", true);
|
||||
arena_walk_timer.restart();
|
||||
}
|
||||
if (key_pressed(KEY_RIGHT) && player_arena_facing != 1) {
|
||||
player_arena_facing = 1;
|
||||
speak_with_history("east", true);
|
||||
arena_walk_timer.restart();
|
||||
}
|
||||
|
||||
// Movement with walk timer (like main game)
|
||||
if (arena_walk_timer.elapsed > walk_speed && !player_arena_jumping) {
|
||||
if (key_down(KEY_LEFT) && player_arena_x > 0) {
|
||||
player_arena_facing = 0;
|
||||
player_arena_x--;
|
||||
arena_walk_timer.restart();
|
||||
check_player_chasm_fall();
|
||||
if (player_health > 0) play_footstep_sound();
|
||||
} else if (key_down(KEY_RIGHT) && player_arena_x < UNICORN_ARENA_SIZE - 1) {
|
||||
player_arena_facing = 1;
|
||||
player_arena_x++;
|
||||
arena_walk_timer.restart();
|
||||
check_player_chasm_fall();
|
||||
if (player_health > 0) play_footstep_sound();
|
||||
}
|
||||
}
|
||||
|
||||
// Jumping
|
||||
if (key_pressed(KEY_UP) && !player_arena_jumping) {
|
||||
player_arena_jumping = true;
|
||||
arena_jump_timer.restart();
|
||||
p.play_stationary("sounds/jump.ogg", false);
|
||||
player_arena_y = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void play_footstep_sound() {
|
||||
if (player_arena_x >= BRIDGE_START && player_arena_x <= BRIDGE_END && !bridge_collapsed) {
|
||||
p.play_stationary("sounds/terrain/wood.ogg", false);
|
||||
} else {
|
||||
p.play_stationary("sounds/terrain/grass.ogg", false);
|
||||
}
|
||||
}
|
||||
|
||||
void check_player_chasm_fall() {
|
||||
// If bridge is collapsed and player walks into the chasm, they fall 100 feet
|
||||
if (bridge_collapsed && player_arena_x >= BRIDGE_START && player_arena_x <= BRIDGE_END) {
|
||||
// Play falling sequence like falling from a 100-foot tree
|
||||
timer fall_timer;
|
||||
int fall_handle = -1;
|
||||
int feet_fallen = 0;
|
||||
int total_fall = 100;
|
||||
|
||||
while (feet_fallen < total_fall) {
|
||||
if (fall_timer.elapsed >= 100) {
|
||||
fall_timer.restart();
|
||||
feet_fallen++;
|
||||
|
||||
if (fall_handle != -1) {
|
||||
p.destroy_sound(fall_handle);
|
||||
}
|
||||
|
||||
float height_remaining = float(total_fall - feet_fallen);
|
||||
float pitch_percent = 50.0 + (50.0 * (height_remaining / float(total_fall)));
|
||||
if (pitch_percent < 50.0) pitch_percent = 50.0;
|
||||
if (pitch_percent > 100.0) pitch_percent = 100.0;
|
||||
|
||||
fall_handle = p.play_stationary_extended("sounds/actions/falling.ogg", true, 0, 0, 0, pitch_percent);
|
||||
}
|
||||
wait(5);
|
||||
}
|
||||
|
||||
if (fall_handle != -1) {
|
||||
p.destroy_sound(fall_handle);
|
||||
}
|
||||
p.play_stationary("sounds/actions/hit_ground.ogg", false);
|
||||
player_health = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void update_player_jump() {
|
||||
if (player_arena_jumping && arena_jump_timer.elapsed > 800) {
|
||||
player_arena_jumping = false;
|
||||
player_arena_y = 0;
|
||||
play_footstep_sound(); // Land sound
|
||||
}
|
||||
}
|
||||
|
||||
void handle_player_actions() {
|
||||
// Can't attack while jumping
|
||||
if (player_arena_jumping) return;
|
||||
|
||||
// Attack cooldown like main game
|
||||
int attack_cooldown = 1000;
|
||||
if (spear_equipped) attack_cooldown = 800;
|
||||
if (axe_equipped) attack_cooldown = 1600;
|
||||
|
||||
if ((key_down(KEY_LCTRL) || key_down(KEY_RCTRL)) && arena_attack_timer.elapsed > attack_cooldown) {
|
||||
arena_attack_timer.restart();
|
||||
|
||||
// Check for bridge supports
|
||||
int target_support = -1;
|
||||
|
||||
// Check Left Support (at BRIDGE_START)
|
||||
if (abs(player_arena_x - BRIDGE_START) <= 1) {
|
||||
target_support = 0;
|
||||
}
|
||||
// Check Right Support (at BRIDGE_END)
|
||||
else if (abs(player_arena_x - BRIDGE_END) <= 1) {
|
||||
target_support = 1;
|
||||
}
|
||||
|
||||
if (target_support != -1) {
|
||||
if (bridge_supports_health[target_support] > 0) {
|
||||
// Only axe can damage supports (like chopping trees)
|
||||
if (axe_equipped) {
|
||||
bridge_supports_health[target_support] -= AXE_DAMAGE;
|
||||
p.play_stationary("sounds/weapons/axe_hit.ogg", false);
|
||||
|
||||
if (bridge_supports_health[target_support] <= 0) {
|
||||
check_bridge_collapse();
|
||||
}
|
||||
} else if (spear_equipped) {
|
||||
// Spear just makes sound, no damage (like hitting trees)
|
||||
p.play_stationary("sounds/weapons/spear_hit.ogg", false);
|
||||
} else {
|
||||
// No weapon or sling - swing sound, no effect
|
||||
p.play_stationary("sounds/weapons/axe_swing.ogg", false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Normal attack (useless vs unicorn but gives feedback)
|
||||
if (abs(player_arena_x - unicorn.x) <= 1) {
|
||||
p.play_stationary("sounds/weapons/axe_hit.ogg", false);
|
||||
} else {
|
||||
p.play_stationary("sounds/weapons/axe_swing.ogg", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void check_bridge_collapse() {
|
||||
// Bridge collapses when any support is destroyed
|
||||
if (bridge_supports_health[0] <= 0 || bridge_supports_health[1] <= 0) {
|
||||
bridge_collapsed = true;
|
||||
p.play_stationary("sounds/actions/break_snare.ogg", false);
|
||||
|
||||
// If player is on bridge, they fall too
|
||||
if (player_arena_x >= BRIDGE_START && player_arena_x <= BRIDGE_END) {
|
||||
check_player_chasm_fall();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void update_unicorn() {
|
||||
if (unicorn.move_timer.elapsed >= unicorn.speed) {
|
||||
unicorn.move_timer.restart();
|
||||
|
||||
// Move
|
||||
if (unicorn.facing == 1) {
|
||||
unicorn.x++;
|
||||
if (unicorn.x >= UNICORN_ARENA_SIZE) {
|
||||
unicorn.facing = 0; // Turn around
|
||||
unicorn.x = UNICORN_ARENA_SIZE - 1;
|
||||
}
|
||||
} else {
|
||||
unicorn.x--;
|
||||
if (unicorn.x < 0) {
|
||||
unicorn.facing = 1; // Turn around
|
||||
unicorn.x = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Bridge Logic
|
||||
if (unicorn.x >= BRIDGE_START && unicorn.x <= BRIDGE_END && !bridge_collapsed) {
|
||||
unicorn.on_bridge = true;
|
||||
} else {
|
||||
unicorn.on_bridge = false;
|
||||
}
|
||||
|
||||
// Collision with Player
|
||||
if (unicorn.x == player_arena_x && player_arena_y == 0) {
|
||||
player_health -= 10;
|
||||
p.play_stationary("sounds/actions/hit_ground.ogg", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void update_unicorn_audio() {
|
||||
// Determine sound based on surface
|
||||
string sound_file = "sounds/bosses/unicorn/unicorn_galloping.ogg";
|
||||
if (unicorn.on_bridge) {
|
||||
sound_file = "sounds/bosses/unicorn/unicorn_on_bridge.ogg";
|
||||
}
|
||||
|
||||
// Check if we need to switch sounds (different file or no active sound)
|
||||
bool need_new_sound = (unicorn.sound_handle == -1 || !p.sound_is_active(unicorn.sound_handle) || current_unicorn_sound != sound_file);
|
||||
|
||||
if (need_new_sound) {
|
||||
// Stop old sound if playing
|
||||
if (unicorn.sound_handle != -1) {
|
||||
p.destroy_sound(unicorn.sound_handle);
|
||||
}
|
||||
// Start new positioned sound using shared helper
|
||||
unicorn.sound_handle = play_1d_with_volume_step(sound_file, player_arena_x, unicorn.x, true, UNICORN_SOUND_VOLUME_STEP);
|
||||
current_unicorn_sound = sound_file;
|
||||
} else {
|
||||
// Update position of existing sound
|
||||
p.update_sound_1d(unicorn.sound_handle, unicorn.x);
|
||||
}
|
||||
}
|
||||
|
||||
void play_unicorn_death_sequence() {
|
||||
// Stop unicorn's normal sound
|
||||
if (unicorn.sound_handle != -1) {
|
||||
p.destroy_sound(unicorn.sound_handle);
|
||||
unicorn.sound_handle = -1;
|
||||
}
|
||||
|
||||
// Simulate 100-foot fall, updating every 100ms like normal falling
|
||||
timer fall_timer;
|
||||
int fall_handle = -1;
|
||||
int feet_fallen = 0;
|
||||
int total_fall = 100;
|
||||
|
||||
while (feet_fallen < total_fall) {
|
||||
if (fall_timer.elapsed >= 100) {
|
||||
fall_timer.restart();
|
||||
feet_fallen++;
|
||||
|
||||
// Restart falling sound with decreasing pitch each foot
|
||||
if (fall_handle != -1) {
|
||||
p.destroy_sound(fall_handle);
|
||||
}
|
||||
|
||||
// Pitch ranges from 100 (start) to 50 (end) like normal falling
|
||||
float height_remaining = float(total_fall - feet_fallen);
|
||||
float pitch_percent = 50.0 + (50.0 * (height_remaining / float(total_fall)));
|
||||
if (pitch_percent < 50.0) pitch_percent = 50.0;
|
||||
if (pitch_percent > 100.0) pitch_percent = 100.0;
|
||||
|
||||
fall_handle = p.play_extended_1d("sounds/actions/falling.ogg", player_arena_x, unicorn.x, 0, 0, true, 0, 0.0, 0.0, pitch_percent);
|
||||
if (fall_handle != -1) {
|
||||
p.update_sound_positioning_values(fall_handle, -1.0, UNICORN_SOUND_VOLUME_STEP, true);
|
||||
}
|
||||
}
|
||||
wait(5);
|
||||
}
|
||||
|
||||
// Cleanup falling sound and play impact
|
||||
if (fall_handle != -1) {
|
||||
p.destroy_sound(fall_handle);
|
||||
}
|
||||
play_1d_with_volume_step("sounds/bosses/unicorn/unicorn_falls.ogg", player_arena_x, unicorn.x, false, UNICORN_SOUND_VOLUME_STEP);
|
||||
}
|
||||
|
||||
void give_unicorn_rewards() {
|
||||
speak_with_history("Victory!", true);
|
||||
favor += 50;
|
||||
}
|
||||
@@ -687,6 +687,58 @@ void perform_search(int current_x)
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Forest terrain - check for sticks and vines
|
||||
bool is_forest_terrain = false;
|
||||
string current_terrain = "";
|
||||
|
||||
// Check expanded areas for forest/deep_forest
|
||||
if (expanded_area_start != -1 && current_x >= expanded_area_start && current_x <= expanded_area_end) {
|
||||
// Check for mountain terrain first
|
||||
MountainRange@ mountain = get_mountain_at(current_x);
|
||||
if (mountain !is null) {
|
||||
current_terrain = mountain.get_terrain_at(current_x);
|
||||
} else {
|
||||
// Regular expanded area - check terrain type
|
||||
int index = current_x - expanded_area_start;
|
||||
if (index >= 0 && index < int(expanded_terrain_types.length())) {
|
||||
current_terrain = expanded_terrain_types[index];
|
||||
// Handle "mountain:terrain" format from older saves
|
||||
if (current_terrain.find("mountain:") == 0) {
|
||||
current_terrain = current_terrain.substr(9);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (current_terrain == "forest" || current_terrain == "deep_forest") {
|
||||
is_forest_terrain = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_forest_terrain)
|
||||
{
|
||||
bool can_find_stick = inv_sticks < get_personal_stack_limit();
|
||||
bool can_find_vine = inv_vines < get_personal_stack_limit();
|
||||
|
||||
if (!can_find_stick && !can_find_vine) {
|
||||
speak_with_history("You can't carry any more sticks or vines.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Random choice: if both available, 50/50. If only one, find that.
|
||||
bool find_stick = can_find_stick && (!can_find_vine || random(0, 1) == 0);
|
||||
|
||||
if (find_stick) {
|
||||
inv_sticks++;
|
||||
p.play_stationary("sounds/items/stick.ogg", false);
|
||||
speak_with_history("Found a stick.", true);
|
||||
} else {
|
||||
inv_vines++;
|
||||
p.play_stationary("sounds/items/vine.ogg", false);
|
||||
speak_with_history("Found a vine.", true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
speak_with_history("Found nothing.", true);
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,9 @@ timer attack_timer;
|
||||
// Search state
|
||||
bool searching = false;
|
||||
|
||||
// Adventure state
|
||||
int last_adventure_day = -1;
|
||||
|
||||
// Pause state
|
||||
bool game_paused = false;
|
||||
|
||||
|
||||
@@ -176,6 +176,7 @@ void reset_game_state() {
|
||||
base_max_health = 10;
|
||||
max_health = 10;
|
||||
favor = 0.0;
|
||||
last_adventure_day = -1;
|
||||
incense_hours_remaining = 0;
|
||||
incense_burning = false;
|
||||
blessing_speed_active = false;
|
||||
@@ -442,6 +443,7 @@ bool load_game_state_from_raw(const string&in rawData) {
|
||||
if (get_raw_number(rawData, "player_base_health", value)) base_max_health = value;
|
||||
if (get_raw_number(rawData, "player_max_health", value)) max_health = value;
|
||||
if (get_raw_number(rawData, "player_favor", value)) favor = value;
|
||||
if (get_raw_number(rawData, "player_last_adventure_day", value)) last_adventure_day = value;
|
||||
if (get_raw_number(rawData, "incense_hours_remaining", value)) incense_hours_remaining = value;
|
||||
if (get_raw_bool(rawData, "incense_burning", bool_value)) incense_burning = bool_value;
|
||||
if (get_raw_number(rawData, "time_current_hour", value)) current_hour = value;
|
||||
@@ -537,6 +539,7 @@ bool save_game_state() {
|
||||
saveData.set("player_base_health", base_max_health);
|
||||
saveData.set("player_max_health", max_health);
|
||||
saveData.set("player_favor", favor);
|
||||
saveData.set("player_last_adventure_day", last_adventure_day);
|
||||
saveData.set("incense_hours_remaining", incense_hours_remaining);
|
||||
saveData.set("incense_burning", incense_burning);
|
||||
|
||||
@@ -567,6 +570,7 @@ bool save_game_state() {
|
||||
saveData.set("inventory_skin_tunics", inv_skin_tunics);
|
||||
saveData.set("inventory_moccasins", inv_moccasins);
|
||||
saveData.set("inventory_skin_pouches", inv_skin_pouches);
|
||||
saveData.set("inventory_backpacks", inv_backpacks);
|
||||
saveData.set("inventory_small_game_types", join_string_array(inv_small_game_types));
|
||||
|
||||
saveData.set("storage_stones", storage_stones);
|
||||
@@ -596,6 +600,7 @@ bool save_game_state() {
|
||||
saveData.set("storage_skin_tunics", storage_skin_tunics);
|
||||
saveData.set("storage_moccasins", storage_moccasins);
|
||||
saveData.set("storage_skin_pouches", storage_skin_pouches);
|
||||
saveData.set("storage_backpacks", storage_backpacks);
|
||||
saveData.set("storage_small_game_types", join_string_array(storage_small_game_types));
|
||||
|
||||
saveData.set("equipment_spear_equipped", spear_equipped);
|
||||
@@ -790,6 +795,7 @@ bool load_game_state() {
|
||||
max_health = int(get_number(saveData, "player_max_health", 10));
|
||||
base_max_health = int(get_number(saveData, "player_base_health", max_health));
|
||||
favor = get_number(saveData, "player_favor", 0.0);
|
||||
last_adventure_day = int(get_number(saveData, "player_last_adventure_day", -1));
|
||||
incense_hours_remaining = int(get_number(saveData, "incense_hours_remaining", 0));
|
||||
incense_burning = get_bool(saveData, "incense_burning", false);
|
||||
if (incense_hours_remaining > 0) incense_burning = true;
|
||||
@@ -826,6 +832,7 @@ bool load_game_state() {
|
||||
inv_skin_tunics = int(get_number(saveData, "inventory_skin_tunics", 0));
|
||||
inv_moccasins = int(get_number(saveData, "inventory_moccasins", 0));
|
||||
inv_skin_pouches = int(get_number(saveData, "inventory_skin_pouches", 0));
|
||||
inv_backpacks = int(get_number(saveData, "inventory_backpacks", 0));
|
||||
|
||||
string[] loadedSmallGameTypes = get_string_list_or_split(saveData, "inventory_small_game_types");
|
||||
inv_small_game_types.resize(0);
|
||||
@@ -868,6 +875,7 @@ bool load_game_state() {
|
||||
storage_skin_tunics = int(get_number(saveData, "storage_skin_tunics", 0));
|
||||
storage_moccasins = int(get_number(saveData, "storage_moccasins", 0));
|
||||
storage_skin_pouches = int(get_number(saveData, "storage_skin_pouches", 0));
|
||||
storage_backpacks = int(get_number(saveData, "storage_backpacks", 0));
|
||||
|
||||
string[] loadedStorageSmallGameTypes = get_string_list_or_split(saveData, "storage_small_game_types");
|
||||
storage_small_game_types.resize(0);
|
||||
|
||||
114
src/text_reader.nvgt
Normal file
114
src/text_reader.nvgt
Normal file
@@ -0,0 +1,114 @@
|
||||
// text_reader.nvgt - Simple text document reader/editor using NVGT's audio_form
|
||||
// Provides accessible navigation through text documents with optional editing
|
||||
|
||||
#include "form.nvgt"
|
||||
|
||||
// Opens a text reader/editor window with string content
|
||||
// Parameters:
|
||||
// content: The text content to display (can be file contents or direct string)
|
||||
// title: Window title (default: "Text Reader")
|
||||
// readonly: If true, text cannot be edited (default: true)
|
||||
// Returns: The modified text if readonly=false and user presses OK, empty string if canceled or readonly=true
|
||||
string text_reader(string content, string title = "Text Reader", bool readonly = true) {
|
||||
audio_form f;
|
||||
f.create_window(title, false, true);
|
||||
|
||||
// Create the multiline input box
|
||||
// In readonly mode, it's still navigable with arrows/home/end/etc
|
||||
// In edit mode, user can modify text
|
||||
int text_control = f.create_input_box(
|
||||
(readonly ? "Document (read only)" : "Document (editable)"),
|
||||
content,
|
||||
"", // no password mask
|
||||
0, // no max length
|
||||
readonly,
|
||||
true, // multiline = true
|
||||
true // multiline_enter = true (Ctrl+Enter for newlines)
|
||||
);
|
||||
|
||||
int ok_button = -1;
|
||||
int close_button = -1;
|
||||
|
||||
if (readonly) {
|
||||
// In readonly mode, just have a Close button
|
||||
close_button = f.create_button("&Close", true, true);
|
||||
} else {
|
||||
// In edit mode, have OK and Cancel buttons
|
||||
ok_button = f.create_button("&OK", true);
|
||||
close_button = f.create_button("&Cancel", false, true);
|
||||
}
|
||||
|
||||
f.focus(text_control);
|
||||
|
||||
// Monitor loop
|
||||
while (true) {
|
||||
f.monitor();
|
||||
wait(5);
|
||||
|
||||
// Check if user pressed OK (edit mode only)
|
||||
if (!readonly && ok_button != -1 && f.is_pressed(ok_button)) {
|
||||
return f.get_text(text_control);
|
||||
}
|
||||
|
||||
// Check if user pressed Close/Cancel
|
||||
if (close_button != -1 && f.is_pressed(close_button)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Check for Escape key as alternative close method
|
||||
if (key_pressed(KEY_ESCAPE)) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
// Opens a text reader/editor window with an array of lines
|
||||
// Parameters:
|
||||
// lines: Array of text lines to display
|
||||
// title: Window title (default: "Text Reader")
|
||||
// readonly: If true, text cannot be edited (default: true)
|
||||
// Returns: The modified text if readonly=false and user presses OK, empty string if canceled or readonly=true
|
||||
string text_reader_lines(string[] lines, string title = "Text Reader", bool readonly = true) {
|
||||
string content = join(lines, "\n");
|
||||
return text_reader(content, title, readonly);
|
||||
}
|
||||
|
||||
// Convenience function to read a file and display it in the text reader
|
||||
// Parameters:
|
||||
// file_path: Path to the file to read
|
||||
// title: Window title (default: uses file_path as title)
|
||||
// readonly: If true, text cannot be edited (default: true)
|
||||
// Returns: The modified text if readonly=false and user saves, empty string otherwise
|
||||
string text_reader_file(string file_path, string title = "", bool readonly = true) {
|
||||
file f;
|
||||
if (!f.open(file_path, "rb")) {
|
||||
screen_reader_speak("Failed to open file: " + file_path, true);
|
||||
return "";
|
||||
}
|
||||
|
||||
string content = f.read();
|
||||
f.close();
|
||||
|
||||
// Use file_path as title if no custom title provided
|
||||
if (title == "") {
|
||||
title = file_path;
|
||||
}
|
||||
|
||||
string result = text_reader(content, title, readonly);
|
||||
|
||||
// If in edit mode and user pressed OK, save the file
|
||||
if (!readonly && result != "") {
|
||||
if (f.open(file_path, "wb")) {
|
||||
f.write(result);
|
||||
f.close();
|
||||
screen_reader_speak("File saved successfully", true);
|
||||
return result;
|
||||
} else {
|
||||
screen_reader_speak("Failed to save file", true);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -74,13 +74,17 @@ void expand_regular_area() {
|
||||
|
||||
// Generate a single terrain type for the entire new area
|
||||
string terrain_type;
|
||||
int terrain_roll = random(0, 2);
|
||||
int terrain_roll = random(0, 4);
|
||||
if (terrain_roll == 0) {
|
||||
terrain_type = "stone";
|
||||
} else if (terrain_roll == 1) {
|
||||
terrain_type = "grass";
|
||||
} else {
|
||||
} else if (terrain_roll == 2) {
|
||||
terrain_type = "snow";
|
||||
} else if (terrain_roll == 3) {
|
||||
terrain_type = "forest";
|
||||
} else {
|
||||
terrain_type = "deep_forest";
|
||||
}
|
||||
|
||||
for (int i = 0; i < EXPANSION_SIZE; i++) {
|
||||
|
||||
35
src/ui.nvgt
35
src/ui.nvgt
@@ -1,4 +1,39 @@
|
||||
// UI helpers
|
||||
string get_terrain_at_position(int pos_x) {
|
||||
// Check for water first (streams in expanded areas or mountain streams)
|
||||
if (is_position_in_water(pos_x) || is_mountain_stream_at(pos_x)) {
|
||||
return "water";
|
||||
}
|
||||
|
||||
// Check mountain terrain
|
||||
MountainRange@ mountain = get_mountain_at(pos_x);
|
||||
if (mountain !is null) {
|
||||
return mountain.get_terrain_at(pos_x);
|
||||
}
|
||||
|
||||
// Base area
|
||||
if (pos_x <= BASE_END) return "wood";
|
||||
|
||||
// Grass area
|
||||
if (pos_x <= GRASS_END) return "grass";
|
||||
|
||||
// Gravel area
|
||||
if (pos_x <= GRAVEL_END) return "gravel";
|
||||
|
||||
// Expanded areas
|
||||
int index = pos_x - expanded_area_start;
|
||||
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);
|
||||
}
|
||||
return terrain;
|
||||
}
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
string ui_input_box(const string title, const string prompt, const string default_value) {
|
||||
string result = virtual_input_box(title, prompt, default_value);
|
||||
show_window("Draugnorak");
|
||||
|
||||
@@ -46,10 +46,12 @@ class MountainRange {
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (elevations[i] > 20) {
|
||||
terrain_types[i] = "snow";
|
||||
} else if (elevations[i] > 8) {
|
||||
} else if (elevations[i] > 12) {
|
||||
terrain_types[i] = "stone";
|
||||
} else if (elevations[i] > 6) {
|
||||
terrain_types[i] = "forest";
|
||||
} else {
|
||||
terrain_types[i] = "gravel";
|
||||
terrain_types[i] = "deep_forest";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user