├── .gitattributes ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE.md ├── README.md ├── include ├── Common.h ├── ExampleMessageOptions.h ├── FontManager.h ├── Language.h ├── LanguageDefinitions.h ├── Message.h ├── Options.h ├── OptionsStructures.h ├── SCTMain.h ├── ScrollArea.h ├── SkillIconManager.h ├── TemplateInterpreter.h ├── Texture.h ├── UtilStructures.h └── imgui_sct_widgets.h ├── languages ├── LANGUAGE_CHANGELOG.md ├── english │ └── lang.json └── old language files (pre 2.0c) │ ├── chinese │ └── lang.ini │ ├── english │ └── lang.ini │ └── french │ └── lang.ini ├── src ├── Common.cpp ├── ExampleMessageOptions.cpp ├── FontManager.cpp ├── Language.cpp ├── Message.cpp ├── Options.cpp ├── OptionsStructures.cpp ├── SCTMain.cpp ├── ScrollArea.cpp ├── SkillIconManager.cpp ├── TemplateInterpreter.cpp ├── Texture.cpp ├── TextureD3D11.cpp ├── imgui_sct_widgets.cpp └── main.cpp └── template.vcxproj.user.in /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | 35 | .vs 36 | *.vcxproj 37 | *.vcxproj.filters 38 | *.vcxproj.user 39 | CMakeCache.txt 40 | CMakeFiles 41 | Debug 42 | Release 43 | RelWithDebInfo 44 | *.bat 45 | *.sln 46 | cmake_install.cmake 47 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "submodules/simpleini"] 2 | path = submodules/simpleini 3 | url = https://github.com/brofield/simpleini 4 | [submodule "submodules/stb"] 5 | path = submodules/stb 6 | url = https://github.com/nothings/stb 7 | [submodule "submodules/json"] 8 | path = submodules/json 9 | url = https://github.com/nlohmann/json 10 | [submodule "submodules/httplib"] 11 | path = submodules/httplib 12 | url = https://github.com/yhirose/cpp-httplib 13 | [submodule "submodules/object_threadsafe"] 14 | path = submodules/object_threadsafe 15 | url = https://github.com/AlexeyAB/object_threadsafe 16 | [submodule "submodules/imgui"] 17 | path = submodules/imgui 18 | url = https://github.com/ocornut/imgui 19 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6) 2 | 3 | set(TARGET_NAME gw2-sct) 4 | 5 | # Project Name 6 | PROJECT(${TARGET_NAME}) 7 | 8 | link_directories(${DXSDK_DIR}\\Lib\\x64) 9 | link_directories(${OPENSSL_LIBRARY_DIR}) 10 | 11 | ######################################################### 12 | # Include Files 13 | ######################################################### 14 | 15 | FILE(GLOB HEADERS include/*.h include/*.hpp) 16 | FILE(GLOB SOURCES src/*.cpp src/*.c) 17 | 18 | FILE(GLOB IMGUI_SOURCES submodules/imgui/*.cpp submodules/imgui/misc/cpp/*.cpp submodules/imgui/*.c) 19 | FILE(GLOB SIMPLEINI_SOURCES submodules/simpleini/*.cpp submodules/simpleini/*.c) 20 | 21 | list(APPEND SOURCES ${IMGUI_SOURCES} ${CURLITE_SOURCES} ${SIMPLEINI_SOURCES}) 22 | 23 | add_library(${TARGET_NAME} SHARED ${SOURCES} ${HEADERS}) 24 | set_property(TARGET ${TARGET_NAME} PROPERTY CXX_STANDARD 20) 25 | SET_SOURCE_FILES_PROPERTIES( ${SOURCES} PROPERTIES LANGUAGE CXX ) 26 | 27 | target_include_directories(${TARGET_NAME} PUBLIC 28 | "$" 29 | "$" 30 | "$" 31 | "$" 32 | "$" 33 | "$" 34 | "$" 35 | "$" 36 | ) 37 | 38 | include_directories(${DXSDK_DIR}\\Include) 39 | include_directories(${OPENSSL_INCLUDE_DIR}) 40 | 41 | add_definitions("-DNOMINMAX") 42 | 43 | if(MSVC) 44 | target_compile_options(${TARGET_NAME} PUBLIC "/Zc:preprocessor") 45 | target_compile_options(${TARGET_NAME} PUBLIC "/MP") 46 | endif() 47 | 48 | add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND if exist \"${GW2_PATH}${GW2_SUBPATH}\\d3d9_arcdps_sct.dll\" del \"${GW2_PATH}${GW2_SUBPATH}\\d3d9_arcdps_sct.dll\") 49 | add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND xcopy \"$(TargetPath)\" \"${GW2_PATH}${GW2_SUBPATH}\\*\" /d /y) 50 | add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND ren \"${GW2_PATH}${GW2_SUBPATH}\\$(TargetFileName)\" \"d3d9_arcdps_sct.dll\") 51 | 52 | set( DEBUG_COMMAND "${GW2_PATH}\\Gw2-64.exe" ) 53 | set( DEBUG_COMMAND_ARGUMENTS "-maploadinfo" ) 54 | set( DEBUG_ENVIRONMENT "${GW2_PATH}" ) 55 | configure_file( template.vcxproj.user.in ${CMAKE_BINARY_DIR}/${TARGET_NAME}.vcxproj.user @ONLY ) 56 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2024 Artenuvielle 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Guild Wars 2 - Scrolling Combat Text (GW2SCT) 2 | 3 | This is an addon for arcdps by deltaconnected (https://www.deltaconnected.com/arcdps/) and adds a highly customizable floating and scrolling combat text beside the users character. Incoming damage is displayed on the left side, outgoing damage is displayed on the right side. 4 | 5 | ### Prerequisites 6 | 7 | GW2SCT requires ArcDPS to work. You can install arcdps by following the instructions in this site: 8 | https://www.deltaconnected.com/arcdps/ 9 | 10 | ### Installing 11 | 12 | Go to the the [release page](https://github.com/Artenuvielle/GW2-SCT/releases) and download the newest release. Copy the file "d3d9_arcdps_sct.dll" into the "bin64/"" subfolder of your game directory. 13 | 14 | If you want to add your own fonts copy them in the "addons/sct/fonts/" subfolder of your game directory. You can configure the output in the arcdps options panel (opened by default with Alt+Shift+T). 15 | 16 | If for some reason the addon is not loading for you, check that you have the newest version of the Microsoft Visual C++ Redistributables installed. Get it [here](https://support.microsoft.com/de-de/help/2977003/the-latest-supported-visual-c-downloads). 17 | 18 | ### Translations 19 | 20 | Translations can be found on this repository in the according language folder. If you have a translations for a new language or improvements for one, get in touch with me. 21 | 22 | Translations available and authors: 23 | * english (complete for 2.0-RC4, [Artenuvielle](https://github.com/Artenuvielle)) 24 | 25 | Available translations and authors prior to version 2.0-RC3: 26 | * chinese (only complete for 1.1-RC3, [jiangyi0923](https://github.com/jiangyi0923)) 27 | * french (only complete for 1.2-RC3, [livarkhal](https://github.com/livarkhal)) 28 | 29 | If you want to check which translation strings were changed in which version have a look [here](https://github.com/Artenuvielle/GW2-SCT/blob/master/languages/LANGUAGE_CHANGELOG.md). 30 | 31 | ## Contributing 32 | 33 | Feel free to contact me on [Reddit](https://www.reddit.com/user/Artenuvielle/) if you have suggestions or encounter any bugs not listed below. 34 | 35 | ## Known Bugs 36 | 37 | * When having problems accessing the GW2 render API no skill icons can be downloaded or it takes a very long time. Download and extract the images in icons.zip file from [here](https://github.com/Artenuvielle/GW2-SCT/issues/11#issuecomment-606794158) and into the "addons/sct/icons" subfolder of your game directory. 38 | 39 | ## Version History 40 | 41 | * 2.0 42 | * fixed crash issues with messages trying to render after being deleted already 43 | * 2.0-RC5 44 | * moved texture creation into imgui callback to resolve issues with crashing 45 | * creating font atlases in advance to improve loading time 46 | * 2.0-RC4 47 | * removed d3d9 support (game doesn't use d3d9 renderer anymore) 48 | * changes to improve stability when creating and updating d3d11 textures 49 | * 2.0-RC3 50 | * profile system (have multiple profiles of setting to quickly switch between and also have character bound profiles) 51 | * transparency option for skill icons 52 | * recording and playback of messages for easier option adjustment 53 | * switched language and skill icon persistence format 54 | * switched skill remap persistence format (if you want to remap e.g. skill id 46469 to 9168 and 30308 to 9120, put `{"46469":"9168","30308":"9120"}` into remap.json) 55 | * 2.0-RC2 56 | * published source code 57 | * output messages to multiple scroll areas 58 | * switched options persistence format 59 | * added %r parameter into template system for profession names 60 | * 2.0-RC1 61 | * added own font rendering system (problems with other addons font scaling should be fixed) 62 | * changed memory pool for textures (this caused the game to freeze when resizing game window previously) 63 | * 1.3-RC4 64 | * updated multiple libraries used 65 | * fixed skill filter options tab 66 | * fixed game crashing if for any reason the GW2 rendering API was not reachable 67 | * 1.3-RC3 68 | * fixed color pickers not working in message options tab 69 | * fixed active options tab not being highlighted in menu bar 70 | * 1.3-RC2 71 | * updated imgui library to version 1.80 72 | * fixed compatibility with arcdps exports (feb.23.2021 changes) 73 | * 1.3-RC1 74 | * added options for changing default font size and default crit font size 75 | * improved skill filtering (all previously filtered skill ids won't work any more, please readd them manually) 76 | * added a couple informational tooltips 77 | * added posibility to read in a remap.ini for remapping skill ids to different ones for retrieving the correct icons (in the ini under [Active] section add key values like '46469 = 9168'; this e.g. fixes [SoJ](https://github.com/Artenuvielle/GW2-SCT/issues/9#issuecomment-604472445)) 78 | * fixed a bug in language file loading 79 | * fixed a bug when deleting first scroll area in list 80 | * fixed a bug when deleting first filtered skill id in list 81 | * improved debug logging for skill icons download 82 | * 1.2-RC3 83 | * Changed skill icon loading code to be compatible with d912pxy 84 | * 1.2-RC2 85 | * Improved loading of language files 86 | * Increased maximal scrolling speed for supporting higher display resolutions. 87 | * Small tweak to skill icon loading to maybe fix some issues. 88 | * 1.2-RC1 89 | * Added scroll areas 90 | * Added translation support 91 | * Added support for chinese fonts 92 | * Added option for filtering out skills of a given id 93 | * Optimized message interpreting being run once per message before render and then on the fly only when options change (boosts FPS). 94 | * Fixed the mixup of damage and healing due to arcdps update 95 | * Fixed skill icons not being displayed for pets 96 | * 1.1-RC3 97 | * Added support for ArcDPS API revision 1 98 | * Added list of skill ids to filter from all messages 99 | * 1.1-RC2 100 | * Added skill icons as template parameter 101 | * Fixed %p for pet/clone messages 102 | * Added option to select master font 103 | * Added option to hide messages to yourself in outgoing window 104 | * 1.1-RC1 105 | * Added options window (removed options dropdown in arcdps options window) 106 | * Added editable message templates 107 | * 1.0 108 | * Fixed that enemy damage was shown as pet/clone damage 109 | * 1.0-RC1 110 | * initial release 111 | 112 | ## Authors 113 | 114 | * **René Martin** - *Initial work* - [Artenuvielle](https://github.com/Artenuvielle) 115 | 116 | ## License 117 | 118 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details. 119 | -------------------------------------------------------------------------------- /include/Common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define SCT_VERSION_STRING "2.0d" 4 | #define ARC_VERSION_REQUIRED "20211026.203453-403-x64" 5 | #define langStringG(KEY) GW2_SCT::Language::get(KEY) 6 | #define langString(SECTION, KEY) GW2_SCT::Language::get(SECTION, KEY) 7 | #define langStringImguiSafe(SECTION, KEY) GW2_SCT::Language::get(SECTION, KEY, true) 8 | 9 | #define CONCAT_FOR_EVAL(a, ...) a ## __VA_ARGS__ 10 | #define CONCAT_WITHOUT_EVAL(a, ...) CONCAT_FOR_EVAL(a, __VA_ARGS__) 11 | 12 | #define IIF_0(t, ...) __VA_ARGS__ 13 | #define IIF_1(t, ...) t 14 | #define IIF(cond) CONCAT_FOR_EVAL(IIF_, cond) 15 | 16 | #define GET_PARAM_1(n, ...) n 17 | #define GET_PARAM_2(x, n, ...) n 18 | #define GET_PARAM_3(x, y, n, ...) n 19 | 20 | #define GET_PARAM_1_OR_ZERO(...) GET_PARAM_1(__VA_ARGS__, 0,) 21 | #define GET_PARAM_2_OR_ZERO(...) GET_PARAM_2(__VA_ARGS__, 0,) 22 | #define GET_PARAM_3_OR_ZERO(...) GET_PARAM_3(__VA_ARGS__, 0, 0,) 23 | 24 | #define PROBE(x) x, 1, 25 | #define IS_PAREN_PROBE(...) PROBE(~) 26 | #define IS_PAREN(x) GET_PARAM_2_OR_ZERO(IS_PAREN_PROBE x) 27 | 28 | #define NOT_ZERO(x) GET_PARAM_2_OR_ZERO(CONCAT_WITHOUT_EVAL(NOT_ZERO_, CONCAT_WITHOUT_EVAL(x, _))) 29 | #define NOT_ZERO__ PROBE(~) 30 | #define NOT_ZERO_0_ PROBE(~) 31 | 32 | #define COMPL(b) CONCAT_FOR_EVAL(COMPL_, b) 33 | #define COMPL_0 1 34 | #define COMPL_1 0 35 | 36 | #define GET_BOOL(x) COMPL(NOT_ZERO(x)) 37 | 38 | #define TYPE_HAS_NAMESPACE(Type) IIF(IS_PAREN(Type))(GET_BOOL(GET_PARAM_2_OR_ZERO Type), 0) 39 | #define TYPE_HAS_TEMPLATE(Type) IIF(IS_PAREN(Type))(GET_BOOL(GET_PARAM_3_OR_ZERO Type), 0) 40 | 41 | #define TYPE_ONLY_NAMESPACE(Type) IIF(TYPE_HAS_NAMESPACE(Type))(GET_PARAM_2_OR_ZERO Type::,) 42 | #define TYPE_ONLY_NAMESPACE_NAME(Type) IIF(TYPE_HAS_NAMESPACE(Type))(GET_PARAM_2_OR_ZERO Type,) 43 | #define TYPE_ONLY_NAME(Type) IIF(IS_PAREN(Type))(GET_PARAM_1 Type, Type) 44 | #define TYPE_ONLY_TEMPLATE(Type) IIF(TYPE_HAS_TEMPLATE(Type))(,) 45 | #define TYPE_ONLY_TEMPLATE_NAME(Type) IIF(TYPE_HAS_TEMPLATE(Type))(GET_PARAM_3_OR_ZERO Type,) 46 | #define TYPE_FULL_NAME(Type) TYPE_ONLY_NAMESPACE(Type)TYPE_ONLY_NAME(Type)TYPE_ONLY_TEMPLATE(Type) 47 | 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include "FontManager.h" 55 | 56 | #include 57 | #include 58 | #include 59 | template void log_rec(std::stringstream& strStream, T t) { 60 | strStream << t; 61 | } 62 | template void log_rec(std::stringstream& strStream, T t, Args... args) { 63 | strStream << t; 64 | log_rec(strStream, args...); 65 | } 66 | 67 | extern std::ofstream logFile; 68 | 69 | #ifdef _DEBUG 70 | #define _CRT_SECURE_NO_WARNINGS 71 | extern HANDLE debug_console_hnd; 72 | #endif 73 | 74 | template void log(Args... args) { 75 | std::stringstream strStream; 76 | strStream << "GW2 SCT: "; 77 | log_rec(strStream, args...); 78 | strStream << "\n"; 79 | #ifdef _DEBUG 80 | OutputDebugString(strStream.str().c_str()); 81 | 82 | DWORD written = 0; 83 | WriteConsoleA(debug_console_hnd, strStream.str().c_str(), (DWORD)strStream.str().length(), &written, 0); 84 | #endif 85 | logFile << strStream.str(); 86 | } 87 | 88 | #define LOG(...) log(__VA_ARGS__); 89 | 90 | extern float windowHeight; 91 | extern float windowWidth; 92 | 93 | extern std::map> fontMap; 94 | extern GW2_SCT::FontType* defaultFont; 95 | extern char* arcvers; 96 | 97 | const float defaultFontSize = 22.f; 98 | 99 | /* combat event */ 100 | typedef struct cbtevent { 101 | uint64_t time; /* timegettime() at time of event */ 102 | uint64_t src_agent; /* unique identifier */ 103 | uint64_t dst_agent; /* unique identifier */ 104 | int32_t value; /* event-specific */ 105 | int32_t buff_dmg; /* estimated buff damage. zero on application event */ 106 | uint16_t overstack_value; /* estimated overwritten stack duration for buff application */ 107 | uint16_t skillid; /* skill id */ 108 | uint16_t src_instid; /* agent map instance id */ 109 | uint16_t dst_instid; /* agent map instance id */ 110 | uint16_t src_master_instid; /* master source agent map instance id if source is a minion/pet */ 111 | uint8_t iss_offset; /* internal tracking. garbage */ 112 | uint8_t iss_offset_target; /* internal tracking. garbage */ 113 | uint8_t iss_bd_offset; /* internal tracking. garbage */ 114 | uint8_t iss_bd_offset_target; /* internal tracking. garbage */ 115 | uint8_t iss_alt_offset; /* internal tracking. garbage */ 116 | uint8_t iss_alt_offset_target; /* internal tracking. garbage */ 117 | uint8_t skar; /* internal tracking. garbage */ 118 | uint8_t skar_alt; /* internal tracking. garbage */ 119 | uint8_t skar_use_alt; /* internal tracking. garbage */ 120 | uint8_t iff; /* from iff enum */ 121 | uint8_t buff; /* buff application, removal, or damage event */ 122 | uint8_t result; /* from cbtresult enum */ 123 | uint8_t is_activation; /* from cbtactivation enum */ 124 | uint8_t is_buffremove; /* buff removed. src=relevant, dst=caused it (for strips/cleanses). from cbtr enum */ 125 | uint8_t is_ninety; /* source agent health was over 90% */ 126 | uint8_t is_fifty; /* target agent health was under 50% */ 127 | uint8_t is_moving; /* source agent was moving */ 128 | uint8_t is_statechange; /* from cbtstatechange enum */ 129 | uint8_t is_flanking; /* target agent was not facing source */ 130 | uint8_t is_shields; /* all or partial damage was vs barrier/shield */ 131 | uint8_t is_offcycle; /* zero if buff dmg happened during tick, non-zero otherwise */ 132 | uint8_t pad64; /* internal tracking. garbage */ 133 | }; 134 | 135 | /* combat event - for logging (revision 1, when byte16 == 1) */ 136 | typedef struct cbtevent1 { 137 | uint64_t time; /* timegettime() at time of event */ 138 | uint64_t src_agent; /* unique identifier */ 139 | uint64_t dst_agent; /* unique identifier */ 140 | int32_t value; /* event-specific */ 141 | int32_t buff_dmg; /* estimated buff damage. zero on application event */ 142 | uint32_t overstack_value; /* estimated overwritten stack duration for buff application */ 143 | uint32_t skillid; /* skill id */ 144 | uint16_t src_instid; /* agent map instance id */ 145 | uint16_t dst_instid; /* agent map instance id */ 146 | uint16_t src_master_instid; /* master source agent map instance id if source is a minion/pet */ 147 | uint16_t dst_master_instid; /* master destination agent map instance id if destination is a minion/pet */ 148 | uint8_t iff; /* from iff enum */ 149 | uint8_t buff; /* buff application, removal, or damage event */ 150 | uint8_t result; /* from cbtresult enum */ 151 | uint8_t is_activation; /* from cbtactivation enum */ 152 | uint8_t is_buffremove; /* buff removed. src=relevant, dst=caused it (for strips/cleanses). from cbtr enum */ 153 | uint8_t is_ninety; /* source agent health was over 90% */ 154 | uint8_t is_fifty; /* target agent health was under 50% */ 155 | uint8_t is_moving; /* source agent was moving */ 156 | uint8_t is_statechange; /* from cbtstatechange enum */ 157 | uint8_t is_flanking; /* target agent was not facing source */ 158 | uint8_t is_shields; /* all or partial damage was vs barrier/shield */ 159 | uint8_t is_offcycle; 160 | uint8_t pad61; 161 | uint8_t pad62; 162 | uint8_t pad63; 163 | uint8_t pad64; 164 | }; 165 | 166 | /* combat result (physical) */ 167 | enum cbtresult { 168 | CBTR_NORMAL, // good physical hit 169 | CBTR_CRIT, // physical hit was crit 170 | CBTR_GLANCE, // physical hit was glance 171 | CBTR_BLOCK, // physical hit was blocked eg. mesmer shield 4 172 | CBTR_EVADE, // physical hit was evaded, eg. dodge or mesmer sword 2 173 | CBTR_INTERRUPT, // physical hit interrupted something 174 | CBTR_ABSORB, // physical hit was "invlun" or absorbed eg. guardian elite 175 | CBTR_BLIND, // physical hit missed 176 | CBTR_KILLINGBLOW, // hit was killing hit 177 | CBTR_DOWNED, // hit was downing hit 178 | }; 179 | 180 | /* agent short */ 181 | typedef struct ag { 182 | const char* name; /* agent name. may be null. valid only at time of event. utf8 */ 183 | uintptr_t id; /* agent unique identifier */ 184 | uint32_t prof; /* profession at time of event. refer to evtc notes for identification */ 185 | uint32_t elite; /* elite spec at time of event. refer to evtc notes for identification */ 186 | uint32_t self; /* 1 if self, 0 if not */ 187 | uint16_t team; /* sep21+ */ 188 | } ag; 189 | 190 | /* iff */ 191 | enum iff { 192 | IFF_FRIEND, // green vs green, red vs red 193 | IFF_FOE, // green vs red 194 | IFF_UNKNOWN // something very wrong happened 195 | }; 196 | 197 | enum profession { 198 | PROFESSION_UNDEFINED = 0, 199 | PROFESSION_GUARDIAN, 200 | PROFESSION_WARRIOR, 201 | PROFESSION_ENGINEER, 202 | PROFESSION_RANGER, 203 | PROFESSION_THIEF, 204 | PROFESSION_ELEMENTALIST, 205 | PROFESSION_MESMER, 206 | PROFESSION_NECROMANCER, 207 | PROFESSION_REVENANT 208 | }; 209 | 210 | namespace GW2_SCT { 211 | extern uint32_t d3dversion; 212 | extern ID3D11Device* d3Device11; 213 | extern ID3D11DeviceContext* d3D11Context; 214 | extern IDXGISwapChain* d3d11SwapChain; 215 | 216 | enum class MessageCategory { 217 | PLAYER_OUT = 0, 218 | PLAYER_IN, 219 | PET_OUT, 220 | PET_IN 221 | }; 222 | #define NUM_CATEGORIES 4 223 | int messageCategoryToInt(MessageCategory category); 224 | MessageCategory intToMessageCategory(int i); 225 | 226 | enum class MessageType { 227 | NONE = 0, 228 | PHYSICAL, 229 | CRIT, 230 | BLEEDING, 231 | BURNING, 232 | POISON, 233 | CONFUSION, 234 | RETALIATION, 235 | TORMENT, 236 | DOT, 237 | HEAL, 238 | HOT, 239 | SHIELD_RECEIVE, 240 | SHIELD_REMOVE, 241 | BLOCK, 242 | EVADE, 243 | INVULNERABLE, 244 | MISS 245 | }; 246 | #define NUM_MESSAGE_TYPES 18 247 | int messageTypeToInt(MessageType type); 248 | MessageType intToMessageType(int i); 249 | } 250 | 251 | std::string getExePath(); 252 | std::string getSCTPath(); 253 | bool file_exist(const std::string& name); 254 | bool getFilesInDirectory(std::string path, std::vector& files); 255 | ImU32 stoc(std::string); 256 | 257 | typedef int FontId; 258 | GW2_SCT::FontType* getFontType(int, bool withMaster = true); 259 | 260 | bool floatEqual(float a, float b); 261 | 262 | std::string fotos(FontId i, bool withMaster = true); 263 | FontId stofo(std::string const& s, bool withMaster = true); -------------------------------------------------------------------------------- /include/ExampleMessageOptions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "Common.h" 7 | #include "Message.h" 8 | #include "SCTMain.h" 9 | 10 | namespace GW2_SCT { 11 | class ExampleMessageOptions { 12 | public: 13 | static void paint(); 14 | static void open(); 15 | static void setMain(SCTMain* m) { main = m; }; 16 | static void receiveMessage(std::shared_ptr); 17 | private: 18 | enum State { 19 | READY_TO_RECORD, 20 | RECORDING, 21 | EMITTING 22 | }; 23 | struct MessageInformation { 24 | MessageCategory category; 25 | MessageType type; 26 | std::shared_ptr data; 27 | }; 28 | static void emitMessages(); 29 | static bool windowIsOpen; 30 | static State state; 31 | static std::chrono::system_clock::time_point recordingStart; 32 | static SCTMain* main; 33 | static std::multimap messagesToEmmit; 34 | static std::thread* emitterThread; 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /include/FontManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "stb_truetype.h" 8 | #include "Texture.h" 9 | 10 | namespace GW2_SCT { 11 | class Glyph { 12 | public: 13 | static Glyph* GetGlyph(const stbtt_fontinfo* font, float scale, int codepoint, int ascent); 14 | static void cleanup(); 15 | int getX1(); 16 | int getX2(); 17 | int getY1(); 18 | int getY2(); 19 | size_t getWidth(); 20 | size_t getHeight(); 21 | int getCodepoint(); 22 | int getOffsetTop(); 23 | float getLeftSideBearing(); 24 | unsigned char* getBitmap(); 25 | float getAdvanceAndKerning(int nextCodepoint); 26 | private: 27 | static std::unordered_map>> _knownGlyphs; 28 | Glyph(const stbtt_fontinfo* font, float scale, int codepoint, int ascent); 29 | ~Glyph(); 30 | const stbtt_fontinfo* _font = nullptr; 31 | float _scale = 0; 32 | int _codepoint = 0, _x1 = 0, _x2 = 0, _y1 = 0, _y2 = 0, _advance = 0, _lsb = 0, _offsetTop = 0; 33 | size_t _width = 0, _height = 0; 34 | unsigned char* _bitmap = nullptr; 35 | std::unordered_map _advanceAndKerningCache; 36 | float getRealAdvanceAndKerning(int nextCodepoint); 37 | }; 38 | class FontType { 39 | public: 40 | FontType(unsigned char* data, size_t size); 41 | static void ensureAtlasCreation(); 42 | static void cleanup(); 43 | ImVec2 calcRequiredSpaceForTextAtSize(std::string text, float fontSize); 44 | void bakeGlyphsAtSize(std::string text, float fontSize); 45 | void drawAtSize(std::string text, float fontSize, ImVec2 position, ImU32 color); 46 | #if _DEBUG 47 | void drawAtlas(); 48 | #endif 49 | private: 50 | stbtt_fontinfo _info; 51 | int _ascent, _descent, _lineGap; 52 | std::unordered_map _cachedScales; 53 | std::unordered_map _isCachedScaleExact; 54 | std::unordered_map _cachedRealScales; 55 | float getCachedScale(float fontSize); 56 | bool isCachedScaleExactForSize(float fontSize); 57 | float getRealScale(float fontSize); 58 | 59 | struct GlyphAtlas { 60 | struct GlyphAtlasLineDefinition { 61 | float lineHeight; 62 | ImVec2 nextGlyphPosition; 63 | }; 64 | MutableTexture* texture = nullptr; 65 | std::vector linesWithSpaces; 66 | GlyphAtlas(); 67 | ~GlyphAtlas(); 68 | }; 69 | struct GlyphPositionDefinition { 70 | size_t atlasID; 71 | int x, y; 72 | ImVec2 uvStart, uvEnd; 73 | Glyph* glyph; 74 | GlyphPositionDefinition(size_t atlasID, int x, int y, Glyph* glyph); 75 | GlyphPositionDefinition() {}; 76 | ImVec2 getPos(); 77 | ImVec2 getSize(); 78 | ImVec2 getSize(float scale); 79 | ImVec2 offsetFrom(ImVec2 origin); 80 | ImVec2 offsetFrom(ImVec2 origin, float scale); 81 | }; 82 | std::unordered_map> _glyphPositionsAtSizes; 83 | 84 | static std::vector _allocatedAtlases; 85 | static std::mutex _allocatedAtlassesMutex; 86 | }; 87 | 88 | class FontManager { 89 | public: 90 | static void init(); 91 | static void cleanup(); 92 | static FontType* getDefaultFont(); 93 | private: 94 | static FontType* loadFontTypeFromFile(std::string path); 95 | static FontType* DEFAULT_FONT; 96 | }; 97 | } 98 | -------------------------------------------------------------------------------- /include/Language.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "Common.h" 5 | #include "json.hpp" 6 | #include "LanguageDefinitions.h" 7 | 8 | // from https://stackoverflow.com/questions/147267/easy-way-to-use-variables-of-enum-types-as-string-in-c#202511 9 | // expansion macro for enum value definition 10 | #define ENUM_VALUE(name,assign) name assign, 11 | 12 | // expansion macro for enum to string conversion 13 | #define ENUM_CASE(name,assign) case name: return #name; 14 | 15 | // expansion macro for string to enum conversion 16 | #define ENUM_STRCMP(name,assign) if (!strcmp(str,#name)) return name; 17 | 18 | /// declare the access function and define enum values 19 | #define DECLARE_ENUM(EnumType,ENUM_DEF) \ 20 | enum EnumType { \ 21 | ENUM_DEF(ENUM_VALUE) \ 22 | }; \ 23 | const char *GetString(EnumType dummy); \ 24 | EnumType Get##EnumType##Value(const char *string); \ 25 | 26 | /// define the access function names 27 | #define DEFINE_ENUM(EnumType,ENUM_DEF) \ 28 | const char *GetString(EnumType value) \ 29 | { \ 30 | switch(value) \ 31 | { \ 32 | ENUM_DEF(ENUM_CASE) \ 33 | default: return ""; /* handle input error */ \ 34 | } \ 35 | } \ 36 | EnumType Get##EnumType##Value(const char *str) \ 37 | { \ 38 | ENUM_DEF(ENUM_STRCMP) \ 39 | return (EnumType)0; /* handle input error */ \ 40 | } \ 41 | 42 | namespace GW2_SCT { 43 | DECLARE_ENUM(LanguageCategory, LANGUAGE_CATEGORY) 44 | DECLARE_ENUM(LanguageKey, LANGUAGE_KEY) 45 | 46 | class Language { 47 | public: 48 | static void load(); 49 | static void reload(); 50 | static const char* get(LanguageKey key, bool imgui_safe = false); 51 | static const char* get(LanguageCategory section, LanguageKey key, bool imgui_safe = false); 52 | struct LanguageEntry { 53 | private: 54 | std::string value; 55 | bool optional = false; 56 | bool resolved = false; 57 | bool hasValue = false; 58 | LanguageEntry* reference = nullptr; 59 | std::vector referenced = {}; 60 | std::vector> entriesToResolve = {}; 61 | public: 62 | LanguageEntry(std::vector> resolveList) : entriesToResolve(resolveList) {}; 63 | LanguageEntry() {}; 64 | LanguageEntry(std::string s) : value(s), optional(true), resolved(true), hasValue(true) {}; 65 | bool resolve() { 66 | if (!resolved) { 67 | if (reference != nullptr) { 68 | reference->referenced.erase(std::find(reference->referenced.begin(), reference->referenced.end(), this)); 69 | reference = nullptr; 70 | } 71 | for (auto& entry : entriesToResolve) { 72 | auto catIt = languageStrings.find(entry.first); 73 | if (catIt != languageStrings.end()) { 74 | auto keyIt = catIt->second.find(entry.second); 75 | if (keyIt != catIt->second.end() && keyIt->second.resolve()) { 76 | value = keyIt->second.value; 77 | hasValue = true; 78 | reference = &keyIt->second; 79 | keyIt->second.referenced.push_back(this); 80 | break; 81 | } 82 | } 83 | } 84 | resolved = true; 85 | } 86 | return hasValue; 87 | } 88 | void resetResolve() { 89 | resolved = false; 90 | } 91 | bool isOptional() { return optional; } 92 | bool isReference() { return reference != nullptr; } 93 | bool isReferenced() { return referenced.size() > 0; } 94 | void setValue(std::string v) { 95 | value = v; 96 | hasValue = true; 97 | resolved = true; 98 | if (reference != nullptr) { 99 | reference->referenced.erase(std::find(reference->referenced.begin(), reference->referenced.end(), this)); 100 | reference = nullptr; 101 | } 102 | } 103 | operator std::string() const { 104 | return value; 105 | } 106 | operator const char*() const { 107 | return value.c_str(); 108 | } 109 | }; 110 | private: 111 | Language() {} 112 | static std::map> languageStrings; 113 | static std::map> languageStringsImguiSafe; 114 | static const nlohmann::ordered_json defaultLanguage; 115 | static nlohmann::ordered_json loadedLanguage; 116 | static void saveDefault(const char*); 117 | static bool loadSuccessful; 118 | }; 119 | } -------------------------------------------------------------------------------- /include/LanguageDefinitions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define LANGUAGE_CATEGORY(F)\ 4 | F(No_Category,=0)\ 5 | F(General,)\ 6 | F(Option_UI,)\ 7 | F(Example_Message_UI,)\ 8 | F(Message,)\ 9 | F(Scroll_Area_Option_UI,)\ 10 | F(Receiver_Option_UI,)\ 11 | F(Skill_Filter_Option_UI,)\ 12 | F(Skill_Icons_Option_UI,)\ 13 | F(Profile_Option_UI,)\ 14 | F(Message_General,)\ 15 | F(Message_Category_Player_Out,)\ 16 | F(Message_Category_Player_In,)\ 17 | F(Message_Category_Pet_Out,)\ 18 | F(Message_Category_Pet_In,)\ 19 | F(Message_Type_Physical,)\ 20 | F(Message_Type_Crit,)\ 21 | F(Message_Type_Bleeding,)\ 22 | F(Message_Type_Burning,)\ 23 | F(Message_Type_Poison,)\ 24 | F(Message_Type_Confusion,)\ 25 | F(Message_Type_Retaliation,)\ 26 | F(Message_Type_Torment,)\ 27 | F(Message_Type_DoT,)\ 28 | F(Message_Type_Heal,)\ 29 | F(Message_Type_HoT,)\ 30 | F(Message_Type_Shield_Got,)\ 31 | F(Message_Type_Shield,)\ 32 | F(Message_Type_Block,)\ 33 | F(Message_Type_Evade,)\ 34 | F(Message_Type_Invulnerable,)\ 35 | F(Message_Type_Miss,)\ 36 | F(Message_Player_Out_Physical,)\ 37 | F(Message_Player_Out_Crit,)\ 38 | F(Message_Player_Out_Bleeding,)\ 39 | F(Message_Player_Out_Burning,)\ 40 | F(Message_Player_Out_Poison,)\ 41 | F(Message_Player_Out_Confusion,)\ 42 | F(Message_Player_Out_Retaliation,)\ 43 | F(Message_Player_Out_Torment,)\ 44 | F(Message_Player_Out_DoT,)\ 45 | F(Message_Player_Out_Heal,)\ 46 | F(Message_Player_Out_HoT,)\ 47 | F(Message_Player_Out_Shield_Got,)\ 48 | F(Message_Player_Out_Shield,)\ 49 | F(Message_Player_Out_Block,)\ 50 | F(Message_Player_Out_Evade,)\ 51 | F(Message_Player_Out_Invulnerable,)\ 52 | F(Message_Player_Out_Miss,)\ 53 | F(Message_Player_In_Physical,)\ 54 | F(Message_Player_In_Crit,)\ 55 | F(Message_Player_In_Bleeding,)\ 56 | F(Message_Player_In_Burning,)\ 57 | F(Message_Player_In_Poison,)\ 58 | F(Message_Player_In_Confusion,)\ 59 | F(Message_Player_In_Retaliation,)\ 60 | F(Message_Player_In_Torment,)\ 61 | F(Message_Player_In_DoT,)\ 62 | F(Message_Player_In_Heal,)\ 63 | F(Message_Player_In_HoT,)\ 64 | F(Message_Player_In_Shield_Got,)\ 65 | F(Message_Player_In_Shield,)\ 66 | F(Message_Player_In_Block,)\ 67 | F(Message_Player_In_Evade,)\ 68 | F(Message_Player_In_Invulnerable,)\ 69 | F(Message_Player_In_Miss,)\ 70 | F(Message_Pet_Out_Physical,)\ 71 | F(Message_Pet_Out_Crit,)\ 72 | F(Message_Pet_Out_Bleeding,)\ 73 | F(Message_Pet_Out_Burning,)\ 74 | F(Message_Pet_Out_Poison,)\ 75 | F(Message_Pet_Out_Confusion,)\ 76 | F(Message_Pet_Out_Retaliation,)\ 77 | F(Message_Pet_Out_Torment,)\ 78 | F(Message_Pet_Out_DoT,)\ 79 | F(Message_Pet_Out_Heal,)\ 80 | F(Message_Pet_Out_HoT,)\ 81 | F(Message_Pet_Out_Shield_Got,)\ 82 | F(Message_Pet_Out_Shield,)\ 83 | F(Message_Pet_Out_Block,)\ 84 | F(Message_Pet_Out_Evade,)\ 85 | F(Message_Pet_Out_Invulnerable,)\ 86 | F(Message_Pet_Out_Miss,)\ 87 | F(Message_Pet_In_Physical,)\ 88 | F(Message_Pet_In_Crit,)\ 89 | F(Message_Pet_In_Bleeding,)\ 90 | F(Message_Pet_In_Burning,)\ 91 | F(Message_Pet_In_Poison,)\ 92 | F(Message_Pet_In_Confusion,)\ 93 | F(Message_Pet_In_Retaliation,)\ 94 | F(Message_Pet_In_Torment,)\ 95 | F(Message_Pet_In_DoT,)\ 96 | F(Message_Pet_In_Heal,)\ 97 | F(Message_Pet_In_HoT,)\ 98 | F(Message_Pet_In_Shield_Got,)\ 99 | F(Message_Pet_In_Shield,)\ 100 | F(Message_Pet_In_Block,)\ 101 | F(Message_Pet_In_Evade,)\ 102 | F(Message_Pet_In_Invulnerable,)\ 103 | F(Message_Pet_In_Miss,) 104 | 105 | #define LANGUAGE_KEY(F)\ 106 | F(No_Key,=0)\ 107 | F(Outdated_Popup_Title,)\ 108 | F(Outdated_Popup_Content,)\ 109 | F(Outdated_Popup_Found_Version,)\ 110 | F(Outdated_Popup_Required_Version,)\ 111 | F(Outdated_Popup_Confirmation,)\ 112 | F(Font,)\ 113 | F(Font_Size,)\ 114 | F(Font_Size_Type,)\ 115 | F(Default_Font_Size,)\ 116 | F(Default_Crit_Font_Size,)\ 117 | F(Custom_Font_Size,)\ 118 | F(Skill_Filter_Type_ID,)\ 119 | F(Skill_Filter_Type_Name,)\ 120 | F(Font_Master,)\ 121 | F(Font_Default,)\ 122 | F(Text_Align_Left,)\ 123 | F(Text_Align_Center,)\ 124 | F(Text_Align_Right,)\ 125 | F(Text_Curve_Left,)\ 126 | F(Text_Curve_Straight,)\ 127 | F(Text_Curve_Right,)\ 128 | F(Old_Save_Popup_Title,)\ 129 | F(Old_Save_Popup_Content,)\ 130 | F(Old_Save_Popup_Backup_Made,)\ 131 | F(Old_Save_Popup_Confirmation,)\ 132 | F(Unknown_Skill_Source,)\ 133 | F(Unknown_Skill_Target,)\ 134 | F(Unknown_Skill_Name,)\ 135 | F(Default_Scroll_Area_Incoming,)\ 136 | F(Default_Scroll_Area_Outgoing,)\ 137 | F(Title,)\ 138 | F(Menu_Bar_General,)\ 139 | F(Menu_Bar_Scroll_Areas,)\ 140 | F(Menu_Bar_Profession_Colors,)\ 141 | F(Menu_Bar_Filtered_Skills,)\ 142 | F(Menu_Bar_Skill_Icons,)\ 143 | F(Menu_Bar_Profiles,)\ 144 | F(General_Enabled,)\ 145 | F(General_Scrolling_Speed,)\ 146 | F(General_Scrolling_Speed_Toolip,)\ 147 | F(General_Drop_Shadow,)\ 148 | F(General_Max_Messages,)\ 149 | F(General_Max_Messages_Toolip,)\ 150 | F(General_Combine_Messages,)\ 151 | F(General_Combine_Messages_Toolip,)\ 152 | F(General_Self_Only_As_Incoming,)\ 153 | F(General_Self_Only_As_Incoming_Toolip,)\ 154 | F(General_Out_Only_For_Target,)\ 155 | F(General_Out_Only_For_Target_Toolip,)\ 156 | F(Scroll_Areas_New,)\ 157 | F(Scroll_Areas_Name,)\ 158 | F(Receiver_Name,) \ 159 | F(Messages_Category,)\ 160 | F(Messages_Category_Player_Out,)\ 161 | F(Messages_Category_Player_In,)\ 162 | F(Messages_Category_Pet_Out,)\ 163 | F(Messages_Category_Pet_In,)\ 164 | F(Messages_Type,)\ 165 | F(Messages_Type_Physical,)\ 166 | F(Messages_Type_Crit,)\ 167 | F(Messages_Type_Bleeding,)\ 168 | F(Messages_Type_Burning,)\ 169 | F(Messages_Type_Poison,)\ 170 | F(Messages_Type_Confusion,)\ 171 | F(Messages_Type_Retaliation,)\ 172 | F(Messages_Type_Torment,)\ 173 | F(Messages_Type_Dot,)\ 174 | F(Messages_Type_Heal,)\ 175 | F(Messages_Type_Hot,)\ 176 | F(Messages_Type_Shield_Receive,)\ 177 | F(Messages_Type_Shield_Remove,)\ 178 | F(Messages_Type_Block,)\ 179 | F(Messages_Type_Evade,)\ 180 | F(Messages_Type_Invulnerable,)\ 181 | F(Messages_Type_Miss,)\ 182 | F(Profession_Colors_Guardian,)\ 183 | F(Profession_Colors_Warrior,)\ 184 | F(Profession_Colors_Engineer,)\ 185 | F(Profession_Colors_Ranger,)\ 186 | F(Profession_Colors_Thief,)\ 187 | F(Profession_Colors_Elementalist,)\ 188 | F(Profession_Colors_Mesmer,)\ 189 | F(Profession_Colors_Necromancer,)\ 190 | F(Profession_Colors_Revenant,)\ 191 | F(Profession_Colors_Undetectable,)\ 192 | F(Skill_Icons_Warning,)\ 193 | F(Skill_Icons_Enable,)\ 194 | F(Skill_Icons_Preload_Description,)\ 195 | F(Skill_Icons_Preload,)\ 196 | F(Skill_Icons_Display_Type,)\ 197 | F(Skill_Icons_Display_Type_Normal,)\ 198 | F(Skill_Icons_Display_Type_Black_Culled,)\ 199 | F(Skill_Icons_Display_Type_Black_Border_Culled,)\ 200 | F(Skill_Icons_Display_Type_Black_Border_Touching_Culled,)\ 201 | F(Multiple_Sources,)\ 202 | F(Number_Of_Hits,)\ 203 | F(Horizontal_Offset,)\ 204 | F(Vertical_Offset,)\ 205 | F(Width,)\ 206 | F(Height,)\ 207 | F(Text_Align,)\ 208 | F(Text_Flow,)\ 209 | F(All_Receivers,)\ 210 | F(New_Receiver,)\ 211 | F(Template,)\ 212 | F(Available_Parameters,)\ 213 | F(Delete_Confirmation_Title,)\ 214 | F(Delete_Confirmation_Content,)\ 215 | F(Delete_Confirmation_Confirmation,)\ 216 | F(Delete_Confirmation_Cancel,)\ 217 | F(Parameter_Description_v,)\ 218 | F(Parameter_Description_n,)\ 219 | F(Parameter_Description_s,)\ 220 | F(Parameter_Description_p,)\ 221 | F(Parameter_Description_c,)\ 222 | F(Parameter_Description_r,)\ 223 | F(Parameter_Description_i,)\ 224 | F(Default_Value,)\ 225 | F(Filter_By,)\ 226 | F(Profile_Description,)\ 227 | F(Master_Profile,)\ 228 | F(Character_Specific_Profile_Heading,)\ 229 | F(Character_Specific_Profile_Enabled,)\ 230 | F(Current_Profile_Heading,)\ 231 | F(Current_Profile_Name,)\ 232 | F(Create_Profile_Copy,)\ 233 | F(Delete_Profile,)\ 234 | F(Profile_Copy_Suffix,)\ 235 | F(Start_Recording,)\ 236 | F(Stop_Recording,)\ 237 | F(Start_Emitting,)\ 238 | F(Stop_Emitting,)\ 239 | F(Clear_Recorded_Messages,) 240 | -------------------------------------------------------------------------------- /include/Message.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "Common.h" 7 | #include "Options.h" 8 | 9 | namespace GW2_SCT { 10 | struct MessageData { 11 | char* skillName = nullptr; 12 | char* entityName = nullptr; 13 | char* otherEntityName = nullptr; 14 | int32_t value = 0; 15 | uint32_t overstack_value = 0; 16 | int32_t buffValue = 0; 17 | uint32_t skillId = 0; 18 | uint64_t entityId = 0; 19 | uint32_t entityProf = 0; 20 | uint64_t otherEntityId = 0; 21 | uint32_t otherEntityProf = 0; 22 | bool hasToBeFiltered = false; 23 | public: 24 | MessageData(cbtevent* ev, ag* entity, ag * otherEntity, const char* skillname); 25 | MessageData(cbtevent1* ev, ag* entity, ag * otherEntity, const char* skillname); 26 | #ifdef _DEBUG 27 | MessageData(int32_t value, int32_t buffValue, uint32_t overstack_value, uint32_t skillId, ag* entity, ag* otherEntity, const char* skillname); 28 | #endif // _DEBUG 29 | MessageData() {}; 30 | MessageData(const MessageData& toCopy); 31 | }; 32 | 33 | struct MessageHandler { 34 | std::vector&, std::vector&)>> tryToCombineWithFunctions; 35 | std::map&)>> parameterToStringFunctions; 36 | MessageHandler( 37 | std::vector&, std::vector&)>> tryToCombineWithFunctions, 38 | std::map&)>> parameterToStringFunctions 39 | ); 40 | }; 41 | 42 | class EventMessage { 43 | public: 44 | EventMessage(MessageCategory category, MessageType type, cbtevent* ev, ag* src, ag* dst, char* skillname); 45 | EventMessage(MessageCategory category, MessageType type, cbtevent1* ev, ag* src, ag* dst, char* skillname); 46 | EventMessage(MessageCategory category, MessageType type, std::shared_ptr); 47 | ~EventMessage(); 48 | std::string getStringForOptions(std::shared_ptr opt); 49 | std::shared_ptr getCopyOfFirstData(); 50 | MessageCategory getCategory(); 51 | MessageType getType(); 52 | bool hasToBeFiltered(); 53 | bool tryToCombineWith(std::shared_ptr m); 54 | std::chrono::system_clock::time_point getTimepoint(); 55 | private: 56 | std::chrono::system_clock::time_point timepoint; 57 | MessageCategory category; 58 | MessageType type; 59 | std::vector messageDatas; 60 | static std::map> messageHandlers; 61 | }; 62 | } -------------------------------------------------------------------------------- /include/Options.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "UtilStructures.h" 7 | #include "OptionsStructures.h" 8 | 9 | namespace GW2_SCT { 10 | std::map mapParameterListToLanguage(const char* section, std::vector list); 11 | 12 | class Options { 13 | public: 14 | static bool getIsSkillFiltered(uint32_t skillId, const char* skillName); 15 | static const std::shared_ptr get() { return profile.operator std::shared_ptr(); } 16 | static ObservableValue> profile; 17 | static void paint(); 18 | static void open(); 19 | static void save(); 20 | static void load(); 21 | static void loadProfile(std::string characterName); 22 | static std::string getFontSelectionString(bool withMaster = true) { return withMaster ? fontSelectionStringWithMaster : fontSelectionString; }; 23 | static std::string getFontSizeTypeSelectionString() { return fontSizeTypeSelectionString; }; 24 | static std::string getSkillFilterTypeSelectionString() { return skillFilterTypeSelectionString; }; 25 | static bool isOptionsWindowOpen(); 26 | private: 27 | static void setDefault(); 28 | static void paintGeneral(); 29 | static void paintScrollAreas(); 30 | static void paintProfessionColors(); 31 | static void paintSkillFilters(); 32 | static void paintSkillIcons(); 33 | static void paintProfiles(); 34 | static options_struct options; 35 | static bool windowIsOpen; 36 | static std::string fontSelectionString; 37 | static std::string fontSizeTypeSelectionString; 38 | static std::string fontSelectionStringWithMaster; 39 | static std::string skillFilterTypeSelectionString; 40 | static std::string backupFileName; 41 | 42 | static std::string currentProfileName; 43 | static std::string currentCharacterName; 44 | }; 45 | 46 | struct receiver_information { 47 | std::string iniPrefix; 48 | std::map options; 49 | message_receiver_options_struct defaultReceiver; 50 | }; 51 | extern const std::map> receiverInformationPerCategoryAndType; 52 | extern const std::map categoryNames; 53 | extern const std::map typeNames; 54 | extern const std::map skillIconsDisplayTypeNames; 55 | } 56 | -------------------------------------------------------------------------------- /include/OptionsStructures.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "json.hpp" 5 | #include "Common.h" 6 | #include "UtilStructures.h" 7 | 8 | namespace GW2_SCT { 9 | enum class TextAlign { 10 | LEFT = 0, 11 | CENTER, 12 | RIGHT 13 | }; 14 | extern int textAlignToInt(TextAlign type); 15 | extern TextAlign intToTextAlign(int i); 16 | 17 | enum class TextCurve { 18 | LEFT = 0, 19 | STRAIGHT, 20 | RIGHT, 21 | STATIC 22 | }; 23 | extern int textCurveToInt(TextCurve type); 24 | extern TextCurve intToTextCurve(int i); 25 | 26 | enum class FilterType { 27 | SKILL_ID = 0, 28 | SKILL_NAME 29 | }; 30 | extern int filterTypeToInt(FilterType type); 31 | extern FilterType intToFilterType(int i); 32 | 33 | enum class SkillIconDisplayType { 34 | NORMAL = 0, 35 | BLACK_CULLED, 36 | BORDER_BLACK_CULLED, 37 | BORDER_TOUCHING_BLACK_CULLED 38 | }; 39 | extern int skillIconDisplayTypeToInt(SkillIconDisplayType type); 40 | extern SkillIconDisplayType intSkillIconDisplayType(int i); 41 | 42 | class profile_options_struct; 43 | class scroll_area_options_struct; 44 | class message_receiver_options_struct; 45 | class filter_options_struct; 46 | 47 | class options_struct { 48 | public: 49 | std::string revision = "1.0"; 50 | std::string globalProfile = "default"; 51 | shared_ptr_map_with_creation profiles; 52 | std::map characterProfileMap; 53 | }; 54 | void to_json(nlohmann::json& j, const options_struct& p); 55 | void from_json(const nlohmann::json& j, options_struct& p); 56 | 57 | class profile_options_struct { 58 | public: 59 | bool sctEnabled = true; 60 | float scrollSpeed = 90.f; 61 | bool dropShadow = true; 62 | int messagesInStack = 3; 63 | bool combineAllMessages = true; 64 | FontId masterFont = 0; 65 | float defaultFontSize = 22.f; 66 | float defaultCritFontSize = 30.f; 67 | bool selfMessageOnlyIncoming = false; 68 | bool outgoingOnlyToTarget = false; 69 | std::string professionColorGuardian = "72C1D9"; 70 | std::string professionColorWarrior = "FFD166"; 71 | std::string professionColorEngineer = "D09C59"; 72 | std::string professionColorRanger = "8EDF44"; 73 | std::string professionColorThief = "C08F95"; 74 | std::string professionColorElementalist = "F68A87"; 75 | std::string professionColorMesmer = "B679D5"; 76 | std::string professionColorNecromancer = "52A76F"; 77 | std::string professionColorRevenant = "D16E5A"; 78 | std::string professionColorDefault = "FF0000"; 79 | ObservableVector> scrollAreaOptions = {}; 80 | std::vector skillFilters = {}; 81 | ObservableValue skillIconsEnabled = false; 82 | ObservableValue preloadAllSkillIcons = false; 83 | SkillIconDisplayType skillIconsDisplayType = SkillIconDisplayType::NORMAL; 84 | }; 85 | void to_json(nlohmann::json& j, const profile_options_struct& p); 86 | void from_json(const nlohmann::json& j, profile_options_struct& p); 87 | 88 | enum class ScrollAreaOutlineState { 89 | NONE = 0, 90 | LIGHT, 91 | FULL 92 | }; 93 | 94 | class scroll_area_options_struct { 95 | public: 96 | std::string name = ""; 97 | float offsetX = 0; 98 | float offsetY = 0; 99 | float width = 0; 100 | float height = 0; 101 | TextAlign textAlign = TextAlign::LEFT; 102 | TextCurve textCurve = TextCurve::LEFT; 103 | ScrollAreaOutlineState outlineState = ScrollAreaOutlineState::NONE; 104 | ObservableVector> receivers = {}; 105 | }; 106 | void to_json(nlohmann::json& j, const scroll_area_options_struct& p); 107 | void from_json(const nlohmann::json& j, scroll_area_options_struct& p); 108 | 109 | class message_receiver_options_struct { 110 | public: 111 | std::string name = ""; 112 | MessageCategory category = MessageCategory::PLAYER_OUT; 113 | MessageType type = MessageType::PHYSICAL; 114 | bool enabled = true; 115 | ObservableValue outputTemplate = std::string(""); 116 | ObservableValue color = std::string("#FFFFFF"); 117 | ObservableValue font = 0; 118 | ObservableValue fontSize = 22.f; 119 | }; 120 | void to_json(nlohmann::json& j, const message_receiver_options_struct& p); 121 | void from_json(const nlohmann::json& j, message_receiver_options_struct& p); 122 | 123 | class filter_options_struct { 124 | public: 125 | FilterType type = FilterType::SKILL_ID; 126 | uint32_t skillId = 0; 127 | std::string skillName = ""; 128 | }; 129 | void to_json(nlohmann::json& j, const filter_options_struct& p); 130 | void from_json(const nlohmann::json& j, filter_options_struct& p); 131 | } -------------------------------------------------------------------------------- /include/SCTMain.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "Common.h" 8 | #include "Options.h" 9 | #include "ScrollArea.h" 10 | 11 | /* arcdps export table */ 12 | typedef struct arcdps_exports { 13 | uintptr_t size; /* size of exports table */ 14 | uint32_t sig; /* pick a number between 0 and uint32_t max that isn't used by other modules */ 15 | uint32_t imguivers; /* set this to IMGUI_VERSION_NUM. if you don't use imgui, 18000 (as of 2021-02-02) */ 16 | const char* out_name; /* name string */ 17 | const char* out_build; /* build string */ 18 | void* wnd_nofilter; /* wndproc callback, fn(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam), return assigned to umsg */ 19 | void* combat; /* combat event callback, fn(cbtevent* ev, ag* src, ag* dst, char* skillname, uint64_t id, uint64_t revision) */ 20 | void* imgui; /* ::present callback, before imgui::render, fn(uint32_t not_charsel_or_loading, uint32_t hide_if_combat_or_ooc) */ 21 | void* options_end; /* ::present callback, appending to the end of options window in arcdps, fn() */ 22 | void* combat_local; /* combat event callback like area but from chat log, fn(cbtevent* ev, ag* src, ag* dst, char* skillname, uint64_t id, uint64_t revision) */ 23 | void* wnd_filter; /* wndproc callback like wnd_nofilter above, input filered using modifiers */ 24 | void* options_windows; /* called once per 'window' option checkbox, with null at the end, non-zero return disables arcdps drawing that checkbox, fn(char* windowname) */ 25 | }; 26 | 27 | namespace GW2_SCT { 28 | class SCTMain { 29 | public: 30 | SCTMain(); 31 | ~SCTMain(); 32 | 33 | arcdps_exports* Init(char* arcvers, void* mod_wnd, void* mod_combat, void* mod_imgui, void* mod_options, void* mod_combat_local); 34 | uintptr_t Release(); 35 | uintptr_t WindowUpdate(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); 36 | uintptr_t CombatEventArea(cbtevent* ev, ag* src, ag* dst, char* skillname, uint64_t id, uint64_t revision); 37 | uintptr_t CombatEventLocal(cbtevent* ev, ag* src, ag* dst, char* skillname, uint64_t id, uint64_t revision); 38 | uintptr_t UIUpdate(); 39 | uintptr_t UIOptions(); 40 | void sendMessageToEmission(std::shared_ptr m); 41 | private: 42 | uint32_t remapSkillID(uint32_t originalID); 43 | arcdps_exports arc_exports; 44 | 45 | uintptr_t selfInstID = -1; 46 | uintptr_t targetAgentId = -1; 47 | std::map skillRemaps; 48 | std::vector> scrollAreas; 49 | 50 | long currentScrollAreaEraseCallbackId = -1; 51 | long currentScrollAreaPushBackCallbackId = -1; 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /include/ScrollArea.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "OptionsStructures.h" 8 | #include "Message.h" 9 | #include "TemplateInterpreter.h" 10 | 11 | namespace GW2_SCT { 12 | class ScrollArea { 13 | public: 14 | ScrollArea(std::shared_ptr options); 15 | void receiveMessage(std::shared_ptr m); 16 | void paint(); 17 | private: 18 | struct MessagePrerender { 19 | std::shared_ptr message; 20 | std::string str; 21 | FontType* font; 22 | float fontSize; 23 | MessageCategory category; 24 | MessageType type; 25 | std::shared_ptr options; 26 | std::vector interpretedText; 27 | float interpretedTextWidth; 28 | bool prerenderNeeded = true; 29 | long templateObserverId, colorObserverId, fontObserverId, fontSizeObserverId; 30 | public: 31 | MessagePrerender(std::shared_ptr message, std::shared_ptr options); 32 | MessagePrerender(const MessagePrerender& copy); 33 | ~MessagePrerender(); 34 | void update(); 35 | void prerenderText(); 36 | }; 37 | 38 | std::mutex messageQueueMutex; 39 | std::deque messageQueue = std::deque(); 40 | 41 | void paintOutline(); 42 | bool paintMessage(MessagePrerender& m, __int64 time); 43 | 44 | std::list>> paintedMessages; 45 | 46 | std::shared_ptr options; 47 | }; 48 | } -------------------------------------------------------------------------------- /include/SkillIconManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Texture.h" 3 | #include "OptionsStructures.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace GW2_SCT { 13 | 14 | class SkillIcon { 15 | public: 16 | ImVec2 draw(ImVec2 pos, ImVec2 size, ImU32 color = 0xFFFFFFFF); 17 | SkillIcon(std::shared_ptr> fileData, uint32_t skillID); 18 | ~SkillIcon(); 19 | private: 20 | void loadTexture(SkillIconDisplayType displayType); 21 | std::unordered_map textures; 22 | std::shared_ptr> fileData; 23 | uint32_t skillID; 24 | std::unordered_map texturesCreated = {}; 25 | }; 26 | 27 | class SkillIconManager { 28 | public: 29 | static void init(); 30 | static void cleanup(); 31 | static SkillIcon* getIcon(uint32_t skillID); 32 | private: 33 | static void internalInit(); 34 | static void spawnLoadThread(); 35 | static void loadThreadCycle(); 36 | static int requestsPerMinute; 37 | static std::unordered_map> staticFiles; 38 | static sf::contfree_safe_ptr> checkedIDs; 39 | static sf::contfree_safe_ptr> requestedIDs; 40 | static sf::contfree_safe_ptr> loadedIcons; 41 | static std::thread loadThread; 42 | static std::atomic keepLoadThreadRunning; 43 | 44 | static long skillIconsEnabledCallbackId; 45 | static long preloadAllSkillIconsId; 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /include/TemplateInterpreter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "imgui.h" 6 | #include "SkillIconManager.h" 7 | #include "FontManager.h" 8 | 9 | ImVec2 getTextSize(const char* text, GW2_SCT::FontType* font, float fontSize, bool use_bbc = true); 10 | 11 | namespace GW2_SCT { 12 | class TemplateInterpreter { 13 | public: 14 | struct InterpretedText { 15 | std::string str; 16 | ImVec2 offset; 17 | ImVec2 size; 18 | ImU32 color; 19 | SkillIcon* icon; 20 | InterpretedText(std::string str, ImVec2 size, ImVec2 offset, ImU32 color, SkillIcon* icon = nullptr) 21 | : str(str), size(size), offset(offset), color(color), icon(icon) {} 22 | }; 23 | static bool validate(std::string, std::map); 24 | static std::vector interpret(GW2_SCT::FontType* font, float fontSize, ImU32 defaultColor, std::string t); 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /include/Texture.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "imgui.h" 3 | #include 4 | #include 5 | #include 6 | 7 | namespace GW2_SCT { 8 | class Texture { 9 | public: 10 | Texture(int width, int height); 11 | void draw(ImVec2 pos, ImVec2 size, ImU32 color); 12 | void draw(ImVec2 pos, ImVec2 size, ImVec2 uvStart, ImVec2 uvEnd, ImU32 color); 13 | virtual void internalDraw(ImVec2 pos, ImVec2 size, ImVec2 uvStart, ImVec2 uvEnd, ImU32 color) = 0; 14 | virtual bool internalCreate() = 0; 15 | void ensureCreation(); 16 | protected: 17 | int _textureWidth; 18 | int _textureHeight; 19 | bool _created = false; 20 | int _creationTries = 0; 21 | std::chrono::time_point _nextCreationTry; 22 | }; 23 | 24 | class ImmutableTexture : public Texture { 25 | public: 26 | static ImmutableTexture* Create(int width, int height, unsigned char* data); 27 | static void Release(ImmutableTexture*); 28 | ImmutableTexture(int width, int height); 29 | }; 30 | 31 | class MutableTexture : public Texture { 32 | public: 33 | typedef struct { 34 | int id; 35 | unsigned char* data; 36 | int rowPitch; 37 | int bytePerPixel; 38 | } UpdateData; 39 | static MutableTexture* Create(int width, int height); 40 | static void Release(MutableTexture*); 41 | MutableTexture(int width, int height); 42 | bool startUpdate(ImVec2 pos, ImVec2 size, UpdateData* out); 43 | bool endUpdate(); 44 | virtual bool internalStartUpdate(ImVec2 pos, ImVec2 size, UpdateData* out) = 0; 45 | virtual bool internalEndUpdate() = 0; 46 | private: 47 | bool _isCurrentlyUpdating = false; 48 | }; 49 | 50 | class TextureD3D11 { 51 | protected: 52 | TextureD3D11(int width, int height, unsigned char* data); 53 | ~TextureD3D11(); 54 | bool create(); 55 | int width; 56 | int height; 57 | unsigned char* data; 58 | ID3D11Texture2D* _texture11 = nullptr; 59 | ID3D11ShaderResourceView* _texture11View = nullptr; 60 | }; 61 | class ImmutableTextureD3D11 : public ImmutableTexture, public TextureD3D11 { 62 | public: 63 | ImmutableTextureD3D11(int width, int height, unsigned char* data); 64 | protected: 65 | void internalDraw(ImVec2 pos, ImVec2 size, ImVec2 uvStart, ImVec2 uvEnd, ImU32 color) override; 66 | bool internalCreate() override; 67 | }; 68 | 69 | class MutableTextureD3D11 : public MutableTexture, public TextureD3D11 { 70 | public: 71 | MutableTextureD3D11(int width, int height); 72 | ~MutableTextureD3D11(); 73 | protected: 74 | void internalDraw(ImVec2 pos, ImVec2 size, ImVec2 uvStart, ImVec2 uvEnd, ImU32 color) override; 75 | bool internalCreate() override; 76 | bool internalStartUpdate(ImVec2 pos, ImVec2 size, UpdateData* out) override; 77 | bool internalEndUpdate() override; 78 | private: 79 | ID3D11Texture2D* _texture11Staging = nullptr; 80 | bool _stagingChanged = false; 81 | std::mutex _stagingMutex; 82 | }; 83 | } 84 | -------------------------------------------------------------------------------- /include/UtilStructures.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "json.hpp" 6 | 7 | template 8 | class ObservableVector : public std::vector { 9 | public: 10 | ObservableVector() {} 11 | ~ObservableVector() {} 12 | 13 | long addOnEraseCallback(std::function callback) { 14 | onEraseCallbacks.insert(std::pair>(nextEraseCallbacksIndex, callback)); 15 | nextEraseCallbacksIndex++; 16 | return nextEraseCallbacksIndex - 1; 17 | } 18 | 19 | void removeOnEraseCallback(long id) { 20 | auto found = onEraseCallbacks.find(id); 21 | if (found != onEraseCallbacks.end()) { 22 | onEraseCallbacks.erase(found); 23 | } 24 | } 25 | 26 | constexpr std::vector::iterator erase(std::vector::const_iterator pos) { 27 | auto it = std::vector::begin(); 28 | int i = 0; 29 | while (it != std::vector::end()) { 30 | if (it == pos) { 31 | for (auto& callback : onEraseCallbacks) callback.second(i); 32 | break; 33 | } 34 | it++; 35 | i++; 36 | } 37 | return std::vector::erase(pos); 38 | } 39 | 40 | long addOnPushBackCallback(std::function callback) { 41 | onPushBackCallbacks.insert(std::pair>(nextPushBackCallbacksIndex, callback)); 42 | nextPushBackCallbacksIndex++; 43 | return nextPushBackCallbacksIndex - 1; 44 | } 45 | 46 | void removeOnPushBackCallback(long id) { 47 | auto found = onPushBackCallbacks.find(id); 48 | if (found != onPushBackCallbacks.end()) { 49 | onPushBackCallbacks.erase(found); 50 | } 51 | } 52 | 53 | constexpr void push_back(const T& val) { 54 | std::vector::push_back(val); 55 | size_t s = std::vector::size(); 56 | if (s > 0) 57 | for (auto& callback : onPushBackCallbacks) callback.second(val); 58 | } 59 | private: 60 | std::map> onEraseCallbacks = {}; 61 | long nextEraseCallbacksIndex = 0; 62 | 63 | std::map> onPushBackCallbacks = {}; 64 | long nextPushBackCallbacksIndex = 0; 65 | }; 66 | 67 | template 68 | class ObservableValue { 69 | public: 70 | ObservableValue(T init) : value(init) {}; 71 | ObservableValue& operator=(const ObservableValue& other) { 72 | T oldValue = value; 73 | value = other.value; 74 | for (auto& callback : onAssignCallbacks) callback.second(oldValue, other.value); 75 | return *this; 76 | } 77 | 78 | long onAssign(std::function callback) { 79 | onAssignCallbacks.insert(std::pair>(nextAssignCallbackIndex, callback)); 80 | nextAssignCallbackIndex++; 81 | return nextAssignCallbackIndex - 1; 82 | } 83 | 84 | void removeOnAssign(long id) { 85 | auto found = onAssignCallbacks.find(id); 86 | if (found != onAssignCallbacks.end()) { 87 | onAssignCallbacks.erase(found); 88 | } 89 | } 90 | 91 | operator T() const { return value; }; 92 | T& operator->() { return value; } 93 | 94 | friend std::ostream& operator<< (std::ostream& stream, const ObservableValue& ov) { 95 | return stream << ov.value; 96 | } 97 | 98 | private: 99 | class TemporaryProxy { 100 | public: 101 | TemporaryProxy(ObservableValue* proxyCaller, const T& originalValue) : proxyCaller(proxyCaller), originalValue(originalValue), newValue(originalValue) { 102 | }; 103 | ~TemporaryProxy() { 104 | if (originalValue != newValue) { 105 | proxyCaller->operator=(newValue); 106 | } 107 | }; 108 | operator T* () { 109 | return &newValue; 110 | } 111 | private: 112 | ObservableValue* proxyCaller; 113 | const T originalValue; 114 | T newValue; 115 | }; 116 | 117 | public: 118 | TemporaryProxy operator&() { 119 | return TemporaryProxy(this, value); 120 | } 121 | 122 | private: 123 | T value; 124 | std::map> onAssignCallbacks = {}; 125 | long nextAssignCallbackIndex = 0; 126 | }; 127 | 128 | template 129 | void to_json(nlohmann::json& j, const ObservableValue& p) { 130 | T t = p; 131 | j = t; 132 | } 133 | 134 | template 135 | void from_json(const nlohmann::json& j, ObservableValue& p) { 136 | T t = j; 137 | p = t; 138 | } 139 | 140 | template 141 | class shared_ptr_map_with_creation : public std::map> { 142 | public: 143 | std::shared_ptr& operator[](const K& key) { 144 | if (std::map>::find(key) == std::map>::end()) { 145 | std::map>::insert(std::pair>(key, std::make_shared())); 146 | } 147 | return std::map>::operator[](key); 148 | } 149 | }; 150 | -------------------------------------------------------------------------------- /include/imgui_sct_widgets.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "OptionsStructures.h" 3 | 4 | enum ReceiverCollapsibleChangeFlags { 5 | ReceiverCollapsible_Remove = 1 << 0 6 | }; 7 | 8 | enum FilterIDOptionLineChangeFlags { 9 | FilterOptionLine_Value = 1 << 0, 10 | FilterOptionLine_Remove = 1 << 1 11 | }; 12 | 13 | namespace ImGui { 14 | inline std::string BuildLabel(std::string visibleLabelPart, std::string invisibleLabelPart, std::string indexString) { 15 | return visibleLabelPart + "##" + invisibleLabelPart + "-" + indexString; 16 | } 17 | inline std::string BuildLabel(const char* visibleLabelPart, std::string invisibleLabelPart, std::string indexString) { 18 | return BuildLabel(std::string(visibleLabelPart), invisibleLabelPart, indexString); 19 | } 20 | inline std::string BuildLabel(std::string visibleLabelPart, std::string invisibleLabelPart, int index) { 21 | return BuildLabel(visibleLabelPart, invisibleLabelPart, std::to_string(index)); 22 | } 23 | inline std::string BuildLabel(const char* visibleLabelPart, std::string invisibleLabelPart, int index) { 24 | return BuildLabel(std::string(visibleLabelPart), invisibleLabelPart, index); 25 | } 26 | inline std::string BuildLabel(std::string invisibleLabelPart, std::string indexString) { 27 | return "##" + invisibleLabelPart + "-" + indexString; 28 | } 29 | inline std::string BuildLabel(std::string invisibleLabelPart, int index) { 30 | return BuildLabel(invisibleLabelPart, std::to_string(index)); 31 | } 32 | inline std::string BuildVisibleLabel(std::string visibleLabelPart, std::string invisibleLabelPart) { 33 | return visibleLabelPart + "##" + invisibleLabelPart; 34 | } 35 | inline std::string BuildVisibleLabel(const char* visibleLabelPart, std::string invisibleLabelPart) { 36 | return BuildVisibleLabel(std::string(visibleLabelPart), invisibleLabelPart); 37 | } 38 | 39 | void HexColorEdit(const char* label, std::string* color); 40 | void SameLineEnd(float offset_from_end_x); 41 | int ReceiverCollapsible(int index, std::shared_ptr receiverOptions); 42 | bool NewReceiverLine(GW2_SCT::MessageCategory* categoryOut, GW2_SCT::MessageType* typeOut); 43 | int FilterOptionLine(uint32_t id, GW2_SCT::filter_options_struct* opt); 44 | bool ClampingDragFloat(const char* label, float* v, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* display_format = "%.3f", float power = 1.0f); 45 | bool ClampingDragInt(const char* label, int* v, float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* display_format = "%.0f"); 46 | 47 | void BeginDisabled(); 48 | void EndDisabled(); 49 | 50 | bool HasWindow(); 51 | } 52 | -------------------------------------------------------------------------------- /languages/LANGUAGE_CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Language file Changelog 2 | 3 | Here you can find all language strings added for any given version if you want to improve a language file. 4 | 5 | ## Version History 6 | 7 | * 2.0-RC3 8 | * switched to json file format 9 | * all categories containing message parameter descriptions or default templates were renamed and any of their keys can now be omitted, if a key is omitted this way its value is first searched in the definitions for the according message type, then message catagory and then general options (e.g. `[Message_Pet_Out_Heal] -> Parameter_Description_s` will take the first value of `[Message_Type_Heal] -> Parameter_Description_s`, `[Message_Category_Pet_Out] -> Parameter_Description_s` or `[Message_General] -> Parameter_Description_s`) 10 | * Added: 11 | * `[Option_UI] -> Menu_Bar_Profiles` 12 | * `[Profile_Option_UI] -> Delete_Confirmation_Title` 13 | * `[Profile_Option_UI] -> Delete_Confirmation_Content` 14 | * `[Profile_Option_UI] -> Delete_Confirmation_Confirmation` 15 | * `[Profile_Option_UI] -> Delete_Confirmation_Cancel` 16 | * `[Profile_Option_UI] -> Profile_Description` 17 | * `[Profile_Option_UI] -> Master_Profile` 18 | * `[Profile_Option_UI] -> Character_Specific_Profile_Heading` 19 | * `[Profile_Option_UI] -> Character_Specific_Profile_Enabled` 20 | * `[Profile_Option_UI] -> Current_Profile_Heading` 21 | * `[Profile_Option_UI] -> Current_Profile_Name` 22 | * `[Profile_Option_UI] -> Create_Profile_Copy` 23 | * `[Profile_Option_UI] -> Delete_Profile` 24 | * `[Profile_Option_UI] -> Profile_Copy_Suffix` 25 | * `[Skill_Filter_Option_UI] -> Filter_By` 26 | * `[Skill_Icons_Option_UI] -> Skill_Icons_Display_Type` 27 | * `[Skill_Icons_Option_UI] -> Skill_Icons_Display_Type_Normal` 28 | * `[Skill_Icons_Option_UI] -> Skill_Icons_Display_Type_Black_Culled` 29 | * `[Skill_Icons_Option_UI] -> Skill_Icons_Display_Type_Black_Border_Culled` 30 | * `[Skill_Icons_Option_UI] -> Skill_Icons_Display_Type_Black_Border_Touching_Culled` 31 | * Moved: 32 | * `[Option_UI] -> Skill_Icons_Warning` to `[Skill_Icons_Option_UI] -> Skill_Icons_Warning` 33 | * `[Option_UI] -> Skill_Icons_Enable` to `[Skill_Icons_Option_UI] -> Skill_Icons_Enable` 34 | * `[Option_UI] -> Skill_Icons_Preload_Description` to `[Skill_Icons_Option_UI] -> Skill_Icons_Preload_Description` 35 | * `[Option_UI] -> Skill_Icons_Preload` to `[Skill_Icons_Option_UI] -> Skill_Icons_Preload` 36 | * Removed: 37 | * `[Option_UI] -> Menu_Bar_Messages` 38 | * 2.0-RC2 39 | * Added: 40 | * `[General] -> Font_Size_Type` 41 | * `[General] -> Default_Font_Size` 42 | * `[General] -> Default_Crit_Font_Size` 43 | * `[General] -> Custom_Font_Size` 44 | * `[General] -> Skill_Filter_Type_ID` 45 | * `[General] -> Skill_Filter_Type_Name` 46 | * `[Option_UI] -> Menu_Bar_Filtered_Skills` 47 | * `[Option_UI] -> General_Scrolling_Speed_Toolip` 48 | * `[Option_UI] -> General_Max_Messages_Toolip` 49 | * `[Option_UI] -> General_Combine_Messages_Toolip` 50 | * `[Option_UI] -> General_Self_Only_As_Incoming_Toolip` 51 | * `[Option_UI] -> General_Out_Only_For_Target_Toolip` 52 | * `[Option_UI] -> Scroll_Areas_Name` 53 | * `[Option_UI] -> Receiver_Name` 54 | * `[Option_UI] -> Messages_Type` 55 | * `[Option_UI] -> Messages_Type_Physical` 56 | * `[Option_UI] -> Messages_Type_Crit` 57 | * `[Option_UI] -> Messages_Type_Bleeding` 58 | * `[Option_UI] -> Messages_Type_Burning` 59 | * `[Option_UI] -> Messages_Type_Poison` 60 | * `[Option_UI] -> Messages_Type_Confusion` 61 | * `[Option_UI] -> Messages_Type_Retaliation` 62 | * `[Option_UI] -> Messages_Type_Torment` 63 | * `[Option_UI] -> Messages_Type_Dot` 64 | * `[Option_UI] -> Messages_Type_Heal` 65 | * `[Option_UI] -> Messages_Type_Hot` 66 | * `[Option_UI] -> Messages_Type_Shield_Receive` 67 | * `[Option_UI] -> Messages_Type_Shield_Remove` 68 | * `[Option_UI] -> Messages_Type_Block` 69 | * `[Option_UI] -> Messages_Type_Evade` 70 | * `[Option_UI] -> Messages_Type_Invulnerable` 71 | * `[Option_UI] -> Messages_Type_Miss` 72 | * `[Scroll_Area_Option_UI] -> All_Receivers` 73 | * `[Scroll_Area_Option_UI] -> New_Receiver` 74 | * for incoming and incoming pet message category and type sections: `[...] -> Parameter_Description_r` 75 | * Moved: 76 | * `[Option_UI] -> Scroll_Areas_New` to `[Scroll_Area_Option_UI] -> Scroll_Areas_New` 77 | * Updated: 78 | * `[General] -> Text_Align_Center` 79 | * `[Scroll_Area_Option_UI] -> Delete_Confirmation_Content` 80 | * Removed: 81 | * `[Option_UI] -> Menu_Bar_Filtered_Skill_IDs` 82 | * `[Option_UI] -> Scroll_Areas_None_Exist` 83 | * for all message category and type sections: `[...] -> title` 84 | * 1.3-RC1 85 | * Added: 86 | * `[General] -> General_Scrolling_Speed_Toolip` 87 | * `[General] -> General_Max_Messages_Toolip` 88 | * `[General] -> General_Combine_Messages_Toolip` 89 | * `[General] -> General_Self_Only_As_Incoming_Toolip` 90 | * `[General] -> General_Out_Only_For_Target_Toolip` 91 | * `[General] -> Font_Size_Type` 92 | * `[General] -> Default_Font_Size` 93 | * `[General] -> Default_Crit_Font_Size` 94 | * `[General] -> Custom_Font_Size` 95 | * `[Option_UI] -> Menu_Bar_Filtered_Skills` 96 | * Removed: 97 | * `[Option_UI] -> Menu_Bar_Filtered_Skill_IDs` 98 | * 1.1-RC3 99 | * initially started language files 100 | -------------------------------------------------------------------------------- /languages/english/lang.json: -------------------------------------------------------------------------------- 1 | { 2 | "General": { 3 | "Outdated_Popup_Title": "ArcDPS outdated", 4 | "Outdated_Popup_Content": "You are running an old version of ArcDPS some features of SCT\nmay not work properly on can cause crashes. Updating ArcDPS is advised.", 5 | "Outdated_Popup_Found_Version": "Found version", 6 | "Outdated_Popup_Required_Version": "Required version", 7 | "Outdated_Popup_Confirmation": "OK", 8 | "Font": "Font", 9 | "Font_Size": "Font Size", 10 | "Font_Size_Type": "Font Size Type", 11 | "Default_Font_Size": "Default Font Size", 12 | "Default_Crit_Font_Size": "Default Crit Font Size", 13 | "Custom_Font_Size": "Custom Font Size", 14 | "Skill_Filter_Type_ID": "Skill ID", 15 | "Skill_Filter_Type_Name": "Skill Name", 16 | "Font_Master": "Master Font", 17 | "Font_Default": "Default Font", 18 | "Text_Align_Left": "Left", 19 | "Text_Align_Center": "Middle", 20 | "Text_Align_Right": "Right", 21 | "Text_Curve_Left": "Left", 22 | "Text_Curve_Straight": "Straight", 23 | "Text_Curve_Right": "Right", 24 | "Old_Save_Popup_Title": "Old Save File Version", 25 | "Old_Save_Popup_Content": "The found options file was saved with an older version of GW2 SCT.\nSome of your settings may have been reverted to default.", 26 | "Old_Save_Popup_Backup_Made": "A backup of your old options save file can be found at:", 27 | "Old_Save_Popup_Confirmation": "OK", 28 | "Unknown_Skill_Source": "", 29 | "Unknown_Skill_Target": "", 30 | "Unknown_Skill_Name": "", 31 | "Default_Scroll_Area_Incoming": "Incoming Events", 32 | "Default_Scroll_Area_Outgoing": "Outgoing Events" 33 | }, 34 | "Option_UI": { 35 | "Title": "SCT Options", 36 | "Menu_Bar_General": "General", 37 | "Menu_Bar_Scroll_Areas": "Scroll Areas", 38 | "Menu_Bar_Profession_Colors": "Profession Colors", 39 | "Menu_Bar_Filtered_Skills": "Filtered Skills", 40 | "Menu_Bar_Skill_Icons": "Skill Icons", 41 | "Menu_Bar_Profiles": "Profiles", 42 | "General_Enabled": "SCT enabled", 43 | "General_Scrolling_Speed": "Scrolling Speed", 44 | "General_Scrolling_Speed_Toolip": "The velocity at which each message is scrolling downwards in pixels per seconds.", 45 | "General_Drop_Shadow": "Drop Shadow", 46 | "General_Max_Messages": "Maximal Messages in Stack", 47 | "General_Max_Messages_Toolip": "Messages that want to appear on screen but there is not enough room for them yet\nremain in a stack. When the size of the stack would exceed this given number all\nmessages will be forced to teleport a bit downwards for a new message to appear.", 48 | "General_Combine_Messages": "Combine all Messages", 49 | "General_Combine_Messages_Toolip": "If enabled new messages will only be combined with the newest message in the\nstack if possible.\nIf disabled new messages will be combined with any of the messages in stack\nif possible, starting from newest to oldest.", 50 | "General_Self_Only_As_Incoming": "Show self messages only as incoming", 51 | "General_Self_Only_As_Incoming_Toolip": "This option effects only messages with both the source and destination\nbeing yourself.\nIf enabled these messages will only be triggered as 'Player Incoming'\nmessages.\nIf disabled these messages will be triggered as both 'Player Incoming'\nand 'Player outgoing' messages.", 52 | "General_Out_Only_For_Target": "Show outgoing messages only for target", 53 | "General_Out_Only_For_Target_Toolip": "If enabled only messages that have your current target as destination will\nbe triggered in both categories 'Player Outgoing' and 'Pet Outgoing'.", 54 | "Scroll_Areas_Name": "Scroll Area Name", 55 | "Receiver_Name": "Receiver Name", 56 | "Messages_Category": "Message Category", 57 | "Messages_Category_Player_Out": "Player Outgoing", 58 | "Messages_Category_Player_In": "Player Incoming", 59 | "Messages_Category_Pet_Out": "Pet Outgoing", 60 | "Messages_Category_Pet_In": "Pet Incoming", 61 | "Messages_Type": "Message Type", 62 | "Messages_Type_Physical": "Physical Hit", 63 | "Messages_Type_Crit": "Physical Crit", 64 | "Messages_Type_Bleeding": "Bleeding Damage", 65 | "Messages_Type_Burning": "Burning Damage", 66 | "Messages_Type_Poison": "Poison Damage", 67 | "Messages_Type_Confusion": "Confusion Damage", 68 | "Messages_Type_Retaliation": "Retaliation Damage", 69 | "Messages_Type_Torment": "Torment Damage", 70 | "Messages_Type_Dot": "Other Continuous Damage", 71 | "Messages_Type_Heal": "Heal", 72 | "Messages_Type_Hot": "Heal over Time", 73 | "Messages_Type_Shield_Receive": "Shield Created", 74 | "Messages_Type_Shield_Remove": "Shielded Damage", 75 | "Messages_Type_Block": "Block", 76 | "Messages_Type_Evade": "Evade", 77 | "Messages_Type_Invulnerable": "Invulnerable", 78 | "Messages_Type_Miss": "Miss", 79 | "Profession_Colors_Guardian": "Guardian", 80 | "Profession_Colors_Warrior": "Warrior", 81 | "Profession_Colors_Engineer": "Engineer", 82 | "Profession_Colors_Ranger": "Ranger", 83 | "Profession_Colors_Thief": "Thief", 84 | "Profession_Colors_Elementalist": "Elementalist", 85 | "Profession_Colors_Mesmer": "Mesmer", 86 | "Profession_Colors_Necromancer": "Necromancer", 87 | "Profession_Colors_Revenant": "Revenant", 88 | "Profession_Colors_Undetectable": "Undetectable profession" 89 | }, 90 | "Example_Message_UI": { 91 | "Title": "SCT Example Messages", 92 | "Start_Recording": "Start recording messages", 93 | "Stop_Recording": "Stop recording messages", 94 | "Start_Emitting": "Start emitting messages", 95 | "Stop_Emitting": "Stop emitting messages", 96 | "Clear_Recorded_Messages": "Clear all" 97 | }, 98 | "Message": { 99 | "Multiple_Sources": "Multiple", 100 | "Number_Of_Hits": "Hits" 101 | }, 102 | "Scroll_Area_Option_UI": { 103 | "Scroll_Areas_New": "New Scroll Area", 104 | "Horizontal_Offset": "Horizontal Offset", 105 | "Vertical_Offset": "Vertical Offset", 106 | "Width": "Width", 107 | "Height": "Height", 108 | "Text_Align": "Text Align", 109 | "Text_Flow": "Text Flow", 110 | "All_Receivers": "Message receivers:", 111 | "New_Receiver": "New receiver:", 112 | "Delete_Confirmation_Title": "Delete?", 113 | "Delete_Confirmation_Content": "Are you sure you want to delete this scroll area?\nAll receivers registered within this scroll area\nwill be deleted aswell.\n\n", 114 | "Delete_Confirmation_Confirmation": "OK", 115 | "Delete_Confirmation_Cancel": "Cancel" 116 | }, 117 | "Receiver_Option_UI": { 118 | "Template": "Template", 119 | "Available_Parameters": "Available template parameters", 120 | "Delete_Confirmation_Title": "Delete?", 121 | "Delete_Confirmation_Content": "Are you sure you want to delete this message receiver?\n\n", 122 | "Delete_Confirmation_Confirmation": "OK", 123 | "Delete_Confirmation_Cancel": "Cancel" 124 | }, 125 | "Skill_Filter_Option_UI": { 126 | "Delete_Confirmation_Title": "Delete?", 127 | "Delete_Confirmation_Content": "Are you sure you want to delete this skill id filter?", 128 | "Delete_Confirmation_Confirmation": "OK", 129 | "Delete_Confirmation_Cancel": "Cancel", 130 | "Filter_By": "Filter by" 131 | }, 132 | "Skill_Icons_Option_UI": { 133 | "Skill_Icons_Warning": "Warning: Using skill icons will establish an additional connection to the GW2 rendering API. All downloaded icons will be saved in", 134 | "Skill_Icons_Enable": "Enable skill icons", 135 | "Skill_Icons_Preload_Description": "Preloading skill icons will download every possible skill icon from the GW2 rendering API. This will require ~25MB of free memory space available. If not enabled, icons will be only downloaded as needed.", 136 | "Skill_Icons_Preload": "Preload all skill icons", 137 | "Skill_Icons_Display_Type": "Transparency", 138 | "Skill_Icons_Display_Type_Normal": "None", 139 | "Skill_Icons_Display_Type_Black_Culled": "Cull dark pixels", 140 | "Skill_Icons_Display_Type_Black_Border_Culled": "Cull border squares of dark pixels", 141 | "Skill_Icons_Display_Type_Black_Border_Touching_Culled": "Cull dark pixels touching border or other transparent pixels" 142 | }, 143 | "Profile_Option_UI": { 144 | "Delete_Confirmation_Title": "Delete Profile?", 145 | "Delete_Confirmation_Content": "Are you sure you want to delete this profile filter? All options set within will be lost.", 146 | "Delete_Confirmation_Confirmation": "OK", 147 | "Delete_Confirmation_Cancel": "Cancel", 148 | "Profile_Description": "Profiles store all your other SCT options and can be quickly changed in this tab. You can either define which profile is used for the in game character you are currently playing or otherwise the profile selectected as master profile will be loaded.", 149 | "Master_Profile": "Master Profile", 150 | "Character_Specific_Profile_Heading": "Character specific profile:", 151 | "Character_Specific_Profile_Enabled": "enable specific profile for ", 152 | "Current_Profile_Heading": "Current profile information:", 153 | "Current_Profile_Name": "Profile Name", 154 | "Create_Profile_Copy": "Create a Copy", 155 | "Delete_Profile": "Delete this profile", 156 | "Profile_Copy_Suffix": "Copy" 157 | }, 158 | "Message_General": { 159 | "Parameter_Description_v": "Amount of damage", 160 | "Parameter_Description_n": "Name of the skills target", 161 | "Parameter_Description_s": "Skillname", 162 | "Parameter_Description_p": "Name of the pet/clone attacking", 163 | "Parameter_Description_c": "Color for the skills source profession (use: [col=%c]...[/col])", 164 | "Parameter_Description_r": "Profession name of source", 165 | "Parameter_Description_i": "Skillicon", 166 | "Default_Value": "%v %i" 167 | }, 168 | "Message_Category_Player_In": { 169 | "Parameter_Description_n": "Name of the attacker" 170 | }, 171 | "Message_Category_Pet_In": { 172 | "Parameter_Description_n": "Name of the attacker", 173 | "Parameter_Description_p": "Name of the pet/clone being attacked", 174 | "Default_Value": "%i Pet -%v" 175 | }, 176 | "Message_Type_Heal": { 177 | "Parameter_Description_v": "Amount of heal", 178 | "Default_Value": "+%v %i" 179 | }, 180 | "Message_Type_HoT": { 181 | "Parameter_Description_v": "Amount of heal", 182 | "Default_Value": "+%v %i" 183 | }, 184 | "Message_Type_Shield_Got": { 185 | "Parameter_Description_v": "Amount of shield", 186 | "Default_Value": "+%v %i" 187 | }, 188 | "Message_Type_Shield": { 189 | "Default_Value": "%v -=absorb=- %i" 190 | }, 191 | "Message_Type_Block": { 192 | "Default_Value": "Block! %i" 193 | }, 194 | "Message_Type_Evade": { 195 | "Default_Value": "Evade! %i" 196 | }, 197 | "Message_Type_Invulnerable": { 198 | "Default_Value": "Invulnerable! %i" 199 | }, 200 | "Message_Type_Miss": { 201 | "Default_Value": "Miss! %i" 202 | }, 203 | "Message_Player_In_Physical": { 204 | "Default_Value": "%i ([col=%c]%n[/col]) -[col=FFFFFF]%v[/col]" 205 | }, 206 | "Message_Player_In_Crit": { 207 | "Default_Value": "%i ([col=%c]%n[/col]) -[col=FFFFFF]%v[/col]" 208 | }, 209 | "Message_Player_In_Bleeding": { 210 | "Default_Value": "%i ([col=%c]%n[/col]) -[col=E84B30]%v[/col]" 211 | }, 212 | "Message_Player_In_Burning": { 213 | "Default_Value": "%i ([col=%c]%n[/col]) -[col=FF9E32]%v[/col]" 214 | }, 215 | "Message_Player_In_Poison": { 216 | "Default_Value": "%i ([col=%c]%n[/col]) -[col=00C400]%v[/col]" 217 | }, 218 | "Message_Player_In_Confusion": { 219 | "Default_Value": "%i ([col=%c]%n[/col]) -[col=B243FF]%v[/col]" 220 | }, 221 | "Message_Player_In_Retaliation": { 222 | "Default_Value": "%i ([col=%c]%n[/col]) -[col=FFED00]%v[/col]" 223 | }, 224 | "Message_Player_In_Torment": { 225 | "Default_Value": "%i ([col=%c]%n[/col]) -[col=24451F]%v[/col]" 226 | }, 227 | "Message_Player_In_DoT": { 228 | "Default_Value": "%i ([col=%c]%n[/col]) -[col=45CDFF]%v[/col]" 229 | }, 230 | "Message_Player_In_Heal": { 231 | "Parameter_Description_n": "Name of the healing source", 232 | "Default_Value": "%i +%v" 233 | }, 234 | "Message_Player_In_HoT": { 235 | "Parameter_Description_n": "Name of the healing source", 236 | "Default_Value": "%i +%v" 237 | }, 238 | "Message_Player_In_ShieldGot": { 239 | "Parameter_Description_n": "Name of the shield source", 240 | "Default_Value": "%i +%v" 241 | }, 242 | "Message_Player_In_Shield": { 243 | "Default_Value": "%i %v -=absorb=-" 244 | }, 245 | "Message_Player_In_Block": { 246 | "Default_Value": "%i Block!" 247 | }, 248 | "Message_Player_In_Evade": { 249 | "Default_Value": "%i Evade!" 250 | }, 251 | "Message_Player_In_Invulnerable": { 252 | "Default_Value": "%i Invulnerable!" 253 | }, 254 | "Message_Player_In_Miss": { 255 | "Default_Value": "%i Miss!" 256 | }, 257 | "Message_Pet_Out_Heal": { 258 | "Parameter_Description_v": "Name of the pet/clone healing" 259 | }, 260 | "Message_Pet_Out_HoT": { 261 | "Parameter_Description_p": "Name of the pet/clone healing" 262 | }, 263 | "Message_Pet_Out_ShieldGot": { 264 | "Parameter_Description_p": "Name of the pet/clone shielding" 265 | }, 266 | "Message_Pet_In_Heal": { 267 | "Parameter_Description_n": "Name of the healing source", 268 | "Parameter_Description_p": "Name of the pet/clone being healed", 269 | "Default_Value": "%i Pet +%v" 270 | }, 271 | "Message_Pet_In_HoT": { 272 | "Parameter_Description_n": "Name of the healing source", 273 | "Parameter_Description_p": "Name of the pet/clone being healed", 274 | "Default_Value": "%i Pet +%v" 275 | }, 276 | "Message_Pet_In_ShieldGot": { 277 | "Parameter_Description_n": "Name of the shield source", 278 | "Parameter_Description_p": "Name of the pet/clone being shielded", 279 | "Default_Value": "%i Pet +%v" 280 | }, 281 | "Message_Pet_In_Shield": { 282 | "Default_Value": "%i Pet %v -=absorb=-" 283 | }, 284 | "Message_Pet_In_Block": { 285 | "Default_Value": "%i Pet Block!" 286 | }, 287 | "Message_Pet_In_Evade": { 288 | "Default_Value": "%i Pet Evade!" 289 | }, 290 | "Message_Pet_In_Invulnerable": { 291 | "Default_Value": "%i Pet Invulnerable!" 292 | }, 293 | "Message_Pet_In_Miss": { 294 | "Default_Value": "%i Pet Miss!" 295 | } 296 | } -------------------------------------------------------------------------------- /languages/old language files (pre 2.0c)/chinese/lang.ini: -------------------------------------------------------------------------------- 1 | [General] 2 | Outdated_Popup_Title = ArcDPS版本过低 3 | Outdated_Popup_Content = 您正在运行旧版本的ArcDPS 本插件的一些功能\n可能无法正常工作可能导致崩溃.建议更新ArcDPS. 4 | Outdated_Popup_Found_Version = 找到一个新版本 5 | Outdated_Popup_Required_Version = 所需版本 6 | Outdated_Popup_Confirmation = 确定 7 | Font = 字体 8 | Font_Size = 字体大小 9 | Font_Master = 主要字体 10 | Font_Default = 默认字体 11 | Text_Align_Left = 左边 12 | Text_Align_Center = 中间 13 | Text_Align_Right = 右边 14 | Text_Curve_Left = 左边 15 | Text_Curve_Straight = 直行 16 | Text_Curve_Right = 右边 17 | Old_Save_Popup_Title = 保存旧文件版本 18 | Old_Save_Popup_Content = 找到的选项文件与较旧版本的GW2浮动输出一起保存.\n您的某些设置可能已恢复为默认值. 19 | Old_Save_Popup_Backup_Made = 您可以在以下位置找到旧选项保存文件的备份: 20 | Old_Save_Popup_Confirmation = 确定 21 | Unknown_Skill_Source = <区域> 22 | Unknown_Skill_Target = <区域> 23 | Unknown_Skill_Name = <技能> 24 | Default_Scroll_Area_Incoming = Incoming Events 25 | Default_Scroll_Area_Outgoing = Outgoing Events 26 | 27 | 28 | [Option_UI] 29 | Title = SCT 设置 30 | Menu_Bar_General = 一般 31 | Menu_Bar_Scroll_Areas = 滚动区域 32 | Menu_Bar_Messages = 消息 33 | Menu_Bar_Profession_Colors = 职业颜色 34 | Menu_Bar_Filtered_Skill_IDs = 过滤技能ID 35 | Menu_Bar_Skill_Icons = 技能图标 36 | General_Enabled = SCT enabled 37 | General_Scrolling_Speed = 滚动速度 38 | General_Drop_Shadow = 下降阴影 39 | General_Max_Messages = 列队中的最大消息数 40 | General_Combine_Messages = 合并所有消息 41 | General_Self_Only_As_Incoming = 仅显示自己的输出 42 | General_Out_Only_For_Target = 仅显示目标的输出 43 | Scroll_Areas_None_Exist = 无 44 | Scroll_Areas_New = 新滚动区域 45 | Messages_Category = 消息类别 46 | Messages_Category_Player_Out = 玩家输出 47 | Messages_Category_Player_In = 玩家恢复 48 | Messages_Category_Pet_Out = 宠物输出 49 | Messages_Category_Pet_In = 宠物恢复 50 | Profession_Colors_Guardian = 守护 51 | Profession_Colors_Warrior = 战士 52 | Profession_Colors_Engineer = 工程师 53 | Profession_Colors_Ranger = 游侠 54 | Profession_Colors_Thief = 盗贼 55 | Profession_Colors_Elementalist = 元素 56 | Profession_Colors_Mesmer = 幻术 57 | Profession_Colors_Necromancer = 死灵 58 | Profession_Colors_Revenant = 魂武 59 | Profession_Colors_Undetectable = 无法探测到职业 60 | Skill_Icons_Warning = 警告:使用技能图标将建立与GW2渲染API的附加连接. 所有下载的图标都将保存在 61 | Skill_Icons_Enable = 启用技能图标 62 | Skill_Icons_Preload_Description = 预加载技能图标将从GW2渲染API下载每个可能的技能图标.这将需要大约25MB的可用内存空间.如果未启用,则仅根据需要下载图标. 63 | Skill_Icons_Preload = 预加载所有技能图标 64 | 65 | 66 | [Message] 67 | Multiple_Sources = 暴击 68 | Number_Of_Hits = 击中 69 | 70 | 71 | [Scroll_Area_Option_UI] 72 | Delete_Confirmation_Title = 删除? 73 | Delete_Confirmation_Content = 您确定要删除此滚动区域吗?\n可能会有指向此区域的消息\n在重定向到另一个滚动\n区域之前不会显示.\n\n 74 | Delete_Confirmation_Confirmation = 确定 75 | Delete_Confirmation_Cancel = 取消 76 | Horizontal_Offset = 水平偏移 77 | Vertical_Offset = 垂直偏移 78 | Width = 宽 79 | Height = 高 80 | Text_Align = 文字对齐 81 | Text_Flow = 流动文字 82 | 83 | 84 | [Message_Option_UI] 85 | Output_Scroll_Area = 输出滚动区域 86 | Template = 模板 87 | Available_Parameters = 可用的模板参数 88 | 89 | 90 | [Skill_Filter_Option_UI] 91 | Delete_Confirmation_Title = 删除? 92 | Delete_Confirmation_Content = 您确定要删除此技能ID过滤器吗? 93 | Delete_Confirmation_Confirmation = 确定 94 | Delete_Confirmation_Cancel = 取消 95 | 96 | 97 | [messagePlayerOutgoingPhysical] 98 | title = 物理命中 99 | Parameter_Description_v = 伤害量 100 | Parameter_Description_n = 技能目标的名称 101 | Parameter_Description_s = 技能名称 102 | Parameter_Description_i = 技能图标 103 | Default_Value = %v %i 104 | 105 | 106 | [messagePlayerOutgoingCrit] 107 | title = 物理暴击 108 | Parameter_Description_v = 伤害量 109 | Parameter_Description_n = 技能目标的名称 110 | Parameter_Description_s = 技能名称 111 | Parameter_Description_i = 技能图标 112 | Default_Value = %v %i 113 | 114 | 115 | [messagePlayerOutgoingBleeding] 116 | title = 流血伤害 117 | Parameter_Description_v = 伤害量 118 | Parameter_Description_n = 技能目标的名称 119 | Parameter_Description_s = 技能名称 120 | Parameter_Description_i = 技能图标 121 | Default_Value = %v %i 122 | 123 | 124 | [messagePlayerOutgoingBurning] 125 | title = 燃烧伤害 126 | Parameter_Description_v = 伤害量 127 | Parameter_Description_n = 技能目标的名称 128 | Parameter_Description_s = 技能名称 129 | Parameter_Description_i = 技能图标 130 | Default_Value = %v %i 131 | 132 | 133 | [messagePlayerOutgoingPoison] 134 | title = 中毒伤害 135 | Parameter_Description_v = 伤害量 136 | Parameter_Description_n = 技能目标的名称 137 | Parameter_Description_s = 技能名称 138 | Parameter_Description_i = 技能图标 139 | Default_Value = %v %i 140 | 141 | 142 | [messagePlayerOutgoingConfusion] 143 | title = 困惑伤害 144 | Parameter_Description_v = 伤害量 145 | Parameter_Description_n = 技能目标的名称 146 | Parameter_Description_s = 技能名称 147 | Parameter_Description_i = 技能图标 148 | Default_Value = %v %i 149 | 150 | 151 | [messagePlayerOutgoingRetaliation] 152 | title = 反弹伤害 153 | Parameter_Description_v = 伤害量 154 | Parameter_Description_n = 技能目标的名称 155 | Parameter_Description_s = 技能名称 156 | Parameter_Description_i = 技能图标 157 | Default_Value = %v %i 158 | 159 | 160 | [messagePlayerOutgoingTorment] 161 | title = 折磨伤害 162 | Parameter_Description_v = 伤害量 163 | Parameter_Description_n = 技能目标的名称 164 | Parameter_Description_s = 技能名称 165 | Parameter_Description_i = 技能图标 166 | Default_Value = %v %i 167 | 168 | 169 | [messagePlayerOutgoingDoT] 170 | title = 其他症状伤害 171 | Parameter_Description_v = 伤害量 172 | Parameter_Description_n = 技能目标的名称 173 | Parameter_Description_s = 技能名称 174 | Parameter_Description_i = 技能图标 175 | Default_Value = %v %i 176 | 177 | 178 | [messagePlayerOutgoingHeal] 179 | title = 治疗 180 | Parameter_Description_v = 治疗量 181 | Parameter_Description_n = 技能目标的名称 182 | Parameter_Description_s = 技能名称 183 | Parameter_Description_i = 技能图标 184 | Default_Value = +%v %i 185 | 186 | 187 | [messagePlayerOutgoingHoT] 188 | title = 再生 189 | Parameter_Description_v = 治疗量 190 | Parameter_Description_n = 技能目标的名称 191 | Parameter_Description_s = 技能名称 192 | Parameter_Description_i = 技能图标 193 | Default_Value = +%v %i 194 | 195 | 196 | [messagePlayerOutgoingShieldGot] 197 | title = Shield Received 198 | Parameter_Description_v = Amount of Shield 199 | Parameter_Description_n = Name of the skills target 200 | Parameter_Description_s = Skillname 201 | Parameter_Description_i = Skillicon 202 | Default_Value = +%v %i 203 | 204 | 205 | [messagePlayerOutgoingShield] 206 | title = 屏障伤害 207 | Parameter_Description_v = 伤害量 208 | Parameter_Description_n = 技能目标的名称 209 | Parameter_Description_s = 技能名称 210 | Parameter_Description_i = 技能图标 211 | Default_Value = %v -=吸收=- %i 212 | 213 | 214 | [messagePlayerOutgoingBlock] 215 | title = 阻挡 216 | Parameter_Description_n = 技能目标的名称 217 | Parameter_Description_s = 技能名称 218 | Parameter_Description_i = 技能图标 219 | Default_Value = 阻挡! %i 220 | 221 | 222 | [messagePlayerOutgoingEvade] 223 | title = 闪避 224 | Parameter_Description_n = 技能目标的名称 225 | Parameter_Description_s = 技能名称 226 | Parameter_Description_i = 技能图标 227 | Default_Value = 闪避! %i 228 | 229 | 230 | [messagePlayerOutgoingInvulnerable] 231 | title = 无敌 232 | Parameter_Description_n = 技能目标的名称 233 | Parameter_Description_s = 技能名称 234 | Parameter_Description_i = 技能图标 235 | Default_Value = 无敌! %i 236 | 237 | 238 | [messagePlayerOutgoingMiss] 239 | title = 未击中 240 | Parameter_Description_n = 技能目标的名称 241 | Parameter_Description_s = 技能名称 242 | Parameter_Description_i = 技能图标 243 | Default_Value = 未击中! %i 244 | 245 | 246 | [messagePlayerIncomingPhysical] 247 | title = 物理命中 248 | Parameter_Description_v = 伤害量 249 | Parameter_Description_n = 攻击者的名字 250 | Parameter_Description_s = 技能名称 251 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 252 | Parameter_Description_i = 技能图标 253 | Default_Value = %i ([col=%c]%n[/col]) -[col=FFFFFF]%v[/col] 254 | 255 | 256 | [messagePlayerIncomingCrit] 257 | title = 物理暴击 258 | Parameter_Description_v = 伤害量 259 | Parameter_Description_n = 攻击者的名字 260 | Parameter_Description_s = 技能名称 261 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 262 | Parameter_Description_i = 技能图标 263 | Default_Value = %i ([col=%c]%n[/col]) -[col=FFFFFF]%v[/col] 264 | 265 | 266 | [messagePlayerIncomingBleeding] 267 | title = 流血伤害 268 | Parameter_Description_v = 伤害量 269 | Parameter_Description_n = 攻击者的名字 270 | Parameter_Description_s = 技能名称 271 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 272 | Parameter_Description_i = 技能图标 273 | Default_Value = %i ([col=%c]%n[/col]) -[col=E84B30]%v[/col] 274 | 275 | 276 | [messagePlayerIncomingBurning] 277 | title = 燃烧伤害 278 | Parameter_Description_v = 伤害量 279 | Parameter_Description_n = 攻击者的名字 280 | Parameter_Description_s = 技能名称 281 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 282 | Parameter_Description_i = 技能图标 283 | Default_Value = %i ([col=%c]%n[/col]) -[col=FF9E32]%v[/col] 284 | 285 | 286 | [messagePlayerIncomingPoison] 287 | title = 中毒伤害 288 | Parameter_Description_v = 伤害量 289 | Parameter_Description_n = 攻击者的名字 290 | Parameter_Description_s = 技能名称 291 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 292 | Parameter_Description_i = 技能图标 293 | Default_Value = %i ([col=%c]%n[/col]) -[col=00C400]%v[/col] 294 | 295 | 296 | [messagePlayerIncomingConfusion] 297 | title = 困惑伤害 298 | Parameter_Description_v = 伤害量 299 | Parameter_Description_n = 攻击者的名字 300 | Parameter_Description_s = 技能名称 301 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 302 | Parameter_Description_i = 技能图标 303 | Default_Value = %i ([col=%c]%n[/col]) -[col=B243FF]%v[/col] 304 | 305 | 306 | [messagePlayerIncomingRetaliation] 307 | title = 反弹伤害 308 | Parameter_Description_v = 伤害量 309 | Parameter_Description_n = 攻击者的名字 310 | Parameter_Description_s = 技能名称 311 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 312 | Parameter_Description_i = 技能图标 313 | Default_Value = %i ([col=%c]%n[/col]) -[col=FFED00]%v[/col] 314 | 315 | 316 | [messagePlayerIncomingTorment] 317 | title = 折磨伤害 318 | Parameter_Description_v = 伤害量 319 | Parameter_Description_n = 攻击者的名字 320 | Parameter_Description_s = 技能名称 321 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 322 | Parameter_Description_i = 技能图标 323 | Default_Value = %i ([col=%c]%n[/col]) -[col=24451F]%v[/col] 324 | 325 | 326 | [messagePlayerIncomingDoT] 327 | title = 其他症状伤害 328 | Parameter_Description_v = 伤害量 329 | Parameter_Description_n = 攻击者的名字 330 | Parameter_Description_s = 技能名称 331 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 332 | Parameter_Description_i = 技能图标 333 | Default_Value = %i ([col=%c]%n[/col]) -[col=45CDFF]%v[/col] 334 | 335 | 336 | [messagePlayerIncomingHeal] 337 | title = 治疗 338 | Parameter_Description_v = 治疗量 339 | Parameter_Description_n = Name of the healing source 340 | Parameter_Description_s = 技能名称 341 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 342 | Parameter_Description_i = 技能图标 343 | Default_Value = %i +%v 344 | 345 | 346 | [messagePlayerIncomingHoT] 347 | title = 再生 348 | Parameter_Description_v = 治疗量 349 | Parameter_Description_n = Name of the healing source 350 | Parameter_Description_s = 技能名称 351 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 352 | Parameter_Description_i = 技能图标 353 | Default_Value = %i +%v 354 | 355 | 356 | [messagePlayerIncomingShieldGot] 357 | title = Shielded 358 | Parameter_Description_v = Amount of damage 359 | Parameter_Description_n = Name of the shield source 360 | Parameter_Description_s = Skillname 361 | Parameter_Description_c = Color for the skills source profession (use: [col=%c]...[/col]) 362 | Parameter_Description_i = Skillicon 363 | Default_Value = %i +%v 364 | 365 | 366 | [messagePlayerIncomingShield] 367 | title = 屏障 368 | Parameter_Description_v = 伤害量 369 | Parameter_Description_n = 攻击者的名字 370 | Parameter_Description_s = 技能名称 371 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 372 | Parameter_Description_i = 技能图标 373 | Default_Value = %i %v -=吸收=- 374 | 375 | 376 | [messagePlayerIncomingBlock] 377 | title = 阻挡 378 | Parameter_Description_n = 攻击者的名字 379 | Parameter_Description_s = 技能名称 380 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 381 | Parameter_Description_i = 技能图标 382 | Default_Value = %i 阻挡! 383 | 384 | 385 | [messagePlayerIncomingEvade] 386 | title = 闪避 387 | Parameter_Description_n = 攻击者的名字 388 | Parameter_Description_s = 技能名称 389 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 390 | Parameter_Description_i = 技能图标 391 | Default_Value = %i Evade! 392 | 393 | 394 | [messagePlayerIncomingInvulnerable] 395 | title = 无敌 396 | Parameter_Description_n = 攻击者的名字 397 | Parameter_Description_s = 技能名称 398 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 399 | Parameter_Description_i = 技能图标 400 | Default_Value = %i 无敌! 401 | 402 | 403 | [messagePlayerIncomingMiss] 404 | title = 未击中 405 | Parameter_Description_n = 攻击者的名字 406 | Parameter_Description_s = 技能名称 407 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 408 | Parameter_Description_i = 技能图标 409 | Default_Value = %i 未击中! 410 | 411 | 412 | [messagePetOutgoingPhysical] 413 | title = 物理命中 414 | Parameter_Description_v = 伤害量 415 | Parameter_Description_n = 技能目标的名称 416 | Parameter_Description_s = 技能名称 417 | Parameter_Description_p = 宠物/分身攻击的名称 418 | Parameter_Description_i = 技能图标 419 | Default_Value = %v %i 420 | 421 | 422 | [messagePetOutgoingCrit] 423 | title = 物理暴击 424 | Parameter_Description_v = 伤害量 425 | Parameter_Description_n = 技能目标的名称 426 | Parameter_Description_s = 技能名称 427 | Parameter_Description_p = 宠物/分身攻击的名称 428 | Parameter_Description_i = 技能图标 429 | Default_Value = %v %i 430 | 431 | 432 | [messagePetOutgoingBleeding] 433 | title = 流血伤害 434 | Parameter_Description_v = 伤害量 435 | Parameter_Description_n = 技能目标的名称 436 | Parameter_Description_s = 技能名称 437 | Parameter_Description_p = 宠物/分身攻击的名称 438 | Parameter_Description_i = 技能图标 439 | Default_Value = %v %i 440 | 441 | 442 | [messagePetOutgoingBurning] 443 | title = 燃烧伤害 444 | Parameter_Description_v = 伤害量 445 | Parameter_Description_n = 技能目标的名称 446 | Parameter_Description_s = 技能名称 447 | Parameter_Description_p = 宠物/分身攻击的名称 448 | Parameter_Description_i = 技能图标 449 | Default_Value = %v %i 450 | 451 | 452 | [messagePetOutgoingPoison] 453 | title = 中毒伤害 454 | Parameter_Description_v = 伤害量 455 | Parameter_Description_n = 技能目标的名称 456 | Parameter_Description_s = 技能名称 457 | Parameter_Description_p = 宠物/分身攻击的名称 458 | Parameter_Description_i = 技能图标 459 | Default_Value = %v %i 460 | 461 | 462 | [messagePetOutgoingConfusion] 463 | title = 困惑伤害 464 | Parameter_Description_v = 伤害量 465 | Parameter_Description_n = 技能目标的名称 466 | Parameter_Description_s = 技能名称 467 | Parameter_Description_p = 宠物/分身攻击的名称 468 | Parameter_Description_i = 技能图标 469 | Default_Value = %v %i 470 | 471 | 472 | [messagePetOutgoingRetaliation] 473 | title = 反弹伤害 474 | Parameter_Description_v = 伤害量 475 | Parameter_Description_n = 技能目标的名称 476 | Parameter_Description_s = 技能名称 477 | Parameter_Description_p = 宠物/分身攻击的名称 478 | Parameter_Description_i = 技能图标 479 | Default_Value = %v %i 480 | 481 | 482 | [messagePetOutgoingTorment] 483 | title = 折磨伤害 484 | Parameter_Description_v = 伤害量 485 | Parameter_Description_n = 技能目标的名称 486 | Parameter_Description_s = 技能名称 487 | Parameter_Description_p = 宠物/分身攻击的名称 488 | Parameter_Description_i = 技能图标 489 | Default_Value = %v %i 490 | 491 | 492 | [messagePetOutgoingDoT] 493 | title = 其他症状伤害 494 | Parameter_Description_v = 伤害量 495 | Parameter_Description_n = 技能目标的名称 496 | Parameter_Description_s = 技能名称 497 | Parameter_Description_p = 宠物/分身攻击的名称 498 | Parameter_Description_i = 技能图标 499 | Default_Value = %v %i 500 | 501 | 502 | [messagePetOutgoingHeal] 503 | title = 治疗 504 | Parameter_Description_v = 治疗量 505 | Parameter_Description_n = 技能目标的名称 506 | Parameter_Description_s = 技能名称 507 | Parameter_Description_p = 宠物/克隆治疗的名称 508 | Parameter_Description_i = 技能图标 509 | Default_Value = +%v %i 510 | 511 | 512 | [messagePetOutgoingHoT] 513 | title = 再生 514 | Parameter_Description_v = 治疗量 515 | Parameter_Description_n = 技能目标的名称 516 | Parameter_Description_s = 技能名称 517 | Parameter_Description_p = 宠物/克隆治疗的名称 518 | Parameter_Description_i = 技能图标 519 | Default_Value = +%v %i 520 | 521 | 522 | [messagePetOutgoingShieldGot] 523 | title = Shielded 524 | Parameter_Description_v = Amount of shield 525 | Parameter_Description_n = Name of the skills target 526 | Parameter_Description_s = Skillname 527 | Parameter_Description_p = Name of the pet/clone shielding 528 | Parameter_Description_i = Skillicon 529 | Default_Value = +%v %i 530 | 531 | 532 | [messagePetOutgoingShield] 533 | title = 屏障 534 | Parameter_Description_v = 伤害量 535 | Parameter_Description_n = 技能目标的名称 536 | Parameter_Description_s = 技能名称 537 | Parameter_Description_p = 宠物/分身攻击的名称 538 | Parameter_Description_i = 技能图标 539 | Default_Value = %v -=吸收=- %i 540 | 541 | 542 | [messagePetOutgoingBlock] 543 | title = 阻挡 544 | Parameter_Description_n = 技能目标的名称 545 | Parameter_Description_s = 技能名称 546 | Parameter_Description_p = 宠物/分身攻击的名称 547 | Parameter_Description_i = 技能图标 548 | Default_Value = 阻挡! %i 549 | 550 | 551 | [messagePetOutgoingEvade] 552 | title = 闪避 553 | Parameter_Description_n = 技能目标的名称 554 | Parameter_Description_s = 技能名称 555 | Parameter_Description_p = 宠物/分身攻击的名称 556 | Parameter_Description_i = 技能图标 557 | Default_Value = 闪避! %i 558 | 559 | 560 | [messagePetOutgoingInvulnerable] 561 | title = 无敌 562 | Parameter_Description_n = 技能目标的名称 563 | Parameter_Description_s = 技能名称 564 | Parameter_Description_p = 宠物/分身攻击的名称 565 | Parameter_Description_i = 技能图标 566 | Default_Value = 无敌! %i 567 | 568 | 569 | [messagePetOutgoingMiss] 570 | title = 未击中 571 | Parameter_Description_n = 技能目标的名称 572 | Parameter_Description_s = 技能名称 573 | Parameter_Description_p = 宠物/分身攻击的名称 574 | Parameter_Description_i = 技能图标 575 | Default_Value = 未击中! %i 576 | 577 | 578 | [messagePetIncomingPhysical] 579 | title = 物理命中 580 | Parameter_Description_v = 伤害量 581 | Parameter_Description_n = 攻击者的名字 582 | Parameter_Description_s = 技能名称 583 | Parameter_Description_p = 宠物/分身攻击的名称 584 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 585 | Parameter_Description_i = 技能图标 586 | Default_Value = %i 宠物 -%v 587 | 588 | 589 | [messagePetIncomingCrit] 590 | title = 物理暴击 591 | Parameter_Description_v = 伤害量 592 | Parameter_Description_n = 攻击者的名字 593 | Parameter_Description_s = 技能名称 594 | Parameter_Description_p = 宠物/分身攻击的名称 595 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 596 | Parameter_Description_i = 技能图标 597 | Default_Value = %i 宠物 -%v 598 | 599 | 600 | [messagePetIncomingBleeding] 601 | title = 流血伤害 602 | Parameter_Description_v = 伤害量 603 | Parameter_Description_n = 攻击者的名字 604 | Parameter_Description_s = 技能名称 605 | Parameter_Description_p = 宠物/分身攻击的名称 606 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 607 | Parameter_Description_i = 技能图标 608 | Default_Value = %i 宠物 -%v 609 | 610 | 611 | [messagePetIncomingBurning] 612 | title = 燃烧伤害 613 | Parameter_Description_v = 伤害量 614 | Parameter_Description_n = 攻击者的名字 615 | Parameter_Description_s = 技能名称 616 | Parameter_Description_p = 宠物/分身攻击的名称 617 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 618 | Parameter_Description_i = 技能图标 619 | Default_Value = %i 宠物 -%v 620 | 621 | 622 | [messagePetIncomingPoison] 623 | title = 中毒伤害 624 | Parameter_Description_v = 伤害量 625 | Parameter_Description_n = 攻击者的名字 626 | Parameter_Description_s = 技能名称 627 | Parameter_Description_p = 宠物/分身攻击的名称 628 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 629 | Parameter_Description_i = 技能图标 630 | Default_Value = %i 宠物 -%v 631 | 632 | 633 | [messagePetIncomingConfusion] 634 | title = 困惑伤害 635 | Parameter_Description_v = 伤害量 636 | Parameter_Description_n = 攻击者的名字 637 | Parameter_Description_s = 技能名称 638 | Parameter_Description_p = 宠物/分身攻击的名称 639 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 640 | Parameter_Description_i = 技能图标 641 | Default_Value = %i 宠物 -%v 642 | 643 | 644 | [messagePetIncomingRetaliation] 645 | title = 反弹伤害 646 | Parameter_Description_v = 伤害量 647 | Parameter_Description_n = 攻击者的名字 648 | Parameter_Description_s = 技能名称 649 | Parameter_Description_p = 宠物/分身攻击的名称 650 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 651 | Parameter_Description_i = 技能图标 652 | Default_Value = %i 宠物 -%v 653 | 654 | 655 | [messagePetIncomingTorment] 656 | title = 折磨伤害 657 | Parameter_Description_v = 伤害量 658 | Parameter_Description_n = 攻击者的名字 659 | Parameter_Description_s = 技能名称 660 | Parameter_Description_p = 宠物/分身攻击的名称 661 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 662 | Parameter_Description_i = 技能图标 663 | Default_Value = %i 宠物 -%v 664 | 665 | 666 | [messagePetIncomingDoT] 667 | title = 其他症状伤害 668 | Parameter_Description_v = 伤害量 669 | Parameter_Description_n = 攻击者的名字 670 | Parameter_Description_s = 技能名称 671 | Parameter_Description_p = 宠物/分身攻击的名称 672 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 673 | Parameter_Description_i = 技能图标 674 | Default_Value = %i 宠物 -%v 675 | 676 | 677 | [messagePetIncomingHeal] 678 | title = 治疗 679 | Parameter_Description_v = 伤害量 680 | Parameter_Description_n = 治疗源的名称 681 | Parameter_Description_s = 技能名称 682 | Parameter_Description_p = 宠物/克隆治疗的名称 683 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 684 | Parameter_Description_i = 技能图标 685 | Default_Value = %i Pet +%v 686 | 687 | 688 | [messagePetIncomingHoT] 689 | title = 再生 690 | Parameter_Description_v = 伤害量 691 | Parameter_Description_n = 治疗源的名称 692 | Parameter_Description_s = 技能名称 693 | Parameter_Description_p = 宠物/克隆治疗的名称 694 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 695 | Parameter_Description_i = 技能图标 696 | Default_Value = %i 宠物 +%v 697 | 698 | 699 | [messagePetIncomingShieldGot] 700 | title = Shielded 701 | Parameter_Description_v = Amount of shield 702 | Parameter_Description_n = Name of the shield source 703 | Parameter_Description_s = Skillname 704 | Parameter_Description_p = Name of the pet/clone attacking 705 | Parameter_Description_c = Color for the skills source profession (use: [col=%c]...[/col]) 706 | Parameter_Description_i = Skillicon 707 | Default_Value = %i Pet +%v 708 | 709 | 710 | [messagePetIncomingShield] 711 | title = 屏障 712 | Parameter_Description_v = 伤害量 713 | Parameter_Description_n = 攻击者的名字 714 | Parameter_Description_s = 技能名称 715 | Parameter_Description_p = 宠物/分身攻击的名称 716 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 717 | Parameter_Description_i = 技能图标 718 | Default_Value = %i 宠物 %v -=吸收=- 719 | 720 | 721 | [messagePetIncomingBlock] 722 | title = 阻挡 723 | Parameter_Description_n = 攻击者的名字 724 | Parameter_Description_s = 技能名称 725 | Parameter_Description_p = 宠物/分身攻击的名称 726 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 727 | Parameter_Description_i = 技能图标 728 | Default_Value = %i 宠物 阻挡! 729 | 730 | 731 | [messagePetIncomingEvade] 732 | title = 闪避 733 | Parameter_Description_n = 攻击者的名字 734 | Parameter_Description_s = 技能名称 735 | Parameter_Description_p = 宠物/分身攻击的名称 736 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 737 | Parameter_Description_i = 技能图标 738 | Default_Value = %i 宠物 闪避! 739 | 740 | 741 | [messagePetIncomingInvulnerable] 742 | title = 无敌 743 | Parameter_Description_n = 攻击者的名字 744 | Parameter_Description_s = 技能名称 745 | Parameter_Description_p = 宠物/分身攻击的名称 746 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 747 | Parameter_Description_i = 技能图标 748 | Default_Value = %i 宠物 无敌! 749 | 750 | 751 | [messagePetIncomingMiss] 752 | title = 未命中 753 | Parameter_Description_n = 攻击者的名字 754 | Parameter_Description_s = 技能名称 755 | Parameter_Description_p = 宠物/分身攻击的名称 756 | Parameter_Description_c = 职业技能的颜色 (用: [col=%c]...[/col]) 757 | Parameter_Description_i = 技能图标 758 | Default_Value = %i 宠物 未命中! 759 | -------------------------------------------------------------------------------- /src/Common.cpp: -------------------------------------------------------------------------------- 1 | #include "Common.h" 2 | #include "Language.h" 3 | #include 4 | 5 | #ifdef _DEBUG 6 | HANDLE debug_console_hnd; 7 | #endif 8 | 9 | uint32_t GW2_SCT::d3dversion; 10 | ID3D11Device* GW2_SCT::d3Device11; 11 | ID3D11DeviceContext* GW2_SCT::d3D11Context; 12 | IDXGISwapChain* GW2_SCT::d3d11SwapChain; 13 | std::ofstream logFile; 14 | char* arcvers; 15 | bool ensuredSCTDirectoryExists = false; 16 | 17 | std::map> fontMap; 18 | GW2_SCT::FontType* defaultFont; 19 | 20 | std::string getExePath() { 21 | char buffer[MAX_PATH]; 22 | GetModuleFileName(NULL, buffer, MAX_PATH); 23 | std::string::size_type pos = std::string(buffer).find_last_of("\\/"); 24 | return std::string(buffer).substr(0, pos); 25 | } 26 | 27 | std::string getSCTPath() { 28 | std::string addonPath = getExePath() + "\\addons\\sct\\"; 29 | if (!ensuredSCTDirectoryExists) { 30 | std::string addonPath = getExePath() + "\\addons\\"; 31 | CreateDirectory(addonPath.c_str(), NULL); 32 | addonPath += "sct\\"; 33 | CreateDirectory(addonPath.c_str(), NULL); 34 | ensuredSCTDirectoryExists = true; 35 | LOG("Created SCT addon dir."); 36 | } 37 | return addonPath; 38 | } 39 | 40 | bool file_exist(const std::string& name) { 41 | struct stat buffer; 42 | return (stat(name.c_str(), &buffer) == 0); 43 | } 44 | 45 | bool getFilesInDirectory(std::string path, std::vector& files) { 46 | HANDLE hFind = INVALID_HANDLE_VALUE; 47 | WIN32_FIND_DATA ffd; 48 | std::string spec; 49 | 50 | spec = path + "*"; 51 | 52 | hFind = FindFirstFile(spec.c_str(), &ffd); 53 | if (hFind == INVALID_HANDLE_VALUE) { 54 | return false; 55 | } 56 | 57 | do { 58 | if (strcmp(ffd.cFileName, ".") != 0 && 59 | strcmp(ffd.cFileName, "..") != 0) { 60 | files.push_back(ffd.cFileName); 61 | } 62 | } while (FindNextFile(hFind, &ffd) != 0); 63 | 64 | if (GetLastError() != ERROR_NO_MORE_FILES) { 65 | FindClose(hFind); 66 | return false; 67 | } 68 | 69 | FindClose(hFind); 70 | hFind = INVALID_HANDLE_VALUE; 71 | 72 | return true; 73 | } 74 | 75 | ImU32 stoc(std::string s) { 76 | int num = stoi(s, 0, 16); 77 | int r = num / 0x10000; 78 | int g = (num / 0x100) % 0x100; 79 | int b = num % 0x100; 80 | return ImGui::GetColorU32(ImVec4(r / 255.f, g / 255.f, b / 255.f, 1.f)); 81 | } 82 | 83 | GW2_SCT::FontType* getFontType(int fontId, bool withMaster) { 84 | GW2_SCT::FontType* font = defaultFont; 85 | // -1 because of ID 0 being master font 86 | if (fontMap.find(fontId - (withMaster ? 1 : 0)) != fontMap.end()) { 87 | font = fontMap[fontId - (withMaster ? 1 : 0)].second; 88 | } 89 | return font; 90 | } 91 | 92 | std::string fotos(FontId i, bool withMaster) { 93 | if (fontMap.find(i - (withMaster ? 1 : 0)) != fontMap.end()) { 94 | return fontMap[i - (withMaster ? 1 : 0)].first; 95 | } 96 | return withMaster ? langStringG(GW2_SCT::LanguageKey::Font_Master) : langStringG(GW2_SCT::LanguageKey::Font_Default); 97 | } 98 | 99 | FontId stofo(std::string const& s, bool withMaster) { 100 | for (std::map>::iterator it = fontMap.begin(); it != fontMap.end(); ++it) { 101 | if (it->second.first.compare(s) == 0) { 102 | return it->first + (withMaster ? 1 : 0); 103 | } 104 | } 105 | return 0; 106 | } 107 | 108 | int GW2_SCT::messageCategoryToInt(MessageCategory category) { 109 | return (int)category; 110 | } 111 | 112 | GW2_SCT::MessageCategory GW2_SCT::intToMessageCategory(int i) { 113 | if (i < 0 || i > NUM_CATEGORIES) return (MessageCategory)0; 114 | return (MessageCategory)i; 115 | } 116 | 117 | int GW2_SCT::messageTypeToInt(MessageType type) { 118 | return (int)type; 119 | } 120 | 121 | GW2_SCT::MessageType GW2_SCT::intToMessageType(int i) { 122 | if (i < 0 || i > NUM_MESSAGE_TYPES) return (MessageType)0; 123 | return (MessageType)i; 124 | } 125 | 126 | bool floatEqual(float a, float b) { 127 | float diff = a - b; 128 | return (diff < std::numeric_limits::epsilon()) && (-diff < std::numeric_limits::epsilon()); 129 | } 130 | -------------------------------------------------------------------------------- /src/ExampleMessageOptions.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "ExampleMessageOptions.h" 3 | #include "imgui.h" 4 | #include "imgui_stdlib.h" 5 | #include "imgui_sct_widgets.h" 6 | #include "Options.h" 7 | #include "Language.h" 8 | 9 | bool GW2_SCT::ExampleMessageOptions::windowIsOpen = false; 10 | GW2_SCT::ExampleMessageOptions::State GW2_SCT::ExampleMessageOptions::state = GW2_SCT::ExampleMessageOptions::State::READY_TO_RECORD; 11 | std::chrono::system_clock::time_point GW2_SCT::ExampleMessageOptions::recordingStart = std::chrono::system_clock::now(); 12 | GW2_SCT::SCTMain* GW2_SCT::ExampleMessageOptions::main = nullptr; 13 | std::thread* GW2_SCT::ExampleMessageOptions::emitterThread = nullptr; 14 | 15 | #ifdef _DEBUG 16 | using namespace std::chrono_literals; 17 | cbtevent1 minimalEvent(int32_t value, int32_t buff_dmg, uint32_t overstack_value, uint32_t skillid) { 18 | cbtevent1 ret; 19 | ret.value = value; 20 | ret.overstack_value = buff_dmg; 21 | ret.buff_dmg = overstack_value; 22 | ret.skillid = skillid; 23 | return ret; 24 | } 25 | ag agMe { "Me", 1, profession::PROFESSION_ELEMENTALIST, 0, 1 }; 26 | ag agFoe { "Foe", 2, profession::PROFESSION_ENGINEER, 0, 0 }; 27 | ag agFriend { "Friend", 3, profession::PROFESSION_GUARDIAN, 0, 0 }; 28 | ag agPet { "Pet", 4, profession::PROFESSION_UNDEFINED, 0, 0 }; 29 | #endif // _DEBUG 30 | 31 | 32 | std::multimap GW2_SCT::ExampleMessageOptions::messagesToEmmit = { 33 | #ifdef _DEBUG 34 | { std::chrono::system_clock::duration(0ms), { GW2_SCT::MessageCategory::PLAYER_OUT, GW2_SCT::MessageType::PHYSICAL, std::make_shared(-1234, 0, 0, 5489, &agMe, &agFoe, "Lightning Whip")}}, 35 | { std::chrono::system_clock::duration(100ms), { GW2_SCT::MessageCategory::PLAYER_OUT, GW2_SCT::MessageType::CRIT, std::make_shared(-1855, 0, 0, 5489, &agMe, &agFoe, "Lightning Whip")}}, 36 | { std::chrono::system_clock::duration(0ms), { GW2_SCT::MessageCategory::PLAYER_IN, GW2_SCT::MessageType::PHYSICAL, std::make_shared(-123, 0, 0, 5827, &agFoe, &agMe, "Fragmentation Shot")}}, 37 | { std::chrono::system_clock::duration(250ms), { GW2_SCT::MessageCategory::PLAYER_IN, GW2_SCT::MessageType::BLEEDING, std::make_shared(0, -789, 0, 736, &agFoe, &agMe, "Fragmentation Shot")}}, 38 | { std::chrono::system_clock::duration(500ms), { GW2_SCT::MessageCategory::PLAYER_IN, GW2_SCT::MessageType::BLEEDING, std::make_shared(0, -789, 0, 736, &agFoe, &agMe, "Fragmentation Shot")}}, 39 | { std::chrono::system_clock::duration(750ms), { GW2_SCT::MessageCategory::PLAYER_IN, GW2_SCT::MessageType::BLEEDING, std::make_shared(0, -456, 0, 736, &agFoe, &agMe, "Fragmentation Shot")}}, 40 | { std::chrono::system_clock::duration(1000ms), { GW2_SCT::MessageCategory::PLAYER_IN, GW2_SCT::MessageType::BLEEDING, std::make_shared(0, -34, 0, 736, &agFoe, &agMe, "Fragmentation Shot")}} 41 | #endif // _DEBUG 42 | }; 43 | 44 | void GW2_SCT::ExampleMessageOptions::open() { 45 | windowIsOpen = true; 46 | } 47 | 48 | void GW2_SCT::ExampleMessageOptions::paint() { 49 | if (windowIsOpen) { 50 | ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.60f, 0.60f, 0.60f, 0.30f)); 51 | ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 8)); 52 | ImGui::Begin(langString(LanguageCategory::Example_Message_UI, LanguageKey::Title), &windowIsOpen, ImGuiWindowFlags_NoCollapse); 53 | 54 | const float square_size = ImGui::GetFontSize(); 55 | ImGuiStyle style = ImGui::GetStyle(); 56 | 57 | State initialState = state; 58 | 59 | if (initialState != State::READY_TO_RECORD) ImGui::BeginDisabled(); 60 | if (ImGui::Button(ImGui::BuildVisibleLabel(langString(LanguageCategory::Example_Message_UI, LanguageKey::Start_Recording), "start-recording-button").c_str())) { 61 | state = State::RECORDING; 62 | recordingStart = std::chrono::system_clock::now(); 63 | } 64 | if (initialState != State::READY_TO_RECORD) ImGui::EndDisabled(); 65 | 66 | ImGui::SameLine(); 67 | 68 | if (initialState != State::RECORDING) ImGui::BeginDisabled(); 69 | if (ImGui::Button(ImGui::BuildVisibleLabel(langString(LanguageCategory::Example_Message_UI, LanguageKey::Stop_Recording), "stop-recording-button").c_str())) { 70 | state = State::READY_TO_RECORD; 71 | } 72 | if (initialState != State::RECORDING) ImGui::EndDisabled(); 73 | 74 | ImGui::BeginChild("messagesToEmitPane", ImVec2(ImGui::GetWindowWidth(), -ImGui::GetFrameHeightWithSpacing()), true); 75 | int i = 0; 76 | static MessageInformation* currentlyEditing = nullptr; 77 | for (auto messageToEmmit = messagesToEmmit.begin(); messageToEmmit != messagesToEmmit.end(); messageToEmmit++) { 78 | std::string s = std::to_string(std::chrono::duration_cast(messageToEmmit->first).count()) + "." + std::to_string(std::chrono::duration_cast(messageToEmmit->first).count() % 1000 / 10) + "s"; 79 | s += " - " + categoryNames.at(messageToEmmit->second.category); 80 | s += " | " + typeNames.at(messageToEmmit->second.type) + " - "; 81 | if (messageToEmmit->second.data->entityName != nullptr) { 82 | s += " " + std::string(messageToEmmit->second.data->entityName); 83 | } 84 | if (messageToEmmit->second.data->otherEntityName != nullptr) { 85 | s += " -> " + std::string(messageToEmmit->second.data->otherEntityName); 86 | } 87 | if (messageToEmmit->second.data->skillName != nullptr) { 88 | s += " (" + std::string(messageToEmmit->second.data->skillName) + ")"; 89 | } 90 | ImGui::SetCursorPosY(ImGui::GetCursorPosY() + style.FramePadding.y); 91 | ImGui::Text(s.c_str()); 92 | ImGui::SameLineEnd(square_size + style.FramePadding.y + style.WindowPadding.x * 2); 93 | ImGui::SetCursorPosY(ImGui::GetCursorPosY() - style.FramePadding.y); 94 | ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.67f, 0.40f, 0.40f, 0.60f)); 95 | if (initialState != State::EMITTING) ImGui::BeginDisabled(); 96 | if (ImGui::Button(ImGui::BuildLabel("-", "delete-example-message", i).c_str(), ImVec2(square_size + style.FramePadding.y * 2, square_size + style.FramePadding.y * 2))) { 97 | messageToEmmit = messagesToEmmit.erase(messageToEmmit); 98 | } 99 | if (initialState != State::EMITTING) ImGui::EndDisabled(); 100 | ImGui::PopStyleColor(); 101 | i++; 102 | } 103 | ImGui::EndChild(); 104 | 105 | if (initialState == State::EMITTING) ImGui::BeginDisabled(); 106 | if (ImGui::Button(ImGui::BuildVisibleLabel(langString(LanguageCategory::Example_Message_UI, LanguageKey::Start_Emitting), "start-emitting-button").c_str())) { 107 | state = State::EMITTING; 108 | emitterThread = new std::thread(ExampleMessageOptions::emitMessages); 109 | } 110 | if (initialState == State::EMITTING) ImGui::EndDisabled(); 111 | 112 | ImGui::SameLine(); 113 | 114 | if (initialState != State::EMITTING) ImGui::BeginDisabled(); 115 | if (ImGui::Button(ImGui::BuildVisibleLabel(langString(LanguageCategory::Example_Message_UI, LanguageKey::Stop_Emitting), "stop-emitting-button").c_str())) { 116 | state = State::READY_TO_RECORD; 117 | emitterThread->join(); 118 | delete emitterThread; 119 | } 120 | if (initialState != State::EMITTING) ImGui::EndDisabled(); 121 | 122 | ImGui::SameLine(); 123 | 124 | bool messagesEmpty = messagesToEmmit.empty(); 125 | if (messagesEmpty) ImGui::BeginDisabled(); 126 | if (ImGui::Button(ImGui::BuildVisibleLabel(langString(LanguageCategory::Example_Message_UI, LanguageKey::Clear_Recorded_Messages), "Clear all").c_str())) { 127 | messagesToEmmit.clear(); 128 | } 129 | if (messagesEmpty) ImGui::EndDisabled(); 130 | 131 | ImGui::End(); 132 | ImGui::PopStyleVar(); 133 | ImGui::PopStyleColor(); 134 | } else if (state == State::RECORDING) { 135 | state = State::READY_TO_RECORD; 136 | } 137 | } 138 | 139 | void GW2_SCT::ExampleMessageOptions::receiveMessage(std::shared_ptr m) { 140 | if (state == State::RECORDING) { 141 | auto messageDataCopy = m->getCopyOfFirstData(); 142 | if (!messageDataCopy->hasToBeFiltered) { 143 | messagesToEmmit.insert(std::pair(m->getTimepoint() - recordingStart, { m->getCategory(), m->getType(), messageDataCopy })); 144 | } 145 | } 146 | } 147 | 148 | void GW2_SCT::ExampleMessageOptions::emitMessages() { 149 | auto start = std::chrono::system_clock::now(); 150 | for (auto it = messagesToEmmit.begin(); state == State::EMITTING && it != messagesToEmmit.end(); it++) { 151 | while (it->first > std::chrono::system_clock::now() - start) { 152 | std::this_thread::sleep_for(std::chrono::milliseconds(50)); 153 | if (state != State::EMITTING) return; 154 | } 155 | if (main != nullptr) { 156 | main->sendMessageToEmission(std::make_shared(it->second.category, it->second.type, it->second.data)); 157 | } 158 | } 159 | state = State::READY_TO_RECORD; 160 | } 161 | -------------------------------------------------------------------------------- /src/OptionsStructures.cpp: -------------------------------------------------------------------------------- 1 | #include "OptionsStructures.h" 2 | 3 | namespace nlohmann { 4 | template 5 | struct adl_serializer> { 6 | static void to_json(json& j, const std::shared_ptr& opt) { 7 | if (opt.get()) { 8 | j = *opt; 9 | } 10 | else { 11 | j = nullptr; 12 | } 13 | } 14 | static void from_json(const json& j, std::shared_ptr& opt) { 15 | if (j.is_null()) { 16 | opt = nullptr; 17 | } else { 18 | opt = std::make_shared(j.get()); 19 | } 20 | } 21 | }; 22 | } 23 | 24 | inline int GW2_SCT::textAlignToInt(GW2_SCT::TextAlign type) { 25 | return (int)type; 26 | } 27 | 28 | inline GW2_SCT::TextAlign GW2_SCT::intToTextAlign(int i) { 29 | if (i < 0 || i > 1) return (GW2_SCT::TextAlign)0; 30 | return (GW2_SCT::TextAlign)i; 31 | } 32 | 33 | inline int GW2_SCT::textCurveToInt(GW2_SCT::TextCurve type) { 34 | return (int)type; 35 | } 36 | 37 | inline GW2_SCT::TextCurve GW2_SCT::intToTextCurve(int i) { 38 | if (i < 0 || i > 1) return (GW2_SCT::TextCurve)0; 39 | return (GW2_SCT::TextCurve)i; 40 | } 41 | 42 | inline int GW2_SCT::filterTypeToInt(GW2_SCT::FilterType type) { 43 | return (int)type; 44 | } 45 | 46 | inline GW2_SCT::FilterType GW2_SCT::intToFilterType(int i) { 47 | if (i < 0 || i > 1) return (GW2_SCT::FilterType)0; 48 | return (GW2_SCT::FilterType)i; 49 | } 50 | 51 | inline int GW2_SCT::skillIconDisplayTypeToInt(GW2_SCT::SkillIconDisplayType type) { 52 | return (int)type; 53 | } 54 | 55 | inline GW2_SCT::SkillIconDisplayType GW2_SCT::intSkillIconDisplayType(int i) { 56 | if (i < 0 || i > 3) return (GW2_SCT::SkillIconDisplayType)0; 57 | return (GW2_SCT::SkillIconDisplayType)i; 58 | } 59 | 60 | void GW2_SCT::to_json(nlohmann::json& j, const options_struct& p) { 61 | j = nlohmann::json{ 62 | {"revision", p.revision}, 63 | {"globalProfile", p.globalProfile}, 64 | {"profiles", p.profiles}, 65 | {"characterProfileMap", p.characterProfileMap} 66 | }; 67 | } 68 | 69 | void GW2_SCT::from_json(const nlohmann::json& j, options_struct& p) { 70 | j.at("revision").get_to(p.revision); 71 | j.at("globalProfile").get_to(p.globalProfile); 72 | j.at("profiles").get_to(p.profiles); 73 | j.at("characterProfileMap").get_to(p.characterProfileMap); 74 | } 75 | 76 | void GW2_SCT::to_json(nlohmann::json& j, const profile_options_struct& p) { 77 | j = nlohmann::json{ 78 | {"sctEnabled", p.sctEnabled}, 79 | {"scrollingSpeed", p.scrollSpeed}, 80 | {"dropShadow", p.dropShadow}, 81 | {"maximalMessagesInStack", p.messagesInStack}, 82 | {"combineAllMessages", p.combineAllMessages}, 83 | {"masterFont", fotos(p.masterFont, false)}, 84 | {"defaultFontSize", p.defaultFontSize}, 85 | {"defaultCritFontSize", p.defaultCritFontSize}, 86 | {"selfMessageOnlyIncoming", p.selfMessageOnlyIncoming}, 87 | {"outgoingOnlyToTarget", p.outgoingOnlyToTarget}, 88 | {"professionColorGuardian", p.professionColorGuardian}, 89 | {"professionColorWarrior", p.professionColorWarrior}, 90 | {"professionColorEngineer", p.professionColorEngineer}, 91 | {"professionColorRanger", p.professionColorRanger}, 92 | {"professionColorThief", p.professionColorThief}, 93 | {"professionColorElementalist", p.professionColorElementalist}, 94 | {"professionColorMesmer", p.professionColorMesmer}, 95 | {"professionColorNecromancer", p.professionColorNecromancer}, 96 | {"professionColorRevenant", p.professionColorRevenant}, 97 | {"professionColorDefault", p.professionColorDefault}, 98 | {"scrollAreas", p.scrollAreaOptions}, 99 | {"filteredIDs", p.skillFilters}, 100 | {"skillIconsEnabled", p.skillIconsEnabled}, 101 | {"skillIconsPreload", p.preloadAllSkillIcons}, 102 | {"skillIconsDisplayType", p.skillIconsDisplayType} 103 | }; 104 | } 105 | 106 | void GW2_SCT::from_json(const nlohmann::json& j, profile_options_struct& p) { 107 | j.at("sctEnabled").get_to(p.sctEnabled); 108 | j.at("scrollingSpeed").get_to(p.scrollSpeed); 109 | j.at("dropShadow").get_to(p.dropShadow); 110 | j.at("maximalMessagesInStack").get_to(p.messagesInStack); 111 | j.at("combineAllMessages").get_to(p.combineAllMessages); 112 | p.masterFont = stofo(j.at("masterFont").get(), false); 113 | j.at("defaultFontSize").get_to(p.defaultFontSize); 114 | j.at("defaultCritFontSize").get_to(p.defaultCritFontSize); 115 | j.at("selfMessageOnlyIncoming").get_to(p.selfMessageOnlyIncoming); 116 | j.at("outgoingOnlyToTarget").get_to(p.outgoingOnlyToTarget); 117 | j.at("professionColorGuardian").get_to(p.professionColorGuardian); 118 | j.at("professionColorWarrior").get_to(p.professionColorWarrior); 119 | j.at("professionColorEngineer").get_to(p.professionColorEngineer); 120 | j.at("professionColorRanger").get_to(p.professionColorRanger); 121 | j.at("professionColorThief").get_to(p.professionColorThief); 122 | j.at("professionColorElementalist").get_to(p.professionColorElementalist); 123 | j.at("professionColorMesmer").get_to(p.professionColorMesmer); 124 | j.at("professionColorNecromancer").get_to(p.professionColorNecromancer); 125 | j.at("professionColorRevenant").get_to(p.professionColorRevenant); 126 | j.at("professionColorDefault").get_to(p.professionColorDefault); 127 | j.at("scrollAreas").get_to(p.scrollAreaOptions); 128 | j.at("filteredIDs").get_to(p.skillFilters); 129 | j.at("skillIconsEnabled").get_to(p.skillIconsEnabled); 130 | j.at("skillIconsPreload").get_to(p.preloadAllSkillIcons); 131 | j.at("skillIconsDisplayType").get_to(p.skillIconsDisplayType); 132 | } 133 | 134 | void GW2_SCT::to_json(nlohmann::json& j, const scroll_area_options_struct& p) { 135 | j = nlohmann::json{ 136 | {"name", p.name}, 137 | {"horrizontalOffset", p.offsetX}, 138 | {"verticalOffset", p.offsetY}, 139 | {"width", p.width}, 140 | {"height", p.height}, 141 | {"textAlign", p.textAlign}, 142 | {"textFlow", p.textCurve}, 143 | {"messageReceivers", p.receivers} 144 | }; 145 | } 146 | 147 | void GW2_SCT::from_json(const nlohmann::json& j, scroll_area_options_struct& p) { 148 | j.at("name").get_to(p.name); 149 | j.at("horrizontalOffset").get_to(p.offsetX); 150 | j.at("verticalOffset").get_to(p.offsetY); 151 | j.at("width").get_to(p.width); 152 | j.at("height").get_to(p.height); 153 | j.at("textAlign").get_to(p.textAlign); 154 | j.at("textFlow").get_to(p.textCurve); 155 | j.at("messageReceivers").get_to(p.receivers); 156 | } 157 | 158 | void GW2_SCT::to_json(nlohmann::json& j, const message_receiver_options_struct& p) { 159 | j = nlohmann::json{ 160 | {"name", p.name}, 161 | {"category", p.category}, 162 | {"type", p.type}, 163 | {"enabled", p.enabled}, 164 | {"outputTemplate", p.outputTemplate}, 165 | {"color", p.color}, 166 | {"font", fotos(p.font, true)}, 167 | {"fontSize", p.fontSize} 168 | }; 169 | } 170 | 171 | void GW2_SCT::from_json(const nlohmann::json& j, message_receiver_options_struct& p) { 172 | j.at("name").get_to(p.name); 173 | j.at("category").get_to(p.category); 174 | j.at("type").get_to(p.type); 175 | j.at("enabled").get_to(p.enabled); 176 | j.at("outputTemplate").get_to(p.outputTemplate); 177 | j.at("color").get_to(p.color); 178 | p.font = stofo(j.at("font").get(), true); 179 | j.at("fontSize").get_to(p.fontSize); 180 | } 181 | 182 | void GW2_SCT::to_json(nlohmann::json& j, const filter_options_struct& p) { 183 | j = nlohmann::json{ 184 | {"type", p.type}, 185 | {"id", p.skillId}, 186 | {"name", p.skillName} 187 | }; 188 | } 189 | 190 | void GW2_SCT::from_json(const nlohmann::json& j, filter_options_struct& p) { 191 | j.at("type").get_to(p.type); 192 | j.at("id").get_to(p.skillId); 193 | j.at("name").get_to(p.skillName); 194 | } -------------------------------------------------------------------------------- /src/SCTMain.cpp: -------------------------------------------------------------------------------- 1 | #include "SCTMain.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "imgui.h" 7 | #include "Options.h" 8 | #include "SkillIconManager.h" 9 | #include "FontManager.h" 10 | #include "Language.h" 11 | #include "ExampleMessageOptions.h" 12 | #include 13 | 14 | #if _DEBUG 15 | long uiFrames = 0; 16 | float uiTime = 0; 17 | #endif 18 | 19 | float windowWidth; 20 | float windowHeight; 21 | 22 | GW2_SCT::SCTMain::SCTMain() {} 23 | 24 | GW2_SCT::SCTMain::~SCTMain() { 25 | this->Release(); 26 | Options::save(); 27 | FontManager::cleanup(); 28 | } 29 | 30 | arcdps_exports* GW2_SCT::SCTMain::Init(char* arcvers, void* mod_wnd, void* mod_combat, void* mod_imgui, void* mod_options, void* mod_combat_local) { 31 | logFile = std::ofstream(getSCTPath() + "sct.log"); 32 | LOG("Running arcvers: ", arcvers); 33 | LOG("Running sct version: ", SCT_VERSION_STRING, " / ", __DATE__, " / ", __TIME__); 34 | 35 | Options::profile.onAssign([=](std::shared_ptr oldProfile, std::shared_ptr newProfile) { 36 | if (currentScrollAreaPushBackCallbackId >= 0) { 37 | oldProfile->scrollAreaOptions.removeOnPushBackCallback(currentScrollAreaPushBackCallbackId); 38 | } 39 | if (currentScrollAreaEraseCallbackId >= 0) { 40 | oldProfile->scrollAreaOptions.removeOnEraseCallback(currentScrollAreaEraseCallbackId); 41 | } 42 | currentScrollAreaPushBackCallbackId = newProfile->scrollAreaOptions.addOnPushBackCallback([=](const std::shared_ptr& newVal) { 43 | scrollAreas.push_back(std::make_shared(newVal)); 44 | }); 45 | currentScrollAreaEraseCallbackId = newProfile->scrollAreaOptions.addOnEraseCallback([=](int pos) { 46 | scrollAreas.erase(std::begin(scrollAreas) + pos); 47 | }); 48 | }); 49 | LOG("Set up options changing hook"); 50 | SkillIconManager::init(); 51 | LOG("Started skill icon manager"); 52 | FontManager::init(); 53 | LOG("Started font manager"); 54 | Options::load(); 55 | LOG("Loaded options"); 56 | for (const auto& scrollAreaOptions : Options::get()->scrollAreaOptions) 57 | scrollAreas.push_back(std::make_shared(scrollAreaOptions)); 58 | LOG("Created ", Options::get()->scrollAreaOptions.size(), " scroll areas"); 59 | 60 | if (d3Device11 != nullptr) { 61 | if (d3d11SwapChain != nullptr) { 62 | DXGI_SWAP_CHAIN_DESC desc; 63 | if (SUCCEEDED(d3d11SwapChain->GetDesc(&desc))) { 64 | RECT rect; 65 | if (GetWindowRect(desc.OutputWindow, &rect)) { 66 | windowWidth = rect.right - rect.left; 67 | windowHeight = rect.bottom - rect.top; 68 | } 69 | } 70 | } 71 | LOG("Found d3d11 device."); 72 | if (d3D11Context != nullptr) { 73 | LOG("Found d3d11 context aswell."); 74 | } else { 75 | LOG("But found no d3d11 context."); 76 | } 77 | } else { 78 | LOG("Found no d3 device (version: ", d3dversion, ")!"); 79 | } 80 | 81 | std::string remapJsonFilename = getSCTPath() + "remap.json"; 82 | if (file_exist(remapJsonFilename)) { 83 | LOG("Loading remap.json"); 84 | std::string line, text; 85 | std::ifstream in(remapJsonFilename); 86 | while (std::getline(in, line)) { 87 | text += line + "\n"; 88 | } 89 | in.close(); 90 | try { 91 | std::map remapJsonValues = nlohmann::json::parse(text); 92 | for (const auto& entry : remapJsonValues) { 93 | uint32_t from = stoi(std::string(entry.first)); 94 | uint32_t to = stoi(std::string(entry.second)); 95 | while (skillRemaps.find(to) != skillRemaps.end()) { 96 | if (from == to) break; 97 | to = skillRemaps[to]; 98 | } 99 | if (from == to) break; 100 | LOG("Remapping id ", from, " to id ", to); 101 | for (auto currentRemap : skillRemaps) { 102 | if (currentRemap.second == from) { 103 | skillRemaps[currentRemap.first] = to; 104 | LOG("Touched remap from id ", currentRemap.first, " to id ", to); 105 | } 106 | } 107 | skillRemaps[from] = to; 108 | } 109 | LOG("Loaded remap.json successfully: ", skillRemaps.size(), " remapped skills") 110 | } 111 | catch (std::exception& e) { 112 | LOG("Error parsing remap.json"); 113 | } 114 | } else { 115 | LOG("No remap.json file loaded"); 116 | } 117 | 118 | ExampleMessageOptions::setMain(this); 119 | 120 | /* for arcdps */ 121 | memset(&arc_exports, 0, sizeof(arcdps_exports)); 122 | arc_exports.size = sizeof(arcdps_exports); 123 | arc_exports.sig = 0x79167910; 124 | arc_exports.imguivers = IMGUI_VERSION_NUM; 125 | arc_exports.out_name = "GW2 SCT"; 126 | arc_exports.out_build = SCT_VERSION_STRING; 127 | arc_exports.wnd_nofilter = mod_wnd; 128 | arc_exports.combat = mod_combat; 129 | arc_exports.imgui = mod_imgui; 130 | arc_exports.options_end = mod_options; 131 | arc_exports.combat_local = mod_combat_local; 132 | return &arc_exports; 133 | } 134 | 135 | uintptr_t GW2_SCT::SCTMain::Release() { 136 | SkillIconManager::cleanup(); 137 | logFile.flush(); 138 | logFile.close(); 139 | return 0; 140 | } 141 | 142 | uintptr_t GW2_SCT::SCTMain::WindowUpdate(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { 143 | switch (uMsg) { 144 | case WM_SIZE: 145 | windowWidth = LOWORD(lParam); 146 | windowHeight = HIWORD(lParam); 147 | break; 148 | } 149 | return uMsg; 150 | } 151 | 152 | uintptr_t GW2_SCT::SCTMain::CombatEventArea(cbtevent * ev, ag * src, ag * dst, char * skillname, uint64_t id, uint64_t revision) { 153 | if (ev != nullptr) { 154 | if (revision == 1) { 155 | cbtevent1* ev1 = reinterpret_cast(ev); 156 | if (src && src->self && selfInstID != ev1->src_instid) { 157 | selfInstID = ev1->src_instid; 158 | Options::loadProfile(std::string(src->name)); 159 | } 160 | } 161 | else { 162 | if (src && src->self && selfInstID != ev->src_instid) { 163 | selfInstID = ev->src_instid; 164 | Options::loadProfile(std::string(src->name)); 165 | } 166 | } 167 | } 168 | 169 | return 0; 170 | } 171 | 172 | uintptr_t GW2_SCT::SCTMain::CombatEventLocal(cbtevent * ev, ag * src, ag * dst, char * skillname, uint64_t id, uint64_t revision) { 173 | /* combat event. skillname may be null. non-null skillname will remain static until module is unloaded. refer to evtc notes for complete detail */ 174 | if (ev) { 175 | if (revision == 1) { 176 | cbtevent1* ev1 = reinterpret_cast(ev); 177 | /* default names */ 178 | if (!src->name || !strlen(src->name)) src->name = _strdup(langStringG(LanguageKey::Unknown_Skill_Source)); 179 | if (!dst->name || !strlen(dst->name)) dst->name = _strdup(langStringG(LanguageKey::Unknown_Skill_Target)); 180 | if (!skillname || !strlen(skillname)) skillname = _strdup(langStringG(LanguageKey::Unknown_Skill_Name)); 181 | 182 | if (src->self) { 183 | selfInstID = ev1->src_instid; 184 | } 185 | if (dst->self) { 186 | selfInstID = ev1->dst_instid; 187 | } 188 | 189 | /* statechange */ 190 | if (ev1->is_statechange) { 191 | return 0; 192 | } 193 | 194 | /* activation */ 195 | else if (ev1->is_activation) { 196 | return 0; 197 | } 198 | 199 | /* buff remove */ 200 | else if (ev1->is_buffremove) { 201 | return 0; 202 | } 203 | 204 | else { 205 | std::vector types; 206 | if (ev1->buff) { 207 | if (ev1->buff_dmg > 0) { 208 | if (ev1->overstack_value != 0) { 209 | ev1->buff_dmg -= ev1->overstack_value; 210 | types.push_back(MessageType::SHIELD_RECEIVE); 211 | } 212 | if (ev1->buff_dmg > 0) { 213 | types.push_back(MessageType::HOT); 214 | } 215 | } 216 | else if (ev1->buff_dmg < 0) { 217 | if (ev1->overstack_value > 0) { 218 | ev1->buff_dmg += ev1->overstack_value; 219 | types.push_back(MessageType::SHIELD_REMOVE); 220 | } 221 | if (ev1->buff_dmg < 0) { 222 | switch (ev1->skillid) 223 | { 224 | case 736: types.push_back(MessageType::BLEEDING); break; 225 | case 737: types.push_back(MessageType::BURNING); break; 226 | case 723: types.push_back(MessageType::POISON); break; 227 | case 861: types.push_back(MessageType::CONFUSION); break; 228 | case 873: types.push_back(MessageType::RETALIATION); break; 229 | case 19426: types.push_back(MessageType::TORMENT); break; 230 | default: types.push_back(MessageType::DOT); break; 231 | } 232 | } 233 | } 234 | } 235 | else { 236 | if (ev1->value > 0) { 237 | if (ev1->overstack_value != 0) { 238 | ev1->value += ev1->overstack_value; 239 | types.push_back(MessageType::SHIELD_RECEIVE); 240 | } 241 | if (ev1->value > 0) { 242 | types.push_back(MessageType::HEAL); 243 | } 244 | } 245 | else { 246 | if (ev1->overstack_value > 0) { 247 | ev1->value += ev1->overstack_value; 248 | types.push_back(MessageType::SHIELD_REMOVE); 249 | } 250 | if (ev1->overstack_value <= 0 || ev1->value < 0) { 251 | switch (ev1->result) { 252 | case CBTR_GLANCE: 253 | case CBTR_INTERRUPT: 254 | case CBTR_NORMAL: types.push_back(MessageType::PHYSICAL); break; 255 | case CBTR_CRIT: types.push_back(MessageType::CRIT); break; 256 | case CBTR_BLOCK: types.push_back(MessageType::BLOCK); break; 257 | case CBTR_EVADE: types.push_back(MessageType::EVADE); break; 258 | case CBTR_ABSORB: types.push_back(MessageType::INVULNERABLE); break; 259 | case CBTR_BLIND: types.push_back(MessageType::MISS); break; 260 | default: 261 | break; 262 | } 263 | } 264 | } 265 | } 266 | if (types.size() > 0) { 267 | ev1->skillid = remapSkillID(ev1->skillid); 268 | } 269 | for (auto type : types) { 270 | if (src->self == 1 && (!Options::get()->outgoingOnlyToTarget || dst->id == targetAgentId)) { 271 | if (!Options::get()->selfMessageOnlyIncoming || dst->self != 1) { 272 | std::shared_ptr m = std::make_shared(MessageCategory::PLAYER_OUT, type, ev1, src, dst, skillname); 273 | sendMessageToEmission(m); 274 | } 275 | } 276 | else if (ev1->src_master_instid == selfInstID && (!Options::get()->outgoingOnlyToTarget || dst->id == targetAgentId)) { 277 | std::shared_ptr m = std::make_shared(MessageCategory::PET_OUT, type, ev1, src, dst, skillname); 278 | sendMessageToEmission(m); 279 | } 280 | if (dst->self == 1) { 281 | std::shared_ptr m = std::make_shared(MessageCategory::PLAYER_IN, type, ev1, src, dst, skillname); 282 | sendMessageToEmission(m); 283 | } 284 | else if (ev1->dst_master_instid == selfInstID) { 285 | std::shared_ptr m = std::make_shared(MessageCategory::PET_IN, type, ev1, src, dst, skillname); 286 | sendMessageToEmission(m); 287 | } 288 | } 289 | } 290 | } else { 291 | /* default names */ 292 | if (!src->name || !strlen(src->name)) src->name = _strdup(langStringG(LanguageKey::Unknown_Skill_Source)); 293 | if (!dst->name || !strlen(dst->name)) dst->name = _strdup(langStringG(LanguageKey::Unknown_Skill_Target)); 294 | if (!skillname || !strlen(skillname)) skillname = _strdup(langStringG(LanguageKey::Unknown_Skill_Name)); 295 | 296 | if (src->self) { 297 | selfInstID = ev->src_instid; 298 | } 299 | if (dst->self) { 300 | selfInstID = ev->dst_instid; 301 | } 302 | 303 | /* statechange */ 304 | if (ev->is_statechange) { 305 | return 0; 306 | } 307 | 308 | /* activation */ 309 | else if (ev->is_activation) { 310 | return 0; 311 | } 312 | 313 | /* buff remove */ 314 | else if (ev->is_buffremove) { 315 | return 0; 316 | } 317 | 318 | else { 319 | MessageType type = MessageType::NONE; 320 | if (ev->buff) { 321 | if (ev->buff_dmg < 0) { 322 | type = MessageType::HOT; 323 | } 324 | else if (ev->buff_dmg > 0) { 325 | switch (ev->skillid) 326 | { 327 | case 736: type = MessageType::BLEEDING; break; 328 | case 737: type = MessageType::BURNING; break; 329 | case 723: type = MessageType::POISON; break; 330 | case 861: type = MessageType::CONFUSION; break; 331 | case 873: type = MessageType::RETALIATION; break; 332 | case 19426: type = MessageType::TORMENT; break; 333 | default: type = MessageType::DOT; break; 334 | } 335 | } 336 | } 337 | else { 338 | if (ev->value < 0) { 339 | type = MessageType::HEAL; 340 | } 341 | else switch (ev->result) { 342 | case CBTR_GLANCE: 343 | case CBTR_NORMAL: type = MessageType::PHYSICAL; break; 344 | case CBTR_CRIT: type = MessageType::CRIT; break; 345 | case CBTR_BLOCK: type = MessageType::BLOCK; break; 346 | case CBTR_EVADE: type = MessageType::EVADE; break; 347 | case CBTR_ABSORB: type = MessageType::INVULNERABLE; break; 348 | case CBTR_BLIND: type = MessageType::MISS; break; 349 | default: 350 | break; 351 | } 352 | } 353 | if (type != MessageType::NONE) { 354 | if (src->self) { 355 | if (!Options::get()->selfMessageOnlyIncoming || !dst->self) { 356 | std::shared_ptr m = std::make_shared(MessageCategory::PLAYER_OUT, type, ev, src, dst, skillname); 357 | sendMessageToEmission(m); 358 | } 359 | } 360 | else if (ev->src_master_instid == selfInstID) { 361 | std::shared_ptr m = std::make_shared(MessageCategory::PET_OUT, type, ev, src, dst, skillname); 362 | sendMessageToEmission(m); 363 | } 364 | if (dst->self) { 365 | std::shared_ptr m = std::make_shared(MessageCategory::PLAYER_IN, type, ev, src, dst, skillname); 366 | sendMessageToEmission(m); 367 | } 368 | /*else if (ev->dst_master_instid == selfInstID) { 369 | std::shared_ptr m = std::make_shared(MessageCategory::PET_IN, type, ev, src, dst, skillname); 370 | ScrollArea::emitMessage(m); 371 | }*/ 372 | } 373 | } 374 | } 375 | } 376 | else { 377 | if (src != nullptr) { 378 | targetAgentId = src->id; 379 | } 380 | } 381 | return 0; 382 | } 383 | 384 | uintptr_t GW2_SCT::SCTMain::UIUpdate() { 385 | #if _DEBUG 386 | auto start_time = std::chrono::high_resolution_clock::now(); 387 | #endif 388 | FontType::ensureAtlasCreation(); 389 | Options::paint(); 390 | ExampleMessageOptions::paint(); 391 | if (Options::get()->sctEnabled) { 392 | ImVec2 windowSize((float)windowWidth, (float)windowHeight); 393 | ImGui::SetNextWindowPos(ImVec2(0, 0)); 394 | ImGui::SetNextWindowSize(windowSize); 395 | ImGui::Begin("SCT", NULL, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoBackground); 396 | 397 | for (std::shared_ptr scrollArea : scrollAreas) { 398 | scrollArea->paint(); 399 | } 400 | 401 | ImGui::End(); 402 | } 403 | #if _DEBUG 404 | auto time = std::chrono::high_resolution_clock::now() - start_time; 405 | uiFrames++; 406 | uiTime += time / std::chrono::microseconds(1); 407 | if (uiFrames >= 1000) { 408 | LOG("time per ui update: ", uiTime / uiFrames, "ns"); 409 | uiFrames = 0; 410 | uiTime = 0; 411 | } 412 | #endif 413 | return 0; 414 | } 415 | 416 | uintptr_t GW2_SCT::SCTMain::UIOptions() { 417 | ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { 4, 3 }); 418 | if (ImGui::Button(langString(LanguageCategory::Option_UI, LanguageKey::Title))) 419 | Options::open(); 420 | if (ImGui::Button(langString(LanguageCategory::Example_Message_UI, LanguageKey::Title))) 421 | ExampleMessageOptions::open(); 422 | ImGui::PopStyleVar(); 423 | return 0; 424 | } 425 | 426 | void GW2_SCT::SCTMain::sendMessageToEmission(std::shared_ptr m) { 427 | if (m->hasToBeFiltered()) return; 428 | for (auto scrollArea : scrollAreas) { 429 | scrollArea->receiveMessage(m); 430 | } 431 | ExampleMessageOptions::receiveMessage(m); 432 | } 433 | 434 | inline void clear(std::queue>& q) { 435 | std::queue> empty; 436 | std::swap(q, empty); 437 | } 438 | 439 | uint32_t GW2_SCT::SCTMain::remapSkillID(uint32_t originalID) { 440 | if (skillRemaps.count(originalID) == 0) return originalID; 441 | else return skillRemaps[originalID]; 442 | } 443 | -------------------------------------------------------------------------------- /src/ScrollArea.cpp: -------------------------------------------------------------------------------- 1 | #include "ScrollArea.h" 2 | #include "imgui.h" 3 | #include "Common.h" 4 | #include "Options.h" 5 | 6 | using namespace std::chrono; 7 | 8 | GW2_SCT::ScrollArea::ScrollArea(std::shared_ptr options) : options(options) { 9 | paintedMessages = std::list>>(); 10 | } 11 | 12 | void GW2_SCT::ScrollArea::receiveMessage(std::shared_ptr m) { 13 | for (auto& receiver : options->receivers) { 14 | if (receiver->enabled && m->getCategory() == receiver->category && m->getType() == receiver->type) { 15 | std::unique_lock mlock(messageQueueMutex); 16 | if (!messageQueue.empty()) { 17 | if (Options::get()->combineAllMessages) { 18 | for (auto it = messageQueue.rbegin(); it != messageQueue.rend(); ++it) { 19 | if (it->options == receiver && it->message->tryToCombineWith(m)) { 20 | it->update(); 21 | mlock.unlock(); 22 | return; 23 | } 24 | } 25 | } else { 26 | auto backMessage = messageQueue.rbegin(); 27 | if (backMessage->options == receiver && backMessage->message->tryToCombineWith(m)) { 28 | backMessage->update(); 29 | mlock.unlock(); 30 | return; 31 | } 32 | } 33 | } 34 | MessagePrerender preMessage = MessagePrerender(m, receiver); 35 | if (preMessage.options != nullptr) 36 | messageQueue.push_back(std::move(preMessage)); 37 | mlock.unlock(); 38 | return; 39 | } 40 | } 41 | } 42 | 43 | void GW2_SCT::ScrollArea::paintOutline() { 44 | if (options->outlineState != ScrollAreaOutlineState::NONE) { 45 | FLOAT x = windowWidth * 0.5f + options->offsetX; 46 | FLOAT y = windowHeight * 0.5f + options->offsetY; 47 | FLOAT w = options->width; 48 | FLOAT h = options->height; 49 | 50 | ImDrawList* draw_list = ImGui::GetWindowDrawList(); 51 | if (options->outlineState == ScrollAreaOutlineState::FULL) { 52 | draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + w, y + h), ImGui::GetColorU32(ImVec4(.15f, .15f, .15f, .66f))); 53 | draw_list->AddRect(ImVec2(x, y), ImVec2(x + w, y + h), ImGui::GetColorU32(ImVec4(1.f, 1.f, 1.f, .66f))); 54 | } else { 55 | draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + w, y + h), ImGui::GetColorU32(ImVec4(.15f, .15f, .15f, .33f))); 56 | draw_list->AddRect(ImVec2(x, y), ImVec2(x + w, y + h), ImGui::GetColorU32(ImVec4(1.f, 1.f, 1.f, .33f))); 57 | } 58 | } 59 | } 60 | 61 | void GW2_SCT::ScrollArea::paint() { 62 | 63 | std::unique_lock mlock(messageQueueMutex); 64 | if (!messageQueue.empty()) { 65 | MessagePrerender& m = messageQueue.front(); 66 | time_point now = system_clock::now(); 67 | if (paintedMessages.empty()) { 68 | paintedMessages.push_back(std::pair>(m, now)); 69 | messageQueue.pop_front(); 70 | } else { 71 | float spaceRequiredForNextMessage = getTextSize(m.str.c_str(), m.font, m.fontSize).y; 72 | float msForSpaceToClear = spaceRequiredForNextMessage / Options::get()->scrollSpeed * 1000; 73 | __int64 msSinceLastPaintedMessage = duration_cast(now - paintedMessages.back().second).count(); 74 | __int64 msUntilNextMessageCanBePainted = (__int64)msForSpaceToClear - msSinceLastPaintedMessage; 75 | if (msUntilNextMessageCanBePainted > 0) { 76 | if (messageQueue.size() > Options::get()->messagesInStack) { 77 | for (auto it = paintedMessages.rbegin(); it != paintedMessages.rend(); ++it) { 78 | it->second = it->second - milliseconds(msUntilNextMessageCanBePainted); 79 | } 80 | paintedMessages.push_back(std::pair>(m, now)); 81 | messageQueue.pop_front(); 82 | } 83 | } else { 84 | paintedMessages.push_back(std::pair>(m, now)); 85 | messageQueue.pop_front(); 86 | } 87 | } 88 | } 89 | mlock.unlock(); 90 | 91 | paintOutline(); 92 | 93 | auto it = paintedMessages.begin(); 94 | while(it != paintedMessages.end()) { 95 | __int64 t = duration_cast(system_clock::now() - it->second).count(); 96 | if (paintMessage(it->first, t)) { 97 | it++; 98 | } else { 99 | it = paintedMessages.erase(it); 100 | } 101 | } 102 | } 103 | 104 | bool GW2_SCT::ScrollArea::paintMessage(MessagePrerender& m, __int64 time) { 105 | float animatedHeight = time * 0.001f * Options::get()->scrollSpeed; 106 | float alpha = 1; 107 | float percentage = animatedHeight / options->height; 108 | float fadeLength = 0.2f; 109 | if (percentage > 1.f) { 110 | return false; 111 | } 112 | else if (percentage > 1.f - fadeLength) { 113 | alpha = 1 - (percentage - 1.f + fadeLength) / fadeLength; 114 | } 115 | 116 | if (m.prerenderNeeded) m.prerenderText(); 117 | 118 | ImVec2 pos(windowWidth * 0.5f + options->offsetX, windowHeight * 0.5f + options->offsetY); 119 | 120 | switch (options->textCurve) 121 | { 122 | case GW2_SCT::TextCurve::LEFT: 123 | pos.x += options->width * (2 * percentage - 1) * (2 * percentage - 1); 124 | pos.y += animatedHeight; 125 | break; 126 | case GW2_SCT::TextCurve::RIGHT: 127 | pos.x += options->width * (1 - (2 * percentage - 1) * (2 * percentage - 1)); 128 | pos.y += animatedHeight; 129 | break; 130 | case GW2_SCT::TextCurve::STRAIGHT: 131 | pos.y += animatedHeight; 132 | break; 133 | case GW2_SCT::TextCurve::STATIC: 134 | break; 135 | } 136 | 137 | if (options->textAlign != TextAlign::LEFT) { 138 | switch (options->textAlign) 139 | { 140 | case GW2_SCT::TextAlign::CENTER: 141 | pos.x -= 0.5f * m.interpretedTextWidth; 142 | break; 143 | case GW2_SCT::TextAlign::RIGHT: 144 | pos.x -= m.interpretedTextWidth; 145 | break; 146 | } 147 | } 148 | 149 | ImDrawList* draw_list = ImGui::GetWindowDrawList(); 150 | for (TemplateInterpreter::InterpretedText text : m.interpretedText) { 151 | ImVec2 curPos = ImVec2(pos.x + text.offset.x, pos.y += text.offset.y); 152 | if (text.icon == nullptr) { 153 | ImU32 blackWithAlpha = ImGui::GetColorU32(ImVec4(0, 0, 0, alpha)); 154 | if (GW2_SCT::Options::get()->dropShadow) { 155 | m.font->drawAtSize(text.str, m.fontSize, ImVec2(curPos.x + 2, curPos.y + 2), blackWithAlpha); 156 | } 157 | ImU32 actualCol = text.color & ImGui::GetColorU32(ImVec4(1, 1, 1, alpha)); 158 | m.font->drawAtSize(text.str, m.fontSize, curPos, actualCol); 159 | } 160 | else { 161 | text.icon->draw(curPos, text.size, ImGui::GetColorU32(ImVec4(1, 1, 1, alpha))); 162 | } 163 | } 164 | 165 | return true; 166 | } 167 | 168 | GW2_SCT::ScrollArea::MessagePrerender::MessagePrerender(std::shared_ptr message, std::shared_ptr options) 169 | : message(message), options(options) { 170 | category = message->getCategory(); 171 | type = message->getType(); 172 | update(); 173 | templateObserverId = options->outputTemplate.onAssign([this](const std::string& oldVal, const std::string& newVal) { this->update(); }); 174 | colorObserverId = options->color.onAssign([this](const std::string& oldVal, const std::string& newVal) { this->update(); }); 175 | fontObserverId = options->font.onAssign([this](const FontId& oldVal, const FontId& newVal) { this->update(); }); 176 | fontSizeObserverId = options->fontSize.onAssign([this](const float& oldVal, const float& newVal) { this->update(); }); 177 | // TODO: add callbacks when changing other values like general font size 178 | } 179 | 180 | GW2_SCT::ScrollArea::MessagePrerender::MessagePrerender(const MessagePrerender& copy) { 181 | message = copy.message; 182 | options = copy.options; 183 | category = copy.category; 184 | type = copy.type; 185 | if (copy.prerenderNeeded) { 186 | update(); 187 | } else { 188 | str = copy.str; 189 | font = copy.font; 190 | fontSize = copy.fontSize; 191 | options = copy.options; 192 | interpretedText = std::vector(copy.interpretedText); 193 | interpretedTextWidth = copy.interpretedTextWidth; 194 | prerenderNeeded = copy.prerenderNeeded; 195 | } 196 | templateObserverId = options->outputTemplate.onAssign([this](const std::string& oldVal, const std::string& newVal) { this->update(); }); 197 | colorObserverId = options->color.onAssign([this](const std::string& oldVal, const std::string& newVal) { this->update(); }); 198 | fontObserverId = options->font.onAssign([this](const FontId& oldVal, const FontId& newVal) { this->update(); }); 199 | fontSizeObserverId = options->fontSize.onAssign([this](const float& oldVal, const float& newVal) { this->update(); }); 200 | } 201 | 202 | GW2_SCT::ScrollArea::MessagePrerender::~MessagePrerender() { 203 | options->outputTemplate.removeOnAssign(templateObserverId); 204 | options->color.removeOnAssign(colorObserverId); 205 | options->font.removeOnAssign(fontObserverId); 206 | options->fontSize.removeOnAssign(fontSizeObserverId); 207 | } 208 | 209 | void GW2_SCT::ScrollArea::MessagePrerender::update() { 210 | if (message.get() == nullptr) { 211 | LOG("ERROR: calling update on pre-render without message"); 212 | str = ""; 213 | prerenderNeeded = false; 214 | return; 215 | } 216 | str = message->getStringForOptions(options); 217 | font = getFontType(options->font); 218 | fontSize = options->fontSize; 219 | if (fontSize < 0) { 220 | if (floatEqual(fontSize, -1.f)) fontSize = GW2_SCT::Options::get()->defaultFontSize; 221 | else if (floatEqual(fontSize, -2.f)) fontSize = GW2_SCT::Options::get()->defaultCritFontSize; 222 | } 223 | font->bakeGlyphsAtSize(str, fontSize); 224 | prerenderNeeded = true; 225 | } 226 | 227 | void GW2_SCT::ScrollArea::MessagePrerender::prerenderText() { 228 | if (options != nullptr) { 229 | interpretedText = TemplateInterpreter::interpret(font, fontSize, stoc(options->color), str); 230 | } else { 231 | interpretedText = {}; 232 | } 233 | if (interpretedText.size() > 0) { 234 | interpretedTextWidth = interpretedText.back().offset.x + interpretedText.back().size.x; 235 | } 236 | prerenderNeeded = false; 237 | } 238 | -------------------------------------------------------------------------------- /src/SkillIconManager.cpp: -------------------------------------------------------------------------------- 1 | #define CPPHTTPLIB_OPENSSL_SUPPORT 2 | #include "httplib.h" 3 | #include "SkillIconManager.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "Common.h" 9 | #include "Options.h" 10 | #define STB_IMAGE_IMPLEMENTATION 11 | #include 12 | 13 | #pragma comment(lib, "libssl") 14 | #pragma comment(lib, "libcrypto") 15 | 16 | httplib::Client renderingClient("https://render.guildwars2.com"); 17 | httplib::Client apiClient("https://api.guildwars2.com"); 18 | 19 | nlohmann::json getJSON(std::string url, std::function callback = nullptr) { 20 | if (auto res = apiClient.Get(url.c_str())) { 21 | if (callback != nullptr) 22 | callback(res->headers); 23 | 24 | return nlohmann::json::parse(res->body); 25 | } else { 26 | throw std::exception(("Error sending GET request to https://api.guildwars2.com" + url + ": " + to_string(res.error())).c_str()); 27 | } 28 | } 29 | 30 | std::shared_ptr> loadBinaryFileData(std::string filename) { 31 | std::ifstream in(filename, std::ios::binary); //open file 32 | in >> std::noskipws; //we don't want to skip spaces 33 | 34 | //initialize a vector with a pair of istream_iterators 35 | std::shared_ptr> ret = std::make_shared>(std::istream_iterator(in), std::istream_iterator()); 36 | return ret; 37 | } 38 | 39 | sf::contfree_safe_ptr> GW2_SCT::SkillIconManager::checkedIDs; 40 | sf::contfree_safe_ptr> GW2_SCT::SkillIconManager::loadedIcons; 41 | sf::contfree_safe_ptr> GW2_SCT::SkillIconManager::requestedIDs; 42 | std::thread GW2_SCT::SkillIconManager::loadThread; 43 | int GW2_SCT::SkillIconManager::requestsPerMinute = 200; 44 | std::atomic GW2_SCT::SkillIconManager::keepLoadThreadRunning; 45 | 46 | long GW2_SCT::SkillIconManager::skillIconsEnabledCallbackId = -1; 47 | long GW2_SCT::SkillIconManager::preloadAllSkillIconsId = -1; 48 | 49 | std::unordered_map> GW2_SCT::SkillIconManager::staticFiles = { 50 | { 736, { "79FF0046A5F9ADA3B4C4EC19ADB4CB124D5F0021", "102848" } }, //Bleeding 51 | { 737, { "B47BF5803FED2718D7474EAF9617629AD068EE10", "102849" } }, //Burning 52 | { 723, { "559B0AF9FB5E1243D2649FAAE660CCB338AACC19", "102840" } }, //Poison 53 | { 861, { "289AA0A4644F0E044DED3D3F39CED958E1DDFF53", "102880" } }, //Confusion 54 | { 873, { "27F233F7D4CE4E9EFE040E3D665B7B0643557B6E", "102883" } }, //Retaliation 55 | { 19426, { "10BABF2708CA3575730AC662A2E72EC292565B08", "598887" } }, //Torment 56 | { 718, { "F69996772B9E18FD18AD0AABAB25D7E3FC42F261", "102835" } }, //Regeneration 57 | { 17495, { "F69996772B9E18FD18AD0AABAB25D7E3FC42F261", "102835" } }, //Regeneration 58 | { 17674, { "F69996772B9E18FD18AD0AABAB25D7E3FC42F261", "102835" } } //Regeneration 59 | }; 60 | 61 | void GW2_SCT::SkillIconManager::init() { 62 | Options::profile.onAssign([=](std::shared_ptr oldProfile, std::shared_ptr newProfile) { 63 | if (skillIconsEnabledCallbackId >= 0) { 64 | oldProfile->skillIconsEnabled.removeOnAssign(skillIconsEnabledCallbackId); 65 | } 66 | if (preloadAllSkillIconsId >= 0) { 67 | oldProfile->preloadAllSkillIcons.removeOnAssign(preloadAllSkillIconsId); 68 | } 69 | if (newProfile->skillIconsEnabled) SkillIconManager::internalInit(); 70 | skillIconsEnabledCallbackId = newProfile->skillIconsEnabled.onAssign([=](const bool& wasEnabled, const bool& isNowEnabled) { 71 | if (wasEnabled == isNowEnabled) return; 72 | if (isNowEnabled) { 73 | std::thread t(SkillIconManager::internalInit); 74 | t.detach(); 75 | } 76 | else { 77 | if (loadThread.joinable()) { 78 | keepLoadThreadRunning = false; 79 | loadThread.detach(); 80 | } 81 | } 82 | }); 83 | preloadAllSkillIconsId = newProfile->preloadAllSkillIcons.onAssign([=](const bool& wasEnabled, const bool& isNowEnabled) { 84 | if (wasEnabled == isNowEnabled) return; 85 | if (isNowEnabled) { 86 | std::thread t([]() { 87 | requestedIDs->clear(); 88 | if (Options::get()->preloadAllSkillIcons) { 89 | auto s_checkedIDs = sf::slock_safe_ptr(checkedIDs); 90 | for ( 91 | auto checkableIDIterator = s_checkedIDs->begin(); 92 | checkableIDIterator != s_checkedIDs->end(); 93 | checkableIDIterator++ 94 | ) { 95 | if (!checkableIDIterator->second) { 96 | requestedIDs->push_back(checkableIDIterator->first); 97 | } 98 | } 99 | } 100 | }); 101 | t.detach(); 102 | } 103 | }); 104 | }); 105 | } 106 | 107 | void GW2_SCT::SkillIconManager::cleanup() { 108 | if (loadThread.joinable()) { 109 | keepLoadThreadRunning = false; 110 | loadThread.join(); 111 | } 112 | } 113 | 114 | void GW2_SCT::SkillIconManager::internalInit() { 115 | try { 116 | if (Options::get()->skillIconsEnabled) { 117 | std::regex matcher("X-Rate-Limit-Limit: ([0-9]+)"); 118 | std::vector skillIdList = getJSON("/v2/skills", [](httplib::Headers headers) { 119 | auto foundHeader = headers.find("X-Rate-Limit-Limit"); 120 | if (foundHeader != headers.end()) { 121 | requestsPerMinute = std::min(std::stoi(foundHeader->second), requestsPerMinute); 122 | } 123 | }); 124 | bool preload = Options::get()->preloadAllSkillIcons; 125 | checkedIDs->clear(); 126 | for (const auto& skillId: skillIdList) { 127 | checkedIDs->insert({ skillId, false }); 128 | if (preload) { 129 | requestedIDs->push_back(skillId); 130 | } 131 | } 132 | for (auto it : staticFiles) { 133 | checkedIDs->insert({ it.first, false }); 134 | } 135 | 136 | spawnLoadThread(); 137 | } 138 | } catch (std::exception& e) { 139 | LOG("Skill icon thread error: ", e.what()); 140 | } 141 | } 142 | 143 | void GW2_SCT::SkillIconManager::spawnLoadThread() { 144 | if (loadThread.joinable()) { 145 | keepLoadThreadRunning = false; 146 | loadThread.join(); 147 | } 148 | keepLoadThreadRunning = true; 149 | loadThread = std::thread(GW2_SCT::SkillIconManager::loadThreadCycle); 150 | } 151 | 152 | 153 | template 154 | std::string join(Range const& elements, const char* const delimiter) { 155 | std::ostringstream os; 156 | auto b = begin(elements), e = end(elements); 157 | 158 | if (b != e) { 159 | std::copy(b, prev(e), std::ostream_iterator(os, delimiter)); 160 | b = prev(e); 161 | } 162 | if (b != e) { 163 | os << *b; 164 | } 165 | 166 | return os.str(); 167 | } 168 | 169 | void GW2_SCT::SkillIconManager::loadThreadCycle() { 170 | LOG("Skillicon load thread started"); 171 | std::vector files; 172 | std::string iconPath = getSCTPath() + "icons\\"; 173 | CreateDirectory(iconPath.c_str(), NULL); 174 | if (getFilesInDirectory(iconPath, files)) { 175 | for (std::string iconFile : files) { 176 | size_t itDot = iconFile.find_last_of("."); 177 | if (iconFile.substr(itDot + 1) == "jpg") { 178 | std::string fileName = iconFile.substr(0, itDot); 179 | uint32_t skillID = std::strtoul(fileName.c_str(), NULL, 10); 180 | if (skillID != 0) { 181 | loadedIcons->insert(std::pair(skillID, SkillIcon(loadBinaryFileData(iconPath + iconFile), skillID))); 182 | { 183 | std::lock_guard lock(requestedIDs); 184 | if (std::find(requestedIDs->begin(), requestedIDs->end(), skillID) == requestedIDs->end()) { 185 | requestedIDs->push_back(skillID); 186 | } 187 | } 188 | } 189 | } 190 | } 191 | } 192 | 193 | std::string skillJsonFilename = getSCTPath() + "skill.json"; 194 | std::map skillJsonValues; 195 | if (file_exist(skillJsonFilename)) { 196 | LOG("Loading skill.json"); 197 | std::string line, text; 198 | std::ifstream in(skillJsonFilename); 199 | while (std::getline(in, line)) { 200 | text += line + "\n"; 201 | } 202 | in.close(); 203 | try { 204 | skillJsonValues = nlohmann::json::parse(text); 205 | } catch (std::exception& e) { 206 | LOG("Error parsing skill.json"); 207 | } 208 | } else { 209 | LOG("Warning: could not find a skill.json"); 210 | } 211 | 212 | std::regex renderAPIURLMatcher("/file/([A-Z0-9]+)/([0-9]+)\\.(png|jpg)"); 213 | while (keepLoadThreadRunning) { 214 | if (requestedIDs->size() > 0) { 215 | std::list> loadableIconURLs; 216 | std::vector idListToRequestFromApi = {}; 217 | while (requestedIDs->size() > 0 && idListToRequestFromApi.size() <= 10) { 218 | int frontRequestedSkillId = requestedIDs->front(); 219 | requestedIDs->pop_front(); 220 | 221 | auto iteratorToFoundStaticFileInformation = staticFiles.find(frontRequestedSkillId); 222 | if (iteratorToFoundStaticFileInformation != staticFiles.end()) { 223 | loadableIconURLs.push_back({ iteratorToFoundStaticFileInformation->first, iteratorToFoundStaticFileInformation->second.first, iteratorToFoundStaticFileInformation->second.second }); 224 | } else { 225 | idListToRequestFromApi.push_back(frontRequestedSkillId); 226 | } 227 | } 228 | if (idListToRequestFromApi.size() > 0) { 229 | std::string idStringToRequestFromApi = join(idListToRequestFromApi, ","); 230 | try { 231 | std::this_thread::sleep_for(std::chrono::milliseconds(60000 / requestsPerMinute)); 232 | nlohmann::json possibleSkillInformationList = getJSON("/v2/skills?ids=" + idStringToRequestFromApi); 233 | if (possibleSkillInformationList.is_array()) { 234 | std::vector skillInformationList = possibleSkillInformationList; 235 | for (auto& skillInformation : skillInformationList) { 236 | if (!skillInformation["icon"].is_null()) { 237 | std::string renderingApiURL = skillInformation["icon"]; 238 | std::string signature = std::regex_replace(renderingApiURL, renderAPIURLMatcher, "$1", std::regex_constants::format_no_copy); 239 | std::string fileId = std::regex_replace(renderingApiURL, renderAPIURLMatcher, "$2", std::regex_constants::format_no_copy); 240 | loadableIconURLs.push_back(std::make_tuple(skillInformation["id"], signature, fileId)); 241 | } 242 | } 243 | } 244 | } 245 | catch (std::exception& e) { 246 | LOG("Could not receive skill information for IDs: ", idStringToRequestFromApi, "(", e.what(), ")"); 247 | for (auto& unresolvedSkillId : idListToRequestFromApi) { 248 | requestedIDs->push_back(unresolvedSkillId); 249 | } 250 | } 251 | } 252 | 253 | std::list>>> binaryLoadedIcons; 254 | for (auto it = loadableIconURLs.begin(); it != loadableIconURLs.end() && keepLoadThreadRunning; ++it) { 255 | uint32_t curSkillId = std::get<0>(*it); 256 | try { 257 | std::string desc = std::get<1>(*it) + "/" + std::get<2>(*it); 258 | auto it = skillJsonValues.find(curSkillId); 259 | std::string iniVal = it == skillJsonValues.end() ? "" : it->second; 260 | std::string curImagePath = iconPath + std::to_string(curSkillId) + ".jpg"; 261 | if (iniVal.compare(desc) != 0 || !std::filesystem::exists(curImagePath.c_str())) { 262 | std::this_thread::sleep_for(std::chrono::milliseconds(60000 / requestsPerMinute)); 263 | LOG("Downloading skill icon: ", curSkillId); 264 | std::ofstream fileStream(curImagePath, std::ofstream::binary); 265 | 266 | renderingClient.Get(("/file/" + desc).c_str(), [&](const char* data, size_t data_length) { 267 | for (const char* i = data; i < data + data_length; i++) { 268 | fileStream << (int8_t)*i; 269 | } 270 | return true; 271 | }); 272 | 273 | fileStream.close(); 274 | LOG("Finished downloading skill icon."); 275 | binaryLoadedIcons.push_back(std::pair>>(curSkillId, loadBinaryFileData(curImagePath))); 276 | skillJsonValues[curSkillId] = desc; 277 | } 278 | } 279 | catch (std::exception& e) { 280 | LOG("Error during downloading skill icon image for skill ", std::to_string(curSkillId), ": ", e.what()); 281 | requestedIDs->push_back(curSkillId); 282 | } 283 | } 284 | while (binaryLoadedIcons.size() > 0 && keepLoadThreadRunning) { 285 | (*checkedIDs)[binaryLoadedIcons.front().first] = true; 286 | loadedIcons->insert(std::pair(binaryLoadedIcons.front().first, SkillIcon(binaryLoadedIcons.front().second, binaryLoadedIcons.front().first))); 287 | binaryLoadedIcons.pop_front(); 288 | } 289 | } else { 290 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 291 | } 292 | } 293 | 294 | std::ofstream out(skillJsonFilename); 295 | nlohmann::json outJson = skillJsonValues; 296 | #if _DEBUG 297 | out << outJson.dump(2); 298 | #else 299 | out << outJson.dump(); 300 | #endif 301 | out.close(); 302 | LOG("Skillicon load thread stopping"); 303 | } 304 | 305 | GW2_SCT::SkillIcon* GW2_SCT::SkillIconManager::getIcon(uint32_t skillID) { 306 | if (Options::get()->skillIconsEnabled) { 307 | if (loadedIcons->find(skillID) != loadedIcons->end()) { 308 | SkillIcon* ret = &(loadedIcons->at(skillID)); 309 | return ret; 310 | } 311 | if (!Options::get()->preloadAllSkillIcons) { 312 | if (!(*checkedIDs)[skillID]) { 313 | requestedIDs->push_back(skillID); 314 | return nullptr; 315 | } 316 | } 317 | } 318 | return nullptr; 319 | } 320 | 321 | ImVec2 GW2_SCT::SkillIcon::draw(ImVec2 pos, ImVec2 size, ImU32 color) { 322 | SkillIconDisplayType requestedDisplayType = Options::get()->skillIconsDisplayType; 323 | if (!texturesCreated[requestedDisplayType]) { 324 | loadTexture(requestedDisplayType); 325 | } 326 | if (textures[requestedDisplayType] == nullptr) { 327 | return ImVec2(0,0); 328 | } 329 | 330 | ImVec2 end(pos.x + size.x, pos.y + size.y); 331 | if (textures[requestedDisplayType] != nullptr) { 332 | textures[requestedDisplayType]->draw(pos, size, color); 333 | } else { 334 | return ImVec2(0, 0); 335 | } 336 | return size; 337 | } 338 | 339 | GW2_SCT::SkillIcon::SkillIcon(std::shared_ptr> fileData, uint32_t skillID) 340 | : fileData(fileData), skillID(skillID) {} 341 | 342 | GW2_SCT::SkillIcon::~SkillIcon() { 343 | for (auto& texture: textures) 344 | if (texture.second != nullptr)ImmutableTexture::Release(texture.second); 345 | } 346 | 347 | struct HexCharStruct { 348 | unsigned char c; 349 | HexCharStruct(unsigned char _c) : c(_c) { } 350 | }; 351 | 352 | inline std::ostream& operator<<(std::ostream& o, const HexCharStruct& hs) { 353 | return (o << std::hex << (int)hs.c); 354 | } 355 | 356 | inline HexCharStruct hex(unsigned char _c) { 357 | return HexCharStruct(_c); 358 | } 359 | 360 | void logImageData(std::shared_ptr> data) { 361 | std::stringstream str; 362 | int i = 0; 363 | for (auto it = data->begin(); it != data->end(); it++) { 364 | str << " 0x" << std::setfill('0') << std::setw(2) << hex(*it); 365 | i++; 366 | if (i >= 16) { 367 | std::string out = str.str(); 368 | out.erase(out.begin()); 369 | LOG(out); 370 | str = std::stringstream(); 371 | i = 0; 372 | } 373 | } 374 | } 375 | 376 | void logImageData(unsigned char* data, size_t size) { 377 | std::stringstream str; 378 | int i = 0; 379 | while (i < size) { 380 | str << " 0x" << std::setfill('0') << std::setw(2) << hex(*(data + i)); 381 | i++; 382 | if (i % 16 == 0) { 383 | std::string out = str.str(); 384 | out.erase(out.begin()); 385 | LOG(out); 386 | str = std::stringstream(); 387 | } 388 | } 389 | } 390 | 391 | struct ImageDataHelper { 392 | unsigned char* data; 393 | int row_size; 394 | private: 395 | struct ImageDataHelperRow { 396 | unsigned char* row_start; 397 | unsigned char* operator[](int x) { 398 | return { row_start + x * 4 }; 399 | } 400 | operator unsigned char* () { return row_start; }; 401 | }; 402 | public: 403 | ImageDataHelperRow operator[](int y) { 404 | return { data + y * 4 * row_size }; 405 | } 406 | }; 407 | 408 | void setScaledTransparency(unsigned char* cur) { 409 | int r = cur[0]; 410 | int g = cur[1]; 411 | int b = cur[2]; 412 | cur[3] = std::min(0xfe, (r + r + r + b + g + g + g + g) >> 3 * 0xff / 10); 413 | } 414 | 415 | bool pixelIsBlack(unsigned char* cur) { 416 | int r = cur[0]; 417 | int g = cur[1]; 418 | int b = cur[2]; 419 | return (r + r + r + b + g + g + g + g) >> 3 < 10; 420 | } 421 | 422 | bool borderNIsBlack(ImageDataHelper data, int n, int image_width, int image_height) { 423 | for (int x = n; x < image_width - n; x++) if (!pixelIsBlack(data[n][x])) return false; 424 | for (int x = n; x < image_width - n; x++) if (!pixelIsBlack(data[image_height - n - 1][x])) return false; 425 | for (int y = n + 1; y < image_height - n - 1; y++) if (!pixelIsBlack(data[y][n])) return false; 426 | for (int y = n + 1; y < image_height - n - 1; y++) if (!pixelIsBlack(data[y][image_width - n - 1])) return false; 427 | return true; 428 | } 429 | 430 | void convertRGBAToARGBAndCull(unsigned char* image_data, int image_width, int image_height, GW2_SCT::SkillIconDisplayType displayType) { 431 | unsigned char* cur = image_data; 432 | for (int y = 0; y < image_height; y++) { 433 | for (int x = 0; x < image_width; x++) { 434 | char red = cur[0]; 435 | cur[0] = cur[2]; 436 | cur[2] = red; 437 | if (displayType == GW2_SCT::SkillIconDisplayType::BLACK_CULLED) { 438 | if (pixelIsBlack(cur)) { 439 | setScaledTransparency(cur); 440 | } 441 | } 442 | cur += 4; 443 | } 444 | } 445 | if (displayType == GW2_SCT::SkillIconDisplayType::BORDER_TOUCHING_BLACK_CULLED) { 446 | ImageDataHelper image{ image_data, image_width }; 447 | std::queue> pixelsToResolve; 448 | for (int x = 0; x < image_width; x++) pixelsToResolve.push(std::pair(x, 0)); 449 | for (int x = 0; x < image_width; x++) pixelsToResolve.push(std::pair(x, image_height - 1)); 450 | for (int y = 1; y < image_height - 1; y++) pixelsToResolve.push(std::pair(0, y)); 451 | for (int y = 1; y < image_height - 1; y++) pixelsToResolve.push(std::pair(image_width - 1, y)); 452 | while (pixelsToResolve.size() > 0) { 453 | auto pixelIndex = pixelsToResolve.front(); 454 | pixelsToResolve.pop(); 455 | cur = image[pixelIndex.second][pixelIndex.first]; 456 | if (cur[3] == 0xff && pixelIsBlack(cur)) { 457 | setScaledTransparency(cur); 458 | if (pixelIndex.first - 1 > 0) pixelsToResolve.push(std::pair(pixelIndex.first - 1, pixelIndex.second)); 459 | if (pixelIndex.second - 1 > 0) pixelsToResolve.push(std::pair(pixelIndex.first, pixelIndex.second - 1)); 460 | if (pixelIndex.first + 1 < image_width) pixelsToResolve.push(std::pair(pixelIndex.first + 1, pixelIndex.second)); 461 | if (pixelIndex.first + 1 < image_height) pixelsToResolve.push(std::pair(pixelIndex.first, pixelIndex.second + 1)); 462 | } 463 | } 464 | } 465 | if (displayType == GW2_SCT::SkillIconDisplayType::BORDER_BLACK_CULLED) { 466 | ImageDataHelper image{ image_data, image_width }; 467 | int n = 0; 468 | while (borderNIsBlack(image, n, image_width, image_height)) { 469 | for (int x = n; x < image_width - n; x++) setScaledTransparency(image[n][x]); 470 | for (int x = n; x < image_width - n; x++) setScaledTransparency(image[image_height - n - 1][x]); 471 | for (int y = n + 1; y < image_height - n - 1; y++) setScaledTransparency(image[y][n]); 472 | for (int y = n + 1; y < image_height - n - 1; y++) setScaledTransparency(image[y][image_width - n - 1]); 473 | n++; 474 | } 475 | } 476 | } 477 | 478 | void GW2_SCT::SkillIcon::loadTexture(GW2_SCT::SkillIconDisplayType displayType) { 479 | texturesCreated[displayType] = true; 480 | 481 | if (fileData->size() < 100) { 482 | LOG("Icon: ", std::to_string(skillID)); 483 | LOG("WARNING - loaded file for texture too small: ", fileData->size()); 484 | logImageData(fileData); 485 | } 486 | 487 | // Load from file 488 | int image_width = 0; 489 | int image_height = 0; 490 | int nBpp = 4; // Bytes per pixel 491 | unsigned char* image_data = stbi_load_from_memory(fileData->data(), (int)fileData->size(), &image_width, &image_height, NULL, nBpp); 492 | if (image_data == NULL) { 493 | textures[displayType] = nullptr; 494 | LOG("stbi_load_from_memory image_data == NULL"); 495 | logImageData(fileData); 496 | return; 497 | } 498 | 499 | convertRGBAToARGBAndCull(image_data, image_width, image_height, displayType); 500 | 501 | // Upload texture to graphics system 502 | if (textures[displayType] != nullptr) ImmutableTexture::Release(textures[displayType]); 503 | textures[displayType] = ImmutableTexture::Create(image_width, image_height, image_data); 504 | if (textures[displayType] == nullptr) { 505 | LOG("Could not upload skill icon data to graphics system."); 506 | } 507 | 508 | stbi_image_free(image_data); 509 | } 510 | -------------------------------------------------------------------------------- /src/TemplateInterpreter.cpp: -------------------------------------------------------------------------------- 1 | #include "TemplateInterpreter.h" 2 | #include 3 | #include 4 | #include 5 | #include "Common.h" 6 | #include "Options.h" 7 | 8 | ImVec2 CalcTextSizeWithFontSize(const char* text, ImFont* font, float font_size) 9 | { 10 | ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, 0.0f, text); 11 | 12 | // Cancel out character spacing for the last character of a line (it is baked into glyph->XAdvance field) 13 | const float font_scale = font_size / font->FontSize; 14 | const float character_spacing_x = 1.0f * font_scale; 15 | if (text_size.x > 0.0f) 16 | text_size.x -= character_spacing_x; 17 | text_size.x = (float)(int)(text_size.x + 0.95f); 18 | 19 | return text_size; 20 | } 21 | 22 | ImVec2 getTextSize(const char* t, GW2_SCT::FontType* font, float fontSize, bool use_bbc) { 23 | std::string text = std::string(t); 24 | std::string currentText = ""; 25 | auto it = text.begin(); 26 | 27 | if (use_bbc) { 28 | while (it != text.end()) { 29 | if (*it == '[') { 30 | ++it; 31 | switch (*it) { 32 | case '[': 33 | currentText += *it; 34 | break; 35 | default: 36 | while (*it != ']') { 37 | ++it; 38 | } 39 | } 40 | } 41 | else if (*it == ']') { 42 | currentText += *it; 43 | ++it; 44 | } 45 | else { 46 | currentText += *it; 47 | } 48 | ++it; 49 | } 50 | } else { 51 | currentText = text; 52 | } 53 | 54 | return font->calcRequiredSpaceForTextAtSize(currentText, fontSize); 55 | } 56 | 57 | bool GW2_SCT::TemplateInterpreter::validate(std::string t, std::map params) { 58 | int openColors = 0; 59 | std::vector currentCommands; 60 | std::string tempText; 61 | 62 | for (auto it = t.begin(); it != t.end(); it++) { 63 | switch (*it) 64 | { 65 | case ']': 66 | it++; 67 | if (it == t.end() || *it != ']') return false; 68 | break; 69 | case '[': 70 | it++; 71 | if (it == t.end()) return false; 72 | switch (*it) 73 | { 74 | case '[': 75 | break; 76 | case '/': 77 | it++; 78 | if (it == t.end()) return false; 79 | tempText = ""; 80 | while (it != t.end() && *it != ']') { 81 | tempText += *it; 82 | it++; 83 | } 84 | if (it == t.end()) return false; 85 | if (currentCommands.empty()) return false; 86 | if (tempText.compare(currentCommands.back()) != 0) return false; 87 | currentCommands.pop_back(); 88 | if (tempText == "col") { 89 | openColors--; 90 | } 91 | break; 92 | default: 93 | tempText = ""; 94 | while (it != t.end() && *it != '=' && *it != ']') { 95 | tempText += *it; 96 | it++; 97 | } 98 | if (it == t.end()) return false; 99 | currentCommands.push_back(tempText); 100 | if (tempText == "col") { 101 | tempText = ""; 102 | if (*it != '=') return false; 103 | it++; 104 | while (it != t.end() && *it != ']') { 105 | tempText += *it; 106 | it++; 107 | } 108 | if (it == t.end()) return false; 109 | if (std::regex_match(tempText, std::regex("(%c|[0-9A-F]{6})"))) { 110 | openColors++; 111 | } 112 | else { 113 | return false; 114 | } 115 | } 116 | else if (tempText == "icon") { 117 | tempText = ""; 118 | if (*it != '=') return false; 119 | it++; 120 | while (it != t.end() && *it != ']') { 121 | tempText += *it; 122 | it++; 123 | } 124 | if (it == t.end()) return false; 125 | if (std::regex_match(tempText, std::regex("[0-9]+"))) { 126 | openColors++; 127 | } 128 | else { 129 | return false; 130 | } 131 | } 132 | else { 133 | return false; 134 | } 135 | break; 136 | } 137 | break; 138 | case '%': 139 | it++; 140 | if (it == t.end()) return false; 141 | if (*it != '%' && params.find(*it) == params.end()) { 142 | return false; 143 | } 144 | break; 145 | } 146 | } 147 | return openColors == 0; 148 | } 149 | 150 | const std::vector emptyInterpreted = {}; 151 | 152 | std::vector GW2_SCT::TemplateInterpreter::interpret(GW2_SCT::FontType* font, float fontSize, ImU32 defaultColor, std::string t) { 153 | std::vector interpreted; 154 | std::vector currentCommands; 155 | std::vector colors{ defaultColor }; 156 | std::string tempText; 157 | std::string currentText = ""; 158 | ImVec2 currentOffset = ImVec2(0.f, 0.f); 159 | 160 | for (auto it = t.begin(); it != t.end(); it++) { 161 | switch (*it) 162 | { 163 | case ']': 164 | it++; 165 | if (it == t.end() || *it != ']') return emptyInterpreted; 166 | currentText += *it; 167 | break; 168 | case '[': 169 | it++; 170 | if (it == t.end()) return emptyInterpreted; 171 | switch (*it) 172 | { 173 | case '[': 174 | currentText += *it; 175 | break; 176 | case '/': 177 | it++; 178 | if (it == t.end()) return emptyInterpreted; 179 | tempText = ""; 180 | while (it != t.end() && *it != ']') { 181 | tempText += *it; 182 | it++; 183 | } 184 | if (it == t.end()) return emptyInterpreted; 185 | if (currentCommands.empty()) return emptyInterpreted; 186 | if (tempText.compare(currentCommands.back()) != 0) return emptyInterpreted; 187 | currentCommands.pop_back(); 188 | if (tempText == "col") { 189 | if (currentText != "") { 190 | ImVec2 s = getTextSize(currentText.c_str(), font, fontSize, false); 191 | interpreted.push_back({ currentText, s, currentOffset, colors.back() }); 192 | currentOffset.x += s.x; 193 | currentText = ""; 194 | } 195 | colors.pop_back(); 196 | } 197 | else { 198 | currentText = ""; 199 | } 200 | break; 201 | default: 202 | if (currentText != "") { 203 | ImVec2 s = getTextSize(currentText.c_str(), font, fontSize, false); 204 | interpreted.push_back({ currentText, s, currentOffset, colors.back() }); 205 | currentOffset.x += s.x; 206 | currentText = ""; 207 | } 208 | tempText = ""; 209 | while (it != t.end() && *it != '=' && *it != ']') { 210 | tempText += *it; 211 | it++; 212 | } 213 | if (it == t.end()) return emptyInterpreted; 214 | currentCommands.push_back(tempText); 215 | if (tempText == "col") { 216 | tempText = ""; 217 | if (*it != '=') return emptyInterpreted; 218 | it++; 219 | while (it != t.end() && *it != ']') { 220 | tempText += *it; 221 | it++; 222 | } 223 | if (it == t.end()) return emptyInterpreted; 224 | if (std::regex_match(tempText, std::regex("([0-9A-F]{6})"))) { 225 | colors.push_back(stoc(tempText)); 226 | } 227 | else { 228 | return emptyInterpreted; 229 | } 230 | } 231 | else if (tempText == "icon") { 232 | tempText = ""; 233 | if (*it != '=') return emptyInterpreted; 234 | it++; 235 | while (it != t.end() && *it != ']') { 236 | tempText += *it; 237 | it++; 238 | } 239 | if (it == t.end()) return emptyInterpreted; 240 | if (std::regex_match(tempText, std::regex("([0-9]+)"))) { 241 | SkillIcon* icon = SkillIconManager::getIcon(std::stoi(tempText.c_str())); 242 | if (icon != nullptr) { 243 | interpreted.push_back({ "", ImVec2(fontSize, fontSize), currentOffset, defaultColor, icon}); 244 | currentOffset.x += fontSize; 245 | } 246 | } 247 | else { 248 | return emptyInterpreted; 249 | } 250 | } 251 | else { 252 | return emptyInterpreted; 253 | } 254 | break; 255 | } 256 | break; 257 | default: 258 | currentText += *it; 259 | } 260 | } 261 | if (currentText != "") { 262 | ImVec2 s = getTextSize(currentText.c_str(), font, fontSize, false); 263 | interpreted.push_back({ currentText, s, currentOffset, colors.back() }); 264 | currentOffset.x += s.x; 265 | currentText = ""; 266 | } 267 | return interpreted; 268 | } 269 | -------------------------------------------------------------------------------- /src/Texture.cpp: -------------------------------------------------------------------------------- 1 | #include "Texture.h" 2 | #include "Common.h" 3 | 4 | GW2_SCT::Texture::Texture(int width, int height) : _textureWidth(width), _textureHeight(height), _nextCreationTry(std::chrono::system_clock::now()) {} 5 | 6 | void GW2_SCT::Texture::draw(ImVec2 pos, ImVec2 size, ImU32 color) { 7 | return draw(pos, size, ImVec2(0, 0), ImVec2(1, 1), color); 8 | } 9 | 10 | void GW2_SCT::Texture::draw(ImVec2 pos, ImVec2 size, ImVec2 uvStart, ImVec2 uvEnd, ImU32 color) { 11 | if (!_created && std::chrono::system_clock::now() > _nextCreationTry) { 12 | _created = internalCreate(); 13 | if (!_created) { 14 | _creationTries++; 15 | _nextCreationTry = std::chrono::system_clock::now() + std::chrono::seconds(_creationTries); 16 | } 17 | } 18 | if (_created) { 19 | internalDraw(pos, size, uvStart, uvEnd, color); 20 | } 21 | } 22 | 23 | void GW2_SCT::Texture::ensureCreation() { 24 | if (!_created && std::chrono::system_clock::now() > _nextCreationTry) { 25 | _created = internalCreate(); 26 | if (!_created) { 27 | _creationTries++; 28 | _nextCreationTry = std::chrono::system_clock::now() + std::chrono::seconds(_creationTries); 29 | } 30 | } 31 | } 32 | 33 | GW2_SCT::ImmutableTexture* GW2_SCT::ImmutableTexture::Create(int width, int height, unsigned char* data) { 34 | if (d3Device11 != nullptr) { 35 | return new ImmutableTextureD3D11(width, height, data); 36 | } 37 | return nullptr; 38 | } 39 | 40 | void GW2_SCT::ImmutableTexture::Release(ImmutableTexture* tex) { 41 | if (tex == nullptr) return; 42 | if (d3Device11 != nullptr) { 43 | ImmutableTextureD3D11* res = (ImmutableTextureD3D11*)tex; 44 | delete res; 45 | } 46 | } 47 | 48 | GW2_SCT::ImmutableTexture::ImmutableTexture(int width, int height) : Texture(width, height) {} 49 | 50 | GW2_SCT::MutableTexture* GW2_SCT::MutableTexture::Create(int width, int height) { 51 | if (d3Device11 != nullptr) { 52 | return new MutableTextureD3D11(width, height); 53 | } 54 | return nullptr; 55 | } 56 | 57 | void GW2_SCT::MutableTexture::Release(MutableTexture* tex) { 58 | if (tex == nullptr) return; 59 | if (d3Device11 != nullptr) { 60 | MutableTextureD3D11* res = (MutableTextureD3D11*)tex; 61 | delete res; 62 | } 63 | } 64 | 65 | GW2_SCT::MutableTexture::MutableTexture(int width, int height) : Texture(width, height) {} 66 | 67 | bool GW2_SCT::MutableTexture::startUpdate(ImVec2 pos, ImVec2 size, UpdateData* out) { 68 | if (_isCurrentlyUpdating || !_created) return false; 69 | _isCurrentlyUpdating = true; 70 | if (internalStartUpdate(pos, size, out)) { 71 | return true; 72 | } else { 73 | _isCurrentlyUpdating = false; 74 | return false; 75 | } 76 | } 77 | 78 | bool GW2_SCT::MutableTexture::endUpdate() { 79 | if (!_isCurrentlyUpdating) return false; 80 | _isCurrentlyUpdating = false; 81 | if (internalEndUpdate()) { 82 | return true; 83 | } else { 84 | _isCurrentlyUpdating = true; 85 | return false; 86 | } 87 | } -------------------------------------------------------------------------------- /src/TextureD3D11.cpp: -------------------------------------------------------------------------------- 1 | #include "Texture.h" 2 | #include "Common.h" 3 | 4 | GW2_SCT::TextureD3D11::TextureD3D11(int width, int height, unsigned char* data) : width(width), height(height) { 5 | if (data == nullptr) { 6 | this->data = nullptr; 7 | } 8 | else { 9 | size_t size = sizeof(unsigned char) * width * height * 4; 10 | this->data = (unsigned char*) malloc(size); 11 | if (this->data != nullptr) { 12 | memcpy(this->data, data, size); 13 | } 14 | } 15 | } 16 | 17 | GW2_SCT::TextureD3D11::~TextureD3D11() { 18 | if (_texture11View != nullptr) _texture11View->Release(); 19 | if (_texture11 != nullptr) _texture11->Release(); 20 | } 21 | 22 | bool GW2_SCT::TextureD3D11::create() { 23 | if (d3Device11 == nullptr) return false; 24 | HRESULT res; 25 | D3D11_TEXTURE2D_DESC desc = {}; 26 | desc.Width = width; 27 | desc.Height = height; 28 | desc.MipLevels = 1; 29 | desc.ArraySize = 1; 30 | desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; 31 | desc.SampleDesc.Count = 1; 32 | desc.Usage = D3D11_USAGE_DEFAULT; 33 | desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; 34 | desc.CPUAccessFlags = 0; 35 | 36 | if (data != nullptr) { 37 | D3D11_SUBRESOURCE_DATA subResource; 38 | subResource.pSysMem = data; 39 | subResource.SysMemPitch = desc.Width * 4; 40 | subResource.SysMemSlicePitch = 0; 41 | 42 | if (FAILED(res = d3Device11->CreateTexture2D(&desc, &subResource, &_texture11))) { 43 | LOG("d3Device11->CreateTexture2D failed: " + std::to_string(res)); 44 | return false; 45 | } 46 | } 47 | else { 48 | if (FAILED(res = d3Device11->CreateTexture2D(&desc, NULL, &_texture11))) { 49 | LOG("d3Device11->CreateTexture2D failed: " + std::to_string(res)); 50 | return false; 51 | } 52 | } 53 | 54 | D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; 55 | ZeroMemory(&srvDesc, sizeof(srvDesc)); 56 | srvDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; 57 | srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; 58 | srvDesc.Texture2D.MipLevels = desc.MipLevels; 59 | srvDesc.Texture2D.MostDetailedMip = 0; 60 | 61 | if (FAILED(res = d3Device11->CreateShaderResourceView(_texture11, &srvDesc, &_texture11View))) { 62 | LOG("d3Device11->CreateShaderResourceView failed: " + std::to_string(res)); 63 | _texture11->Release(); 64 | return false; 65 | } 66 | return true; 67 | } 68 | 69 | GW2_SCT::ImmutableTextureD3D11::ImmutableTextureD3D11(int width, int height, unsigned char* data) 70 | : TextureD3D11(width, height, data), ImmutableTexture(width, height) {} 71 | 72 | void GW2_SCT::ImmutableTextureD3D11::internalDraw(ImVec2 pos, ImVec2 size, ImVec2 uvStart, ImVec2 uvEnd, ImU32 color) { 73 | ImGui::GetWindowDrawList()->AddImage(_texture11View, pos, ImVec2(pos.x + size.x, pos.y + size.y), uvStart, uvEnd, color); 74 | } 75 | 76 | bool GW2_SCT::ImmutableTextureD3D11::internalCreate() { 77 | return create(); 78 | } 79 | 80 | GW2_SCT::MutableTextureD3D11::MutableTextureD3D11(int width, int height) 81 | : TextureD3D11(width, height, nullptr), MutableTexture(width, height) {} 82 | 83 | GW2_SCT::MutableTextureD3D11::~MutableTextureD3D11() { 84 | if (_texture11Staging != nullptr) _texture11Staging->Release(); 85 | } 86 | 87 | void GW2_SCT::MutableTextureD3D11::internalDraw(ImVec2 pos, ImVec2 size, ImVec2 uvStart, ImVec2 uvEnd, ImU32 color) { 88 | if (_stagingChanged && d3D11Context != nullptr) { 89 | std::lock_guard lock(_stagingMutex); 90 | d3D11Context->CopyResource(_texture11, _texture11Staging); 91 | _stagingChanged = false; 92 | } 93 | ImGui::GetWindowDrawList()->AddImage(_texture11View, pos, ImVec2(pos.x + size.x, pos.y + size.y), uvStart, uvEnd, color); 94 | } 95 | 96 | bool GW2_SCT::MutableTextureD3D11::internalCreate() { 97 | if (!create()) return false; 98 | 99 | if (d3Device11 == nullptr) return false; 100 | 101 | HRESULT res; 102 | D3D11_TEXTURE2D_DESC desc = {}; 103 | desc.Width = width; 104 | desc.Height = height; 105 | desc.MipLevels = 1; 106 | desc.ArraySize = 1; 107 | desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; 108 | desc.SampleDesc.Count = 1; 109 | desc.Usage = D3D11_USAGE_STAGING; 110 | desc.BindFlags = 0; 111 | desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE | D3D11_CPU_ACCESS_READ; 112 | 113 | if (FAILED(res = d3Device11->CreateTexture2D(&desc, NULL, &_texture11Staging))) { 114 | LOG("d3Device11->CreateTexture2D failed: " + std::to_string(res)); 115 | return false; 116 | } 117 | return true; 118 | } 119 | 120 | bool GW2_SCT::MutableTextureD3D11::internalStartUpdate(ImVec2 pos, ImVec2 size, UpdateData* out) { 121 | if (d3D11Context == nullptr) return false; 122 | 123 | HRESULT res; 124 | D3D11_MAPPED_SUBRESOURCE mapped; 125 | std::lock_guard lock(_stagingMutex); 126 | if (FAILED(res = d3D11Context->Map(_texture11Staging, 0, D3D11_MAP_READ_WRITE, 0, &mapped))) { 127 | LOG("Could not map staging texture.", std::to_string(res));; 128 | return false; 129 | } 130 | out->data = (unsigned char*)mapped.pData + static_cast(pos.y) * mapped.RowPitch + static_cast(pos.x) * 4; 131 | out->rowPitch = mapped.RowPitch; 132 | out->bytePerPixel = 4; 133 | return true; 134 | } 135 | 136 | bool GW2_SCT::MutableTextureD3D11::internalEndUpdate() { 137 | if (d3D11Context == nullptr) return false; 138 | 139 | d3D11Context->Unmap(_texture11Staging, 0); 140 | _stagingChanged = true; 141 | return true; 142 | } 143 | -------------------------------------------------------------------------------- /src/imgui_sct_widgets.cpp: -------------------------------------------------------------------------------- 1 | #include "imgui_sct_widgets.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #define IMGUI_DEFINE_MATH_OPERATORS 8 | #include 9 | #include "Language.h" 10 | #include "Common.h" 11 | #include "TemplateInterpreter.h" 12 | #include "Options.h" 13 | 14 | constexpr const float& clampf(const float& v, const float& lo, const float& hi) { 15 | return (v < lo) ? lo : (hi < v) ? hi : v; 16 | } 17 | constexpr const int& clampi(const int& v, const int& lo, const int& hi) { 18 | return (v < lo) ? lo : (hi < v) ? hi : v; 19 | } 20 | 21 | bool ImGui::ClampingDragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* display_format, float power) { 22 | float start = *v; 23 | DragFloat(label, v, v_speed, v_min, v_max, display_format, power); 24 | *v = clampf(*v, v_min, v_max); 25 | return start != *v; 26 | } 27 | 28 | bool ImGui::ClampingDragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* display_format) { 29 | int start = *v; 30 | DragInt(label, v, v_speed, v_min, v_max, display_format); 31 | *v = clampi(*v, v_min, v_max); 32 | return start != *v; 33 | } 34 | 35 | void ImGui::HexColorEdit(const char* label, std::string* color) { 36 | int num = std::stoi(*color, 0, 16); 37 | int red = num / 0x10000; 38 | int green = (num / 0x100) % 0x100; 39 | int blue = num % 0x100; 40 | float col[4] = { red / 255.f, green / 255.f, blue / 255.f, 1.0 }; 41 | if (ImGui::ColorEdit3(label, col, ImGuiColorEditFlags_NoInputs)) { 42 | std::stringstream stm; 43 | stm << std::uppercase << std::setfill('0') << std::setw(2) << std::hex << static_cast(col[0] * 255.f); 44 | stm << std::uppercase << std::setfill('0') << std::setw(2) << std::hex << static_cast(col[1] * 255.f); 45 | stm << std::uppercase << std::setfill('0') << std::setw(2) << std::hex << static_cast(col[2] * 255.f); 46 | *color = stm.str(); 47 | } 48 | if (ImGui::IsItemHovered()) 49 | ImGui::SetTooltip("Color:\n(%.2f,%.2f,%.2f)\n#%s", col[0], col[1], col[2], color->c_str()); 50 | } 51 | 52 | void ImGui::SameLineEnd(float offset_from_end_x) { 53 | const ImGuiStyle& style = GImGui->Style; 54 | SameLine(GImGui->CurrentWindow->Size.x - offset_from_end_x - (GImGui->CurrentWindow->ScrollbarY ? style.ScrollbarSize : 0), 0); 55 | } 56 | 57 | int ImGui::ReceiverCollapsible(int index, std::shared_ptr receiverOptions) { 58 | int returnFlags = 0; 59 | const ImGuiStyle& style = GImGui->Style; 60 | const float square_size = GImGui->FontSize; 61 | 62 | bool isOpen = false; 63 | std::string indexString = std::to_string(index); 64 | std::string deleteModalLabel = BuildLabel(langString(GW2_SCT::LanguageCategory::Receiver_Option_UI, GW2_SCT::LanguageKey::Delete_Confirmation_Title), "receiver-delete-modal", indexString); 65 | if (TreeNodeEx(BuildLabel("receiver-collapsible", std::to_string(index)).c_str(), ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_AllowItemOverlap | ImGuiTreeNodeFlags_NoAutoOpenOnLog)) { 66 | isOpen = true; 67 | SameLine(style.FramePadding.x * 3 + GImGui->FontSize); 68 | Text(receiverOptions->name.c_str()); 69 | SameLineEnd(2 * (square_size + style.FramePadding.y * 2) + style.ItemInnerSpacing.x); 70 | HexColorEdit(BuildLabel("receiver-color-picker", indexString).c_str(), &receiverOptions->color); 71 | SameLine(0, style.ItemInnerSpacing.x); 72 | PushStyleColor(ImGuiCol_Button, ImVec4(0.67f, 0.40f, 0.40f, 0.60f)); 73 | if (Button(BuildLabel("-", "receiver-delete-button", indexString).c_str(), ImVec2(square_size + style.FramePadding.y * 2, square_size + style.FramePadding.y * 2))) { 74 | OpenPopup(deleteModalLabel.c_str()); 75 | } 76 | PopStyleColor(); 77 | 78 | InputText(BuildLabel(langString(GW2_SCT::LanguageCategory::Option_UI, GW2_SCT::LanguageKey::Receiver_Name), "receiver-name", indexString).c_str(), &receiverOptions->name); 79 | 80 | if (BeginCombo(BuildLabel(langString(GW2_SCT::LanguageCategory::Option_UI, GW2_SCT::LanguageKey::Messages_Category), "receiver-category-combo", indexString).c_str(), GW2_SCT::categoryNames.at(receiverOptions->category).c_str())) { 81 | int categoryIterator = 0; 82 | for (auto& categoryAndNamePair : GW2_SCT::categoryNames) { 83 | if (Selectable(BuildLabel(categoryAndNamePair.second, "receiver-category-combo", indexString + std::to_string(categoryIterator)).c_str(), receiverOptions->category == categoryAndNamePair.first)) { 84 | receiverOptions->category = categoryAndNamePair.first; 85 | } 86 | categoryIterator++; 87 | } 88 | EndCombo(); 89 | } 90 | if (BeginCombo(BuildLabel(langString(GW2_SCT::LanguageCategory::Option_UI, GW2_SCT::LanguageKey::Messages_Category), "receiver-type-combo", indexString).c_str(), GW2_SCT::typeNames.at(receiverOptions->type).c_str())) { 91 | int typeIterator = 0; 92 | for (auto& typeAndNamePair : GW2_SCT::typeNames) { 93 | if (Selectable(BuildLabel(typeAndNamePair.second, "receiver-type-combo", indexString + std::to_string(typeIterator)).c_str(), receiverOptions->type == typeAndNamePair.first)) { 94 | receiverOptions->type = typeAndNamePair.first; 95 | } 96 | typeIterator++; 97 | } 98 | EndCombo(); 99 | } 100 | 101 | { 102 | std::string edit = receiverOptions->outputTemplate; 103 | struct UserData { 104 | bool changedBG = false; 105 | std::map options; 106 | } ud; 107 | ud.options = GW2_SCT::receiverInformationPerCategoryAndType.at(receiverOptions->category).at(receiverOptions->type).options; 108 | if (InputText(BuildLabel(langString(GW2_SCT::LanguageCategory::Receiver_Option_UI, GW2_SCT::LanguageKey::Template), "receiver-template-input", indexString).c_str(), &edit, ImGuiInputTextFlags_CallbackAlways, [](ImGuiInputTextCallbackData* data) { 109 | UserData* d = static_cast(data->UserData); 110 | if (!GW2_SCT::TemplateInterpreter::validate(std::string(data->Buf), d->options)) { //validate here 111 | PushStyleColor(ImGuiCol_FrameBg, ImVec4(1.f, 0.f, 0.f, .6f)); 112 | d->changedBG = true; 113 | } 114 | return 0; 115 | }, &ud)) { 116 | if (!ud.changedBG) { 117 | receiverOptions->outputTemplate = edit; 118 | } 119 | } 120 | if (IsItemHovered() && ud.options.size() > 0) { 121 | std::stringstream stb; 122 | stb << langString(GW2_SCT::LanguageCategory::Receiver_Option_UI, GW2_SCT::LanguageKey::Available_Parameters) << ":"; 123 | for (std::pair i : ud.options) { 124 | stb << "\n%%" << i.first << "\t" << i.second; 125 | } 126 | BeginTooltip(); 127 | Text(stb.str().c_str()); 128 | EndTooltip(); 129 | } 130 | if (ud.changedBG) { 131 | PopStyleColor(); 132 | } 133 | 134 | Combo(langStringG(GW2_SCT::LanguageKey::Font), &receiverOptions->font, GW2_SCT::Options::getFontSelectionString().c_str()); 135 | int selected = 2; 136 | if (receiverOptions->fontSize < 0) { 137 | if (floatEqual(receiverOptions->fontSize, -1.f)) selected = 0; 138 | else if (floatEqual(receiverOptions->fontSize, -2.f)) selected = 1; 139 | } 140 | if (Combo(langStringG(GW2_SCT::LanguageKey::Font_Size_Type), &selected, GW2_SCT::Options::getFontSizeTypeSelectionString().c_str())) { 141 | switch (selected) 142 | { 143 | case 0: 144 | receiverOptions->fontSize = -1.f; 145 | break; 146 | case 1: 147 | receiverOptions->fontSize = -2.f; 148 | break; 149 | case 2: 150 | receiverOptions->fontSize = receiverOptions->type == GW2_SCT::MessageType::CRIT ? GW2_SCT::Options::get()->defaultCritFontSize : GW2_SCT::Options::get()->defaultFontSize; 151 | break; 152 | } 153 | } 154 | if (selected == 2) { 155 | DragFloat(langStringG(GW2_SCT::LanguageKey::Font_Size), &receiverOptions->fontSize, 1.f, 5.f, 100.f); 156 | } 157 | else { 158 | float f = (selected == 0) ? GW2_SCT::Options::get()->defaultFontSize : GW2_SCT::Options::get()->defaultCritFontSize; 159 | PushStyleVar(ImGuiStyleVar_Alpha, style.Alpha * 0.5f); 160 | ClampingDragFloat(langStringG(GW2_SCT::LanguageKey::Font_Size), &f, 0.f, f, f); 161 | PopStyleVar(); 162 | } 163 | } 164 | TreePop(); 165 | } 166 | if (!isOpen) { 167 | SameLine(style.FramePadding.x * 3 + GImGui->FontSize); 168 | Text(receiverOptions->name.c_str()); 169 | SameLineEnd(2 * (square_size + style.FramePadding.y * 2) + style.ItemInnerSpacing.x); 170 | HexColorEdit(BuildLabel("receiver-color-picker", indexString).c_str(), &receiverOptions->color); 171 | SameLine(0, style.ItemInnerSpacing.x); 172 | PushStyleColor(ImGuiCol_Button, ImVec4(0.67f, 0.40f, 0.40f, 0.60f)); 173 | if (Button(BuildLabel("-", "receiver-delete-button", indexString).c_str(), ImVec2(square_size + style.FramePadding.y * 2, square_size + style.FramePadding.y * 2))) { 174 | OpenPopup(deleteModalLabel.c_str()); 175 | } 176 | PopStyleColor(); 177 | } 178 | 179 | if (ImGui::BeginPopupModal(deleteModalLabel.c_str())) { 180 | ImGui::Text(langString(GW2_SCT::LanguageCategory::Scroll_Area_Option_UI, GW2_SCT::LanguageKey::Delete_Confirmation_Content)); 181 | ImGui::Separator(); 182 | if (ImGui::Button(langString(GW2_SCT::LanguageCategory::Scroll_Area_Option_UI, GW2_SCT::LanguageKey::Delete_Confirmation_Confirmation), ImVec2(120, 0))) { 183 | returnFlags |= ReceiverCollapsible_Remove; 184 | ImGui::CloseCurrentPopup(); 185 | } 186 | ImGui::SameLine(); 187 | if (ImGui::Button(langString(GW2_SCT::LanguageCategory::Scroll_Area_Option_UI, GW2_SCT::LanguageKey::Delete_Confirmation_Cancel), ImVec2(120, 0))) { 188 | ImGui::CloseCurrentPopup(); 189 | } 190 | ImGui::EndPopup(); 191 | } 192 | 193 | return returnFlags; 194 | } 195 | 196 | bool ImGui::NewReceiverLine(GW2_SCT::MessageCategory* categoryOut, GW2_SCT::MessageType* typeOut) { 197 | const ImGuiStyle& style = GImGui->Style; 198 | const float button_size = GImGui->FontSize + style.FramePadding.y * 2; 199 | const float item_width = (GImGui->CurrentWindow->Size.x - 1 * button_size - 2 * style.ItemInnerSpacing.x - (GImGui->CurrentWindow->ScrollbarY ? style.ScrollbarSize : 0)) / 2; 200 | PushItemWidth(item_width); 201 | 202 | if (BeginCombo("##new-receiver-category-select", GW2_SCT::categoryNames.at(*categoryOut).c_str())) { 203 | int categoryIterator = 0; 204 | for (auto& categoryAndNamePair : GW2_SCT::categoryNames) { 205 | if (Selectable(BuildLabel(categoryAndNamePair.second, "new-receiver-category-select", categoryIterator).c_str(), *categoryOut == categoryAndNamePair.first)) { 206 | *categoryOut = categoryAndNamePair.first; 207 | } 208 | categoryIterator++; 209 | } 210 | EndCombo(); 211 | } 212 | SameLine(); 213 | if (BeginCombo("##new-receiver-type-select", GW2_SCT::typeNames.at(*typeOut).c_str())) { 214 | int typeIterator = 0; 215 | for (auto& typeAndNamePair : GW2_SCT::typeNames) { 216 | if (Selectable(BuildLabel(typeAndNamePair.second, "new-receiver-type-select", typeIterator).c_str(), *typeOut == typeAndNamePair.first)) { 217 | *typeOut = typeAndNamePair.first; 218 | } 219 | } 220 | EndCombo(); 221 | } 222 | PopItemWidth(); 223 | SameLineEnd(button_size); 224 | bool ret = false; 225 | PushStyleColor(ImGuiCol_Button, ImVec4(0.67f, 0.40f, 0.40f, 0.60f)); 226 | if (Button("+", ImVec2(button_size, button_size))) { 227 | ret = true; 228 | } 229 | PopStyleColor(); 230 | SetCursorPosX(GetCursorPosX() + style.ItemInnerSpacing.x); 231 | Text(langString(GW2_SCT::LanguageCategory::Option_UI, GW2_SCT::LanguageKey::Messages_Category)); 232 | SameLine(item_width + style.ItemInnerSpacing.x); 233 | Text(langString(GW2_SCT::LanguageCategory::Option_UI, GW2_SCT::LanguageKey::Messages_Type)); 234 | return ret; 235 | } 236 | 237 | int ImGui::FilterOptionLine(uint32_t i, GW2_SCT::filter_options_struct* opt) { 238 | ImGuiWindow* window = GetCurrentWindow(); 239 | if (window->SkipItems) 240 | return false; 241 | std::string iStr = std::to_string(i); 242 | std::string labelStr = "##filter-id-line-" + iStr; 243 | const char* label = labelStr.c_str(); 244 | 245 | ImGuiContext& g = *GImGui; 246 | const ImGuiStyle& style = g.Style; 247 | const float square_size = g.FontSize; 248 | const float available_size = window->Size.x - (square_size + style.FramePadding.y * 2) - (window->ScrollbarY ? style.ScrollbarSize : 0) - 2 * style.WindowPadding.x; 249 | int value_changed = 0; 250 | 251 | BeginGroup(); 252 | PushID(label); 253 | 254 | int typeInt = filterTypeToInt(opt->type); 255 | ImGui::PushItemWidth((available_size - style.ItemInnerSpacing.x) * 0.3f); 256 | if (Combo(("##filter-" + iStr + "-type").c_str(), &typeInt, GW2_SCT::Options::getSkillFilterTypeSelectionString().c_str())) { 257 | opt->type = GW2_SCT::intToFilterType(typeInt); 258 | } 259 | ImGui::PopItemWidth(); 260 | SameLine((available_size - style.ItemInnerSpacing.x) * 0.3f, style.ItemInnerSpacing.x); 261 | ImGui::PushItemWidth((available_size - style.ItemInnerSpacing.x) * 0.7f); 262 | if (opt->type == GW2_SCT::FilterType::SKILL_ID) { 263 | std::vector arr(512); 264 | std::string idStr = std::to_string(opt->skillId); 265 | std::copy(idStr.begin(), idStr.size() < 512 ? idStr.end() : idStr.begin() + 512, arr.begin()); 266 | 267 | struct UserData { 268 | bool changedBG = false; 269 | } ud; 270 | 271 | if (InputText(("##filter-" + iStr + "-id").c_str(), arr.data(), 512, ImGuiInputTextFlags_CallbackAlways, [](ImGuiInputTextCallbackData* data) { 272 | UserData* d = static_cast(data->UserData); 273 | std::string buf(data->Buf); 274 | if (buf.empty() || buf.find_first_not_of("0123456789") != std::string::npos) { //validate here 275 | PushStyleColor(ImGuiCol_FrameBg, ImVec4(1.f, 0.f, 0.f, .6f)); 276 | d->changedBG = true; 277 | } 278 | return 0; 279 | }, &ud)) { 280 | if (!ud.changedBG) { 281 | opt->skillId = stoi(std::string(arr.data())); 282 | value_changed = FilterOptionLine_Value; 283 | } 284 | } 285 | if (ud.changedBG) { 286 | PopStyleColor(); 287 | } 288 | } else if (opt->type == GW2_SCT::FilterType::SKILL_NAME) { 289 | std::vector arr(512); 290 | std::string idStr(opt->skillName); 291 | std::copy(idStr.begin(), idStr.size() < 512 ? idStr.end() : idStr.begin() + 512, arr.begin()); 292 | 293 | if (InputText(("##filter-" + iStr + "-skillname").c_str(), arr.data(), 512)) { 294 | opt->skillName = std::string(arr.data()); 295 | value_changed = FilterOptionLine_Value; 296 | } 297 | } 298 | ImGui::PopItemWidth(); 299 | 300 | SameLine(available_size, style.ItemInnerSpacing.x); 301 | 302 | PushStyleColor(ImGuiCol_Button, ImVec4(0.67f, 0.40f, 0.40f, 0.60f)); 303 | if (Button("-", ImVec2(square_size + style.FramePadding.y * 2, square_size + style.FramePadding.y * 2))) { 304 | OpenPopup(langString(GW2_SCT::LanguageCategory::Skill_Filter_Option_UI, GW2_SCT::LanguageKey::Delete_Confirmation_Title)); 305 | } 306 | PopStyleColor(); 307 | 308 | if (BeginPopupModal(langString(GW2_SCT::LanguageCategory::Skill_Filter_Option_UI, GW2_SCT::LanguageKey::Delete_Confirmation_Title))) { 309 | Text(langString(GW2_SCT::LanguageCategory::Skill_Filter_Option_UI, GW2_SCT::LanguageKey::Delete_Confirmation_Content)); 310 | Separator(); 311 | if (Button(langString(GW2_SCT::LanguageCategory::Skill_Filter_Option_UI, GW2_SCT::LanguageKey::Delete_Confirmation_Confirmation), ImVec2(120, 0))) { 312 | value_changed |= FilterOptionLine_Remove; 313 | CloseCurrentPopup(); 314 | } 315 | SameLine(); 316 | if (Button(langString(GW2_SCT::LanguageCategory::Skill_Filter_Option_UI, GW2_SCT::LanguageKey::Delete_Confirmation_Cancel), ImVec2(120, 0))) { 317 | CloseCurrentPopup(); 318 | } 319 | EndPopup(); 320 | } 321 | 322 | PopID(); 323 | EndGroup(); 324 | 325 | return value_changed; 326 | } 327 | 328 | void ImGui::BeginDisabled() { 329 | ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); 330 | ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.6f, 0.6f, 1.0f)); 331 | } 332 | 333 | void ImGui::EndDisabled() { 334 | ImGui::PopStyleColor(); 335 | ImGui::PopItemFlag(); 336 | } 337 | 338 | bool ImGui::HasWindow() { 339 | if (GImGui == nullptr) return false; 340 | ImGuiWindow* window = GetCurrentWindowRead(); 341 | return window != nullptr; 342 | } 343 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * arcdps submodule scrolling combat text 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "SCTMain.h" 12 | #include 13 | #include "Language.h" 14 | #include "imgui_sct_widgets.h" 15 | 16 | #pragma comment(lib, "d3d11") 17 | 18 | /* proto/globals */ 19 | arcdps_exports arc_exports; 20 | void dll_init(HANDLE hModule); 21 | void dll_exit(); 22 | extern "C" __declspec(dllexport) void* get_init_addr(char* arcversion, ImGuiContext* imguictx, void* id3dptr, HANDLE arcdll, void* mallocfn, void* freefn, uint32_t d3dversion); 23 | extern "C" __declspec(dllexport) void* get_release_addr(); 24 | arcdps_exports* mod_init(); 25 | uintptr_t mod_release(); 26 | uintptr_t mod_wnd(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); 27 | uintptr_t mod_combat_area(cbtevent* ev, ag* src, ag* dst, char* skillname, uint64_t id, uint64_t revision); 28 | uintptr_t mod_combat_local(cbtevent* ev, ag* src, ag* dst, char* skillname, uint64_t id, uint64_t revision); 29 | uintptr_t mod_imgui(); 30 | uintptr_t mod_options(); 31 | 32 | GW2_SCT::SCTMain* sct; 33 | bool isArcdpsOutdated = false; 34 | 35 | /* dll main -- winapi */ 36 | BOOL APIENTRY DllMain(HANDLE hModule, DWORD ulReasonForCall, LPVOID lpReserved) { 37 | switch (ulReasonForCall) { 38 | case DLL_PROCESS_ATTACH: dll_init(hModule); break; 39 | case DLL_PROCESS_DETACH: dll_exit(); break; 40 | 41 | case DLL_THREAD_ATTACH: break; 42 | case DLL_THREAD_DETACH: break; 43 | } 44 | return 1; 45 | } 46 | 47 | /* dll attach -- from winapi */ 48 | void dll_init(HANDLE hModule) { 49 | sct = new GW2_SCT::SCTMain(); 50 | return; 51 | } 52 | 53 | /* dll detach -- from winapi */ 54 | void dll_exit() { 55 | delete sct; 56 | return; 57 | } 58 | 59 | /* export -- arcdps looks for this exported function and calls the address it returns on client load */ 60 | extern "C" __declspec(dllexport) void* get_init_addr(char* arcversion, ImGuiContext* imguictx, void* id3dptr, HANDLE arcdll, void* mallocfn, void* freefn, uint32_t d3dversion) { 61 | arcvers = arcversion; 62 | ImGui::SetCurrentContext((ImGuiContext*)imguictx); 63 | ImGui::SetAllocatorFunctions((void *(*)(size_t, void*))mallocfn, (void(*)(void*, void*))freefn); // on imgui 1.80+ 64 | GW2_SCT::d3dversion = d3dversion; 65 | 66 | std::string arcversString(arcvers); 67 | arcversString = arcversString.substr(0, arcversString.find_first_of('.')); 68 | std::string arcreqString(ARC_VERSION_REQUIRED); 69 | arcreqString = arcreqString.substr(0, arcreqString.find_first_of('.')); 70 | isArcdpsOutdated = arcversString.compare(arcreqString) < 0; 71 | 72 | if (d3dversion == 11) { 73 | GW2_SCT::d3d11SwapChain = (IDXGISwapChain*)id3dptr; 74 | ID3D11Device* d3Device11; 75 | ((IDXGISwapChain*)id3dptr)->GetDevice(__uuidof(ID3D11Device), (void**)&d3Device11); 76 | if (d3Device11 != nullptr) { 77 | ID3D11DeviceContext* d3D11Context; 78 | d3Device11->GetImmediateContext(&d3D11Context); 79 | GW2_SCT::d3Device11 = d3Device11; 80 | GW2_SCT::d3D11Context = d3D11Context; 81 | } 82 | } 83 | return mod_init; 84 | } 85 | 86 | /* export -- arcdps looks for this exported function and calls the address it returns */ 87 | extern "C" __declspec(dllexport) void* get_release_addr() { 88 | arcvers = 0; 89 | return mod_release; 90 | } 91 | 92 | arcdps_exports* mod_init() { 93 | #ifdef _DEBUG 94 | //AllocConsole(); 95 | debug_console_hnd = GetStdHandle(STD_OUTPUT_HANDLE); 96 | #endif 97 | arcdps_exports* ret = sct->Init(arcvers, mod_wnd, mod_combat_area, mod_imgui, mod_options, mod_combat_local); 98 | return ret; 99 | } 100 | 101 | uintptr_t mod_release() { 102 | return sct->Release(); 103 | } 104 | 105 | /* window callback -- return is assigned to umsg (return zero to not be processed by arcdps or game) */ 106 | uintptr_t mod_wnd(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { 107 | return sct->WindowUpdate(hWnd, uMsg, wParam, lParam); 108 | } 109 | 110 | uintptr_t mod_imgui() { 111 | if (!ImGui::HasWindow()) return 0; 112 | if (isArcdpsOutdated) { 113 | ImGui::OpenPopup(langStringG(GW2_SCT::LanguageKey::Outdated_Popup_Title)); 114 | } 115 | if (ImGui::BeginPopupModal(langStringG(GW2_SCT::LanguageKey::Outdated_Popup_Title))) { 116 | std::string s = std::string(langStringG(GW2_SCT::LanguageKey::Outdated_Popup_Content)) + "\n\n" + std::string(langStringG(GW2_SCT::LanguageKey::Outdated_Popup_Found_Version)) + ": " + std::string(arcvers) + "\n" + std::string(langStringG(GW2_SCT::LanguageKey::Outdated_Popup_Required_Version)) + ": " + ARC_VERSION_REQUIRED; 117 | ImGui::Text(s.c_str()); 118 | ImGui::Separator(); 119 | if (ImGui::Button(langStringG(GW2_SCT::LanguageKey::Outdated_Popup_Confirmation), ImVec2(120, 0))) { 120 | isArcdpsOutdated = false; 121 | ImGui::CloseCurrentPopup(); 122 | } 123 | ImGui::EndPopup(); 124 | } 125 | return sct->UIUpdate(); 126 | } 127 | 128 | uintptr_t mod_options() { 129 | return sct->UIOptions(); 130 | } 131 | 132 | /* combat callback -- may be called asynchronously. return ignored */ 133 | uintptr_t mod_combat_area(cbtevent* ev, ag* src, ag* dst, char* skillname, uint64_t id, uint64_t revision) { 134 | return sct->CombatEventArea(ev, src, dst, skillname, id, revision); 135 | } 136 | 137 | /* combat callback -- may be called asynchronously. return ignored */ 138 | uintptr_t mod_combat_local(cbtevent* ev, ag* src, ag* dst, char* skillname, uint64_t id, uint64_t revision) { 139 | return sct->CombatEventLocal(ev, src, dst, skillname, id, revision); 140 | } 141 | -------------------------------------------------------------------------------- /template.vcxproj.user.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @DEBUG_COMMAND@ 5 | @DEBUG_COMMAND_ARGUMENTS@ 6 | @DEBUG_ENVIRONMENT@ 7 | WindowsLocalDebugger 8 | 9 | --------------------------------------------------------------------------------