Files
oboe/src/universe/universe.cpp

1458 lines
44 KiB
C++

/*
* 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(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<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]);
}
// 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;