611 lines
22 KiB
Plaintext
611 lines
22 KiB
Plaintext
#include "sound_pool.nvgt"
|
|
#include "virtual_dialogs.nvgt"
|
|
#include "music.nvgt"
|
|
|
|
// Audio
|
|
// Increased pool size to 300 to prevent sound cutoffs and missing sounds
|
|
sound_pool p(300);
|
|
|
|
#include "src/constants.nvgt"
|
|
#include "src/i18n.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 "libstorm-nvgt/ui.nvgt"
|
|
#include "libstorm-nvgt/menu_helpers.nvgt"
|
|
#include "libstorm-nvgt/menu_music.nvgt"
|
|
#include "src/terrain_lookup.nvgt"
|
|
#include "src/inventory.nvgt"
|
|
#include "src/pet_system.nvgt"
|
|
#include "src/quest_system.nvgt"
|
|
#include "src/environment.nvgt"
|
|
#include "src/combat.nvgt"
|
|
#include "src/fylgja_system.nvgt"
|
|
#include "src/save_system.nvgt"
|
|
#include "src/base_system.nvgt"
|
|
#include "src/time_system.nvgt"
|
|
#include "src/fishing.nvgt"
|
|
#include "src/weather.nvgt"
|
|
#include "src/audio_utils.nvgt"
|
|
#include "src/creature_audio.nvgt"
|
|
#include "libstorm-nvgt/speech_history.nvgt"
|
|
#include "src/text_reader_aliases.nvgt"
|
|
#include "src/notify_compat.nvgt"
|
|
#include "src/learn_sounds.nvgt"
|
|
#include "src/intro.nvgt"
|
|
#include "src/bosses/adventure_system.nvgt"
|
|
|
|
const string MAIN_MENU_MUSIC_TRACK = "sounds/menu/Draugnorak.ogg; loop; f=1";
|
|
const int MAIN_MENU_MUSIC_FADE_IN_MS = 1;
|
|
const int MAIN_MENU_MUSIC_FADE_OUT_MS = 800;
|
|
music_manager mainMenuMusic;
|
|
|
|
const int MAIN_MENU_NEW_GAME = 0;
|
|
const int MAIN_MENU_LOAD_GAME = 1;
|
|
const int MAIN_MENU_STORY = 2;
|
|
const int MAIN_MENU_LEARN_SOUNDS = 3;
|
|
const int MAIN_MENU_EXIT = 4;
|
|
|
|
void start_main_menu_music() {
|
|
menu_music_resume_or_play(mainMenuMusic, MAIN_MENU_MUSIC_TRACK, MAIN_MENU_MUSIC_FADE_IN_MS);
|
|
}
|
|
|
|
void stop_main_menu_music() {
|
|
menu_music_pause(mainMenuMusic, MAIN_MENU_MUSIC_FADE_OUT_MS);
|
|
}
|
|
|
|
int run_main_menu() {
|
|
start_main_menu_music();
|
|
speak_with_history(tr("system.main_menu.prompt"), true);
|
|
|
|
int selection = 0;
|
|
string load_label = has_save_game() ? i18n_text("Load Game") : i18n_text("Load Game (no saves found)");
|
|
string[] options;
|
|
string[] entry_paths;
|
|
int[] entry_types; // 0 = action, 1 = document
|
|
int[] action_ids;
|
|
|
|
options.insert_last(i18n_text("New Game"));
|
|
entry_paths.insert_last("");
|
|
entry_types.insert_last(0);
|
|
action_ids.insert_last(MAIN_MENU_NEW_GAME);
|
|
|
|
options.insert_last(load_label);
|
|
entry_paths.insert_last("");
|
|
entry_types.insert_last(0);
|
|
action_ids.insert_last(MAIN_MENU_LOAD_GAME);
|
|
|
|
options.insert_last(tr("system.menu.main.story"));
|
|
entry_paths.insert_last("");
|
|
entry_types.insert_last(0);
|
|
action_ids.insert_last(MAIN_MENU_STORY);
|
|
|
|
options.insert_last(i18n_text("Learn Sounds"));
|
|
entry_paths.insert_last("");
|
|
entry_types.insert_last(0);
|
|
action_ids.insert_last(MAIN_MENU_LEARN_SOUNDS);
|
|
|
|
string[] doc_labels;
|
|
string[] doc_paths;
|
|
int[] doc_types;
|
|
add_doc_entries(doc_labels, doc_paths, doc_types);
|
|
for (uint i = 0; i < doc_labels.length(); i++) {
|
|
options.insert_last(doc_labels[i]);
|
|
entry_paths.insert_last(doc_paths[i]);
|
|
entry_types.insert_last(1);
|
|
action_ids.insert_last(-1);
|
|
}
|
|
|
|
options.insert_last(i18n_text("Exit"));
|
|
entry_paths.insert_last("");
|
|
entry_types.insert_last(0);
|
|
action_ids.insert_last(MAIN_MENU_EXIT);
|
|
int exit_action = MAIN_MENU_EXIT;
|
|
|
|
speak_with_history(options[selection], true);
|
|
|
|
while (true) {
|
|
wait(5);
|
|
mainMenuMusic.loop();
|
|
handle_global_volume_keys();
|
|
|
|
if (key_pressed(KEY_DOWN)) {
|
|
play_menu_move_sound();
|
|
selection++;
|
|
if (selection >= options.length())
|
|
selection = 0;
|
|
speak_with_history(options[selection], true);
|
|
}
|
|
|
|
if (key_pressed(KEY_UP)) {
|
|
play_menu_move_sound();
|
|
selection--;
|
|
if (selection < 0)
|
|
selection = options.length() - 1;
|
|
speak_with_history(options[selection], true);
|
|
}
|
|
|
|
if (key_pressed(KEY_RETURN)) {
|
|
play_menu_select_sound();
|
|
if (entry_types[selection] == 1) {
|
|
stop_main_menu_music();
|
|
text_reader_file(entry_paths[selection], options[selection], true);
|
|
start_main_menu_music();
|
|
speak_with_history(options[selection], true);
|
|
continue;
|
|
}
|
|
stop_main_menu_music();
|
|
return action_ids[selection];
|
|
}
|
|
|
|
if (key_pressed(KEY_ESCAPE)) {
|
|
stop_main_menu_music();
|
|
return exit_action;
|
|
}
|
|
}
|
|
|
|
return exit_action;
|
|
}
|
|
|
|
void run_game() {
|
|
// 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
|
|
init_master_volume();
|
|
|
|
show_window("Draugnorak");
|
|
ui_set_default_window_title("Draugnorak");
|
|
i18n_init();
|
|
string localizedWindowTitle = tr("system.ui.window_title");
|
|
show_window(localizedWindowTitle);
|
|
ui_set_default_window_title(localizedWindowTitle);
|
|
init_flying_creature_configs();
|
|
init_item_registry();
|
|
init_pet_sounds();
|
|
init_search_pools();
|
|
|
|
while (true) {
|
|
bool game_started = false;
|
|
while (!game_started) {
|
|
int selection = run_main_menu();
|
|
|
|
if (selection == MAIN_MENU_NEW_GAME) {
|
|
if (!setup_new_character()) {
|
|
continue;
|
|
}
|
|
start_new_game();
|
|
speak_with_history(tr("system.game.new_game_started"), true);
|
|
game_started = true;
|
|
} else if (selection == MAIN_MENU_LOAD_GAME) {
|
|
if (!has_save_game()) {
|
|
ui_info_box(tr("system.ui.window_title"), i18n_text("Load Game"), tr("system.load_game.no_saves_found"));
|
|
continue;
|
|
}
|
|
string selectedFile;
|
|
if (!select_save_file(selectedFile)) {
|
|
continue;
|
|
}
|
|
if (load_game_state_from_file(selectedFile)) {
|
|
speak_with_history(tr("system.game.loaded"), true);
|
|
game_started = true;
|
|
} else {
|
|
string message = last_save_error;
|
|
if (message == "")
|
|
message = tr("system.load_game.unable_to_load");
|
|
ui_info_box(tr("system.ui.window_title"), i18n_text("Load Game"), message);
|
|
}
|
|
} else if (selection == MAIN_MENU_STORY) {
|
|
run_story_dialog();
|
|
continue;
|
|
} else if (selection == MAIN_MENU_LEARN_SOUNDS) {
|
|
run_learn_sounds_menu();
|
|
continue;
|
|
} else {
|
|
exit();
|
|
}
|
|
}
|
|
|
|
while (game_started) {
|
|
wait(5);
|
|
handle_global_volume_keys();
|
|
|
|
if (return_to_main_menu_requested) {
|
|
game_paused = false;
|
|
menuBackgroundUpdatesEnabled = true;
|
|
p.resume_all();
|
|
reset_game_state();
|
|
return_to_main_menu_requested = false;
|
|
break;
|
|
}
|
|
|
|
// Pause toggle
|
|
if (key_pressed(KEY_BACK)) {
|
|
game_paused = !game_paused;
|
|
if (game_paused) {
|
|
p.pause_all();
|
|
speak_with_history(tr("system.game.paused_prompt"), true);
|
|
} else {
|
|
p.resume_all();
|
|
// Restart all timers to prevent time from accumulating while paused
|
|
restart_all_timers();
|
|
speak_with_history(tr("system.game.resumed"), true);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Skip all game logic while paused
|
|
if (game_paused) {
|
|
continue;
|
|
}
|
|
|
|
check_pet_call_key();
|
|
|
|
if (key_pressed(KEY_ESCAPE)) {
|
|
int really_exit = ui_question("", tr("system.exit.confirm"));
|
|
if (really_exit == 1) {
|
|
return_to_main_menu_requested = true;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// 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_weapon_range_audio_all();
|
|
update_world_drops();
|
|
update_pets();
|
|
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();
|
|
dictionary burningArgs;
|
|
burningArgs.set("health", player_health);
|
|
speak_with_history(trf("system.combat.burning", burningArgs), 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();
|
|
dictionary healthArgs;
|
|
healthArgs.set("health", player_health);
|
|
speak_with_history(trf("system.combat.health_status", healthArgs), true);
|
|
}
|
|
}
|
|
|
|
// Death check
|
|
if (player_health <= 0) {
|
|
if (!try_consume_heal_scroll()) {
|
|
speak_with_history(tr("system.combat.you_died"), true);
|
|
wait(2000);
|
|
return_to_main_menu_requested = true;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (fylgjaCharging) {
|
|
update_fylgja_charge();
|
|
continue;
|
|
}
|
|
|
|
// 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_fylgja_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(get_health_report(), true);
|
|
}
|
|
|
|
// Coordinates Key
|
|
if (key_pressed(KEY_X)) {
|
|
string direction_label = (facing == 1) ? tr("system.direction.east") : tr("system.direction.west");
|
|
string terrain = i18n_translate_fragment_value(get_terrain_at_position(x));
|
|
if (get_mountain_at(x) !is null) {
|
|
dictionary terrainArgs;
|
|
terrainArgs.set("terrain", terrain);
|
|
terrain = trf("system.character.report.terrain_mountains", terrainArgs);
|
|
}
|
|
dictionary positionArgs;
|
|
positionArgs.set("direction", direction_label);
|
|
positionArgs.set("x", x);
|
|
positionArgs.set("y", y);
|
|
positionArgs.set("terrain", terrain);
|
|
speak_with_history(trf("system.character.report.position", positionArgs), 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;
|
|
|
|
bool left_active = key_down(KEY_LEFT);
|
|
bool right_active = key_down(KEY_RIGHT);
|
|
|
|
// Movement Logic
|
|
if (key_pressed(KEY_LEFT) && facing != 0 && !climbing && !falling && !rope_climbing) {
|
|
facing = 0;
|
|
speak_with_history(tr("system.direction.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(tr("system.direction.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 a tree.
|
|
Tree @current_tree = get_tree_at(x);
|
|
int ground_elevation = get_mountain_elevation_at(x);
|
|
if ((left_active || right_active) && y > ground_elevation && !jumping && !falling && !rope_climbing &&
|
|
current_tree !is null) {
|
|
// Fall out of tree
|
|
climbing = false;
|
|
start_falling();
|
|
}
|
|
|
|
if (left_active && 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 (right_active && 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));
|
|
bool search_key_down = shift_down || key_down(KEY_SLASH) || key_down(KEY_Z);
|
|
if (!search_key_down && !searching) {
|
|
search_timer.restart();
|
|
}
|
|
// Apply rune gathering bonus to search time
|
|
int search_time = apply_rune_gather_bonus(2000);
|
|
if (search_key_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);
|
|
}
|
|
|
|
update_fishing();
|
|
update_bow_shot();
|
|
|
|
bool ctrl_down = (key_down(KEY_LCTRL) || key_down(KEY_RCTRL));
|
|
|
|
// Bow draw detection
|
|
if (bow_equipped) {
|
|
if (ctrl_down && !bow_drawing) {
|
|
if (get_personal_count(ITEM_ARROWS) > 0) {
|
|
bow_drawing = true;
|
|
bow_draw_timer.restart();
|
|
p.play_stationary("sounds/weapons/bow_draw.ogg", false);
|
|
} else {
|
|
speak_ammo_blocked("No arrows.");
|
|
}
|
|
}
|
|
|
|
if (bow_drawing && !ctrl_down) {
|
|
release_bow_attack(x);
|
|
bow_drawing = false;
|
|
}
|
|
}
|
|
if (!bow_equipped && bow_drawing) {
|
|
bow_drawing = false;
|
|
}
|
|
|
|
// Sling charge detection
|
|
if (!bow_equipped && sling_equipped && ctrl_down && !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_ammo_blocked("No stones.");
|
|
}
|
|
}
|
|
|
|
// Update sling charge state while holding
|
|
if (sling_charging && ctrl_down) {
|
|
update_sling_charge();
|
|
}
|
|
|
|
// Sling release detection
|
|
if (sling_charging && !ctrl_down) {
|
|
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 (!bow_equipped && !bow_drawing && !sling_equipped && !sling_charging) {
|
|
int attack_cooldown = 1000;
|
|
if (spear_equipped)
|
|
attack_cooldown = 800;
|
|
if (axe_equipped)
|
|
attack_cooldown = 1600;
|
|
|
|
if (!fishing_pole_equipped && ctrl_down && attack_timer.elapsed > attack_cooldown) {
|
|
attack_timer.restart();
|
|
perform_attack(x);
|
|
}
|
|
}
|
|
|
|
// Audio Listener Update
|
|
p.update_listener_1d(x);
|
|
}
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
try {
|
|
run_game();
|
|
} catch {
|
|
log_unhandled_exception("main");
|
|
ui_info_box("Draugnorak", "Unhandled exception", "A crash log was written to your save data folder.");
|
|
}
|
|
}
|