A ton of changes including bug fixes, new craftable items, altar with favor system added. Info for player and base added.

This commit is contained in:
Storm Dragon
2026-01-18 22:25:38 -05:00
parent 95a3f4a96c
commit 489408a181
40 changed files with 3910 additions and 1046 deletions

View File

@@ -12,11 +12,23 @@ bool is_daytime = true;
bool sun_setting_warned = false;
bool sunrise_warned = false;
// Crossfade state
bool crossfade_active = false;
bool crossfade_to_night = false; // true = fading to night, false = fading to day
timer crossfade_timer;
const int CROSSFADE_DURATION = 60000; // 1 minute (1 game hour)
const float CROSSFADE_MIN_VOLUME = -40.0; // dB, effectively silent but not extreme
const float CROSSFADE_MAX_VOLUME = 0.0; // dB, full volume
// Expansion and invasion tracking
bool area_expanded_today = false;
bool invasion_active = false;
int invasion_start_hour = -1;
string[] expanded_terrain_types;
int invasion_chance = 25;
bool invasion_triggered_today = false;
bool invasion_roll_done_today = false;
int invasion_scheduled_hour = -1;
void init_time() {
current_hour = 8;
@@ -25,23 +37,29 @@ void init_time() {
is_daytime = true;
sun_setting_warned = false;
sunrise_warned = false;
crossfade_active = false;
crossfade_to_night = false;
area_expanded_today = false;
invasion_active = false;
invasion_start_hour = -1;
invasion_chance = 25;
invasion_triggered_today = false;
invasion_roll_done_today = false;
invasion_scheduled_hour = -1;
update_ambience(true); // Force start
}
void expand_area() {
if (expanded_area_start != -1) {
return; // Already expanded
}
// Play invasion sound
p.play_stationary("sounds/enemies/invasion.ogg", false);
// Calculate new area
expanded_area_start = MAP_SIZE;
expanded_area_end = MAP_SIZE + EXPANSION_SIZE - 1;
int new_start = MAP_SIZE;
int new_end = MAP_SIZE + EXPANSION_SIZE - 1;
if (expanded_area_start == -1) {
expanded_area_start = new_start;
}
expanded_area_end = new_end;
MAP_SIZE += EXPANSION_SIZE;
// Generate a single terrain type for the entire new area
@@ -55,9 +73,8 @@ void expand_area() {
terrain_type = "snow";
}
expanded_terrain_types.resize(EXPANSION_SIZE);
for (int i = 0; i < EXPANSION_SIZE; i++) {
expanded_terrain_types[i] = terrain_type;
expanded_terrain_types.insert_last(terrain_type);
}
// Place exactly one feature: either a stream or a tree
@@ -66,7 +83,7 @@ void expand_area() {
if (place_stream) {
int stream_width = random(1, 5);
int stream_start = random(0, EXPANSION_SIZE - stream_width);
int actual_start = expanded_area_start + stream_start;
int actual_start = new_start + stream_start;
add_world_stream(actual_start, stream_width);
string width_desc = "very small";
@@ -77,7 +94,7 @@ void expand_area() {
notify("A " + width_desc + " stream flows through the new area at x " + actual_start + ".");
} else {
int tree_pos = random(expanded_area_start, expanded_area_end);
int tree_pos = random(new_start, new_end);
Tree@ t = Tree(tree_pos);
trees.insert_last(t);
}
@@ -87,16 +104,97 @@ void expand_area() {
}
void start_invasion() {
expand_area();
invasion_active = true;
invasion_start_hour = current_hour;
notify("Bandits are invading from the new area!");
}
void update_invasion_chance_for_new_day() {
if (current_day == 2) {
invasion_chance = 100;
return;
}
if (current_day > 2) {
if (invasion_triggered_today) {
invasion_chance = 25;
} else {
invasion_chance += 25;
if (invasion_chance > 100) invasion_chance = 100;
}
}
}
int get_random_invasion_hour(int min_hour) {
if (min_hour < 6) min_hour = 6;
if (min_hour > 11) return -1;
return random(min_hour, 11);
}
void schedule_invasion() {
if (invasion_scheduled_hour != -1) return;
int hour = get_random_invasion_hour(current_hour);
if (hour == -1) return;
invasion_scheduled_hour = hour;
}
void check_scheduled_invasion() {
if (invasion_active || invasion_triggered_today) return;
if (invasion_scheduled_hour == -1) return;
if (current_hour == invasion_scheduled_hour) {
invasion_scheduled_hour = -1;
invasion_triggered_today = true;
start_invasion();
} else if (current_hour > 11) {
invasion_scheduled_hour = -1;
}
}
void attempt_daily_invasion() {
if (current_day < 2) return;
if (invasion_triggered_today || invasion_active) return;
if (invasion_roll_done_today) return;
invasion_roll_done_today = true;
int roll = random(1, 100);
if (roll > invasion_chance) return;
schedule_invasion();
check_scheduled_invasion();
}
void attempt_resident_recruitment() {
if (barricade_health <= 0) {
return;
}
int chance = random(25, 35);
int roll = random(1, 100);
if (roll > chance) {
return;
}
int added = random(1, 3);
residents_count += added;
string join_message = (added == 1) ? "A survivor joins your base." : "" + added + " survivors join your base.";
notify(join_message);
}
void end_invasion() {
invasion_active = false;
invasion_start_hour = -1;
clear_bandits();
transition_bandits_to_wandering();
notify("The bandit invasion has ended.");
attempt_resident_recruitment();
}
void transition_bandits_to_wandering() {
for (uint i = 0; i < bandits.length(); i++) {
bandits[i].behavior_state = "wandering";
bandits[i].wander_direction = random(-1, 1);
bandits[i].wander_direction_change_interval = random(BANDIT_WANDER_DIRECTION_CHANGE_MIN, BANDIT_WANDER_DIRECTION_CHANGE_MAX);
bandits[i].wander_direction_timer.restart();
}
}
void check_invasion_status() {
@@ -114,18 +212,62 @@ void check_invasion_status() {
}
void manage_bandits_during_invasion() {
if (!invasion_active) return;
if (expanded_area_start == -1) return;
// Bandits only appear during daytime (6 AM to 7 PM)
// Clear ALL bandits at nighttime (undead eat them)
if (!is_daytime) {
clear_bandits();
return;
}
// Maintain BANDIT_MAX_COUNT bandits during invasion
while (bandits.length() < BANDIT_MAX_COUNT) {
spawn_bandit(expanded_area_start, expanded_area_end);
// During daytime: if invasion is active, maintain bandit count
if (invasion_active && expanded_area_start != -1) {
while (bandits.length() < BANDIT_MAX_COUNT) {
spawn_bandit(expanded_area_start, expanded_area_end);
}
}
// If invasion not active, wandering bandits persist during daytime
}
void update_blessings() {
if (blessing_speed_active && blessing_speed_timer.elapsed >= BLESSING_SPEED_DURATION) {
blessing_speed_active = false;
update_max_health_from_equipment();
screen_reader_speak("The speed blessing fades.", true);
}
}
void attempt_blessing() {
if (favor < 1.0) return;
int roll = random(1, 100);
if (roll > BLESSING_TRIGGER_CHANCE) return;
int[] options;
if (player_health < max_health) options.insert_last(0);
if (!blessing_speed_active) options.insert_last(1);
if (barricade_health < BARRICADE_MAX_HEALTH) options.insert_last(2);
if (options.length() == 0) return;
int choice = options[random(0, options.length() - 1)];
favor -= 1.0;
if (favor < 0) favor = 0;
if (choice == 0) {
int before = player_health;
player_health += BLESSING_HEAL_AMOUNT;
if (player_health > max_health) player_health = max_health;
int healed = player_health - before;
string bonus = (healed > 0) ? "You feel restored. +" + healed + " health." : "You feel restored.";
notify("The gods' favor shines upon you. " + bonus);
} else if (choice == 1) {
blessing_speed_active = true;
blessing_speed_timer.restart();
update_max_health_from_equipment();
notify("The gods' favor shines upon you. You feel swift for a while.");
} else if (choice == 2) {
int gained = add_barricade_health(BLESSING_BARRICADE_REPAIR);
string bonus = (gained > 0)
? "A divine force repairs the barricade. +" + gained + " health."
: "A divine force surrounds the barricade.";
notify("The gods' favor shines upon you. " + bonus);
}
}
@@ -137,6 +279,12 @@ void update_time() {
current_hour = 0;
current_day++;
area_expanded_today = false; // Reset for new day
update_invasion_chance_for_new_day();
invasion_triggered_today = false;
invasion_roll_done_today = false;
invasion_scheduled_hour = -1;
quest_roll_done_today = false;
consume_food_for_residents();
}
if (current_hour == 18 && !sun_setting_warned) {
@@ -152,24 +300,32 @@ void update_time() {
sunrise_warned = false;
}
// Check for area expansion (day 2+, daytime morning before noon, not yet expanded today)
if (current_day >= 2 && current_hour >= 6 && current_hour < 12 && !area_expanded_today && expanded_area_start == -1) {
int roll = random(1, 100);
if (roll <= EXPANSION_CHANCE) {
expand_area();
// Start invasion immediately after expansion (morning, during daytime)
start_invasion();
}
}
// Check invasion status
check_invasion_status();
check_ambience_transition();
// TODO: add resident defense using stored weapons once storage exists.
if (is_daytime && residents_count > 0 && barricade_health < BARRICADE_MAX_HEALTH && current_hour % 4 == 0) {
if (storage_meat > 0) {
int gained = add_barricade_health(residents_count);
if (gained > 0 && x <= BASE_END) {
screen_reader_speak("Residents repaired the barricade. +" + gained + " health.", true);
}
}
}
if (current_hour == 6) {
save_game_state();
}
if (current_hour == 6) {
attempt_daily_invasion();
attempt_daily_quest();
}
keep_base_fires_fed();
check_scheduled_invasion();
attempt_blessing();
}
// Manage bandits during active invasion
@@ -199,40 +355,108 @@ string get_time_string() {
}
void check_ambience_transition() {
// Definition of Day: 6 AM to 7 PM (19:00) ?
// Let's say Day is 6 (6AM) to 19 (7PM). Night is 20 (8PM) to 5 (5AM).
// Or simpler: 6 to 18 (6PM).
bool now_day = (current_hour >= 6 && current_hour < 19);
if (now_day != is_daytime) {
is_daytime = now_day;
update_ambience(false);
// Day is 6 (6AM) to 18 (6PM inclusive, so transition starts at hour 18)
// Night is 19 (7PM) to 5 (5AM inclusive, so transition starts at hour 5)
// Crossfade begins at hour 18 (sunset) and hour 5 (sunrise)
// Start crossfade to night at hour 18
if (current_hour == 18 && is_daytime && !crossfade_active) {
start_crossfade(true); // Fade to night
}
// Start crossfade to day at hour 5
else if (current_hour == 5 && !is_daytime && !crossfade_active) {
start_crossfade(false); // Fade to day
}
}
void start_crossfade(bool to_night) {
crossfade_active = true;
crossfade_to_night = to_night;
crossfade_timer.restart();
// Start the incoming sound at minimum volume (silent)
if (to_night) {
// Starting night sound
if (night_sound_handle == -1 || !p.sound_is_active(night_sound_handle)) {
night_sound_handle = p.play_stationary("sounds/nature/night.ogg", true);
}
p.update_sound_start_values(night_sound_handle, 0.0, CROSSFADE_MIN_VOLUME, 1.0);
} else {
// Starting day sound
if (day_sound_handle == -1 || !p.sound_is_active(day_sound_handle)) {
day_sound_handle = p.play_stationary("sounds/nature/day.ogg", true);
}
p.update_sound_start_values(day_sound_handle, 0.0, CROSSFADE_MIN_VOLUME, 1.0);
}
}
void update_crossfade() {
if (!crossfade_active) return;
float progress = float(crossfade_timer.elapsed) / float(CROSSFADE_DURATION);
if (progress > 1.0) progress = 1.0;
// Volume interpolation: fade out goes 0 -> -40, fade in goes -40 -> 0
float volume_range = CROSSFADE_MAX_VOLUME - CROSSFADE_MIN_VOLUME; // 40 dB range
float fade_out_vol = CROSSFADE_MAX_VOLUME - (volume_range * progress); // 0 -> -40
float fade_in_vol = CROSSFADE_MIN_VOLUME + (volume_range * progress); // -40 -> 0
if (crossfade_to_night) {
// Fading day out, night in
if (day_sound_handle != -1) p.update_sound_start_values(day_sound_handle, 0.0, fade_out_vol, 1.0);
if (night_sound_handle != -1) p.update_sound_start_values(night_sound_handle, 0.0, fade_in_vol, 1.0);
} else {
// Fading night out, day in
if (night_sound_handle != -1) p.update_sound_start_values(night_sound_handle, 0.0, fade_out_vol, 1.0);
if (day_sound_handle != -1) p.update_sound_start_values(day_sound_handle, 0.0, fade_in_vol, 1.0);
}
// Complete the crossfade
if (progress >= 1.0) {
complete_crossfade();
}
}
void complete_crossfade() {
crossfade_active = false;
if (crossfade_to_night) {
// Destroy day sound, ensure night is at full volume
if (day_sound_handle != -1) {
p.destroy_sound(day_sound_handle);
day_sound_handle = -1;
}
if (night_sound_handle != -1) p.update_sound_start_values(night_sound_handle, 0.0, 0.0, 1.0);
is_daytime = false;
} else {
// Destroy night sound, ensure day is at full volume
if (night_sound_handle != -1) {
p.destroy_sound(night_sound_handle);
night_sound_handle = -1;
}
if (day_sound_handle != -1) p.update_sound_start_values(day_sound_handle, 0.0, 0.0, 1.0);
is_daytime = true;
}
}
void update_ambience(bool force_restart) {
if (is_daytime) {
// Transition to Day
// Start day ambience
if (night_sound_handle != -1) {
p.destroy_sound(night_sound_handle);
night_sound_handle = -1;
}
if (day_sound_handle == -1 || !p.sound_is_active(day_sound_handle)) {
// Play looped, stationary (or relative to player x?)
// Usually ambience is 2D/Global. play_stationary_extended allows looping.
// Or play_1d at player position if we want panning?
// "sounds/nature/day.ogg"
day_sound_handle = p.play_stationary("sounds/nature/day.ogg", true);
day_sound_handle = p.play_stationary("sounds/nature/day.ogg", true);
}
} else {
// Transition to Night
// Start night ambience
if (day_sound_handle != -1) {
p.destroy_sound(day_sound_handle);
day_sound_handle = -1;
}
if (night_sound_handle == -1 || !p.sound_is_active(night_sound_handle)) {
night_sound_handle = p.play_stationary("sounds/nature/night.ogg", true);
night_sound_handle = p.play_stationary("sounds/nature/night.ogg", true);
}
}
}