Files
oboe/src/boe.fileio.cpp

590 lines
19 KiB
C++

#include <iostream>
#include <fstream>
#include "boe.global.hpp"
#include "universe.hpp"
#include "boe.fileio.hpp"
#include "boe.text.hpp"
#include "boe.town.hpp"
#include "boe.items.hpp"
#include "boe.graphics.hpp"
#include "boe.locutils.hpp"
#include "boe.newgraph.hpp"
#include "boe.dlgutil.hpp"
#include "boe.infodlg.hpp"
#include "boe.graphutil.hpp"
#include "graphtool.hpp"
#include "soundtool.hpp"
#include "mathutil.hpp"
#include "dlogutil.hpp"
#include "fileio.hpp"
#include <boost/filesystem.hpp>
#include "restypes.hpp"
#define DONE_BUTTON_ITEM 1
extern eStatMode stat_screen_mode;
extern bool give_delays;
extern eGameMode overall_mode;
extern bool play_sounds,sys_7_avail,save_maps,party_in_memory,in_scen_debug,ghost_mode,node_step_through;
extern location center;
extern long register_flag;
extern sf::RenderWindow mainPtr;
extern short current_pc;
extern bool map_visible;
extern sf::RenderWindow mini_map;
extern short which_combat_type;
extern short cur_town_talk_loaded;
extern cUniverse univ;
std::vector<scen_header_type> scen_headers;
extern bool mac_is_intel;
bool loaded_yet = false, got_nagged = false;
std::string last_load_file = "Blades of Exile Save";
fs::path file_to_load;
fs::path store_file_reply;
short jl;
extern bool cur_scen_is_mac;
void print_write_position ();
void save_outdoor_maps();
void add_outdoor_maps();
short specials_res_id,data_dump_file_id;
char start_name[256];
short start_volume,data_volume;
extern fs::path progDir;
cCustomGraphics spec_scen_g;
extern bool pc_gworld_loaded;
extern sf::Texture pc_gworld;
void finish_load_party(){
bool town_restore = univ.town.num < 200;
bool in_scen = univ.party.scen_name.length() > 0;
party_in_memory = true;
in_scen_debug = false;
ghost_mode = false;
node_step_through = false;
// now if not in scen, this is it.
if(!in_scen) {
if(overall_mode != MODE_STARTUP) {
reload_startup();
draw_startup(0);
}
if(!pc_gworld_loaded) {
pc_gworld.loadFromImage(*ResMgr::get<ImageRsrc>("pcs"));
pc_gworld_loaded = true;
}
overall_mode = MODE_STARTUP;
return;
}
fs::path path;
path = progDir/"Blades of Exile Scenarios";
path /= univ.party.scen_name;
std::cout<<"Searching for scenario at:\n"<<path<<'\n';
if(!load_scenario(path, univ.scenario))
return;
// Saved creatures may not have had their monster attributes saved
// Make sure that they know what they are!
// Cast to cMonster base class and assign, to avoid clobbering other attributes
for(int i = 0; i < 4; i++) {
for(size_t j = 0; j < univ.party.creature_save[i].size(); j++) {
int number = univ.party.creature_save[i][j].number;
cMonster& monst = univ.party.creature_save[i][j];
monst = univ.scenario.scen_monsters[number];
}
}
for(size_t j = 0; j < univ.town.monst.size(); j++) {
int number = univ.town.monst[j].number;
cMonster& monst = univ.town.monst[j];
monst = univ.scenario.scen_monsters[number];
}
// if at this point, startup must be over, so make this call to make sure we're ready,
// graphics wise
end_startup();
overall_mode = town_restore ? MODE_TOWN : MODE_OUTDOORS;
stat_screen_mode = MODE_INVEN;
build_outdoors();
erase_out_specials();
if(!town_restore) {
center = univ.party.p_loc;
}
else {
for(int i = 0; i < univ.town.monst.size(); i++){
univ.town.monst[i].targ_loc.x = 0;
univ.town.monst[i].targ_loc.y = 0;
}
// Set up field booleans
for(int j = 0; j < univ.town->max_dim(); j++)
for(int k = 0; k < univ.town->max_dim(); k++) {
if(univ.town.is_quickfire(j,k))
univ.town.quickfire_present = true;
if(univ.scenario.ter_types[univ.town->terrain(j,k)].special == eTerSpec::CONVEYOR)
univ.town.belt_present = true;
}
center = univ.town.p_loc;
}
redraw_screen(REFRESH_ALL);
current_pc = first_active_pc();
loaded_yet = true;
last_load_file = file_to_load.filename().string();
store_file_reply = file_to_load;
add_string_to_buf("Load: Game loaded.");
// Set sounds, map saving, and speed
if(((play_sounds) && (PSD[SDF_NO_SOUNDS] == 1)) ||
(!play_sounds && (PSD[SDF_NO_SOUNDS] == 0))) {
flip_sound();
}
give_delays = PSD[SDF_NO_FRILLS];
if(PSD[SDF_NO_MAPS] == 0)
save_maps = true;
else save_maps = false;
in_scen_debug = false;
}
void shift_universe_left() {
short i,j;
make_cursor_watch();
save_outdoor_maps();
univ.party.outdoor_corner.x--;
univ.party.i_w_c.x++;
univ.party.p_loc.x += 48;
for(i = 48; i < 96; i++)
for(j = 0; j < 96; j++)
univ.out.out_e[i][j] = univ.out.out_e[i - 48][j];
for(i = 0; i < 48; i++)
for(j = 0; j < 96; j++)
univ.out.out_e[i][j] = 0;
for(i = 0; i < 10; i++) {
if(univ.party.out_c[i].m_loc.x > 48)
univ.party.out_c[i].exists = false;
if(univ.party.out_c[i].exists)
univ.party.out_c[i].m_loc.x += 48;
}
build_outdoors();
restore_cursor();
}
void shift_universe_right() {
short i,j;
make_cursor_watch();
save_outdoor_maps();
univ.party.outdoor_corner.x++;
univ.party.i_w_c.x--;
univ.party.p_loc.x -= 48;
for(i = 0; i < 48; i++)
for(j = 0; j < 96; j++)
univ.out.out_e[i][j] = univ.out.out_e[i + 48][j];
for(i = 48; i < 96; i++)
for(j = 0; j < 96; j++)
univ.out.out_e[i][j] = 0;
for(i = 0; i < 10; i++) {
if(univ.party.out_c[i].m_loc.x < 48)
univ.party.out_c[i].exists = false;
if(univ.party.out_c[i].exists)
univ.party.out_c[i].m_loc.x -= 48;
}
build_outdoors();
restore_cursor();
}
void shift_universe_up() {
short i,j;
make_cursor_watch();
save_outdoor_maps();
univ.party.outdoor_corner.y--;
univ.party.i_w_c.y++;
univ.party.p_loc.y += 48;
for(i = 0; i < 96; i++)
for(j = 48; j < 96; j++)
univ.out.out_e[i][j] = univ.out.out_e[i][j - 48];
for(i = 0; i < 96; i++)
for(j = 0; j < 48; j++)
univ.out.out_e[i][j] = 0;
for(i = 0; i < 10; i++) {
if(univ.party.out_c[i].m_loc.y > 48)
univ.party.out_c[i].exists = false;
if(univ.party.out_c[i].exists)
univ.party.out_c[i].m_loc.y += 48;
}
build_outdoors();
restore_cursor();
}
void shift_universe_down() {
short i,j;
make_cursor_watch();
save_outdoor_maps();
univ.party.outdoor_corner.y++;
univ.party.i_w_c.y--;
univ.party.p_loc.y = univ.party.p_loc.y - 48;
for(i = 0; i < 96; i++)
for(j = 0; j < 48; j++)
univ.out.out_e[i][j] = univ.out.out_e[i][j + 48];
for(i = 0; i < 96; i++)
for(j = 48; j < 96; j++)
univ.out.out_e[i][j] = 0;
for(i = 0; i < 10; i++) {
if(univ.party.out_c[i].m_loc.y < 48)
univ.party.out_c[i].exists = false;
if(univ.party.out_c[i].exists)
univ.party.out_c[i].m_loc.y = univ.party.out_c[i].m_loc.y - 48;
}
build_outdoors();
restore_cursor();
}
void position_party(short out_x,short out_y,short pc_pos_x,short pc_pos_y) {
short i,j;
if((pc_pos_x != minmax(0,47,pc_pos_x)) || (pc_pos_y != minmax(0,47,pc_pos_y)) ||
(out_x != minmax(0,univ.scenario.outdoors.width() - 1,out_x)) || (out_y != minmax(0,univ.scenario.outdoors.height() - 1,out_y))) {
giveError("The scenario has tried to place you in an out of bounds outdoor location.");
return;
}
save_outdoor_maps();
univ.party.p_loc.x = pc_pos_x;
univ.party.p_loc.y = pc_pos_y;
univ.party.loc_in_sec = global_to_local(univ.party.p_loc);
if((univ.party.outdoor_corner.x != out_x) || (univ.party.outdoor_corner.y != out_y)) {
univ.party.outdoor_corner.x = out_x;
univ.party.outdoor_corner.y = out_y;
}
univ.party.i_w_c.x = (univ.party.p_loc.x > 47) ? 1 : 0;
univ.party.i_w_c.y = (univ.party.p_loc.y > 47) ? 1 : 0;
for(i = 0; i < 10; i++)
univ.party.out_c[i].exists = false;
for(i = 0; i < 96; i++)
for(j = 0; j < 96; j++)
univ.out.out_e[i][j] = 0;
build_outdoors();
}
void build_outdoors() {
short i,j;
size_t x = univ.party.outdoor_corner.x, y = univ.party.outdoor_corner.y;
for(i = 0; i < 48; i++)
for(j = 0; j < 48; j++) {
univ.out[i][j] = univ.scenario.outdoors[x][y]->terrain[i][j];
if(x + 1 < univ.scenario.outdoors.width())
univ.out[48 + i][j] = univ.scenario.outdoors[x+1][y]->terrain[i][j];
if(y + 1 < univ.scenario.outdoors.height())
univ.out[i][48 + j] = univ.scenario.outdoors[x][y+1]->terrain[i][j];
if(x + 1 < univ.scenario.outdoors.width() && y + 1 < univ.scenario.outdoors.height())
univ.out[48 + i][48 + j] = univ.scenario.outdoors[x+1][y+1]->terrain[i][j];
}
fix_boats();
add_outdoor_maps();
// make_out_trim();
// TODO: This might be another relic of the "demo" mode
if(overall_mode != MODE_STARTUP)
erase_out_specials();
for(i = 0; i < 10; i++)
if(univ.party.out_c[i].exists)
if((univ.party.out_c[i].m_loc.x < 0) || (univ.party.out_c[i].m_loc.y < 0) ||
(univ.party.out_c[i].m_loc.x > 95) || (univ.party.out_c[i].m_loc.y > 95))
univ.party.out_c[i].exists = false;
}
short onm(char x_sector,char y_sector) {
short i;
i = y_sector * univ.scenario.outdoors.width() + x_sector;
return i;
}
// This adds the current outdoor map info to the saved outdoor map info
void save_outdoor_maps() {
short i,j;
for(i = 0; i < 48; i++)
for(j = 0; j < 48; j++) {
if(univ.out.out_e[i][j] > 0)
univ.out_maps[onm(univ.party.outdoor_corner.x,univ.party.outdoor_corner.y)][i / 8][j] =
univ.out_maps[onm(univ.party.outdoor_corner.x,univ.party.outdoor_corner.y)][i / 8][j] |
(char) (1 << i % 8);
if(univ.party.outdoor_corner.x + 1 < univ.scenario.outdoors.width()) {
if(univ.out.out_e[i + 48][j] > 0)
univ.out_maps[onm(univ.party.outdoor_corner.x + 1,univ.party.outdoor_corner.y)][i / 8][j] =
univ.out_maps[onm(univ.party.outdoor_corner.x + 1,univ.party.outdoor_corner.y)][i / 8][j] |
(char) (1 << i % 8);
}
if(univ.party.outdoor_corner.y + 1 < univ.scenario.outdoors.height()) {
if(univ.out.out_e[i][j + 48] > 0)
univ.out_maps[onm(univ.party.outdoor_corner.x,univ.party.outdoor_corner.y + 1)][i / 8][j] =
univ.out_maps[onm(univ.party.outdoor_corner.x,univ.party.outdoor_corner.y + 1)][i / 8][j] |
(char) (1 << i % 8);
}
if((univ.party.outdoor_corner.y + 1 < univ.scenario.outdoors.height()) &&
(univ.party.outdoor_corner.x + 1 < univ.scenario.outdoors.width())) {
if(univ.out.out_e[i + 48][j + 48] > 0)
univ.out_maps[onm(univ.party.outdoor_corner.x + 1,univ.party.outdoor_corner.y + 1)][i / 8][j] =
univ.out_maps[onm(univ.party.outdoor_corner.x + 1,univ.party.outdoor_corner.y + 1)][i / 8][j] |
(char) (1 << i % 8);
}
}
}
void add_outdoor_maps() { // This takes the existing outdoor map info and supplements it with the saved map info
short i,j;
for(i = 0; i < 48; i++)
for(j = 0; j < 48; j++) {
if((univ.out.out_e[i][j] == 0) &&
((univ.out_maps[onm(univ.party.outdoor_corner.x,univ.party.outdoor_corner.y)][i / 8][j] &
(char) (1 << i % 8)) != 0))
univ.out.out_e[i][j] = 1;
if(univ.party.outdoor_corner.x + 1 < univ.scenario.outdoors.width()) {
if((univ.out.out_e[i + 48][j] == 0) &&
((univ.out_maps[onm(univ.party.outdoor_corner.x + 1,univ.party.outdoor_corner.y)][i / 8][j] &
(char) (1 << i % 8)) != 0))
univ.out.out_e[i + 48][j] = 1;
}
if(univ.party.outdoor_corner.y + 1 < univ.scenario.outdoors.height()) {
if((univ.out.out_e[i][j + 48] == 0) &&
((univ.out_maps[onm(univ.party.outdoor_corner.x,univ.party.outdoor_corner.y + 1)][i / 8][j] &
(char) (1 << i % 8)) != 0))
univ.out.out_e[i][j + 48] = 1;
}
if((univ.party.outdoor_corner.y + 1 < univ.scenario.outdoors.height()) &&
(univ.party.outdoor_corner.x + 1 < univ.scenario.outdoors.width())) {
if((univ.out.out_e[i + 48][j + 48] == 0) &&
((univ.out_maps[onm(univ.party.outdoor_corner.x + 1,univ.party.outdoor_corner.y + 1)][i / 8][j] &
(char) (1 << i % 8)) != 0))
univ.out.out_e[i + 48][j + 48] = 1;
}
}
}
void fix_boats() {
short i;
for(i = 0; i < univ.party.boats.size(); i++)
if((univ.party.boats[i].exists) && (univ.party.boats[i].which_town == 200)) {
if(univ.party.boats[i].sector.x == univ.party.outdoor_corner.x)
univ.party.boats[i].loc.x = univ.party.boats[i].loc_in_sec.x;
else if(univ.party.boats[i].sector.x == univ.party.outdoor_corner.x + 1)
univ.party.boats[i].loc.x = univ.party.boats[i].loc_in_sec.x + 48;
else univ.party.boats[i].loc.x = 500;
if(univ.party.boats[i].sector.y == univ.party.outdoor_corner.y)
univ.party.boats[i].loc.y = univ.party.boats[i].loc_in_sec.y;
else if(univ.party.boats[i].sector.y == univ.party.outdoor_corner.y + 1)
univ.party.boats[i].loc.y = univ.party.boats[i].loc_in_sec.y + 48;
else univ.party.boats[i].loc.y = 500;
}
for(i = 0; i < univ.party.horses.size(); i++)
if((univ.party.horses[i].exists) && (univ.party.horses[i].which_town == 200)) {
if(univ.party.horses[i].sector.x == univ.party.outdoor_corner.x)
univ.party.horses[i].loc.x = univ.party.horses[i].loc_in_sec.x;
else if(univ.party.horses[i].sector.x == univ.party.outdoor_corner.x + 1)
univ.party.horses[i].loc.x = univ.party.horses[i].loc_in_sec.x + 48;
else univ.party.horses[i].loc.x = 500;
if(univ.party.horses[i].sector.y == univ.party.outdoor_corner.y)
univ.party.horses[i].loc.y = univ.party.horses[i].loc_in_sec.y;
else if(univ.party.horses[i].sector.y == univ.party.outdoor_corner.y + 1)
univ.party.horses[i].loc.y = univ.party.horses[i].loc_in_sec.y + 48;
else univ.party.horses[i].loc.y = 500;
}
}
void start_data_dump() {
fs::path path = progDir/"Data Dump.txt";
std::ofstream fout(path.c_str());
fout << "Begin data dump:\n";
fout << " Overall mode " << overall_mode << "\n";
fout << " Outdoor loc " << univ.party.outdoor_corner.x << " " << univ.party.outdoor_corner.y;
fout << " Ploc " << univ.party.p_loc.x << " " << univ.party.p_loc.y << "\n";
if((is_town()) || (is_combat())) {
fout << " Town num " << univ.town.num << " Town loc " << univ.town.p_loc.x << " " << univ.town.p_loc.y << " \n";
if(is_combat()) {
fout << " Combat type " << which_combat_type << " \n";
}
for(long i = 0; i < univ.town.monst.size(); i++) {
fout << " Monster " << i << " Status " << univ.town.monst[i].active;
fout << " Loc " << univ.town.monst[i].cur_loc.x << " " << univ.town.monst[i].cur_loc.y;
fout << " Number " << univ.town.monst[i].number << " Att " << univ.town.monst[i].attitude;
fout << " Tf " << univ.town.monst[i].time_flag << "\n";
}
}
}
fs::path locate_scenario(std::string scen_name) {
std::transform(scen_name.begin(), scen_name.end(), scen_name.begin(), tolower);
fs::path scenDir = progDir/"Blades of Exile Scenarios", scenPath;
for(fs::recursive_directory_iterator iter(scenDir); iter != fs::recursive_directory_iterator(); iter++) {
fs::file_status stat = iter->status();
std::string fname = iter->path().filename().string().c_str();
std::transform(fname.begin(), fname.end(), fname.begin(), tolower);
if(fname == "header.exs") {
if(scen_name != "header.exs") continue;
// We want to support a scenario whose main filename is header.exs, just in case.
// However, any unpacked scenarios would have a header.exs.
// So, skip them if they're in a .boes folder.
fname = iter->path().parent_path().filename().string().c_str();
std::transform(fname.begin(), fname.end(), fname.begin(), tolower);
size_t dot = fname.find_first_of('.');
if(dot != std::string::npos && fname.substr(dot) == ".boes")
continue;
}
if(fname != scen_name) continue;
size_t dot = fname.find_first_of('.');
if(dot == std::string::npos) continue;
if(fname.substr(dot) == ".exs" && stat.type() == fs::regular_file) {
scenPath = iter->path();
break;
}
if(fname.substr(dot) == ".boes" && (stat.type() == fs::regular_file || stat.type() == fs::directory_file)) {
scenPath = iter->path();
break;
}
}
return scenPath;
}
void build_scen_headers() {
fs::path scenDir = progDir;
scenDir /= "Blades of Exile Scenarios";
std::cout << progDir << '\n' << scenDir << std::endl;
scen_headers.clear();
fs::recursive_directory_iterator iter(scenDir);
make_cursor_watch();
while(iter != fs::recursive_directory_iterator()) {
fs::file_status stat = iter->status();
if(stat.type() == fs::regular_file)
load_scenario_header(iter->path());
iter++;
}
if(scen_headers.size() == 0) { // no scens present
// TODO: Should something be done here?
} else {
std::sort(scen_headers.begin(), scen_headers.end(), [](scen_header_type hdr_a, scen_header_type hdr_b) -> bool {
std::string a = hdr_a.name, b = hdr_b.name;
std::transform(a.begin(), a.end(), a.begin(), tolower);
std::transform(b.begin(), b.end(), b.begin(), tolower);
if(a.substr(0,2) == "a ") a.erase(a.begin(), a.begin() + 2);
else if(a.substr(0,4) == "the ") a.erase(a.begin(), a.begin() + 4);
if(b.substr(0,2) == "a ") b.erase(b.begin(), b.begin() + 2);
else if(b.substr(0,4) == "the ") b.erase(b.begin(), b.begin() + 4);
return a < b;
});
}
}
// This is only called at startup, when bringing headers of active scenarios.
bool load_scenario_header(fs::path file/*,short header_entry*/){
bool file_ok = false;
std::string fname = file.filename().string();
int dot = fname.find_first_of('.');
if(dot == std::string::npos)
return false; // If it has no file extension, it's not a valid scenario.
std::string file_ext = fname.substr(dot);
std::transform(file_ext.begin(), file_ext.end(), file_ext.begin(), tolower);
if(file_ext == ".exs") {
std::ifstream fin(file.string(), std::ios::binary);
if(fin.fail()) return false;
scenario_header_flags curScen;
long len = (long) sizeof(scenario_header_flags);
if(!fin.read((char*)&curScen, len)) return false;
if(curScen.flag1 == 10 && curScen.flag2 == 20 && curScen.flag3 == 30 && curScen.flag4 == 40)
file_ok = true; // Legacy Mac scenario
else if(curScen.flag1 == 20 && curScen.flag2 == 40 && curScen.flag3 == 60 && curScen.flag4 == 80)
file_ok = true; // Legacy Windows scenario
else if(curScen.flag1 == 'O' && curScen.flag2 == 'B' && curScen.flag3 == 'O' && curScen.flag4 == 'E')
file_ok = true; // Unpacked OBoE scenario
} else if(file_ext == ".boes") {
if(fs::is_directory(file)) {
if(fs::exists(file/"header.exs"))
return load_scenario_header(file/"header.exs");
} else {
unsigned char magic[2];
std::ifstream fin(file.string(), std::ios::binary);
if(fin.fail()) return false;
if(!fin.read((char*)magic, 2)) return false;
// Check for the gzip magic number
if(magic[0] == 0x1f && magic[1] == 0x8b)
file_ok = true;
}
}
if(!file_ok) return false;
// So file is (probably) OK, so load in string data and close it.
cScenario temp_scenario;
if(!load_scenario(file, temp_scenario, true))
return false;
scen_header_type scen_head;
scen_head.name = temp_scenario.scen_name;
scen_head.who1 = temp_scenario.who_wrote[0];
scen_head.who2 = temp_scenario.who_wrote[1];
scen_head.file = fname;
scen_head.intro_pic = temp_scenario.intro_pic;
scen_head.rating = temp_scenario.rating;
scen_head.difficulty = temp_scenario.difficulty;
std::copy(temp_scenario.format.ver, temp_scenario.format.ver + 3, scen_head.ver);
std::copy(temp_scenario.format.prog_make_ver, temp_scenario.format.prog_make_ver + 3, scen_head.prog_make_ver);
if(scen_head.file.substr(0,dot) == "valleydy" ||
scen_head.file.substr(0,dot) == "stealth" ||
scen_head.file.substr(0,dot) == "zakhazi"/* ||
scen_strs.file.substr(0,dot) == "busywork" */)
return false;
scen_headers.push_back(scen_head);
return true;
}