Multiple characters allowed. Starting base health lowered. Fylgjr system implemented.
This commit is contained in:
@@ -181,6 +181,20 @@ void play_item_collect_sound(string itemName)
|
||||
}
|
||||
}
|
||||
|
||||
string get_player_damage_sound() {
|
||||
if (player_sex == SEX_FEMALE) {
|
||||
return "sounds/player_female_damage.ogg";
|
||||
}
|
||||
return "sounds/player_male_damage.ogg";
|
||||
}
|
||||
|
||||
void play_player_damage_sound() {
|
||||
string soundFile = get_player_damage_sound();
|
||||
if (file_exists(soundFile)) {
|
||||
p.play_stationary(soundFile, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Safe sound handle cleanup - checks if handle is valid and sound is active before destroying
|
||||
void safe_destroy_sound(int &inout handle)
|
||||
{
|
||||
|
||||
@@ -473,7 +473,7 @@ void attempt_resident_snare_retrieval() {
|
||||
}
|
||||
}
|
||||
|
||||
// Resident butchering - processes up to two games per day when blessed
|
||||
// Resident butchering - processes up to residents_count games per day (doubled when blessed)
|
||||
void attempt_resident_butchering() {
|
||||
// Need residents
|
||||
if (residents_count <= 0) return;
|
||||
@@ -490,7 +490,7 @@ void attempt_resident_butchering() {
|
||||
// Need a fire in base
|
||||
if (!has_burning_fire_in_base()) return;
|
||||
|
||||
int attempts = get_resident_effect_multiplier();
|
||||
int attempts = residents_count * get_resident_effect_multiplier();
|
||||
int break_chance = get_resident_break_chance(RESIDENT_TOOL_BREAK_CHANCE);
|
||||
for (int attempt = 0; attempt < attempts; attempt++) {
|
||||
// Need game in storage
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
// Shared combat helpers for adventures
|
||||
|
||||
const int ADVENTURE_WEAPON_BOW = 1;
|
||||
const int ADVENTURE_WEAPON_SLING = 2;
|
||||
const int ADVENTURE_WEAPON_SPEAR = 3;
|
||||
const int ADVENTURE_WEAPON_AXE = 4;
|
||||
|
||||
funcdef int AdventureRangedReleaseCallback(int player_x, int direction, int range, int weapon_type, int damage);
|
||||
|
||||
bool adventure_arrow_recover_pending = false;
|
||||
|
||||
void reset_adventure_combat_state() {
|
||||
bow_drawing = false;
|
||||
sling_charging = false;
|
||||
adventure_arrow_recover_pending = false;
|
||||
stop_bow_shot_audio();
|
||||
safe_destroy_sound(sling_sound_handle);
|
||||
last_sling_stage = -1;
|
||||
}
|
||||
|
||||
void update_weapon_range_audio_with_listener(int listener_x, int creature_pos, bool &inout was_in_range) {
|
||||
int range = get_current_weapon_range();
|
||||
bool in_range = (range >= 0) && (abs(creature_pos - listener_x) <= range);
|
||||
if (in_range && !was_in_range) {
|
||||
play_weapon_range_sound("sounds/enemies/enter_range.ogg", creature_pos);
|
||||
} else if (!in_range && was_in_range) {
|
||||
play_weapon_range_sound("sounds/enemies/exit_range.ogg", creature_pos);
|
||||
}
|
||||
was_in_range = in_range;
|
||||
}
|
||||
|
||||
void adventure_start_bow_shot_audio(int listener_x, int start_x, int end_x, int hit_x, int duration_ms) {
|
||||
stop_bow_shot_audio();
|
||||
bow_shot_active = true;
|
||||
bow_shot_timer.restart();
|
||||
bow_shot_start_x = start_x;
|
||||
bow_shot_end_x = end_x;
|
||||
bow_shot_hit_x = hit_x;
|
||||
bow_shot_duration_ms = duration_ms;
|
||||
if (bow_shot_duration_ms < 1) bow_shot_duration_ms = 1;
|
||||
|
||||
bow_shot_sound_handle = play_1d_with_volume_step(
|
||||
"sounds/weapons/arrow_flies.ogg",
|
||||
listener_x,
|
||||
bow_shot_start_x,
|
||||
false,
|
||||
PLAYER_WEAPON_SOUND_VOLUME_STEP
|
||||
);
|
||||
}
|
||||
|
||||
void adventure_update_bow_shot(int listener_x) {
|
||||
if (!bow_shot_active) return;
|
||||
if (bow_shot_duration_ms < 1) bow_shot_duration_ms = 1;
|
||||
|
||||
int elapsed = bow_shot_timer.elapsed;
|
||||
float progress = float(elapsed) / float(bow_shot_duration_ms);
|
||||
if (progress > 1.0f) progress = 1.0f;
|
||||
|
||||
int travel = int(float(bow_shot_end_x - bow_shot_start_x) * progress);
|
||||
int current_pos = bow_shot_start_x + travel;
|
||||
if (bow_shot_sound_handle != -1) {
|
||||
p.update_sound_1d(bow_shot_sound_handle, current_pos);
|
||||
}
|
||||
|
||||
if (elapsed >= bow_shot_duration_ms) {
|
||||
int hit_x = bow_shot_hit_x;
|
||||
bool recover_pending = adventure_arrow_recover_pending;
|
||||
stop_bow_shot_audio();
|
||||
adventure_arrow_recover_pending = false;
|
||||
if (hit_x >= 0) {
|
||||
play_1d_with_volume_step(
|
||||
"sounds/weapons/arrow_hit.ogg",
|
||||
listener_x,
|
||||
hit_x,
|
||||
false,
|
||||
PLAYER_WEAPON_SOUND_VOLUME_STEP
|
||||
);
|
||||
}
|
||||
if (recover_pending) {
|
||||
add_personal_count(ITEM_ARROWS, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void adventure_release_bow_attack(int player_x, int player_facing, AdventureRangedReleaseCallback@ ranged_callback) {
|
||||
if (get_personal_count(ITEM_ARROWS) <= 0) {
|
||||
speak_ammo_blocked("No arrows.");
|
||||
return;
|
||||
}
|
||||
|
||||
int damage = get_bow_draw_damage(bow_draw_timer.elapsed);
|
||||
add_personal_count(ITEM_ARROWS, -1);
|
||||
p.play_stationary("sounds/weapons/bow_fire.ogg", false);
|
||||
|
||||
int search_direction = (player_facing == 1) ? 1 : -1;
|
||||
int target_x = -1;
|
||||
if (@ranged_callback !is null) {
|
||||
target_x = ranged_callback(player_x, search_direction, BOW_RANGE, ADVENTURE_WEAPON_BOW, damage);
|
||||
}
|
||||
|
||||
int end_x = (target_x != -1)
|
||||
? target_x
|
||||
: (player_x + (search_direction * (BOW_RANGE + BOW_MISS_EXTRA_TILES)));
|
||||
|
||||
int duration_ms = ARROW_FLIES_DURATION_MS;
|
||||
if (target_x != -1) {
|
||||
int distance = abs(target_x - player_x);
|
||||
if (distance < 1) distance = 1;
|
||||
duration_ms = int(float(ARROW_FLIES_DURATION_MS) * (float(distance) / float(BOW_RANGE)));
|
||||
if (duration_ms < 1) duration_ms = 1;
|
||||
}
|
||||
|
||||
adventure_arrow_recover_pending = (random(1, 100) <= 25);
|
||||
adventure_start_bow_shot_audio(player_x, player_x, end_x, target_x, duration_ms);
|
||||
}
|
||||
|
||||
void adventure_release_sling_attack(int player_x, int player_facing, AdventureRangedReleaseCallback@ ranged_callback) {
|
||||
add_personal_count(ITEM_STONES, -1);
|
||||
|
||||
int elapsed = sling_charge_timer.elapsed;
|
||||
int cycle_time = 1500;
|
||||
int time_in_cycle = elapsed % cycle_time;
|
||||
int stage = time_in_cycle / 500; // 0=low, 1=in-range, 2=high
|
||||
|
||||
if (stage != 1) {
|
||||
speak_with_history("Stone missed.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
int search_direction = (player_facing == 1) ? 1 : -1;
|
||||
int target_x = -1;
|
||||
int damage = random(SLING_DAMAGE_MIN, SLING_DAMAGE_MAX);
|
||||
|
||||
if (@ranged_callback !is null) {
|
||||
target_x = ranged_callback(player_x, search_direction, SLING_RANGE, ADVENTURE_WEAPON_SLING, damage);
|
||||
}
|
||||
|
||||
if (target_x == -1) {
|
||||
speak_with_history("Stone missed.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
play_1d_with_volume_step("sounds/weapons/sling_hit.ogg", player_x, target_x, false, PLAYER_WEAPON_SOUND_VOLUME_STEP);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Adventure System
|
||||
// Handles triggering and managing terrain-specific adventures
|
||||
|
||||
#include "src/bosses/adventure_combat.nvgt"
|
||||
#include "src/bosses/unicorn/unicorn_boss.nvgt"
|
||||
|
||||
void check_adventure_menu(int player_x) {
|
||||
|
||||
@@ -16,7 +16,7 @@ class UnicornBoss {
|
||||
}
|
||||
|
||||
void reset() {
|
||||
health = 10000;
|
||||
health = 450;
|
||||
speed = UNICORN_SPEED;
|
||||
facing = 0; // Start facing west (toward player)
|
||||
x = 0;
|
||||
@@ -33,6 +33,8 @@ 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
|
||||
const bool UNICORN_BOW_CAN_DAMAGE_SUPPORTS = false;
|
||||
const bool UNICORN_SLING_CAN_DAMAGE_SUPPORTS = false;
|
||||
|
||||
// State
|
||||
UnicornBoss unicorn;
|
||||
@@ -46,16 +48,23 @@ 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 = "";
|
||||
bool unicorn_defeated = false;
|
||||
bool unicorn_defeated_by_fall = false;
|
||||
bool unicorn_in_weapon_range = false;
|
||||
|
||||
void init_unicorn_adventure() {
|
||||
unicorn.reset();
|
||||
unicorn.x = UNICORN_ARENA_SIZE - 1; // Start at east end
|
||||
|
||||
reset_adventure_combat_state();
|
||||
player_arena_x = 0; // Start player at west end
|
||||
player_arena_y = 0;
|
||||
player_arena_jumping = false;
|
||||
bridge_collapsed = false;
|
||||
current_unicorn_sound = "";
|
||||
unicorn_defeated = false;
|
||||
unicorn_defeated_by_fall = false;
|
||||
unicorn_in_weapon_range = false;
|
||||
|
||||
// Initialize supports
|
||||
bridge_supports_health.resize(2);
|
||||
@@ -65,6 +74,7 @@ void init_unicorn_adventure() {
|
||||
|
||||
void cleanup_unicorn_adventure() {
|
||||
p.destroy_all();
|
||||
reset_adventure_combat_state();
|
||||
if (unicorn.sound_handle != -1) {
|
||||
p.destroy_sound(unicorn.sound_handle);
|
||||
unicorn.sound_handle = -1;
|
||||
@@ -90,6 +100,7 @@ void run_unicorn_adventure() {
|
||||
intro.insert_last("Strategy:");
|
||||
intro.insert_last(" - Use your axe to destroy a bridge support");
|
||||
intro.insert_last(" - Lure the Unicorn onto the bridge");
|
||||
intro.insert_last(" - Or fight the Unicorn directly (it has massive health)");
|
||||
intro.insert_last(" - Jump (UP arrow) to avoid being trampled");
|
||||
intro.insert_last(" - When the bridge collapses with the Unicorn on it, you win!");
|
||||
intro.insert_last("");
|
||||
@@ -130,10 +141,24 @@ void run_unicorn_adventure() {
|
||||
// Updates
|
||||
update_player_jump();
|
||||
update_unicorn();
|
||||
adventure_update_bow_shot(player_arena_x);
|
||||
update_unicorn_weapon_range_audio();
|
||||
|
||||
// Check Conditions - unicorn falls when on collapsed bridge
|
||||
if (bridge_collapsed && unicorn.x >= BRIDGE_START && unicorn.x <= BRIDGE_END) {
|
||||
play_unicorn_death_sequence();
|
||||
if (!unicorn_defeated && bridge_collapsed && unicorn.x >= BRIDGE_START && unicorn.x <= BRIDGE_END) {
|
||||
mark_unicorn_defeated(true);
|
||||
}
|
||||
|
||||
if (!unicorn_defeated && unicorn.health <= 0) {
|
||||
mark_unicorn_defeated(false);
|
||||
}
|
||||
|
||||
if (unicorn_defeated) {
|
||||
if (unicorn_defeated_by_fall) {
|
||||
play_unicorn_death_sequence();
|
||||
} else {
|
||||
play_unicorn_ground_death_sequence();
|
||||
}
|
||||
cleanup_unicorn_adventure();
|
||||
give_unicorn_rewards();
|
||||
return;
|
||||
@@ -247,55 +272,207 @@ 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;
|
||||
bool ctrl_down = (key_down(KEY_LCTRL) || key_down(KEY_RCTRL));
|
||||
|
||||
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);
|
||||
// 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 {
|
||||
p.play_stationary("sounds/weapons/axe_swing.ogg", false);
|
||||
speak_ammo_blocked("No arrows.");
|
||||
}
|
||||
}
|
||||
|
||||
if (bow_drawing && !ctrl_down) {
|
||||
adventure_release_bow_attack(player_arena_x, player_arena_facing, @unicorn_ranged_attack);
|
||||
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) {
|
||||
adventure_release_sling_attack(player_arena_x, player_arena_facing, @unicorn_ranged_attack);
|
||||
sling_charging = false;
|
||||
safe_destroy_sound(sling_sound_handle);
|
||||
}
|
||||
|
||||
// Non-sling weapon attacks (existing pattern)
|
||||
if (!bow_equipped && !bow_drawing && !sling_equipped && !sling_charging) {
|
||||
if (fishing_pole_equipped) return;
|
||||
|
||||
int weapon_type = get_unicorn_melee_weapon_type();
|
||||
if (weapon_type == -1) return;
|
||||
|
||||
int attack_cooldown = 1000;
|
||||
if (weapon_type == ADVENTURE_WEAPON_SPEAR) attack_cooldown = 800;
|
||||
if (weapon_type == ADVENTURE_WEAPON_AXE) attack_cooldown = 1600;
|
||||
|
||||
if (ctrl_down && arena_attack_timer.elapsed > attack_cooldown) {
|
||||
arena_attack_timer.restart();
|
||||
play_unicorn_melee_swing(weapon_type);
|
||||
if (unicorn_melee_hit(weapon_type)) {
|
||||
play_unicorn_melee_hit(weapon_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int get_unicorn_melee_weapon_type() {
|
||||
if (spear_equipped) return ADVENTURE_WEAPON_SPEAR;
|
||||
if (axe_equipped) return ADVENTURE_WEAPON_AXE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void play_unicorn_melee_swing(int weapon_type) {
|
||||
if (weapon_type == ADVENTURE_WEAPON_SPEAR) {
|
||||
p.play_stationary("sounds/weapons/spear_swing.ogg", false);
|
||||
} else if (weapon_type == ADVENTURE_WEAPON_AXE) {
|
||||
p.play_stationary("sounds/weapons/axe_swing.ogg", false);
|
||||
}
|
||||
}
|
||||
|
||||
void play_unicorn_melee_hit(int weapon_type) {
|
||||
if (weapon_type == ADVENTURE_WEAPON_SPEAR) {
|
||||
p.play_stationary("sounds/weapons/spear_hit.ogg", false);
|
||||
} else if (weapon_type == ADVENTURE_WEAPON_AXE) {
|
||||
p.play_stationary("sounds/weapons/axe_hit.ogg", false);
|
||||
}
|
||||
}
|
||||
|
||||
bool unicorn_melee_hit(int weapon_type) {
|
||||
int target_support = -1;
|
||||
if (abs(player_arena_x - BRIDGE_START) <= 1) {
|
||||
target_support = 0;
|
||||
} else if (abs(player_arena_x - BRIDGE_END) <= 1) {
|
||||
target_support = 1;
|
||||
}
|
||||
|
||||
if (target_support != -1 && bridge_supports_health[target_support] > 0) {
|
||||
if (weapon_type == ADVENTURE_WEAPON_AXE) {
|
||||
bridge_supports_health[target_support] -= AXE_DAMAGE;
|
||||
if (bridge_supports_health[target_support] <= 0) {
|
||||
check_bridge_collapse();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (abs(player_arena_x - unicorn.x) <= 1) {
|
||||
int damage = (weapon_type == ADVENTURE_WEAPON_SPEAR) ? SPEAR_DAMAGE : AXE_DAMAGE;
|
||||
apply_unicorn_damage(damage);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int find_unicorn_ranged_target(int player_x, int direction, int range) {
|
||||
int unicorn_distance = (unicorn.x - player_x) * direction;
|
||||
if (unicorn_distance > 0 && unicorn_distance <= range) {
|
||||
return unicorn.x;
|
||||
}
|
||||
|
||||
for (int dist = 1; dist <= range; dist++) {
|
||||
int check_x = player_x + (dist * direction);
|
||||
if (check_x < 0 || check_x >= UNICORN_ARENA_SIZE) break;
|
||||
|
||||
if (bridge_supports_health[0] > 0 && check_x == BRIDGE_START) {
|
||||
return check_x;
|
||||
}
|
||||
|
||||
if (bridge_supports_health[1] > 0 && check_x == BRIDGE_END) {
|
||||
return check_x;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int unicorn_ranged_attack(int player_x, int direction, int range, int weapon_type, int damage) {
|
||||
int target_x = find_unicorn_ranged_target(player_x, direction, range);
|
||||
if (target_x == -1) return -1;
|
||||
|
||||
if (target_x == unicorn.x) {
|
||||
apply_unicorn_damage(damage);
|
||||
return target_x;
|
||||
}
|
||||
|
||||
if (target_x == BRIDGE_START || target_x == BRIDGE_END) {
|
||||
int support_index = (target_x == BRIDGE_START) ? 0 : 1;
|
||||
bool can_damage = false;
|
||||
if (weapon_type == ADVENTURE_WEAPON_BOW) {
|
||||
can_damage = UNICORN_BOW_CAN_DAMAGE_SUPPORTS;
|
||||
} else if (weapon_type == ADVENTURE_WEAPON_SLING) {
|
||||
can_damage = UNICORN_SLING_CAN_DAMAGE_SUPPORTS;
|
||||
}
|
||||
|
||||
if (can_damage && bridge_supports_health[support_index] > 0) {
|
||||
bridge_supports_health[support_index] -= damage;
|
||||
if (bridge_supports_health[support_index] <= 0) {
|
||||
check_bridge_collapse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return target_x;
|
||||
}
|
||||
|
||||
void mark_unicorn_defeated(bool by_fall) {
|
||||
if (unicorn_defeated) return;
|
||||
unicorn_defeated = true;
|
||||
unicorn_defeated_by_fall = by_fall;
|
||||
if (by_fall) {
|
||||
unicorn.health = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void apply_unicorn_damage(int damage) {
|
||||
if (damage <= 0) return;
|
||||
if (unicorn.health <= 0) return;
|
||||
|
||||
unicorn.health -= damage;
|
||||
if (unicorn.health <= 0) {
|
||||
unicorn.health = 0;
|
||||
mark_unicorn_defeated(false);
|
||||
}
|
||||
}
|
||||
|
||||
void play_unicorn_ground_death_sequence() {
|
||||
if (unicorn.sound_handle != -1) {
|
||||
p.destroy_sound(unicorn.sound_handle);
|
||||
unicorn.sound_handle = -1;
|
||||
}
|
||||
p.play_stationary("sounds/actions/hit_ground.ogg", false);
|
||||
wait(800);
|
||||
}
|
||||
|
||||
void update_unicorn_weapon_range_audio() {
|
||||
update_weapon_range_audio_with_listener(player_arena_x, unicorn.x, unicorn_in_weapon_range);
|
||||
}
|
||||
|
||||
void check_bridge_collapse() {
|
||||
// Bridge collapses when any support is destroyed
|
||||
if (bridge_supports_health[0] <= 0 || bridge_supports_health[1] <= 0) {
|
||||
@@ -339,6 +516,7 @@ void update_unicorn() {
|
||||
if (unicorn.x == player_arena_x && player_arena_y == 0) {
|
||||
player_health -= 10;
|
||||
p.play_stationary("sounds/actions/hit_ground.ogg", false);
|
||||
play_player_damage_sound();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -440,6 +618,8 @@ void give_unicorn_rewards() {
|
||||
rewards.insert_last("You have already mastered the Rune of Swiftness.");
|
||||
}
|
||||
|
||||
append_adventure_completion_rewards(ADVENTURE_UNICORN, rewards);
|
||||
|
||||
// Display rewards in text reader
|
||||
text_reader_lines(rewards, "Unicorn Victory", true);
|
||||
}
|
||||
|
||||
+17
-2
@@ -281,9 +281,23 @@ void release_bow_attack(int player_x) {
|
||||
bool hit_flying_creature = false;
|
||||
bool hit_boar = false;
|
||||
int target_x = find_ranged_enemy(player_x, BOW_RANGE, search_direction, true, hit_bandit, hit_boar, hit_flying_creature);
|
||||
bool hit_tree = false;
|
||||
|
||||
if (target_x == -1) {
|
||||
for (int dist = 1; dist <= BOW_RANGE; dist++) {
|
||||
int check_x = player_x + (dist * search_direction);
|
||||
if (check_x < 0 || check_x >= MAP_SIZE) break;
|
||||
Tree@ tree = get_tree_at(check_x);
|
||||
if (tree != null && !tree.is_chopped) {
|
||||
target_x = check_x;
|
||||
hit_tree = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int hit_type = BOW_HIT_NONE;
|
||||
if (target_x != -1) {
|
||||
if (target_x != -1 && !hit_tree) {
|
||||
if (hit_bandit) {
|
||||
damage_bandit_at(target_x, damage);
|
||||
hit_type = BOW_HIT_BANDIT;
|
||||
@@ -297,6 +311,8 @@ void release_bow_attack(int player_x) {
|
||||
damage_zombie_at(target_x, damage);
|
||||
hit_type = BOW_HIT_ZOMBIE;
|
||||
}
|
||||
} else if (hit_tree) {
|
||||
hit_type = BOW_HIT_NONE;
|
||||
}
|
||||
|
||||
int end_x = (target_x != -1)
|
||||
@@ -351,7 +367,6 @@ void release_sling_attack(int player_x) {
|
||||
if (tree != null && !tree.is_chopped) {
|
||||
// Stone hits tree but doesn't damage it
|
||||
play_1d_with_volume_step("sounds/weapons/sling_hit.ogg", player_x, check_x, false, PLAYER_WEAPON_SOUND_VOLUME_STEP);
|
||||
speak_with_history("Stone hit tree at " + check_x + ".", true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -28,6 +28,9 @@ const int BLESSING_TRIGGER_CHANCE = 10;
|
||||
const int BLESSING_WALK_SPEED = 320;
|
||||
const int FISH_WEIGHT_MIN = 1;
|
||||
const int FISH_WEIGHT_MAX = 30;
|
||||
// Player sex constants
|
||||
const int SEX_MALE = 0;
|
||||
const int SEX_FEMALE = 1;
|
||||
|
||||
// Weapon damage
|
||||
const int SPEAR_DAMAGE = 3;
|
||||
@@ -77,7 +80,7 @@ const int BOAR_CHARGE_SPEED = 500; // ms per tile when charging
|
||||
const int BOAR_SPAWN_CHANCE_PER_HOUR = 30;
|
||||
|
||||
// Barricade configuration
|
||||
const int BARRICADE_BASE_HEALTH = 100;
|
||||
const int BARRICADE_BASE_HEALTH = 40;
|
||||
const int BARRICADE_MAX_HEALTH = 500;
|
||||
const int BARRICADE_STICK_COST = 3;
|
||||
const int BARRICADE_STICK_HEALTH = 10;
|
||||
@@ -247,7 +250,7 @@ const int FALL_DAMAGE_MAX = 4;
|
||||
// Base Automation
|
||||
const int RESIDENT_SLING_COOLDOWN = 4000; // 4 seconds between shots
|
||||
const int RESIDENT_COLLECTION_CHANCE = 10; // 10% chance per basket per hour
|
||||
const int RESIDENT_FORAGING_CHANCE = 50; // 50% chance per resident per attempt (twice daily)
|
||||
const int RESIDENT_FORAGING_CHANCE = 50; // 50% chance per resident per attempt (daily)
|
||||
|
||||
// Utility functions
|
||||
int abs(int value) {
|
||||
|
||||
@@ -184,6 +184,7 @@ bool try_attack_player_bandit(Bandit@ bandit) {
|
||||
} else if (bandit.weapon_type == "axe") {
|
||||
play_creature_attack_sound("sounds/weapons/axe_hit.ogg", x, bandit.position, BANDIT_SOUND_VOLUME_STEP);
|
||||
}
|
||||
play_player_damage_sound();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -199,14 +200,13 @@ void try_attack_barricade_bandit(Bandit@ bandit) {
|
||||
barricade_health -= damage;
|
||||
if (barricade_health < 0) barricade_health = 0;
|
||||
|
||||
// Play weapon swing sound
|
||||
// Play weapon swing sound (barricade hits share a common impact sound)
|
||||
if (bandit.weapon_type == "spear") {
|
||||
play_creature_attack_sound("sounds/weapons/spear_swing.ogg", x, bandit.position, BANDIT_SOUND_VOLUME_STEP);
|
||||
play_creature_attack_sound("sounds/weapons/spear_hit.ogg", x, bandit.position, BANDIT_SOUND_VOLUME_STEP);
|
||||
} else if (bandit.weapon_type == "axe") {
|
||||
play_creature_attack_sound("sounds/weapons/axe_swing.ogg", x, bandit.position, BANDIT_SOUND_VOLUME_STEP);
|
||||
play_creature_attack_sound("sounds/weapons/axe_hit.ogg", x, bandit.position, BANDIT_SOUND_VOLUME_STEP);
|
||||
}
|
||||
play_creature_attack_sound("sounds/weapons/axe_hit.ogg", x, bandit.position, BANDIT_SOUND_VOLUME_STEP);
|
||||
|
||||
// Resident defense counter-attack
|
||||
if (can_residents_defend()) {
|
||||
|
||||
@@ -119,12 +119,10 @@ bool try_attack_player_ground_game(GroundGame@ game) {
|
||||
game.attack_timer.restart();
|
||||
|
||||
// Attack!
|
||||
// TODO: Add specific boar attack sound? For now re-use zombie hit as generic impact
|
||||
play_creature_attack_sound("sounds/enemies/zombie_hits_player.ogg", x, game.position, BOAR_SOUND_VOLUME_STEP);
|
||||
|
||||
int damage = random(BOAR_DAMAGE_MIN, BOAR_DAMAGE_MAX);
|
||||
player_health -= damage;
|
||||
if (player_health < 0) player_health = 0;
|
||||
play_player_damage_sound();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ void try_attack_barricade_undead(Undead@ undead) {
|
||||
barricade_health -= damage;
|
||||
if (barricade_health < 0) barricade_health = 0;
|
||||
|
||||
play_creature_attack_sound("sounds/enemies/zombie_hits_player.ogg", x, undead.position, ZOMBIE_SOUND_VOLUME_STEP);
|
||||
play_creature_attack_sound("sounds/weapons/axe_hit.ogg", x, undead.position, ZOMBIE_SOUND_VOLUME_STEP);
|
||||
|
||||
// Resident defense counter-attack
|
||||
if (can_residents_defend()) {
|
||||
@@ -144,7 +144,7 @@ bool try_attack_player_undead(Undead@ undead) {
|
||||
player_health = 0;
|
||||
}
|
||||
|
||||
play_creature_attack_sound("sounds/enemies/zombie_hits_player.ogg", x, undead.position, ZOMBIE_SOUND_VOLUME_STEP);
|
||||
play_player_damage_sound();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@ void apply_falling_damage(int fall_height) {
|
||||
// Apply damage
|
||||
player_health -= damage;
|
||||
if (player_health < 0) player_health = 0;
|
||||
if (damage > 0) {
|
||||
play_player_damage_sound();
|
||||
}
|
||||
|
||||
// Feedback
|
||||
speak_with_history("Fell " + fall_height + " feet! Took " + damage + " damage. " + player_health + " health remaining.", true);
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
// Fylgja system
|
||||
// Tracks adventure completion stages, unlocks, and activation
|
||||
|
||||
const int ADVENTURE_UNICORN = 1;
|
||||
const int FYLGJA_STAGE_COUNT = 9;
|
||||
const int FYLGJA_UNICORN = 0;
|
||||
const int UNICORN_TRAMPLE_DAMAGE = 20;
|
||||
|
||||
string[] fylgjaStageNames = {
|
||||
"tenuous",
|
||||
"faint",
|
||||
"stirring",
|
||||
"budding",
|
||||
"kindled",
|
||||
"bound",
|
||||
"sworn",
|
||||
"ascendant",
|
||||
"ultimate"
|
||||
};
|
||||
|
||||
int[] adventureIds = {ADVENTURE_UNICORN};
|
||||
string[] adventureStageTargets = {"unicorn"};
|
||||
int[] adventureCompletionCounts = {0};
|
||||
|
||||
int[] fylgjaAdventureIds = {ADVENTURE_UNICORN};
|
||||
string[] fylgjaNames = {"Unicorn"};
|
||||
|
||||
int lastFylgjaDay = -1;
|
||||
bool fylgjaCharging = false;
|
||||
int fylgjaChargeDirection = 1;
|
||||
timer fylgjaChargeTimer;
|
||||
int fylgjaSoundHandle = -1;
|
||||
string currentFylgjaSound = "";
|
||||
|
||||
void reset_fylgja_state() {
|
||||
lastFylgjaDay = -1;
|
||||
fylgjaCharging = false;
|
||||
fylgjaChargeDirection = 1;
|
||||
currentFylgjaSound = "";
|
||||
if (fylgjaSoundHandle != -1) {
|
||||
p.destroy_sound(fylgjaSoundHandle);
|
||||
fylgjaSoundHandle = -1;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < adventureCompletionCounts.length(); i++) {
|
||||
adventureCompletionCounts[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int get_adventure_index(int adventureId) {
|
||||
for (uint i = 0; i < adventureIds.length(); i++) {
|
||||
if (adventureIds[i] == adventureId) return int(i);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int get_fylgja_index_for_adventure(int adventureId) {
|
||||
for (uint i = 0; i < fylgjaAdventureIds.length(); i++) {
|
||||
if (fylgjaAdventureIds[i] == adventureId) return int(i);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool is_fylgja_unlocked(int fylgjaIndex) {
|
||||
if (fylgjaIndex < 0 || fylgjaIndex >= int(fylgjaAdventureIds.length())) return false;
|
||||
int adventureIndex = get_adventure_index(fylgjaAdventureIds[fylgjaIndex]);
|
||||
if (adventureIndex < 0) return false;
|
||||
return adventureCompletionCounts[adventureIndex] >= FYLGJA_STAGE_COUNT;
|
||||
}
|
||||
|
||||
int get_unlocked_fylgja_count() {
|
||||
int unlockedCount = 0;
|
||||
for (uint i = 0; i < fylgjaNames.length(); i++) {
|
||||
if (is_fylgja_unlocked(int(i))) unlockedCount++;
|
||||
}
|
||||
return unlockedCount;
|
||||
}
|
||||
|
||||
void append_adventure_completion_rewards(int adventureId, string[]@ rewards) {
|
||||
int adventureIndex = get_adventure_index(adventureId);
|
||||
if (adventureIndex < 0 || @rewards == null) return;
|
||||
|
||||
adventureCompletionCounts[adventureIndex]++;
|
||||
int completionCount = adventureCompletionCounts[adventureIndex];
|
||||
|
||||
int stageIndex = completionCount - 1;
|
||||
if (stageIndex < 0) stageIndex = 0;
|
||||
if (stageIndex >= int(fylgjaStageNames.length())) stageIndex = int(fylgjaStageNames.length()) - 1;
|
||||
|
||||
string stageName = fylgjaStageNames[stageIndex];
|
||||
string targetName = adventureStageTargets[adventureIndex];
|
||||
rewards.insert_last("You have a " + stageName + " connection with the " + targetName + ".");
|
||||
|
||||
int fylgjaIndex = get_fylgja_index_for_adventure(adventureId);
|
||||
if (fylgjaIndex == -1) return;
|
||||
|
||||
if (completionCount >= FYLGJA_STAGE_COUNT) {
|
||||
if (completionCount == FYLGJA_STAGE_COUNT) {
|
||||
rewards.insert_last("You have unlocked the " + fylgjaNames[fylgjaIndex] + " Fylgja!");
|
||||
} else {
|
||||
rewards.insert_last("You have already unlocked the " + fylgjaNames[fylgjaIndex] + " Fylgja.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void check_fylgja_menu() {
|
||||
if (!key_pressed(KEY_F)) return;
|
||||
if (fylgjaCharging) return;
|
||||
if (get_unlocked_fylgja_count() == 0) return;
|
||||
|
||||
if (lastFylgjaDay == current_day) {
|
||||
speak_with_history("You have already used your Fylgja today.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
run_fylgja_menu();
|
||||
}
|
||||
|
||||
void run_fylgja_menu() {
|
||||
string[] options;
|
||||
int[] fylgjaIndices;
|
||||
|
||||
for (uint i = 0; i < fylgjaNames.length(); i++) {
|
||||
if (is_fylgja_unlocked(int(i))) {
|
||||
options.insert_last(fylgjaNames[i] + " Fylgja");
|
||||
fylgjaIndices.insert_last(int(i));
|
||||
}
|
||||
}
|
||||
|
||||
if (options.length() == 0) return;
|
||||
|
||||
speak_with_history("Fylgja menu.", true);
|
||||
|
||||
int selection = 0;
|
||||
speak_with_history(options[selection], true);
|
||||
|
||||
while (true) {
|
||||
wait(5);
|
||||
menu_background_tick();
|
||||
|
||||
if (key_pressed(KEY_ESCAPE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_DOWN)) {
|
||||
selection++;
|
||||
if (selection >= int(options.length())) selection = 0;
|
||||
speak_with_history(options[selection], true);
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_UP)) {
|
||||
selection--;
|
||||
if (selection < 0) selection = int(options.length()) - 1;
|
||||
speak_with_history(options[selection], true);
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_RETURN)) {
|
||||
int chosenIndex = fylgjaIndices[selection];
|
||||
if (activate_fylgja(chosenIndex)) {
|
||||
lastFylgjaDay = current_day;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool activate_fylgja(int fylgjaIndex) {
|
||||
if (fylgjaIndex == FYLGJA_UNICORN) {
|
||||
start_unicorn_fylgja_charge();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void start_unicorn_fylgja_charge() {
|
||||
reset_fishing_session();
|
||||
|
||||
if (sling_charging) {
|
||||
sling_charging = false;
|
||||
if (sling_sound_handle != -1) {
|
||||
p.destroy_sound(sling_sound_handle);
|
||||
sling_sound_handle = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (bow_drawing) {
|
||||
bow_drawing = false;
|
||||
}
|
||||
|
||||
searching = false;
|
||||
search_timer.restart();
|
||||
search_delay_timer.restart();
|
||||
|
||||
jumping = false;
|
||||
climbing = false;
|
||||
falling = false;
|
||||
rope_climbing = false;
|
||||
pending_rope_climb_x = -1;
|
||||
pending_rope_climb_elevation = 0;
|
||||
y = get_mountain_elevation_at(x);
|
||||
|
||||
fylgjaCharging = true;
|
||||
fylgjaChargeDirection = facing;
|
||||
fylgjaChargeTimer.restart();
|
||||
currentFylgjaSound = "";
|
||||
update_fylgja_charge_audio();
|
||||
}
|
||||
|
||||
bool should_stop_charge_for_climb_up(int fromX, int toX) {
|
||||
MountainRange@ mountain = get_mountain_at(toX);
|
||||
if (mountain is null) return false;
|
||||
|
||||
int elevationChange = mountain.get_elevation_change(fromX, toX);
|
||||
return elevationChange >= MOUNTAIN_STEEP_THRESHOLD;
|
||||
}
|
||||
|
||||
void apply_fylgja_trample_damage(int posX) {
|
||||
damage_undead_at(posX, UNICORN_TRAMPLE_DAMAGE);
|
||||
damage_bandit_at(posX, UNICORN_TRAMPLE_DAMAGE);
|
||||
damage_boar_at(posX, UNICORN_TRAMPLE_DAMAGE);
|
||||
}
|
||||
|
||||
string get_unicorn_charge_sound(int posX) {
|
||||
string terrain = get_terrain_at_position(posX);
|
||||
if (terrain == "stone" || terrain == "gravel") {
|
||||
return "sounds/bosses/unicorn/unicorn_on_bridge.ogg";
|
||||
}
|
||||
return "sounds/bosses/unicorn/unicorn_galloping.ogg";
|
||||
}
|
||||
|
||||
void update_fylgja_charge_audio() {
|
||||
string soundFile = get_unicorn_charge_sound(x);
|
||||
bool needNewSound = (fylgjaSoundHandle == -1 || !p.sound_is_active(fylgjaSoundHandle) || currentFylgjaSound != soundFile);
|
||||
|
||||
if (needNewSound) {
|
||||
if (fylgjaSoundHandle != -1) {
|
||||
p.destroy_sound(fylgjaSoundHandle);
|
||||
fylgjaSoundHandle = -1;
|
||||
}
|
||||
fylgjaSoundHandle = play_1d_with_volume_step(soundFile, x, x, true, UNICORN_SOUND_VOLUME_STEP);
|
||||
currentFylgjaSound = soundFile;
|
||||
} else {
|
||||
p.update_sound_1d(fylgjaSoundHandle, x);
|
||||
}
|
||||
}
|
||||
|
||||
void stop_fylgja_charge() {
|
||||
fylgjaCharging = false;
|
||||
currentFylgjaSound = "";
|
||||
if (fylgjaSoundHandle != -1) {
|
||||
p.destroy_sound(fylgjaSoundHandle);
|
||||
fylgjaSoundHandle = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void update_fylgja_charge() {
|
||||
if (!fylgjaCharging) return;
|
||||
|
||||
if (key_down(KEY_LSHIFT) || key_down(KEY_RSHIFT)) {
|
||||
stop_fylgja_charge();
|
||||
return;
|
||||
}
|
||||
|
||||
if (x <= BASE_END || x <= 0 || x >= MAP_SIZE - 1) {
|
||||
stop_fylgja_charge();
|
||||
return;
|
||||
}
|
||||
|
||||
if (fylgjaChargeTimer.elapsed >= UNICORN_SPEED) {
|
||||
fylgjaChargeTimer.restart();
|
||||
int step = (fylgjaChargeDirection == 1) ? 1 : -1;
|
||||
int targetX = x + step;
|
||||
|
||||
if (targetX < 0 || targetX >= MAP_SIZE) {
|
||||
stop_fylgja_charge();
|
||||
return;
|
||||
}
|
||||
|
||||
if (should_stop_charge_for_climb_up(x, targetX)) {
|
||||
stop_fylgja_charge();
|
||||
return;
|
||||
}
|
||||
|
||||
int previousY = y;
|
||||
x = targetX;
|
||||
int targetElevation = get_mountain_elevation_at(x);
|
||||
if (targetElevation != y) {
|
||||
y = targetElevation;
|
||||
if (targetElevation < previousY) {
|
||||
int fallHeight = previousY - targetElevation;
|
||||
apply_falling_damage(fallHeight);
|
||||
}
|
||||
}
|
||||
|
||||
check_snare_collision(x);
|
||||
apply_fylgja_trample_damage(x);
|
||||
|
||||
if (x <= BASE_END || x <= 0 || x >= MAP_SIZE - 1) {
|
||||
stop_fylgja_charge();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
update_fylgja_charge_audio();
|
||||
update_fishing();
|
||||
update_bow_shot();
|
||||
p.update_listener_1d(x);
|
||||
}
|
||||
@@ -39,6 +39,7 @@ int equipped_feet = EQUIP_NONE;
|
||||
|
||||
// Quick slots
|
||||
int[] quick_slots;
|
||||
int[] quick_slot_runes;
|
||||
int[] item_count_slots;
|
||||
|
||||
void reset_quick_slots() {
|
||||
@@ -47,6 +48,11 @@ void reset_quick_slots() {
|
||||
quick_slots[i] = -1;
|
||||
}
|
||||
|
||||
quick_slot_runes.resize(10);
|
||||
for (uint i = 0; i < quick_slot_runes.length(); i++) {
|
||||
quick_slot_runes[i] = RUNE_NONE;
|
||||
}
|
||||
|
||||
item_count_slots.resize(10);
|
||||
for (uint i = 0; i < item_count_slots.length(); i++) {
|
||||
item_count_slots[i] = -1;
|
||||
@@ -272,6 +278,10 @@ void activate_quick_slot(int slot_index) {
|
||||
}
|
||||
|
||||
int equip_type = quick_slots[slot_index];
|
||||
int rune_type = RUNE_NONE;
|
||||
if (slot_index >= 0 && slot_index < int(quick_slot_runes.length())) {
|
||||
rune_type = quick_slot_runes[slot_index];
|
||||
}
|
||||
if (equip_type < 0) {
|
||||
int item_type = -1;
|
||||
if (slot_index >= 0 && slot_index < int(item_count_slots.length())) {
|
||||
@@ -287,6 +297,16 @@ void activate_quick_slot(int slot_index) {
|
||||
}
|
||||
|
||||
if (equipment_is_equipped(equip_type)) {
|
||||
if (get_equipped_rune_for_slot(equip_type) != rune_type) {
|
||||
if (rune_type != RUNE_NONE && get_runed_item_count(equip_type, rune_type) <= 0) {
|
||||
speak_with_history("Item not available.", true);
|
||||
return;
|
||||
}
|
||||
set_equipped_rune_for_slot(equip_type, rune_type);
|
||||
update_max_health_from_equipment();
|
||||
speak_with_history(get_equipment_name(equip_type) + " equipped.", true);
|
||||
return;
|
||||
}
|
||||
unequip_equipment_type(equip_type);
|
||||
clear_equipped_rune_for_slot(equip_type);
|
||||
update_max_health_from_equipment();
|
||||
@@ -294,12 +314,18 @@ void activate_quick_slot(int slot_index) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!equipment_available(equip_type)) {
|
||||
if (rune_type != RUNE_NONE) {
|
||||
if (get_runed_item_count(equip_type, rune_type) <= 0) {
|
||||
speak_with_history("Item not available.", true);
|
||||
return;
|
||||
}
|
||||
} else if (!equipment_available(equip_type)) {
|
||||
speak_with_history("Item not available.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
equip_equipment_type(equip_type);
|
||||
set_equipped_rune_for_slot(equip_type, rune_type);
|
||||
update_max_health_from_equipment();
|
||||
speak_with_history(get_equipment_name(equip_type) + " equipped.", true);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,10 @@ void show_character_info() {
|
||||
else missing_slots.insert_last("feet");
|
||||
|
||||
string info = "Character info. ";
|
||||
if (player_name != "") {
|
||||
string sex_label = (player_sex == SEX_FEMALE) ? "Female" : "Male";
|
||||
info += "Name " + player_name + ". Sex " + sex_label + ". ";
|
||||
}
|
||||
info += "Health " + player_health + " of " + max_health + ". ";
|
||||
info += "Weapon " + get_equipped_weapon_name() + ". ";
|
||||
if (equipped_clothing.length() > 0) {
|
||||
|
||||
@@ -192,6 +192,9 @@ void run_equipment_menu() {
|
||||
int equip_type = equipment_types[filtered_indices[selection]];
|
||||
int rune_type = rune_types[filtered_indices[selection]];
|
||||
quick_slots[slot_index] = equip_type;
|
||||
if (slot_index >= 0 && slot_index < int(quick_slot_runes.length())) {
|
||||
quick_slot_runes[slot_index] = rune_type;
|
||||
}
|
||||
string name = get_full_equipment_name(equip_type, rune_type);
|
||||
speak_with_history(name + " set to slot " + slot_index + ".", true);
|
||||
}
|
||||
|
||||
+3
-1
@@ -2,6 +2,8 @@
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int facing = 1; // 0: left, 1: right
|
||||
string player_name = "";
|
||||
int player_sex = SEX_MALE;
|
||||
bool jumping = false;
|
||||
bool climbing = false;
|
||||
bool falling = false;
|
||||
@@ -85,7 +87,6 @@ timer jumptimer;
|
||||
timer search_timer;
|
||||
timer search_delay_timer;
|
||||
timer attack_timer;
|
||||
|
||||
// Search state
|
||||
bool searching = false;
|
||||
|
||||
@@ -104,6 +105,7 @@ void restart_all_timers() {
|
||||
sling_charge_timer.restart();
|
||||
bow_draw_timer.restart();
|
||||
bow_shot_timer.restart();
|
||||
fylgjaChargeTimer.restart();
|
||||
|
||||
// Fire fuel timers
|
||||
for (uint i = 0; i < world_fires.length(); i++) {
|
||||
|
||||
+417
-7
@@ -1,12 +1,35 @@
|
||||
// Save system
|
||||
|
||||
const string SAVE_FILE_PATH = "save.dat";
|
||||
const string SAVE_EXTENSION = ".dat";
|
||||
const string SAVE_ENCRYPTION_KEY = "draugnorak_save_v1";
|
||||
const int SAVE_VERSION = 2;
|
||||
const int SAVE_VERSION = 3;
|
||||
string last_save_error = "";
|
||||
string current_save_file = "";
|
||||
|
||||
string[] get_save_files() {
|
||||
string[] result;
|
||||
string[]@ items = glob("*" + SAVE_EXTENSION);
|
||||
if (@items == null) return result;
|
||||
|
||||
for (uint i = 0; i < items.length(); i++) {
|
||||
string item = items[i];
|
||||
if (item.length() >= SAVE_EXTENSION.length() &&
|
||||
item.substr(item.length() - SAVE_EXTENSION.length()) == SAVE_EXTENSION) {
|
||||
result.insert_last(item);
|
||||
}
|
||||
}
|
||||
if (result.length() > 1) {
|
||||
result.sort(sort_string_case_insensitive);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool sort_string_case_insensitive(const string &in a, const string &in b) {
|
||||
return a.lower() < b.lower();
|
||||
}
|
||||
|
||||
bool has_save_game() {
|
||||
return file_exists(SAVE_FILE_PATH);
|
||||
return get_save_files().length() > 0;
|
||||
}
|
||||
|
||||
string encrypt_save_data(const string&in rawData) {
|
||||
@@ -93,6 +116,345 @@ string[] get_string_list(dictionary@ data, const string&in key) {
|
||||
return result;
|
||||
}
|
||||
|
||||
string flatten_exception_text(const string&in text) {
|
||||
string result = "";
|
||||
bool lastWasSpace = false;
|
||||
for (uint i = 0; i < text.length(); i++) {
|
||||
string ch = text.substr(i, 1);
|
||||
if (ch == "\r" || ch == "\n") {
|
||||
if (!lastWasSpace) {
|
||||
result += " | ";
|
||||
lastWasSpace = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
result += ch;
|
||||
lastWasSpace = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
string format_log_timestamp() {
|
||||
datetime dt;
|
||||
string stamp = dt.format(DATE_TIME_FORMAT_RFC1123);
|
||||
return "[" + stamp + "]";
|
||||
}
|
||||
|
||||
void log_unhandled_exception(const string&in context) {
|
||||
string info = get_exception_info();
|
||||
string filePath = get_exception_file();
|
||||
int line = get_exception_line();
|
||||
string func = get_exception_function();
|
||||
string stack = flatten_exception_text(last_exception_call_stack);
|
||||
|
||||
string message = "Unhandled exception";
|
||||
if (context != "") message += " (" + context + ")";
|
||||
if (info != "") message += ": " + info;
|
||||
if (filePath != "") message += " at " + filePath;
|
||||
if (line > 0) message += ":" + line;
|
||||
if (func != "") message += " in " + func;
|
||||
if (stack != "") message += " | stack: " + stack;
|
||||
message += " " + format_log_timestamp();
|
||||
|
||||
file logFile;
|
||||
if (logFile.open("crash.log", "ab")) {
|
||||
logFile.write(message + "\r\n");
|
||||
logFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
string normalize_player_name(string name) {
|
||||
string result = "";
|
||||
bool lastWasSpace = true;
|
||||
for (uint i = 0; i < name.length(); i++) {
|
||||
string ch = name.substr(i, 1);
|
||||
bool isSpace = (ch == " " || ch == "\t" || ch == "\r" || ch == "\n");
|
||||
if (isSpace) {
|
||||
if (!lastWasSpace) {
|
||||
result += " ";
|
||||
lastWasSpace = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
result += ch;
|
||||
lastWasSpace = false;
|
||||
}
|
||||
if (result.length() > 0 && result.substr(result.length() - 1) == " ") {
|
||||
result = result.substr(0, result.length() - 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool is_windows_reserved_name(const string&in upperName) {
|
||||
if (upperName == "CON" || upperName == "PRN" || upperName == "AUX" || upperName == "NUL") return true;
|
||||
if (upperName.length() == 4 && upperName.substr(0, 3) == "COM") {
|
||||
int num = parse_int(upperName.substr(3));
|
||||
if (num >= 1 && num <= 9) return true;
|
||||
}
|
||||
if (upperName.length() == 4 && upperName.substr(0, 3) == "LPT") {
|
||||
int num = parse_int(upperName.substr(3));
|
||||
if (num >= 1 && num <= 9) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
string sanitize_save_filename_base(string name) {
|
||||
string normalized = normalize_player_name(name);
|
||||
string result = "";
|
||||
bool lastSeparator = false;
|
||||
|
||||
for (uint i = 0; i < normalized.length(); i++) {
|
||||
string ch = normalized.substr(i, 1);
|
||||
bool isUpper = (ch >= "A" && ch <= "Z");
|
||||
bool isLower = (ch >= "a" && ch <= "z");
|
||||
bool isDigit = (ch >= "0" && ch <= "9");
|
||||
if (isUpper || isLower || isDigit) {
|
||||
result += ch;
|
||||
lastSeparator = false;
|
||||
continue;
|
||||
}
|
||||
if (ch == " " || ch == "_" || ch == "-") {
|
||||
if (!lastSeparator && result.length() > 0) {
|
||||
result += "_";
|
||||
lastSeparator = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (result.length() > 0 && result.substr(result.length() - 1) == "_") {
|
||||
result = result.substr(0, result.length() - 1);
|
||||
}
|
||||
if (result.length() == 0) {
|
||||
result = "character";
|
||||
}
|
||||
if (result.length() > 40) {
|
||||
result = result.substr(0, 40);
|
||||
}
|
||||
while (result.length() > 0 && result.substr(result.length() - 1) == "_") {
|
||||
result = result.substr(0, result.length() - 1);
|
||||
}
|
||||
string upperName = result.upper();
|
||||
if (upperName == "." || upperName == "..") {
|
||||
result = "character";
|
||||
upperName = result.upper();
|
||||
}
|
||||
if (is_windows_reserved_name(upperName)) {
|
||||
result = "save_" + result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
string get_save_filename_for_name(const string&in name) {
|
||||
return sanitize_save_filename_base(name) + SAVE_EXTENSION;
|
||||
}
|
||||
|
||||
string strip_save_extension(const string&in filename) {
|
||||
if (filename.length() >= SAVE_EXTENSION.length() &&
|
||||
filename.substr(filename.length() - SAVE_EXTENSION.length()) == SAVE_EXTENSION) {
|
||||
return filename.substr(0, filename.length() - SAVE_EXTENSION.length());
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
|
||||
bool read_save_metadata(const string&in filename, string &out displayName, int &out sex, int &out day) {
|
||||
string encryptedData;
|
||||
if (!read_file_string(filename, encryptedData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string rawData = decrypt_save_data(encryptedData);
|
||||
dictionary@ saveData = deserialize(rawData);
|
||||
if (@saveData == null || !dictionary_has_keys(saveData)) {
|
||||
saveData = deserialize(encryptedData);
|
||||
}
|
||||
if (@saveData == null || !dictionary_has_keys(saveData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
displayName = "";
|
||||
if (!saveData.get("player_name", displayName)) {
|
||||
displayName = "";
|
||||
}
|
||||
sex = int(get_number(saveData, "player_sex", SEX_MALE));
|
||||
if (sex != SEX_FEMALE) sex = SEX_MALE;
|
||||
day = int(get_number(saveData, "time_current_day", 1));
|
||||
if (day < 1) day = 1;
|
||||
if (displayName == "") {
|
||||
displayName = strip_save_extension(filename);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_name_used(const string&in name, const string[]@ usedNames) {
|
||||
string target = name.lower();
|
||||
if (@usedNames != null) {
|
||||
for (uint i = 0; i < usedNames.length(); i++) {
|
||||
if (usedNames[i].lower() == target) return true;
|
||||
}
|
||||
}
|
||||
if (file_exists(get_save_filename_for_name(name))) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
string pick_random_name(const string[]@ pool, const string[]@ usedNames) {
|
||||
string[] available;
|
||||
if (@pool != null) {
|
||||
for (uint i = 0; i < pool.length(); i++) {
|
||||
if (!is_name_used(pool[i], usedNames)) {
|
||||
available.insert_last(pool[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
const string[]@ pickFrom = @available;
|
||||
if (available.length() == 0) {
|
||||
@pickFrom = pool;
|
||||
}
|
||||
if (@pickFrom == null || pickFrom.length() == 0) return "character";
|
||||
int index = random(0, int(pickFrom.length()) - 1);
|
||||
return pickFrom[index];
|
||||
}
|
||||
|
||||
string[] get_existing_character_names() {
|
||||
string[] names;
|
||||
string[] files = get_save_files();
|
||||
for (uint i = 0; i < files.length(); i++) {
|
||||
string displayName;
|
||||
int sex = SEX_MALE;
|
||||
int day = 1;
|
||||
if (read_save_metadata(files[i], displayName, sex, day)) {
|
||||
if (displayName.length() > 0) {
|
||||
names.insert_last(displayName);
|
||||
}
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
string[] male_name_pool = {
|
||||
"Arne", "Asbjorn", "Aegir", "Bjorn", "Brand", "Egil", "Einar", "Eirik", "Erik", "Gunnar",
|
||||
"Gudmund", "Hakon", "Halfdan", "Hallvard", "Harald", "Hjalmar", "Hrafn", "Hrolf", "Ivar", "Ketil",
|
||||
"Knut", "Leif", "Magnus", "Njord", "Odd", "Olaf", "Orm", "Ragnar", "Roald", "Rolf",
|
||||
"Sigurd", "Sten", "Stig", "Sven", "Svend", "Thor", "Toke", "Torbjorn", "Torstein", "Trygve",
|
||||
"Ulf", "Ulrik", "Valdemar", "Vidar", "Yngvar", "Haldor", "Skjold", "Eystein", "Gorm", "Havard"
|
||||
};
|
||||
|
||||
string[] female_name_pool = {
|
||||
"Astrid", "Asta", "Birgit", "Brynhild", "Dagny", "Eira", "Freya", "Frida", "Gerda", "Gudrun",
|
||||
"Gunhild", "Halla", "Helga", "Hild", "Hilda", "Inga", "Ingrid", "Kari", "Lagertha", "Liv",
|
||||
"Ragna", "Ragnhild", "Randi", "Runa", "Sif", "Signy", "Sigrid", "Solveig", "Sunniva", "Thora",
|
||||
"Thyra", "Tora", "Tove", "Tyrna", "Ulla", "Yrsa", "Ylva", "Aud", "Eydis", "Herdis",
|
||||
"Ingunn", "Jorunn", "Ragnheid", "Sigrun", "Torhild", "Ase", "Alfhild", "Gudlaug", "Katra", "Rikissa"
|
||||
};
|
||||
|
||||
string pick_random_name_for_sex(int sex, const string[]@ usedNames) {
|
||||
if (sex == SEX_FEMALE) {
|
||||
return pick_random_name(female_name_pool, usedNames);
|
||||
}
|
||||
return pick_random_name(male_name_pool, usedNames);
|
||||
}
|
||||
|
||||
bool select_player_sex(int &out sex) {
|
||||
string[] options = {"Male", "Female"};
|
||||
int selection = 0;
|
||||
string prompt = "Choose your character's sex.";
|
||||
speak_with_history(prompt + " " + options[selection], true);
|
||||
|
||||
while (true) {
|
||||
wait(5);
|
||||
if (key_pressed(KEY_DOWN)) {
|
||||
selection++;
|
||||
if (selection >= options.length()) selection = 0;
|
||||
speak_with_history(prompt + " " + options[selection], true);
|
||||
}
|
||||
if (key_pressed(KEY_UP)) {
|
||||
selection--;
|
||||
if (selection < 0) selection = options.length() - 1;
|
||||
speak_with_history(prompt + " " + options[selection], true);
|
||||
}
|
||||
if (key_pressed(KEY_RETURN)) {
|
||||
sex = (selection == 1) ? SEX_FEMALE : SEX_MALE;
|
||||
return true;
|
||||
}
|
||||
if (key_pressed(KEY_ESCAPE)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool setup_new_character() {
|
||||
int selectedSex = SEX_MALE;
|
||||
if (!select_player_sex(selectedSex)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string[] existingNames = get_existing_character_names();
|
||||
while (true) {
|
||||
string entered = ui_input_box("Draugnorak", "Enter your name or press Enter for random.", "");
|
||||
string normalized = normalize_player_name(entered);
|
||||
if (normalized.length() == 0) {
|
||||
normalized = pick_random_name_for_sex(selectedSex, existingNames);
|
||||
}
|
||||
string saveFile = get_save_filename_for_name(normalized);
|
||||
if (file_exists(saveFile)) {
|
||||
int confirm = ui_question("", "Save found for " + normalized + ". Overwrite?");
|
||||
if (confirm != 1) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
player_name = normalized;
|
||||
player_sex = selectedSex;
|
||||
current_save_file = saveFile;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool select_save_file(string &out filename) {
|
||||
string[] files = get_save_files();
|
||||
if (files.length() == 0) return false;
|
||||
|
||||
string[] options;
|
||||
for (uint i = 0; i < files.length(); i++) {
|
||||
string displayName;
|
||||
int sex = SEX_MALE;
|
||||
int day = 1;
|
||||
if (!read_save_metadata(files[i], displayName, sex, day)) {
|
||||
displayName = strip_save_extension(files[i]);
|
||||
options.insert_last(displayName);
|
||||
} else {
|
||||
string sex_label = (sex == SEX_FEMALE) ? "female" : "male";
|
||||
options.insert_last(displayName + ", " + sex_label + ", day " + day);
|
||||
}
|
||||
}
|
||||
|
||||
int selection = 0;
|
||||
speak_with_history("Load game. Select character.", true);
|
||||
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 = int(options.length()) - 1;
|
||||
speak_with_history(options[selection], true);
|
||||
}
|
||||
if (key_pressed(KEY_RETURN)) {
|
||||
filename = files[selection];
|
||||
return true;
|
||||
}
|
||||
if (key_pressed(KEY_ESCAPE)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void stop_active_sounds() {
|
||||
if (day_sound_handle != -1) {
|
||||
p.destroy_sound(day_sound_handle);
|
||||
@@ -217,6 +579,7 @@ void reset_game_state() {
|
||||
incense_burning = false;
|
||||
blessing_speed_active = false;
|
||||
blessing_resident_active = false;
|
||||
reset_fylgja_state();
|
||||
|
||||
// Reset inventory using the registry system
|
||||
reset_inventory();
|
||||
@@ -283,6 +646,12 @@ void start_new_game() {
|
||||
init_barricade();
|
||||
init_time();
|
||||
init_weather();
|
||||
if (player_name.length() == 0) {
|
||||
player_name = "Character";
|
||||
}
|
||||
if (current_save_file == "") {
|
||||
current_save_file = get_save_filename_for_name(player_name);
|
||||
}
|
||||
save_game_state();
|
||||
}
|
||||
|
||||
@@ -398,8 +767,11 @@ bool save_game_state() {
|
||||
saveData.set("player_health", player_health);
|
||||
saveData.set("player_base_health", base_max_health);
|
||||
saveData.set("player_max_health", max_health);
|
||||
saveData.set("player_name", player_name);
|
||||
saveData.set("player_sex", player_sex);
|
||||
saveData.set("player_favor", favor);
|
||||
saveData.set("player_last_adventure_day", last_adventure_day);
|
||||
saveData.set("adventure_completion_counts", serialize_inventory_array(adventureCompletionCounts));
|
||||
saveData.set("incense_hours_remaining", incense_hours_remaining);
|
||||
saveData.set("incense_burning", incense_burning);
|
||||
|
||||
@@ -435,6 +807,11 @@ bool save_game_state() {
|
||||
quickSlotData.insert_last("" + quick_slots[i]);
|
||||
}
|
||||
saveData.set("equipment_quick_slots", join_string_array(quickSlotData));
|
||||
string[] quickSlotRuneData;
|
||||
for (uint i = 0; i < quick_slot_runes.length(); i++) {
|
||||
quickSlotRuneData.insert_last("" + quick_slot_runes[i]);
|
||||
}
|
||||
saveData.set("equipment_quick_slot_runes", join_string_array(quickSlotRuneData));
|
||||
|
||||
string[] itemCountSlotData;
|
||||
for (uint i = 0; i < item_count_slots.length(); i++) {
|
||||
@@ -585,20 +962,23 @@ bool save_game_state() {
|
||||
}
|
||||
saveData.set("drops_data", join_string_array(dropData));
|
||||
|
||||
if (current_save_file == "") {
|
||||
current_save_file = get_save_filename_for_name(player_name);
|
||||
}
|
||||
string rawData = saveData.serialize();
|
||||
string encryptedData = encrypt_save_data(rawData);
|
||||
return save_data(SAVE_FILE_PATH, encryptedData);
|
||||
return save_data(current_save_file, encryptedData);
|
||||
}
|
||||
|
||||
bool load_game_state() {
|
||||
bool load_game_state_from_file(const string&in filename) {
|
||||
last_save_error = "";
|
||||
if (!file_exists(SAVE_FILE_PATH)) {
|
||||
if (!file_exists(filename)) {
|
||||
last_save_error = "No save file found.";
|
||||
return false;
|
||||
}
|
||||
|
||||
string encryptedData;
|
||||
if (!read_file_string(SAVE_FILE_PATH, encryptedData)) {
|
||||
if (!read_file_string(filename, encryptedData)) {
|
||||
last_save_error = "Unable to read save file.";
|
||||
return false;
|
||||
}
|
||||
@@ -623,6 +1003,7 @@ bool load_game_state() {
|
||||
}
|
||||
|
||||
reset_game_state();
|
||||
current_save_file = filename;
|
||||
|
||||
MAP_SIZE = int(get_number(saveData, "world_map_size", 35));
|
||||
expanded_area_start = int(get_number(saveData, "world_expanded_area_start", -1));
|
||||
@@ -651,8 +1032,20 @@ bool load_game_state() {
|
||||
player_health = int(get_number(saveData, "player_health", 10));
|
||||
max_health = int(get_number(saveData, "player_max_health", 10));
|
||||
base_max_health = int(get_number(saveData, "player_base_health", max_health));
|
||||
string loadedName;
|
||||
if (saveData.get("player_name", loadedName)) {
|
||||
player_name = loadedName;
|
||||
} else {
|
||||
player_name = strip_save_extension(filename);
|
||||
}
|
||||
player_sex = int(get_number(saveData, "player_sex", SEX_MALE));
|
||||
if (player_sex != SEX_FEMALE) player_sex = SEX_MALE;
|
||||
favor = get_number(saveData, "player_favor", 0.0);
|
||||
last_adventure_day = int(get_number(saveData, "player_last_adventure_day", -1));
|
||||
string adventureCountsStr;
|
||||
if (saveData.get("adventure_completion_counts", adventureCountsStr)) {
|
||||
deserialize_inventory_array(adventureCountsStr, adventureCompletionCounts, int(adventureIds.length()));
|
||||
}
|
||||
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;
|
||||
@@ -757,6 +1150,15 @@ bool load_game_state() {
|
||||
quick_slots[i] = slot_value;
|
||||
}
|
||||
}
|
||||
string[] loadedQuickSlotRunes = get_string_list_or_split(saveData, "equipment_quick_slot_runes");
|
||||
uint rune_slot_count = loadedQuickSlotRunes.length();
|
||||
if (rune_slot_count > quick_slot_runes.length()) rune_slot_count = quick_slot_runes.length();
|
||||
for (uint i = 0; i < rune_slot_count; i++) {
|
||||
int slot_value = parse_int(loadedQuickSlotRunes[i]);
|
||||
if (slot_value >= RUNE_NONE) {
|
||||
quick_slot_runes[i] = slot_value;
|
||||
}
|
||||
}
|
||||
|
||||
string[] loadedItemCountSlots = get_string_list_or_split(saveData, "item_count_slots");
|
||||
uint count_slot_count = loadedItemCountSlots.length();
|
||||
@@ -1085,3 +1487,11 @@ bool load_game_state() {
|
||||
update_ambience(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool load_game_state() {
|
||||
if (current_save_file == "") {
|
||||
last_save_error = "No save selected.";
|
||||
return false;
|
||||
}
|
||||
return load_game_state_from_file(current_save_file);
|
||||
}
|
||||
|
||||
+15
-8
@@ -454,11 +454,21 @@ void update_time() {
|
||||
}
|
||||
}
|
||||
|
||||
if (is_daytime && residents_count > 0 && barricade_health < BARRICADE_MAX_HEALTH && current_hour % 4 == 0) {
|
||||
if (has_any_storage_food()) {
|
||||
int gained = add_barricade_health(residents_count * get_resident_effect_multiplier());
|
||||
if (gained > 0 && x <= BASE_END) {
|
||||
speak_with_history("Residents repaired the barricade. +" + gained + " health.", true);
|
||||
if (is_daytime && residents_count > 0 && barricade_health < BARRICADE_MAX_HEALTH) {
|
||||
const int day_start_hour = 6;
|
||||
const int day_end_hour = 18; // Exclusive for repair scheduling (12-hour window)
|
||||
if (current_hour >= day_start_hour && current_hour < day_end_hour) {
|
||||
int repair_window_hours = day_end_hour - day_start_hour;
|
||||
int interval = repair_window_hours / residents_count;
|
||||
if (interval < 1) interval = 1;
|
||||
int day_hour = current_hour - day_start_hour;
|
||||
if (day_hour % interval == 0) {
|
||||
if (has_any_storage_food()) {
|
||||
int gained = add_barricade_health(residents_count * get_resident_effect_multiplier());
|
||||
if (gained > 0 && x <= BASE_END) {
|
||||
speak_with_history("Residents repaired the barricade. +" + gained + " health.", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -469,9 +479,6 @@ void update_time() {
|
||||
attempt_resident_butchering();
|
||||
attempt_resident_foraging();
|
||||
}
|
||||
if (current_hour == 12) {
|
||||
attempt_resident_foraging();
|
||||
}
|
||||
attempt_daily_invasion();
|
||||
keep_base_fires_fed();
|
||||
update_incense_burning();
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ string get_terrain_at_position(int pos_x) {
|
||||
}
|
||||
|
||||
string ui_input_box(const string title, const string prompt, const string default_value) {
|
||||
string result = virtual_input_box(title, prompt, default_value);
|
||||
string result = virtual_input_box(prompt, prompt, default_value);
|
||||
show_window("Draugnorak");
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user