A couple of terrain types added. First adventure added, incomplete.

This commit is contained in:
Storm Dragon
2026-01-22 14:43:25 -05:00
parent d2387b4506
commit 3097a245ca
17 changed files with 733 additions and 5 deletions

View 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();
}
}

View 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;
}