Files
oboe/src/game/boe.town.cpp

1673 lines
55 KiB
C++

#include <SFML/OpenGL.hpp>
#include <cstdio>
#include <queue>
#include <limits>
#include "boe.global.hpp"
#include "universe/universe.hpp"
#include "boe.graphutil.hpp"
#include "boe.graphics.hpp"
#include "boe.newgraph.hpp"
#include "boe.fileio.hpp"
#include "boe.items.hpp"
#include "boe.monster.hpp"
#include "boe.town.hpp"
#include "boe.combat.hpp"
#include "boe.party.hpp"
#include "boe.text.hpp"
#include "sounds.hpp"
#include "boe.locutils.hpp"
#include "boe.specials.hpp"
#include "boe.infodlg.hpp"
#include "boe.ui.hpp"
#include "mathutil.hpp"
#include "gfx/render_image.hpp"
#include "gfx/render_shapes.hpp"
#include "gfx/render_text.hpp"
#include "gfx/tiling.hpp"
#include "dialogxml/dialogs/strdlog.hpp"
#include "tools/winutil.hpp"
#include "fileio/resmgr/res_image.hpp"
#include "tools/cursors.hpp"
#include "replay.hpp"
extern short store_spell_target,which_combat_type,combat_active_pc;
extern eGameMode overall_mode;
extern eItemWinMode stat_window;
extern location center;
extern short store_current_pc,current_ground;
extern eGameMode store_pre_shop_mode,store_pre_talk_mode;
extern std::queue<pending_special_type> special_queue;
extern bool prime_time();
extern bool map_visible;
extern location hor_vert_place[14];
extern location diag_place[14];
extern location golem_m_locs[16];
extern cUniverse univ;
extern cCustomGraphics spec_scen_g;
bool need_map_full_refresh = true,forcing_map_button_redraw = false;
// In the 0..65535 range, this colour was {65535,65535,52428}
sf::Color parchment = {255,255,205};
long pause_dummy;
location town_map_adj;
short town_force = 200,store_which_shop,store_min,store_max,store_shop,store_selling_pc;
short sell_offset = 0;
location town_force_loc;
bool shop_button_active[12];
rectangle map_title_rect = {3,50,15,300};
rectangle map_bar_rect = {15,50,27,300};
rectangle map_tooltip_rect = {270,50,307,300};
void force_town_enter(short which_town,location where_start) {
town_force = which_town;
town_force_loc = where_start;
}
bool need_enter_town_autosave = false;
//short entry_dir; // if 9, go to forced
void start_town_mode(short which_town, short entry_dir, bool debug_enter) {
short town_number;
short former_town;
bool monsters_loaded = false,town_toast = false;
location loc;
bool play_town_sound = false;
if(town_force < 200)
which_town = town_force;
else play_town_sound = true;
former_town = town_number = which_town;
if(town_number < 0 || town_number >= univ.scenario.towns.size()) {
showError("The scenario tried to put you into a town that doesn't exist.",
"Requested town: " + std::to_string(town_number) + "|Max town: " + std::to_string(univ.scenario.towns.size()));
return;
}
// Now adjust town number as necessary.
for(const auto& mod : univ.scenario.town_mods)
if(mod.spec >= 0 && mod.spec < 200 && town_number == mod.spec && univ.party.sd_legit(mod.x, mod.y)) {
former_town = town_number;
town_number += PSD[mod.x][mod.y];
// Now update horses & boats
for(auto& horse : univ.party.horses)
if(horse.exists && horse.which_town == former_town)
horse.which_town = town_number;
for(auto& boat : univ.party.boats)
if(boat.exists && boat.which_town == former_town)
boat.which_town = town_number;
}
if(town_number < 0 || town_number >= univ.scenario.towns.size()) {
showError("The scenario tried to put you into a town that doesn't exist.",
"Requested town: " + std::to_string(former_town) + "|Adjusted town: " + std::to_string(town_number) + "|Max town: " + std::to_string(univ.scenario.towns.size()));
return;
}
overall_mode = MODE_TOWN;
univ.party.town_num = town_number;
if(play_town_sound) {
if(univ.town->lighting_type > 0)
play_sound(95);
else play_sound(16);
}
// TODO: This means cleared webs reappear, for example. Is that right?
univ.town.place_preset_fields();
for(location unlocked : univ.town->door_unlocked){
if(is_unlockable(unlocked)){
ter_num_t terrain = univ.town->terrain(unlocked.x,unlocked.y);
univ.town->terrain(unlocked.x,unlocked.y) = univ.scenario.ter_types[terrain].flag1;
}
}
univ.town.belt_present = false;
// Set up map, using stored map
for(short i = 0; i < univ.town->max_dim; i++)
for(short j = 0; j < univ.town->max_dim; j++) {
if(univ.town->maps[j][i])
make_explored(i,j);
// TODO: Don't hard-code these ground types!
if(univ.town->terrain(i,j) == 0)
current_ground = 0;
else if(univ.town->terrain(i,j) == 2)
current_ground = 2;
if(univ.scenario.ter_types[univ.town->terrain(i,j)].special == eTerSpec::CONVEYOR)
univ.town.belt_present = true;
}
univ.town.monst.which_town = town_number;
univ.town.monst.hostile = false;
std::set<cCreature*> no_thrash;
for(auto& pop : univ.party.creature_save)
if(town_number == pop.which_town) {
univ.town.monst = pop;
monsters_loaded = true;
for(auto& monst : univ.town.monst) {
if(loc_off_act_area(monst.cur_loc))
monst.active = eCreatureStatus::DEAD;
if(monst.active == eCreatureStatus::ALERTED)
monst.active = eCreatureStatus::IDLE;
monst.cur_loc = monst.start_loc;
monst.health = monst.m_health;
monst.mp = monst.max_mp;
monst.morale = monst.m_morale;
monst.status.clear();
if(monst.summon_time > 0)
monst.active = eCreatureStatus::DEAD;
monst.target = 6;
// Bonus SP and HP wear off
if(monst.mp > monst.max_mp)
monst.mp = monst.max_mp;
if(monst.health > monst.m_health)
monst.health = monst.m_health;
}
// Now, travelling NPCs might have arrived. Go through and put them in.
// These should have protected status (i.e. spec1 >= 200, spec1 <= 204)
for(auto& monst : univ.town.monst) {
switch(monst.time_flag){
case eMonstTime::ALWAYS: break; // Nothing to do.
case eMonstTime::SOMETIMES_A: case eMonstTime::SOMETIMES_B: case eMonstTime::SOMETIMES_C:
if((univ.party.calc_day() % 3) + 3 != int(monst.time_flag))
monst.active = eCreatureStatus::DEAD;
else {
monst.active = eCreatureStatus::IDLE;
monst.spec_enc_code = 0;
monst.cur_loc = monst.start_loc;
monst.health = monst.m_health;
}
break ;
// Now, appearing/disappearing monsters might have arrived/disappeared.
case eMonstTime::APPEAR_ON_DAY:
if(day_reached(monst.monster_time, monst.time_code)) {
monst.active = eCreatureStatus::IDLE;
monst.time_flag = eMonstTime::ALWAYS;
}
break;
case eMonstTime::DISAPPEAR_ON_DAY:
if(day_reached(monst.monster_time, monst.time_code)) {
monst.active = eCreatureStatus::DEAD;
monst.time_flag = eMonstTime::ALWAYS;
}
break;
case eMonstTime::APPEAR_WHEN_EVENT:
if(univ.party.key_times.find(monst.time_code) == univ.party.key_times.end())
break; // Event hasn't happened yet
if(univ.party.calc_day() >= univ.party.key_times[monst.time_code]){ //calc_day is used because of the "definition" of univ.party.key_times
monst.active = eCreatureStatus::IDLE;
monst.time_flag = eMonstTime::ALWAYS;
}
break;
case eMonstTime::DISAPPEAR_WHEN_EVENT:
if(univ.party.key_times.find(monst.time_code) == univ.party.key_times.end())
break; // Event hasn't happened yet
if(univ.party.calc_day() >= univ.party.key_times[monst.time_code]){
monst.active = eCreatureStatus::DEAD;
monst.time_flag = eMonstTime::ALWAYS;
}
break;
case eMonstTime::APPEAR_AFTER_CHOP:
// TODO: Should these two cases be separated?
if(univ.town->town_chop_time > 0 && day_reached(univ.town->town_chop_time,univ.town->town_chop_key))
no_thrash.insert(&monst);
else if(univ.town->is_cleaned_out())
no_thrash.insert(&monst);
else monst.active = eCreatureStatus::DEAD;
if(no_thrash.count(&monst) > 0)
monst.time_flag = eMonstTime::ALWAYS;
break;
}
}
// TODO: THIS IS A TEMPORARY HACK TO GET i VALUE
int i = std::find_if(univ.party.creature_save.begin(), univ.party.creature_save.end(), [&pop](cPopulation& p) {return &p == &pop;}) - univ.party.creature_save.begin();
univ.town.update_fields(univ.party.setup[i]);
}
if(!monsters_loaded) {
univ.town.monst.clear();
for(short i = 0; i < univ.town->creatures.size(); i++){
if(univ.town->creatures[i].number > 0) {
// First set up the values.
const cTownperson& preset = univ.town->creatures[i];
univ.town.monst.assign(i, preset, univ.scenario.scen_monsters[preset.number], univ.party.easy_mode, univ.difficulty_adjust());
cCreature& monst = univ.town.monst[i];
if(monst.spec_enc_code > 0)
monst.active = eCreatureStatus::DEAD;
// Now, if necessary, fry the monster.
switch(monst.time_flag) {
case eMonstTime::APPEAR_ON_DAY:
if(!day_reached(monst.monster_time, monst.time_code))
monst.active = eCreatureStatus::DEAD;
break;
case eMonstTime::DISAPPEAR_ON_DAY:
if(day_reached(monst.monster_time, monst.time_code))
monst.active = eCreatureStatus::DEAD;
break;
case eMonstTime::SOMETIMES_A: case eMonstTime::SOMETIMES_B: case eMonstTime::SOMETIMES_C:
if((univ.party.calc_day() % 3) + 3 != int(monst.time_flag)) {
monst.active = eCreatureStatus::DEAD;
}
else {
monst.active = eCreatureStatus::IDLE;
}
break;
case eMonstTime::APPEAR_WHEN_EVENT:
if(univ.party.key_times.find(monst.time_code) == univ.party.key_times.end())
monst.active = eCreatureStatus::DEAD; // Event hasn't happened yet
else if(univ.party.calc_day() < univ.party.key_times[univ.town.monst[i].time_code])
monst.active = eCreatureStatus::DEAD; // This would only be reached if the time was set back (or in a legacy save) TODO: Is this right? Should they both set it to dead?
break;
case eMonstTime::DISAPPEAR_WHEN_EVENT:
if(univ.party.key_times.find(monst.time_code) == univ.party.key_times.end())
break; // Event hasn't happened yet
if(univ.party.calc_day() >= univ.party.key_times[univ.town.monst[i].time_code])
monst.active = eCreatureStatus::DEAD;
break;
case eMonstTime::APPEAR_AFTER_CHOP:
// TODO: Should these two cases be separated?
if(univ.town->town_chop_time > 0 && day_reached(univ.town->town_chop_time,univ.town->town_chop_key))
no_thrash.insert(&monst);
else if(univ.town->is_cleaned_out())
no_thrash.insert(&monst);
else monst.active = eCreatureStatus::DEAD;
break;
case eMonstTime::ALWAYS:
break;
}
if(monst.is_alive()) {
// In forcecage?
if(univ.town.is_force_cage(monst.cur_loc.x, monst.cur_loc.y))
monst.status[eStatus::FORCECAGE] = 1000;
}
}
}
}
// Now munch all large monsters that are misplaced
// only large monsters, as some smaller monsters are intentionally placed
// where they cannot be
for(short i = 0; i < univ.town.monst.size(); i++) {
if(univ.town.monst[i].is_alive())
if(((univ.town.monst[i].x_width > 1) || (univ.town.monst[i].y_width > 1)) &&
!monst_can_be_there(univ.town.monst[i].cur_loc,i))
univ.town.monst[i].active = eCreatureStatus::DEAD;
}
// Thrash town?
if(univ.town->is_cleaned_out()) {
town_toast = true;
add_string_to_buf("Area has been cleaned out.");
}
if(univ.town->town_chop_time > 0) {
if(day_reached(univ.town->town_chop_time,univ.town->town_chop_key)) {
add_string_to_buf("Area has been abandoned.");
for(auto& monst : univ.town.monst)
if(monst.is_alive() && no_thrash.count(&monst) == 0 && !monst.is_friendly())
no_thrash.insert(&monst);
town_toast = true;
}
}
if(town_toast) {
for(auto& monst : univ.town.monst)
if(no_thrash.count(&monst) == 0)
monst.active = eCreatureStatus::DEAD;
}
bool specials_queued = false;
if(!debug_enter)
specials_queued = handle_town_specials(town_number, (short) town_toast,(entry_dir < 9) ? univ.town->start_locs[entry_dir] : town_force_loc);
// Flush excess doomguards and viscous goos
for(short i = 0; i < univ.town.monst.size(); i++)
if((univ.town.monst[i].abil[eMonstAbil::SPLITS].active) &&
(i >= univ.town->creatures.size() || univ.town.monst[i].number != univ.town->creatures[i].number))
univ.town.monst[i].active = eCreatureStatus::DEAD;
// Set up field booleans, correct for doors
for(short j = 0; j < univ.town->max_dim; j++)
for(short k = 0; k < univ.town->max_dim; k++) {
loc.x = j; loc.y = k;
if(is_door(loc)) {
univ.town.set_web(j,k,false);
univ.town.set_crate(j,k,false);
univ.town.set_barrel(j,k,false);
univ.town.set_fire_barr(j,k,false);
univ.town.set_force_barr(j,k,false);
univ.town.set_quickfire(j,k,false);
}
if(univ.town.is_quickfire(j,k))
univ.town.quickfire_present = true;
}
// Set up items, maybe place items already there
univ.town.items.clear();
auto storage_iter = univ.scenario.store_item_rects.find(town_number);
if(storage_iter != univ.scenario.store_item_rects.end()) {
univ.town.items = univ.party.stored_items[town_number];
}
for(short i = 0; i < univ.town->preset_items.size(); i++)
if(univ.town->preset_items[i].code >= 0) {
const cTown::cItem& preset = univ.town->preset_items[i];
// place the preset item, if party hasn't gotten it already
univ.town.items.push_back(univ.scenario.get_stored_item(preset.code));
cItem& item = univ.town.items.back();
item.item_loc = preset.loc;
// Don't place special items if already in the party's possession
if(item.variety == eItemType::SPECIAL && univ.party.spec_items.count(item.item_level)) {
univ.town.items.pop_back();
continue;
}
// Don't place quest items if party already started
if(item.variety == eItemType::QUEST && univ.party.active_quests[item.item_level].status != eQuestStatus::AVAILABLE) {
univ.town.items.pop_back();
continue;
}
// Don't place the item if the party already took it, unless it's always there
if(univ.town->is_item_taken(i) && !preset.always_there) {
univ.town.items.pop_back();
continue;
}
// Not use the items data flags, starting with forcing an ability
if(preset.ability != eEnchant::NONE) {
// TODO: What other ways might there be to use this?
switch(item.variety) {
case eItemType::ONE_HANDED:
case eItemType::TWO_HANDED: {
item.enchant_weapon(preset.ability);
break;
}
default: break; // Silence compiler warning
}
}
if(preset.charges > 0) {
eItemType variety = item.variety;
if(item.charges > 0)
item.charges = preset.charges;
else if(variety == eItemType::GOLD || variety == eItemType::FOOD)
item.item_level = preset.charges;
}
if(town_toast)
item.property = false;
else
item.property = preset.property;
item.contained = preset.contained;
int x = item.item_loc.x, y = item.item_loc.y;
if(item.contained && (univ.town.is_barrel(x,y) || univ.town.is_crate(x,y)))
item.held = true;
item.is_special = i + 1;
}
for(auto& monst : univ.town.monst)
if(loc_off_act_area(monst.cur_loc))
monst.active = eCreatureStatus::DEAD;
for(auto& item : univ.town.items)
if(loc_off_act_area(item.item_loc))
item.variety = eItemType::NO_ITEM;
// Clean out unwanted monsters
for(auto& monst : univ.town.monst)
if(univ.party.sd_legit(monst.spec1, monst.spec2)) {
if(PSD[monst.spec1][monst.spec2] > 0)
monst.active = eCreatureStatus::DEAD;
}
erase_town_specials();
// make_town_trim(0);
univ.party.town_loc = (entry_dir < 9) ? univ.town->start_locs[entry_dir] : town_force_loc;
center = univ.party.town_loc;
if(univ.party.in_boat >= 0) {
univ.party.boats[univ.party.in_boat].which_town = which_town;
univ.party.boats[univ.party.in_boat].loc = univ.party.town_loc;
}
if(univ.party.in_horse >= 0) {
univ.party.horses[univ.party.in_horse].which_town = which_town;
univ.party.horses[univ.party.in_horse].loc = univ.party.town_loc;
}
// End flying
if(univ.party.status[ePartyStatus::FLIGHT] > 0) {
univ.party.status[ePartyStatus::FLIGHT] = 0;
add_string_to_buf("You land, and enter. ");
}
// No hostile monsters present.
univ.party.hostiles_present = 0;
add_string_to_buf("Now entering:");
add_string_to_buf(" " + univ.town->name);
// clear entry space, and check exploration
univ.town.set_web(univ.party.town_loc.x,univ.party.town_loc.y,false);
univ.town.set_crate(univ.party.town_loc.x,univ.party.town_loc.y,false);
univ.town.set_barrel(univ.party.town_loc.x,univ.party.town_loc.y,false);
univ.town.set_fire_barr(univ.party.town_loc.x,univ.party.town_loc.y,false);
univ.town.set_force_barr(univ.party.town_loc.x,univ.party.town_loc.y,false);
univ.town.set_quickfire(univ.party.town_loc.x,univ.party.town_loc.y,false);
update_explored(univ.party.town_loc);
// If a PC dead, drop his items
for(cPlayer& pc : univ.party) {
if(pc.main_status == eMainStatus::ALIVE || isSplit(pc.main_status))
continue;
for(cItem& item : pc.items)
if(item.variety != eItemType::NO_ITEM) {
place_item(item,univ.party.town_loc);
item.variety = eItemType::NO_ITEM;
}
}
for(auto& monst : univ.town.monst) {
monst.targ_loc.x = 0;
monst.targ_loc.y = 0;
}
clear_map();
reset_item_max();
town_force = 200;
// TODO: One problem with this - it paints the terrain after the town entry dialog is dismissed
// ... except it actually doesn't, because the town enter special is only queued, not run immediately.
draw_terrain(1);
// If special nodes still need to be called, we can't do the autosave yet.
if(specials_queued) need_enter_town_autosave = true;
else try_auto_save("EnterTown");
}
location end_town_mode(bool switching_level,location destination, bool debug_leave) { // returns new party location
location to_return;
bool data_saved = false,combat_end = false;
if(is_combat())
combat_end = true;
// Bonus SP and HP wear off
for(short i = 0; i < 6; i++) {
if(univ.party[i].cur_sp > univ.party[i].max_sp)
univ.party[i].cur_sp = univ.party[i].max_sp;
if(univ.party[i].cur_health > univ.party[i].max_health)
univ.party[i].cur_health = univ.party[i].max_health;
}
if(overall_mode == MODE_TOWN) {
for(auto& pop : univ.party.creature_save)
if(pop.which_town == univ.party.town_num) {
pop = univ.town.monst;
// TODO: THIS IS A TEMPORARY HACK TO GET i VALUE
int i = std::find_if(univ.party.creature_save.begin(), univ.party.creature_save.end(), [&pop](cPopulation& p) {return &p == &pop;}) - univ.party.creature_save.begin();
univ.town.save_setup(univ.party.setup[i]);
data_saved = true;
break;
}
if(!data_saved) {
univ.party.creature_save[univ.party.at_which_save_slot] = univ.town.monst;
univ.town.save_setup(univ.party.setup[univ.party.at_which_save_slot]);
univ.party.at_which_save_slot = (univ.party.at_which_save_slot == 3) ? 0 : univ.party.at_which_save_slot + 1;
}
// Store items, if necessary
auto storage_iter = univ.scenario.store_item_rects.find(univ.party.town_num);
if(storage_iter != univ.scenario.store_item_rects.end()) {
auto& stored_items = univ.party.stored_items[univ.party.town_num];
stored_items.clear();
for(short i = 0; i < univ.town.items.size(); i++)
if(univ.town.items[i].variety != eItemType::NO_ITEM && univ.town.items[i].is_special == 0 &&
univ.town.items[i].item_loc.x >= storage_iter->second.left &&
univ.town.items[i].item_loc.x <= storage_iter->second.right &&
univ.town.items[i].item_loc.y >= storage_iter->second.top &&
univ.town.items[i].item_loc.y <= storage_iter->second.bottom) {
stored_items.push_back(univ.town.items[i]);
}
}
// Now store map
for(short i = 0; i < univ.town->max_dim; i++)
for(short j = 0; j < univ.town->max_dim; j++)
if(is_explored(i,j)) {
univ.town->maps[j].set(i);
}
to_return = univ.party.out_loc;
auto& timers = univ.party.party_event_timers;
timers.erase(std::remove_if(timers.begin(), timers.end(), [](const cTimer& t) {
return t.node_type == eSpecCtxType::TOWN;
}), timers.end());
}
// Check for exit specials, if leaving town
if(!switching_level && !debug_leave) {
to_return = univ.party.out_loc;
if(is_town()) {
if(destination.x <= univ.town->in_town_rect.left) {
if(univ.town->exits[1].x > 0)
to_return = local_to_global(univ.town->exits[1]);
else to_return.x--;
univ.party.out_loc = to_return; univ.party.out_loc.x++;
handle_leave_town_specials(univ.party.town_num, univ.town->exits[1].spec,destination) ;
}
else if(destination.x >= univ.town->in_town_rect.right) {
if(univ.town->exits[3].x > 0)
to_return = local_to_global(univ.town->exits[3]);
else to_return.x++;
univ.party.out_loc = to_return; univ.party.out_loc.x--;
handle_leave_town_specials(univ.party.town_num, univ.town->exits[3].spec,destination) ;
}
else if(destination.y <= univ.town->in_town_rect.top) {
if(univ.town->exits[0].x > 0)
to_return = local_to_global(univ.town->exits[0]);
else to_return.y--;
univ.party.out_loc = to_return; univ.party.out_loc.y++;
handle_leave_town_specials(univ.party.town_num, univ.town->exits[0].spec,destination) ;
}
else if(destination.y >= univ.town->in_town_rect.bottom) {
if(univ.town->exits[2].x > 0)
to_return = local_to_global(univ.town->exits[2]);
else to_return.y++;
univ.party.out_loc = to_return; univ.party.out_loc.y--;
handle_leave_town_specials(univ.party.town_num, univ.town->exits[2].spec,destination) ;
}
}
}
if(!switching_level) {
overall_mode = MODE_OUTDOORS;
erase_out_specials();
univ.party.status[ePartyStatus::STEALTH] = 0;
univ.party.status[ePartyStatus::DETECT_LIFE] = 0; // TODO: Yes? No? Maybe?
for(cPlayer& who : univ.party)
who.clear_brief_status();
update_explored(to_return);
redraw_screen(REFRESH_TERRAIN | REFRESH_TEXT);
}
if(!combat_end)
clear_map();
univ.party.town_num = 200; // should be harmless...
return to_return;
}
bool handle_town_specials(short /*town_number*/, bool town_dead,location /*start_loc*/) {
return queue_special(eSpecCtx::ENTER_TOWN, eSpecCtxType::TOWN, town_dead ? univ.town->spec_on_entry_if_dead : univ.town->spec_on_entry, univ.party.town_loc);
}
void handle_leave_town_specials(short /*town_number*/, short which_spec,location /*start_loc*/) {
queue_special(eSpecCtx::LEAVE_TOWN, eSpecCtxType::TOWN, which_spec, univ.party.out_loc);
}
bool abil_exists(eItemAbil abil) { // use when outdoors
for(const cPlayer& pc : univ.party)
for(const cItem& item : pc.items)
if(item.variety != eItemType::NO_ITEM && item.ability == abil)
return true;
for(short i = 0; i < 3; i++)
for(short j = 0; j < univ.town.items.size(); j++)
if(univ.party.stored_items[i][j].variety != eItemType::NO_ITEM && univ.party.stored_items[i][j].ability == abil)
return true;
return false;
}
void start_town_combat(eDirection direction) {
place_party(direction);
if(univ.cur_pc == 6)
for(short i = 0; i < 6; i++)
if(univ.party[i].main_status == eMainStatus::ALIVE) {
univ.cur_pc = i;
break;
}
center = univ.current_pc().combat_pos;
which_combat_type = 1;
overall_mode = MODE_COMBAT;
combat_active_pc = 6;
for(short i = 0; i < univ.town.monst.size(); i++)
univ.town.monst[i].target = 6;
for(short i = 0; i < 6; i++) {
univ.party[i].last_attacked = nullptr;
univ.party[i].parry = 0;
univ.party[i].direction = direction;
univ.current_pc().direction = direction;
if(univ.party[i].main_status == eMainStatus::ALIVE)
update_explored(univ.party[i].combat_pos);
}
store_current_pc = univ.cur_pc;
univ.cur_pc = 0;
set_pc_moves();
pick_next_pc();
center = univ.current_pc().combat_pos;
UI::toolbar.draw(mainPtr());
put_pc_screen();
set_stat_window_for_pc(univ.cur_pc);
give_help(48,49);
}
eDirection end_town_combat() {
short num_tries = 0,r1;
int in_cage = 0;
location cage_loc;
bool same_cage = true;
for(int i = 0; i < 6; i++) {
if(univ.party[i].status[eStatus::FORCECAGE] > 0) {
if(in_cage == 0)
cage_loc = univ.party[i].get_loc();
in_cage++;
}
if(univ.party[i].get_loc() != cage_loc)
same_cage = false;
}
if(in_cage != 0 && in_cage != univ.party.count() && !same_cage) {
add_string_to_buf(" Someone trapped.");
return DIR_HERE;
}
// TODO: Don't allow ending combat if two PCs are separated by an impassable wall with no way around it
r1 = get_ran(1,0,5);
while(univ.party[r1].main_status != eMainStatus::ALIVE && num_tries++ < 1000)
r1 = get_ran(1,0,5);
univ.party.town_loc = univ.party[r1].combat_pos;
overall_mode = MODE_TOWN;
univ.cur_pc = store_current_pc;
if(univ.current_pc().main_status != eMainStatus::ALIVE)
univ.cur_pc = first_active_pc();
for(short i = 0; i < 6; i++) {
univ.party[i].parry = 0;
univ.party[i].combat_pos = {-1,-1};
}
return univ.party[r1].direction;
}
void place_party(short direction) {
bool in_cage = false;
for(int n = 0; n < 6; n++)
if(univ.party[n].status[eStatus::FORCECAGE])
in_cage = true;
if(in_cage) {
for(int n = 0; n < 6; n++)
univ.party[n].combat_pos = univ.party.town_loc;
return;
}
bool spot_ok[14] = {true,true,true,true,true,true,true,
true,true,true,true,true,true,true};
location pos_locs[14];
location check_loc;
short x_adj,y_adj,how_many_ok = 1,where_in_a = 0;
for(short i = 0; i < 14; i++) {
check_loc = univ.party.town_loc;
if(direction % 4 < 2)
x_adj = ((direction % 2 == 0) ? hor_vert_place[i].x : diag_place[i].x);
else x_adj = ((direction % 2 == 0) ? hor_vert_place[i].y : diag_place[i].y);
if(direction % 2 == 0)
x_adj = (direction < 4) ? x_adj : -1 * x_adj;
else x_adj = ((direction == 1) || (direction == 7)) ? -1 * x_adj : x_adj;
check_loc.x -= x_adj;
if(direction % 4 < 2)
y_adj = ((direction % 2 == 0) ? hor_vert_place[i].y : diag_place[i].y);
else y_adj = ((direction % 2 == 0) ? hor_vert_place[i].x : diag_place[i].x);
if(direction % 2 == 0)
y_adj = ((direction > 1) && (direction < 6)) ? y_adj : -1 * y_adj;
else y_adj = ((direction == 3) || (direction == 1)) ? -1 * y_adj : y_adj;
check_loc.y -= y_adj;
pos_locs[i] = check_loc;
if(!is_blocked(check_loc) && !is_special(check_loc) && sight_obscurity(check_loc.x,check_loc.y) == 0
&& can_see_light(univ.party.town_loc,check_loc,combat_obscurity) < 1 && !loc_off_act_area(check_loc)) {
spot_ok[i] = true;
how_many_ok += (i > 1) ? 1 : 0;
}
else spot_ok[i] = false;
if(i == 0)
spot_ok[i] = true;
}
for(short i = 0; i < 6; i++) {
if(univ.party[i].main_status == eMainStatus::ALIVE) {
if(how_many_ok == 1)
univ.party[i].combat_pos = pos_locs[where_in_a];
else {
univ.party[i].combat_pos = pos_locs[where_in_a];
if(how_many_ok > 1)
where_in_a++;
how_many_ok--;
// if(how_many_ok > 1) {
while(!spot_ok[where_in_a] && where_in_a < 14)
where_in_a++;
// }
}
}
}
}
void create_out_combat_terrain(short ter_type,short num_walls,bool is_road) {
short r1,arena;
static const short ter_base[20] = {
2,0,36,50,71, 0,0,0, 0,2,
2,2, 2, 0, 0, 36,0,2, 0,2,
};
static const location special_ter_locs[15] = {
loc(11,10),loc(11,14),loc(10,20),loc(11,26),loc(9,30),
loc(15,19),loc(23,19),loc(19,29),loc(20,11),loc(28,16),
loc(28,24),loc(27,19),loc(27,29),loc(15,28),loc(19,19),
};
static const ter_num_t cave_pillar[4][4] = {
{0 ,14,11,1 },
{14,19,20,11},
{17,18,21,8 },
{1 ,17,8 ,0 }
};
static const ter_num_t mntn_pillar[4][4] = {
{37,29,27,36},
{29,33,34,27},
{31,32,35,25},
{36,31,25,37}
};
static const ter_num_t surf_lake[4][4] = {
{56,55,54,3 },
{57,50,61,54},
{58,51,59,53},
{3 ,4 ,58,52}
};
static const ter_num_t cave_lake[4][4] = {
{93,96,71,71},
{96,71,71,71},
{71,71,71,96},
{71,71,71,96}
};
static const ter_num_t surf_fume[4][4] = {
{75,75,75,36},
{75,75,75,75},
{75,75,75,75},
{36,37,75,75}
};
static const ter_num_t cave_fume[4][4] = {
{98,0 ,75,75},
{0 ,75,75,75},
{75,75,75,0 },
{75,75,75,0 }
};
// TODO: There's no surface bonfire or bedding in the default terrain set!
static const ter_num_t surf_camp[4][4] = {
{105, 2,4, 2},
{ 2,104,2,105},
{115, 2,4, 2},
{ 2,105,2,115},
};
static const ter_num_t cave_camp[4][4] = {
{105, 0, 1, 0},
{ 0,104, 0,105},
{ 92, 0,93, 0},
{ 0,105, 0, 92},
};
static const short terrain_odds[20][10] = {
{ 3, 80, 4, 40, 115, 20, 114, 10, 112, 1}, // Grassy field
{ 1, 50, 93, 25, 94, 5, 98, 10, 95, 1}, // Ordinary cave
{37, 20}, // Mountain
{64, 3, 63, 1}, // Surface bridge
{74, 1}, // Cave bridge
{84,700, 97, 30, 98, 20, 92, 4, 95, 1}, // Rubble-strewn cave
{93,280, 91,300, 92,270, 95, 7, 98, 10}, // Cave forest
{ 1,800, 93,600, 94, 10, 92, 10, 95, 4}, // Cave mushrooms
{ 1,700, 96,200, 95,100, 92, 10, 112, 5}, // Cave swamp
{ 3,600, 87, 90, 110, 20, 114, 6, 113, 2}, // Surface rocks
{ 3,200, 4,400, 111,250}, // Surface swamp
{ 3,200, 4,300, 112, 50, 113, 60, 114,100}, // Surface woods
{ 3,100, 4,250, 115,120, 114, 30, 112, 2}, // Surface shrubbery
{ 1, 25, 76, 15, 98,300, 97,280, 75, 5}, // Stalagmites
{ 1,150, 94, 80, 98, 20, 76, 20, 75, 5}, // Cave fumarole
{37,150, 76, 20, 75, 5}, // Surface fumarole
{ 1, 50, 93, 25, 94, 5, 98, 10, 95, 1}, // Cave camp
{ 3, 80, 4, 40, 115, 20, 114, 10, 112, 1}, // Surface camp
{ 1,600, 97, 50, 98, 80, 93, 10, 84, 10}, // Cave crops
{ 3,500, 4,500, 110, 50, 87, 10}, // Surface crops
}; // ter then odds then ter then odds ...
// These sets only include the ones that can have a road running through them
static const std::set<int> cave_arenas = {1,5,6,7,8,13};
static const std::set<int> surface_arenas = {0,2,9,10,11,12};
location stuff_ul;
arena = univ.scenario.ter_types[ter_type].combat_arena;
if(arena >= 1000) {
arena -= 1000;
// We take the terrain from the specified town, and nothing else.
// No preset creatures, items, special nodes, etc.
// Furthermore, if it's a large town, we drop the outer 8 tiles.
size_t town_size = univ.scenario.towns[arena]->max_dim;
int offset = max(0,town_size - 48);
rectangle town_bounds = univ.scenario.towns[arena]->in_town_rect;
// Just in case the town boundary is somehow larger than the town...
town_bounds.left = minmax(0,town_size - 1, town_bounds.left);
town_bounds.right = minmax(0,town_size - 1, town_bounds.right);
town_bounds.top = minmax(0,town_size - 1, town_bounds.top);
town_bounds.bottom = minmax(0,town_size - 1, town_bounds.bottom);
for(short i = 0; i < 48; i++)
for(short j = 0; j < 48; j++) {
// This test also accounts for small towns since the town boundary is never larger than the town
if(town_bounds.contains(i + offset, j + offset))
univ.town->terrain(i,j) = univ.scenario.towns[arena]->terrain(i + offset,j + offset);
else univ.town->terrain(i,j) = 90;
}
// The game uses the upper left corner to replace spaces that are blocked, so it needs to be set to something sensible.
// We'll take the terrain at the first entry location.
location fetch = univ.scenario.towns[arena]->start_locs[0];
univ.town->terrain(0,0) = univ.scenario.towns[arena]->terrain(fetch.x, fetch.y);
// And now we're done, so skip the rest of this function.
return;
}
for(short i = 0; i < 48; i++)
for(short j = 0; j < 48; j++) {
if((j <= 8) || (j >= 35) || (i <= 8) || (i >= 35))
univ.town->terrain(i,j) = 90;
else univ.town->terrain(i,j) = ter_base[arena];
}
for(short i = 0; i < 48; i++)
for(short j = 0; j < 48; j++)
for(short k = 0; k < 5; k++)
if((univ.town->terrain(i,j) != 90) && (get_ran(1,1,1000) < terrain_odds[arena][k * 2 + 1]))
univ.town->terrain(i,j) = terrain_odds[arena][k * 2];
univ.town->terrain(0,0) = ter_base[arena];
bool is_bridge = (arena == 3 || arena == 4);
if(arena == 3 || (is_road && surface_arenas.count(arena))) {
univ.town->terrain(0,0) = 83;
for(short i = (is_bridge ? 15 : 19); i < (is_bridge ? 26 : 23); i++)
for(short j = 9; j < 35; j++)
univ.town->terrain(i,j) = 83;
}
if(arena == 4 || (is_road && cave_arenas.count(arena))) {
univ.town->terrain(0,0) = 82;
for(short i = (is_bridge ? 15 : 19); i < (is_bridge ? 26 : 23); i++)
for(short j = 9; j < 35; j++)
univ.town->terrain(i,j) = 82;
}
if(arena == 18 || arena == 19) {
for(short i = 12; i < 15; i++)
for(short j = 9; j < 35; j++)
if(j != 17 && j != 26)
univ.town->terrain(i,j) = ter_type;
for(short i = 17; i < 20; i++)
for(short j = 9; j < 35; j++)
if(j != 17 && j != 26)
univ.town->terrain(i,j) = ter_type;
for(short i = 22; i < 25; i++)
for(short j = 9; j < 35; j++)
if(j != 17 && j != 26)
univ.town->terrain(i,j) = ter_type;
for(short i = 27; i < 30; i++)
for(short j = 9; j < 35; j++)
if(j != 17 && j != 26)
univ.town->terrain(i,j) = ter_type;
}
if(arena == 14 || arena == 15)
univ.town->terrain(0,0) = 75;
// Now place special lakes, etc.
if(arena == 2 || arena == 15)
for(short i = 0; i < 15; i++)
if(get_ran(1,0,5) == 1) {
stuff_ul = special_ter_locs[i];
for(short j = 0; j < 4; j++)
for(short k = 0; k < 4; k++)
univ.town->terrain(stuff_ul.x + j,stuff_ul.y + k) = mntn_pillar[k][j];
}
if(univ.town->terrain(0,0) == 0)
for(short i = 0; i < 15; i++)
if(get_ran(1,0,25) == 1) {
stuff_ul = special_ter_locs[i];
for(short j = 0; j < 4; j++)
for(short k = 0; k < 4; k++)
univ.town->terrain(stuff_ul.x + j,stuff_ul.y + k) = cave_pillar[k][j];
}
if(univ.town->terrain(0,0) == 0)
for(short i = 0; i < 15; i++)
if(get_ran(1,0,40) == 1) {
stuff_ul = special_ter_locs[i];
for(short j = 0; j < 4; j++)
for(short k = 0; k < 4; k++)
univ.town->terrain(stuff_ul.x + j,stuff_ul.y + k) = cave_lake[k][j];
}
if(univ.town->terrain(0,0) == 2)
for(short i = 0; i < 15; i++)
if(get_ran(1,0,40) == 1) {
stuff_ul = special_ter_locs[i];
for(short j = 0; j < 4; j++)
for(short k = 0; k < 4; k++)
univ.town->terrain(stuff_ul.x + j,stuff_ul.y + k) = surf_lake[k][j];
}
if(arena == 14)
for(short i = 0; i < 15; i++)
if(get_ran(1,0,5) == 1) {
stuff_ul = special_ter_locs[i];
for(short j = 0; j < 4; j++)
for(short k = 0; k < 4; k++)
univ.town->terrain(stuff_ul.x + j,stuff_ul.y + k) = cave_fume[k][j];
}
if(arena == 15)
for(short i = 0; i < 15; i++)
if(get_ran(1,0,5) == 1) {
stuff_ul = special_ter_locs[i];
for(short j = 0; j < 4; j++)
for(short k = 0; k < 4; k++)
univ.town->terrain(stuff_ul.x + j,stuff_ul.y + k) = surf_fume[k][j];
}
if(arena == 16) {
stuff_ul = loc(18,14);
for(short j = 0; j < 4; j++)
for(short k = 0; k < 4; k++)
univ.town->terrain(stuff_ul.x + j,stuff_ul.y + k) = cave_camp[k][j];
}
if(arena == 17) {
stuff_ul = loc(18,14);
for(short j = 0; j < 4; j++)
for(short k = 0; k < 4; k++)
univ.town->terrain(stuff_ul.x + j,stuff_ul.y + k) = surf_camp[k][j];
}
if(ter_base[ter_type] == 0) {
for(short i = 0; i < num_walls; i++) {
r1 = get_ran(1,0,3);
for(short j = 9; j < 35; j++)
switch(r1) {
case 0:
univ.town->terrain(j,8) = 6;
break;
case 1:
univ.town->terrain(8,j) = 9;
break;
case 2:
univ.town->terrain(j,35) = 12;
break;
case 3:
univ.town->terrain(32,j) = 15;
break;
}
}
if((univ.town->terrain(17,8) == 6) && (univ.town->terrain(8,20) == 9))
univ.town->terrain(8,8) = 21;
if((univ.town->terrain(32,20) == 15) && (univ.town->terrain(17,35) == 12))
univ.town->terrain(32,35) = 19;
if((univ.town->terrain(17,8) == 6) && (univ.town->terrain(32,20) == 15))
univ.town->terrain(32,8) = 32;
if((univ.town->terrain(8,20) == 9) && (univ.town->terrain(17,35) == 12))
univ.town->terrain(8,35) = 20;
}
if(ter_base[ter_type] == 36) {
for(short i = 0; i < num_walls; i++) {
r1 = get_ran(1,0,3);
for(short j = 9; j < 35; j++)
switch(r1) {
case 0:
univ.town->terrain(j,8) = 24;
break;
case 1:
univ.town->terrain(8,j) = 26;
break;
case 2:
univ.town->terrain(j,35) = 28;
break;
case 3:
univ.town->terrain(32,j) = 30;
break;
}
}
if((univ.town->terrain(17,8) == 6) && (univ.town->terrain(8,20) == 9))
univ.town->terrain(8,8) = 35;
if((univ.town->terrain(32,20) == 15) && (univ.town->terrain(17,35) == 12))
univ.town->terrain(32,35) = 33;
if((univ.town->terrain(17,8) == 6) && (univ.town->terrain(32,20) == 15))
univ.town->terrain(32,8) = 32;
if((univ.town->terrain(8,20) == 9) && (univ.town->terrain(17,35) == 12))
univ.town->terrain(8,35) = 34;
}
// TODO: Looks like I haven't yet implemented the generalized arenas
// make_town_trim(1);
}
void elim_monst(unsigned short which,short spec_a,short spec_b) {
if(!univ.party.sd_legit(spec_a,spec_b))
return;
if(PSD[spec_a][spec_b] > 0) {
for(short i = 0; i < univ.town.monst.size(); i++)
if(univ.town.monst[i].number == which) {
univ.town.monst[i].active = eCreatureStatus::DEAD;
}
}
}
//short print_mes; // 0 - no 1 - yes
void dump_gold(short print_mes) {
// Mildly kludgy gold check
if(univ.party.gold > MAX_GOLD) {
univ.party.gold = MAX_GOLD;
if(print_mes == 1) {
put_pc_screen();
add_string_to_buf("Excess gold dropped.");
print_buf();
}
}
if(univ.party.food > MAX_FOOD) {
univ.party.food = MAX_FOOD;
if(print_mes == 1) {
put_pc_screen();
add_string_to_buf("Excess food dropped.");
print_buf();
}
}
}
bool is_unlockable(location where) {
ter_num_t terrain = univ.town->terrain(where.x,where.y);
if(univ.scenario.ter_types[terrain].special == eTerSpec::UNLOCKABLE) {
return true;
}
return false;
}
void pick_lock(location where,short pc_num) {
ter_num_t terrain;
short r1;
bool will_break = false;
short unlock_adjust;
terrain = univ.town->terrain(where.x,where.y);
cInvenSlot which_item = univ.party[pc_num].has_abil_equip(eItemAbil::LOCKPICKS);
// This should no longer ever happen:
if(!which_item) {
add_string_to_buf(" Need lockpick equipped.");
return;
}
r1 = get_ran(1,1,100) + which_item->abil_strength * 7;
if(r1 < 75)
will_break = true;
r1 = get_ran(1,1,100) - 5 * univ.party[pc_num].stat_adj(eSkill::DEXTERITY) + univ.town.door_diff_adjust() * 7
- 5 * univ.party[pc_num].skill(eSkill::LOCKPICKING) - which_item->abil_strength * 7;
// Nimble?
if(univ.party[pc_num].traits[eTrait::NIMBLE])
r1 -= 8;
if(univ.party[pc_num].has_abil_equip(eItemAbil::THIEVING))
r1 = r1 - 12;
unlock_adjust = univ.scenario.ter_types[terrain].flag2;
if((unlock_adjust >= 5) || (r1 > (unlock_adjust * 15 + 30))) {
add_string_to_buf(" Didn't work.");
if(will_break) {
add_string_to_buf(" Pick breaks.");
univ.party[pc_num].remove_charge(which_item.slot);
if(stat_window == pc_num)
put_item_screen(stat_window);
}
play_sound(41);
}
else {
add_string_to_buf(" Door unlocked.");
play_sound(9);
univ.town->terrain(where.x,where.y) = univ.scenario.ter_types[terrain].flag1;
univ.town->door_unlocked.push_back(where);
}
}
void bash_door(location where,short pc_num) {
ter_num_t terrain;
short r1,unlock_adjust;
terrain = univ.town->terrain(where.x,where.y);
r1 = get_ran(1,1,100) - 15 * univ.party[pc_num].stat_adj(eSkill::STRENGTH) + univ.town.door_diff_adjust() * 4;
if(univ.scenario.ter_types[terrain].special != eTerSpec::UNLOCKABLE) {
add_string_to_buf(" Wrong terrain type.");
return;
}
unlock_adjust = univ.scenario.ter_types[terrain].flag2;
if(unlock_adjust >= 5 || r1 > (unlock_adjust * 15 + 40) || univ.scenario.ter_types[terrain].flag3 != 1) {
add_string_to_buf(" Didn't work.");
damage_pc(univ.party[pc_num],get_ran(1,1,4),eDamageType::SPECIAL,eRace::UNKNOWN);
}
else {
add_string_to_buf(" Lock breaks.");
play_sound(9);
univ.town->terrain(where.x,where.y) = univ.scenario.ter_types[terrain].flag1;
univ.town->door_unlocked.push_back(where);
}
}
void erase_town_specials() {
if((is_combat()) && (which_combat_type == 0))
return;
if(!is_town() && !is_combat())
return;
erase_completed_specials(*univ.town, [](location where){
univ.town.set_spot(where.x, where.y, false);
});
}
void erase_out_specials() {
for(short i = 0; i < 2; i++) {
for (short j = 0; j < 2; j++) {
if (!quadrant_legal(i, j)) continue;
auto& sector = univ.scenario.get_sector(univ.party.outdoor_corner.x + i, univ.party.outdoor_corner.y + j);
erase_hidden_towns(sector, i, j);
erase_completed_specials(sector, [&sector](location where){
sector.special_spot[where.x][where.y] = false;
});
}
}
}
void erase_hidden_towns(cOutdoors& sector, int quadrant_x, int quadrant_y) {
for(short tile_index = 0; tile_index < sector.city_locs.size(); tile_index++) {
auto city_loc = sector.city_locs[tile_index];
if(!univ.scenario.is_town_entrance_valid(city_loc) ||
!sector.is_on_map(city_loc) ||
!does_location_have_special(sector, city_loc, eTerSpec::TOWN_ENTRANCE)) {
continue;
}
auto town_pos_x = AREA_MEDIUM * quadrant_x + sector.city_locs[tile_index].x;
auto town_pos_y = AREA_MEDIUM * quadrant_y + sector.city_locs[tile_index].y;
auto spec_pos_x = sector.city_locs[tile_index].x;
auto spec_pos_y = sector.city_locs[tile_index].y;
auto area_index = sector.terrain[spec_pos_x][spec_pos_y];
if(!univ.scenario.towns[sector.city_locs[tile_index].spec]->can_find) {
univ.out[town_pos_x][town_pos_y] = univ.scenario.ter_types[area_index].flag1;
} else {
univ.out[town_pos_x][town_pos_y] = area_index;
}
}
}
void erase_completed_specials(cArea& sector, std::function<void(location)> clear_spot) {
for(auto tile_index = 0; tile_index < sector.special_locs.size(); tile_index++) {
if(sector.special_locs[tile_index].spec < 0 ||
sector.special_locs[tile_index].spec >= sector.specials.size())
continue;
auto sn = sector.specials[sector.special_locs[tile_index].spec];
if(univ.party.sd_legit(sn.sd1, sn.sd2) && PSD[sn.sd1][sn.sd2] == SDF_COMPLETE) {
auto completed_special = sector.special_locs[tile_index];
if(!sector.is_on_map(completed_special)) {
beep();
add_string_to_buf("Area corrupt. Problem fixed.");
print_nums(completed_special.x, completed_special.y, tile_index);
sector.special_locs[tile_index].spec = -1;
}
clear_spot(completed_special);
}
}
}
bool does_location_have_special(cOutdoors& sector, location loc, eTerSpec special) {
auto terrain_index = sector.terrain[loc.x][loc.y];
return univ.scenario.ter_types[terrain_index].special == special;
}
// TODO: I don't think we need this
void clear_map() {
rectangle map_world_rect(map_gworld());
// if(!map_visible) {
// return;
// }
// draw_map(mini_map(),11);
fill_rect(map_gworld(), map_world_rect, sf::Color::Black);
draw_map(true);
}
const short view_max_dim = 40;
rectangle minimap_view_rect() {
rectangle view_rect;
short total_size, party_loc_x, party_loc_y;
if((is_out()) || ((is_combat()) && (which_combat_type == 0)) ||
((overall_mode == MODE_TALKING) && (store_pre_talk_mode == MODE_OUTDOORS)) ||
((overall_mode == MODE_SHOPPING) && (store_pre_shop_mode == MODE_OUTDOORS))) {
total_size = 48;
party_loc_x = univ.party.loc_in_sec.x;
party_loc_y = univ.party.loc_in_sec.y;
}
else {
total_size = univ.town->max_dim;
party_loc_x = univ.party.town_loc.x;
party_loc_y = univ.party.town_loc.y;
}
if(total_size <= view_max_dim){
view_rect = {0, 0, total_size, total_size};
}else{
short scroll_max = total_size - view_max_dim;
view_rect.left = minmax(0, scroll_max, party_loc_x - view_max_dim / 2);
view_rect.top = minmax(0, scroll_max, party_loc_y - view_max_dim / 2);
view_rect.right = view_rect.left + view_max_dim;
view_rect.bottom = view_rect.top + view_max_dim;
}
return view_rect;
}
void draw_map(bool need_refresh, std::string tooltip_text) {
if(!map_visible) return;
pic_num_t pic;
rectangle the_rect;
rectangle map_world_rect(map_gworld());
location where;
location kludge;
rectangle draw_rect,orig_draw_rect = {0,0,6,6},ter_temp_from,base_source_rect = {0,0,12,12};
rectangle dlogpicrect = {6,6,42,42};
bool draw_pcs = true,out_mode;
rectangle area_to_draw_on = {29,47,269,287};
ter_num_t what_ter;
bool expl;
rectangle custom_from;
town_map_adj.x = 0;
town_map_adj.y = 0;
// We use view_rect as a camera to show only as much of the map as can fit in the window
rectangle view_rect = minimap_view_rect();
if(is_combat())
draw_pcs = false;
const char* title_string = "Your map:";
bool canMap = true;
if((is_combat()) && (which_combat_type == 0)) {
title_string = "No map in combat.";
canMap = false;
}else if((is_town() && univ.town->defy_mapping)) {
title_string = "This place defies mapping.";
canMap = false;
}
else if(need_refresh) {
// CHECKME: unsure, but on osx, if mainPtr().setActive() is not called,
// the following code does not update the gworld texture if we are
// called just after closing a dialog
mainPtr().setActive();
map_gworld().setActive();
// Now, if shopping or talking, just don't touch anything.
if((overall_mode == MODE_SHOPPING) || (overall_mode == MODE_TALKING))
view_rect.right = -1;
// Otherwise, clear to black first:
else
fill_rect(map_gworld(), map_world_rect, sf::Color::Black);
if((is_out()) ||
((is_combat()) && (which_combat_type == 0)) ||
((overall_mode == MODE_TALKING) && (store_pre_talk_mode == MODE_OUTDOORS)) ||
((overall_mode == MODE_SHOPPING) && (store_pre_shop_mode == MODE_OUTDOORS)))
out_mode = true;
else out_mode = false;
// While iterating the map, calculate the discovered extent of rooms/areas
const std::vector<info_rect_t>& area_desc = is_out() ? univ.out->area_desc : univ.town->area_desc;
std::vector<rectangle> known_area_rects(area_desc.size(), {std::numeric_limits<int>::max(),std::numeric_limits<int>::max(),std::numeric_limits<int>::min(),std::numeric_limits<int>::min()});
sf::Texture& small_ter_gworld = *ResMgr::graphics.get("termap");
for(where.x = view_rect.left; where.x < view_rect.right; where.x++)
for(where.y = view_rect.top; where.y < view_rect.bottom; where.y++) {
draw_rect = orig_draw_rect;
draw_rect.offset(6 * (where.x - view_rect.left), 6 * (where.y - view_rect.top));
if(out_mode)
what_ter = univ.out[where.x + 48 * univ.party.i_w_c.x][where.y + 48 * univ.party.i_w_c.y];
else what_ter = univ.town->terrain(where.x,where.y);
ter_temp_from = base_source_rect;
if(out_mode)
expl = univ.out.out_e[where.x + 48 * univ.party.i_w_c.x][where.y + 48 * univ.party.i_w_c.y];
else expl = is_explored(where.x,where.y);
// Draw tile
if(expl != 0) {
pic = univ.scenario.ter_types[what_ter].map_pic;
bool drawLargeIcon = false;
if(pic == NO_PIC) {
pic = univ.scenario.ter_types[what_ter].picture;
drawLargeIcon = true;
}
if(pic >= 1000) {
if(spec_scen_g) {
//print_nums(0,99,pic);
std::shared_ptr<const sf::Texture> src_gw;
if(drawLargeIcon) {
pic = pic % 1000;
graf_pos_ref(src_gw, custom_from) = spec_scen_g.find_graphic(pic);
rect_draw_some_item(*src_gw,custom_from,map_gworld(),draw_rect);
} else {
graf_pos_ref(src_gw, custom_from) = spec_scen_g.find_graphic(pic % 1000);
custom_from.right = custom_from.left + 12;
custom_from.bottom = custom_from.top + 12;
pic /= 1000; pic--;
custom_from.offset((pic / 3) * 12, (pic % 3) * 12);
rect_draw_some_item(*src_gw, custom_from, map_gworld(), draw_rect);
}
}
} else if(drawLargeIcon) {
if(pic >= 960) {
custom_from = calc_rect(4 * ((pic - 960) / 5),(pic - 960) % 5);
rect_draw_some_item(*ResMgr::graphics.get("teranim"), custom_from, map_gworld(), draw_rect);
} else {
int which_sheet = pic / 50;
auto src_gw = &ResMgr::graphics.get("ter" + std::to_string(1 + which_sheet));
pic %= 50;
custom_from = calc_rect(pic % 10, pic / 10);
rect_draw_some_item(*src_gw, custom_from, map_gworld(), draw_rect);
}
} else {
if(univ.scenario.ter_types[what_ter].picture < 960)
ter_temp_from.offset(12 * (univ.scenario.ter_types[what_ter].picture % 20),
12 * (univ.scenario.ter_types[what_ter].picture / 20));
else ter_temp_from.offset(12 * 20,
12 * (univ.scenario.ter_types[what_ter].picture - 960));
rect_draw_some_item(small_ter_gworld,ter_temp_from,map_gworld(),draw_rect);
}
if(is_out() ? univ.out->roads[where.x][where.y] : univ.town.is_road(where.x,where.y)) {
draw_rect.inset(1,1);
rect_draw_some_item(*ResMgr::graphics.get("fields"),{80,28,84,32},map_gworld(),draw_rect);
}
for(int i = 0; i < area_desc.size(); ++i){
location real_where = where;
if(is_out()) real_where = local_to_global(where);
info_rect_t area = area_desc[i];
rectangle& known_bounds = known_area_rects[i];
// tile is in an area rectangle. see if it extends the party's known bounds of the area
if(!area.empty() && area.contains(where) && !is_blocked(real_where, false)){
if(where.x < known_bounds.left) known_bounds.left = where.x;
if(where.y < known_bounds.top) known_bounds.top = where.y;
if(where.x + 1 > known_bounds.right) known_bounds.right = where.x + 1;
if(where.y + 1 > known_bounds.bottom) known_bounds.bottom = where.y + 1;
}
}
}
}
// Draw known extent of area rectangles
for(const rectangle& known_bounds : known_area_rects){
if(!known_bounds.empty()){
draw_rect.top = 6 * (known_bounds.top - view_rect.top);
draw_rect.left = 6 * (known_bounds.left - view_rect.left);
draw_rect.bottom = 6 * (known_bounds.bottom - view_rect.top);
draw_rect.right = 6 * (known_bounds.right - view_rect.left);
frame_rect(map_gworld(), draw_rect, Colours::RED);
}
}
map_gworld().display();
// this stops flickering if the display time is too long
glFlush();
}
mini_map().setActive(false);
// Now place terrain map gworld
TextStyle style;
style.font = FONT_BOLD;
style.pointSize = 10;;
the_rect = rectangle(mini_map());
tileImage(mini_map(), the_rect, bg[4]);
cParentless mapWin(mini_map());
cPict theGraphic(mapWin);
theGraphic.setBounds(dlogpicrect);
theGraphic.setPict(21, PIC_DLOG);
theGraphic.setFormat(TXT_FRAME, FRM_NONE);
theGraphic.draw();
style.colour = sf::Color::White;
style.lineHeight = 12;
win_draw_string(mini_map(), map_title_rect,title_string,eTextMode::WRAP,style);
win_draw_string(mini_map(), map_bar_rect,"(Hit Escape to close.)",eTextMode::WRAP,style);
if(canMap) {
rect_draw_some_item(map_gworld().getTexture(),the_rect,mini_map(),area_to_draw_on);
auto mark_loc = [view_rect, &draw_rect, area_to_draw_on](location where, sf::Color inner, sf::Color outer, eTerSpec if_spec = eTerSpec::NONE) -> void {
location real_where = where;
if(is_out()) real_where = local_to_global(where);
if((is_explored(real_where.x,real_where.y)) &&
((where.x >= view_rect.left) && (where.x < view_rect.right)
&& where.y >= view_rect.top && where.y < view_rect.bottom)){
// if if_spec is specified, make sure the terrain on the spot has the given special
if(if_spec != eTerSpec::NONE){
ter_num_t ter = is_out() ? univ.out[real_where] : univ.town->terrain(real_where.x, real_where.y);
if(univ.scenario.ter_types[ter].special != if_spec) return;
}
draw_rect.left = area_to_draw_on.left + 6 * (where.x - view_rect.left);
draw_rect.top = area_to_draw_on.top + 6 * (where.y - view_rect.top);
draw_rect.right = draw_rect.left + 6;
draw_rect.bottom = draw_rect.top + 6;
fill_rect(mini_map(), draw_rect, inner);
frame_circle(mini_map(), draw_rect, outer);
}
};
// Now place PCs and monsters
if(draw_pcs) {
if((is_town()) && (univ.party.status[ePartyStatus::DETECT_LIFE] > 0))
for(short i = 0; i < univ.town.monst.size(); i++)
if(univ.town.monst[i].is_alive()) {
where = univ.town.monst[i].cur_loc;
mark_loc(where, Colours::GREEN, Colours::BLUE);
}
if((overall_mode != MODE_SHOPPING) && (overall_mode != MODE_TALKING)) {
where = (is_town()) ? univ.party.town_loc : global_to_local(univ.party.out_loc);
mark_loc(where, Colours::RED, Colours::BLACK);
}
}
// Draw signs
const std::vector<sign_loc_t>& sign_locs = is_out() ? univ.out->sign_locs : univ.town->sign_locs;
for(sign_loc_t sign : sign_locs){
mark_loc(sign, Colours::EMPTY, Colours::YELLOW, eTerSpec::IS_A_SIGN);
}
// Draw town entrances
if(is_out()){
const std::vector<spec_loc_t>& city_locs = univ.out->city_locs;
// TODO don't draw hidden ones
for(spec_loc_t city : city_locs){
mark_loc(city, Colours::EMPTY, Colours::GREEN, eTerSpec::TOWN_ENTRANCE);
}
}
// Draw vehicles
for(auto& boat : univ.party.boats) {
if(!vehicle_is_here(boat)) continue;
mark_loc(boat.loc, Colours::MAROON, Colours::BLACK);
}
for(auto& horse : univ.party.horses) {
if(!vehicle_is_here(horse)) continue;
mark_loc(horse.loc, Colours::MAROON, Colours::BLACK);
}
}
win_draw_string(mini_map(), map_tooltip_rect,tooltip_text,eTextMode::WRAP,style);
mini_map().setActive(false);
mini_map().display();
// Now exit gracefully
mainPtr().setActive();
}
bool is_door(location destination) {
if(univ.scenario.ter_types[univ.town->terrain(destination.x,destination.y)].special == eTerSpec::UNLOCKABLE ||
univ.scenario.ter_types[univ.town->terrain(destination.x,destination.y)].special == eTerSpec::CHANGE_WHEN_STEP_ON)
return true;
return false;
}
extern void close_map(bool record);
void display_map() {
if(recording){
record_action("display_map", "");
}
if(!prime_time()) {
ASB("Map: " + FINISH_FIRST);
print_buf();
return;
}
// Hide the automap if it's already visible
if(map_visible){
close_map(false);
return;
}
give_help(62,0);
rectangle the_rect;
rectangle dlogpicrect = {6,6,42,42};
mini_map().setVisible(true);
map_visible = true;
draw_map(true);
makeFrontWindow(mainPtr(), mini_map());
set_cursor(sword_curs);
}
void check_done() {
}
bool quadrant_legal(short i, short j) {
if(univ.party.outdoor_corner.x + i >= univ.scenario.outdoors.width())
return false;
if(univ.party.outdoor_corner.y + j >= univ.scenario.outdoors.height())
return false;
if(univ.party.outdoor_corner.x + i < 0)
return false;
if(univ.party.outdoor_corner.y + j < 0)
return false;
return true;
}
void push_thing(ePushableThing type, location pusher_loc, location thing_loc) {
location to_loc = push_loc(pusher_loc, thing_loc);
move_thing(type, thing_loc, to_loc);
}
void move_thing(ePushableThing type, location from_loc, location to_loc) {
// Get the thing out of its spot
switch(type){
case PUSH_CRATE:
univ.town.set_crate(from_loc.x, from_loc.y, false);
break;
case PUSH_BARREL:
univ.town.set_barrel(from_loc.x, from_loc.y, false);
break;
case PUSH_BLOCK:
univ.town.set_block(from_loc.x, from_loc.y, false);
break;
}
// If it wasn't deleted, put it in the new spot
if(to_loc.x > 0){
switch(type){
case PUSH_CRATE:
univ.town.set_crate(to_loc.x, to_loc.y, true);
break;
case PUSH_BARREL:
univ.town.set_barrel(to_loc.x, to_loc.y, true);
break;
case PUSH_BLOCK:
univ.town.set_block(to_loc.x, to_loc.y, true);
break;
}
}
// Move items inside crate or barrel
if(type == PUSH_CRATE || type == PUSH_BARREL){
for(short i = 0; i < univ.town.items.size(); i++)
if(univ.town.items[i].variety != eItemType::NO_ITEM && univ.town.items[i].item_loc == from_loc
&& univ.town.items[i].contained && univ.town.items[i].held)
univ.town.items[i].item_loc = to_loc;
}
}