471 lines
15 KiB
Plaintext
471 lines
15 KiB
Plaintext
// Save system
|
|
|
|
const string SAVE_FILE_PATH = "save.dat";
|
|
const string SAVE_ENCRYPTION_KEY = "draugnorak_save_v1";
|
|
const int SAVE_VERSION = 1;
|
|
|
|
bool has_save_game() {
|
|
return file_exists(SAVE_FILE_PATH);
|
|
}
|
|
|
|
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 defaultValue;
|
|
return value;
|
|
}
|
|
|
|
bool get_bool(dictionary@ data, const string&in key, bool defaultValue) {
|
|
bool value;
|
|
if (@data == null) return defaultValue;
|
|
if (!data.get(key, value)) return defaultValue;
|
|
return value;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
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();
|
|
}
|
|
|
|
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;
|
|
searching = false;
|
|
|
|
player_health = 10;
|
|
max_health = 10;
|
|
|
|
inv_stones = 0;
|
|
inv_sticks = 0;
|
|
inv_vines = 0;
|
|
inv_logs = 0;
|
|
inv_clay = 0;
|
|
inv_small_game = 0;
|
|
inv_small_game_types.resize(0);
|
|
inv_meat = 0;
|
|
inv_skins = 0;
|
|
inv_spears = 0;
|
|
inv_snares = 0;
|
|
inv_axes = 0;
|
|
inv_knives = 0;
|
|
inv_fishing_poles = 0;
|
|
inv_slings = 0;
|
|
|
|
spear_equipped = false;
|
|
axe_equipped = false;
|
|
sling_equipped = false;
|
|
|
|
MAP_SIZE = 35;
|
|
expanded_area_start = -1;
|
|
expanded_area_end = -1;
|
|
expanded_terrain_types.resize(0);
|
|
|
|
barricade_health = 0;
|
|
barricade_initialized = false;
|
|
|
|
current_hour = 8;
|
|
current_day = 1;
|
|
is_daytime = true;
|
|
sun_setting_warned = false;
|
|
sunrise_warned = false;
|
|
area_expanded_today = false;
|
|
invasion_active = false;
|
|
invasion_start_hour = -1;
|
|
|
|
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();
|
|
fall_timer.restart();
|
|
climb_timer.restart();
|
|
}
|
|
|
|
void start_new_game() {
|
|
reset_game_state();
|
|
spawn_trees(5, 19);
|
|
init_barricade();
|
|
init_time();
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
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_max_health", max_health);
|
|
|
|
saveData.set("inventory_stones", inv_stones);
|
|
saveData.set("inventory_sticks", inv_sticks);
|
|
saveData.set("inventory_vines", inv_vines);
|
|
saveData.set("inventory_logs", inv_logs);
|
|
saveData.set("inventory_clay", inv_clay);
|
|
saveData.set("inventory_small_game", inv_small_game);
|
|
saveData.set("inventory_meat", inv_meat);
|
|
saveData.set("inventory_skins", inv_skins);
|
|
saveData.set("inventory_spears", inv_spears);
|
|
saveData.set("inventory_snares", inv_snares);
|
|
saveData.set("inventory_axes", inv_axes);
|
|
saveData.set("inventory_knives", inv_knives);
|
|
saveData.set("inventory_fishing_poles", inv_fishing_poles);
|
|
saveData.set("inventory_slings", inv_slings);
|
|
saveData.set("inventory_small_game_types", inv_small_game_types);
|
|
|
|
saveData.set("equipment_spear_equipped", spear_equipped);
|
|
saveData.set("equipment_axe_equipped", axe_equipped);
|
|
saveData.set("equipment_sling_equipped", sling_equipped);
|
|
|
|
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("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_expanded_terrain_types", expanded_terrain_types);
|
|
|
|
string[] treeData;
|
|
for (uint i = 0; i < trees.length(); i++) {
|
|
treeData.insert_last(serialize_tree(trees[i]));
|
|
}
|
|
saveData.set("trees_data", treeData);
|
|
|
|
string[] snareData;
|
|
for (uint i = 0; i < world_snares.length(); i++) {
|
|
snareData.insert_last(serialize_snare(world_snares[i]));
|
|
}
|
|
saveData.set("snares_data", snareData);
|
|
|
|
string[] fireData;
|
|
for (uint i = 0; i < world_fires.length(); i++) {
|
|
fireData.insert_last(serialize_fire(world_fires[i]));
|
|
}
|
|
saveData.set("fires_data", fireData);
|
|
|
|
string[] firepitPositions;
|
|
for (uint i = 0; i < world_firepits.length(); i++) {
|
|
firepitPositions.insert_last("" + world_firepits[i].position);
|
|
}
|
|
saveData.set("firepits_positions", 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", herbPositions);
|
|
|
|
string[] streamData;
|
|
for (uint i = 0; i < world_streams.length(); i++) {
|
|
streamData.insert_last(serialize_stream(world_streams[i]));
|
|
}
|
|
saveData.set("streams_data", streamData);
|
|
|
|
string rawData = saveData.serialize();
|
|
string encryptedData = encrypt_save_data(rawData);
|
|
return save_data(SAVE_FILE_PATH, encryptedData);
|
|
}
|
|
|
|
bool load_game_state() {
|
|
if (!file_exists(SAVE_FILE_PATH)) {
|
|
return false;
|
|
}
|
|
|
|
string encryptedData;
|
|
if (!read_file_string(SAVE_FILE_PATH, encryptedData)) {
|
|
return false;
|
|
}
|
|
|
|
string rawData = decrypt_save_data(encryptedData);
|
|
if (rawData.length() == 0) {
|
|
return false;
|
|
}
|
|
|
|
dictionary@ saveData = deserialize(rawData);
|
|
if (@saveData == null) {
|
|
return false;
|
|
}
|
|
|
|
reset_game_state();
|
|
|
|
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(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));
|
|
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));
|
|
|
|
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;
|
|
|
|
inv_stones = int(get_number(saveData, "inventory_stones", 0));
|
|
inv_sticks = int(get_number(saveData, "inventory_sticks", 0));
|
|
inv_vines = int(get_number(saveData, "inventory_vines", 0));
|
|
inv_logs = int(get_number(saveData, "inventory_logs", 0));
|
|
inv_clay = int(get_number(saveData, "inventory_clay", 0));
|
|
inv_small_game = int(get_number(saveData, "inventory_small_game", 0));
|
|
inv_meat = int(get_number(saveData, "inventory_meat", 0));
|
|
inv_skins = int(get_number(saveData, "inventory_skins", 0));
|
|
inv_spears = int(get_number(saveData, "inventory_spears", 0));
|
|
inv_snares = int(get_number(saveData, "inventory_snares", 0));
|
|
inv_axes = int(get_number(saveData, "inventory_axes", 0));
|
|
inv_knives = int(get_number(saveData, "inventory_knives", 0));
|
|
inv_fishing_poles = int(get_number(saveData, "inventory_fishing_poles", 0));
|
|
inv_slings = int(get_number(saveData, "inventory_slings", 0));
|
|
|
|
string[] loadedSmallGameTypes = get_string_list(saveData, "inventory_small_game_types");
|
|
inv_small_game_types.resize(0);
|
|
for (uint i = 0; i < loadedSmallGameTypes.length(); i++) {
|
|
inv_small_game_types.insert_last(loadedSmallGameTypes[i]);
|
|
}
|
|
|
|
if (inv_small_game_types.length() == 0 && inv_small_game > 0) {
|
|
for (int i = 0; i < inv_small_game; i++) {
|
|
inv_small_game_types.insert_last("small game");
|
|
}
|
|
} else {
|
|
inv_small_game = inv_small_game_types.length();
|
|
}
|
|
|
|
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);
|
|
|
|
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));
|
|
|
|
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[] treeData = get_string_list(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);
|
|
}
|
|
|
|
string[] snareData = get_string_list(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);
|
|
snare.minute_timer.restart();
|
|
world_snares.insert_last(snare);
|
|
}
|
|
|
|
string[] fireData = get_string_list(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(saveData, "firepits_positions");
|
|
for (uint i = 0; i < firepitPositions.length(); i++) {
|
|
add_world_firepit(parse_int(firepitPositions[i]));
|
|
}
|
|
|
|
string[] herbPositions = get_string_list(saveData, "herb_gardens_positions");
|
|
for (uint i = 0; i < herbPositions.length(); i++) {
|
|
add_world_herb_garden(parse_int(herbPositions[i]));
|
|
}
|
|
|
|
string[] streamData = get_string_list(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);
|
|
}
|
|
|
|
update_ambience(true);
|
|
return true;
|
|
}
|