Fix fields array not matching the size of the town
The fields array was fixed at 64x64, which is fine for all towns supported in legacy BoE. However, we intend to support even larger towns in the future, and also it seems silly to hold so much extra space for a smaller town. So now, the fields array is a 2D vector that matches the size of the terrain vector. The setup array is similarly a list of 2D vectors. This radically changes the format used to store the setup array in a saved game. Older saves won't crash the game, but fields will be messed up or missing. Resetting towns is recommended.
This commit is contained in:
@@ -347,17 +347,10 @@ bool load_party_v2(fs::path file_to_load, cUniverse& real_univ){
|
||||
showError("Loading Blades of Exile save file failed.");
|
||||
return false;
|
||||
}
|
||||
uint16_t magic;
|
||||
fin.read((char*)&magic, 2);
|
||||
fin.read((char*)&univ.party.setup, sizeof(univ.party.setup));
|
||||
if(magic == 0x0E0B) { // should be 0x0B0E!
|
||||
for(auto& i : univ.party.setup) {
|
||||
for(auto& j : i) {
|
||||
for(auto& k : j) {
|
||||
flip_short(reinterpret_cast<int16_t*>(&k));
|
||||
}
|
||||
}
|
||||
}
|
||||
file.readFrom(fin);
|
||||
auto& page = file[0];
|
||||
for(size_t i = 0; i < univ.party.setup.size(); i++) {
|
||||
page[std::to_string(i)].extract(univ.party.setup[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,9 +454,13 @@ bool save_party(fs::path dest_file, const cUniverse& univ) {
|
||||
|
||||
{
|
||||
std::ostream& fout = partyOut.newFile("save/setup.dat");
|
||||
static uint16_t magic = 0x0B0E;
|
||||
fout.write((char*)&magic, 2);
|
||||
fout.write((char*)&univ.party.setup, sizeof(univ.party.setup));
|
||||
auto& page = file.add();
|
||||
page.add("OBOE");
|
||||
for(size_t i = 0; i < univ.party.setup.size(); i++) {
|
||||
page[std::to_string(i)].encode(univ.party.setup[i]);
|
||||
}
|
||||
file.writeTo(fout);
|
||||
file.clear();
|
||||
}
|
||||
|
||||
if(univ.party.town_num < 200) {
|
||||
|
@@ -274,10 +274,6 @@ void start_outdoor_combat(cOutdoors::cCreature encounter,location where,short nu
|
||||
|
||||
// Basically, in outdoor combat, we create kind of a 48x48 town for
|
||||
// the combat to take place in
|
||||
for(short i = 0; i < 48; i++)
|
||||
for(short j = 0; j < 48; j++) {
|
||||
univ.town.fields[i][j] = 0;
|
||||
}
|
||||
univ.town.prep_arena();
|
||||
univ.town->in_town_rect = town_rect;
|
||||
|
||||
|
@@ -715,8 +715,6 @@ bool combat_move_monster(short which,location destination) {
|
||||
location find_clear_spot(location from_where,short mode) {
|
||||
location loc,store_loc;
|
||||
short num_tries = 0,r1;
|
||||
// Here 254 indicates the low byte of the town fields, minus explored spaces (which is lowest bit).
|
||||
unsigned long blocking_fields = SPECIAL_SPOT | OBJECT_CRATE | OBJECT_BARREL | OBJECT_BLOCK | FIELD_QUICKFIRE | 254;
|
||||
|
||||
while(num_tries < 75) {
|
||||
num_tries++;
|
||||
@@ -729,7 +727,7 @@ location find_clear_spot(location from_where,short mode) {
|
||||
&& can_see_light(from_where,loc,combat_obscurity) == 0
|
||||
&& (!is_combat() || univ.target_there(loc,TARG_PC) == nullptr)
|
||||
&& (!(is_town()) || (loc != univ.party.town_loc))
|
||||
&& (!(univ.town.fields[loc.x][loc.y] & blocking_fields))) {
|
||||
&& (!univ.town.is_summon_safe(loc.x, loc.y))) {
|
||||
if((mode == 0) || ((mode == 1) && (adjacent(from_where,loc))))
|
||||
return loc;
|
||||
else store_loc = loc;
|
||||
|
@@ -75,7 +75,6 @@ void start_town_mode(short which_town, short entry_dir) {
|
||||
short at_which_save_slot,former_town;
|
||||
bool monsters_loaded = false,town_toast = false;
|
||||
location loc;
|
||||
unsigned short temp;
|
||||
bool play_town_sound = false;
|
||||
|
||||
if(town_force < 200)
|
||||
@@ -230,15 +229,9 @@ void start_town_mode(short which_town, short entry_dir) {
|
||||
}
|
||||
}
|
||||
|
||||
for(short j = 0; j < univ.town->max_dim; j++)
|
||||
for(short k = 0; k < univ.town->max_dim; k++) { // now load in saved setup,
|
||||
// except that pushable things restore to orig locs
|
||||
// TODO: THIS IS A TEMPORARY HACK TO GET i VALUE
|
||||
int i = std::find_if(univ.party.creature_save.begin(), univ.party.creature_save.end(), [&pop](cPopulation& p) {return &p == &pop;}) - univ.party.creature_save.begin();
|
||||
temp = univ.party.setup[i][j][k] << 8;
|
||||
temp &= ~(OBJECT_CRATE | OBJECT_BARREL | OBJECT_BLOCK);
|
||||
univ.town.fields[j][k] |= temp;
|
||||
}
|
||||
// TODO: THIS IS A TEMPORARY HACK TO GET i VALUE
|
||||
int i = std::find_if(univ.party.creature_save.begin(), univ.party.creature_save.end(), [&pop](cPopulation& p) {return &p == &pop;}) - univ.party.creature_save.begin();
|
||||
univ.town.update_fields(univ.party.setup[i]);
|
||||
}
|
||||
|
||||
if(!monsters_loaded) {
|
||||
@@ -547,16 +540,13 @@ location end_town_mode(short switching_level,location destination) { // returns
|
||||
pop = univ.town.monst;
|
||||
// TODO: THIS IS A TEMPORARY HACK TO GET i VALUE
|
||||
int i = std::find_if(univ.party.creature_save.begin(), univ.party.creature_save.end(), [&pop](cPopulation& p) {return &p == &pop;}) - univ.party.creature_save.begin();
|
||||
for(short j = 0; j < univ.town->max_dim; j++)
|
||||
for(short k = 0; k < univ.town->max_dim; k++)
|
||||
univ.party.setup[i][j][k] = (univ.town.fields[j][k] & 0xff00) >> 8;
|
||||
univ.town.save_setup(univ.party.setup[i]);
|
||||
data_saved = true;
|
||||
break;
|
||||
}
|
||||
if(!data_saved) {
|
||||
univ.party.creature_save[univ.party.at_which_save_slot] = univ.town.monst;
|
||||
for(short j = 0; j < univ.town->max_dim; j++)
|
||||
for(short k = 0; k < univ.town->max_dim; k++)
|
||||
univ.party.setup[univ.party.at_which_save_slot][j][k] = (univ.town.fields[j][k] & 0xff00) >> 8;
|
||||
univ.town.save_setup(univ.party.setup[univ.party.at_which_save_slot]);
|
||||
univ.party.at_which_save_slot = (univ.party.at_which_save_slot == 3) ? 0 : univ.party.at_which_save_slot + 1;
|
||||
}
|
||||
|
||||
@@ -930,7 +920,6 @@ void create_out_combat_terrain(short ter_type,short num_walls,bool is_road) {
|
||||
|
||||
for(short i = 0; i < 48; i++)
|
||||
for(short j = 0; j < 48; j++) {
|
||||
univ.town.fields[i][j] = 0;
|
||||
if((j <= 8) || (j >= 35) || (i <= 8) || (i >= 35))
|
||||
univ.town->terrain(i,j) = 90;
|
||||
else univ.town->terrain(i,j) = ter_base[arena];
|
||||
|
@@ -69,6 +69,12 @@ public:
|
||||
assign_from_container(list);
|
||||
return *this;
|
||||
}
|
||||
void fill(Type val) {
|
||||
row_ref& me = *this;
|
||||
for(int i = 0; i < ref.width(); i++) {
|
||||
me[i] = val;
|
||||
}
|
||||
}
|
||||
// Seems like defining a move assignment operator deletes the copy constructor. Don't want that.
|
||||
row_ref(const row_ref&) = default;
|
||||
};
|
||||
@@ -117,6 +123,12 @@ public:
|
||||
assign_from_container(list);
|
||||
return *this;
|
||||
}
|
||||
void fill(Type val) {
|
||||
col_ref& me = *this;
|
||||
for(int i = 0; i < ref.width(); i++) {
|
||||
me[i] = val;
|
||||
}
|
||||
}
|
||||
// Seems like defining a move assignment operator deletes the copy constructor. Don't want that.
|
||||
col_ref(const col_ref&) = default;
|
||||
};
|
||||
@@ -244,6 +256,13 @@ public:
|
||||
bool empty() const {
|
||||
return data.empty();
|
||||
}
|
||||
void fill(Type val) {
|
||||
for(size_t x = 0; x < w; x++) {
|
||||
for(size_t y = 0; y < h; y++) {
|
||||
(*this)[x][y] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
vector2d() : w(0), h(0) {}
|
||||
vector2d(size_t w, size_t h) : vector2d() {
|
||||
resize(w,h);
|
||||
|
@@ -92,6 +92,7 @@ cParty::cParty(const cParty& other)
|
||||
, total_xp_gained(other.total_xp_gained)
|
||||
, total_dam_taken(other.total_dam_taken)
|
||||
, scen_name(other.scen_name)
|
||||
, setup(other.setup)
|
||||
, stored_items(other.stored_items)
|
||||
, summons(other.summons)
|
||||
, scen_won(other.scen_won)
|
||||
@@ -100,7 +101,6 @@ cParty::cParty(const cParty& other)
|
||||
, pointers(other.pointers)
|
||||
{
|
||||
memcpy(stuff_done, other.stuff_done, sizeof(stuff_done));
|
||||
memcpy(setup, other.setup, sizeof(setup));
|
||||
for(int i = 0; i < 6; i++) {
|
||||
adven[i].reset(new cPlayer(*this, *other.adven[i]));
|
||||
}
|
||||
@@ -162,6 +162,7 @@ void cParty::swap(cParty& other) {
|
||||
std::swap(total_dam_taken, other.total_dam_taken);
|
||||
std::swap(scen_name, other.scen_name);
|
||||
std::swap(adven, other.adven);
|
||||
std::swap(setup, other.setup);
|
||||
std::swap(stored_items, other.stored_items);
|
||||
std::swap(summons, other.summons);
|
||||
std::swap(scen_won, other.scen_won);
|
||||
@@ -173,9 +174,6 @@ void cParty::swap(cParty& other) {
|
||||
memcpy(stuff_done, other.stuff_done, sizeof(stuff_done));
|
||||
memcpy(other.stuff_done, temp_sdf, sizeof(stuff_done));
|
||||
unsigned short temp_setup[4][64][64];
|
||||
memcpy(temp_setup, setup, sizeof(setup));
|
||||
memcpy(setup, other.setup, sizeof(setup));
|
||||
memcpy(other.setup, temp_setup, sizeof(setup));
|
||||
for(size_t i = 0; i < adven.size(); i++) {
|
||||
std::swap(adven[i], other.adven[i]);
|
||||
}
|
||||
|
@@ -129,7 +129,7 @@ public:
|
||||
private:
|
||||
std::array<std::unique_ptr<cPlayer>,6> adven;
|
||||
public:
|
||||
unsigned short setup[4][64][64]; // formerly setup_save_type
|
||||
std::array<vector2d<unsigned short>, 4> setup; // formerly setup_save_type
|
||||
std::array<std::vector<cItem>,3> stored_items; // formerly stored_items_list_type
|
||||
|
||||
std::vector<cMonster> summons; // an array of monsters which can be summoned by the party's items yet don't originate from this scenario
|
||||
|
@@ -108,10 +108,8 @@ cTown& cCurTown::operator * (){
|
||||
|
||||
void cCurTown::place_preset_fields() {
|
||||
// Initialize barriers, etc. Note non-sfx gets forgotten if this is a town recently visited.
|
||||
for(int i = 0; i < 64; i++)
|
||||
for(int j = 0; j < 64; j++) {
|
||||
fields[i][j] = 0;
|
||||
}
|
||||
fields.resize(record()->max_dim, record()->max_dim);
|
||||
fields.fill(0);
|
||||
for(size_t i = 0; i < record()->preset_fields.size(); i++) {
|
||||
switch(record()->preset_fields[i].type){
|
||||
case OBJECT_BLOCK:
|
||||
@@ -176,6 +174,26 @@ void cCurTown::place_preset_fields() {
|
||||
}
|
||||
}
|
||||
|
||||
void cCurTown::update_fields(const vector2d<unsigned short>& setup) {
|
||||
for(short i = 0; i < record()->max_dim && i < setup.width(); i++) {
|
||||
for(short j = 0; j < record()->max_dim && j < setup.height(); j++) {
|
||||
// except that pushable things restore to orig locs
|
||||
unsigned short temp = setup[i][j] << 8;
|
||||
temp &= ~(OBJECT_CRATE | OBJECT_BARREL | OBJECT_BLOCK);
|
||||
univ.town.fields[i][j] |= temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cCurTown::save_setup(vector2d<unsigned short>& setup) const {
|
||||
setup.resize(record()->max_dim, record()->max_dim);
|
||||
for(short i = 0; i < record()->max_dim; i++) {
|
||||
for(short j = 0; j < record()->max_dim; j++) {
|
||||
setup[i][j] = fields[i][j] >> 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cSpeech& cCurTown::cur_talk() {
|
||||
// Make sure we actually have a valid speech stored
|
||||
return univ.scenario.towns[cur_talk_loaded]->talking;
|
||||
@@ -190,6 +208,8 @@ bool cCurTown::prep_talk(short which) {
|
||||
void cCurTown::prep_arena() {
|
||||
if(arena != nullptr) delete arena;
|
||||
arena = new cTown(univ.scenario, AREA_MEDIUM);
|
||||
fields.resize(AREA_MEDIUM, AREA_MEDIUM);
|
||||
fields.fill(0);
|
||||
}
|
||||
|
||||
cCurTown::~cCurTown() {
|
||||
@@ -201,6 +221,13 @@ cTown*const cCurTown::record() const {
|
||||
return univ.scenario.towns[univ.party.town_num];
|
||||
}
|
||||
|
||||
bool cCurTown::is_summon_safe(short x, short y) const {
|
||||
if(x > record()->max_dim || y > record()->max_dim) return false;
|
||||
// Here 254 indicates the low byte of the town fields, minus explored spaces (which is lowest bit).
|
||||
static const unsigned long blocking_fields = SPECIAL_SPOT | OBJECT_CRATE | OBJECT_BARREL | OBJECT_BLOCK | FIELD_QUICKFIRE | 254;
|
||||
return fields[x][y] & blocking_fields;
|
||||
}
|
||||
|
||||
bool cCurTown::is_explored(short x, short y) const{
|
||||
if(x > record()->max_dim || y > record()->max_dim) return false;
|
||||
return fields[x][y] & SPECIAL_EXPLORED;
|
||||
@@ -817,14 +844,7 @@ void cCurTown::writeTo(cTagFile& file) const {
|
||||
}
|
||||
}
|
||||
auto& fields_page = file.add();
|
||||
vector2d<as_hex<unsigned long>> fields_tmp;
|
||||
fields_tmp.resize(64, 64);
|
||||
for(size_t x = 0; x < 64; x++) {
|
||||
for(size_t y = 0; y < 64; y++) {
|
||||
fields_tmp[x][y] = fields[x][y];
|
||||
}
|
||||
}
|
||||
fields_page["FIELDS"].encode(fields_tmp);
|
||||
fields_page["FIELDS"].encode(fields);
|
||||
fields_page["TERRAIN"].encode(record()->terrain);
|
||||
// TODO: Do we need to save special_spot?
|
||||
}
|
||||
@@ -837,13 +857,11 @@ void cCurTown::readFrom(const cTagFile& file){
|
||||
monst.hostile = page.contains("HOSTILE");
|
||||
page["AT"] >> univ.party.town_loc.x >> univ.party.town_loc.y;
|
||||
} else if(page.getFirstKey() == "FIELDS" || page.getFirstKey() == "TERRAIN") {
|
||||
vector2d<as_hex<unsigned long>> fields_tmp;
|
||||
page["FIELDS"].extract(fields_tmp);
|
||||
page["FIELDS"].extract(fields);
|
||||
page["TERRAIN"].extract(record()->terrain);
|
||||
fields_tmp.resize(64, 64);
|
||||
for(size_t x = 0; x < 64; x++) {
|
||||
for(size_t y = 0; y < 64; y++) {
|
||||
fields[x][y] = fields_tmp[x][y];
|
||||
fields.resize(record()->max_dim, record()->max_dim);
|
||||
for(size_t x = 0; x < record()->max_dim; x++) {
|
||||
for(size_t y = 0; y < record()->max_dim; y++) {
|
||||
if(is_quickfire(x, y)) {
|
||||
quickfire_present = true;
|
||||
}
|
||||
@@ -876,9 +894,6 @@ void cCurTown::readFrom(const cTagFile& file){
|
||||
cCurTown::cCurTown(cUniverse& univ) : univ(univ) {
|
||||
arena = nullptr;
|
||||
univ.party.town_num = 200;
|
||||
for(int i = 0; i < 64; i++)
|
||||
for(int j = 0; j < 64; j++)
|
||||
fields[i][j] = 0L;
|
||||
}
|
||||
|
||||
cCurOut::cCurOut(cUniverse& univ) : univ(univ) {}
|
||||
@@ -973,7 +988,7 @@ void cCurTown::copy(const cCurTown& other) {
|
||||
difficulty = other.difficulty;
|
||||
monst = other.monst;
|
||||
items = other.items;
|
||||
memcpy(fields, other.fields, sizeof(fields));
|
||||
fields = other.fields;
|
||||
}
|
||||
|
||||
void cCurTown::swap(cCurTown& other) {
|
||||
@@ -983,10 +998,7 @@ void cCurTown::swap(cCurTown& other) {
|
||||
std::swap(difficulty, other.difficulty);
|
||||
monst.swap(other.monst);
|
||||
std::swap(items, other.items);
|
||||
unsigned long temp[64][64];
|
||||
memcpy(temp, other.fields, sizeof(fields));
|
||||
memcpy(other.fields, fields, sizeof(fields));
|
||||
memcpy(fields, temp, sizeof(fields));
|
||||
fields.swap(other.fields);
|
||||
}
|
||||
|
||||
void cUniverse::check_monst(cMonster& monst) {
|
||||
|
@@ -39,6 +39,7 @@ class cCurTown {
|
||||
cUniverse& univ;
|
||||
cTown* arena;
|
||||
cTown*const record() const;
|
||||
vector2d<unsigned long> fields;
|
||||
public:
|
||||
bool quickfire_present = false, belt_present = false;
|
||||
// formerly current_town_type
|
||||
@@ -47,8 +48,6 @@ public:
|
||||
|
||||
std::vector<cItem> items; // formerly town_item_list type
|
||||
|
||||
unsigned long fields[64][64];
|
||||
|
||||
void import_legacy(legacy::current_town_type& old);
|
||||
void import_legacy(legacy::town_item_list& old);
|
||||
void import_legacy(unsigned char(& old_sfx)[64][64], unsigned char(& old_misc_i)[64][64]);
|
||||
@@ -62,6 +61,9 @@ public:
|
||||
bool prep_talk(short which); // Prepare for loading specified speech, returning true if already loaded
|
||||
void prep_arena(); // Set up for a combat arena
|
||||
void place_preset_fields();
|
||||
void update_fields(const vector2d<unsigned short>& setup);
|
||||
void save_setup(vector2d<unsigned short>& setup) const;
|
||||
bool is_summon_safe(short x, short y) const;
|
||||
|
||||
bool is_explored(short x, short y) const;
|
||||
bool is_force_wall(short x, short y) const;
|
||||
|
Reference in New Issue
Block a user