Updates to horses and residents. Needs testing.
This commit is contained in:
@@ -134,25 +134,31 @@ Pets are optional companions that can fight and retrieve drops
|
|||||||
|
|
||||||
### Calling and status
|
### Calling and status
|
||||||
- **Space** call your pet
|
- **Space** call your pet
|
||||||
- If your pet is out Space calls it back
|
- If your pet is out Space recalls it and your pet runs back to you
|
||||||
- If your pet is with you Space sends it out and costs 1 loyalty
|
- If your pet is with you Space sends it out and costs 1 loyalty
|
||||||
|
- If loyalty is 2 or less your pet will not go out and says it is hungry and unresponsive
|
||||||
- If your pet is knocked out Space reports remaining recovery time
|
- If your pet is knocked out Space reports remaining recovery time
|
||||||
- Press H to hear pet health and loyalty when you have a pet
|
- Press H to hear pet health and loyalty when you have a pet
|
||||||
|
|
||||||
### Loyalty and feeding
|
### Loyalty and feeding
|
||||||
- Loyalty starts at 5 and max is 10
|
- Loyalty starts at 5 and max is 10
|
||||||
- If no food is available loyalty drops by 2 each hour
|
- While your pet is out loyalty drops by 1 each hour
|
||||||
- Every 8 hours your pet eats 1 food and gains 2 loyalty if food is available
|
- At loyalty 1 you get a hungry warning notification
|
||||||
|
- If loyalty reaches 0 your pet leaves
|
||||||
|
- Every 8 hours your pet can eat 1 food only while it is not out
|
||||||
|
- Eating restores loyalty to max
|
||||||
- Food can be meat smoked fish raw fish or basket of fruits and nuts
|
- Food can be meat smoked fish raw fish or basket of fruits and nuts
|
||||||
- Food is taken from your inventory first then storage
|
- Food is taken from your inventory first then storage
|
||||||
- If loyalty reaches 0 your pet leaves
|
|
||||||
|
|
||||||
### Help in the field
|
### Help in the field
|
||||||
- When out your pet attacks bandits undead and boars within range
|
- When out your pet attacks bandits undead and boars within range
|
||||||
- Each successful hit costs the pet 1 health
|
- Each successful hit costs the pet 1 health
|
||||||
|
- Pets do not heal while out
|
||||||
- If health reaches 0 the pet is knocked out for 3 hours and then recovers
|
- If health reaches 0 the pet is knocked out for 3 hours and then recovers
|
||||||
- When out your pet can retrieve world drops like small game boar carcasses and arrows
|
- When out your pet can retrieve world drops like small game boar carcasses and arrows
|
||||||
|
- Retrieval messages are spoken without the pet sound cue
|
||||||
- If you cannot carry the item the pet keeps it and it is lost
|
- If you cannot carry the item the pet keeps it and it is lost
|
||||||
|
- Pets stay out and follow you until you recall them with Space or they leave from loyalty loss
|
||||||
|
|
||||||
### Random finds
|
### Random finds
|
||||||
- If loyalty is at least 5 your pet can bring back sticks vines stones or clay
|
- If loyalty is at least 5 your pet can bring back sticks vines stones or clay
|
||||||
|
|||||||
BIN
sounds/pets/black_cat.ogg
LFS
Normal file
BIN
sounds/pets/black_cat.ogg
LFS
Normal file
Binary file not shown.
BIN
sounds/terrain/fly.ogg
LFS
Normal file
BIN
sounds/terrain/fly.ogg
LFS
Normal file
Binary file not shown.
@@ -44,13 +44,15 @@ int get_horse_success_bonus() {
|
|||||||
return count * HORSE_SUCCESS_BONUS_PER;
|
return count * HORSE_SUCCESS_BONUS_PER;
|
||||||
}
|
}
|
||||||
|
|
||||||
int get_horse_damage_bonus() {
|
int get_horse_resident_cooldown_reduction() {
|
||||||
if (world_stables.length() == 0) return 0;
|
if (world_stables.length() == 0) return 0;
|
||||||
if (world_storages.length() == 0) return 0;
|
if (world_storages.length() == 0) return 0;
|
||||||
if (horses_count <= 0) return 0;
|
if (horses_count <= 0) return 0;
|
||||||
int count = horses_count;
|
int count = horses_count;
|
||||||
if (count > MAX_HORSES) count = MAX_HORSES;
|
if (count > MAX_HORSES) count = MAX_HORSES;
|
||||||
return count / HORSE_DAMAGE_BONUS_STEP;
|
int cooldown_range = RESIDENT_COMBAT_BASE_COOLDOWN - RESIDENT_COMBAT_TARGET_COOLDOWN;
|
||||||
|
if (cooldown_range <= 0) return 0;
|
||||||
|
return (cooldown_range * count) / MAX_HORSES;
|
||||||
}
|
}
|
||||||
|
|
||||||
int get_resident_success_chance(int base_chance) {
|
int get_resident_success_chance(int base_chance) {
|
||||||
@@ -80,12 +82,19 @@ int get_resident_cooldown(int base_cooldown) {
|
|||||||
return cooldown;
|
return cooldown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int get_resident_combat_cooldown() {
|
||||||
|
int base_cooldown = RESIDENT_COMBAT_BASE_COOLDOWN - get_horse_resident_cooldown_reduction();
|
||||||
|
if (base_cooldown < RESIDENT_COMBAT_TARGET_COOLDOWN) {
|
||||||
|
base_cooldown = RESIDENT_COMBAT_TARGET_COOLDOWN;
|
||||||
|
}
|
||||||
|
return get_resident_cooldown(base_cooldown);
|
||||||
|
}
|
||||||
|
|
||||||
int apply_resident_damage_bonus(int damage) {
|
int apply_resident_damage_bonus(int damage) {
|
||||||
int adjusted = damage;
|
int adjusted = damage;
|
||||||
if (blessing_resident_active) {
|
if (blessing_resident_active) {
|
||||||
adjusted *= get_resident_effect_multiplier();
|
adjusted *= get_resident_effect_multiplier();
|
||||||
}
|
}
|
||||||
adjusted += get_horse_damage_bonus();
|
|
||||||
return adjusted;
|
return adjusted;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,22 +267,43 @@ void attempt_livestock_production() {
|
|||||||
|
|
||||||
void keep_base_fires_fed() {
|
void keep_base_fires_fed() {
|
||||||
if (residents_count <= 0) return;
|
if (residents_count <= 0) return;
|
||||||
if (!has_any_storage_food()) return;
|
if (get_storage_count(ITEM_VINES) <= 0 && get_storage_count(ITEM_STICKS) <= 0 && get_storage_count(ITEM_LOGS) <= 0) return;
|
||||||
if (get_storage_count(ITEM_STICKS) <= 0 && get_storage_count(ITEM_LOGS) <= 0) return;
|
|
||||||
|
// Residents tend fires once per in-game hour from time_system.
|
||||||
|
// Keep a 1-hour buffer above the 24-hour floor so fuel does not dip below 24 between hourly checks.
|
||||||
|
const int fire_floor_ms = 24 * 60000;
|
||||||
|
const int fire_target_ms = fire_floor_ms + 60000;
|
||||||
|
const int vine_fuel_ms = 60000; // 1 hour
|
||||||
|
const int stick_fuel_ms = 300000; // 5 hours
|
||||||
|
const int log_fuel_ms = 720000; // 12 hours
|
||||||
|
|
||||||
for (uint i = 0; i < world_fires.length(); i++) {
|
for (uint i = 0; i < world_fires.length(); i++) {
|
||||||
if (world_fires[i].position > BASE_END) continue;
|
if (world_fires[i].position > BASE_END) continue;
|
||||||
if (!world_fires[i].is_burning()) continue;
|
if (!world_fires[i].is_burning()) continue;
|
||||||
if (world_fires[i].fuel_remaining > 300000) continue;
|
if (world_fires[i].fuel_remaining >= fire_target_ms) continue;
|
||||||
|
|
||||||
if (get_storage_count(ITEM_STICKS) > 0) {
|
while (world_fires[i].fuel_remaining < fire_target_ms) {
|
||||||
add_storage_count(ITEM_STICKS, -1);
|
int needed = fire_target_ms - world_fires[i].fuel_remaining;
|
||||||
world_fires[i].add_fuel(300000); // 5 minutes
|
|
||||||
} else if (get_storage_count(ITEM_LOGS) > 0) {
|
if (needed >= log_fuel_ms && get_storage_count(ITEM_LOGS) > 0) {
|
||||||
add_storage_count(ITEM_LOGS, -1);
|
add_storage_count(ITEM_LOGS, -1);
|
||||||
world_fires[i].add_fuel(720000); // 12 minutes
|
world_fires[i].add_fuel(log_fuel_ms);
|
||||||
|
} else if (needed >= stick_fuel_ms && get_storage_count(ITEM_STICKS) > 0) {
|
||||||
|
add_storage_count(ITEM_STICKS, -1);
|
||||||
|
world_fires[i].add_fuel(stick_fuel_ms);
|
||||||
|
} else if (get_storage_count(ITEM_VINES) > 0) {
|
||||||
|
add_storage_count(ITEM_VINES, -1);
|
||||||
|
world_fires[i].add_fuel(vine_fuel_ms);
|
||||||
|
} else if (get_storage_count(ITEM_STICKS) > 0) {
|
||||||
|
add_storage_count(ITEM_STICKS, -1);
|
||||||
|
world_fires[i].add_fuel(stick_fuel_ms);
|
||||||
|
} else if (get_storage_count(ITEM_LOGS) > 0) {
|
||||||
|
add_storage_count(ITEM_LOGS, -1);
|
||||||
|
world_fires[i].add_fuel(log_fuel_ms);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,8 +404,11 @@ int choose_defense_weapon_type() {
|
|||||||
return (roll <= spearCount) ? RESIDENT_WEAPON_SPEAR : RESIDENT_WEAPON_SLING;
|
return (roll <= spearCount) ? RESIDENT_WEAPON_SPEAR : RESIDENT_WEAPON_SLING;
|
||||||
}
|
}
|
||||||
|
|
||||||
int perform_resident_defense() {
|
timer resident_combat_timer;
|
||||||
|
|
||||||
|
int perform_resident_defense(int target_pos) {
|
||||||
if (!can_residents_defend()) return 0;
|
if (!can_residents_defend()) return 0;
|
||||||
|
if (resident_combat_timer.elapsed < get_resident_combat_cooldown()) return 0;
|
||||||
|
|
||||||
// Choose weapon type (bows preferred, otherwise weighted by availability)
|
// Choose weapon type (bows preferred, otherwise weighted by availability)
|
||||||
int weapon_type = choose_defense_weapon_type();
|
int weapon_type = choose_defense_weapon_type();
|
||||||
@@ -389,9 +422,12 @@ int perform_resident_defense() {
|
|||||||
|
|
||||||
int damage = 0;
|
int damage = 0;
|
||||||
if (weapon_type == RESIDENT_WEAPON_BOW && bowCount > 0) {
|
if (weapon_type == RESIDENT_WEAPON_BOW && bowCount > 0) {
|
||||||
damage = apply_resident_damage_bonus(random(RESIDENT_SLING_DAMAGE_MIN, RESIDENT_SLING_DAMAGE_MAX));
|
damage = apply_resident_damage_bonus(random(RESIDENT_BOW_DAMAGE_MIN, RESIDENT_BOW_DAMAGE_MAX));
|
||||||
add_storage_count(ITEM_ARROWS, -1);
|
add_storage_count(ITEM_ARROWS, -1);
|
||||||
play_1d_with_volume_step("sounds/weapons/bow_fire.ogg", x, BASE_END + 1, false, RESIDENT_DEFENSE_VOLUME_STEP);
|
play_1d_with_volume_step("sounds/weapons/bow_fire.ogg", x, BASE_END + 1, false, RESIDENT_DEFENSE_VOLUME_STEP);
|
||||||
|
if (target_pos >= 0 && random(1, 100) <= 25 && get_drop_at(target_pos) == null) {
|
||||||
|
add_world_drop(target_pos, "arrow");
|
||||||
|
}
|
||||||
} else if (weapon_type == RESIDENT_WEAPON_SPEAR && spearCount > 0) {
|
} else if (weapon_type == RESIDENT_WEAPON_SPEAR && spearCount > 0) {
|
||||||
damage = apply_resident_damage_bonus(RESIDENT_SPEAR_DAMAGE);
|
damage = apply_resident_damage_bonus(RESIDENT_SPEAR_DAMAGE);
|
||||||
// Weapons don't get consumed on use - they break via daily breakage check
|
// Weapons don't get consumed on use - they break via daily breakage check
|
||||||
@@ -404,12 +440,14 @@ int perform_resident_defense() {
|
|||||||
play_1d_with_volume_step("sounds/weapons/sling_hit.ogg", x, BASE_END + 1, false, RESIDENT_DEFENSE_VOLUME_STEP);
|
play_1d_with_volume_step("sounds/weapons/sling_hit.ogg", x, BASE_END + 1, false, RESIDENT_DEFENSE_VOLUME_STEP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (damage > 0) {
|
||||||
|
resident_combat_timer.restart();
|
||||||
|
}
|
||||||
|
|
||||||
return damage;
|
return damage;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proactive resident ranged defense
|
// Proactive resident ranged defense
|
||||||
timer resident_ranged_timer;
|
|
||||||
|
|
||||||
void attempt_resident_ranged_defense() {
|
void attempt_resident_ranged_defense() {
|
||||||
// Only if residents exist and have ranged weapons
|
// Only if residents exist and have ranged weapons
|
||||||
if (residents_count <= 0) return;
|
if (residents_count <= 0) return;
|
||||||
@@ -419,8 +457,8 @@ void attempt_resident_ranged_defense() {
|
|||||||
bool has_sling = (slingCount > 0 && get_storage_count(ITEM_STONES) > 0);
|
bool has_sling = (slingCount > 0 && get_storage_count(ITEM_STONES) > 0);
|
||||||
if (!has_bow && !has_sling) return;
|
if (!has_bow && !has_sling) return;
|
||||||
|
|
||||||
// Cooldown between shots
|
// Shared cooldown for all resident combat actions
|
||||||
if (resident_ranged_timer.elapsed < get_resident_cooldown(RESIDENT_SLING_COOLDOWN)) return;
|
if (resident_combat_timer.elapsed < get_resident_combat_cooldown()) return;
|
||||||
|
|
||||||
int range = has_bow ? BOW_RANGE : SLING_RANGE;
|
int range = has_bow ? BOW_RANGE : SLING_RANGE;
|
||||||
// Find nearest enemy within range
|
// Find nearest enemy within range
|
||||||
@@ -454,13 +492,18 @@ void attempt_resident_ranged_defense() {
|
|||||||
if (targetPos == -1) return;
|
if (targetPos == -1) return;
|
||||||
|
|
||||||
// Shoot!
|
// Shoot!
|
||||||
resident_ranged_timer.restart();
|
resident_combat_timer.restart();
|
||||||
int damage = apply_resident_damage_bonus(random(RESIDENT_SLING_DAMAGE_MIN, RESIDENT_SLING_DAMAGE_MAX));
|
int damage = 0;
|
||||||
if (has_bow) {
|
if (has_bow) {
|
||||||
|
damage = apply_resident_damage_bonus(random(RESIDENT_BOW_DAMAGE_MIN, RESIDENT_BOW_DAMAGE_MAX));
|
||||||
add_storage_count(ITEM_ARROWS, -1);
|
add_storage_count(ITEM_ARROWS, -1);
|
||||||
play_1d_with_volume_step("sounds/weapons/bow_fire.ogg", x, BASE_END + 1, false, RESIDENT_DEFENSE_VOLUME_STEP);
|
play_1d_with_volume_step("sounds/weapons/bow_fire.ogg", x, BASE_END + 1, false, RESIDENT_DEFENSE_VOLUME_STEP);
|
||||||
play_1d_with_volume_step("sounds/weapons/arrow_hit.ogg", x, targetPos, false, RESIDENT_DEFENSE_VOLUME_STEP);
|
play_1d_with_volume_step("sounds/weapons/arrow_hit.ogg", x, targetPos, false, RESIDENT_DEFENSE_VOLUME_STEP);
|
||||||
|
if (random(1, 100) <= 25 && get_drop_at(targetPos) == null) {
|
||||||
|
add_world_drop(targetPos, "arrow");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
damage = apply_resident_damage_bonus(random(RESIDENT_SLING_DAMAGE_MIN, RESIDENT_SLING_DAMAGE_MAX));
|
||||||
add_storage_count(ITEM_STONES, -1);
|
add_storage_count(ITEM_STONES, -1);
|
||||||
play_1d_with_volume_step("sounds/weapons/sling_hit.ogg", x, targetPos, false, RESIDENT_DEFENSE_VOLUME_STEP);
|
play_1d_with_volume_step("sounds/weapons/sling_hit.ogg", x, targetPos, false, RESIDENT_DEFENSE_VOLUME_STEP);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,8 +29,11 @@ const int BLESSING_HEAL_AMOUNT = 3;
|
|||||||
const int BLESSING_BARRICADE_REPAIR = 20;
|
const int BLESSING_BARRICADE_REPAIR = 20;
|
||||||
const int BLESSING_SPEED_DURATION = 300000;
|
const int BLESSING_SPEED_DURATION = 300000;
|
||||||
const int BLESSING_RESIDENT_DURATION = 300000;
|
const int BLESSING_RESIDENT_DURATION = 300000;
|
||||||
|
const int BLESSING_SEARCH_DURATION = 300000;
|
||||||
const int BLESSING_TRIGGER_CHANCE = 10;
|
const int BLESSING_TRIGGER_CHANCE = 10;
|
||||||
const int BLESSING_WALK_SPEED = 320;
|
const int BLESSING_WALK_SPEED = 320;
|
||||||
|
const int BLESSING_SEARCH_GATHER_BONUS = 30;
|
||||||
|
const int GATHER_TIME_REDUCTION_CAP = 75;
|
||||||
const int FISH_WEIGHT_MIN = 1;
|
const int FISH_WEIGHT_MIN = 1;
|
||||||
const int FISH_WEIGHT_MAX = 30;
|
const int FISH_WEIGHT_MAX = 30;
|
||||||
// Player sex constants
|
// Player sex constants
|
||||||
@@ -153,7 +156,6 @@ const int MAX_LIVESTOCK = 9;
|
|||||||
const int HORSE_ADVENTURE_CHANCE = 45;
|
const int HORSE_ADVENTURE_CHANCE = 45;
|
||||||
const int LIVESTOCK_ADVENTURE_CHANCE = 45;
|
const int LIVESTOCK_ADVENTURE_CHANCE = 45;
|
||||||
const int HORSE_SUCCESS_BONUS_PER = 2;
|
const int HORSE_SUCCESS_BONUS_PER = 2;
|
||||||
const int HORSE_DAMAGE_BONUS_STEP = 3;
|
|
||||||
const int LIVESTOCK_MEAT_CHANCE = 6;
|
const int LIVESTOCK_MEAT_CHANCE = 6;
|
||||||
const int LIVESTOCK_SKIN_CHANCE = 3;
|
const int LIVESTOCK_SKIN_CHANCE = 3;
|
||||||
const int LIVESTOCK_FEATHER_CHANCE = 2;
|
const int LIVESTOCK_FEATHER_CHANCE = 2;
|
||||||
@@ -238,6 +240,10 @@ const int RESIDENT_WEAPON_BREAK_CHANCE = 10;
|
|||||||
const int RESIDENT_SPEAR_DAMAGE = 2;
|
const int RESIDENT_SPEAR_DAMAGE = 2;
|
||||||
const int RESIDENT_SLING_DAMAGE_MIN = 3;
|
const int RESIDENT_SLING_DAMAGE_MIN = 3;
|
||||||
const int RESIDENT_SLING_DAMAGE_MAX = 5;
|
const int RESIDENT_SLING_DAMAGE_MAX = 5;
|
||||||
|
const int RESIDENT_BOW_DAMAGE_MIN = 4;
|
||||||
|
const int RESIDENT_BOW_DAMAGE_MAX = 6;
|
||||||
|
const int RESIDENT_COMBAT_BASE_COOLDOWN = 3200; // Base attack delay (2x player axe)
|
||||||
|
const int RESIDENT_COMBAT_TARGET_COOLDOWN = 1600; // At max horses, about player axe speed
|
||||||
const int RESIDENT_SNARE_ESCAPE_CHANCE = 5; // 5% chance game escapes when resident retrieves
|
const int RESIDENT_SNARE_ESCAPE_CHANCE = 5; // 5% chance game escapes when resident retrieves
|
||||||
const int RESIDENT_SNARE_CHECK_CHANCE = 15; // 15% chance per hour to check snares
|
const int RESIDENT_SNARE_CHECK_CHANCE = 15; // 15% chance per hour to check snares
|
||||||
const int RESIDENT_FISHING_CHANCE = 6; // 6% chance per resident per hour to catch a fish
|
const int RESIDENT_FISHING_CHANCE = 6; // 6% chance per resident per hour to catch a fish
|
||||||
@@ -348,7 +354,6 @@ const int FALL_DAMAGE_MIN = 0;
|
|||||||
const int FALL_DAMAGE_MAX = 4;
|
const int FALL_DAMAGE_MAX = 4;
|
||||||
|
|
||||||
// Base Automation
|
// Base Automation
|
||||||
const int RESIDENT_SLING_COOLDOWN = 4000; // 4 seconds between shots
|
|
||||||
const int RESIDENT_COLLECTION_CHANCE = 10; // 10% chance per basket per hour
|
const int RESIDENT_COLLECTION_CHANCE = 10; // 10% chance per basket per hour
|
||||||
const int RESIDENT_FORAGING_CHANCE = 50; // 50% chance per resident per attempt (daily)
|
const int RESIDENT_FORAGING_CHANCE = 50; // 50% chance per resident per attempt (daily)
|
||||||
|
|
||||||
|
|||||||
@@ -184,6 +184,54 @@ int pick_bandit_spawn_east_of_player(int min_distance, int max_distance, int ran
|
|||||||
return pick_bandit_spawn_position(start, end);
|
return pick_bandit_spawn_position(start, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int pick_bandit_spawn_west_of_player(int min_distance, int max_distance, int range_start, int range_end) {
|
||||||
|
int min_dist = min_distance;
|
||||||
|
int max_dist = max_distance;
|
||||||
|
if (min_dist < 0) min_dist = 0;
|
||||||
|
if (max_dist < min_dist) {
|
||||||
|
int temp = min_dist;
|
||||||
|
min_dist = max_dist;
|
||||||
|
max_dist = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
int start = x - max_dist;
|
||||||
|
int end = x - min_dist;
|
||||||
|
|
||||||
|
int range_start_norm = range_start;
|
||||||
|
int range_end_norm = range_end;
|
||||||
|
if (range_start_norm > range_end_norm) {
|
||||||
|
int temp = range_start_norm;
|
||||||
|
range_start_norm = range_end_norm;
|
||||||
|
range_end_norm = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start < range_start_norm) start = range_start_norm;
|
||||||
|
if (end > range_end_norm) end = range_end_norm;
|
||||||
|
|
||||||
|
if (start > end) return -1;
|
||||||
|
|
||||||
|
return pick_bandit_spawn_position(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
int pick_bandit_spawn_near_player(int range_start, int range_end) {
|
||||||
|
// Preferred: 30-50 tiles east of player.
|
||||||
|
int spawn_x = pick_bandit_spawn_east_of_player(BANDIT_SPAWN_MIN_DISTANCE, BANDIT_SPAWN_MAX_DISTANCE, range_start, range_end);
|
||||||
|
if (spawn_x != -1) return spawn_x;
|
||||||
|
|
||||||
|
// Fallback: 30-50 tiles west when east side is not available.
|
||||||
|
spawn_x = pick_bandit_spawn_west_of_player(BANDIT_SPAWN_MIN_DISTANCE, BANDIT_SPAWN_MAX_DISTANCE, range_start, range_end);
|
||||||
|
if (spawn_x != -1) return spawn_x;
|
||||||
|
|
||||||
|
// If map bounds are tight, relax minimum distance but keep around player.
|
||||||
|
spawn_x = pick_bandit_spawn_east_of_player(1, BANDIT_SPAWN_MAX_DISTANCE, range_start, range_end);
|
||||||
|
if (spawn_x != -1) return spawn_x;
|
||||||
|
|
||||||
|
spawn_x = pick_bandit_spawn_west_of_player(1, BANDIT_SPAWN_MAX_DISTANCE, range_start, range_end);
|
||||||
|
if (spawn_x != -1) return spawn_x;
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
int count_bandits_in_range(int range_start, int range_end) {
|
int count_bandits_in_range(int range_start, int range_end) {
|
||||||
int start = range_start;
|
int start = range_start;
|
||||||
int end = range_end;
|
int end = range_end;
|
||||||
@@ -205,7 +253,7 @@ int count_bandits_in_range(int range_start, int range_end) {
|
|||||||
void spawn_bandit(int expansion_start, int expansion_end, const string&in invader_type = "bandit") {
|
void spawn_bandit(int expansion_start, int expansion_end, const string&in invader_type = "bandit") {
|
||||||
int spawn_x = -1;
|
int spawn_x = -1;
|
||||||
if (invasion_active) {
|
if (invasion_active) {
|
||||||
spawn_x = pick_bandit_spawn_east_of_player(BANDIT_SPAWN_MIN_DISTANCE, BANDIT_SPAWN_MAX_DISTANCE, expansion_start, expansion_end);
|
spawn_x = pick_bandit_spawn_near_player(expansion_start, expansion_end);
|
||||||
}
|
}
|
||||||
if (spawn_x == -1) {
|
if (spawn_x == -1) {
|
||||||
spawn_x = pick_bandit_spawn_position(expansion_start, expansion_end);
|
spawn_x = pick_bandit_spawn_position(expansion_start, expansion_end);
|
||||||
@@ -309,7 +357,7 @@ void try_attack_barricade_bandit(Bandit@ bandit) {
|
|||||||
|
|
||||||
// Resident defense counter-attack
|
// Resident defense counter-attack
|
||||||
if (can_residents_defend()) {
|
if (can_residents_defend()) {
|
||||||
int counterDamage = perform_resident_defense();
|
int counterDamage = perform_resident_defense(bandit.position);
|
||||||
if (counterDamage > 0) {
|
if (counterDamage > 0) {
|
||||||
int before_health = bandit.health;
|
int before_health = bandit.health;
|
||||||
damage_bandit_at(bandit.position, counterDamage);
|
damage_bandit_at(bandit.position, counterDamage);
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ void try_attack_barricade_undead(Undead@ undead) {
|
|||||||
|
|
||||||
// Resident defense counter-attack
|
// Resident defense counter-attack
|
||||||
if (can_residents_defend()) {
|
if (can_residents_defend()) {
|
||||||
int counterDamage = perform_resident_defense();
|
int counterDamage = perform_resident_defense(undead.position);
|
||||||
if (counterDamage > 0) {
|
if (counterDamage > 0) {
|
||||||
int before_health = undead.health;
|
int before_health = undead.health;
|
||||||
damage_undead_at(undead.position, counterDamage);
|
damage_undead_at(undead.position, counterDamage);
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ string petSoundPath = "";
|
|||||||
string petType = "";
|
string petType = "";
|
||||||
string petGender = "";
|
string petGender = "";
|
||||||
int petLoyalty = 0;
|
int petLoyalty = 0;
|
||||||
|
bool petHungryWarned = false;
|
||||||
bool petOut = false;
|
bool petOut = false;
|
||||||
|
bool petRecallRequested = false;
|
||||||
int petPosition = 0;
|
int petPosition = 0;
|
||||||
bool petPositionValid = false;
|
bool petPositionValid = false;
|
||||||
int petHealth = 0;
|
int petHealth = 0;
|
||||||
@@ -23,6 +25,7 @@ int petEventSoundHandle = -1;
|
|||||||
const int PET_TRAVEL_NONE = 0;
|
const int PET_TRAVEL_NONE = 0;
|
||||||
const int PET_TRAVEL_ATTACK = 1;
|
const int PET_TRAVEL_ATTACK = 1;
|
||||||
const int PET_TRAVEL_RETRIEVE = 2;
|
const int PET_TRAVEL_RETRIEVE = 2;
|
||||||
|
const int PET_TRAVEL_RETURN = 3;
|
||||||
|
|
||||||
bool petTravelActive = false;
|
bool petTravelActive = false;
|
||||||
int petTravelAction = PET_TRAVEL_NONE;
|
int petTravelAction = PET_TRAVEL_NONE;
|
||||||
@@ -193,11 +196,15 @@ bool consume_pet_food() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void queue_pet_event(const string&in message, int soundPos = -1) {
|
void queue_pet_event(const string&in message, int soundPos = -1, bool playSound = true) {
|
||||||
if (!petActive) return;
|
if (!petActive) return;
|
||||||
if (message.length() == 0) return;
|
if (message.length() == 0) return;
|
||||||
petEventMessages.insert_last(message);
|
petEventMessages.insert_last(message);
|
||||||
petEventSounds.insert_last(petSoundPath);
|
if (playSound) {
|
||||||
|
petEventSounds.insert_last(petSoundPath);
|
||||||
|
} else {
|
||||||
|
petEventSounds.insert_last("");
|
||||||
|
}
|
||||||
petEventPositions.insert_last(soundPos);
|
petEventPositions.insert_last(soundPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,6 +233,44 @@ int get_pet_search_origin() {
|
|||||||
return get_pet_listener_pos();
|
return get_pet_listener_pos();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool is_hawk_pet() {
|
||||||
|
return normalize_pet_name_text(petType).lower() == "hawk";
|
||||||
|
}
|
||||||
|
|
||||||
|
void play_pet_travel_step_sound(int stepPos) {
|
||||||
|
int listenerPos = get_pet_listener_pos();
|
||||||
|
if (abs(stepPos - listenerPos) > CREATURE_DEFAULT_FOOTSTEP_DISTANCE) return;
|
||||||
|
|
||||||
|
if (is_hawk_pet()) {
|
||||||
|
string flySoundPath = "sounds/terrain/fly.ogg";
|
||||||
|
if (file_exists(flySoundPath)) {
|
||||||
|
play_1d_with_volume_step(flySoundPath, listenerPos, stepPos, false, CREATURE_DEFAULT_VOLUME_STEP);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
play_creature_footstep(listenerPos, stepPos, BASE_END, GRASS_END);
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_pet_travel_position(int currentPos) {
|
||||||
|
int previousPos = petPositionValid ? petPosition : petTravelStartPos;
|
||||||
|
if (!petPositionValid) {
|
||||||
|
petPosition = petTravelStartPos;
|
||||||
|
petPositionValid = true;
|
||||||
|
previousPos = petPosition;
|
||||||
|
}
|
||||||
|
if (currentPos == previousPos) return;
|
||||||
|
|
||||||
|
int stepDir = (currentPos > previousPos) ? 1 : -1;
|
||||||
|
int stepPos = previousPos + stepDir;
|
||||||
|
while (true) {
|
||||||
|
play_pet_travel_step_sound(stepPos);
|
||||||
|
if (stepPos == currentPos) break;
|
||||||
|
stepPos += stepDir;
|
||||||
|
}
|
||||||
|
petPosition = currentPos;
|
||||||
|
}
|
||||||
|
|
||||||
bool is_pet_knocked_out() {
|
bool is_pet_knocked_out() {
|
||||||
return petKnockoutHoursRemaining > 0 || petHealth <= 0;
|
return petKnockoutHoursRemaining > 0 || petHealth <= 0;
|
||||||
}
|
}
|
||||||
@@ -274,6 +319,18 @@ void queue_pet_return_event() {
|
|||||||
queue_pet_event("A " + get_pet_display_name() + " returns to you.");
|
queue_pet_event("A " + get_pet_display_name() + " returns to you.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool is_pet_at_player_position() {
|
||||||
|
return petPositionValid && petPosition == get_pet_listener_pos();
|
||||||
|
}
|
||||||
|
|
||||||
|
void complete_pet_return() {
|
||||||
|
petOut = false;
|
||||||
|
petRecallRequested = false;
|
||||||
|
petPositionValid = false;
|
||||||
|
queue_pet_return_event();
|
||||||
|
stop_pet_travel();
|
||||||
|
}
|
||||||
|
|
||||||
void knock_out_pet() {
|
void knock_out_pet() {
|
||||||
if (!petActive) return;
|
if (!petActive) return;
|
||||||
petHealth = 0;
|
petHealth = 0;
|
||||||
@@ -329,7 +386,9 @@ void reset_pet_state() {
|
|||||||
petType = "";
|
petType = "";
|
||||||
petGender = "";
|
petGender = "";
|
||||||
petLoyalty = 0;
|
petLoyalty = 0;
|
||||||
|
petHungryWarned = false;
|
||||||
petOut = false;
|
petOut = false;
|
||||||
|
petRecallRequested = false;
|
||||||
petPosition = 0;
|
petPosition = 0;
|
||||||
petPositionValid = false;
|
petPositionValid = false;
|
||||||
petHealth = PET_HEALTH_MAX;
|
petHealth = PET_HEALTH_MAX;
|
||||||
@@ -393,21 +452,26 @@ void check_pet_call_key() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (petOut) {
|
if (petOut) {
|
||||||
petOut = false;
|
petRecallRequested = true;
|
||||||
stop_pet_travel();
|
play_pet_recall_ack();
|
||||||
petPositionValid = false;
|
speak_with_history("Your pet is on its way.", true);
|
||||||
queue_pet_return_event();
|
request_pet_return();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (petLoyalty <= 2) {
|
||||||
|
speak_with_history("A " + get_pet_display_name() + " is hungry and unresponsive.", true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
adjust_pet_loyalty(-PET_LOYALTY_CALLOUT_COST);
|
adjust_pet_loyalty(-PET_LOYALTY_CALLOUT_COST);
|
||||||
if (!petActive) return;
|
if (!petActive) return;
|
||||||
petOut = true;
|
petOut = true;
|
||||||
|
petRecallRequested = false;
|
||||||
petPosition = get_pet_listener_pos();
|
petPosition = get_pet_listener_pos();
|
||||||
petPositionValid = true;
|
petPositionValid = true;
|
||||||
if (file_exists("sounds/action/call_pet.ogg")) {
|
if (file_exists("sounds/actions/call_pet.ogg")) {
|
||||||
/* But I can feel it, black water washes over me.
|
/* But I can feel it, black water washes over me.
|
||||||
As it soothes I call to you with my control. */
|
As it soothes I call to you with my control. */
|
||||||
p.play_stationary("sounds/action/call_pet.ogg", false);
|
p.play_stationary("sounds/actions/call_pet.ogg", false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,7 +481,9 @@ void adopt_pet(const string&in soundPath) {
|
|||||||
petType = get_pet_name_from_sound_path(soundPath);
|
petType = get_pet_name_from_sound_path(soundPath);
|
||||||
petGender = (random(0, 1) == 0) ? "Male" : "Female";
|
petGender = (random(0, 1) == 0) ? "Male" : "Female";
|
||||||
petLoyalty = PET_START_LOYALTY;
|
petLoyalty = PET_START_LOYALTY;
|
||||||
|
petHungryWarned = false;
|
||||||
petOut = false;
|
petOut = false;
|
||||||
|
petRecallRequested = false;
|
||||||
petPosition = 0;
|
petPosition = 0;
|
||||||
petPositionValid = false;
|
petPositionValid = false;
|
||||||
petHealth = PET_HEALTH_MAX;
|
petHealth = PET_HEALTH_MAX;
|
||||||
@@ -452,6 +518,8 @@ void start_pet_travel_attack(int targetPos, const string&in targetLabel, int tar
|
|||||||
petTravelActive = true;
|
petTravelActive = true;
|
||||||
petTravelAction = PET_TRAVEL_ATTACK;
|
petTravelAction = PET_TRAVEL_ATTACK;
|
||||||
petTravelStartPos = petPositionValid ? petPosition : get_pet_listener_pos();
|
petTravelStartPos = petPositionValid ? petPosition : get_pet_listener_pos();
|
||||||
|
petPosition = petTravelStartPos;
|
||||||
|
petPositionValid = true;
|
||||||
petTravelTargetPos = targetPos;
|
petTravelTargetPos = targetPos;
|
||||||
petTravelTargetLabel = targetLabel;
|
petTravelTargetLabel = targetLabel;
|
||||||
petTravelTargetKind = targetKind;
|
petTravelTargetKind = targetKind;
|
||||||
@@ -474,11 +542,63 @@ void start_pet_travel_retrieve(int targetPos) {
|
|||||||
petTravelActive = true;
|
petTravelActive = true;
|
||||||
petTravelAction = PET_TRAVEL_RETRIEVE;
|
petTravelAction = PET_TRAVEL_RETRIEVE;
|
||||||
petTravelStartPos = petPositionValid ? petPosition : get_pet_listener_pos();
|
petTravelStartPos = petPositionValid ? petPosition : get_pet_listener_pos();
|
||||||
|
petPosition = petTravelStartPos;
|
||||||
|
petPositionValid = true;
|
||||||
petTravelTargetPos = targetPos;
|
petTravelTargetPos = targetPos;
|
||||||
petTravelDurationMs = get_pet_travel_duration_ms(petTravelStartPos, targetPos);
|
petTravelDurationMs = get_pet_travel_duration_ms(petTravelStartPos, targetPos);
|
||||||
petTravelTimer.restart();
|
petTravelTimer.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void start_pet_travel_return(int targetPos) {
|
||||||
|
stop_pet_travel();
|
||||||
|
petTravelActive = true;
|
||||||
|
petTravelAction = PET_TRAVEL_RETURN;
|
||||||
|
petTravelStartPos = petPositionValid ? petPosition : get_pet_listener_pos();
|
||||||
|
petPosition = petTravelStartPos;
|
||||||
|
petPositionValid = true;
|
||||||
|
petTravelTargetPos = targetPos;
|
||||||
|
petTravelDurationMs = get_pet_travel_duration_ms(petTravelStartPos, targetPos);
|
||||||
|
petTravelTimer.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
void request_pet_return() {
|
||||||
|
if (!petActive || !petOut) return;
|
||||||
|
if (!petPositionValid) {
|
||||||
|
petPosition = get_pet_listener_pos();
|
||||||
|
petPositionValid = true;
|
||||||
|
}
|
||||||
|
if (is_pet_at_player_position()) {
|
||||||
|
complete_pet_return();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
start_pet_travel_return(get_pet_listener_pos());
|
||||||
|
}
|
||||||
|
|
||||||
|
void request_pet_follow() {
|
||||||
|
if (!petActive || !petOut) return;
|
||||||
|
petRecallRequested = false;
|
||||||
|
if (!petPositionValid) {
|
||||||
|
petPosition = get_pet_listener_pos();
|
||||||
|
petPositionValid = true;
|
||||||
|
}
|
||||||
|
if (is_pet_at_player_position()) {
|
||||||
|
stop_pet_travel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
start_pet_travel_return(get_pet_listener_pos());
|
||||||
|
}
|
||||||
|
|
||||||
|
void play_pet_recall_ack() {
|
||||||
|
if (!petActive) return;
|
||||||
|
if (petSoundPath == "" || !file_exists(petSoundPath)) return;
|
||||||
|
int listenerPos = get_pet_listener_pos();
|
||||||
|
if (petPositionValid) {
|
||||||
|
play_1d_with_volume_step(petSoundPath, listenerPos, petPosition, false, PLAYER_WEAPON_SOUND_VOLUME_STEP);
|
||||||
|
} else {
|
||||||
|
p.play_stationary(petSoundPath, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void update_pet_travel() {
|
void update_pet_travel() {
|
||||||
if (!petTravelActive) return;
|
if (!petTravelActive) return;
|
||||||
if (petTravelDurationMs < 1) petTravelDurationMs = 1;
|
if (petTravelDurationMs < 1) petTravelDurationMs = 1;
|
||||||
@@ -488,6 +608,8 @@ void update_pet_travel() {
|
|||||||
if (find_pet_attack_target_by_kind(petTravelTargetKind, petTravelTargetPos, refreshedTargetPos)) {
|
if (find_pet_attack_target_by_kind(petTravelTargetKind, petTravelTargetPos, refreshedTargetPos)) {
|
||||||
petTravelTargetPos = refreshedTargetPos;
|
petTravelTargetPos = refreshedTargetPos;
|
||||||
}
|
}
|
||||||
|
} else if (petTravelAction == PET_TRAVEL_RETURN) {
|
||||||
|
petTravelTargetPos = get_pet_listener_pos();
|
||||||
}
|
}
|
||||||
|
|
||||||
int elapsed = petTravelTimer.elapsed;
|
int elapsed = petTravelTimer.elapsed;
|
||||||
@@ -496,6 +618,7 @@ void update_pet_travel() {
|
|||||||
|
|
||||||
int travel = int(float(petTravelTargetPos - petTravelStartPos) * progress);
|
int travel = int(float(petTravelTargetPos - petTravelStartPos) * progress);
|
||||||
int currentPos = petTravelStartPos + travel;
|
int currentPos = petTravelStartPos + travel;
|
||||||
|
update_pet_travel_position(currentPos);
|
||||||
if (petTravelSoundHandle != -1) {
|
if (petTravelSoundHandle != -1) {
|
||||||
p.update_sound_1d(petTravelSoundHandle, currentPos);
|
p.update_sound_1d(petTravelSoundHandle, currentPos);
|
||||||
}
|
}
|
||||||
@@ -539,9 +662,7 @@ void update_pet_travel() {
|
|||||||
start_pet_travel_retrieve(drop.position);
|
start_pet_travel_retrieve(drop.position);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
petOut = false;
|
request_pet_follow();
|
||||||
petPositionValid = false;
|
|
||||||
queue_pet_return_event();
|
|
||||||
}
|
}
|
||||||
} else if (petTravelAction == PET_TRAVEL_RETRIEVE) {
|
} else if (petTravelAction == PET_TRAVEL_RETRIEVE) {
|
||||||
WorldDrop@ drop = get_drop_at(petTravelTargetPos);
|
WorldDrop@ drop = get_drop_at(petTravelTargetPos);
|
||||||
@@ -549,7 +670,7 @@ void update_pet_travel() {
|
|||||||
string message = "";
|
string message = "";
|
||||||
if (try_pet_pickup_world_drop(drop, message)) {
|
if (try_pet_pickup_world_drop(drop, message)) {
|
||||||
remove_drop_at(drop.position);
|
remove_drop_at(drop.position);
|
||||||
queue_pet_event(message);
|
queue_pet_event(message, -1, false);
|
||||||
petPosition = petTravelTargetPos;
|
petPosition = petTravelTargetPos;
|
||||||
petPositionValid = true;
|
petPositionValid = true;
|
||||||
WorldDrop@ nextDrop = find_pet_drop_target();
|
WorldDrop@ nextDrop = find_pet_drop_target();
|
||||||
@@ -566,11 +687,22 @@ void update_pet_travel() {
|
|||||||
start_pet_travel_attack(nextTargetPos, nextTargetLabel, nextTargetKind);
|
start_pet_travel_attack(nextTargetPos, nextTargetLabel, nextTargetKind);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
petOut = false;
|
request_pet_follow();
|
||||||
petPositionValid = false;
|
|
||||||
queue_pet_return_event();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (petTravelAction == PET_TRAVEL_RETURN) {
|
||||||
|
petPosition = petTravelTargetPos;
|
||||||
|
petPositionValid = true;
|
||||||
|
if (is_pet_at_player_position()) {
|
||||||
|
if (petRecallRequested) {
|
||||||
|
complete_pet_return();
|
||||||
|
} else {
|
||||||
|
stop_pet_travel();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
start_pet_travel_return(get_pet_listener_pos());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
stop_pet_travel();
|
stop_pet_travel();
|
||||||
@@ -840,6 +972,25 @@ void update_pet_attack() {
|
|||||||
start_pet_travel_attack(targetPos, targetLabel, targetKind);
|
start_pet_travel_attack(targetPos, targetLabel, targetKind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void update_pet_follow() {
|
||||||
|
if (!petActive) return;
|
||||||
|
if (!petOut) return;
|
||||||
|
if (petAdventureMode) return;
|
||||||
|
if (is_pet_knocked_out()) return;
|
||||||
|
if (petTravelActive) return;
|
||||||
|
|
||||||
|
WorldDrop@ drop = find_pet_drop_target();
|
||||||
|
if (drop !is null) return;
|
||||||
|
|
||||||
|
int targetPos = -1;
|
||||||
|
string targetLabel = "";
|
||||||
|
int targetKind = -1;
|
||||||
|
if (find_pet_attack_target(targetPos, targetLabel, targetKind)) return;
|
||||||
|
|
||||||
|
if (is_pet_at_player_position()) return;
|
||||||
|
request_pet_follow();
|
||||||
|
}
|
||||||
|
|
||||||
void attempt_pet_random_find() {
|
void attempt_pet_random_find() {
|
||||||
if (!petActive) return;
|
if (!petActive) return;
|
||||||
if (!petOut) return;
|
if (!petOut) return;
|
||||||
@@ -853,9 +1004,8 @@ void attempt_pet_random_find() {
|
|||||||
|
|
||||||
add_personal_count(itemType, added);
|
add_personal_count(itemType, added);
|
||||||
string itemName = (added == 1) ? item_registry[itemType].singular : item_registry[itemType].name;
|
string itemName = (added == 1) ? item_registry[itemType].singular : item_registry[itemType].name;
|
||||||
queue_pet_event("Your " + get_pet_display_name() + " retrieved " + added + " " + itemName + ". A " + get_pet_display_name() + " returns to you.", x);
|
queue_pet_event("Your " + get_pet_display_name() + " retrieved " + added + " " + itemName + ".", petPositionValid ? petPosition : x, false);
|
||||||
petOut = false;
|
request_pet_follow();
|
||||||
petPositionValid = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void handle_pet_hourly_update(int hour) {
|
void handle_pet_hourly_update(int hour) {
|
||||||
@@ -870,21 +1020,26 @@ void handle_pet_hourly_update(int hour) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (petKnockoutHoursRemaining <= 0 && petHealth > 0 && petHealth < PET_HEALTH_MAX) {
|
if (petKnockoutHoursRemaining <= 0 && !petOut && petHealth > 0 && petHealth < PET_HEALTH_MAX) {
|
||||||
petHealth += 1;
|
petHealth += 1;
|
||||||
if (petHealth > PET_HEALTH_MAX) petHealth = PET_HEALTH_MAX;
|
if (petHealth > PET_HEALTH_MAX) petHealth = PET_HEALTH_MAX;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!has_pet_food_available()) {
|
if (petOut) {
|
||||||
adjust_pet_loyalty(-PET_LOYALTY_HUNGER_LOSS);
|
adjust_pet_loyalty(-1);
|
||||||
|
if (!petActive) return;
|
||||||
|
if (petLoyalty <= 1 && !petHungryWarned) {
|
||||||
|
notify("A " + get_pet_display_name() + " is getting hungry.");
|
||||||
|
petHungryWarned = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!petActive) return;
|
if (hour % 8 == 0 && !petOut) {
|
||||||
|
|
||||||
if (hour % 8 == 0) {
|
|
||||||
if (consume_pet_food()) {
|
if (consume_pet_food()) {
|
||||||
petLoyalty += PET_LOYALTY_EAT_BONUS;
|
petLoyalty = PET_LOYALTY_MAX;
|
||||||
clamp_pet_loyalty();
|
if (petLoyalty > 1) {
|
||||||
|
petHungryWarned = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -896,6 +1051,7 @@ void update_pets() {
|
|||||||
if (petActive && petOut) {
|
if (petActive && petOut) {
|
||||||
update_pet_retrieval();
|
update_pet_retrieval();
|
||||||
update_pet_attack();
|
update_pet_attack();
|
||||||
|
update_pet_follow();
|
||||||
}
|
}
|
||||||
update_pet_events();
|
update_pet_events();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,6 +80,8 @@ bool blessing_speed_active = false;
|
|||||||
timer blessing_speed_timer;
|
timer blessing_speed_timer;
|
||||||
bool blessing_resident_active = false;
|
bool blessing_resident_active = false;
|
||||||
timer blessing_resident_timer;
|
timer blessing_resident_timer;
|
||||||
|
bool blessing_search_active = false;
|
||||||
|
timer blessing_search_timer;
|
||||||
|
|
||||||
// Timers
|
// Timers
|
||||||
timer walktimer;
|
timer walktimer;
|
||||||
|
|||||||
@@ -36,14 +36,17 @@ int get_total_rune_gather_bonus() {
|
|||||||
return bonus;
|
return bonus;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply gathering time reduction based on rune bonuses
|
// Apply gathering time reduction based on runes and blessings
|
||||||
// Takes base time in ms, returns reduced time
|
// Takes base time in ms, returns reduced time
|
||||||
int apply_rune_gather_bonus(int base_time) {
|
int apply_rune_gather_bonus(int base_time) {
|
||||||
int bonus_percent = get_total_rune_gather_bonus();
|
int bonus_percent = get_total_rune_gather_bonus();
|
||||||
|
if (blessing_search_active) {
|
||||||
|
bonus_percent += BLESSING_SEARCH_GATHER_BONUS;
|
||||||
|
}
|
||||||
if (bonus_percent <= 0) return base_time;
|
if (bonus_percent <= 0) return base_time;
|
||||||
|
|
||||||
// Cap at 50% reduction to prevent instant gathering
|
// Keep gathering from becoming instant.
|
||||||
if (bonus_percent > 50) bonus_percent = 50;
|
if (bonus_percent > GATHER_TIME_REDUCTION_CAP) bonus_percent = GATHER_TIME_REDUCTION_CAP;
|
||||||
|
|
||||||
int reduction = (base_time * bonus_percent) / 100;
|
int reduction = (base_time * bonus_percent) / 100;
|
||||||
return base_time - reduction;
|
return base_time - reduction;
|
||||||
|
|||||||
@@ -623,6 +623,7 @@ void reset_game_state() {
|
|||||||
incense_burning = false;
|
incense_burning = false;
|
||||||
blessing_speed_active = false;
|
blessing_speed_active = false;
|
||||||
blessing_resident_active = false;
|
blessing_resident_active = false;
|
||||||
|
blessing_search_active = false;
|
||||||
reset_fylgja_state();
|
reset_fylgja_state();
|
||||||
reset_pet_state();
|
reset_pet_state();
|
||||||
|
|
||||||
|
|||||||
@@ -662,6 +662,10 @@ void update_blessings() {
|
|||||||
blessing_resident_active = false;
|
blessing_resident_active = false;
|
||||||
speak_with_history("The residents' purpose fades.", true);
|
speak_with_history("The residents' purpose fades.", true);
|
||||||
}
|
}
|
||||||
|
if (blessing_search_active && blessing_search_timer.elapsed >= BLESSING_SEARCH_DURATION) {
|
||||||
|
blessing_search_active = false;
|
||||||
|
speak_with_history("The eagle's sight fades.", true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void attempt_blessing() {
|
void attempt_blessing() {
|
||||||
@@ -674,6 +678,7 @@ void attempt_blessing() {
|
|||||||
if (!blessing_speed_active) options.insert_last(1);
|
if (!blessing_speed_active) options.insert_last(1);
|
||||||
if (barricade_health < BARRICADE_MAX_HEALTH) options.insert_last(2);
|
if (barricade_health < BARRICADE_MAX_HEALTH) options.insert_last(2);
|
||||||
if (residents_count > 0 && !blessing_resident_active) options.insert_last(3);
|
if (residents_count > 0 && !blessing_resident_active) options.insert_last(3);
|
||||||
|
if (!blessing_search_active) options.insert_last(4);
|
||||||
if (options.length() == 0) return;
|
if (options.length() == 0) return;
|
||||||
|
|
||||||
int choice = options[random(0, options.length() - 1)];
|
int choice = options[random(0, options.length() - 1)];
|
||||||
@@ -708,6 +713,10 @@ void attempt_blessing() {
|
|||||||
blessing_resident_active = true;
|
blessing_resident_active = true;
|
||||||
blessing_resident_timer.restart();
|
blessing_resident_timer.restart();
|
||||||
notify(god_name + " radiance fills residents with purpose.");
|
notify(god_name + " radiance fills residents with purpose.");
|
||||||
|
} else if (choice == 4) {
|
||||||
|
blessing_search_active = true;
|
||||||
|
blessing_search_timer.restart();
|
||||||
|
notify(god_name + " favor grants you the eyes of an eagle.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user