/* * universe.cpp * BoE * * Created by Celtic Minstrel on 24/04/09. * */ #include "universe.hpp" #include #include #include #include #include #include "town.hpp" #include "oldstructs.hpp" #include "mathutil.hpp" #include "fileio.hpp" #include "gfxsheets.hpp" void cCurOut::import_legacy(legacy::out_info_type& old){ for(int i = 0; i < 96; i++) for(int j = 0; j < 96; j++) expl[i][j] = old.expl[i][j]; } void cCurTown::import_legacy(legacy::current_town_type& old){ univ.party.town_num = old.town_num; difficulty = old.difficulty; record()->import_legacy(old.town); for(int i = 0; i < 64; i++) for(int j = 0; j < 64; j++) fields[i][j] = old.explored[i][j]; monst.import_legacy(old.monst); univ.party.town_loc.x = old.p_loc.x; univ.party.town_loc.y = old.p_loc.y; cur_talk_loaded = old.town_num; } void cCurTown::import_legacy(legacy::big_tr_type& old){ for(short i = 0; i < record()->max_dim; i++) for(short j = 0; j < record()->max_dim; j++) record()->terrain(i,j) = old.terrain[i][j]; record()->area_desc.resize(16); for(short i = 0; i < 16; i++){ record()->area_desc[i].top = old.room_rect[i].top; record()->area_desc[i].left = old.room_rect[i].left; record()->area_desc[i].bottom = old.room_rect[i].bottom; record()->area_desc[i].right = old.room_rect[i].right; } record()->creatures.resize(60); for(short i = 0; i < 60; i++) record()->creatures[i].import_legacy(old.creatures[i]); for(short i = 0; i < record()->max_dim; i++) for(short j = 0; j < record()->max_dim; j++) { record()->lighting[i][j] = old.lighting[i / 8][j] & (1 << (i % 8)); } } void cCurTown::import_legacy(legacy::town_item_list& old){ items.resize(115); for(int i = 0; i < 115; i++) items[i].import_legacy(old.items[i]); } void cUniverse::import_legacy(legacy::stored_town_maps_type& old){ for(int n = 0; n < scenario.towns.size(); n++) for(int i = 0; i < 64; i++) for(int j = 0; j < 64; j++) scenario.towns[n]->maps[j][i] = old.town_maps[n][i / 8][j] & (1 << (i % 8)); } static short onm(char x_sector,char y_sector, char w) { return y_sector * w + x_sector; } void cUniverse::import_legacy(legacy::stored_outdoor_maps_type& old){ for(int x = 0; x < scenario.outdoors.width(); x++) for(int y = 0; y < scenario.outdoors.height(); y++) for(int i = 0; i < 48; i++) for(int j = 0; j < 48; j++) scenario.outdoors[x][y]->maps[i][j] = old.outdoor_maps[onm(x,y,scenario.outdoors.width())][i / 8][j] & (1 << i % 8); } void cCurTown::import_legacy(unsigned char(& old_sfx)[64][64], unsigned char(& old_misc_i)[64][64]){ for(int i = 0; i < 64; i++) for(int j = 0; j < 64; j++){ unsigned long tmp_sfx, tmp_misc_i; tmp_sfx = old_sfx[i][j]; tmp_misc_i = old_misc_i[i][j]; tmp_sfx <<= 16; tmp_misc_i <<= 8; fields[i][j] |= tmp_sfx; fields[i][j] |= tmp_misc_i; } } cTown* cCurTown::operator -> (){ return record(); } cTown& cCurTown::operator * (){ return *record(); } 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; } for(size_t i = 0; i < record()->preset_fields.size(); i++) { switch(record()->preset_fields[i].type){ case OBJECT_BLOCK: set_block(record()->preset_fields[i].loc.x,record()->preset_fields[i].loc.y,true); break; case SPECIAL_SPOT: set_spot(record()->preset_fields[i].loc.x,record()->preset_fields[i].loc.y,true); break; case SPECIAL_ROAD: set_road(record()->preset_fields[i].loc.x,record()->preset_fields[i].loc.y,true); break; case FIELD_WEB: set_web(record()->preset_fields[i].loc.x,record()->preset_fields[i].loc.y,true); break; case OBJECT_CRATE: set_crate(record()->preset_fields[i].loc.x,record()->preset_fields[i].loc.y,true); break; case OBJECT_BARREL: set_barrel(record()->preset_fields[i].loc.x,record()->preset_fields[i].loc.y,true); break; case BARRIER_FIRE: set_fire_barr(record()->preset_fields[i].loc.x,record()->preset_fields[i].loc.y,true); break; case BARRIER_FORCE: set_force_barr(record()->preset_fields[i].loc.x,record()->preset_fields[i].loc.y,true); break; case BARRIER_CAGE: set_force_cage(record()->preset_fields[i].loc.x,record()->preset_fields[i].loc.y,true); break; case FIELD_QUICKFIRE: set_quickfire(record()->preset_fields[i].loc.x,record()->preset_fields[i].loc.y,true); break; case SFX_SMALL_BLOOD: set_sm_blood(record()->preset_fields[i].loc.x,record()->preset_fields[i].loc.y,true); break; case SFX_MEDIUM_BLOOD: set_med_blood(record()->preset_fields[i].loc.x,record()->preset_fields[i].loc.y,true); break; case SFX_LARGE_BLOOD: set_lg_blood(record()->preset_fields[i].loc.x,record()->preset_fields[i].loc.y,true); break; case SFX_SMALL_SLIME: set_sm_slime(record()->preset_fields[i].loc.x,record()->preset_fields[i].loc.y,true); break; case SFX_LARGE_SLIME: set_lg_slime(record()->preset_fields[i].loc.x,record()->preset_fields[i].loc.y,true); break; case SFX_ASH: set_ash(record()->preset_fields[i].loc.x,record()->preset_fields[i].loc.y,true); break; case SFX_BONES: set_bones(record()->preset_fields[i].loc.x,record()->preset_fields[i].loc.y,true); break; case SFX_RUBBLE: set_rubble(record()->preset_fields[i].loc.x,record()->preset_fields[i].loc.y,true); break; // The rest can't be preset, but enumerate them in case a new field is added. case FIELD_DISPEL: case FIELD_SMASH: case SPECIAL_EXPLORED: case CLOUD_SLEEP: case CLOUD_STINK: case FIELD_ANTIMAGIC: case WALL_BLADES: case WALL_FIRE: case WALL_FORCE: case WALL_ICE: break; } } } cSpeech& cCurTown::cur_talk() { // Make sure we actually have a valid speech stored return univ.scenario.towns[cur_talk_loaded]->talking; } bool cCurTown::prep_talk(short which) { if(which == cur_talk_loaded) return true; cur_talk_loaded = which; return true; } void cCurTown::prep_arena() { if(arena != nullptr) delete arena; arena = new cTown(univ.scenario, AREA_MEDIUM); } cCurTown::~cCurTown() { if(arena != nullptr) delete arena; } cTown*const cCurTown::record() const { if(univ.party.town_num == 200) return arena; return univ.scenario.towns[univ.party.town_num]; } 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; } bool cCurTown::is_force_wall(short x, short y) const{ if(x > record()->max_dim || y > record()->max_dim) return false; return fields[x][y] & WALL_FORCE; } bool cCurTown::is_fire_wall(short x, short y) const{ if(x > record()->max_dim || y > record()->max_dim) return false; return fields[x][y] & WALL_FIRE; } bool cCurTown::is_antimagic(short x, short y) const{ if(x > record()->max_dim || y > record()->max_dim) return false; return fields[x][y] & FIELD_ANTIMAGIC; } bool cCurTown::is_scloud(short x, short y) const{ // stinking cloud if(x > record()->max_dim || y > record()->max_dim) return false; return fields[x][y] & CLOUD_STINK; } bool cCurTown::is_ice_wall(short x, short y) const{ if(x > record()->max_dim || y > record()->max_dim) return false; return fields[x][y] & WALL_ICE; } bool cCurTown::is_blade_wall(short x, short y) const{ if(x > record()->max_dim || y > record()->max_dim) return false; return fields[x][y] & WALL_BLADES; } bool cCurTown::is_sleep_cloud(short x, short y) const{ if(x > record()->max_dim || y > record()->max_dim) return false; return fields[x][y] & CLOUD_SLEEP; } bool cCurTown::is_block(short x, short y) const{ // currently unused if(x > record()->max_dim || y > record()->max_dim) return false; return fields[x][y] & OBJECT_BLOCK; } bool cCurTown::is_spot(short x, short y) const{ return fields[x][y] & SPECIAL_SPOT; } bool cCurTown::is_road(short x, short y) const{ return fields[x][y] & SPECIAL_ROAD; } bool cCurTown::is_special(short x, short y) const{ if(x > record()->max_dim || y > record()->max_dim) return false; location check(x,y); for(int i = 0; i < record()->special_locs.size(); i++) if(check == record()->special_locs[i] && record()->special_locs[i].spec >= 0) return true; return false; } bool cCurTown::is_web(short x, short y) const{ if(x > record()->max_dim || y > record()->max_dim) return false; return fields[x][y] & FIELD_WEB; } bool cCurTown::is_crate(short x, short y) const{ if(x > record()->max_dim || y > record()->max_dim) return false; return fields[x][y] & OBJECT_CRATE; } bool cCurTown::is_barrel(short x, short y) const{ if(x > record()->max_dim || y > record()->max_dim) return false; return fields[x][y] & OBJECT_BARREL; } bool cCurTown::is_fire_barr(short x, short y) const{ if(x > record()->max_dim || y > record()->max_dim) return false; return fields[x][y] & BARRIER_FIRE; } bool cCurTown::is_force_barr(short x, short y) const{ if(x > record()->max_dim || y > record()->max_dim) return false; return fields[x][y] & BARRIER_FORCE; } bool cCurTown::is_quickfire(short x, short y) const{ if(x > record()->max_dim || y > record()->max_dim) return false; return fields[x][y] & FIELD_QUICKFIRE; } bool cCurTown::is_sm_blood(short x, short y) const{ if(x > record()->max_dim || y > record()->max_dim) return false; return fields[x][y] & SFX_SMALL_BLOOD; } bool cCurTown::is_med_blood(short x, short y) const{ if(x > record()->max_dim || y > record()->max_dim) return false; return fields[x][y] & SFX_MEDIUM_BLOOD; } bool cCurTown::is_lg_blood(short x, short y) const{ if(x > record()->max_dim || y > record()->max_dim) return false; return fields[x][y] & SFX_LARGE_BLOOD; } bool cCurTown::is_sm_slime(short x, short y) const{ if(x > record()->max_dim || y > record()->max_dim) return false; return fields[x][y] & SFX_SMALL_SLIME; } bool cCurTown::is_lg_slime(short x, short y) const{ if(x > record()->max_dim || y > record()->max_dim) return false; return fields[x][y] & SFX_LARGE_SLIME; } bool cCurTown::is_ash(short x, short y) const{ if(x > record()->max_dim || y > record()->max_dim) return false; return fields[x][y] & SFX_ASH; } bool cCurTown::is_bones(short x, short y) const{ if(x > record()->max_dim || y > record()->max_dim) return false; return fields[x][y] & SFX_BONES; } bool cCurTown::is_rubble(short x, short y) const{ if(x > record()->max_dim || y > record()->max_dim) return false; return fields[x][y] & SFX_RUBBLE; } bool cCurTown::is_force_cage(short x, short y) const{ if(x > record()->max_dim || y > record()->max_dim) return false; return fields[x][y] & BARRIER_CAGE; } bool cCurTown::set_explored(short x, short y, bool b){ if(x > record()->max_dim || y > record()->max_dim) return false; if(b) fields[x][y] |= SPECIAL_EXPLORED; else fields[x][y] &= ~SPECIAL_EXPLORED; return true; } bool cCurTown::set_force_wall(short x, short y, bool b){ if(x > record()->max_dim || y > record()->max_dim) return false; if(b){ // If certain things are on space, there's no room for field. if(is_impassable(x,y)) return false; if(is_antimagic(x,y) || is_blade_wall(x,y) || is_quickfire(x,y)) return false; if(is_crate(x,y) || is_barrel(x,y) || is_fire_barr(x,y) || is_force_barr(x,y)) return false; set_web(x,y,false); set_fire_wall(x,y,false); fields[x][y] |= WALL_FORCE; } else fields[x][y] &= ~WALL_FORCE; return true; } bool cCurTown::set_fire_wall(short x, short y, bool b){ if(x > record()->max_dim || y > record()->max_dim) return false; if(b){ // If certain things are on space, there's no room for field. if(is_impassable(x,y)) return false; if(is_antimagic(x,y) || is_blade_wall(x,y) || is_quickfire(x,y) || is_ice_wall(x,y)) return false; if(is_crate(x,y) || is_barrel(x,y) || is_fire_barr(x,y) || is_force_barr(x,y)) return false; if(is_web(x,y) || is_scloud(x,y) || is_sleep_cloud(x,y)) return false; set_web(x,y,false); set_fire_wall(x,y,false); fields[x][y] |= WALL_FIRE; } else fields[x][y] &= ~WALL_FIRE; return true; } bool cCurTown::set_antimagic(short x, short y, bool b){ if(x > record()->max_dim || y > record()->max_dim) return false; if(b){ // If certain things are on space, there's no room for a field. if(is_impassable(x,y)) return false; if(is_quickfire(x,y) || is_force_wall(x,y) || is_fire_wall(x,y)) return false; set_force_wall(x,y,false); set_fire_wall(x,y,false); set_antimagic(x,y,false); set_scloud(x,y,false); set_ice_wall(x,y,false); set_blade_wall(x,y,false); set_sleep_cloud(x,y,false); fields[x][y] |= FIELD_ANTIMAGIC; } else fields[x][y] &= ~FIELD_ANTIMAGIC; return true; } bool cCurTown::set_scloud(short x, short y, bool b){ // stinking cloud if(x > record()->max_dim || y > record()->max_dim) return false; if(b){ // If certain things are on space, there's no room for cloud. if(is_impassable(x,y)) return false; if(is_force_wall(x,y) || is_fire_wall(x,y) || is_ice_wall(x,y) || is_blade_wall(x,y)) return false; if(is_antimagic(x,y) || is_sleep_cloud(x,y) || is_quickfire(x,y)) return false; if(is_fire_barr(x,y) || is_force_barr(x,y)) return false; fields[x][y] |= CLOUD_STINK; } else fields[x][y] &= ~CLOUD_STINK; return true; } bool cCurTown::set_ice_wall(short x, short y, bool b){ if(x > record()->max_dim || y > record()->max_dim) return false; if(b){ // If certain things are on space, ther's no room for a field. if(is_impassable(x,y)) return false; if(is_force_wall(x,y) || is_blade_wall(x,y) || is_antimagic(x,y)) return false; if(is_web(x,y) || is_crate(x,y) || is_barrel(x,y)) return false; if(is_fire_barr(x,y) || is_force_barr(x,y) || is_quickfire(x,y)) return false; set_fire_wall(x,y,false); set_scloud(x,y,false); fields[x][y] |= WALL_ICE; } else fields[x][y] &= ~WALL_ICE; return true; } bool cCurTown::set_blade_wall(short x, short y, bool b){ if(x > record()->max_dim || y > record()->max_dim) return false; if(b){ // if certain things are on space, there's no room for a field. if(is_impassable(x,y)) return false; if(is_fire_barr(x,y) || is_force_barr(x,y) || is_quickfire(x,y) || is_antimagic(x,y)) return false; set_force_wall(x,y,false); set_fire_wall(x,y,false); fields[x][y] |= WALL_BLADES; } else fields[x][y] &= ~WALL_BLADES; return true; } bool cCurTown::set_sleep_cloud(short x, short y, bool b){ if(x > record()->max_dim || y > record()->max_dim) return false; if(b){ // if certain things are on space, there's no room for cloud. if(is_impassable(x,y)) return false; if(is_fire_barr(x,y) || is_force_barr(x,y) || is_quickfire(x,y) || is_antimagic(x,y)) return false; set_force_wall(x,y,false); set_fire_wall(x,y,false); fields[x][y] |= CLOUD_SLEEP; } else fields[x][y] &= ~CLOUD_SLEEP; return true; } bool cCurTown::set_block(short x, short y, bool b){ // currently unused if(x > record()->max_dim || y > record()->max_dim) return false; if(b) fields[x][y] |= OBJECT_BLOCK; else fields[x][y] &= ~OBJECT_BLOCK; return true; } bool cCurTown::set_spot(short x, short y, bool b){ if(x > record()->max_dim || y > record()->max_dim) return false; if(b) fields[x][y] |= SPECIAL_SPOT; else fields[x][y] &= ~SPECIAL_SPOT; return true; } bool cCurTown::set_road(short x, short y, bool b){ if(x > record()->max_dim || y > record()->max_dim) return false; if(b) fields[x][y] |= SPECIAL_ROAD; else fields[x][y] &= ~SPECIAL_ROAD; return true; } bool cCurTown::set_web(short x, short y, bool b){ if(x > record()->max_dim || y > record()->max_dim) return false; if(b){ // If certain things are on the space, there's no room for webs if(is_impassable(x,y)) return false; if(is_fire_barr(x,y) || is_force_barr(x,y) || is_quickfire(x,y)) return false; if(is_force_wall(x,y) || is_fire_wall(x,y) || is_antimagic(x, y)) return false; if(is_ice_wall(x, y) || is_blade_wall(x,y) || is_sleep_cloud(x,y)) return false; fields[x][y] |= FIELD_WEB; } else fields[x][y] &= ~FIELD_WEB; return true; } bool cCurTown::set_crate(short x, short y, bool b){ if(x > record()->max_dim || y > record()->max_dim) return false; if(b){ // If certain things are on the space, there's no room for a crate. if(is_fire_barr(x,y) || is_force_barr(x,y) || is_quickfire(x,y) || is_barrel(x,y)) return false; fields[x][y] |= OBJECT_CRATE; } else fields[x][y] &= ~OBJECT_CRATE; return true; } bool cCurTown::set_barrel(short x, short y, bool b){ if(x > record()->max_dim || y > record()->max_dim) return false; if(b){ // If certain things are on the space, there's no room for a crate. if(is_fire_barr(x,y) || is_force_barr(x,y) || is_quickfire(x,y) || is_crate(x,y)) return false; fields[x][y] |= OBJECT_BARREL; } else fields[x][y] &= ~OBJECT_BARREL; return true; } bool cCurTown::set_fire_barr(short x, short y, bool b){ if(x > record()->max_dim || y > record()->max_dim) return false; if(b){ // If certain things are on the space, there's no room for a barrier. if(is_barrel(x,y) || is_force_barr(x,y) || is_quickfire(x,y) || is_crate(x,y)) return false; if(is_antimagic(x,y) && get_ran(1,0,3) < 3) return false; // Cancel out fields set_web(x,y,false); set_force_wall(x,y,false); set_fire_wall(x,y,false); set_antimagic(x,y,false); set_scloud(x,y,false); set_ice_wall(x,y,false); set_blade_wall(x,y,false); set_sleep_cloud(x,y,false); fields[x][y] |= BARRIER_FIRE; } else fields[x][y] &= ~BARRIER_FIRE; return true; } bool cCurTown::set_force_barr(short x, short y, bool b){ if(x > record()->max_dim || y > record()->max_dim) return false; if(b){ // If certain things are on the space, there's no room for a barrier. if(is_fire_barr(x,y) || is_barrel(x,y) || is_quickfire(x,y) || is_crate(x,y)) return false; if(is_antimagic(x,y) && get_ran(1,0,2) < 2) return false; // Cancel out fields set_web(x,y,false); set_force_wall(x,y,false); set_fire_wall(x,y,false); set_antimagic(x,y,false); set_scloud(x,y,false); set_ice_wall(x,y,false); set_blade_wall(x,y,false); set_sleep_cloud(x,y,false); fields[x][y] |= BARRIER_FORCE; } else fields[x][y] &= ~BARRIER_FORCE; return true; } bool cCurTown::set_quickfire(short x, short y, bool b){ if(x > record()->max_dim || y > record()->max_dim) return false; if(b){ // If certain things are on space, there's no room for quickfire. ter_num_t ter = record()->terrain(x,y); if(univ.scenario.ter_types[ter].blockage == eTerObstruct::BLOCK_SIGHT) return false; // TODO: Isn't it a little odd that BLOCK_MOVE_AND_SHOOT isn't included here? if(univ.scenario.ter_types[ter].blockage == eTerObstruct::BLOCK_MOVE_AND_SIGHT) return false; if(is_antimagic(x,y) && get_ran(1,0,1) == 0) return false; if(is_force_barr(x,y) || is_fire_barr(x,y)) return false; quickfire_present = true; set_force_wall(x,y,false); set_fire_wall(x,y,false); set_antimagic(x,y,false); set_scloud(x,y,false); set_ice_wall(x,y,false); set_blade_wall(x,y,false); set_sleep_cloud(x,y,false); set_web(x,y,false); set_crate(x,y,false); set_barrel(x,y,false); set_force_barr(x,y,false); set_fire_barr(x,y,false); fields[x][y] |= FIELD_QUICKFIRE; } else fields[x][y] &= ~FIELD_QUICKFIRE; return true; } bool cCurTown::free_for_sfx(short x, short y) { ter_num_t ter; ter = record()->terrain(x,y); if(univ.scenario.ter_types[ter].blockage != eTerObstruct::CLEAR) return false; return true; } bool cCurTown::set_sm_blood(short x, short y, bool b){ if(x > record()->max_dim || y > record()->max_dim) return false; if(b){ if(!free_for_sfx(x,y)) return false; if(is_med_blood(x,y) || is_lg_blood(x,y)) return false; set_sm_slime(x,y,false); set_lg_slime(x,y,false); set_ash(x,y,false); set_bones(x,y,false); set_rubble(x,y,false); fields[x][y] |= SFX_SMALL_BLOOD; } else fields[x][y] &= ~SFX_SMALL_BLOOD; return true; } bool cCurTown::set_med_blood(short x, short y, bool b){ if(x > record()->max_dim || y > record()->max_dim) return false; if(b){ if(!free_for_sfx(x,y)) return false; if(is_lg_blood(x,y)) return false; set_sm_blood(x,y,false); set_sm_slime(x,y,false); set_lg_slime(x,y,false); set_ash(x,y,false); set_bones(x,y,false); set_rubble(x,y,false); fields[x][y] |= SFX_MEDIUM_BLOOD; } else fields[x][y] &= ~SFX_MEDIUM_BLOOD; return true; } bool cCurTown::set_lg_blood(short x, short y, bool b){ if(x > record()->max_dim || y > record()->max_dim) return false; if(b){ if(!free_for_sfx(x,y)) return false; set_sm_blood(x,y,false); set_med_blood(x,y,false); set_sm_slime(x,y,false); set_lg_slime(x,y,false); set_ash(x,y,false); set_bones(x,y,false); set_rubble(x,y,false); fields[x][y] |= SFX_LARGE_BLOOD; } else fields[x][y] &= ~SFX_LARGE_BLOOD; return true; } bool cCurTown::set_sm_slime(short x, short y, bool b){ if(b){ if(!free_for_sfx(x,y)) return false; if(is_lg_slime(x,y)) return false; set_sm_blood(x,y,false); set_med_blood(x,y,false); set_lg_blood(x,y,false); set_ash(x,y,false); set_bones(x,y,false); set_rubble(x,y,false); fields[x][y] |= SFX_SMALL_SLIME; } else fields[x][y] &= ~SFX_SMALL_SLIME; return true; } bool cCurTown::set_lg_slime(short x, short y, bool b){ if(x > record()->max_dim || y > record()->max_dim) return false; if(b){ if(!free_for_sfx(x,y)) return false; set_sm_blood(x,y,false); set_med_blood(x,y,false); set_lg_blood(x,y,false); set_sm_slime(x,y,false); set_ash(x,y,false); set_bones(x,y,false); set_rubble(x,y,false); fields[x][y] |= SFX_LARGE_SLIME; } else fields[x][y] &= ~SFX_LARGE_SLIME; return true; } bool cCurTown::set_ash(short x, short y, bool b){ if(x > record()->max_dim || y > record()->max_dim) return false; if(b){ if(!free_for_sfx(x,y)) return false; set_sm_blood(x,y,false); set_med_blood(x,y,false); set_lg_blood(x,y,false); set_sm_slime(x,y,false); set_lg_slime(x,y,false); set_bones(x,y,false); set_rubble(x,y,false); fields[x][y] |= SFX_ASH; } else fields[x][y] &= ~SFX_ASH; return true; } bool cCurTown::set_bones(short x, short y, bool b){ if(x > record()->max_dim || y > record()->max_dim) return false; if(b){ if(!free_for_sfx(x,y)) return false; set_sm_blood(x,y,false); set_med_blood(x,y,false); set_lg_blood(x,y,false); set_sm_slime(x,y,false); set_lg_slime(x,y,false); set_ash(x,y,false); set_rubble(x,y,false); fields[x][y] |= SFX_BONES; } else fields[x][y] &= ~SFX_BONES; return true; } bool cCurTown::set_rubble(short x, short y, bool b){ if(x > record()->max_dim || y > record()->max_dim) return false; if(b){ if(!free_for_sfx(x,y)) return false; set_sm_blood(x,y,false); set_med_blood(x,y,false); set_lg_blood(x,y,false); set_sm_slime(x,y,false); set_lg_slime(x,y,false); set_ash(x,y,false); set_bones(x,y,false); fields[x][y] |= SFX_RUBBLE; } else fields[x][y] &= ~SFX_RUBBLE; return true; } bool cCurTown::set_force_cage(short x, short y, bool b){ // TODO: Consider whether placing a forcecage should erase anything already present, or fail due to something already present // TODO: Also consider checking for forcecage in some of the other placement functions. if(x > record()->max_dim || y > record()->max_dim) return false; if(b) fields[x][y] |= BARRIER_CAGE; else fields[x][y] &= ~BARRIER_CAGE; return true; } // TODO: This seems to be wrong; impassable implies "blocks movement", which two other blockages also do bool cCurTown::is_impassable(short i,short j) { ter_num_t ter; ter = record()->terrain(i,j); if(univ.scenario.ter_types[ter].blockage == eTerObstruct::BLOCK_MOVE_AND_SIGHT) return true; else return false; } ter_num_t(& cCurOut::operator [] (size_t i))[96]{ return out[i]; } ter_num_t& cCurOut::operator [] (location loc) { return out[loc.x][loc.y]; } void cCurOut::writeTo(std::ostream& file) const { writeArray(file, expl, 96, 96); writeArray(file, out, 96, 96); writeArray(file, out_e, 96, 96); // file << "OUTDOORS 0 0" << std::endl; // outdoors[0][0].writeTo(file); // file << "OUTDOORS 0 1" << std::endl; // outdoors[0][1].writeTo(file); // file << "OUTDOORS 1 0" << std::endl; // outdoors[1][0].writeTo(file); // file << "OUTDOORS 1 1" << std::endl; // outdoors[1][1].writeTo(file); // file << std::endl; } void cCurOut::readFrom(std::istream& file) { readArray(file, expl, 96, 96); readArray(file, out, 96, 96); readArray(file, out_e, 96, 96); } void cCurTown::writeTo(std::ostream& file) const { file << "TOWN " << univ.party.town_num << '\n'; file << "DIFFICULTY " << difficulty << '\n'; if(monst.hostile) file << "HOSTILE" << '\n'; file << "AT " << univ.party.town_loc.x << ' ' << univ.party.town_loc.y << '\n'; file << '\f'; for(size_t i = 0; i < items.size(); i++) if(items[i].variety != eItemType::NO_ITEM){ file << "ITEM " << i << '\n'; items[i].writeTo(file); file << '\f'; } file << '\f'; for(int i = 0; i < monst.size(); i++) { if(monst[i].active > 0) { file << "CREATURE " << i << '\n'; monst[i].writeTo(file); file << '\f'; } } file << '\f'; file << "FIELDS\n"; file << std::hex; writeArray(file, fields, record()->max_dim, record()->max_dim); file << std::dec; file << "TERRAIN\n"; record()->writeTerrainTo(file); // TODO: Do we need to save special_spot? } void cCurTown::readFrom(std::istream& file){ std::istringstream bin, sin; std::string cur; getline(file, cur, '\f'); bin.str(cur); while(bin){ getline(bin, cur); sin.str(cur); sin >> cur; if(cur == "TOWN") sin >> univ.party.town_num; else if(cur == "DIFFICULTY") sin >> difficulty; else if(cur == "HOSTILE") monst.hostile = true; else if(cur == "AT") sin >> univ.party.town_loc.x >> univ.party.town_loc.y; sin.clear(); } bin.clear(); while(file) { getline(file, cur, '\f'); bin.str(cur); bin >> cur; if(cur == "FIELDS") { int num = univ.party.town_num; bin >> std::hex; readArray(bin, fields, univ.scenario.towns[num]->max_dim, univ.scenario.towns[num]->max_dim); bin >> std::dec; } else if(cur == "ITEM") { int i; bin >> i; if(i >= items.size()) items.resize(i + 1); items[i].readFrom(bin); } else if(cur == "CREATURE") { int i; bin >> i; monst.readFrom(bin, i); monst[i].active = true; } else if(cur == "TERRAIN") univ.scenario.towns[univ.party.town_num]->readTerrainFrom(bin); bin.clear(); } } 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) {} cOutdoors* cCurOut::operator->() { short x = univ.party.outdoor_corner.x + univ.party.i_w_c.x, y = univ.party.outdoor_corner.y + univ.party.i_w_c.y; return univ.scenario.outdoors[x][y]; } bool cCurOut::is_spot(int x, int y) { int sector_x = 0, sector_y = 0; if(x >= 48) sector_x++, x -= 48; if(y >= 48) sector_y++, y -= 48; sector_x += univ.party.outdoor_corner.x; sector_y += univ.party.outdoor_corner.y; return univ.scenario.outdoors[sector_x][sector_y]->special_spot[x][y]; } bool cCurOut::is_road(int x, int y) { int sector_x = 0, sector_y = 0; if(x >= 48) sector_x++, x -= 48; if(y >= 48) sector_y++, y -= 48; sector_x += univ.party.outdoor_corner.x; sector_y += univ.party.outdoor_corner.y; return univ.scenario.outdoors[sector_x][sector_y]->roads[x][y]; } cUniverse::cUniverse(long party_type) : party(party_type), out(*this), town(*this) {} cUniverse::cUniverse(const cUniverse& other) : strbuf(other.strbuf) , extrabufs(other.extrabufs) , cur_pc(other.cur_pc) , scenario(other.scenario) , party(other.party) , stored_pcs(other.stored_pcs) , town(*this) , out(*this) , file(other.file) , debug_mode(other.debug_mode) , ghost_mode(other.ghost_mode) , node_step_through(other.node_step_through) { for(auto& p : stored_pcs) { p.second = new cPlayer(*p.second); } town.copy(other.town); out.copy(other.out); } cUniverse::cUniverse(cUniverse&& other) : town(*this), out(*this) { swap(other); } cUniverse& cUniverse::operator=(cUniverse other) { swap(other); return *this; } void cUniverse::swap(cUniverse& other) { party.swap(other.party); town.swap(other.town); out.swap(other.out); scenario.swap(other.scenario); std::swap(stored_pcs, other.stored_pcs); std::swap(file, other.file); std::swap(debug_mode, other.debug_mode); std::swap(ghost_mode, other.ghost_mode); std::swap(node_step_through, other.node_step_through); std::swap(cur_pc, other.cur_pc); std::swap(strbuf, other.strbuf); std::swap(extrabufs, other.extrabufs); } void cCurOut::copy(const cCurOut& other) { memcpy(expl, other.expl, sizeof(expl)); memcpy(out, other.out, sizeof(out)); memcpy(out_e, other.out_e, sizeof(out_e)); } void cCurOut::swap(cCurOut& other) { cCurOut temp(univ); temp.copy(other); other.copy(*this); copy(temp); } void cCurTown::copy(const cCurTown& other) { cur_talk_loaded = other.cur_talk_loaded; quickfire_present = other.quickfire_present; belt_present = other.belt_present; difficulty = other.difficulty; monst = other.monst; items = other.items; memcpy(fields, other.fields, sizeof(fields)); } void cCurTown::swap(cCurTown& other) { std::swap(cur_talk_loaded, other.cur_talk_loaded); std::swap(quickfire_present, other.quickfire_present); std::swap(belt_present, other.belt_present); 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)); } void cUniverse::check_monst(cMonster& monst) { if(monst.see_spec == -2) return; // Avoid infinite recursion monst.see_spec = -2; if(monst.picture_num >= 10000) { int pic = monst.picture_num - 10000; int sz = pic / 1000, base = pic % 1000; int numGraph = 4; if(sz > 1) numGraph *= 2; if(sz == 4) numGraph *= 2; for(int i = 0; i < numGraph; i++) used_graphics.insert(base + i); } else if(monst.picture_num >= 1000) { update_monsters[monst.picture_num - 1000].insert(&monst); } for(auto& abil : monst.abil) { switch(getMonstAbilCategory(abil.first)) { case eMonstAbilCat::MISSILE: if(abil.second.missile.pic >= 10000) { for(int i = 0; i < 4; i++) used_graphics.insert(abil.second.missile.pic - 10000 + i); } else if(abil.second.missile.pic >= 1000) { update_missiles[abil.second.missile.pic - 1000].insert(&abil.second.missile.pic); } break; case eMonstAbilCat::GENERAL: if(abil.second.gen.pic >= 10000) { for(int i = 0; i < 4; i++) used_graphics.insert(abil.second.gen.pic - 10000 + i); } else if(abil.second.gen.pic >= 1000) { update_missiles[abil.second.gen.pic - 1000].insert(&abil.second.gen.pic); } break; case eMonstAbilCat::SUMMON: if(abil.second.summon.type == eMonstSummon::TYPE) check_monst(scenario.scen_monsters[abil.second.summon.what]); break; case eMonstAbilCat::RADIATE: case eMonstAbilCat::SPECIAL: case eMonstAbilCat::INVALID: break; } } } void cUniverse::check_item(cItem& item) { if(item.variety == eItemType::NO_ITEM) return; if(item.graphic_num >= 10000) used_graphics.insert(item.graphic_num - 10000); else if(item.graphic_num >= 1000) update_items[item.graphic_num - 1000].insert(&item); if(item.ability == eItemAbil::SUMMONING || item.ability == eItemAbil::MASS_SUMMONING) { mon_num_t monst = item.abil_data[1]; if(monst >= 10000) check_monst(party.summons[monst - 10000]); else check_monst(scenario.scen_monsters[monst]); } if(item.variety == eItemType::ARROW || item.variety == eItemType::BOLTS || item.variety == eItemType::MISSILE_NO_AMMO || item.variety == eItemType::THROWN_MISSILE) { if(item.missile >= 10000) for(int i = 0; i < 4; i++) used_graphics.insert(item.missile - 10000 + i); else if(item.missile >= 1000) update_missiles[item.missile - 1000].insert(&item.missile); } } // This attempts to find the index of a living entity in the party or town // Assuming success, the two get_target() calls are a round-trip // Returns maxint on failure (which could happen eg with a stored PC or a monster from a saved town) size_t cUniverse::get_target_i(iLiving& who) { if(dynamic_cast(&who)) return 6; else if(cPlayer* check = dynamic_cast(&who)) { for(int i = 0; i < 6; i++) if(check == &party[i]) return i; } else if(cCreature* check = dynamic_cast(&who)) { for(size_t i = 0; i < town.monst.size(); i++) if(check == &town.monst[i]) return i + 100; } return -1; } // TODO: Both of these have an issue that they'll return garbage if called outdoors // It's less of a problem with target_there since that should never actually be called outdoors, // but get_target would be called by "affect PC" special nodes which could be run outdoors. iLiving& cUniverse::get_target(size_t which) { size_t maxint = -1; if(which == maxint || which == 6) return party; else if(which < 6) return party[which]; else if(which >= 100 && which < 100 + town.monst.size()) return town.monst[which - 100]; else throw std::string("Tried to get nonexistent target!"); } iLiving* cUniverse::target_there(location where, eTargetType type) { if(type == TARG_ANY || type == TARG_PC) { for(int i = 0; i < 6; i++) if(party[i].is_alive() && where == party[i].get_loc()) return &party[i]; } if(type == TARG_ANY || type == TARG_MONST) { for(size_t i = 0; i < town.monst.size(); i++) if(town.monst[i].is_alive() && town.monst[i].on_space(where)) return &town.monst[i]; } return nullptr; } unsigned char& cUniverse::cpn_flag(unsigned int x, unsigned int y, std::string id) { if(id.empty()) id = scenario.campaign_id; if(id.empty()) id = scenario.scen_name; if(x >= 25 || y >= 25) throw std::range_error("Attempted to access a campaign flag out of range (0..25)"); return party.campaign_flags[id].idx[x][y]; } extern cCustomGraphics spec_scen_g; pic_num_t cUniverse::addGraphic(pic_num_t pic, ePicType type) { // Now find the first unused slot with sufficient space for the graphic we're adding int needSlots = 0; switch(type - PIC_PARTY) { case PIC_MONST: needSlots = 4; break; case PIC_MONST_WIDE: needSlots = 8; break; case PIC_MONST_TALL: needSlots = 8; break; case PIC_MONST_LG: needSlots = 16; break; case PIC_ITEM: needSlots = 1; break; case PIC_PC: needSlots = 4; break; case PIC_MISSILE: needSlots = 4; break; default: break; } pic_num_t pos = -1; bool foundSpace = false; while(!foundSpace) { // First find an empty slot. while(used_graphics.count(++pos)); // Then check if there's enough space. foundSpace = true; for(pic_num_t i = 1; i < needSlots; i++) { if(used_graphics.count(pos + i)) foundSpace = false; } } // And finally, actually transfer the graphic over spec_scen_g.copy_graphic(pos, pic, needSlots); // Also mark these slots used so we don't overwrite them with the next copy for(pic_num_t i = 1; i < needSlots; i++) { used_graphics.insert(pos + i); } return pos; } void cUniverse::exportGraphics() { // First determine which graphics are used, and which need to be copied. // The party sheet can contain the following types of graphics: // - Monster graphics for monsters summoned by custom items or captured in the party's soul crystal // - Item graphics for custom items that the party has in their possession or in their saved item rectangles // - Custom PC graphics // TODO: Missile graphics for custom monsters // So basically, almost all the graphics are linked to items. used_graphics.clear(); for(int i = 0; i < 6; i++) { if(party[i].main_status == eMainStatus::ABSENT) continue; if(party[i].which_graphic >= 10000) { for(int j = 0; j < 4; j++) used_graphics.insert(party[i].which_graphic - 10000 + j); } else if(party[i].which_graphic >= 1000) update_pcs[party[i].which_graphic - 1000].insert(&party[i]); for(size_t j = 0; j < party[i].items.size(); j++) { check_item(party[i].items[j]); } } for(size_t i = 0; i < party.stored_items.size(); i++) { for(size_t j = 0; j < party.stored_items[i].size(); j++) { check_item(party.stored_items[i][j]); } } for(mon_num_t monst : party.imprisoned_monst) { if(monst > 0 && monst < scenario.scen_monsters.size()) check_monst(scenario.scen_monsters[monst]); } // And then, just add all the graphics, and update references to them for(auto pic : update_pcs) { pic_num_t pos = addGraphic(pic.first, PIC_PC); for(auto& pc : pic.second) pc->which_graphic = 10000 + pos; } update_pcs.clear(); for(auto pic : update_items) { pic_num_t pos = addGraphic(pic.first, PIC_ITEM); for(auto& item : pic.second) item->graphic_num = 10000 + pos; } update_items.clear(); for(auto pic : update_missiles) { pic_num_t pos = addGraphic(pic.first, PIC_MISSILE); for(auto& missile : pic.second) *missile = 10000 + pos; } update_missiles.clear(); for(auto pic : update_monsters) { int sz = pic.first / 1000, base = pic.first % 1000; ePicType type; switch(sz) { case 1: type = PIC_MONST; break; case 2: type = PIC_MONST_WIDE; break; case 3: type = PIC_MONST_TALL; break; case 4: type = PIC_MONST_LG; break; default: continue; } pic_num_t pos = addGraphic(base, type); for(auto& monst : pic.second) monst->picture_num = 10000 + sz * 1000 + pos; } update_monsters.clear(); } void cUniverse::exportSummons() { std::set used_monsters, need_monsters; std::map> update_items; for(int i = 0; i < 6; i++) { if(party[i].main_status == eMainStatus::ABSENT) continue; for(size_t j = 0; j < party[i].items.size(); j++) { if(party[i].items[j].variety == eItemType::NO_ITEM) continue; if(party[i].items[j].ability == eItemAbil::SUMMONING || party[i].items[j].ability == eItemAbil::MASS_SUMMONING) { mon_num_t monst = party[i].items[j].abil_data[1]; if(monst >= 10000) used_monsters.insert(monst - 10000); else { need_monsters.insert(monst); update_items[monst].insert(&party[i].items[j]); } } } } for(size_t i = 0; i < party.stored_items.size(); i++) { for(size_t j = 0; j < party.stored_items[i].size(); j++) { if(party.stored_items[i][j].variety == eItemType::NO_ITEM) continue; if(party.stored_items[i][j].ability == eItemAbil::SUMMONING||party.stored_items[i][j].ability == eItemAbil::MASS_SUMMONING) { mon_num_t monst = party.stored_items[i][j].abil_data[1]; if(monst >= 10000) used_monsters.insert(monst - 10000); else { need_monsters.insert(monst); update_items[monst].insert(&party.stored_items[i][j]); } } } } for(mon_num_t monst : party.imprisoned_monst) { if(monst == 0) continue; if(monst >= 10000) used_monsters.insert(monst - 10000); else need_monsters.insert(monst); } std::stack last_check; for(mon_num_t m : need_monsters) last_check.push(m); while(!last_check.empty()) { mon_num_t monst = last_check.top(); last_check.pop(); cMonster& what = scenario.scen_monsters[monst]; if(what.abil[eMonstAbil::SUMMON].active && what.abil[eMonstAbil::SUMMON].summon.type == eMonstSummon::TYPE) { mon_num_t summon = what.abil[eMonstAbil::SUMMON].summon.what; if(summon >= 10000) used_monsters.insert(summon - 10000); else if(!need_monsters.count(summon)) { last_check.push(summon); need_monsters.insert(summon); } } } // Now that we know which exported summon slots are still in use and which new monsters need to be exported, // we can copy the monster records from the scenario record into the exported summon slots. if(used_monsters.empty()) party.summons.clear(); else { auto max = std::max_element(used_monsters.begin(), used_monsters.end()); party.summons.resize(*max + 1); } for(mon_num_t monst : need_monsters) { mon_num_t dest = -1; while(used_monsters.count(++dest)); used_monsters.insert(dest); if(dest < party.summons.size()) party.summons[dest] = scenario.scen_monsters[monst]; else party.summons.push_back(scenario.scen_monsters[monst]); for(auto& item : update_items[monst]) item->abil_data[1] = 10000 + dest; for(mon_num_t& sc : party.imprisoned_monst) if(sc == monst) sc = dest + 10000; } } short cUniverse::difficulty_adjust() const { short party_level = 0; short adj = 1; if(!scenario.adjust_diff) return 1; for(short i = 0; i < 6; i++) if(party[i].main_status == eMainStatus::ALIVE) party_level += party[i].level; if((scenario.difficulty <= 0) && (party_level >= 60)) adj++; if((scenario.difficulty <= 1) && (party_level >= 130)) adj++; if((scenario.difficulty <= 2) && (party_level >= 210)) adj++; return adj; } cUniverse::~cUniverse() { clear_stored_pcs(); } void cUniverse::clear_stored_pcs() { for(auto& p : stored_pcs) delete p.second; stored_pcs.clear(); } short cCurTown::countMonsters(){ short to_ret = 0; for(short i = 0; i < monst.size(); i++) if(monst[i].active > 0) to_ret++; return to_ret; } void cUniverse::enter_scenario(const std::string& name) { using namespace std::placeholders; party.age = 0; memset(party.stuff_done, 0, sizeof(party.stuff_done)); party.light_level = 0; party.outdoor_corner = scenario.out_sec_start; party.i_w_c = {0, 0}; party.loc_in_sec = scenario.out_start; party.out_loc = scenario.out_start; party.boats.clear(); party.horses.clear(); std::copy_if(scenario.boats.begin(), scenario.boats.end(), std::back_inserter(party.boats), std::bind(&cVehicle::exists, _1)); std::copy_if(scenario.horses.begin(), scenario.horses.end(), std::back_inserter(party.horses), std::bind(&cVehicle::exists, _1)); for(auto& pc : party) { pc.status.clear(); if(isSplit(pc.main_status)) pc.main_status -= eMainStatus::SPLIT; pc.cur_health = pc.max_health; pc.cur_sp = pc.max_sp; } party.in_boat = -1; party.in_horse = -1; for(auto& pop : party.creature_save) pop.which_town = 200; for(short i = 0; i < 10; i++) party.out_c[i].exists = false; party.magic_store_items.clear(); // TODO: Now uncertain if the journal should really persist // univ.party.journal.clear(); party.special_notes.clear(); party.talk_save.clear(); party.direction = DIR_N; party.at_which_save_slot = 0; for(auto town : scenario.towns) { town->can_find = !town->is_hidden; town->m_killed = 0; town->clear_items_taken(); for(auto& m : town->maps) m.reset(); } party.key_times.clear(); party.party_event_timers.clear(); party.spec_items.clear(); for(short i = 0; i < scenario.special_items.size(); i++) { if(scenario.special_items[i].flags >= 10) party.spec_items.insert(i); } for(short i = 0; i < scenario.quests.size(); i++) { if(scenario.quests[i].flags >= 10) { party.active_quests[i] = cJob(1); } } refresh_store_items(); for(short i = 0; i < 96; i++) for(short j = 0; j < 96; j++) out.out_e[i][j] = 0; for(short i = 0; i < 3; i++) party.stored_items[i].clear(); for(auto sector : scenario.outdoors) for(auto& m : sector->maps) m.reset(); party.scen_name = name; } void cUniverse::generate_job_bank(int which, job_bank_t& bank) { std::fill(bank.jobs.begin(), bank.jobs.end(), -1); bank.inited = true; size_t iSlot = 0; for(size_t i = 0; iSlot < 4 && i < scenario.quests.size(); i++) { if(scenario.quests[i].bank1 != which && scenario.quests[i].bank2 != which) continue; if(party.active_quests[i].status != eQuestStatus::AVAILABLE) continue; if(get_ran(1,1,100) <= 50 - bank.anger) bank.jobs[iSlot++] = i; } } cItem cUniverse::get_random_store_item(int loot_type, bool allow_junk_treasure) { cItem item = scenario.return_treasure(loot_type, allow_junk_treasure); if(item.variety == eItemType::GOLD || item.variety == eItemType::SPECIAL || item.variety == eItemType::FOOD || item.variety == eItemType::QUEST) item = cItem(); item.ident = true; return item; } void cUniverse::refresh_store_items() { for(size_t i = 0; i < scenario.shops.size(); i++) { if(scenario.shops[i].getType() != eShopType::RANDOM) continue; for(int j = 0; j < scenario.shops[i].size(); j++) { cShopItem entry = scenario.shops[i].getItem(j); if(entry.type == eShopItemType::TREASURE) { party.magic_store_items[i][j] = get_random_store_item(entry.item.item_level, entry.item.item_level == 0); continue; } else if(entry.type == eShopItemType::CLASS) { std::set choices; for(int k = 0; k < scenario.scen_items.size(); k++) { if(scenario.scen_items[k].special_class == entry.item.special_class) choices.insert(k); } int choice = get_ran(1,0,choices.size()); if(choice < choices.size()) { auto iter = choices.begin(); std::advance(iter, choice); party.magic_store_items[i][j] = scenario.scen_items[*iter]; continue; } } else if(entry.type == eShopItemType::OPT_ITEM) { int roll = get_ran(1,1,100); if(roll <= entry.quantity / 1000) { party.magic_store_items[i][j] = entry.item; continue; } } } } for(int i = 0; i < party.job_banks.size(); i++) { generate_job_bank(i, party.job_banks[i]); } } cPlayer& cUniverse::current_pc() { return party[cur_pc]; } void(* cUniverse::print_result)(std::string) = nullptr;