#include "sound_pool.nvgt" #include "virtual_dialogs.nvgt" // Audio // Increased pool size to 300 to prevent sound cutoffs and missing sounds sound_pool p(300); #include "src/constants.nvgt" #include "src/player.nvgt" #include "src/creature_base.nvgt" #include "src/creature_death.nvgt" #include "src/world_object_base.nvgt" #include "src/enemies/undead.nvgt" #include "src/enemies/bandit.nvgt" #include "src/enemies/ground_game.nvgt" #include "src/enemies/flying_creatures.nvgt" #include "src/world/world_drops.nvgt" #include "src/world/world_snares.nvgt" #include "src/world/world_fires.nvgt" #include "src/world/world_buildings.nvgt" #include "src/world/world_streams.nvgt" #include "src/world/mountains.nvgt" #include "src/world/barricade.nvgt" #include "src/world_state.nvgt" #include "src/ui.nvgt" #include "src/inventory.nvgt" #include "src/quest_system.nvgt" #include "src/environment.nvgt" #include "src/combat.nvgt" #include "src/save_system.nvgt" #include "src/base_system.nvgt" #include "src/time_system.nvgt" #include "src/weather.nvgt" #include "src/audio_utils.nvgt" #include "src/creature_audio.nvgt" #include "src/notify.nvgt" #include "src/speech_history.nvgt" #include "src/text_reader.nvgt" #include "src/bosses/adventure_system.nvgt" int run_main_menu() { speak_with_history("Draugnorak. Main menu.", true); int selection = 0; string load_label = has_save_game() ? "Load Game" : "Load Game (no save found)"; string[] options = {"New Game", load_label, "Exit"}; speak_with_history(options[selection], true); while(true) { wait(5); 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)) { return selection; } if (key_pressed(KEY_ESCAPE)) { return 2; } } return 2; } 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"); init_flying_creature_configs(); init_item_registry(); bool game_started = false; while (!game_started) { int selection = run_main_menu(); if (selection == 0) { // Check if save file exists and confirm before overwriting if (has_save_game()) { int confirm = ui_question("", "Save found. Are you sure you want to start a new game?"); if (confirm != 1) { continue; // Return to main menu } } start_new_game(); speak_with_history("New game started.", true); game_started = true; } else if (selection == 1) { if (load_game_state()) { speak_with_history("Game loaded.", true); game_started = true; } else { if (has_save_game()) { string message = last_save_error; if (message == "") message = "Unable to load save."; ui_info_box("Draugnorak", "Load Game", message); } else { ui_info_box("Draugnorak", "Load Game", "No save found."); } } } else { exit(); } } while(true) { wait(5); // Pause toggle if(key_pressed(KEY_BACK)) { game_paused = !game_paused; if (game_paused) { p.pause_all(); speak_with_history("Paused. Press backspace to resume.", true); } else { p.resume_all(); // Restart all timers to prevent time from accumulating while paused restart_all_timers(); speak_with_history("Resumed.", true); } continue; } // Skip all game logic while paused if (game_paused) { continue; } if(key_pressed(KEY_ESCAPE)) { int really_exit = ui_question("", "Really exit?"); if (really_exit == 1) { exit(); } } // Time & Environment updates update_time(); update_crossfade(); update_weather(); update_environment(); update_snares(); update_streams(); update_fires(); update_zombies(); update_bandits(); update_boars(); update_flying_creatures(); update_world_drops(); update_blessings(); update_notifications(); // 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(); speak_with_history("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(); speak_with_history(player_health + " health.", true); } } // Death check if (player_health <= 0) { speak_with_history("You have died.", true); wait(2000); exit(); } // 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(); check_quest_menu(); check_quick_slot_keys(); check_time_input(); check_notification_keys(); check_speech_history_keys(); // Health Key if (key_pressed(KEY_H)) { speak_with_history(player_health + " health of " + max_health, true); } // Coordinates Key if (key_pressed(KEY_X)) { string direction_label = (facing == 1) ? "east" : "west"; 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) if (key_pressed(KEY_B)) { run_base_info_menu(); } // Climbing and Falling Updates update_climbing(); update_falling(); update_rope_climbing(); check_rope_climb_fall(); update_mountains(); // Down arrow to climb down from tree or start rope climb down if (key_pressed(KEY_DOWN)) { // 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)) { // 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) { // Get ground elevation at current position int ground_elevation = get_mountain_elevation_at(x); // Check if on tree tile Tree@ tree = get_tree_at(x); if (tree != null && !tree.is_chopped && y == ground_elevation) { // Start climbing the tree start_climbing_tree(x); } else if (y == ground_elevation) { // Normal jump (only if on the ground) p.play_stationary("sounds/jump.ogg", false); jumping = true; jumptimer.restart(); } } } if(jumping && jumptimer.elapsed > 850) { jumping = false; // Reset y to ground elevation after jump y = get_mountain_elevation_at(x); play_land_sound(x, BASE_END, GRASS_END); // Check for snare on landing? check_snare_collision(x); } // Set y to 3 above ground during jump if(jumping && y == get_mountain_elevation_at(x)) { y = get_mountain_elevation_at(x) + 3; } movetime = jumping ? jump_speed : walk_speed; // Movement Logic if (key_pressed(KEY_LEFT) && facing != 0 && !climbing && !falling && !rope_climbing) { facing = 0; speak_with_history("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 && !rope_climbing) { facing = 1; speak_with_history("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 (not in mountain) MountainRange@ current_mountain = get_mountain_at(x); Tree@ current_tree = get_tree_at(x); int ground_elevation = get_mountain_elevation_at(x); if((key_down(KEY_LEFT) || key_down(KEY_RIGHT)) && y > ground_elevation && !jumping && !falling && !rope_climbing && current_mountain is null && current_tree !is null) { // Fall out of tree climbing = false; start_falling(); } if(key_down(KEY_LEFT) && x > 0 && !climbing && !falling && !rope_climbing) { facing = 0; int target_x = x - 1; // Check mountain movement and deep water if (can_move_mountain(x, target_x) && can_enter_stream_tile(target_x)) { x = target_x; // Always update elevation to match ground (0 if not in mountain) int new_elevation = get_mountain_elevation_at(x); if (new_elevation != y) { 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 && !rope_climbing) { facing = 1; int target_x = x + 1; // Check mountain movement and deep water if (can_move_mountain(x, target_x) && can_enter_stream_tile(target_x)) { x = target_x; // Always update elevation to match ground (0 if not in mountain) int new_elevation = get_mountain_elevation_at(x); if (new_elevation != y) { y = new_elevation; } walktimer.restart(); if(!jumping) { play_footstep(x, BASE_END, GRASS_END); check_snare_collision(x); } } } } // Reset search timer if shift is used with other keys if((key_down(KEY_LSHIFT) || key_down(KEY_RSHIFT))) { if(key_pressed(KEY_COMMA) || key_pressed(KEY_PERIOD)) { searching = false; search_delay_timer.restart(); } } // Searching Logic bool shift_down = (key_down(KEY_LSHIFT) || key_down(KEY_RSHIFT)); if (!shift_down) { if (searching) { searching = false; } search_timer.restart(); } // Apply rune gathering bonus to search time int search_time = apply_rune_gather_bonus(2000); if (shift_down && search_timer.elapsed > search_time && !searching) { searching = true; search_delay_timer.restart(); } // Complete search after delay if(searching && search_delay_timer.elapsed >= 1000) { searching = false; search_timer.restart(); perform_search(x); } // Sling charge detection if (sling_equipped && (key_down(KEY_LCTRL) || key_down(KEY_RCTRL)) && !sling_charging) { if (get_personal_count(ITEM_STONES) > 0) { sling_charging = true; sling_charge_timer.restart(); sling_sound_handle = p.play_stationary("sounds/weapons/sling_swing.ogg", true); last_sling_stage = -1; } else { speak_with_history("No stones.", true); } } // Update sling charge state while holding if (sling_charging && (key_down(KEY_LCTRL) || key_down(KEY_RCTRL))) { update_sling_charge(); } // Sling release detection if (sling_charging && (!key_down(KEY_LCTRL) && !key_down(KEY_RCTRL))) { release_sling_attack(x); sling_charging = false; if (sling_sound_handle != -1) { p.destroy_sound(sling_sound_handle); sling_sound_handle = -1; } } // Non-sling weapon attacks (existing pattern) if (!sling_equipped && !sling_charging) { 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)) && attack_timer.elapsed > attack_cooldown) { attack_timer.restart(); perform_attack(x); } } // Audio Listener Update p.update_listener_1d(x); } }