#include "boe.global.hpp" #include "tools/replay.hpp" #include "universe/universe.hpp" #include "cli.hpp" #include #include #include #include #include #include #include #include #include #include "boe.graphics.hpp" #include "boe.newgraph.hpp" #include "boe.fileio.hpp" #include "boe.actions.hpp" #include "boe.text.hpp" #include "boe.party.hpp" #include "boe.items.hpp" #include "boe.locutils.hpp" #include "boe.town.hpp" #include "boe.dlgutil.hpp" #include "boe.infodlg.hpp" #include "boe.main.hpp" #include "boe.consts.hpp" #include "boe.ui.hpp" #include "boe.specials.hpp" #include "tools/winutil.hpp" #include "sounds.hpp" #include "gfx/render_image.hpp" #include "gfx/tiling.hpp" #include "mathutil.hpp" #include "fileio/fileio.hpp" #include "dialogxml/dialogs/strdlog.hpp" #include "dialogxml/dialogs/choicedlog.hpp" #include "dialogxml/widgets/scrollbar.hpp" #include "boe.menus.hpp" #include "tools/cursors.hpp" #include "tools/keymods.hpp" #include "tools/prefs.hpp" #include "dialogxml/widgets/button.hpp" #include "tools/enum_map.hpp" #include "tools/event_listener.hpp" #include "tools/drawable_manager.hpp" bool All_Done = false; sf::RenderWindow mainPtr; short had_text_freeze = 0,num_fonts; bool first_startup_update = true; bool first_sound_played = false,spell_forced = false; bool party_in_memory = false; std::shared_ptr text_sbar, item_sbar, shop_sbar; std::shared_ptr done_btn, help_btn; // TODO: move these 3 to boe.ui.cpp ? extern const rectangle sbar_rect = {285,560,423,576}; extern const rectangle shop_sbar_rect = {69,272,359,288}; extern const rectangle item_sbar_rect = {148,560,255,576}; bool bgm_on = false,bgm_init = false; location store_anim_ul; cUniverse univ; bool flushingInput = false, ae_loading = false; long start_time; std::deque fake_event_queue; short on_spell_menu[2][62]; short on_monst_menu[256]; extern bool map_visible; extern sf::View mainView; extern rectangle shop_frame; extern enum_map(eGuiArea, rectangle) win_to_rects; std::string scenario_temp_dir_name = "scenario"; extern fs::path tempDir; /* Display globals */ short combat_posing_monster = -1, current_working_monster = -1; // 0-5 PC 100 + x - monster x short fast_bang = false; // Note: This mostly behaves as a boolean variable, but values other than 1 are assigned to it, so I made it a short std::vector spec_item_array; short current_spell_range; eGameMode overall_mode = MODE_STARTUP; bool changed_display_mode = false; eItemWinMode stat_window = ITEM_WIN_PC1; bool monsters_going = false,boom_anim_active = false; bool finished_init = false; sf::RenderWindow mini_map; short which_item_page[6] = {0,0,0,0,0,0}; // Remembers which of the 2 item pages pc looked at short current_ground = 0; eStatMode stat_screen_mode; short anim_step = -1; // Spell casting globals eSpell store_mage = eSpell::LIGHT, store_priest = eSpell::BLESS_MINOR; short store_mage_lev = 0, store_priest_lev = 0; short store_spell_target = 6,pc_casting; short num_targets_left = 0; location spell_targets[8]; extern void spell_cast_hit_return(); /* Combat globals */ short which_combat_type; location center; short combat_active_pc; effect_pat_type current_pat; short missile_firer,current_monst_tactic; short store_current_pc = 0; // TODO: these should be members of some global entity instead of being here std::unordered_map > event_listeners; cDrawableManager drawable_mgr; sf::Clock animTimer; extern long anim_ticks; static void init_boe(int, char*[]); static void showWelcome(); void handle_quit_event(); void handle_help_toc(); void menu_give_help(short help1); extern void edit_stuff_done(); #ifdef __APPLE__ eMenuChoice menuChoice=eMenuChoice::MENU_CHOICE_NONE; short menuChoiceId=-1; #endif int main(int argc, char* argv[]) { #if 0 void debug_oldstructs(); debug_oldstructs(); #endif try{ cDialog::redraw_everything = &redraw_everything; init_boe(argc, argv); if(!get_bool_pref("GameRunBefore")) showWelcome(); else if(get_bool_pref("GiveIntroHint", true)) tip_of_day(); set_pref("GameRunBefore", true); finished_init = true; if(ae_loading) { finish_load_party(); post_load(); } menu_activate(); restore_cursor(); handle_events(); close_program(); return 0; } catch(std::exception& x) { showFatalError(x.what()); throw; } catch(std::string& x) { showFatalError(x); throw; } catch(...) { showFatalError("An unknown error occurred!"); throw; } } static void init_sbar(std::shared_ptr& sbar, const std::string& name, rectangle rect, rectangle events_rect, int max, int pgSz, int start = 0) { sbar.reset(new cScrollbar(mainPtr)); sbar->setName(name); sbar->setBounds(rect); sbar->setMaximum(max); sbar->setPosition(start); sbar->setPageSize(pgSz); sbar->set_wheel_event_rect(events_rect); sbar->hide(); drawable_mgr.add_drawable(UI_LAYER_DEFAULT, name, sbar); event_listeners[name] = std::dynamic_pointer_cast(sbar); } static void init_scrollbars() { // Cover entire transcript + scrollbar const rectangle transcript_events_rect { win_to_rects[WINRECT_TRANSCRIPT].top, win_to_rects[WINRECT_TRANSCRIPT].left, sbar_rect.bottom, sbar_rect.right }; // Cover entire inventory + scrollbar const rectangle inventory_events_rect { win_to_rects[WINRECT_INVEN].top, win_to_rects[WINRECT_INVEN].left, item_sbar_rect.bottom, item_sbar_rect.right }; // MAGIC NUMBERS: max size, page size, initial position - all in abstract "step" units init_sbar(text_sbar, "transcript-scrollbar", sbar_rect, transcript_events_rect, 58, 11, 58); init_sbar(item_sbar, "inventory-scrollbar", item_sbar_rect, inventory_events_rect, 16, 8); init_sbar(shop_sbar, "shop-scrollbar", shop_sbar_rect, shop_frame, 16, 8); } static void init_btn(std::shared_ptr& btn, eBtnType type, location loc) { btn.reset(new cButton(mainPtr)); btn->setBtnType(type); btn->relocate(loc); btn->hide(); } static void init_buttons() { // MAGIC NUMBERS: move to boe.ui.cpp ? init_btn(done_btn, BTN_DONE, {231,395}); init_btn(help_btn, BTN_HELP, {273,12}); } // NOTE: this should possibly be moved to boe.ui.cpp at some point static void init_ui() { cDialog::init(); init_scrollbars(); init_buttons(); } bool record_advance_time = false; static void process_args(int argc, char* argv[]) { preprocess_args(argc, argv); clara::Args args(argc, argv); clara::Parser cli; bool record_unique = false; boost::optional record_to, replay, saved_game; boost::optional replay_speed; cli |= clara::Opt(record_to, "record")["--record"]("Records a replay of your session to the specified XML file."); cli |= clara::Opt(record_unique)["--unique"]("When recording, automatically insert a timestamp into the filename to guarantee uniqueness."); cli |= clara::Opt(record_advance_time)["--advance-time"]("Record and validate advance_time() calls for internal testing of the replay system."); cli |= clara::Opt(replay, "replay-file")["--replay"]("Replays a previously-recorded session from the specified XML file."); cli |= clara::Opt(replay_speed, "fps")["--replay-speed"]("Specifies how quickly actions are processed while replaying"); cli |= clara::Arg(saved_game, "save-file")("Launch and load a saved game file."); bool show_help = false; cli |= clara::Help(show_help); if(auto result = cli.parse(args)); else { std::cerr << "Error in command line: " << result.errorMessage() << std::endl; exit(1); } if(show_help) { cli.writeToStream(std::cout); exit(0); } if(record_to && init_action_log(record_unique || record_to->empty() ? "record-unique" : "record", *record_to)) return; if(replay && init_action_log("replay", *replay)) { if(replay_speed) { extern boost::optional replay_fps_limit; replay_fps_limit.emplace(*replay_speed); } return; } if(saved_game) { if(!load_party(*saved_game, univ)) { std::cout << "Failed to load save file: " << *saved_game << std::endl; return; } if(!finished_init) { ae_loading = true; overall_mode = MODE_STARTUP; } else finish_load_party(); if(overall_mode != MODE_STARTUP) end_startup(); if(overall_mode != MODE_STARTUP) post_load(); } } static void replay_next_action() { bool did_something = false, need_redraw = false, need_reprint = false; std::string _last_action_type = last_action_type; Element& next_action = pop_next_action(); std::string t = next_action.Value(); int enum_v; // NOTE: Action replay blocks need to return early unless the action advances time if(overall_mode == MODE_STARTUP && t == "startup_button_click"){ auto info = info_from_action(next_action); eStartButton btn = static_cast(std::stoi(info["btn"])); eKeyMod mods = static_cast(std::stoi(info["mods"])); handle_startup_button_click(btn, mods); return; }else if(t == "load_party"){ decode_file(next_action.GetText(), tempDir / "temp.exg"); load_party(tempDir / "temp.exg", univ); finish_load_party(); return; }else if(t == "move"){ location l = location_from_action(next_action); handle_move(l, did_something, need_redraw, need_reprint); }else if(t == "handle_switch_pc"){ short which_pc = short_from_action(next_action); handle_switch_pc(which_pc, need_redraw, need_reprint); }else if(t == "handle_switch_pc_items"){ short which_pc = short_from_action(next_action); handle_switch_pc_items(which_pc, need_redraw); update_item_stats_area(need_reprint); }else if(t == "handle_equip_item"){ short item_hit = short_from_action(next_action); handle_equip_item(item_hit, need_redraw); update_item_stats_area(need_reprint); }else if(t == "handle_use_item"){ short item_hit = short_from_action(next_action); handle_use_item(item_hit, did_something, need_redraw); update_item_stats_area(need_reprint); }else if(t == "handle_item_shop_action"){ short item_hit = short_from_action(next_action); handle_item_shop_action(item_hit); update_item_stats_area(need_reprint); }else if(t == "handle_alchemy"){ handle_alchemy(need_redraw, need_reprint); return; }else if(t == "handle_wait"){ handle_wait(did_something, need_redraw, need_reprint); return; }else if(t == "handle_combat_switch"){ handle_combat_switch(did_something, need_redraw, need_reprint); }else if(t == "handle_missile"){ handle_missile(need_redraw, need_reprint); }else if(t == "handle_get_items"){ handle_get_items(did_something, need_redraw, need_reprint); }else if(t == "handle_drop_item_id"){ short item_hit = short_from_action(next_action); handle_drop_item(item_hit, need_redraw); update_item_stats_area(need_reprint); }else if(t == "handle_drop_item_location"){ location destination = location_from_action(next_action); handle_drop_item(destination, need_redraw); }else if(t == "handle_give_item"){ short item_hit = short_from_action(next_action); handle_give_item(item_hit, did_something, need_redraw); update_item_stats_area(need_reprint); }else if(t == "close_window"){ // TODO do last handle_quit_event(); }else if(t == "arrow_button_click"){ rectangle button_rect = boost::lexical_cast(next_action.GetText()); arrow_button_click(button_rect); return; }else if(t == "show_dialog_action"){ show_dialog_action(next_action.GetText()); return; }else if(t == "handle_drop_pc"){ handle_drop_pc(); return; }else if(t == "new_party"){ new_party(); return; }else if(t == "pick_preferences"){ pick_preferences(); return; }else if(t == "do_abort"){ do_abort(); return; }else if(t == "handle_new_pc_graphic"){ handle_new_pc_graphic(); return; }else if(t == "handle_rename_pc"){ handle_rename_pc(); return; }else if(t == "handle_new_pc"){ handle_new_pc(); return; }else if(t == "journal"){ journal(); return; }else if(t == "talk_notes"){ talk_notes(); return; }else if(t == "adventure_notes"){ adventure_notes(); return; }else if(t == "print_party_stats"){ print_party_stats(); return; }else if(t == "display_alchemy"){ display_alchemy(); return; }else if(t == "display_spells"){ auto info = info_from_action(next_action); enum_v = boost::lexical_cast(info["mode"]); eSkill mode = static_cast(enum_v); short force_spell = boost::lexical_cast(info["force_spell"]); display_spells(mode, force_spell, nullptr); }else if(t == "display_skills"){ enum_v = boost::lexical_cast(next_action.GetText()); eSkill force_skill = static_cast(enum_v); display_skills(force_skill, nullptr); }else if(t == "tip_of_day"){ tip_of_day(); return; }else if(t == "showWelcome"){ showWelcome(); return; }else if(t == "display_map"){ display_map(); }else if(t == "handle_help_toc"){ handle_help_toc(); return; }else if(t == "menu_give_help"){ short help1 = short_from_action(next_action); menu_give_help(help1); return; }else if(t == "handle_begin_look"){ bool right_button = str_to_bool(next_action.GetText()); handle_begin_look(right_button, need_redraw); }else if(t == "handle_look"){ auto info = info_from_action(next_action); location destination = boost::lexical_cast(info["destination"]); bool right_button = str_to_bool(info["right_button"]); eKeyMod mods = static_cast(std::stoi(info["mods"])); handle_look(destination, right_button, mods, need_redraw, need_reprint); return; }else if(t == "screen_shift"){ auto info = info_from_action(next_action); int dx = std::stoi(info["dx"]); int dy = std::stoi(info["dy"]); screen_shift(dx, dy, need_redraw); return; }else if(t == "handle_rest"){ handle_rest(need_redraw, need_reprint); }else if(t == "handle_menu_spell"){ eSpell spell_picked = static_cast(std::stoi(next_action.GetText())); handle_menu_spell(spell_picked); }else if(t == "handle_spellcast"){ auto info = info_from_action(next_action); eSkill which_type = boost::lexical_cast(info["which_type"]); spell_forced = str_to_bool(info["spell_forced"]); handle_spellcast(which_type, did_something, need_redraw, need_reprint); }else if(t == "handle_target_space"){ auto info = info_from_action(next_action); location destination = boost::lexical_cast(info["destination"]); num_targets_left = boost::lexical_cast(info["num_targets_left"]); handle_target_space(destination, did_something, need_redraw, need_reprint); }else if(t == "spell_cast_hit_return"){ spell_cast_hit_return(); return; }else if(t == "handle_pause"){ handle_pause(did_something, need_redraw); }else if(t == "handle_bash_select"){ handle_bash_pick_select(need_reprint, true); }else if(t == "handle_pick_select"){ handle_bash_pick_select(need_reprint, false); }else if(t == "handle_bash"){ location destination = location_from_action(next_action); handle_bash_pick(destination, did_something, need_redraw, true); }else if(t == "handle_pick"){ location destination = location_from_action(next_action); handle_bash_pick(destination, did_something, need_redraw, false); }else if(t == "handle_use_space_select"){ handle_use_space_select(need_reprint); }else if(t == "handle_use_space"){ location destination = location_from_action(next_action); handle_use_space(destination, did_something, need_redraw); }else if(t == "show_inventory"){ show_inventory(); }else if(t == "give_help"){ auto info = info_from_action(next_action); short help1 = boost::lexical_cast(info["help1"]); short help2 = boost::lexical_cast(info["help2"]); give_help(help1, help2); }else if(t == "toggle_debug_mode"){ toggle_debug_mode(); }else if(t == "debug_give_item"){ debug_give_item(); }else if(t == "debug_print_location"){ debug_print_location(); }else if(t == "debug_step_through"){ debug_step_through(); }else if(t == "debug_leave_town"){ debug_leave_town(); }else if(t == "debug_kill"){ debug_kill(); }else if(t == "debug_magic_map"){ debug_magic_map(); }else if(t == "debug_enter_town"){ debug_enter_town(); }else if(t == "debug_refresh_stores"){ debug_refresh_stores(); }else if(t == "debug_clean_up"){ debug_clean_up(); }else if(t == "debug_stealth_detect_life_firewalk"){ debug_stealth_detect_life_firewalk(); }else if(t == "debug_fly"){ debug_fly(); }else if(t == "debug_ghost_mode"){ debug_ghost_mode(); }else if(t == "debug_return_to_start"){ debug_return_to_start(); }else if(t == "handle_victory"){ handle_victory(); }else if(t == "debug_increase_age"){ debug_increase_age(); }else if(t == "debug_towns_forget"){ debug_towns_forget(); }else if(t == "edit_stuff_done"){ edit_stuff_done(); }else if(t == "debug_heal"){ debug_heal(); }else if(t == "debug_heal_plus_extra"){ debug_heal_plus_extra(); }else if(t == "handle_print_pc_hp"){ handle_print_pc_hp(boost::lexical_cast(next_action.GetText())); }else if(t == "handle_print_pc_sp"){ handle_print_pc_sp(boost::lexical_cast(next_action.GetText())); }else if(t == "give_pc_info"){ give_pc_info(boost::lexical_cast(next_action.GetText())); }else if(t == "handle_trade_places"){ handle_trade_places(boost::lexical_cast(next_action.GetText())); }else if(t == "handle_begin_talk"){ handle_begin_talk(need_reprint); }else if(t == "handle_talk"){ handle_talk(location_from_action(next_action), did_something, need_redraw, need_reprint); }else if(t == "click_talk_rect"){ word_rect_t word_rect = word_rect_from_action(next_action); click_talk_rect(word_rect); handle_talk_node(word_rect.node); return; }else if(t == "click_shop_rect"){ rectangle rect = boost::lexical_cast(next_action.GetText()); click_shop_rect(rect); return; }else if(t == "end_shop_mode"){ end_shop_mode(); return; }else if(t == "scrollbar_setPosition"){ auto info = info_from_action(next_action); std::string name = info["name"]; long newPos = boost::lexical_cast(info["newPos"]); std::shared_ptr sbar = std::dynamic_pointer_cast(event_listeners[name]); sbar->setPosition(newPos); return; }else if(t == "use_spec_item"){ use_spec_item(boost::lexical_cast(next_action.GetText()), need_redraw); update_item_stats_area(need_reprint); }else if(t == "show_item_info"){ show_item_info(boost::lexical_cast(next_action.GetText())); update_item_stats_area(need_reprint); return; }else if(t == "set_stat_window"){ set_stat_window(static_cast(boost::lexical_cast(next_action.GetText()))); return; }else if(t == "handle_sale"){ handle_sale(boost::lexical_cast(next_action.GetText())); }else if(t == "handle_info_request"){ handle_info_request(boost::lexical_cast(next_action.GetText())); }else if(t == "close_map"){ close_map(true); return; }else if(t == "handle_toggle_active"){ handle_toggle_active(need_reprint); return; }else if(t == "handle_parry"){ handle_parry(did_something, need_redraw, need_reprint); }else if(t == "handle_monster_info_menu"){ handle_monster_info_menu(boost::lexical_cast(next_action.GetText())); }else if(t == "cancel_item_target"){ cancel_item_target(); }else if(t == "advance_time"){ if(record_advance_time){ throw std::string { "Replay system internal error! advance_time() was supposed to be called by the last action, but wasn't: " } + _last_action_type; }else{ throw std::string { "The action log you're replaying contains advance_time() recordings. The additional --advance-time flag is required to process it." }; } }else{ std::ostringstream sstr; sstr << "Couldn't replay action: " << next_action; throw sstr.str(); } // TODO some of these actions shouldn't call advance_time(). They should return // from the function early advance_time(did_something, need_redraw, need_reprint); } void init_boe(int argc, char* argv[]) { set_up_apple_events(); init_directories(argv[0]); process_args(argc, argv); #ifdef __APPLE__ init_menubar(); // Do this first of all because otherwise a default File and Window menu will be seen #endif sync_prefs(); init_shaders(); init_tiling(); init_snd_tool(); adjust_window_mode(); init_ui(); // If we don't do this now it'll flash white to start with mainPtr.clear(sf::Color::Black); mainPtr.display(); set_cursor(watch_curs); init_buf(); // Seed the RNG if(replaying) { Element& srand_element = pop_next_action("srand"); std::string ts(srand_element.GetText()); game_rand.seed(std::stoi(ts)); } else { auto t = time(nullptr); if (recording) { std::string ts = boost::lexical_cast(t); record_action("srand", ts); } game_rand.seed(t); } std::cout << game_rand() << std::endl; init_screen_locs(); init_startup(); flushingInput = true; cFramerateLimiter fps_limiter; // Hidden preference to hide the startup logo - should be kept hidden if(get_bool_pref("ShowStartupLogo", true)) show_logo(fps_limiter); // The preference to hide the startup splash is exposed however. if(get_bool_pref("ShowStartupSplash", true)) plop_fancy_startup(fps_limiter); cUniverse::print_result = iLiving::print_result = add_string_to_buf; cPlayer::give_help = give_help; init_fileio(); init_spell_menus(); init_mini_map(); redraw_screen(REFRESH_NONE); showMenuBar(); } void showWelcome() { if(recording){ record_action("showWelcome", ""); } cChoiceDlog welcome("welcome"); welcome->attachClickHandlers([](cDialog& self, std::string clicked, eKeyMod) { launchURL(self[clicked].getText()); return false; }, {"spidweb", "scen", "forum", "home", "src"}); welcome.show(); } using Key = sf::Keyboard::Key; std::map delayed_keys; const int ARROW_SIMUL_FRAMES = 3; // Terrain map coordinates to simulate a click for 8-directional movement // ordered to correspond with eDirection // TODO terrain_click is duplicated (with different ordering) in boe.actions.cpp location terrain_click[8] = { {150,155}, // north {180,135}, // northeast {180,185}, // east {180,215}, // southeast {150,215}, // south {120,215}, // southwest {120,185}, // west {120,155}, // northwest }; static void fire_delayed_key(Key code) { bool isUpPressed = delayed_keys[Key::Up] > 0; bool isDownPressed = delayed_keys[Key::Down] > 0; bool isLeftPressed = delayed_keys[Key::Left] > 0; bool isRightPressed = delayed_keys[Key::Right] > 0; delayed_keys[Key::Up] = 0; delayed_keys[Key::Down] = 0; delayed_keys[Key::Left] = 0; delayed_keys[Key::Right] = 0; bool diagonal = false; int dir = -1; if(code == Key::Up && !isDownPressed) { if(isLeftPressed){ dir = DIR_NW; diagonal = true; } else if(isRightPressed){ dir = DIR_NE; diagonal = true; } else dir = DIR_N; } else if(code == Key::Down && !isUpPressed) { if(isLeftPressed){ dir = DIR_SW; diagonal = true; } else if(isRightPressed){ dir = DIR_SE; diagonal = true; } else dir = DIR_S; } else if(code == Key::Left && !isRightPressed) { if(isUpPressed){ dir = DIR_NW; diagonal = true; } else if(isDownPressed){ dir = DIR_SW; diagonal = true; } else dir = DIR_W; } else if(code == Key::Right && !isLeftPressed) { if(isUpPressed){ dir = DIR_NE; diagonal = true; } else if(isDownPressed){ dir = DIR_SE; diagonal = true; } else dir = DIR_E; } else { return; } if(diagonal){ mainPtr.setKeyRepeatEnabled(false); }else{ mainPtr.setKeyRepeatEnabled(true); } if(dir != -1){ sf::Event pass_event = {sf::Event::MouseButtonPressed}; location pass_point = mainPtr.mapCoordsToPixel(terrain_click[dir], mainView); pass_event.mouseButton.x = pass_point.x; pass_event.mouseButton.y = pass_point.y; queue_fake_event(pass_event); } } static void handle_delayed_key(Key code) { // a keypress of this code is already delayed, so push it through: if(delayed_keys[code] > 0) fire_delayed_key(code); delayed_keys[code] = ARROW_SIMUL_FRAMES; } static void update_delayed_keys() { for(auto elem : delayed_keys){ Key code = elem.first; int countdown = elem.second; if(countdown > 0){ --countdown; delayed_keys[code] = countdown; if(countdown == 0){ fire_delayed_key(code); } } } } void handle_events() { sf::Event currentEvent; cFramerateLimiter fps_limiter; delayed_keys[Key::Left] = 0; delayed_keys[Key::Right] = 0; delayed_keys[Key::Up] = 0; delayed_keys[Key::Down] = 0; while(!All_Done) { if(replaying && has_next_action()){ replay_next_action(); }else{ #ifdef __APPLE__ if (menuChoiceId>=0) { eMenuChoice aMenuChoice=menuChoice; menuChoice=eMenuChoice::MENU_CHOICE_NONE; switch(aMenuChoice) { case eMenuChoice::MENU_CHOICE_GENERIC: handle_menu_choice(eMenu(menuChoiceId)); break; case eMenuChoice::MENU_CHOICE_SPELL: handle_menu_spell(eSpell(menuChoiceId)); break; case eMenuChoice::MENU_CHOICE_MONSTER_INFO: handle_monster_info_menu(menuChoiceId); break; case eMenuChoice::MENU_CHOICE_NONE: break; } menuChoiceId=-1; } #endif update_delayed_keys(); while(!fake_event_queue.empty()){ const sf::Event& next_event = fake_event_queue.front(); fake_event_queue.pop_front(); handle_one_event(next_event, fps_limiter); } while(mainPtr.pollEvent(currentEvent)) handle_one_event(currentEvent, fps_limiter); // It would be nice to have minimap inside the main game window (we have lots of screen space in fullscreen mode). // Alternatively, minimap could live on its own thread. // But for now we just handle events from both windows on this thread. while(map_visible && mini_map.pollEvent(currentEvent)) handle_one_minimap_event(currentEvent); } if(changed_display_mode) { changed_display_mode = false; adjust_window_mode(); init_mini_map(); } // Still no idea what this does. It's possible that this does not work at all. flushingInput = false; // Ideally this call should update all of the things that are happening in the world current tick. // NOTE that update does not mean draw. update_everything(); // Ideally, this should be the only draw call that is done in a cycle. redraw_everything(); // Prevent the loop from executing too fast. fps_limiter.frame_finished(); } } void handle_quit_event() { if(recording){ record_action("close_window", ""); } if(overall_mode == MODE_STARTUP) { if(party_in_memory) { std::string choice = cChoiceDlog("quit-confirm-save", {"save","quit","cancel"}).show(); if(choice == "cancel") return; if(choice == "save") { fs::path file = nav_put_or_temp_party(); if(file.empty()) return; save_party(file, univ); } } All_Done = true; }else if(overall_mode == MODE_TOWN || overall_mode == MODE_OUTDOORS){ std::string choice = cChoiceDlog("quit-confirm-save", {"save", "quit", "cancel"}).show(); if(choice == "cancel") return; if(choice == "save") save_party(univ.file, univ); } else { std::string choice = cChoiceDlog("quit-confirm-nosave", {"quit", "cancel"}).show(); if(choice == "cancel") return; } All_Done = true; } void handle_one_event(const sf::Event& event, cFramerateLimiter& fps_limiter) { // What does this do and should it be here? through_sending(); // What does this do and should it be here? clear_sound_memory(); // If it's just a modifier key, update the state if(kb.handleModifier(event)) return; // Check if any of the event listeners want this event. for(auto & listener : event_listeners) { if(listener.second->handle_event(event)) return; } switch(event.type) { case sf::Event::KeyPressed: if(flushingInput) return; if (delayed_keys.find(event.key.code) != delayed_keys.end()){ handle_delayed_key(event.key.code); } else if(!(event.key.*systemKey)) { mainPtr.setKeyRepeatEnabled(true); handle_keystroke(event, fps_limiter); } break; case sf::Event::MouseButtonPressed: if(flushingInput) return; Mouse_Pressed(event, fps_limiter); break; case sf::Event::MouseLeft: // Make sure we don't have an arrow cursor when it's outside the window set_cursor(sword_curs); break; case sf::Event::GainedFocus: makeFrontWindow(mainPtr); change_cursor({event.mouseMove.x, event.mouseMove.y}); return; case sf::Event::MouseMoved: change_cursor({event.mouseMove.x, event.mouseMove.y}); return; // TODO: EVENT TYPE DEPRECATED IN SFML 2.5.1 case sf::Event::MouseWheelMoved: if(flushingInput) return; handle_scroll(event); break; case sf::Event::Closed: handle_quit_event(); break; default: break; // There's several events we don't need to handle at all } } void queue_fake_event(const sf::Event& event) { fake_event_queue.push_back(event); } void handle_one_minimap_event(const sf::Event& event) { if(event.type == sf::Event::Closed) { close_map(true); } else if(event.type == sf::Event::GainedFocus) { makeFrontWindow(mainPtr); } else if(event.type == sf::Event::KeyPressed) { switch(event.key.code) { case sf::Keyboard::Escape: close_map(true); break; default: break; } } } void update_terrain_animation() { static const long fortyTicks = time_in_ticks(40).asMilliseconds(); if(overall_mode == MODE_STARTUP) return; if(!get_bool_pref("DrawTerrainAnimation", true)) return; if(animTimer.getElapsedTime().asMilliseconds() < fortyTicks) return; anim_ticks++; animTimer.restart(); } void update_startup_animation() { static const long twentyTicks = time_in_ticks(20).asMilliseconds(); if(overall_mode != MODE_STARTUP) return; if(animTimer.getElapsedTime().asMilliseconds() < twentyTicks) return; draw_startup_anim(true); animTimer.restart(); } void update_everything() { update_terrain_animation(); update_startup_animation(); } void redraw_everything() { redraw_screen(REFRESH_ALL); if(map_visible) draw_map(false); } void Mouse_Pressed(const sf::Event& event, cFramerateLimiter& fps_limiter) { // What is this stuff? Why is it here? if(had_text_freeze > 0) { had_text_freeze--; return; } if(overall_mode == MODE_STARTUP) { All_Done = handle_startup_press({event.mouseButton.x, event.mouseButton.y}); } else { All_Done = handle_action(event, fps_limiter); } // Why does every mouse click activate a menu? menu_activate(); } void close_program() { // TODO: Ultimately we would like to have cleanup happen automatically, negating the need for this function // On the Mac, prefs are synced automatically. However, doing it manually won't hurt. // On other platforms, we need to do it manually. sync_prefs(); } extern fs::path progDir; void handle_help_toc() { if(recording){ record_action("handle_help_toc", ""); } if(fs::is_directory(progDir/"doc")) launchURL("file://" + (progDir/"doc/game/Contents.html").string()); else launchURL("http://openboe.com/docs/game/Contents.html"); } void menu_give_help(short help1){ if(recording){ record_action("menu_give_help", boost::lexical_cast(help1)); } give_help(help1, 0); } void handle_menu_choice(eMenu item_hit) { std::string dialogToShow; sf::Event dummyEvent = {sf::Event::KeyPressed}; switch(item_hit) { case eMenu::NONE: break; case eMenu::FILE_OPEN: do_load(); break; case eMenu::FILE_SAVE: do_save(0); break; case eMenu::FILE_SAVE_AS: do_save(1); break; case eMenu::FILE_NEW: new_party(); break; case eMenu::FILE_ABORT: do_abort(); break; case eMenu::PREFS: pick_preferences(); break; case eMenu::QUIT: handle_quit_event(); break; case eMenu::OPTIONS_PC_GRAPHIC: handle_new_pc_graphic(); break; case eMenu::OPTIONS_DELETE_PC: handle_drop_pc(); break; case eMenu::OPTIONS_RENAME_PC: handle_rename_pc(); break; case eMenu::OPTIONS_NEW_PC: handle_new_pc(); break; case eMenu::OPTIONS_JOURNAL: journal(); break; case eMenu::OPTIONS_TALK_NOTES: talk_notes(); break; case eMenu::OPTIONS_ENCOUNTER_NOTES: adventure_notes(); break; case eMenu::OPTIONS_STATS: print_party_stats(); break; case eMenu::HELP_OUT: dialogToShow = "help-outdoor"; break; case eMenu::HELP_TOWN: dialogToShow = "help-town"; break; case eMenu::HELP_COMBAT: dialogToShow = "help-combat"; break; case eMenu::HELP_BARRIER: dialogToShow = "help-fields"; break; case eMenu::HELP_HINTS: dialogToShow = "help-hints"; break; case eMenu::HELP_SPELLS: dialogToShow = "help-magic"; break; case eMenu::ABOUT: dialogToShow = "about-boe"; break; case eMenu::LIBRARY_MAGE: display_spells(eSkill::MAGE_SPELLS,100,nullptr); break; case eMenu::LIBRARY_PRIEST: display_spells(eSkill::PRIEST_SPELLS,100,nullptr); break; case eMenu::LIBRARY_SKILLS: display_skills(eSkill::INVALID,nullptr); break; case eMenu::LIBRARY_ALCHEMY: // TODO: Create a dedicated dialog for alchemy info display_alchemy(); break; case eMenu::LIBRARY_TIPS: tip_of_day(); break; case eMenu::LIBRARY_INTRO: showWelcome(); break; case eMenu::ACTIONS_ALCHEMY: // This will be recorded when the fake event is processed dummyEvent.key.code = sf::Keyboard::A; dummyEvent.key.shift = true; queue_fake_event(dummyEvent); break; case eMenu::ACTIONS_WAIT: // This will be recorded when the fake event is processed dummyEvent.key.code = sf::Keyboard::W; queue_fake_event(dummyEvent); break; case eMenu::ACTIONS_AUTOMAP: display_map(); break; case eMenu::HELP_TOC: handle_help_toc(); break; case eMenu::ABOUT_MAGE: case eMenu::ABOUT_PRIEST: menu_give_help(209); break; case eMenu::ABOUT_MONSTERS: menu_give_help(212); break; } if(!dialogToShow.empty()) { show_dialog_action(dialogToShow); } } // TODO: Let this function take a cMonster* instead of the item_hit void handle_monster_info_menu(int item_hit) { if(recording){ record_action("handle_monster_info_menu", boost::lexical_cast(item_hit)); } display_monst(item_hit - 1, nullptr,1); } static cursor_type get_mode_cursor(){ switch(overall_mode){ case MODE_TOWN_TARGET: case MODE_SPELL_TARGET: case MODE_FIRING: case MODE_THROWING: case MODE_FANCY_TARGET: return target_curs; case MODE_TALK_TOWN: return talk_curs; case MODE_USE_TOWN: return key_curs; case MODE_BASH_TOWN: return boot_curs; case MODE_DROP_TOWN: case MODE_DROP_COMBAT: return drop_curs; case MODE_LOOK_OUTDOORS: case MODE_LOOK_TOWN: case MODE_LOOK_COMBAT: return look_curs; case MODE_RESTING: return wait_curs; default: return sword_curs; } return sword_curs; // this should never be reached, though } void change_cursor(location where_curs) { cursor_type cursor_needed; location cursor_direction; extern enum_map(eGuiArea, rectangle) win_to_rects; rectangle world_screen = win_to_rects[WINRECT_TERVIEW]; world_screen.inset(13, 13); where_curs = mainPtr.mapPixelToCoords(where_curs, mainView); if(!world_screen.contains(where_curs)) cursor_needed = sword_curs; else cursor_needed = get_mode_cursor(); if((overall_mode == MODE_OUTDOORS || overall_mode == MODE_TOWN || overall_mode == MODE_COMBAT) && world_screen.contains(where_curs)){ cursor_direction = get_cur_direction(where_curs); cursor_needed = arrow_curs[cursor_direction.y + 1][cursor_direction.x + 1]; } if(cursor_needed != Cursor::current) set_cursor(cursor_needed); } void move_sound(ter_num_t ter,short step){ static bool on_swamp = false; eTerSpec spec = univ.scenario.ter_types[ter].special; eStepSnd snd = univ.scenario.ter_types[ter].step_sound; // if on swamp don't play squish sound : BoE legacy behavior, can be removed safely if(snd == eStepSnd::SPLASH && !flying() && univ.party.in_boat < 0){ if(on_swamp && get_ran(1,1,100) >= 10) return; on_swamp = true; } else on_swamp = false; if(!monsters_going && !is_combat() && (univ.party.in_boat >= 0)) { if(spec == eTerSpec::TOWN_ENTRANCE) return; play_sound(48); //play boat sound } else if(!monsters_going && !is_combat() && (univ.party.in_horse >= 0)) { play_sound(85); //so play horse sound } else switch(univ.scenario.ter_types[ter].step_sound){ case eStepSnd::SQUISH: play_sound(55); break; case eStepSnd::CRUNCH: play_sound(47); break; case eStepSnd::NONE: break; case eStepSnd::SPLASH: play_sound(17); break; case eStepSnd::STEP: if(step % 2 == 0) //footsteps alternate sound play_sound(49); else play_sound(50); } } void incidental_noises(bool on_surface){ short sound_to_play; if(on_surface){ if(get_ran(1,1,100) < 40){ sound_to_play = get_ran(1,1,3); switch(sound_to_play){ case 1: play_sound(76); break; case 2: play_sound(77); break; case 3: play_sound(91); break; } } }else{ if(get_ran(1,1,100) < 40){ sound_to_play = get_ran(1,1,2); switch(sound_to_play){ case 1: play_sound(78); break; case 2: play_sound(79); break; } } } // Dog: 80 // Cat: 81 // Sheep: 82 // Cow: 83 // Chicken: 92 } void pause(short length) { long len; len = (long) length; // Before pausing, make sure the screen is updated. redraw_screen(REFRESH_NONE); if(get_bool_pref("DrawTerrainFrills", true)) sf::sleep(time_in_ticks(len)); }