1736 lines
65 KiB
Plaintext
1736 lines
65 KiB
Plaintext
// Save system
|
|
|
|
const string SAVE_EXTENSION = ".dat";
|
|
const string SAVE_ENCRYPTION_KEY = "draugnorak_save_v1";
|
|
const int SAVE_VERSION = 3;
|
|
string last_save_error = "";
|
|
string current_save_file = "";
|
|
|
|
string[] get_save_files() {
|
|
string[] result;
|
|
string[]@ items = glob("*" + SAVE_EXTENSION);
|
|
if (@items == null) return result;
|
|
|
|
for (uint i = 0; i < items.length(); i++) {
|
|
string item = items[i];
|
|
if (item.length() >= SAVE_EXTENSION.length() &&
|
|
item.substr(item.length() - SAVE_EXTENSION.length()) == SAVE_EXTENSION) {
|
|
result.insert_last(item);
|
|
}
|
|
}
|
|
if (result.length() > 1) {
|
|
result.sort(sort_string_case_insensitive);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool sort_string_case_insensitive(const string &in a, const string &in b) {
|
|
return a.lower() < b.lower();
|
|
}
|
|
|
|
bool has_save_game() {
|
|
return get_save_files().length() > 0;
|
|
}
|
|
|
|
string encrypt_save_data(const string&in rawData) {
|
|
return string_aes_encrypt(rawData, SAVE_ENCRYPTION_KEY);
|
|
}
|
|
|
|
string decrypt_save_data(const string&in encryptedData) {
|
|
return string_aes_decrypt(encryptedData, SAVE_ENCRYPTION_KEY);
|
|
}
|
|
|
|
bool save_data(const string&in filename, const string&in data) {
|
|
if (data.length() == 0) {
|
|
return false;
|
|
}
|
|
file tmp;
|
|
if (!tmp.open(filename, "wb")) {
|
|
return false;
|
|
}
|
|
|
|
if (tmp.write(data) < data.length()) {
|
|
tmp.close();
|
|
return false;
|
|
}
|
|
|
|
tmp.close();
|
|
return true;
|
|
}
|
|
|
|
bool read_file_string(const string&in filename, string&out data) {
|
|
file tmp;
|
|
if (!tmp.open(filename, "rb")) {
|
|
return false;
|
|
}
|
|
data = tmp.read();
|
|
tmp.close();
|
|
return data.length() > 0;
|
|
}
|
|
|
|
double get_number(dictionary@ data, const string&in key, double defaultValue) {
|
|
double value;
|
|
if (@data == null) return defaultValue;
|
|
if (data.get(key, value)) return value;
|
|
int value_int;
|
|
if (data.get(key, value_int)) return value_int;
|
|
string value_str;
|
|
if (data.get(key, value_str)) {
|
|
return parse_int(value_str);
|
|
}
|
|
return defaultValue;
|
|
}
|
|
|
|
bool get_bool(dictionary@ data, const string&in key, bool defaultValue) {
|
|
bool value;
|
|
if (@data == null) return defaultValue;
|
|
if (data.get(key, value)) return value;
|
|
int value_int;
|
|
if (data.get(key, value_int)) return value_int != 0;
|
|
string value_str;
|
|
if (data.get(key, value_str)) return value_str == "1" || value_str == "true";
|
|
return defaultValue;
|
|
}
|
|
|
|
bool dictionary_has_keys(dictionary@ data) {
|
|
if (@data == null) return false;
|
|
string[]@ keys = data.get_keys();
|
|
return keys.length() > 0;
|
|
}
|
|
|
|
bool has_number_key(dictionary@ data, const string&in key) {
|
|
double value;
|
|
if (@data == null) return false;
|
|
if (data.get(key, value)) return true;
|
|
int value_int;
|
|
if (data.get(key, value_int)) return true;
|
|
string value_str;
|
|
if (data.get(key, value_str)) return value_str.length() > 0;
|
|
return false;
|
|
}
|
|
|
|
string[] get_string_list(dictionary@ data, const string&in key) {
|
|
string[] result;
|
|
if (@data == null) return result;
|
|
if (!data.get(key, result)) return result;
|
|
return result;
|
|
}
|
|
|
|
string flatten_exception_text(const string&in text) {
|
|
string result = "";
|
|
bool lastWasSpace = false;
|
|
for (uint i = 0; i < text.length(); i++) {
|
|
string ch = text.substr(i, 1);
|
|
if (ch == "\r" || ch == "\n") {
|
|
if (!lastWasSpace) {
|
|
result += " | ";
|
|
lastWasSpace = true;
|
|
}
|
|
continue;
|
|
}
|
|
result += ch;
|
|
lastWasSpace = false;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
string format_log_timestamp() {
|
|
datetime dt;
|
|
string stamp = dt.format(DATE_TIME_FORMAT_RFC1123);
|
|
return "[" + stamp + "]";
|
|
}
|
|
|
|
void log_unhandled_exception(const string&in context) {
|
|
string info = get_exception_info();
|
|
string filePath = get_exception_file();
|
|
int line = get_exception_line();
|
|
string func = get_exception_function();
|
|
string stack = flatten_exception_text(last_exception_call_stack);
|
|
|
|
string message = "Unhandled exception";
|
|
if (context != "") message += " (" + context + ")";
|
|
if (info != "") message += ": " + info;
|
|
if (filePath != "") message += " at " + filePath;
|
|
if (line > 0) message += ":" + line;
|
|
if (func != "") message += " in " + func;
|
|
if (stack != "") message += " | stack: " + stack;
|
|
message += " " + format_log_timestamp();
|
|
|
|
file logFile;
|
|
if (logFile.open("crash.log", "ab")) {
|
|
logFile.write(message + "\r\n");
|
|
logFile.close();
|
|
}
|
|
}
|
|
|
|
string normalize_player_name(string name) {
|
|
string result = "";
|
|
bool lastWasSpace = true;
|
|
for (uint i = 0; i < name.length(); i++) {
|
|
string ch = name.substr(i, 1);
|
|
bool isSpace = (ch == " " || ch == "\t" || ch == "\r" || ch == "\n");
|
|
if (isSpace) {
|
|
if (!lastWasSpace) {
|
|
result += " ";
|
|
lastWasSpace = true;
|
|
}
|
|
continue;
|
|
}
|
|
result += ch;
|
|
lastWasSpace = false;
|
|
}
|
|
if (result.length() > 0 && result.substr(result.length() - 1) == " ") {
|
|
result = result.substr(0, result.length() - 1);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool is_windows_reserved_name(const string&in upperName) {
|
|
if (upperName == "CON" || upperName == "PRN" || upperName == "AUX" || upperName == "NUL") return true;
|
|
if (upperName.length() == 4 && upperName.substr(0, 3) == "COM") {
|
|
int num = parse_int(upperName.substr(3));
|
|
if (num >= 1 && num <= 9) return true;
|
|
}
|
|
if (upperName.length() == 4 && upperName.substr(0, 3) == "LPT") {
|
|
int num = parse_int(upperName.substr(3));
|
|
if (num >= 1 && num <= 9) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
string sanitize_save_filename_base(string name) {
|
|
string normalized = normalize_player_name(name);
|
|
string result = "";
|
|
bool lastSeparator = false;
|
|
|
|
for (uint i = 0; i < normalized.length(); i++) {
|
|
string ch = normalized.substr(i, 1);
|
|
bool isUpper = (ch >= "A" && ch <= "Z");
|
|
bool isLower = (ch >= "a" && ch <= "z");
|
|
bool isDigit = (ch >= "0" && ch <= "9");
|
|
if (isUpper || isLower || isDigit) {
|
|
result += ch;
|
|
lastSeparator = false;
|
|
continue;
|
|
}
|
|
if (ch == " " || ch == "_" || ch == "-") {
|
|
if (!lastSeparator && result.length() > 0) {
|
|
result += "_";
|
|
lastSeparator = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
while (result.length() > 0 && result.substr(result.length() - 1) == "_") {
|
|
result = result.substr(0, result.length() - 1);
|
|
}
|
|
if (result.length() == 0) {
|
|
result = "character";
|
|
}
|
|
if (result.length() > 40) {
|
|
result = result.substr(0, 40);
|
|
}
|
|
while (result.length() > 0 && result.substr(result.length() - 1) == "_") {
|
|
result = result.substr(0, result.length() - 1);
|
|
}
|
|
string upperName = result.upper();
|
|
if (upperName == "." || upperName == "..") {
|
|
result = "character";
|
|
upperName = result.upper();
|
|
}
|
|
if (is_windows_reserved_name(upperName)) {
|
|
result = "save_" + result;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
string get_save_filename_for_name(const string&in name) {
|
|
return sanitize_save_filename_base(name) + SAVE_EXTENSION;
|
|
}
|
|
|
|
string strip_save_extension(const string&in filename) {
|
|
if (filename.length() >= SAVE_EXTENSION.length() &&
|
|
filename.substr(filename.length() - SAVE_EXTENSION.length()) == SAVE_EXTENSION) {
|
|
return filename.substr(0, filename.length() - SAVE_EXTENSION.length());
|
|
}
|
|
return filename;
|
|
}
|
|
|
|
bool read_save_metadata(const string&in filename, string &out displayName, int &out sex, int &out day) {
|
|
string encryptedData;
|
|
if (!read_file_string(filename, encryptedData)) {
|
|
return false;
|
|
}
|
|
|
|
string rawData = decrypt_save_data(encryptedData);
|
|
dictionary@ saveData = deserialize(rawData);
|
|
if (@saveData == null || !dictionary_has_keys(saveData)) {
|
|
saveData = deserialize(encryptedData);
|
|
}
|
|
if (@saveData == null || !dictionary_has_keys(saveData)) {
|
|
return false;
|
|
}
|
|
|
|
displayName = "";
|
|
if (!saveData.get("player_name", displayName)) {
|
|
displayName = "";
|
|
}
|
|
sex = int(get_number(saveData, "player_sex", SEX_MALE));
|
|
if (sex != SEX_FEMALE) sex = SEX_MALE;
|
|
day = int(get_number(saveData, "time_current_day", 1));
|
|
if (day < 1) day = 1;
|
|
if (displayName == "") {
|
|
displayName = strip_save_extension(filename);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool is_name_used(const string&in name, const string[]@ usedNames) {
|
|
string target = name.lower();
|
|
if (@usedNames != null) {
|
|
for (uint i = 0; i < usedNames.length(); i++) {
|
|
if (usedNames[i].lower() == target) return true;
|
|
}
|
|
}
|
|
if (file_exists(get_save_filename_for_name(name))) return true;
|
|
return false;
|
|
}
|
|
|
|
string pick_random_name(const string[]@ pool, const string[]@ usedNames) {
|
|
string[] available;
|
|
if (@pool != null) {
|
|
for (uint i = 0; i < pool.length(); i++) {
|
|
if (!is_name_used(pool[i], usedNames)) {
|
|
available.insert_last(pool[i]);
|
|
}
|
|
}
|
|
}
|
|
const string[]@ pickFrom = @available;
|
|
if (available.length() == 0) {
|
|
@pickFrom = pool;
|
|
}
|
|
if (@pickFrom == null || pickFrom.length() == 0) return "character";
|
|
int index = random(0, int(pickFrom.length()) - 1);
|
|
return pickFrom[index];
|
|
}
|
|
|
|
string[] get_existing_character_names() {
|
|
string[] names;
|
|
string[] files = get_save_files();
|
|
for (uint i = 0; i < files.length(); i++) {
|
|
string displayName;
|
|
int sex = SEX_MALE;
|
|
int day = 1;
|
|
if (read_save_metadata(files[i], displayName, sex, day)) {
|
|
if (displayName.length() > 0) {
|
|
names.insert_last(displayName);
|
|
}
|
|
}
|
|
}
|
|
return names;
|
|
}
|
|
|
|
string[] male_name_pool = {
|
|
"Arne", "Asbjorn", "Aegir", "Bjorn", "Brand", "Egil", "Einar", "Eirik", "Erik", "Gunnar",
|
|
"Gudmund", "Hakon", "Halfdan", "Hallvard", "Harald", "Hjalmar", "Hrafn", "Hrolf", "Ivar", "Ketil",
|
|
"Knut", "Leif", "Magnus", "Njord", "Odd", "Olaf", "Orm", "Ragnar", "Roald", "Rolf",
|
|
"Sigurd", "Sten", "Stig", "Sven", "Svend", "Thor", "Toke", "Torbjorn", "Torstein", "Trygve",
|
|
"Ulf", "Ulrik", "Valdemar", "Vidar", "Yngvar", "Haldor", "Skjold", "Eystein", "Gorm", "Havard"
|
|
};
|
|
|
|
string[] female_name_pool = {
|
|
"Astrid", "Asta", "Birgit", "Brynhild", "Dagny", "Eira", "Freya", "Frida", "Gerda", "Gudrun",
|
|
"Gunhild", "Halla", "Helga", "Hild", "Hilda", "Inga", "Ingrid", "Kari", "Lagertha", "Liv",
|
|
"Ragna", "Ragnhild", "Randi", "Runa", "Sif", "Signy", "Sigrid", "Solveig", "Sunniva", "Thora",
|
|
"Thyra", "Tora", "Tove", "Tyrna", "Ulla", "Yrsa", "Ylva", "Aud", "Eydis", "Herdis",
|
|
"Ingunn", "Jorunn", "Ragnheid", "Sigrun", "Torhild", "Ase", "Alfhild", "Gudlaug", "Katra", "Rikissa"
|
|
};
|
|
|
|
string pick_random_name_for_sex(int sex, const string[]@ usedNames) {
|
|
if (sex == SEX_FEMALE) {
|
|
return pick_random_name(female_name_pool, usedNames);
|
|
}
|
|
return pick_random_name(male_name_pool, usedNames);
|
|
}
|
|
|
|
bool select_player_sex(int &out sex) {
|
|
string[] options = {"Male", "Female"};
|
|
int selection = 0;
|
|
string prompt = "Choose your sex.";
|
|
speak_with_history(prompt + " " + options[selection], true);
|
|
|
|
while (true) {
|
|
wait(5);
|
|
if (key_pressed(KEY_DOWN)) {
|
|
play_menu_move_sound();
|
|
selection++;
|
|
if (selection >= options.length()) selection = 0;
|
|
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(options[selection], true);
|
|
}
|
|
if (key_pressed(KEY_RETURN)) {
|
|
play_menu_select_sound();
|
|
sex = (selection == 1) ? SEX_FEMALE : SEX_MALE;
|
|
return true;
|
|
}
|
|
if (key_pressed(KEY_ESCAPE)) {
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool setup_new_character() {
|
|
int selectedSex = SEX_MALE;
|
|
if (!select_player_sex(selectedSex)) {
|
|
return false;
|
|
}
|
|
|
|
string[] existingNames = get_existing_character_names();
|
|
while (true) {
|
|
string entered = ui_input_box("Draugnorak", "Enter your name or press Enter for random.", "");
|
|
string normalized = normalize_player_name(entered);
|
|
if (normalized.length() == 0) {
|
|
normalized = pick_random_name_for_sex(selectedSex, existingNames);
|
|
}
|
|
string saveFile = get_save_filename_for_name(normalized);
|
|
if (file_exists(saveFile)) {
|
|
int confirm = ui_question("", "Save found for " + normalized + ". Overwrite?");
|
|
if (confirm != 1) {
|
|
continue;
|
|
}
|
|
}
|
|
player_name = normalized;
|
|
player_sex = selectedSex;
|
|
current_save_file = saveFile;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool select_save_file(string &out filename) {
|
|
while (true) {
|
|
string[] files = get_save_files();
|
|
if (files.length() == 0) return false;
|
|
|
|
string[] options;
|
|
string[] displayNames;
|
|
int[] sexValues;
|
|
int[] dayValues;
|
|
bool[] hasMetadata;
|
|
for (uint i = 0; i < files.length(); i++) {
|
|
string displayName;
|
|
int sex = SEX_MALE;
|
|
int day = 1;
|
|
bool gotMeta = read_save_metadata(files[i], displayName, sex, day);
|
|
if (!gotMeta) {
|
|
displayName = strip_save_extension(files[i]);
|
|
options.insert_last(displayName);
|
|
} else {
|
|
string sex_label = (sex == SEX_FEMALE) ? "female" : "male";
|
|
options.insert_last(displayName + ", " + sex_label + ", day " + day);
|
|
}
|
|
displayNames.insert_last(displayName);
|
|
sexValues.insert_last(sex);
|
|
dayValues.insert_last(day);
|
|
hasMetadata.insert_last(gotMeta);
|
|
}
|
|
|
|
int selection = 0;
|
|
speak_with_history("Load game. Select character. Press delete to remove a save.", true);
|
|
speak_with_history(options[selection], true);
|
|
|
|
bool refreshList = false;
|
|
while (true) {
|
|
wait(5);
|
|
if (key_pressed(KEY_DOWN)) {
|
|
play_menu_move_sound();
|
|
selection++;
|
|
if (selection >= 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();
|
|
filename = files[selection];
|
|
return true;
|
|
}
|
|
if (key_pressed(KEY_DELETE)) {
|
|
string prompt = "Are you sure you want to delete the character " + displayNames[selection];
|
|
if (hasMetadata[selection]) {
|
|
string sex_label = (sexValues[selection] == SEX_FEMALE) ? "female" : "male";
|
|
prompt += " gender " + sex_label + " days " + dayValues[selection] + "?";
|
|
} else {
|
|
prompt += "?";
|
|
}
|
|
int confirm = ui_question("", prompt);
|
|
if (confirm == 1) {
|
|
if (file_delete(files[selection])) {
|
|
speak_with_history("Save deleted.", true);
|
|
refreshList = true;
|
|
break;
|
|
} else {
|
|
ui_info_box("Draugnorak", "Delete Save", "Unable to delete save.");
|
|
speak_with_history(options[selection], true);
|
|
}
|
|
} else {
|
|
speak_with_history(options[selection], true);
|
|
}
|
|
}
|
|
if (key_pressed(KEY_ESCAPE)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!refreshList) {
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void stop_active_sounds() {
|
|
if (day_sound_handle != -1) {
|
|
p.destroy_sound(day_sound_handle);
|
|
day_sound_handle = -1;
|
|
}
|
|
if (night_sound_handle != -1) {
|
|
p.destroy_sound(night_sound_handle);
|
|
night_sound_handle = -1;
|
|
}
|
|
if (fall_sound_handle != -1) {
|
|
p.destroy_sound(fall_sound_handle);
|
|
fall_sound_handle = -1;
|
|
}
|
|
if (sling_sound_handle != -1) {
|
|
p.destroy_sound(sling_sound_handle);
|
|
sling_sound_handle = -1;
|
|
}
|
|
if (bow_shot_sound_handle != -1) {
|
|
p.destroy_sound(bow_shot_sound_handle);
|
|
bow_shot_sound_handle = -1;
|
|
}
|
|
stop_all_weather_sounds();
|
|
}
|
|
|
|
void clear_world_objects() {
|
|
for (uint i = 0; i < world_snares.length(); i++) {
|
|
world_snares[i].destroy();
|
|
}
|
|
world_snares.resize(0);
|
|
|
|
for (uint i = 0; i < world_fires.length(); i++) {
|
|
world_fires[i].destroy();
|
|
}
|
|
world_fires.resize(0);
|
|
|
|
for (uint i = 0; i < world_streams.length(); i++) {
|
|
world_streams[i].destroy();
|
|
}
|
|
world_streams.resize(0);
|
|
|
|
world_firepits.resize(0);
|
|
world_herb_gardens.resize(0);
|
|
world_storages.resize(0);
|
|
world_pastures.resize(0);
|
|
world_stables.resize(0);
|
|
world_altars.resize(0);
|
|
|
|
for (uint i = 0; i < trees.length(); i++) {
|
|
if (trees[i].sound_handle != -1) {
|
|
p.destroy_sound(trees[i].sound_handle);
|
|
trees[i].sound_handle = -1;
|
|
}
|
|
}
|
|
trees.resize(0);
|
|
|
|
clear_zombies();
|
|
clear_bandits();
|
|
clear_flying_creatures();
|
|
clear_mountains();
|
|
clear_world_drops();
|
|
}
|
|
|
|
void reset_game_state() {
|
|
stop_active_sounds();
|
|
clear_world_objects();
|
|
|
|
x = 0;
|
|
y = 0;
|
|
facing = 1;
|
|
jumping = false;
|
|
climbing = false;
|
|
falling = false;
|
|
climb_target_y = 0;
|
|
fall_start_y = 0;
|
|
sling_charging = false;
|
|
bow_drawing = false;
|
|
bow_shot_active = false;
|
|
bow_shot_start_x = 0;
|
|
bow_shot_end_x = 0;
|
|
bow_shot_hit_x = -1;
|
|
bow_shot_hit_type = 0;
|
|
bow_shot_duration_ms = 0;
|
|
bow_shot_sound_handle = -1;
|
|
bow_shot_drop_pending = false;
|
|
bow_shot_drop_pos = -1;
|
|
searching = false;
|
|
rope_climbing = false;
|
|
rope_climb_up = true;
|
|
rope_climb_target_x = 0;
|
|
rope_climb_target_y = 0;
|
|
rope_climb_start_y = 0;
|
|
rope_climb_sound_handle = -1;
|
|
pending_rope_climb_x = -1;
|
|
pending_rope_climb_elevation = 0;
|
|
is_casting = false;
|
|
line_in_water = false;
|
|
fish_on_line = false;
|
|
is_reeling = false;
|
|
cast_position = -1;
|
|
reel_position = -1;
|
|
line_position = -1;
|
|
cast_origin_x = -1;
|
|
cast_direction = 0;
|
|
reel_direction = 0;
|
|
reel_start_direction = 0;
|
|
target_stream_start = -1;
|
|
target_stream_end = -1;
|
|
catch_chance = 0;
|
|
fishing_checks_done = 0;
|
|
hooked_fish_type = "";
|
|
if (cast_sound_handle != -1) {
|
|
p.destroy_sound(cast_sound_handle);
|
|
cast_sound_handle = -1;
|
|
}
|
|
|
|
player_health = 10;
|
|
base_max_health = 10;
|
|
max_health = 10;
|
|
favor = 0.0;
|
|
last_adventure_day = -1;
|
|
incense_hours_remaining = 0;
|
|
incense_burning = false;
|
|
blessing_speed_active = false;
|
|
blessing_resident_active = false;
|
|
reset_fylgja_state();
|
|
reset_pet_state();
|
|
|
|
// Reset inventory using the registry system
|
|
reset_inventory();
|
|
|
|
spear_equipped = false;
|
|
axe_equipped = false;
|
|
sling_equipped = false;
|
|
bow_equipped = false;
|
|
fishing_pole_equipped = false;
|
|
equipped_head = EQUIP_NONE;
|
|
equipped_torso = EQUIP_NONE;
|
|
equipped_arms = EQUIP_NONE;
|
|
equipped_hands = EQUIP_NONE;
|
|
equipped_legs = EQUIP_NONE;
|
|
equipped_feet = EQUIP_NONE;
|
|
reset_quick_slots();
|
|
update_max_health_from_equipment();
|
|
|
|
MAP_SIZE = 35;
|
|
expanded_area_start = -1;
|
|
expanded_area_end = -1;
|
|
expanded_terrain_types.resize(0);
|
|
|
|
barricade_health = 0;
|
|
barricade_initialized = false;
|
|
residents_count = 0;
|
|
undead_residents_count = 0;
|
|
undead_residents_pending = 0;
|
|
horses_count = 0;
|
|
livestock_count = 0;
|
|
storage_level = STORAGE_LEVEL_BASE;
|
|
|
|
current_hour = 8;
|
|
current_day = 1;
|
|
is_daytime = true;
|
|
sun_setting_warned = false;
|
|
sunrise_warned = false;
|
|
crossfade_active = false;
|
|
crossfade_to_night = false;
|
|
area_expanded_today = false;
|
|
invasion_active = false;
|
|
invasion_start_hour = -1;
|
|
invasion_chance = 25;
|
|
invasion_triggered_today = false;
|
|
invasion_roll_done_today = false;
|
|
invasion_scheduled_hour = -1;
|
|
invasion_started_once = false;
|
|
zombie_swarm_active = false;
|
|
zombie_swarm_start_hour = -1;
|
|
zombie_swarm_scheduled_hour = -1;
|
|
zombie_swarm_triggered_today = false;
|
|
zombie_swarm_roll_done_today = false;
|
|
zombie_swarm_duration_hours = 0;
|
|
quest_roll_done_today = false;
|
|
quest_queue.resize(0);
|
|
playerItemBreakChance = PLAYER_ITEM_BREAK_CHANCE_MIN;
|
|
playerItemBreaksToday = 0;
|
|
playerItemBreakPending = false;
|
|
playerItemBreakPendingType = -1;
|
|
playerItemBreakMessage = "";
|
|
playerItemBreakSoundHandle = -1;
|
|
playerItemBreakSoundPending = false;
|
|
|
|
walktimer.restart();
|
|
jumptimer.restart();
|
|
search_timer.restart();
|
|
search_delay_timer.restart();
|
|
attack_timer.restart();
|
|
hour_timer.restart();
|
|
fire_damage_timer.restart();
|
|
healing_timer.restart();
|
|
sling_charge_timer.restart();
|
|
bow_draw_timer.restart();
|
|
bow_shot_timer.restart();
|
|
fall_timer.restart();
|
|
climb_timer.restart();
|
|
}
|
|
|
|
void start_new_game() {
|
|
reset_game_state();
|
|
spawn_trees(5, 19);
|
|
normalize_tree_positions();
|
|
init_barricade();
|
|
ensure_base_storage();
|
|
init_time();
|
|
init_weather();
|
|
if (player_name.length() == 0) {
|
|
player_name = "Character";
|
|
}
|
|
if (current_save_file == "") {
|
|
current_save_file = get_save_filename_for_name(player_name);
|
|
}
|
|
save_game_state();
|
|
}
|
|
|
|
string serialize_bool(bool value) {
|
|
return value ? "1" : "0";
|
|
}
|
|
|
|
string serialize_tree(Tree@ tree) {
|
|
return tree.position + "|" + tree.sticks + "|" + tree.vines + "|" + tree.health + "|" + tree.height + "|" + serialize_bool(tree.depleted) + "|" + serialize_bool(tree.is_chopped) + "|" + tree.minutes_since_depletion;
|
|
}
|
|
|
|
string serialize_snare(WorldSnare@ snare) {
|
|
return snare.position + "|" + serialize_bool(snare.has_catch) + "|" + snare.catch_type + "|" + snare.catch_chance + "|" + snare.escape_chance + "|" + serialize_bool(snare.active) + "|" + snare.hours_with_catch;
|
|
}
|
|
|
|
string serialize_fire(WorldFire@ fire) {
|
|
return fire.position + "|" + fire.fuel_remaining + "|" + serialize_bool(fire.low_fuel_warned);
|
|
}
|
|
|
|
string serialize_stream(WorldStream@ stream) {
|
|
return stream.start_position + "|" + stream.get_width();
|
|
}
|
|
|
|
string serialize_bandit(Bandit@ bandit) {
|
|
return bandit.position + "|" + bandit.health + "|" + bandit.weapon_type + "|" + bandit.behavior_state + "|" + bandit.wander_direction + "|" + bandit.move_interval + "|" + bandit.invader_type + "|" + bandit.home_start + "|" + bandit.home_end;
|
|
}
|
|
|
|
string serialize_mountain(MountainRange@ mountain) {
|
|
string result = mountain.start_position + "|" + mountain.end_position + "|";
|
|
|
|
// Serialize elevations
|
|
for (int i = 0; i < int(mountain.elevations.length()); i++) {
|
|
if (i > 0) result += ",";
|
|
result += mountain.elevations[i];
|
|
}
|
|
result += "|";
|
|
|
|
// Serialize terrain types
|
|
for (int i = 0; i < int(mountain.terrain_types.length()); i++) {
|
|
if (i > 0) result += ",";
|
|
result += mountain.terrain_types[i];
|
|
}
|
|
result += "|";
|
|
|
|
// Serialize stream positions
|
|
for (uint i = 0; i < mountain.stream_positions.length(); i++) {
|
|
if (i > 0) result += ",";
|
|
result += mountain.stream_positions[i];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
string join_string_array(const string[]@ arr) {
|
|
if (@arr == null || arr.length() == 0) return "";
|
|
string result = arr[0];
|
|
for (uint i = 1; i < arr.length(); i++) {
|
|
result += "\n" + arr[i];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
string[] split_string_array(const string&in data) {
|
|
string[] result;
|
|
if (data.length() == 0) return result;
|
|
result = data.split("\n");
|
|
return result;
|
|
}
|
|
|
|
string[] get_string_list_or_split(dictionary@ data, const string&in key) {
|
|
string[] result = get_string_list(data, key);
|
|
if (result.length() > 0) return result;
|
|
string value;
|
|
if (@data != null && data.get(key, value)) {
|
|
return split_string_array(value);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Serialize inventory array to comma-separated string
|
|
string serialize_inventory_array(const int[]@ arr) {
|
|
if (@arr == null || arr.length() == 0) return "";
|
|
string result = "" + arr[0];
|
|
for (uint i = 1; i < arr.length(); i++) {
|
|
result += "," + arr[i];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Deserialize comma-separated string to inventory array
|
|
void deserialize_inventory_array(const string&in data, int[]@ arr, int expected_size) {
|
|
arr.resize(expected_size);
|
|
for (int i = 0; i < expected_size; i++) {
|
|
arr[i] = 0;
|
|
}
|
|
if (data.length() == 0) return;
|
|
|
|
string[]@ parts = data.split(",");
|
|
uint count = parts.length();
|
|
if (count > uint(expected_size)) count = uint(expected_size);
|
|
for (uint i = 0; i < count; i++) {
|
|
arr[i] = parse_int(parts[i]);
|
|
}
|
|
}
|
|
|
|
bool save_game_state() {
|
|
dictionary saveData;
|
|
|
|
saveData.set("version", SAVE_VERSION);
|
|
saveData.set("player_x", x);
|
|
saveData.set("player_y", y);
|
|
saveData.set("player_facing", facing);
|
|
saveData.set("player_health", player_health);
|
|
saveData.set("player_base_health", base_max_health);
|
|
saveData.set("player_max_health", max_health);
|
|
saveData.set("player_name", player_name);
|
|
saveData.set("player_sex", player_sex);
|
|
saveData.set("player_favor", favor);
|
|
saveData.set("player_last_adventure_day", last_adventure_day);
|
|
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);
|
|
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));
|
|
saveData.set("storage_inventory", serialize_inventory_array(storage_inventory));
|
|
saveData.set("personal_small_game_types", join_string_array(personal_small_game_types));
|
|
saveData.set("storage_small_game_types", join_string_array(storage_small_game_types));
|
|
string[] personalFishWeightsData;
|
|
for (uint i = 0; i < personal_fish_weights.length(); i++) {
|
|
personalFishWeightsData.insert_last("" + personal_fish_weights[i]);
|
|
}
|
|
saveData.set("personal_fish_weights", join_string_array(personalFishWeightsData));
|
|
string[] storageFishWeightsData;
|
|
for (uint i = 0; i < storage_fish_weights.length(); i++) {
|
|
storageFishWeightsData.insert_last("" + storage_fish_weights[i]);
|
|
}
|
|
saveData.set("storage_fish_weights", join_string_array(storageFishWeightsData));
|
|
|
|
saveData.set("equipment_spear_equipped", spear_equipped);
|
|
saveData.set("equipment_axe_equipped", axe_equipped);
|
|
saveData.set("equipment_sling_equipped", sling_equipped);
|
|
saveData.set("equipment_bow_equipped", bow_equipped);
|
|
saveData.set("equipment_fishing_pole_equipped", fishing_pole_equipped);
|
|
saveData.set("equipment_head", equipped_head);
|
|
saveData.set("equipment_torso", equipped_torso);
|
|
saveData.set("equipment_arms", equipped_arms);
|
|
saveData.set("equipment_hands", equipped_hands);
|
|
saveData.set("equipment_legs", equipped_legs);
|
|
saveData.set("equipment_feet", equipped_feet);
|
|
string[] quickSlotData;
|
|
for (uint i = 0; i < quick_slots.length(); i++) {
|
|
quickSlotData.insert_last("" + quick_slots[i]);
|
|
}
|
|
saveData.set("equipment_quick_slots", join_string_array(quickSlotData));
|
|
string[] quickSlotRuneData;
|
|
for (uint i = 0; i < quick_slot_runes.length(); i++) {
|
|
quickSlotRuneData.insert_last("" + quick_slot_runes[i]);
|
|
}
|
|
saveData.set("equipment_quick_slot_runes", join_string_array(quickSlotRuneData));
|
|
|
|
string[] itemCountSlotData;
|
|
for (uint i = 0; i < item_count_slots.length(); i++) {
|
|
itemCountSlotData.insert_last("" + item_count_slots[i]);
|
|
}
|
|
saveData.set("item_count_slots", join_string_array(itemCountSlotData));
|
|
|
|
// Rune system data
|
|
saveData.set("rune_swiftness_unlocked", rune_swiftness_unlocked);
|
|
saveData.set("rune_destruction_unlocked", rune_destruction_unlocked);
|
|
saveData.set("unicorn_boss_defeated", unicorn_boss_defeated);
|
|
saveData.set("equipped_head_rune", equipped_head_rune);
|
|
saveData.set("equipped_torso_rune", equipped_torso_rune);
|
|
saveData.set("equipped_arms_rune", equipped_arms_rune);
|
|
saveData.set("equipped_hands_rune", equipped_hands_rune);
|
|
saveData.set("equipped_legs_rune", equipped_legs_rune);
|
|
saveData.set("equipped_feet_rune", equipped_feet_rune);
|
|
saveData.set("equipped_weapon_rune", equipped_weapon_rune);
|
|
|
|
// Save runed items dictionary as key:value pairs
|
|
string[] runed_items_data;
|
|
string[]@ keys = runed_items.get_keys();
|
|
for (uint i = 0; i < keys.length(); i++) {
|
|
string key = keys[i];
|
|
int count = int(runed_items[key]);
|
|
if (count > 0) {
|
|
runed_items_data.insert_last(key + "=" + count);
|
|
}
|
|
}
|
|
saveData.set("runed_items", join_string_array(runed_items_data));
|
|
|
|
// Save stored runed items dictionary
|
|
string[] stored_runed_items_data;
|
|
string[]@ stored_keys = stored_runed_items.get_keys();
|
|
for (uint i = 0; i < stored_keys.length(); i++) {
|
|
string key = stored_keys[i];
|
|
int count = int(stored_runed_items[key]);
|
|
if (count > 0) {
|
|
stored_runed_items_data.insert_last(key + "=" + count);
|
|
}
|
|
}
|
|
saveData.set("stored_runed_items", join_string_array(stored_runed_items_data));
|
|
|
|
saveData.set("time_current_hour", current_hour);
|
|
saveData.set("time_current_day", current_day);
|
|
saveData.set("time_is_daytime", is_daytime);
|
|
saveData.set("time_sun_setting_warned", sun_setting_warned);
|
|
saveData.set("time_sunrise_warned", sunrise_warned);
|
|
saveData.set("time_area_expanded_today", area_expanded_today);
|
|
saveData.set("time_invasion_active", invasion_active);
|
|
saveData.set("time_invasion_start_hour", invasion_start_hour);
|
|
saveData.set("time_invasion_chance", invasion_chance);
|
|
saveData.set("time_invasion_triggered_today", invasion_triggered_today);
|
|
saveData.set("time_invasion_roll_done_today", invasion_roll_done_today);
|
|
saveData.set("time_invasion_scheduled_hour", invasion_scheduled_hour);
|
|
saveData.set("time_invasion_enemy_type", invasion_enemy_type);
|
|
saveData.set("time_invasion_started_once", invasion_started_once);
|
|
saveData.set("time_zombie_swarm_active", zombie_swarm_active);
|
|
saveData.set("time_zombie_swarm_start_hour", zombie_swarm_start_hour);
|
|
saveData.set("time_zombie_swarm_scheduled_hour", zombie_swarm_scheduled_hour);
|
|
saveData.set("time_zombie_swarm_triggered_today", zombie_swarm_triggered_today);
|
|
saveData.set("time_zombie_swarm_roll_done_today", zombie_swarm_roll_done_today);
|
|
saveData.set("time_zombie_swarm_duration_hours", zombie_swarm_duration_hours);
|
|
saveData.set("player_item_break_chance", playerItemBreakChance);
|
|
saveData.set("player_item_breaks_today", playerItemBreaksToday);
|
|
saveData.set("player_item_break_pending", playerItemBreakPending);
|
|
saveData.set("player_item_break_pending_type", playerItemBreakPendingType);
|
|
saveData.set("quest_roll_done_today", quest_roll_done_today);
|
|
saveData.set("wight_spawn_chance", wight_spawn_chance);
|
|
saveData.set("wight_spawned_this_night", wight_spawned_this_night_count);
|
|
saveData.set("vampyr_spawn_chance", vampyr_spawn_chance);
|
|
saveData.set("vampyr_spawned_this_night", vampyr_spawned_this_night_count);
|
|
string[] questData;
|
|
for (uint i = 0; i < quest_queue.length(); i++) {
|
|
questData.insert_last("" + quest_queue[i]);
|
|
}
|
|
saveData.set("quest_queue", join_string_array(questData));
|
|
|
|
saveData.set("weather_data", serialize_weather());
|
|
|
|
saveData.set("world_map_size", MAP_SIZE);
|
|
saveData.set("world_expanded_area_start", expanded_area_start);
|
|
saveData.set("world_expanded_area_end", expanded_area_end);
|
|
saveData.set("world_barricade_initialized", barricade_initialized);
|
|
saveData.set("world_barricade_health", barricade_health);
|
|
saveData.set("world_residents_count", residents_count);
|
|
saveData.set("world_undead_residents_count", undead_residents_count);
|
|
saveData.set("world_undead_residents_pending", undead_residents_pending);
|
|
saveData.set("world_horses_count", horses_count);
|
|
saveData.set("world_livestock_count", livestock_count);
|
|
saveData.set("world_storage_level", storage_level);
|
|
saveData.set("world_expanded_terrain_types", join_string_array(expanded_terrain_types));
|
|
|
|
string[] treeData;
|
|
for (uint i = 0; i < trees.length(); i++) {
|
|
treeData.insert_last(serialize_tree(trees[i]));
|
|
}
|
|
saveData.set("trees_data", join_string_array(treeData));
|
|
|
|
string[] snareData;
|
|
for (uint i = 0; i < world_snares.length(); i++) {
|
|
snareData.insert_last(serialize_snare(world_snares[i]));
|
|
}
|
|
saveData.set("snares_data", join_string_array(snareData));
|
|
|
|
string[] fireData;
|
|
for (uint i = 0; i < world_fires.length(); i++) {
|
|
fireData.insert_last(serialize_fire(world_fires[i]));
|
|
}
|
|
saveData.set("fires_data", join_string_array(fireData));
|
|
|
|
string[] firepitPositions;
|
|
for (uint i = 0; i < world_firepits.length(); i++) {
|
|
firepitPositions.insert_last("" + world_firepits[i].position);
|
|
}
|
|
saveData.set("firepits_positions", join_string_array(firepitPositions));
|
|
|
|
string[] herbPositions;
|
|
for (uint i = 0; i < world_herb_gardens.length(); i++) {
|
|
herbPositions.insert_last("" + world_herb_gardens[i].position);
|
|
}
|
|
saveData.set("herb_gardens_positions", join_string_array(herbPositions));
|
|
|
|
string[] storagePositions;
|
|
for (uint i = 0; i < world_storages.length(); i++) {
|
|
storagePositions.insert_last("" + world_storages[i].position);
|
|
}
|
|
saveData.set("storages_positions", join_string_array(storagePositions));
|
|
|
|
string[] pasturePositions;
|
|
for (uint i = 0; i < world_pastures.length(); i++) {
|
|
pasturePositions.insert_last("" + world_pastures[i].position);
|
|
}
|
|
saveData.set("pastures_positions", join_string_array(pasturePositions));
|
|
|
|
string[] stablePositions;
|
|
for (uint i = 0; i < world_stables.length(); i++) {
|
|
stablePositions.insert_last("" + world_stables[i].position);
|
|
}
|
|
saveData.set("stables_positions", join_string_array(stablePositions));
|
|
|
|
string[] altarPositions;
|
|
for (uint i = 0; i < world_altars.length(); i++) {
|
|
altarPositions.insert_last("" + world_altars[i].position);
|
|
}
|
|
saveData.set("altars_positions", join_string_array(altarPositions));
|
|
|
|
string[] streamData;
|
|
for (uint i = 0; i < world_streams.length(); i++) {
|
|
streamData.insert_last(serialize_stream(world_streams[i]));
|
|
}
|
|
saveData.set("streams_data", join_string_array(streamData));
|
|
|
|
string[] banditData;
|
|
for (uint i = 0; i < bandits.length(); i++) {
|
|
banditData.insert_last(serialize_bandit(bandits[i]));
|
|
}
|
|
saveData.set("bandits_data", join_string_array(banditData));
|
|
|
|
string[] mountainData;
|
|
for (uint i = 0; i < world_mountains.length(); i++) {
|
|
mountainData.insert_last(serialize_mountain(world_mountains[i]));
|
|
}
|
|
saveData.set("mountains_data", join_string_array(mountainData));
|
|
|
|
string[] dropData;
|
|
for (uint i = 0; i < world_drops.length(); i++) {
|
|
dropData.insert_last(world_drops[i].position + "|" + world_drops[i].type);
|
|
}
|
|
saveData.set("drops_data", join_string_array(dropData));
|
|
|
|
if (current_save_file == "") {
|
|
current_save_file = get_save_filename_for_name(player_name);
|
|
}
|
|
string rawData = saveData.serialize();
|
|
string encryptedData = encrypt_save_data(rawData);
|
|
return save_data(current_save_file, encryptedData);
|
|
}
|
|
|
|
bool load_game_state_from_file(const string&in filename) {
|
|
last_save_error = "";
|
|
if (!file_exists(filename)) {
|
|
last_save_error = "No save file found.";
|
|
return false;
|
|
}
|
|
|
|
string encryptedData;
|
|
if (!read_file_string(filename, encryptedData)) {
|
|
last_save_error = "Unable to read save file.";
|
|
return false;
|
|
}
|
|
|
|
string rawData = decrypt_save_data(encryptedData);
|
|
dictionary@ saveData = deserialize(rawData);
|
|
if (@saveData == null || !dictionary_has_keys(saveData)) {
|
|
saveData = deserialize(encryptedData);
|
|
}
|
|
if (@saveData == null || !dictionary_has_keys(saveData)) {
|
|
last_save_error = "Save file is corrupted or unreadable.";
|
|
return false;
|
|
}
|
|
double version;
|
|
bool has_version = saveData.get("version", version);
|
|
if (!has_version) {
|
|
if (!has_number_key(saveData, "player_x") || !has_number_key(saveData, "player_health") || !has_number_key(saveData, "time_current_day")) {
|
|
last_save_error = "Save file is missing required data.";
|
|
return false;
|
|
}
|
|
version = 0;
|
|
}
|
|
|
|
reset_game_state();
|
|
current_save_file = filename;
|
|
|
|
MAP_SIZE = int(get_number(saveData, "world_map_size", 35));
|
|
expanded_area_start = int(get_number(saveData, "world_expanded_area_start", -1));
|
|
expanded_area_end = int(get_number(saveData, "world_expanded_area_end", -1));
|
|
|
|
string[] loadedTerrain = get_string_list_or_split(saveData, "world_expanded_terrain_types");
|
|
expanded_terrain_types.resize(0);
|
|
for (uint i = 0; i < loadedTerrain.length(); i++) {
|
|
expanded_terrain_types.insert_last(loadedTerrain[i]);
|
|
}
|
|
|
|
barricade_initialized = get_bool(saveData, "world_barricade_initialized", true);
|
|
barricade_health = int(get_number(saveData, "world_barricade_health", BARRICADE_BASE_HEALTH));
|
|
residents_count = int(get_number(saveData, "world_residents_count", 0));
|
|
if (residents_count < 0) residents_count = 0;
|
|
undead_residents_count = int(get_number(saveData, "world_undead_residents_count", 0));
|
|
if (undead_residents_count < 0) undead_residents_count = 0;
|
|
undead_residents_pending = int(get_number(saveData, "world_undead_residents_pending", 0));
|
|
if (undead_residents_pending < 0) undead_residents_pending = 0;
|
|
horses_count = int(get_number(saveData, "world_horses_count", 0));
|
|
livestock_count = int(get_number(saveData, "world_livestock_count", 0));
|
|
if (horses_count < 0) horses_count = 0;
|
|
if (livestock_count < 0) livestock_count = 0;
|
|
if (horses_count > MAX_HORSES) horses_count = MAX_HORSES;
|
|
if (livestock_count > MAX_LIVESTOCK) livestock_count = MAX_LIVESTOCK;
|
|
if (!barricade_initialized) {
|
|
init_barricade();
|
|
} else {
|
|
if (barricade_health < 0) barricade_health = 0;
|
|
if (barricade_health > BARRICADE_MAX_HEALTH) barricade_health = BARRICADE_MAX_HEALTH;
|
|
}
|
|
|
|
x = int(get_number(saveData, "player_x", 0));
|
|
y = int(get_number(saveData, "player_y", 0));
|
|
facing = int(get_number(saveData, "player_facing", 1));
|
|
player_health = int(get_number(saveData, "player_health", 10));
|
|
max_health = int(get_number(saveData, "player_max_health", 10));
|
|
base_max_health = int(get_number(saveData, "player_base_health", max_health));
|
|
string loadedName;
|
|
if (saveData.get("player_name", loadedName)) {
|
|
player_name = loadedName;
|
|
} else {
|
|
player_name = strip_save_extension(filename);
|
|
}
|
|
player_sex = int(get_number(saveData, "player_sex", SEX_MALE));
|
|
if (player_sex != SEX_FEMALE) player_sex = SEX_MALE;
|
|
favor = get_number(saveData, "player_favor", 0.0);
|
|
last_adventure_day = int(get_number(saveData, "player_last_adventure_day", -1));
|
|
string adventureCountsStr;
|
|
if (saveData.get("adventure_completion_counts", adventureCountsStr)) {
|
|
deserialize_inventory_array(adventureCountsStr, adventureCompletionCounts, int(adventureIds.length()));
|
|
}
|
|
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));
|
|
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;
|
|
petSoundPath = "";
|
|
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();
|
|
}
|
|
|
|
if (x < 0) x = 0;
|
|
if (x >= MAP_SIZE) x = MAP_SIZE - 1;
|
|
if (y < 0) y = 0;
|
|
if (facing != 0 && facing != 1) facing = 1;
|
|
|
|
// Load inventory using new array format
|
|
string personal_inv_str;
|
|
if (saveData.get("personal_inventory", personal_inv_str)) {
|
|
deserialize_inventory_array(personal_inv_str, personal_inventory, ITEM_COUNT);
|
|
}
|
|
|
|
string storage_inv_str;
|
|
if (saveData.get("storage_inventory", storage_inv_str)) {
|
|
deserialize_inventory_array(storage_inv_str, storage_inventory, ITEM_COUNT);
|
|
}
|
|
|
|
// Load small game types
|
|
string[] loadedSmallGameTypes = get_string_list_or_split(saveData, "personal_small_game_types");
|
|
personal_small_game_types.resize(0);
|
|
for (uint i = 0; i < loadedSmallGameTypes.length(); i++) {
|
|
personal_small_game_types.insert_last(loadedSmallGameTypes[i]);
|
|
}
|
|
|
|
// Sync small game count with types array
|
|
int small_game_count = get_personal_count(ITEM_SMALL_GAME);
|
|
if (personal_small_game_types.length() == 0 && small_game_count > 0) {
|
|
for (int i = 0; i < small_game_count; i++) {
|
|
personal_small_game_types.insert_last("small game");
|
|
}
|
|
} else {
|
|
set_personal_count(ITEM_SMALL_GAME, int(personal_small_game_types.length()));
|
|
}
|
|
|
|
string[] loadedStorageSmallGameTypes = get_string_list_or_split(saveData, "storage_small_game_types");
|
|
storage_small_game_types.resize(0);
|
|
for (uint i = 0; i < loadedStorageSmallGameTypes.length(); i++) {
|
|
storage_small_game_types.insert_last(loadedStorageSmallGameTypes[i]);
|
|
}
|
|
|
|
int storage_small_game_count = get_storage_count(ITEM_SMALL_GAME);
|
|
if (storage_small_game_types.length() == 0 && storage_small_game_count > 0) {
|
|
for (int i = 0; i < storage_small_game_count; i++) {
|
|
storage_small_game_types.insert_last("small game");
|
|
}
|
|
} else {
|
|
set_storage_count(ITEM_SMALL_GAME, int(storage_small_game_types.length()));
|
|
}
|
|
|
|
string[] loadedPersonalFishWeights = get_string_list_or_split(saveData, "personal_fish_weights");
|
|
personal_fish_weights.resize(0);
|
|
for (uint i = 0; i < loadedPersonalFishWeights.length(); i++) {
|
|
personal_fish_weights.insert_last(parse_int(loadedPersonalFishWeights[i]));
|
|
}
|
|
int personal_fish_count = get_personal_count(ITEM_FISH);
|
|
if (personal_fish_weights.length() < personal_fish_count) {
|
|
int missing = personal_fish_count - int(personal_fish_weights.length());
|
|
for (int i = 0; i < missing; i++) {
|
|
personal_fish_weights.insert_last(get_default_fish_weight());
|
|
}
|
|
} else if (personal_fish_weights.length() > personal_fish_count) {
|
|
personal_fish_weights.resize(personal_fish_count);
|
|
}
|
|
|
|
string[] loadedStorageFishWeights = get_string_list_or_split(saveData, "storage_fish_weights");
|
|
storage_fish_weights.resize(0);
|
|
for (uint i = 0; i < loadedStorageFishWeights.length(); i++) {
|
|
storage_fish_weights.insert_last(parse_int(loadedStorageFishWeights[i]));
|
|
}
|
|
int storage_fish_count = get_storage_count(ITEM_FISH);
|
|
if (storage_fish_weights.length() < storage_fish_count) {
|
|
int missing = storage_fish_count - int(storage_fish_weights.length());
|
|
for (int i = 0; i < missing; i++) {
|
|
storage_fish_weights.insert_last(get_default_fish_weight());
|
|
}
|
|
} else if (storage_fish_weights.length() > storage_fish_count) {
|
|
storage_fish_weights.resize(storage_fish_count);
|
|
}
|
|
|
|
spear_equipped = get_bool(saveData, "equipment_spear_equipped", false);
|
|
axe_equipped = get_bool(saveData, "equipment_axe_equipped", false);
|
|
sling_equipped = get_bool(saveData, "equipment_sling_equipped", false);
|
|
bow_equipped = get_bool(saveData, "equipment_bow_equipped", false);
|
|
fishing_pole_equipped = get_bool(saveData, "equipment_fishing_pole_equipped", false);
|
|
equipped_head = int(get_number(saveData, "equipment_head", EQUIP_NONE));
|
|
equipped_torso = int(get_number(saveData, "equipment_torso", EQUIP_NONE));
|
|
equipped_arms = int(get_number(saveData, "equipment_arms", EQUIP_NONE));
|
|
equipped_hands = int(get_number(saveData, "equipment_hands", EQUIP_NONE));
|
|
equipped_legs = int(get_number(saveData, "equipment_legs", EQUIP_NONE));
|
|
equipped_feet = int(get_number(saveData, "equipment_feet", EQUIP_NONE));
|
|
|
|
reset_quick_slots();
|
|
string[] loadedQuickSlots = get_string_list_or_split(saveData, "equipment_quick_slots");
|
|
uint slot_count = loadedQuickSlots.length();
|
|
if (slot_count > quick_slots.length()) slot_count = quick_slots.length();
|
|
for (uint i = 0; i < slot_count; i++) {
|
|
int slot_value = parse_int(loadedQuickSlots[i]);
|
|
if (slot_value >= EQUIP_NONE && slot_value <= EQUIP_FISHING_POLE) {
|
|
quick_slots[i] = slot_value;
|
|
}
|
|
}
|
|
string[] loadedQuickSlotRunes = get_string_list_or_split(saveData, "equipment_quick_slot_runes");
|
|
uint rune_slot_count = loadedQuickSlotRunes.length();
|
|
if (rune_slot_count > quick_slot_runes.length()) rune_slot_count = quick_slot_runes.length();
|
|
for (uint i = 0; i < rune_slot_count; i++) {
|
|
int slot_value = parse_int(loadedQuickSlotRunes[i]);
|
|
if (slot_value >= RUNE_NONE) {
|
|
quick_slot_runes[i] = slot_value;
|
|
}
|
|
}
|
|
|
|
string[] loadedItemCountSlots = get_string_list_or_split(saveData, "item_count_slots");
|
|
uint count_slot_count = loadedItemCountSlots.length();
|
|
if (count_slot_count > item_count_slots.length()) count_slot_count = item_count_slots.length();
|
|
for (uint i = 0; i < count_slot_count; i++) {
|
|
int slot_value = parse_int(loadedItemCountSlots[i]);
|
|
if (slot_value >= 0 && slot_value < ITEM_COUNT) {
|
|
item_count_slots[i] = slot_value;
|
|
} else if (is_runed_item_type(slot_value)) {
|
|
item_count_slots[i] = slot_value;
|
|
}
|
|
}
|
|
|
|
// Load rune system data
|
|
rune_swiftness_unlocked = get_bool(saveData, "rune_swiftness_unlocked", false);
|
|
rune_destruction_unlocked = get_bool(saveData, "rune_destruction_unlocked", false);
|
|
unicorn_boss_defeated = get_bool(saveData, "unicorn_boss_defeated", false);
|
|
equipped_head_rune = int(get_number(saveData, "equipped_head_rune", RUNE_NONE));
|
|
equipped_torso_rune = int(get_number(saveData, "equipped_torso_rune", RUNE_NONE));
|
|
equipped_arms_rune = int(get_number(saveData, "equipped_arms_rune", RUNE_NONE));
|
|
equipped_hands_rune = int(get_number(saveData, "equipped_hands_rune", RUNE_NONE));
|
|
equipped_legs_rune = int(get_number(saveData, "equipped_legs_rune", RUNE_NONE));
|
|
equipped_feet_rune = int(get_number(saveData, "equipped_feet_rune", RUNE_NONE));
|
|
equipped_weapon_rune = int(get_number(saveData, "equipped_weapon_rune", RUNE_NONE));
|
|
|
|
// Load runed items dictionary
|
|
runed_items.delete_all();
|
|
string[] loaded_runed_items = get_string_list_or_split(saveData, "runed_items");
|
|
for (uint i = 0; i < loaded_runed_items.length(); i++) {
|
|
string entry = loaded_runed_items[i];
|
|
int eq_pos = entry.find("=");
|
|
if (eq_pos > 0) {
|
|
string key = entry.substr(0, eq_pos);
|
|
int count = parse_int(entry.substr(eq_pos + 1));
|
|
if (count > 0) {
|
|
runed_items.set(key, count);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load stored runed items dictionary
|
|
stored_runed_items.delete_all();
|
|
string[] loaded_stored_runed_items = get_string_list_or_split(saveData, "stored_runed_items");
|
|
for (uint i = 0; i < loaded_stored_runed_items.length(); i++) {
|
|
string entry = loaded_stored_runed_items[i];
|
|
int eq_pos = entry.find("=");
|
|
if (eq_pos > 0) {
|
|
string key = entry.substr(0, eq_pos);
|
|
int count = parse_int(entry.substr(eq_pos + 1));
|
|
if (count > 0) {
|
|
stored_runed_items.set(key, count);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate equipped items now that runed items are loaded
|
|
if (!equipment_available(EQUIP_SPEAR)) spear_equipped = false;
|
|
if (!equipment_available(EQUIP_AXE)) axe_equipped = false;
|
|
if (!equipment_available(EQUIP_SLING)) sling_equipped = false;
|
|
if (!equipment_available(EQUIP_BOW)) bow_equipped = false;
|
|
if (!equipment_available(EQUIP_FISHING_POLE)) fishing_pole_equipped = false;
|
|
|
|
bool any_weapon_equipped = spear_equipped || axe_equipped || sling_equipped || bow_equipped || fishing_pole_equipped;
|
|
if (!any_weapon_equipped) equipped_weapon_rune = RUNE_NONE;
|
|
|
|
if (!equipment_available(equipped_head)) {
|
|
equipped_head = EQUIP_NONE;
|
|
equipped_head_rune = RUNE_NONE;
|
|
}
|
|
if (!equipment_available(equipped_torso)) {
|
|
equipped_torso = EQUIP_NONE;
|
|
equipped_torso_rune = RUNE_NONE;
|
|
}
|
|
if (!equipment_available(equipped_hands)) {
|
|
equipped_hands = EQUIP_NONE;
|
|
equipped_hands_rune = RUNE_NONE;
|
|
}
|
|
if (!equipment_available(equipped_legs)) {
|
|
equipped_legs = EQUIP_NONE;
|
|
equipped_legs_rune = RUNE_NONE;
|
|
}
|
|
if (!equipment_available(equipped_feet)) {
|
|
equipped_feet = EQUIP_NONE;
|
|
equipped_feet_rune = RUNE_NONE;
|
|
}
|
|
if (!equipment_available(equipped_arms)) {
|
|
equipped_arms = EQUIP_NONE;
|
|
equipped_arms_rune = RUNE_NONE;
|
|
}
|
|
|
|
// Now that both equipment and runes are loaded, update stats
|
|
update_max_health_from_equipment();
|
|
|
|
current_hour = int(get_number(saveData, "time_current_hour", 8));
|
|
current_day = int(get_number(saveData, "time_current_day", 1));
|
|
sun_setting_warned = get_bool(saveData, "time_sun_setting_warned", false);
|
|
sunrise_warned = get_bool(saveData, "time_sunrise_warned", false);
|
|
area_expanded_today = get_bool(saveData, "time_area_expanded_today", false);
|
|
invasion_active = get_bool(saveData, "time_invasion_active", false);
|
|
invasion_start_hour = int(get_number(saveData, "time_invasion_start_hour", -1));
|
|
invasion_chance = int(get_number(saveData, "time_invasion_chance", 25));
|
|
invasion_triggered_today = get_bool(saveData, "time_invasion_triggered_today", false);
|
|
invasion_roll_done_today = get_bool(saveData, "time_invasion_roll_done_today", false);
|
|
invasion_scheduled_hour = int(get_number(saveData, "time_invasion_scheduled_hour", -1));
|
|
string loaded_invasion_type;
|
|
if (saveData.get("time_invasion_enemy_type", loaded_invasion_type)) {
|
|
invasion_enemy_type = loaded_invasion_type;
|
|
} else {
|
|
invasion_enemy_type = "bandit";
|
|
}
|
|
if (invasion_enemy_type == "") {
|
|
invasion_enemy_type = "bandit";
|
|
}
|
|
bool loaded_invasion_started = false;
|
|
if (saveData.get("time_invasion_started_once", loaded_invasion_started)) {
|
|
invasion_started_once = loaded_invasion_started;
|
|
} else {
|
|
invasion_started_once = (expanded_area_start != -1);
|
|
}
|
|
zombie_swarm_active = get_bool(saveData, "time_zombie_swarm_active", false);
|
|
zombie_swarm_start_hour = int(get_number(saveData, "time_zombie_swarm_start_hour", -1));
|
|
zombie_swarm_scheduled_hour = int(get_number(saveData, "time_zombie_swarm_scheduled_hour", -1));
|
|
zombie_swarm_triggered_today = get_bool(saveData, "time_zombie_swarm_triggered_today", false);
|
|
zombie_swarm_roll_done_today = get_bool(saveData, "time_zombie_swarm_roll_done_today", false);
|
|
zombie_swarm_duration_hours = int(get_number(saveData, "time_zombie_swarm_duration_hours", 0));
|
|
if (invasion_chance < 0) invasion_chance = 0;
|
|
if (invasion_chance > 100) invasion_chance = 100;
|
|
if (invasion_scheduled_hour < -1) invasion_scheduled_hour = -1;
|
|
if (invasion_scheduled_hour > 23) invasion_scheduled_hour = -1;
|
|
if (zombie_swarm_start_hour < -1 || zombie_swarm_start_hour > 23) zombie_swarm_start_hour = -1;
|
|
if (zombie_swarm_scheduled_hour < -1 || zombie_swarm_scheduled_hour > 23) zombie_swarm_scheduled_hour = -1;
|
|
if (zombie_swarm_duration_hours < 0) zombie_swarm_duration_hours = 0;
|
|
if (!zombie_swarm_active) {
|
|
zombie_swarm_start_hour = -1;
|
|
zombie_swarm_duration_hours = 0;
|
|
}
|
|
playerItemBreakChance = float(get_number(saveData, "player_item_break_chance", PLAYER_ITEM_BREAK_CHANCE_MIN));
|
|
playerItemBreaksToday = int(get_number(saveData, "player_item_breaks_today", 0));
|
|
playerItemBreakPending = get_bool(saveData, "player_item_break_pending", false);
|
|
playerItemBreakPendingType = int(get_number(saveData, "player_item_break_pending_type", -1));
|
|
if (playerItemBreakChance < PLAYER_ITEM_BREAK_CHANCE_MIN) {
|
|
playerItemBreakChance = PLAYER_ITEM_BREAK_CHANCE_MIN;
|
|
}
|
|
if (playerItemBreakChance > PLAYER_ITEM_BREAK_CHANCE_MAX) {
|
|
playerItemBreakChance = PLAYER_ITEM_BREAK_CHANCE_MAX;
|
|
}
|
|
if (playerItemBreaksToday < 0) playerItemBreaksToday = 0;
|
|
if (playerItemBreaksToday > PLAYER_ITEM_BREAKS_PER_DAY) {
|
|
playerItemBreaksToday = PLAYER_ITEM_BREAKS_PER_DAY;
|
|
}
|
|
if (playerItemBreakPending) {
|
|
bool validPending = (playerItemBreakPendingType >= 0 && playerItemBreakPendingType < ITEM_COUNT)
|
|
|| is_runed_item_type(playerItemBreakPendingType);
|
|
if (!validPending) {
|
|
playerItemBreakPending = false;
|
|
playerItemBreakPendingType = -1;
|
|
}
|
|
} else {
|
|
playerItemBreakPendingType = -1;
|
|
}
|
|
reset_player_item_break_audio_state();
|
|
quest_roll_done_today = get_bool(saveData, "quest_roll_done_today", false);
|
|
wight_spawn_chance = int(get_number(saveData, "wight_spawn_chance", WIGHT_SPAWN_CHANCE_START));
|
|
if (has_number_key(saveData, "wight_spawned_this_night")) {
|
|
wight_spawned_this_night_count = int(get_number(saveData, "wight_spawned_this_night", 0));
|
|
} else {
|
|
wight_spawned_this_night_count = get_bool(saveData, "wight_spawned_this_night", false) ? 1 : 0;
|
|
}
|
|
if (wight_spawned_this_night_count < 0) wight_spawned_this_night_count = 0;
|
|
if (wight_spawn_chance < WIGHT_SPAWN_CHANCE_START) wight_spawn_chance = WIGHT_SPAWN_CHANCE_START;
|
|
if (wight_spawn_chance > 100) wight_spawn_chance = 100;
|
|
vampyr_spawn_chance = int(get_number(saveData, "vampyr_spawn_chance", VAMPYR_SPAWN_CHANCE_START));
|
|
if (has_number_key(saveData, "vampyr_spawned_this_night")) {
|
|
vampyr_spawned_this_night_count = int(get_number(saveData, "vampyr_spawned_this_night", 0));
|
|
} else {
|
|
vampyr_spawned_this_night_count = get_bool(saveData, "vampyr_spawned_this_night", false) ? 1 : 0;
|
|
}
|
|
if (vampyr_spawned_this_night_count < 0) vampyr_spawned_this_night_count = 0;
|
|
if (vampyr_spawn_chance < VAMPYR_SPAWN_CHANCE_START) vampyr_spawn_chance = VAMPYR_SPAWN_CHANCE_START;
|
|
if (vampyr_spawn_chance > 100) vampyr_spawn_chance = 100;
|
|
|
|
quest_queue.resize(0);
|
|
string[] loadedQuests = get_string_list_or_split(saveData, "quest_queue");
|
|
for (uint i = 0; i < loadedQuests.length(); i++) {
|
|
int quest_type = parse_int(loadedQuests[i]);
|
|
if (quest_type >= 0 && quest_type < QUEST_TYPE_COUNT) {
|
|
quest_queue.insert_last(quest_type);
|
|
if (quest_queue.length() >= QUEST_MAX_ACTIVE) break;
|
|
}
|
|
}
|
|
|
|
if (current_hour < 0) current_hour = 0;
|
|
if (current_hour > 23) current_hour = 23;
|
|
if (current_day < 1) current_day = 1;
|
|
|
|
is_daytime = (current_hour >= 6 && current_hour < 19);
|
|
hour_timer.restart();
|
|
|
|
string weather_data;
|
|
if (saveData.get("weather_data", weather_data) && weather_data.length() > 0) {
|
|
deserialize_weather(weather_data);
|
|
} else {
|
|
init_weather();
|
|
}
|
|
|
|
string[] treeData = get_string_list_or_split(saveData, "trees_data");
|
|
for (uint i = 0; i < treeData.length(); i++) {
|
|
string[]@ parts = treeData[i].split("|");
|
|
if (parts.length() < 8) continue;
|
|
|
|
int pos = parse_int(parts[0]);
|
|
Tree@ tree = Tree(pos);
|
|
tree.sticks = parse_int(parts[1]);
|
|
tree.vines = parse_int(parts[2]);
|
|
tree.health = parse_int(parts[3]);
|
|
tree.height = parse_int(parts[4]);
|
|
tree.depleted = (parse_int(parts[5]) == 1);
|
|
tree.is_chopped = (parse_int(parts[6]) == 1);
|
|
tree.minutes_since_depletion = parse_int(parts[7]);
|
|
tree.regen_timer.restart();
|
|
trees.insert_last(tree);
|
|
}
|
|
normalize_tree_positions();
|
|
|
|
string[] snareData = get_string_list_or_split(saveData, "snares_data");
|
|
for (uint i = 0; i < snareData.length(); i++) {
|
|
string[]@ parts = snareData[i].split("|");
|
|
if (parts.length() < 6) continue;
|
|
|
|
int pos = parse_int(parts[0]);
|
|
WorldSnare@ snare = WorldSnare(pos);
|
|
snare.has_catch = (parse_int(parts[1]) == 1);
|
|
snare.catch_type = parts[2];
|
|
snare.catch_chance = parse_int(parts[3]);
|
|
snare.escape_chance = parse_int(parts[4]);
|
|
snare.active = (parse_int(parts[5]) == 1);
|
|
// Load hours_with_catch if available (for backwards compatibility with old saves)
|
|
if (parts.length() >= 7) {
|
|
snare.hours_with_catch = parse_int(parts[6]);
|
|
}
|
|
snare.hour_timer.restart();
|
|
world_snares.insert_last(snare);
|
|
}
|
|
|
|
string[] fireData = get_string_list_or_split(saveData, "fires_data");
|
|
for (uint i = 0; i < fireData.length(); i++) {
|
|
string[]@ parts = fireData[i].split("|");
|
|
if (parts.length() < 3) continue;
|
|
|
|
int pos = parse_int(parts[0]);
|
|
WorldFire@ fire = WorldFire(pos);
|
|
fire.fuel_remaining = parse_int(parts[1]);
|
|
fire.low_fuel_warned = (parse_int(parts[2]) == 1);
|
|
fire.fuel_timer.restart();
|
|
world_fires.insert_last(fire);
|
|
}
|
|
|
|
string[] firepitPositions = get_string_list_or_split(saveData, "firepits_positions");
|
|
for (uint i = 0; i < firepitPositions.length(); i++) {
|
|
add_world_firepit(parse_int(firepitPositions[i]));
|
|
}
|
|
|
|
string[] herbPositions = get_string_list_or_split(saveData, "herb_gardens_positions");
|
|
for (uint i = 0; i < herbPositions.length(); i++) {
|
|
add_world_herb_garden(parse_int(herbPositions[i]));
|
|
}
|
|
|
|
string[] storagePositions = get_string_list_or_split(saveData, "storages_positions");
|
|
for (uint i = 0; i < storagePositions.length(); i++) {
|
|
add_world_storage(parse_int(storagePositions[i]));
|
|
}
|
|
int loadedStorageLevel = int(get_number(saveData, "world_storage_level", -1));
|
|
if (loadedStorageLevel >= STORAGE_LEVEL_BASE && loadedStorageLevel <= STORAGE_LEVEL_UPGRADE_2) {
|
|
storage_level = loadedStorageLevel;
|
|
} else {
|
|
storage_level = (world_storages.length() > 0) ? STORAGE_LEVEL_UPGRADE_1 : STORAGE_LEVEL_BASE;
|
|
}
|
|
ensure_base_storage();
|
|
|
|
string[] pasturePositions = get_string_list_or_split(saveData, "pastures_positions");
|
|
for (uint i = 0; i < pasturePositions.length(); i++) {
|
|
add_world_pasture(parse_int(pasturePositions[i]));
|
|
}
|
|
|
|
string[] stablePositions = get_string_list_or_split(saveData, "stables_positions");
|
|
for (uint i = 0; i < stablePositions.length(); i++) {
|
|
add_world_stable(parse_int(stablePositions[i]));
|
|
}
|
|
|
|
string[] altarPositions = get_string_list_or_split(saveData, "altars_positions");
|
|
for (uint i = 0; i < altarPositions.length(); i++) {
|
|
add_world_altar(parse_int(altarPositions[i]));
|
|
}
|
|
|
|
string[] streamData = get_string_list_or_split(saveData, "streams_data");
|
|
for (uint i = 0; i < streamData.length(); i++) {
|
|
string[]@ parts = streamData[i].split("|");
|
|
if (parts.length() < 2) continue;
|
|
|
|
int startPos = parse_int(parts[0]);
|
|
int width = parse_int(parts[1]);
|
|
if (width < 1) width = 1;
|
|
add_world_stream(startPos, width);
|
|
}
|
|
|
|
string[] banditData = get_string_list_or_split(saveData, "bandits_data");
|
|
for (uint i = 0; i < banditData.length(); i++) {
|
|
string[]@ parts = banditData[i].split("|");
|
|
if (parts.length() < 6) continue;
|
|
|
|
int pos = parse_int(parts[0]);
|
|
int health = parse_int(parts[1]);
|
|
string weapon = parts[2];
|
|
string state = parts[3];
|
|
int wander_dir = parse_int(parts[4]);
|
|
int move_int = parse_int(parts[5]);
|
|
string invader_type = "bandit";
|
|
int home_start = pos;
|
|
int home_end = pos;
|
|
if (parts.length() >= 7) {
|
|
invader_type = parts[6];
|
|
if (invader_type == "") invader_type = "bandit";
|
|
}
|
|
if (parts.length() >= 9) {
|
|
home_start = parse_int(parts[7]);
|
|
home_end = parse_int(parts[8]);
|
|
} else {
|
|
if (expanded_area_start != -1 && pos >= expanded_area_start) {
|
|
int area_start = -1;
|
|
int area_end = -1;
|
|
if (get_audio_area_bounds_for_position(pos, area_start, area_end)) {
|
|
home_start = area_start;
|
|
home_end = area_end;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create bandit with dummy expansion area (position will be overridden)
|
|
Bandit@ b = Bandit(pos, home_start, home_end, invader_type);
|
|
b.position = pos;
|
|
b.health = health;
|
|
b.weapon_type = weapon;
|
|
b.behavior_state = state;
|
|
b.wander_direction = wander_dir;
|
|
b.move_interval = move_int;
|
|
b.invader_type = invader_type;
|
|
b.wander_direction_change_interval = random(BANDIT_WANDER_DIRECTION_CHANGE_MIN, BANDIT_WANDER_DIRECTION_CHANGE_MAX);
|
|
b.wander_direction_timer.restart();
|
|
b.move_timer.restart();
|
|
b.attack_timer.restart();
|
|
|
|
// Restore alert sound based on invader type
|
|
b.alert_sound = pick_invader_alert_sound(invader_type);
|
|
|
|
bandits.insert_last(b);
|
|
// Start looping sound for loaded bandit
|
|
int[] areaStarts;
|
|
int[] areaEnds;
|
|
get_active_audio_areas(areaStarts, areaEnds);
|
|
if (areaStarts.length() == 0 || range_overlaps_active_areas(b.position, b.position, areaStarts, areaEnds)) {
|
|
b.sound_handle = play_1d_with_volume_step(b.alert_sound, x, b.position, true, BANDIT_SOUND_VOLUME_STEP);
|
|
}
|
|
}
|
|
|
|
string[] mountainData = get_string_list_or_split(saveData, "mountains_data");
|
|
for (uint i = 0; i < mountainData.length(); i++) {
|
|
string[]@ parts = mountainData[i].split("|");
|
|
if (parts.length() < 5) continue;
|
|
|
|
int start_pos = parse_int(parts[0]);
|
|
int end_pos = parse_int(parts[1]);
|
|
int size = end_pos - start_pos + 1;
|
|
|
|
// Create mountain with minimal init (we'll override everything)
|
|
MountainRange@ mountain = MountainRange(start_pos, 1);
|
|
mountain.start_position = start_pos;
|
|
mountain.end_position = end_pos;
|
|
|
|
// Parse elevations
|
|
string[]@ elev_parts = parts[2].split(",");
|
|
mountain.elevations.resize(elev_parts.length());
|
|
for (uint j = 0; j < elev_parts.length(); j++) {
|
|
mountain.elevations[j] = parse_int(elev_parts[j]);
|
|
}
|
|
|
|
// Parse terrain types
|
|
string[]@ terrain_parts = parts[3].split(",");
|
|
mountain.terrain_types.resize(terrain_parts.length());
|
|
for (uint j = 0; j < terrain_parts.length(); j++) {
|
|
mountain.terrain_types[j] = terrain_parts[j];
|
|
}
|
|
|
|
// Parse stream positions
|
|
if (parts[4].length() > 0) {
|
|
string[]@ stream_parts = parts[4].split(",");
|
|
for (uint j = 0; j < stream_parts.length(); j++) {
|
|
mountain.stream_positions.insert_last(parse_int(stream_parts[j]));
|
|
}
|
|
}
|
|
|
|
world_mountains.insert_last(mountain);
|
|
}
|
|
|
|
string[] dropData = get_string_list_or_split(saveData, "drops_data");
|
|
for (uint i = 0; i < dropData.length(); i++) {
|
|
string[]@ parts = dropData[i].split("|");
|
|
if (parts.length() < 2) continue;
|
|
add_world_drop(parse_int(parts[0]), parts[1]);
|
|
}
|
|
|
|
update_ambience(true);
|
|
return true;
|
|
}
|
|
|
|
bool load_game_state() {
|
|
if (current_save_file == "") {
|
|
last_save_error = "No save selected.";
|
|
return false;
|
|
}
|
|
return load_game_state_from_file(current_save_file);
|
|
}
|