#include #include #include #include #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 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 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 cave_arenas = {1,5,6,7,8,13}; static const std::set 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, [§or](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 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& area_desc = is_out() ? univ.out->area_desc : univ.town->area_desc; std::vector known_area_rects(area_desc.size(), {std::numeric_limits::max(),std::numeric_limits::max(),std::numeric_limits::min(),std::numeric_limits::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 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_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& 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; } }