A few bugs fixed. Very very rough draft of pet system. Hopefully it works, maybe it doesn't.
This commit is contained in:
@@ -205,3 +205,39 @@ void safe_destroy_sound(int &inout handle)
|
||||
handle = -1;
|
||||
}
|
||||
}
|
||||
|
||||
float master_volume_db = MASTER_VOLUME_MAX_DB;
|
||||
|
||||
void init_master_volume() {
|
||||
master_volume_db = MASTER_VOLUME_MAX_DB;
|
||||
sound_master_volume = master_volume_db;
|
||||
}
|
||||
|
||||
void set_game_master_volume_db(float volume_db, bool announce = true) {
|
||||
float clamped = volume_db;
|
||||
if (clamped > MASTER_VOLUME_MAX_DB) clamped = MASTER_VOLUME_MAX_DB;
|
||||
if (clamped < MASTER_VOLUME_MIN_DB) clamped = MASTER_VOLUME_MIN_DB;
|
||||
|
||||
if (clamped == master_volume_db) return;
|
||||
|
||||
master_volume_db = clamped;
|
||||
sound_master_volume = master_volume_db;
|
||||
|
||||
if (announce) {
|
||||
float range = MASTER_VOLUME_MAX_DB - MASTER_VOLUME_MIN_DB;
|
||||
float normalized = (master_volume_db - MASTER_VOLUME_MIN_DB) / range;
|
||||
int volumePercent = int(normalized * 100.0f + 0.5f);
|
||||
if (volumePercent < 0) volumePercent = 0;
|
||||
if (volumePercent > 100) volumePercent = 100;
|
||||
screen_reader_speak("Volume " + volumePercent + ".", true);
|
||||
}
|
||||
}
|
||||
|
||||
void handle_global_volume_keys() {
|
||||
if (key_pressed(KEY_PAGEDOWN)) {
|
||||
set_game_master_volume_db(master_volume_db - MASTER_VOLUME_STEP_DB);
|
||||
}
|
||||
if (key_pressed(KEY_PAGEUP)) {
|
||||
set_game_master_volume_db(master_volume_db + MASTER_VOLUME_STEP_DB);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ void run_adventure_menu(int player_x) {
|
||||
|
||||
while (true) {
|
||||
wait(5);
|
||||
handle_global_volume_keys();
|
||||
|
||||
if (key_pressed(KEY_ESCAPE)) {
|
||||
speak_with_history("Closed.", true);
|
||||
|
||||
@@ -261,6 +261,7 @@ void run_bandit_hideout_adventure() {
|
||||
bool adventurePaused = false;
|
||||
while (true) {
|
||||
wait(5);
|
||||
handle_global_volume_keys();
|
||||
|
||||
if (key_pressed(KEY_BACK)) {
|
||||
adventurePaused = !adventurePaused;
|
||||
@@ -280,6 +281,8 @@ void run_bandit_hideout_adventure() {
|
||||
continue;
|
||||
}
|
||||
|
||||
check_pet_call_key();
|
||||
|
||||
if (key_pressed(KEY_ESCAPE)) {
|
||||
cleanup_bandit_hideout_adventure();
|
||||
speak_with_history("You flee the hideout.", true);
|
||||
@@ -760,4 +763,5 @@ void give_bandit_hideout_rewards() {
|
||||
}
|
||||
|
||||
text_reader_lines(rewards, "Bandit's Hideout", true);
|
||||
attempt_pet_offer_from_adventure();
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ void run_unicorn_adventure() {
|
||||
bool adventurePaused = false;
|
||||
while (true) {
|
||||
wait(5);
|
||||
handle_global_volume_keys();
|
||||
|
||||
if (key_pressed(KEY_BACK)) {
|
||||
adventurePaused = !adventurePaused;
|
||||
@@ -141,6 +142,8 @@ void run_unicorn_adventure() {
|
||||
continue;
|
||||
}
|
||||
|
||||
check_pet_call_key();
|
||||
|
||||
// Input Handling
|
||||
if (key_pressed(KEY_ESCAPE)) {
|
||||
cleanup_unicorn_adventure();
|
||||
@@ -670,4 +673,5 @@ void give_unicorn_rewards() {
|
||||
|
||||
// Display rewards in text reader
|
||||
text_reader_lines(rewards, "Unicorn Victory", true);
|
||||
attempt_pet_offer_from_adventure();
|
||||
}
|
||||
|
||||
@@ -187,6 +187,9 @@ const int EXPANSION_ROAMER_MAX_PER_AREA = 3;
|
||||
const int AUDIO_TILE_SCALE = 10;
|
||||
const float AUDIO_PAN_STEP = 2.0;
|
||||
const float AUDIO_VOLUME_STEP = 3.0;
|
||||
const float MASTER_VOLUME_MAX_DB = 0.0;
|
||||
const float MASTER_VOLUME_MIN_DB = -60.0;
|
||||
const float MASTER_VOLUME_STEP_DB = 3.0;
|
||||
const int SNARE_SOUND_RANGE = 2;
|
||||
const float SNARE_SOUND_VOLUME_STEP = 4.0; // More audible for locating snares
|
||||
const float SNARE_SOUND_PAN_STEP = 4.0; // Stronger pan for direction
|
||||
@@ -245,6 +248,24 @@ const float PLAYER_ITEM_BREAK_CHANCE_MIN = 1.0;
|
||||
const float PLAYER_ITEM_BREAK_CHANCE_MAX = 100.0;
|
||||
const float PLAYER_ITEM_BREAK_CHANCE_INCREMENT = 0.1;
|
||||
const int PLAYER_ITEM_BREAKS_PER_DAY = 2;
|
||||
const int PLAYER_ITEM_BREAK_ROLL_MAX = 200;
|
||||
|
||||
// Pet system
|
||||
const int PET_START_LOYALTY = 5;
|
||||
const int PET_LOYALTY_HELP_LOSS = 1;
|
||||
const int PET_LOYALTY_HUNGER_LOSS = 2;
|
||||
const int PET_LOYALTY_ACTION_COST = 1;
|
||||
const int PET_LOYALTY_EAT_BONUS = 2;
|
||||
const int PET_LOYALTY_MAX = 10;
|
||||
const int PET_ATTACK_COOLDOWN = 1600; // Same as axe
|
||||
const int PET_RETRIEVE_COOLDOWN = 1000;
|
||||
const int PET_RANDOM_FIND_CHANCE = 20; // Percent per hour when loyalty is high
|
||||
const int PET_ADVENTURE_CHANCE = 5; // Percent chance after adventure victory
|
||||
const int PET_TREE_HAWK_CHANCE = 10; // Percent chance after reaching top of a tree
|
||||
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;
|
||||
|
||||
// Goose settings
|
||||
const int GOOSE_HEALTH = 1;
|
||||
|
||||
@@ -5,8 +5,6 @@ void run_barricade_menu() {
|
||||
return;
|
||||
}
|
||||
|
||||
speak_with_history("Barricade.", true);
|
||||
|
||||
int selection = 0;
|
||||
string[] options;
|
||||
int[] action_types; // 0 = sticks, 1 = vines, 2 = log, 3 = stones
|
||||
@@ -32,6 +30,7 @@ void run_barricade_menu() {
|
||||
speak_with_history("No materials to reinforce the barricade.", true);
|
||||
return;
|
||||
}
|
||||
speak_with_history("Barricade. " + options[selection], true);
|
||||
|
||||
while(true) {
|
||||
wait(5);
|
||||
|
||||
@@ -26,8 +26,6 @@ bool has_building_options() {
|
||||
}
|
||||
|
||||
void run_buildings_menu() {
|
||||
speak_with_history("Buildings.", true);
|
||||
|
||||
int selection = 0;
|
||||
string[] options;
|
||||
int[] building_types;
|
||||
@@ -95,6 +93,7 @@ void run_buildings_menu() {
|
||||
speak_with_history("No buildings available.", true);
|
||||
return;
|
||||
}
|
||||
speak_with_history("Buildings. " + options[selection], true);
|
||||
|
||||
while(true) {
|
||||
wait(5);
|
||||
|
||||
@@ -43,8 +43,6 @@ void consume_pouches(int amount) {
|
||||
}
|
||||
|
||||
void run_clothing_menu() {
|
||||
speak_with_history("Clothing.", true);
|
||||
|
||||
int selection = 0;
|
||||
string[] options = {
|
||||
"Skin Hat (1 Skin, 1 Vine)",
|
||||
@@ -55,6 +53,7 @@ void run_clothing_menu() {
|
||||
"Skin Pouch (2 Skins, 1 Vine)",
|
||||
"Backpack (11 Skins, 5 Vines, 4 Skin Pouches)"
|
||||
};
|
||||
speak_with_history("Clothing. " + options[selection], true);
|
||||
|
||||
while(true) {
|
||||
wait(5);
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
// Crafting materials
|
||||
void run_materials_menu() {
|
||||
speak_with_history("Materials.", true);
|
||||
|
||||
int selection = 0;
|
||||
string[] options = {
|
||||
"Butcher Game [Requires Game, Knife, Fire nearby]",
|
||||
@@ -10,6 +8,7 @@ void run_materials_menu() {
|
||||
"Bowstring (3 Sinew) [Requires Fire nearby]",
|
||||
"Incense (6 Sticks, 2 Vines, 1 Reed) [Requires Altar]"
|
||||
};
|
||||
speak_with_history("Materials. " + options[selection], true);
|
||||
|
||||
while(true) {
|
||||
wait(5);
|
||||
|
||||
@@ -75,8 +75,6 @@ void run_runes_menu() {
|
||||
return;
|
||||
}
|
||||
|
||||
speak_with_history("Runes.", true);
|
||||
|
||||
// Build list of unlocked runes
|
||||
string[] rune_options;
|
||||
int[] rune_types;
|
||||
@@ -95,6 +93,7 @@ void run_runes_menu() {
|
||||
}
|
||||
|
||||
int selection = 0;
|
||||
speak_with_history("Runes. " + rune_options[selection], true);
|
||||
|
||||
while(true) {
|
||||
wait(5);
|
||||
@@ -130,8 +129,6 @@ void run_runes_menu() {
|
||||
}
|
||||
|
||||
void run_rune_equipment_menu(int rune_type) {
|
||||
speak_with_history("Select equipment to engrave with " + get_rune_name(rune_type) + ".", true);
|
||||
|
||||
// Build list of equipment that can be runed
|
||||
string[] equipment_options;
|
||||
int[] equipment_types;
|
||||
@@ -153,6 +150,7 @@ void run_rune_equipment_menu(int rune_type) {
|
||||
}
|
||||
|
||||
int selection = 0;
|
||||
speak_with_history("Select equipment to engrave with " + get_rune_name(rune_type) + ". " + equipment_options[selection], true);
|
||||
|
||||
while(true) {
|
||||
wait(5);
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
// Crafting tools
|
||||
void run_tools_menu() {
|
||||
speak_with_history("Tools.", true);
|
||||
|
||||
int selection = 0;
|
||||
string[] options = {
|
||||
"Stone Knife (2 Stones)",
|
||||
@@ -14,6 +12,7 @@ void run_tools_menu() {
|
||||
"Reed Basket (3 Reeds)",
|
||||
"Clay Pot (3 Clay)"
|
||||
};
|
||||
speak_with_history("Tools. " + options[selection], true);
|
||||
|
||||
while(true) {
|
||||
wait(5);
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
// Crafting weapons
|
||||
void run_weapons_menu() {
|
||||
speak_with_history("Weapons.", true);
|
||||
|
||||
int selection = 0;
|
||||
string[] options = {
|
||||
"Spear (1 Stick, 1 Vine, 1 Stone) [Requires Knife]",
|
||||
"Sling (1 Skin, 2 Vines)",
|
||||
"Bow (1 Stick, 1 Bowstring)"
|
||||
};
|
||||
speak_with_history("Weapons. " + options[selection], true);
|
||||
|
||||
while(true) {
|
||||
wait(5);
|
||||
|
||||
@@ -8,8 +8,6 @@ void check_crafting_menu(int x, int base_end_tile) {
|
||||
}
|
||||
|
||||
void run_crafting_menu() {
|
||||
speak_with_history("Crafting menu.", true);
|
||||
|
||||
int selection = 0;
|
||||
string[] categories;
|
||||
int[] category_types;
|
||||
@@ -35,6 +33,7 @@ void run_crafting_menu() {
|
||||
categories.insert_last("Runes");
|
||||
category_types.insert_last(6);
|
||||
}
|
||||
speak_with_history("Crafting menu. " + categories[selection], true);
|
||||
|
||||
while(true) {
|
||||
wait(5);
|
||||
|
||||
@@ -96,6 +96,13 @@ string get_creature_death_sound_from_alert(string alert_sound)
|
||||
filename = filename.substr(0, dot_index);
|
||||
}
|
||||
|
||||
if (filename == "bandit3" || filename == "bandit4") {
|
||||
string female_death = "sounds/enemies/bandit_female_dies.ogg";
|
||||
if (file_exists(female_death)) {
|
||||
return female_death;
|
||||
}
|
||||
}
|
||||
|
||||
// Strip trailing digits (bandit1 -> bandit, goblin2 -> goblin)
|
||||
while (filename.length() > 0) {
|
||||
string last_char = filename.substr(filename.length() - 1, 1);
|
||||
|
||||
@@ -936,6 +936,7 @@ void update_climbing() {
|
||||
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);
|
||||
attempt_pet_offer_from_tree();
|
||||
}
|
||||
}
|
||||
// Climbing down
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
// Main key check functions (called from main game loop)
|
||||
void check_inventory_keys(int x) {
|
||||
if (key_pressed(KEY_P)) {
|
||||
show_character_info();
|
||||
run_character_info_menu();
|
||||
return;
|
||||
}
|
||||
if (key_pressed(KEY_I)) {
|
||||
|
||||
@@ -221,6 +221,7 @@ void run_learn_sounds_menu() {
|
||||
|
||||
while (true) {
|
||||
wait(5);
|
||||
handle_global_volume_keys();
|
||||
|
||||
if (key_pressed(KEY_ESCAPE)) {
|
||||
speak_with_history("Closed.", true);
|
||||
|
||||
@@ -7,8 +7,6 @@ void run_base_info_menu() {
|
||||
return;
|
||||
}
|
||||
|
||||
speak_with_history("Base info.", true);
|
||||
|
||||
int selection = 0;
|
||||
string[] options;
|
||||
options.insert_last("Barricade health " + barricade_health + " of " + BARRICADE_MAX_HEALTH);
|
||||
@@ -53,6 +51,11 @@ void run_base_info_menu() {
|
||||
int[] filtered_indices;
|
||||
string[] filtered_options;
|
||||
apply_menu_filter(filter_text, options, filtered_indices, filtered_options);
|
||||
if (filtered_options.length() == 0) {
|
||||
speak_with_history("Base info. No options.", true);
|
||||
} else {
|
||||
speak_with_history("Base info. " + filtered_options[selection], true);
|
||||
}
|
||||
|
||||
while(true) {
|
||||
wait(5);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Character info display
|
||||
// Functions for displaying character stats and equipment
|
||||
|
||||
void show_character_info() {
|
||||
void run_character_info_menu() {
|
||||
string[] equipped_clothing;
|
||||
string[] missing_slots;
|
||||
|
||||
@@ -19,22 +19,85 @@ void show_character_info() {
|
||||
if (equipped_feet == EQUIP_MOCCASINS) equipped_clothing.insert_last("Moccasins");
|
||||
else missing_slots.insert_last("feet");
|
||||
|
||||
string info = "Character info. ";
|
||||
string[] options;
|
||||
if (player_name != "") {
|
||||
string sex_label = (player_sex == SEX_FEMALE) ? "Female" : "Male";
|
||||
info += "Name " + player_name + ". Sex " + sex_label + ". ";
|
||||
}
|
||||
info += "Health " + player_health + " of " + max_health + ". ";
|
||||
info += "Weapon " + get_equipped_weapon_name() + ". ";
|
||||
if (equipped_clothing.length() > 0) {
|
||||
info += "Clothing equipped: " + join_string_list(equipped_clothing) + ". ";
|
||||
options.insert_last("Name " + player_name + ". Sex " + sex_label + ".");
|
||||
} else {
|
||||
info += "No clothing equipped. ";
|
||||
options.insert_last("Name unknown.");
|
||||
}
|
||||
options.insert_last("Health " + player_health + " of " + max_health + ".");
|
||||
options.insert_last("Weapon " + get_equipped_weapon_name() + ".");
|
||||
if (equipped_clothing.length() > 0) {
|
||||
options.insert_last("Clothing equipped: " + join_string_list(equipped_clothing) + ".");
|
||||
} else {
|
||||
options.insert_last("No clothing equipped.");
|
||||
}
|
||||
if (missing_slots.length() > 0) {
|
||||
info += "Missing " + join_string_list(missing_slots) + ". ";
|
||||
options.insert_last("Missing " + join_string_list(missing_slots) + ".");
|
||||
}
|
||||
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 + ".");
|
||||
} else {
|
||||
options.insert_last("Pet none.");
|
||||
}
|
||||
|
||||
int selection = 0;
|
||||
string filter_text = "";
|
||||
int[] filtered_indices;
|
||||
string[] filtered_options;
|
||||
apply_menu_filter(filter_text, options, filtered_indices, filtered_options);
|
||||
if (filtered_options.length() == 0) {
|
||||
speak_with_history("Character info. No options.", true);
|
||||
} else {
|
||||
speak_with_history("Character info. " + filtered_options[selection], true);
|
||||
}
|
||||
|
||||
while(true) {
|
||||
wait(5);
|
||||
if (menu_background_tick()) {
|
||||
return;
|
||||
}
|
||||
if (key_pressed(KEY_ESCAPE)) {
|
||||
speak_with_history("Closed.", true);
|
||||
break;
|
||||
}
|
||||
|
||||
bool filter_changed = update_menu_filter_state(filter_text, options, filtered_indices, filtered_options, selection);
|
||||
if (filter_changed) {
|
||||
if (filtered_options.length() == 0) {
|
||||
if (filter_text.length() > 0) {
|
||||
speak_with_history("No matches for " + filter_text + ".", true);
|
||||
} else {
|
||||
speak_with_history("No options.", true);
|
||||
}
|
||||
} else {
|
||||
speak_with_history(filtered_options[selection], true);
|
||||
}
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_DOWN)) {
|
||||
if (filtered_options.length() > 0) {
|
||||
play_menu_move_sound();
|
||||
selection++;
|
||||
if (selection >= int(filtered_options.length())) selection = 0;
|
||||
speak_with_history(filtered_options[selection], true);
|
||||
}
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_UP)) {
|
||||
if (filtered_options.length() > 0) {
|
||||
play_menu_move_sound();
|
||||
selection--;
|
||||
if (selection < 0) selection = int(filtered_options.length()) - 1;
|
||||
speak_with_history(filtered_options[selection], true);
|
||||
}
|
||||
}
|
||||
}
|
||||
info += "Favor " + format_favor(favor) + ". ";
|
||||
info += "Speed " + get_speed_status() + ".";
|
||||
speak_with_history(info, true);
|
||||
}
|
||||
|
||||
void show_character_info() {
|
||||
run_character_info_menu();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ bool menu_background_tick() {
|
||||
if (return_to_main_menu_requested) {
|
||||
return true;
|
||||
}
|
||||
handle_global_volume_keys();
|
||||
if (!menuBackgroundUpdatesEnabled) {
|
||||
return false;
|
||||
}
|
||||
@@ -22,6 +23,7 @@ bool menu_background_tick() {
|
||||
update_flying_creatures();
|
||||
update_weapon_range_audio_all();
|
||||
update_world_drops();
|
||||
update_pets();
|
||||
update_blessings();
|
||||
update_notifications();
|
||||
|
||||
|
||||
@@ -0,0 +1,647 @@
|
||||
// Pet system
|
||||
// Handles pet acquisition, loyalty, feeding, retrieval, and combat support
|
||||
|
||||
bool petActive = false;
|
||||
string petSoundPath = "";
|
||||
string petType = "";
|
||||
string petGender = "";
|
||||
int petLoyalty = 0;
|
||||
bool petOut = false;
|
||||
|
||||
timer petAttackTimer;
|
||||
timer petRetrieveTimer;
|
||||
|
||||
string[] petEventMessages;
|
||||
string[] petEventSounds;
|
||||
int[] petEventPositions;
|
||||
int petEventSoundHandle = -1;
|
||||
|
||||
const int PET_TRAVEL_NONE = 0;
|
||||
const int PET_TRAVEL_ATTACK = 1;
|
||||
const int PET_TRAVEL_RETRIEVE = 2;
|
||||
|
||||
bool petTravelActive = false;
|
||||
int petTravelAction = PET_TRAVEL_NONE;
|
||||
int petTravelStartPos = 0;
|
||||
int petTravelTargetPos = -1;
|
||||
int petTravelTargetKind = -1;
|
||||
string petTravelTargetLabel = "";
|
||||
int petTravelDurationMs = 0;
|
||||
int petTravelSoundHandle = -1;
|
||||
timer petTravelTimer;
|
||||
|
||||
string[] petSoundPaths;
|
||||
bool petSoundsInitialized = false;
|
||||
|
||||
string normalize_pet_path(const string&in path) {
|
||||
return path.replace("\\", "/", true);
|
||||
}
|
||||
|
||||
string collapse_pet_spaces(const string&in text) {
|
||||
string result = text;
|
||||
while (result.find_first(" ") > -1) {
|
||||
result = result.replace(" ", " ", true);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void gather_pet_sound_files(const string&in basePath, string[]@ outFiles) {
|
||||
string[]@ files = find_files(basePath + "/*.ogg");
|
||||
if (@files !is null) {
|
||||
for (uint i = 0; i < files.length(); i++) {
|
||||
outFiles.insert_last(basePath + "/" + files[i]);
|
||||
}
|
||||
}
|
||||
|
||||
string[]@ folders = find_directories(basePath + "/*");
|
||||
if (@folders !is null) {
|
||||
for (uint i = 0; i < folders.length(); i++) {
|
||||
gather_pet_sound_files(basePath + "/" + folders[i], outFiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool sort_pet_sound_paths(const string &in a, const string &in b) {
|
||||
return a.lower() < b.lower();
|
||||
}
|
||||
|
||||
void init_pet_sounds() {
|
||||
if (petSoundsInitialized) return;
|
||||
petSoundsInitialized = true;
|
||||
petSoundPaths.resize(0);
|
||||
if (!directory_exists("sounds/pets")) return;
|
||||
gather_pet_sound_files("sounds/pets", petSoundPaths);
|
||||
if (petSoundPaths.length() > 1) {
|
||||
petSoundPaths.sort(sort_pet_sound_paths);
|
||||
}
|
||||
}
|
||||
|
||||
string get_pet_name_from_sound_path(const string&in soundPath) {
|
||||
string normalizedPath = normalize_pet_path(soundPath);
|
||||
int slashPos = normalizedPath.find_last_of("/");
|
||||
string name = (slashPos >= 0) ? normalizedPath.substr(slashPos + 1) : normalizedPath;
|
||||
string lowerName = name.lower();
|
||||
if (lowerName.length() > 4 && lowerName.substr(lowerName.length() - 4) == ".ogg") {
|
||||
name = name.substr(0, name.length() - 4);
|
||||
}
|
||||
name = name.replace("_", " ", true);
|
||||
name = name.replace("-", " ", true);
|
||||
name = collapse_pet_spaces(name);
|
||||
name = name.lower();
|
||||
name.trim_whitespace_this();
|
||||
if (name.length() == 0) return "Pet";
|
||||
string first = name.substr(0, 1).upper();
|
||||
if (name.length() == 1) return first;
|
||||
return first + name.substr(1);
|
||||
}
|
||||
|
||||
string get_pet_sound_for_name(const string&in petName) {
|
||||
init_pet_sounds();
|
||||
string target = petName.lower();
|
||||
for (uint i = 0; i < petSoundPaths.length(); i++) {
|
||||
string normalizedPath = normalize_pet_path(petSoundPaths[i]);
|
||||
int slashPos = normalizedPath.find_last_of("/");
|
||||
string fileName = (slashPos >= 0) ? normalizedPath.substr(slashPos + 1) : normalizedPath;
|
||||
string lowerName = fileName.lower();
|
||||
if (lowerName.length() > 4 && lowerName.substr(lowerName.length() - 4) == ".ogg") {
|
||||
lowerName = lowerName.substr(0, lowerName.length() - 4);
|
||||
}
|
||||
if (lowerName == target) {
|
||||
return petSoundPaths[i];
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
int get_pet_food_personal_total() {
|
||||
return get_personal_count(ITEM_MEAT)
|
||||
+ get_personal_count(ITEM_SMOKED_FISH)
|
||||
+ get_personal_count(ITEM_FISH)
|
||||
+ get_personal_count(ITEM_BASKET_FOOD);
|
||||
}
|
||||
|
||||
int get_pet_food_storage_total() {
|
||||
return get_storage_count(ITEM_MEAT)
|
||||
+ get_storage_count(ITEM_SMOKED_FISH)
|
||||
+ get_storage_count(ITEM_FISH)
|
||||
+ get_storage_count(ITEM_BASKET_FOOD);
|
||||
}
|
||||
|
||||
bool has_pet_food_available() {
|
||||
return get_pet_food_personal_total() + get_pet_food_storage_total() > 0;
|
||||
}
|
||||
|
||||
bool consume_pet_food() {
|
||||
if (get_personal_count(ITEM_MEAT) > 0) {
|
||||
add_personal_count(ITEM_MEAT, -1);
|
||||
return true;
|
||||
}
|
||||
if (get_personal_count(ITEM_SMOKED_FISH) > 0) {
|
||||
add_personal_count(ITEM_SMOKED_FISH, -1);
|
||||
return true;
|
||||
}
|
||||
if (get_personal_count(ITEM_FISH) > 0) {
|
||||
pop_personal_fish_weight();
|
||||
add_personal_count(ITEM_FISH, -1);
|
||||
return true;
|
||||
}
|
||||
if (get_personal_count(ITEM_BASKET_FOOD) > 0) {
|
||||
add_personal_count(ITEM_BASKET_FOOD, -1);
|
||||
return true;
|
||||
}
|
||||
if (get_storage_count(ITEM_MEAT) > 0) {
|
||||
add_storage_count(ITEM_MEAT, -1);
|
||||
return true;
|
||||
}
|
||||
if (get_storage_count(ITEM_SMOKED_FISH) > 0) {
|
||||
add_storage_count(ITEM_SMOKED_FISH, -1);
|
||||
return true;
|
||||
}
|
||||
if (get_storage_count(ITEM_FISH) > 0) {
|
||||
pop_storage_fish_weight();
|
||||
add_storage_count(ITEM_FISH, -1);
|
||||
return true;
|
||||
}
|
||||
if (get_storage_count(ITEM_BASKET_FOOD) > 0) {
|
||||
add_storage_count(ITEM_BASKET_FOOD, -1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void queue_pet_event(const string&in message, int soundPos = -1) {
|
||||
if (!petActive) return;
|
||||
if (message.length() == 0) return;
|
||||
petEventMessages.insert_last(message);
|
||||
petEventSounds.insert_last(petSoundPath);
|
||||
petEventPositions.insert_last(soundPos);
|
||||
}
|
||||
|
||||
void update_pet_events() {
|
||||
if (petEventMessages.length() == 0) {
|
||||
petEventSoundHandle = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (petEventSoundHandle != -1) {
|
||||
if (p.sound_is_active(petEventSoundHandle)) {
|
||||
return;
|
||||
}
|
||||
string message = petEventMessages[0];
|
||||
petEventMessages.remove_at(0);
|
||||
petEventSounds.remove_at(0);
|
||||
petEventPositions.remove_at(0);
|
||||
petEventSoundHandle = -1;
|
||||
speak_with_history(message, true);
|
||||
return;
|
||||
}
|
||||
|
||||
string soundPath = petEventSounds[0];
|
||||
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);
|
||||
} else {
|
||||
petEventSoundHandle = p.play_stationary(soundPath, false);
|
||||
}
|
||||
if (petEventSoundHandle != -1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
string message = petEventMessages[0];
|
||||
petEventMessages.remove_at(0);
|
||||
petEventSounds.remove_at(0);
|
||||
petEventPositions.remove_at(0);
|
||||
speak_with_history(message, true);
|
||||
}
|
||||
|
||||
void reset_pet_state() {
|
||||
petActive = false;
|
||||
petSoundPath = "";
|
||||
petType = "";
|
||||
petGender = "";
|
||||
petLoyalty = 0;
|
||||
petOut = false;
|
||||
petEventMessages.resize(0);
|
||||
petEventSounds.resize(0);
|
||||
petEventPositions.resize(0);
|
||||
petEventSoundHandle = -1;
|
||||
petTravelActive = false;
|
||||
petTravelAction = PET_TRAVEL_NONE;
|
||||
petTravelStartPos = 0;
|
||||
petTravelTargetPos = -1;
|
||||
petTravelTargetKind = -1;
|
||||
petTravelTargetLabel = "";
|
||||
petTravelDurationMs = 0;
|
||||
safe_destroy_sound(petTravelSoundHandle);
|
||||
petTravelSoundHandle = -1;
|
||||
petAttackTimer.restart();
|
||||
petRetrieveTimer.restart();
|
||||
petTravelTimer.restart();
|
||||
}
|
||||
|
||||
void pet_leave() {
|
||||
string oldPet = petType;
|
||||
reset_pet_state();
|
||||
if (oldPet != "") {
|
||||
speak_with_history(oldPet + " leaves.", true);
|
||||
}
|
||||
}
|
||||
|
||||
void clamp_pet_loyalty() {
|
||||
if (petLoyalty < 0) petLoyalty = 0;
|
||||
if (petLoyalty > PET_LOYALTY_MAX) petLoyalty = PET_LOYALTY_MAX;
|
||||
}
|
||||
|
||||
void adjust_pet_loyalty(int delta) {
|
||||
if (!petActive) return;
|
||||
petLoyalty += delta;
|
||||
clamp_pet_loyalty();
|
||||
if (petLoyalty <= 0) {
|
||||
pet_leave();
|
||||
}
|
||||
}
|
||||
|
||||
void check_pet_call_key() {
|
||||
if (!key_pressed(KEY_SPACE)) return;
|
||||
if (!petActive) {
|
||||
speak_with_history("No pet.", true);
|
||||
return;
|
||||
}
|
||||
if (petOut) {
|
||||
petOut = false;
|
||||
stop_pet_travel();
|
||||
speak_with_history("Pet called back.", true);
|
||||
return;
|
||||
}
|
||||
petOut = true;
|
||||
if (file_exists("sounds/pets/call_pet.ogg")) {
|
||||
p.play_stationary("sounds/pets/call_pet.ogg", false);
|
||||
}
|
||||
}
|
||||
|
||||
void adopt_pet(const string&in soundPath) {
|
||||
petActive = true;
|
||||
petSoundPath = soundPath;
|
||||
petType = get_pet_name_from_sound_path(soundPath);
|
||||
petGender = (random(0, 1) == 0) ? "Male" : "Female";
|
||||
petLoyalty = PET_START_LOYALTY;
|
||||
petOut = false;
|
||||
petAttackTimer.restart();
|
||||
petRetrieveTimer.restart();
|
||||
petTravelTimer.restart();
|
||||
speak_with_history("A " + petType + " joins you.", true);
|
||||
}
|
||||
|
||||
void stop_pet_travel() {
|
||||
petTravelActive = false;
|
||||
petTravelAction = PET_TRAVEL_NONE;
|
||||
petTravelStartPos = 0;
|
||||
petTravelTargetPos = -1;
|
||||
petTravelTargetKind = -1;
|
||||
petTravelTargetLabel = "";
|
||||
petTravelDurationMs = 0;
|
||||
safe_destroy_sound(petTravelSoundHandle);
|
||||
petTravelSoundHandle = -1;
|
||||
}
|
||||
|
||||
int get_pet_travel_duration_ms(int targetPos) {
|
||||
int distance = abs(targetPos - x);
|
||||
int duration = distance * PET_MOVE_SPEED;
|
||||
if (duration < PET_TRAVEL_MIN_MS) duration = PET_TRAVEL_MIN_MS;
|
||||
return duration;
|
||||
}
|
||||
|
||||
void start_pet_travel_attack(int targetPos, const string&in targetLabel, int targetKind) {
|
||||
stop_pet_travel();
|
||||
petTravelActive = true;
|
||||
petTravelAction = PET_TRAVEL_ATTACK;
|
||||
petTravelStartPos = x;
|
||||
petTravelTargetPos = targetPos;
|
||||
petTravelTargetLabel = targetLabel;
|
||||
petTravelTargetKind = targetKind;
|
||||
petTravelDurationMs = get_pet_travel_duration_ms(targetPos);
|
||||
petTravelTimer.restart();
|
||||
|
||||
if (petSoundPath != "" && file_exists(petSoundPath)) {
|
||||
petTravelSoundHandle = play_1d_with_volume_step(
|
||||
petSoundPath,
|
||||
x,
|
||||
petTravelStartPos,
|
||||
true,
|
||||
PLAYER_WEAPON_SOUND_VOLUME_STEP
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void start_pet_travel_retrieve(int targetPos) {
|
||||
stop_pet_travel();
|
||||
petTravelActive = true;
|
||||
petTravelAction = PET_TRAVEL_RETRIEVE;
|
||||
petTravelStartPos = x;
|
||||
petTravelTargetPos = targetPos;
|
||||
petTravelDurationMs = get_pet_travel_duration_ms(targetPos);
|
||||
petTravelTimer.restart();
|
||||
}
|
||||
|
||||
void update_pet_travel() {
|
||||
if (!petTravelActive) return;
|
||||
if (petTravelDurationMs < 1) petTravelDurationMs = 1;
|
||||
|
||||
int elapsed = petTravelTimer.elapsed;
|
||||
float progress = float(elapsed) / float(petTravelDurationMs);
|
||||
if (progress > 1.0f) progress = 1.0f;
|
||||
|
||||
int travel = int(float(petTravelTargetPos - petTravelStartPos) * progress);
|
||||
int currentPos = petTravelStartPos + travel;
|
||||
if (petTravelSoundHandle != -1) {
|
||||
p.update_sound_1d(petTravelSoundHandle, currentPos);
|
||||
}
|
||||
|
||||
if (elapsed < petTravelDurationMs) return;
|
||||
|
||||
safe_destroy_sound(petTravelSoundHandle);
|
||||
petTravelSoundHandle = -1;
|
||||
|
||||
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 (hit) {
|
||||
queue_pet_event("Your " + petType + " attacks the " + petTravelTargetLabel + ".", petTravelTargetPos);
|
||||
}
|
||||
adjust_pet_loyalty(-PET_LOYALTY_ACTION_COST);
|
||||
} else if (petTravelAction == PET_TRAVEL_RETRIEVE) {
|
||||
WorldDrop@ drop = get_drop_at(petTravelTargetPos);
|
||||
if (drop !is null) {
|
||||
string message = "";
|
||||
if (try_pet_pickup_world_drop(drop, message)) {
|
||||
remove_drop_at(drop.position);
|
||||
queue_pet_event(message);
|
||||
petOut = false;
|
||||
}
|
||||
}
|
||||
adjust_pet_loyalty(-PET_LOYALTY_ACTION_COST);
|
||||
}
|
||||
|
||||
stop_pet_travel();
|
||||
}
|
||||
|
||||
bool run_pet_offer_menu(const string&in soundPath, const string&in reasonText) {
|
||||
if (petActive) return false;
|
||||
if (soundPath == "" || !file_exists(soundPath)) return false;
|
||||
if (!has_pet_food_available()) return false;
|
||||
|
||||
string petName = get_pet_name_from_sound_path(soundPath);
|
||||
string prompt = "A friendly looking " + petName + " begs for food. Accept?";
|
||||
if (reasonText != "") {
|
||||
prompt += " " + reasonText;
|
||||
}
|
||||
|
||||
string[] options;
|
||||
options.insert_last("Yes");
|
||||
options.insert_last("No");
|
||||
int selection = 0;
|
||||
|
||||
speak_with_history(prompt + " " + options[selection], true);
|
||||
|
||||
while (true) {
|
||||
wait(5);
|
||||
if (menu_background_tick()) {
|
||||
return false;
|
||||
}
|
||||
if (key_pressed(KEY_ESCAPE)) {
|
||||
speak_with_history("Declined.", true);
|
||||
return false;
|
||||
}
|
||||
if (key_pressed(KEY_DOWN)) {
|
||||
play_menu_move_sound();
|
||||
selection++;
|
||||
if (selection >= int(options.length())) selection = 0;
|
||||
speak_with_history(options[selection], true);
|
||||
}
|
||||
if (key_pressed(KEY_UP)) {
|
||||
play_menu_move_sound();
|
||||
selection--;
|
||||
if (selection < 0) selection = int(options.length()) - 1;
|
||||
speak_with_history(options[selection], true);
|
||||
}
|
||||
if (key_pressed(KEY_RETURN)) {
|
||||
play_menu_select_sound();
|
||||
if (selection == 0) {
|
||||
adopt_pet(soundPath);
|
||||
return true;
|
||||
}
|
||||
speak_with_history("Declined.", true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void attempt_pet_offer_random(const string&in reasonText) {
|
||||
if (petActive) return;
|
||||
if (!has_pet_food_available()) return;
|
||||
init_pet_sounds();
|
||||
if (petSoundPaths.length() == 0) return;
|
||||
int pick = random(0, petSoundPaths.length() - 1);
|
||||
run_pet_offer_menu(petSoundPaths[pick], reasonText);
|
||||
}
|
||||
|
||||
void attempt_pet_offer_from_quest(int score) {
|
||||
if (score < QUEST_LOG_SCORE) return;
|
||||
attempt_pet_offer_random("");
|
||||
}
|
||||
|
||||
void attempt_pet_offer_from_adventure() {
|
||||
if (petActive) return;
|
||||
if (random(1, 100) > PET_ADVENTURE_CHANCE) return;
|
||||
attempt_pet_offer_random("");
|
||||
}
|
||||
|
||||
void attempt_pet_offer_from_tree() {
|
||||
if (petActive) return;
|
||||
if (random(1, 100) > PET_TREE_HAWK_CHANCE) return;
|
||||
string hawkSound = get_pet_sound_for_name("hawk");
|
||||
if (hawkSound == "") return;
|
||||
run_pet_offer_menu(hawkSound, "");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
add_personal_count(ITEM_SMALL_GAME, 1);
|
||||
personal_small_game_types.insert_last(gameType);
|
||||
message = "Your " + petType + " retrieved " + gameType + ".";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool try_pet_pickup_world_drop(WorldDrop@ drop, string &out message) {
|
||||
if (drop is null) return false;
|
||||
if (get_flying_creature_config_by_drop_type(drop.type) !is null) {
|
||||
return try_pet_pickup_small_game(drop.type, message);
|
||||
}
|
||||
if (drop.type == "arrow") {
|
||||
int maxArrows = get_arrow_limit();
|
||||
if (maxArrows <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (get_personal_count(ITEM_ARROWS) >= maxArrows) {
|
||||
return false;
|
||||
}
|
||||
add_personal_count(ITEM_ARROWS, 1);
|
||||
message = "Your " + petType + " retrieved an arrow.";
|
||||
return true;
|
||||
}
|
||||
if (drop.type == "boar carcass") {
|
||||
if (get_personal_count(ITEM_BOAR_CARCASSES) >= get_personal_stack_limit()) {
|
||||
return false;
|
||||
}
|
||||
add_personal_count(ITEM_BOAR_CARCASSES, 1);
|
||||
message = "Your " + petType + " retrieved a boar carcass.";
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
WorldDrop@ find_pet_drop_target() {
|
||||
int bestDistance = PET_RANGE + 1;
|
||||
WorldDrop@ best = null;
|
||||
for (uint i = 0; i < world_drops.length(); i++) {
|
||||
int distance = abs(world_drops[i].position - x);
|
||||
if (distance > PET_RANGE) continue;
|
||||
if (distance < bestDistance) {
|
||||
bestDistance = distance;
|
||||
@best = world_drops[i];
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
void update_pet_retrieval() {
|
||||
if (!petActive) return;
|
||||
if (!petOut) return;
|
||||
if (petLoyalty <= 0) return;
|
||||
if (petRetrieveTimer.elapsed < PET_RETRIEVE_COOLDOWN) return;
|
||||
if (petEventMessages.length() > 2) return;
|
||||
if (petTravelActive) return;
|
||||
|
||||
WorldDrop@ drop = find_pet_drop_target();
|
||||
if (drop is null) return;
|
||||
|
||||
petRetrieveTimer.restart();
|
||||
start_pet_travel_retrieve(drop.position);
|
||||
}
|
||||
|
||||
bool find_pet_attack_target(int &out targetPos, string &out targetLabel, int &out targetKind) {
|
||||
int bestDistance = PET_RANGE + 1;
|
||||
targetPos = -1;
|
||||
targetLabel = "";
|
||||
targetKind = -1;
|
||||
|
||||
for (uint i = 0; i < bandits.length(); i++) {
|
||||
int distance = abs(bandits[i].position - x);
|
||||
if (distance > PET_RANGE) continue;
|
||||
if (distance < bestDistance) {
|
||||
bestDistance = distance;
|
||||
targetPos = bandits[i].position;
|
||||
targetLabel = "bandit";
|
||||
targetKind = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint i = 0; i < undeads.length(); i++) {
|
||||
if (undeads[i].undead_type == "undead_resident") continue;
|
||||
int distance = abs(undeads[i].position - x);
|
||||
if (distance > PET_RANGE) continue;
|
||||
if (distance < bestDistance) {
|
||||
bestDistance = distance;
|
||||
targetPos = undeads[i].position;
|
||||
targetLabel = "undead";
|
||||
targetKind = 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint i = 0; i < ground_games.length(); i++) {
|
||||
int distance = abs(ground_games[i].position - x);
|
||||
if (distance > PET_RANGE) continue;
|
||||
if (distance < bestDistance) {
|
||||
bestDistance = distance;
|
||||
targetPos = ground_games[i].position;
|
||||
targetLabel = "boar";
|
||||
targetKind = 2;
|
||||
}
|
||||
}
|
||||
|
||||
return targetPos != -1;
|
||||
}
|
||||
|
||||
void update_pet_attack() {
|
||||
if (!petActive) return;
|
||||
if (!petOut) return;
|
||||
if (petLoyalty <= 0) return;
|
||||
if (petAttackTimer.elapsed < PET_ATTACK_COOLDOWN) return;
|
||||
if (petTravelActive) return;
|
||||
|
||||
int targetPos = -1;
|
||||
string targetLabel = "";
|
||||
int targetKind = -1;
|
||||
if (!find_pet_attack_target(targetPos, targetLabel, targetKind)) return;
|
||||
|
||||
petAttackTimer.restart();
|
||||
start_pet_travel_attack(targetPos, targetLabel, targetKind);
|
||||
}
|
||||
|
||||
void attempt_pet_random_find() {
|
||||
if (!petActive) return;
|
||||
if (!petOut) return;
|
||||
if (petLoyalty < PET_LOYALTY_BONUS_THRESHOLD) return;
|
||||
if (random(1, 100) > PET_RANDOM_FIND_CHANCE) return;
|
||||
|
||||
int[] possibleItems = {ITEM_STICKS, ITEM_VINES, ITEM_STONES, ITEM_CLAY};
|
||||
int itemType = possibleItems[random(0, possibleItems.length() - 1)];
|
||||
int added = add_to_stack(get_personal_count(itemType), 1);
|
||||
if (added <= 0) return;
|
||||
|
||||
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);
|
||||
adjust_pet_loyalty(-PET_LOYALTY_ACTION_COST);
|
||||
petOut = false;
|
||||
}
|
||||
|
||||
void handle_pet_hourly_update(int hour) {
|
||||
if (!petActive) return;
|
||||
|
||||
if (get_pet_food_personal_total() == 0) {
|
||||
adjust_pet_loyalty(-PET_LOYALTY_HUNGER_LOSS);
|
||||
}
|
||||
|
||||
if (!petActive) return;
|
||||
|
||||
if (hour % 8 == 0) {
|
||||
if (consume_pet_food()) {
|
||||
petLoyalty += PET_LOYALTY_EAT_BONUS;
|
||||
clamp_pet_loyalty();
|
||||
}
|
||||
}
|
||||
|
||||
attempt_pet_random_find();
|
||||
}
|
||||
|
||||
void update_pets() {
|
||||
update_pet_travel();
|
||||
if (petActive && petOut) {
|
||||
update_pet_retrieval();
|
||||
update_pet_attack();
|
||||
}
|
||||
update_pet_events();
|
||||
}
|
||||
@@ -153,6 +153,7 @@ void apply_quest_reward(int score) {
|
||||
|
||||
message += "\nScore: " + score;
|
||||
text_reader(message, "Quest Rewards", true);
|
||||
attempt_pet_offer_from_quest(score);
|
||||
}
|
||||
|
||||
void run_quest(int quest_type) {
|
||||
|
||||
@@ -51,6 +51,7 @@ int run_bat_invasion() {
|
||||
|
||||
while (flight_timer.elapsed < flight_time) {
|
||||
wait(update_interval);
|
||||
handle_global_volume_keys();
|
||||
|
||||
// Calculate current position based on elapsed time
|
||||
float progress = float(flight_timer.elapsed) / float(flight_time);
|
||||
|
||||
@@ -32,6 +32,7 @@ int run_catch_the_boomerang() {
|
||||
|
||||
while (true) {
|
||||
wait(5);
|
||||
handle_global_volume_keys();
|
||||
if (key_pressed(KEY_SPACE)) {
|
||||
break;
|
||||
}
|
||||
@@ -46,6 +47,7 @@ int run_catch_the_boomerang() {
|
||||
|
||||
while (!resolved) {
|
||||
wait(stepDelay);
|
||||
handle_global_volume_keys();
|
||||
|
||||
p.play_2d(boomerangSound, 0.0, 0.0, 0.0, boomerangY, false);
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ void run_practice_mode() {
|
||||
|
||||
while (true) {
|
||||
wait(5);
|
||||
handle_global_volume_keys();
|
||||
|
||||
// Check for practice note input
|
||||
int note = get_note_from_key();
|
||||
@@ -79,12 +80,14 @@ int run_enchanted_melody() {
|
||||
while (true) {
|
||||
for (uint i = 0; i < pattern.length(); i++) {
|
||||
play_note(pattern[i]);
|
||||
handle_global_volume_keys();
|
||||
wait(600);
|
||||
}
|
||||
|
||||
uint index = 0;
|
||||
while (index < pattern.length()) {
|
||||
wait(5);
|
||||
handle_global_volume_keys();
|
||||
int note = get_note_from_key();
|
||||
if (note == -1) continue;
|
||||
play_note(note);
|
||||
|
||||
@@ -44,6 +44,8 @@ int run_escape_from_hel() {
|
||||
int step_time = base_step_time - (total_steps * 8);
|
||||
if (step_time < 50) step_time = 50; // Minimum to prevent audio/timing issues
|
||||
|
||||
handle_global_volume_keys();
|
||||
|
||||
// Check if jump finished
|
||||
if (jumping && jump_timer.elapsed >= JUMP_DURATION) {
|
||||
jumping = false;
|
||||
@@ -61,6 +63,7 @@ int run_escape_from_hel() {
|
||||
// Wait for step duration, checking for jump input
|
||||
while (step_timer.elapsed < step_time) {
|
||||
wait(5);
|
||||
handle_global_volume_keys();
|
||||
|
||||
// Allow jump anytime when not already jumping
|
||||
if (!jumping && key_pressed(KEY_SPACE)) {
|
||||
|
||||
@@ -87,6 +87,7 @@ int run_skeletal_bard() {
|
||||
speak_with_history("You entered " + guess + " notes and the actual number was " + length + ". " + points + " points. Press Enter to continue.", true);
|
||||
while (true) {
|
||||
wait(5);
|
||||
handle_global_volume_keys();
|
||||
if (return_to_main_menu_requested) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
+43
-3
@@ -356,7 +356,7 @@ string pick_random_name_for_sex(int sex, const string[]@ usedNames) {
|
||||
bool select_player_sex(int &out sex) {
|
||||
string[] options = {"Male", "Female"};
|
||||
int selection = 0;
|
||||
string prompt = "Choose your character's sex.";
|
||||
string prompt = "Choose your sex.";
|
||||
speak_with_history(prompt + " " + options[selection], true);
|
||||
|
||||
while (true) {
|
||||
@@ -365,13 +365,13 @@ bool select_player_sex(int &out sex) {
|
||||
play_menu_move_sound();
|
||||
selection++;
|
||||
if (selection >= options.length()) selection = 0;
|
||||
speak_with_history(prompt + " " + options[selection], true);
|
||||
speak_with_history(options[selection], true);
|
||||
}
|
||||
if (key_pressed(KEY_UP)) {
|
||||
play_menu_move_sound();
|
||||
selection--;
|
||||
if (selection < 0) selection = options.length() - 1;
|
||||
speak_with_history(prompt + " " + options[selection], true);
|
||||
speak_with_history(options[selection], true);
|
||||
}
|
||||
if (key_pressed(KEY_RETURN)) {
|
||||
play_menu_select_sound();
|
||||
@@ -586,6 +586,7 @@ void reset_game_state() {
|
||||
blessing_speed_active = false;
|
||||
blessing_resident_active = false;
|
||||
reset_fylgja_state();
|
||||
reset_pet_state();
|
||||
|
||||
// Reset inventory using the registry system
|
||||
reset_inventory();
|
||||
@@ -800,6 +801,11 @@ bool save_game_state() {
|
||||
saveData.set("adventure_completion_counts", serialize_inventory_array(adventureCompletionCounts));
|
||||
saveData.set("incense_hours_remaining", incense_hours_remaining);
|
||||
saveData.set("incense_burning", incense_burning);
|
||||
saveData.set("pet_active", petActive);
|
||||
saveData.set("pet_sound_path", petSoundPath);
|
||||
saveData.set("pet_type", petType);
|
||||
saveData.set("pet_gender", petGender);
|
||||
saveData.set("pet_loyalty", petLoyalty);
|
||||
|
||||
// Save inventory arrays using new compact format
|
||||
saveData.set("personal_inventory", serialize_inventory_array(personal_inventory));
|
||||
@@ -1106,6 +1112,40 @@ bool load_game_state_from_file(const string&in filename) {
|
||||
incense_hours_remaining = int(get_number(saveData, "incense_hours_remaining", 0));
|
||||
incense_burning = get_bool(saveData, "incense_burning", false);
|
||||
if (incense_hours_remaining > 0) incense_burning = true;
|
||||
petActive = get_bool(saveData, "pet_active", false);
|
||||
string loadedPetSound;
|
||||
petSoundPath = "";
|
||||
if (saveData.get("pet_sound_path", loadedPetSound)) {
|
||||
petSoundPath = loadedPetSound;
|
||||
}
|
||||
string loadedPetType;
|
||||
petType = "";
|
||||
if (saveData.get("pet_type", loadedPetType)) {
|
||||
petType = loadedPetType;
|
||||
}
|
||||
string loadedPetGender;
|
||||
petGender = "";
|
||||
if (saveData.get("pet_gender", loadedPetGender)) {
|
||||
petGender = loadedPetGender;
|
||||
}
|
||||
petLoyalty = int(get_number(saveData, "pet_loyalty", 0));
|
||||
if (!petActive) {
|
||||
petSoundPath = "";
|
||||
petType = "";
|
||||
petGender = "";
|
||||
petLoyalty = 0;
|
||||
}
|
||||
if (petActive && petSoundPath != "" && !file_exists(petSoundPath)) {
|
||||
petActive = false;
|
||||
petSoundPath = "";
|
||||
petType = "";
|
||||
petGender = "";
|
||||
petLoyalty = 0;
|
||||
}
|
||||
if (petActive) {
|
||||
petAttackTimer.restart();
|
||||
petRetrieveTimer.restart();
|
||||
}
|
||||
|
||||
if (x < 0) x = 0;
|
||||
if (x >= MAP_SIZE) x = MAP_SIZE - 1;
|
||||
|
||||
@@ -44,6 +44,7 @@ string text_reader(string content, string title = "Text Reader", bool readonly =
|
||||
while (true) {
|
||||
f.monitor();
|
||||
wait(5);
|
||||
handle_global_volume_keys();
|
||||
|
||||
// Check if user pressed OK (edit mode only)
|
||||
if (!readonly && ok_button != -1 && f.is_pressed(ok_button)) {
|
||||
|
||||
+36
-2
@@ -139,7 +139,7 @@ void attempt_player_item_break_check() {
|
||||
get_breakable_personal_item_types(breakableItems);
|
||||
if (breakableItems.length() == 0) return;
|
||||
|
||||
int roll = random(1, 100);
|
||||
int roll = random(1, PLAYER_ITEM_BREAK_ROLL_MAX);
|
||||
int checkChance = int(playerItemBreakChance); // Floor for comparison
|
||||
if (roll <= checkChance) {
|
||||
int pickIndex = random(0, int(breakableItems.length()) - 1);
|
||||
@@ -610,6 +610,11 @@ void end_invasion() {
|
||||
|
||||
void transition_bandits_to_wandering() {
|
||||
for (uint i = 0; i < bandits.length(); i++) {
|
||||
if (bandits[i].position < bandits[i].home_start) {
|
||||
bandits[i].home_start = bandits[i].position;
|
||||
} else if (bandits[i].position > bandits[i].home_end) {
|
||||
bandits[i].home_end = bandits[i].position;
|
||||
}
|
||||
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);
|
||||
@@ -745,6 +750,7 @@ void update_time() {
|
||||
if (current_hour % 8 == 0) {
|
||||
consume_food_for_residents();
|
||||
}
|
||||
handle_pet_hourly_update(current_hour);
|
||||
|
||||
if (current_hour == 18 && !sun_setting_warned) {
|
||||
notify("The sun is setting.");
|
||||
@@ -870,7 +876,35 @@ string get_time_string() {
|
||||
period = "pm";
|
||||
}
|
||||
|
||||
return display_hour + " oclock " + period + " day " + current_day;
|
||||
string result = display_hour + " oclock " + period + " day " + current_day;
|
||||
string[] conditions;
|
||||
if (is_daytime) {
|
||||
if (weather_state == WEATHER_CLEAR) {
|
||||
conditions.insert_last("Sunny");
|
||||
} else {
|
||||
conditions.insert_last("Daylight");
|
||||
}
|
||||
} else {
|
||||
conditions.insert_last("Dark");
|
||||
}
|
||||
if (weather_state == WEATHER_WINDY) {
|
||||
conditions.insert_last("Windy");
|
||||
} else if (weather_state == WEATHER_RAINY) {
|
||||
conditions.insert_last("Raining");
|
||||
} else if (weather_state == WEATHER_STORMY) {
|
||||
conditions.insert_last("Storming");
|
||||
}
|
||||
if (!is_daytime) {
|
||||
conditions.insert_last("Moon and stars");
|
||||
}
|
||||
if (conditions.length() > 0) {
|
||||
string condition_text = conditions[0];
|
||||
for (uint i = 1; i < conditions.length(); i++) {
|
||||
condition_text += ", " + conditions[i];
|
||||
}
|
||||
result += ". " + condition_text;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void check_ambience_transition() {
|
||||
|
||||
Reference in New Issue
Block a user