New flying game turkey added. Zombie swarms added. Enemies from areas can now be encountered in those areas they do not leave the area unless invading.
This commit is contained in:
BIN
sounds/game/turkey.ogg
LFS
Normal file
BIN
sounds/game/turkey.ogg
LFS
Normal file
Binary file not shown.
@@ -739,6 +739,9 @@ void attempt_resident_butchering() {
|
|||||||
meat_yield = 1;
|
meat_yield = 1;
|
||||||
feathers_yield = random(3, 6);
|
feathers_yield = random(3, 6);
|
||||||
down_yield = random(1, 3);
|
down_yield = random(1, 3);
|
||||||
|
} else if (game_type == "turkey") {
|
||||||
|
meat_yield = 1;
|
||||||
|
feathers_yield = random(1, 4);
|
||||||
} else if (is_boar) {
|
} else if (is_boar) {
|
||||||
meat_yield = random(2, 3);
|
meat_yield = random(2, 3);
|
||||||
skins_yield = 3;
|
skins_yield = 3;
|
||||||
|
|||||||
@@ -53,6 +53,15 @@ const int ARROW_CAPACITY_PER_QUIVER = 12;
|
|||||||
const int ZOMBIE_HEALTH = 12;
|
const int ZOMBIE_HEALTH = 12;
|
||||||
const int ZOMBIE_MAX_COUNT = 5;
|
const int ZOMBIE_MAX_COUNT = 5;
|
||||||
const int ZOMBIE_MAX_COUNT_CAP = 12;
|
const int ZOMBIE_MAX_COUNT_CAP = 12;
|
||||||
|
const int ZOMBIE_SWARM_START_DAY = 4;
|
||||||
|
const int ZOMBIE_SWARM_INTERVAL_DAYS = 9;
|
||||||
|
const int ZOMBIE_SWARM_CHANCE_INTERVAL_DAYS = 4;
|
||||||
|
const int ZOMBIE_SWARM_BASE_DURATION_HOURS = 1;
|
||||||
|
const int ZOMBIE_SWARM_DURATION_STEP_HOURS = 1;
|
||||||
|
const int ZOMBIE_SWARM_ZOMBIE_MAX_BONUS = 3;
|
||||||
|
const int ZOMBIE_SWARM_CHANCE_START = 15;
|
||||||
|
const int ZOMBIE_SWARM_CHANCE_STEP = 5;
|
||||||
|
const int ZOMBIE_SWARM_CHANCE_CAP = 95;
|
||||||
const int ZOMBIE_MOVE_INTERVAL = 1000;
|
const int ZOMBIE_MOVE_INTERVAL = 1000;
|
||||||
const int ZOMBIE_ATTACK_INTERVAL = 1600;
|
const int ZOMBIE_ATTACK_INTERVAL = 1600;
|
||||||
const int ZOMBIE_DAMAGE_MIN = 4;
|
const int ZOMBIE_DAMAGE_MIN = 4;
|
||||||
@@ -156,6 +165,8 @@ const int BANDIT_DETECTION_RADIUS = 5;
|
|||||||
const int BANDIT_WANDER_DIRECTION_CHANGE_MIN = 3000;
|
const int BANDIT_WANDER_DIRECTION_CHANGE_MIN = 3000;
|
||||||
const int BANDIT_WANDER_DIRECTION_CHANGE_MAX = 8000;
|
const int BANDIT_WANDER_DIRECTION_CHANGE_MAX = 8000;
|
||||||
const int INVADER_SOUND_VARIANTS_MAX = 5;
|
const int INVADER_SOUND_VARIANTS_MAX = 5;
|
||||||
|
const int EXPANSION_ROAMER_SPAWN_INTERVAL_HOURS = 2;
|
||||||
|
const int EXPANSION_ROAMER_MAX_PER_AREA = 3;
|
||||||
|
|
||||||
// Audio ranges and volume falloff
|
// Audio ranges and volume falloff
|
||||||
// Formula: final_volume = start_volume - (volume_step × distance)
|
// Formula: final_volume = start_volume - (volume_step × distance)
|
||||||
@@ -181,6 +192,7 @@ const float TREE_SOUND_VOLUME_STEP = 4.0; // Similar to snares for good audibil
|
|||||||
const int TREE_SOUND_RANGE = 4;
|
const int TREE_SOUND_RANGE = 4;
|
||||||
const int TREE_MIN_DISTANCE = 10;
|
const int TREE_MIN_DISTANCE = 10;
|
||||||
const int TREE_MAX_PER_AREA = 2;
|
const int TREE_MAX_PER_AREA = 2;
|
||||||
|
const int TREE_AVOID_STEEP_CLIMB_RANGE = 3;
|
||||||
|
|
||||||
const float RESIDENT_DEFENSE_VOLUME_STEP = 3.0; // Default volume for resident counter-attacks
|
const float RESIDENT_DEFENSE_VOLUME_STEP = 3.0; // Default volume for resident counter-attacks
|
||||||
const float PLAYER_WEAPON_SOUND_VOLUME_STEP = 3.0;
|
const float PLAYER_WEAPON_SOUND_VOLUME_STEP = 3.0;
|
||||||
@@ -237,6 +249,22 @@ const int GOOSE_MAX_COUNT = 3;
|
|||||||
const int GOOSE_HOURLY_SPAWN_CHANCE = 40; // Percent chance per hour to spawn a goose
|
const int GOOSE_HOURLY_SPAWN_CHANCE = 40; // Percent chance per hour to spawn a goose
|
||||||
const int GOOSE_SIGHT_RANGE = 0;
|
const int GOOSE_SIGHT_RANGE = 0;
|
||||||
|
|
||||||
|
// Turkey settings
|
||||||
|
const int TURKEY_HEALTH = 1;
|
||||||
|
const int TURKEY_MOVE_INTERVAL_MIN = 800;
|
||||||
|
const int TURKEY_MOVE_INTERVAL_MAX = 2000;
|
||||||
|
const int TURKEY_FLYING_HEIGHT_MIN = 10;
|
||||||
|
const int TURKEY_FLYING_HEIGHT_MAX = 30;
|
||||||
|
const float TURKEY_SOUND_VOLUME_STEP = 3.0;
|
||||||
|
const int TURKEY_FLIGHT_SOUND_DELAY_MIN = 2000;
|
||||||
|
const int TURKEY_FLIGHT_SOUND_DELAY_MAX = 5000;
|
||||||
|
const int TURKEY_FALL_SPEED = 100; // ms per foot
|
||||||
|
const int TURKEY_FLY_AWAY_CHANCE = 0; // Chance out of 1000 per tick to fly away
|
||||||
|
const int TURKEY_MAX_DIST_FROM_FOREST = 0; // How far they can wander from forest
|
||||||
|
const int TURKEY_MAX_COUNT = 3;
|
||||||
|
const int TURKEY_HOURLY_SPAWN_CHANCE = 40; // Percent chance per hour to spawn a turkey
|
||||||
|
const int TURKEY_SIGHT_RANGE = 0;
|
||||||
|
|
||||||
// Weather settings
|
// Weather settings
|
||||||
const int WEATHER_FADE_DURATION = 8000; // 8 seconds for smooth audio transitions
|
const int WEATHER_FADE_DURATION = 8000; // 8 seconds for smooth audio transitions
|
||||||
const float WEATHER_MIN_VOLUME = -30.0;
|
const float WEATHER_MIN_VOLUME = -30.0;
|
||||||
|
|||||||
@@ -368,6 +368,10 @@ void butcher_small_game() {
|
|||||||
add_personal_count(ITEM_FEATHERS, random(3, 6));
|
add_personal_count(ITEM_FEATHERS, random(3, 6));
|
||||||
add_personal_count(ITEM_DOWN, random(1, 3));
|
add_personal_count(ITEM_DOWN, random(1, 3));
|
||||||
speak_with_history("Butchered goose. Got 1 meat, feathers, and down.", true);
|
speak_with_history("Butchered goose. Got 1 meat, feathers, and down.", true);
|
||||||
|
} else if (game_type == "turkey") {
|
||||||
|
add_personal_count(ITEM_MEAT, 1);
|
||||||
|
add_personal_count(ITEM_FEATHERS, random(1, 4));
|
||||||
|
speak_with_history("Butchered turkey. Got 1 meat and feathers.", true);
|
||||||
} else if (game_type == "boar carcass") {
|
} else if (game_type == "boar carcass") {
|
||||||
add_personal_count(ITEM_MEAT, random(2, 3));
|
add_personal_count(ITEM_MEAT, random(2, 3));
|
||||||
add_personal_count(ITEM_SKINS, 3);
|
add_personal_count(ITEM_SKINS, 3);
|
||||||
@@ -446,6 +450,7 @@ void butcher_small_game_max() {
|
|||||||
int total_down = 0;
|
int total_down = 0;
|
||||||
int total_sinew = 0;
|
int total_sinew = 0;
|
||||||
int geese_count = 0;
|
int geese_count = 0;
|
||||||
|
int turkey_count = 0;
|
||||||
int boars_count = 0;
|
int boars_count = 0;
|
||||||
|
|
||||||
for (int i = 0; i < max_craft; i++) {
|
for (int i = 0; i < max_craft; i++) {
|
||||||
@@ -459,12 +464,16 @@ void butcher_small_game_max() {
|
|||||||
personal_small_game_types.remove_at(0);
|
personal_small_game_types.remove_at(0);
|
||||||
add_personal_count(ITEM_SMALL_GAME, -1);
|
add_personal_count(ITEM_SMALL_GAME, -1);
|
||||||
if (game_type == "goose") geese_count++;
|
if (game_type == "goose") geese_count++;
|
||||||
|
if (game_type == "turkey") turkey_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (game_type == "goose") {
|
if (game_type == "goose") {
|
||||||
total_meat++;
|
total_meat++;
|
||||||
total_feathers += random(3, 6);
|
total_feathers += random(3, 6);
|
||||||
total_down += random(1, 3);
|
total_down += random(1, 3);
|
||||||
|
} else if (game_type == "turkey") {
|
||||||
|
total_meat++;
|
||||||
|
total_feathers += random(1, 4);
|
||||||
} else if (game_type == "boar carcass") {
|
} else if (game_type == "boar carcass") {
|
||||||
total_meat += random(2, 3);
|
total_meat += random(2, 3);
|
||||||
total_skins += 3;
|
total_skins += 3;
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ class Bandit {
|
|||||||
timer move_timer;
|
timer move_timer;
|
||||||
timer attack_timer;
|
timer attack_timer;
|
||||||
int move_interval;
|
int move_interval;
|
||||||
|
int home_start;
|
||||||
|
int home_end;
|
||||||
|
|
||||||
// Wandering behavior properties
|
// Wandering behavior properties
|
||||||
string behavior_state; // "aggressive" or "wandering"
|
string behavior_state; // "aggressive" or "wandering"
|
||||||
@@ -52,11 +54,25 @@ class Bandit {
|
|||||||
int wander_direction_change_interval;
|
int wander_direction_change_interval;
|
||||||
|
|
||||||
Bandit(int pos, int expansion_start, int expansion_end, string invader = "bandit") {
|
Bandit(int pos, int expansion_start, int expansion_end, string invader = "bandit") {
|
||||||
|
int range_start = expansion_start;
|
||||||
|
int range_end = expansion_end;
|
||||||
|
if (range_start > range_end) {
|
||||||
|
int temp = range_start;
|
||||||
|
range_start = range_end;
|
||||||
|
range_end = temp;
|
||||||
|
}
|
||||||
|
|
||||||
// Spawn somewhere in the expanded area
|
// Spawn somewhere in the expanded area
|
||||||
position = random(expansion_start, expansion_end);
|
if (pos < range_start || pos > range_end) {
|
||||||
|
position = random(range_start, range_end);
|
||||||
|
} else {
|
||||||
|
position = pos;
|
||||||
|
}
|
||||||
health = BANDIT_HEALTH;
|
health = BANDIT_HEALTH;
|
||||||
sound_handle = -1;
|
sound_handle = -1;
|
||||||
invader_type = invader;
|
invader_type = invader;
|
||||||
|
home_start = range_start;
|
||||||
|
home_end = range_end;
|
||||||
|
|
||||||
// Choose random alert sound
|
// Choose random alert sound
|
||||||
alert_sound = pick_invader_alert_sound(invader_type);
|
alert_sound = pick_invader_alert_sound(invader_type);
|
||||||
@@ -114,20 +130,68 @@ Bandit@ get_bandit_at(int pos) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void spawn_bandit(int expansion_start, int expansion_end, const string&in invader_type = "bandit") {
|
int pick_bandit_spawn_position(int range_start, int range_end) {
|
||||||
int spawn_x = -1;
|
int start = range_start;
|
||||||
for (int attempts = 0; attempts < 20; attempts++) {
|
int end = range_end;
|
||||||
int candidate = random(expansion_start, expansion_end);
|
if (start > end) {
|
||||||
if (get_bandit_at(candidate) == null) {
|
int temp = start;
|
||||||
spawn_x = candidate;
|
start = end;
|
||||||
break;
|
end = temp;
|
||||||
}
|
|
||||||
}
|
|
||||||
if (spawn_x == -1) {
|
|
||||||
spawn_x = random(expansion_start, expansion_end);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Bandit@ b = Bandit(spawn_x, expansion_start, expansion_end, invader_type);
|
for (int attempts = 0; attempts < 20; attempts++) {
|
||||||
|
int candidate = random(start, end);
|
||||||
|
if (candidate == x) continue;
|
||||||
|
if (get_bandit_at(candidate) != null) continue;
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int candidate = start; candidate <= end; candidate++) {
|
||||||
|
if (candidate == x) continue;
|
||||||
|
if (get_bandit_at(candidate) != null) continue;
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count_bandits_in_range(int range_start, int range_end) {
|
||||||
|
int start = range_start;
|
||||||
|
int end = range_end;
|
||||||
|
if (start > end) {
|
||||||
|
int temp = start;
|
||||||
|
start = end;
|
||||||
|
end = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
for (uint i = 0; i < bandits.length(); i++) {
|
||||||
|
if (bandits[i].position >= start && bandits[i].position <= end) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void spawn_bandit(int expansion_start, int expansion_end, const string&in invader_type = "bandit") {
|
||||||
|
int spawn_x = pick_bandit_spawn_position(expansion_start, expansion_end);
|
||||||
|
if (spawn_x == -1) return;
|
||||||
|
|
||||||
|
int home_start = expansion_start;
|
||||||
|
int home_end = expansion_end;
|
||||||
|
if (expanded_area_start != -1 && spawn_x >= expanded_area_start) {
|
||||||
|
int area_start = -1;
|
||||||
|
int area_end = -1;
|
||||||
|
if (get_audio_area_bounds_for_position(spawn_x, area_start, area_end)) {
|
||||||
|
home_start = area_start;
|
||||||
|
home_end = area_end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Bandit@ b = Bandit(spawn_x, home_start, home_end, invader_type);
|
||||||
|
if (!invasion_active) {
|
||||||
|
b.behavior_state = "wandering";
|
||||||
|
}
|
||||||
bandits.insert_last(b);
|
bandits.insert_last(b);
|
||||||
// Play looping sound that follows the bandit
|
// Play looping sound that follows the bandit
|
||||||
int[] areaStarts;
|
int[] areaStarts;
|
||||||
@@ -226,6 +290,15 @@ void try_attack_barricade_bandit(Bandit@ bandit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void update_bandit(Bandit@ bandit, bool audio_active) {
|
void update_bandit(Bandit@ bandit, bool audio_active) {
|
||||||
|
bool enforce_home = (!invasion_active && bandit.home_start <= bandit.home_end);
|
||||||
|
if (enforce_home) {
|
||||||
|
if (bandit.position < bandit.home_start) {
|
||||||
|
bandit.position = bandit.home_start;
|
||||||
|
} else if (bandit.position > bandit.home_end) {
|
||||||
|
bandit.position = bandit.home_end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update looping sound position
|
// Update looping sound position
|
||||||
if (!audio_active) {
|
if (!audio_active) {
|
||||||
if (bandit.sound_handle != -1) {
|
if (bandit.sound_handle != -1) {
|
||||||
@@ -277,6 +350,10 @@ void update_bandit(Bandit@ bandit, bool audio_active) {
|
|||||||
|
|
||||||
// Check bounds
|
// Check bounds
|
||||||
if (target_x >= 0 && target_x < MAP_SIZE) {
|
if (target_x >= 0 && target_x < MAP_SIZE) {
|
||||||
|
if (enforce_home && (target_x < bandit.home_start || target_x > bandit.home_end)) {
|
||||||
|
bandit.wander_direction = -bandit.wander_direction;
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Don't wander into base if barricade is up
|
// Don't wander into base if barricade is up
|
||||||
if (target_x <= BASE_END && barricade_health > 0) {
|
if (target_x <= BASE_END && barricade_health > 0) {
|
||||||
// Change direction instead
|
// Change direction instead
|
||||||
@@ -321,6 +398,10 @@ void update_bandit(Bandit@ bandit, bool audio_active) {
|
|||||||
int target_x = bandit.position + direction;
|
int target_x = bandit.position + direction;
|
||||||
if (target_x < 0 || target_x >= MAP_SIZE) return;
|
if (target_x < 0 || target_x >= MAP_SIZE) return;
|
||||||
|
|
||||||
|
if (enforce_home && (target_x < bandit.home_start || target_x > bandit.home_end)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Don't enter base if barricade is up
|
// Don't enter base if barricade is up
|
||||||
if (target_x <= BASE_END && barricade_health > 0) {
|
if (target_x <= BASE_END && barricade_health > 0) {
|
||||||
try_attack_barricade_bandit(bandit);
|
try_attack_barricade_bandit(bandit);
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
// Config-driven system for spawning and managing flying creatures near water
|
// Config-driven system for spawning and managing flying creatures near water
|
||||||
|
|
||||||
string[] goose_sounds = {"sounds/game/goose.ogg"};
|
string[] goose_sounds = {"sounds/game/goose.ogg"};
|
||||||
|
string[] turkey_sounds = {"sounds/game/turkey.ogg"};
|
||||||
|
|
||||||
class FlyingCreatureConfig {
|
class FlyingCreatureConfig {
|
||||||
string id;
|
string id;
|
||||||
string drop_type;
|
string drop_type;
|
||||||
|
string spawn_mode; // "water" or "forest"
|
||||||
string[] sounds;
|
string[] sounds;
|
||||||
string fall_sound;
|
string fall_sound;
|
||||||
string impact_sound;
|
string impact_sound;
|
||||||
@@ -95,6 +97,7 @@ void init_flying_creature_configs() {
|
|||||||
FlyingCreatureConfig@ goose_cfg = FlyingCreatureConfig();
|
FlyingCreatureConfig@ goose_cfg = FlyingCreatureConfig();
|
||||||
goose_cfg.id = "goose";
|
goose_cfg.id = "goose";
|
||||||
goose_cfg.drop_type = "goose";
|
goose_cfg.drop_type = "goose";
|
||||||
|
goose_cfg.spawn_mode = "water";
|
||||||
goose_cfg.sounds = goose_sounds;
|
goose_cfg.sounds = goose_sounds;
|
||||||
goose_cfg.fall_sound = "sounds/actions/falling.ogg";
|
goose_cfg.fall_sound = "sounds/actions/falling.ogg";
|
||||||
goose_cfg.impact_sound = "sounds/game/game_falls.ogg";
|
goose_cfg.impact_sound = "sounds/game/game_falls.ogg";
|
||||||
@@ -114,6 +117,30 @@ void init_flying_creature_configs() {
|
|||||||
goose_cfg.sight_range = GOOSE_SIGHT_RANGE;
|
goose_cfg.sight_range = GOOSE_SIGHT_RANGE;
|
||||||
goose_cfg.flee_on_sight = false;
|
goose_cfg.flee_on_sight = false;
|
||||||
flying_creature_configs.insert_last(goose_cfg);
|
flying_creature_configs.insert_last(goose_cfg);
|
||||||
|
|
||||||
|
FlyingCreatureConfig@ turkey_cfg = FlyingCreatureConfig();
|
||||||
|
turkey_cfg.id = "turkey";
|
||||||
|
turkey_cfg.drop_type = "turkey";
|
||||||
|
turkey_cfg.spawn_mode = "forest";
|
||||||
|
turkey_cfg.sounds = turkey_sounds;
|
||||||
|
turkey_cfg.fall_sound = "sounds/actions/falling.ogg";
|
||||||
|
turkey_cfg.impact_sound = "sounds/game/game_falls.ogg";
|
||||||
|
turkey_cfg.health = TURKEY_HEALTH;
|
||||||
|
turkey_cfg.move_interval_min = TURKEY_MOVE_INTERVAL_MIN;
|
||||||
|
turkey_cfg.move_interval_max = TURKEY_MOVE_INTERVAL_MAX;
|
||||||
|
turkey_cfg.min_height = TURKEY_FLYING_HEIGHT_MIN;
|
||||||
|
turkey_cfg.max_height = TURKEY_FLYING_HEIGHT_MAX;
|
||||||
|
turkey_cfg.sound_volume_step = TURKEY_SOUND_VOLUME_STEP;
|
||||||
|
turkey_cfg.sound_delay_min = TURKEY_FLIGHT_SOUND_DELAY_MIN;
|
||||||
|
turkey_cfg.sound_delay_max = TURKEY_FLIGHT_SOUND_DELAY_MAX;
|
||||||
|
turkey_cfg.fall_speed = TURKEY_FALL_SPEED;
|
||||||
|
turkey_cfg.fly_away_chance = TURKEY_FLY_AWAY_CHANCE;
|
||||||
|
turkey_cfg.max_dist_from_water = TURKEY_MAX_DIST_FROM_FOREST;
|
||||||
|
turkey_cfg.hourly_spawn_chance = TURKEY_HOURLY_SPAWN_CHANCE;
|
||||||
|
turkey_cfg.max_count = TURKEY_MAX_COUNT;
|
||||||
|
turkey_cfg.sight_range = TURKEY_SIGHT_RANGE;
|
||||||
|
turkey_cfg.flee_on_sight = false;
|
||||||
|
flying_creature_configs.insert_last(turkey_cfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
FlyingCreatureConfig@ get_flying_creature_config(string creature_type) {
|
FlyingCreatureConfig@ get_flying_creature_config(string creature_type) {
|
||||||
@@ -169,30 +196,34 @@ int get_flying_creature_count(string creature_type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool get_random_flying_creature_area(FlyingCreatureConfig@ cfg, int &out area_start, int &out area_end) {
|
bool get_random_flying_creature_area(FlyingCreatureConfig@ cfg, int &out area_start, int &out area_end) {
|
||||||
int stream_count = int(world_streams.length());
|
if (cfg.spawn_mode == "forest") {
|
||||||
int mountain_stream_count = 0;
|
if (!get_random_forest_area(area_start, area_end)) return false;
|
||||||
for (uint i = 0; i < world_mountains.length(); i++) {
|
|
||||||
mountain_stream_count += int(world_mountains[i].stream_positions.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
int total_areas = stream_count + mountain_stream_count;
|
|
||||||
if (total_areas <= 0) return false;
|
|
||||||
|
|
||||||
int pick = random(0, total_areas - 1);
|
|
||||||
if (pick < stream_count) {
|
|
||||||
area_start = world_streams[pick].start_position;
|
|
||||||
area_end = world_streams[pick].end_position;
|
|
||||||
} else {
|
} else {
|
||||||
pick -= stream_count;
|
int stream_count = int(world_streams.length());
|
||||||
|
int mountain_stream_count = 0;
|
||||||
for (uint i = 0; i < world_mountains.length(); i++) {
|
for (uint i = 0; i < world_mountains.length(); i++) {
|
||||||
int local_count = int(world_mountains[i].stream_positions.length());
|
mountain_stream_count += int(world_mountains[i].stream_positions.length());
|
||||||
if (pick < local_count) {
|
}
|
||||||
int stream_pos = world_mountains[i].start_position + world_mountains[i].stream_positions[pick];
|
|
||||||
area_start = stream_pos;
|
int total_areas = stream_count + mountain_stream_count;
|
||||||
area_end = stream_pos;
|
if (total_areas <= 0) return false;
|
||||||
break;
|
|
||||||
|
int pick = random(0, total_areas - 1);
|
||||||
|
if (pick < stream_count) {
|
||||||
|
area_start = world_streams[pick].start_position;
|
||||||
|
area_end = world_streams[pick].end_position;
|
||||||
|
} else {
|
||||||
|
pick -= stream_count;
|
||||||
|
for (uint i = 0; i < world_mountains.length(); i++) {
|
||||||
|
int local_count = int(world_mountains[i].stream_positions.length());
|
||||||
|
if (pick < local_count) {
|
||||||
|
int stream_pos = world_mountains[i].start_position + world_mountains[i].stream_positions[pick];
|
||||||
|
area_start = stream_pos;
|
||||||
|
area_end = stream_pos;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pick -= local_count;
|
||||||
}
|
}
|
||||||
pick -= local_count;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,6 +234,56 @@ bool get_random_flying_creature_area(FlyingCreatureConfig@ cfg, int &out area_st
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get_random_forest_area(int &out area_start, int &out area_end) {
|
||||||
|
if (expanded_area_start == -1) return false;
|
||||||
|
int total = int(expanded_terrain_types.length());
|
||||||
|
if (total <= 0) return false;
|
||||||
|
|
||||||
|
int[] segment_starts;
|
||||||
|
int[] segment_ends;
|
||||||
|
int total_tiles = 0;
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
while (index < total) {
|
||||||
|
string terrain = expanded_terrain_types[index];
|
||||||
|
if (terrain.find("mountain:") == 0) {
|
||||||
|
terrain = terrain.substr(9);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (terrain == "forest" || terrain == "deep_forest") {
|
||||||
|
int segment_start = index;
|
||||||
|
while (index + 1 < total) {
|
||||||
|
string nextTerrain = expanded_terrain_types[index + 1];
|
||||||
|
if (nextTerrain.find("mountain:") == 0) {
|
||||||
|
nextTerrain = nextTerrain.substr(9);
|
||||||
|
}
|
||||||
|
if (nextTerrain != terrain) break;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
int segment_end = index;
|
||||||
|
segment_starts.insert_last(expanded_area_start + segment_start);
|
||||||
|
segment_ends.insert_last(expanded_area_start + segment_end);
|
||||||
|
total_tiles += (segment_end - segment_start + 1);
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (total_tiles <= 0) return false;
|
||||||
|
|
||||||
|
int pick = random(0, total_tiles - 1);
|
||||||
|
for (uint i = 0; i < segment_starts.length(); i++) {
|
||||||
|
int segment_len = segment_ends[i] - segment_starts[i] + 1;
|
||||||
|
if (pick < segment_len) {
|
||||||
|
area_start = segment_starts[i];
|
||||||
|
area_end = segment_ends[i];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
pick -= segment_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool find_flying_creature_spawn(FlyingCreatureConfig@ cfg, int &out spawn_x, int &out area_start, int &out area_end) {
|
bool find_flying_creature_spawn(FlyingCreatureConfig@ cfg, int &out spawn_x, int &out area_start, int &out area_end) {
|
||||||
if (!get_random_flying_creature_area(cfg, area_start, area_end)) return false;
|
if (!get_random_flying_creature_area(cfg, area_start, area_end)) return false;
|
||||||
|
|
||||||
|
|||||||
@@ -157,19 +157,35 @@ Undead@ get_undead_at(int pos) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void spawn_undead(const string &in undead_type = "zombie") {
|
int pick_undead_spawn_position(int range_start, int range_end) {
|
||||||
int spawn_x = -1;
|
int start = range_start;
|
||||||
|
int end = range_end;
|
||||||
|
if (start > end) {
|
||||||
|
int temp = start;
|
||||||
|
start = end;
|
||||||
|
end = temp;
|
||||||
|
}
|
||||||
|
|
||||||
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(start, end);
|
||||||
if (get_undead_at(candidate) == null) {
|
if (candidate == x) continue;
|
||||||
spawn_x = candidate;
|
if (get_undead_at(candidate) != null) continue;
|
||||||
break;
|
return candidate;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (spawn_x == -1) {
|
|
||||||
spawn_x = random(BASE_END + 1, MAP_SIZE - 1);
|
for (int candidate = start; candidate <= end; candidate++) {
|
||||||
|
if (candidate == x) continue;
|
||||||
|
if (get_undead_at(candidate) != null) continue;
|
||||||
|
return candidate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void spawn_undead(const string &in undead_type = "zombie") {
|
||||||
|
int spawn_x = pick_undead_spawn_position(BASE_END + 1, MAP_SIZE - 1);
|
||||||
|
if (spawn_x == -1) return;
|
||||||
|
|
||||||
Undead@ undead = Undead(spawn_x, undead_type);
|
Undead@ undead = Undead(spawn_x, undead_type);
|
||||||
undeads.insert_last(undead);
|
undeads.insert_last(undead);
|
||||||
// Play looping sound that follows the undead
|
// Play looping sound that follows the undead
|
||||||
@@ -354,8 +370,16 @@ void update_undead(Undead@ undead, bool audio_active) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
direction = random(-1, 1);
|
if (zombie_swarm_active && undead.undead_type == "zombie") {
|
||||||
if (direction == 0) return;
|
if (undead.position > BASE_END + 1) {
|
||||||
|
direction = -1;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
direction = random(-1, 1);
|
||||||
|
if (direction == 0) return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int target_x = undead.position + direction;
|
int target_x = undead.position + direction;
|
||||||
@@ -374,16 +398,31 @@ void update_undead(Undead@ undead, bool audio_active) {
|
|||||||
|
|
||||||
void update_undeads() {
|
void update_undeads() {
|
||||||
ensure_undead_range_audio_registration();
|
ensure_undead_range_audio_registration();
|
||||||
if (is_daytime) {
|
if (is_daytime && !zombie_swarm_active) {
|
||||||
clear_undeads();
|
clear_undeads();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_daytime && zombie_swarm_active) {
|
||||||
|
for (int i = int(undeads.length()) - 1; i >= 0; i--) {
|
||||||
|
if (undeads[i].undead_type != "zombie") {
|
||||||
|
if (undeads[i].sound_handle != -1) {
|
||||||
|
p.destroy_sound(undeads[i].sound_handle);
|
||||||
|
undeads[i].sound_handle = -1;
|
||||||
|
}
|
||||||
|
undeads.remove_at(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int extra = 0;
|
int extra = 0;
|
||||||
if (MAP_SIZE > 35) {
|
if (MAP_SIZE > 35) {
|
||||||
extra = (MAP_SIZE - 35) / 15;
|
extra = (MAP_SIZE - 35) / 15;
|
||||||
}
|
}
|
||||||
int maxCount = ZOMBIE_MAX_COUNT + extra;
|
int maxCount = ZOMBIE_MAX_COUNT + extra;
|
||||||
|
if (zombie_swarm_active) {
|
||||||
|
maxCount += ZOMBIE_SWARM_ZOMBIE_MAX_BONUS;
|
||||||
|
}
|
||||||
if (maxCount > ZOMBIE_MAX_COUNT_CAP) maxCount = ZOMBIE_MAX_COUNT_CAP;
|
if (maxCount > ZOMBIE_MAX_COUNT_CAP) maxCount = ZOMBIE_MAX_COUNT_CAP;
|
||||||
|
|
||||||
int zombie_count = 0;
|
int zombie_count = 0;
|
||||||
@@ -401,9 +440,11 @@ void update_undeads() {
|
|||||||
zombie_count++;
|
zombie_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (undead_resident_count < undead_residents_count) {
|
if (!is_daytime) {
|
||||||
spawn_undead("undead_resident");
|
while (undead_resident_count < undead_residents_count) {
|
||||||
undead_resident_count++;
|
spawn_undead("undead_resident");
|
||||||
|
undead_resident_count++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int[] areaStarts;
|
int[] areaStarts;
|
||||||
|
|||||||
@@ -316,12 +316,37 @@ int count_trees_in_area(int areaStart, int areaEnd, Tree@ ignoreTree) {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool is_near_required_climb(int pos, int radius) {
|
||||||
|
MountainRange@ mountain = get_mountain_at(pos);
|
||||||
|
if (mountain is null) return false;
|
||||||
|
|
||||||
|
int startPos = mountain.start_position;
|
||||||
|
int endPos = mountain.end_position;
|
||||||
|
int edgeStart = pos - radius - 1;
|
||||||
|
int edgeEnd = pos + radius;
|
||||||
|
|
||||||
|
if (edgeStart < startPos) edgeStart = startPos;
|
||||||
|
if (edgeEnd > endPos - 1) edgeEnd = endPos - 1;
|
||||||
|
|
||||||
|
for (int xPos = edgeStart; xPos <= edgeEnd; xPos++) {
|
||||||
|
if (mountain.is_steep_section(xPos, xPos + 1)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool tree_too_close_in_area(int pos, int areaStart, int areaEnd, Tree@ ignoreTree) {
|
bool tree_too_close_in_area(int pos, int areaStart, int areaEnd, Tree@ ignoreTree) {
|
||||||
// Keep trees away from the base edge
|
// Keep trees away from the base edge
|
||||||
if (pos < BASE_END + 5) {
|
if (pos < BASE_END + 5) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_near_required_climb(pos, TREE_AVOID_STEEP_CLIMB_RANGE)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
for (uint i = 0; i < trees.length(); i++) {
|
for (uint i = 0; i < trees.length(); i++) {
|
||||||
if (@trees[i] is ignoreTree) continue;
|
if (@trees[i] is ignoreTree) continue;
|
||||||
if (trees[i].position < areaStart || trees[i].position > areaEnd) continue;
|
if (trees[i].position < areaStart || trees[i].position > areaEnd) continue;
|
||||||
@@ -883,14 +908,19 @@ void start_climbing_tree(int target_x) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ground_elevation = get_mountain_elevation_at(target_x);
|
||||||
climbing = true;
|
climbing = true;
|
||||||
climb_target_y = tree.height;
|
climb_target_y = ground_elevation + tree.height;
|
||||||
climb_timer.restart();
|
climb_timer.restart();
|
||||||
speak_with_history("Started climbing tree. Height is " + tree.height + " feet.", true);
|
speak_with_history("Started climbing tree. Height is " + tree.height + " feet.", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void update_climbing() {
|
void update_climbing() {
|
||||||
if (!climbing) return;
|
if (!climbing) return;
|
||||||
|
if (y == climb_target_y) {
|
||||||
|
climbing = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Climb at 1 foot per 500ms
|
// Climb at 1 foot per 500ms
|
||||||
if (climb_timer.elapsed > 500) {
|
if (climb_timer.elapsed > 500) {
|
||||||
@@ -903,7 +933,9 @@ void update_climbing() {
|
|||||||
|
|
||||||
if (y >= climb_target_y) {
|
if (y >= climb_target_y) {
|
||||||
climbing = false;
|
climbing = false;
|
||||||
speak_with_history("Reached the top at " + y + " feet.", true);
|
int ground_elevation = get_mountain_elevation_at(x);
|
||||||
|
int height_above_ground = y - ground_elevation;
|
||||||
|
speak_with_history("Reached the top at " + height_above_ground + " feet.", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Climbing down
|
// Climbing down
|
||||||
@@ -911,9 +943,9 @@ void update_climbing() {
|
|||||||
y--;
|
y--;
|
||||||
p.play_stationary("sounds/actions/climb_tree.ogg", false);
|
p.play_stationary("sounds/actions/climb_tree.ogg", false);
|
||||||
|
|
||||||
if (y <= 0) {
|
if (y <= climb_target_y) {
|
||||||
climbing = false;
|
climbing = false;
|
||||||
y = 0;
|
y = climb_target_y;
|
||||||
speak_with_history("Safely reached the ground.", true);
|
speak_with_history("Safely reached the ground.", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -921,10 +953,11 @@ void update_climbing() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void climb_down_tree() {
|
void climb_down_tree() {
|
||||||
if (y == 0 || climbing) return;
|
int ground_elevation = get_mountain_elevation_at(x);
|
||||||
|
if (y == ground_elevation || climbing) return;
|
||||||
|
|
||||||
climbing = true;
|
climbing = true;
|
||||||
climb_target_y = 0;
|
climb_target_y = ground_elevation;
|
||||||
climb_timer.restart();
|
climb_timer.restart();
|
||||||
speak_with_history("Climbing down.", true);
|
speak_with_history("Climbing down.", true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -625,6 +625,13 @@ void reset_game_state() {
|
|||||||
invasion_triggered_today = false;
|
invasion_triggered_today = false;
|
||||||
invasion_roll_done_today = false;
|
invasion_roll_done_today = false;
|
||||||
invasion_scheduled_hour = -1;
|
invasion_scheduled_hour = -1;
|
||||||
|
invasion_started_once = false;
|
||||||
|
zombie_swarm_active = false;
|
||||||
|
zombie_swarm_start_hour = -1;
|
||||||
|
zombie_swarm_scheduled_hour = -1;
|
||||||
|
zombie_swarm_triggered_today = false;
|
||||||
|
zombie_swarm_roll_done_today = false;
|
||||||
|
zombie_swarm_duration_hours = 0;
|
||||||
quest_roll_done_today = false;
|
quest_roll_done_today = false;
|
||||||
quest_queue.resize(0);
|
quest_queue.resize(0);
|
||||||
playerItemBreakChance = PLAYER_ITEM_BREAK_CHANCE_MIN;
|
playerItemBreakChance = PLAYER_ITEM_BREAK_CHANCE_MIN;
|
||||||
@@ -687,7 +694,7 @@ string serialize_stream(WorldStream@ stream) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
string serialize_bandit(Bandit@ bandit) {
|
string serialize_bandit(Bandit@ bandit) {
|
||||||
return bandit.position + "|" + bandit.health + "|" + bandit.weapon_type + "|" + bandit.behavior_state + "|" + bandit.wander_direction + "|" + bandit.move_interval + "|" + bandit.invader_type;
|
return bandit.position + "|" + bandit.health + "|" + bandit.weapon_type + "|" + bandit.behavior_state + "|" + bandit.wander_direction + "|" + bandit.move_interval + "|" + bandit.invader_type + "|" + bandit.home_start + "|" + bandit.home_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
string serialize_mountain(MountainRange@ mountain) {
|
string serialize_mountain(MountainRange@ mountain) {
|
||||||
@@ -879,6 +886,13 @@ bool save_game_state() {
|
|||||||
saveData.set("time_invasion_roll_done_today", invasion_roll_done_today);
|
saveData.set("time_invasion_roll_done_today", invasion_roll_done_today);
|
||||||
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("time_invasion_started_once", invasion_started_once);
|
||||||
|
saveData.set("time_zombie_swarm_active", zombie_swarm_active);
|
||||||
|
saveData.set("time_zombie_swarm_start_hour", zombie_swarm_start_hour);
|
||||||
|
saveData.set("time_zombie_swarm_scheduled_hour", zombie_swarm_scheduled_hour);
|
||||||
|
saveData.set("time_zombie_swarm_triggered_today", zombie_swarm_triggered_today);
|
||||||
|
saveData.set("time_zombie_swarm_roll_done_today", zombie_swarm_roll_done_today);
|
||||||
|
saveData.set("time_zombie_swarm_duration_hours", zombie_swarm_duration_hours);
|
||||||
saveData.set("player_item_break_chance", playerItemBreakChance);
|
saveData.set("player_item_break_chance", playerItemBreakChance);
|
||||||
saveData.set("player_item_breaks_today", playerItemBreaksToday);
|
saveData.set("player_item_breaks_today", playerItemBreaksToday);
|
||||||
saveData.set("player_item_break_pending", playerItemBreakPending);
|
saveData.set("player_item_break_pending", playerItemBreakPending);
|
||||||
@@ -1306,10 +1320,29 @@ bool load_game_state_from_file(const string&in filename) {
|
|||||||
if (invasion_enemy_type == "") {
|
if (invasion_enemy_type == "") {
|
||||||
invasion_enemy_type = "bandit";
|
invasion_enemy_type = "bandit";
|
||||||
}
|
}
|
||||||
|
bool loaded_invasion_started = false;
|
||||||
|
if (saveData.get("time_invasion_started_once", loaded_invasion_started)) {
|
||||||
|
invasion_started_once = loaded_invasion_started;
|
||||||
|
} else {
|
||||||
|
invasion_started_once = (expanded_area_start != -1);
|
||||||
|
}
|
||||||
|
zombie_swarm_active = get_bool(saveData, "time_zombie_swarm_active", false);
|
||||||
|
zombie_swarm_start_hour = int(get_number(saveData, "time_zombie_swarm_start_hour", -1));
|
||||||
|
zombie_swarm_scheduled_hour = int(get_number(saveData, "time_zombie_swarm_scheduled_hour", -1));
|
||||||
|
zombie_swarm_triggered_today = get_bool(saveData, "time_zombie_swarm_triggered_today", false);
|
||||||
|
zombie_swarm_roll_done_today = get_bool(saveData, "time_zombie_swarm_roll_done_today", false);
|
||||||
|
zombie_swarm_duration_hours = int(get_number(saveData, "time_zombie_swarm_duration_hours", 0));
|
||||||
if (invasion_chance < 0) invasion_chance = 0;
|
if (invasion_chance < 0) invasion_chance = 0;
|
||||||
if (invasion_chance > 100) invasion_chance = 100;
|
if (invasion_chance > 100) invasion_chance = 100;
|
||||||
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;
|
||||||
|
if (zombie_swarm_start_hour < -1 || zombie_swarm_start_hour > 23) zombie_swarm_start_hour = -1;
|
||||||
|
if (zombie_swarm_scheduled_hour < -1 || zombie_swarm_scheduled_hour > 23) zombie_swarm_scheduled_hour = -1;
|
||||||
|
if (zombie_swarm_duration_hours < 0) zombie_swarm_duration_hours = 0;
|
||||||
|
if (!zombie_swarm_active) {
|
||||||
|
zombie_swarm_start_hour = -1;
|
||||||
|
zombie_swarm_duration_hours = 0;
|
||||||
|
}
|
||||||
playerItemBreakChance = float(get_number(saveData, "player_item_break_chance", PLAYER_ITEM_BREAK_CHANCE_MIN));
|
playerItemBreakChance = float(get_number(saveData, "player_item_break_chance", PLAYER_ITEM_BREAK_CHANCE_MIN));
|
||||||
playerItemBreaksToday = int(get_number(saveData, "player_item_breaks_today", 0));
|
playerItemBreaksToday = int(get_number(saveData, "player_item_breaks_today", 0));
|
||||||
playerItemBreakPending = get_bool(saveData, "player_item_break_pending", false);
|
playerItemBreakPending = get_bool(saveData, "player_item_break_pending", false);
|
||||||
@@ -1484,13 +1517,28 @@ bool load_game_state_from_file(const string&in filename) {
|
|||||||
int wander_dir = parse_int(parts[4]);
|
int wander_dir = parse_int(parts[4]);
|
||||||
int move_int = parse_int(parts[5]);
|
int move_int = parse_int(parts[5]);
|
||||||
string invader_type = "bandit";
|
string invader_type = "bandit";
|
||||||
|
int home_start = pos;
|
||||||
|
int home_end = pos;
|
||||||
if (parts.length() >= 7) {
|
if (parts.length() >= 7) {
|
||||||
invader_type = parts[6];
|
invader_type = parts[6];
|
||||||
if (invader_type == "") invader_type = "bandit";
|
if (invader_type == "") invader_type = "bandit";
|
||||||
}
|
}
|
||||||
|
if (parts.length() >= 9) {
|
||||||
|
home_start = parse_int(parts[7]);
|
||||||
|
home_end = parse_int(parts[8]);
|
||||||
|
} else {
|
||||||
|
if (expanded_area_start != -1 && pos >= expanded_area_start) {
|
||||||
|
int area_start = -1;
|
||||||
|
int area_end = -1;
|
||||||
|
if (get_audio_area_bounds_for_position(pos, area_start, area_end)) {
|
||||||
|
home_start = area_start;
|
||||||
|
home_end = area_end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create bandit with dummy expansion area (position will be overridden)
|
// Create bandit with dummy expansion area (position will be overridden)
|
||||||
Bandit@ b = Bandit(pos, pos, pos, invader_type);
|
Bandit@ b = Bandit(pos, home_start, home_end, invader_type);
|
||||||
b.position = pos;
|
b.position = pos;
|
||||||
b.health = health;
|
b.health = health;
|
||||||
b.weapon_type = weapon;
|
b.weapon_type = weapon;
|
||||||
|
|||||||
@@ -38,6 +38,15 @@ bool invasion_triggered_today = false;
|
|||||||
bool invasion_roll_done_today = false;
|
bool invasion_roll_done_today = false;
|
||||||
int invasion_scheduled_hour = -1;
|
int invasion_scheduled_hour = -1;
|
||||||
string invasion_enemy_type = "bandit";
|
string invasion_enemy_type = "bandit";
|
||||||
|
bool invasion_started_once = false;
|
||||||
|
|
||||||
|
// Zombie swarm tracking
|
||||||
|
bool zombie_swarm_active = false;
|
||||||
|
int zombie_swarm_start_hour = -1;
|
||||||
|
int zombie_swarm_scheduled_hour = -1;
|
||||||
|
bool zombie_swarm_triggered_today = false;
|
||||||
|
bool zombie_swarm_roll_done_today = false;
|
||||||
|
int zombie_swarm_duration_hours = 0;
|
||||||
|
|
||||||
// Invasion mapping: "terrain=enemy" (defaults to bandits when no match)
|
// Invasion mapping: "terrain=enemy" (defaults to bandits when no match)
|
||||||
// Terrain keys: "mountain" for mountain ranges, or regular types like "grass", "snow", "forest", "deep_forest", "stone".
|
// Terrain keys: "mountain" for mountain ranges, or regular types like "grass", "snow", "forest", "deep_forest", "stone".
|
||||||
@@ -61,6 +70,13 @@ void init_time() {
|
|||||||
invasion_roll_done_today = false;
|
invasion_roll_done_today = false;
|
||||||
invasion_scheduled_hour = -1;
|
invasion_scheduled_hour = -1;
|
||||||
invasion_enemy_type = "bandit";
|
invasion_enemy_type = "bandit";
|
||||||
|
invasion_started_once = false;
|
||||||
|
zombie_swarm_active = false;
|
||||||
|
zombie_swarm_start_hour = -1;
|
||||||
|
zombie_swarm_scheduled_hour = -1;
|
||||||
|
zombie_swarm_triggered_today = false;
|
||||||
|
zombie_swarm_roll_done_today = false;
|
||||||
|
zombie_swarm_duration_hours = 0;
|
||||||
reset_player_item_break_state();
|
reset_player_item_break_state();
|
||||||
update_ambience(true); // Force start
|
update_ambience(true); // Force start
|
||||||
}
|
}
|
||||||
@@ -311,6 +327,7 @@ void start_invasion() {
|
|||||||
invasion_active = true;
|
invasion_active = true;
|
||||||
invasion_start_hour = current_hour;
|
invasion_start_hour = current_hour;
|
||||||
invasion_enemy_type = get_invasion_enemy_type_for_terrain(expansion_terrain);
|
invasion_enemy_type = get_invasion_enemy_type_for_terrain(expansion_terrain);
|
||||||
|
invasion_started_once = true;
|
||||||
string source = (expansion_terrain == "mountain") ? "the mountains" : "the new area";
|
string source = (expansion_terrain == "mountain") ? "the mountains" : "the new area";
|
||||||
string enemy_plural = get_invasion_enemy_plural(invasion_enemy_type);
|
string enemy_plural = get_invasion_enemy_plural(invasion_enemy_type);
|
||||||
notify(enemy_plural + " are invading from " + source + "!");
|
notify(enemy_plural + " are invading from " + source + "!");
|
||||||
@@ -377,6 +394,144 @@ void attempt_daily_invasion() {
|
|||||||
check_scheduled_invasion();
|
check_scheduled_invasion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool can_roll_zombie_swarm_today() {
|
||||||
|
return current_day >= ZOMBIE_SWARM_START_DAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_zombie_swarm_duration_hours() {
|
||||||
|
int offset = current_day - ZOMBIE_SWARM_START_DAY;
|
||||||
|
if (offset < 0) offset = 0;
|
||||||
|
int cycles = offset / ZOMBIE_SWARM_INTERVAL_DAYS;
|
||||||
|
int duration = ZOMBIE_SWARM_BASE_DURATION_HOURS + (cycles * ZOMBIE_SWARM_DURATION_STEP_HOURS);
|
||||||
|
if (duration < 1) duration = 1;
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_zombie_swarm_chance_for_day() {
|
||||||
|
int offset = current_day - ZOMBIE_SWARM_START_DAY;
|
||||||
|
if (offset < 0) offset = 0;
|
||||||
|
int cycles = offset / ZOMBIE_SWARM_CHANCE_INTERVAL_DAYS;
|
||||||
|
int chance = ZOMBIE_SWARM_CHANCE_START + (cycles * ZOMBIE_SWARM_CHANCE_STEP);
|
||||||
|
if (chance > ZOMBIE_SWARM_CHANCE_CAP) chance = ZOMBIE_SWARM_CHANCE_CAP;
|
||||||
|
if (chance < 0) chance = 0;
|
||||||
|
return chance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void start_zombie_swarm() {
|
||||||
|
zombie_swarm_active = true;
|
||||||
|
zombie_swarm_start_hour = current_hour;
|
||||||
|
zombie_swarm_duration_hours = get_zombie_swarm_duration_hours();
|
||||||
|
speak_with_history("A swarm of zombies has been spotted.", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void end_zombie_swarm() {
|
||||||
|
zombie_swarm_active = false;
|
||||||
|
zombie_swarm_start_hour = -1;
|
||||||
|
zombie_swarm_duration_hours = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void check_zombie_swarm_status() {
|
||||||
|
if (!zombie_swarm_active) return;
|
||||||
|
int hours_elapsed = current_hour - zombie_swarm_start_hour;
|
||||||
|
if (hours_elapsed < 0) {
|
||||||
|
hours_elapsed += 24;
|
||||||
|
}
|
||||||
|
if (hours_elapsed >= zombie_swarm_duration_hours) {
|
||||||
|
end_zombie_swarm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void schedule_zombie_swarm() {
|
||||||
|
if (zombie_swarm_scheduled_hour != -1) return;
|
||||||
|
int hour = get_random_invasion_hour(current_hour);
|
||||||
|
if (hour == -1) return;
|
||||||
|
zombie_swarm_scheduled_hour = hour;
|
||||||
|
}
|
||||||
|
|
||||||
|
void check_scheduled_zombie_swarm() {
|
||||||
|
if (zombie_swarm_active) return;
|
||||||
|
if (zombie_swarm_scheduled_hour != -1) {
|
||||||
|
if (current_hour == zombie_swarm_scheduled_hour) {
|
||||||
|
zombie_swarm_scheduled_hour = -1;
|
||||||
|
zombie_swarm_triggered_today = true;
|
||||||
|
start_zombie_swarm();
|
||||||
|
} else if (current_hour > 11) {
|
||||||
|
zombie_swarm_scheduled_hour = -1;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (zombie_swarm_triggered_today) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void attempt_daily_zombie_swarm() {
|
||||||
|
if (!can_roll_zombie_swarm_today()) return;
|
||||||
|
if (zombie_swarm_roll_done_today || zombie_swarm_triggered_today || zombie_swarm_active) return;
|
||||||
|
if (current_hour < 6) return;
|
||||||
|
|
||||||
|
zombie_swarm_roll_done_today = true;
|
||||||
|
int chance = get_zombie_swarm_chance_for_day();
|
||||||
|
int roll = random(1, 100);
|
||||||
|
if (roll > chance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_hour > 11) {
|
||||||
|
zombie_swarm_triggered_today = true;
|
||||||
|
start_zombie_swarm();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
schedule_zombie_swarm();
|
||||||
|
check_scheduled_zombie_swarm();
|
||||||
|
}
|
||||||
|
|
||||||
|
void get_expanded_area_segments(int[]@ areaStarts, int[]@ areaEnds, string[]@ areaTypes) {
|
||||||
|
areaStarts.resize(0);
|
||||||
|
areaEnds.resize(0);
|
||||||
|
areaTypes.resize(0);
|
||||||
|
if (expanded_area_start == -1) return;
|
||||||
|
int total = int(expanded_terrain_types.length());
|
||||||
|
if (total <= 0) return;
|
||||||
|
|
||||||
|
string current_type = get_expanded_area_type(0);
|
||||||
|
int segment_start = 0;
|
||||||
|
for (int i = 1; i < total; i++) {
|
||||||
|
string segment_type = get_expanded_area_type(i);
|
||||||
|
if (segment_type != current_type) {
|
||||||
|
areaStarts.insert_last(expanded_area_start + segment_start);
|
||||||
|
areaEnds.insert_last(expanded_area_start + i - 1);
|
||||||
|
areaTypes.insert_last(current_type);
|
||||||
|
segment_start = i;
|
||||||
|
current_type = segment_type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
areaStarts.insert_last(expanded_area_start + segment_start);
|
||||||
|
areaEnds.insert_last(expanded_area_start + total - 1);
|
||||||
|
areaTypes.insert_last(current_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void attempt_expansion_roamer_spawn() {
|
||||||
|
if (!is_daytime) return;
|
||||||
|
if (invasion_active) return;
|
||||||
|
if (!invasion_started_once) return;
|
||||||
|
if (expanded_area_start == -1) return;
|
||||||
|
if (current_hour % EXPANSION_ROAMER_SPAWN_INTERVAL_HOURS != 0) return;
|
||||||
|
|
||||||
|
int[] areaStarts;
|
||||||
|
int[] areaEnds;
|
||||||
|
string[] areaTypes;
|
||||||
|
get_expanded_area_segments(areaStarts, areaEnds, areaTypes);
|
||||||
|
if (areaStarts.length() == 0) return;
|
||||||
|
|
||||||
|
for (uint i = 0; i < areaStarts.length(); i++) {
|
||||||
|
int count = count_bandits_in_range(areaStarts[i], areaEnds[i]);
|
||||||
|
if (count >= EXPANSION_ROAMER_MAX_PER_AREA) continue;
|
||||||
|
string invader_type = get_invasion_enemy_type_for_terrain(areaTypes[i]);
|
||||||
|
spawn_bandit(areaStarts[i], areaEnds[i], invader_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void attempt_resident_recruitment() {
|
void attempt_resident_recruitment() {
|
||||||
if (barricade_health <= 0) {
|
if (barricade_health <= 0) {
|
||||||
return;
|
return;
|
||||||
@@ -561,6 +716,9 @@ void update_time() {
|
|||||||
invasion_triggered_today = false;
|
invasion_triggered_today = false;
|
||||||
invasion_roll_done_today = false;
|
invasion_roll_done_today = false;
|
||||||
invasion_scheduled_hour = -1;
|
invasion_scheduled_hour = -1;
|
||||||
|
zombie_swarm_triggered_today = false;
|
||||||
|
zombie_swarm_scheduled_hour = -1;
|
||||||
|
zombie_swarm_roll_done_today = false;
|
||||||
quest_roll_done_today = false;
|
quest_roll_done_today = false;
|
||||||
playerItemBreaksToday = 0;
|
playerItemBreaksToday = 0;
|
||||||
}
|
}
|
||||||
@@ -585,6 +743,7 @@ void update_time() {
|
|||||||
|
|
||||||
// Check invasion status
|
// Check invasion status
|
||||||
check_invasion_status();
|
check_invasion_status();
|
||||||
|
check_zombie_swarm_status();
|
||||||
|
|
||||||
check_ambience_transition();
|
check_ambience_transition();
|
||||||
// Safety: if crossfade failed or was skipped, align day/night with the current hour.
|
// Safety: if crossfade failed or was skipped, align day/night with the current hour.
|
||||||
@@ -629,6 +788,7 @@ void update_time() {
|
|||||||
attempt_resident_foraging();
|
attempt_resident_foraging();
|
||||||
}
|
}
|
||||||
attempt_daily_invasion();
|
attempt_daily_invasion();
|
||||||
|
attempt_daily_zombie_swarm();
|
||||||
keep_base_fires_fed();
|
keep_base_fires_fed();
|
||||||
attempt_player_item_break_check();
|
attempt_player_item_break_check();
|
||||||
update_incense_burning();
|
update_incense_burning();
|
||||||
@@ -637,6 +797,8 @@ void update_time() {
|
|||||||
attempt_hourly_wight_spawn();
|
attempt_hourly_wight_spawn();
|
||||||
attempt_hourly_vampyr_spawn();
|
attempt_hourly_vampyr_spawn();
|
||||||
check_scheduled_invasion();
|
check_scheduled_invasion();
|
||||||
|
check_scheduled_zombie_swarm();
|
||||||
|
attempt_expansion_roamer_spawn();
|
||||||
attempt_blessing();
|
attempt_blessing();
|
||||||
check_weather_transition();
|
check_weather_transition();
|
||||||
attempt_resident_collection();
|
attempt_resident_collection();
|
||||||
|
|||||||
Reference in New Issue
Block a user