diff --git a/.gitignore b/.gitignore index c2b14e19..f50b7cbd 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,8 @@ src/*.vsproj/*/Release/ src/*.vsproj/Debug/ src/*.vsproj/Release/ src/*.vsproj/*/ipch/ +# Output files to work around VS's lack of output within the IDE +bladeslog.txt # Shader Builder projects # It's an sqlite3 database and not particularly important, so exclude it @@ -53,3 +55,6 @@ src/tools/*.sbproj # Doxygen stuff src/doxy/html/ src/doxy/doxy_warnings.txt + +# Windows junk files +Thumbs.db diff --git a/src/BoE.vsproj/Common/Common.vcxproj b/src/BoE.vsproj/Common/Common.vcxproj index 4a856cce..7b100016 100644 --- a/src/BoE.vsproj/Common/Common.vcxproj +++ b/src/BoE.vsproj/Common/Common.vcxproj @@ -11,6 +11,7 @@ + @@ -45,6 +46,7 @@ + @@ -96,16 +98,19 @@ + + + {1347FE79-73BD-4176-9280-0FE39E3534E2} @@ -146,6 +151,7 @@ ..\..\tools\resmgr;..\..\tools\gzstream;..\..\tools;..\..\dialogxml\xml-parser;..\..\dialogxml;..\..\classes;..\..;%(AdditionalIncludeDirectories) false /FS %(AdditionalOptions) + 4800;4290 Windows @@ -153,6 +159,10 @@ opengl32.lib;sfml-system.lib;sfml-window.lib;sfml-graphics.lib;sfml-audio.lib;zlib.lib;%(AdditionalDependencies) false + + call $(ProjectDir)CopyFiles.bat $(SolutionDir)$(Configuration) + Copy resource files + diff --git a/src/BoE.vsproj/Common/Common.vcxproj.filters b/src/BoE.vsproj/Common/Common.vcxproj.filters index 6af471e9..9b4dea7a 100644 --- a/src/BoE.vsproj/Common/Common.vcxproj.filters +++ b/src/BoE.vsproj/Common/Common.vcxproj.filters @@ -194,6 +194,12 @@ Tools\Header Files + + Classes + + + Classes + @@ -322,5 +328,14 @@ Tools\Source Files + + Tools\Source Files + + + Tools\Source Files + + + Tools\Source Files + \ No newline at end of file diff --git a/src/BoE.vsproj/Common/CopyFiles.bat b/src/BoE.vsproj/Common/CopyFiles.bat new file mode 100644 index 00000000..fe167814 --- /dev/null +++ b/src/BoE.vsproj/Common/CopyFiles.bat @@ -0,0 +1,31 @@ + +@echo off +echo Copying files... +set ResourceDir=%1\..\..\..\rsrc +set TargetDir=%1 +set ScenEdDir=%TargetDir%\"Scenario Editor" +@echo on + +@echo Copying graphics... +xcopy %ResourceDir%\graphics.exd %ScenEdDir%\graphics.exd /s /y /i /d + +@echo Copying sound effects... +xcopy %ResourceDir%\sounds.exa %ScenEdDir%\sounds.exa /s /y /i /d + +@echo Copying dialog definitions... +xcopy %ResourceDir%\dialogs\*.xml %TargetDir%\data\dialogs /s /y /i /d + +@echo Copying fonts... +xcopy %ResourceDir%\fonts %TargetDir%\data\fonts /s /y /i /d + +@echo Copying string lists... +xcopy %ResourceDir%\strings %TargetDir%\data\strings /s /y /i /d + +@echo Copying shaders... +xcopy %1\..\..\tools\mask.* %TargetDir%\data\shaders /s /y /i /d + +@echo Copying base scenarios... +xcopy %ResourceDir%\"Blades of Exile Bases" %ScenEdDir%\"Blades of Exile Base" /s /y /i /d + +@echo Copying scenario files... +xcopy %ResourceDir%\"Blades of Exile Scenarios" %TargetDir%\"Blades of Exile Scenarios" /s /y /i /d \ No newline at end of file diff --git a/src/BoE.vsproj/Game/Blades of Exile.rc b/src/BoE.vsproj/Game/Blades of Exile.rc index 02a7f919..424b32e2 100644 Binary files a/src/BoE.vsproj/Game/Blades of Exile.rc and b/src/BoE.vsproj/Game/Blades of Exile.rc differ diff --git a/src/BoE.vsproj/Game/Blades of Exile.vcxproj b/src/BoE.vsproj/Game/Blades of Exile.vcxproj index 225e595e..04f42e95 100644 --- a/src/BoE.vsproj/Game/Blades of Exile.vcxproj +++ b/src/BoE.vsproj/Game/Blades of Exile.vcxproj @@ -53,14 +53,18 @@ WIN32;_DEBUG;_WINDOWS;TIXML_USE_TICPP;%(PreprocessorDefinitions) - ..\..\tools\resmgr;..\..\tools\gzstream;..\..\tools;..\..\dialogxml\xml-parser;..\..\dialogxml;..\..\classes;..\..;%(AdditionalIncludeDirectories) + ..\..\tools\resmgr;..\..\tools\gzstream;..\..\tools;..\..\dialogxml\xml-parser;..\..\dialogxml;..\..\classes;..\..;.;%(AdditionalIncludeDirectories) + 4800;4290 Windows true - opengl32.lib;sfml-system.lib;sfml-window.lib;sfml-graphics.lib;sfml-audio.lib;zlib.lib;%(AdditionalDependencies) + opengl32.lib;sfml-system-d.lib;sfml-window-d.lib;sfml-graphics-d.lib;sfml-audio-d.lib;zlib.lib;%(AdditionalDependencies) mainCRTStartup + + ..;%(AdditionalIncludeDirectories) + @@ -124,6 +128,7 @@ + diff --git a/src/BoE.vsproj/Game/Blades of Exile.vcxproj.filters b/src/BoE.vsproj/Game/Blades of Exile.vcxproj.filters index 7f56b160..c3f1d780 100644 --- a/src/BoE.vsproj/Game/Blades of Exile.vcxproj.filters +++ b/src/BoE.vsproj/Game/Blades of Exile.vcxproj.filters @@ -159,5 +159,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/src/BoE.vsproj/Game/Resource.h b/src/BoE.vsproj/Game/Resource.h index 56170d5c..bc686d36 100644 --- a/src/BoE.vsproj/Game/Resource.h +++ b/src/BoE.vsproj/Game/Resource.h @@ -2,30 +2,51 @@ // Microsoft Visual C++ generated include file. // Used by Blades of Exile.rc // +#define IDI_BLADESOFEXILE 107 +#define IDC_BLADESOFEXILE 109 +#define IDM_FILE_OPEN 110 +#define IDM_FILE_SAVE 111 +#define IDM_FILE_SAVE_AS 112 +#define IDM_FILE_NEW 113 +#define IDM_FILE_PREFS 114 +#define IDM_FILE_QUIT 115 +#define IDM_OPTIONS_GRAPHIC 116 +#define IDM_OPTIONS_NAME 117 +#define IDM_OPTIONS_NEW 118 +#define IDM_OPTIONS_DELETE 119 +#define IDM_OPTIONS_TALKING 120 +#define IDM_OPTIONS_ENCOUNTER 121 +#define IDM_OPTIONS_STATS 122 +#define IDM_ACTIONS_ALCHEMY 123 +#define IDM_ACTIONS_WAIT 124 +#define IDM_ACTIONS_MAP 125 +#define IDM_MONSTERS_ABOUT 126 +#define IDM_MAGE_ABOUT 127 +#define IDM_PRIEST_ABOUT 128 +#define IDM_FILE_ABORT 129 +#define IDM_LIBRARY_MAGE 130 +#define IDM_LIBRARY_PRIEST 131 +#define IDM_LIBRARY_SKILLS 132 +#define IDM_LIBRARY_ALCHEMY 133 +#define IDM_LIBRARY_TIPS 134 +#define IDM_LIBRARY_INTRO 135 +#define IDM_HELP_INDEX 136 +#define IDM_HELP_OUTDOOR 137 +#define IDM_HELP_COMBAT 138 +#define IDM_HELP_BARRIER 139 +#define IDM_HELP_HINTS 140 +#define IDM_HELP_SPELLS 141 +#define IDM_HELP_ABOUT 142 +#define IDM_HELP_TOWN 143 -#define IDS_APP_TITLE 103 - -#define IDR_MAINFRAME 128 -#define IDD_BLADESOFEXILE_DIALOG 102 -#define IDD_ABOUTBOX 103 -#define IDM_ABOUT 104 -#define IDM_EXIT 105 -#define IDI_BLADESOFEXILE 107 -#define IDI_SMALL 108 -#define IDC_BLADESOFEXILE 109 -#define IDC_MYICON 2 -#ifndef IDC_STATIC -#define IDC_STATIC -1 -#endif // Next default values for new objects -// +// #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS - -#define _APS_NO_MFC 130 -#define _APS_NEXT_RESOURCE_VALUE 129 -#define _APS_NEXT_COMMAND_VALUE 32771 -#define _APS_NEXT_CONTROL_VALUE 1000 -#define _APS_NEXT_SYMED_VALUE 110 +#define _APS_NO_MFC 1 +#define _APS_NEXT_RESOURCE_VALUE 129 +#define _APS_NEXT_COMMAND_VALUE 32774 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 145 #endif #endif diff --git a/src/BoE.vsproj/PcEdit/Char Editor.vcxproj b/src/BoE.vsproj/PcEdit/Char Editor.vcxproj index cc54f084..57a7c927 100644 --- a/src/BoE.vsproj/PcEdit/Char Editor.vcxproj +++ b/src/BoE.vsproj/PcEdit/Char Editor.vcxproj @@ -41,6 +41,7 @@ true + $(SolutionDir)$(Configuration)\Scenario Editor\ false @@ -52,7 +53,8 @@ Level3 Disabled WIN32;_DEBUG;_WINDOWS;TIXML_USE_TICPP;%(PreprocessorDefinitions) - ..\..\tools\resmgr;..\..\tools\gzstream;..\..\tools;..\..\dialogxml\xml-parser;..\..\dialogxml;..\..\classes;..\..;%(AdditionalIncludeDirectories) + ..\..\tools\resmgr;..\..\tools\gzstream;..\..\tools;..\..\dialogxml\xml-parser;..\..\dialogxml;..\..\classes;..\..;.;%(AdditionalIncludeDirectories) + 4800;4290 Windows @@ -84,6 +86,7 @@ + diff --git a/src/BoE.vsproj/PcEdit/Char Editor.vcxproj.filters b/src/BoE.vsproj/PcEdit/Char Editor.vcxproj.filters index 0402c0a4..a30cd1b2 100644 --- a/src/BoE.vsproj/PcEdit/Char Editor.vcxproj.filters +++ b/src/BoE.vsproj/PcEdit/Char Editor.vcxproj.filters @@ -30,6 +30,9 @@ Source Files + + Source Files + diff --git a/src/BoE.vsproj/ScenEdit/Scen Editor.vcxproj b/src/BoE.vsproj/ScenEdit/Scen Editor.vcxproj index b3ef575e..577f694f 100644 --- a/src/BoE.vsproj/ScenEdit/Scen Editor.vcxproj +++ b/src/BoE.vsproj/ScenEdit/Scen Editor.vcxproj @@ -41,6 +41,7 @@ true + $(SolutionDir)$(Configuration)\Scenario Editor\ false @@ -52,7 +53,8 @@ Level3 Disabled WIN32;_DEBUG;_WINDOWS;TIXML_USE_TICPP;%(PreprocessorDefinitions) - ..\..\tools\resmgr;..\..\tools\gzstream;..\..\tools;..\..\dialogxml\xml-parser;..\..\dialogxml;..\..\classes;..\..;%(AdditionalIncludeDirectories) + ..\..\tools\resmgr;..\..\tools\gzstream;..\..\tools;..\..\dialogxml\xml-parser;..\..\dialogxml;..\..\classes;..\..;.;%(AdditionalIncludeDirectories) + 4800;4290 Windows @@ -89,6 +91,7 @@ + diff --git a/src/BoE.vsproj/ScenEdit/Scen Editor.vcxproj.filters b/src/BoE.vsproj/ScenEdit/Scen Editor.vcxproj.filters index b9437549..5f4b11d3 100644 --- a/src/BoE.vsproj/ScenEdit/Scen Editor.vcxproj.filters +++ b/src/BoE.vsproj/ScenEdit/Scen Editor.vcxproj.filters @@ -44,6 +44,9 @@ Source Files + + Source Files + diff --git a/src/boe.actions.cpp b/src/boe.actions.cpp index bbfebf62..59d689ee 100644 --- a/src/boe.actions.cpp +++ b/src/boe.actions.cpp @@ -81,7 +81,8 @@ long dummy; short store_selling_values[8] = {0,0,0,0,0,0,0,0}; extern cShop active_shop; -extern short cen_x, cen_y, stat_window,give_delays;//,pc_moves[6]; +extern short cen_x, cen_y, stat_window;//,pc_moves[6]; +extern bool give_delays; extern eGameMode overall_mode; extern location to_create; extern bool All_Done,play_sounds,frills_on,spell_forced,save_maps,monsters_going; diff --git a/src/boe.fileio.cpp b/src/boe.fileio.cpp index e8e95b7a..e7443aec 100644 --- a/src/boe.fileio.cpp +++ b/src/boe.fileio.cpp @@ -27,7 +27,8 @@ #define DONE_BUTTON_ITEM 1 -extern short give_delays,stat_screen_mode; +extern short stat_screen_mode; +extern bool give_delays; extern eGameMode overall_mode; extern bool play_sounds,sys_7_avail,save_maps,party_in_memory,in_scen_debug,ghost_mode; extern location center; diff --git a/src/boe.graphics.cpp b/src/boe.graphics.cpp index 59805cff..69584272 100644 --- a/src/boe.graphics.cpp +++ b/src/boe.graphics.cpp @@ -29,7 +29,8 @@ #include "boe.menus.h" extern sf::RenderWindow mainPtr; -extern short stat_window,give_delays; +extern short stat_window; +extern bool give_delays; extern eGameMode overall_mode; extern short current_spell_range; extern bool anim_onscreen,play_sounds,frills_on,startup_loaded,party_in_memory; @@ -167,6 +168,7 @@ void adjust_window_mode() { sf::ContextSettings winSettings; winSettings.stencilBits = 1; sf::VideoMode desktop = sf::VideoMode::getDesktopMode(); + hideMenuBar(); // TODO: Make display_mode an enum if(display_mode == 5) { @@ -194,6 +196,8 @@ void adjust_window_mode() { item_sbar->relocate({ul.x + 546,ul.y + 146}); shop_sbar->relocate({ul.x + 258,ul.y + 67}); } + init_menubar(); + showMenuBar(); } // TODO: Move to boe.startup.cpp diff --git a/src/boe.graphutil.cpp b/src/boe.graphutil.cpp index 837487ea..cccfd2e3 100644 --- a/src/boe.graphutil.cpp +++ b/src/boe.graphutil.cpp @@ -23,7 +23,8 @@ extern sf::RenderWindow mainPtr; extern rectangle windRect; -extern short stat_window,give_delays; +extern short stat_window; +extern bool give_delays; extern eGameMode overall_mode; extern short current_spell_range; extern bool anim_onscreen,play_sounds,frills_on,startup_loaded; diff --git a/src/boe.main.cpp b/src/boe.main.cpp index 49ce2b77..63912c1d 100644 --- a/src/boe.main.cpp +++ b/src/boe.main.cpp @@ -28,6 +28,7 @@ #include "scrollbar.hpp" #include "boe.menus.h" #include "cursors.h" +#include "prefs.hpp" extern cursor_type arrow_curs[3][3]; extern cursor_type current_cursor; @@ -91,7 +92,7 @@ eGameMode overall_mode = MODE_STARTUP; bool first_update = true,anim_onscreen = false,frills_on = true,changed_display_mode = false,suppress_stat_screen = false; short stat_window = 0,store_modifier; bool monsters_going = false,boom_anim_active = false; -short give_delays = 0; +bool give_delays = false; sf::RenderWindow mini_map; //rectangle d_rects[80]; @@ -145,6 +146,7 @@ int main(int /*argc*/, char* argv[]) { init_directories(argv[0]); //data_store = (piles_of_stuff_dumping_type *) NewPtr(sizeof(piles_of_stuff_dumping_type)); init_menubar(); // Do this first of all because otherwise a default File and Window menu will be seen + sync_prefs(); init_graph_tool(); Initialize(); init_fileio(); @@ -427,19 +429,9 @@ void Mouse_Pressed() { void close_program() { // TODO: Ultimately we would like to have cleanup happen automatically, negating the need for this function //end_music(); -} - -void handle_apple_menu(int item_hit) { - - switch(item_hit) { - case 1: - cChoiceDlog("about-boe").show(); - break; - default: -// GetItem (apple_menu,item_hit,desk_acc_name); -// desk_acc_num = OpenDeskAcc(desk_acc_name); - break; - } + // 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(); } void handle_file_menu(int item_hit) { @@ -603,6 +595,7 @@ void handle_help_menu(int item_hit) { case 4: dialogToShow = "help-fields"; break; case 6: dialogToShow = "help-hints"; break; case 7: dialogToShow = "help-magic"; break; + case 10: dialogToShow = "about-boe"; break; } if(!dialogToShow.empty()) cChoiceDlog(dialogToShow).show(); @@ -825,7 +818,7 @@ void pause(short length) { redraw_screen(REFRESH_NONE); mainPtr.display(); - if(give_delays == 0) + if(give_delays) sf::sleep(time_in_ticks(len)); } diff --git a/src/boe.main.h b/src/boe.main.h index b469f16e..4c109ea6 100644 --- a/src/boe.main.h +++ b/src/boe.main.h @@ -7,8 +7,6 @@ void Handle_One_Event(); void Handle_Update(); void Mouse_Pressed(); void close_program(); -//void handle_menu_choice(long choice); -void handle_apple_menu(int item_hit); void handle_file_menu(int item_hit); void handle_options_menu(int item_hit); void handle_help_menu(int item_hit); diff --git a/src/boe.menus.mac.mm b/src/boe.menus.mac.mm index 48927e8f..8d86d8df 100644 --- a/src/boe.menus.mac.mm +++ b/src/boe.menus.mac.mm @@ -34,7 +34,6 @@ MenuHandle apple_menu,file_menu,extra_menu,help_menu,monster_info_menu,library_m MenuHandle actions_menu,music_menu,mage_spells_menu,priest_spells_menu; @interface MenuHandler : NSObject --(void) appMenu:(id) sender; -(void) fileMenu:(id) sender; -(void) optMenu:(id) sender; -(void) actMenu:(id) sender; @@ -104,6 +103,9 @@ static void setMenuCallback(NSMenuItem* item, id targ, SEL selector, int num) { } void init_menubar() { + static bool inited = false; + if(inited) return; + inited = true; NSApplication* app = [NSApplication sharedApplication]; [NSBundle loadNibNamed: @"menu" owner: app]; menu_bar_handle = [app mainMenu]; @@ -120,8 +122,7 @@ void init_menubar() { MenuHandler* handler = [[[MenuHandler alloc] init] retain]; setMenuCallback([help_menu itemAtIndex: 0], handler, @selector(onlineHelp:), 0); - // TODO: Do away with "handle_apple_menu" and "appMenu" in favour of pushing "About" to the help menu - setMenuCallback([apple_menu itemWithTitle: @"About Blades of Exile"], handler, @selector(appMenu:), 1); + setMenuCallback([apple_menu itemWithTitle: @"About Blades of Exile"], handler, @selector(helpMenu:), 10); setMenuCallback([apple_menu itemWithTitle: @"Preferences…"], handler, @selector(fileMenu:), 6); setMenuCallback([apple_menu itemWithTitle: @"Quit Blades of Exile"], handler, @selector(fileMenu:), 8); // TODO: Check to make sure that Cocoa 0-indexes its menus @@ -271,7 +272,6 @@ void menu_activate() { [[file_menu itemWithTitle: @"Save As…"] setEnabled: YES]; } -void handle_apple_menu(int item_hit); void handle_file_menu(int item_hit); void handle_options_menu(int item_hit); void handle_help_menu(int item_hit); @@ -281,10 +281,6 @@ void handle_monster_info_menu(int item_hit); void handle_menu_spell(eSpell spell_picked); @implementation MenuHandler --(void) appMenu:(id) sender { - handle_apple_menu([[sender representedObject] intValue]); -} - -(void) fileMenu:(id) sender { handle_file_menu([[sender representedObject] intValue]); } diff --git a/src/boe.menus.win.cpp b/src/boe.menus.win.cpp new file mode 100644 index 00000000..7c6b9a40 --- /dev/null +++ b/src/boe.menus.win.cpp @@ -0,0 +1,265 @@ + +#include "boe.menus.h" +#include +#include "Resource.h" +#include "universe.h" +#include "boe.party.h" +#include "boe.infodlg.h" +#include "boe.consts.h" +#include "spell.hpp" + +// Include this last because some #defines in the Windows headers cause compile errors in my headers. +// Fortunately they're on symbols not used in this file, so this should work. +#include + +// This is the index of each menu on the menubar +enum { + FILE_MENU_POS = 0, + OPT_MENU_POS = 1, + ACT_MENU_POS = 2, + MONST_MENU_POS = 3, + MAGE_MENU_POS = 4, + PRIEST_MENU_POS = 5, + LIB_MENU_POS = 6, + HELP_MENU_POS = 7, +}; + +extern short on_spell_menu[2][62]; +extern short on_monst_menu[256]; +extern bool party_in_memory; +extern short current_pc; +extern cUniverse univ; +extern eGameMode overall_mode; +extern sf::RenderWindow mainPtr; +LONG_PTR mainProc; +HMENU menuHandle = NULL; + +LRESULT CALLBACK menuProc(HWND handle, UINT message, WPARAM wParam, LPARAM lParam); + +void init_menubar() { + HWND winHandle = mainPtr.getSystemHandle(); + if(winHandle == NULL) return; + if(menuHandle == NULL) + menuHandle = LoadMenu(GetModuleHandle(NULL), MAKEINTRESOURCE(IDC_BLADESOFEXILE)); + SetMenu(winHandle, menuHandle); + // Now we have to do a little hack to handle menu messages. + // We replace SFML's window procedure with our own, which checks for menu events and then forwards to SFML's procedure. + mainProc = SetWindowLongPtr(winHandle, GWLP_WNDPROC, reinterpret_cast(&menuProc)); +} + +void adjust_monst_menu() { + if(menuHandle == NULL) return; + short i, monst_pos = 0; + HMENU monst_menu; + + if(overall_mode == MODE_STARTUP) return; + + monst_menu = GetSubMenu(menuHandle, MONST_MENU_POS); + for(i = 0; i < 256; i++) { + on_monst_menu[i] = -1; + } + for(i = 1; i < 256; i++) { + if((i == 1) || (univ.party.m_noted[i] > 0)) { + on_monst_menu[monst_pos] = i; + monst_pos++; + } + } + + while(GetMenuItemCount(monst_menu) > 2) { + RemoveMenu(monst_menu, 2, MF_BYPOSITION); + } + for(i = 0; i < 256; i++) { + if(on_monst_menu[i] >= 0) { + std::string monst_name = univ.scenario.scen_monsters[on_monst_menu[i]].m_name; + AppendMenuA(monst_menu, MF_STRING | MF_ENABLED, 1000 + i, monst_name.c_str()); + } + } + DrawMenuBar(mainPtr.getSystemHandle()); +} + +void init_spell_menus() { + short i, j; + + for(i = 0; i < 2; i++) + for(j = 0; j < 62; j++) + on_spell_menu[i][j] = -1; +} + +void adjust_spell_menus() { + if(menuHandle == NULL) return; + short i, j, spell_pos = 0; + HMENU spell_menu; + char spell_name[256]; + short old_on_spell_menu[2][62]; + bool need_menu_change = false; + + if(overall_mode == MODE_STARTUP || current_pc == 6) + return; + + for(i = 0; i < 2; i++) + for(j = 0; j < 62; j++) + old_on_spell_menu[i][j] = on_spell_menu[i][j]; + + spell_menu = GetSubMenu(menuHandle, MAGE_MENU_POS); + + for(i = 0; i < 62; i++) { + on_spell_menu[0][i] = -1; + } + for(i = 0; i < 62; i++) + if(pc_can_cast_spell(current_pc, cSpell::fromNum(eSkill::MAGE_SPELLS, i))) { + on_spell_menu[0][spell_pos] = i; + spell_pos++; + } + for(i = 0; i < 62; i++) + if(on_spell_menu[0][i] != old_on_spell_menu[0][i]) + need_menu_change = true; + if(need_menu_change) { + while(GetMenuItemCount(spell_menu) > 2) { + RemoveMenu(spell_menu, 2, MF_BYPOSITION); + } + for(i = 0; i < 62; i++) + if(on_spell_menu[0][i] >= 0) { + eSpell spell = cSpell::fromNum(eSkill::MAGE_SPELLS, on_spell_menu[0][i]); + std::string name = get_str("magic-names", i + 1); + if((*spell).cost >= 0) + sprintf(spell_name, " L%d - %s, C %d", (*spell).level, name.c_str(), (*spell).cost); + else sprintf(spell_name, " L%d - %s, C ?", (*spell).level, name.c_str()); + AppendMenuA(spell_menu, MF_STRING | MF_ENABLED, 2000 + i, spell_name); + } + } + + need_menu_change = false; + spell_pos = 0; + + spell_menu = GetSubMenu(menuHandle, PRIEST_MENU_POS); + + for(i = 0; i < 62; i++) { + on_spell_menu[1][i] = -1; + } + for(i = 0; i < 62; i++) + if(pc_can_cast_spell(current_pc, cSpell::fromNum(eSkill::PRIEST_SPELLS, i))) { + on_spell_menu[1][spell_pos] = i; + spell_pos++; + } + for(i = 0; i < 62; i++) + if(on_spell_menu[1][i] != old_on_spell_menu[1][i]) + need_menu_change = true; + if(need_menu_change) { + while(GetMenuItemCount(spell_menu) > 2) { + RemoveMenu(spell_menu, 2, MF_BYPOSITION); + } + for(i = 0; i < 62; i++) + if(on_spell_menu[1][i] >= 0) { + eSpell spell = cSpell::fromNum(eSkill::MAGE_SPELLS, on_spell_menu[1][i]); + std::string name = get_str("magic-names", i + 101); + if((*spell).cost >= 0) + sprintf(spell_name, " L%d - %s, C %d", (*spell).level, name.c_str(), (*spell).cost); + else sprintf(spell_name, " L%d - %s, C ?", (*spell).level, name.c_str()); + AppendMenuA(spell_menu, MF_STRING | MF_ENABLED, 2000 + i, spell_name); + } + } + + +} + +void menu_activate() { + if(menuHandle == NULL) return; + HMENU file_menu = GetSubMenu(menuHandle, FILE_MENU_POS); + if(overall_mode == MODE_STARTUP) { + EnableMenuItem(menuHandle, OPT_MENU_POS, MF_GRAYED | MF_BYPOSITION); + EnableMenuItem(menuHandle, MONST_MENU_POS, MF_GRAYED | MF_BYPOSITION); + EnableMenuItem(menuHandle, ACT_MENU_POS, MF_GRAYED | MF_BYPOSITION); + EnableMenuItem(menuHandle, MAGE_MENU_POS, MF_GRAYED | MF_BYPOSITION); + EnableMenuItem(menuHandle, PRIEST_MENU_POS, MF_GRAYED | MF_BYPOSITION); + + EnableMenuItem(file_menu, IDM_FILE_SAVE, MF_GRAYED | MF_BYCOMMAND); + if(!party_in_memory) { + EnableMenuItem(file_menu, IDM_FILE_SAVE_AS, MF_GRAYED | MF_BYCOMMAND); + } else { + EnableMenuItem(file_menu, IDM_FILE_SAVE_AS, MF_ENABLED | MF_BYCOMMAND); + } + } else { + EnableMenuItem(menuHandle, OPT_MENU_POS, MF_ENABLED | MF_BYPOSITION); + EnableMenuItem(menuHandle, MONST_MENU_POS, MF_ENABLED | MF_BYPOSITION); + EnableMenuItem(menuHandle, ACT_MENU_POS, MF_ENABLED | MF_BYPOSITION); + EnableMenuItem(menuHandle, MAGE_MENU_POS, MF_ENABLED | MF_BYPOSITION); + EnableMenuItem(menuHandle, PRIEST_MENU_POS, MF_ENABLED | MF_BYPOSITION); + + EnableMenuItem(file_menu, IDM_FILE_SAVE, MF_ENABLED | MF_BYCOMMAND); + EnableMenuItem(file_menu, IDM_FILE_SAVE_AS, MF_ENABLED | MF_BYCOMMAND); + } + DrawMenuBar(mainPtr.getSystemHandle()); +} + +void hideMenuBar() { + SetMenu(mainPtr.getSystemHandle(), NULL); + DrawMenuBar(mainPtr.getSystemHandle()); +} + +void showMenuBar() { + SetMenu(mainPtr.getSystemHandle(), menuHandle); + DrawMenuBar(mainPtr.getSystemHandle()); +} + +void handle_file_menu(int item_hit); +void handle_options_menu(int item_hit); +void handle_help_menu(int item_hit); +void handle_library_menu(int item_hit); +void handle_actions_menu(int item_hit); +void handle_monster_info_menu(int item_hit); +void handle_menu_spell(eSpell spell_picked); + +LRESULT CALLBACK menuProc(HWND handle, UINT message, WPARAM wParam, LPARAM lParam) { + if(message == WM_COMMAND) { + int cmd = LOWORD(wParam); + if(cmd >= 1000 && cmd < 2000) { + handle_monster_info_menu(cmd - 1000); + } else if(cmd >= 2000 && cmd < 3000) { + handle_menu_spell(cSpell::fromNum(eSkill::MAGE_SPELLS, cmd - 2000)); + } else if(cmd >= 3000 && cmd < 4000) { + handle_menu_spell(cSpell::fromNum(eSkill::PRIEST_SPELLS, cmd - 3000)); + } else switch(cmd) { + // File menu + case IDM_FILE_NEW: handle_file_menu(4); break; + case IDM_FILE_OPEN: handle_file_menu(1); break; + case IDM_FILE_ABORT: handle_file_menu(0); break; + case IDM_FILE_SAVE: handle_file_menu(2); break; + case IDM_FILE_SAVE_AS: handle_file_menu(3); break; + case IDM_FILE_PREFS: handle_file_menu(6); break; + case IDM_FILE_QUIT: handle_file_menu(8); break; + // Options menu + case IDM_OPTIONS_GRAPHIC: handle_options_menu(1); break; + case IDM_OPTIONS_NAME: handle_options_menu(2); break; + case IDM_OPTIONS_NEW: handle_options_menu(3); break; + case IDM_OPTIONS_DELETE: handle_options_menu(4); break; + case IDM_OPTIONS_TALKING: handle_options_menu(6); break; + case IDM_OPTIONS_ENCOUNTER: handle_options_menu(7); break; + case IDM_OPTIONS_STATS: handle_options_menu(8); break; + // Actions menu + case IDM_ACTIONS_ALCHEMY: handle_actions_menu(1); break; + case IDM_ACTIONS_WAIT: handle_actions_menu(2); break; + case IDM_ACTIONS_MAP: handle_actions_menu(3); break; + // Monster/Spell menu About items + case IDM_MONSTERS_ABOUT: break; + case IDM_MAGE_ABOUT: give_help(209, 0); break; + case IDM_PRIEST_ABOUT: give_help(209, 0); break; + // Library menu + case IDM_LIBRARY_MAGE: handle_library_menu(1); break; + case IDM_LIBRARY_PRIEST: handle_library_menu(2); break; + case IDM_LIBRARY_ALCHEMY: handle_library_menu(3); break; + case IDM_LIBRARY_SKILLS: handle_library_menu(4); break; + case IDM_LIBRARY_TIPS: handle_library_menu(5); break; + case IDM_LIBRARY_INTRO: handle_library_menu(7); break; + // Help menu + case IDM_HELP_INDEX: ShellExecuteA(NULL, "open", "https://calref.net/~sylae/boe-doc/game/Contents.html", NULL, NULL, SW_SHOWNORMAL); break; + case IDM_HELP_ABOUT: handle_help_menu(10); break; + case IDM_HELP_OUTDOOR: handle_help_menu(1); break; + case IDM_HELP_TOWN: handle_help_menu(2); break; + case IDM_HELP_COMBAT: handle_help_menu(3); break; + case IDM_HELP_BARRIER: handle_help_menu(4); break; + case IDM_HELP_HINTS: handle_help_menu(6); break; + case IDM_HELP_SPELLS: handle_help_menu(7); break; + } + } + return CallWindowProc(reinterpret_cast(mainProc), handle, message, wParam, lParam); +} diff --git a/src/boe.party.cpp b/src/boe.party.cpp index b0c0588c..983a121c 100644 --- a/src/boe.party.cpp +++ b/src/boe.party.cpp @@ -242,7 +242,7 @@ void put_party_in_scen(std::string scen_name) { bool item_took = false; for(j = 0; j < 6; j++) { - univ.party[i].status.clear(); + univ.party[j].status.clear(); if(isSplit(univ.party[j].main_status)) univ.party[j].main_status -= eMainStatus::SPLIT; univ.party[j].cur_health = univ.party[j].max_health; diff --git a/src/classes/location.cpp b/src/classes/location.cpp index 52878f76..179984c6 100644 --- a/src/classes/location.cpp +++ b/src/classes/location.cpp @@ -73,7 +73,13 @@ rectangle::rectangle() : top(0), left(0), right(0), bottom(0) {} rectangle::rectangle(location tl, location br) : top(tl.y), left(tl.x), right(br.x), bottom(br.y) {} rectangle::rectangle(int t, int l, int b, int r) : top(t), left(l), right(r), bottom(b) {} rectangle::rectangle(sf::Texture& texture) : top(0), left(0), right(texture.getSize().x), bottom(texture.getSize().y) {} -rectangle::rectangle(sf::RenderTarget& texture) : top(0), left(0), right(texture.getSize().x), bottom(texture.getSize().y) {} +rectangle::rectangle(sf::RenderTarget& texture) : top(0), left(0), right(texture.getSize().x), bottom(texture.getSize().y) { + // This is a hack to work around the menubar in Windows affecting the rect returned by getSize(), despite the available area being unchanged. + extern sf::RenderWindow mainPtr; + extern int getMenubarHeight(); + if(&texture == &mainPtr) + bottom += getMenubarHeight(); +} bool rectangle::contains(location p){ if(p.y >= top && p.y <= bottom && p.x >= left && p.x <= right) diff --git a/src/classes/regtown.cpp b/src/classes/regtown.cpp index 55b07e5b..b55845eb 100644 --- a/src/classes/regtown.cpp +++ b/src/classes/regtown.cpp @@ -294,6 +294,7 @@ cBigTown::cBigTown(cScenario& scenario, bool init_strings) : cTown(scenario, ini terrain(i,j) = scenario.default_ground * 2; lighting(i / 8,j) = 0; } + init_start(); } cMedTown::cMedTown(cScenario& scenario, bool init_strings) : cTown(scenario, init_strings) { @@ -310,6 +311,7 @@ cMedTown::cMedTown(cScenario& scenario, bool init_strings) : cTown(scenario, ini terrain(i,j) = scenario.default_ground * 2; lighting(i / 8,j) = 0; } + init_start(); } cTinyTown::cTinyTown(cScenario& scenario, bool init_strings) : cTown(scenario, init_strings) { @@ -326,6 +328,7 @@ cTinyTown::cTinyTown(cScenario& scenario, bool init_strings) : cTown(scenario, i terrain(i,j) = scenario.default_ground * 2; lighting(i / 8,j) = 0; } + init_start(); } short cBigTown::max_dim() const { diff --git a/src/classes/town.cpp b/src/classes/town.cpp index ef8f0a8e..9e842379 100644 --- a/src/classes/town.cpp +++ b/src/classes/town.cpp @@ -79,9 +79,8 @@ void cTown::append(legacy::town_record_type& old){ strong_barriers = defy_scrying = defy_mapping = false; } -short max_dim[3] = {64,48,32}; cTown::cTown(cScenario& scenario, bool init_strings) : scenario(scenario) { - short i,s; + short i; location d_loc(100,0); cTown::cWandering d_wan = {0,0,0,0}; cTown::cItem null_item = {loc(),-1,0,0,0,0,0}; @@ -103,19 +102,6 @@ cTown::cTown(cScenario& scenario, bool init_strings) : scenario(scenario) { exit_locs[i].x = -1; exit_locs[i].y = -1; } -// s = town->max_dim(); - start_locs[0].x = s / 2; - start_locs[0].y = 4; - start_locs[2].x = s / 2; - start_locs[2].y = s - 5; - start_locs[1].x = s - 5; - start_locs[1].y = s / 2; - start_locs[3].x = 4; - start_locs[3].y = s / 2; - in_town_rect.top = 3; - in_town_rect.bottom = s - 4; - in_town_rect.left = 3; - in_town_rect.right = s - 4; for(i = 0; i < 64; i++) preset_items[i] = null_item; max_num_monst = 30000; @@ -175,6 +161,22 @@ cTown::cTown(cScenario& scenario, bool init_strings) : scenario(scenario) { } } +void cTown::init_start() { + short s = this->max_dim(); + start_locs[0].x = s / 2; + start_locs[0].y = 4; + start_locs[2].x = s / 2; + start_locs[2].y = s - 5; + start_locs[1].x = s - 5; + start_locs[1].y = s / 2; + start_locs[3].x = 4; + start_locs[3].y = s / 2; + in_town_rect.top = 3; + in_town_rect.bottom = s - 4; + in_town_rect.left = 3; + in_town_rect.right = s - 4; +} + void cTown::cWandering::append(legacy::wandering_type old){ monst[0] = old.monst[0]; monst[1] = old.monst[1]; diff --git a/src/classes/town.h b/src/classes/town.h index e09fcf3e..f86422e8 100644 --- a/src/classes/town.h +++ b/src/classes/town.h @@ -128,6 +128,7 @@ public: virtual short max_dim() const = 0; virtual short max_monst() const = 0; virtual short max_items() const = 0; + void init_start(); void set_up_lights(); short light_obscurity(short x,short y); // Obscurity function used for calculating lighting diff --git a/src/dialogxml/dialog.cpp b/src/dialogxml/dialog.cpp index b82d9142..fb920dca 100644 --- a/src/dialogxml/dialog.cpp +++ b/src/dialogxml/dialog.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include "dialog.hpp" #include "graphtool.h" #include "soundtool.h" @@ -176,40 +175,19 @@ template<> pair cDialog::parse(Element& who /*pict*/){ return p; } -class dlogStringFilter : public boost::iterator_facade { - friend class boost::iterator_core_access; -public: - using value_type = std::string::value_type; - using iter_type = std::string::iterator; -private: - bool found_nl; - iter_type base; - bool equal(const dlogStringFilter& other) const { - return base == other.base; - } - value_type dereference() const { - if(found_nl) return ' '; - return *base; - } - void increment() { - if(found_nl) { +string dlogStringFilter(string toFilter) { + string filtered; + bool found_nl = false; + for(char c : toFilter) { + if(c == '\n' || c == '\r') found_nl = true; + else if(c != '\t' && c != 0) { + if(found_nl && !filtered.empty()) filtered += ' '; found_nl = false; - return; - } - base++; - while(*base == '\t' || *base == '\n' || *base == '\r') { - found_nl = found_nl || *base != '\t'; - base++; + filtered += c; } } -public: - // TODO: This currently assumes that a string's end() iterator can be dereferenced (presumably to produce a null terminator). - // This is a poor assumption (though it seems to work in practice). - dlogStringFilter(iter_type base) : found_nl(false), base(base) { - if(*base == '\t' || *base == '\n' || *base == '\r') - increment(); - } -}; + return filtered; +} template<> pair cDialog::parse(Element& who /*text*/){ pair p; @@ -294,7 +272,7 @@ template<> pair cDialog::parse(Element& who /*text*/){ // TODO: De-magic the | character if(type == TiXmlNode::ELEMENT && val == "br") content += '|'; // because vertical bar is replaced by a newline when drawing strings else if(type == TiXmlNode::TEXT) - copy(dlogStringFilter(val.begin()), dlogStringFilter(val.end()), std::inserter(content, content.end())); + content += dlogStringFilter(val); else if(type != TiXmlNode::COMMENT) { val = '<' + val + '>'; throw xBadVal("text",xBadVal::CONTENT,content + val,node->Row(),node->Column(),fname); @@ -492,7 +470,7 @@ template<> pair cDialog::parse(Element& who /*button*/){ if(content.length() > 0) throw xBadVal("button",xBadVal::CONTENT,content + val,node->Row(),node->Column(),fname); // p.second->labelWithKey = true; }else if(type == TiXmlNode::TEXT) - copy(dlogStringFilter(val.begin()), dlogStringFilter(val.end()), std::inserter(content, content.end())); + content += dlogStringFilter(val); else if(type != TiXmlNode::COMMENT) { val = '<' + val + '>'; throw xBadVal("button",xBadVal::CONTENT,val,node->Row(),node->Column(),fname); @@ -647,7 +625,7 @@ template<> pair cDialog::parse(Element& who /*LED*/){ int type = node->Type(); node->GetValue(&val); if(type == TiXmlNode::TEXT) - copy(dlogStringFilter(val.begin()), dlogStringFilter(val.end()), std::inserter(content, content.end())); + content += dlogStringFilter(val); else if(type != TiXmlNode::COMMENT) { val = '<' + val + '>'; throw xBadVal("led",xBadVal::CONTENT,content + val,node->Row(),node->Column(),fname); @@ -745,7 +723,7 @@ template<> pair cDialog::parse(Element& who /*field*/){ int type = node->Type(); node->GetValue(&val); if(type == TiXmlNode::TEXT) - copy(dlogStringFilter(val.begin()), dlogStringFilter(val.end()), std::inserter(content, content.end())); + content += dlogStringFilter(val); else if(type != TiXmlNode::COMMENT) { val = '<' + val + '>'; throw xBadVal("field",xBadVal::CONTENT,val,node->Row(),node->Column(),fname); @@ -1026,11 +1004,13 @@ bool cDialog::sendInput(cKey key) { void cDialog::run(){ cDialog* formerTop = topWindow; + // TODO: The introduction of the static topWindow means I may be able to use this instead of parent->win; do I still need parent? + sf::RenderWindow* parentWin = &(parent ? parent->win : mainPtr); cursor_type former_curs = current_cursor; set_cursor(sword_curs); using kb = sf::Keyboard; kb::Key k; - cKey key, pendingKey; + cKey key, pendingKey = {true}; sf::Event currentEvent; std::string itemHit = ""; dialogNotToast = true; @@ -1047,7 +1027,7 @@ void cDialog::run(){ win.setVisible(true); makeFrontWindow(parent ? parent-> win : mainPtr); makeFrontWindow(win); - ModalSession dlog(win); + ModalSession dlog(win, *parentWin); animTimer.restart(); while(dialogNotToast){ draw(); @@ -1197,11 +1177,10 @@ void cDialog::run(){ itemHit.clear(); } win.setVisible(false); - // TODO: The introduction of the static topWindow means I may be able to use this instead of parent->win; do I still need parent? - sf::RenderWindow* parentWin = &(parent ? parent->win : mainPtr); while(parentWin->pollEvent(currentEvent)); set_cursor(former_curs); topWindow = formerTop; + makeFrontWindow(*parentWin); } template void cDialog::handleTabOrder(string& itemHit, Iter begin, Iter end) { diff --git a/src/pcedit/pc.graphics.cpp b/src/pcedit/pc.graphics.cpp index 298fa2ce..3ff31c53 100644 --- a/src/pcedit/pc.graphics.cpp +++ b/src/pcedit/pc.graphics.cpp @@ -57,7 +57,7 @@ short store_page_on,store_num_i; // TODO: The duplication of rectangle here shouldn't be necessary... rectangle ed_buttons_from[2] = {rectangle{0,0,57,57},rectangle{0,57,57,114}}; short current_pressed_button = -1; -sf::Texture spec_scen_g; // not actually needed; just here to silence compiler because it's reference in fileio.h +cCustomGraphics spec_scen_g; // not actually needed; just here to silence compiler because it's referenced in fileio.h // (actually, it WILL be needed eventually; the same is true about most of the rest of these.) sf::Texture items_gworld,tiny_obj_gworld,fields_gworld,roads_gworld,boom_gworld,missiles_gworld; sf::Texture monst_gworld[NUM_MONST_SHEETS],terrain_gworld[NUM_TER_SHEETS],anim_gworld,talkfaces_gworld; diff --git a/src/pcedit/pc.main.cpp b/src/pcedit/pc.main.cpp index e2ca8f6f..ce89ff0b 100644 --- a/src/pcedit/pc.main.cpp +++ b/src/pcedit/pc.main.cpp @@ -67,9 +67,6 @@ short store_flags[3]; //stored_town_maps_type town_maps; //stored_outdoor_maps_type o_maps; -/* Display globals */ -short give_delays = 0; /* XXX this wasn't defined anywhere, is this right? -jmr */ - /* Prototypes */ int main(int argc, char* argv[]); void Initialize(void); @@ -100,8 +97,8 @@ char start_name[256]; //MW specified return type was 'void', changed to ISO C style for Carbonisation -jmr int main(int /*argc*/, char* argv[]) { try { - init_menubar(); init_directories(argv[0]); + init_menubar(); Initialize(); init_fileio(); init_main_buttons(); diff --git a/src/scenedit/scen.main.cpp b/src/scenedit/scen.main.cpp index 070e3040..ac982d4f 100644 --- a/src/scenedit/scen.main.cpp +++ b/src/scenedit/scen.main.cpp @@ -75,6 +75,8 @@ rectangle right_sbar_rect; //Changed to ISO C specified argument and return type. int main(int, char* argv[]) { try { + + init_directories(argv[0]); init_menubar(); //outdoor_record_type dummy_outdoor, *store2; @@ -83,8 +85,6 @@ int main(int, char* argv[]) { init_current_terrain(); //create_file(); //ExitToShell(); - - init_directories(argv[0]); Initialize(); init_fileio(); init_snd_tool(); diff --git a/src/tools/cursors.h b/src/tools/cursors.h index 2dd9e863..f89f3210 100644 --- a/src/tools/cursors.h +++ b/src/tools/cursors.h @@ -10,6 +10,9 @@ #define BOE_CURSORS_H #include +#include + +namespace fs = boost::filesystem; enum cursor_type { wand_curs = 0, @@ -43,7 +46,7 @@ enum cursor_type { class Cursor { void* ptr; public: - Cursor(std::string imgPath, float hotSpotX, float hotSpotY); + Cursor(fs::path imgPath, float hotSpotX, float hotSpotY); ~Cursor(); void apply(); }; diff --git a/src/tools/cursors.mac.mm b/src/tools/cursors.mac.mm index 5d6cd8f9..b1cd0c41 100644 --- a/src/tools/cursors.mac.mm +++ b/src/tools/cursors.mac.mm @@ -11,23 +11,6 @@ #include #include "restypes.hpp" -cursor_type current_cursor = sword_curs; -cursor_type arrow_curs[3][3] = { - {NW_curs, N_curs, NE_curs}, - {W_curs,wait_curs,E_curs}, - {SW_curs, S_curs, SE_curs}, -}; - -const std::string cursors[25] = { - "wand", "eyedropper", "brush", "spraycan", - "eraser", "topleft", "bottomright", "hand", - "NW", "N", "NE", - "W", "wait", "E", - "SW", "S", "SE", - "sword", "boot", "drop", "target", - "talk", "key", "look", "watch", -}; - static NSImage* imageFromURL(CFURLRef url){ CGImageSourceRef imageSource = CGImageSourceCreateWithURL(url, NULL); CGImageRef theImage = nil; diff --git a/src/tools/cursors.win.cpp b/src/tools/cursors.win.cpp new file mode 100644 index 00000000..28b02866 --- /dev/null +++ b/src/tools/cursors.win.cpp @@ -0,0 +1,141 @@ + +#include "cursors.h" +#include +#include +#include +#include "restypes.hpp" + +extern cursor_type current_cursor; +extern sf::RenderWindow mainPtr; + +const std::string cursors[25] = { + "wand", "eyedropper", "brush", "spraycan", + "eraser", "topleft", "bottomright", "hand", + "NW", "N", "NE", + "W", "wait", "E", + "SW", "S", "SE", + "sword", "boot", "drop", "target", + "talk", "key", "look", "watch", +}; + +static const COLORREF clrMagenta = RGB(255, 0, 255); + +// This function adapted from +// It loads an image from a file and replaces transparency with magenta. +// This is probably undesirable in the general case, but since we only expect transparent GIFs, it should work out. +HBITMAP LoadPicture(std::string filename) { + sf::Image gif; + gif.loadFromFile(filename); + HDC dc = CreateCompatibleDC(NULL); + HBITMAP bmp = CreateCompatibleBitmap(dc, gif.getSize().x, gif.getSize().y); + SelectObject(dc, &bmp); + // Not exactly efficient, but it gets the job done. + for(int x = 0; x < gif.getSize().x; x++) { + for(int y = 0; y < gif.getSize().y; y++) { + sf::Color clr = gif.getPixel(x, y); + if(clr == sf::Color::Transparent) + SetPixel(dc, x, y, clrMagenta); + else SetPixel(dc, x, y, RGB(clr.r, clr.g, clr.b)); + } + } + DeleteDC(dc); + return bmp; +} + +// This function taken from +void GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTransparent, HBITMAP& hAndMaskBitmap, HBITMAP& hXorMaskBitmap) { + HDC hDC = GetDC(NULL); + HDC hMainDC = CreateCompatibleDC(hDC); + HDC hAndMaskDC = CreateCompatibleDC(hDC); + HDC hXorMaskDC = CreateCompatibleDC(hDC); + + //Get the dimensions of the source bitmap + BITMAP bm; + GetObject(hSourceBitmap, sizeof(BITMAP), &bm); + + hAndMaskBitmap = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight); + hXorMaskBitmap = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight); + + //Select the bitmaps to DC + HBITMAP hOldMainBitmap = (HBITMAP)SelectObject(hMainDC, hSourceBitmap); + HBITMAP hOldAndMaskBitmap = (HBITMAP)SelectObject(hAndMaskDC, hAndMaskBitmap); + HBITMAP hOldXorMaskBitmap = (HBITMAP)SelectObject(hXorMaskDC, hXorMaskBitmap); + + //Scan each pixel of the souce bitmap and create the masks + COLORREF MainBitPixel; + for(int x = 0; x(cursors[which_c]); + curs.apply(); + } +} + +void restore_cursor() { + set_cursor(current_cursor); +} diff --git a/src/tools/fileio.cpp b/src/tools/fileio.cpp index 880b82a9..b2c7171e 100644 --- a/src/tools/fileio.cpp +++ b/src/tools/fileio.cpp @@ -22,6 +22,7 @@ #include "porting.h" #include "restypes.hpp" #include "tarball.hpp" +#include "cursors.h" bool cur_scen_is_mac = true, mac_is_intel; extern sf::Texture items_gworld,tiny_obj_gworld,fields_gworld,roads_gworld,boom_gworld,missiles_gworld; @@ -43,6 +44,14 @@ static bool load_town_talk(fs::path town_base, short which_town, cSpeech& the_ta static bool load_party_v1(fs::path file_to_load, cUniverse& univ, bool town_restore, bool in_scen, bool maps_there, bool must_port); static bool load_party_v2(fs::path file_to_load, cUniverse& univ, bool town_restore, bool in_scen, bool maps_there); +// Cursors included here so that they needn't be unnecessarily duplicated in platform-specific files +cursor_type current_cursor = sword_curs; +cursor_type arrow_curs[3][3] = { + {NW_curs, N_curs, NE_curs}, + {W_curs, wait_curs, E_curs}, + {SW_curs, S_curs, SE_curs}, +}; + #include void init_directories(const char* exec_path) { @@ -54,7 +63,7 @@ void init_directories(const char* exec_path) { #endif progDir = progDir.parent_path(); if(progDir.filename() == "Scenario Editor") progDir = progDir.parent_path(); - std::cout << progDir << '\n'; + std::cout << progDir << std::endl; // Initialize the resource manager paths ResMgr::pushPath(progDir/"Scenario Editor"/"graphics.exd"/"mac"); ResMgr::pushPath(progDir/"Scenario Editor"/"graphics.exd"/"mac"/"cursors"); diff --git a/src/tools/graphtool.cpp b/src/tools/graphtool.cpp index 465ec518..deff6193 100644 --- a/src/tools/graphtool.cpp +++ b/src/tools/graphtool.cpp @@ -68,7 +68,7 @@ void init_graph_tool(){ fin.read(vbuf, size); if(!maskShader.loadFromMemory(vbuf, fbuf)) { - fprintf(stderr,"Error: Failed to load shaders from %s\nVertex:\n%s\nFragment:\n%s",shaderPath.c_str(),vbuf,fbuf); + fprintf(stderr,"Error: Failed to load shaders from %s\nVertex:\n%s\nFragment:\n%s",shaderPath.string().c_str(),vbuf,fbuf); } delete[] fbuf; delete[] vbuf; diff --git a/src/tools/prefs.win.cpp b/src/tools/prefs.win.cpp new file mode 100644 index 00000000..8e4a45b3 --- /dev/null +++ b/src/tools/prefs.win.cpp @@ -0,0 +1,133 @@ + +#include "prefs.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fs = boost::filesystem; +std::map prefs; +using iarray = std::vector; +static bool prefsLoaded = false, prefsDirty = false; + +void set_pref(std::string keypath, bool value) { + prefsDirty = true; + prefs[keypath] = value; +} + +bool get_bool_pref(std::string keypath, bool fallback) { + if(prefs.find(keypath) == prefs.end()) return fallback; + if(prefs[keypath].type() == typeid(bool)) return boost::any_cast(prefs[keypath]); + return fallback; +} + +void set_pref(std::string keypath, int value) { + prefsDirty = true; + prefs[keypath] = value; +} + +int get_int_pref(std::string keypath, int fallback) { + if(prefs.find(keypath) == prefs.end()) return fallback; + if(prefs[keypath].type() == typeid(int)) return boost::any_cast(prefs[keypath]); + return fallback; +} + +void append_iarray_pref(std::string keypath, int value) { + prefsDirty = true; + if(prefs.find(keypath) == prefs.end() || prefs[keypath].type() != typeid(iarray)) + prefs[keypath] = iarray{value}; + else { + iarray& arr = boost::any_cast(prefs[keypath]); + arr.push_back(value); + prefs[keypath] = arr; + } +} + +std::vector get_iarray_pref(std::string keypath) { + if(prefs.find(keypath) == prefs.end()) return {}; + if(prefs[keypath].type() == typeid(iarray)) return boost::any_cast(prefs[keypath]); + return {}; +} + +void clear_pref(std::string keypath) { + prefsDirty = true; + auto iter = prefs.find(keypath); + if(iter != prefs.end()) prefs.erase(iter); +} + +static bool save_prefs(fs::path fpath) { + if(!prefsDirty) return true; + fs::create_directories(fpath.parent_path()); + std::ofstream fout(fpath.string().c_str()); + for(auto& kv : prefs) { + if(kv.second.type() == typeid(iarray)) { + iarray& arr = boost::any_cast(kv.second); + fout << kv.first << " = ["; + for(int i : arr) fout << i << ' '; + fout.seekp(-1,std::ios::cur); // To overwrite the final space written in the loop + fout << ']' << std::endl; + } else if(kv.second.type() == typeid(bool)) + fout << kv.first << " = " << std::boolalpha << boost::any_cast(kv.second) << std::endl; + else if(kv.second.type() == typeid(int)) + fout << kv.first << " = " << boost::any_cast(kv.second) << std::endl; + else printf("Warning: Unknown preference value type, skipping...\n"); + if(!fout) { + perror("Error writing preferences"); + return false; + } + } + prefsDirty = false; + return true; +} + +static bool load_prefs(fs::path fpath) { + prefsDirty = false; + std::map temp_prefs; + std::ifstream fin(fpath.string().c_str()); + std::string line; + while(std::getline(fin, line)) { + if(!fin) { + perror("Error reading preferences"); + return false; + } + if(line[0] == '#') continue; + int eq = line.find_first_of('='); + if(eq == std::string::npos) { + printf("Error reading preferences: line is not a comment and lacks an = sign:\n\t%s\n", line.c_str()); + return false; + } + int key_end = line.find_last_not_of(' ', eq - 1), val_beg = line.find_first_not_of(' ', eq + 1); + if(val_beg == std::string::npos) { + printf("Error reading preferences: line is missing value:\n\t%s\n", line.c_str()); + return false; + } + if(key_end == std::string::npos) { + printf("Error reading preferences: line is missing key:\n\t%s\n", line.c_str()); + return false; + } + std::string key = line.substr(0, key_end + 1), val = line.substr(val_beg); + if(val == "true") temp_prefs[key] = true; + else if(val == "false") temp_prefs[key] = false; + else if(val[0] == '[') { + int arr_end = val.find_first_of(']'); + std::istringstream arr_vals(val.substr(1, arr_end - 1)); + int i = 0; + iarray vals; + while(arr_vals >> i) vals.push_back(i); + temp_prefs[key] = vals; + } else temp_prefs[key] = boost::lexical_cast(val); + } + prefs.swap(temp_prefs); + return prefsLoaded = true; +} + +extern fs::path tempDir; +bool sync_prefs() { + fs::path prefsPath = tempDir.parent_path() / "bladesprefs.ini"; + if(prefsLoaded) return save_prefs(prefsPath); + else return load_prefs(prefsPath); +} diff --git a/src/tools/resmgr/resmgr.hpp b/src/tools/resmgr/resmgr.hpp index 64b06dc6..537361c2 100644 --- a/src/tools/resmgr/resmgr.hpp +++ b/src/tools/resmgr/resmgr.hpp @@ -59,7 +59,7 @@ namespace ResMgr { std::stack tmpPaths = resPaths(); while(!tmpPaths.empty()) { fs::path thisPath = tmpPaths.top()/path; - printf("Testing %s...\n",thisPath.c_str()); + printf("Testing %s...\n",thisPath.string().c_str()); if(fs::exists(thisPath)) { pathFound()[path] = tmpPaths.top(); return thisPath; @@ -165,7 +165,7 @@ namespace ResMgr { /// @tparam type The type of resource the path applies to. /// @param path The path at which resources of this type may be found. template void pushPath(fs::path path) { - printf("Pushing path %s in %s...\n",path.c_str(),__FUNCTION__); + printf("Pushing path %s in %s...\n",path.string().c_str(),__FUNCTION__); resPool::resPaths().push(path); if(resPool::resPaths().empty()) printf("A problem occurred.\n"); } diff --git a/src/tools/winutil.h b/src/tools/winutil.h index 7c9deb7e..029c109b 100644 --- a/src/tools/winutil.h +++ b/src/tools/winutil.h @@ -34,11 +34,15 @@ std::string get_clipboard(); void beep(); +// Calculates how much of the window is occupied by the menubar +int getMenubarHeight(); + class ModalSession { void* session; + sf::Window& parent; public: void pumpEvents(); - ModalSession(sf::Window& win); + ModalSession(sf::Window& win, sf::Window& parent); ~ModalSession(); }; diff --git a/src/tools/winutil.mac.mm b/src/tools/winutil.mac.mm index a96a60b4..6d9d6716 100644 --- a/src/tools/winutil.mac.mm +++ b/src/tools/winutil.mac.mm @@ -145,6 +145,11 @@ void beep() { NSBeep(); } +int getMenubarHeight() { + // Mac menubar isn't in the window, so we return 0 here. + return 0; +} + NSOpenPanel* dlg_get_scen; NSOpenPanel* dlg_get_game; NSSavePanel* dlg_put_scen; diff --git a/src/tools/winutil.win.cpp b/src/tools/winutil.win.cpp new file mode 100644 index 00000000..d5a9403a --- /dev/null +++ b/src/tools/winutil.win.cpp @@ -0,0 +1,222 @@ + +#include "winutil.h" +#include +#include + +extern sf::RenderWindow mainPtr; +OPENFILENAME getParty, getScen, putParty, putScen; + +// TODO: I'm sure there's a better way to do this (maybe one that's keyboard layout agnostic) +// The proper way would involve use of the TextEntered event +char keyToChar(sf::Keyboard::Key key, bool isShift) { + using kb = sf::Keyboard; + switch(key) { + case kb::A: return isShift ? 'A' : 'a'; + case kb::B: return isShift ? 'B' : 'b'; + case kb::C: return isShift ? 'C' : 'c'; + case kb::D: return isShift ? 'D' : 'd'; + case kb::E: return isShift ? 'E' : 'e'; + case kb::F: return isShift ? 'F' : 'f'; + case kb::G: return isShift ? 'G' : 'g'; + case kb::H: return isShift ? 'H' : 'h'; + case kb::I: return isShift ? 'I' : 'i'; + case kb::J: return isShift ? 'J' : 'j'; + case kb::K: return isShift ? 'K' : 'k'; + case kb::L: return isShift ? 'L' : 'l'; + case kb::M: return isShift ? 'M' : 'm'; + case kb::N: return isShift ? 'N' : 'n'; + case kb::O: return isShift ? 'O' : 'o'; + case kb::P: return isShift ? 'P' : 'p'; + case kb::Q: return isShift ? 'Q' : 'q'; + case kb::R: return isShift ? 'R' : 'r'; + case kb::S: return isShift ? 'S' : 's'; + case kb::T: return isShift ? 'T' : 't'; + case kb::U: return isShift ? 'U' : 'u'; + case kb::V: return isShift ? 'V' : 'v'; + case kb::W: return isShift ? 'W' : 'w'; + case kb::X: return isShift ? 'X' : 'x'; + case kb::Y: return isShift ? 'Y' : 'y'; + case kb::Z: return isShift ? 'Z' : 'z'; + case kb::Num1: return isShift ? '!' : '1'; + case kb::Num2: return isShift ? '@' : '2'; + case kb::Num3: return isShift ? '#' : '3'; + case kb::Num4: return isShift ? '$' : '4'; + case kb::Num5: return isShift ? '%' : '5'; + case kb::Num6: return isShift ? '^' : '6'; + case kb::Num7: return isShift ? '&' : '7'; + case kb::Num8: return isShift ? '*' : '8'; + case kb::Num9: return isShift ? '(' : '9'; + case kb::Num0: return isShift ? ')' : '0'; + case kb::Tilde: return isShift ? '~' : '`'; + case kb::Dash: return isShift ? '_' : '-'; + case kb::Equal: return isShift ? '+' : '='; + case kb::LBracket: return isShift ? '{' : '['; + case kb::RBracket: return isShift ? '}' : ']'; + case kb::SemiColon: return isShift ? ':' : ';'; + case kb::Quote: return isShift ? '"' : '\''; + case kb::Comma: return isShift ? '<' : ','; + case kb::Period: return isShift ? '>' : '.'; + case kb::Slash: return isShift ? '?' : '/'; + case kb::BackSlash: return isShift ? '|' : '\\'; + case kb::Tab: return '\t'; + case kb::Space: return ' '; + case kb::Return: return '\n'; + case kb::BackSpace: return '\b'; + case kb::Delete: return '\x7f'; + case kb::Numpad0: return '0'; + case kb::Numpad1: return '1'; + case kb::Numpad2: return '2'; + case kb::Numpad3: return '3'; + case kb::Numpad4: return '4'; + case kb::Numpad5: return '5'; + case kb::Numpad6: return '6'; + case kb::Numpad7: return '7'; + case kb::Numpad8: return '8'; + case kb::Numpad9: return '9'; + // TODO: Should have Equal here, but SFML doesn't distinguish between normal and keybad equal :/ + // Ditto for the decimal point. + case kb::Divide: return '/'; + case kb::Multiply: return '*'; + case kb::Subtract: return '-'; + case kb::Add: return '+'; + default: break; + } + return 0; +} + +void makeFrontWindow(sf::Window& win) { + HWND win_handle = win.getSystemHandle(); + BringWindowToTop(win_handle); +} + +void setWindowFloating(sf::Window& win, bool floating) { + HWND win_handle = win.getSystemHandle(); + UINT flags = SWP_NOMOVE | SWP_NOSIZE; + HWND newPos = floating ? HWND_TOPMOST : HWND_TOP; + // The flags param specifies that these 0's are ignored. + SetWindowPos(win_handle, newPos, 0, 0, 0, 0, flags); +} + +void init_fileio() { + OPENFILENAME base_dlg; + memset(&base_dlg, 0, sizeof(OPENFILENAME)); + // Common values + base_dlg.lStructSize = sizeof(OPENFILENAME); + base_dlg.hwndOwner = mainPtr.getSystemHandle(); + base_dlg.nFilterIndex = 1; + base_dlg.Flags = OFN_DONTADDTORECENT | OFN_ENABLESIZING | OFN_EXPLORER | OFN_HIDEREADONLY | OFN_NOCHANGEDIR; + getParty = getScen = putParty = putScen = base_dlg; + // Open saved game dialog + getParty.lpstrFilter = LPCTSTR("Blades of Exile Saved Games\0*.exg\0Legacy Saved Games\0*.mac;*.boe;*.SAV\0"); + getParty.lpstrTitle = LPCTSTR("Load Game"); + getParty.Flags |= OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST; + // Open scenario dialog + getScen.lpstrFilter = LPCTSTR("Blades of Exile Scenarios\0*.boes\0Legacy or Unpacked Scenarios\0*.exs\0"); + getScen.lpstrTitle = LPCTSTR("Open Scenario"); + // Save game dialog + putParty.lpstrFilter = LPCTSTR("Blades of Exile Saved Games\0*.exg\0"); + putParty.lpstrTitle = LPCTSTR("Save Game"); + putParty.Flags |= OFN_OVERWRITEPROMPT; + // Save scenario dialog + putScen.lpstrFilter = LPCTSTR("Blades of Exile Scenario\0*.boes\0"); + putScen.lpstrTitle = LPCTSTR("Save Scenario"); +} + +static std::string runFileDlog(OPENFILENAME& dlg, const std::string& file, bool save) { + size_t sz = std::max(MAX_PATH, file.length()) + 1; + char* path = new char[sz]; + std::copy(file.begin(), file.end(), path); + std::fill(path + file.length(), path + sz, 0); + + putParty.lpstrFile = path; + putParty.nMaxFile = sz - 1; + putParty.nFileOffset = file.find_last_of('\\'); + if(putParty.nFileOffset == std::string::npos) + putParty.nFileOffset = 0; + else putParty.nFileOffset++; + putParty.nFileExtension = file.find_last_of('.'); + if(putParty.nFileExtension == std::string::npos) + putParty.nFileExtension = 0; + else putParty.nFileExtension++; + + if(save) GetSaveFileName(&dlg); + else GetOpenFileName(&dlg); + + std::string result = putParty.lpstrFile; + putParty.lpstrFile = NULL; + putParty.nMaxFile = 0; + delete path; + putParty.Flags &= ~OFN_EXTENSIONDIFFERENT; + putParty.Flags &= ~OFN_READONLY; + return result; +} + +fs::path nav_get_party() { + return runFileDlog(getParty, "Blades of Exile Save.exg", false); +} + +fs::path nav_put_party(fs::path def) { + return runFileDlog(putParty, def.string(), true); +} + +fs::path nav_get_scenario() { + return runFileDlog(getScen, "", false); +} + +fs::path nav_put_scenario(fs::path def) { + return runFileDlog(putScen, def.string(), true); +} + +void set_clipboard(std::string text) { + if(text.empty()) return; + if(!OpenClipboard(mainPtr.getSystemHandle())) return; + HGLOBAL hData = (char*) GlobalAlloc(GMEM_MOVEABLE, text.length() + 1); + char* data = (char*)GlobalLock(hData); + std::copy(text.c_str(), text.c_str() + text.length(), data); + data[text.length()] = 0; + GlobalUnlock(hData); + EmptyClipboard(); + SetClipboardData(CF_TEXT, hData); + CloseClipboard(); +} + +std::string get_clipboard() { + if(!OpenClipboard(NULL)) return ""; + HGLOBAL hData = GetClipboardData(CF_TEXT); + char* data = (char*) GlobalLock(hData); + std::string contents(data); + GlobalUnlock(hData); + CloseClipboard(); + return contents; +} + +void beep() { + MessageBeep(-1); +} + +// TODO: Implement modal session. +// It seems that Windows doesn't have anything similar to the Mac modal session, so I might have to somehow simulate it myself. +void ModalSession::pumpEvents() { + HWND win_handle = (HWND)session; +} + +ModalSession::ModalSession(sf::Window& win, sf::Window& parent) : parent(parent) { + session = win.getSystemHandle(); + EnableWindow(parent.getSystemHandle(), false); +} + +ModalSession::~ModalSession() { + HWND win_handle = (HWND)session; + EnableWindow(parent.getSystemHandle(), true); + RedrawWindow(parent.getSystemHandle(), NULL, NULL, RDW_NOCHILDREN | RDW_UPDATENOW); +} + +void set_up_apple_events() { + // TODO: Probably nothing to do here on Windows? + // Basically what might possibly happen here is to set up stuff to handle dragging a file onto the application icon. + // The Mac version also handles requests from the operating system for the application to quit. +} + +int getMenubarHeight() { + return GetSystemMetrics(SM_CYMENU); +}