A couple of terrain types added. First adventure added, incomplete.
This commit is contained in:
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;
|
||||
}
|
||||
Reference in New Issue
Block a user