#include #include #include #include #include #include "boe.global.hpp" #include "universe/universe.hpp" #include "boe.graphics.hpp" #include "boe.newgraph.hpp" #include "boe.graphutil.hpp" #include "boe.monster.hpp" #include "boe.locutils.hpp" #include "boe.text.hpp" #include "gfx/render_image.hpp" #include "gfx/render_shapes.hpp" #include "gfx/render_text.hpp" #include "gfx/tiling.hpp" #include "sounds.hpp" #include "mathutil.hpp" #include "dialogxml/widgets/button.hpp" #include "tools/enum_map.hpp" #include "tools/drawable_manager.hpp" #include "boe.party.hpp" #include "boe.town.hpp" #include "boe.items.hpp" #include "boe.dlgutil.hpp" #include "boe.infodlg.hpp" #include "boe.ui.hpp" #include "dialogxml/widgets/scrollbar.hpp" #include "fileio/resmgr/res_image.hpp" #include "boe.menus.hpp" #include "tools/winutil.hpp" #include "tools/prefs.hpp" #include "replay.hpp" #include #ifndef MSBUILD_GITREV #include "tools/gitrev.hpp" #endif extern eItemWinMode stat_window; extern eGameMode overall_mode; extern short current_spell_range; extern bool party_in_memory; extern bool flushingInput; extern bool cartoon_happening, fog_lifted; extern short anim_step; extern effect_pat_type current_pat; extern location center; extern short which_combat_type; extern bool monsters_going,boom_anim_active; extern sf::Image spell_pict; extern short current_ground; extern short num_targets_left; extern location spell_targets[8]; extern std::shared_ptr text_sbar,item_sbar,shop_sbar; extern std::shared_ptr done_btn, help_btn; extern const rectangle sbar_rect,item_sbar_rect,shop_sbar_rect; extern rectangle startup_top; extern rectangle talk_area_rect, word_place_rect; extern location store_anim_ul; extern long register_flag; extern long ed_flag,ed_key; extern short fast_bang; extern tessel_ref_t bg[]; extern cUniverse univ; extern cCustomGraphics spec_scen_g; bool map_visible = false; extern std::string save_talk_str1, save_talk_str2; extern cDrawableManager drawable_mgr; extern void close_map(bool record = false); extern sf::View mainView; extern location mouse_window_coords(); extern bool mouse_to_terrain_coords(location& out_loc, bool relative); rectangle menuBarRect; Region originalGrayRgn, newGrayRgn, underBarRgn; sf::View mainView; extern enum_map(eGuiArea, rectangle) win_to_rects; // 0 - title 1 - button 2 - credits 3 - base button rectangle startup_from[4] = {{0,0,274,602},{274,0,322,301},{0,301,67,579},{274,301,314,341}}; extern enum_map(eStartButton, rectangle) startup_button; rectangle top_left_rec = {0,0,36,28}; short which_graphic_index[6] = {50,50,50,50,50,50}; char combat_graphics[5] = {28,29,36,79,2}; short debug_nums[6] = {0,0,0,0,0,0}; char light_area[13][13]; char unexplored_area[13][13]; // Declare the graphics sf::RenderTexture& pc_stats_gworld() { static sf::RenderTexture instance; return instance; } sf::RenderTexture& item_stats_gworld() { static sf::RenderTexture instance; return instance; } sf::RenderTexture& text_area_gworld() { static sf::RenderTexture instance; return instance; } sf::RenderTexture& terrain_screen_gworld() { static sf::RenderTexture instance; return instance; } sf::RenderTexture& text_bar_gworld() { static sf::RenderTexture instance; return instance; } sf::RenderTexture& map_gworld() { static sf::RenderTexture instance; return instance; } sf::RenderTexture& talk_gworld() { static sf::RenderTexture instance; return instance; } sf::RenderWindow& mainPtr() { static sf::RenderWindow instance; return instance; } sf::RenderWindow& mini_map() { static sf::RenderWindow instance; return instance; } bool has_run_anim = false,currently_loading_graphics = false; rectangle main_win_rect = {0,0,410,250}; rectangle main_win2_source_rect = {0,0,410,265}; rectangle main_win2_rect = {0,250,410,515}; rectangle tiny_world_1_source_rect = {0,0,190,145}, tiny_world_1_rect = {195,242,385,475}; rectangle share_mess_source_rect = {0,0,59,120}, share_mess_rect = {120,384,179,504}; rectangle start_buttons_source_rect = {0,0,186,190}, start_buttons_rect = {214,30,400,220}; // Array to store which spots have been seen. Time-saver for drawing fields char spot_seen[9][9]; char anim_str[60]; location anim_str_loc; extern tessel_ref_t bw_pats[6]; extern short combat_posing_monster , current_working_monster ; // 0-5 PC 100 + x - monster x bool supressing_some_spaces = false; location ok_space[4] = {loc(),loc(),loc(),loc()}; sf::Image hold_pict; void adjust_window_mode() { sf::ContextSettings winSettings; winSettings.stencilBits = 1; sf::VideoMode desktop = sf::VideoMode::getDesktopMode(); hideMenuBar(); double ui_scale = get_ui_scale(); if(ui_scale < 0.1) ui_scale = 1.0; int width = boe_width * ui_scale, height = boe_height * ui_scale; // TODO: Make display_mode an enum // 0 - center 1- ul 2 - ur 3 - dl 4 - dr 5 - small win int mode = get_int_pref("DisplayMode"); if(mode == 5) { // Increase window height to make room for the menubar on Linux int winHeight = height; winHeight += os_specific_y_offset(); mainPtr().create(sf::VideoMode(width, winHeight, 32), "Blades of Exile", sf::Style::Titlebar | sf::Style::Close, winSettings); // Center the small window on the desktop int win_x = get_int_pref("MainWindowX", static_cast((desktop.width - width) / 2)); int win_y = get_int_pref("MainWindowY", static_cast((desktop.height - height) / 2)); mainPtr().setPosition({win_x, win_y}); } else { mainPtr().create(desktop, "Blades of Exile", sf::Style::None, winSettings); mainPtr().setPosition({0,0}); } // Initialize the view mainView.setSize(width, height); mainView.setCenter(width / 2, height / 2); sf::RenderWindow& p = mainPtr(); sf::FloatRect mainPort = compute_viewport(p, mode, ui_scale, width, height); mainView.setViewport(mainPort); #ifndef __APPLE__ // This overrides Dock icon on OSX, which isn't what we want at all const ImageRsrc& icon = ResMgr::graphics.get("icon", true); mainPtr().setIcon(icon->getSize().x, icon->getSize().y, icon->copyToImage().getPixelsPtr()); #endif #ifdef SFML_SYSTEM_WINDOWS // On windows, the file dialogs are constructed with mainPtr() as a parent, // so they need to be reconstructed after mainPtr() is. init_fileio(); #endif init_menubar(); adjust_window_for_menubar(mode, width, height); showMenuBar(); } // Viewport is applied to the View and designates where in the OS window the source of the View is drawn. sf::FloatRect compute_viewport(const sf::RenderWindow& mainPtr, int mode, float ui_scale, float width, float height) { sf::FloatRect viewport; // Dimensions of the OS window. rectangle windRect { mainPtr }; // Width and height: how large the viewport is. They seem to be calculated // in terms of *source* dimensions, with values above 1 resulting in an upscale. viewport.width = ui_scale * width / windRect.width(); viewport.height = ui_scale * height / windRect.height(); // Buffer in pixels between ui edge and window edge. There seem to be // implicit (builtin) buffers the top and bottom of the UI so this is just for the sides. const int extra_horizontal_buffer { 7 }; // Left and top: where the viewport is. // Left and top seem to be in terms *target* dimensions, // so top = 0.5 with window height 450 means 225 px offset from the top. if(mode == 0) { // Fullscreen centered viewport.left = float((windRect.width() - width) / 2) / windRect.width(); viewport.top = float((windRect.height() - height - os_specific_y_offset()) / 2)/ windRect.height(); } else if(mode == 1) { // Fullscreen top left viewport.left = float(extra_horizontal_buffer) / windRect.width(); viewport.top = float(os_specific_y_offset()) / windRect.height(); } else if(mode == 2) { // Fullscreen top right viewport.left = float(windRect.right - width - extra_horizontal_buffer) / windRect.width(); viewport.top = float(os_specific_y_offset()) / windRect.height(); } else if(mode == 3) { // Fullscreen bottom left viewport.left = float(extra_horizontal_buffer) / windRect.width(); // DIRTY HACK: windRect in fullscreen modes gives us the entire display, but at the same time // there could be a windows taskbar / mac os dock / xfce taskbar / etc that consumes a part // of that display, and that we do not know size of. So we need to account for that somehow, // so we add 28 more pixels (this was the amount in the previous version of this code). viewport.top = float(windRect.bottom - height - os_specific_y_offset() - 28) / windRect.height(); } else if(mode == 4) { // Fullscreen bottom right viewport.left = float(windRect.right - width - extra_horizontal_buffer) / windRect.width(); // DIRTY HACK: same as for mode 3 viewport.top = float(windRect.bottom - height - os_specific_y_offset() - 28) / windRect.height(); } else if(mode == 5) { // Small windowed viewport.left = 0; viewport.top = float(os_specific_y_offset()) / windRect.height(); } return viewport; } void init_startup() { // Preload the main startup images ResMgr::graphics.get("startup", true); ResMgr::graphics.get("startbut", true); ResMgr::graphics.get("startanim", true); } void draw_startup(short but_type) { sf::Texture& startup_gworld = *ResMgr::graphics.get("startup", true); rect_draw_some_item(startup_gworld,startup_from[0],mainPtr(),startup_top); for(auto btn : startup_button.keys()) { rect_draw_some_item(startup_gworld,startup_from[1],mainPtr(),startup_button[btn]); draw_start_button(btn,but_type); } draw_startup_anim(false); draw_startup_stats(); } void draw_startup_anim(bool advance) { static short startup_anim_pos = 43; // was a global variable, but since it's only used in this function I moved it here rectangle anim_to = {4,1,44,276},anim_from; rectangle anim_size = {0,0,48,301}; anim_from = anim_to; anim_from.offset(-1,-4 + startup_anim_pos); auto scroll_sprite = *ResMgr::graphics.get("startanim",true); if(advance) startup_anim_pos = (startup_anim_pos + 1) % scroll_sprite.getSize().y; rect_draw_some_item(*ResMgr::graphics.get("startbut",true),anim_size,mainPtr(),startup_button[STARTBTN_SCROLL]); anim_to.offset(startup_button[STARTBTN_SCROLL].left, startup_button[STARTBTN_SCROLL].top); rect_draw_some_item(scroll_sprite,anim_from,mainPtr(),anim_to,sf::BlendAlpha); } void draw_startup_stats() { rectangle from_rect,to_rect,party_to = {0,0,36,28},pc_rect,frame_rect; TextStyle style; style.font = FONT_DUNGEON; style.pointSize = 24; to_rect = startup_top; to_rect.offset(20 - 18, 35); style.colour = sf::Color::White; style.lineHeight = 18; if(!party_in_memory) { style.pointSize = 20; to_rect.offset(193,40); win_draw_string(mainPtr(),to_rect,"No Party in Memory",eTextMode::WRAP,style); } else { frame_rect = startup_top; frame_rect.inset(50,50); frame_rect.top += 30; frame_rect.left += 18; frame_rect.offset(-9,10); // TODO: Maybe I should rename that variable ::frame_rect(mainPtr(), frame_rect, sf::Color::White); to_rect.offset(221,37); win_draw_string(mainPtr(),to_rect,"Your party:",eTextMode::WRAP,style); style.pointSize = 12; style.font = FONT_BOLD; for(short i = 0; i < 6; i++) { pc_rect = startup_top; pc_rect.left += 18; pc_rect.right = pc_rect.left + 300; pc_rect.bottom = pc_rect.top + 79; pc_rect.offset(60 + 232 * (i / 3) - 9,95 + 45 * (i % 3)); if(univ.party[i].main_status != eMainStatus::ABSENT) { to_rect = party_to; to_rect.offset(pc_rect.left,pc_rect.top); pic_num_t pic = univ.party[i].which_graphic; // TODO This doesn't make sense. If we're in the startup menu, there are no scenario custom graphics. // Doesn't this need to find it saved in the party? if(pic >= 1000) { std::shared_ptr gw; graf_pos_ref(gw, from_rect) = spec_scen_g.find_graphic(pic % 1000, pic >= 10000); rect_draw_some_item(*gw,from_rect,mainPtr(),to_rect,sf::BlendAlpha); } else if(pic >= 100) { pic -= 100; // Note that we assume it's a 1x1 graphic. // PCs can't be larger than that, but we leave it to the scenario designer to avoid assigning larger graphics. from_rect = get_monster_template_rect(pic, 0, 0); int which_sheet = m_pic_index[pic].i / 20; sf::Texture& monst_gworld = *ResMgr::graphics.get("monst" + std::to_string(1 + which_sheet)); rect_draw_some_item(monst_gworld,from_rect,mainPtr(),to_rect,sf::BlendAlpha); } else { from_rect = calc_rect(2 * (pic / 8), pic % 8); sf::Texture& pc_gworld = *ResMgr::graphics.get("pcs"); rect_draw_some_item(pc_gworld,from_rect,mainPtr(),to_rect,sf::BlendAlpha); } style.pointSize = 14; pc_rect.offset(35,0); rectangle name_rect = pc_rect; name_rect.width() = frame_rect.width() * 0.4; win_draw_string(mainPtr(),name_rect,univ.party[i].name,eTextMode::ELLIPSIS,style); to_rect.offset(pc_rect.left + 8,pc_rect.top + 8); } style.pointSize = 12; pc_rect.offset(12,16); std::string status = "Level " + std::to_string(univ.party[i].level); switch(univ.party[i].main_status) { case eMainStatus::ALIVE: switch(univ.party[i].race) { case eRace::HUMAN: status += " Human"; break; case eRace::NEPHIL: status += " Nephilim"; break; case eRace::SLITH: status += " Slithzerikai"; break; case eRace::VAHNATAI: status += " Vahnatai"; break; case eRace::REPTILE: status += " Reptile"; break; case eRace::BEAST: status += " Beast"; break; case eRace::IMPORTANT: status += " V.I.P."; break; case eRace::MAGE: status += " Human Mage"; break; case eRace::PRIEST: status += " Human Priest"; break; case eRace::HUMANOID: status += " Humanoid"; break; case eRace::DEMON: status += " Demon"; break; case eRace::UNDEAD: status += " Undead"; break; case eRace::GIANT: status += " Giant"; break; case eRace::SLIME: status += " Slime"; break; case eRace::STONE: status += " Golem"; break; case eRace::BUG: status += " Bug"; break; case eRace::DRAGON: status += " Dragon"; break; case eRace::MAGICAL: status += " Magical Creature"; break; case eRace::PLANT: status += " Plant"; break; case eRace::BIRD: status += " Bird"; break; default: status += " *ERROR INVALID RACE*"; break; } win_draw_string(mainPtr(),pc_rect,status,eTextMode::WRAP,style); pc_rect.offset(0,13); status = "Health " + std::to_string(univ.party[i].max_health); status += ", Spell pts. " + std::to_string(univ.party[i].max_sp); break; case eMainStatus::DEAD: status = "Dead"; break; case eMainStatus::DUST: status = "Dust"; break; case eMainStatus::STONE: status = "Stone"; break; case eMainStatus::FLED: status = "Fled"; break; default: //absent, and all variations thereof; do nothing status.clear(); break; } if(!status.empty()) win_draw_string(mainPtr(),pc_rect,status,eTextMode::WRAP,style); } } std::ostringstream sout; sout << "Created 1997, Free Open Source, v" << oboeVersionString(); #if defined(GIT_REVISION) && defined(GIT_TAG_REVISION) if(strcmp(GIT_REVISION, GIT_TAG_REVISION) != 0) { sout << " [" << GIT_REVISION << "]"; } #endif std::string copyright = sout.str(); style.font = FONT_BOLD; style.pointSize = 10; pc_rect = startup_top; pc_rect.offset(5,5); pc_rect.top = pc_rect.bottom - 30; pc_rect.left = pc_rect.right - string_length(copyright, style) - 32; // Windows replaced it with "That is not dead which can eternally lie..." - I don't think that's quite appropriate though. win_draw_string(mainPtr(), pc_rect, copyright, eTextMode::WRAP, style); if(univ.debug_mode){ pc_rect = startup_top; pc_rect.offset(5,5); pc_rect.top = pc_rect.bottom - 30; pc_rect.left = pc_rect.left + 32; win_draw_string(mainPtr(), pc_rect, "Debug Mode: On", eTextMode::WRAP, style); } } void draw_start_button(eStartButton which_position,short which_button) { rectangle from_rect,to_rect; const char *button_labels[MAX_eStartButton]; for(int i = 0; i < MAX_eStartButton; ++i){ button_labels[i] = startup_button_names[i].c_str(); } // The 0..65535 version of the blue component was 14472; the commented version was 43144431 sf::Color base_color = {0,0,57}; from_rect = startup_from[3]; from_rect.offset((which_button > 0) ? 40 : 0,0); to_rect = startup_button[which_position]; to_rect.left += 4; to_rect.top += 4; to_rect.right = to_rect.left + 40; to_rect.bottom = to_rect.top + 40; rect_draw_some_item(*ResMgr::graphics.get("startup",true),from_rect,mainPtr(),to_rect); TextStyle style; style.font = FONT_DUNGEON; style.pointSize = 24; to_rect = startup_button[which_position]; //to_rect.left += 80; to_rect.offset(10, 5); if(which_button == 5) which_button = 4; // In the 0..65535 range, this was 14472 + (12288 * which_button) base_color.b += (48 * which_button); style.colour = base_color; style.lineHeight = 18; win_draw_string(mainPtr(),to_rect,button_labels[which_position],eTextMode::CENTRE,style); } bool arrow_button_click(rectangle button_rect, cFramerateLimiter* fps_limiter) { if(recording){ // In a replay, this action is purely cosmetic, for playing the animation and sound // accompanying a click on a button whose real action is recorded afterward record_action("arrow_button_click", boost::lexical_cast(button_rect)); } // Draw depressed: mainPtr().setActive(); clip_rect(mainPtr(), button_rect); refresh_stat_areas(1); mainPtr().display(); // Mini-event loop so that the click doesn't happen until releasing the mouse button: bool done = false, clicked = false, depressed = true; if(replaying) clicked = true; else{ sf::Event e; while(!done){ refresh_stat_areas(depressed ? 1 : 0); while(pollEvent(mainPtr(), e)){ if(e.type == sf::Event::MouseButtonReleased){ done = true; location clickPos = mouse_window_coords(); clicked = button_rect.contains(clickPos); depressed = false; break; } else if(e.type == sf::Event::MouseMoved){ location toPos = mouse_window_coords(); depressed = button_rect.contains(toPos); } } fps_limiter->frame_finished(); } } undo_clip(mainPtr()); play_sound(37, time_in_ticks(5)); refresh_stat_areas(0); return clicked; } void reload_startup() { close_map(); init_startup(); text_sbar->hide(); item_sbar->hide(); shop_sbar->hide(); done_btn->hide(); help_btn->hide(); } void end_startup() { load_main_screen(); text_sbar->show(); item_sbar->show(); } static void loadImageToRenderTexture(sf::RenderTexture& tex, std::string imgName) { // Clear scale-aware text stored for the previous texture instance: clear_scale_aware_text(tex); sf::Texture& temp_gworld = *ResMgr::graphics.get(imgName); rectangle texrect(temp_gworld); tex.create(texrect.width(), texrect.height()); rect_draw_some_item(temp_gworld, texrect, tex, texrect, sf::BlendNone); } void load_main_screen() { // Preload the main game interface images ResMgr::graphics.get("invenbtns"); loadImageToRenderTexture(terrain_screen_gworld(), "terscreen"); loadImageToRenderTexture(pc_stats_gworld(), "statarea"); loadImageToRenderTexture(item_stats_gworld(), "inventory"); loadImageToRenderTexture(text_area_gworld(), "transcript"); loadImageToRenderTexture(text_bar_gworld(), "textbar"); ResMgr::graphics.get("buttons"); } void redraw_screen(int refresh) { // We may need to update some of the offscreen textures if(overall_mode != MODE_STARTUP) { if(refresh & REFRESH_TERRAIN) draw_terrain(1); if(refresh & REFRESH_STATS) put_pc_screen(); if(refresh & REFRESH_INVEN) put_item_screen(stat_window); if(refresh & REFRESH_TRANS) print_buf(); } // Temporarily switch to the original view to fill in the background mainPtr().setView(mainPtr().getDefaultView()); put_background(); mainPtr().setView(mainView); switch(overall_mode) { case MODE_STARTUP: draw_startup(0); break; case MODE_TALKING: if(refresh & REFRESH_DLOG) place_talk_str(save_talk_str1, save_talk_str2, 0, rectangle()); refresh_talking(); break; case MODE_SHOPPING: if(refresh & REFRESH_DLOG) draw_shop_graphics(false,false,{0,0,0,0}); refresh_shopping(); break; default: redraw_terrain(); if(refresh & REFRESH_BAR){ draw_text_bar(std::make_pair("", "")); draw_text_bar(); } refresh_text_bar(); UI::toolbar.draw(mainPtr()); break; } if(overall_mode == MODE_COMBAT) frame_active_pc(univ.current_pc().combat_pos); if(overall_mode == MODE_FANCY_TARGET) draw_targets(center); if(overall_mode != MODE_STARTUP) { if(!is_out()) draw_targeting_line(); refresh_stat_areas(0); } done_btn->draw(); help_btn->draw(); drawable_mgr.draw_all(); extern location get_cur_direction(); get_cur_direction(); mainPtr().display(); } void put_background() { tessel_ref_t bg_pict; if(overall_mode == MODE_STARTUP) bg_pict = bg[4]; else if(overall_mode == MODE_RESTING) bg_pict = bg[4]; else if(is_out()) { if(univ.out->bg_out >= 0) bg_pict = bg[univ.out->bg_out]; else bg_pict = bg[univ.scenario.bg_out]; } else if(is_combat()) { if(which_combat_type == 1 && univ.town->bg_fight >= 0) // TODO: Verify this means town combat bg_pict = bg[univ.town->bg_fight]; else if(univ.out->bg_fight >= 0) bg_pict = bg[univ.out->bg_fight]; else bg_pict = bg[univ.scenario.bg_fight]; } else if(univ.town->lighting_type != LIGHT_NORMAL) { if(univ.town->bg_town >= 0) bg_pict = bg[univ.town->bg_town]; else if(univ.out->bg_dungeon >= 0) bg_pict = bg[univ.out->bg_dungeon]; else bg_pict = bg[univ.scenario.bg_dungeon]; } else { if(univ.town->bg_town >= 0) bg_pict = bg[univ.town->bg_town]; else if(univ.out->bg_town >= 0) bg_pict = bg[univ.out->bg_town]; else bg_pict = bg[univ.scenario.bg_town]; } tileImage(mainPtr(), rectangle(mainPtr()), bg_pict); } std::pair text_bar_text() { std::string text = ""; std::string right_text = ""; text = get_location(); if((is_combat()) && (univ.cur_pc < 6) && !monsters_going) { std::ostringstream sout; cPlayer& current_pc = univ.current_pc(); sout << current_pc.name << " (ap: " << current_pc.ap << ')'; // Spellcasters print a hint for recasting. // There's not enough space to print 2 hints for dual-casters, // so just handle the last type cast. eSkill type = current_pc.last_cast_type; std::string hint_prefix = ""; std::ostringstream hint_out; switch(type){ case eSkill::MAGE_SPELLS: hint_prefix = "M"; break; case eSkill::PRIEST_SPELLS: hint_prefix = "P"; break; // The only other expected value is eSkill::INVALID default: break; } if(!hint_prefix.empty()){ hint_out << hint_prefix << ": "; if(current_pc.last_cast[type] != eSpell::NONE){ const cSpell& spell = (*current_pc.last_cast[type]); if(pc_can_cast_spell(current_pc,type) == CAST_OK && spell.cost <= current_pc.get_magic()) { hint_out << "Recast " << spell.name(); }else{ hint_out << "Cannot recast"; } }else{ hint_out << "No spell to recast"; } } text = sout.str(); right_text = hint_out.str(); } if((is_combat()) && (monsters_going)) // Print bar for 1st monster with >0 ap - that is monster that is going for(short i = 0; i < univ.town.monst.size(); i++) if((univ.town.monst[i].is_alive()) && (univ.town.monst[i].ap > 0)) { text = print_monster_going(univ.town.monst[i].number,univ.town.monst[i].ap); i = 400; } return std::make_pair(text, right_text); } void draw_text_bar() { draw_text_bar(text_bar_text()); } void draw_text_bar(std::pair text) { static std::pair store_text = std::make_pair("", ""); static bool had_statuses = false; bool has_statuses = false; for(auto next : univ.party.status) { if(next.second > 0) { has_statuses = true; } } if(text.first != store_text.first || text.second != store_text.second || had_statuses != has_statuses){ store_text = text; had_statuses = has_statuses; put_text_bar(text.first, text.second); refresh_text_bar(); } } void put_text_bar(std::string str, std::string right_str) { text_bar_gworld().setActive(false); auto& bar_gw = *ResMgr::graphics.get("textbar"); rect_draw_some_item(bar_gw, rectangle(bar_gw), text_bar_gworld(), rectangle(bar_gw)); clear_scale_aware_text(text_bar_gworld()); TextStyle style; style.colour = sf::Color::White; style.font = FONT_BOLD; style.pointSize = 12; style.lineHeight = 12; rectangle to_rect = rectangle(text_bar_gworld()); to_rect.top += 7; to_rect.left += 5; to_rect.right -= 5; size_t filled_on_right = 0; // the recast hint will replace status icons: if(!right_str.empty()){ filled_on_right = string_length(" " + right_str, style); // Style has to be wrap to get right-alignment win_draw_string(text_bar_gworld(), to_rect, right_str, eTextMode::WRAP, style, true); }else if(!monsters_going){ sf::Texture& status_gworld = *ResMgr::graphics.get("staticons"); rectangle icon_rect = to_rect; icon_rect.top -= 2; icon_rect.left = to_rect.right - 15; icon_rect.width() = 12; icon_rect.height() = 12; for(auto next : univ.party.status) { const auto& statInfo = *next.first; if(next.second > 0) { rect_draw_some_item(status_gworld, get_stat_effect_rect(statInfo.icon), text_bar_gworld(), icon_rect, sf::BlendAlpha); icon_rect.offset(-15, 0); filled_on_right += 15; } } } // The recast hint and status icons will never take more than half the text bar. Give the name/AP/location string the rest. to_rect.right -= filled_on_right; win_draw_string(text_bar_gworld(), to_rect, str, eTextMode::ELLIPSIS, style); text_bar_gworld().setActive(); text_bar_gworld().display(); } void refresh_text_bar() { mainPtr().setActive(false); rect_draw_some_item(text_bar_gworld(), rectangle(text_bar_gworld()), mainPtr(), win_to_rects[WINRECT_STATUS]); mainPtr().setActive(); } // this is used for determinign whether to round off walkway corners // right now, trying a restrictive rule (just cave floor and grass, mainly) bool is_nature(short x, short y, unsigned short ground_t) { ter_num_t ter_type; ter_type = coord_to_ter((short) x,(short) y); return ground_t == univ.scenario.ter_types[ter_type].ground_type; } std::vector forcecage_locs; extern std::list posted_labels; //mode ... if 1, don't place on screen after redoing // if 2, only redraw over active monst void draw_terrain(short mode) { location where_draw; location sector_p_in,view_loc; char can_draw; ter_num_t spec_terrain; bool draw_frills = true; if(overall_mode == MODE_TALKING || overall_mode == MODE_SHOPPING || overall_mode == MODE_STARTUP) return; if(mode == 2) { if(current_working_monster < 0) return; supressing_some_spaces = true; for(short i = 0; i < 4; i++) ok_space[i].x = -1; if(current_working_monster >= 100) { for(short i = 0; i < univ.town.monst[current_working_monster - 100].x_width; i++) for(short j = 0; j < univ.town.monst[current_working_monster - 100].y_width; j++) { ok_space[i + 2 * j].x = univ.town.monst[current_working_monster - 100].cur_loc.x + i; ok_space[i + 2 * j].y = univ.town.monst[current_working_monster - 100].cur_loc.y + j; ok_space[i + 2 * j].x = ok_space[i + 2 * j].x - center.x + 4; ok_space[i + 2 * j].y = ok_space[i + 2 * j].y - center.y + 4; } } if(current_working_monster < 6) { ok_space[0] = univ.party[current_working_monster].combat_pos; ok_space[0].x = ok_space[0].x - center.x + 4; ok_space[0].y = ok_space[0].y - center.y + 4; } mode = 0; } mainPtr().setActive(); int max_dim_x, max_dim_y; if(is_out()){ max_dim_x = min(96, 48 * univ.scenario.outdoors.width()); max_dim_y = min(96, 48 * univ.scenario.outdoors.height()); }else { max_dim_x = max_dim_y = univ.town->max_dim; } for(short i = 0; i < 13; i++) for(short j = 0; j < 13; j++) { light_area[i][j] = 0; unexplored_area[i][j] = 0; } sector_p_in.x = univ.party.outdoor_corner.x + univ.party.i_w_c.x; sector_p_in.y = univ.party.outdoor_corner.y + univ.party.i_w_c.y; if(is_town()) view_loc = univ.party.town_loc; if(is_combat()) view_loc = univ.party[(univ.cur_pc < 6) ? univ.cur_pc : first_active_pc()].combat_pos; for(short i = 0; i < 13; i++) for(short j = 0; j < 13; j++) { where_draw = (is_out()) ? univ.party.out_loc : center; where_draw.x += i - 6; where_draw.y += j - 6; if (where_draw.x < 0 || where_draw.y < 0 || where_draw.x >= max_dim_x || where_draw.y >= max_dim_y) continue; if(!(is_out())) light_area[i][j] = (is_town()) ? pt_in_light(view_loc,where_draw) : combat_pt_in_light(where_draw); if(!is_out() && !univ.town.is_on_map(where_draw.x, where_draw.y)) unexplored_area[i][j] = 0; else unexplored_area[i][j] = 1 - is_explored(where_draw.x,where_draw.y); } forcecage_locs.clear(); for(short q = 0; q < 9; q++) { for(short r = 0; r < 9; r++) { where_draw = (is_out()) ? univ.party.out_loc : center; where_draw.x += q - 4; where_draw.y += r - 4; if (where_draw.x < 0 || where_draw.y < 0 || where_draw.x >= max_dim_x || where_draw.y >= max_dim_y){ // Out of bounds. Draw darkness draw_one_terrain_spot(q,r,-1); continue; } draw_frills = true; if(!is_out() && !univ.town.is_on_map(where_draw.x, where_draw.y)) { draw_frills = false; // Warning - this section changes where_draw if(where_draw.x < 0) where_draw.x = -1; if(where_draw.x > univ.town->max_dim - 1) where_draw.x = univ.town->max_dim; if(where_draw.y < 0) where_draw.y = -1; if(where_draw.y > univ.town->max_dim - 1) where_draw.y = univ.town->max_dim; if(can_see_light(view_loc,where_draw,sight_obscurity) < 5) can_draw = 1; else can_draw = 0; spec_terrain = 0; } else if(is_out()) { if(!univ.out.is_on_map(where_draw.x, where_draw.y)) can_draw = 0; else { spec_terrain = univ.out[where_draw.x][where_draw.y]; can_draw = univ.out.out_e[where_draw.x][where_draw.y]; } } else if(is_combat()) { spec_terrain = univ.town->terrain(where_draw.x,where_draw.y); can_draw = (((is_explored(where_draw.x,where_draw.y)) || (which_combat_type == 0) || (monsters_going) || (overall_mode != MODE_COMBAT)) && (party_can_see(where_draw) < 6)) ? 1 : 0; } else { spec_terrain = univ.town->terrain(where_draw.x,where_draw.y); can_draw = is_explored(where_draw.x,where_draw.y); if(can_draw > 0) { if(!pt_in_light(univ.party.town_loc,where_draw)) can_draw = 0; } if((overall_mode == MODE_LOOK_TOWN) && (can_draw == 0)) can_draw = (party_can_see(where_draw) < 6) ? 1 : 0; } spot_seen[q][r] = can_draw; if(fog_lifted) can_draw = true; if((can_draw != 0) && (overall_mode != MODE_RESTING)) { // if can see, not a pit, and not resting eTrimType trim = univ.scenario.ter_types[spec_terrain].trim_type; // Finally, draw this terrain spot if(trim == eTrimType::WALKWAY){ int trim = -1; unsigned short ground_t = univ.scenario.ter_types[spec_terrain].trim_ter; ter_num_t ground_ter = univ.scenario.get_ter_from_ground(ground_t); if(!loc_off_act_area(where_draw)) { if(is_nature(where_draw.x - 1,where_draw.y,ground_t)){ // check left if(is_nature(where_draw.x,where_draw.y - 1,ground_t)){ // check up if(is_nature(where_draw.x + 1,where_draw.y,ground_t)){ // check right if(is_nature(where_draw.x,where_draw.y + 1,ground_t)) // check down trim = 8; else trim = 4; }else if(is_nature(where_draw.x,where_draw.y + 1,ground_t)) // check down trim = 7; else trim = 1; }else if(is_nature(where_draw.x,where_draw.y + 1,ground_t)){ // check down if(is_nature(where_draw.x + 1,where_draw.y,ground_t)) // check right trim = 6; else trim = 0; } }else if(is_nature(where_draw.x,where_draw.y - 1,ground_t)){ // check up if(is_nature(where_draw.x + 1,where_draw.y,ground_t)){ // check right if(is_nature(where_draw.x,where_draw.y + 1,ground_t)) // check down trim = 5; else trim = 2; } }else if(is_nature(where_draw.x + 1,where_draw.y,ground_t)){ // check right if(is_nature(where_draw.x,where_draw.y + 1,ground_t)) // check down trim = 3; } } draw_one_terrain_spot(q,r,trim < 0 ? spec_terrain : ground_ter); if(trim >= 0) draw_trim(q,r,trim + 50,spec_terrain); }else if(spec_terrain == 65535) { draw_one_terrain_spot(q,r,-1); }else{ current_ground = univ.scenario.get_ground_from_ter(spec_terrain); draw_one_terrain_spot(q,r,spec_terrain); } } else { // Can't see. Place darkness. draw_one_terrain_spot(q,r,-1); } if((can_draw != 0) && (overall_mode != MODE_RESTING) && draw_frills) place_trim((short) q,(short) r,where_draw,spec_terrain); // if((is_town() && univ.town.is_spot(where_draw.x,where_draw.y)) || // (is_out() && univ.out.outdoors[univ.party.i_w_c.x][univ.party.i_w_c.y].special_spot[where_draw.x][where_draw.y])) // Draw_Some_Item(roads_gworld, calc_rect(6, 0), terrain_screen_gworld, loc(q,r), 1, 0); // TODO: Move draw_sfx, draw_items, draw_fields, draw_spec_items, etc to here if(is_town() || is_combat()) draw_items(where_draw); if(is_out() && univ.out.out_e[where_draw.x][where_draw.y] && univ.out.is_road(where_draw.x,where_draw.y)) place_road(q,r,where_draw,true); else if(is_town() && univ.town.is_explored(where_draw.x,where_draw.y) && univ.town.is_road(where_draw.x, where_draw.y)) place_road(q,r,where_draw,true); else place_road(q,r,where_draw,false); draw_fields(where_draw); //draw_monsters(where_draw); //draw_vehicles(where_draw); //if(is_combat) draw_pcs(where_draw); else draw_party(where_draw); } } // if((overall_mode != MODE_RESTING) && (!is_out())) // draw_sfx(); // // // Now place items // if((overall_mode > MODE_OUTDOORS) && (overall_mode != MODE_LOOK_OUTDOORS) && (overall_mode != MODE_RESTING)) // draw_items(); // // // Now place fields // if((overall_mode != MODE_RESTING) && (!is_out())) { // draw_fields(); // draw_spec_items(); // } // // Not camping. Place misc. stuff if(overall_mode != MODE_RESTING) { if(is_out()) draw_outd_boats(univ.party.out_loc); else if((is_town()) || (which_combat_type == 1)) draw_town_boat(center); draw_monsters(); } if(is_out() || (is_town() && point_onscreen(univ.party.town_loc,center)) || (overall_mode == MODE_RESTING)) draw_party_symbol(center); else if(overall_mode != MODE_LOOK_TOWN) draw_pcs(center); // Draw top half of forcecages (this list is populated by draw_fields) // TODO: Move into the above loop to eliminate global variable for(location fc_loc : forcecage_locs) Draw_Some_Item(*ResMgr::graphics.get("fields"),calc_rect(2,0),terrain_screen_gworld(),fc_loc,1,0); // Draw any posted labels, then clear them out clip_rect(terrain_screen_gworld(), {13, 13, 337, 265}); for(text_label_t lbl : posted_labels) draw_text_label(lbl); undo_clip(terrain_screen_gworld()); posted_labels.clear(); // Now do the light mask thing apply_light_mask(false); apply_unseen_mask(); terrain_screen_gworld().display(); if(mode == 0) { redraw_terrain(); draw_text_bar(); if(is_combat()) frame_active_pc(center); if(overall_mode == MODE_FANCY_TARGET) draw_targets(center); } supressing_some_spaces = false; } static ter_num_t get_ground_for_shore(ter_num_t ter){ if(univ.scenario.ter_types[ter].block_horse) return current_ground; else if(univ.scenario.ter_types[ter].blocksMove()) return current_ground; else return ter; } void place_trim(short q,short r,location where,ter_num_t ter_type) { bool at_top = false,at_bot = false,at_left = false,at_right = false; location targ; // FIrst quick check ... if a pit or barrier in outdoor combat, no trim if((is_combat()) && (which_combat_type == 0) && (ter_type == 90)) return; targ.x = q; targ.y = r; if(supressing_some_spaces && (targ != ok_space[0]) && (targ != ok_space[1]) && (targ != ok_space[2]) && (targ != ok_space[3])) return; if(where.x == 0) at_left = true; if(where.y == 0) at_top = true; if((overall_mode == MODE_OUTDOORS) || (overall_mode == MODE_LOOK_OUTDOORS)) { if(where.x == 95) at_right = true; if(where.y == 95) at_bot = true; } else { // TODO: Shouldn't we subtract one here? // The outdoors case (above) does subtract 1, so one of them must be wrong... if(where.x == univ.town->max_dim) at_right = true; if(where.y == univ.town->max_dim) at_bot = true; } // First, trim for fluids if(is_fluid(ter_type)){ short trim = get_fluid_trim(where, ter_type); if(trim != 0) { ter_num_t shore; if(trim & 2){ // upper right shore = coord_to_ter(where.x + 1, where.y - 1); shore = get_ground_for_shore(shore); draw_trim(q,r,5,shore); }if(trim & 8){ // lower right shore = coord_to_ter(where.x + 1, where.y + 1); shore = get_ground_for_shore(shore); draw_trim(q,r,7,shore); }if(trim & 32){ // lower left shore = coord_to_ter(where.x - 1, where.y + 1); shore = get_ground_for_shore(shore); draw_trim(q,r,6,shore); }if(trim & 128){ // upper left shore = coord_to_ter(where.x - 1, where.y - 1); shore = get_ground_for_shore(shore); draw_trim(q,r,4,shore); }if(trim & 1){ // top shore = coord_to_ter(where.x, where.y - 1); shore = get_ground_for_shore(shore); draw_trim(q,r,2,shore); }if(trim & 4){ // right shore = coord_to_ter(where.x + 1, where.y); shore = get_ground_for_shore(shore); draw_trim(q,r,1,shore); }if(trim & 16){ // bottom shore = coord_to_ter(where.x, where.y + 1); shore = get_ground_for_shore(shore); draw_trim(q,r,3,shore); }if(trim & 64){ // left shore = coord_to_ter(where.x - 1, where.y); shore = get_ground_for_shore(shore); draw_trim(q,r,0,shore); } } } if(is_wall(ter_type) && !at_top && !at_bot && !at_left && !at_right){ //if(((ter_type >= 100) && (ter_type <= 137)) && !at_top && // !at_bot && !at_left && !at_right) { ter_num_t to_left = coord_to_ter(where.x - 1,where.y); ter_num_t above = coord_to_ter(where.x,where.y - 1); ter_num_t to_right = coord_to_ter(where.x + 1,where.y); ter_num_t below = coord_to_ter(where.x,where.y + 1); if(is_wall(to_left) && is_wall(above) && is_ground(to_right) && is_ground(below)) draw_trim(q,r,11,to_right); if(is_wall(to_left) && is_wall(below) && is_ground(to_right) && is_ground(above)) draw_trim(q,r,9,to_right); if(is_wall(to_right) && is_wall(above) && is_ground(to_left) && is_ground(below)) draw_trim(q,r,10,to_left); if(is_wall(to_right) && is_wall(below) && is_ground(to_left) && is_ground(above)) draw_trim(q,r,8,to_left); if(is_ground(to_left) && is_ground(above) && is_ground(to_right) && is_wall(below)) { draw_trim(q,r,8,to_right); draw_trim(q,r,9,to_right); } if(is_wall(to_left) && is_ground(below) && is_ground(to_right) && is_ground(above)) { draw_trim(q,r,9,to_right); draw_trim(q,r,11,to_right); } if(is_ground(to_right) && is_wall(above) && is_ground(to_left) && is_ground(below)) { draw_trim(q,r,10,to_left); draw_trim(q,r,11,to_left); } if(is_wall(to_right) && is_ground(below) && is_ground(to_left) && is_ground(above)) { draw_trim(q,r,8,to_left); draw_trim(q,r,10,to_left); } if(is_ground(to_right) && is_ground(below) && is_ground(to_left) && is_ground(above)) { draw_trim(q,r,8,to_left); draw_trim(q,r,9,to_right); draw_trim(q,r,10,to_left); draw_trim(q,r,11,to_right); } // } } } static void init_trim_mask(std::unique_ptr& mask, rectangle src_rect) { static sf::RenderTexture render; static bool init = false; if(!init){ render.create(28, 36); init = true; } rectangle dest_rect; dest_rect.top = src_rect.top % 36; dest_rect.bottom = (src_rect.bottom - 1) % 36 + 1; dest_rect.left = src_rect.left % 28; dest_rect.right = (src_rect.right - 1) % 28 + 1; render.create(28, 36); render.clear(sf::Color::White); rect_draw_some_item(*ResMgr::graphics.get("trim"), src_rect, render, dest_rect); // render.display(); // Using it as a mask, we don't need to flip mask.reset(new sf::Texture); mask->create(28, 36); mask->update(render.getTexture().copyToImage()); } //which_trim is 3 -> drawing wall trim -> might shift down if ground is grass //short which_mode; // 0 top 1 bottom 2 left 3 right 4 up left 5 up right 6 down right 7 down left void draw_trim(short q,short r,short which_trim,ter_num_t ground_ter) { // which_trim // 0 - left, 1 - right, 2 - top, 3 - bottom, 4 - tl, 5 - tr, 6 - bl, 7 - br // 8 - wall tl, 9 - wall tr, 10 - wall bl, 11 - wall br // 50 - walkway bl, 51 - walkway tl, 52 - walkway tr, 53 - walkway br // 54 - walkway top, 55 - walkway right, 56 - walkway bottom, 57 - walkway left // 58 - lone walkway static rectangle trim_rects[12] = { {0,0,36,14}, {0,0,36,14}, {0,0,18,28}, {0,0,18,28}, {0,0,18,14}, {0,0,18,14}, {0,0,18,14}, {0,0,18,14}, {0,0,18,14}, {0,0,18,14}, {0,0,18,14}, {0,0,18,14}, }; static rectangle walkway_rects[9] = { {0,0,36,28}, {0,0,36,28}, {0,0,36,28}, {0,0,36,28}, {0,0,36,28}, {0,0,36,28}, {0,0,36,28}, {0,0,36,28}, {0,0,36,28}, }; static std::unique_ptr trim_masks[12], walkway_masks[9]; rectangle from_rect = {0,0,36,28},to_rect; std::shared_ptr from_gworld; sf::Texture* mask; static bool inited = false; if(!inited){ inited = true; trim_rects[1].offset(14,0); trim_rects[2].offset(28,0); trim_rects[3].offset(28,18); trim_rects[4].offset(56,0); trim_rects[5].offset(70,0); trim_rects[6].offset(56,18); trim_rects[7].offset(70,18); trim_rects[8].offset(84,0); trim_rects[9].offset(98,0); trim_rects[10].offset(84,18); trim_rects[11].offset(98,18); for(short i = 0; i < 12; i++) trim_rects[i].offset(112,36); for(short i = 0; i < 8 ; i++) walkway_rects[i].offset((i%4)*28,(i/4)*36); walkway_rects[8].offset(196,0); } sf::Color test_color = {0,0,0}, store_color; unsigned short pic = univ.scenario.ter_types[ground_ter].picture; if(pic < 960){ int which_sheet = pic / 50; from_gworld = &ResMgr::graphics.get("ter" + std::to_string(1 + which_sheet)); pic %= 50; from_rect.offset(28 * (pic % 10), 36 * (pic / 10)); }else if(pic < 1000){ from_gworld = &ResMgr::graphics.get("teranim"); pic -= 960; from_rect.offset(112 * (pic / 5),36 * (pic % 5)); }else{ pic %= 1000; graf_pos_ref(from_gworld, from_rect) = spec_scen_g.find_graphic(pic); } if(which_trim < 50) { if(!trim_masks[which_trim]) init_trim_mask(trim_masks[which_trim], trim_rects[which_trim]); mask = trim_masks[which_trim].get(); } else { int which = which_trim - 50; if(!walkway_masks[which]) init_trim_mask(walkway_masks[which], walkway_rects[which]); mask = walkway_masks[which].get(); } to_rect = coord_to_rect(q,r); rect_draw_some_item(*from_gworld, from_rect, *mask, terrain_screen_gworld(), to_rect); } static bool extend_road_terrain(int x, int y) { if(is_out() && univ.out.is_road(x,y)) return true; if(is_town() && univ.town.is_road(x,y)) return true; ter_num_t ter = coord_to_ter(x,y); eTrimType trim = univ.scenario.ter_types[ter].trim_type; eTerSpec spec = univ.scenario.ter_types[ter].special; ter_num_t flag = univ.scenario.ter_types[ter].flag1; if(trim == eTrimType::CITY || trim == eTrimType::WALKWAY) return true; if(spec == eTerSpec::BRIDGE) return true; if(spec == eTerSpec::TOWN_ENTRANCE && trim != eTrimType::NONE) return true; // cave entrance, most likely if(spec == eTerSpec::UNLOCKABLE || spec == eTerSpec::CHANGE_WHEN_STEP_ON) return true; // closed door, possibly locked; or closed portcullis if(spec == eTerSpec::CHANGE_WHEN_USED && univ.scenario.ter_types[flag].special == eTerSpec::CHANGE_WHEN_STEP_ON && univ.scenario.ter_types[flag].flag1 == ter) return true; // open door (I think) TODO: Verify this works if(spec == eTerSpec::LOCKABLE) return true; // open portcullis (most likely) return false; } static bool can_build_roads_on(ter_num_t ter) { if(impassable(ter)) return false; if(univ.scenario.ter_types[ter].special == eTerSpec::BRIDGE) return false; return true; } static bool connect_roads(ter_num_t ter){ if(ter >= univ.scenario.ter_types.size()) return false; eTrimType trim = univ.scenario.ter_types[ter].trim_type; eTerSpec spec = univ.scenario.ter_types[ter].special; if(trim == eTrimType::CITY) return true; if(spec == eTerSpec::TOWN_ENTRANCE && trim != eTrimType::NONE) return true; // cave entrance, most likely return false; } void place_road(short q,short r,location where,bool here) { rectangle to_rect; static const rectangle road_rects[5] = { {76,28,80,41}, // horizontal partial {72,60,90,64}, // vertical partial {72,28,75,56}, // horizontal full {72,56,108,60}, // vertical full {80,28,84,32}, // central spot }; static const rectangle road_dest_rects[7] = { {0,12,18,16}, // top {16,15,20,28}, // right {18,12,36,16}, // bottom {16,0,20,13}, // left {0,12,36,16}, // top + bottom {16,0,20,28}, // right + left {16,12,20,16}, // central spot }; sf::Texture& roads_gworld = *ResMgr::graphics.get("fields"); if(here){ to_rect = road_dest_rects[6]; to_rect.offset(13 + q * 28,13 + r * 36); rect_draw_some_item(roads_gworld, road_rects[4], terrain_screen_gworld(), to_rect); if((where.y == 0) || extend_road_terrain(where.x, where.y - 1)) { to_rect = road_dest_rects[0]; to_rect.offset(13 + q * 28,13 + r * 36); rect_draw_some_item (roads_gworld, road_rects[1], terrain_screen_gworld(), to_rect); } if(((is_out()) && (where.x == 96)) || (!(is_out()) && (where.x == univ.town->max_dim - 1)) || extend_road_terrain(where.x + 1, where.y)) { to_rect = road_dest_rects[1]; to_rect.offset(13 + q * 28,13 + r * 36); rect_draw_some_item (roads_gworld, road_rects[0], terrain_screen_gworld(), to_rect); } if(((is_out()) && (where.y == 96)) || (!(is_out()) && (where.y == univ.town->max_dim - 1)) || extend_road_terrain(where.x, where.y + 1)) { to_rect = road_dest_rects[2]; to_rect.offset(13 + q * 28,13 + r * 36); rect_draw_some_item (roads_gworld, road_rects[1], terrain_screen_gworld(), to_rect); } if((where.x == 0) || extend_road_terrain(where.x - 1, where.y)) { to_rect = road_dest_rects[3]; to_rect.offset(13 + q * 28,13 + r * 36); rect_draw_some_item (roads_gworld, road_rects[0], terrain_screen_gworld(), to_rect); } }else{ // TODO: I suspect this branch is now irrelevant. ter_num_t ter = coord_to_ter(where.x, where.y); ter_num_t ref = coord_to_ter(where.x,where.y); bool horz = false, vert = false; eTrimType trim = eTrimType::NONE, vertTrim = eTrimType::NONE; if(ref < univ.scenario.ter_types.size()) { trim = univ.scenario.ter_types[ref].trim_type; } if(ter < univ.scenario.ter_types.size()) { vertTrim = univ.scenario.ter_types[ter].trim_type; } if(where.y > 0) ter = coord_to_ter(where.x,where.y - 1); if((where.y == 0) || connect_roads(ter)) vert = can_build_roads_on(ref); else if((vertTrim == eTrimType::S && trim == eTrimType::N) || (vertTrim == eTrimType::N && trim == eTrimType::S)) vert = can_build_roads_on(ref); if(((is_out()) && (where.x < 96)) || (!(is_out()) && (where.x < univ.town->max_dim - 1))) ter = coord_to_ter(where.x + 1,where.y); eTrimType horzTrim = univ.scenario.ter_types[ter].trim_type; if(((is_out()) && (where.x == 96)) || (!(is_out()) && (where.x == univ.town->max_dim - 1)) || connect_roads(ter)) horz = can_build_roads_on(ref); else if((horzTrim == eTrimType::W && trim == eTrimType::E) || (horzTrim == eTrimType::E && trim == eTrimType::W)) horz = can_build_roads_on(ref); if(vert){ if(((is_out()) && (where.y < 96)) || (!(is_out()) && (where.y < univ.town->max_dim - 1))) ter = coord_to_ter(where.x,where.y + 1); eTrimType vertTrim = univ.scenario.ter_types[ter].trim_type; if(((is_out()) && (where.y == 96)) || (!(is_out()) && (where.y == univ.town->max_dim - 1)) || connect_roads(ter)) vert = can_build_roads_on(ref); else if((vertTrim == eTrimType::S && trim == eTrimType::N) || (vertTrim == eTrimType::N && trim == eTrimType::S)) vert = can_build_roads_on(ref); else vert = false; } if(horz){ if(where.x > 0) ter = coord_to_ter(where.x - 1,where.y); eTrimType horzTrim = univ.scenario.ter_types[ter].trim_type; if((where.x == 0) || connect_roads(ter)) horz = can_build_roads_on(ref); else if((horzTrim == eTrimType::W && trim == eTrimType::E) || (horzTrim == eTrimType::E && trim == eTrimType::W)) horz = can_build_roads_on(ref); else horz = false; } if(horz){ to_rect = road_dest_rects[5]; to_rect.offset(13 + q * 28,13 + r * 36); rect_draw_some_item (roads_gworld, road_rects[2], terrain_screen_gworld(), to_rect); } if(vert){ to_rect = road_dest_rects[4]; to_rect.offset(13 + q * 28,13 + r * 36); rect_draw_some_item (roads_gworld, road_rects[3], terrain_screen_gworld(), to_rect); } } } void draw_rest_screen() { eGameMode store_mode; store_mode = overall_mode; overall_mode = MODE_RESTING; for(int q = 0; q < 9; q++) { for(int r = 0; r < 9; r++) { draw_one_terrain_spot(q,r,-1); } } draw_party_symbol(center); terrain_screen_gworld().display(); overall_mode = store_mode ; } // mode // if 100, force // type // 0 - flame 1 - magic 2 - poison 3 - blood 4 - cold 5 - unblockable 6 - acid // sound // negative - pass sound explicitly // 0 or more: use lookup // 0 - normal ouch 1 - small sword 2 - loud sword // 3 - pole 4 - club 5 - fireball hit 6 - squish 7 - cold // 8 - acid 9 - claw 10 - bite 11 - slime 12 - zap 13 - missile hit void boom_space(location where,short mode,short type,short damage,short sound) { location where_draw(4,4); rectangle source_rect = {0,0,36,28},text_rect,dest_rect = {13,13,49,41},big_to = {13,13,337,265},store_rect; short del_len; short x_adj = 0,y_adj = 0; short sound_lookup[20] = { 97,69,70,71,72, 73,55,75,42,86, 87,88,89,98,0, 0,0,0,0,0}; short sound_to_play = sound < 0 ? -sound : sound_lookup[sound]; //sound_key = type / 10; //type = type % 10; // if((cartoon_happening) && (anim_step < 140)) // return; if((mode != 100) && (party_can_see(where) == 6)) return; if(type < 0 || type > 6) return; if((boom_anim_active) && (type != 3)) return; mainPtr().setView(mainPtr().getDefaultView()); put_background(); mainPtr().setView(mainView); draw_terrain(); refresh_text_bar(); UI::toolbar.draw(mainPtr()); // Redraw terrain in proper position if(((!point_onscreen(center,where) && is_combat())) ) { play_sound(sound_to_play); return; } else if(is_combat()) { if(which_combat_type == 1) draw_terrain(0); else draw_terrain(0); } else if(fast_bang < 2) { draw_terrain(0); if(fast_bang == 1) fast_bang = 2; } refresh_stat_areas(0); where_draw.x = where.x - center.x + 4; where_draw.y = where.y - center.y + 4; // source_rect.left += 28 * type; // source_rect.right += 28 * type; // adjust for possible big monster if(iLiving* who = univ.target_there(where, TARG_MONST)) { cCreature* monst = dynamic_cast(who); x_adj += 14 * (monst->x_width - 1); y_adj += 18 * (monst->y_width - 1); } dest_rect.offset(where_draw.x * 28,where_draw.y * 36); source_rect = store_rect = dest_rect; dest_rect.offset(x_adj,y_adj); dest_rect &= big_to; dest_rect.offset(win_to_rects[WINRECT_TERVIEW].topLeft()); source_rect.offset(-store_rect.left + 28 * type,-store_rect.top); rect_draw_some_item(*ResMgr::graphics.get("booms"),source_rect,mainPtr(),dest_rect,sf::BlendAlpha); if(damage > 0 && dest_rect.right - dest_rect.left >= 28 && dest_rect.bottom - dest_rect.top >= 36) { TextStyle style; style.lineHeight = 10; text_rect = dest_rect; text_rect.top += 13; text_rect.height() = 10; std::string dam_str = std::to_string(damage); style.colour = sf::Color::White; text_rect.offset(-1,-1); win_draw_string(mainPtr(),text_rect,dam_str,eTextMode::CENTRE,style); text_rect.offset(2,2); win_draw_string(mainPtr(),text_rect,dam_str,eTextMode::CENTRE,style); style.colour = sf::Color::Black; text_rect.offset(-1,-1); win_draw_string(mainPtr(),text_rect,dam_str,eTextMode::CENTRE,style); } item_sbar->draw(); mainPtr().display(); bool skip_boom_delay = get_bool_pref("SkipBoomDelay"); play_sound((skip_boom_delay?-1:1)*sound_to_play); // Fast sound effects need extra delay for the boom to show std::set fast_sounds = { 55, 42 }; // squish, acid, should there be others? if((fast_sounds.count(sound_to_play)) && (fast_bang == 0) && (!skip_boom_delay)) sf::sleep(time_in_ticks(12)); if(fast_bang == 0 && !skip_boom_delay) { del_len = get_int_pref("GameSpeed") * 3 + 4; if(!get_bool_pref("PlaySounds", true)) sf::sleep(time_in_ticks(del_len)); } redraw_terrain(); if(is_combat()) frame_active_pc(center); } // dir = 0 - down, 1 - left, 2 - right, 3 - up, 4 - down/left, 5 - up/left, 6 - up/right, 7 - down/right // pos = row or column to centre the arrow in, range 0..8, ignored for dir >= 4 static void draw_one_pointing_arrow(int dir, int pos) { rectangle from_rect = {62, 61, 70, 69}; from_rect.offset(9 * (dir % 4), 9 * (dir / 4)); rectangle ter_view_rect = win_to_rects[WINRECT_TERVIEW]; rectangle to_rect = ter_view_rect; // Left-pointing arrows if(dir == 1 || dir == 4 || dir == 5) to_rect.left += 2; // Right-pointing arrows if(dir == 2 || dir == 6 || dir == 7) to_rect.left = to_rect.right - 10; // Up-pointing arrows if(dir == 3 || dir == 5 || dir == 6) to_rect.top += 2; // Down-pointing arrows if(dir == 0 || dir == 4 || dir == 7) to_rect.top = to_rect.bottom - 10; // Horizontal arrows if(dir == 0 || dir == 3) to_rect.left += 23 + pos * 28; // Vertical arrows if(dir == 1 || dir == 2) to_rect.top += 23 + pos * 36; to_rect.width() = to_rect.height() = 8; rect_draw_some_item(*ResMgr::graphics.get("invenbtns"), from_rect, mainPtr(), to_rect, sf::BlendAlpha); } void draw_pointing_arrows() { if(monsters_going || !scrollableModes.count(overall_mode)) return; draw_one_pointing_arrow(0, 3); draw_one_pointing_arrow(0, 5); draw_one_pointing_arrow(1, 3); draw_one_pointing_arrow(1, 5); draw_one_pointing_arrow(2, 3); draw_one_pointing_arrow(2, 5); draw_one_pointing_arrow(3, 3); draw_one_pointing_arrow(3, 5); draw_one_pointing_arrow(4, 0); draw_one_pointing_arrow(5, 0); draw_one_pointing_arrow(6, 0); draw_one_pointing_arrow(7, 0); } void redraw_terrain() { rectangle to_rect = win_to_rects[WINRECT_TERVIEW], from_rect(terrain_screen_gworld()); rect_draw_some_item(terrain_screen_gworld().getTexture(), from_rect, mainPtr(), to_rect); apply_light_mask(true); // Now place arrows draw_pointing_arrows(); } void draw_targets(location center) { if(!univ.party.is_alive()) return; const rectangle src_rect{0,46,12,58}; sf::Texture& src_gworld = *ResMgr::graphics.get("invenbtns"); for(short i = 0; i < 8; i++) if((spell_targets[i].x != -1) && (point_onscreen(center,spell_targets[i]))) { rectangle dest_rect = coord_to_rect(spell_targets[i].x - center.x + 4,spell_targets[i].y - center.y + 4); dest_rect.inset(8,12); dest_rect.offset(win_to_rects[WINRECT_TERVIEW].topLeft()); rect_draw_some_item(src_gworld,src_rect,mainPtr(),dest_rect,sf::BlendAlpha); } } //mode; // 0 - red 1 - green void frame_space(location where,short mode,short width,short height) { location where_put; rectangle to_frame; if(!point_onscreen(center,where)) return; where_put.x = 4 + where.x - center.x; where_put.y = 4 + where.y - center.y; to_frame.top = 13 + where_put.y * 36; to_frame.left = 13 + where_put.x * 28; to_frame.bottom = 49 + where_put.y * 36 + 36 * (height - 1); to_frame.right = 41 + where_put.x * 28 + 28 * (width - 1); to_frame.offset(win_to_rects[WINRECT_TERVIEW].topLeft()); frame_roundrect(mainPtr(), to_frame, 8, (mode == 0) ? Colours::RED : Colours::GREEN); } void erase_spot(short i,short j) { rectangle to_erase; to_erase = coord_to_rect(i,j); fill_rect(mainPtr(), to_erase, sf::Color::Black); } void draw_targeting_line() { location which_space,store_loc; rectangle target_rect; location from_loc; location where_curs = mouse_window_coords(); if(overall_mode >= MODE_COMBAT) from_loc = univ.current_pc().combat_pos; else from_loc = univ.party.town_loc; extern bool targeting_line_visible; if(targeting_line_visible && ((overall_mode == MODE_SPELL_TARGET) || (overall_mode == MODE_FIRING) || (overall_mode == MODE_THROWING) || (overall_mode == MODE_FANCY_TARGET) || ((overall_mode == MODE_TOWN_TARGET) && (current_pat[4][4] != 0)))) { if(mouse_to_terrain_coords(which_space, false)) { int xBound = (short) (from_loc.x - center.x + 4); int yBound = (short) (from_loc.y - center.y + 4); xBound = (xBound * 28) + 46; yBound = (yBound * 36) + 38; if((can_see_light(from_loc,which_space,sight_obscurity) < 5) && (dist(from_loc,which_space) <= current_spell_range)) { mainPtr().setActive(false); rectangle on_screen_terrain_area = win_to_rects[WINRECT_TERVIEW]; on_screen_terrain_area.inset(13, 13); clip_rect(mainPtr(), on_screen_terrain_area); draw_line(mainPtr(), where_curs, location(xBound, yBound), 2, {128,128,128}, sf::BlendAdd); // Now place targeting pattern for(short i = 0; i < 9; i++) for(short j = 0; j < 9; j++) { store_loc.x = center.x + i - 4; store_loc.y = center.y + j - 4; if((abs(store_loc.x - which_space.x) <= 4) && (abs(store_loc.y - which_space.y) <= 4) && (current_pat[store_loc.x - which_space.x + 4][store_loc.y - which_space.y + 4] != 0)) { target_rect.left = 13 + 28 * i + 19; target_rect.right = target_rect.left + 28; target_rect.top = 13 + 36 * j + 7; target_rect.bottom = target_rect.top + 36; frame_rect(mainPtr(), target_rect, sf::Color::White); target_rect.inset(-5,-5); // Now place number of shots left, if drawing center of target if((overall_mode == MODE_FANCY_TARGET) && (store_loc.x - which_space.x + 4 == 4) && (store_loc.y - which_space.y + 4 == 4)) { TextStyle style; style.colour = sf::Color::White; style.lineHeight = 12; const char chr[2] = {static_cast(num_targets_left + '0')}; int x = ((target_rect.left + target_rect.right) / 2) - 3; int y = (target_rect.top + target_rect.bottom) / 2; win_draw_string(mainPtr(), rectangle(y, x, y + 12, x + 12), chr, eTextMode::CENTRE, style); } } } mainPtr().setActive(); undo_clip(mainPtr()); } } } } void redraw_partial_terrain(rectangle redraw_rect) { rectangle from_rect; from_rect = redraw_rect; // as rect_draw_some_item will shift redraw_rect before drawing, we need to compensate redraw_rect.offset(5,5); rect_draw_some_item(terrain_screen_gworld().getTexture(),from_rect,mainPtr(),redraw_rect); } void debug_show_texture(const sf::Texture& texture, float seconds, std::string label) { sf::RenderWindow debug_window; debug_window.create(sf::VideoMode(texture.getSize().x, texture.getSize().y, 32), label); debug_window.setVisible(true); makeFrontWindow(debug_window); rect_draw_some_item(texture, rectangle(texture), debug_window, rectangle(texture)); debug_window.display(); sf::sleep(sf::seconds(seconds)); debug_window.setVisible(false); }