Pets a lot nicer now, hopefully most of the bugs fixed.

This commit is contained in:
Storm Dragon
2026-02-12 18:23:12 -05:00
parent 23c7c78460
commit 6687863fbe
9 changed files with 349 additions and 37 deletions

View File

@@ -318,7 +318,7 @@ void run_game()
// Health Key
if (key_pressed(KEY_H)) {
speak_with_history(player_health + " health of " + max_health, true);
speak_with_history(get_health_report(), true);
}
// Coordinates Key

View File

@@ -1,10 +1,12 @@
Billy Wolfe: Designer, coder, and sounds
https://stormux.org
Zack Benton: Sounds
Michael Taboada: sounds
Chris Wright: Beta Tester
Deedra Waters: Beta Tester
Michael Taboada: sounds
Sarah Russell: Beta Tester
Zack Benton: Beta Tester, Mac Support, Sounds

View File

@@ -143,6 +143,32 @@ HideoutBandit@ get_hideout_bandit_at(int pos) {
return null;
}
bool pet_find_hideout_target(int originPos, int referencePos, int &out targetPos, string &out targetLabel, int &out targetKind) {
int bestDistance = PET_RANGE + 1;
targetPos = -1;
targetLabel = "";
targetKind = -1;
for (uint i = 0; i < hideoutBandits.length(); i++) {
int distanceToOrigin = abs(hideoutBandits[i].position - originPos);
if (distanceToOrigin > PET_RANGE) continue;
int distance = abs(hideoutBandits[i].position - referencePos);
if (distance < bestDistance) {
bestDistance = distance;
targetPos = hideoutBandits[i].position;
targetLabel = "bandit";
targetKind = 0;
}
}
return targetPos != -1;
}
bool pet_damage_hideout_target(int targetKind, int targetPos, int damage) {
if (targetKind != 0) return false;
return damage_hideout_bandit_at(targetPos, damage);
}
int clamp_hideout_spawn_start(int startX) {
if (startX < 0) return 0;
if (startX >= BANDIT_HIDEOUT_MAP_SIZE) return BANDIT_HIDEOUT_MAP_SIZE - 1;
@@ -236,6 +262,7 @@ void init_bandit_hideout_adventure() {
void cleanup_bandit_hideout_adventure() {
clear_hideout_bandits();
end_pet_adventure();
reset_adventure_combat_state();
p.destroy_all();
}
@@ -258,6 +285,8 @@ void run_bandit_hideout_adventure() {
intro.insert_last("Bandits will, of course, not take this lying down.");
text_reader_lines(intro, "Adventure", true);
begin_pet_adventure(@pet_find_hideout_target, @pet_damage_hideout_target, hideoutPlayerX);
bool adventurePaused = false;
while (true) {
wait(5);
@@ -296,7 +325,7 @@ void run_bandit_hideout_adventure() {
check_speech_history_keys();
if (key_pressed(KEY_H)) {
speak_with_history(player_health + " health of " + max_health, true);
speak_with_history(get_health_report(), true);
}
if (key_pressed(KEY_X)) {
@@ -312,6 +341,8 @@ void run_bandit_hideout_adventure() {
update_hideout_search();
update_hideout_bandits();
update_pet_adventure_position(hideoutPlayerX);
update_pets();
adventure_update_bow_shot(hideoutPlayerX);
if (hideoutBarricadeHealth <= 0) {

View File

@@ -86,6 +86,7 @@ void init_unicorn_adventure() {
void cleanup_unicorn_adventure() {
p.destroy_all();
reset_adventure_combat_state();
end_pet_adventure();
if (unicorn.sound_handle != -1) {
p.destroy_sound(unicorn.sound_handle);
unicorn.sound_handle = -1;
@@ -118,6 +119,8 @@ void run_unicorn_adventure() {
intro.insert_last("Controls: LEFT/RIGHT to move, UP to jump, CTRL to attack, ESC to flee");
text_reader_lines(intro, "Adventure", true);
begin_pet_adventure(@pet_find_unicorn_target, @pet_damage_unicorn_target, player_arena_x);
// Adventure Loop
bool adventurePaused = false;
while (true) {
@@ -159,7 +162,7 @@ void run_unicorn_adventure() {
// Health
if (key_pressed(KEY_H)) {
speak_with_history(player_health + " health of " + max_health, true);
speak_with_history(get_health_report(), true);
}
// Coordinates
@@ -177,6 +180,8 @@ void run_unicorn_adventure() {
update_unicorn();
adventure_update_bow_shot(player_arena_x);
update_unicorn_weapon_range_audio();
update_pet_adventure_position(player_arena_x);
update_pets();
// Check Conditions - unicorn falls when on collapsed bridge
if (!unicorn_defeated && bridge_collapsed && unicorn.x >= BRIDGE_START && unicorn.x <= BRIDGE_END) {
@@ -496,6 +501,24 @@ void apply_unicorn_damage(int damage) {
}
}
bool pet_find_unicorn_target(int originPos, int referencePos, int &out targetPos, string &out targetLabel, int &out targetKind) {
targetPos = -1;
targetLabel = "";
targetKind = -1;
if (unicorn.health <= 0) return false;
if (abs(unicorn.x - originPos) > PET_RANGE) return false;
targetPos = unicorn.x;
targetLabel = "unicorn";
targetKind = 0;
return true;
}
bool pet_damage_unicorn_target(int targetKind, int targetPos, int damage) {
if (targetKind != 0) return false;
apply_unicorn_damage(damage);
return true;
}
void play_unicorn_ground_death_sequence() {
if (unicorn.sound_handle != -1) {
p.destroy_sound(unicorn.sound_handle);

View File

@@ -266,6 +266,9 @@ const int PET_LOYALTY_BONUS_THRESHOLD = 5;
const int PET_MOVE_SPEED = 320; // Slightly faster than base walk speed
const int PET_TRAVEL_MIN_MS = 100;
const int PET_RANGE = BOW_RANGE + 2;
const int PET_HEALTH_MAX = 10;
const int PET_ATTACK_SELF_DAMAGE = 1;
const int PET_KNOCKOUT_COOLDOWN_HOURS = 3;
// Goose settings
const int GOOSE_HEALTH = 1;

View File

@@ -13,7 +13,8 @@ string[] learnSoundSkipList = {
"sounds/actions/fishpole.ogg",
"sounds/actions/hit_ground.ogg",
"sounds/menu/",
"sounds/nature/"
"sounds/nature/",
"sounds/pets/"
};
// Description entries: keep paths/texts aligned by index.

View File

@@ -39,7 +39,13 @@ void run_character_info_menu() {
options.insert_last("Favor " + format_favor(favor) + ".");
options.insert_last("Speed " + get_speed_status() + ".");
if (petActive) {
options.insert_last("Pet " + petType + ". Gender " + petGender + ". Loyalty " + petLoyalty + ".");
string petInfo = "Pet " + get_pet_display_name() + ". " + petHealth + " health of " + PET_HEALTH_MAX + ".";
petInfo += " Loyalty " + petLoyalty + " of " + PET_LOYALTY_MAX + ".";
if (petKnockoutHoursRemaining > 0) {
string hourLabel = (petKnockoutHoursRemaining == 1) ? "hour" : "hours";
petInfo += " Knocked out. " + petKnockoutHoursRemaining + " " + hourLabel + " remaining.";
}
options.insert_last(petInfo);
} else {
options.insert_last("Pet none.");
}

View File

@@ -9,6 +9,8 @@ int petLoyalty = 0;
bool petOut = false;
int petPosition = 0;
bool petPositionValid = false;
int petHealth = 0;
int petKnockoutHoursRemaining = 0;
timer petAttackTimer;
timer petRetrieveTimer;
@@ -35,6 +37,17 @@ timer petTravelTimer;
string[] petSoundPaths;
bool petSoundsInitialized = false;
funcdef bool PetAdventureFindTargetCallback(int originPos, int referencePos, int &out targetPos, string &out targetLabel, int &out targetKind);
funcdef bool PetAdventureDamageCallback(int targetKind, int targetPos, int damage);
bool petAdventureMode = false;
int petAdventurePlayerPos = -1;
bool petAdventurePreOut = false;
int petAdventurePrePosition = 0;
bool petAdventurePrePositionValid = false;
PetAdventureFindTargetCallback@ petAdventureFindTarget = null;
PetAdventureDamageCallback@ petAdventureDamageTarget = null;
string normalize_pet_path(const string&in path) {
return path.replace("\\", "/", true);
}
@@ -47,6 +60,15 @@ string collapse_pet_spaces(const string&in text) {
return result;
}
string normalize_pet_name_text(const string&in raw) {
string name = raw;
name = name.replace("_", " ", true);
name = name.replace("-", " ", true);
name = collapse_pet_spaces(name);
name.trim_whitespace_this();
return name;
}
void gather_pet_sound_files(const string&in basePath, string[]@ outFiles) {
string[]@ files = find_files(basePath + "/*.ogg");
if (@files !is null) {
@@ -179,6 +201,89 @@ void queue_pet_event(const string&in message, int soundPos = -1) {
petEventPositions.insert_last(soundPos);
}
string get_pet_display_name() {
string name = normalize_pet_name_text(petType);
if (name == "") return "pet";
return name;
}
int get_pet_listener_pos() {
if (petAdventurePlayerPos >= 0) return petAdventurePlayerPos;
return x;
}
string get_health_report() {
string report = player_health + " health of " + max_health;
if (petActive) {
report += ", " + get_pet_display_name() + ", " + petHealth + " health of " + PET_HEALTH_MAX;
report += ", loyalty " + petLoyalty + " of " + PET_LOYALTY_MAX;
}
return report;
}
int get_pet_search_origin() {
if (petOut && petPositionValid) return petPosition;
return get_pet_listener_pos();
}
bool is_pet_knocked_out() {
return petKnockoutHoursRemaining > 0 || petHealth <= 0;
}
void begin_pet_adventure(PetAdventureFindTargetCallback@ findTarget, PetAdventureDamageCallback@ damageTarget, int playerPos) {
petAdventureMode = true;
petAdventurePlayerPos = playerPos;
petAdventurePreOut = petOut;
petAdventurePrePosition = petPosition;
petAdventurePrePositionValid = petPositionValid;
@petAdventureFindTarget = findTarget;
@petAdventureDamageTarget = damageTarget;
stop_pet_travel();
petOut = false;
petPositionValid = false;
}
void update_pet_adventure_position(int playerPos) {
petAdventurePlayerPos = playerPos;
}
void end_pet_adventure() {
petAdventureMode = false;
petAdventurePlayerPos = -1;
@petAdventureFindTarget = null;
@petAdventureDamageTarget = null;
stop_pet_travel();
if (is_pet_knocked_out()) {
petOut = false;
petPositionValid = false;
} else if (petAdventurePreOut) {
petOut = true;
petPosition = petAdventurePrePosition;
petPositionValid = petAdventurePrePositionValid;
} else {
petOut = false;
petPositionValid = false;
}
petAdventurePreOut = false;
petAdventurePrePosition = 0;
petAdventurePrePositionValid = false;
}
void queue_pet_return_event() {
if (!petActive) return;
queue_pet_event("A " + get_pet_display_name() + " returns to you.");
}
void knock_out_pet() {
if (!petActive) return;
petHealth = 0;
petKnockoutHoursRemaining = PET_KNOCKOUT_COOLDOWN_HOURS;
petOut = false;
petPositionValid = false;
stop_pet_travel();
queue_pet_event("Your " + get_pet_display_name() + " has been knocked out.");
}
void update_pet_events() {
if (petEventMessages.length() == 0) {
petEventSoundHandle = -1;
@@ -202,7 +307,7 @@ void update_pet_events() {
int soundPos = petEventPositions[0];
if (soundPath != "" && file_exists(soundPath)) {
if (soundPos >= 0) {
petEventSoundHandle = play_1d_with_volume_step(soundPath, x, soundPos, false, PLAYER_WEAPON_SOUND_VOLUME_STEP);
petEventSoundHandle = play_1d_with_volume_step(soundPath, get_pet_listener_pos(), soundPos, false, PLAYER_WEAPON_SOUND_VOLUME_STEP);
} else {
petEventSoundHandle = p.play_stationary(soundPath, false);
}
@@ -227,9 +332,14 @@ void reset_pet_state() {
petOut = false;
petPosition = 0;
petPositionValid = false;
petHealth = PET_HEALTH_MAX;
petKnockoutHoursRemaining = 0;
petHealth = 0;
petKnockoutHoursRemaining = 0;
petEventMessages.resize(0);
petEventSounds.resize(0);
petEventPositions.resize(0);
safe_destroy_sound(petEventSoundHandle);
petEventSoundHandle = -1;
petTravelActive = false;
petTravelAction = PET_TRAVEL_NONE;
@@ -246,7 +356,7 @@ void reset_pet_state() {
}
void pet_leave() {
string oldPet = petType;
string oldPet = normalize_pet_name_text(petType);
reset_pet_state();
if (oldPet != "") {
speak_with_history(oldPet + " leaves.", true);
@@ -273,17 +383,26 @@ void check_pet_call_key() {
speak_with_history("No pet.", true);
return;
}
if (is_pet_knocked_out()) {
string message = "Your " + get_pet_display_name() + " has been knocked out.";
if (petKnockoutHoursRemaining > 0) {
string hourLabel = (petKnockoutHoursRemaining == 1) ? "hour" : "hours";
message += " " + petKnockoutHoursRemaining + " " + hourLabel + " remaining.";
}
speak_with_history(message, true);
return;
}
if (petOut) {
petOut = false;
stop_pet_travel();
petPositionValid = false;
speak_with_history("Pet called back.", true);
queue_pet_return_event();
return;
}
adjust_pet_loyalty(-PET_LOYALTY_CALLOUT_COST);
if (!petActive) return;
petOut = true;
petPosition = x;
petPosition = get_pet_listener_pos();
petPositionValid = true;
if (file_exists("sounds/pets/call_pet.ogg")) {
p.play_stationary("sounds/pets/call_pet.ogg", false);
@@ -299,10 +418,12 @@ void adopt_pet(const string&in soundPath) {
petOut = false;
petPosition = 0;
petPositionValid = false;
petHealth = PET_HEALTH_MAX;
petKnockoutHoursRemaining = 0;
petAttackTimer.restart();
petRetrieveTimer.restart();
petTravelTimer.restart();
speak_with_history("A " + petType + " joins you.", true);
speak_with_history("A " + get_pet_display_name() + " joins you.", true);
}
void stop_pet_travel() {
@@ -328,7 +449,7 @@ void start_pet_travel_attack(int targetPos, const string&in targetLabel, int tar
stop_pet_travel();
petTravelActive = true;
petTravelAction = PET_TRAVEL_ATTACK;
petTravelStartPos = petPositionValid ? petPosition : x;
petTravelStartPos = petPositionValid ? petPosition : get_pet_listener_pos();
petTravelTargetPos = targetPos;
petTravelTargetLabel = targetLabel;
petTravelTargetKind = targetKind;
@@ -338,7 +459,7 @@ void start_pet_travel_attack(int targetPos, const string&in targetLabel, int tar
if (petSoundPath != "" && file_exists(petSoundPath)) {
petTravelSoundHandle = play_1d_with_volume_step(
petSoundPath,
x,
get_pet_listener_pos(),
petTravelStartPos,
true,
PLAYER_WEAPON_SOUND_VOLUME_STEP
@@ -350,7 +471,7 @@ void start_pet_travel_retrieve(int targetPos) {
stop_pet_travel();
petTravelActive = true;
petTravelAction = PET_TRAVEL_RETRIEVE;
petTravelStartPos = petPositionValid ? petPosition : x;
petTravelStartPos = petPositionValid ? petPosition : get_pet_listener_pos();
petTravelTargetPos = targetPos;
petTravelDurationMs = get_pet_travel_duration_ms(petTravelStartPos, targetPos);
petTravelTimer.restart();
@@ -360,6 +481,13 @@ void update_pet_travel() {
if (!petTravelActive) return;
if (petTravelDurationMs < 1) petTravelDurationMs = 1;
if (petTravelAction == PET_TRAVEL_ATTACK) {
int refreshedTargetPos = -1;
if (find_pet_attack_target_by_kind(petTravelTargetKind, petTravelTargetPos, refreshedTargetPos)) {
petTravelTargetPos = refreshedTargetPos;
}
}
int elapsed = petTravelTimer.elapsed;
float progress = float(elapsed) / float(petTravelDurationMs);
if (progress > 1.0f) progress = 1.0f;
@@ -378,24 +506,40 @@ void update_pet_travel() {
if (petTravelAction == PET_TRAVEL_ATTACK) {
int damage = BOW_DAMAGE_MAX;
bool hit = false;
if (petTravelTargetKind == 0) {
hit = damage_bandit_at(petTravelTargetPos, damage);
} else if (petTravelTargetKind == 1) {
hit = damage_undead_at(petTravelTargetPos, damage);
} else if (petTravelTargetKind == 2) {
hit = damage_boar_at(petTravelTargetPos, damage);
if (petAdventureMode && @petAdventureDamageTarget !is null) {
hit = petAdventureDamageTarget(petTravelTargetKind, petTravelTargetPos, damage);
} else {
if (petTravelTargetKind == 0) {
hit = damage_bandit_at(petTravelTargetPos, damage);
} else if (petTravelTargetKind == 1) {
hit = damage_undead_at(petTravelTargetPos, damage);
} else if (petTravelTargetKind == 2) {
hit = damage_boar_at(petTravelTargetPos, damage);
}
}
if (hit) {
queue_pet_event("Your " + petType + " attacks the " + petTravelTargetLabel + ".", petTravelTargetPos);
petHealth -= PET_ATTACK_SELF_DAMAGE;
if (petHealth <= 0) {
knock_out_pet();
return;
}
}
petPosition = petTravelTargetPos;
petPositionValid = true;
int nextTargetPos = -1;
string nextTargetLabel = "";
int nextTargetKind = -1;
if (!find_pet_attack_target(nextTargetPos, nextTargetLabel, nextTargetKind)) {
bool hasNextTarget = find_pet_attack_target(nextTargetPos, nextTargetLabel, nextTargetKind);
if (!hasNextTarget) {
WorldDrop@ drop = find_pet_drop_target();
if (drop !is null) {
petRetrieveTimer.restart();
start_pet_travel_retrieve(drop.position);
return;
}
petOut = false;
petPositionValid = false;
queue_pet_return_event();
}
} else if (petTravelAction == PET_TRAVEL_RETRIEVE) {
WorldDrop@ drop = get_drop_at(petTravelTargetPos);
@@ -404,8 +548,25 @@ void update_pet_travel() {
if (try_pet_pickup_world_drop(drop, message)) {
remove_drop_at(drop.position);
queue_pet_event(message);
petPosition = petTravelTargetPos;
petPositionValid = true;
WorldDrop@ nextDrop = find_pet_drop_target();
if (nextDrop !is null) {
petRetrieveTimer.restart();
start_pet_travel_retrieve(nextDrop.position);
return;
}
int nextTargetPos = -1;
string nextTargetLabel = "";
int nextTargetKind = -1;
if (find_pet_attack_target(nextTargetPos, nextTargetLabel, nextTargetKind)) {
petAttackTimer.restart();
start_pet_travel_attack(nextTargetPos, nextTargetLabel, nextTargetKind);
return;
}
petOut = false;
petPositionValid = false;
queue_pet_return_event();
}
}
}
@@ -495,11 +656,12 @@ void attempt_pet_offer_from_tree() {
bool try_pet_pickup_small_game(const string&in gameType, string &out message) {
if (get_personal_count(ITEM_SMALL_GAME) >= get_personal_stack_limit()) {
return false;
message = "You can't carry a " + gameType + ", so you give it to a " + get_pet_display_name() + " to chew on.";
return true;
}
add_personal_count(ITEM_SMALL_GAME, 1);
personal_small_game_types.insert_last(gameType);
message = "Your " + petType + " retrieved " + gameType + ".";
message = "Your " + get_pet_display_name() + " retrieved " + gameType + ". A " + get_pet_display_name() + " returns to you.";
return true;
}
@@ -511,21 +673,24 @@ bool try_pet_pickup_world_drop(WorldDrop@ drop, string &out message) {
if (drop.type == "arrow") {
int maxArrows = get_arrow_limit();
if (maxArrows <= 0) {
return false;
message = "You can't carry an arrow, so you give it to a " + get_pet_display_name() + " to chew on.";
return true;
}
if (get_personal_count(ITEM_ARROWS) >= maxArrows) {
return false;
message = "You can't carry an arrow, so you give it to a " + get_pet_display_name() + " to chew on.";
return true;
}
add_personal_count(ITEM_ARROWS, 1);
message = "Your " + petType + " retrieved an arrow.";
message = "Your " + get_pet_display_name() + " retrieved an arrow. A " + get_pet_display_name() + " returns to you.";
return true;
}
if (drop.type == "boar carcass") {
if (get_personal_count(ITEM_BOAR_CARCASSES) >= get_personal_stack_limit()) {
return false;
message = "You can't carry a boar carcass, so you give it to a " + get_pet_display_name() + " to chew on.";
return true;
}
add_personal_count(ITEM_BOAR_CARCASSES, 1);
message = "Your " + petType + " retrieved a boar carcass.";
message = "Your " + get_pet_display_name() + " retrieved a boar carcass. A " + get_pet_display_name() + " returns to you.";
return true;
}
return false;
@@ -534,8 +699,9 @@ bool try_pet_pickup_world_drop(WorldDrop@ drop, string &out message) {
WorldDrop@ find_pet_drop_target() {
int bestDistance = PET_RANGE + 1;
WorldDrop@ best = null;
int origin = get_pet_search_origin();
for (uint i = 0; i < world_drops.length(); i++) {
int distance = abs(world_drops[i].position - x);
int distance = abs(world_drops[i].position - origin);
if (distance > PET_RANGE) continue;
if (distance < bestDistance) {
bestDistance = distance;
@@ -549,6 +715,8 @@ void update_pet_retrieval() {
if (!petActive) return;
if (!petOut) return;
if (petLoyalty <= 0) return;
if (petAdventureMode) return;
if (is_pet_knocked_out()) return;
if (petRetrieveTimer.elapsed < PET_RETRIEVE_COOLDOWN) return;
if (petEventMessages.length() > 2) return;
if (petTravelActive) return;
@@ -565,9 +733,13 @@ bool find_pet_attack_target(int &out targetPos, string &out targetLabel, int &ou
targetPos = -1;
targetLabel = "";
targetKind = -1;
int origin = get_pet_search_origin();
if (petAdventureMode && @petAdventureFindTarget !is null) {
return petAdventureFindTarget(origin, origin, targetPos, targetLabel, targetKind);
}
for (uint i = 0; i < bandits.length(); i++) {
int distance = abs(bandits[i].position - x);
int distance = abs(bandits[i].position - origin);
if (distance > PET_RANGE) continue;
if (distance < bestDistance) {
bestDistance = distance;
@@ -579,7 +751,7 @@ bool find_pet_attack_target(int &out targetPos, string &out targetLabel, int &ou
for (uint i = 0; i < undeads.length(); i++) {
if (undeads[i].undead_type == "undead_resident") continue;
int distance = abs(undeads[i].position - x);
int distance = abs(undeads[i].position - origin);
if (distance > PET_RANGE) continue;
if (distance < bestDistance) {
bestDistance = distance;
@@ -590,7 +762,7 @@ bool find_pet_attack_target(int &out targetPos, string &out targetLabel, int &ou
}
for (uint i = 0; i < ground_games.length(); i++) {
int distance = abs(ground_games[i].position - x);
int distance = abs(ground_games[i].position - origin);
if (distance > PET_RANGE) continue;
if (distance < bestDistance) {
bestDistance = distance;
@@ -603,10 +775,57 @@ bool find_pet_attack_target(int &out targetPos, string &out targetLabel, int &ou
return targetPos != -1;
}
bool find_pet_attack_target_by_kind(int targetKind, int referencePos, int &out targetPos) {
int bestDistance = PET_RANGE + 1;
targetPos = -1;
int origin = get_pet_search_origin();
if (petAdventureMode && @petAdventureFindTarget !is null) {
string targetLabel = "";
int foundKind = -1;
return petAdventureFindTarget(origin, referencePos, targetPos, targetLabel, foundKind);
}
if (targetKind == 0) {
for (uint i = 0; i < bandits.length(); i++) {
int distanceToOrigin = abs(bandits[i].position - origin);
if (distanceToOrigin > PET_RANGE) continue;
int distance = abs(bandits[i].position - referencePos);
if (distance < bestDistance) {
bestDistance = distance;
targetPos = bandits[i].position;
}
}
} else if (targetKind == 1) {
for (uint i = 0; i < undeads.length(); i++) {
if (undeads[i].undead_type == "undead_resident") continue;
int distanceToOrigin = abs(undeads[i].position - origin);
if (distanceToOrigin > PET_RANGE) continue;
int distance = abs(undeads[i].position - referencePos);
if (distance < bestDistance) {
bestDistance = distance;
targetPos = undeads[i].position;
}
}
} else if (targetKind == 2) {
for (uint i = 0; i < ground_games.length(); i++) {
int distanceToOrigin = abs(ground_games[i].position - origin);
if (distanceToOrigin > PET_RANGE) continue;
int distance = abs(ground_games[i].position - referencePos);
if (distance < bestDistance) {
bestDistance = distance;
targetPos = ground_games[i].position;
}
}
}
return targetPos != -1;
}
void update_pet_attack() {
if (!petActive) return;
if (!petOut) return;
if (petLoyalty <= 0) return;
if (is_pet_knocked_out()) return;
if (petAttackTimer.elapsed < PET_ATTACK_COOLDOWN) return;
if (petTravelActive) return;
@@ -632,7 +851,7 @@ void attempt_pet_random_find() {
add_personal_count(itemType, added);
string itemName = (added == 1) ? item_registry[itemType].singular : item_registry[itemType].name;
queue_pet_event("Your " + petType + " retrieved " + added + " " + itemName + ".", x);
queue_pet_event("Your " + get_pet_display_name() + " retrieved " + added + " " + itemName + ". A " + get_pet_display_name() + " returns to you.", x);
petOut = false;
petPositionValid = false;
}
@@ -640,7 +859,16 @@ void attempt_pet_random_find() {
void handle_pet_hourly_update(int hour) {
if (!petActive) return;
if (get_pet_food_personal_total() == 0) {
if (petKnockoutHoursRemaining > 0) {
petKnockoutHoursRemaining--;
if (petKnockoutHoursRemaining <= 0) {
petKnockoutHoursRemaining = 0;
petHealth = PET_HEALTH_MAX;
notify("Your " + get_pet_display_name() + " has recovered from its injuries.");
}
}
if (!has_pet_food_available()) {
adjust_pet_loyalty(-PET_LOYALTY_HUNGER_LOSS);
}

View File

@@ -806,6 +806,8 @@ bool save_game_state() {
saveData.set("pet_type", petType);
saveData.set("pet_gender", petGender);
saveData.set("pet_loyalty", petLoyalty);
saveData.set("pet_health", petHealth);
saveData.set("pet_ko_hours_remaining", petKnockoutHoursRemaining);
// Save inventory arrays using new compact format
saveData.set("personal_inventory", serialize_inventory_array(personal_inventory));
@@ -1129,11 +1131,15 @@ bool load_game_state_from_file(const string&in filename) {
petGender = loadedPetGender;
}
petLoyalty = int(get_number(saveData, "pet_loyalty", 0));
petHealth = int(get_number(saveData, "pet_health", PET_HEALTH_MAX));
petKnockoutHoursRemaining = int(get_number(saveData, "pet_ko_hours_remaining", 0));
if (!petActive) {
petSoundPath = "";
petType = "";
petGender = "";
petLoyalty = 0;
petHealth = 0;
petKnockoutHoursRemaining = 0;
}
if (petActive && petSoundPath != "" && !file_exists(petSoundPath)) {
petActive = false;
@@ -1141,8 +1147,20 @@ bool load_game_state_from_file(const string&in filename) {
petType = "";
petGender = "";
petLoyalty = 0;
petHealth = 0;
petKnockoutHoursRemaining = 0;
}
if (petActive) {
if (petKnockoutHoursRemaining < 0) petKnockoutHoursRemaining = 0;
if (petKnockoutHoursRemaining > PET_KNOCKOUT_COOLDOWN_HOURS) {
petKnockoutHoursRemaining = PET_KNOCKOUT_COOLDOWN_HOURS;
}
if (petKnockoutHoursRemaining > 0) {
petHealth = 0;
} else {
if (petHealth <= 0) petHealth = PET_HEALTH_MAX;
if (petHealth > PET_HEALTH_MAX) petHealth = PET_HEALTH_MAX;
}
petAttackTimer.restart();
petRetrieveTimer.restart();
}