Oh wow, I thought I already pushed some of this stuff. Let's see if I can remember it all. Undead residents added. Whights added, Vampyrs added. Bandit Hideout adventure added.

This commit is contained in:
Storm Dragon
2026-02-04 00:52:16 -05:00
parent e1928a1039
commit 78a6156656
18 changed files with 1029 additions and 27 deletions

View File

@@ -11,7 +11,7 @@ chmod +x draugnorak
## Quick Start ## Quick Start
- Move with Left/Right, jump with Up. - Move with Left/Right, jump with Up.
- Hold Shift for 1 second to search the current area. - Hold Shift, /, or Z for 1 second to search the current area.
- Craft basic tools in the base with C. - Craft basic tools in the base with C.
- Set snares and build fires to survive. - Set snares and build fires to survive.
@@ -20,7 +20,7 @@ Some of the first things you will want are a stone knife, spear, stone axe, and
- **Left/Right**: Move. - **Left/Right**: Move.
- **Up**: Jump, climb trees, start rope climbs when prompted. - **Up**: Jump, climb trees, start rope climbs when prompted.
- **Down**: Climb down trees or descend rope climbs when prompted. - **Down**: Climb down trees or descend rope climbs when prompted.
- **Shift (hold)**: Search area (1-second hold, 1-second delay). - **Shift, /, or Z (hold)**: Search area (1-second hold, 1-second delay).
- **Control (hold/release)**: Attack with equipped weapon. Sling uses a charge window. - **Control (hold/release)**: Attack with equipped weapon. Sling uses a charge window.
- **A**: Action menu (place snare, feed fire, burn incense). - **A**: Action menu (place snare, feed fire, burn incense).
- **C**: Crafting menu (base only). - **C**: Crafting menu (base only).

View File

@@ -394,12 +394,13 @@ void run_game()
// Searching Logic // Searching Logic
bool shift_down = (key_down(KEY_LSHIFT) || key_down(KEY_RSHIFT)); bool shift_down = (key_down(KEY_LSHIFT) || key_down(KEY_RSHIFT));
if (!shift_down && !searching) { bool search_key_down = shift_down || key_down(KEY_SLASH) || key_down(KEY_Z);
if (!search_key_down && !searching) {
search_timer.restart(); search_timer.restart();
} }
// Apply rune gathering bonus to search time // Apply rune gathering bonus to search time
int search_time = apply_rune_gather_bonus(2000); int search_time = apply_rune_gather_bonus(2000);
if (shift_down && search_timer.elapsed > search_time && !searching) if (search_key_down && search_timer.elapsed > search_time && !searching)
{ {
searching = true; searching = true;
search_delay_timer.restart(); search_delay_timer.restart();

Binary file not shown.

Binary file not shown.

BIN
sounds/enemies/vampyr1.ogg LFS Normal file

Binary file not shown.

BIN
sounds/enemies/vampyr2.ogg LFS Normal file

Binary file not shown.

BIN
sounds/enemies/vampyr3.ogg LFS Normal file

Binary file not shown.

BIN
sounds/enemies/vampyr4.ogg LFS Normal file

Binary file not shown.

BIN
sounds/enemies/vampyr_dies.ogg LFS Normal file

Binary file not shown.

BIN
sounds/enemies/wight1.ogg LFS Normal file

Binary file not shown.

BIN
sounds/enemies/wight_dies.ogg LFS Normal file

Binary file not shown.

View File

@@ -3,6 +3,7 @@
#include "src/bosses/adventure_combat.nvgt" #include "src/bosses/adventure_combat.nvgt"
#include "src/bosses/unicorn/unicorn_boss.nvgt" #include "src/bosses/unicorn/unicorn_boss.nvgt"
#include "src/bosses/bandit_hideout.nvgt"
void check_adventure_menu(int player_x) { void check_adventure_menu(int player_x) {
if (key_pressed(KEY_TAB)) { if (key_pressed(KEY_TAB)) {
@@ -26,7 +27,7 @@ void run_adventure_menu(int player_x) {
// Check available adventures based on terrain // Check available adventures based on terrain
string[] options; string[] options;
int[] adventure_ids; // 1 = Unicorn int[] adventure_ids; // 1 = Unicorn, 2 = Bandit's Hideout
if (mountain !is null) { if (mountain !is null) {
// Mountain terrain // Mountain terrain
@@ -34,6 +35,11 @@ void run_adventure_menu(int player_x) {
adventure_ids.insert_last(1); adventure_ids.insert_last(1);
} }
if (mountain is null && (terrain == "forest" || terrain == "deep_forest")) {
options.insert_last("Bandit's Hideout");
adventure_ids.insert_last(ADVENTURE_BANDIT_HIDEOUT);
}
if (options.length() == 0) { if (options.length() == 0) {
speak_with_history("No adventures found in this area.", true); speak_with_history("No adventures found in this area.", true);
return; return;
@@ -78,5 +84,7 @@ void start_adventure(int adventure_id) {
last_adventure_day = current_day; last_adventure_day = current_day;
if (adventure_id == 1) { if (adventure_id == 1) {
run_unicorn_adventure(); run_unicorn_adventure();
} else if (adventure_id == ADVENTURE_BANDIT_HIDEOUT) {
run_bandit_hideout_adventure();
} }
} }

View File

@@ -0,0 +1,669 @@
// Bandit's Hideout Adventure logic
// Terrain: Forest / Deep Forest
// Objective: Break the barricade at the enemy base.
const int ADVENTURE_BANDIT_HIDEOUT = 2;
const int BANDIT_HIDEOUT_MAP_SIZE = 100;
const int BANDIT_HIDEOUT_SEGMENT_MIN = 5;
const int BANDIT_HIDEOUT_SEGMENT_MAX = 10;
const int BANDIT_HIDEOUT_BANDIT_COUNT = 5;
const int BANDIT_HIDEOUT_BASE_SPAWN_RANGE = 10;
const int BANDIT_HIDEOUT_START_SPAWN_RANGE = 10;
const int BANDIT_HIDEOUT_BARRICADE_HP_PER_DAY = 34;
const int BANDIT_HIDEOUT_BARRICADE_HP_MAX = 500;
const double BANDIT_HIDEOUT_FAVOR_PER_KILL = 0.2;
const double BANDIT_HIDEOUT_BASE_FAVOR = 3.0;
const double BANDIT_HIDEOUT_FAVOR_MAX = 10.0;
class HideoutBandit {
int position;
int health;
string alertSound;
string weaponType; // "spear" or "axe"
int soundHandle;
bool inWeaponRange;
timer moveTimer;
timer attackTimer;
int moveInterval;
HideoutBandit(int pos, const string&in alert, const string&in weapon, int interval) {
position = pos;
health = BANDIT_HEALTH;
alertSound = alert;
weaponType = weapon;
moveInterval = interval;
soundHandle = -1;
inWeaponRange = false;
moveTimer.restart();
attackTimer.restart();
}
}
HideoutBandit@[] hideoutBandits;
string[] hideoutTerrain;
int hideoutPlayerX = 0;
int hideoutPlayerFacing = 1; // 0 = west, 1 = east
int hideoutBaseX = BANDIT_HIDEOUT_MAP_SIZE - 1;
int hideoutBarricadeHealth = 0;
int hideoutBarricadeMax = 0;
int hideoutBanditsKilled = 0;
timer hideoutWalkTimer;
timer hideoutAttackTimer;
timer hideoutSearchTimer;
timer hideoutSearchDelayTimer;
bool hideoutSearching = false;
string pick_hideout_terrain() {
int roll = random(0, 2);
if (roll == 0) return "grass";
if (roll == 1) return "gravel";
return "stone";
}
void build_hideout_terrain() {
hideoutTerrain.resize(BANDIT_HIDEOUT_MAP_SIZE);
int index = 0;
while (index < BANDIT_HIDEOUT_MAP_SIZE) {
int segmentLength = random(BANDIT_HIDEOUT_SEGMENT_MIN, BANDIT_HIDEOUT_SEGMENT_MAX);
string terrain = pick_hideout_terrain();
for (int i = 0; i < segmentLength && index < BANDIT_HIDEOUT_MAP_SIZE; i++) {
hideoutTerrain[index] = terrain;
index++;
}
}
if (hideoutBaseX >= 0 && hideoutBaseX < int(hideoutTerrain.length())) {
hideoutTerrain[hideoutBaseX] = "stone";
}
}
string get_hideout_terrain_at(int pos) {
if (pos < 0 || pos >= int(hideoutTerrain.length())) return "grass";
string terrain = hideoutTerrain[pos];
if (terrain == "") return "grass";
return terrain;
}
string get_hideout_footstep_sound(int pos) {
string terrain = get_hideout_terrain_at(pos);
if (terrain == "stone") return "sounds/terrain/stone.ogg";
if (terrain == "gravel") return "sounds/terrain/gravel.ogg";
return "sounds/terrain/grass.ogg";
}
void play_hideout_player_footstep() {
string soundFile = get_hideout_footstep_sound(hideoutPlayerX);
if (file_exists(soundFile)) {
p.play_stationary(soundFile, false);
}
}
void play_hideout_positional_footstep(int listenerX, int stepX, int maxDistance, float volumeStep) {
if (abs(stepX - listenerX) > maxDistance) return;
string soundFile = get_hideout_footstep_sound(stepX);
if (file_exists(soundFile)) {
play_1d_with_volume_step(soundFile, listenerX, stepX, false, volumeStep);
}
}
void clear_hideout_bandits() {
for (uint i = 0; i < hideoutBandits.length(); i++) {
if (hideoutBandits[i].soundHandle != -1) {
p.destroy_sound(hideoutBandits[i].soundHandle);
hideoutBandits[i].soundHandle = -1;
}
hideoutBandits[i].inWeaponRange = false;
}
hideoutBandits.resize(0);
}
HideoutBandit@ get_hideout_bandit_at(int pos) {
for (uint i = 0; i < hideoutBandits.length(); i++) {
if (hideoutBandits[i].position == pos) {
return @hideoutBandits[i];
}
}
return null;
}
int clamp_hideout_spawn_start(int startX) {
if (startX < 0) return 0;
if (startX >= BANDIT_HIDEOUT_MAP_SIZE) return BANDIT_HIDEOUT_MAP_SIZE - 1;
return startX;
}
int clamp_hideout_spawn_end(int endX) {
if (endX < 0) return 0;
if (endX >= BANDIT_HIDEOUT_MAP_SIZE) return BANDIT_HIDEOUT_MAP_SIZE - 1;
return endX;
}
int pick_hideout_spawn_position(int startX, int endX) {
int startClamp = clamp_hideout_spawn_start(startX);
int endClamp = clamp_hideout_spawn_end(endX);
if (startClamp > endClamp) {
int swapTemp = startClamp;
startClamp = endClamp;
endClamp = swapTemp;
}
int spawnX = -1;
for (int attempt = 0; attempt < 20; attempt++) {
int candidate = random(startClamp, endClamp);
if (candidate == hideoutPlayerX) continue;
if (get_hideout_bandit_at(candidate) != null) continue;
spawnX = candidate;
break;
}
if (spawnX == -1) {
spawnX = random(startClamp, endClamp);
}
return spawnX;
}
void spawn_hideout_bandit_in_range(int startX, int endX) {
int spawnX = pick_hideout_spawn_position(startX, endX);
string alertSound = pick_invader_alert_sound("bandit");
if (alertSound == "") alertSound = "sounds/enemies/bandit1.ogg";
string weaponType = (random(0, 1) == 0) ? "spear" : "axe";
int moveInterval = random(BANDIT_MOVE_INTERVAL_MIN, BANDIT_MOVE_INTERVAL_MAX);
HideoutBandit@ bandit = HideoutBandit(spawnX, alertSound, weaponType, moveInterval);
hideoutBandits.insert_last(bandit);
bandit.soundHandle = play_1d_with_volume_step(bandit.alertSound, hideoutPlayerX, bandit.position, true, BANDIT_SOUND_VOLUME_STEP);
}
void spawn_hideout_bandits_initial() {
int baseSpawnStart = hideoutBaseX - (BANDIT_HIDEOUT_BASE_SPAWN_RANGE - 1);
int baseSpawnEnd = hideoutBaseX;
for (int i = 0; i < BANDIT_HIDEOUT_BANDIT_COUNT; i++) {
spawn_hideout_bandit_in_range(baseSpawnStart, baseSpawnEnd);
}
}
void respawn_hideout_bandit() {
int startSpawnStart = 0;
int startSpawnEnd = BANDIT_HIDEOUT_START_SPAWN_RANGE - 1;
if (startSpawnEnd > hideoutBaseX) startSpawnEnd = hideoutBaseX;
int baseSpawnStart = hideoutBaseX - (BANDIT_HIDEOUT_BASE_SPAWN_RANGE - 1);
int baseSpawnEnd = hideoutBaseX;
int roll = random(0, 1);
if (roll == 0) {
spawn_hideout_bandit_in_range(startSpawnStart, startSpawnEnd);
} else {
spawn_hideout_bandit_in_range(baseSpawnStart, baseSpawnEnd);
}
}
void init_bandit_hideout_adventure() {
reset_adventure_combat_state();
hideoutPlayerX = 0;
hideoutPlayerFacing = 1;
hideoutBaseX = BANDIT_HIDEOUT_MAP_SIZE - 1;
hideoutBanditsKilled = 0;
int barricadeBase = current_day * BANDIT_HIDEOUT_BARRICADE_HP_PER_DAY;
if (barricadeBase < BANDIT_HIDEOUT_BARRICADE_HP_PER_DAY) barricadeBase = BANDIT_HIDEOUT_BARRICADE_HP_PER_DAY;
if (barricadeBase > BANDIT_HIDEOUT_BARRICADE_HP_MAX) barricadeBase = BANDIT_HIDEOUT_BARRICADE_HP_MAX;
hideoutBarricadeMax = barricadeBase;
hideoutBarricadeHealth = barricadeBase;
build_hideout_terrain();
clear_hideout_bandits();
spawn_hideout_bandits_initial();
}
void cleanup_bandit_hideout_adventure() {
clear_hideout_bandits();
reset_adventure_combat_state();
p.destroy_all();
}
void run_bandit_hideout_adventure() {
// Stop main game sounds
p.destroy_all();
init_bandit_hideout_adventure();
string[] intro;
intro.insert_last("=== Bandit's Hideout ===");
intro.insert_last("");
intro.insert_last("You find a hidden bandit base deep in the forest.");
intro.insert_last("The base lies far to the east, guarded by a barricade.");
intro.insert_last("");
intro.insert_last("Objective:");
intro.insert_last(" - Reach the base and destroy the barricade");
intro.insert_last("");
intro.insert_last("Bandits will, of course, not take this lying down.");
text_reader_lines(intro, "Adventure", true);
while (true) {
wait(5);
if (key_pressed(KEY_ESCAPE)) {
cleanup_bandit_hideout_adventure();
speak_with_history("You flee the hideout.", true);
return;
}
// Standard game keys
check_quick_slot_keys();
check_notification_keys();
check_speech_history_keys();
if (key_pressed(KEY_H)) {
speak_with_history(player_health + " health of " + max_health, true);
}
if (key_pressed(KEY_X)) {
int distanceToBase = hideoutBaseX - hideoutPlayerX;
if (distanceToBase < 0) distanceToBase = 0;
string terrain = get_hideout_terrain_at(hideoutPlayerX);
speak_with_history("x " + hideoutPlayerX + ", terrain " + terrain + ". Base " + distanceToBase + " tiles east. Barricade " + hideoutBarricadeHealth + " of " + hideoutBarricadeMax + ".", true);
}
handle_hideout_player_movement();
handle_hideout_player_actions();
update_hideout_search();
update_hideout_bandits();
adventure_update_bow_shot(hideoutPlayerX);
if (hideoutBarricadeHealth <= 0) {
cleanup_bandit_hideout_adventure();
p.play_stationary("sounds/actions/break_snare.ogg", false);
give_bandit_hideout_rewards();
return;
}
if (player_health <= 0) {
cleanup_bandit_hideout_adventure();
speak_with_history("The bandits cut you down.", true);
return;
}
p.update_listener_1d(hideoutPlayerX);
}
}
void handle_hideout_player_movement() {
if (key_pressed(KEY_LEFT) && hideoutPlayerFacing != 0) {
hideoutPlayerFacing = 0;
speak_with_history("west", true);
hideoutWalkTimer.restart();
}
if (key_pressed(KEY_RIGHT) && hideoutPlayerFacing != 1) {
hideoutPlayerFacing = 1;
speak_with_history("east", true);
hideoutWalkTimer.restart();
}
if (hideoutWalkTimer.elapsed > walk_speed) {
if (key_down(KEY_LEFT) && hideoutPlayerX > 0) {
hideoutPlayerFacing = 0;
hideoutPlayerX--;
hideoutWalkTimer.restart();
if (player_health > 0) play_hideout_player_footstep();
} else if (key_down(KEY_RIGHT) && hideoutPlayerX < hideoutBaseX) {
hideoutPlayerFacing = 1;
hideoutPlayerX++;
hideoutWalkTimer.restart();
if (player_health > 0) play_hideout_player_footstep();
}
}
}
void handle_hideout_player_actions() {
bool ctrlDown = (key_down(KEY_LCTRL) || key_down(KEY_RCTRL));
// Bow draw detection
if (bow_equipped) {
if (ctrlDown && !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 && !ctrlDown) {
adventure_release_bow_attack(hideoutPlayerX, hideoutPlayerFacing, @bandit_hideout_ranged_attack);
bow_drawing = false;
}
}
if (!bow_equipped && bow_drawing) {
bow_drawing = false;
}
// Sling charge detection
if (!bow_equipped && sling_equipped && ctrlDown && !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.");
}
}
if (sling_charging && ctrlDown) {
update_sling_charge();
}
if (sling_charging && !ctrlDown) {
adventure_release_sling_attack(hideoutPlayerX, hideoutPlayerFacing, @bandit_hideout_ranged_attack);
sling_charging = false;
safe_destroy_sound(sling_sound_handle);
}
if (!bow_equipped && !bow_drawing && !sling_equipped && !sling_charging) {
if (fishing_pole_equipped) return;
int weaponType = get_hideout_melee_weapon_type();
if (weaponType == -1) return;
int attackCooldown = 1000;
if (weaponType == ADVENTURE_WEAPON_SPEAR) attackCooldown = 800;
if (weaponType == ADVENTURE_WEAPON_AXE) attackCooldown = 1600;
if (ctrlDown && hideoutAttackTimer.elapsed > attackCooldown) {
hideoutAttackTimer.restart();
play_hideout_melee_swing(weaponType);
if (hideout_melee_hit(weaponType)) {
play_hideout_melee_hit(weaponType);
}
}
}
}
void update_hideout_search() {
bool shiftDown = (key_down(KEY_LSHIFT) || key_down(KEY_RSHIFT));
if (shiftDown) {
if (key_pressed(KEY_COMMA) || key_pressed(KEY_PERIOD)) {
hideoutSearching = false;
hideoutSearchDelayTimer.restart();
}
}
bool searchKeyDown = shiftDown || key_down(KEY_SLASH) || key_down(KEY_Z);
if (!searchKeyDown && !hideoutSearching) {
hideoutSearchTimer.restart();
}
int searchTime = apply_rune_gather_bonus(2000);
if (searchKeyDown && hideoutSearchTimer.elapsed > searchTime && !hideoutSearching) {
hideoutSearching = true;
hideoutSearchDelayTimer.restart();
}
if (hideoutSearching && hideoutSearchDelayTimer.elapsed >= 1000) {
hideoutSearching = false;
hideoutSearchTimer.restart();
perform_hideout_search();
}
}
void perform_hideout_search() {
if (random(1, 100) <= 10) {
speak_with_history("Found nothing.", true);
return;
}
string terrain = get_hideout_terrain_at(hideoutPlayerX);
if (terrain == "stone" || terrain == "gravel") {
if (try_search_for_terrain(terrain)) {
return;
}
}
speak_with_history("Found nothing.", true);
}
int get_hideout_melee_weapon_type() {
if (spear_equipped) return ADVENTURE_WEAPON_SPEAR;
if (axe_equipped) return ADVENTURE_WEAPON_AXE;
return -1;
}
void play_hideout_melee_swing(int weaponType) {
if (weaponType == ADVENTURE_WEAPON_SPEAR) {
p.play_stationary("sounds/weapons/spear_swing.ogg", false);
} else if (weaponType == ADVENTURE_WEAPON_AXE) {
p.play_stationary("sounds/weapons/axe_swing.ogg", false);
}
}
void play_hideout_melee_hit(int weaponType) {
if (weaponType == ADVENTURE_WEAPON_SPEAR) {
p.play_stationary("sounds/weapons/spear_hit.ogg", false);
} else if (weaponType == ADVENTURE_WEAPON_AXE) {
p.play_stationary("sounds/weapons/axe_hit.ogg", false);
}
}
bool hideout_melee_hit(int weaponType) {
int range = (weaponType == ADVENTURE_WEAPON_SPEAR) ? 1 : 0;
int damage = (weaponType == ADVENTURE_WEAPON_SPEAR) ? SPEAR_DAMAGE : AXE_DAMAGE;
for (int offset = -range; offset <= range; offset++) {
int targetX = hideoutPlayerX + offset;
if (damage_hideout_bandit_at(targetX, damage)) {
play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", hideoutPlayerX, targetX, BANDIT_SOUND_VOLUME_STEP);
return true;
}
}
if (abs(hideoutPlayerX - hideoutBaseX) <= range) {
if (apply_hideout_barricade_damage(damage)) {
return true;
}
}
return false;
}
bool apply_hideout_barricade_damage(int damage) {
if (damage <= 0) return false;
if (hideoutBarricadeHealth <= 0) return false;
hideoutBarricadeHealth -= damage;
if (hideoutBarricadeHealth < 0) hideoutBarricadeHealth = 0;
play_1d_with_volume_step("sounds/weapons/axe_hit.ogg", hideoutPlayerX, hideoutBaseX, false, BANDIT_SOUND_VOLUME_STEP);
return true;
}
int find_hideout_ranged_target(int playerX, int direction, int range) {
for (int dist = 1; dist <= range; dist++) {
int checkX = playerX + (dist * direction);
if (checkX < 0 || checkX >= BANDIT_HIDEOUT_MAP_SIZE) break;
if (get_hideout_bandit_at(checkX) != null) {
return checkX;
}
if (hideoutBarricadeHealth > 0 && checkX == hideoutBaseX) {
return checkX;
}
}
return -1;
}
int bandit_hideout_ranged_attack(int playerX, int direction, int range, int weaponType, int damage) {
int targetX = find_hideout_ranged_target(playerX, direction, range);
if (targetX == -1) return -1;
if (targetX == hideoutBaseX) {
apply_hideout_barricade_damage(damage);
return targetX;
}
if (damage_hideout_bandit_at(targetX, damage)) {
return targetX;
}
return -1;
}
bool damage_hideout_bandit_at(int pos, int damage) {
for (uint i = 0; i < hideoutBandits.length(); i++) {
if (hideoutBandits[i].position == pos) {
hideoutBandits[i].health -= damage;
if (hideoutBandits[i].health <= 0) {
if (hideoutBandits[i].soundHandle != -1) {
p.destroy_sound(hideoutBandits[i].soundHandle);
hideoutBandits[i].soundHandle = -1;
}
if (hideoutBandits[i].inWeaponRange) {
play_weapon_range_sound("sounds/enemies/exit_range.ogg", hideoutBandits[i].position);
}
play_creature_death_sounds("sounds/enemies/enemy_falls.ogg", hideoutBandits[i].alertSound, hideoutPlayerX, pos, BANDIT_SOUND_VOLUME_STEP);
hideoutBandits.remove_at(i);
hideoutBanditsKilled++;
respawn_hideout_bandit();
}
return true;
}
}
return false;
}
bool try_hideout_bandit_attack_player(HideoutBandit@ bandit) {
if (player_health <= 0) return false;
if (abs(bandit.position - hideoutPlayerX) > 1) return false;
if (bandit.attackTimer.elapsed < BANDIT_ATTACK_INTERVAL) return false;
bandit.attackTimer.restart();
if (bandit.weaponType == "spear") {
play_creature_attack_sound("sounds/weapons/spear_swing.ogg", hideoutPlayerX, bandit.position, BANDIT_SOUND_VOLUME_STEP);
} else {
play_creature_attack_sound("sounds/weapons/axe_swing.ogg", hideoutPlayerX, bandit.position, BANDIT_SOUND_VOLUME_STEP);
}
int damage = random(BANDIT_DAMAGE_MIN, BANDIT_DAMAGE_MAX);
player_health -= damage;
if (player_health < 0) player_health = 0;
if (bandit.weaponType == "spear") {
play_creature_attack_sound("sounds/weapons/spear_hit.ogg", hideoutPlayerX, bandit.position, BANDIT_SOUND_VOLUME_STEP);
} else {
play_creature_attack_sound("sounds/weapons/axe_hit.ogg", hideoutPlayerX, bandit.position, BANDIT_SOUND_VOLUME_STEP);
}
play_player_damage_sound();
return true;
}
void update_hideout_bandit_audio(HideoutBandit@ bandit) {
if (bandit.soundHandle != -1 && p.sound_is_active(bandit.soundHandle)) {
p.update_sound_1d(bandit.soundHandle, bandit.position);
return;
}
if (bandit.soundHandle != -1) {
p.destroy_sound(bandit.soundHandle);
}
bandit.soundHandle = play_1d_with_volume_step(bandit.alertSound, hideoutPlayerX, bandit.position, true, BANDIT_SOUND_VOLUME_STEP);
}
void update_hideout_bandit(HideoutBandit@ bandit) {
update_weapon_range_audio_with_listener(hideoutPlayerX, bandit.position, bandit.inWeaponRange);
if (try_hideout_bandit_attack_player(bandit)) {
update_hideout_bandit_audio(bandit);
return;
}
if (bandit.moveTimer.elapsed < bandit.moveInterval) {
update_hideout_bandit_audio(bandit);
return;
}
bandit.moveTimer.restart();
int direction = 0;
if (hideoutPlayerX > bandit.position) direction = 1;
else if (hideoutPlayerX < bandit.position) direction = -1;
if (direction != 0) {
int targetX = bandit.position + direction;
if (targetX >= 0 && targetX < BANDIT_HIDEOUT_MAP_SIZE) {
if (get_hideout_bandit_at(targetX) == null) {
bandit.position = targetX;
play_hideout_positional_footstep(hideoutPlayerX, bandit.position, BANDIT_FOOTSTEP_MAX_DISTANCE, BANDIT_SOUND_VOLUME_STEP);
}
}
}
update_hideout_bandit_audio(bandit);
}
void update_hideout_bandits() {
for (uint i = 0; i < hideoutBandits.length(); i++) {
update_hideout_bandit(hideoutBandits[i]);
}
}
int add_hideout_storage_item(int itemType, int amount) {
if (amount <= 0) return 0;
int capacity = BASE_STORAGE_MAX - get_storage_count(itemType);
if (capacity <= 0) return 0;
int addedAmount = amount;
if (addedAmount > capacity) addedAmount = capacity;
if (addedAmount <= 0) return 0;
add_storage_count(itemType, addedAmount);
if (itemType == ITEM_SMALL_GAME) {
for (int i = 0; i < addedAmount; i++) {
storage_small_game_types.insert_last("small game");
}
}
if (itemType == ITEM_FISH) {
for (int i = 0; i < addedAmount; i++) {
add_storage_fish_weight(get_default_fish_weight());
}
}
return addedAmount;
}
void give_bandit_hideout_rewards() {
speak_with_history("Victory!", true);
string[] rewards;
rewards.insert_last("=== Victory Rewards ===");
rewards.insert_last("");
rewards.insert_last("Bandits defeated: " + hideoutBanditsKilled + ".");
if (world_altars.length() > 0) {
double favorReward = BANDIT_HIDEOUT_BASE_FAVOR + (hideoutBanditsKilled * BANDIT_HIDEOUT_FAVOR_PER_KILL);
if (favorReward > BANDIT_HIDEOUT_FAVOR_MAX) favorReward = BANDIT_HIDEOUT_FAVOR_MAX;
if (favorReward < BANDIT_HIDEOUT_BASE_FAVOR) favorReward = BANDIT_HIDEOUT_BASE_FAVOR;
favor += favorReward;
rewards.insert_last("Favor awarded: " + format_favor(favorReward) + ".");
} else {
rewards.insert_last("Altar not built. No favor awarded.");
}
rewards.insert_last("");
if (world_storages.length() == 0) {
rewards.insert_last("Storage not built. No item rewards.");
} else {
rewards.insert_last("Storage rewards:");
bool anyItems = false;
for (int itemType = 0; itemType < ITEM_COUNT; itemType++) {
int roll = random(0, 9);
if (roll <= 0) continue;
int addedAmount = add_hideout_storage_item(itemType, roll);
if (addedAmount <= 0) continue;
rewards.insert_last(get_item_display_name(itemType) + ": +" + addedAmount + ".");
anyItems = true;
}
if (!anyItems) {
rewards.insert_last("No items were recovered.");
}
}
text_reader_lines(rewards, "Bandit's Hideout", true);
}

View File

@@ -63,6 +63,25 @@ const int ZOMBIE_FOOTSTEP_MAX_DISTANCE = 5;
const float ZOMBIE_SOUND_VOLUME_STEP = 3.0; const float ZOMBIE_SOUND_VOLUME_STEP = 3.0;
const int ZOMBIE_ATTACK_MAX_HEIGHT = 6; const int ZOMBIE_ATTACK_MAX_HEIGHT = 6;
// Undead resident settings (stronger than zombies)
const int UNDEAD_RESIDENT_HEALTH = 16;
const int UNDEAD_RESIDENT_DAMAGE_MIN = 5;
const int UNDEAD_RESIDENT_DAMAGE_MAX = 7;
// Wight settings (undead elite)
const int WIGHT_HEALTH = 40;
const int WIGHT_DAMAGE_MIN = 6;
const int WIGHT_DAMAGE_MAX = 8;
const int WIGHT_SPAWN_CHANCE_START = 5;
const int WIGHT_SPAWN_CHANCE_STEP = 5;
// Vampyr settings (undead abductor)
const int VAMPYR_HEALTH = 40;
const int VAMPYR_SPAWN_CHANCE_START = 5;
const int VAMPYR_SPAWN_CHANCE_STEP = 5;
const int VAMPYR_CAPTURE_CHANCE = 50;
const int VAMPYR_CAPTURE_INTERVAL = 1000;
// Boar settings // Boar settings
const int BOAR_HEALTH = 4; const int BOAR_HEALTH = 4;
const int BOAR_MAX_COUNT = 1; const int BOAR_MAX_COUNT = 1;

View File

@@ -1,32 +1,113 @@
// Undead creatures (zombies, vampires, ghosts, etc.) // Undead creatures (zombies, wights, vampyrs, ghosts, etc.)
// Currently only zombies are implemented // Currently zombies, wights, vampyrs, and undead residents are implemented
string[] undead_zombie_sounds = {"sounds/enemies/zombie1.ogg"}; string[] undead_zombie_sounds = {"sounds/enemies/zombie1.ogg"};
string[] undead_wight_sounds = {"sounds/enemies/wight1.ogg"};
string[] undead_vampyr_sounds = {
"sounds/enemies/vampyr1.ogg",
"sounds/enemies/vampyr2.ogg",
"sounds/enemies/vampyr3.ogg",
"sounds/enemies/vampyr4.ogg"
};
string[] undead_resident_sounds = {"sounds/enemies/undead_resident1.ogg"};
int wight_spawn_chance = WIGHT_SPAWN_CHANCE_START;
bool wight_spawned_this_night = false;
int vampyr_spawn_chance = VAMPYR_SPAWN_CHANCE_START;
bool vampyr_spawned_this_night = false;
int get_undead_base_health(const string &in undead_type) {
if (undead_type == "wight") return WIGHT_HEALTH;
if (undead_type == "vampyr") return VAMPYR_HEALTH;
if (undead_type == "undead_resident") return UNDEAD_RESIDENT_HEALTH;
return ZOMBIE_HEALTH;
}
int get_undead_damage_min(const string &in undead_type) {
if (undead_type == "wight") return WIGHT_DAMAGE_MIN;
if (undead_type == "vampyr") return WIGHT_DAMAGE_MIN;
if (undead_type == "undead_resident") return UNDEAD_RESIDENT_DAMAGE_MIN;
return ZOMBIE_DAMAGE_MIN;
}
int get_undead_damage_max(const string &in undead_type) {
if (undead_type == "wight") return WIGHT_DAMAGE_MAX;
if (undead_type == "vampyr") return WIGHT_DAMAGE_MAX;
if (undead_type == "undead_resident") return UNDEAD_RESIDENT_DAMAGE_MAX;
return ZOMBIE_DAMAGE_MAX;
}
string pick_undead_voice_sound(const string &in undead_type) {
if (undead_type == "wight") {
int sound_index = random(0, undead_wight_sounds.length() - 1);
return undead_wight_sounds[sound_index];
}
if (undead_type == "vampyr") {
int sound_index = random(0, undead_vampyr_sounds.length() - 1);
return undead_vampyr_sounds[sound_index];
}
if (undead_type == "undead_resident") {
int sound_index = random(0, undead_resident_sounds.length() - 1);
return undead_resident_sounds[sound_index];
}
int sound_index = random(0, undead_zombie_sounds.length() - 1);
return undead_zombie_sounds[sound_index];
}
string get_undead_label(const string &in undead_type) {
if (undead_type == "wight") return "wight";
if (undead_type == "vampyr") return "vampyr";
if (undead_type == "undead_resident") return "undead resident";
return "zombie";
}
class Undead { class Undead {
int position; int position;
int health; int health;
string undead_type; // "zombie", future: "vampire", "ghost", etc. string undead_type; // "zombie", "wight", "vampyr", "undead_resident", future: "ghost", etc.
string voice_sound; string voice_sound;
int sound_handle; int sound_handle;
bool in_weapon_range; bool in_weapon_range;
bool retreating;
bool suppress_voice;
bool should_despawn;
timer move_timer; timer move_timer;
timer attack_timer; timer attack_timer;
Undead(int pos, string type = "zombie") { Undead(int pos, string type = "zombie") {
position = pos; position = pos;
undead_type = type; undead_type = type;
health = ZOMBIE_HEALTH; health = get_undead_base_health(undead_type);
int sound_index = random(0, undead_zombie_sounds.length() - 1); voice_sound = pick_undead_voice_sound(undead_type);
voice_sound = undead_zombie_sounds[sound_index];
sound_handle = -1; sound_handle = -1;
in_weapon_range = false; in_weapon_range = false;
retreating = false;
suppress_voice = false;
should_despawn = false;
move_timer.restart(); move_timer.restart();
attack_timer.restart(); attack_timer.restart();
} }
} }
Undead@[] undeads; Undead@[] undeads;
bool has_wight() {
for (uint i = 0; i < undeads.length(); i++) {
if (undeads[i].undead_type == "wight") {
return true;
}
}
return false;
}
bool has_vampyr() {
for (uint i = 0; i < undeads.length(); i++) {
if (undeads[i].undead_type == "vampyr") {
return true;
}
}
return false;
}
void update_undead_weapon_range_audio() { void update_undead_weapon_range_audio() {
for (uint i = 0; i < undeads.length(); i++) { for (uint i = 0; i < undeads.length(); i++) {
update_weapon_range_audio(undeads[i].position, undeads[i].in_weapon_range); update_weapon_range_audio(undeads[i].position, undeads[i].in_weapon_range);
@@ -61,7 +142,7 @@ Undead@ get_undead_at(int pos) {
return null; return null;
} }
void spawn_undead() { void spawn_undead(const string &in undead_type = "zombie") {
int spawn_x = -1; int spawn_x = -1;
for (int attempts = 0; attempts < 20; attempts++) { for (int attempts = 0; attempts < 20; attempts++) {
int candidate = random(BASE_END + 1, MAP_SIZE - 1); int candidate = random(BASE_END + 1, MAP_SIZE - 1);
@@ -74,14 +155,18 @@ void spawn_undead() {
spawn_x = random(BASE_END + 1, MAP_SIZE - 1); spawn_x = random(BASE_END + 1, MAP_SIZE - 1);
} }
Undead@ z = Undead(spawn_x, "zombie"); Undead@ undead = Undead(spawn_x, undead_type);
undeads.insert_last(z); undeads.insert_last(undead);
// Play looping sound that follows the zombie // Play looping sound that follows the undead
int[] areaStarts; int[] areaStarts;
int[] areaEnds; int[] areaEnds;
get_active_audio_areas(areaStarts, areaEnds); get_active_audio_areas(areaStarts, areaEnds);
if (areaStarts.length() == 0 || range_overlaps_active_areas(spawn_x, spawn_x, areaStarts, areaEnds)) { if (areaStarts.length() == 0 || range_overlaps_active_areas(spawn_x, spawn_x, areaStarts, areaEnds)) {
z.sound_handle = play_1d_with_volume_step(z.voice_sound, x, spawn_x, true, ZOMBIE_SOUND_VOLUME_STEP); bool loop_voice = (undead_type != "vampyr");
if (undead_type == "vampyr") {
undead.voice_sound = pick_undead_voice_sound(undead_type);
}
undead.sound_handle = play_1d_with_volume_step(undead.voice_sound, x, spawn_x, loop_voice, ZOMBIE_SOUND_VOLUME_STEP);
} }
} }
@@ -90,7 +175,7 @@ void try_attack_barricade_undead(Undead@ undead) {
if (undead.attack_timer.elapsed < ZOMBIE_ATTACK_INTERVAL) return; if (undead.attack_timer.elapsed < ZOMBIE_ATTACK_INTERVAL) return;
undead.attack_timer.restart(); undead.attack_timer.restart();
int damage = random(ZOMBIE_DAMAGE_MIN, ZOMBIE_DAMAGE_MAX); int damage = random(get_undead_damage_min(undead.undead_type), get_undead_damage_max(undead.undead_type));
barricade_health -= damage; barricade_health -= damage;
if (barricade_health < 0) barricade_health = 0; if (barricade_health < 0) barricade_health = 0;
@@ -103,7 +188,7 @@ void try_attack_barricade_undead(Undead@ undead) {
int before_health = undead.health; int before_health = undead.health;
damage_undead_at(undead.position, counterDamage); damage_undead_at(undead.position, counterDamage);
if (before_health - counterDamage <= 0 && x <= BASE_END) { if (before_health - counterDamage <= 0 && x <= BASE_END) {
speak_with_history("Residents killed an attacking zombie.", true); speak_with_history("Residents killed an attacking " + get_undead_label(undead.undead_type) + ".", true);
} }
} }
} }
@@ -118,6 +203,10 @@ bool can_undead_attack_player(Undead@ undead) {
return false; return false;
} }
if (undead.undead_type == "vampyr") {
return false;
}
if (barricade_health > 0 && x <= BASE_END) { if (barricade_health > 0 && x <= BASE_END) {
return false; return false;
} }
@@ -142,7 +231,7 @@ bool try_attack_player_undead(Undead@ undead) {
} }
undead.attack_timer.restart(); undead.attack_timer.restart();
int damage = random(ZOMBIE_DAMAGE_MIN, ZOMBIE_DAMAGE_MAX); int damage = random(get_undead_damage_min(undead.undead_type), get_undead_damage_max(undead.undead_type));
player_health -= damage; player_health -= damage;
if (player_health < 0) { if (player_health < 0) {
player_health = 0; player_health = 0;
@@ -152,9 +241,42 @@ bool try_attack_player_undead(Undead@ undead) {
return true; return true;
} }
void start_vampyr_retreat(Undead@ undead) {
undead.retreating = true;
undead.suppress_voice = true;
if (undead.sound_handle != -1) {
p.destroy_sound(undead.sound_handle);
undead.sound_handle = -1;
}
undead.move_timer.restart();
}
void try_capture_resident_vampyr(Undead@ undead) {
if (undead.attack_timer.elapsed < VAMPYR_CAPTURE_INTERVAL) {
return;
}
undead.attack_timer.restart();
if (residents_count <= 0) {
start_vampyr_retreat(undead);
return;
}
if (random(1, 100) <= VAMPYR_CAPTURE_CHANCE) {
residents_count--;
if (residents_count < 0) residents_count = 0;
undead_residents_pending++;
speak_with_history("A resident has been taken.", true);
start_vampyr_retreat(undead);
}
}
void update_undead(Undead@ undead, bool audio_active) { void update_undead(Undead@ undead, bool audio_active) {
bool is_vampyr = (undead.undead_type == "vampyr");
bool loop_voice = !is_vampyr;
// Update looping sound position // Update looping sound position
if (!audio_active) { if (!audio_active || undead.suppress_voice) {
if (undead.sound_handle != -1) { if (undead.sound_handle != -1) {
p.destroy_sound(undead.sound_handle); p.destroy_sound(undead.sound_handle);
undead.sound_handle = -1; undead.sound_handle = -1;
@@ -166,23 +288,49 @@ void update_undead(Undead@ undead, bool audio_active) {
if (undead.sound_handle != -1) { if (undead.sound_handle != -1) {
p.destroy_sound(undead.sound_handle); p.destroy_sound(undead.sound_handle);
} }
undead.sound_handle = play_1d_with_volume_step(undead.voice_sound, x, undead.position, true, ZOMBIE_SOUND_VOLUME_STEP); if (is_vampyr) {
undead.voice_sound = pick_undead_voice_sound(undead.undead_type);
}
undead.sound_handle = play_1d_with_volume_step(undead.voice_sound, x, undead.position, loop_voice, ZOMBIE_SOUND_VOLUME_STEP);
} }
if (try_attack_player_undead(undead)) { if (try_attack_player_undead(undead)) {
return; return;
} }
if (undead.undead_type == "vampyr" && !undead.retreating && barricade_health > 0 && undead.position == BASE_END + 1) {
try_capture_resident_vampyr(undead);
return;
}
if (undead.move_timer.elapsed < ZOMBIE_MOVE_INTERVAL) return; if (undead.move_timer.elapsed < ZOMBIE_MOVE_INTERVAL) return;
undead.move_timer.restart(); undead.move_timer.restart();
if (barricade_health > 0 && undead.position == BASE_END + 1) { if (undead.undead_type != "vampyr" && barricade_health > 0 && undead.position == BASE_END + 1) {
try_attack_barricade_undead(undead); try_attack_barricade_undead(undead);
return; return;
} }
int direction = 0; int direction = 0;
if (x > BASE_END) { if (undead.undead_type == "vampyr") {
if (undead.retreating) {
if (undead.position >= MAP_SIZE - 1) {
undead.should_despawn = true;
return;
}
direction = 1;
} else if (undead.position > 0) {
direction = -1;
} else {
return;
}
} else if (undead.undead_type == "wight") {
if (undead.position > 0) {
direction = -1;
} else {
return;
}
} else if (x > BASE_END) {
if (x > undead.position) { if (x > undead.position) {
direction = 1; direction = 1;
} else if (x < undead.position) { } else if (x < undead.position) {
@@ -198,7 +346,7 @@ void update_undead(Undead@ undead, bool audio_active) {
int target_x = undead.position + direction; int target_x = undead.position + direction;
if (target_x < 0 || target_x >= MAP_SIZE) return; if (target_x < 0 || target_x >= MAP_SIZE) return;
if (target_x <= BASE_END && barricade_health > 0) { if (undead.undead_type != "vampyr" && target_x <= BASE_END && barricade_health > 0) {
try_attack_barricade_undead(undead); try_attack_barricade_undead(undead);
return; return;
} }
@@ -223,8 +371,24 @@ void update_undeads() {
int maxCount = ZOMBIE_MAX_COUNT + extra; int maxCount = ZOMBIE_MAX_COUNT + extra;
if (maxCount > ZOMBIE_MAX_COUNT_CAP) maxCount = ZOMBIE_MAX_COUNT_CAP; if (maxCount > ZOMBIE_MAX_COUNT_CAP) maxCount = ZOMBIE_MAX_COUNT_CAP;
while (undeads.length() < maxCount) { int zombie_count = 0;
spawn_undead(); int undead_resident_count = 0;
for (uint i = 0; i < undeads.length(); i++) {
if (undeads[i].undead_type == "zombie") {
zombie_count++;
} else if (undeads[i].undead_type == "undead_resident") {
undead_resident_count++;
}
}
while (zombie_count < maxCount) {
spawn_undead("zombie");
zombie_count++;
}
while (undead_resident_count < undead_residents_count) {
spawn_undead("undead_resident");
undead_resident_count++;
} }
int[] areaStarts; int[] areaStarts;
@@ -236,6 +400,88 @@ void update_undeads() {
bool audio_active = !limit_audio || range_overlaps_active_areas(undeads[i].position, undeads[i].position, areaStarts, areaEnds); bool audio_active = !limit_audio || range_overlaps_active_areas(undeads[i].position, undeads[i].position, areaStarts, areaEnds);
update_undead(undeads[i], audio_active); update_undead(undeads[i], audio_active);
} }
for (int i = int(undeads.length()) - 1; i >= 0; i--) {
if (undeads[i].should_despawn) {
if (undeads[i].sound_handle != -1) {
p.destroy_sound(undeads[i].sound_handle);
undeads[i].sound_handle = -1;
}
undeads.remove_at(i);
}
}
}
void attempt_hourly_wight_spawn() {
if (current_hour == 19) {
wight_spawned_this_night = false;
}
if (is_daytime) {
return;
}
if (has_vampyr()) {
return;
}
if (wight_spawned_this_night) {
return;
}
if (has_wight()) {
wight_spawned_this_night = true;
return;
}
int roll = random(1, 100);
if (roll <= wight_spawn_chance) {
spawn_undead("wight");
wight_spawned_this_night = true;
wight_spawn_chance = WIGHT_SPAWN_CHANCE_START;
return;
}
wight_spawn_chance += WIGHT_SPAWN_CHANCE_STEP;
if (wight_spawn_chance > 100) wight_spawn_chance = 100;
}
void attempt_hourly_vampyr_spawn() {
if (current_hour == 19) {
vampyr_spawned_this_night = false;
}
if (is_daytime) {
return;
}
if (residents_count <= 0) {
return;
}
if (has_wight()) {
return;
}
if (vampyr_spawned_this_night) {
return;
}
if (has_vampyr()) {
vampyr_spawned_this_night = true;
return;
}
int roll = random(1, 100);
if (roll <= vampyr_spawn_chance) {
spawn_undead("vampyr");
vampyr_spawned_this_night = true;
vampyr_spawn_chance = VAMPYR_SPAWN_CHANCE_START;
return;
}
vampyr_spawn_chance += VAMPYR_SPAWN_CHANCE_STEP;
if (vampyr_spawn_chance > 100) vampyr_spawn_chance = 100;
} }
bool damage_undead_at(int pos, int damage) { bool damage_undead_at(int pos, int damage) {
@@ -246,6 +492,10 @@ bool damage_undead_at(int pos, int damage) {
if (world_altars.length() > 0) { if (world_altars.length() > 0) {
favor += 0.2; favor += 0.2;
} }
if (undeads[i].undead_type == "undead_resident") {
undead_residents_count--;
if (undead_residents_count < 0) undead_residents_count = 0;
}
if (undeads[i].sound_handle != -1) { if (undeads[i].sound_handle != -1) {
p.destroy_sound(undeads[i].sound_handle); p.destroy_sound(undeads[i].sound_handle);
undeads[i].sound_handle = -1; undeads[i].sound_handle = -1;
@@ -265,4 +515,4 @@ Undead@ get_zombie_at(int pos) { return get_undead_at(pos); }
bool damage_zombie_at(int pos, int damage) { return damage_undead_at(pos, damage); } bool damage_zombie_at(int pos, int damage) { return damage_undead_at(pos, damage); }
void update_zombies() { update_undeads(); } void update_zombies() { update_undeads(); }
void clear_zombies() { clear_undeads(); } void clear_zombies() { clear_undeads(); }
void spawn_zombie() { spawn_undead(); } void spawn_zombie() { spawn_undead("zombie"); }

View File

@@ -606,6 +606,8 @@ void reset_game_state() {
barricade_health = 0; barricade_health = 0;
barricade_initialized = false; barricade_initialized = false;
residents_count = 0; residents_count = 0;
undead_residents_count = 0;
undead_residents_pending = 0;
current_hour = 8; current_hour = 8;
current_day = 1; current_day = 1;
@@ -868,6 +870,10 @@ bool save_game_state() {
saveData.set("time_invasion_scheduled_hour", invasion_scheduled_hour); saveData.set("time_invasion_scheduled_hour", invasion_scheduled_hour);
saveData.set("time_invasion_enemy_type", invasion_enemy_type); saveData.set("time_invasion_enemy_type", invasion_enemy_type);
saveData.set("quest_roll_done_today", quest_roll_done_today); saveData.set("quest_roll_done_today", quest_roll_done_today);
saveData.set("wight_spawn_chance", wight_spawn_chance);
saveData.set("wight_spawned_this_night", wight_spawned_this_night);
saveData.set("vampyr_spawn_chance", vampyr_spawn_chance);
saveData.set("vampyr_spawned_this_night", vampyr_spawned_this_night);
string[] questData; string[] questData;
for (uint i = 0; i < quest_queue.length(); i++) { for (uint i = 0; i < quest_queue.length(); i++) {
questData.insert_last("" + quest_queue[i]); questData.insert_last("" + quest_queue[i]);
@@ -882,6 +888,8 @@ bool save_game_state() {
saveData.set("world_barricade_initialized", barricade_initialized); saveData.set("world_barricade_initialized", barricade_initialized);
saveData.set("world_barricade_health", barricade_health); saveData.set("world_barricade_health", barricade_health);
saveData.set("world_residents_count", residents_count); saveData.set("world_residents_count", residents_count);
saveData.set("world_undead_residents_count", undead_residents_count);
saveData.set("world_undead_residents_pending", undead_residents_pending);
saveData.set("world_expanded_terrain_types", join_string_array(expanded_terrain_types)); saveData.set("world_expanded_terrain_types", join_string_array(expanded_terrain_types));
string[] treeData; string[] treeData;
@@ -1019,6 +1027,10 @@ bool load_game_state_from_file(const string&in filename) {
barricade_health = int(get_number(saveData, "world_barricade_health", BARRICADE_BASE_HEALTH)); barricade_health = int(get_number(saveData, "world_barricade_health", BARRICADE_BASE_HEALTH));
residents_count = int(get_number(saveData, "world_residents_count", 0)); residents_count = int(get_number(saveData, "world_residents_count", 0));
if (residents_count < 0) residents_count = 0; if (residents_count < 0) residents_count = 0;
undead_residents_count = int(get_number(saveData, "world_undead_residents_count", 0));
if (undead_residents_count < 0) undead_residents_count = 0;
undead_residents_pending = int(get_number(saveData, "world_undead_residents_pending", 0));
if (undead_residents_pending < 0) undead_residents_pending = 0;
if (!barricade_initialized) { if (!barricade_initialized) {
init_barricade(); init_barricade();
} else { } else {
@@ -1276,6 +1288,14 @@ bool load_game_state_from_file(const string&in filename) {
if (invasion_scheduled_hour < -1) invasion_scheduled_hour = -1; if (invasion_scheduled_hour < -1) invasion_scheduled_hour = -1;
if (invasion_scheduled_hour > 23) invasion_scheduled_hour = -1; if (invasion_scheduled_hour > 23) invasion_scheduled_hour = -1;
quest_roll_done_today = get_bool(saveData, "quest_roll_done_today", false); quest_roll_done_today = get_bool(saveData, "quest_roll_done_today", false);
wight_spawn_chance = int(get_number(saveData, "wight_spawn_chance", WIGHT_SPAWN_CHANCE_START));
wight_spawned_this_night = get_bool(saveData, "wight_spawned_this_night", false);
if (wight_spawn_chance < WIGHT_SPAWN_CHANCE_START) wight_spawn_chance = WIGHT_SPAWN_CHANCE_START;
if (wight_spawn_chance > 100) wight_spawn_chance = 100;
vampyr_spawn_chance = int(get_number(saveData, "vampyr_spawn_chance", VAMPYR_SPAWN_CHANCE_START));
vampyr_spawned_this_night = get_bool(saveData, "vampyr_spawned_this_night", false);
if (vampyr_spawn_chance < VAMPYR_SPAWN_CHANCE_START) vampyr_spawn_chance = VAMPYR_SPAWN_CHANCE_START;
if (vampyr_spawn_chance > 100) vampyr_spawn_chance = 100;
quest_queue.resize(0); quest_queue.resize(0);
string[] loadedQuests = get_string_list_or_split(saveData, "quest_queue"); string[] loadedQuests = get_string_list_or_split(saveData, "quest_queue");

View File

@@ -517,6 +517,10 @@ void update_time() {
} }
if (current_hour == 6) { if (current_hour == 6) {
if (undead_residents_pending > 0) {
undead_residents_count += undead_residents_pending;
undead_residents_pending = 0;
}
process_daily_weapon_breakage(); process_daily_weapon_breakage();
attempt_daily_quest(); attempt_daily_quest();
attempt_resident_butchering(); attempt_resident_butchering();
@@ -529,6 +533,8 @@ void update_time() {
update_incense_burning(); update_incense_burning();
attempt_hourly_flying_creature_spawn(); attempt_hourly_flying_creature_spawn();
attempt_hourly_boar_spawn(); attempt_hourly_boar_spawn();
attempt_hourly_wight_spawn();
attempt_hourly_vampyr_spawn();
check_scheduled_invasion(); check_scheduled_invasion();
attempt_blessing(); attempt_blessing();
check_weather_transition(); check_weather_transition();

View File

@@ -4,6 +4,8 @@
int barricade_health = 0; int barricade_health = 0;
bool barricade_initialized = false; bool barricade_initialized = false;
int residents_count = 0; int residents_count = 0;
int undead_residents_count = 0;
int undead_residents_pending = 0;
void init_barricade() { void init_barricade() {
if (barricade_initialized) { if (barricade_initialized) {