text: /*
   *  universe.cpp
   *  BoE
   *
   *  Created by Celtic Minstrel on 24/04/09.
   *
   */
  
  #include "universe.hpp"
  
  #include <string>
  #include <vector>
  #include <map>
  #include <sstream>
  #include <stack>
  
  #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(ePartyPreset 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<cParty*>(&who))
  		return 6;
  	else if(cPlayer* check = dynamic_cast<cPlayer*>(&who)) {
  		for(int i = 0; i < 6; i++)
  			if(check == &party[i])
  				return i;
  	} else if(cCreature* check = dynamic_cast<cCreature*>(&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]);
  		else if(monst >= 10000 && monst - 10000 < party.summons.size())
  			check_monst(party.summons[monst - 10000]);
  	}
  	// 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<mon_num_t> used_monsters, need_monsters;
  	std::map<mon_num_t, update_info<cItem>> 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<mon_num_t> 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<int> 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;
  