diff options
438 files changed, 6538 insertions, 8368 deletions
diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index 779491ae..1c124dd0 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -46,6 +46,7 @@ jobs: steps: - uses: actions/checkout@v4 with: + fetch-depth: 0 # required to generate the version from git tags submodules: true - uses: snapcore/action-build@v1 diff --git a/CMakeLists.txt b/CMakeLists.txt index 11cdaf03..64221f96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -147,7 +147,8 @@ if(WIN32) $ENV{MINGW_PREFIX}/bin/libaom.dll $ENV{MINGW_PREFIX}/bin/libdav1d-7.dll $ENV{MINGW_PREFIX}/bin/libsharpyuv-0.dll - $ENV{MINGW_PREFIX}/bin/libSvtAv1Enc-2.dll + $ENV{MINGW_PREFIX}/bin/libSvtAv1Enc-3.dll + $ENV{MINGW_PREFIX}/bin/libFLAC.dll $ENV{MINGW_PREFIX}/bin/libyuv.dll $ENV{MINGW_PREFIX}/bin/libbrotlienc.dll $ENV{MINGW_PREFIX}/bin/libhwy.dll @@ -156,14 +157,17 @@ if(WIN32) $ENV{MINGW_PREFIX}/bin/libjbig-0.dll $ENV{MINGW_PREFIX}/bin/libLerc.dll $ENV{MINGW_PREFIX}/bin/libopus-0.dll - $ENV{MINGW_PREFIX}/bin/rav1e.dll + $ENV{MINGW_PREFIX}/bin/librav1e.dll $ENV{MINGW_PREFIX}/bin/libbz2-1.dll $ENV{MINGW_PREFIX}/bin/libglib-2.0-0.dll $ENV{MINGW_PREFIX}/bin/libogg-0.dll + $ENV{MINGW_PREFIX}/bin/libvorbisfile-3.dll + $ENV{MINGW_PREFIX}/bin/libvorbis-0.dll + $ENV{MINGW_PREFIX}/bin/libwavpack-1.dll $ENV{MINGW_PREFIX}/bin/libgraphite2.dll $ENV{MINGW_PREFIX}/bin/liblcms2-2.dll $ENV{MINGW_PREFIX}/bin/libpcre2-8-0.dll - $ENV{MINGW_PREFIX}/bin/libmodplug-1.dll + $ENV{MINGW_PREFIX}/bin/libxmp.dll DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() include(CPack) @@ -3,12 +3,15 @@ - Added VSync and windowed fullscreen options - Added support for scaling the graphics - Added ability to open external links in news, chat and NPC dialogs +- Added ability to mention assigned keys in NPC dialogs - Added support for text formatting to NPC dialogs - Added support for reading most client-data settings from settings.xml - Added support for XML includes, both absolute and relative - Added support for map/layer mask +- Added support for item sprite replacements - Added support for particle effects on equipment - Added support for hit/miss sounds on equipment for all players +- Added support for players changing into monsters or NPCs - Added online player list to Social window - Added notification sound on receiving whisper - Added default ports when connecting to a custom server @@ -56,6 +59,8 @@ - Fixed empty Equipment window on freshly created character - Fixed choosing default world when using -D command-line parameter - Fixed storing of player relations +- Fixed handling of custom port in update URL +- Fixed stutter when new music starts playing - Updated to tmwAthena protocol changes - Updated to Manaserv protocol changes (specials, guilds, debug mode, skills, text particles) - CMake: Use GNUInstallDirs and made PKG_DATADIR / PKG_BINDIR paths modifiable diff --git a/data/graphics/gui/button-icon-specials.png b/data/graphics/gui/button-icon-abilities.png Binary files differindex 31c66e81..31c66e81 100644 --- a/data/graphics/gui/button-icon-specials.png +++ b/data/graphics/gui/button-icon-abilities.png diff --git a/data/graphics/gui/colors.xml b/data/graphics/gui/colors.xml deleted file mode 100644 index 4a35d081..00000000 --- a/data/graphics/gui/colors.xml +++ /dev/null @@ -1,50 +0,0 @@ -<colors> - <color id="TEXT" color="#000000" /> - <color id="SHADOW" color="#000000" /> - <color id="OUTLINE" color="#000000" /> - <color id="PROGRESS_BAR" color="#ffffff" /> - <color id="BUTTON" color="#000000" /> - <color id="BUTTON_DISABLED" color="#333333" /> - <color id="TAB" color="#000000" /> - <color id="PARTY_CHAT_TAB" color="#f48055" /> - <color id="PARTY_SOCIAL_TAB" color="#ff00d8" /> - <color id="BACKGROUND" color="#ffffff" /> - <color id="HIGHLIGHT" color="#c0c0c0" /> - <color id="TAB_FLASH" color="#ff0000" effect="pulse" /> - <color id="SHOP_WARNING" color="#910000" /> - <color id="ITEM_EQUIPPED" color="#000091" /> - <color id="CHAT" color="#000000" /> - <color id="GM" color="#ff0000" /> - <color id="PLAYER" color="#1fa052" /> - <color id="WHISPER" color="#0000ff" /> - <color id="IS" color="#a08527" /> - <color id="PARTY" color="#ff00d8" /> - <color id="GUILD" color="#ff00d8" /> - <color id="SERVER" color="#8415e2" /> - <color id="LOGGER" color="#919191" /> - <color id="HYPERLINK" color="#e50d0d" /> - <color id="UNKNOWN_ITEM" color="#000000" /> - <color id="GENERIC" color="#21a5b1" /> - <color id="HEAD" color="#527fa4" /> - <color id="USABLE" color="#268d24" /> - <color id="TORSO" color="#d12aa4" /> - <color id="ONEHAND" color="#f42a2a" /> - <color id="LEGS" color="#699900" /> - <color id="FEET" color="#aa1d48" /> - <color id="TWOHAND" color="#f46d0e" /> - <color id="SHIELD" color="#9c2424" /> - <color id="RING" color="#0000ff" /> - <color id="NECKLACE" color="#ff00ff" /> - <color id="ARMS" color="#9c24e8" /> - <color id="AMMO" color="#8b6311" /> - <color id="SERVER_VERSION_NOT_SUPPORTED" color="#DC0000" /> - - <progressbar id="DEFAULT" color="#969696" /> - <progressbar id="HP" color="#ff0000,e28000,c38948,0f6a20" /> - <progressbar id="MP" color="#1a66e6" /> - <progressbar id="NO_MP" color="#646464" /> - <progressbar id="EXP" color="#8fc0d3" /> - <progressbar id="INVY_SLOTS" color="#e1c819" /> - <progressbar id="WEIGHT" color="#0000ff,ffff00,ff0000" /> - <progressbar id="JOB" color="#e187cb" /> -</colors> diff --git a/data/graphics/gui/resize.png b/data/graphics/gui/resize.png Binary files differindex 6b31ac64..61cb2cc2 100644 --- a/data/graphics/gui/resize.png +++ b/data/graphics/gui/resize.png diff --git a/data/graphics/gui/speechbubble.xml b/data/graphics/gui/speechbubble.xml deleted file mode 100644 index 84b6557b..00000000 --- a/data/graphics/gui/speechbubble.xml +++ /dev/null @@ -1,18 +0,0 @@ -<skinset name="SpeechBubble" image="bubble.png"> - <widget type="Window"> - <!-- Top Row --> - <part type="top-left-corner" xpos="0" ypos="0" width="5" height="5" /> - <part type="top-edge" xpos="5" ypos="0" width="5" height="5" /> - <part type="top-right-corner" xpos="10" ypos="0" width="5" height="5" /> - - <!-- Middle Row --> - <part type="left-edge" xpos="0" ypos="5" width="5" height="5" /> - <part type="bg-quad" xpos="5" ypos="5" width="5" height="5" /> - <part type="right-edge" xpos="10" ypos="5" width="5" height="5" /> - - <!-- Bottom Row --> - <part type="bottom-left-corner" xpos="0" ypos="10" width="5" height="5" /> - <part type="bottom-edge" xpos="5" ypos="10" width="5" height="5" /> - <part type="bottom-right-corner" xpos="10" ypos="10" width="5" height="5" /> - </widget> -</skinset> diff --git a/data/graphics/gui/theme.xml b/data/graphics/gui/theme.xml new file mode 100644 index 00000000..b0484c45 --- /dev/null +++ b/data/graphics/gui/theme.xml @@ -0,0 +1,280 @@ +<theme name="Mana"> + <color id="TEXT" color="#000000" /> + <color id="SHADOW" color="#000000" /> + <color id="OUTLINE" color="#000000" /> + <color id="PARTY_CHAT_TAB" color="#f48055" /> + <color id="PARTY_SOCIAL_TAB" color="#ff00d8" /> + <color id="BACKGROUND" color="#ffffff" /> + <color id="HIGHLIGHT" color="#c0c0c0" /> + <color id="TAB_FLASH" color="#ff0000" effect="pulse" /> + <color id="SHOP_WARNING" color="#910000" /> + <color id="ITEM_EQUIPPED" color="#000091" /> + <color id="CHAT" color="#000000" /> + <color id="BUBBLE_TEXT" color="#ffffff" /> + <color id="GM" color="#ff0000" /> + <color id="PLAYER" color="#1fa052" /> + <color id="WHISPER" color="#0000ff" /> + <color id="IS" color="#a08527" /> + <color id="PARTY" color="#ff00d8" /> + <color id="GUILD" color="#ff00d8" /> + <color id="SERVER" color="#8415e2" /> + <color id="LOGGER" color="#919191" /> + <color id="HYPERLINK" color="#e50d0d" /> + <color id="UNKNOWN_ITEM" color="#000000" /> + <color id="GENERIC" color="#21a5b1" /> + <color id="HEAD" color="#527fa4" /> + <color id="USABLE" color="#268d24" /> + <color id="TORSO" color="#d12aa4" /> + <color id="ONEHAND" color="#f42a2a" /> + <color id="LEGS" color="#699900" /> + <color id="FEET" color="#aa1d48" /> + <color id="TWOHAND" color="#f46d0e" /> + <color id="SHIELD" color="#9c2424" /> + <color id="RING" color="#0000ff" /> + <color id="NECKLACE" color="#ff00ff" /> + <color id="ARMS" color="#9c24e8" /> + <color id="AMMO" color="#8b6311" /> + <color id="SERVER_VERSION_NOT_SUPPORTED" color="#DC0000" /> + + <progressbar id="DEFAULT" color="#969696" /> + <progressbar id="HP" color="#ff0000,e28000,c38948,0f6a20" /> + <progressbar id="MP" color="#1a66e6" /> + <progressbar id="NO_MP" color="#646464" /> + <progressbar id="EXP" color="#8fc0d3" /> + <progressbar id="INVY_SLOTS" color="#e1c819" /> + <progressbar id="WEIGHT" color="#0000ff,ffff00,ff0000" /> + <progressbar id="JOB" color="#e187cb" /> + + <skin type="Window" frameSize="0" padding="3" titleBarHeight="20" titleOffsetX="7" titleOffsetY="5"> + <state> + <img src="window.png" left="4" right="4" top="4" bottom="4" fill="repeat" /> + </state> + </skin> + + <skin type="ResizeGrip" padding="3"> + <state> + <img src="resize.png" /> + </state> + </skin> + + <skin type="Popup" padding="6"> + <state> + <img src="window.png" left="4" right="4" top="4" bottom="4" fill="repeat" /> + </state> + </skin> + + <skin type="SpeechBubble"> + <state> + <img src="bubble.png|W:#000000" left="5" right="5" top="5" bottom="5" height="15" /> + </state> + </skin> + + <skin type="Button" padding="4"> + <state disabled="true"> + <text color="#333333" /> + <img src="button_disabled.png" left="10" right="10" top="5" bottom="5" /> + </state> + <state selected="true"> + <img src="buttonpress.png" left="10" right="10" top="5" bottom="5" /> + </state> + <state hovered="true"> + <img src="buttonhi.png" left="10" right="10" top="5" bottom="5" /> + </state> + <state> + <img src="button.png" left="10" right="10" top="5" bottom="5" /> + </state> + </skin> + + <skin type="Tab" padding="4"> + <state selected="true"> + <img src="tabselected.png" left="10" right="10" top="5" bottom="9" /> + </state> + <state hovered="true"> + <img src="tab_hilight.png" left="10" right="10" top="14" bottom="2" /> + </state> + <state> + <img src="tab.png" left="10" right="10" top="14" bottom="2" /> + </state> + </skin> + + <skin type="CheckBox" padding="3" spacing="4"> + <state disabled="true" selected="true"> + <img src="checkbox.png" x="27" y="0" width="9" height="10" offsetX="2" offsetY="5" /> + </state> + <state disabled="true"> + <img src="checkbox.png" x="18" y="0" width="9" height="10" offsetX="2" offsetY="5" /> + </state> + <state selected="true" hovered="true"> + <img src="checkbox.png" x="45" y="0" width="9" height="10" offsetX="2" offsetY="5" /> + </state> + <state hovered="true"> + <img src="checkbox.png" x="36" y="0" width="9" height="10" offsetX="2" offsetY="5" /> + </state> + <state selected="true"> + <img src="checkbox.png" x="9" y="0" width="9" height="10" offsetX="2" offsetY="5" /> + </state> + <state> + <img src="checkbox.png" x="0" y="0" width="9" height="10" offsetX="2" offsetY="5" /> + </state> + </skin> + + <skin type="RadioButton" padding="3" spacing="4"> + <state hovered="true" selected="true"> + <img src="radioin_highlight.png" offsetX="2" offsetY="5" /> + </state> + <state hovered="true"> + <img src="radioout_highlight.png" offsetX="2" offsetY="5" /> + </state> + <state selected="true"> + <img src="radioin.png" offsetX="2" offsetY="5" /> + </state> + <state> + <img src="radioout.png" offsetX="2" offsetY="5" /> + </state> + </skin> + + <skin type="TextField" frameSize="2" padding="2"> + <state> + <img src="deepbox.png" left="4" right="4" top="4" bottom="4" /> + </state> + </skin> + + <skin type="ScrollArea" frameSize="2" padding="2"> + <state> + <img src="deepbox.png" left="4" right="4" top="4" bottom="4" /> + </state> + </skin> + + <skin type="DropDownFrame" frameSize="2" padding="1"> + <state> + <img src="deepbox.png" left="4" right="4" top="4" bottom="4" /> + </state> + </skin> + + <skin type="DropDownButton"> + <state selected="true" hovered="true"> + <img src="vscroll_up_pressed.png" offsetX="-2" offsetY="2" /> + </state> + <state selected="true"> + <img src="vscroll_up_default.png" offsetX="-2" offsetY="2" /> + </state> + <state hovered="true"> + <img src="vscroll_down_pressed.png" offsetX="-2" offsetY="2" /> + </state> + <state> + <img src="vscroll_down_default.png" offsetX="-2" offsetY="2" /> + </state> + </skin> + + <skin type="ScrollAreaVBar"> + <state> + <rect color="#000000" alpha="32" /> + </state> + </skin> + + <skin type="ScrollAreaHBar"> + <state> + <rect color="#000000" alpha="32" /> + </state> + </skin> + + <skin type="ScrollAreaHMarker"> + <state hovered="true"> + <img src="vscroll_highlight.png" left="4" right="4" top="4" bottom="4" fill="repeat" /> + </state> + <state> + <img src="vscroll_grey.png" left="4" right="4" top="4" bottom="4" fill="repeat" /> + </state> + </skin> + + <skin type="ScrollAreaVMarker"> + <state hovered="true"> + <img src="vscroll_highlight.png" left="4" right="4" top="4" bottom="4" fill="repeat" /> + </state> + <state> + <img src="vscroll_grey.png" left="4" right="4" top="4" bottom="4" fill="repeat" /> + </state> + </skin> + + <skin type="ProgressBar"> + <state> + <text color="#ffffff" outlineColor="#000000"/> + <img src="vscroll_grey.png" left="4" right="4" top="4" bottom="4" fill="repeat" /> + </state> + </skin> + + <skin type="Slider" padding="4"> + <state hovered="true"> + <img src="slider_hilight.png" height="6" left="4" right="4" top="6" offsetY="4" /> + </state> + <state> + <img src="slider.png" height="6" left="4" right="4" top="6" offsetY="4" /> + </state> + </skin> + + <skin type="SliderHandle"> + <state hovered="true"> + <img src="slider_hilight.png" x="6" y="8" width="9" height="10" offsetY="2" /> + </state> + <state> + <img src="slider.png" x="6" y="8" width="9" height="10" offsetY="2" /> + </state> + </skin> + + <skin type="ButtonUp"> + <state selected="true"> + <img src="vscroll_up_pressed.png" /> + </state> + <state> + <img src="vscroll_up_default.png" /> + </state> + </skin> + + <skin type="ButtonDown"> + <state selected="true"> + <img src="vscroll_down_pressed.png" /> + </state> + <state> + <img src="vscroll_down_default.png" /> + </state> + </skin> + + <skin type="ButtonLeft"> + <state selected="true"> + <img src="hscroll_left_pressed.png" /> + </state> + <state> + <img src="hscroll_left_default.png" /> + </state> + </skin> + + <skin type="ButtonRight"> + <state selected="true"> + <img src="hscroll_right_pressed.png" /> + </state> + <state> + <img src="hscroll_right_default.png" /> + </state> + </skin> + + <skin type="ButtonClose" padding="3"> + <state> + <img src="close_button.png" /> + </state> + </skin> + + <skin type="ButtonSticky" padding="3"> + <state selected="true"> + <img src="sticky_button.png" x="15" width="15" height="15" /> + </state> + <state> + <img src="sticky_button.png" width="15" height="15" /> + </state> + </skin> + + <skin type="ShortcutBox"> + <state> + <img src="item_shortcut_bgr.png" /> + </state> + </skin> +</theme> diff --git a/data/graphics/gui/window.xml b/data/graphics/gui/window.xml deleted file mode 100644 index f27dbc7f..00000000 --- a/data/graphics/gui/window.xml +++ /dev/null @@ -1,18 +0,0 @@ -<skinset name="Default" image="window.png"> - <widget type="Window"> - <!-- Top Row --> - <part type="top-left-corner" xpos="0" ypos="0" width="4" height="4" /> - <part type="top-edge" xpos="4" ypos="0" width="32" height="4" /> - <part type="top-right-corner" xpos="36" ypos="0" width="4" height="4" /> - - <!-- Middle Row --> - <part type="left-edge" xpos="0" ypos="4" width="4" height="216" /> - <part type="bg-quad" xpos="4" ypos="4" width="32" height="216" /> - <part type="right-edge" xpos="36" ypos="4" width="4" height="216" /> - - <!-- Bottom Row --> - <part type="bottom-left-corner" xpos="0" ypos="220" width="4" height="4" /> - <part type="bottom-edge" xpos="4" ypos="220" width="32" height="4" /> - <part type="bottom-right-corner" xpos="36" ypos="220" width="4" height="4" /> - </widget> -</skinset> diff --git a/libs/guichan b/libs/guichan -Subproject c13a939ed4d205f2686f8a555cd42fbeaa89366 +Subproject c30941892acb9334d43bb5290bf7e6d5942b91e diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 34e3d36c..711552fa 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,7 +1,6 @@ name: mana -version: git adopt-info: mana -base: core22 +base: core24 grade: stable confinement: strict @@ -32,9 +31,13 @@ parts: parse-info: - usr/share/metainfo/org.manasource.Mana.metainfo.xml source: . + override-pull: | + craftctl default + craftctl set version=$(git describe --tags | sed 's/v//') build-packages: - build-essential - gettext + - git - libcurl4-openssl-dev - libguichan-dev - libphysfs-dev diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d5d512e8..23505dcd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,6 @@ include(FindPkgConfig) pkg_check_modules(SDL2 REQUIRED - sdl2>=2.0.5 + sdl2>=2.0.10 SDL2_image SDL2_mixer SDL2_net @@ -266,8 +266,8 @@ set(SRCS gui/socialwindow.h gui/speechbubble.cpp gui/speechbubble.h - gui/specialswindow.cpp - gui/specialswindow.h + gui/abilitieswindow.cpp + gui/abilitieswindow.h gui/statuswindow.cpp gui/statuswindow.h gui/textdialog.cpp @@ -307,7 +307,7 @@ set(SRCS net/partyhandler.h net/playerhandler.h net/serverinfo.h - net/specialhandler.h + net/abilityhandler.h net/tradehandler.h net/worldinfo.h resources/action.cpp @@ -354,10 +354,12 @@ set(SRCS resources/settingsmanager.h resources/soundeffect.h resources/soundeffect.cpp - resources/specialdb.cpp - resources/specialdb.h + resources/abilitydb.cpp + resources/abilitydb.h resources/spritedef.h resources/spritedef.cpp + resources/statuseffectdb.cpp + resources/statuseffectdb.h resources/theme.cpp resources/theme.h resources/userpalette.cpp @@ -369,8 +371,12 @@ set(SRCS utils/copynpaste.cpp utils/copynpaste.h utils/dtor.h + utils/filesystem.h utils/gettext.h utils/mathutils.h + utils/mkdir.cpp + utils/mkdir.h + utils/mutex.h utils/path.cpp utils/path.h utils/physfsrwops.c @@ -383,9 +389,6 @@ set(SRCS utils/stringutils.h utils/time.cpp utils/time.h - utils/mutex.h - utils/mkdir.cpp - utils/mkdir.h utils/xml.cpp utils/xml.h utils/zlib.cpp @@ -396,8 +399,6 @@ set(SRCS actorsprite.h actorspritemanager.cpp actorspritemanager.h - animatedsprite.cpp - animatedsprite.h animationparticle.cpp animationparticle.h avatar.cpp @@ -440,8 +441,6 @@ set(SRCS guild.h imageparticle.cpp imageparticle.h - imagesprite.cpp - imagesprite.h inventory.cpp inventory.h item.cpp @@ -464,8 +463,6 @@ set(SRCS openglgraphics.h particle.cpp particle.h - particlecontainer.cpp - particlecontainer.h particleemitter.cpp particleemitter.h particleemitterprop.h @@ -488,6 +485,7 @@ set(SRCS simpleanimation.h sound.cpp sound.h + sprite.cpp sprite.h statuseffect.cpp statuseffect.h @@ -549,8 +547,8 @@ set(SRCS_TMWA net/tmwa/playerhandler.cpp net/tmwa/playerhandler.h net/tmwa/protocol.h - net/tmwa/specialhandler.cpp - net/tmwa/specialhandler.h + net/tmwa/abilityhandler.cpp + net/tmwa/abilityhandler.h net/tmwa/token.h net/tmwa/tradehandler.cpp net/tmwa/tradehandler.h) @@ -600,8 +598,8 @@ set(SRCS_MANA net/manaserv/playerhandler.cpp net/manaserv/playerhandler.h net/manaserv/manaserv_protocol.h - net/manaserv/specialhandler.cpp - net/manaserv/specialhandler.h + net/manaserv/abilityhandler.cpp + net/manaserv/abilityhandler.h net/manaserv/tradehandler.cpp net/manaserv/tradehandler.h) diff --git a/src/actor.h b/src/actor.h index cd0da7fb..a54fba0a 100644 --- a/src/actor.h +++ b/src/actor.h @@ -18,8 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef ACTOR_H -#define ACTOR_H +#pragma once #include "vector.h" @@ -49,20 +48,6 @@ public: virtual bool draw(Graphics *graphics, int offsetX, int offsetY) const = 0; /** - * Returns the horizontal size of the actors graphical representation - * in pixels or 0 when it is undefined. - */ - virtual int getWidth() const - { return 0; } - - /** - * Returns the vertical size of the actors graphical representation - * in pixels or 0 when it is undefined. - */ - virtual int getHeight() const - { return 0; } - - /** * Returns the pixel position of this actor. */ const Vector &getPosition() const @@ -131,5 +116,3 @@ protected: private: Actors::iterator mMapActor; }; - -#endif // ACTOR_H diff --git a/src/actorsprite.cpp b/src/actorsprite.cpp index 989de8d9..7cb46bd4 100644 --- a/src/actorsprite.cpp +++ b/src/actorsprite.cpp @@ -22,40 +22,34 @@ #include "configuration.h" #include "event.h" -#include "imagesprite.h" #include "localplayer.h" #include "log.h" +#include "particle.h" #include "simpleanimation.h" -#include "statuseffect.h" +#include "sprite.h" #include "resources/animation.h" -#include "resources/image.h" #include "resources/imageset.h" #include "resources/resourcemanager.h" #include "resources/theme.h" #include "utils/time.h" +#include <algorithm> #include <cassert> #define EFFECTS_FILE "effects.xml" -ImageSet *ActorSprite::targetCursorImages[2][NUM_TC]; +ResourceRef<ImageSet> ActorSprite::targetCursorImages[2][NUM_TC]; SimpleAnimation *ActorSprite::targetCursor[2][NUM_TC]; bool ActorSprite::loaded = false; -ActorSprite::ActorSprite(int id): - mId(id), - mStatusParticleEffects(&mStunParticleEffects, false), - mChildParticleEffects(&mStatusParticleEffects, false) +ActorSprite::ActorSprite(int id) + : mId(id) {} ActorSprite::~ActorSprite() { - setMap(nullptr); - - mUsedTargetCursor = nullptr; - // Notify listeners of the destruction. Event event(Event::Destroyed); event.setActor("source", this); @@ -64,13 +58,7 @@ ActorSprite::~ActorSprite() int ActorSprite::getDrawOrder() const { - int drawOrder = Actor::getDrawOrder(); - - // See note at ActorSprite::draw - if (mMap) - drawOrder += mMap->getTileHeight() / 2; - - return drawOrder; + return Actor::getDrawOrder() + paths.getIntValue("spriteOffsetY"); } bool ActorSprite::draw(Graphics *graphics, int offsetX, int offsetY) const @@ -81,47 +69,33 @@ bool ActorSprite::draw(Graphics *graphics, int offsetX, int offsetY) const if (mUsedTargetCursor) mUsedTargetCursor->draw(graphics, px, py); - // This is makes sure that actors positioned on the center of a tile have - // their sprite aligned to the bottom of that tile, mainly to maintain - // compatibility with older clients. - if (mMap) - py += mMap->getTileHeight() / 2; - return drawSpriteAt(graphics, px, py); } bool ActorSprite::drawSpriteAt(Graphics *graphics, int x, int y) const { - return CompoundSprite::draw(graphics, x, y); + y += paths.getIntValue("spriteOffsetY"); + return mSprites.draw(graphics, x, y); } void ActorSprite::logic() { // Update sprite animations - update(Time::deltaTimeMs()); + mSprites.update(Time::deltaTimeMs()); if (mUsedTargetCursor) mUsedTargetCursor->update(Time::deltaTimeMs()); - // Restart status/particle effects, if needed - if (mMustResetParticles) - { - mMustResetParticles = false; - for (int statusEffect : mStatusEffects) - { - const StatusEffect *effect = StatusEffect::getStatusEffect(statusEffect, true); - if (effect && effect->particleEffectIsPersistent()) - updateStatusEffect(statusEffect, true); - } - } - - // See note at ActorSprite::draw - float py = mPos.y; - if (mMap) - py += mMap->getTileHeight() / 2; + // Erase all extinct particle effects + mChildParticleEffects.erase(std::remove_if(mChildParticleEffects.begin(), + mChildParticleEffects.end(), + [](const Particle *p) { return p->isExtinct(); }), + mChildParticleEffects.end()); - // Update particle effects - mChildParticleEffects.moveTo(mPos.x, py); + // Move the remaining + const float py = mPos.y + paths.getIntValue("spriteOffsetY"); + for (Particle *p : mChildParticleEffects) + p->moveTo(mPos.x, py); } void ActorSprite::setMap(Map* map) @@ -130,12 +104,11 @@ void ActorSprite::setMap(Map* map) // Clear particle effect list because child particles became invalid mChildParticleEffects.clear(); - mMustResetParticles = true; // Reset status particles on next redraw } void ActorSprite::controlParticle(Particle *particle) { - mChildParticleEffects.addLocally(particle); + mChildParticleEffects.emplace_back(particle); } void ActorSprite::setTargetType(TargetCursorType type) @@ -146,115 +119,22 @@ void ActorSprite::setTargetType(TargetCursorType type) mUsedTargetCursor = targetCursor[type][getTargetCursorSize()]; } -void ActorSprite::setStatusEffect(int index, bool active) -{ - const bool wasActive = mStatusEffects.find(index) != mStatusEffects.end(); - - if (active != wasActive) - { - updateStatusEffect(index, active); - if (active) - mStatusEffects.insert(index); - else - mStatusEffects.erase(index); - } -} - -void ActorSprite::setStatusEffectBlock(int offset, uint16_t newEffects) -{ - for (int i = 0; i < STATUS_EFFECTS; i++) - { - int index = StatusEffect::blockEffectIndexToEffectIndex(offset + i); - - if (index != -1) - setStatusEffect(index, (newEffects & (1 << i)) > 0); - } -} - -void ActorSprite::updateStunMode(int oldMode, int newMode) -{ - if (this == local_player) - { - Event event(Event::Stun); - event.setInt("oldMode", oldMode); - event.setInt("newMode", newMode); - event.trigger(Event::ActorSpriteChannel); - } - - handleStatusEffect(StatusEffect::getStatusEffect(oldMode, false), -1); - handleStatusEffect(StatusEffect::getStatusEffect(newMode, true), -1); -} - -void ActorSprite::updateStatusEffect(int index, bool newStatus) -{ - if (this == local_player) - { - Event event(Event::UpdateStatusEffect); - event.setInt("index", index); - event.setBool("newStatus", newStatus); - event.trigger(Event::ActorSpriteChannel); - } - - handleStatusEffect(StatusEffect::getStatusEffect(index, newStatus), index); -} - -void ActorSprite::handleStatusEffect(StatusEffect *effect, int effectId) -{ - if (!effect) - return; - - // TODO: Find out how this is meant to be used - // (SpriteAction != Being::Action) - //SpriteAction action = effect->getAction(); - //if (action != ACTION_INVALID) - // setAction(action); - - Particle *particle = effect->getParticle(); - - if (effectId >= 0) - { - mStatusParticleEffects.setLocally(effectId, particle); - } - else - { - mStunParticleEffects.clearLocally(); - if (particle) - mStunParticleEffects.addLocally(particle); - } -} - void ActorSprite::setupSpriteDisplay(const SpriteDisplay &display, bool forceDisplay) { - clear(); + mSprites.clear(); for (const auto &sprite : display.sprites) { std::string file = paths.getStringValue("sprites") + sprite.sprite; - addSprite(AnimatedSprite::load(file, sprite.variant)); + mSprites.add(Sprite::load(file, sprite.variant)); } // Ensure that something is shown, if desired - if (size() == 0 && forceDisplay) + if (mSprites.size() == 0 && forceDisplay) { - if (display.image.empty()) - { - addSprite(AnimatedSprite::load(paths.getStringValue("sprites") - + paths.getStringValue("spriteErrorFile"))); - } - else - { - ResourceManager *resman = ResourceManager::getInstance(); - std::string imagePath = paths.getStringValue("itemIcons") - + display.image; - Image *img = resman->getImage(imagePath); - - if (!img) - img = Theme::getImageFromTheme( - paths.getStringValue("unknownItemFile")); - - addSprite(new ImageSprite(img)); - } + mSprites.add(Sprite::load(paths.getStringValue("sprites") + + paths.getStringValue("spriteErrorFile"))); } mChildParticleEffects.clear(); @@ -265,8 +145,6 @@ void ActorSprite::setupSpriteDisplay(const SpriteDisplay &display, for (const auto &particle : display.particles) controlParticle(particleEngine->addEffect(particle, 0, 0)); } - - mMustResetParticles = true; } void ActorSprite::load() @@ -320,8 +198,7 @@ void ActorSprite::cleanupTargetCursors() for (int type = 0; type < NUM_TCT; type++) { delete targetCursor[type][size]; - if (targetCursorImages[type][size]) - targetCursorImages[type][size]->decRef(); + targetCursorImages[type][size] = nullptr; } } } @@ -333,8 +210,7 @@ void ActorSprite::loadTargetCursor(const std::string &filename, assert(size < 3); ResourceManager *resman = ResourceManager::getInstance(); - ImageSet *currentImageSet = resman->getImageSet(filename, width, height); - + auto currentImageSet = resman->getImageSet(filename, width, height); if (!currentImageSet) { logger->log("Error loading target cursor: %s", filename.c_str()); diff --git a/src/actorsprite.h b/src/actorsprite.h index 8c20f81f..5993e4ea 100644 --- a/src/actorsprite.h +++ b/src/actorsprite.h @@ -18,21 +18,16 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef ACTORSPRITE_H -#define ACTORSPRITE_H +#pragma once #include "actor.h" #include "compoundsprite.h" #include "map.h" -#include "particlecontainer.h" - -#include <cstdint> -#include <set> +#include "particle.h" class SimpleAnimation; -class StatusEffect; -class ActorSprite : public CompoundSprite, public Actor +class ActorSprite : public Actor { public: enum Type @@ -41,7 +36,8 @@ public: PLAYER, NPC, MONSTER, - FLOOR_ITEM + FLOOR_ITEM, + PORTAL }; enum TargetCursorSize @@ -78,7 +74,7 @@ public: bool draw(Graphics *graphics, int offsetX, int offsetY) const override; - virtual bool drawSpriteAt(Graphics *graphics, int x, int y) const; + bool drawSpriteAt(Graphics *graphics, int x, int y) const; virtual void logic(); @@ -111,79 +107,25 @@ public: */ void untarget() { mUsedTargetCursor = nullptr; } - /** - * Sets the actor's stun mode. If zero, the being is `normal', otherwise it - * is `stunned' in some fashion. - */ - void setStunMode(int stunMode) - { - if (mStunMode != stunMode) - updateStunMode(mStunMode, stunMode); - mStunMode = stunMode; - } - - void setStatusEffect(int index, bool active); - - /** - * A status effect block is a 16 bit mask of status effects. We assign each - * such flag a block ID of offset + bitnr. - * - * These are NOT the same as the status effect indices. - */ - void setStatusEffectBlock(int offset, uint16_t flags); - - void setAlpha(float alpha) override - { CompoundSprite::setAlpha(alpha); } + void setAlpha(float alpha) override { mSprites.setAlpha(alpha); } + float getAlpha() const override { return mSprites.getAlpha(); } - float getAlpha() const override - { return CompoundSprite::getAlpha(); } - - int getWidth() const override - { return CompoundSprite::getWidth(); } - - int getHeight() const override - { return CompoundSprite::getHeight(); } + int getWidth() const { return mSprites.getWidth(); } + int getHeight() const { return mSprites.getHeight(); } static void load(); - static void unload(); protected: - /** - * Notify self that the stun mode has been updated. Invoked by - * setStunMode if something changed. - */ - virtual void updateStunMode(int oldMode, int newMode); - - /** - * Notify self that a status effect has flipped. - * The new flag is passed. - */ - virtual void updateStatusEffect(int index, bool newStatus); - - /** - * Handle an update to a status or stun effect - * - * \param effect The StatusEffect to effect - * \param effectId -1 for stun, otherwise the effect index - */ - virtual void handleStatusEffect(StatusEffect *effect, int effectId); - void setupSpriteDisplay(const SpriteDisplay &display, bool forceDisplay = true); int mId; - uint16_t mStunMode = 0; /**< Stun mode; zero if not stunned */ - std::set<int> mStatusEffects; /**< set of active status effects */ + std::vector<ParticleHandle> mChildParticleEffects; - ParticleList mStunParticleEffects; - ParticleVector mStatusParticleEffects; - ParticleList mChildParticleEffects; + CompoundSprite mSprites; private: - /** Reset particle status effects on next redraw? */ - bool mMustResetParticles = false; - /** Load the target cursors into memory */ static void initTargetCursor(); @@ -197,7 +139,7 @@ private: int width, int height, int type, int size); /** Images of the target cursor. */ - static ImageSet *targetCursorImages[NUM_TCT][NUM_TC]; + static ResourceRef<ImageSet> targetCursorImages[NUM_TCT][NUM_TC]; /** Animated target cursors. */ static SimpleAnimation *targetCursor[NUM_TCT][NUM_TC]; @@ -207,5 +149,3 @@ private: /** Target cursor being used */ SimpleAnimation *mUsedTargetCursor = nullptr; }; - -#endif // ACTORSPRITE_H diff --git a/src/actorspritemanager.cpp b/src/actorspritemanager.cpp index f7da58ec..74705f80 100644 --- a/src/actorspritemanager.cpp +++ b/src/actorspritemanager.cpp @@ -65,8 +65,8 @@ class PlayerNPCNamesLister : public AutoCompleteLister ActorSpriteManager::ActorSpriteManager() { - mPlayerNames = new PlayerNamesLister; - mPlayerNPCNames = new PlayerNPCNamesLister; + mPlayerNames = std::make_unique<PlayerNamesLister>(); + mPlayerNPCNames = std::make_unique<PlayerNPCNamesLister>(); listen(Event::ConfigChannel); } @@ -342,12 +342,12 @@ bool ActorSpriteManager::hasActorSprite(ActorSprite *someActor) const AutoCompleteLister *ActorSpriteManager::getPlayerNameLister() const { - return mPlayerNames; + return mPlayerNames.get(); } AutoCompleteLister *ActorSpriteManager::getPlayerNPCNameLister() const { - return mPlayerNPCNames; + return mPlayerNPCNames.get(); } void ActorSpriteManager::updatePlayerNames() diff --git a/src/actorspritemanager.h b/src/actorspritemanager.h index edbe51eb..5df50006 100644 --- a/src/actorspritemanager.h +++ b/src/actorspritemanager.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef ACTORSPRITEMANAGER_H -#define ACTORSPRITEMANAGER_H +#pragma once #include "actorsprite.h" #include "being.h" @@ -29,6 +28,8 @@ #include "gui/widgets/textfield.h" +#include <memory> + class LocalPlayer; class Map; @@ -169,13 +170,11 @@ class ActorSpriteManager : public EventListener friend class PlayerNamesLister; friend class PlayerNPCNamesLister; - AutoCompleteLister *mPlayerNames; - AutoCompleteLister *mPlayerNPCNames; + std::unique_ptr<AutoCompleteLister> mPlayerNames; + std::unique_ptr<AutoCompleteLister> mPlayerNPCNames; ActorSprites mActors; ActorSprites mDeleteActors; Map *mMap; }; extern ActorSpriteManager *actorSpriteManager; - -#endif // ACTORSPRITEMANAGER_H diff --git a/src/animatedsprite.h b/src/animatedsprite.h deleted file mode 100644 index 34655bff..00000000 --- a/src/animatedsprite.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - * The Mana Client - * Copyright (C) 2004-2009 The Mana World Development Team - * Copyright (C) 2009-2012 The Mana Developers - * - * This file is part of The Mana Client. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef ANIMATEDSPRITE_H -#define ANIMATEDSPRITE_H - -#include "sprite.h" - -#include <string> - -class Animation; -struct Frame; - -/** - * Animates a sprite by adding playback state. - */ -class AnimatedSprite final : public Sprite -{ - public: - /** - * Constructor. - * @param sprite the sprite to animate - */ - AnimatedSprite(SpriteDef *sprite); - - /** - * An helper function, which will request the sprite to animate - * from the resource manager. - * - * @param filename the file of the sprite to animate - * @param variant the sprite variant - */ - static AnimatedSprite *load(const std::string &filename, - int variant = 0); - - ~AnimatedSprite() override; - - bool reset() override; - - bool play(const std::string &action) override; - - bool update(int time) override; - - bool draw(Graphics *graphics, int posX, int posY) const override; - - int getWidth() const override; - - int getHeight() const override; - - int getOffsetX() const override; - - int getOffsetY() const override; - - const Image *getImage() const override; - - bool setDirection(SpriteDirection direction) override; - - int getDuration() const override; - - private: - bool updateCurrentAnimation(int dt); - - SpriteDirection mDirection = DIRECTION_DOWN; /**< The sprite direction. */ - - int mFrameIndex = 0; /**< The index of the current frame. */ - int mFrameTime = 0; /**< The time since start of frame. */ - - ResourceRef<SpriteDef> mSprite; /**< The sprite definition. */ - Action *mAction = nullptr; /**< The currently active action. */ - Animation *mAnimation = nullptr; /**< The currently active animation. */ - Frame *mFrame = nullptr; /**< The currently active frame. */ -}; - -#endif diff --git a/src/animationparticle.h b/src/animationparticle.h index 432664e5..162eb192 100644 --- a/src/animationparticle.h +++ b/src/animationparticle.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef ANIMATION_PARTICLE_H -#define ANIMATION_PARTICLE_H +#pragma once #include "imageparticle.h" #include "simpleanimation.h" @@ -44,5 +43,3 @@ class AnimationParticle : public ImageParticle private: SimpleAnimation mAnimation; /**< Used animation for this particle */ }; - -#endif diff --git a/src/avatar.h b/src/avatar.h index 29fc8c56..7c8de4fa 100644 --- a/src/avatar.h +++ b/src/avatar.h @@ -19,15 +19,14 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef AVATAR_H -#define AVATAR_H +#pragma once #include <string> class Avatar { public: - Avatar(const std::string &name = std::string()); + explicit Avatar(const std::string &name = {}); /** * Returns the avatar's name. @@ -68,5 +67,3 @@ private: bool mOnline; bool mDisplayBold; }; - -#endif // AVATAR_H diff --git a/src/being.cpp b/src/being.cpp index 56ed65f7..db9a9deb 100644 --- a/src/being.cpp +++ b/src/being.cpp @@ -22,7 +22,6 @@ #include "being.h" #include "actorspritemanager.h" -#include "animatedsprite.h" #include "client.h" #include "configuration.h" #include "effectmanager.h" @@ -36,6 +35,8 @@ #include "party.h" #include "playerrelations.h" #include "sound.h" +#include "sprite.h" +#include "statuseffect.h" #include "text.h" #include "gui/gui.h" @@ -53,6 +54,7 @@ #include "resources/iteminfo.h" #include "resources/monsterdb.h" #include "resources/npcdb.h" +#include "resources/statuseffectdb.h" #include "resources/theme.h" #include "resources/userpalette.h" @@ -60,25 +62,18 @@ #include <cmath> -Being::Being(int id, Type type, int subtype, Map *map): - ActorSprite(id), - mInfo(BeingInfo::Unknown), - mType(type) +Being::Being(int id, Type type, int subtype, Map *map) + : ActorSprite(id) + , mInfo(BeingInfo::Unknown) { setMap(map); - setSubtype(subtype); + setType(type, subtype); mSpeechBubble = new SpeechBubble; + mSpeechBubble->addDeathListener(this); mMoveSpeed = Net::getPlayerHandler()->getDefaultMoveSpeed(); - if (getType() == PLAYER) - mShowName = config.visibleNames; - - if (getType() == PLAYER || getType() == NPC) - setShowName(true); - - updateColors(); listen(Event::ConfigChannel); listen(Event::ChatChannel); } @@ -88,20 +83,27 @@ Being::~Being() delete mSpeechBubble; delete mDispName; delete mText; - mSpeechBubble = nullptr; - mDispName = nullptr; - mText = nullptr; - - removeAllSpriteParticles(); } -void Being::setSubtype(Uint16 subtype) +/** + * Can be used to change the type of the being. + * + * Practical use: players (usually GMs) can change into monsters and back. + */ +void Being::setType(Type type, int subtype) { - if (subtype == mSubType) + if (mType == type && mSubType == subtype) return; + mType = type; mSubType = subtype; + for (auto &spriteState : mSpriteStates) + { + spriteState.visibleId = 0; + spriteState.particles.clear(); + } + switch (getType()) { case MONSTER: @@ -112,8 +114,12 @@ void Being::setSubtype(Uint16 subtype) case NPC: mInfo = NPCDB::get(mSubType); setupSpriteDisplay(mInfo->display, false); + mShowName = true; break; case PLAYER: { + mSprites.clear(); + mChildParticleEffects.clear(); + int id = -100 - subtype; // Prevent showing errors when sprite doesn't exist @@ -121,12 +127,22 @@ void Being::setSubtype(Uint16 subtype) id = -100; setSprite(Net::getCharHandler()->baseSprite(), id); + restoreAllSpriteParticles(); + mShowName = this == local_player ? config.showOwnName + : config.visibleNames; break; } case FLOOR_ITEM: + case PORTAL: case UNKNOWN: break; } + + mSprites.doRedraw(); + + updateName(); + updateNamePosition(); + updateColors(); } bool Being::isTargetSelection() const @@ -172,7 +188,7 @@ void Being::setPosition(const Vector &pos) { Actor::setPosition(pos); - updateCoords(); + updateNamePosition(); if (mText) mText->adviseXY(getPixelX(), getSpeechTextYPosition()); @@ -294,7 +310,7 @@ void Being::setSpeech(const std::string &text, int time) mText = new Text(mSpeech, getPixelX(), getSpeechTextYPosition(), gcn::Graphics::CENTER, - &userPalette->getColor(UserPalette::PARTICLE), + &Theme::getThemeColor(Theme::BUBBLE_TEXT), true); } } @@ -303,8 +319,8 @@ void Being::takeDamage(Being *attacker, int amount, AttackType type, int attackId) { gcn::Font *font; - std::string damage = amount ? toString(amount) : type == FLEE ? - "dodge" : "miss"; + std::string damage = amount ? toString(amount) + : (type == FLEE ? "dodge" : "miss"); const gcn::Color *color; font = gui->getInfoParticleFont(); @@ -360,7 +376,7 @@ void Being::takeDamage(Being *attacker, int amount, if (amount > 0) { - auto &hurtSfx = mInfo->getSound(SoundEvent::HURT); + auto &hurtSfx = mInfo->getSound(SoundEvent::Hurt); if (attacker) sound.playSfx(hurtSfx, attacker->getPixelX(), attacker->getPixelY()); else @@ -433,14 +449,14 @@ void Being::handleAttack(Being *victim, int damage, int attackId) if (!itemInfo) itemInfo = &itemDb->get(-100 - mSubType); - const auto event = damage > 0 ? EquipmentSoundEvent::HIT - : EquipmentSoundEvent::STRIKE; + const auto event = damage > 0 ? EquipmentSoundEvent::Hit + : EquipmentSoundEvent::Strike; const auto &soundFile = itemInfo->getSound(event); sound.playSfx(soundFile, getPixelX(), getPixelY()); } else { - const auto event = damage > 0 ? SoundEvent::HIT : SoundEvent::MISS; + const auto event = damage > 0 ? SoundEvent::Hit : SoundEvent::Miss; const auto &soundFile = mInfo->getSound(event); sound.playSfx(soundFile, getPixelX(), getPixelY()); } @@ -449,17 +465,11 @@ void Being::handleAttack(Being *victim, int damage, int attackId) void Being::setName(const std::string &name) { if (getType() == NPC) - { mName = name.substr(0, name.find('#', 0)); - showName(); - } else - { mName = name; - if (getType() == PLAYER && getShowName()) - showName(); - } + updateName(); } void Being::setShowName(bool doShowName) @@ -468,14 +478,7 @@ void Being::setShowName(bool doShowName) return; mShowName = doShowName; - - if (doShowName) - showName(); - else - { - delete mDispName; - mDispName = nullptr; - } + updateName(); } void Being::setGuildName(const std::string &name) @@ -590,7 +593,34 @@ void Being::fireMissile(Being *victim, const std::string &particle) missile->setDieDistance(8); missile->setLifetime(900); } +} + +void Being::setStatusEffect(int id, bool active) +{ + const auto it = mStatusEffects.find(id); + const bool wasActive = it != mStatusEffects.end(); + if (active != wasActive) + { + if (active) + mStatusEffects.insert(id); + else + mStatusEffects.erase(it); + + updateStatusEffect(id, active); + } +} + +void Being::updateStatusEffect(int id, bool newStatus) +{ + auto effect = StatusEffectDB::getStatusEffect(id); + if (!effect) + return; + + if (Particle *particle = effect->getParticle(newStatus)) + mStatusParticleEffects[id] = ParticleHandle(particle); + else + mStatusParticleEffects.erase(id); } void Being::setAction(Action action, int attackId) @@ -612,12 +642,12 @@ void Being::setAction(Action action, int attackId) if (mEquippedWeapon) { currentAction = mEquippedWeapon->attackAction; - reset(); + mSprites.reset(); } else { currentAction = mInfo->getAttack(attackId).action; - reset(); + mSprites.reset(); // Attack particle effect if (Particle::enabled) @@ -634,7 +664,6 @@ void Being::setAction(Action action, int attackId) } effectManager->trigger(effectId, this, rotation); } - } break; @@ -646,7 +675,7 @@ void Being::setAction(Action action, int attackId) break; case DEAD: currentAction = SpriteAction::DEAD; - sound.playSfx(mInfo->getSound(SoundEvent::DIE), + sound.playSfx(mInfo->getSound(SoundEvent::Die), getPixelX(), getPixelY()); break; case STAND: @@ -656,7 +685,7 @@ void Being::setAction(Action action, int attackId) if (currentAction != SpriteAction::INVALID) { - play(currentAction); + mSprites.play(currentAction); mAction = action; } @@ -664,6 +693,14 @@ void Being::setAction(Action action, int attackId) mActionTimer.set(); } +void Being::setAction(const std::string &action) +{ + // Actions are triggered by strings from abilities when using manaserv, + // it's not necessarily an attack, but it seems the most appropriate value. + mAction = ATTACK; + mSprites.play(action); +} + void Being::lookAt(const Vector &destPos) { // We first handle simple cases @@ -744,14 +781,14 @@ void Being::lookAt(const Vector &destPos) } } -void Being::setDirection(Uint8 direction) +void Being::setDirection(uint8_t direction) { if (!direction || mDirection == direction) return; mDirection = direction; - SpriteDirection dir; + SpriteDirection dir = DIRECTION_DEFAULT; if (mDirection & UP) dir = DIRECTION_UP; else if (mDirection & DOWN) @@ -762,7 +799,8 @@ void Being::setDirection(Uint8 direction) dir = DIRECTION_LEFT; mSpriteDirection = dir; - CompoundSprite::setDirection(dir); + updatePlayerSprites(); + mSprites.setDirection(dir); } int Being::getCollisionRadius() const @@ -780,15 +818,34 @@ void Being::logic() mText = nullptr; } - if (mRestoreSpriteParticlesOnLogic) + if (mRestoreParticlesOnLogic) { - mRestoreSpriteParticlesOnLogic = false; + mRestoreParticlesOnLogic = false; + restoreAllSpriteParticles(); + + // Restart status/particle effects, if needed + for (int id : mStatusEffects) + { + const StatusEffect *effect = StatusEffectDB::getStatusEffect(id); + if (effect && effect->persistentParticleEffect) + updateStatusEffect(id, true); + } } if (mAction != DEAD && !mSpeedPixelsPerSecond.isNull()) { updateMovement(); + + // Update particle effects + const float py = mPos.y + paths.getIntValue("spriteOffsetY"); + + for (auto &spriteState : mSpriteStates) + for (auto &particle : spriteState.particles) + particle->moveTo(mPos.x, py); + + for (auto &[_, p] : mStatusParticleEffects) + p->moveTo(mPos.x, py); } ActorSprite::logic(); @@ -796,7 +853,7 @@ void Being::logic() // Remove it after 1.5 secs if the dead animation isn't long enough, // or simply play it until it's finished. if (!isAlive() && Net::getGameHandler()->removeDeadBeings() && getType() != PLAYER) - if (mActionTimer.elapsed() > std::max(getDuration(), 1500)) + if (mActionTimer.elapsed() > std::max(mSprites.getMaxDuration(), 1500)) actorSpriteManager->scheduleDelete(this); } @@ -940,7 +997,7 @@ void Being::drawSpeech(int offsetX, int offsetY) mText = new Text(mSpeech, getPixelX(), getPixelY() - getHeight(), gcn::Graphics::CENTER, - &userPalette->getColor(UserPalette::PARTICLE), + &Theme::getThemeColor(Theme::BUBBLE_TEXT), true); } } @@ -953,7 +1010,7 @@ void Being::drawSpeech(int offsetX, int offsetY) } } -void Being::updateCoords() +void Being::updateNamePosition() { if (!mDispName) return; @@ -971,24 +1028,28 @@ void Being::flashName(int time) mDispName->flash(time); } -void Being::showName() +void Being::updateName() { delete mDispName; mDispName = nullptr; + + if (!mShowName) + return; + std::string mDisplayName(mName); if (getType() == PLAYER) { if (config.showGender) { - if (getGender() == Gender::FEMALE) + if (getGender() == Gender::Female) mDisplayName += " \u2640"; - else if (getGender() == Gender::MALE) + else if (getGender() == Gender::Male) mDisplayName += " \u2642"; } // Display the IP when under tmw-Athena (GM only). - if (Net::getNetworkType() == ServerType::TMWATHENA && local_player + if (Net::getNetworkType() == ServerType::TmwAthena && local_player && local_player->getShowIp() && getIp()) { mDisplayName += strprintf(" %s", ipToString(getIp())); @@ -1013,7 +1074,7 @@ void Being::showName() mDispName = new FlashText(mDisplayName, getPixelX(), getPixelY(), gcn::Graphics::CENTER, mNameColor, font); - updateCoords(); + updateNamePosition(); } void Being::addSpriteParticles(SpriteState &spriteState, const SpriteDisplay &display) @@ -1026,27 +1087,15 @@ void Being::addSpriteParticles(SpriteState &spriteState, const SpriteDisplay &di for (const auto &particle : display.particles) { Particle *p = particleEngine->addEffect(particle, 0, 0, 0); - controlParticle(p); - spriteState.particles.push_back(p); + spriteState.particles.emplace_back(p); } } -void Being::removeSpriteParticles(SpriteState &spriteState) -{ - for (auto particle : spriteState.particles) - mChildParticleEffects.removeLocally(particle); - - spriteState.particles.clear(); -} - -void Being::removeAllSpriteParticles() -{ - for (auto &spriteState : mSpriteStates) - removeSpriteParticles(spriteState); -} - void Being::restoreAllSpriteParticles() { + if (mType != PLAYER) + return; + for (auto &spriteState : mSpriteStates) { if (spriteState.id) @@ -1094,17 +1143,121 @@ void Being::updateColors() } if (mDispName) - { mDispName->setColor(mNameColor); +} + +/** + * Updates the visible sprite IDs of the player, taking into account the item + * replacements. + */ +void Being::updatePlayerSprites() +{ + if (mType != PLAYER) + return; + + // hack for allow different logic in dead player + const int direction = mAction == DEAD ? DIRECTION_DEAD : mSpriteDirection; + + // Get the current item IDs + std::vector<int> itemIDs(mSpriteStates.size()); + for (size_t i = 0; i < mSpriteStates.size(); i++) + itemIDs[i] = mSpriteStates[i].id; + + // Apply the replacements + for (auto &spriteState : mSpriteStates) + { + if (!spriteState.id) + continue; + + auto &itemInfo = itemDb->get(spriteState.id); + for (const auto &replacement : itemInfo.replacements) + { + if (replacement.direction != DIRECTION_ALL && replacement.direction != direction) + continue; + + if (replacement.sprite == SPRITE_ALL) + { + if (replacement.items.empty()) + { + itemIDs.assign(itemIDs.size(), 0); + } + else + { + for (int &id : itemIDs) + { + for (auto &item : replacement.items) + if (!item.from || id == item.from) + id = item.to; + } + } + } + else if (replacement.sprite < itemIDs.size()) + { + int &id = itemIDs[replacement.sprite]; + + if (replacement.items.empty()) + { + id = 0; + } + else + { + for (auto &item : replacement.items) + if (!item.from || id == item.from) + id = item.to; + } + } + } } + + // Set the new sprites + bool newSpriteSet = false; + + mSprites.ensureSize(mSpriteStates.size()); + + for (size_t i = 0; i < mSpriteStates.size(); i++) + { + auto &spriteState = mSpriteStates[i]; + if (spriteState.visibleId == itemIDs[i]) + continue; + + spriteState.visibleId = itemIDs[i]; + + if (spriteState.visibleId == 0) + { + mSprites.set(i, nullptr); + } + else + { + newSpriteSet = true; + + auto &itemInfo = itemDb->get(spriteState.visibleId); + std::string filename = itemInfo.getSprite(mGender, mSubType); + Sprite *equipmentSprite = nullptr; + + if (!filename.empty()) + { + if (!spriteState.color.empty()) + filename += "|" + spriteState.color; + + equipmentSprite = Sprite::load( + paths.getStringValue("sprites") + filename); + + if (equipmentSprite) + equipmentSprite->setDirection(getSpriteDirection()); + } + + mSprites.set(i, equipmentSprite); + } + } + + // Make sure any new sprites are set to the correct action + if (newSpriteSet) + setAction(mAction); } void Being::setSprite(unsigned slot, int id, const std::string &color, bool isWeapon) { - if (slot >= size()) - ensureSize(slot + 1); - if (slot >= mSpriteStates.size()) mSpriteStates.resize(slot + 1); @@ -1112,45 +1265,35 @@ void Being::setSprite(unsigned slot, int id, const std::string &color, // Clear current particles when the ID changes if (spriteState.id != id) - removeSpriteParticles(spriteState); + spriteState.particles.clear(); + + // Clear the current sprite when the color changes + if (spriteState.color != color && spriteState.visibleId) + { + spriteState.visibleId = 0; + mSprites.set(slot, nullptr); + } spriteState.id = id; spriteState.color = color; if (id == 0) // id = 0 means unequip { - removeSprite(slot); - if (isWeapon) mEquippedWeapon = nullptr; } else { auto &itemInfo = itemDb->get(id); - std::string filename = itemInfo.getSprite(mGender, mSubType); - AnimatedSprite *equipmentSprite = nullptr; - - if (!filename.empty()) - { - if (!color.empty()) - filename += "|" + color; - - equipmentSprite = AnimatedSprite::load( - paths.getStringValue("sprites") + filename); - } - - if (equipmentSprite) - equipmentSprite->setDirection(getSpriteDirection()); - CompoundSprite::setSprite(slot, equipmentSprite); - - addSpriteParticles(spriteState, itemInfo.display); + if (mType == PLAYER) + addSpriteParticles(spriteState, itemInfo.display); if (isWeapon) mEquippedWeapon = &itemInfo; - - setAction(mAction); } + + updatePlayerSprites(); } void Being::setSpriteID(unsigned slot, int id) @@ -1168,13 +1311,7 @@ void Being::setSpriteColor(unsigned slot, const std::string &color) bool Being::drawnWhenBehind() const { // For now, just draw actors with only one layer when obscured - return CompoundSprite::getNumberOfLayers() == 1; -} - -void Being::updateName() -{ - if (mShowName) - showName(); + return mSprites.getNumberOfLayers() == 1; } void Being::setGender(Gender gender) @@ -1183,15 +1320,21 @@ void Being::setGender(Gender gender) { mGender = gender; - // Reload all subsprites + // Reset all sprites to force reload with the correct gender for (size_t i = 0; i < mSpriteStates.size(); i++) { - auto &sprite = mSpriteStates[i]; - if (sprite.id != 0) - setSprite(i, sprite.id, sprite.color); + auto &spriteState = mSpriteStates[i]; + if (spriteState.visibleId) + { + mSprites.set(i, nullptr); + spriteState.visibleId = 0; + } } - updateName(); + updatePlayerSprites(); + + if (config.showGender) + updateName(); } } @@ -1202,6 +1345,17 @@ void Being::setGM(bool gm) updateColors(); } +void Being::setIp(int ip) +{ + if (mIp == ip) + return; + + mIp = ip; + + if (local_player && local_player->getShowIp()) + updateName(); +} + bool Being::canTalk() { return mType == NPC; @@ -1239,11 +1393,20 @@ void Being::event(Event::Channel channel, const Event &event) } } +void Being::death(const gcn::Event &event) +{ + if (event.getSource() == mSpeechBubble) + mSpeechBubble = nullptr; +} + void Being::setMap(Map *map) { - // Remove sprite particles because ActorSprite is going to kill them all - removeAllSpriteParticles(); - mRestoreSpriteParticlesOnLogic = true; + for (auto &spriteState : mSpriteStates) + spriteState.particles.clear(); + + mStatusParticleEffects.clear(); + + mRestoreParticlesOnLogic = true; ActorSprite::setMap(map); diff --git a/src/being.h b/src/being.h index b8c5a0f3..96749651 100644 --- a/src/being.h +++ b/src/being.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef BEING_H -#define BEING_H +#pragma once #include "actorsprite.h" #include "eventlistener.h" @@ -32,8 +31,11 @@ #include "utils/time.h" #include <guichan/color.hpp> +#include <guichan/deathlistener.hpp> +#include <cstdint> #include <map> +#include <set> #include <string> #include <vector> @@ -54,13 +56,13 @@ class Text; enum class Gender { - MALE = 0, - FEMALE = 1, - UNSPECIFIED = 2, - HIDDEN = 3 + Male = 0, + Female = 1, + Unspecified = 2, + Hidden = 3 }; -class Being : public ActorSprite, public EventListener +class Being : public ActorSprite, public EventListener, public gcn::DeathListener { public: /** @@ -110,7 +112,8 @@ class Being : public ActorSprite, public EventListener * Constructor. * * @param id a unique being id - * @param subtype partly determines the type of the being + * @param type type of being + * @param subtype refers to a specific npc, monster, species, etc. * @param map the map the being is on */ Being(int id, Type type, int subtype, Map *map); @@ -120,6 +123,8 @@ class Being : public ActorSprite, public EventListener Type getType() const final { return mType; } + void setType(Type type, int subtype); + /** * Removes all path nodes from this being. */ @@ -270,6 +275,9 @@ class Being : public ActorSprite, public EventListener void setSpriteColor(unsigned slot, const std::string &color = std::string()); + unsigned getSpriteCount() const + { return mSpriteStates.size(); } + bool drawnWhenBehind() const override; /** @@ -284,11 +292,6 @@ class Being : public ActorSprite, public EventListener uint16_t getSubType() const { return mSubType; } - /** - * Set Being's subtype (mostly for view for monsters and NPCs) - */ - void setSubtype(uint16_t subtype); - const BeingInfo &getInfo() const { return *mInfo; } @@ -334,6 +337,11 @@ class Being : public ActorSprite, public EventListener virtual void setAction(Action action, int attackId = 1); /** + * Sets the current action by name. + */ + void setAction(const std::string &action); + + /** * Get the being's action currently performed. */ Action getCurrentAction() const { return mAction; } @@ -381,6 +389,8 @@ class Being : public ActorSprite, public EventListener */ void fireMissile(Being *target, const std::string &particle); + void setStatusEffect(int id, bool active); + /** * Returns the path this being is following. An empty path is returned * when this being isn't following any path currently. @@ -417,7 +427,7 @@ class Being : public ActorSprite, public EventListener * Sets the IP or an IP hash. * The TMW-Athena server sends this information only to GMs. */ - void setIp(int ip) { mIp = ip; } + void setIp(int ip); /** * Returns the player's IP or an IP hash. @@ -429,8 +439,12 @@ class Being : public ActorSprite, public EventListener void talkTo(); + // EventListener void event(Event::Channel channel, const Event &event) override; + // gcn::DeathListener + void death(const gcn::Event &event) override; + void setMap(Map *map) final; /** @@ -443,8 +457,9 @@ class Being : public ActorSprite, public EventListener protected: struct SpriteState { int id = 0; + int visibleId = 0; std::string color; - std::vector<Particle*> particles; + std::vector<ParticleHandle> particles; }; /** @@ -455,16 +470,13 @@ class Being : public ActorSprite, public EventListener /** * Updates name's location. */ - void updateCoords(); - - void showName(); + void updateNamePosition(); void addSpriteParticles(SpriteState &spriteState, const SpriteDisplay &display); - void removeSpriteParticles(SpriteState &spriteState); - void removeAllSpriteParticles(); void restoreAllSpriteParticles(); void updateColors(); + void updatePlayerSprites(); /** * Gets the advised Y chat text position. @@ -476,6 +488,12 @@ class Being : public ActorSprite, public EventListener */ virtual void pathFinished() {} + /** + * Notify self that a status effect has flipped. + * The new flag is passed. + */ + virtual void updateStatusEffect(int index, bool newStatus); + const BeingInfo *mInfo; Timer mActionTimer; /**< Time spent in current action. TODO: Remove use of it */ @@ -486,7 +504,7 @@ class Being : public ActorSprite, public EventListener int mAttackSpeed = 350; /**< Attack speed */ Action mAction = STAND; /**< Action the being is performing */ - uint16_t mSubType = 0xFFFF; /**< Subtype (graphical view, basically) */ + int mSubType = 0xFFFF; /**< Subtype (graphical view, basically) */ uint8_t mDirection = DOWN; /**< Facing direction */ uint8_t mSpriteDirection = DIRECTION_DOWN; /**< Facing direction */ @@ -511,9 +529,9 @@ class Being : public ActorSprite, public EventListener Vector mDest; /**< destination coordinates. */ std::vector<SpriteState> mSpriteStates; - bool mRestoreSpriteParticlesOnLogic = false; + bool mRestoreParticlesOnLogic = false; - Gender mGender = Gender::UNSPECIFIED; + Gender mGender = Gender::Unspecified; // Character guild information std::map<int, Guild*> mGuilds; @@ -524,7 +542,10 @@ class Being : public ActorSprite, public EventListener private: void updateMovement(); - const Type mType; + Type mType = UNKNOWN; + + std::set<int> mStatusEffects; /**< set of active status effects */ + std::map<int, ParticleHandle> mStatusParticleEffects; /** Speech Bubble components */ SpeechBubble *mSpeechBubble; @@ -542,8 +563,5 @@ class Being : public ActorSprite, public EventListener Vector mSpeedPixelsPerSecond; int mDamageTaken = 0; - int mIp = 0; }; - -#endif diff --git a/src/channel.h b/src/channel.h index c0d48632..8d8acf15 100644 --- a/src/channel.h +++ b/src/channel.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef CHANNEL_H -#define CHANNEL_H +#pragma once #include <string> @@ -79,5 +78,3 @@ class Channel std::string mAnnouncement; ChannelTab *mTab; }; - -#endif // CHANNEL_H diff --git a/src/channelmanager.h b/src/channelmanager.h index ffff3047..baaa196a 100644 --- a/src/channelmanager.h +++ b/src/channelmanager.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef CHANNELMANAGER_H -#define CHANNELMANAGER_H +#pragma once #include <list> #include <string> @@ -46,5 +45,3 @@ private: }; extern ChannelManager *channelManager; - -#endif diff --git a/src/chatlogger.h b/src/chatlogger.h index e9e53798..45fe7da2 100644 --- a/src/chatlogger.h +++ b/src/chatlogger.h @@ -19,8 +19,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#ifndef CHATLOG_H -#define CHATLOG_H +#pragma once #include <fstream> @@ -66,5 +65,3 @@ class ChatLogger }; extern ChatLogger *chatLogger; - -#endif diff --git a/src/client.cpp b/src/client.cpp index 40f41b30..99c5bbcf 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -68,6 +68,7 @@ #include "resources/userpalette.h" #include "resources/settingsmanager.h" +#include "utils/filesystem.h" #include "utils/gettext.h" #include "utils/mkdir.h" #if defined(_WIN32) || defined(__APPLE__) @@ -76,7 +77,6 @@ #include "utils/stringutils.h" #include "utils/time.h" -#include <physfs.h> #include <SDL_image.h> #ifdef _WIN32 @@ -220,9 +220,7 @@ Client::Client(const Options &options): SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); - ResourceManager *resman = ResourceManager::getInstance(); - - if (!resman->setWriteDir(mLocalDataDir)) + if (!FS::setWriteDir(mLocalDataDir)) { logger->error(strprintf("%s couldn't be set as write directory! " "Exiting.", mLocalDataDir.c_str())); @@ -233,9 +231,8 @@ Client::Client(const Options &options): #else mPackageDir = PKG_DATADIR "data"; #endif - resman->addToSearchPath(mPackageDir, false); - - resman->addToSearchPath("data", false); + ResourceManager::addToSearchPath(mPackageDir, false); + ResourceManager::addToSearchPath("data", false); // Add branding/data to PhysFS search path if (!options.brandingPath.empty()) @@ -251,15 +248,15 @@ Client::Client(const Options &options): int loc = path.find_last_of('/'); #endif if (loc > 0) - resman->addToSearchPath(path.substr(0, loc + 1) + "data", false); + ResourceManager::addToSearchPath(path.substr(0, loc + 1) + "data", false); } // Add the main data directories to our PhysicsFS search path if (!options.dataPath.empty()) - resman->addToSearchPath(options.dataPath, false); + ResourceManager::addToSearchPath(options.dataPath, false); // Add the local data directory to PhysicsFS search path - resman->addToSearchPath(mLocalDataDir, false); + ResourceManager::addToSearchPath(mLocalDataDir, false); bool useOpenGL = !mOptions.noOpenGL && config.opengl; @@ -287,7 +284,7 @@ Client::Client(const Options &options): #else iconFile += ".png"; #endif - iconFile = resman->getPath(iconFile); + iconFile = ResourceManager::getPath(iconFile); logger->log("Loading icon from file: %s", iconFile.c_str()); #ifdef _WIN32 static SDL_SysWMinfo pInfo; @@ -310,13 +307,11 @@ Client::Client(const Options &options): } #endif - Theme::prepareThemePath(); - // Initialize the item and emote shortcuts. itemShortcut = new ItemShortcut; emoteShortcut = new EmoteShortcut; - gui = new Gui(graphics); + gui = new Gui(graphics, Theme::prepareThemePath()); // Initialize sound engine try @@ -355,12 +350,12 @@ Client::Client(const Options &options): loginData.remember = config.remember; loginData.registerLogin = false; - if (mCurrentServer.type == ServerType::UNKNOWN && mCurrentServer.port != 0) + if (mCurrentServer.type == ServerType::Unknown && mCurrentServer.port != 0) { mCurrentServer.type = ServerInfo::defaultServerTypeForPort(mCurrentServer.port); } - if (mCurrentServer.type == ServerType::UNKNOWN) + if (mCurrentServer.type == ServerType::Unknown) { mCurrentServer.type = ServerInfo::parseType( branding.getValue("defaultServerType", "tmwathena")); @@ -386,7 +381,13 @@ Client::Client(const Options &options): loginData.username = config.username; if (mState != STATE_ERROR) - mState = STATE_CHOOSE_SERVER; + { + // If a server was passed on the command line, or branding + // provides a server and a blank server list, we skip the + // server selection dialog. + mState = mCurrentServer.isValid() ? STATE_CONNECT_SERVER + : STATE_CHOOSE_SERVER; + } // Initialize seconds counter mSecondsCounterId = SDL_AddTimer(1000, nextSecond, nullptr); @@ -404,6 +405,8 @@ Client::~Client() CharDB::unload(); delete itemDb; + ActorSprite::unload(); + // Before config.write() since it writes the shortcuts to the config delete itemShortcut; delete emoteShortcut; @@ -567,27 +570,12 @@ int Client::exec() case STATE_CHOOSE_SERVER: logger->log("State: CHOOSE SERVER"); - // If a server was passed on the command line, or branding - // provides a server and a blank server list, we skip the - // server selection dialog. - if (!mCurrentServer.hostname.empty() && mCurrentServer.port) - { - mState = STATE_CONNECT_SERVER; - - // Reset options so that cancelling or connect - // timeout will show the server dialog. - mOptions.serverName.clear(); - mOptions.serverPort = 0; - } - else - { - // Don't allow an alpha opacity - // lower than the default value - Theme::instance()->setMinimumOpacity(0.8f); + // Don't allow an alpha opacity + // lower than the default value + gui->getTheme()->setMinimumOpacity(0.8f); - mCurrentDialog = new ServerDialog(&mCurrentServer, - mConfigDir); - } + mCurrentDialog = new ServerDialog(&mCurrentServer, + mConfigDir); break; case STATE_CONNECT_SERVER: @@ -603,7 +591,7 @@ int Client::exec() logger->log("State: LOGIN"); // Don't allow an alpha opacity // lower than the default value - Theme::instance()->setMinimumOpacity(0.8f); + gui->getTheme()->setMinimumOpacity(0.8f); if (mOptions.username.empty() || mOptions.password.empty()) { @@ -654,22 +642,17 @@ int Client::exec() break; case STATE_UPDATE: - // Determine which source to use for the update host - if (!mOptions.updateHost.empty()) - mUpdateHost = mOptions.updateHost; - else - mUpdateHost = loginData.updateHost; - initUpdatesDir(); + logger->log("State: UPDATE"); if (mOptions.skipUpdate) { mState = STATE_LOAD_DATA; } - else + else if (initUpdatesDir()) { - logger->log("State: UPDATE"); mCurrentDialog = new UpdaterWindow(mUpdateHost, - mLocalDataDir + "/" + mUpdatesDir,mOptions.dataPath.empty()); + mLocalDataDir + "/" + mUpdatesDir, + mOptions.dataPath.empty()); } break; @@ -681,7 +664,7 @@ int Client::exec() if (mOptions.dataPath.empty()) { // Add customdata directory - ResourceManager::getInstance()->searchAndAddArchives( + ResourceManager::searchAndAddArchives( "customdata/", "zip", false); @@ -697,10 +680,10 @@ int Client::exec() switch (Net::getNetworkType()) { - case ServerType::TMWATHENA: + case ServerType::TmwAthena: itemDb = new TmwAthena::TaItemDB; break; - case ServerType::MANASERV: + case ServerType::ManaServ: itemDb = new ManaServ::ManaServItemDB; break; default: @@ -732,7 +715,7 @@ int Client::exec() logger->log("State: CHAR SELECT"); // Don't allow an alpha opacity // lower than the default value - Theme::instance()->setMinimumOpacity(0.8f); + gui->getTheme()->setMinimumOpacity(0.8f); mCurrentDialog = new CharSelectDialog(&loginData); @@ -749,6 +732,7 @@ int Client::exec() // Choosing character on the command line should work only // once, clear it so that 'switch character' works. mOptions.character.clear(); + mOptions.chooseDefault = false; break; @@ -758,7 +742,7 @@ int Client::exec() Net::getGameHandler()->connect(); mCurrentDialog = new ConnectionDialog( _("Connecting to the game server"), - Net::getNetworkType() == ServerType::TMWATHENA ? + Net::getNetworkType() == ServerType::TmwAthena ? STATE_CHOOSE_SERVER : STATE_SWITCH_CHARACTER); break; @@ -781,7 +765,7 @@ int Client::exec() sound.fadeOutMusic(1000); // Allow any alpha opacity - Theme::instance()->setMinimumOpacity(-1.0f); + gui->getTheme()->setMinimumOpacity(0.0f); delete mSetupButton; delete mDesktop; @@ -919,13 +903,10 @@ int Client::exec() case STATE_EXIT: logger->log("State: EXIT"); - Net::unload(); break; case STATE_FORCE_QUIT: logger->log("State: FORCE QUIT"); - if (Net::getGeneralHandler()) - Net::getGeneralHandler()->unload(); mState = STATE_EXIT; break; @@ -943,6 +924,8 @@ int Client::exec() } } + Net::unload(); + return 0; } @@ -981,7 +964,7 @@ void Client::action(const gcn::ActionEvent &event) void Client::initRootDir() { - mRootDir = PHYSFS_getBaseDir(); + mRootDir = FS::getBaseDir(); #ifdef _WIN32 std::string portableName = mRootDir + "portable.xml"; struct stat statbuf; @@ -1041,15 +1024,15 @@ void Client::initHomeDir() if (mLocalDataDir.empty()) { #if defined __HAIKU__ - mLocalDataDir = PHYSFS_getUserDir(); + mLocalDataDir = FS::getUserDir(); mLocalDataDir += "/config/data/Mana"; #elif defined _WIN32 mLocalDataDir = getSpecialFolderLocation(FOLDERID_LocalAppData); if (mLocalDataDir.empty()) - mLocalDataDir = PHYSFS_getUserDir(); + mLocalDataDir = FS::getUserDir(); mLocalDataDir += "/Mana"; #else - mLocalDataDir = PHYSFS_getPrefDir("manasource.org", "mana"); + mLocalDataDir = FS::getPrefDir("manasource.org", "mana"); #endif } @@ -1067,12 +1050,12 @@ void Client::initHomeDir() #ifdef __APPLE__ mConfigDir = mLocalDataDir + "/" + app; #elif defined __HAIKU__ - mConfigDir = PHYSFS_getPrefDir("manasource.org", "Mana"); + mConfigDir = FS::getPrefDir("manasource.org", "Mana"); mConfigDir += app; #elif defined _WIN32 - mConfigDir = PHYSFS_getPrefDir("Mana", app.c_str()); + mConfigDir = FS::getPrefDir("Mana", app.c_str()); #else - mConfigDir = std::string(PHYSFS_getUserDir()) + ".config/mana/" + app; + mConfigDir = std::string(FS::getUserDir()) + ".config/mana/" + app; #endif } @@ -1104,39 +1087,37 @@ void Client::initConfiguration() * Parse the update host and determine the updates directory * Then verify that the directory exists (creating if needed). */ -void Client::initUpdatesDir() +bool Client::initUpdatesDir() { - // If updatesHost is currently empty, fill it from config file - if (mUpdateHost.empty()) + // Determine which source to use for the update host + if (!mOptions.updateHost.empty()) + mUpdateHost = mOptions.updateHost; + else if (!loginData.updateHost.empty()) + mUpdateHost = loginData.updateHost; + else mUpdateHost = config.updatehost; - // Exit on empty update host. - if (mUpdateHost.empty()) - return; + // Remove any trailing slashes at the end of the URL + while (!mUpdateHost.empty() && mUpdateHost.back() == '/') + mUpdateHost.pop_back(); - logger->log("Setting update host: %s", mUpdateHost.c_str()); - - std::string updateHost = getHostNameFromURL(mUpdateHost); - - // Exit on a wrong update host. - if (updateHost.length() < 2) + if (mUpdateHost.empty()) { - // Show the original updateHostname in the error message. - errorMessage = strprintf(_("Invalid update host: %s"), - mUpdateHost.c_str()); - mState = STATE_ERROR; - return; + logger->log("No update host provided"); + mUpdatesDir.clear(); + mState = STATE_LOAD_DATA; + return false; } - mUpdateHost = updateHost; - mUpdatesDir = "updates/" + mUpdateHost; + mUpdatesDir = "updates/" + getDirectoryFromURL(mUpdateHost); - ResourceManager *resman = ResourceManager::getInstance(); + logger->log("Update host: %s", mUpdateHost.c_str()); + logger->log("Updates dir: %s", mUpdatesDir.c_str()); // Verify that the updates directory exists. Create if necessary. - if (!resman->isDirectory("/" + mUpdatesDir)) + if (!FS::isDirectory(mUpdatesDir)) { - if (!resman->mkdir("/" + mUpdatesDir)) + if (!FS::mkdir(mUpdatesDir)) { #if defined _WIN32 std::string newDir = mLocalDataDir + "\\" + mUpdatesDir; @@ -1165,9 +1146,12 @@ void Client::initUpdatesDir() strprintf(_("Error creating updates directory!\n(%s/%s)"), mLocalDataDir.c_str(), mUpdatesDir.c_str()); mState = STATE_ERROR; + return false; #endif } } + + return true; } void Client::initScreenshotDir() @@ -1183,7 +1167,7 @@ void Client::initScreenshotDir() if (mScreenshotDir.empty()) mScreenshotDir = getSpecialFolderLocation(FOLDERID_Desktop); #else - mScreenshotDir = std::string(PHYSFS_getUserDir()) + "Desktop"; + mScreenshotDir = std::string(FS::getUserDir()) + "Desktop"; #endif if (config.useScreenshotDirectorySuffix) diff --git a/src/client.h b/src/client.h index e7127ddc..a3e9c572 100644 --- a/src/client.h +++ b/src/client.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef CLIENT_H -#define CLIENT_H +#pragma once #include "video.h" @@ -135,7 +134,7 @@ public: std::string configDir; std::string localDataDir; std::string screenshotDir; - ServerType serverType = ServerType::UNKNOWN; + ServerType serverType = ServerType::Unknown; std::string serverName; uint16_t serverPort = 0; @@ -203,7 +202,7 @@ private: void initRootDir(); void initHomeDir(); void initConfiguration(); - void initUpdatesDir(); + bool initUpdatesDir(); void initScreenshotDir(); void accountLogin(LoginData *loginData); @@ -236,8 +235,5 @@ private: SDL_Surface *mIcon = nullptr; SDL_TimerID mSecondsCounterId = 0; - FpsManager mFpsManager; }; - -#endif // CLIENT_H diff --git a/src/commandhandler.cpp b/src/commandhandler.cpp index 75cca1ab..dd4cb2c0 100644 --- a/src/commandhandler.cpp +++ b/src/commandhandler.cpp @@ -122,7 +122,7 @@ void CommandHandler::handleCommand(const std::string &command, ChatTab *tab) { handlePresent(args, tab); } - else if (type == "showip" && Net::getNetworkType() == ServerType::TMWATHENA) + else if (type == "showip" && Net::getNetworkType() == ServerType::TmwAthena) { handleShowIp(args, tab); } @@ -523,15 +523,15 @@ void CommandHandler::handleIgnore(const std::string &args, ChatTab *tab) return; } - if (player_relations.getRelation(args) == PlayerRelation::IGNORED) + if (player_relations.getRelation(args) == PlayerRelation::Ignored) { tab->chatLog(_("Player already ignored!"), BY_SERVER); return; } else - player_relations.setRelation(args, PlayerRelation::IGNORED); + player_relations.setRelation(args, PlayerRelation::Ignored); - if (player_relations.getRelation(args) == PlayerRelation::IGNORED) + if (player_relations.getRelation(args) == PlayerRelation::Ignored) tab->chatLog(_("Player successfully ignored!"), BY_SERVER); else tab->chatLog(_("Player could not be ignored!"), BY_SERVER); @@ -545,7 +545,7 @@ void CommandHandler::handleUnignore(const std::string &args, ChatTab *tab) return; } - if (player_relations.getRelation(args) == PlayerRelation::IGNORED) + if (player_relations.getRelation(args) == PlayerRelation::Ignored) player_relations.removePlayer(args); else { @@ -553,7 +553,7 @@ void CommandHandler::handleUnignore(const std::string &args, ChatTab *tab) return; } - if (player_relations.getRelation(args) != PlayerRelation::IGNORED) + if (player_relations.getRelation(args) != PlayerRelation::Ignored) tab->chatLog(_("Player no longer ignored!"), BY_SERVER); else tab->chatLog(_("Player could not be unignored!"), BY_SERVER); @@ -561,5 +561,7 @@ void CommandHandler::handleUnignore(const std::string &args, ChatTab *tab) void CommandHandler::handleAway(const std::string &args, ChatTab *tab) { - local_player->setAway(args); + if (!args.empty()) + config.afkMessage = args; + local_player->setAwayMode(!local_player->getAwayMode()); } diff --git a/src/commandhandler.h b/src/commandhandler.h index 42f68b2f..e13b0562 100644 --- a/src/commandhandler.h +++ b/src/commandhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef COMMANDHANDLER_H -#define COMMANDHANDLER_H +#pragma once #include <string> @@ -143,5 +142,3 @@ class CommandHandler }; extern CommandHandler *commandHandler; - -#endif // COMMANDHANDLER_H diff --git a/src/compoundsprite.cpp b/src/compoundsprite.cpp index 2e39fb47..8193d8c0 100644 --- a/src/compoundsprite.cpp +++ b/src/compoundsprite.cpp @@ -29,15 +29,9 @@ #include <SDL.h> -CompoundSprite::CompoundSprite() -{ -} - CompoundSprite::~CompoundSprite() { delete_all(mSprites); - mSprites.clear(); - delete mImage; delete mAlphaImage; } @@ -80,8 +74,7 @@ bool CompoundSprite::update(int time) bool CompoundSprite::draw(Graphics *graphics, int posX, int posY) const { - if (mNeedsRedraw) - redraw(); + doRedraw(); if (mSprites.empty()) // Nothing to draw return false; @@ -96,19 +89,15 @@ bool CompoundSprite::draw(Graphics *graphics, int posX, int posY) const if (mAlpha && mAlphaImage) { - if (mAlphaImage->getAlpha() != mAlpha) - mAlphaImage->setAlpha(mAlpha); - - return graphics->drawImage(mAlphaImage, - posX, posY); + mAlphaImage->setAlpha(mAlpha); + return graphics->drawImage(mAlphaImage, posX, posY); } for (auto sprite : mSprites) { if (sprite) { - if (sprite->getAlpha() != mAlpha) - sprite->setAlpha(mAlpha); + sprite->setAlpha(mAlpha); sprite->draw(graphics, posX - sprite->getWidth() / 2, posY - sprite->getHeight()); } } @@ -116,11 +105,6 @@ bool CompoundSprite::draw(Graphics *graphics, int posX, int posY) const return false; } -const Image *CompoundSprite::getImage() const -{ - return mImage; -} - bool CompoundSprite::setDirection(SpriteDirection direction) { bool ret = false; @@ -141,13 +125,13 @@ int CompoundSprite::getNumberOfLayers() const return size(); } -void CompoundSprite::addSprite(Sprite *sprite) +void CompoundSprite::add(Sprite *sprite) { mSprites.push_back(sprite); mNeedsRedraw = true; } -void CompoundSprite::setSprite(int layer, Sprite *sprite) +void CompoundSprite::set(int layer, Sprite *sprite) { // Skip if it won't change anything if (mSprites.at(layer) == sprite) @@ -158,17 +142,6 @@ void CompoundSprite::setSprite(int layer, Sprite *sprite) mNeedsRedraw = true; } -void CompoundSprite::removeSprite(int layer) -{ - // Skip if it won't change anything - if (!mSprites.at(layer)) - return; - - delete mSprites.at(layer); - mSprites.at(layer) = nullptr; - mNeedsRedraw = true; -} - void CompoundSprite::clear() { // Skip if it won't change anything @@ -189,7 +162,7 @@ void CompoundSprite::ensureSize(size_t layerCount) mSprites.resize(layerCount); } -int CompoundSprite::getDuration() const +int CompoundSprite::getMaxDuration() const { int duration = 0; for (auto sprite : mSprites) @@ -220,8 +193,9 @@ static void updateValues(int &dimension, int &pos, int imgDimUL, int imgDimRD, i void CompoundSprite::redraw() const { #if 1 // TODO_SDL2: Does it make sense to implement CompoundSprite? - mWidth = mSprites.at(0)->getWidth(); - mHeight = mSprites.at(0)->getHeight(); + auto baseSprite = mSprites.empty() ? nullptr : mSprites.at(0); + mWidth = baseSprite ? baseSprite->getWidth() : 0; + mHeight = baseSprite ? baseSprite->getHeight() : 0; mOffsetX = 0; mOffsetY = 0; mNeedsRedraw = false; diff --git a/src/compoundsprite.h b/src/compoundsprite.h index 4daffccb..5aa11686 100644 --- a/src/compoundsprite.h +++ b/src/compoundsprite.h @@ -18,74 +18,50 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef COMPOUNDSPRITE_H -#define COMPOUNDSPRITE_H +#pragma once #include "sprite.h" #include <vector> -class Image; - -class CompoundSprite : public Sprite +class CompoundSprite { public: - CompoundSprite(); - - ~CompoundSprite() override; - - bool reset() final; - - bool play(const std::string &action) final; + CompoundSprite() = default; + ~CompoundSprite(); - bool update(int time) final; - - bool draw(Graphics *graphics, int posX, int posY) const override; + bool reset(); + bool play(const std::string &action); + bool update(int time); + bool draw(Graphics *graphics, int posX, int posY) const; /** * Gets the width in pixels of the first sprite in the list. */ - int getWidth() const override - { return mWidth; } + int getWidth() const { doRedraw(); return mWidth; } /** * Gets the height in pixels of the first sprite in the list. */ - int getHeight() const override - { return mHeight; } - - int getOffsetX() const final - { return mOffsetX; } - - int getOffsetY() const final - { return mOffsetY; } + int getHeight() const { doRedraw(); return mHeight; } - const Image *getImage() const final; + float getAlpha() const { return mAlpha; } + void setAlpha(float alpha) { mAlpha = alpha; } - bool setDirection(SpriteDirection direction) final; + bool setDirection(SpriteDirection direction); int getNumberOfLayers() const; - int getDuration() const final; + int getMaxDuration() const; - size_t size() const - { return mSprites.size(); } - - void addSprite(Sprite *sprite); - - void setSprite(int layer, Sprite *sprite); - - Sprite *getSprite(int layer) const - { return mSprites.at(layer); } - - void removeSprite(int layer); + size_t size() const { return mSprites.size(); } + void add(Sprite *sprite); + void set(int layer, Sprite *sprite); void clear(); - void ensureSize(size_t layerCount); - void doRedraw() - { mNeedsRedraw = true; } + void doRedraw() const; private: void redraw() const; @@ -98,7 +74,12 @@ private: mutable bool mNeedsRedraw = false; + float mAlpha = 1.0f; std::vector<Sprite*> mSprites; }; -#endif // COMPOUNDSPRITE_H +inline void CompoundSprite::doRedraw() const +{ + if (mNeedsRedraw) + redraw(); +} diff --git a/src/configuration.cpp b/src/configuration.cpp index ead228c4..5b45a21d 100644 --- a/src/configuration.cpp +++ b/src/configuration.cpp @@ -302,9 +302,9 @@ static const char *serverTypeToString(ServerType type) { switch (type) { - case ServerType::TMWATHENA: + case ServerType::TmwAthena: return "TmwAthena"; - case ServerType::MANASERV: + case ServerType::ManaServ: return "ManaServ"; default: return ""; @@ -550,7 +550,7 @@ void deserialize(XML::Node node, Config &config) for (auto node : node.children()) { if (node.name() == "player") { std::string playerName; - PlayerRelation relation = PlayerRelation::NEUTRAL; + PlayerRelation relation = PlayerRelation::Neutral; for (auto node : node.children()) { if (node.name() == "option") { diff --git a/src/configuration.h b/src/configuration.h index e0ffae51..510ac269 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef CONFIGURATION_H -#define CONFIGURATION_H +#pragma once #include "being.h" #include "defaults.h" @@ -273,5 +272,3 @@ void setConfigValue(T Config::*member, const T &value) config.*member = value; Event(Event::ConfigOptionChanged, member).trigger(Event::ConfigChannel); } - -#endif diff --git a/src/defaults.cpp b/src/defaults.cpp index dff06bbb..8a0f18a3 100644 --- a/src/defaults.cpp +++ b/src/defaults.cpp @@ -98,6 +98,11 @@ DefaultsData* getPathsDefaults() AddDEF(pathsData, "hitEffectId", 26); AddDEF(pathsData, "criticalHitEffectId", 28); + // This is makes sure that actors positioned on the center of a tile have + // their sprite aligned to the bottom of that tile. The default maintains + // compatibility with existing sprites. + AddDEF(pathsData, "spriteOffsetY", 16); + AddDEF(pathsData, "minimaps", "graphics/minimaps/"); AddDEF(pathsData, "maps", "maps/"); diff --git a/src/defaults.h b/src/defaults.h index a588a543..8180c694 100644 --- a/src/defaults.h +++ b/src/defaults.h @@ -18,8 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef DEFAULTS_H -#define DEFAULTS_H +#pragma once #include <map> #include <string> @@ -29,5 +28,3 @@ using DefaultsData = std::map<std::string, VariableData *>; DefaultsData* getBrandingDefaults(); DefaultsData* getPathsDefaults(); - -#endif diff --git a/src/effectmanager.cpp b/src/effectmanager.cpp index 903f08b2..32280555 100644 --- a/src/effectmanager.cpp +++ b/src/effectmanager.cpp @@ -49,56 +49,57 @@ EffectManager::EffectManager() for (auto node : root.children()) { - if (node.name() == "effect") + int effectId; + + if (node.name() == "effect" && node.attribute("id", effectId)) { - EffectDescription &ed = mEffects.emplace_back(); - ed.id = node.getProperty("id", -1); - ed.GFX = node.getProperty("particle", ""); - ed.SFX = node.getProperty("audio", ""); + EffectDescription &ed = mEffects[effectId]; + node.attribute("particle", ed.particle); + node.attribute("audio", ed.sfx); } } } -EffectManager::~EffectManager() -{ -} +EffectManager::~EffectManager() = default; -bool EffectManager::trigger(int id, Being* being, int rotation) +bool EffectManager::trigger(int id, Being *being, int rotation) { - bool rValue = false; - for (auto &effect : mEffects) + auto it = mEffects.find(id); + if (it == mEffects.end()) { - if (effect.id == id) - { - rValue = true; - if (!effect.GFX.empty()) - { - Particle *selfFX; - selfFX = particleEngine->addEffect(effect.GFX, 0, 0, rotation); - being->controlParticle(selfFX); - } - if (!effect.SFX.empty()) - sound.playSfx(effect.SFX); - break; - } + logger->log("EffectManager::trigger: effect %d not found", id); + return false; + } + + EffectDescription &effect = it->second; + + if (!effect.particle.empty()) + { + if (Particle *selfFX = particleEngine->addEffect(effect.particle, 0, 0, rotation)) + being->controlParticle(selfFX); } - return rValue; + + if (!effect.sfx.empty()) + sound.playSfx(effect.sfx); + + return true; } bool EffectManager::trigger(int id, int x, int y, int rotation) { - bool rValue = false; - for (auto &effect : mEffects) + auto it = mEffects.find(id); + if (it == mEffects.end()) { - if (effect.id == id) - { - rValue = true; - if (!effect.GFX.empty()) - particleEngine->addEffect(effect.GFX, x, y, rotation); - if (!effect.SFX.empty()) - sound.playSfx(effect.SFX); - break; - } + logger->log("EffectManager::trigger: effect %d not found", id); + return false; } - return rValue; + + EffectDescription &effect = it->second; + + if (!effect.particle.empty()) + particleEngine->addEffect(effect.particle, x, y, rotation); + if (!effect.sfx.empty()) + sound.playSfx(effect.sfx); + + return true; } diff --git a/src/effectmanager.h b/src/effectmanager.h index 5936f633..fdc92418 100644 --- a/src/effectmanager.h +++ b/src/effectmanager.h @@ -20,10 +20,9 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef EFFECT_MANAGER_H -#define EFFECT_MANAGER_H +#pragma once -#include <list> +#include <map> #include <string> class Being; @@ -33,9 +32,8 @@ class EffectManager public: struct EffectDescription { - int id; - std::string GFX; - std::string SFX; + std::string particle; + std::string sfx; }; EffectManager(); @@ -47,7 +45,7 @@ class EffectManager * and with the given rotation in degree: * 0° = Down, 90° = left, ... */ - bool trigger(int id, Being* being, int rotation = 0); + bool trigger(int id, Being *being, int rotation = 0); /** * Triggers a effect with the id, at @@ -58,9 +56,7 @@ class EffectManager bool trigger(int id, int x, int y, int rotation = 0); private: - std::list<EffectDescription> mEffects; + std::map<int, EffectDescription> mEffects; }; extern EffectManager *effectManager; - -#endif // EFFECT_MANAGER_H diff --git a/src/emoteshortcut.h b/src/emoteshortcut.h index 598cc124..97648b62 100644 --- a/src/emoteshortcut.h +++ b/src/emoteshortcut.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef EMOTESHORTCUT_H -#define EMOTESHORTCUT_H +#pragma once #define SHORTCUT_EMOTES 12 @@ -115,5 +114,3 @@ class EmoteShortcut }; extern EmoteShortcut *emoteShortcut; - -#endif diff --git a/src/equipment.h b/src/equipment.h index d40ca55d..af059d7a 100644 --- a/src/equipment.h +++ b/src/equipment.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef EQUIPMENT_H -#define EQUIPMENT_H +#pragma once #include <string> @@ -72,5 +71,3 @@ class Equipment private: Backend *mBackend; }; - -#endif diff --git a/src/event.cpp b/src/event.cpp index 11d46a2f..64df4f70 100644 --- a/src/event.cpp +++ b/src/event.cpp @@ -27,8 +27,8 @@ Event::ListenMap Event::mBindings; Event::~Event() { - for (auto it = mData.begin(); it != mData.end(); ++it) - delete it->second; + for (auto &[_, data] : mData) + delete data; } // Integers @@ -191,10 +191,6 @@ void Event::unbind(EventListener *listener, Channel channel) void Event::remove(EventListener *listener) { - auto it = mBindings.begin(); - while (it != mBindings.end()) - { - it->second.erase(listener); - it++; - } + for (auto &[_, listeners] : mBindings) + listeners.erase(listener); } diff --git a/src/event.h b/src/event.h index d3d670e3..06e38007 100644 --- a/src/event.h +++ b/src/event.h @@ -18,8 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef EVENT_H -#define EVENT_H +#pragma once #include <any> #include <map> @@ -75,7 +74,6 @@ public: DoDrop, DoEquip, DoMove, - DoSplit, DoUnequip, DoUse, End, @@ -98,7 +96,6 @@ public: StateChange, StorageCount, StringInput, - Stun, UpdateAttribute, UpdateStat, UpdateStatusEffect, @@ -324,5 +321,3 @@ inline void serverNotice(const std::string &message) event.setString("message", message); event.trigger(Event::NoticesChannel); } - -#endif // EVENT_H diff --git a/src/eventlistener.h b/src/eventlistener.h index 3d4f8967..8c931fa4 100644 --- a/src/eventlistener.h +++ b/src/eventlistener.h @@ -18,8 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef EVENTLISTENER_H -#define EVENTLISTENER_H +#pragma once #include "event.h" @@ -34,5 +33,3 @@ public: virtual void event(Event::Channel channel, const Event &event) = 0; }; - -#endif // EVENTLISTENER_H diff --git a/src/flooritem.cpp b/src/flooritem.cpp index 15975687..9c545560 100644 --- a/src/flooritem.cpp +++ b/src/flooritem.cpp @@ -21,8 +21,13 @@ #include "flooritem.h" +#include "configuration.h" + +#include "resources/image.h" #include "resources/itemdb.h" #include "resources/iteminfo.h" +#include "resources/resourcemanager.h" +#include "resources/theme.h" FloorItem::FloorItem(int id, int itemId, @@ -39,7 +44,33 @@ FloorItem::FloorItem(int id, mX = (int)position.x / map->getTileWidth(); mY = (int)position.y / map->getTileHeight(); - setupSpriteDisplay(itemDb->get(itemId).display); + // Set up sprites and particle effects + auto &info = getInfo(); + setupSpriteDisplay(info.display, false); + + // If no sprites are defined, fall back to the item icon + if (info.display.sprites.empty()) + { + ResourceManager *resman = ResourceManager::getInstance(); + std::string imagePath = paths.getStringValue("itemIcons") + info.display.image; + + mImage = resman->getImage(imagePath); + if (!mImage) + mImage = Theme::getImageFromTheme(paths.getStringValue("unknownItemFile")); + } +} + +bool FloorItem::draw(Graphics *graphics, int offsetX, int offsetY) const +{ + if (mImage) + { + mImage->setAlpha(getAlpha()); + return graphics->drawImage(mImage, + getPixelX() + offsetX - mImage->getWidth() / 2, + getPixelY() + offsetY - mImage->getHeight() / 2); + } + + return ActorSprite::draw(graphics, offsetX, offsetY); } const ItemInfo &FloorItem::getInfo() const diff --git a/src/flooritem.h b/src/flooritem.h index 8326982f..909d6195 100644 --- a/src/flooritem.h +++ b/src/flooritem.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef FLOORITEM_H -#define FLOORITEM_H +#pragma once #include "actorsprite.h" @@ -29,7 +28,7 @@ class ItemInfo; /** * An item lying on the floor. */ -class FloorItem : public ActorSprite +class FloorItem final : public ActorSprite { public: /** @@ -47,11 +46,12 @@ class FloorItem : public ActorSprite Type getType() const override { return FLOOR_ITEM; } + bool draw(Graphics *graphics, int offsetX, int offsetY) const override; + /** * Returns the item ID. */ - int getItemId() const - { return mItemId; } + int getItemId() const { return mItemId; } /** * Returns the item info for this floor item. Useful for adding an item @@ -59,15 +59,11 @@ class FloorItem : public ActorSprite */ const ItemInfo &getInfo() const; - int getTileX() const override - { return mX; } - - int getTileY() const override - { return mY; } + int getTileX() const override { return mX; } + int getTileY() const override { return mY; } private: int mItemId; int mX, mY; + ResourceRef<Image> mImage; }; - -#endif // FLOORITEM_H diff --git a/src/game.cpp b/src/game.cpp index c8b295bb..9657bee7 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -57,7 +57,7 @@ #include "gui/sdlinput.h" #include "gui/setup.h" #include "gui/socialwindow.h" -#include "gui/specialswindow.h" +#include "gui/abilitieswindow.h" #include "gui/skilldialog.h" #include "gui/statuswindow.h" #include "gui/textdialog.h" @@ -74,16 +74,14 @@ #include "resources/imagewriter.h" #include "resources/mapreader.h" -#include "resources/resourcemanager.h" +#include "utils/filesystem.h" #include "utils/gettext.h" #include "utils/mkdir.h" #include <guichan/exception.hpp> #include <guichan/focushandler.hpp> -#include <physfs.h> - #include <fstream> #include <sstream> @@ -106,7 +104,7 @@ DebugWindow *debugWindow; ShortcutWindow *itemShortcutWindow; ShortcutWindow *emoteShortcutWindow; OutfitWindow *outfitWindow; -SpecialsWindow *specialsWindow; +AbilitiesWindow *abilitiesWindow; SocialWindow *socialWindow; ActorSpriteManager *actorSpriteManager; @@ -131,7 +129,7 @@ static void initEngines() effectManager = new EffectManager; particleEngine = new Particle(nullptr); - particleEngine->setupEngine(); + Particle::setupEngine(); Event::trigger(Event::GameChannel, Event::EnginesInitialized); } @@ -161,7 +159,7 @@ static void createGuiWindows() emoteShortcutWindow = new ShortcutWindow("EmoteShortcut", new EmoteShortcutContainer); outfitWindow = new OutfitWindow(); - specialsWindow = new SpecialsWindow(); + abilitiesWindow = new AbilitiesWindow(); socialWindow = new SocialWindow(); localChatTab = new ChatTab(_("General")); @@ -194,7 +192,7 @@ static void destroyGuiWindows() del_0(itemShortcutWindow) del_0(emoteShortcutWindow) del_0(outfitWindow) - del_0(specialsWindow) + del_0(abilitiesWindow) del_0(socialWindow) Event::trigger(Event::NpcChannel, Event::CloseAll); // Cleanup remaining NPC dialogs @@ -252,6 +250,7 @@ Game::~Game() del_0(actorSpriteManager) if (Client::getState() != STATE_CHANGE_MAP) del_0(local_player) + del_0(effectManager) del_0(channelManager) del_0(commandHandler) del_0(joystick) @@ -304,7 +303,7 @@ static bool saveScreenshot() logger->log("Directory %s doesn't exist and can't be created! " "Setting screenshot directory to home.", screenshotDirectory.c_str()); - screenshotDirectory = PHYSFS_getUserDir(); + screenshotDirectory = FS::getUserDir(); } do @@ -928,9 +927,6 @@ void Game::changeMap(const std::string &mapPath) std::string fullMap = paths.getValue("maps", "maps/") + mMapName + ".tmx"; - ResourceManager *resman = ResourceManager::getInstance(); - if (!resman->exists(fullMap)) - fullMap += ".gz"; // Attempt to load the new map Map *newMap = MapReader::readMap(fullMap); @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef GAME_H -#define GAME_H +#pragma once #include <string> @@ -97,5 +96,3 @@ class Game static Game *mInstance; }; - -#endif diff --git a/src/graphics.cpp b/src/graphics.cpp index afdac4f6..9b8cce83 100644 --- a/src/graphics.cpp +++ b/src/graphics.cpp @@ -25,14 +25,40 @@ #include <guichan/exception.hpp> +ImageRect::ImageRect() +{ + memset(grid, 0, sizeof(grid)); +} + +ImageRect::ImageRect(ImageRect &&r) +{ + memcpy(grid, r.grid, sizeof(grid)); + memset(r.grid, 0, sizeof(grid)); +} + +ImageRect::~ImageRect() +{ + for (auto img : grid) + delete img; +} + void ImageRect::setAlpha(float alpha) { for (auto img : grid) - { img->setAlpha(alpha); - } } +int ImageRect::minWidth() const +{ + return grid[ImageRect::UPPER_LEFT]->getWidth() + grid[ImageRect::UPPER_RIGHT]->getWidth(); +} + +int ImageRect::minHeight() const +{ + return grid[ImageRect::UPPER_LEFT]->getHeight() + grid[ImageRect::LOWER_LEFT]->getHeight(); +} + + void Graphics::updateSize(int width, int height, float /*scale*/) { mWidth = width; @@ -55,6 +81,14 @@ bool Graphics::drawImageF(const Image *image, float x, float y) return drawImageF(image, 0, 0, x, y, image->getWidth(), image->getHeight()); } +bool Graphics::drawRescaledImage(const Image *image, int x, int y, int width, int height) +{ + if (!image) + return false; + + return drawRescaledImage(image, 0, 0, x, y, image->getWidth(), image->getHeight(), width, height); +} + bool Graphics::drawRescaledImageF(const Image *image, int srcX, int srcY, float dstX, float dstY, int width, int height, float desiredWidth, float desiredHeight, bool useColor) { return drawRescaledImage(image, @@ -103,51 +137,95 @@ void Graphics::drawImageRect(int x, int y, int w, int h, const Image *bottomLeft, const Image *bottomRight, const Image *top, const Image *right, const Image *bottom, const Image *left, - const Image *center) -{ - pushClipArea(gcn::Rectangle(x, y, w, h)); - - // Draw the center area - drawImagePattern(center, - topLeft->getWidth(), topLeft->getHeight(), - w - topLeft->getWidth() - topRight->getWidth(), - h - topLeft->getHeight() - bottomLeft->getHeight()); - - // Draw the sides - drawImagePattern(top, - left->getWidth(), 0, - w - left->getWidth() - right->getWidth(), top->getHeight()); - drawImagePattern(bottom, - left->getWidth(), h - bottom->getHeight(), - w - left->getWidth() - right->getWidth(), - bottom->getHeight()); - drawImagePattern(left, - 0, top->getHeight(), - left->getWidth(), - h - top->getHeight() - bottom->getHeight()); - drawImagePattern(right, - w - right->getWidth(), top->getHeight(), - right->getWidth(), - h - top->getHeight() - bottom->getHeight()); + const Image *center, + FillMode fillMode) +{ + switch (fillMode) { + case FillMode::Stretch: + // Draw the center area + drawRescaledImage(center, + x + topLeft->getWidth(), + y + topLeft->getHeight(), + w - topLeft->getWidth() - topRight->getWidth(), + h - topLeft->getHeight() - bottomLeft->getHeight()); + + // Draw the sides + drawRescaledImage(top, + x + left->getWidth(), + y, + w - left->getWidth() - right->getWidth(), + top->getHeight()); + + drawRescaledImage(bottom, + x + left->getWidth(), + y + h - bottom->getHeight(), + w - left->getWidth() - right->getWidth(), + bottom->getHeight()); + + drawRescaledImage(left, + x, + y + top->getHeight(), + left->getWidth(), + h - top->getHeight() - bottom->getHeight()); + + drawRescaledImage(right, + x + w - right->getWidth(), + y + top->getHeight(), + right->getWidth(), + h - top->getHeight() - bottom->getHeight()); + break; + case FillMode::Repeat: + // Draw the center area + drawImagePattern(center, + x + topLeft->getWidth(), + y + topLeft->getHeight(), + w - topLeft->getWidth() - topRight->getWidth(), + h - topLeft->getHeight() - bottomLeft->getHeight()); + + // Draw the sides + drawImagePattern(top, + x + left->getWidth(), + y, + w - left->getWidth() - right->getWidth(), + top->getHeight()); + + drawImagePattern(bottom, + x + left->getWidth(), + y + h - bottom->getHeight(), + w - left->getWidth() - right->getWidth(), + bottom->getHeight()); + + drawImagePattern(left, + x, + y + top->getHeight(), + left->getWidth(), + h - top->getHeight() - bottom->getHeight()); + + drawImagePattern(right, + x + w - right->getWidth(), + y + top->getHeight(), + right->getWidth(), + h - top->getHeight() - bottom->getHeight()); + break; + } // Draw the corners - drawImage(topLeft, 0, 0); - drawImage(topRight, w - topRight->getWidth(), 0); - drawImage(bottomLeft, 0, h - bottomLeft->getHeight()); + drawImage(topLeft, x, y); + drawImage(topRight, x + w - topRight->getWidth(), y); + drawImage(bottomLeft, x, y + h - bottomLeft->getHeight()); drawImage(bottomRight, - w - bottomRight->getWidth(), - h - bottomRight->getHeight()); - - popClipArea(); + x + w - bottomRight->getWidth(), + y + h - bottomRight->getHeight()); } void Graphics::drawImageRect(int x, int y, int w, int h, const ImageRect &imgRect) { drawImageRect(x, y, w, h, - imgRect.grid[0], imgRect.grid[2], imgRect.grid[6], imgRect.grid[8], - imgRect.grid[1], imgRect.grid[5], imgRect.grid[7], imgRect.grid[3], - imgRect.grid[4]); + imgRect.grid[0], imgRect.grid[2], imgRect.grid[6], imgRect.grid[8], + imgRect.grid[1], imgRect.grid[5], imgRect.grid[7], imgRect.grid[3], + imgRect.grid[4], + imgRect.fillMode); } void Graphics::_beginDraw() @@ -159,3 +237,20 @@ void Graphics::_endDraw() { popClipArea(); } + +void Graphics::pushClipRect(const gcn::Rectangle &rect) +{ + const gcn::ClipRectangle &carea = mClipStack.top(); + mClipRects.emplace(rect.x + carea.xOffset, + rect.y + carea.yOffset, + rect.width, + rect.height); + + updateClipRect(); +} + +void Graphics::popClipRect() +{ + mClipRects.pop(); + updateClipRect(); +} diff --git a/src/graphics.h b/src/graphics.h index 9db92034..4b03023d 100644 --- a/src/graphics.h +++ b/src/graphics.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef GRAPHICS_H -#define GRAPHICS_H +#pragma once #include <SDL.h> @@ -29,6 +28,12 @@ class Image; +enum class FillMode +{ + Stretch, + Repeat, +}; + /** * 9 images defining a rectangle. 4 corners, 4 sides and a middle area. The * topology is as follows: @@ -62,9 +67,20 @@ public: LOWER_RIGHT = 8 }; + ImageRect(); + ImageRect(const ImageRect &) = delete; + ImageRect(ImageRect &&); + ~ImageRect(); + + ImageRect &operator=(const ImageRect &) = delete; + ImageRect &operator=(ImageRect &&r) = delete; + Image *grid[9]; + FillMode fillMode = FillMode::Stretch; void setAlpha(float alpha); + int minWidth() const; + int minHeight() const; }; /** @@ -106,6 +122,11 @@ class Graphics : public gcn::Graphics /** * Draws a rescaled version of the image. */ + bool drawRescaledImage(const Image *image, int x, int y, int width, int height); + + /** + * Draws a rescaled version of the image. + */ virtual bool drawRescaledImage(const Image *image, int srcX, int srcY, int dstX, int dstY, int width, int height, @@ -167,7 +188,8 @@ class Graphics : public gcn::Graphics const Image *bottomLeft, const Image *bottomRight, const Image *top, const Image *right, const Image *bottom, const Image *left, - const Image *center); + const Image *center, + FillMode fillMode = FillMode::Stretch); /** * Draws a rectangle using images. 4 corner images, 4 side images and 1 @@ -239,13 +261,19 @@ class Graphics : public gcn::Graphics return mColor; } + void pushClipRect(const gcn::Rectangle &rect); + void popClipRect(); + protected: + virtual void updateClipRect() = 0; + int mWidth = 0; int mHeight = 0; float mScale = 1.0f; gcn::Color mColor; + + // Actual clipping rects. Clipping by gcn::Graphics::mClipStack is disabled. + std::stack<gcn::Rectangle> mClipRects; }; extern Graphics *graphics; - -#endif diff --git a/src/gui/specialswindow.cpp b/src/gui/abilitieswindow.cpp index 340a1e41..700fa7ff 100644 --- a/src/gui/specialswindow.cpp +++ b/src/gui/abilitieswindow.cpp @@ -18,7 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include "gui/specialswindow.h" +#include "gui/abilitieswindow.h" #include "log.h" @@ -32,9 +32,9 @@ #include "gui/widgets/windowcontainer.h" #include "net/net.h" -#include "net/specialhandler.h" +#include "net/abilityhandler.h" -#include "resources/specialdb.h" +#include "resources/abilitydb.h" #include "resources/theme.h" #include "utils/dtor.h" @@ -44,19 +44,19 @@ #include <string> -#define SPECIALS_WIDTH 200 -#define SPECIALS_HEIGHT 32 +#define ABILITIES_WIDTH 200 +#define ABILITIES_HEIGHT 32 -class SpecialEntry : public Container +class AbilityEntry : public Container { public: - SpecialEntry(SpecialInfo *info); + AbilityEntry(AbilityInfo *info); void update(int current, int needed); protected: - friend class SpecialsWindow; - SpecialInfo *mInfo; + friend class AbilitiesWindow; + AbilityInfo *mInfo; private: Icon *mIcon = nullptr; // icon to display @@ -65,46 +65,46 @@ class SpecialEntry : public Container ProgressBar *mRechargeBar = nullptr; // recharge bar (only shown when applicable) }; -SpecialsWindow::SpecialsWindow(): - Window(_("Specials")) +AbilitiesWindow::AbilitiesWindow(): + Window(_("Abilities")) { - setWindowName("Specials"); + setWindowName("Abilities"); setCloseButton(true); setResizable(true); setSaveVisible(true); - setDefaultSize(windowContainer->getWidth() - 280, 40, SPECIALS_WIDTH + 20, 225); + setDefaultSize(windowContainer->getWidth() - 280, 40, ABILITIES_WIDTH + 20, 225); setupWindow->registerWindowForReset(this); center(); loadWindowState(); } -SpecialsWindow::~SpecialsWindow() +AbilitiesWindow::~AbilitiesWindow() { // Clear gui } -void SpecialsWindow::action(const gcn::ActionEvent &event) +void AbilitiesWindow::action(const gcn::ActionEvent &event) { if (event.getId() == "use") { - auto *disp = dynamic_cast<SpecialEntry*>(event.getSource()->getParent()); + auto *disp = dynamic_cast<AbilityEntry*>(event.getSource()->getParent()); if (disp) { - if (disp->mInfo->targetMode == SpecialInfo::TARGET_BEING) + if (disp->mInfo->targetMode == AbilityInfo::TARGET_BEING) { Being *target = local_player->getTarget(); if (target) - Net::getSpecialHandler()->use(disp->mInfo->id, 1, target->getId()); + Net::getAbilityHandler()->useOn(disp->mInfo->id, target->getId()); else - Net::getSpecialHandler()->use(disp->mInfo->id); + Net::getAbilityHandler()->use(disp->mInfo->id); } else { // TODO: Allow the player to aim at a position on the map and - // Use special on the map position. + // Use abilities on the map position. } } } @@ -114,73 +114,73 @@ void SpecialsWindow::action(const gcn::ActionEvent &event) } } -void SpecialsWindow::draw(gcn::Graphics *graphics) +void AbilitiesWindow::draw(gcn::Graphics *graphics) { // update the progress bars - std::map<int, Special> specialData = PlayerInfo::getSpecialStatus(); + const std::map<int, Ability> &abilityData = PlayerInfo::getAbilityStatus(); bool foundNew = false; - unsigned int found = 0; // number of entries in specialData which match mEntries + unsigned int found = 0; // number of entries in abilityData which match mEntries - for (auto &special : specialData) + for (auto &[id, ability] : abilityData) { - auto e = mEntries.find(special.first); + auto e = mEntries.find(id); if (e == mEntries.end()) { - // found a new special - abort update and rebuild from scratch + // found a new ability - abort update and rebuild from scratch foundNew = true; break; } - // update progress bar of special - e->second->update(special.second.currentMana, special.second.neededMana); + // update progress bar of ability + e->second->update(ability.currentMana, ability.neededMana); found++; } - // a rebuild is needed when a) the number of specials changed or b) an existing entry isn't found anymore + // a rebuild is needed when a) the number of abilities changed or b) an existing entry isn't found anymore if (foundNew || found != mEntries.size()) - rebuild(specialData); + rebuild(abilityData); Window::draw(graphics); } -void SpecialsWindow::rebuild(const std::map<int, Special> &specialData) +void AbilitiesWindow::rebuild(const std::map<int, Ability> &abilityData) { delete_all(mEntries); mEntries.clear(); int vPos = 0; //vertical position of next placed element - for (auto special : specialData) + for (auto &[id, ability] : abilityData) { - logger->log("Updating special GUI for %d", special.first); + logger->log("Updating ability GUI for %d", id); - SpecialInfo *info = SpecialDB::get(special.first); + AbilityInfo *info = AbilityDB::get(id); if (info) { - info->rechargeCurrent = special.second.currentMana; - info->rechargeNeeded = special.second.neededMana; - auto* entry = new SpecialEntry(info); + info->rechargeCurrent = ability.currentMana; + info->rechargeNeeded = ability.neededMana; + auto *entry = new AbilityEntry(info); entry->setPosition(0, vPos); vPos += entry->getHeight() + 3; add(entry); - mEntries[special.first] = entry; + mEntries[id] = entry; } else { - logger->log("Warning: No info available of special %d", special.first); + logger->log("Warning: No info available of ability %d", id); } } } -SpecialEntry::SpecialEntry(SpecialInfo *info) : +AbilityEntry::AbilityEntry(AbilityInfo *info) : mInfo(info) { - setSize(SPECIALS_WIDTH, SPECIALS_HEIGHT); + setSize(ABILITIES_WIDTH, ABILITIES_HEIGHT); if (!info->icon.empty()) mIcon = new Icon(info->icon); else - mIcon = new Icon(Theme::resolveThemePath("unknown-item.png")); + mIcon = new Icon(Theme::getImageFromTheme("unknown-item.png")); mIcon->setPosition(1, 0); add(mIcon); @@ -189,7 +189,7 @@ SpecialEntry::SpecialEntry(SpecialInfo *info) : mNameLabel->setPosition(35, 0); add(mNameLabel); - mUse = new Button("Use", "use", specialsWindow); + mUse = new Button("Use", "use", abilitiesWindow); mUse->setPosition(getWidth() - mUse->getWidth(), 5); add(mUse); @@ -203,7 +203,7 @@ SpecialEntry::SpecialEntry(SpecialInfo *info) : } } -void SpecialEntry::update(int current, int needed) +void AbilityEntry::update(int current, int needed) { if (mRechargeBar) { diff --git a/src/gui/specialswindow.h b/src/gui/abilitieswindow.h index 4ed4db3e..ddc5a9e4 100644 --- a/src/gui/specialswindow.h +++ b/src/gui/abilitieswindow.h @@ -18,8 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SPECIALSWINDOW_H -#define SPECIALSWINDOW_H +#pragma once #include "playerinfo.h" @@ -29,14 +28,14 @@ #include <map> -class SpecialEntry; +class AbilityEntry; -class SpecialsWindow : public Window, public gcn::ActionListener +class AbilitiesWindow : public Window, public gcn::ActionListener { public: - SpecialsWindow(); + AbilitiesWindow(); - ~SpecialsWindow() override; + ~AbilitiesWindow() override; /** * Called when receiving actions from widget. @@ -45,16 +44,14 @@ class SpecialsWindow : public Window, public gcn::ActionListener void draw(gcn::Graphics *graphics) override; - bool hasSpecials() const + bool hasAbilities() const { return !mEntries.empty(); } private: - // (re)constructs the list of specials - void rebuild(const std::map<int, Special> &specialData); + // (re)constructs the list of abilities + void rebuild(const std::map<int, Ability> &abilityData); - std::map<int, SpecialEntry *> mEntries; + std::map<int, AbilityEntry *> mEntries; }; -extern SpecialsWindow *specialsWindow; - -#endif // SPECIALSWINDOW_H +extern AbilitiesWindow *abilitiesWindow; diff --git a/src/gui/beingpopup.h b/src/gui/beingpopup.h index 45169464..377b1b4e 100644 --- a/src/gui/beingpopup.h +++ b/src/gui/beingpopup.h @@ -18,8 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef BEINGPOPUP_H -#define BEINGPOPUP_H +#pragma once #include "gui/widgets/popup.h" @@ -47,5 +46,3 @@ class BeingPopup : public Popup Label *mBeingName; Label *mBeingParty; }; - -#endif // BEINGPOPUP_H diff --git a/src/gui/buydialog.h b/src/gui/buydialog.h index b1126225..4c67ef0d 100644 --- a/src/gui/buydialog.h +++ b/src/gui/buydialog.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef BUY_H -#define BUY_H +#pragma once #include "guichanfwd.h" @@ -124,5 +123,3 @@ class BuyDialog : public Window, public gcn::ActionListener, int mAmountItems; int mMaxItems; }; - -#endif diff --git a/src/gui/buyselldialog.h b/src/gui/buyselldialog.h index 2e324d42..be382ffa 100644 --- a/src/gui/buyselldialog.h +++ b/src/gui/buyselldialog.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef BUYSELL_H -#define BUYSELL_H +#pragma once #include "gui/widgets/window.h" @@ -63,5 +62,3 @@ class BuySellDialog : public Window, public gcn::ActionListener int mNpcId; gcn::Button *mBuyButton; }; - -#endif diff --git a/src/gui/changeemaildialog.h b/src/gui/changeemaildialog.h index 37d3ce01..cdf5664c 100644 --- a/src/gui/changeemaildialog.h +++ b/src/gui/changeemaildialog.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef GUI_CHANGEEMAIL_H -#define GUI_CHANGEEMAIL_H +#pragma once #include "gui/widgets/window.h" @@ -64,5 +63,3 @@ class ChangeEmailDialog : public Window, public gcn::ActionListener LoginData *mLoginData; }; - -#endif // GUI_CHANGEEMAIL_H diff --git a/src/gui/changepassworddialog.h b/src/gui/changepassworddialog.h index 22d449d1..541e0444 100644 --- a/src/gui/changepassworddialog.h +++ b/src/gui/changepassworddialog.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef CHANGEPASSWORDDIALOG_H -#define CHANGEPASSWORDDIALOG_H +#pragma once #include "gui/widgets/window.h" @@ -59,5 +58,3 @@ class ChangePasswordDialog : public Window, public gcn::ActionListener LoginData *mLoginData; }; - -#endif diff --git a/src/gui/charcreatedialog.cpp b/src/gui/charcreatedialog.cpp index fa1144b6..cff7d822 100644 --- a/src/gui/charcreatedialog.cpp +++ b/src/gui/charcreatedialog.cpp @@ -50,7 +50,7 @@ CharCreateDialog::CharCreateDialog(CharSelectDialog *parent, int slot): mSlot(slot) { mPlayer = new Being(0, ActorSprite::PLAYER, 0, nullptr); - mPlayer->setGender(Gender::MALE); + mPlayer->setGender(Gender::Male); const std::vector<int> &items = CharDB::getDefaultItems(); for (size_t i = 0; i < items.size(); ++i) @@ -70,18 +70,6 @@ CharCreateDialog::CharCreateDialog(CharSelectDialog *parent, int slot): mNameField = new TextField(std::string()); mNameLabel = new Label(_("Name:")); - mNextHairColorButton = new Button("", "nextcolor", this); - mPrevHairColorButton = new Button("", "prevcolor", this); - mPrevHairColorButton->setButtonIcon("tab_arrows_left.png"); - mNextHairColorButton->setButtonIcon("tab_arrows_right.png"); - - mHairColorLabel = new Label(_("Hair color:")); - mNextHairStyleButton = new Button("", "nextstyle", this); - mPrevHairStyleButton = new Button("", "prevstyle", this); - mPrevHairStyleButton->setButtonIcon("tab_arrows_left.png"); - mNextHairStyleButton->setButtonIcon("tab_arrows_right.png"); - - mHairStyleLabel = new Label(_("Hair style:")); mCreateButton = new Button(_("Create"), "create", this); mCancelButton = new Button(_("Cancel"), "cancel", this); mMale = new RadioButton(_("Male"), "gender"); @@ -112,12 +100,6 @@ CharCreateDialog::CharCreateDialog(CharSelectDialog *parent, int slot): mNameLabel->setPosition(5, 5); mNameField->setDimension( gcn::Rectangle(45, 5, w - 45 - 7, mNameField->getHeight())); - mPrevHairColorButton->setPosition(90, 35); - mNextHairColorButton->setPosition(165, 35); - mHairColorLabel->setPosition(5, 40); - mPrevHairStyleButton->setPosition(90, 64); - mNextHairStyleButton->setPosition(165, 64); - mHairStyleLabel->setPosition(5, 70); mAttributesLeft->setPosition(15, 280); updateSliders(); mCancelButton->setPosition( @@ -136,16 +118,36 @@ CharCreateDialog::CharCreateDialog(CharSelectDialog *parent, int slot): if (mHairColorsIds.size() > 1) { - add(mNextHairColorButton); - add(mPrevHairColorButton); - add(mHairColorLabel); + auto hairColorLabel = new Label(_("Hair color:")); + auto nextHairColorButton = new Button("", "nextcolor", this); + auto prevHairColorButton = new Button("", "prevcolor", this); + prevHairColorButton->setButtonIcon("tab_arrows_left.png"); + nextHairColorButton->setButtonIcon("tab_arrows_right.png"); + + hairColorLabel->setPosition(5, 40); + prevHairColorButton->setPosition(90, 35); + nextHairColorButton->setPosition(165, 35); + + add(nextHairColorButton); + add(prevHairColorButton); + add(hairColorLabel); } if (mHairStylesIds.size() > 1) { - add(mNextHairStyleButton); - add(mPrevHairStyleButton); - add(mHairStyleLabel); + auto hairStyleLabel = new Label(_("Hair style:")); + auto nextHairStyleButton = new Button("", "nextstyle", this); + auto prevHairStyleButton = new Button("", "prevstyle", this); + prevHairStyleButton->setButtonIcon("tab_arrows_left.png"); + nextHairStyleButton->setButtonIcon("tab_arrows_right.png"); + + hairStyleLabel->setPosition(5, 70); + prevHairStyleButton->setPosition(90, 64); + nextHairStyleButton->setPosition(165, 64); + + add(nextHairStyleButton); + add(prevHairStyleButton); + add(hairStyleLabel); } add(mAttributesLeft); @@ -165,14 +167,15 @@ CharCreateDialog::~CharCreateDialog() delete mPlayer; // Make sure the char server handler knows that we're gone - Net::getCharHandler()->setCharCreateDialog(nullptr); + if (auto charHandler = Net::getCharHandler()) + charHandler->setCharCreateDialog(nullptr); } void CharCreateDialog::action(const gcn::ActionEvent &event) { if (event.getId() == "create") { - if (Net::getNetworkType() == ServerType::MANASERV + if (Net::getNetworkType() == ServerType::ManaServ || getName().length() >= 4) { // Attempt to create the character @@ -180,7 +183,7 @@ void CharCreateDialog::action(const gcn::ActionEvent &event) int characterSlot = mSlot; // On Manaserv, the slots start at 1, so we offset them. - if (Net::getNetworkType() == ServerType::MANASERV) + if (Net::getNetworkType() == ServerType::ManaServ) ++characterSlot; // Should avoid the most common crash case @@ -231,9 +234,9 @@ void CharCreateDialog::action(const gcn::ActionEvent &event) else if (event.getId() == "gender") { if (mMale->isSelected()) - mPlayer->setGender(Gender::MALE); + mPlayer->setGender(Gender::Male); else - mPlayer->setGender(Gender::FEMALE); + mPlayer->setGender(Gender::Female); } } @@ -341,7 +344,7 @@ void CharCreateDialog::setAttributes(const std::vector<std::string> &labels, add(attributeLabel); auto *attributeSlider = new Slider(min, max); - attributeSlider->setDimension(gcn::Rectangle(75, y, 100, 10)); + attributeSlider->setDimension(gcn::Rectangle(75, y, 100, attributeSlider->getHeight())); attributeSlider->setActionEventId("statslider"); attributeSlider->addActionListener(this); add(attributeSlider); @@ -373,7 +376,7 @@ void CharCreateDialog::setAttributes(const std::vector<std::string> &labels, void CharCreateDialog::setDefaultGender(Gender gender) { - if (gender == Gender::FEMALE) + if (gender == Gender::Female) { mFemale->setSelected(true); mMale->setSelected(false); diff --git a/src/gui/charcreatedialog.h b/src/gui/charcreatedialog.h index 46db6229..f1759650 100644 --- a/src/gui/charcreatedialog.h +++ b/src/gui/charcreatedialog.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef CHAR_CREATE_DIALOG_H -#define CHAR_CREATE_DIALOG_H +#pragma once #include "being.h" #include "guichanfwd.h" @@ -82,12 +81,6 @@ class CharCreateDialog : public Window, public gcn::ActionListener gcn::TextField *mNameField; gcn::Label *mNameLabel; - Button *mNextHairColorButton; - Button *mPrevHairColorButton; - gcn::Label *mHairColorLabel; - Button *mNextHairStyleButton; - Button *mPrevHairStyleButton; - gcn::Label *mHairStyleLabel; gcn::RadioButton *mMale; gcn::RadioButton *mFemale; @@ -114,5 +107,3 @@ class CharCreateDialog : public Window, public gcn::ActionListener int mSlot; }; - -#endif // CHAR_CREATE_DIALOG_H diff --git a/src/gui/charselectdialog.cpp b/src/gui/charselectdialog.cpp index 7792813c..2485be69 100644 --- a/src/gui/charselectdialog.cpp +++ b/src/gui/charselectdialog.cpp @@ -150,7 +150,7 @@ CharSelectDialog::CharSelectDialog(LoginData *loginData): for (int i = 0; i < (int)mLoginData->characterSlots; i++) { mCharacterEntries.push_back(new CharacterDisplay(this)); - place(i % SLOTS_PER_ROW, (int)i / SLOTS_PER_ROW, mCharacterEntries[i]); + place(i % SLOTS_PER_ROW, i / SLOTS_PER_ROW, mCharacterEntries[i]); } reflowLayout(); @@ -266,7 +266,7 @@ void CharSelectDialog::setCharacters(const Net::Characters &characters) { // Slots Number start at 1 for Manaserv, so we offset them by one. int characterSlot = character->slot; - if (Net::getNetworkType() == ServerType::MANASERV && characterSlot > 0) + if (Net::getNetworkType() == ServerType::ManaServ && characterSlot > 0) --characterSlot; if (characterSlot >= (int)mCharacterEntries.size()) diff --git a/src/gui/charselectdialog.h b/src/gui/charselectdialog.h index 2deb5a7d..e5e558c9 100644 --- a/src/gui/charselectdialog.h +++ b/src/gui/charselectdialog.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef CHAR_SELECT_H -#define CHAR_SELECT_H +#pragma once #include "gui/widgets/window.h" @@ -100,5 +99,3 @@ class CharSelectDialog : public Window, public gcn::ActionListener, Net::CharHandler *mCharHandler; }; - -#endif diff --git a/src/gui/chatwindow.cpp b/src/gui/chatwindow.cpp index 453baea2..8a34c961 100644 --- a/src/gui/chatwindow.cpp +++ b/src/gui/chatwindow.cpp @@ -76,18 +76,17 @@ class ChatAutoComplete : public AutoCompleteLister { void getAutoCompleteList(std::vector<std::string> &list) const override { - auto *tab = static_cast<ChatTab*>(chatWindow->mChatTabs - ->getSelectedTab()); - - return tab->getAutoCompleteList(list); + auto tab = static_cast<ChatTab *>(chatWindow->mChatTabs->getSelectedTab()); + tab->getAutoCompleteList(list); } }; ChatWindow::ChatWindow(): Window(_("Chat")), - mHistory(new TextHistory()), + mItemLinkHandler(new ItemLinkHandler(this)), + mChatInput(new ChatInput), mAutoComplete(new ChatAutoComplete), - mTmpVisible(false) + mChatTabs(new TabbedArea) { listen(Event::ChatChannel); listen(Event::NoticesChannel); @@ -106,21 +105,16 @@ ChatWindow::ChatWindow(): setMinWidth(150); setMinHeight(90); - mItemLinkHandler = new ItemLinkHandler(this); - - mChatInput = new ChatInput; mChatInput->setActionEventId("chatinput"); mChatInput->addActionListener(this); - mChatTabs = new TabbedArea; - getLayout().setPadding(3); place(0, 0, mChatTabs, 3, 3); place(0, 3, mChatInput, 3); loadWindowState(); - mChatInput->setHistory(mHistory); + mChatInput->setHistory(&mHistory); mChatInput->setAutoComplete(mAutoComplete); mRecorder = new Recorder(this); @@ -131,7 +125,6 @@ ChatWindow::~ChatWindow() delete mRecorder; removeAllWhispers(); delete mItemLinkHandler; - delete mHistory; delete mAutoComplete; } @@ -146,15 +139,10 @@ ChatTab *ChatWindow::getFocused() const return static_cast<ChatTab*>(mChatTabs->getSelectedTab()); } -void ChatWindow::clearTab(ChatTab *tab) -{ - if (tab) - tab->clearText(); -} - void ChatWindow::clearTab() { - clearTab(getFocused()); + if (auto tab = getFocused()) + tab->clearText(); } void ChatWindow::prevTab() diff --git a/src/gui/chatwindow.h b/src/gui/chatwindow.h index 59ab6d99..6cac6f8e 100644 --- a/src/gui/chatwindow.h +++ b/src/gui/chatwindow.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef CHAT_H -#define CHAT_H +#pragma once #include "eventlistener.h" @@ -32,21 +31,14 @@ #include <guichan/widget.hpp> #include <guichan/widgetlistener.hpp> -#include <list> #include <string> #include <map> -#include <vector> -class BrowserBox; class ChatTab; -class Channel; class ChatInput; class Recorder; -class ScrollArea; class TabbedArea; class ItemLinkHandler; -class Tab; -class WhisperTab; #define DEFAULT_CHAT_WINDOW_SCROLL 7 // 1 means `1/8th of the window size'. @@ -99,11 +91,6 @@ class ChatWindow : public Window, ChatTab *getFocused() const; /** - * Clear the given tab. - */ - void clearTab(ChatTab *tab); - - /** * Clear the current tab. */ void clearTab(); @@ -202,11 +189,11 @@ class ChatWindow : public Window, /** Input box for typing chat messages. */ ChatInput *mChatInput; - TextHistory *mHistory; + TextHistory mHistory; AutoCompleteLister *mAutoComplete; private: - bool mTmpVisible; + bool mTmpVisible = false; /** Tabbed area for holding each channel. */ TabbedArea *mChatTabs; @@ -216,5 +203,3 @@ class ChatWindow : public Window, }; extern ChatWindow *chatWindow; - -#endif diff --git a/src/gui/confirmdialog.h b/src/gui/confirmdialog.h index 8eb3f46b..ba51558e 100644 --- a/src/gui/confirmdialog.h +++ b/src/gui/confirmdialog.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef OPTION_DIALOG_H -#define OPTION_DIALOG_H +#pragma once #include "gui/widgets/window.h" @@ -47,5 +46,3 @@ class ConfirmDialog : public Window, public gcn::ActionListener private: TextBox *mTextBox; }; - -#endif diff --git a/src/gui/connectiondialog.h b/src/gui/connectiondialog.h index e76a0a3e..6e477435 100644 --- a/src/gui/connectiondialog.h +++ b/src/gui/connectiondialog.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef CONNECTION_H -#define CONNECTION_H +#pragma once #include "client.h" @@ -58,5 +57,3 @@ class ConnectionDialog : public Window, gcn::ActionListener gcn::Label *mLabel; State mCancelState; }; - -#endif diff --git a/src/gui/customserverdialog.cpp b/src/gui/customserverdialog.cpp index c7c03b1a..5524e459 100644 --- a/src/gui/customserverdialog.cpp +++ b/src/gui/customserverdialog.cpp @@ -37,11 +37,10 @@ std::string TypeListModel::getElementAt(int elementIndex) if (elementIndex == 0) return "TmwAthena"; #ifdef MANASERV_SUPPORT - else if (elementIndex == 1) + if (elementIndex == 1) return "ManaServ"; #endif - else - return "Unknown"; + return "Unknown"; } CustomServerDialog::CustomServerDialog(ServerDialog *parent, int index): @@ -62,8 +61,8 @@ CustomServerDialog::CustomServerDialog(ServerDialog *parent, int index): mPortField = new TextField(std::string()); #ifdef MANASERV_SUPPORT - mTypeListModel = new TypeListModel(); - mTypeField = new DropDown(mTypeListModel); + mTypeListModel = std::make_unique<TypeListModel>(); + mTypeField = new DropDown(mTypeListModel.get()); mTypeField->setSelected(0); // TmwAthena by default for now. #endif @@ -77,46 +76,25 @@ CustomServerDialog::CustomServerDialog(ServerDialog *parent, int index): mPortField->addActionListener(this); place(0, 0, nameLabel); - place(1, 0, mNameField, 4).setPadding(3); + place(1, 0, mNameField, 4).setPadding(2); place(0, 1, serverAdressLabel); - place(1, 1, mServerAddressField, 4).setPadding(3); + place(1, 1, mServerAddressField, 4).setPadding(2); place(0, 2, portLabel); - place(1, 2, mPortField, 4).setPadding(3); + place(1, 2, mPortField, 4).setPadding(2); #ifdef MANASERV_SUPPORT place(0, 3, typeLabel); - place(1, 3, mTypeField).setPadding(3); + place(1, 3, mTypeField).setPadding(2); #endif place(0, 4, descriptionLabel); - place(1, 4, mDescriptionField, 4).setPadding(3); + place(1, 4, mDescriptionField, 4).setPadding(2); place(4, 5, mOkButton); place(3, 5, mCancelButton); - // Do this manually instead of calling reflowLayout so we can enforce a - // minimum width. - int width = 0, height = 0; - getLayout().reflow(width, height); - if (width < 300) - { - width = 300; - getLayout().reflow(width, height); - } - if (height < 120) - { - height = 120; - getLayout().reflow(width, height); - } - - setContentSize(width, height); - - setMinWidth(getWidth()); - setMinHeight(getHeight()); - setDefaultSize(getWidth(), getHeight(), ImageRect::CENTER); + reflowLayout(); + setLocationRelativeTo(getParentWindow()); - setResizable(false); addKeyListener(this); - loadWindowState(); - // Add the entry's info when in modify mode. if (index > -1) { @@ -126,28 +104,17 @@ CustomServerDialog::CustomServerDialog(ServerDialog *parent, int index): mServerAddressField->setText(serverInfo.hostname); mPortField->setText(toString(serverInfo.port)); #ifdef MANASERV_SUPPORT - mTypeField->setSelected(serverInfo.type == ServerType::TMWATHENA ? + mTypeField->setSelected(serverInfo.type == ServerType::TmwAthena ? 0 : 1); #endif } - setLocationRelativeTo(getParentWindow()); setVisible(true); mNameField->requestFocus(); } -CustomServerDialog::~CustomServerDialog() -{ -#ifdef MANASERV_SUPPORT - delete mTypeListModel; -#endif -} - -void CustomServerDialog::logic() -{ - Window::logic(); -} +CustomServerDialog::~CustomServerDialog() = default; void CustomServerDialog::action(const gcn::ActionEvent &event) { @@ -178,13 +145,13 @@ void CustomServerDialog::action(const gcn::ActionEvent &event) switch (mTypeField->getSelected()) { case 0: - serverInfo.type = ServerType::TMWATHENA; + serverInfo.type = ServerType::TmwAthena; break; case 1: - serverInfo.type = ServerType::MANASERV; + serverInfo.type = ServerType::ManaServ; break; default: - serverInfo.type = ServerType::UNKNOWN; + serverInfo.type = ServerType::Unknown; } #else serverInfo.type = ServerType::TMWATHENA; diff --git a/src/gui/customserverdialog.h b/src/gui/customserverdialog.h index 8b0af4c8..e523260c 100644 --- a/src/gui/customserverdialog.h +++ b/src/gui/customserverdialog.h @@ -18,15 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef CUSTOMSERVERDIALOG_H -#define CUSTOMSERVERDIALOG_H - -class Button; -class Label; -class TextField; -class DropDown; -class ServerDialog; -class TypeListModel; +#pragma once #include "gui/widgets/window.h" @@ -34,6 +26,12 @@ class TypeListModel; #include <guichan/keylistener.hpp> #include <guichan/listmodel.hpp> +#include <memory> + +class Button; +class DropDown; +class ServerDialog; +class TextField; /** * Server Type List Model @@ -65,7 +63,6 @@ class CustomServerDialog : public Window, { public: CustomServerDialog(ServerDialog *parent, int index = -1); - ~CustomServerDialog() override; /** @@ -75,22 +72,18 @@ class CustomServerDialog : public Window, void keyPressed(gcn::KeyEvent &keyEvent) override; - void logic() override; - private: TextField *mServerAddressField; TextField *mPortField; - TextField *mNameField; + TextField *mNameField; TextField *mDescriptionField; Button *mOkButton; Button *mCancelButton; #ifdef MANASERV_SUPPORT DropDown *mTypeField; - TypeListModel *mTypeListModel; + std::unique_ptr<TypeListModel> mTypeListModel; #endif ServerDialog *mServerDialog; // The index of the entry to modify, -1 when only adding a new entry. int mIndex; }; - -#endif // CUSTOMSERVERDIALOG_H diff --git a/src/gui/debugwindow.cpp b/src/gui/debugwindow.cpp index 3c514191..17020062 100644 --- a/src/gui/debugwindow.cpp +++ b/src/gui/debugwindow.cpp @@ -244,11 +244,15 @@ DebugWindow::DebugWindow() place(0, 0, tabs, 2, 2); loadWindowState(); - Tab *tabInfo = new Tab; - tabInfo->setCaption(_("Info")); - tabs->addTab(tabInfo, new DebugInfo); - - Tab *tabSwitches = new Tab; - tabSwitches->setCaption(_("Switches")); - tabs->addTab(tabSwitches, new DebugSwitches); + mInfoTab = std::make_unique<Tab>(); + mInfoTab->setCaption(_("Info")); + mInfoWidget = std::make_unique<DebugInfo>(); + tabs->addTab(mInfoTab.get(), mInfoWidget.get()); + + mSwitchesTab = std::make_unique<Tab>(); + mSwitchesTab->setCaption(_("Switches")); + mSwitchesWidget = std::make_unique<DebugSwitches>(); + tabs->addTab(mSwitchesTab.get(), mSwitchesWidget.get()); } + +DebugWindow::~DebugWindow() = default; diff --git a/src/gui/debugwindow.h b/src/gui/debugwindow.h index 3376ae18..807f0d0f 100644 --- a/src/gui/debugwindow.h +++ b/src/gui/debugwindow.h @@ -19,11 +19,14 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef DEBUGWINDOW_H -#define DEBUGWINDOW_H +#pragma once #include "gui/widgets/window.h" +#include <memory> + +class Tab; + /** * The debug window. * @@ -33,8 +36,13 @@ class DebugWindow : public Window { public: DebugWindow(); + ~DebugWindow() override; + + private: + std::unique_ptr<Tab> mInfoTab; + std::unique_ptr<Tab> mSwitchesTab; + std::unique_ptr<gcn::Widget> mInfoWidget; + std::unique_ptr<gcn::Widget> mSwitchesWidget; }; extern DebugWindow *debugWindow; - -#endif diff --git a/src/gui/emotepopup.cpp b/src/gui/emotepopup.cpp index 589d5087..e759ab25 100644 --- a/src/gui/emotepopup.cpp +++ b/src/gui/emotepopup.cpp @@ -25,7 +25,6 @@ #include "configuration.h" #include "emoteshortcut.h" #include "graphics.h" -#include "imagesprite.h" #include "log.h" #include "resources/emotedb.h" @@ -53,13 +52,12 @@ EmotePopup::EmotePopup() setVisible(true); } -EmotePopup::~EmotePopup() -{ - mSelectionImage->decRef(); -} +EmotePopup::~EmotePopup() = default; void EmotePopup::draw(gcn::Graphics *graphics) { + auto *g = static_cast<Graphics*>(graphics); + Popup::draw(graphics); const int emoteCount = EmoteDB::getEmoteCount(); @@ -81,13 +79,14 @@ void EmotePopup::draw(gcn::Graphics *graphics) // Draw selection image below hovered item if (i == mHoveredEmoteIndex) - { - static_cast<Graphics*>(graphics)->drawImage( - mSelectionImage, emoteX, emoteY + 4); - } + g->drawImage(mSelectionImage, emoteX, emoteY + 4); // Draw emote icon - EmoteDB::getByIndex(i).sprite->draw(static_cast<Graphics*>(graphics), emoteX, emoteY); + if (auto image = EmoteDB::getByIndex(i).image) + { + image->setAlpha(1.0f); + g->drawImage(image, emoteX, emoteY); + } } } diff --git a/src/gui/emotepopup.h b/src/gui/emotepopup.h index ef3fffed..c95c5723 100644 --- a/src/gui/emotepopup.h +++ b/src/gui/emotepopup.h @@ -20,10 +20,10 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef EMOTEPOPUP_H -#define EMOTEPOPUP_H +#pragma once #include "gui/widgets/popup.h" +#include "resources/resource.h" #include <guichan/mouselistener.hpp> @@ -104,7 +104,7 @@ class EmotePopup : public Popup */ void distributeValueChangedEvent(); - Image *mSelectionImage; + ResourceRef<Image> mSelectionImage; int mSelectedEmoteId = -1; int mHoveredEmoteIndex = -1; @@ -116,5 +116,3 @@ class EmotePopup : public Popup static const int gridWidth; static const int gridHeight; }; - -#endif diff --git a/src/gui/equipmentwindow.cpp b/src/gui/equipmentwindow.cpp index e7eeb048..ff3c6630 100644 --- a/src/gui/equipmentwindow.cpp +++ b/src/gui/equipmentwindow.cpp @@ -65,13 +65,14 @@ EquipmentWindow::EquipmentWindow(Equipment *equipment): setWindowName("Equipment"); setCloseButton(true); setSaveVisible(true); - setDefaultSize(180, 300, ImageRect::CENTER); + setContentSize(175, 290); + setDefaultSize(getWidth(), getHeight(), ImageRect::CENTER); loadWindowState(); mUnequip = new Button(_("Unequip"), "unequip", this); const gcn::Rectangle &area = getChildrenArea(); - mUnequip->setPosition(area.width - mUnequip->getWidth() - 5, - area.height - mUnequip->getHeight() - 5); + mUnequip->setPosition(area.width - mUnequip->getWidth() - getPadding(), + area.height - mUnequip->getHeight() - getPadding()); mUnequip->setEnabled(false); add(playerBox); @@ -96,10 +97,7 @@ void EquipmentWindow::loadEquipBoxes() Net::getInventoryHandler()->getBoxBackground(i); if (!backgroundFile.empty()) - { - box.backgroundImage = - Theme::instance()->getImageFromTheme(backgroundFile); - } + box.backgroundImage = Theme::getImageFromTheme(backgroundFile); } } @@ -111,7 +109,6 @@ EquipmentWindow::~EquipmentWindow() void EquipmentWindow::draw(gcn::Graphics *graphics) { Window::draw(graphics); - Window::drawChildren(graphics); // Draw equipment boxes auto *g = static_cast<Graphics*>(graphics); @@ -240,6 +237,8 @@ void EquipmentWindow::mousePressed(gcn::MouseEvent& mouseEvent) void EquipmentWindow::mouseMoved(gcn::MouseEvent &event) { + Window::mouseMoved(event); + const int x = event.getX(); const int y = event.getY(); @@ -264,6 +263,8 @@ void EquipmentWindow::mouseMoved(gcn::MouseEvent &event) void EquipmentWindow::mouseExited(gcn::MouseEvent &event) { + Window::mouseExited(event); + mItemPopup->setVisible(false); } diff --git a/src/gui/equipmentwindow.h b/src/gui/equipmentwindow.h index 1b63c866..f46d1175 100644 --- a/src/gui/equipmentwindow.h +++ b/src/gui/equipmentwindow.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef EQUIPMENTWINDOW_H -#define EQUIPMENTWINDOW_H +#pragma once #include "equipment.h" #include "resources/image.h" @@ -29,6 +28,8 @@ #include <guichan/actionlistener.hpp> +#include <vector> + class Inventory; class Item; class ItemPopup; @@ -53,6 +54,8 @@ class EquipmentWindow : public Window, public gcn::ActionListener void action(const gcn::ActionEvent &event) override; void mousePressed(gcn::MouseEvent& mouseEvent) override; + void mouseMoved(gcn::MouseEvent &event) override; + void mouseExited(gcn::MouseEvent &event) override; /** * Loads the correct amount of displayed equip boxes. @@ -73,7 +76,7 @@ class EquipmentWindow : public Window, public gcn::ActionListener { int posX = 0; int posY = 0; - Image *backgroundImage = nullptr; + ResourceRef<Image> backgroundImage; }; std::vector<EquipBox> mBoxes; /**< Equipment boxes. */ @@ -82,9 +85,6 @@ class EquipmentWindow : public Window, public gcn::ActionListener Equipment *mEquipment; private: - void mouseExited(gcn::MouseEvent &event) override; - void mouseMoved(gcn::MouseEvent &event) override; - int getBoxIndex(int x, int y) const; Item *getItem(int x, int y) const; std::string getSlotName(int x, int y) const; @@ -96,5 +96,3 @@ class EquipmentWindow : public Window, public gcn::ActionListener }; extern EquipmentWindow *equipmentWindow; - -#endif // EQUIPMENTWINDOW_H diff --git a/src/gui/focushandler.h b/src/gui/focushandler.h index eb59bcf3..d90898bd 100644 --- a/src/gui/focushandler.h +++ b/src/gui/focushandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef FOCUSHANDLER_H -#define FOCUSHANDLER_H +#pragma once #include <guichan/focushandler.hpp> @@ -73,5 +72,3 @@ class FocusHandler : public gcn::FocusHandler */ std::list<gcn::Widget*> mModalStack; }; - -#endif diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index a59242dc..d72bd56d 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -37,6 +37,7 @@ #include "resources/resourcemanager.h" #include "resources/theme.h" +#include "utils/filesystem.h" #include <guichan/exception.hpp> #include <guichan/image.hpp> @@ -55,8 +56,9 @@ gcn::Font *monoFont = nullptr; bool Gui::debugDraw; -Gui::Gui(Graphics *graphics) - : mCustomCursorScale(Client::getVideo().settings().scale()) +Gui::Gui(Graphics *graphics, const std::string &themePath) + : mTheme(new Theme(themePath)) + , mCustomCursorScale(Client::getVideo().settings().scale()) { logger->log("Initializing GUI..."); // Set graphics @@ -78,12 +80,10 @@ Gui::Gui(Graphics *graphics) Window::setWindowContainer(guiTop); setTop(guiTop); - ResourceManager *resman = ResourceManager::getInstance(); - // Set global font const int fontSize = config.fontSize; std::string fontFile = branding.getValue("font", "fonts/dejavusans.ttf"); - std::string path = resman->getPath(fontFile); + std::string path = ResourceManager::getPath(fontFile); // Initialize the font scale before creating the fonts TrueTypeFont::updateFontScale(graphics->getScale()); @@ -101,7 +101,7 @@ Gui::Gui(Graphics *graphics) // Set bold font fontFile = branding.getValue("boldFont", "fonts/dejavusans-bold.ttf"); - path = resman->getPath(fontFile); + path = ResourceManager::getPath(fontFile); try { boldFont = new TrueTypeFont(path, fontSize); @@ -114,7 +114,7 @@ Gui::Gui(Graphics *graphics) // Set mono font fontFile = branding.getValue("monoFont", "fonts/dejavusans-mono.ttf"); - path = resman->getPath(fontFile); + path = ResourceManager::getPath(fontFile); try { monoFont = new TrueTypeFont(path, fontSize); @@ -151,8 +151,6 @@ Gui::~Gui() delete getTop(); delete guiInput; - - Theme::deleteInstance(); } void Gui::logic() @@ -258,7 +256,7 @@ void Gui::handleTextInput(const TextInput &textInput) static SDL_Surface *loadSurface(const std::string &path) { - if (SDL_RWops *file = ResourceManager::getInstance()->open(path)) + if (SDL_RWops *file = FS::openRWops(path)) return IMG_Load_RW(file, 1); return nullptr; } @@ -270,7 +268,7 @@ void Gui::loadCustomCursors() mCustomMouseCursors.clear(); - const std::string cursorPath = Theme::resolveThemePath("mouse.png"); + const std::string cursorPath = mTheme->resolvePath("mouse.png"); SDL_Surface *mouseSurface = loadSurface(cursorPath); if (!mouseSurface) { @@ -301,7 +299,7 @@ void Gui::loadCustomCursors() 0, targetCursorSize, targetCursorSize, 32, rmask, gmask, bmask, amask); - for (int i = 0; i <= static_cast<int>(Cursor::LAST); ++i) + for (int i = 0; i < static_cast<int>(Cursor::Count); ++i) { int x = i % columns * cursorSize; int y = i / columns * cursorSize; @@ -330,22 +328,22 @@ void Gui::loadSystemCursors() constexpr struct { Cursor cursor; SDL_SystemCursor systemCursor; - } cursors[static_cast<int>(Cursor::LAST) + 1] = { - { Cursor::POINTER, SDL_SYSTEM_CURSOR_ARROW }, - { Cursor::RESIZE_ACROSS, SDL_SYSTEM_CURSOR_SIZEWE }, - { Cursor::RESIZE_DOWN, SDL_SYSTEM_CURSOR_SIZENS }, - { Cursor::RESIZE_DOWN_LEFT, SDL_SYSTEM_CURSOR_SIZENESW }, - { Cursor::RESIZE_DOWN_RIGHT, SDL_SYSTEM_CURSOR_SIZENWSE }, - { Cursor::FIGHT, SDL_SYSTEM_CURSOR_HAND }, - { Cursor::PICKUP, SDL_SYSTEM_CURSOR_HAND }, - { Cursor::TALK, SDL_SYSTEM_CURSOR_HAND }, - { Cursor::ACTION, SDL_SYSTEM_CURSOR_HAND }, - { Cursor::LEFT, SDL_SYSTEM_CURSOR_ARROW }, - { Cursor::UP, SDL_SYSTEM_CURSOR_ARROW }, - { Cursor::RIGHT, SDL_SYSTEM_CURSOR_ARROW }, - { Cursor::DOWN, SDL_SYSTEM_CURSOR_ARROW }, - { Cursor::DRAG, SDL_SYSTEM_CURSOR_SIZEALL }, - { Cursor::HAND, SDL_SYSTEM_CURSOR_HAND }, + } cursors[static_cast<int>(Cursor::Count)] = { + { Cursor::Pointer, SDL_SYSTEM_CURSOR_ARROW }, + { Cursor::ResizeAcross, SDL_SYSTEM_CURSOR_SIZEWE }, + { Cursor::ResizeDown, SDL_SYSTEM_CURSOR_SIZENS }, + { Cursor::ResizeDownLeft, SDL_SYSTEM_CURSOR_SIZENESW }, + { Cursor::ResizeDownRight, SDL_SYSTEM_CURSOR_SIZENWSE }, + { Cursor::Fight, SDL_SYSTEM_CURSOR_HAND }, + { Cursor::PickUp, SDL_SYSTEM_CURSOR_HAND }, + { Cursor::Talk, SDL_SYSTEM_CURSOR_HAND }, + { Cursor::Action, SDL_SYSTEM_CURSOR_HAND }, + { Cursor::Left, SDL_SYSTEM_CURSOR_ARROW }, + { Cursor::Up, SDL_SYSTEM_CURSOR_ARROW }, + { Cursor::Right, SDL_SYSTEM_CURSOR_ARROW }, + { Cursor::Down, SDL_SYSTEM_CURSOR_ARROW }, + { Cursor::Drag, SDL_SYSTEM_CURSOR_SIZEALL }, + { Cursor::Hand, SDL_SYSTEM_CURSOR_HAND }, }; for (auto cursor : cursors) diff --git a/src/gui/gui.h b/src/gui/gui.h index fd1dcf94..450514f5 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef GUI_H -#define GUI_H +#pragma once #include "eventlistener.h" #include "guichanfwd.h" @@ -31,8 +30,10 @@ #include <SDL.h> +#include <memory> #include <vector> +class Theme; class TextInput; class Graphics; class SDLInput; @@ -49,23 +50,24 @@ class SDLInput; * Cursors are in graphic order from left to right. * CURSOR_POINTER should be left untouched. */ -enum class Cursor { - POINTER = 0, - RESIZE_ACROSS, - RESIZE_DOWN, - RESIZE_DOWN_LEFT, - RESIZE_DOWN_RIGHT, - FIGHT, - PICKUP, - TALK, - ACTION, - LEFT, - UP, - RIGHT, - DOWN, - DRAG, - HAND, - LAST = HAND, +enum class Cursor +{ + Pointer = 0, + ResizeAcross, + ResizeDown, + ResizeDownLeft, + ResizeDownRight, + Fight, + PickUp, + Talk, + Action, + Left, + Up, + Right, + Down, + Drag, + Hand, + Count, }; /** @@ -76,7 +78,7 @@ enum class Cursor { class Gui final : public gcn::Gui, public EventListener { public: - Gui(Graphics *screen); + Gui(Graphics *screen, const std::string &themePath); ~Gui() override; @@ -121,6 +123,12 @@ class Gui final : public gcn::Gui, public EventListener */ void setCursorType(Cursor cursor); + /** + * The global GUI theme. + */ + Theme *getTheme() const + { return mTheme.get(); } + static bool debugDraw; protected: @@ -133,6 +141,7 @@ class Gui final : public gcn::Gui, public EventListener void loadCustomCursors(); void loadSystemCursors(); + std::unique_ptr<Theme> mTheme; /**< The global GUI theme */ gcn::Font *mGuiFont; /**< The global GUI font */ gcn::Font *mInfoParticleFont; /**< Font for Info Particles*/ bool mCustomCursor = false; /**< Show custom cursor */ @@ -140,7 +149,7 @@ class Gui final : public gcn::Gui, public EventListener std::vector<SDL_Cursor *> mSystemMouseCursors; std::vector<SDL_Cursor *> mCustomMouseCursors; Timer mMouseActivityTimer; - Cursor mCursorType = Cursor::POINTER; + Cursor mCursorType = Cursor::Pointer; }; extern Gui *gui; /**< The GUI system */ @@ -155,5 +164,3 @@ extern gcn::Font *boldFont; * Monospaced text font */ extern gcn::Font *monoFont; - -#endif // GUI_H diff --git a/src/gui/helpwindow.cpp b/src/gui/helpwindow.cpp index 7bb31188..e0e21610 100644 --- a/src/gui/helpwindow.cpp +++ b/src/gui/helpwindow.cpp @@ -29,9 +29,10 @@ #include "gui/widgets/layout.h" #include "gui/widgets/scrollarea.h" -#include "resources/resourcemanager.h" #include "configuration.h" +#include "log.h" +#include "utils/filesystem.h" #include "utils/gettext.h" HelpWindow::HelpWindow(): @@ -47,7 +48,6 @@ HelpWindow::HelpWindow(): setDefaultSize(500, 400, ImageRect::CENTER); mBrowserBox = new BrowserBox; - mBrowserBox->setFrameSize(4); mScrollArea = new ScrollArea(mBrowserBox); auto *okButton = new Button(_("Close"), "close", this); @@ -88,12 +88,20 @@ void HelpWindow::loadHelp(const std::string &helpFile) void HelpWindow::loadFile(const std::string &file) { - ResourceManager *resman = ResourceManager::getInstance(); std::string helpPath = branding.getStringValue("helpPath"); if (helpPath.empty()) helpPath = paths.getStringValue("help"); - const auto lines = resman->loadTextFile(helpPath + file + ".txt"); - for (auto &line : lines) - mBrowserBox->addRow(line); + const std::string fileName = helpPath + file + ".txt"; + + size_t contentsLength; + char *fileContents = (char *) FS::loadFile(fileName, contentsLength); + if (!fileContents) + { + logger->log("Couldn't load text file: %s", fileName.c_str()); + return; + } + + mBrowserBox->addRows(std::string_view(fileContents, contentsLength)); + SDL_free(fileContents); } diff --git a/src/gui/helpwindow.h b/src/gui/helpwindow.h index 30fa450e..2daf2480 100644 --- a/src/gui/helpwindow.h +++ b/src/gui/helpwindow.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef HELP_H -#define HELP_H +#pragma once #include "gui/widgets/linkhandler.h" #include "gui/widgets/window.h" @@ -62,5 +61,3 @@ class HelpWindow : public Window, public LinkHandler, }; extern HelpWindow *helpWindow; - -#endif diff --git a/src/gui/inventorywindow.cpp b/src/gui/inventorywindow.cpp index 048f83f5..ab2e9c86 100644 --- a/src/gui/inventorywindow.cpp +++ b/src/gui/inventorywindow.cpp @@ -30,7 +30,6 @@ #include "gui/itemamountwindow.h" #include "gui/setup.h" -#include "gui/sdlinput.h" #include "gui/viewport.h" #include "gui/widgets/button.h" @@ -40,9 +39,6 @@ #include "gui/widgets/progressbar.h" #include "gui/widgets/scrollarea.h" -#include "net/inventoryhandler.h" -#include "net/net.h" - #include "resources/iteminfo.h" #include "resources/theme.h" @@ -80,7 +76,7 @@ InventoryWindow::InventoryWindow(Inventory *inventory): mItems = new ItemContainer(mInventory); mItems->addSelectionListener(this); - gcn::ScrollArea *invenScroll = new ScrollArea(mItems); + auto invenScroll = new ScrollArea(mItems); invenScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); mSlotsLabel = new Label(_("Slots:")); @@ -105,7 +101,6 @@ InventoryWindow::InventoryWindow(Inventory *inventory): mEquipButton = new Button(_("Equip"), "equip", this); mUseButton = new Button(_("Activate"), "activate", this); mDropButton = new Button(_("Drop..."), "drop", this); - mSplitButton = new Button(_("Split"), "split", this); mOutfitButton = new Button(_("Outfits"), "outfit", this); mWeightLabel = new Label(_("Weight:")); @@ -121,7 +116,6 @@ InventoryWindow::InventoryWindow(Inventory *inventory): place(0, 3, mUseButton); place(1, 3, mEquipButton); place(3, 3, mDropButton); - place(4, 3, mSplitButton); place(7, 3, mOutfitButton); updateWeight(); @@ -182,7 +176,6 @@ void InventoryWindow::action(const gcn::ActionEvent &event) if (!inventoryWindow->isVisible()) return; Item *item = inventoryWindow->getSelectedItem(); - if (!item) return; @@ -190,12 +183,13 @@ void InventoryWindow::action(const gcn::ActionEvent &event) } Item *item = mItems->getSelectedItem(); - if (!item) return; if (event.getId() == "activate") + { item->doEvent(Event::DoUse); + } else if (event.getId() == "equip") { if (item->isEquippable()) @@ -214,15 +208,9 @@ void InventoryWindow::action(const gcn::ActionEvent &event) { ItemAmountWindow::showWindow(ItemAmountWindow::ItemDrop, this, item); } - else if (event.getId() == "split") - { - ItemAmountWindow::showWindow(ItemAmountWindow::ItemSplit, this, item, - (item->getQuantity() - 1)); - } else if (event.getId() == "retrieve") { Item *item = mItems->getSelectedItem(); - if (!item) return; @@ -247,26 +235,27 @@ void InventoryWindow::mouseClicked(gcn::MouseEvent &event) Window::mouseClicked(event); Item *item = mItems->getSelectedItem(); + if (!item) + return; - if (event.getSource() == mItems && item && isDoubleClick(item->getInvIndex())) + if (event.getSource() == mItems && isDoubleClick(item->getInvIndex()) + && isMainInventory()) { - if (isMainInventory() && item->getInfo().activatable) + if (item->getInfo().activatable) { action(gcn::ActionEvent(mUseButton, mUseButton->getActionEventId())); } - else if (isMainInventory() && item->isEquippable()) + else if (item->isEquippable()) { action(gcn::ActionEvent(mEquipButton, mEquipButton->getActionEventId())); } + return; } if (event.getButton() == gcn::MouseEvent::RIGHT) { - if (!item) - return; - /* Convert relative to the window coordinates to absolute screen * coordinates. */ @@ -279,10 +268,6 @@ void InventoryWindow::mouseClicked(gcn::MouseEvent &event) { if (instances.size() > 1 && keyboard.isKeyActive(KeyboardConfig::KEY_EMOTE)) { - Item *item = mItems->getSelectedItem(); - - if(!item) - return; if (mInventory->isMainInventory()) { Event event(Event::DoMove); @@ -305,32 +290,10 @@ void InventoryWindow::mouseClicked(gcn::MouseEvent &event) } } -void InventoryWindow::keyPressed(gcn::KeyEvent &event) -{ - switch (event.getKey().getValue()) - { - case Key::LEFT_SHIFT: - case Key::RIGHT_SHIFT: - mSplit = true; - break; - } -} - void InventoryWindow::keyReleased(gcn::KeyEvent &event) { if (isInputFocused()) - { mItems->setFilter(mFilterText->getText()); - return; - } - - switch (event.getKey().getValue()) - { - case Key::LEFT_SHIFT: - case Key::RIGHT_SHIFT: - mSplit = false; - break; - } } void InventoryWindow::valueChanged(const gcn::SelectionEvent &event) @@ -338,15 +301,6 @@ void InventoryWindow::valueChanged(const gcn::SelectionEvent &event) if (!mInventory->isMainInventory()) return; - Item *item = mItems->getSelectedItem(); - - if (mSplit && Net::getInventoryHandler()-> - canSplit(mItems->getSelectedItem()) && item) - { - ItemAmountWindow::showWindow(ItemAmountWindow::ItemSplit, this, item, - (item->getQuantity() - 1)); - } - updateButtons(); } @@ -359,44 +313,19 @@ void InventoryWindow::updateButtons() mUseButton->setEnabled(false); mEquipButton->setEnabled(false); mDropButton->setEnabled(false); - mSplitButton->setEnabled(false); - return; } mDropButton->setEnabled(true); - if (item->isEquippable()) - { - if (item->isEquipped()) - mEquipButton->setCaption(_("Unequip")); - else - mEquipButton->setCaption(_("Equip")); - mEquipButton->setEnabled(true); - } - else - mEquipButton->setEnabled(false); - + mEquipButton->setCaption(item->isEquipped() ? _("Unequip") : _("Equip")); + mEquipButton->setEnabled(item->isEquippable()); mEquipButton->adjustSize(); mUseButton->setEnabled(item->getInfo().activatable); - if (item->getQuantity() > 1) - mDropButton->setCaption(_("Drop...")); - else - mDropButton->setCaption(_("Drop")); - - if (Net::getInventoryHandler()->canSplit(item)) - mSplitButton->setEnabled(true); - else - mSplitButton->setEnabled(false); - - mSplitButton->adjustSize(); -} - -void InventoryWindow::setSplitAllowed(bool allowed) -{ - mSplitButton->setVisible(allowed); + mDropButton->setCaption(item->getQuantity() > 1 ? _("Drop...") : _("Drop")); + mDropButton->adjustSize(); } void InventoryWindow::close() @@ -418,12 +347,9 @@ void InventoryWindow::event(Event::Channel channel, const Event &event) { if (event.getType() == Event::UpdateAttribute) { - int id = event.getInt("id"); - if (id == TOTAL_WEIGHT || - id == MAX_WEIGHT) - { + const int id = event.getInt("id"); + if (id == TOTAL_WEIGHT || id == MAX_WEIGHT) updateWeight(); - } } } @@ -432,8 +358,8 @@ void InventoryWindow::updateWeight() if (!isMainInventory()) return; - int total = PlayerInfo::getAttribute(TOTAL_WEIGHT); - int max = PlayerInfo::getAttribute(MAX_WEIGHT); + const int total = PlayerInfo::getAttribute(TOTAL_WEIGHT); + const int max = PlayerInfo::getAttribute(MAX_WEIGHT); if (max <= 0) return; @@ -451,21 +377,14 @@ bool InventoryWindow::isInputFocused() const bool InventoryWindow::isAnyInputFocused() { - auto it = instances.begin(); - auto it_end = instances.end(); - - for (; it != it_end; it++) - { - if ((*it)->isInputFocused()) - { + for (auto instance : instances) + if (instance->isInputFocused()) return true; - } - } return false; } -void InventoryWindow::slotsChanged(Inventory* inventory) +void InventoryWindow::slotsChanged(Inventory *inventory) { if (inventory == mInventory) { @@ -473,7 +392,6 @@ void InventoryWindow::slotsChanged(Inventory* inventory) const int maxSlots = mInventory->getSize(); mSlotsBar->setProgress((float) usedSlots / maxSlots); - mSlotsBar->setText(strprintf("%d/%d", usedSlots, maxSlots)); } } diff --git a/src/gui/inventorywindow.h b/src/gui/inventorywindow.h index 048b229c..267d8dc1 100644 --- a/src/gui/inventorywindow.h +++ b/src/gui/inventorywindow.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef INVENTORYWINDOW_H -#define INVENTORYWINDOW_H +#pragma once #include "inventory.h" #include "eventlistener.h" @@ -28,8 +27,6 @@ #include "gui/widgets/window.h" #include "gui/widgets/textfield.h" -#include "net/inventoryhandler.h" - #include <guichan/actionlistener.hpp> #include <guichan/keylistener.hpp> #include <guichan/selectionlistener.hpp> @@ -44,12 +41,12 @@ class TextBox; * * \ingroup Interface */ -class InventoryWindow : public Window, - public gcn::ActionListener, - public gcn::KeyListener, - public gcn::SelectionListener, - public InventoryListener, - public EventListener +class InventoryWindow final : public Window, + public gcn::ActionListener, + public gcn::KeyListener, + public gcn::SelectionListener, + public InventoryListener, + public EventListener { public: InventoryWindow(Inventory *inventory); @@ -64,7 +61,7 @@ class InventoryWindow : public Window, /** * Returns the selected item. */ - Item* getSelectedItem() const; + Item *getSelectedItem() const; /** * Handles closing of the window @@ -77,11 +74,6 @@ class InventoryWindow : public Window, void mouseClicked(gcn::MouseEvent &event) override; /** - * Handles the key presses. - */ - void keyPressed(gcn::KeyEvent &event) override; - - /** * Handles the key releases. */ void keyReleased(gcn::KeyEvent &event) override; @@ -92,11 +84,6 @@ class InventoryWindow : public Window, void valueChanged(const gcn::SelectionEvent &event) override; /** - * Sets whether the split button should be shown. - */ - void setSplitAllowed(bool allowed); - - /** * Closes the Storage Window, as well as telling the server that the * window has been closed. */ @@ -111,9 +98,9 @@ class InventoryWindow : public Window, static bool isAnyInputFocused(); - void slotsChanged(Inventory* inventory) override; + void slotsChanged(Inventory *inventory) override; - bool isMainInventory() { return mInventory->isMainInventory(); } + bool isMainInventory() const { return mInventory->isMainInventory(); } void event(Event::Channel channel, const Event &event) override; @@ -134,16 +121,12 @@ class InventoryWindow : public Window, std::string mWeight, mSlots; - gcn::Button *mUseButton, *mEquipButton, *mDropButton, *mSplitButton, + gcn::Button *mUseButton, *mEquipButton, *mDropButton, *mOutfitButton, *mStoreButton, *mRetrieveButton; gcn::Label *mWeightLabel, *mSlotsLabel, *mFilterLabel; ProgressBar *mWeightBar, *mSlotsBar; - - bool mSplit = false; }; extern InventoryWindow *inventoryWindow; - -#endif diff --git a/src/gui/itemamountwindow.cpp b/src/gui/itemamountwindow.cpp index 947b5bdc..da5dc073 100644 --- a/src/gui/itemamountwindow.cpp +++ b/src/gui/itemamountwindow.cpp @@ -49,9 +49,6 @@ void ItemAmountWindow::finish(Item *item, int amount, Usage usage) case ItemDrop: item->doEvent(Event::DoDrop, amount); break; - case ItemSplit: - item->doEvent(Event::DoSplit, amount); - break; case StoreAdd: { Event event(Event::DoMove); @@ -148,9 +145,6 @@ ItemAmountWindow::ItemAmountWindow(Usage usage, Window *parent, Item *item, case StoreRemove: setCaption(_("Select amount of items to retrieve.")); break; - case ItemSplit: - setCaption(_("Select amount of items to split.")); - break; } setLocationRelativeTo(getParentWindow()); diff --git a/src/gui/itemamountwindow.h b/src/gui/itemamountwindow.h index d0ac52d5..489fdbef 100644 --- a/src/gui/itemamountwindow.h +++ b/src/gui/itemamountwindow.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef ITEM_AMOUNT_WINDOW_H -#define ITEM_AMOUNT_WINDOW_H +#pragma once #include "gui/widgets/window.h" @@ -33,7 +32,7 @@ class ItemPopup; class Icon; /** - * Window used for selecting the amount of items to drop, trade or split. + * Window used for selecting the amount of items to drop, trade or store. * * \ingroup Interface */ @@ -47,7 +46,6 @@ class ItemAmountWindow : public Window, ItemDrop, StoreAdd, StoreRemove, - ItemSplit }; /** @@ -100,5 +98,3 @@ class ItemAmountWindow : public Window, bool mEnabledKeyboard; }; - -#endif // ITEM_AMOUNT_WINDOW_H diff --git a/src/gui/itempopup.cpp b/src/gui/itempopup.cpp index 7f1dec3b..35951331 100644 --- a/src/gui/itempopup.cpp +++ b/src/gui/itempopup.cpp @@ -23,7 +23,6 @@ #include "gui/itempopup.h" #include "configuration.h" -#include "graphics.h" #include "units.h" #include "gui/gui.h" @@ -35,7 +34,6 @@ #include "utils/gettext.h" #include "utils/stringutils.h" -#include "resources/image.h" #include "resources/resourcemanager.h" #include "resources/theme.h" @@ -139,6 +137,7 @@ void ItemPopup::setNoItem() mItemDesc->setText(std::string()); mItemEffect->setText(std::string()); + mItemWeight->setText(std::string()); setContentSize(mItemName->getWidth(), mItemName->getHeight()); } @@ -153,8 +152,8 @@ void ItemPopup::setItem(const ItemInfo &item, bool showImage) if (showImage) { ResourceManager *resman = ResourceManager::getInstance(); - auto image = resman->getImageRef(paths.getStringValue("itemIcons") + - item.display.image); + auto image = resman->getImage(paths.getStringValue("itemIcons") + + item.display.image); mIcon->setImage(image); if (image) diff --git a/src/gui/itempopup.h b/src/gui/itempopup.h index 3b213633..535104cf 100644 --- a/src/gui/itempopup.h +++ b/src/gui/itempopup.h @@ -20,8 +20,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef ITEMPOPUP_H -#define ITEMPOPUP_H +#pragma once #include "gui/widgets/popup.h" @@ -74,5 +73,3 @@ class ItemPopup : public Popup ItemType mItemType; Icon *mIcon; }; - -#endif // ITEMPOPUP_H diff --git a/src/gui/logindialog.cpp b/src/gui/logindialog.cpp index 1f96e02d..42ec0842 100644 --- a/src/gui/logindialog.cpp +++ b/src/gui/logindialog.cpp @@ -64,8 +64,8 @@ LoginDialog::LoginDialog(LoginData *loginData): place(0, 0, userLabel); place(0, 1, passLabel); - place(1, 0, mUserField, 3).setPadding(1); - place(1, 1, mPassField, 3).setPadding(1); + place(1, 0, mUserField, 3).setPadding(2); + place(1, 1, mPassField, 3).setPadding(2); place(0, 5, mKeepCheck, 4); place(0, 6, mRegisterButton).setHAlign(LayoutCell::LEFT); place(2, 6, mServerButton); diff --git a/src/gui/logindialog.h b/src/gui/logindialog.h index 67814cd4..fc3fa249 100644 --- a/src/gui/logindialog.h +++ b/src/gui/logindialog.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef LOGIN_H -#define LOGIN_H +#pragma once #include "gui/widgets/window.h" @@ -69,5 +68,3 @@ class LoginDialog : public Window, public gcn::ActionListener, LoginData *mLoginData; }; - -#endif diff --git a/src/gui/minimap.cpp b/src/gui/minimap.cpp index f5206eda..8924bc26 100644 --- a/src/gui/minimap.cpp +++ b/src/gui/minimap.cpp @@ -34,8 +34,10 @@ #include "resources/resourcemanager.h" #include "resources/userpalette.h" +#include "utils/filesystem.h" #include "utils/gettext.h" +#include <algorithm> #include <guichan/font.hpp> Minimap::Minimap(): @@ -58,8 +60,7 @@ Minimap::Minimap(): setVisible(config.showMinimap, isSticky()); } -Minimap::~Minimap() -{} +Minimap::~Minimap() = default; void Minimap::setMap(Map *map) { @@ -86,11 +87,11 @@ void Minimap::setMap(Map *map) std::string minimapName = map->getProperty("minimap"); - if (minimapName.empty() && resman->exists(tempname)) + if (minimapName.empty() && FS::exists(tempname)) minimapName = tempname; if (!minimapName.empty()) - mMapImage = resman->getImageRef(minimapName); + mMapImage = resman->getImage(minimapName); } if (mMapImage) @@ -136,40 +137,38 @@ void Minimap::draw(gcn::Graphics *graphics) { Window::draw(graphics); + if (!mMap) + return; + + auto g = static_cast<Graphics*>(graphics); const gcn::Rectangle a = getChildrenArea(); - graphics->pushClipArea(a); + g->pushClipRect(a); // does actual clipping + g->pushClipArea(a); // only applies an offset + + const int tileWidth = mMap->getTileWidth(); + const int tileHeight = mMap->getTileHeight(); int mapOriginX = 0; int mapOriginY = 0; - if (mMapImage && mMap) + if (mMapImage) { if (mMapImage->getWidth() > a.width || mMapImage->getHeight() > a.height) { const Vector &p = local_player->getPosition(); - mapOriginX = (int) (((a.width) / 2) - (int) (p.x * mWidthProportion) - / mMap->getTileWidth()); - mapOriginY = (int) (((a.height) / 2) - - (int) (p.y * mHeightProportion) - / mMap->getTileHeight()); + mapOriginX = (a.width / 2) - (int) (p.x * mWidthProportion) / tileWidth; + mapOriginY = (a.height / 2) - (int) (p.y * mHeightProportion) / tileHeight; const int minOriginX = a.width - mMapImage->getWidth(); const int minOriginY = a.height - mMapImage->getHeight(); - if (mapOriginX < minOriginX) - mapOriginX = minOriginX; - if (mapOriginY < minOriginY) - mapOriginY = minOriginY; - if (mapOriginX > 0) - mapOriginX = 0; - if (mapOriginY > 0) - mapOriginY = 0; + mapOriginX = std::clamp(mapOriginX, minOriginX, 0); + mapOriginY = std::clamp(mapOriginY, minOriginY, 0); } - static_cast<Graphics*>(graphics)-> - drawImage(mMapImage, mapOriginX, mapOriginY); + g->drawImage(mMapImage, mapOriginX, mapOriginY); } for (auto actor : actorSpriteManager->getAll()) @@ -218,16 +217,14 @@ void Minimap::draw(gcn::Graphics *graphics) const int offsetWidth = (int) ((dotSize - 1) * mWidthProportion); const Vector &pos = being->getPosition(); - if (mMap) - { - graphics->fillRectangle(gcn::Rectangle( - (int) (pos.x * mWidthProportion) / mMap->getTileWidth() - + mapOriginX - offsetWidth, - (int) (pos.y * mHeightProportion) / mMap->getTileHeight() - + mapOriginY - offsetHeight, - dotSize, dotSize)); - } + g->fillRectangle( + gcn::Rectangle((int) (pos.x * mWidthProportion) / tileWidth + mapOriginX - offsetWidth, + (int) (pos.y * mHeightProportion) / tileHeight + mapOriginY + - offsetHeight, + dotSize, + dotSize)); } - graphics->popClipArea(); + g->popClipArea(); + g->popClipRect(); } diff --git a/src/gui/minimap.h b/src/gui/minimap.h index 8212f5b7..bf6cd89d 100644 --- a/src/gui/minimap.h +++ b/src/gui/minimap.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef MINIMAP_H -#define MINIMAP_H +#pragma once #include "gui/widgets/window.h" @@ -67,5 +66,3 @@ class Minimap : public Window }; extern Minimap *minimap; - -#endif diff --git a/src/gui/ministatuswindow.cpp b/src/gui/ministatuswindow.cpp index 598cb722..9d695954 100644 --- a/src/gui/ministatuswindow.cpp +++ b/src/gui/ministatuswindow.cpp @@ -21,10 +21,11 @@ #include "gui/ministatuswindow.h" -#include "animatedsprite.h" #include "configuration.h" +#include "game.h" #include "graphics.h" #include "playerinfo.h" +#include "sprite.h" #include "statuseffect.h" #include "gui/gui.h" @@ -39,16 +40,22 @@ #include "net/tmwa/protocol.h" +#include "resources/statuseffectdb.h" #include "resources/theme.h" #include "utils/gettext.h" #include "utils/stringutils.h" #include "utils/time.h" +#include <algorithm> + +static constexpr int ICON_SPACING = 3; + MiniStatusWindow::MiniStatusWindow(): Popup("MiniStatus") { setPadding(3); + setMinHeight(0); listen(Event::AttributesChannel); listen(Event::ActorSpriteChannel); @@ -83,8 +90,7 @@ MiniStatusWindow::MiniStatusWindow(): add(mMpBar); add(mXpBar); - setContentSize(mXpBar->getX() + mXpBar->getWidth(), - mXpBar->getY() + mXpBar->getHeight()); + updateSize(); auto stateIt = config.windows.find(getPopupName()); setVisible(stateIt != config.windows.end() ? stateIt->second.visible.value_or(true) @@ -95,36 +101,27 @@ MiniStatusWindow::MiniStatusWindow(): addMouseListener(this); } -void MiniStatusWindow::setIcon(int index, AnimatedSprite *sprite) -{ - if (index >= (int) mIcons.size()) - mIcons.resize(index + 1); - - delete mIcons[index]; - mIcons[index] = sprite; -} - -void MiniStatusWindow::eraseIcon(int index) -{ - mIcons.erase(mIcons.begin() + index); -} +MiniStatusWindow::~MiniStatusWindow() = default; void MiniStatusWindow::drawIcons(Graphics *graphics) { - // Draw icons - int icon_x = mXpBar->getX() + mXpBar->getWidth() + 14; - for (auto &icon : mIcons) + const auto game = Game::instance(); + const int tileWidth = game->getCurrentTileWidth(); + const int tileHeight = game->getCurrentTileHeight(); + + int iconX = mXpBar->getX() + mXpBar->getWidth() + ICON_SPACING + tileWidth / 2; + int iconY = ICON_SPACING + tileHeight; + + for (auto &icon : mStatusIcons) { - if (icon) - { - icon->draw(graphics, icon_x, 3); - icon_x += 2 + icon->getWidth(); - } + icon.sprite->draw(graphics, + iconX - icon.sprite->getWidth() / 2, + iconY - icon.sprite->getHeight()); + iconX += ICON_SPACING + icon.sprite->getWidth(); } } -void MiniStatusWindow::event(Event::Channel channel, - const Event &event) +void MiniStatusWindow::event(Event::Channel channel, const Event &event) { if (channel == Event::AttributesChannel) { @@ -146,7 +143,7 @@ void MiniStatusWindow::event(Event::Channel channel, } if (event.getType() == Event::UpdateStat) { - if (Net::getNetworkType() == ServerType::TMWATHENA && + if (Net::getNetworkType() == ServerType::TmwAthena && event.getInt("id") == TmwAthena::MATK) { StatusWindow::updateMPBar(mMpBar); @@ -157,54 +154,28 @@ void MiniStatusWindow::event(Event::Channel channel, { if (event.getType() == Event::UpdateStatusEffect) { - int index = event.getInt("index"); - bool newStatus = event.getBool("newStatus"); + const int id = event.getInt("index"); + const bool newStatus = event.getBool("newStatus"); - StatusEffect *effect = StatusEffect::getStatusEffect(index, - newStatus); + auto effect = StatusEffectDB::getStatusEffect(id); + if (!effect) + return; - if (effect) - { - effect->deliverMessage(); - effect->playSFX(); - - AnimatedSprite *sprite = effect->getIcon(); - - if (!sprite) - { - // delete sprite, if necessary - for (unsigned int i = 0; i < mStatusEffectIcons.size();) - if (mStatusEffectIcons[i] == index) - { - mStatusEffectIcons.erase(mStatusEffectIcons.begin() - + i); - miniStatusWindow->eraseIcon(i); - } - else - i++; - } - else - { - // replace sprite or append - bool found = false; - - for (unsigned int i = 0; i < mStatusEffectIcons.size(); - i++) - if (mStatusEffectIcons[i] == index) - { - miniStatusWindow->setIcon(i, sprite); - found = true; - break; - } - - if (!found) - { // add new - int offset = mStatusEffectIcons.size(); - miniStatusWindow->setIcon(offset, sprite); - mStatusEffectIcons.push_back(index); - } - } - } + effect->deliverMessage(newStatus); + effect->playSfx(newStatus); + + Sprite *sprite = newStatus ? effect->getIconSprite() : nullptr; + auto it = std::find_if(mStatusIcons.begin(), mStatusIcons.end(), + [id](const StatusIcon &icon) { + return icon.effectId == id; + }); + + if (!sprite && it != mStatusIcons.end()) + mStatusIcons.erase(it); + else if (sprite && it == mStatusIcons.end()) + mStatusIcons.push_back(StatusIcon{id, std::unique_ptr<Sprite>(sprite)}); + + updateSize(); } } } @@ -226,43 +197,84 @@ void MiniStatusWindow::logic() } */ - for (auto &icon : mIcons) - if (icon) - icon->update(Time::absoluteTimeMs()); + for (auto &icon : mStatusIcons) + icon.sprite->update(Time::deltaTimeMs()); +} + +void MiniStatusWindow::draw(gcn::Graphics *graphics) +{ + drawChildren(graphics); + + drawIcons(static_cast<Graphics*>(graphics)); } void MiniStatusWindow::mouseMoved(gcn::MouseEvent &event) { Popup::mouseMoved(event); - const int x = event.getX(); - const int y = event.getY(); + std::string tooltip1; + std::string tooltip2; if (event.getSource() == mXpBar) { - mTextPopup->show(x + getX(), y + getY(), - strprintf("%u/%u", PlayerInfo::getAttribute(EXP), - PlayerInfo::getAttribute(EXP_NEEDED)), - strprintf("%s: %u", _("Need"), - PlayerInfo::getAttribute(EXP_NEEDED) - - PlayerInfo::getAttribute(EXP))); + const int xp = PlayerInfo::getAttribute(EXP); + const int xpNeeded = PlayerInfo::getAttribute(EXP_NEEDED); + tooltip1 = strprintf("%u/%u", xp, xpNeeded); + tooltip2 = strprintf("%s: %u", _("Need"), xpNeeded - xp); } else if (event.getSource() == mHpBar) { - mTextPopup->show(x + getX(), y + getY(), - strprintf("%u/%u", PlayerInfo::getAttribute(HP), - PlayerInfo::getAttribute(MAX_HP))); + const int hp = PlayerInfo::getAttribute(HP); + const int maxHp = PlayerInfo::getAttribute(MAX_HP); + tooltip1 = strprintf("%u/%u", hp, maxHp); } else if (event.getSource() == mMpBar) { - mTextPopup->show(x + getX(), y + getY(), - strprintf("%u/%u", PlayerInfo::getAttribute(MP), - PlayerInfo::getAttribute(MAX_MP))); + const int mp = PlayerInfo::getAttribute(MP); + const int maxMp = PlayerInfo::getAttribute(MAX_MP); + tooltip1 = strprintf("%u/%u", mp, maxMp); } else { + // Check if the mouse is over one of the status icons + const auto game = Game::instance(); + const int tileWidth = game->getCurrentTileWidth(); + const int tileHeight = game->getCurrentTileHeight(); + + int iconX = mXpBar->getX() + mXpBar->getWidth() + ICON_SPACING + tileWidth / 2; + int iconY = ICON_SPACING + tileHeight; + + for (const auto &icon : mStatusIcons) + { + int spriteX = iconX + icon.sprite->getOffsetX() - icon.sprite->getWidth() / 2; + int spriteY = iconY + icon.sprite->getOffsetY() - icon.sprite->getHeight(); + + if (event.getX() >= spriteX && + event.getX() < spriteX + icon.sprite->getWidth() && + event.getY() >= spriteY && + event.getY() < spriteY + icon.sprite->getHeight()) + { + auto effect = StatusEffectDB::getStatusEffect(icon.effectId); + if (effect) + tooltip1 = effect->name; + break; + } + + iconX += ICON_SPACING + icon.sprite->getWidth(); + } + } + + if (tooltip1.empty()) + { mTextPopup->setVisible(false); } + else + { + mTextPopup->show(event.getX() + getX(), + event.getY() + getY(), + tooltip1, + tooltip2); + } } void MiniStatusWindow::mouseExited(gcn::MouseEvent &event) @@ -271,3 +283,19 @@ void MiniStatusWindow::mouseExited(gcn::MouseEvent &event) mTextPopup->setVisible(false); } + +void MiniStatusWindow::updateSize() +{ + int width = mXpBar->getX() + mXpBar->getWidth(); + int height = mXpBar->getY() + mXpBar->getHeight(); + + // Increase width based on the size of the status icons + if (!mStatusIcons.empty()) + { + width += ICON_SPACING; + for (const auto &icon : mStatusIcons) + width += ICON_SPACING + icon.sprite->getWidth(); + } + + setContentSize(width, height); +} diff --git a/src/gui/ministatuswindow.h b/src/gui/ministatuswindow.h index db9e4c69..21c4b76e 100644 --- a/src/gui/ministatuswindow.h +++ b/src/gui/ministatuswindow.h @@ -19,16 +19,16 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef MINISTATUS_H -#define MINISTATUS_H +#pragma once #include "eventlistener.h" #include "gui/widgets/popup.h" +#include <memory> #include <vector> -class AnimatedSprite; +class Sprite; class Graphics; class ProgressBar; class TextPopup; @@ -42,28 +42,20 @@ class MiniStatusWindow : public Popup, public EventListener { public: MiniStatusWindow(); - - void drawIcons(Graphics *graphics); + ~MiniStatusWindow() override; void event(Event::Channel channel, const Event &event) override; void logic() override; // Updates icons - void draw(gcn::Graphics *graphics) override - { drawChildren(graphics); } + void draw(gcn::Graphics *graphics) override; void mouseMoved(gcn::MouseEvent &mouseEvent) override; void mouseExited(gcn::MouseEvent &event) override; private: - bool isInBar(ProgressBar *bar, int x, int y) const; - - /** - * Sets one of the icons. - */ - void setIcon(int index, AnimatedSprite *sprite); - - void eraseIcon(int index); + void drawIcons(Graphics *graphics); + void updateSize(); /* * Mini Status Bars @@ -73,10 +65,13 @@ class MiniStatusWindow : public Popup, public EventListener ProgressBar *mXpBar; TextPopup *mTextPopup; - std::vector<int> mStatusEffectIcons; - std::vector<AnimatedSprite *> mIcons; + struct StatusIcon + { + int effectId; + std::unique_ptr<Sprite> sprite; + }; + + std::vector<StatusIcon> mStatusIcons; }; extern MiniStatusWindow *miniStatusWindow; - -#endif diff --git a/src/gui/npcdialog.cpp b/src/gui/npcdialog.cpp index 18b3ff1b..e2e7b040 100644 --- a/src/gui/npcdialog.cpp +++ b/src/gui/npcdialog.cpp @@ -87,8 +87,8 @@ NpcDialog::NpcDialog(int npcId) // Setup output text box mTextBox = new BrowserBox(BrowserBox::AUTO_WRAP); mTextBox->setWrapIndent(15); - mTextBox->setFrameSize(2); mTextBox->setLinkHandler(mItemLinkHandler.get()); + mTextBox->setEnableKeys(true); mScrollArea = new ScrollArea(mTextBox); mScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); diff --git a/src/gui/npcdialog.h b/src/gui/npcdialog.h index f2b50370..8a18a455 100644 --- a/src/gui/npcdialog.h +++ b/src/gui/npcdialog.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NPCDIALOG_H -#define NPCDIALOG_H +#pragma once #include "gui/widgets/window.h" @@ -214,5 +213,3 @@ class NpcDialog final : public Window, NpcInputState mInputState = NPC_INPUT_NONE; NpcActionState mActionState = NPC_ACTION_WAIT; }; - -#endif // NPCDIALOG_H diff --git a/src/gui/npcpostdialog.h b/src/gui/npcpostdialog.h index 61c17ca6..7021b5b4 100644 --- a/src/gui/npcpostdialog.h +++ b/src/gui/npcpostdialog.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef GUI_NPCPOSTDIALOG_H -#define GUI_NPCPOSTDIALOG_H +#pragma once #include "gui/widgets/window.h" @@ -57,5 +56,3 @@ private: TextBox *mText; TextField *mSender; }; - -#endif diff --git a/src/gui/okdialog.h b/src/gui/okdialog.h index fcb327a9..f56f24e2 100644 --- a/src/gui/okdialog.h +++ b/src/gui/okdialog.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef OK_DIALOG_H -#define OK_DIALOG_H +#pragma once #include "gui/widgets/window.h" @@ -47,5 +46,3 @@ class OkDialog : public Window, public gcn::ActionListener private: TextBox *mTextBox; }; - -#endif // OK_DIALOG_H diff --git a/src/gui/outfitwindow.cpp b/src/gui/outfitwindow.cpp index b5033d70..f914c0cc 100644 --- a/src/gui/outfitwindow.cpp +++ b/src/gui/outfitwindow.cpp @@ -78,8 +78,8 @@ OutfitWindow::~OutfitWindow() void OutfitWindow::load() { - for (int o = 0; o < OUTFITS_COUNT; o++) - memset(mOutfits[o].items, -1, sizeof(mOutfits[o].items)); + for (auto &mOutfit : mOutfits) + memset(mOutfit.items, -1, sizeof(mOutfit.items)); for (auto &outfit : config.outfits) { @@ -89,10 +89,8 @@ void OutfitWindow::load() std::string buf; std::stringstream ss(outfit.items); - for (size_t i = 0; (ss >> buf) && i < OUTFIT_ITEM_COUNT; i++) - { + for (int i = 0; (ss >> buf) && i < OUTFIT_ITEM_COUNT; i++) mOutfits[outfit.index].items[i] = atoi(buf.c_str()); - } mOutfits[outfit.index].unequip = outfit.unequip; } @@ -105,16 +103,15 @@ void OutfitWindow::save() std::string outfitStr; for (int o = 0; o < OUTFITS_COUNT; o++) { - auto &items = mOutfits[o].items; bool emptyOutfit = true; - for (int i = 0; i < OUTFIT_ITEM_COUNT; i++) + for (int item : mOutfits[o].items) { if (!outfitStr.empty()) outfitStr += " "; - outfitStr += items[i] ? toString(items[i]) : toString(-1); - emptyOutfit &= items[i] <= 0; + outfitStr += item ? toString(item) : toString(-1); + emptyOutfit &= item <= 0; } if (!emptyOutfit) @@ -159,10 +156,9 @@ void OutfitWindow::wearOutfit(int outfit) if (mOutfits[outfit].unequip) unequipNotInOutfit(outfit); - Item *item; - for (int i = 0; i < OUTFIT_ITEM_COUNT; i++) + for (int i : mOutfits[outfit].items) { - item = PlayerInfo::getInventory()->findItem(mOutfits[outfit].items[i]); + Item *item = PlayerInfo::getInventory()->findItem(i); if (item && !item->isEquipped() && item->getQuantity()) { if (item->isEquippable()) @@ -174,9 +170,7 @@ void OutfitWindow::wearOutfit(int outfit) void OutfitWindow::copyOutfit(int outfit) { for (int i = 0; i < OUTFIT_ITEM_COUNT; i++) - { mOutfits[mCurrentOutfit].items[i] = mOutfits[outfit].items[i]; - } } void OutfitWindow::draw(gcn::Graphics *graphics) @@ -328,9 +322,9 @@ void OutfitWindow::unequipNotInOutfit(int outfit) if (inventory->getItem(i) && inventory->getItem(i)->isEquipped()) { bool found = false; - for (int f = 0; f < OUTFIT_ITEM_COUNT; f++) + for (int item : mOutfits[outfit].items) { - if (inventory->getItem(i)->getId() == mOutfits[outfit].items[f]) + if (inventory->getItem(i)->getId() == item) { found = true; break; diff --git a/src/gui/outfitwindow.h b/src/gui/outfitwindow.h index 56e96795..10de5321 100644 --- a/src/gui/outfitwindow.h +++ b/src/gui/outfitwindow.h @@ -19,15 +19,14 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef OUTFITWINDOW_H -#define OUTFITWINDOW_H +#pragma once #include "gui/widgets/window.h" #include <guichan/actionlistener.hpp> -#define OUTFITS_COUNT 15 -#define OUTFIT_ITEM_COUNT 9 +constexpr int OUTFITS_COUNT = 15; +constexpr int OUTFIT_ITEM_COUNT = 9; class Button; class CheckBox; @@ -91,5 +90,3 @@ class OutfitWindow : public Window, gcn::ActionListener }; extern OutfitWindow *outfitWindow; - -#endif diff --git a/src/gui/palette.h b/src/gui/palette.h index 9de911d5..145a93ac 100644 --- a/src/gui/palette.h +++ b/src/gui/palette.h @@ -20,8 +20,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef PALETTE_H -#define PALETTE_H +#pragma once #include "utils/time.h" @@ -169,5 +168,3 @@ class Palette std::vector<ColorElem> mColors; std::vector<ColorElem*> mGradVector; }; - -#endif diff --git a/src/gui/popupmenu.cpp b/src/gui/popupmenu.cpp index b1165f27..4bafc074 100644 --- a/src/gui/popupmenu.cpp +++ b/src/gui/popupmenu.cpp @@ -91,12 +91,12 @@ void PopupMenu::showPopup(int x, int y, Being *being) switch (player_relations.getRelation(name)) { - case PlayerRelation::NEUTRAL: + case PlayerRelation::Neutral: mBrowserBox->addRow(strprintf("@@friend|%s@@", strprintf(_("Befriend %s"), name.c_str()).c_str())); - case PlayerRelation::FRIEND: + case PlayerRelation::Friend: mBrowserBox->addRow(strprintf("@@disregard|%s@@", strprintf(_("Disregard %s"), name.c_str()).c_str())); @@ -105,7 +105,7 @@ void PopupMenu::showPopup(int x, int y, Being *being) name.c_str()).c_str())); break; - case PlayerRelation::DISREGARDED: + case PlayerRelation::Disregarded: mBrowserBox->addRow(strprintf("@@unignore|%s@@", strprintf(_("Unignore %s"), name.c_str()).c_str())); @@ -114,7 +114,7 @@ void PopupMenu::showPopup(int x, int y, Being *being) name.c_str()).c_str())); break; - case PlayerRelation::IGNORED: + case PlayerRelation::Ignored: mBrowserBox->addRow(strprintf("@@unignore|%s@@", strprintf(_("Unignore %s"), name.c_str()).c_str())); @@ -126,7 +126,7 @@ void PopupMenu::showPopup(int x, int y, Being *being) strprintf(_("Invite %s to join your guild"), name.c_str()).c_str())); if (local_player->isInParty() || - Net::getNetworkType() == ServerType::MANASERV) + Net::getNetworkType() == ServerType::ManaServ) { mBrowserBox->addRow(strprintf("@@party|%s@@", strprintf(_("Invite %s to join your party"), @@ -223,25 +223,25 @@ void PopupMenu::handleLink(const std::string &link) else if (link == "unignore" && being && being->getType() == ActorSprite::PLAYER) { - player_relations.setRelation(being->getName(), PlayerRelation::NEUTRAL); + player_relations.setRelation(being->getName(), PlayerRelation::Neutral); } else if (link == "ignore" && being && being->getType() == ActorSprite::PLAYER) { - player_relations.setRelation(being->getName(), PlayerRelation::IGNORED); + player_relations.setRelation(being->getName(), PlayerRelation::Ignored); } else if (link == "disregard" && being && being->getType() == ActorSprite::PLAYER) { - player_relations.setRelation(being->getName(), PlayerRelation::DISREGARDED); + player_relations.setRelation(being->getName(), PlayerRelation::Disregarded); } else if (link == "friend" && being && being->getType() == ActorSprite::PLAYER) { - player_relations.setRelation(being->getName(), PlayerRelation::FRIEND); + player_relations.setRelation(being->getName(), PlayerRelation::Friend); } // Guild action else if (link == "guild" && being && @@ -249,18 +249,15 @@ void PopupMenu::handleLink(const std::string &link) { local_player->inviteToGuild(being); } - // Pick Up Floor Item action else if ((link == "pickup") && mFloorItem) { local_player->pickUp(mFloorItem); } - // Look To action else if (link == "look") { } - else if (link == "activate" || link == "equip" || link == "unequip") { assert(mItem); @@ -288,49 +285,36 @@ void PopupMenu::handleLink(const std::string &link) else if (mFloorItem) chatWindow->addItemText(mFloorItem->getInfo().name); } - - else if (link == "split") - { - ItemAmountWindow::showWindow(ItemAmountWindow::ItemSplit, - inventoryWindow, mItem); - } - else if (link == "drop") { ItemAmountWindow::showWindow(ItemAmountWindow::ItemDrop, inventoryWindow, mItem); } - else if (link == "store") { ItemAmountWindow::showWindow(ItemAmountWindow::StoreAdd, inventoryWindow, mItem); } - else if (link == "retrieve") { ItemAmountWindow::showWindow(ItemAmountWindow::StoreRemove, mWindow, mItem); } - else if (link == "party" && being && being->getType() == ActorSprite::PLAYER) { Net::getPartyHandler()->invite(being); } - else if (link == "name" && being) { const std::string &name = being->getName(); chatWindow->addInputText(name); } - else if (link == "admin-kick" && being && being->getType() == ActorSprite::PLAYER) { Net::getAdminHandler()->kick(being->getName()); } - // Unknown actions else if (link != "cancel") { @@ -376,11 +360,6 @@ void PopupMenu::showPopup(Window *parent, int x, int y, Item *item, else mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop"))); } - - if (Net::getInventoryHandler()->canSplit(item)) - { - mBrowserBox->addRow(strprintf("@@split|%s@@", _("Split"))); - } } // Assume in storage for now // TODO: make this whole system more flexible, if needed diff --git a/src/gui/popupmenu.h b/src/gui/popupmenu.h index c7199b78..5a5a88ee 100644 --- a/src/gui/popupmenu.h +++ b/src/gui/popupmenu.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef POPUP_MENU_H -#define POPUP_MENU_H +#pragma once #include "gui/widgets/linkhandler.h" #include "gui/widgets/popup.h" @@ -76,5 +75,3 @@ class PopupMenu : public Popup, public LinkHandler */ void showPopup(int x, int y); }; - -#endif diff --git a/src/gui/quitdialog.cpp b/src/gui/quitdialog.cpp index 4afbd419..51831c4c 100644 --- a/src/gui/quitdialog.cpp +++ b/src/gui/quitdialog.cpp @@ -71,7 +71,8 @@ QuitDialog::QuitDialog(QuitDialog** pointerToMe): placeOption(place, mSwitchAccountServer); // Only added if we are connected to a gameserver - if (state == STATE_GAME) placeOption(place, mSwitchCharacter); + if (state == STATE_GAME) + placeOption(place, mSwitchCharacter); } mOptions[0]->setSelected(true); @@ -90,7 +91,9 @@ QuitDialog::QuitDialog(QuitDialog** pointerToMe): QuitDialog::~QuitDialog() { - if (mMyPointer) *mMyPointer = nullptr; + if (mMyPointer) + *mMyPointer = nullptr; + // Optional widgets, so delete them by hand. delete mForceQuit; delete mLogoutQuit; @@ -166,7 +169,8 @@ void QuitDialog::keyPressed(gcn::KeyEvent &keyEvent) mOptions[0]->setSelected(true); return; } - else if (it == mOptions.begin() && dir < 0) + + if (it == mOptions.begin() && dir < 0) it = mOptions.end(); it += dir; diff --git a/src/gui/quitdialog.h b/src/gui/quitdialog.h index d62d5c51..4672e45f 100644 --- a/src/gui/quitdialog.h +++ b/src/gui/quitdialog.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef QUITDIALOG_H -#define QUITDIALOG_H +#pragma once #include "gui/widgets/window.h" @@ -69,5 +68,3 @@ class QuitDialog : public Window, public gcn::ActionListener, QuitDialog **mMyPointer; }; - -#endif diff --git a/src/gui/recorder.cpp b/src/gui/recorder.cpp index 8dd0f8ed..894e3631 100644 --- a/src/gui/recorder.cpp +++ b/src/gui/recorder.cpp @@ -32,15 +32,16 @@ #include "utils/gettext.h" #include "utils/stringutils.h" -Recorder::Recorder(ChatWindow *chat, const std::string &title, - const std::string &buttonTxt) : - Window(title) +Recorder::Recorder(ChatWindow *chat, + const std::string &title, + const std::string &buttonTxt) + : Window(title) + , mChat(chat) { setWindowName("Recorder"); const int offsetX = 2 * getPadding() + 10; const int offsetY = getTitleBarHeight() + getPadding() + 10; - mChat = chat; auto *button = new Button(buttonTxt, "activate", this); // 123 is the default chat window height. If you change this in Chat, please @@ -56,16 +57,12 @@ Recorder::Recorder(ChatWindow *chat, const std::string &title, loadWindowState(); } -Recorder::~Recorder() -{ -} +Recorder::~Recorder() = default; void Recorder::record(const std::string &msg) { if (mStream.is_open()) - { mStream << msg << std::endl; - } } void Recorder::setRecordingFile(const std::string &msg) diff --git a/src/gui/recorder.h b/src/gui/recorder.h index efd73021..8a84f423 100644 --- a/src/gui/recorder.h +++ b/src/gui/recorder.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef RECORD_H -#define RECORD_H +#pragma once #include "gui/widgets/window.h" @@ -72,5 +71,3 @@ class Recorder : public Window, public gcn::ActionListener std::ofstream mStream; }; - -#endif diff --git a/src/gui/register.cpp b/src/gui/register.cpp index d4ebb59c..62114c10 100644 --- a/src/gui/register.cpp +++ b/src/gui/register.cpp @@ -22,14 +22,12 @@ #include "gui/register.h" #include "client.h" -#include "configuration.h" #include "log.h" #include "gui/logindialog.h" #include "gui/okdialog.h" #include "gui/widgets/button.h" -#include "gui/widgets/checkbox.h" #include "gui/widgets/label.h" #include "gui/widgets/layout.h" #include "gui/widgets/passwordfield.h" @@ -227,7 +225,7 @@ void RegisterDialog::action(const gcn::ActionEvent &event) mLoginData->password = mPasswordField->getText(); if (mFemaleButton) mLoginData->gender = mFemaleButton->isSelected() ? - Gender::FEMALE : Gender::MALE; + Gender::Female : Gender::Male; if (mEmailField) mLoginData->email = mEmailField->getText(); mLoginData->registerLogin = true; diff --git a/src/gui/register.h b/src/gui/register.h index 1996cd47..ae9ee582 100644 --- a/src/gui/register.h +++ b/src/gui/register.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef REGISTER_H -#define REGISTER_H +#pragma once #include "gui/widgets/window.h" @@ -94,5 +93,3 @@ class RegisterDialog : public Window, public gcn::ActionListener, LoginData *mLoginData; }; - -#endif diff --git a/src/gui/sdlinput.h b/src/gui/sdlinput.h index a29f17ca..599aafe7 100644 --- a/src/gui/sdlinput.h +++ b/src/gui/sdlinput.h @@ -56,8 +56,7 @@ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef SDLINPUT_H -#define SDLINPUT_H +#pragma once #include <queue> @@ -204,5 +203,3 @@ protected: bool mMouseDown = false; }; - -#endif diff --git a/src/gui/selldialog.cpp b/src/gui/selldialog.cpp index 5f499982..4aeacd6f 100644 --- a/src/gui/selldialog.cpp +++ b/src/gui/selldialog.cpp @@ -205,7 +205,7 @@ void SellDialog::action(const gcn::ActionEvent &event) sellCount = item->sellCurrentDuplicate(mAmountItems); // For Manaserv, the Item id is to be given as index. - if ((Net::getNetworkType() == ServerType::MANASERV)) + if ((Net::getNetworkType() == ServerType::ManaServ)) itemIndex = item->getId(); Net::getNpcHandler()->sellItem(mNpcId, itemIndex, sellCount); diff --git a/src/gui/selldialog.h b/src/gui/selldialog.h index d59343cb..165d9cc8 100644 --- a/src/gui/selldialog.h +++ b/src/gui/selldialog.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SELL_H -#define SELL_H +#pragma once #include "gui/widgets/window.h" @@ -113,5 +112,3 @@ class SellDialog : public Window, gcn::ActionListener, gcn::SelectionListener int mMaxItems = 0; int mAmountItems = 0; }; - -#endif diff --git a/src/gui/serverdialog.cpp b/src/gui/serverdialog.cpp index 01b477da..d86d751a 100644 --- a/src/gui/serverdialog.cpp +++ b/src/gui/serverdialog.cpp @@ -32,26 +32,21 @@ #include "gui/sdlinput.h" #include "gui/widgets/button.h" -#include "gui/widgets/dropdown.h" #include "gui/widgets/label.h" #include "gui/widgets/layout.h" #include "gui/widgets/listbox.h" #include "gui/widgets/scrollarea.h" -#include "gui/widgets/textfield.h" #include "resources/theme.h" #include "utils/gettext.h" #include "utils/stringutils.h" -#include "utils/xml.h" #include <guichan/font.hpp> #include <cstdlib> #include <string> -static const int MAX_SERVERLIST = 6; - ServersListModel::ServersListModel(ServerInfos *servers, ServerDialog *parent): mServers(servers), mVersionStrings(servers->size(), VersionString(0, std::string())), @@ -61,13 +56,11 @@ ServersListModel::ServersListModel(ServerInfos *servers, ServerDialog *parent): int ServersListModel::getNumberOfElements() { - MutexLocker lock(mParent->getMutex()); return mServers->size(); } std::string ServersListModel::getElementAt(int elementIndex) { - MutexLocker lock(mParent->getMutex()); const ServerInfo &server = mServers->at(elementIndex); std::string myServer; myServer += server.hostname; @@ -102,16 +95,15 @@ public: auto *model = static_cast<ServersListModel*>(mListModel); - updateAlpha(); + const int alpha = gui->getTheme()->getGuiAlpha(); - graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT, - (int) (mAlpha * 255.0f))); + graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT, alpha)); graphics->setFont(getFont()); const int height = getRowHeight(); const gcn::Color unsupported = Theme::getThemeColor(Theme::SERVER_VERSION_NOT_SUPPORTED, - (int) (mAlpha * 255.0f)); + alpha); // Draw filled rectangle around the selected list element if (mSelected >= 0) @@ -122,7 +114,7 @@ public: for (int i = 0, y = 0; i < model->getNumberOfElements(); ++i, y += height) { - ServerInfo info = model->getServer(i); + const ServerInfo &info = model->getServer(i); graphics->setColor(Theme::getThemeColor(Theme::TEXT)); @@ -141,7 +133,6 @@ public: if (info.version.first > 0) { graphics->setColor(unsupported); - graphics->drawText(info.version.second, getWidth() - info.version.first - 2, top); } @@ -164,9 +155,8 @@ ServerDialog::ServerDialog(ServerInfo *serverInfo, const std::string &dir): loadCustomServers(); - mServersListModel = new ServersListModel(&mServers, this); - - mServersList = new ServersListBox(mServersListModel); + mServersListModel = std::make_unique<ServersListModel>(&mServers, this); + mServersList = new ServersListBox(mServersListModel.get()); auto *usedScroll = new ScrollArea(mServersList); usedScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); @@ -185,8 +175,8 @@ ServerDialog::ServerDialog(ServerInfo *serverInfo, const std::string &dir): usedScroll->setVerticalScrollAmount(0); place(0, 0, usedScroll, 6, 5).setPadding(3); - place(0, 5, mDescription, 5); - place(0, 6, mDownloadText, 5); + place(0, 5, mDescription, 6); + place(0, 6, mDownloadText, 6); place(0, 7, mManualEntryButton); place(1, 7, mModifyButton); place(2, 7, mDeleteButton); @@ -226,16 +216,7 @@ ServerDialog::ServerDialog(ServerInfo *serverInfo, const std::string &dir): downloadServerList(); } -ServerDialog::~ServerDialog() -{ - if (mDownload) - { - mDownload->cancel(); - delete mDownload; - mDownload = nullptr; - } - delete mServersListModel; -} +ServerDialog::~ServerDialog() = default; void ServerDialog::action(const gcn::ActionEvent &event) { @@ -262,6 +243,7 @@ void ServerDialog::action(const gcn::ActionEvent &event) else { mDownload->cancel(); + mQuitButton->setEnabled(false); mConnectButton->setEnabled(false); mDeleteButton->setEnabled(false); @@ -345,7 +327,6 @@ void ServerDialog::valueChanged(const gcn::SelectionEvent &) // Update the server and post fields according to the new selection const ServerInfo &myServer = mServersListModel->getServer(index); mDescription->setCaption(myServer.description); - mDeleteButton->setEnabled(myServer.save); mModifyButton->setEnabled(myServer.save); } @@ -362,36 +343,42 @@ void ServerDialog::mouseClicked(gcn::MouseEvent &mouseEvent) void ServerDialog::logic() { - { - MutexLocker lock(&mMutex); - if (mDownloadStatus == DOWNLOADING_COMPLETE) - { - mDownloadStatus = DOWNLOADING_OVER; + Window::logic(); - mDescription->setCaption(mServers[0].description); - mDownloadText->setCaption(std::string()); - } - else if (mDownloadStatus == DOWNLOADING_IN_PROGRESS) - { - mDownloadText->setCaption(strprintf(_("Downloading server list..." - "%2.2f%%"), - mDownloadProgress * 100)); - } - else if (mDownloadStatus == DOWNLOADING_IDLE) - { - mDownloadText->setCaption(_("Waiting for server...")); - } - else if (mDownloadStatus == DOWNLOADING_PREPARING) + if (mDownloadDone) + return; + + auto state = mDownload->getState(); + + switch (state.status) { + case DownloadStatus::InProgress: + mDownloadText->setCaption(strprintf(_("Downloading server list..." + "%2.0f%%"), + state.progress * 100)); + break; + + case DownloadStatus::Canceled: + case DownloadStatus::Error: + mDownloadDone = true; + logger->log("Error retrieving server list: %s", mDownload->getError()); + mDownloadText->setCaption(_("Error retrieving server list!")); + break; + + case DownloadStatus::Complete: + mDownloadDone = true; + loadServers(); + + if (mServers.empty()) { - mDownloadText->setCaption(_("Preparing download")); + mDownloadText->setCaption(_("No servers found!")); } - else if (mDownloadStatus == DOWNLOADING_ERROR) + else { - mDownloadText->setCaption(_("Error retreiving server list!")); + mDownloadText->setCaption(std::string()); + mDescription->setCaption(mServers[0].description); } + break; } - - Window::logic(); } void ServerDialog::downloadServerList() @@ -406,7 +393,7 @@ void ServerDialog::downloadServerList() if (listFile.empty()) listFile = "https://www.manasource.org/serverlist.xml"; - mDownload = new Net::Download(this, listFile, &downloadUpdate); + mDownload = std::make_unique<Net::Download>(listFile); mDownload->setFile(mDir + "/serverlist.xml"); mDownload->start(); } @@ -432,90 +419,92 @@ void ServerDialog::loadServers() for (auto serverNode : rootNode.children()) { - if (serverNode.name() != "server") - continue; + if (serverNode.name() == "server") + loadServer(serverNode); + } +} - ServerInfo server; +void ServerDialog::loadServer(XML::Node serverNode) +{ + ServerInfo server; - std::string type = serverNode.getProperty("type", "unknown"); + std::string type = serverNode.getProperty("type", "unknown"); - server.type = ServerInfo::parseType(type); + server.type = ServerInfo::parseType(type); - // Ignore unknown server types - if (server.type == ServerType::UNKNOWN + // Ignore unknown server types + if (server.type == ServerType::Unknown #ifndef MANASERV_SUPPORT - || server.type == ServerType::MANASERV + || server.type == ServerType::MANASERV #endif ) - { - logger->log("Ignoring server entry with unknown type: %s", - type.c_str()); - continue; - } + { + logger->log("Ignoring server entry with unknown type: %s", + type.c_str()); + return; + } - server.name = serverNode.getProperty("name", std::string()); + server.name = serverNode.getProperty("name", std::string()); - std::string version = serverNode.getProperty("minimumVersion", - std::string()); + std::string version = serverNode.getProperty("minimumVersion", + std::string()); - bool meetsMinimumVersion = compareStrI(version, PACKAGE_VERSION) <= 0; + bool meetsMinimumVersion = strcmp(version.c_str(), PACKAGE_VERSION) <= 0; - // For display in the list - if (meetsMinimumVersion) - version.clear(); - else if (version.empty()) - version = _("requires a newer version"); - else - version = strprintf(_("requires v%s"), version.c_str()); + // For display in the list + if (meetsMinimumVersion) + version.clear(); + else if (version.empty()) + version = _("requires a newer version"); + else + version = strprintf(_("requires v%s"), version.c_str()); - for (auto subNode : serverNode.children()) + for (auto subNode : serverNode.children()) + { + if (subNode.name() == "connection") { - if (subNode.name() == "connection") - { - server.hostname = subNode.getProperty("hostname", std::string()); - server.port = subNode.getProperty("port", 0); - if (server.port == 0) - { - // If no port is given, use the default for the given type - server.port = ServerInfo::defaultPortForServerType(server.type); - } - } - else if (subNode.name() == "description") - { - server.description = subNode.textContent(); - } - else if (subNode.name() == "persistentIp") + server.hostname = subNode.getProperty("hostname", std::string()); + server.port = subNode.getProperty("port", 0); + if (server.port == 0) { - const auto text = subNode.textContent(); - server.persistentIp = text == "1" || text == "true"; + // If no port is given, use the default for the given type + server.port = ServerInfo::defaultPortForServerType(server.type); } } + else if (subNode.name() == "description") + { + server.description = subNode.textContent(); + } + else if (subNode.name() == "persistentIp") + { + const auto text = subNode.textContent(); + server.persistentIp = text == "1" || text == "true"; + } + } - server.version.first = gui->getFont()->getWidth(version); - server.version.second = version; + server.version.first = gui->getFont()->getWidth(version); + server.version.second = version; - MutexLocker lock(&mMutex); - // Add the server to the local list if it's not already present - bool found = false; - int i = 0; - for (auto &s : mServers) + // Add the server to the local list if it's not already present + bool found = false; + int i = 0; + for (auto &s : mServers) + { + if (s == server) { - if (s == server) - { - // Use the name listed in the server list - s.name = server.name; - s.version = server.version; - s.description = server.description; - mServersListModel->setVersionString(i, version); - found = true; - break; - } - ++i; + // Use the name listed in the server list + s.name = server.name; + s.version = server.version; + s.description = server.description; + mServersListModel->setVersionString(i, version); + found = true; + break; } - - if (!found) - mServers.push_back(server); + ++i; } + + if (!found) + mServers.push_back(server); } void ServerDialog::loadCustomServers() @@ -558,51 +547,6 @@ void ServerDialog::saveCustomServers(const ServerInfo ¤tServer, int index) // Restore the correct description if (index < 0) index = 0; - mDescription->setCaption(mServers[index].description); -} - -int ServerDialog::downloadUpdate(void *ptr, DownloadStatus status, - size_t total, size_t remaining) -{ - if (status == DOWNLOAD_STATUS_CANCELLED) - return -1; - - auto *sd = reinterpret_cast<ServerDialog*>(ptr); - bool finished = false; - - if (status == DOWNLOAD_STATUS_COMPLETE) - { - finished = true; - } - else if (status < 0) - { - logger->log("Error retreiving server list: %s", - sd->mDownload->getError()); - sd->mDownloadStatus = DOWNLOADING_ERROR; - } - else - { - float progress = (float) remaining / total; - - if (progress != progress) - progress = 0.0f; // check for NaN - else if (progress < 0.0f) - progress = 0.0f; // no idea how this could ever happen, but why not check for it anyway. - else if (progress > 1.0f) - progress = 1.0f; - - MutexLocker lock(&sd->mMutex); - sd->mDownloadStatus = DOWNLOADING_IN_PROGRESS; - sd->mDownloadProgress = progress; - } - - if (finished) - { - sd->loadServers(); - - MutexLocker lock(&sd->mMutex); - sd->mDownloadStatus = DOWNLOADING_COMPLETE; - } - - return 0; + if (static_cast<size_t>(index) < mServers.size()) + mDescription->setCaption(mServers[index].description); } diff --git a/src/gui/serverdialog.h b/src/gui/serverdialog.h index 4db36462..cc2725be 100644 --- a/src/gui/serverdialog.h +++ b/src/gui/serverdialog.h @@ -19,21 +19,20 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SERVERDIALOG_H -#define SERVERDIALOG_H +#pragma once #include "gui/widgets/window.h" #include "net/download.h" #include "net/serverinfo.h" - -#include "utils/mutex.h" +#include "utils/xml.h" #include <guichan/actionlistener.hpp> #include <guichan/keylistener.hpp> #include <guichan/listmodel.hpp> #include <guichan/selectionlistener.hpp> +#include <memory> #include <string> #include <vector> @@ -90,7 +89,6 @@ class ServerDialog : public Window, { public: ServerDialog(ServerInfo *serverInfo, const std::string &dir); - ~ServerDialog() override; /** @@ -110,10 +108,8 @@ class ServerDialog : public Window, void logic() override; protected: - friend class ServersListModel; - Mutex *getMutex() { return &mMutex; } - friend class CustomServerDialog; + /** * Saves the new server entry in the custom server list. * Removes the given entry when the serverInfo is empty. @@ -128,12 +124,10 @@ class ServerDialog : public Window, */ void downloadServerList(); void loadServers(); + void loadServer(XML::Node serverNode); void loadCustomServers(); - static int downloadUpdate(void *ptr, DownloadStatus status, - size_t total, size_t remaining); - Label *mDescription; Button *mQuitButton; Button *mConnectButton; @@ -142,31 +136,13 @@ class ServerDialog : public Window, Button *mDeleteButton; ListBox *mServersList; - ServersListModel *mServersListModel; + std::unique_ptr<ServersListModel> mServersListModel; const std::string &mDir; - enum ServerDialogDownloadStatus - { - DOWNLOADING_ERROR, - DOWNLOADING_PREPARING, - DOWNLOADING_IDLE, - DOWNLOADING_IN_PROGRESS, - DOWNLOADING_COMPLETE, - DOWNLOADING_OVER - }; - - /** Status of the current download. */ - ServerDialogDownloadStatus mDownloadStatus = DOWNLOADING_PREPARING; - - Net::Download *mDownload = nullptr; + std::unique_ptr<Net::Download> mDownload; + bool mDownloadDone = false; Label *mDownloadText; - - Mutex mMutex; - float mDownloadProgress = -1.0f; - ServerInfos mServers; ServerInfo *mServerInfo; }; - -#endif diff --git a/src/gui/setup.h b/src/gui/setup.h index d510267d..ec45f93b 100644 --- a/src/gui/setup.h +++ b/src/gui/setup.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SETUP_H -#define SETUP_H +#pragma once #include "gui/widgets/window.h" @@ -72,5 +71,3 @@ class Setup : public Window, public gcn::ActionListener }; extern Setup* setupWindow; - -#endif diff --git a/src/gui/setup_audio.cpp b/src/gui/setup_audio.cpp index f51bfcb6..43b132d8 100644 --- a/src/gui/setup_audio.cpp +++ b/src/gui/setup_audio.cpp @@ -29,6 +29,7 @@ #include "gui/widgets/checkbox.h" #include "gui/widgets/label.h" +#include "gui/widgets/layout.h" #include "gui/widgets/slider.h" #include "utils/gettext.h" @@ -71,11 +72,11 @@ Setup_Audio::Setup_Audio(): // Do the layout place(0, 0, mSoundCheckBox); - place(0, 1, mSfxSlider); + place(0, 1, mSfxSlider).setVAlign(LayoutCell::CENTER); place(1, 1, sfxLabel); - place(0, 2, mNotificationsSlider); + place(0, 2, mNotificationsSlider).setVAlign(LayoutCell::CENTER); place(1, 2, notificationsLabel); - place(0, 3, mMusicSlider); + place(0, 3, mMusicSlider).setVAlign(LayoutCell::CENTER); place(1, 3, musicLabel); place(0, 4, mDownloadMusicCheckBox); } diff --git a/src/gui/setup_audio.h b/src/gui/setup_audio.h index 4ee277ef..c19b38b6 100644 --- a/src/gui/setup_audio.h +++ b/src/gui/setup_audio.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef GUI_SETUP_AUDIO_H -#define GUI_SETUP_AUDIO_H +#pragma once #include "guichanfwd.h" @@ -51,5 +50,3 @@ class Setup_Audio : public SetupTab, public gcn::ActionListener gcn::Slider *mNotificationsSlider; gcn::Slider *mMusicSlider; }; - -#endif // GUI_SETUP_AUDIO_H diff --git a/src/gui/setup_colors.cpp b/src/gui/setup_colors.cpp index 880f6f8a..c3d6a8c8 100644 --- a/src/gui/setup_colors.cpp +++ b/src/gui/setup_colors.cpp @@ -150,19 +150,19 @@ Setup_Colors::Setup_Colors() : place(0, 0, mScroll, 6, 6).setPadding(2); place(0, 6, mPreviewBox, 6).setPadding(2); place(0, 7, mGradTypeLabel, 3); - place(3, 7, mGradTypeSlider); + place(3, 7, mGradTypeSlider).setVAlign(Layout::CENTER); place(4, 7, mGradTypeText, 2).setPadding(1); place(0, 8, mRedLabel, 3); - place(3, 8, mRedSlider); + place(3, 8, mRedSlider).setVAlign(Layout::CENTER); place(5, 8, mRedText).setPadding(1); place(0, 9, mGreenLabel, 3); - place(3, 9, mGreenSlider); + place(3, 9, mGreenSlider).setVAlign(Layout::CENTER); place(5, 9, mGreenText).setPadding(1); place(0, 10, mBlueLabel, 3); - place(3, 10, mBlueSlider); + place(3, 10, mBlueSlider).setVAlign(Layout::CENTER); place(5, 10, mBlueText).setPadding(1); place(0, 11, mGradDelayLabel, 3); - place(3, 11, mGradDelaySlider); + place(3, 11, mGradDelaySlider).setVAlign(Layout::CENTER); place(5, 11, mGradDelayText).setPadding(1); mGradTypeText->setCaption(std::string()); diff --git a/src/gui/setup_colors.h b/src/gui/setup_colors.h index b534ca8a..9aa5be74 100644 --- a/src/gui/setup_colors.h +++ b/src/gui/setup_colors.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SETUP_COLORS_H -#define SETUP_COLORS_H +#pragma once #include "guichanfwd.h" @@ -87,5 +86,3 @@ class Setup_Colors : public SetupTab, void updateColor(); void updateGradType(); }; - -#endif // SETUP_COLORS_H diff --git a/src/gui/setup_interface.h b/src/gui/setup_interface.h index 530d575a..9f5bbf1a 100644 --- a/src/gui/setup_interface.h +++ b/src/gui/setup_interface.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef GUI_SETUP_INTERFACE_H -#define GUI_SETUP_INTERFACE_H +#pragma once #include "being.h" #include "guichanfwd.h" @@ -77,5 +76,3 @@ class Setup_Interface : public SetupTab, public gcn::ActionListener, gcn::DropDown *mFontSizeDropDown; }; - -#endif diff --git a/src/gui/setup_joystick.h b/src/gui/setup_joystick.h index e51ccbe8..8b319ad2 100644 --- a/src/gui/setup_joystick.h +++ b/src/gui/setup_joystick.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef GUI_SETUP_JOYSTICK_H -#define GUI_SETUP_JOYSTICK_H +#pragma once #include "guichanfwd.h" @@ -44,5 +43,3 @@ class Setup_Joystick : public SetupTab, public gcn::ActionListener bool mJoystickEnabled; gcn::CheckBox *mJoystickCheckBox; }; - -#endif diff --git a/src/gui/setup_keyboard.h b/src/gui/setup_keyboard.h index 1c9d1733..e953bdf3 100644 --- a/src/gui/setup_keyboard.h +++ b/src/gui/setup_keyboard.h @@ -20,8 +20,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef GUI_SETUP_KEYBOARD_H -#define GUI_SETUP_KEYBOARD_H +#pragma once #include "guichanfwd.h" @@ -71,5 +70,3 @@ class Setup_Keyboard : public SetupTab, public gcn::ActionListener bool mKeySetting; /**< flag to check if key being set. */ }; - -#endif diff --git a/src/gui/setup_players.cpp b/src/gui/setup_players.cpp index 67fe79da..7cfa572a 100644 --- a/src/gui/setup_players.cpp +++ b/src/gui/setup_players.cpp @@ -267,13 +267,13 @@ Setup_Players::Setup_Players(): place(0, 0, mPlayerTitleTable, 4); place(0, 1, mPlayerScrollArea, 4, 4).setPadding(2); place(0, 5, mDeleteButton); - place(0, 6, mShowGenderCheckBox, 2).setPadding(2); - place(0, 7, mEnableChatLogCheckBox, 2).setPadding(2); + place(0, 6, mShowGenderCheckBox, 2); + place(0, 7, mEnableChatLogCheckBox, 2); place(2, 5, ignore_action_label); - place(2, 6, mIgnoreActionChoicesBox, 2).setPadding(2); + place(2, 6, mIgnoreActionChoicesBox, 2); place(0, 8, mDefaultTrading); place(0, 9, mDefaultWhisper); - place(0, 10, mWhisperTabCheckBox, 4).setPadding(4); + place(0, 10, mWhisperTabCheckBox, 4); player_relations.addListener(this); } diff --git a/src/gui/setup_players.h b/src/gui/setup_players.h index 126d621b..3ca422d2 100644 --- a/src/gui/setup_players.h +++ b/src/gui/setup_players.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef GUI_SETUP_PLAYERS_H -#define GUI_SETUP_PLAYERS_H +#pragma once #include "guichanfwd.h" #include "playerrelations.h" @@ -70,5 +69,3 @@ private: gcn::CheckBox *mShowGenderCheckBox; gcn::CheckBox *mEnableChatLogCheckBox; }; - -#endif diff --git a/src/gui/setup_video.cpp b/src/gui/setup_video.cpp index 38602a5c..b5322525 100644 --- a/src/gui/setup_video.cpp +++ b/src/gui/setup_video.cpp @@ -325,17 +325,17 @@ Setup_Video::Setup_Video(): place(0, 2, mDisableSDLTransparencyCheckBox, 4); place(0, 3, mFpsCheckBox); - place(1, 3, mFpsSlider, 2); + place(1, 3, mFpsSlider, 2).setVAlign(LayoutCell::CENTER); place(3, 3, mFpsLabel); place(0, 4, mParticleEffectsCheckBox, 4); place(0, 5, particleDetailLabel); - place(1, 5, mParticleDetailSlider, 2); + place(1, 5, mParticleDetailSlider, 2).setVAlign(LayoutCell::CENTER); place(3, 5, mParticleDetailField); place(0, 6, overlayDetailLabel); - place(1, 6, mOverlayDetailSlider, 2); + place(1, 6, mOverlayDetailSlider, 2).setVAlign(LayoutCell::CENTER); place(3, 6, mOverlayDetailField); } @@ -557,6 +557,6 @@ void Setup_Video::refreshScaleList() } mScaleListModel->setVideoSettings(mVideoSettings); - mScaleDropDown->setListModel(mScaleListModel.get()); + mScaleDropDown->adjustHeight(); mScaleDropDown->setSelected(mVideoSettings.userScale); } diff --git a/src/gui/setup_video.h b/src/gui/setup_video.h index 6d4fbe77..969d8e8f 100644 --- a/src/gui/setup_video.h +++ b/src/gui/setup_video.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef GUI_SETUP_VIDEO_H -#define GUI_SETUP_VIDEO_H +#pragma once #include "guichanfwd.h" @@ -30,6 +29,7 @@ #include <guichan/actionlistener.hpp> #include <guichan/keylistener.hpp> +class DropDown; class ResolutionListModel; class ScaleListModel; @@ -60,7 +60,7 @@ class Setup_Video : public SetupTab, public gcn::ActionListener, gcn::DropDown *mWindowModeDropDown; gcn::DropDown *mResolutionDropDown; - gcn::DropDown *mScaleDropDown; + DropDown *mScaleDropDown; gcn::CheckBox *mVSyncCheckBox; gcn::CheckBox *mOpenGLCheckBox; gcn::CheckBox *mCustomCursorCheckBox; @@ -80,5 +80,3 @@ class Setup_Video : public SetupTab, public gcn::ActionListener, gcn::CheckBox *mDisableSDLTransparencyCheckBox; }; - -#endif diff --git a/src/gui/shortcutwindow.cpp b/src/gui/shortcutwindow.cpp index 7d299d2c..82c7678e 100644 --- a/src/gui/shortcutwindow.cpp +++ b/src/gui/shortcutwindow.cpp @@ -41,7 +41,11 @@ ShortcutWindow::ShortcutWindow(const std::string &title, setSaveVisible(true); setupWindow->registerWindowForReset(this); - const int border = getPadding() * 2; + auto scrollArea = new ScrollArea(content); + scrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + scrollArea->setOpaque(false); + + const int border = (getPadding() + content->getFrameSize()) * 2; setMinWidth(content->getBoxWidth() + border); setMinHeight(content->getBoxHeight() + border + GRAB_MARGIN); setMaxWidth(content->getBoxWidth() * content->getMaxItems() + border); @@ -49,10 +53,6 @@ ShortcutWindow::ShortcutWindow(const std::string &title, setDefaultSize(getMinWidth(), getMaxHeight(), ImageRect::LOWER_RIGHT); - auto scrollArea = new ScrollArea(content); - scrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); - scrollArea->setOpaque(false); - place(0, 0, scrollArea, 5, 5).setPadding(0); Layout &layout = getLayout(); diff --git a/src/gui/shortcutwindow.h b/src/gui/shortcutwindow.h index 7494dfed..68e031ad 100644 --- a/src/gui/shortcutwindow.h +++ b/src/gui/shortcutwindow.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SHORTCUTWINDOW_H -#define SHORTCUTWINDOW_H +#pragma once #include "gui/widgets/window.h" @@ -39,5 +38,3 @@ class ShortcutWindow : public Window extern ShortcutWindow *itemShortcutWindow; extern ShortcutWindow *emoteShortcutWindow; - -#endif diff --git a/src/gui/skilldialog.cpp b/src/gui/skilldialog.cpp index 5b85252d..c1911ac5 100644 --- a/src/gui/skilldialog.cpp +++ b/src/gui/skilldialog.cpp @@ -30,7 +30,6 @@ #include "gui/widgets/button.h" #include "gui/widgets/label.h" #include "gui/widgets/listbox.h" -#include "gui/widgets/progressbar.h" #include "gui/widgets/scrollarea.h" #include "gui/widgets/tab.h" #include "gui/widgets/tabbedarea.h" @@ -43,7 +42,6 @@ #include "resources/resourcemanager.h" #include "resources/theme.h" -#include "utils/dtor.h" #include "utils/gettext.h" #include "utils/stringutils.h" #include "utils/xml.h" @@ -61,7 +59,7 @@ struct SkillInfo { unsigned short id; std::string name; - Image *icon = nullptr; + ResourceRef<Image> icon; bool modifiable; bool visible; SkillModel *model = nullptr; @@ -73,25 +71,16 @@ struct SkillInfo float progress; gcn::Color color; - ~SkillInfo() - { - if (icon) - icon->decRef(); - } + ~SkillInfo() = default; void setIcon(const std::string &iconPath) { ResourceManager *res = ResourceManager::getInstance(); if (!iconPath.empty()) - { icon = res->getImage(iconPath); - } if (!icon) - { - icon = Theme::getImageFromTheme( - paths.getStringValue("unknownItemFile")); - } + icon = Theme::getImageFromTheme(paths.getStringValue("unknownItemFile")); } void update(); @@ -113,19 +102,19 @@ public: void updateVisibilities(); - void addSkill(SkillInfo *info) - { mSkills.push_back(info); } + void addSkill(std::unique_ptr<SkillInfo> info) + { mSkills.push_back(std::move(info)); } private: - std::vector<SkillInfo *> mSkills; + std::vector<std::unique_ptr<SkillInfo>> mSkills; std::vector<SkillInfo *> mVisibleSkills; }; class SkillListBox : public ListBox { public: - SkillListBox(SkillModel *model): - ListBox(model) + SkillListBox(SkillModel *model) + : ListBox(model) {} SkillInfo *getSelectedInfo() @@ -142,14 +131,12 @@ public: if (!mListModel) return; - auto* model = static_cast<SkillModel*>(mListModel); - - updateAlpha(); + auto *model = static_cast<SkillModel *>(mListModel); + auto *graphics = static_cast<Graphics *>(gcnGraphics); - auto *graphics = static_cast<Graphics*>(gcnGraphics); + const int alpha = gui->getTheme()->getGuiAlpha(); - graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT, - (int) (mAlpha * 255.0f))); + graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT, alpha)); graphics->setFont(getFont()); // Draw filled rectangle around the selected list element @@ -165,12 +152,8 @@ public: i < model->getNumberOfElements(); ++i, y += getRowHeight()) { - SkillInfo *e = model->getSkillAt(i); - - if (e) - { + if (SkillInfo *e = model->getSkillAt(i)) e->draw(graphics, y, getWidth()); - } } } @@ -180,8 +163,8 @@ public: class SkillTab : public Tab { public: - SkillTab(const std::string &name, SkillListBox *listBox): - mListBox(listBox) + SkillTab(const std::string &name, SkillListBox *listBox) + : mListBox(listBox) { setCaption(name); } @@ -189,7 +172,6 @@ public: ~SkillTab() override { delete mListBox; - mListBox = nullptr; } SkillInfo *getSelectedInfo() @@ -215,11 +197,11 @@ SkillDialog::SkillDialog(): setMinWidth(240); setupWindow->registerWindowForReset(this); - mTabs = new TabbedArea(); + mTabbedArea = new TabbedArea; mPointsLabel = new Label("0"); mIncreaseButton = new Button(_("Up"), "inc", this); - place(0, 0, mTabs, 5, 5); + place(0, 0, mTabbedArea, 5, 5); place(0, 5, mPointsLabel, 4); place(4, 5, mIncreaseButton); @@ -236,7 +218,7 @@ void SkillDialog::action(const gcn::ActionEvent &event) { if (event.getId() == "inc") { - auto *tab = static_cast<SkillTab*>(mTabs->getSelectedTab()); + auto *tab = static_cast<SkillTab*>(mTabbedArea->getSelectedTab()); if (SkillInfo *info = tab->getSelectedInfo()) Net::getPlayerHandler()->increaseSkill(info->id); } @@ -249,12 +231,11 @@ void SkillDialog::action(const gcn::ActionEvent &event) std::string SkillDialog::update(int id) { auto i = mSkills.find(id); - if (i != mSkills.end()) { - SkillInfo *info = i->second; - info->update(); - return info->name; + SkillInfo &info = *i->second; + info.update(); + return info.name; } return std::string(); @@ -267,9 +248,7 @@ void SkillDialog::update() mPointsLabel->adjustSize(); for (auto &skill : mSkills) - { skill.second->update(); - } } void SkillDialog::event(Event::Channel channel, const Event &event) @@ -291,20 +270,11 @@ void SkillDialog::event(Event::Channel channel, const Event &event) void SkillDialog::clearSkills() { - // Fixes issues with removing tabs - if (mTabs->getSelectedTabIndex() != -1) - { - mTabs->setSelectedTab((unsigned int) 0); - - while (mTabs->getSelectedTabIndex() != -1) - { - gcn::Tab *tab = mTabs->getSelectedTab(); - mTabs->removeTabWithIndex(mTabs->getSelectedTabIndex()); - delete tab; - } - } + for (auto &tab : mTabs) + mTabbedArea->removeTab(tab.get()); - delete_all(mSkills); + mTabs.clear(); + mSkillModels.clear(); mSkills.clear(); } @@ -317,40 +287,40 @@ void SkillDialog::loadSkills() int setCount = 0; std::string setName; - ScrollArea *scroll; - SkillListBox *listbox; - SkillTab *tab; if (!root || root.name() != "skills") { logger->log("Error loading skills file: %s", SKILLS_FILE); - if (Net::getNetworkType() == ServerType::TMWATHENA) + if (Net::getNetworkType() == ServerType::TmwAthena) { - auto *model = new SkillModel(); - auto *skill = new SkillInfo; + auto model = std::make_unique<SkillModel>(); + auto skill = std::make_unique<SkillInfo>(); skill->id = 1; skill->name = "basic"; skill->setIcon(std::string()); skill->modifiable = true; skill->visible = true; - skill->model = model; + skill->model = model.get(); skill->update(); - model->addSkill(skill); - mSkills[1] = skill; + mSkills[1] = skill.get(); + model->addSkill(std::move(skill)); model->updateVisibilities(); - listbox = new SkillListBox(model); - scroll = new ScrollArea(listbox); + auto listbox = new SkillListBox(model.get()); + auto scroll = std::make_unique<ScrollArea>(listbox); scroll->setOpaque(false); scroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER); scroll->setVerticalScrollPolicy(ScrollArea::SHOW_ALWAYS); - tab = new SkillTab("Skills", listbox); + auto tab = std::make_unique<SkillTab>("Skills", listbox); + mTabbedArea->addTab(tab.get(), scroll.get()); - mTabs->addTab(tab, scroll); + mTabs.push_back(std::move(tab)); + mTabWidgets.push_back(std::move(scroll)); + mSkillModels.push_back(std::move(model)); update(); } @@ -365,7 +335,7 @@ void SkillDialog::loadSkills() setCount++; setName = set.getProperty("name", strprintf(_("Skill Set %d"), setCount)); - auto *model = new SkillModel(); + auto model = std::make_unique<SkillModel>(); for (auto node : set.children()) { @@ -375,34 +345,39 @@ void SkillDialog::loadSkills() std::string name = node.getProperty("name", strprintf(_("Skill %d"), id)); std::string icon = node.getProperty("icon", ""); - auto *skill = new SkillInfo; + auto skill = std::make_unique<SkillInfo>(); skill->id = id; skill->name = name; skill->setIcon(icon); skill->modifiable = false; skill->visible = false; - skill->model = model; + skill->model = model.get(); skill->update(); - model->addSkill(skill); + mSkills[id] = skill.get(); - mSkills[id] = skill; + model->addSkill(std::move(skill)); } } model->updateVisibilities(); - listbox = new SkillListBox(model); - scroll = new ScrollArea(listbox); + auto listbox = new SkillListBox(model.get()); + auto scroll = std::make_unique<ScrollArea>(listbox); scroll->setOpaque(false); scroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER); scroll->setVerticalScrollPolicy(ScrollArea::SHOW_ALWAYS); - tab = new SkillTab(setName, listbox); + auto tab = std::make_unique<SkillTab>(setName, listbox); + + mTabbedArea->addTab(tab.get(), scroll.get()); - mTabs->addTab(tab, scroll); + mTabs.push_back(std::move(tab)); + mTabWidgets.push_back(std::move(scroll)); + mSkillModels.push_back(std::move(model)); } } + update(); } @@ -412,9 +387,9 @@ void SkillDialog::setModifiable(int id, bool modifiable) if (it != mSkills.end()) { - SkillInfo *info = it->second; - info->modifiable = modifiable; - info->update(); + SkillInfo &info = *it->second; + info.modifiable = modifiable; + info.update(); } } @@ -423,12 +398,8 @@ void SkillModel::updateVisibilities() mVisibleSkills.clear(); for (auto &skill : mSkills) - { if (skill->visible) - { - mVisibleSkills.push_back(skill); - } - } + mVisibleSkills.push_back(skill.get()); } void SkillInfo::update() @@ -504,8 +475,7 @@ void SkillInfo::draw(Graphics *graphics, int y, int width) if (!skillExp.empty()) { - gcn::Rectangle rect(33, y + 15, width - 33, 17); - - ProgressBar::render(graphics, rect, color, progress, skillExp); + const gcn::Rectangle rect(33, y + 15, width - 33, 17); + gui->getTheme()->drawProgressBar(graphics, rect, color, progress, skillExp); } } diff --git a/src/gui/skilldialog.h b/src/gui/skilldialog.h index e88c279f..83dc8dd2 100644 --- a/src/gui/skilldialog.h +++ b/src/gui/skilldialog.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SKILLDIALOG_H -#define SKILLDIALOG_H +#pragma once #include "gui/widgets/window.h" #include "eventlistener.h" @@ -28,10 +27,13 @@ #include <guichan/actionlistener.hpp> #include <map> +#include <memory> +#include <vector> class Button; class Label; class ScrollArea; +class SkillModel; class Tab; class TabbedArea; @@ -46,7 +48,6 @@ class SkillDialog : public Window, public gcn::ActionListener, public EventListe { public: SkillDialog(); - ~SkillDialog() override; void event(Event::Channel channel, const Event &event) override; @@ -75,12 +76,13 @@ class SkillDialog : public Window, public gcn::ActionListener, public EventListe bool hasSkills() { return !mSkills.empty(); } private: - std::map<int, SkillInfo *> mSkills; - TabbedArea *mTabs; + std::vector<std::unique_ptr<SkillModel>> mSkillModels; + std::vector<std::unique_ptr<Tab>> mTabs; + std::vector<std::unique_ptr<gcn::Widget>> mTabWidgets; + std::map<int, SkillInfo*> mSkills; + TabbedArea *mTabbedArea; Label *mPointsLabel; Button *mIncreaseButton; }; extern SkillDialog *skillDialog; - -#endif diff --git a/src/gui/socialwindow.cpp b/src/gui/socialwindow.cpp index 5a15da8f..072d87bc 100644 --- a/src/gui/socialwindow.cpp +++ b/src/gui/socialwindow.cpp @@ -34,6 +34,7 @@ #include "gui/widgets/avatarlistbox.h" #include "gui/widgets/browserbox.h" #include "gui/widgets/button.h" +#include "gui/widgets/layout.h" #include "gui/widgets/linkhandler.h" #include "gui/widgets/popup.h" #include "gui/widgets/scrollarea.h" @@ -236,6 +237,11 @@ private: class PlayerList : public AvatarListModel { public: + ~PlayerList() override + { + delete_all(mPlayers); + } + void setPlayers(const std::vector<Avatar*> &players) { delete_all(mPlayers); @@ -273,7 +279,7 @@ public: mScroll->setVerticalScrollPolicy(gcn::ScrollArea::SHOW_AUTO); } - ~PlayerListTab() + ~PlayerListTab() override { delete mPlayerList; } @@ -355,13 +361,8 @@ SocialWindow::SocialWindow() : setResizable(true); setSaveVisible(true); setCloseButton(true); - setMinWidth(120); - setMinHeight(55); - setDefaultSize(590, 200, 150, 124); setupWindow->registerWindowForReset(this); - loadWindowState(); - mCreateButton = new Button(_("Create"), "create", this); mInviteButton = new Button(_("Invite"), "invite", this); mLeaveButton = new Button(_("Leave"), "leave", this); @@ -372,19 +373,23 @@ SocialWindow::SocialWindow() : place(2, 0, mLeaveButton); place(0, 1, mTabs, 4, 4); - widgetResized(nullptr); + // Determine minimum size + int width = 0, height = 0; + getLayout().reflow(width, height); + setMinimumContentSize(width, height); + + setDefaultSize(590, 200, 150, 124); + loadWindowState(); mCreatePopup = new CreatePopup; mPlayerListTab = new PlayerListTab; - mPlayerListTab->setCaption(strprintf(_("Online (%zu)"), 0ul)); + mPlayerListTab->setCaption(strprintf(_("Online (%u)"), 0u)); mTabs->addTab(mPlayerListTab, mPlayerListTab->mScroll.get()); if (local_player->getParty()) - { addTab(local_player->getParty()); - } else updateButtons(); } @@ -673,7 +678,9 @@ void SocialWindow::showPartyCreate() void SocialWindow::setPlayersOnline(const std::vector<Avatar*> &players) { mPlayerListTab->setPlayers(players); - mPlayerListTab->setCaption(strprintf(_("Online (%zu)"), players.size())); + + unsigned playerCount = static_cast<unsigned>(players.size()); + mPlayerListTab->setCaption(strprintf(_("Online (%u)"), playerCount)); } void SocialWindow::logic() diff --git a/src/gui/socialwindow.h b/src/gui/socialwindow.h index 350f919c..d23f1b86 100644 --- a/src/gui/socialwindow.h +++ b/src/gui/socialwindow.h @@ -18,8 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SOCIALWINDOW_H -#define SOCIALWINDOW_H +#pragma once #include "utils/time.h" @@ -111,5 +110,3 @@ protected: }; extern SocialWindow *socialWindow; - -#endif // SOCIALWINDOW_H diff --git a/src/gui/speechbubble.cpp b/src/gui/speechbubble.cpp index 58747d57..72ec8bd2 100644 --- a/src/gui/speechbubble.cpp +++ b/src/gui/speechbubble.cpp @@ -27,14 +27,11 @@ #include "gui/widgets/label.h" #include "gui/widgets/textbox.h" -#include "resources/theme.h" - #include <guichan/font.hpp> - #include <guichan/widgets/label.hpp> -SpeechBubble::SpeechBubble(): - Popup("Speech", "speechbubble.xml") +SpeechBubble::SpeechBubble() + : Popup("Speech", SkinType::SpeechBubble) { setMinWidth(0); setMinHeight(0); @@ -45,7 +42,7 @@ SpeechBubble::SpeechBubble(): mSpeechBox = new TextBox; mSpeechBox->setEditable(false); mSpeechBox->setOpaque(false); - mSpeechBox->setTextColor(&Theme::getThemeColor(Theme::CHAT)); + mSpeechBox->setTextColor(&Theme::getThemeColor(Theme::BUBBLE_TEXT)); add(mCaption); add(mSpeechBox); @@ -60,11 +57,9 @@ void SpeechBubble::setCaption(const std::string &name, const gcn::Color *color) void SpeechBubble::setText(const std::string &text, bool showName) { - if (text == mText && (mCaption->getWidth() <= mSpeechBox->getMinWidth())) + if (text == mText && mCaption->getWidth() <= mSpeechBox->getMinWidth()) return; - mSpeechBox->setTextColor(&Theme::getThemeColor(Theme::TEXT)); - int width = mCaption->getWidth(); mSpeechBox->setTextWrapped(text, 130 > width ? 130 : width); const int speechWidth = mSpeechBox->getMinWidth(); diff --git a/src/gui/speechbubble.h b/src/gui/speechbubble.h index da677dcd..fbb1a21e 100644 --- a/src/gui/speechbubble.h +++ b/src/gui/speechbubble.h @@ -20,8 +20,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SPEECHBUBBLE_H -#define SPEECHBUBBLE_H +#pragma once #include "gui/widgets/popup.h" @@ -51,5 +50,3 @@ class SpeechBubble : public Popup gcn::Label *mCaption; TextBox *mSpeechBox; }; - -#endif diff --git a/src/gui/statuswindow.cpp b/src/gui/statuswindow.cpp index ffcfa350..108d68cc 100644 --- a/src/gui/statuswindow.cpp +++ b/src/gui/statuswindow.cpp @@ -290,7 +290,7 @@ void StatusWindow::event(Event::Channel channel, it->second->update(); } - if (Net::getNetworkType() == ServerType::TMWATHENA && + if (Net::getNetworkType() == ServerType::TmwAthena && id == TmwAthena::MATK) { updateMPBar(mMpBar, true); diff --git a/src/gui/statuswindow.h b/src/gui/statuswindow.h index 99c3b46a..fc7ad67c 100644 --- a/src/gui/statuswindow.h +++ b/src/gui/statuswindow.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef STATUS_H -#define STATUS_H +#pragma once #include "eventlistener.h" @@ -85,5 +84,3 @@ class StatusWindow : public Window, public EventListener }; extern StatusWindow *statusWindow; - -#endif diff --git a/src/gui/textdialog.h b/src/gui/textdialog.h index 66ab3e53..d9634434 100644 --- a/src/gui/textdialog.h +++ b/src/gui/textdialog.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef GUI_GUILD_DIALOG_H -#define GUI_GUILD_DIALOG_H +#pragma once #include "gui/widgets/window.h" @@ -59,5 +58,3 @@ private: TextField *mTextField; gcn::Button *mOkButton; }; - -#endif diff --git a/src/gui/textpopup.h b/src/gui/textpopup.h index b1c6b2b3..db7d48e7 100644 --- a/src/gui/textpopup.h +++ b/src/gui/textpopup.h @@ -21,8 +21,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#ifndef TEXTPOPUP_H -#define TEXTPOPUP_H +#pragma once #include "gui/widgets/popup.h" @@ -51,5 +50,3 @@ class TextPopup : public Popup gcn::Label *mText1; gcn::Label *mText2; }; - -#endif // TEXTPOPUP_H diff --git a/src/gui/tradewindow.h b/src/gui/tradewindow.h index e9d8d4f1..d54ec35e 100644 --- a/src/gui/tradewindow.h +++ b/src/gui/tradewindow.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef TRADE_H -#define TRADE_H +#pragma once #include "gui/widgets/window.h" @@ -134,5 +133,3 @@ class TradeWindow : public Window, gcn::ActionListener, gcn::SelectionListener }; extern TradeWindow *tradeWindow; - -#endif diff --git a/src/gui/truetypefont.cpp b/src/gui/truetypefont.cpp index 30037f84..444641b5 100644 --- a/src/gui/truetypefont.cpp +++ b/src/gui/truetypefont.cpp @@ -56,16 +56,16 @@ bool operator==(SDL_Color lhs, SDL_Color rhs) class TextChunk { public: - TextChunk(const std::string &text, SDL_Color color) + TextChunk(const std::string &text) : text(text) - , color(color) {} void generate(TTF_Font *font) { + // Always render in white, we'll use color modulation when rendering SDL_Surface *surface = TTF_RenderUTF8_Blended(font, getSafeUtf8String(text), - color); + SDL_Color { 255, 255, 255, 255 }); if (!surface) return; @@ -77,7 +77,6 @@ public: std::unique_ptr<Image> img; const std::string text; - const SDL_Color color; }; std::list<TrueTypeFont*> TrueTypeFont::mFonts; @@ -124,24 +123,13 @@ void TrueTypeFont::drawString(gcn::Graphics *graphics, return; auto *g = static_cast<Graphics *>(graphics); - const gcn::Color col = g->getColor(); - - /* The alpha value is ignored at image generation to avoid caching the - * same text with different alpha values. - */ - const SDL_Color color = { - static_cast<Uint8>(col.r), - static_cast<Uint8>(col.g), - static_cast<Uint8>(col.b), - 255 - }; bool found = false; for (auto i = mCache.begin(); i != mCache.end(); ++i) { auto &chunk = *i; - if (chunk.text == text && chunk.color == color) + if (chunk.text == text) { // Raise priority: move it to front mCache.splice(mCache.begin(), mCache, i); @@ -154,18 +142,17 @@ void TrueTypeFont::drawString(gcn::Graphics *graphics, { if (mCache.size() >= CACHE_SIZE) mCache.pop_back(); - mCache.emplace_front(text, color); + mCache.emplace_front(text); mCache.front().generate(mFont); } if (auto img = mCache.front().img.get()) { - img->setAlpha(col.a / 255.0f); g->drawRescaledImageF(img, 0, 0, x, y, img->getWidth(), img->getHeight(), img->getWidth() / mScale, - img->getHeight() / mScale); + img->getHeight() / mScale, true); } } diff --git a/src/gui/truetypefont.h b/src/gui/truetypefont.h index 9aa308b1..a479537d 100644 --- a/src/gui/truetypefont.h +++ b/src/gui/truetypefont.h @@ -20,8 +20,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef TRUETYPEFONT_H -#define TRUETYPEFONT_H +#pragma once #include <guichan/font.hpp> @@ -82,5 +81,3 @@ class TrueTypeFont : public gcn::Font static std::list<TrueTypeFont*> mFonts; static float mScale; }; - -#endif diff --git a/src/gui/unregisterdialog.h b/src/gui/unregisterdialog.h index c82d7a78..0c94e0af 100644 --- a/src/gui/unregisterdialog.h +++ b/src/gui/unregisterdialog.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef UNREGISTERDIALOG_H -#define UNREGISTERDIALOG_H +#pragma once #include "gui/widgets/window.h" @@ -57,5 +56,3 @@ class UnRegisterDialog : public Window, public gcn::ActionListener LoginData *mLoginData; }; - -#endif diff --git a/src/gui/updaterwindow.cpp b/src/gui/updaterwindow.cpp index 772df725..5cfb45cd 100644 --- a/src/gui/updaterwindow.cpp +++ b/src/gui/updaterwindow.cpp @@ -46,8 +46,8 @@ #include <iostream> #include <fstream> -const std::string xmlUpdateFile = "resources.xml"; -const std::string txtUpdateFile = "resources2.txt"; +constexpr char xmlUpdateFile[] = "resources.xml"; +constexpr char txtUpdateFile[] = "resources2.txt"; /** * Load the given file into a vector of updateFiles. @@ -123,7 +123,6 @@ UpdaterWindow::UpdaterWindow(const std::string &updateHost, Window(_("Updating...")), mUpdateHost(updateHost), mUpdatesDir(updatesDir), - mCurrentFile("news.txt"), mLoadUpdates(applyUpdates), mLinkHandler(std::make_unique<ItemLinkHandler>(this)) { @@ -141,7 +140,6 @@ UpdaterWindow::UpdaterWindow(const std::string &updateHost, mPlayButton = new Button(_("Play"), "play", this); mBrowserBox->setLinkHandler(mLinkHandler.get()); - mBrowserBox->setFrameSize(4); mProgressBar->setSmoothProgress(false); mScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); mPlayButton->setEnabled(false); @@ -161,40 +159,22 @@ UpdaterWindow::UpdaterWindow(const std::string &updateHost, setVisible(true); mCancelButton->requestFocus(); - // Try to download the updates list - download(); + startDownload("news.txt", true); } UpdaterWindow::~UpdaterWindow() { if (mLoadUpdates) loadUpdates(); - - if (mDownload) - { - mDownload->cancel(); - - delete mDownload; - mDownload = nullptr; - } - free(mMemoryBuffer); -} - -void UpdaterWindow::setProgress(float progress) -{ - // Do delayed progress bar update, since Guichan isn't thread-safe - MutexLocker lock(&mDownloadMutex); - mDownloadProgress = progress; } void UpdaterWindow::setLabel(const std::string &str) { - // Do delayed label text update, since Guichan isn't thread-safe - MutexLocker lock(&mDownloadMutex); - mNewLabelCaption = str; + mLabel->setCaption(str); + mLabel->adjustSize(); } -void UpdaterWindow::enable() +void UpdaterWindow::enablePlay() { mCancelButton->setEnabled(false); mPlayButton->setEnabled(true); @@ -204,20 +184,9 @@ void UpdaterWindow::enable() void UpdaterWindow::action(const gcn::ActionEvent &event) { if (event.getId() == "cancel") - { - // Register the user cancel - mUserCancel = true; - // Skip the updating process - if (mDownloadStatus != UPDATE_COMPLETE) - { - mDownload->cancel(); - mDownloadStatus = UPDATE_ERROR; - } - } + cancel(); else if (event.getId() == "play") - { - Client::setState(STATE_LOAD_DATA); - } + play(); } void UpdaterWindow::keyPressed(gcn::KeyEvent &keyEvent) @@ -226,140 +195,63 @@ void UpdaterWindow::keyPressed(gcn::KeyEvent &keyEvent) if (key.getValue() == Key::ESCAPE) { - action(gcn::ActionEvent(nullptr, mCancelButton->getActionEventId())); - Client::setState(STATE_WORLD_SELECT); + if (!cancel()) + { + mLoadUpdates = false; + Client::setState(STATE_WORLD_SELECT); + } } else if (key.getValue() == Key::ENTER) { - if (mDownloadStatus == UPDATE_COMPLETE || - mDownloadStatus == UPDATE_ERROR) - { - action(gcn::ActionEvent(nullptr, mPlayButton->getActionEventId())); - } - else - { - action(gcn::ActionEvent(nullptr, mCancelButton->getActionEventId())); - } + play(); } } -void UpdaterWindow::loadNews() +bool UpdaterWindow::cancel() { - if (!mMemoryBuffer) + // Skip the updating process + if (mDialogState != DialogState::Done) { - logger->log("Couldn't load news"); - return; - } - - // Reallocate and include terminating 0 character - mMemoryBuffer = (char*)realloc(mMemoryBuffer, mDownloadedBytes + 1); - mMemoryBuffer[mDownloadedBytes] = '\0'; - - mBrowserBox->clearRows(); - - // Tokenize and add each line separately - char *line = strtok(mMemoryBuffer, "\n"); - while (line) - { - mBrowserBox->addRow(line); - line = strtok(nullptr, "\n"); + mDownload->cancel(); + return true; } - - // Free the memory buffer now that we don't need it anymore - free(mMemoryBuffer); - mMemoryBuffer = nullptr; - - mScrollArea->setVerticalScrollAmount(0); + return false; } -int UpdaterWindow::updateProgress(void *ptr, DownloadStatus status, - size_t dt, size_t dn) +void UpdaterWindow::play() { - auto *uw = reinterpret_cast<UpdaterWindow *>(ptr); - - if (status == DOWNLOAD_STATUS_COMPLETE) - { - uw->mDownloadComplete = true; - } - else if (status == DOWNLOAD_STATUS_ERROR || - status == DOWNLOAD_STATUS_CANCELLED) - { - uw->mDownloadStatus = UPDATE_ERROR; - } - - float progress = (float) dn / dt; - - if (progress != progress) - progress = 0.0f; // check for NaN - if (progress < 0.0f) - progress = 0.0f; // no idea how this could ever happen, but why not check for it anyway. - if (progress > 1.0f) - progress = 1.0f; - - uw->setLabel( - uw->mCurrentFile + " (" + toString((int) (progress * 100)) + "%)"); - uw->setProgress(progress); - - if (Client::getState() != STATE_UPDATE || uw->mDownloadStatus == UPDATE_ERROR) - { - // If the action was canceled return an error code to stop the mThread - return -1; - } - - return 0; + if (mPlayButton->isEnabled()) + Client::setState(STATE_LOAD_DATA); } -size_t UpdaterWindow::memoryWrite(void *ptr, size_t size, size_t nmemb, void *stream) +void UpdaterWindow::loadNews() { - auto *uw = reinterpret_cast<UpdaterWindow *>(stream); - size_t totalMem = size * nmemb; - uw->mMemoryBuffer = (char*) realloc(uw->mMemoryBuffer, - uw->mDownloadedBytes + totalMem); - if (uw->mMemoryBuffer) - { - memcpy(&(uw->mMemoryBuffer[uw->mDownloadedBytes]), ptr, totalMem); - uw->mDownloadedBytes += totalMem; - } + mBrowserBox->clearRows(); + mBrowserBox->addRows(mDownload->getBuffer()); - return totalMem; + mScrollArea->setVerticalScrollAmount(0); } -void UpdaterWindow::download() +void UpdaterWindow::startDownload(const std::string &fileName, + bool storeInMemory, + std::optional<unsigned long> adler32) { - mDownload = new Net::Download(this, mUpdateHost + "/" + mCurrentFile, - updateProgress); + mDownload = std::make_unique<Net::Download>(mUpdateHost + "/" + fileName); + mCurrentFile = fileName; - if (mStoreInMemory) - { - mDownload->setWriteFunction(UpdaterWindow::memoryWrite); - } + if (storeInMemory) + mDownload->setUseBuffer(); else - { - if (mDownloadStatus == UPDATE_RESOURCES) - { - mDownload->setFile(mUpdatesDir + "/" + mCurrentFile, - mCurrentChecksum); - } - else - { - mDownload->setFile(mUpdatesDir + "/" + mCurrentFile); - } - } + mDownload->setFile(mUpdatesDir + "/" + fileName, adler32); - if (mDownloadStatus != UPDATE_RESOURCES) + if (mDialogState != DialogState::DownloadResources) mDownload->noCache(); - setLabel(mCurrentFile + " (0%)"); - mDownloadComplete = false; - - // TODO: check return mDownload->start(); } void UpdaterWindow::loadUpdates() { - ResourceManager *resman = ResourceManager::getInstance(); - if (mUpdateFiles.empty()) { // updates not downloaded @@ -367,142 +259,144 @@ void UpdaterWindow::loadUpdates() if (mUpdateFiles.empty()) { logger->log("Warning this server does not have a" - " %s file falling back to %s", xmlUpdateFile.c_str(), - txtUpdateFile.c_str()); + " %s file falling back to %s", xmlUpdateFile, + txtUpdateFile); mUpdateFiles = loadTxtFile(mUpdatesDir + "/" + txtUpdateFile); } } for (const UpdateFile &file : mUpdateFiles) - { - resman->addToSearchPath(mUpdatesDir + "/" + file.name, false); - } + ResourceManager::addToSearchPath(mUpdatesDir + "/" + file.name, false); } void UpdaterWindow::logic() { - const std::string xmlUpdateFile = "resources.xml"; - const std::string txtUpdateFile = "resources2.txt"; + Window::logic(); - // Update Scroll logic - mScrollArea->logic(); + if (mDialogState == DialogState::Done) + return; - // Synchronize label caption when necessary - { - MutexLocker lock(&mDownloadMutex); + const auto state = mDownload->getState(); + float progress = 0.0f; - if (mLabel->getCaption() != mNewLabelCaption) - { - mLabel->setCaption(mNewLabelCaption); - mLabel->adjustSize(); - } + switch (state.status) { + case DownloadStatus::InProgress: { + setLabel(mCurrentFile + " (" + toString((int) (state.progress * 100)) + "%)"); + progress = state.progress; + break; + } + + case DownloadStatus::Canceled: + mDialogState = DialogState::Done; + + enablePlay(); + setLabel(_("Download canceled")); + break; + + case DownloadStatus::Error: { + mDialogState = DialogState::Done; - mProgressBar->setProgress(mDownloadProgress); + std::string error = "##1"; + error += mDownload->getError(); + error += "\n\n##1"; + error += _("The update process is incomplete. " + "It is strongly recommended that you try again later."); + mBrowserBox->addRows(error); + + int maxScroll = mScrollArea->getVerticalMaxScroll(); + mScrollArea->setVerticalScrollAmount(maxScroll); + + enablePlay(); + setLabel(_("Error while downloading")); + break; } - switch (mDownloadStatus) + case DownloadStatus::Complete: + downloadCompleted(); + break; + } + + mProgressBar->setProgress(progress); +} + +void UpdaterWindow::downloadCompleted() +{ + switch (mDialogState) { - case UPDATE_ERROR: - // TODO: Only send complete sentences to gettext - mBrowserBox->addRow(std::string()); - mBrowserBox->addRow(_("##1 The update process is incomplete.")); - // TRANSLATORS: Continues "you try again later.". - mBrowserBox->addRow(_("##1 It is strongly recommended that")); - // TRANSLATORS: Begins "It is strongly recommended that". - mBrowserBox->addRow(_("##1 you try again later.")); - - mBrowserBox->addRow(mDownload->getError()); - mScrollArea->setVerticalScrollAmount( - mScrollArea->getVerticalMaxScroll()); - mDownloadStatus = UPDATE_COMPLETE; - break; - case UPDATE_NEWS: - if (mDownloadComplete) - { - // Parse current memory buffer as news and dispose of the data - loadNews(); + case DialogState::DownloadNews: + loadNews(); - mCurrentFile = xmlUpdateFile; - mStoreInMemory = false; - mDownloadStatus = UPDATE_LIST; - download(); // download() changes mDownloadComplete to false - } - break; - case UPDATE_LIST: - if (mDownloadComplete) + mDialogState = DialogState::DownloadList; + startDownload(xmlUpdateFile, false); + break; + + case DialogState::DownloadList: + if (mCurrentFile == xmlUpdateFile) + { + mUpdateFiles = loadXMLFile(mUpdatesDir + "/" + xmlUpdateFile); + if (mUpdateFiles.empty()) { - if (mCurrentFile == xmlUpdateFile) - { - mUpdateFiles = loadXMLFile(mUpdatesDir + "/" + xmlUpdateFile); - if (mUpdateFiles.empty()) - { - logger->log("Warning this server does not have a %s" - " file falling back to %s", - xmlUpdateFile.c_str(), txtUpdateFile.c_str()); - - // If the resources.xml file fails, fall back onto a older version - mCurrentFile = txtUpdateFile; - mStoreInMemory = false; - mDownloadStatus = UPDATE_LIST; - download(); - break; - } - } - else if (mCurrentFile == txtUpdateFile) - { - mUpdateFiles = loadTxtFile(mUpdatesDir + "/" + txtUpdateFile); - } - mStoreInMemory = false; - mDownloadStatus = UPDATE_RESOURCES; + logger->log("Warning this server does not have a %s" + " file falling back to %s", + xmlUpdateFile, txtUpdateFile); + + // If the resources.xml file fails, fall back onto a older version + mDialogState = DialogState::DownloadList; + startDownload(txtUpdateFile, false); + break; } - break; - case UPDATE_RESOURCES: - if (mDownloadComplete) + } + else if (mCurrentFile == txtUpdateFile) + { + mUpdateFiles = loadTxtFile(mUpdatesDir + "/" + txtUpdateFile); + } + + mDialogState = DialogState::DownloadResources; + break; + + case DialogState::DownloadResources: + if (mUpdateIndex < mUpdateFiles.size()) + { + const UpdateFile &thisFile = mUpdateFiles[mUpdateIndex]; + if (!thisFile.required) { - if (mUpdateIndex < mUpdateFiles.size()) + if (!(thisFile.type == "music" && config.downloadMusic)) { - const UpdateFile &thisFile = mUpdateFiles[mUpdateIndex]; - if (!thisFile.required) - { - if (!(thisFile.type == "music" && config.downloadMusic)) - { - mUpdateIndex++; - break; - } - } - mCurrentFile = thisFile.name; - std::stringstream ss(thisFile.hash); - ss >> std::hex >> mCurrentChecksum; - - std::string filename = mUpdatesDir + "/" + mCurrentFile; - FILE *file = fopen(filename.c_str(), "r+b"); - - if (!file || Net::Download::fadler32(file) != mCurrentChecksum) - { - if (file) - fclose(file); - download(); - } - else - { - fclose(file); - logger->log("%s already here", mCurrentFile.c_str()); - } mUpdateIndex++; - } - else - { - // Download of updates completed - mDownloadStatus = UPDATE_COMPLETE; + break; } } - break; - case UPDATE_COMPLETE: - enable(); + + unsigned long checksum; + std::stringstream ss(thisFile.hash); + ss >> std::hex >> checksum; + + std::string filename = mUpdatesDir + "/" + thisFile.name; + FILE *file = fopen(filename.c_str(), "r+b"); + + if (!file || Net::Download::fadler32(file) != checksum) + { + if (file) + fclose(file); + startDownload(thisFile.name, false, checksum); + } + else + { + fclose(file); + logger->log("%s already here", thisFile.name.c_str()); + } + mUpdateIndex++; + } + else + { + // Download of updates completed + mDialogState = DialogState::Done; + enablePlay(); setLabel(_("Completed")); - mDownloadStatus = UPDATE_IDLE; - break; - case UPDATE_IDLE: - break; + } + break; + + case DialogState::Done: + break; } } diff --git a/src/gui/updaterwindow.h b/src/gui/updaterwindow.h index 6ea1d754..dd1400ba 100644 --- a/src/gui/updaterwindow.h +++ b/src/gui/updaterwindow.h @@ -19,15 +19,12 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef UPDATERWINDOW_H -#define UPDATERWINDOW_H +#pragma once #include "gui/widgets/window.h" #include "net/download.h" -#include "utils/mutex.h" - #include <guichan/actionlistener.hpp> #include <guichan/keylistener.hpp> @@ -65,7 +62,7 @@ class UpdaterWindow : public Window, public gcn::ActionListener, * @param updateHost Host where to get the updated files. * @param updatesDir Directory where to store updates (should be absolute * and already created). - * @param applyUpdates If true, the update window will pass the updates to teh + * @param applyUpdates If true, the update window will pass the updates to the * resource manager */ UpdaterWindow(const std::string &updateHost, @@ -74,27 +71,6 @@ class UpdaterWindow : public Window, public gcn::ActionListener, ~UpdaterWindow() override; - /** - * Set's progress bar status - */ - void setProgress(float progress); - - /** - * Set's label above progress - */ - void setLabel(const std::string &); - - /** - * Enables play button - */ - void enable(); - - /** - * Loads and display news. Assumes the news file contents have been loaded - * into the memory buffer. - */ - void loadNews(); - void action(const gcn::ActionEvent &event) override; void keyPressed(gcn::KeyEvent &keyEvent) override; @@ -102,38 +78,38 @@ class UpdaterWindow : public Window, public gcn::ActionListener, void logic() override; private: - void download(); + bool cancel(); + void play(); - /** - * Loads the updates this window has gotten into the resource manager - */ - void loadUpdates(); + void setLabel(const std::string &); + void enablePlay(); + void startDownload(const std::string &fileName, + bool storeInMemory, + std::optional<unsigned long> adler32 = {}); + void downloadCompleted(); /** - * A download callback for progress updates. + * Loads and display news. Assumes the news file contents have been loaded + * into the memory buffer. */ - static int updateProgress(void *ptr, DownloadStatus status, - size_t dt, size_t dn); + void loadNews(); /** - * A libcurl callback for writing to memory. + * Loads the updates this window has gotten into the resource manager */ - static size_t memoryWrite(void *ptr, size_t size, size_t nmemb, - void *stream); + void loadUpdates(); - enum UpdateDownloadStatus + enum class DialogState { - UPDATE_ERROR, - UPDATE_IDLE, - UPDATE_LIST, - UPDATE_COMPLETE, - UPDATE_NEWS, - UPDATE_RESOURCES + DownloadNews, + DownloadList, + DownloadResources, + Done, }; /** Status of the current download. */ - UpdateDownloadStatus mDownloadStatus = UPDATE_NEWS; + DialogState mDialogState = DialogState::DownloadNews; /** Host where we get the updated files. */ std::string mUpdateHost; @@ -144,35 +120,8 @@ private: /** The file currently downloading. */ std::string mCurrentFile; - /** The new label caption to be set in the logic method. */ - std::string mNewLabelCaption; - - /** The new progress value to be set in the logic method. */ - float mDownloadProgress = 0.0f; - - /** The mutex used to guard access to mNewLabelCaption and mDownloadProgress. */ - Mutex mDownloadMutex; - - /** The Adler32 checksum of the file currently downloading. */ - unsigned long mCurrentChecksum = 0; - - /** A flag to indicate whether to use a memory buffer or a regular file. */ - bool mStoreInMemory = true; - - /** Flag that show if current download is complete. */ - bool mDownloadComplete = true; - - /** Flag that show if the user has canceled the update. */ - bool mUserCancel = false; - - /** Byte count currently downloaded in mMemoryBuffer. */ - int mDownloadedBytes = 0; - - /** Buffer for files downloaded to memory. */ - char *mMemoryBuffer = nullptr; - /** Download handle. */ - Net::Download *mDownload = nullptr; + std::unique_ptr<Net::Download> mDownload; /** List of files to download. */ std::vector<UpdateFile> mUpdateFiles; @@ -191,5 +140,3 @@ private: ScrollArea *mScrollArea; /**< Used to scroll news box. */ std::unique_ptr<LinkHandler> mLinkHandler; }; - -#endif diff --git a/src/gui/viewport.cpp b/src/gui/viewport.cpp index 558ce0e6..9e529063 100644 --- a/src/gui/viewport.cpp +++ b/src/gui/viewport.cpp @@ -32,7 +32,6 @@ #include "textmanager.h" #include "gui/gui.h" -#include "gui/ministatuswindow.h" #include "gui/popupmenu.h" #include "gui/beingpopup.h" @@ -237,9 +236,6 @@ void Viewport::draw(gcn::Graphics *gcnGraphics) } } - if (miniStatusWindow) - miniStatusWindow->drawIcons(graphics); - // Draw contained widgets WindowContainer::draw(gcnGraphics); } @@ -309,10 +305,10 @@ void Viewport::_drawDebugPath(Graphics *graphics) unsigned char walkMask; switch (Net::getNetworkType()) { - case ServerType::TMWATHENA: + case ServerType::TmwAthena: walkMask = Map::BLOCKMASK_WALL | Map::BLOCKMASK_CHARACTER; break; - case ServerType::MANASERV: + case ServerType::ManaServ: default: walkMask = Map::BLOCKMASK_WALL; break; @@ -579,18 +575,18 @@ void Viewport::updateCursorType() gui->setCursorType(mHoverBeing->getHoverCursor()); break; default: - gui->setCursorType(Cursor::POINTER); + gui->setCursorType(Cursor::Pointer); break; } // Item mouseover } else if (mHoverItem) { - gui->setCursorType(Cursor::PICKUP); + gui->setCursorType(Cursor::PickUp); } else { - gui->setCursorType(Cursor::POINTER); + gui->setCursorType(Cursor::Pointer); } } diff --git a/src/gui/viewport.h b/src/gui/viewport.h index b69a73b6..bf73f8ca 100644 --- a/src/gui/viewport.h +++ b/src/gui/viewport.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef VIEWPORT_H -#define VIEWPORT_H +#pragma once #include "eventlistener.h" #include "position.h" @@ -221,5 +220,3 @@ class Viewport : public WindowContainer, public gcn::MouseListener, }; extern Viewport *viewport; /**< The viewport. */ - -#endif diff --git a/src/gui/widgets/avatarlistbox.cpp b/src/gui/widgets/avatarlistbox.cpp index ec3327b2..f7d6e801 100644 --- a/src/gui/widgets/avatarlistbox.cpp +++ b/src/gui/widgets/avatarlistbox.cpp @@ -33,8 +33,8 @@ #include <guichan/font.hpp> int AvatarListBox::instances = 0; -Image *AvatarListBox::onlineIcon = nullptr; -Image *AvatarListBox::offlineIcon = nullptr; +ResourceRef<Image> AvatarListBox::onlineIcon; +ResourceRef<Image> AvatarListBox::offlineIcon; AvatarListBox::AvatarListBox(AvatarListModel *model): ListBox(model) @@ -56,8 +56,8 @@ AvatarListBox::~AvatarListBox() if (instances == 0) { - onlineIcon->decRef(); - offlineIcon->decRef(); + onlineIcon = nullptr; + offlineIcon = nullptr; } } @@ -66,14 +66,12 @@ void AvatarListBox::draw(gcn::Graphics *gcnGraphics) if (!mListModel) return; - auto* model = static_cast<AvatarListModel*>(mListModel); + auto *model = static_cast<AvatarListModel *>(mListModel); + auto *graphics = static_cast<Graphics *>(gcnGraphics); - updateAlpha(); + const int alpha = gui->getTheme()->getGuiAlpha(); - auto *graphics = static_cast<Graphics*>(gcnGraphics); - - graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT, - (int) (mAlpha * 255.0f))); + graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT, alpha)); graphics->setFont(getFont()); const int fontHeight = getFont()->getHeight(); diff --git a/src/gui/widgets/avatarlistbox.h b/src/gui/widgets/avatarlistbox.h index 7ee36d1e..9b0588ac 100644 --- a/src/gui/widgets/avatarlistbox.h +++ b/src/gui/widgets/avatarlistbox.h @@ -18,12 +18,12 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef GUI_GUILDLISTBOX_H -#define GUI_GUILDLISTBOX_H +#pragma once #include "avatar.h" #include "gui/widgets/listbox.h" +#include "resources/resource.h" #include <string> @@ -54,8 +54,6 @@ public: private: static int instances; - static Image *onlineIcon; - static Image *offlineIcon; + static ResourceRef<Image> onlineIcon; + static ResourceRef<Image> offlineIcon; }; - -#endif diff --git a/src/gui/widgets/browserbox.cpp b/src/gui/widgets/browserbox.cpp index 9eee9448..91366720 100644 --- a/src/gui/widgets/browserbox.cpp +++ b/src/gui/widgets/browserbox.cpp @@ -22,6 +22,9 @@ #include "gui/widgets/browserbox.h" +#include "keyboardconfig.h" +#include "textrenderer.h" + #include "gui/gui.h" #include "gui/truetypefont.h" #include "gui/widgets/linkhandler.h" @@ -38,6 +41,38 @@ #include <algorithm> +/** + * Check for key replacements in format "###key;" + */ +static void replaceKeys(std::string &text) +{ + auto keyStart = text.find("###"); + + while (keyStart != std::string::npos) + { + const auto keyEnd = text.find(";", keyStart + 3); + if (keyEnd == std::string::npos) + break; + + std::string_view key(text.data() + keyStart + 3, keyEnd - keyStart - 3); + + // Remove "key" prefix + if (key.size() > 3 && key.substr(0, 3) == "key") + key.remove_prefix(3); + + const auto keyName = keyboard.getKeyName(key); + if (!keyName.empty()) + { + text.replace(keyStart, keyEnd - keyStart + 1, keyName); + keyStart = text.find("###", keyStart + keyName.size()); + } + else + { + keyStart = text.find("###", keyEnd + 1); + } + } +} + struct LayoutContext { LayoutContext(gcn::Font *font); @@ -75,28 +110,38 @@ BrowserBox::BrowserBox(Mode mode): BrowserBox::~BrowserBox() = default; -void BrowserBox::addRow(const std::string &row) +void BrowserBox::addRows(std::string_view rows) +{ + std::string_view::size_type start = 0; + std::string_view::size_type end = 0; + while (end != std::string::npos) + { + end = rows.find('\n', start); + addRow(rows.substr(start, end - start)); + start = end + 1; + } +} + +void BrowserBox::addRow(std::string_view row) { TextRow &newRow = mTextRows.emplace_back(); // Use links and user defined colors if (mUseLinksAndUserColors) { - std::string tmp = row; - // Check for links in format "@@link|Caption@@" - auto idx1 = tmp.find("@@"); - while (idx1 != std::string::npos) + auto linkStart = row.find("@@"); + while (linkStart != std::string::npos) { - const auto idx2 = tmp.find("|", idx1); - const auto idx3 = tmp.find("@@", idx2); + const auto linkSep = row.find("|", linkStart); + const auto linkEnd = row.find("@@", linkSep); - if (idx2 == std::string::npos || idx3 == std::string::npos) + if (linkSep == std::string::npos || linkEnd == std::string::npos) break; BrowserLink &link = newRow.links.emplace_back(); - link.link = tmp.substr(idx1 + 2, idx2 - (idx1 + 2)); - link.caption = tmp.substr(idx2 + 1, idx3 - (idx2 + 1)); + link.link = row.substr(linkStart + 2, linkSep - (linkStart + 2)); + link.caption = row.substr(linkSep + 1, linkEnd - (linkSep + 1)); if (link.caption.empty()) { @@ -107,18 +152,18 @@ void BrowserBox::addRow(const std::string &row) link.caption = link.link; } - newRow.text += tmp.substr(0, idx1); + newRow.text += row.substr(0, linkStart); newRow.text += "##<" + link.caption; - tmp.erase(0, idx3 + 2); - if (!tmp.empty()) + row = row.substr(linkEnd + 2); + if (!row.empty()) { newRow.text += "##>"; } - idx1 = tmp.find("@@"); + linkStart = row.find("@@"); } - newRow.text += tmp; + newRow.text += row; } // Don't use links and user defined colors else @@ -126,6 +171,9 @@ void BrowserBox::addRow(const std::string &row) newRow.text = row; } + if (mEnableKeys) + replaceKeys(newRow.text); + // Layout the newly added row LayoutContext context(getFont()); context.y = getHeight(); @@ -175,14 +223,14 @@ void BrowserBox::mousePressed(gcn::MouseEvent &event) if (mHoveredLink) { mLinkHandler->handleLink(mHoveredLink->link); - gui->setCursorType(Cursor::POINTER); + gui->setCursorType(Cursor::Pointer); } } void BrowserBox::mouseMoved(gcn::MouseEvent &event) { updateHoveredLink(event.getX(), event.getY()); - gui->setCursorType(mHoveredLink ? Cursor::HAND : Cursor::POINTER); + gui->setCursorType(mHoveredLink ? Cursor::Hand : Cursor::Pointer); event.consume(); // Suppress mouse cursor change by parent } @@ -233,34 +281,15 @@ void BrowserBox::draw(gcn::Graphics *graphics) if (part.y > yEnd) return; - auto font = part.font; - - // Handle text shadows - if (mShadows) - { - graphics->setColor(Theme::getThemeColor(Theme::SHADOW, - part.color.a / 2)); - - if (mOutline) - font->drawString(graphics, part.text, part.x + 2, part.y + 2); - else - font->drawString(graphics, part.text, part.x + 1, part.y + 1); - } - - if (mOutline) - { - // Text outline - graphics->setColor(Theme::getThemeColor(Theme::OUTLINE, - part.color.a / 4)); - font->drawString(graphics, part.text, part.x + 1, part.y); - font->drawString(graphics, part.text, part.x - 1, part.y); - font->drawString(graphics, part.text, part.x, part.y + 1); - font->drawString(graphics, part.text, part.x, part.y - 1); - } - - // the main text - graphics->setColor(part.color); - font->drawString(graphics, part.text, part.x, part.y); + TextRenderer::renderText(graphics, + part.text, + part.x, + part.y, + Graphics::LEFT, + part.color, + part.font, + mOutline, + mShadows); } } } @@ -276,7 +305,7 @@ void BrowserBox::relayoutText() layoutTextRow(row, context); mLastLayoutWidth = getWidth(); - mLayoutTimer.set(100); + mLayoutTimer.set(33); setHeight(context.y); } @@ -506,7 +535,7 @@ void BrowserBox::updateHoveredLink(int x, int y) void BrowserBox::maybeRelayoutText() { // Reduce relayouting frequency when there is a lot of text - if (mTextRows.size() > 100) + if (mTextRows.size() > 1000) if (!mLayoutTimer.passed()) return; diff --git a/src/gui/widgets/browserbox.h b/src/gui/widgets/browserbox.h index 7278bb59..7066585d 100644 --- a/src/gui/widgets/browserbox.h +++ b/src/gui/widgets/browserbox.h @@ -20,8 +20,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef BROWSERBOX_H -#define BROWSERBOX_H +#pragma once #include "utils/time.h" @@ -118,9 +117,19 @@ class BrowserBox : public gcn::Widget, void disableLinksAndUserColors() { mUseLinksAndUserColors = false; } /** + * Enable or disable the replacement of keys. + */ + void setEnableKeys(bool enable) { mEnableKeys = enable; } + + /** + * Adds one or more text rows to the browser, separated by '\n'. + */ + void addRows(std::string_view rows); + + /** * Adds a text row to the browser. */ - void addRow(const std::string &row); + void addRow(std::string_view row); /** * Remove all rows. @@ -192,10 +201,9 @@ class BrowserBox : public gcn::Widget, bool mShadows = false; bool mOutline = false; bool mUseLinksAndUserColors = true; + bool mEnableKeys = false; std::optional<BrowserLink> mHoveredLink; unsigned int mMaxRows = 0; int mLastLayoutWidth = 0; Timer mLayoutTimer; }; - -#endif diff --git a/src/gui/widgets/button.cpp b/src/gui/widgets/button.cpp index 274f329b..31c3a677 100644 --- a/src/gui/widgets/button.cpp +++ b/src/gui/widgets/button.cpp @@ -21,25 +21,22 @@ #include "gui/widgets/button.h" -#include "configuration.h" #include "graphics.h" +#include "gui/gui.h" #include "gui/textpopup.h" #include "resources/image.h" #include "resources/theme.h" - -#include "utils/dtor.h" +#include "textrenderer.h" #include <guichan/exception.hpp> #include <guichan/font.hpp> int Button::mInstances = 0; -float Button::mAlpha = 1.0; -ImageRect *Button::mButton; TextPopup *Button::mTextPopup = nullptr; -enum{ +enum { BUTTON_STANDARD, // 0 BUTTON_HIGHLIGHTED, // 1 BUTTON_PRESSED, // 2 @@ -47,20 +44,6 @@ enum{ BUTTON_COUNT // 4 - Must be last. }; -struct ButtonData -{ - char const *file; - int gridX; - int gridY; -}; - -static ButtonData const data[BUTTON_COUNT] = { - { "button.png", 0, 0 }, - { "buttonhi.png", 9, 4 }, - { "buttonpress.png", 16, 19 }, - { "button_disabled.png", 25, 23 } -}; - Button::Button() { init(); @@ -80,18 +63,19 @@ Button::Button(const std::string &caption, const std::string &actionEventId, adjustSize(); } -bool Button::setButtonIcon(const std::string& iconFile) +Button::~Button() = default; + +bool Button::setButtonIcon(const std::string &iconFile) { // We clean up possible older references. - if (mButtonIcon) - removeButtonIcon(); + removeButtonIcon(); // If nothing relevant was set, we can quit now. if (iconFile.empty()) return false; // Load the icon frames. - Image *btnIcons = Theme::getImageFromTheme(iconFile); + auto btnIcons = Theme::getImageFromTheme(iconFile); if (!btnIcons) return false; @@ -101,141 +85,76 @@ bool Button::setButtonIcon(const std::string& iconFile) if (frameWidth > 0 && frameHeight > 0) { - mButtonIcon = new Image*[BUTTON_COUNT]; + mButtonIcon.resize(BUTTON_COUNT); + for (int mode = 0; mode < BUTTON_COUNT; ++mode) { - mButtonIcon[mode] = btnIcons->getSubImage(mode * frameWidth, 0, - frameWidth, frameHeight); + mButtonIcon[mode].reset( + btnIcons->getSubImage(mode * frameWidth, 0, frameWidth, frameHeight)); } adjustSize(); } - btnIcons->decRef(); - return (mButtonIcon); + return !mButtonIcon.empty(); } -void Button::removeButtonIcon(bool adjustButtonSize) +void Button::removeButtonIcon() { - if (!mButtonIcon) + if (mButtonIcon.empty()) return; - // Delete potential button icons - for (int mode = 0; mode < BUTTON_COUNT; ++mode) - { - delete mButtonIcon[mode]; - mButtonIcon[mode] = nullptr; - } - delete[] mButtonIcon; - mButtonIcon = nullptr; - - if (adjustButtonSize) - adjustSize(); + mButtonIcon.clear(); + adjustSize(); } void Button::init() { - setFrameSize(0); + auto &skin = gui->getTheme()->getSkin(SkinType::Button); + setFrameSize(skin.frameSize); + setSpacing(skin.padding); if (mInstances == 0) { - // Load the skin - mButton = new ImageRect[BUTTON_COUNT]; - - for (int mode = 0; mode < BUTTON_COUNT; ++mode) - { - Image *modeImage = Theme::getImageFromTheme(data[mode].file); - int a = 0; - for (int y = 0; y < 3; y++) - { - for (int x = 0; x < 3; x++) - { - mButton[mode].grid[a] = modeImage->getSubImage( - data[x].gridX, data[y].gridY, - data[x + 1].gridX - data[x].gridX + 1, - data[y + 1].gridY - data[y].gridY + 1); - a++; - } - } - modeImage->decRef(); - } - updateAlpha(); - - // Load the popup + // Create the tooltip popup. It is shared by all buttons and will get + // deleted by the WindowContainer. if (!mTextPopup) - mTextPopup = new TextPopup(); + mTextPopup = new TextPopup; } mInstances++; } -Button::~Button() +void Button::draw(gcn::Graphics *graphics) { - mInstances--; - - if (mInstances == 0) - { - for (int mode = 0; mode < BUTTON_COUNT; ++mode) - { - std::for_each(mButton[mode].grid, mButton[mode].grid + 9, - dtor<Image*>()); - } - delete[] mButton; + WidgetState widgetState(this); + if (mHasMouse) + widgetState.flags |= STATE_HOVERED; + if (isPressed()) + widgetState.flags |= STATE_SELECTED; - // Remove the popup - delete mTextPopup; - mTextPopup = nullptr; - } - // Don' try to readjust the size when it's about to be deleted. - removeButtonIcon(false); -} + auto &skin = gui->getTheme()->getSkin(SkinType::Button); + skin.draw(static_cast<Graphics *>(graphics), widgetState); -void Button::updateAlpha() -{ - float alpha = std::max(config.guiAlpha, - Theme::instance()->getMinimumOpacity()); + auto skinState = skin.getState(widgetState.flags); + auto font = (skinState && skinState->textFormat.bold) ? boldFont : getFont(); - if (mAlpha != alpha) - { - mAlpha = alpha; - for (int mode = 0; mode < BUTTON_COUNT; ++mode) - { - mButton[mode].setAlpha(mAlpha); - } - } -} - -void Button::draw(gcn::Graphics *graphics) -{ int mode; - if (!isEnabled()) + if (widgetState.flags & STATE_DISABLED) mode = BUTTON_DISABLED; - else if (isPressed()) + else if (widgetState.flags & STATE_SELECTED) mode = BUTTON_PRESSED; - else if (mHasMouse || isFocused()) + else if (widgetState.flags & (STATE_HOVERED | STATE_FOCUSED)) mode = BUTTON_HIGHLIGHTED; else mode = BUTTON_STANDARD; - updateAlpha(); - - static_cast<Graphics*>(graphics)-> - drawImageRect(0, 0, getWidth(), getHeight(), mButton[mode]); - - if (mode == BUTTON_DISABLED) - graphics->setColor(Theme::getThemeColor(Theme::BUTTON_DISABLED)); - else - graphics->setColor(Theme::getThemeColor(Theme::BUTTON)); - + Image *icon = mButtonIcon.empty() ? nullptr : mButtonIcon[mode].get(); int textX = 0; - int textY = getHeight() / 2 - getFont()->getHeight() / 2; + int textY = getHeight() / 2 - font->getHeight() / 2; int btnIconX = 0; - int btnIconY = getHeight() / 2 - - ((mButtonIcon && mButtonIcon[mode]) ? - mButtonIcon[mode]->getHeight() / 2 : 0); - - int btnIconWidth = (mButtonIcon && mButtonIcon[mode]) ? - mButtonIcon[mode]->getWidth() : 0; + int btnIconY = getHeight() / 2 - (icon ? icon->getHeight() / 2 : 0); + int btnIconWidth = icon ? icon->getWidth() : 0; switch (getAlignment()) { @@ -243,7 +162,7 @@ void Button::draw(gcn::Graphics *graphics) if (btnIconWidth) { btnIconX = 4; - textX = btnIconX + mButtonIcon[mode]->getWidth() + 2; + textX = btnIconX + icon->getWidth() + 2; } else { @@ -253,9 +172,8 @@ void Button::draw(gcn::Graphics *graphics) case gcn::Graphics::CENTER: if (btnIconWidth) { - btnIconX = getWidth() / 2 - (getFont()->getWidth(mCaption) - + mButtonIcon[mode]->getWidth() + 2) / 2; - textX = getWidth() / 2 + mButtonIcon[mode]->getWidth() / 2 + 2; + btnIconX = (getWidth() - font->getWidth(mCaption) - icon->getWidth() - 2) / 2; + textX = (getWidth() + icon->getWidth()) / 2 + 2; } else { @@ -264,15 +182,13 @@ void Button::draw(gcn::Graphics *graphics) break; case gcn::Graphics::RIGHT: if (btnIconWidth) - btnIconX = getWidth() - 4 - getFont()->getWidth(mCaption) - 2; + btnIconX = getWidth() - 4 - font->getWidth(mCaption) - 2; textX = getWidth() - 4; break; default: throw GCN_EXCEPTION("Button::draw(). Unknown alignment."); } - graphics->setFont(getFont()); - if (isPressed()) { textX++; textY++; @@ -280,23 +196,32 @@ void Button::draw(gcn::Graphics *graphics) } if (btnIconWidth) - static_cast<Graphics*>(graphics)->drawImage(mButtonIcon[mode], - btnIconX, btnIconY); - graphics->drawText(getCaption(), textX, textY, getAlignment()); + static_cast<Graphics *>(graphics)->drawImage(icon, btnIconX, btnIconY); + + if (auto skinState = skin.getState(widgetState.flags)) + { + auto &textFormat = skinState->textFormat; + TextRenderer::renderText(static_cast<Graphics *>(graphics), + getCaption(), + textX, + textY, + getAlignment(), + font, + textFormat); + } } void Button::adjustSize() { // Size of the image button. int iconWidth = 0, iconHeight = 0; - if (mButtonIcon) + if (!mButtonIcon.empty()) { for (int mode = 0; mode < BUTTON_COUNT; ++mode) { - iconWidth = std::max(iconWidth, mButtonIcon[mode] ? - mButtonIcon[mode]->getWidth() + 2 : 0); - iconHeight = std::max(iconHeight, mButtonIcon[mode] ? - mButtonIcon[mode]->getHeight() : 0); + const Image *icon = mButtonIcon[mode].get(); + iconWidth = std::max(iconWidth, icon->getWidth() + 2); + iconHeight = std::max(iconHeight, icon->getHeight()); } } @@ -311,16 +236,9 @@ void Button::setCaption(const std::string& caption) adjustSize(); } -void Button::logic() -{ - gcn::Button::logic(); - mTextPopup->logic(); -} - void Button::mouseMoved(gcn::MouseEvent &event) { gcn::Button::mouseMoved(event); - mTextPopup->mouseMoved(event); int x = event.getX(); int y = event.getY(); @@ -344,7 +262,6 @@ void Button::mouseMoved(gcn::MouseEvent &event) void Button::mouseExited(gcn::MouseEvent &event) { gcn::Button::mouseExited(event); - mTextPopup->mouseExited(event); mTextPopup->setVisible(false); } diff --git a/src/gui/widgets/button.h b/src/gui/widgets/button.h index a09b4445..97f7307b 100644 --- a/src/gui/widgets/button.h +++ b/src/gui/widgets/button.h @@ -19,12 +19,13 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef BUTTON_H -#define BUTTON_H +#pragma once #include <guichan/widgets/button.hpp> -class ImageRect; +#include <memory> +#include <vector> + class Image; class TextPopup; @@ -55,11 +56,6 @@ class Button : public gcn::Button */ void draw(gcn::Graphics *graphics) override; - /** - * Update the alpha value to the button components. - */ - void updateAlpha(); - void adjustSize(); void setCaption(const std::string &caption); @@ -82,27 +78,22 @@ class Button : public gcn::Button void setButtonPopupText(const std::string &text) { mPopupText = text; } - void logic() override; void mouseMoved(gcn::MouseEvent &event) override; void mouseExited(gcn::MouseEvent &event) override; private: void init(); - void removeButtonIcon(bool adjustButtonSize = true); + void removeButtonIcon(); - static ImageRect* mButton; /**< Button state graphics */ static int mInstances; /**< Number of button instances */ - static float mAlpha; - Image** mButtonIcon = nullptr; /**< Button Icons graphics */ + std::vector<std::unique_ptr<Image>> mButtonIcon; /**< Button Icons graphics */ /** * The buttons popup * @note: This is a global object. One for all the buttons. */ - static TextPopup* mTextPopup; + static TextPopup *mTextPopup; std::string mPopupText; /**< the current button text */ }; - -#endif diff --git a/src/gui/widgets/channeltab.h b/src/gui/widgets/channeltab.h index 2894dacd..d6dc1268 100644 --- a/src/gui/widgets/channeltab.h +++ b/src/gui/widgets/channeltab.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef CHANNELTAB_H -#define CHANNELTAB_H +#pragma once #include "chattab.h" @@ -51,5 +50,3 @@ class ChannelTab : public ChatTab private: Channel *mChannel; }; - -#endif // CHANNELTAB_H diff --git a/src/gui/widgets/chattab.h b/src/gui/widgets/chattab.h index 3e770fe1..dfc07638 100644 --- a/src/gui/widgets/chattab.h +++ b/src/gui/widgets/chattab.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef CHATTAB_H -#define CHATTAB_H +#pragma once #include "gui/chatwindow.h" @@ -135,5 +134,3 @@ class ChatTab : public Tab, public AutoCompleteLister, public EventListener }; extern ChatTab *localChatTab; - -#endif // CHATTAB_H diff --git a/src/gui/widgets/checkbox.cpp b/src/gui/widgets/checkbox.cpp index 274855fd..e6079f2f 100644 --- a/src/gui/widgets/checkbox.cpp +++ b/src/gui/widgets/checkbox.cpp @@ -21,121 +21,43 @@ #include "gui/widgets/checkbox.h" -#include "configuration.h" -#include "graphics.h" +#include "textrenderer.h" -#include "resources/image.h" +#include "gui/gui.h" #include "resources/theme.h" -int CheckBox::instances = 0; -float CheckBox::mAlpha = 1.0; -Image *CheckBox::checkBoxNormal; -Image *CheckBox::checkBoxChecked; -Image *CheckBox::checkBoxDisabled; -Image *CheckBox::checkBoxDisabledChecked; -Image *CheckBox::checkBoxNormalHi; -Image *CheckBox::checkBoxCheckedHi; +#include <guichan/font.hpp> -CheckBox::CheckBox(const std::string &caption, bool selected): - gcn::CheckBox(caption, selected) +CheckBox::CheckBox(const std::string &caption, bool selected) + : gcn::CheckBox(caption, selected) { - if (instances == 0) - { - Image *checkBox = Theme::getImageFromTheme("checkbox.png"); - checkBoxNormal = checkBox->getSubImage(0, 0, 9, 10); - checkBoxChecked = checkBox->getSubImage(9, 0, 9, 10); - checkBoxDisabled = checkBox->getSubImage(18, 0, 9, 10); - checkBoxDisabledChecked = checkBox->getSubImage(27, 0, 9, 10); - checkBoxNormalHi = checkBox->getSubImage(36, 0, 9, 10); - checkBoxCheckedHi = checkBox->getSubImage(45, 0, 9, 10); - checkBoxNormal->setAlpha(mAlpha); - checkBoxChecked->setAlpha(mAlpha); - checkBoxDisabled->setAlpha(mAlpha); - checkBoxDisabledChecked->setAlpha(mAlpha); - checkBoxNormalHi->setAlpha(mAlpha); - checkBoxCheckedHi->setAlpha(mAlpha); - checkBox->decRef(); - } - - instances++; -} - -CheckBox::~CheckBox() -{ - instances--; - - if (instances == 0) - { - delete checkBoxNormal; - delete checkBoxChecked; - delete checkBoxDisabled; - delete checkBoxDisabledChecked; - delete checkBoxNormalHi; - delete checkBoxCheckedHi; - } + auto &skin = gui->getTheme()->getSkin(SkinType::CheckBox); + setWidth(skin.getMinWidth() + 2 * skin.padding + skin.spacing + getFont()->getWidth(caption)); + setHeight(skin.getMinHeight() + 2 * skin.padding); } void CheckBox::draw(gcn::Graphics* graphics) { - drawBox(graphics); - - graphics->setFont(getFont()); - graphics->setColor(Theme::getThemeColor(Theme::TEXT)); - - const int h = getHeight() + getHeight() / 2; - - graphics->drawText(getCaption(), h - 2, 0); -} - -void CheckBox::updateAlpha() -{ - float alpha = std::max(config.guiAlpha, - Theme::instance()->getMinimumOpacity()); - - if (mAlpha != alpha) - { - mAlpha = alpha; - checkBoxNormal->setAlpha(mAlpha); - checkBoxChecked->setAlpha(mAlpha); - checkBoxDisabled->setAlpha(mAlpha); - checkBoxDisabledChecked->setAlpha(mAlpha); - checkBoxNormal->setAlpha(mAlpha); - checkBoxCheckedHi->setAlpha(mAlpha); - } -} + WidgetState widgetState(this); + if (mHasMouse) + widgetState.flags |= STATE_HOVERED; + if (isSelected()) + widgetState.flags |= STATE_SELECTED; -void CheckBox::drawBox(gcn::Graphics* graphics) -{ - Image *box; + auto &skin = gui->getTheme()->getSkin(SkinType::CheckBox); + skin.draw(static_cast<Graphics *>(graphics), widgetState); - if (isEnabled()) - { - if (isSelected()) - { - if (mHasMouse) - box = checkBoxCheckedHi; - else - box = checkBoxChecked; - } - else - { - if (mHasMouse) - box = checkBoxNormalHi; - else - box = checkBoxNormal; - } - } - else + if (auto skinState = skin.getState(widgetState.flags)) { - if (isSelected()) - box = checkBoxDisabledChecked; - else - box = checkBoxDisabled; + auto &textFormat = skinState->textFormat; + TextRenderer::renderText(static_cast<Graphics *>(graphics), + getCaption(), + skin.getMinWidth() + skin.padding + skin.spacing, + skin.padding, + Graphics::LEFT, + textFormat.bold ? boldFont : getFont(), + textFormat); } - - updateAlpha(); - - static_cast<Graphics*>(graphics)->drawImage(box, 2, 2); } void CheckBox::mouseEntered(gcn::MouseEvent& event) diff --git a/src/gui/widgets/checkbox.h b/src/gui/widgets/checkbox.h index f77b1761..ea1a20e7 100644 --- a/src/gui/widgets/checkbox.h +++ b/src/gui/widgets/checkbox.h @@ -19,13 +19,10 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef CHECKBOX_H -#define CHECKBOX_H +#pragma once #include <guichan/widgets/checkbox.hpp> -class Image; - /** * Check box widget. Same as the Guichan check box but with custom look. * @@ -36,43 +33,26 @@ class CheckBox : public gcn::CheckBox public: CheckBox(const std::string &caption, bool selected = false); - ~CheckBox() override; - /** * Draws the caption, then calls drawBox to draw the check box. */ - void draw(gcn::Graphics* graphics) override; - - /** - * Update the alpha value to the checkbox components. - */ - void updateAlpha(); + void draw(gcn::Graphics *graphics) override; /** - * Draws the check box, not the caption. + * Overridden because box is drawn in CheckBox::draw. */ - void drawBox(gcn::Graphics* graphics) override; + void drawBox(gcn::Graphics *graphics) override {} /** * Called when the mouse enteres the widget area. */ - void mouseEntered(gcn::MouseEvent& event) override; + void mouseEntered(gcn::MouseEvent &event) override; /** * Called when the mouse leaves the widget area. */ - void mouseExited(gcn::MouseEvent& event) override; + void mouseExited(gcn::MouseEvent &event) override; private: - static int instances; - static float mAlpha; bool mHasMouse = false; - static Image *checkBoxNormal; - static Image *checkBoxChecked; - static Image *checkBoxDisabled; - static Image *checkBoxDisabledChecked; - static Image *checkBoxNormalHi; - static Image *checkBoxCheckedHi; }; - -#endif diff --git a/src/gui/widgets/container.h b/src/gui/widgets/container.h index ef44c8cd..fbdaa1d4 100644 --- a/src/gui/widgets/container.h +++ b/src/gui/widgets/container.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef GUI_CONTAINER_H -#define GUI_CONTAINER_H +#pragma once #include <guichan/widgets/container.hpp> @@ -44,6 +43,9 @@ class Container : public gcn::Container Container(); ~Container() override; + // Overridden to disable drawing of the frame + void drawFrame(gcn::Graphics *graphics) override {} + protected: /** * Gets the layout handler for this container. @@ -63,5 +65,3 @@ class Container : public gcn::Container private: LayoutHelper *mLayoutHelper = nullptr; }; - -#endif diff --git a/src/gui/widgets/desktop.cpp b/src/gui/widgets/desktop.cpp index c8ded9f5..e424beec 100644 --- a/src/gui/widgets/desktop.cpp +++ b/src/gui/widgets/desktop.cpp @@ -105,7 +105,7 @@ void Desktop::setBestFittingWallpaper() return; ResourceManager *resman = ResourceManager::getInstance(); - auto wallpaper = resman->getImageRef(wallpaperName); + auto wallpaper = resman->getImage(wallpaperName); if (wallpaper) { diff --git a/src/gui/widgets/desktop.h b/src/gui/widgets/desktop.h index 5909ac72..a7aa4a1e 100644 --- a/src/gui/widgets/desktop.h +++ b/src/gui/widgets/desktop.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef DESKTOP_H -#define DESKTOP_H +#pragma once #include "guichanfwd.h" @@ -66,5 +65,3 @@ class Desktop : public Container, gcn::WidgetListener ResourceRef<Image> mWallpaper; gcn::Label *mVersionLabel; }; - -#endif // DESKTOP_H diff --git a/src/gui/widgets/dropdown.cpp b/src/gui/widgets/dropdown.cpp index 8811eb8d..45c8e53f 100644 --- a/src/gui/widgets/dropdown.cpp +++ b/src/gui/widgets/dropdown.cpp @@ -21,127 +21,43 @@ #include "gui/widgets/dropdown.h" -#include "configuration.h" #include "graphics.h" +#include "gui/gui.h" #include "gui/sdlinput.h" #include "gui/widgets/listbox.h" #include "gui/widgets/scrollarea.h" -#include "resources/image.h" #include "resources/theme.h" -#include "utils/dtor.h" - -#include <algorithm> - -int DropDown::instances = 0; -Image *DropDown::buttons[2][2]; -ImageRect DropDown::skin; -float DropDown::mAlpha = 1.0; +#include <guichan/font.hpp> DropDown::DropDown(gcn::ListModel *listModel): gcn::DropDown::DropDown(listModel, new ScrollArea, new ListBox(listModel)) { - setFrameSize(2); - - // Initialize graphics - if (instances == 0) - { - // Load the background skin - - // Get the button skin - buttons[1][0] = Theme::getImageFromTheme("vscroll_up_default.png"); - buttons[0][0] = Theme::getImageFromTheme("vscroll_down_default.png"); - buttons[1][1] = Theme::getImageFromTheme("vscroll_up_pressed.png"); - buttons[0][1] = Theme::getImageFromTheme("vscroll_down_pressed.png"); - - buttons[0][0]->setAlpha(mAlpha); - buttons[0][1]->setAlpha(mAlpha); - buttons[1][0]->setAlpha(mAlpha); - buttons[1][1]->setAlpha(mAlpha); - - // get the border skin - Image *boxBorder = Theme::getImageFromTheme("deepbox.png"); - int gridx[4] = {0, 3, 28, 31}; - int gridy[4] = {0, 3, 28, 31}; - int a = 0; - - for (int y = 0; y < 3; y++) - { - for (int x = 0; x < 3; x++) - { - skin.grid[a] = boxBorder->getSubImage(gridx[x], gridy[y], - gridx[x + 1] - - gridx[x] + 1, - gridy[y + 1] - - gridy[y] + 1); - a++; - } - } + auto &skin = gui->getTheme()->getSkin(SkinType::DropDownFrame); + setFrameSize(skin.frameSize); + mPadding = skin.padding; - skin.setAlpha(mAlpha); - - boxBorder->decRef(); - } - - instances++; + setHeight(getFont()->getHeight() + 2 * mPadding); } DropDown::~DropDown() { - instances--; - // Free images memory - if (instances == 0) - { - buttons[0][0]->decRef(); - buttons[0][1]->decRef(); - buttons[1][0]->decRef(); - buttons[1][1]->decRef(); - - std::for_each(skin.grid, skin.grid + 9, dtor<Image*>()); - } - delete mScrollArea; } -void DropDown::updateAlpha() -{ - float alpha = std::max(config.guiAlpha, - Theme::instance()->getMinimumOpacity()); - - if (mAlpha != alpha) - { - mAlpha = alpha; - - buttons[0][0]->setAlpha(mAlpha); - buttons[0][1]->setAlpha(mAlpha); - buttons[1][0]->setAlpha(mAlpha); - buttons[1][1]->setAlpha(mAlpha); - - skin.setAlpha(mAlpha); - } -} - void DropDown::draw(gcn::Graphics* graphics) { - int h; + const int h = mDroppedDown ? mFoldedUpHeight : getHeight(); - if (mDroppedDown) - h = mFoldedUpHeight; - else - h = getHeight(); - - updateAlpha(); - - const int alpha = (int) (mAlpha * 255.0f); + const int alpha = gui->getTheme()->getGuiAlpha(); gcn::Color faceColor = getBaseColor(); faceColor.a = alpha; - const gcn::Color *highlightColor = &Theme::getThemeColor(Theme::HIGHLIGHT, - alpha); + const gcn::Color *highlightColor = &Theme::getThemeColor(Theme::HIGHLIGHT, alpha); gcn::Color shadowColor = faceColor - 0x303030; shadowColor.a = alpha; @@ -149,13 +65,16 @@ void DropDown::draw(gcn::Graphics* graphics) { graphics->setFont(getFont()); graphics->setColor(Theme::getThemeColor(Theme::TEXT)); - graphics->drawText(mListBox->getListModel()->getElementAt(mListBox->getSelected()), 1, 0); + graphics->drawText(mListBox->getListModel()->getElementAt(mListBox->getSelected()), + mPadding, + mPadding); } if (isFocused()) { graphics->setColor(*highlightColor); - graphics->drawRectangle(gcn::Rectangle(0, 0, getWidth() - h, h)); + graphics->drawRectangle( + gcn::Rectangle(mPadding, mPadding, getWidth() - h - mPadding * 2, h - 2 * mPadding)); } drawButton(graphics); @@ -176,18 +95,65 @@ void DropDown::draw(gcn::Graphics* graphics) void DropDown::drawFrame(gcn::Graphics *graphics) { const int bs = getFrameSize(); - const int w = getWidth() + bs * 2; - const int h = getHeight() + bs * 2; - static_cast<Graphics*>(graphics)->drawImageRect(0, 0, w, h, skin); + WidgetState state(this); + state.width += bs * 2; + state.height += bs * 2; + + gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), SkinType::DropDownFrame, state); +} + +// Overridden so that we can take mPadding into account +void DropDown::adjustHeight() +{ + const int listBoxHeight = mListBox->getHeight(); + int height = getFont()->getHeight() + 2 * mPadding; + + // The addition/subtraction of 2 compensates for the seperation lines + // seperating the selected element view and the scroll area. + + if (mDroppedDown && getParent()) + { + int availableHeight = getParent()->getChildrenArea().height - getY(); + + if (listBoxHeight > availableHeight - height - 2) + { + mScrollArea->setHeight(availableHeight - height - 2); + height = availableHeight; + } + else + { + height += listBoxHeight + 2; + mScrollArea->setHeight(listBoxHeight); + } + } + + setHeight(height); + + mScrollArea->setWidth(getWidth()); + // Resize the ListBox to exactly fit the ScrollArea. + mListBox->setWidth(mScrollArea->getChildrenArea().width); + mScrollArea->setPosition(0, 0); } void DropDown::drawButton(gcn::Graphics *graphics) { - int height = mDroppedDown ? mFoldedUpHeight : getHeight(); + WidgetState state(this); + if (mDroppedDown) + { + state.height = mFoldedUpHeight; + state.flags |= STATE_SELECTED; + } + if (mPushed) + state.flags |= STATE_HOVERED; + + const auto theme = gui->getTheme(); + const int buttonWidth = theme->getMinWidth(SkinType::DropDownButton); + + // FIXME: Needs support for setting alignment in the theme. + state.x = state.width - buttonWidth; - static_cast<Graphics*>(graphics)-> - drawImage(buttons[mDroppedDown][mPushed], getWidth() - height + 2, 1); + theme->drawSkin(static_cast<Graphics *>(graphics), SkinType::DropDownButton, state); } // -- KeyListener notifications @@ -253,3 +219,32 @@ void DropDown::mouseWheelMovedDown(gcn::MouseEvent& mouseEvent) mouseEvent.consume(); distributeActionEvent(); } + +// Overridden to call our version of adjustHeight +void DropDown::dropDown() +{ + if (!mDroppedDown) + { + mDroppedDown = true; + mFoldedUpHeight = getHeight(); + adjustHeight(); + + if (getParent()) + { + getParent()->moveToTop(this); + } + } + + mListBox->requestFocus(); +} + +// Overridden to call our version of adjustHeight +void DropDown::foldUp() +{ + if (mDroppedDown) + { + mDroppedDown = false; + adjustHeight(); + mInternalFocusHandler.focusNone(); + } +} diff --git a/src/gui/widgets/dropdown.h b/src/gui/widgets/dropdown.h index f92c7dd5..a5e2e2f7 100644 --- a/src/gui/widgets/dropdown.h +++ b/src/gui/widgets/dropdown.h @@ -19,14 +19,10 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef DROPDOWN_H -#define DROPDOWN_H +#pragma once #include <guichan/widgets/dropdown.hpp> -class Image; -class ImageRect; - /** * A drop down box from which you can select different values. * @@ -47,15 +43,12 @@ class DropDown : public gcn::DropDown ~DropDown() override; - /** - * Update the alpha value to the graphic components. - */ - static void updateAlpha(); - void draw(gcn::Graphics *graphics) override; void drawFrame(gcn::Graphics *graphics) override; + void adjustHeight(); + // Inherited from FocusListener void focusLost(const gcn::Event& event) override; @@ -80,12 +73,8 @@ class DropDown : public gcn::DropDown */ void drawButton(gcn::Graphics *graphics) override; - // Add own Images. - static int instances; - static Image *buttons[2][2]; - static ImageRect skin; - static float mAlpha; -}; - -#endif // end DROPDOWN_H + void dropDown() override; + void foldUp() override; + int mPadding = 1; +}; diff --git a/src/gui/widgets/emoteshortcutcontainer.cpp b/src/gui/widgets/emoteshortcutcontainer.cpp index 8ecbc9bf..06d80ec2 100644 --- a/src/gui/widgets/emoteshortcutcontainer.cpp +++ b/src/gui/widgets/emoteshortcutcontainer.cpp @@ -21,13 +21,12 @@ #include "gui/widgets/emoteshortcutcontainer.h" -#include "configuration.h" #include "emoteshortcut.h" #include "graphics.h" -#include "imagesprite.h" -#include "item.h" #include "keyboardconfig.h" +#include "gui/gui.h" + #include "resources/emotedb.h" #include "resources/image.h" #include "resources/theme.h" @@ -36,65 +35,52 @@ static const int MAX_ITEMS = 12; EmoteShortcutContainer::EmoteShortcutContainer() { - addMouseListener(this); - addWidgetListener(this); - - mBackgroundImg = Theme::getImageFromTheme("item_shortcut_bgr.png"); - - mBackgroundImg->setAlpha(config.guiAlpha); - mMaxItems = std::min(EmoteDB::getEmoteCount(), MAX_ITEMS); - - mBoxHeight = mBackgroundImg->getHeight(); - mBoxWidth = mBackgroundImg->getWidth(); -} - -EmoteShortcutContainer::~EmoteShortcutContainer() -{ - mBackgroundImg->decRef(); } void EmoteShortcutContainer::draw(gcn::Graphics *graphics) { - if (config.guiAlpha != mAlpha) - { - mAlpha = config.guiAlpha; - mBackgroundImg->setAlpha(mAlpha); - } - auto *g = static_cast<Graphics*>(graphics); + auto theme = gui->getTheme(); graphics->setFont(getFont()); for (int i = 0; i < mMaxItems; i++) { - const int emoteX = (i % mGridWidth) * mBoxWidth; - const int emoteY = (i / mGridWidth) * mBoxHeight; - - g->drawImage(mBackgroundImg, emoteX, emoteY); + WidgetState state; + state.x = (i % mGridWidth) * mBoxWidth; + state.y = (i / mGridWidth) * mBoxHeight; + theme->drawSkin(g, SkinType::ShortcutBox, state); // Draw emote keyboard shortcut. const char *key = SDL_GetKeyName( keyboard.getKeyValue(KeyboardConfig::KEY_EMOTE_1 + i)); graphics->setColor(Theme::getThemeColor(Theme::TEXT)); - g->drawText(key, emoteX + 2, emoteY + 2, gcn::Graphics::LEFT); + g->drawText(key, state.x + 2, state.y + 2, gcn::Graphics::LEFT); int emoteId = emoteShortcut->getEmote(i); if (emoteId != -1) { - EmoteDB::get(emoteId).sprite->draw(g, emoteX + 2, emoteY + 10); + if (auto image = EmoteDB::get(emoteId).image) + { + image->setAlpha(1.0f); + g->drawImage(image, state.x + 2, state.y + 10); + } } } if (mEmoteMoved != -1) { // Draw the emote image being dragged by the cursor. - const ImageSprite *sprite = EmoteDB::get(mEmoteMoved).sprite.get(); + if (auto image = EmoteDB::get(mEmoteMoved).image) + { + image->setAlpha(1.0f); - const int tPosX = mCursorPosX - (sprite->getWidth() / 2); - const int tPosY = mCursorPosY - (sprite->getHeight() / 2); + const int tPosX = mCursorPosX - (image->getWidth() / 2); + const int tPosY = mCursorPosY - (image->getHeight() / 2); - sprite->draw(g, tPosX, tPosY); + g->drawImage(image, tPosX, tPosY); + } } } @@ -115,6 +101,7 @@ void EmoteShortcutContainer::mouseDragged(gcn::MouseEvent &event) emoteShortcut->removeEmote(index); } } + if (mEmoteMoved != -1) { mCursorPosX = event.getX(); @@ -126,7 +113,6 @@ void EmoteShortcutContainer::mouseDragged(gcn::MouseEvent &event) void EmoteShortcutContainer::mousePressed(gcn::MouseEvent &event) { const int index = getIndexFromGrid(event.getX(), event.getY()); - if (index == -1) return; @@ -170,4 +156,3 @@ void EmoteShortcutContainer::mouseReleased(gcn::MouseEvent &event) mEmoteClicked = false; } } - diff --git a/src/gui/widgets/emoteshortcutcontainer.h b/src/gui/widgets/emoteshortcutcontainer.h index ecd41736..57d5efd2 100644 --- a/src/gui/widgets/emoteshortcutcontainer.h +++ b/src/gui/widgets/emoteshortcutcontainer.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef EMOTESHORTCUTCONTAINER_H -#define EMOTESHORTCUTCONTAINER_H +#pragma once #include "gui/widgets/shortcutcontainer.h" @@ -34,8 +33,6 @@ class EmoteShortcutContainer : public ShortcutContainer public: EmoteShortcutContainer(); - ~EmoteShortcutContainer() override; - /** * Draws the items. */ @@ -60,5 +57,3 @@ class EmoteShortcutContainer : public ShortcutContainer bool mEmoteClicked = false; int mEmoteMoved = -1; }; - -#endif diff --git a/src/gui/widgets/flowcontainer.h b/src/gui/widgets/flowcontainer.h index 21daae16..46be0919 100644 --- a/src/gui/widgets/flowcontainer.h +++ b/src/gui/widgets/flowcontainer.h @@ -18,8 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef FLOWCONTAINER_H -#define FLOWCONTAINER_H +#pragma once #include "container.h" @@ -56,5 +55,3 @@ class FlowContainer : public Container, int mGridWidth = 1; int mGridHeight = 1; }; - -#endif diff --git a/src/gui/widgets/icon.cpp b/src/gui/widgets/icon.cpp index 67fd8384..61506a6b 100644 --- a/src/gui/widgets/icon.cpp +++ b/src/gui/widgets/icon.cpp @@ -27,7 +27,7 @@ #include "resources/resourcemanager.h" Icon::Icon(const std::string &file) - : Icon(ResourceManager::getInstance()->getImageRef(file)) + : Icon(ResourceManager::getInstance()->getImage(file)) { } diff --git a/src/gui/widgets/icon.h b/src/gui/widgets/icon.h index 3ebc2c16..5e61520c 100644 --- a/src/gui/widgets/icon.h +++ b/src/gui/widgets/icon.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef ICON_H -#define ICON_H +#pragma once #include "resources/resource.h" @@ -68,5 +67,3 @@ class Icon : public gcn::Widget private: ResourceRef<Image> mImage; }; - -#endif // ICON_H diff --git a/src/gui/widgets/inttextfield.h b/src/gui/widgets/inttextfield.h index d5829404..bebad71d 100644 --- a/src/gui/widgets/inttextfield.h +++ b/src/gui/widgets/inttextfield.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef INTTEXTFIELD_H -#define INTTEXTFIELD_H +#pragma once #include "textfield.h" @@ -71,5 +70,3 @@ class IntTextField : public TextField int mDefault; /**< Default value */ int mValue; /**< Current value */ }; - -#endif diff --git a/src/gui/widgets/itemcontainer.cpp b/src/gui/widgets/itemcontainer.cpp index 940c69f4..d1d00677 100644 --- a/src/gui/widgets/itemcontainer.cpp +++ b/src/gui/widgets/itemcontainer.cpp @@ -64,7 +64,6 @@ ItemContainer::ItemContainer(Inventory *inventory): ItemContainer::~ItemContainer() { - mSelImg->decRef(); delete mItemPopup; } diff --git a/src/gui/widgets/itemcontainer.h b/src/gui/widgets/itemcontainer.h index 51807aba..0a8ac1e2 100644 --- a/src/gui/widgets/itemcontainer.h +++ b/src/gui/widgets/itemcontainer.h @@ -19,8 +19,9 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef ITEMCONTAINER_H -#define ITEMCONTAINER_H +#pragma once + +#include "resources/resource.h" #include <guichan/keylistener.hpp> #include <guichan/mouselistener.hpp> @@ -71,6 +72,9 @@ class ItemContainer : public gcn::Widget, */ void draw(gcn::Graphics *graphics) override; + // Overridden to disable drawing of the frame + void drawFrame(gcn::Graphics *graphics) override {} + // KeyListener void keyPressed(gcn::KeyEvent &event) override; void keyReleased(gcn::KeyEvent &event) override; @@ -178,7 +182,7 @@ class ItemContainer : public gcn::Widget, Inventory *mInventory; int mGridColumns = 1; int mGridRows = 1; - Image *mSelImg; + ResourceRef<Image> mSelImg; int mSelectedIndex = -1; int mHighlightedIndex = -1; int mLastUsedSlot = -1; @@ -196,5 +200,3 @@ class ItemContainer : public gcn::Widget, std::list<gcn::SelectionListener *> mSelectionListeners; }; - -#endif // ITEMCONTAINER_H diff --git a/src/gui/widgets/itemlinkhandler.h b/src/gui/widgets/itemlinkhandler.h index 28e9c11c..58202d33 100644 --- a/src/gui/widgets/itemlinkhandler.h +++ b/src/gui/widgets/itemlinkhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef ITEM_LINK_HANDLER_H -#define ITEM_LINK_HANDLER_H +#pragma once #include "gui/widgets/linkhandler.h" @@ -49,5 +48,3 @@ class ItemLinkHandler : public LinkHandler, gcn::ActionListener Window *mParent = nullptr; std::string mLink; }; - -#endif diff --git a/src/gui/widgets/itemshortcutcontainer.cpp b/src/gui/widgets/itemshortcutcontainer.cpp index 0b8f0c8c..4d0641b1 100644 --- a/src/gui/widgets/itemshortcutcontainer.cpp +++ b/src/gui/widgets/itemshortcutcontainer.cpp @@ -21,7 +21,6 @@ #include "gui/widgets/itemshortcutcontainer.h" -#include "configuration.h" #include "graphics.h" #include "inventory.h" #include "item.h" @@ -39,51 +38,32 @@ #include "utils/stringutils.h" ItemShortcutContainer::ItemShortcutContainer() + : mItemPopup(new ItemPopup) { - addMouseListener(this); - addWidgetListener(this); - - mItemPopup = new ItemPopup; - - mBackgroundImg = Theme::getImageFromTheme("item_shortcut_bgr.png"); mMaxItems = itemShortcut->getItemCount(); - - mBackgroundImg->setAlpha(config.guiAlpha); - - mBoxHeight = mBackgroundImg->getHeight(); - mBoxWidth = mBackgroundImg->getWidth(); } -ItemShortcutContainer::~ItemShortcutContainer() -{ - mBackgroundImg->decRef(); - delete mItemPopup; -} +ItemShortcutContainer::~ItemShortcutContainer() = default; void ItemShortcutContainer::draw(gcn::Graphics *graphics) { - if (config.guiAlpha != mAlpha) - { - mAlpha = config.guiAlpha; - mBackgroundImg->setAlpha(mAlpha); - } - auto *g = static_cast<Graphics*>(graphics); + auto theme = gui->getTheme(); graphics->setFont(getFont()); for (int i = 0; i < mMaxItems; i++) { - const int itemX = (i % mGridWidth) * mBoxWidth; - const int itemY = (i / mGridWidth) * mBoxHeight; - - g->drawImage(mBackgroundImg, itemX, itemY); + WidgetState state; + state.x = (i % mGridWidth) * mBoxWidth; + state.y = (i / mGridWidth) * mBoxHeight; + theme->drawSkin(g, SkinType::ShortcutBox, state); // Draw item keyboard shortcut. const char *key = SDL_GetKeyName( keyboard.getKeyValue(KeyboardConfig::KEY_SHORTCUT_1 + i)); graphics->setColor(Theme::getThemeColor(Theme::TEXT)); - g->drawText(key, itemX + 2, itemY + 2, gcn::Graphics::LEFT); + g->drawText(key, state.x + 2, state.y + 2, gcn::Graphics::LEFT); if (itemShortcut->getItem(i) < 0) continue; @@ -105,11 +85,11 @@ void ItemShortcutContainer::draw(gcn::Graphics *graphics) caption = "Eq."; image->setAlpha(1.0f); - g->drawImage(image, itemX, itemY); + g->drawImage(image, state.x, state.y); if (item->isEquipped()) g->setColor(Theme::getThemeColor(Theme::ITEM_EQUIPPED)); - g->drawText(caption, itemX + mBoxWidth / 2, - itemY + mBoxHeight - 14, gcn::Graphics::CENTER); + g->drawText(caption, state.x + mBoxWidth / 2, + state.y + mBoxHeight - 14, gcn::Graphics::CENTER); } } } @@ -137,23 +117,20 @@ void ItemShortcutContainer::mouseDragged(gcn::MouseEvent &event) if (!mItemMoved && mItemClicked) { const int index = getIndexFromGrid(event.getX(), event.getY()); - if (index == -1) return; const int itemId = itemShortcut->getItem(index); - if (itemId < 0) return; - Item *item = PlayerInfo::getInventory()->findItem(itemId); - - if (item) + if (Item *item = PlayerInfo::getInventory()->findItem(itemId)) { mItemMoved = item; itemShortcut->removeItem(index); } } + if (mItemMoved) { mCursorPosX = event.getX(); @@ -226,18 +203,14 @@ void ItemShortcutContainer::mouseReleased(gcn::MouseEvent &event) void ItemShortcutContainer::mouseMoved(gcn::MouseEvent &event) { const int index = getIndexFromGrid(event.getX(), event.getY()); - if (index == -1) return; const int itemId = itemShortcut->getItem(index); - if (itemId < 0) return; - Item *item = PlayerInfo::getInventory()->findItem(itemId); - - if (item) + if (Item *item = PlayerInfo::getInventory()->findItem(itemId)) { mItemPopup->setItem(item->getInfo()); mItemPopup->position(viewport->getMouseX(), viewport->getMouseY()); diff --git a/src/gui/widgets/itemshortcutcontainer.h b/src/gui/widgets/itemshortcutcontainer.h index 243920a0..63d9e0ef 100644 --- a/src/gui/widgets/itemshortcutcontainer.h +++ b/src/gui/widgets/itemshortcutcontainer.h @@ -19,13 +19,14 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef ITEMSHORTCUTCONTAINER_H -#define ITEMSHORTCUTCONTAINER_H +#pragma once #include "gui/widgets/shortcutcontainer.h" #include <guichan/mouselistener.hpp> +#include <memory> + class Image; class Item; class ItemPopup; @@ -69,7 +70,5 @@ class ItemShortcutContainer : public ShortcutContainer bool mItemClicked = false; Item *mItemMoved = nullptr; - ItemPopup *mItemPopup; + std::unique_ptr<ItemPopup> mItemPopup; }; - -#endif diff --git a/src/gui/widgets/label.cpp b/src/gui/widgets/label.cpp index af5220ef..53a82e14 100644 --- a/src/gui/widgets/label.cpp +++ b/src/gui/widgets/label.cpp @@ -21,8 +21,13 @@ #include "gui/widgets/label.h" +#include "textrenderer.h" + #include "resources/theme.h" +#include <guichan/exception.hpp> +#include <guichan/font.hpp> + Label::Label() { setForegroundColor(Theme::getThemeColor(Theme::TEXT)); @@ -36,5 +41,33 @@ Label::Label(const std::string &caption) : void Label::draw(gcn::Graphics *graphics) { - gcn::Label::draw(static_cast<gcn::Graphics*>(graphics)); + int textX; + int textY = getHeight() / 2 - getFont()->getHeight() / 2; + + switch (getAlignment()) + { + case Graphics::LEFT: + textX = 0; + break; + case Graphics::CENTER: + textX = getWidth() / 2; + break; + case Graphics::RIGHT: + textX = getWidth(); + break; + default: + throw GCN_EXCEPTION("Unknown alignment."); + } + + TextRenderer::renderText(static_cast<Graphics *>(graphics), + getCaption(), + textX, + textY, + getAlignment(), + getForegroundColor(), + getFont(), + mOutlineColor.has_value(), + mShadowColor.has_value(), + mOutlineColor, + mShadowColor); } diff --git a/src/gui/widgets/label.h b/src/gui/widgets/label.h index cb7a8b1c..85bcbe23 100644 --- a/src/gui/widgets/label.h +++ b/src/gui/widgets/label.h @@ -19,14 +19,14 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef LABEL_H -#define LABEL_H +#pragma once #include <guichan/widgets/label.hpp> +#include <optional> /** * Label widget. Same as the Guichan label but modified to use the palette - * system. + * system and support outlines and shadows. * * \ingroup GUI */ @@ -42,9 +42,31 @@ class Label : public gcn::Label Label(const std::string &caption); /** + * Sets the color of the outline. + */ + void setOutlineColor(std::optional<gcn::Color> color); + + /** + * Sets the color of the shadow. + */ + void setShadowColor(std::optional<gcn::Color> color); + + /** * Draws the label. */ void draw(gcn::Graphics *graphics) override; + + private: + std::optional<gcn::Color> mOutlineColor; + std::optional<gcn::Color> mShadowColor; }; -#endif +inline void Label::setOutlineColor(std::optional<gcn::Color> color) +{ + mOutlineColor = color; +} + +inline void Label::setShadowColor(std::optional<gcn::Color> color) +{ + mShadowColor = color; +} diff --git a/src/gui/widgets/layout.h b/src/gui/widgets/layout.h index 4e4b28c5..42f08758 100644 --- a/src/gui/widgets/layout.h +++ b/src/gui/widgets/layout.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef WIDGET_LAYOUT_H -#define WIDGET_LAYOUT_H +#pragma once #include <guichan/widgets/container.hpp> @@ -168,9 +167,12 @@ class LayoutCell }; LayoutCell() = default; - ~LayoutCell(); + // Copy not allowed, as the cell may own an array. + LayoutCell(LayoutCell const &) = delete; + LayoutCell &operator=(LayoutCell const &) = delete; + /** * Sets the padding around the cell content. */ @@ -232,10 +234,6 @@ class LayoutCell void computeSizes(); private: - // Copy not allowed, as the cell may own an array. - LayoutCell(LayoutCell const &); - LayoutCell &operator=(LayoutCell const &); - union { gcn::Widget *mWidget; @@ -310,5 +308,3 @@ class Layout : public LayoutCell private: bool mComputed; }; - -#endif // WIDGET_LAYOUT_H diff --git a/src/gui/widgets/layouthelper.h b/src/gui/widgets/layouthelper.h index 26360a9a..ad01c565 100644 --- a/src/gui/widgets/layouthelper.h +++ b/src/gui/widgets/layouthelper.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef LAYOUTHELPER_H -#define LAYOUTHELPER_H +#pragma once #include "gui/widgets/layout.h" @@ -74,5 +73,3 @@ class LayoutHelper : public gcn::WidgetListener Layout mLayout; /**< Layout handler */ gcn::Container *mContainer; /**< Managed container */ }; - -#endif // LAYOUTHELPER_H diff --git a/src/gui/widgets/linkhandler.h b/src/gui/widgets/linkhandler.h index 33263a25..48b182a1 100644 --- a/src/gui/widgets/linkhandler.h +++ b/src/gui/widgets/linkhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef LINK_HANDLER_H -#define LINK_HANDLER_H +#pragma once #include <string> @@ -35,5 +34,3 @@ class LinkHandler virtual void handleLink(const std::string &link) = 0; }; - -#endif diff --git a/src/gui/widgets/listbox.cpp b/src/gui/widgets/listbox.cpp index 55f0f422..f1fcfd53 100644 --- a/src/gui/widgets/listbox.cpp +++ b/src/gui/widgets/listbox.cpp @@ -21,8 +21,7 @@ #include "gui/widgets/listbox.h" -#include "configuration.h" - +#include "gui/gui.h" #include "gui/sdlinput.h" #include "resources/theme.h" @@ -32,31 +31,19 @@ #include <guichan/key.hpp> #include <guichan/listmodel.hpp> -float ListBox::mAlpha = 1.0; - ListBox::ListBox(gcn::ListModel *listModel): gcn::ListBox(listModel) { } -void ListBox::updateAlpha() -{ - float alpha = std::max(config.guiAlpha, - Theme::instance()->getMinimumOpacity()); - - if (mAlpha != alpha) - mAlpha = alpha; -} - void ListBox::draw(gcn::Graphics *graphics) { if (!mListModel) return; - updateAlpha(); + const int alpha = gui->getTheme()->getGuiAlpha(); - graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT, - (int) (mAlpha * 255.0f))); + graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT, alpha)); graphics->setFont(getFont()); const int height = getRowHeight(); diff --git a/src/gui/widgets/listbox.h b/src/gui/widgets/listbox.h index d16256b1..40bc2fbc 100644 --- a/src/gui/widgets/listbox.h +++ b/src/gui/widgets/listbox.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef LISTBOX_H -#define LISTBOX_H +#pragma once #include <guichan/widgets/listbox.hpp> @@ -43,10 +42,8 @@ class ListBox : public gcn::ListBox */ void draw(gcn::Graphics *graphics) override; - /** - * Update the alpha value to the graphic components. - */ - static void updateAlpha(); + // Overridden to disable drawing of the frame + void drawFrame(gcn::Graphics *graphics) override {} // Inherited from KeyListener @@ -61,9 +58,4 @@ class ListBox : public gcn::ListBox void mouseWheelMovedDown(gcn::MouseEvent& mouseEvent) override; void mouseDragged(gcn::MouseEvent &event) override; - - protected: - static float mAlpha; }; - -#endif diff --git a/src/gui/widgets/passwordfield.h b/src/gui/widgets/passwordfield.h index 4bed0e05..36964843 100644 --- a/src/gui/widgets/passwordfield.h +++ b/src/gui/widgets/passwordfield.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef PASSWORDFIELD_H -#define PASSWORDFIELD_H +#pragma once #include "textfield.h" @@ -42,5 +41,3 @@ class PasswordField : public TextField */ void draw(gcn::Graphics *graphics) override; }; - -#endif diff --git a/src/gui/widgets/playerbox.cpp b/src/gui/widgets/playerbox.cpp index 3bdd6bd1..f251035d 100644 --- a/src/gui/widgets/playerbox.cpp +++ b/src/gui/widgets/playerbox.cpp @@ -22,86 +22,22 @@ #include "gui/widgets/playerbox.h" #include "being.h" -#include "configuration.h" #include "graphics.h" -#include "resources/image.h" -#include "resources/theme.h" - -#include "utils/dtor.h" - -int PlayerBox::instances = 0; -float PlayerBox::mAlpha = 1.0; -ImageRect PlayerBox::background; - -PlayerBox::PlayerBox(const Being *being): - mBeing(being) +PlayerBox::PlayerBox(const Being *being) + : mBeing(being) { - setFrameSize(2); - - if (instances == 0) - { - // Load the background skin - Image *textbox = Theme::getImageFromTheme("deepbox.png"); - int bggridx[4] = {0, 3, 28, 31}; - int bggridy[4] = {0, 3, 28, 31}; - int a = 0; - - for (int y = 0; y < 3; y++) - { - for (int x = 0; x < 3; x++) - { - background.grid[a] = textbox->getSubImage( - bggridx[x], bggridy[y], - bggridx[x + 1] - bggridx[x] + 1, - bggridy[y + 1] - bggridy[y] + 1); - a++; - } - } - - background.setAlpha(config.guiAlpha); - - textbox->decRef(); - } - - instances++; -} - -PlayerBox::~PlayerBox() -{ - instances--; - - mBeing = nullptr; - - if (instances == 0) - { - std::for_each(background.grid, background.grid + 9, dtor<Image*>()); - } } void PlayerBox::draw(gcn::Graphics *graphics) { + ScrollArea::draw(graphics); + if (mBeing) { // Draw character - const int bs = getFrameSize(); - const int x = getWidth() / 2 + bs; - const int y = getHeight() - bs; + const int x = getWidth() / 2; + const int y = (getHeight() + mBeing->getHeight()) / 2 - 12; mBeing->drawSpriteAt(static_cast<Graphics*>(graphics), x, y); } - - if (config.guiAlpha != mAlpha) - { - mAlpha = config.guiAlpha; - background.setAlpha(config.guiAlpha); - } -} - -void PlayerBox::drawFrame(gcn::Graphics *graphics) -{ - const int bs = getFrameSize(); - const int w = getWidth() + bs * 2; - const int h = getHeight() + bs * 2; - - static_cast<Graphics*>(graphics)->drawImageRect(0, 0, w, h, background); } diff --git a/src/gui/widgets/playerbox.h b/src/gui/widgets/playerbox.h index 68dd670e..39392c63 100644 --- a/src/gui/widgets/playerbox.h +++ b/src/gui/widgets/playerbox.h @@ -19,20 +19,18 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef PLAYERBOX_H -#define PLAYERBOX_H +#pragma once -#include <guichan/widgets/scrollarea.hpp> +#include "scrollarea.h" class Being; -class ImageRect; /** * A box showing a player character. * * \ingroup GUI */ -class PlayerBox : public gcn::ScrollArea +class PlayerBox : public ScrollArea { public: /** @@ -41,8 +39,6 @@ class PlayerBox : public gcn::ScrollArea */ PlayerBox(const Being *being = nullptr); - ~PlayerBox() override; - /** * Sets a new player character to be displayed by this box. Setting the * player to <code>NULL</code> causes the box not to draw any @@ -52,21 +48,10 @@ class PlayerBox : public gcn::ScrollArea { mBeing = being; } /** - * Draws the scroll area. + * Draws the scroll area and the player. */ void draw(gcn::Graphics *graphics) override; - /** - * Draws the background and border of the scroll area. - */ - void drawFrame(gcn::Graphics *graphics) override; - private: const Being *mBeing; /**< The character used for display */ - - static float mAlpha; - static int instances; - static ImageRect background; }; - -#endif diff --git a/src/gui/widgets/popup.cpp b/src/gui/widgets/popup.cpp index 94d8cf85..b7c70fe5 100644 --- a/src/gui/widgets/popup.cpp +++ b/src/gui/widgets/popup.cpp @@ -25,28 +25,26 @@ #include "graphics.h" #include "log.h" +#include "gui/gui.h" #include "gui/viewport.h" - #include "gui/widgets/windowcontainer.h" -#include "resources/theme.h" - #include <guichan/exception.hpp> -Popup::Popup(const std::string &name, const std::string &skin): - mPopupName(name), - mMaxWidth(graphics->getWidth()), - mMaxHeight(graphics->getHeight()) +Popup::Popup(const std::string &name, SkinType skinType) + : mPopupName(name) + , mMaxWidth(graphics->getWidth()) + , mMaxHeight(graphics->getHeight()) + , mSkinType(skinType) { logger->log("Popup::Popup(\"%s\")", name.c_str()); if (!windowContainer) throw GCN_EXCEPTION("Popup::Popup(): no windowContainer set"); - setPadding(6); - - // Loads the skin - mSkin = Theme::instance()->load(skin); + auto &skin = gui->getTheme()->getSkin(skinType); + setFrameSize(skin.frameSize); + setPadding(skin.padding); // Add this window to the window container windowContainer->add(this); @@ -58,8 +56,6 @@ Popup::Popup(const std::string &name, const std::string &skin): Popup::~Popup() { logger->log("Popup::~Popup(\"%s\")", mPopupName.c_str()); - - mSkin->instances--; } void Popup::setWindowContainer(WindowContainer *wc) @@ -69,13 +65,20 @@ void Popup::setWindowContainer(WindowContainer *wc) void Popup::draw(gcn::Graphics *graphics) { - auto *g = static_cast<Graphics*>(graphics); - - g->drawImageRect(0, 0, getWidth(), getHeight(), mSkin->getBorder()); + if (getFrameSize() == 0) + drawFrame(graphics); drawChildren(graphics); } +void Popup::drawFrame(gcn::Graphics *graphics) +{ + WidgetState state(this); + state.width += getFrameSize() * 2; + state.height += getFrameSize() * 2; + gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), mSkinType, state); +} + gcn::Rectangle Popup::getChildrenArea() { return gcn::Rectangle(getPadding(), getPadding(), @@ -116,12 +119,12 @@ void Popup::setLocationRelativeTo(gcn::Widget *widget) void Popup::setMinWidth(int width) { - mMinWidth = std::max(width, mSkin->getMinWidth()); + mMinWidth = std::max(gui->getTheme()->getMinWidth(mSkinType), width); } void Popup::setMinHeight(int height) { - mMinHeight = std::max(height, mSkin->getMinHeight()); + mMinHeight = std::max(gui->getTheme()->getMinHeight(mSkinType), height); } void Popup::setMaxWidth(int width) diff --git a/src/gui/widgets/popup.h b/src/gui/widgets/popup.h index c77bf814..012b55de 100644 --- a/src/gui/widgets/popup.h +++ b/src/gui/widgets/popup.h @@ -20,12 +20,12 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef POPUP_H -#define POPUP_H +#pragma once #include "guichanfwd.h" #include "gui/widgets/container.h" +#include "resources/theme.h" #include <guichan/mouselistener.hpp> @@ -53,10 +53,10 @@ class Popup : public Container, public gcn::MouseListener * * @param name A human readable name for the popup. Only useful for * debugging purposes. - * @param skin The location where the Popup's skin XML can be found. + * @param skinType The skin type used when drawing the popup. */ - Popup(const std::string &name = std::string(), - const std::string &skin = "window.xml"); + explicit Popup(const std::string &name = std::string(), + SkinType skinType = SkinType::Popup); /** * Destructor. Deletes all the added widgets. @@ -74,6 +74,11 @@ class Popup : public Container, public gcn::MouseListener void draw(gcn::Graphics *graphics) override; /** + * Draws the popup frame. + */ + void drawFrame(gcn::Graphics *graphics) override; + + /** * Sets the size of this popup. */ void setContentSize(int width, int height); @@ -152,14 +157,12 @@ class Popup : public Container, public gcn::MouseListener void position(int x, int y); private: - std::string mPopupName; /**< Name of the popup */ - int mMinWidth = 100; /**< Minimum popup width */ - int mMinHeight = 40; /**< Minimum popup height */ - int mMaxWidth; /**< Maximum popup width */ - int mMaxHeight; /**< Maximum popup height */ - int mPadding; /**< Holds the padding of the popup. */ - - Skin *mSkin; /**< Skin in use by this popup */ + std::string mPopupName; /**< Name of the popup */ + int mMinWidth = 100; /**< Minimum popup width */ + int mMinHeight = 40; /**< Minimum popup height */ + int mMaxWidth; /**< Maximum popup width */ + int mMaxHeight; /**< Maximum popup height */ + int mPadding; /**< Holds the padding of the popup. */ + + SkinType mSkinType; /**< The skin type used when drawing the popup widget. */ }; - -#endif diff --git a/src/gui/widgets/progressbar.cpp b/src/gui/widgets/progressbar.cpp index 9d41d1af..5cf1b05a 100644 --- a/src/gui/widgets/progressbar.cpp +++ b/src/gui/widgets/progressbar.cpp @@ -21,23 +21,14 @@ #include "gui/widgets/progressbar.h" -#include "configuration.h" #include "graphics.h" -#include "textrenderer.h" #include "gui/gui.h" -#include "resources/image.h" #include "resources/theme.h" -#include "utils/dtor.h" - #include <guichan/font.hpp> -ImageRect ProgressBar::mBorder; -int ProgressBar::mInstances = 0; -float ProgressBar::mAlpha = 1.0; - ProgressBar::ProgressBar(float progress, int width, int height, int color): @@ -53,36 +44,6 @@ ProgressBar::ProgressBar(float progress, mProgress); setSize(width, height); - - if (mInstances == 0) - { - Image *dBorders = Theme::getImageFromTheme("vscroll_grey.png"); - mBorder.grid[0] = dBorders->getSubImage(0, 0, 4, 4); - mBorder.grid[1] = dBorders->getSubImage(4, 0, 3, 4); - mBorder.grid[2] = dBorders->getSubImage(7, 0, 4, 4); - mBorder.grid[3] = dBorders->getSubImage(0, 4, 4, 10); - mBorder.grid[4] = dBorders->getSubImage(4, 4, 3, 10); - mBorder.grid[5] = dBorders->getSubImage(7, 4, 4, 10); - mBorder.grid[6] = dBorders->getSubImage(0, 15, 4, 4); - mBorder.grid[7] = dBorders->getSubImage(4, 15, 3, 4); - mBorder.grid[8] = dBorders->getSubImage(7, 15, 4, 4); - - mBorder.setAlpha(mAlpha); - - dBorders->decRef(); - } - - mInstances++; -} - -ProgressBar::~ProgressBar() -{ - mInstances--; - - if (mInstances == 0) - { - std::for_each(mBorder.grid, mBorder.grid + 9, dtor<Image*>()); - } } void ProgressBar::logic() @@ -114,30 +75,19 @@ void ProgressBar::logic() } } -void ProgressBar::updateAlpha() -{ - float alpha = std::max(config.guiAlpha, - Theme::instance()->getMinimumOpacity()); - - if (mAlpha != alpha) - { - mAlpha = alpha; - mBorder.setAlpha(mAlpha); - } -} - void ProgressBar::draw(gcn::Graphics *graphics) { - updateAlpha(); - - mColor.a = (int) (mAlpha * 255); + mColor.a = gui->getTheme()->getGuiAlpha(); gcn::Rectangle rect = getDimension(); rect.x = 0; rect.y = 0; - render(static_cast<Graphics*>(graphics), rect, mColor, - mProgress, mText); + gui->getTheme()->drawProgressBar(static_cast<Graphics *>(graphics), + rect, + mColor, + mProgress, + mText); } void ProgressBar::setProgress(float progress) @@ -149,9 +99,7 @@ void ProgressBar::setProgress(float progress) mProgress = p; if (mProgressPalette >= 0) - { mColorToGo = Theme::getProgressColor(mProgressPalette, progress); - } } void ProgressBar::setProgressPalette(int progressPalette) @@ -160,9 +108,7 @@ void ProgressBar::setProgressPalette(int progressPalette) mProgressPalette = progressPalette; if (mProgressPalette != oldPalette && mProgressPalette >= 0) - { mColorToGo = Theme::getProgressColor(mProgressPalette, mProgressToGo); - } } void ProgressBar::setColor(const gcn::Color &color) @@ -172,37 +118,3 @@ void ProgressBar::setColor(const gcn::Color &color) if (!mSmoothColorChange) mColor = color; } - -void ProgressBar::render(Graphics *graphics, const gcn::Rectangle &area, - const gcn::Color &color, float progress, - const std::string &text) -{ - gcn::Font *oldFont = graphics->getFont(); - gcn::Color oldColor = graphics->getColor(); - - graphics->drawImageRect(area, mBorder); - - // The bar - if (progress > 0) - { - graphics->setColor(color); - graphics->fillRectangle(gcn::Rectangle(area.x + 4, area.y + 4, - (int) (progress * (area.width - 8)), - area.height - 8)); - } - - // The label - if (!text.empty()) - { - const int textX = area.x + area.width / 2; - const int textY = area.y + (area.height - boldFont->getHeight()) / 2; - - TextRenderer::renderText(graphics, text, textX, textY, - gcn::Graphics::CENTER, - Theme::getThemeColor(Theme::PROGRESS_BAR), - gui->getFont(), true, false); - } - - graphics->setFont(oldFont); - graphics->setColor(oldColor); -} diff --git a/src/gui/widgets/progressbar.h b/src/gui/widgets/progressbar.h index 2f9e665f..52904f5a 100644 --- a/src/gui/widgets/progressbar.h +++ b/src/gui/widgets/progressbar.h @@ -19,16 +19,12 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef PROGRESSBAR_H -#define PROGRESSBAR_H +#pragma once #include <guichan/widget.hpp> #include <string> -class Graphics; -class ImageRect; - /** * A progress bar. * @@ -44,19 +40,12 @@ class ProgressBar : public gcn::Widget int width = 40, int height = 7, int color = -1); - ~ProgressBar() override; - /** * Performs progress bar logic (fading colors) */ void logic() override; /** - * Update the alpha value to the graphic components. - */ - static void updateAlpha(); - - /** * Draws the progress bar. */ void draw(gcn::Graphics *graphics) override; @@ -111,13 +100,6 @@ class ProgressBar : public gcn::Widget void setSmoothColorChange(bool smoothColorChange) { mSmoothColorChange = smoothColorChange; } - /** - * Renders a progressbar with the given properties. - */ - static void render(Graphics *graphics, const gcn::Rectangle &area, - const gcn::Color &color, float progress, - const std::string &text = std::string()); - private: float mProgress, mProgressToGo; bool mSmoothProgress = true; @@ -128,12 +110,4 @@ class ProgressBar : public gcn::Widget bool mSmoothColorChange = true; std::string mText; - - static ImageRect mBorder; - static int mInstances; - static float mAlpha; - - static const gcn::Color TEXT_COLOR; }; - -#endif diff --git a/src/gui/widgets/progressindicator.cpp b/src/gui/widgets/progressindicator.cpp index 496bd8a1..ccd4fd54 100644 --- a/src/gui/widgets/progressindicator.cpp +++ b/src/gui/widgets/progressindicator.cpp @@ -21,10 +21,10 @@ #include "progressindicator.h" #include "graphics.h" +#include "gui/gui.h" #include "simpleanimation.h" #include "resources/animation.h" -#include "resources/imageset.h" #include "resources/resourcemanager.h" #include "resources/theme.h" @@ -32,12 +32,12 @@ ProgressIndicator::ProgressIndicator() { - ImageSet *images = Theme::getImageSetFromTheme("progress-indicator.png", - 32, 32); + const std::string path = gui->getTheme()->resolvePath("progress-indicator.png"); + mImageSet = ResourceManager::getInstance()->getImageSet(path, 32, 32); Animation anim; - for (size_t i = 0; i < images->size(); ++i) - anim.addFrame(images->get(i), 100, 0, 0); + for (size_t i = 0; i < mImageSet->size(); ++i) + anim.addFrame(mImageSet->get(i), 100, 0, 0); mIndicator = std::make_unique<SimpleAnimation>(std::move(anim)); diff --git a/src/gui/widgets/progressindicator.h b/src/gui/widgets/progressindicator.h index 428bbd02..4a6ea339 100644 --- a/src/gui/widgets/progressindicator.h +++ b/src/gui/widgets/progressindicator.h @@ -18,8 +18,9 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef PROGRESSINDICATOR_H -#define PROGRESSINDICATOR_H +#pragma once + +#include "resources/imageset.h" #include <guichan/widget.hpp> @@ -42,6 +43,5 @@ public: private: std::unique_ptr<SimpleAnimation> mIndicator; + ResourceRef<ImageSet> mImageSet; }; - -#endif // PROGRESSINDICATOR_H diff --git a/src/gui/widgets/radiobutton.cpp b/src/gui/widgets/radiobutton.cpp index 92cdacd1..ceba78eb 100644 --- a/src/gui/widgets/radiobutton.cpp +++ b/src/gui/widgets/radiobutton.cpp @@ -21,109 +21,43 @@ #include "gui/widgets/radiobutton.h" -#include "configuration.h" -#include "graphics.h" +#include "textrenderer.h" -#include "resources/image.h" +#include "gui/gui.h" #include "resources/theme.h" -int RadioButton::instances = 0; -float RadioButton::mAlpha = 1.0; -Image *RadioButton::radioNormal; -Image *RadioButton::radioChecked; -Image *RadioButton::radioDisabled; -Image *RadioButton::radioDisabledChecked; -Image *RadioButton::radioNormalHi; -Image *RadioButton::radioCheckedHi; - -RadioButton::RadioButton(const std::string &caption, const std::string &group, - bool marked): - gcn::RadioButton(caption, group, marked) +RadioButton::RadioButton(const std::string &caption, + const std::string &group, + bool marked) + : gcn::RadioButton(caption, group, marked) { - if (instances == 0) - { - radioNormal = Theme::getImageFromTheme("radioout.png"); - radioChecked = Theme::getImageFromTheme("radioin.png"); - radioDisabled = Theme::getImageFromTheme("radioout.png"); - radioDisabledChecked = Theme::getImageFromTheme("radioin.png"); - radioNormalHi = Theme::getImageFromTheme("radioout_highlight.png"); - radioCheckedHi = Theme::getImageFromTheme("radioin_highlight.png"); - radioNormal->setAlpha(mAlpha); - radioChecked->setAlpha(mAlpha); - radioDisabled->setAlpha(mAlpha); - radioDisabledChecked->setAlpha(mAlpha); - radioNormalHi->setAlpha(mAlpha); - radioCheckedHi->setAlpha(mAlpha); - } - - instances++; + auto &skin = gui->getTheme()->getSkin(SkinType::RadioButton); + setWidth(skin.getMinWidth() + 2 * skin.padding + skin.spacing + getFont()->getWidth(caption)); + setHeight(skin.getMinHeight() + 2 * skin.padding); } -RadioButton::~RadioButton() +void RadioButton::draw(gcn::Graphics* graphics) { - instances--; + WidgetState widgetState(this); + if (mHasMouse) + widgetState.flags |= STATE_HOVERED; + if (isSelected()) + widgetState.flags |= STATE_SELECTED; - if (instances == 0) - { - radioNormal->decRef(); - radioChecked->decRef(); - radioDisabled->decRef(); - radioDisabledChecked->decRef(); - radioNormalHi->decRef(); - radioCheckedHi->decRef(); - } -} + auto &skin = gui->getTheme()->getSkin(SkinType::RadioButton); + skin.draw(static_cast<Graphics *>(graphics), widgetState); -void RadioButton::drawBox(gcn::Graphics* graphics) -{ - if (config.guiAlpha != mAlpha) + if (auto skinState = skin.getState(widgetState.flags)) { - mAlpha = config.guiAlpha; - radioNormal->setAlpha(mAlpha); - radioChecked->setAlpha(mAlpha); - radioDisabled->setAlpha(mAlpha); - radioDisabledChecked->setAlpha(mAlpha); - radioNormalHi->setAlpha(mAlpha); - radioCheckedHi->setAlpha(mAlpha); + auto &textFormat = skinState->textFormat; + TextRenderer::renderText(static_cast<Graphics *>(graphics), + getCaption(), + skin.getMinWidth() + skin.padding + skin.spacing, + skin.padding, + Graphics::LEFT, + textFormat.bold ? boldFont : getFont(), + textFormat); } - - Image *box = nullptr; - - if (isEnabled()) - if (isSelected()) - if (mHasMouse) - box = radioCheckedHi; - else - box = radioChecked; - else - if (mHasMouse) - box = radioNormalHi; - else - box = radioNormal; - else - if (isSelected()) - box = radioDisabledChecked; - else - box = radioDisabled; - - if (box) - static_cast<Graphics*>(graphics)->drawImage(box, 2, 2); -} - -void RadioButton::draw(gcn::Graphics* graphics) -{ - graphics->pushClipArea(gcn::Rectangle(1, 1, getWidth() - 1, - getHeight() - 1)); - - drawBox(graphics); - - graphics->popClipArea(); - - graphics->setFont(getFont()); - graphics->setColor(getForegroundColor()); - - int h = getHeight() + getHeight() / 2; - graphics->drawText(getCaption(), h - 2, 0); } void RadioButton::mouseEntered(gcn::MouseEvent& event) diff --git a/src/gui/widgets/radiobutton.h b/src/gui/widgets/radiobutton.h index 2a96ff6e..fda43d01 100644 --- a/src/gui/widgets/radiobutton.h +++ b/src/gui/widgets/radiobutton.h @@ -19,55 +19,40 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef RADIOBUTTON_H -#define RADIOBUTTON_H +#pragma once #include <guichan/widgets/radiobutton.hpp> -class Image; - /** * Guichan based RadioButton with custom look */ class RadioButton : public gcn::RadioButton { public: - RadioButton(const std::string &caption,const std::string &group, - bool marked = false); - - ~RadioButton() override; + RadioButton(const std::string &caption, + const std::string &group, + bool marked = false); /** - * Draws the radiobutton, not the caption. + * Implementation of the draw method. */ - void drawBox(gcn::Graphics* graphics) override; + void draw(gcn::Graphics *graphics) override; /** - * Implementation of the draw methods. - * Thus, avoiding the rhomb around the radio button. + * Overridden because box is drawn in RadioButton::draw. */ - void draw(gcn::Graphics* graphics) override; + void drawBox(gcn::Graphics *graphics) override {} /** * Called when the mouse enteres the widget area. */ - void mouseEntered(gcn::MouseEvent& event) override; + void mouseEntered(gcn::MouseEvent &event) override; /** * Called when the mouse leaves the widget area. */ - void mouseExited(gcn::MouseEvent& event) override; + void mouseExited(gcn::MouseEvent &event) override; private: - static int instances; - static float mAlpha; bool mHasMouse = false; - static Image *radioNormal; - static Image *radioChecked; - static Image *radioDisabled; - static Image *radioDisabledChecked; - static Image *radioNormalHi; - static Image *radioCheckedHi; }; - -#endif // RADIOBUTTON_H diff --git a/src/gui/widgets/resizegrip.cpp b/src/gui/widgets/resizegrip.cpp index dd29a977..c802a405 100644 --- a/src/gui/widgets/resizegrip.cpp +++ b/src/gui/widgets/resizegrip.cpp @@ -21,48 +21,22 @@ #include "gui/widgets/resizegrip.h" -#include "configuration.h" #include "graphics.h" -#include "resources/image.h" +#include "gui/gui.h" #include "resources/theme.h" #include <guichan/graphics.hpp> -Image *ResizeGrip::gripImage = nullptr; -int ResizeGrip::mInstances = 0; -float ResizeGrip::mAlpha = 1.0; - -ResizeGrip::ResizeGrip(const std::string &image) -{ - if (mInstances == 0) - { - // Load the grip image - gripImage = Theme::getImageFromTheme(image); - gripImage->setAlpha(mAlpha); - } - - mInstances++; - - setWidth(gripImage->getWidth() + 2); - setHeight(gripImage->getHeight() + 2); -} - -ResizeGrip::~ResizeGrip() +ResizeGrip::ResizeGrip() { - mInstances--; - - if (mInstances == 0) - gripImage->decRef(); + auto &skin = gui->getTheme()->getSkin(SkinType::ResizeGrip); + setSize(skin.getMinWidth() + skin.padding, skin.getMinHeight() + skin.padding); } void ResizeGrip::draw(gcn::Graphics *graphics) { - if (config.guiAlpha != mAlpha) - { - mAlpha = config.guiAlpha; - gripImage->setAlpha(mAlpha); - } - - static_cast<Graphics*>(graphics)->drawImage(gripImage, 0, 0); + gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), + SkinType::ResizeGrip, + WidgetState(this)); } diff --git a/src/gui/widgets/resizegrip.h b/src/gui/widgets/resizegrip.h index d2f8ca4d..9b4e0611 100644 --- a/src/gui/widgets/resizegrip.h +++ b/src/gui/widgets/resizegrip.h @@ -19,13 +19,10 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef RESIZEGRIP_H -#define RESIZEGRIP_H +#pragma once #include <guichan/widget.hpp> -class Image; - /** * Resize grip. The resize grip is part of a resizable Window. It relies on the * fact that uncaught mouse events are automatically routed to the parent @@ -36,19 +33,10 @@ class Image; class ResizeGrip : public gcn::Widget { public: - ResizeGrip(const std::string &image = "resize.png"); - - ~ResizeGrip() override; + ResizeGrip(); /** * Draws the resize grip. */ void draw(gcn::Graphics *graphics) override; - - private: - static Image *gripImage; /**< Resize grip image */ - static int mInstances; /**< Number of resize grip instances */ - static float mAlpha; }; - -#endif diff --git a/src/gui/widgets/scrollarea.cpp b/src/gui/widgets/scrollarea.cpp index 225a231d..c4d55072 100644 --- a/src/gui/widgets/scrollarea.cpp +++ b/src/gui/widgets/scrollarea.cpp @@ -21,24 +21,12 @@ #include "gui/widgets/scrollarea.h" -#include "configuration.h" #include "graphics.h" -#include "resources/image.h" -#include "resources/theme.h" - -#include "utils/dtor.h" - -int ScrollArea::instances = 0; -float ScrollArea::mAlpha = 1.0; -ImageRect ScrollArea::background; -ImageRect ScrollArea::vMarker; -ImageRect ScrollArea::vMarkerHi; -Image *ScrollArea::buttons[4][2]; +#include "gui/gui.h" ScrollArea::ScrollArea() { - addWidgetListener(this); init(); } @@ -51,24 +39,6 @@ ScrollArea::ScrollArea(gcn::Widget *widget): ScrollArea::~ScrollArea() { delete getContent(); - - instances--; - - if (instances == 0) - { - std::for_each(background.grid, background.grid + 9, dtor<Image*>()); - std::for_each(vMarker.grid, vMarker.grid + 9, dtor<Image*>()); - std::for_each(vMarkerHi.grid, vMarkerHi.grid + 9, dtor<Image*>()); - - buttons[UP][0]->decRef(); - buttons[UP][1]->decRef(); - buttons[DOWN][0]->decRef(); - buttons[DOWN][1]->decRef(); - buttons[LEFT][0]->decRef(); - buttons[LEFT][1]->decRef(); - buttons[RIGHT][0]->decRef(); - buttons[RIGHT][1]->decRef(); - } } void ScrollArea::init() @@ -76,83 +46,24 @@ void ScrollArea::init() // Draw background by default setOpaque(true); - setUpButtonScrollAmount(2); - setDownButtonScrollAmount(2); - setLeftButtonScrollAmount(2); - setRightButtonScrollAmount(2); + auto theme = gui->getTheme(); - if (instances == 0) - { - // Load the background skin - Image *textbox = Theme::getImageFromTheme("deepbox.png"); - const int bggridx[4] = {0, 3, 28, 31}; - const int bggridy[4] = {0, 3, 28, 31}; - int a = 0; + int minWidth = theme->getSkin(SkinType::ScrollAreaVBar).getMinWidth(); + if (minWidth > 0) + setScrollbarWidth(minWidth); - for (int y = 0; y < 3; y++) - { - for (int x = 0; x < 3; x++) - { - background.grid[a] = textbox->getSubImage( - bggridx[x], bggridy[y], - bggridx[x + 1] - bggridx[x] + 1, - bggridy[y + 1] - bggridy[y] + 1); - a++; - } - } - background.setAlpha(config.guiAlpha); - - textbox->decRef(); + if (auto content = getContent()) + content->setFrameSize(theme->getSkin(SkinType::ScrollArea).padding); - // Load vertical scrollbar skin - Image *vscroll = Theme::getImageFromTheme("vscroll_grey.png"); - Image *vscrollHi = Theme::getImageFromTheme("vscroll_highlight.png"); + // The base color is only used when rendering a square in the corner where + // the scrollbars meet. We disable rendering of this square by setting the + // base color to transparent. + setBaseColor(gcn::Color(0, 0, 0, 0)); - int vsgridx[4] = {0, 4, 7, 11}; - int vsgridy[4] = {0, 4, 15, 19}; - a = 0; - - for (int y = 0; y < 3; y++) - { - for (int x = 0; x < 3; x++) - { - vMarker.grid[a] = vscroll->getSubImage( - vsgridx[x], vsgridy[y], - vsgridx[x + 1] - vsgridx[x], - vsgridy[y + 1] - vsgridy[y]); - vMarkerHi.grid[a] = vscrollHi->getSubImage( - vsgridx[x], vsgridy[y], - vsgridx[x + 1] - vsgridx[x], - vsgridy[y + 1] - vsgridy[y]); - a++; - } - } - - vMarker.setAlpha(config.guiAlpha); - vMarkerHi.setAlpha(config.guiAlpha); - - vscroll->decRef(); - vscrollHi->decRef(); - - buttons[UP][0] = - Theme::getImageFromTheme("vscroll_up_default.png"); - buttons[DOWN][0] = - Theme::getImageFromTheme("vscroll_down_default.png"); - buttons[LEFT][0] = - Theme::getImageFromTheme("hscroll_left_default.png"); - buttons[RIGHT][0] = - Theme::getImageFromTheme("hscroll_right_default.png"); - buttons[UP][1] = - Theme::getImageFromTheme("vscroll_up_pressed.png"); - buttons[DOWN][1] = - Theme::getImageFromTheme("vscroll_down_pressed.png"); - buttons[LEFT][1] = - Theme::getImageFromTheme("hscroll_left_pressed.png"); - buttons[RIGHT][1] = - Theme::getImageFromTheme("hscroll_right_pressed.png"); - } - - instances++; + setUpButtonScrollAmount(5); + setDownButtonScrollAmount(5); + setLeftButtonScrollAmount(5); + setRightButtonScrollAmount(5); } void ScrollArea::logic() @@ -201,159 +112,117 @@ void ScrollArea::logic() } } -void ScrollArea::updateAlpha() +void ScrollArea::draw(gcn::Graphics *graphics) { - float alpha = std::max(config.guiAlpha, - Theme::instance()->getMinimumOpacity()); - - if (alpha != mAlpha) - { - mAlpha = alpha; + if (getFrameSize() == 0) + drawFrame(graphics); - background.setAlpha(mAlpha); - vMarker.setAlpha(mAlpha); - vMarkerHi.setAlpha(mAlpha); - } + gcn::ScrollArea::draw(graphics); } -void ScrollArea::draw(gcn::Graphics *graphics) +void ScrollArea::drawFrame(gcn::Graphics *graphics) { - if (mVBarVisible) - { - drawUpButton(graphics); - drawDownButton(graphics); - drawVBar(graphics); - drawVMarker(graphics); - } - - if (mHBarVisible) - { - drawLeftButton(graphics); - drawRightButton(graphics); - drawHBar(graphics); - drawHMarker(graphics); - } + if (!mOpaque) + return; - if (mHBarVisible && mVBarVisible) - { - graphics->setColor(getBaseColor()); - graphics->fillRectangle(gcn::Rectangle(getWidth() - mScrollbarWidth, - getHeight() - mScrollbarWidth, - mScrollbarWidth, - mScrollbarWidth)); - } + const int bs = getFrameSize(); - updateAlpha(); + WidgetState state(this); + state.width += bs * 2; + state.height += + bs * 2; - drawChildren(graphics); + gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), SkinType::ScrollArea, state); } -void ScrollArea::drawFrame(gcn::Graphics *graphics) +void ScrollArea::drawChildren(gcn::Graphics *graphics) { - if (mOpaque) - { - const int bs = getFrameSize(); - const int w = getWidth() + bs * 2; - const int h = getHeight() + bs * 2; + auto g = static_cast<Graphics*>(graphics); + g->pushClipRect(getChildrenArea()); - static_cast<Graphics*>(graphics)-> - drawImageRect(0, 0, w, h, background); - } + gcn::ScrollArea::drawChildren(graphics); + + g->popClipRect(); } void ScrollArea::setOpaque(bool opaque) { mOpaque = opaque; - setFrameSize(mOpaque ? 2 : 0); + + auto &skin = gui->getTheme()->getSkin(SkinType::ScrollArea); + setFrameSize(mOpaque ? skin.frameSize : 0); } -void ScrollArea::drawButton(gcn::Graphics *graphics, BUTTON_DIR dir) +void ScrollArea::drawBackground(gcn::Graphics *graphics) { - int state = 0; - gcn::Rectangle dim; - - switch (dir) - { - case UP: - state = mUpButtonPressed ? 1 : 0; - dim = getUpButtonDimension(); - break; - case DOWN: - state = mDownButtonPressed ? 1 : 0; - dim = getDownButtonDimension(); - break; - case LEFT: - state = mLeftButtonPressed ? 1 : 0; - dim = getLeftButtonDimension(); - break; - case RIGHT: - state = mRightButtonPressed ? 1 : 0; - dim = getRightButtonDimension(); - break; - } - - static_cast<Graphics*>(graphics)-> - drawImage(buttons[dir][state], dim.x, dim.y); + // background is drawn as part of the frame instead } void ScrollArea::drawUpButton(gcn::Graphics *graphics) { - drawButton(graphics, UP); + drawButton(graphics, SkinType::ButtonUp, mUpButtonPressed, getUpButtonDimension()); } void ScrollArea::drawDownButton(gcn::Graphics *graphics) { - drawButton(graphics, DOWN); + drawButton(graphics, SkinType::ButtonDown, mDownButtonPressed, getDownButtonDimension()); } void ScrollArea::drawLeftButton(gcn::Graphics *graphics) { - drawButton(graphics, LEFT); + drawButton(graphics, SkinType::ButtonLeft, mLeftButtonPressed, getLeftButtonDimension()); } void ScrollArea::drawRightButton(gcn::Graphics *graphics) { - drawButton(graphics, RIGHT); + drawButton(graphics, SkinType::ButtonRight, mRightButtonPressed, getRightButtonDimension()); } void ScrollArea::drawVBar(gcn::Graphics *graphics) { - const gcn::Rectangle dim = getVerticalBarDimension(); - graphics->setColor(gcn::Color(0, 0, 0, 32)); - graphics->fillRectangle(dim); - graphics->setColor(gcn::Color(255, 255, 255)); + WidgetState state(getVerticalBarDimension()); + if (mHasMouse && (mX > (getWidth() - getScrollbarWidth()))) + state.flags |= STATE_HOVERED; + + gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), SkinType::ScrollAreaVBar, state); } void ScrollArea::drawHBar(gcn::Graphics *graphics) { - const gcn::Rectangle dim = getHorizontalBarDimension(); - graphics->setColor(gcn::Color(0, 0, 0, 32)); - graphics->fillRectangle(dim); - graphics->setColor(gcn::Color(255, 255, 255)); + WidgetState state(getHorizontalBarDimension()); + if (mHasMouse && (mY > (getHeight() - getScrollbarWidth()))) + state.flags |= STATE_HOVERED; + + gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), SkinType::ScrollAreaHBar, state); } void ScrollArea::drawVMarker(gcn::Graphics *graphics) { - gcn::Rectangle dim = getVerticalMarkerDimension(); - - if ((mHasMouse) && (mX > (getWidth() - getScrollbarWidth()))) - static_cast<Graphics*>(graphics)-> - drawImageRect(dim.x, dim.y, dim.width, dim.height, vMarkerHi); - else - static_cast<Graphics*>(graphics)-> - drawImageRect(dim.x, dim.y, dim.width, dim.height,vMarker); + WidgetState state(getVerticalMarkerDimension()); + if (mHasMouse && (mX > (getWidth() - getScrollbarWidth()))) + state.flags |= STATE_HOVERED; + + gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), SkinType::ScrollAreaVMarker, state); } void ScrollArea::drawHMarker(gcn::Graphics *graphics) { - gcn::Rectangle dim = getHorizontalMarkerDimension(); - - if ((mHasMouse) && (mY > (getHeight() - getScrollbarWidth()))) - static_cast<Graphics*>(graphics)-> - drawImageRect(dim.x, dim.y, dim.width, dim.height, vMarkerHi); - else - static_cast<Graphics*>(graphics)-> - drawImageRect(dim.x, dim.y, dim.width, dim.height, vMarker); + WidgetState state(getHorizontalMarkerDimension()); + if (mHasMouse && (mY > (getHeight() - getScrollbarWidth()))) + state.flags |= STATE_HOVERED; + + gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), SkinType::ScrollAreaHMarker, state); +} + +void ScrollArea::drawButton(gcn::Graphics *graphics, + SkinType skinType, + bool pressed, + const gcn::Rectangle &dim) +{ + WidgetState state(dim); + if (pressed) + state.flags |= STATE_SELECTED; + + gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), skinType, state); } void ScrollArea::mouseMoved(gcn::MouseEvent& event) @@ -371,9 +240,3 @@ void ScrollArea::mouseExited(gcn::MouseEvent& event) { mHasMouse = false; } - -void ScrollArea::widgetResized(const gcn::Event &event) -{ - getContent()->setSize(getWidth() - 2 * getFrameSize(), - getHeight() - 2 * getFrameSize()); -} diff --git a/src/gui/widgets/scrollarea.h b/src/gui/widgets/scrollarea.h index 2fae2d4b..40f1adc1 100644 --- a/src/gui/widgets/scrollarea.h +++ b/src/gui/widgets/scrollarea.h @@ -19,14 +19,11 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SCROLLAREA_H -#define SCROLLAREA_H +#pragma once -#include <guichan/widgets/scrollarea.hpp> -#include <guichan/widgetlistener.hpp> +#include "resources/theme.h" -class Image; -class ImageRect; +#include <guichan/widgets/scrollarea.hpp> /** * A scroll area. @@ -37,7 +34,7 @@ class ImageRect; * * \ingroup GUI */ -class ScrollArea : public gcn::ScrollArea, public gcn::WidgetListener +class ScrollArea : public gcn::ScrollArea { public: /** @@ -65,12 +62,7 @@ class ScrollArea : public gcn::ScrollArea, public gcn::WidgetListener void logic() override; /** - * Update the alpha value to the graphic components. - */ - static void updateAlpha(); - - /** - * Draws the scroll area. + * Overridden to draw the frame if its size is 0. */ void draw(gcn::Graphics *graphics) override; @@ -80,6 +72,11 @@ class ScrollArea : public gcn::ScrollArea, public gcn::WidgetListener void drawFrame(gcn::Graphics *graphics) override; /** + * Applies clipping to the contents. + */ + void drawChildren(gcn::Graphics* graphics) override; + + /** * Sets whether the widget should draw its background or not. */ void setOpaque(bool opaque); @@ -104,22 +101,13 @@ class ScrollArea : public gcn::ScrollArea, public gcn::WidgetListener */ void mouseExited(gcn::MouseEvent& event) override; - void widgetResized(const gcn::Event &event) override; - protected: - enum BUTTON_DIR { - UP, - DOWN, - LEFT, - RIGHT - }; - /** * Initializes the scroll area. */ void init(); - void drawButton(gcn::Graphics *graphics, BUTTON_DIR dir); + void drawBackground(gcn::Graphics *graphics) override; void drawUpButton(gcn::Graphics *graphics) override; void drawDownButton(gcn::Graphics *graphics) override; void drawLeftButton(gcn::Graphics *graphics) override; @@ -129,17 +117,13 @@ class ScrollArea : public gcn::ScrollArea, public gcn::WidgetListener void drawVMarker(gcn::Graphics *graphics) override; void drawHMarker(gcn::Graphics *graphics) override; - static int instances; - static float mAlpha; - static ImageRect background; - static ImageRect vMarker; - static ImageRect vMarkerHi; - static Image *buttons[4][2]; + static void drawButton(gcn::Graphics *graphics, + SkinType skinType, + bool pressed, + const gcn::Rectangle &dim); int mX = 0; int mY = 0; bool mHasMouse = false; bool mOpaque = true; }; - -#endif diff --git a/src/gui/widgets/setuptab.h b/src/gui/widgets/setuptab.h index 0cc35a98..78cef5b2 100644 --- a/src/gui/widgets/setuptab.h +++ b/src/gui/widgets/setuptab.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef GUI_SETUPTAB_H -#define GUI_SETUPTAB_H +#pragma once #include "gui/widgets/container.h" @@ -58,5 +57,3 @@ protected: private: std::string mName; }; - -#endif diff --git a/src/gui/widgets/shopitems.h b/src/gui/widgets/shopitems.h index e213f67c..1b6e1727 100644 --- a/src/gui/widgets/shopitems.h +++ b/src/gui/widgets/shopitems.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SHOP_H -#define SHOP_H +#pragma once #include <guichan/listmodel.hpp> @@ -111,5 +110,3 @@ class ShopItems : public gcn::ListModel /** Look for duplicate entries on addition. */ bool mMergeDuplicates; }; - -#endif // SHOP_H diff --git a/src/gui/widgets/shoplistbox.h b/src/gui/widgets/shoplistbox.h index 4dbd756b..f6a1b12a 100644 --- a/src/gui/widgets/shoplistbox.h +++ b/src/gui/widgets/shoplistbox.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SHOPLISTBOX_H -#define SHOPLISTBOX_H +#pragma once #include "gui/widgets/listbox.h" @@ -97,5 +96,3 @@ class ShopListBox : public ListBox bool mPriceCheck; }; - -#endif // SHOPLISTBOX_H diff --git a/src/gui/widgets/shortcutcontainer.cpp b/src/gui/widgets/shortcutcontainer.cpp index 5925752e..ccf4b082 100644 --- a/src/gui/widgets/shortcutcontainer.cpp +++ b/src/gui/widgets/shortcutcontainer.cpp @@ -21,10 +21,18 @@ #include "gui/widgets/shortcutcontainer.h" -float ShortcutContainer::mAlpha = 1.0; +#include "gui/gui.h" + +#include "resources/theme.h" ShortcutContainer::ShortcutContainer() { + addMouseListener(this); + addWidgetListener(this); + + auto &skin = gui->getTheme()->getSkin(SkinType::ShortcutBox); + mBoxWidth = skin.getMinWidth(); + mBoxHeight = skin.getMinHeight(); } void ShortcutContainer::widgetResized(const gcn::Event &event) diff --git a/src/gui/widgets/shortcutcontainer.h b/src/gui/widgets/shortcutcontainer.h index cab20f27..35a88d7f 100644 --- a/src/gui/widgets/shortcutcontainer.h +++ b/src/gui/widgets/shortcutcontainer.h @@ -19,15 +19,12 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SHORTCUTCONTAINER_H -#define SHORTCUTCONTAINER_H +#pragma once #include <guichan/mouselistener.hpp> #include <guichan/widget.hpp> #include <guichan/widgetlistener.hpp> -class Image; - /** * A generic shortcut container. * @@ -45,20 +42,18 @@ class ShortcutContainer : public gcn::Widget, */ void draw(gcn::Graphics *graphics) override = 0; + // Overridden to disable drawing of the frame + void drawFrame(gcn::Graphics *graphics) override {} + /** * Invoked when a widget changes its size. This is used to determine * the new height of the container. */ void widgetResized(const gcn::Event &event) override; - int getMaxItems() const - { return mMaxItems; } - - int getBoxWidth() const - { return mBoxWidth; } - - int getBoxHeight() const - { return mBoxHeight; } + int getMaxItems() const { return mMaxItems; } + int getBoxWidth() const { return mBoxWidth; } + int getBoxHeight() const { return mBoxHeight; } protected: /** @@ -70,10 +65,6 @@ class ShortcutContainer : public gcn::Widget, */ int getIndexFromGrid(int pointX, int pointY) const; - Image *mBackgroundImg; - - static float mAlpha; - int mMaxItems = 0; int mBoxWidth = 0; int mBoxHeight = 0; @@ -82,5 +73,3 @@ class ShortcutContainer : public gcn::Widget, int mGridWidth = 1; int mGridHeight = 1; }; - -#endif diff --git a/src/gui/widgets/slider.cpp b/src/gui/widgets/slider.cpp index a7ba37e8..bad10c15 100644 --- a/src/gui/widgets/slider.cpp +++ b/src/gui/widgets/slider.cpp @@ -21,19 +21,11 @@ #include "gui/widgets/slider.h" -#include "configuration.h" #include "graphics.h" -#include "resources/image.h" +#include "gui/gui.h" #include "resources/theme.h" -Image *Slider::hStart, *Slider::hMid, *Slider::hEnd, *Slider::hGrip; -Image *Slider::vStart, *Slider::vMid, *Slider::vEnd, *Slider::vGrip; -Image *Slider::hStartHi, *Slider::hMidHi, *Slider::hEndHi, *Slider::hGripHi; -Image *Slider::vStartHi, *Slider::vMidHi, *Slider::vEndHi, *Slider::vGripHi; -float Slider::mAlpha = 1.0; -int Slider::mInstances = 0; - Slider::Slider(double scaleEnd): gcn::Slider(scaleEnd) { @@ -46,154 +38,30 @@ Slider::Slider(double scaleStart, double scaleEnd): init(); } -Slider::~Slider() -{ - mInstances--; - - if (mInstances == 0) - { - delete hStart; - delete hMid; - delete hEnd; - delete hGrip; - delete vStart; - delete vMid; - delete vEnd; - delete vGrip; - delete hStartHi; - delete hMidHi; - delete hEndHi; - delete hGripHi; - delete vStartHi; - delete vMidHi; - delete vEndHi; - delete vGripHi; - } -} - void Slider::init() { - int x, y, w, h,o1,o2; - setFrameSize(0); - - // Load resources - if (mInstances == 0) - { - Image *slider = Theme::getImageFromTheme("slider.png"); - Image *sliderHi = Theme::getImageFromTheme("slider_hilight.png"); - - x = 0; y = 0; - w = 15; h = 6; - o1 = 4; o2 = 11; - hStart = slider->getSubImage(x, y, o1 - x, h); - hMid = slider->getSubImage(o1, y, o2 - o1, h); - hEnd = slider->getSubImage(o2, y, w - o2 + x, h); - hStartHi = sliderHi->getSubImage(x, y, o1 - x, h); - hMidHi = sliderHi->getSubImage(o1, y, o2 - o1, h); - hEndHi = sliderHi->getSubImage(o2, y, w - o2 + x, h); - - x = 6; y = 8; - w = 9; h = 10; - hGrip = slider->getSubImage(x, y, w, h); - hGripHi = sliderHi->getSubImage(x, y, w, h); - - x = 0; y = 6; - w = 6; h = 21; - o1 = 10; o2 = 18; - vStart = slider->getSubImage(x, y, w, o1 - y); - vMid = slider->getSubImage(x, o1, w, o2 - o1); - vEnd = slider->getSubImage(x, o2, w, h - o2 + y); - vStartHi = sliderHi->getSubImage(x, y, w, o1 - y); - vMidHi = sliderHi->getSubImage(x, o1, w, o2 - o1); - vEndHi = sliderHi->getSubImage(x, o2, w, h - o2 + y); - - x = 6; y = 8; - w = 9; h = 10; - vGrip = slider->getSubImage(x, y, w, h); - vGripHi = sliderHi->getSubImage(x, y, w, h); - - slider->decRef(); - sliderHi->decRef(); - } - - mInstances++; - - setMarkerLength(hGrip->getWidth()); -} - -void Slider::updateAlpha() -{ - float alpha = std::max(config.guiAlpha, - Theme::instance()->getMinimumOpacity()); - - if (alpha != mAlpha) - { - mAlpha = alpha; - hStart->setAlpha(mAlpha); - hMid->setAlpha(mAlpha); - hEnd->setAlpha(mAlpha); - hGrip->setAlpha(mAlpha); - hStartHi->setAlpha(mAlpha); - hMidHi->setAlpha(mAlpha); - hEndHi->setAlpha(mAlpha); - hGripHi->setAlpha(mAlpha); - - vStart->setAlpha(mAlpha); - vMid->setAlpha(mAlpha); - vEnd->setAlpha(mAlpha); - vGrip->setAlpha(mAlpha); - vStartHi->setAlpha(mAlpha); - vMidHi->setAlpha(mAlpha); - vEndHi->setAlpha(mAlpha); - vGripHi->setAlpha(mAlpha); - } - + auto theme = gui->getTheme(); + auto &sliderSkin = theme->getSkin(SkinType::Slider); + auto &sliderHandleSkin = theme->getSkin(SkinType::SliderHandle); + setFrameSize(sliderSkin.frameSize); + setMarkerLength(sliderHandleSkin.getMinWidth()); + + setWidth(100); + setHeight(sliderSkin.getMinHeight() + 2 * sliderSkin.padding); } void Slider::draw(gcn::Graphics *graphics) { - int w = getWidth(); - int h = getHeight(); - int x = 0; - int y = mHasMouse?(h - hStartHi->getHeight()) / 2:(h - hStart->getHeight()) / 2; - - updateAlpha(); - - if (!mHasMouse) - { - static_cast<Graphics*>(graphics)->drawImage(hStart, x, y); - - w -= hStart->getWidth() + hEnd->getWidth(); - x += hStart->getWidth(); - - static_cast<Graphics*>(graphics)-> - drawImagePattern(hMid, x, y, w, hMid->getHeight()); + WidgetState state(this); + if (mHasMouse) + state.flags |= STATE_HOVERED; - x += w; - static_cast<Graphics*>(graphics)->drawImage(hEnd, x, y); - } - else - { - static_cast<Graphics*>(graphics)->drawImage(hStartHi, x, y); + auto theme = gui->getTheme(); + theme->drawSkin(static_cast<Graphics*>(graphics), SkinType::Slider, state); - w -= hStartHi->getWidth() + hEndHi->getWidth(); - x += hStartHi->getWidth(); - - static_cast<Graphics*>(graphics)-> - drawImagePattern(hMidHi, x, y, w, hMidHi->getHeight()); - - x += w; - static_cast<Graphics*>(graphics)->drawImage(hEndHi, x, y); - } - - drawMarker(graphics); -} - -void Slider::drawMarker(gcn::Graphics *graphics) -{ - static_cast<Graphics*>(graphics)-> - drawImage(mHasMouse?hGripHi:hGrip, getMarkerPosition(), - (getHeight() - (mHasMouse?hGripHi:hGrip)->getHeight()) / 2); + WidgetState handleState(state); + handleState.x += getMarkerPosition(); + theme->drawSkin(static_cast<Graphics*>(graphics), SkinType::SliderHandle, handleState); } void Slider::mouseEntered(gcn::MouseEvent& event) @@ -205,4 +73,3 @@ void Slider::mouseExited(gcn::MouseEvent& event) { mHasMouse = false; } - diff --git a/src/gui/widgets/slider.h b/src/gui/widgets/slider.h index 3896cb52..52b3b3af 100644 --- a/src/gui/widgets/slider.h +++ b/src/gui/widgets/slider.h @@ -19,13 +19,10 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SLIDER_H -#define SLIDER_H +#pragma once #include <guichan/widgets/slider.hpp> -class Image; - /** * Slider widget. Same as the Guichan slider but with custom look. * @@ -47,22 +44,16 @@ class Slider : public gcn::Slider */ Slider(double scaleStart, double scaleEnd); - ~Slider() override; - - /** - * Update the alpha value to the graphic components. - */ - static void updateAlpha(); - /** * Draws the slider. */ void draw(gcn::Graphics *graphics) override; - /** - * Draws the marker. - */ - void drawMarker(gcn::Graphics *graphics) override; + // Overridden to disable drawing of the frame + void drawFrame(gcn::Graphics *graphics) override {} + + // Marker is drawn in Slider::draw + void drawMarker(gcn::Graphics *graphics) override {} /** * Called when the mouse enteres the widget area. @@ -80,13 +71,5 @@ class Slider : public gcn::Slider */ void init(); - static Image *hStart, *hMid, *hEnd, *hGrip; - static Image *vStart, *vMid, *vEnd, *vGrip; - static Image *hStartHi, *hMidHi, *hEndHi, *hGripHi; - static Image *vStartHi, *vMidHi, *vEndHi, *vGripHi; bool mHasMouse = false; - static float mAlpha; - static int mInstances; }; - -#endif diff --git a/src/gui/widgets/spacer.h b/src/gui/widgets/spacer.h index f6a210dc..2fda6e8c 100644 --- a/src/gui/widgets/spacer.h +++ b/src/gui/widgets/spacer.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SPACER_H -#define SPACER_H +#pragma once #include "guichan/graphics.hpp" #include "guichan/widget.hpp" @@ -48,5 +47,3 @@ class Spacer : public gcn::Widget */ void draw(gcn::Graphics *g) override {} }; - -#endif // SPACER_H diff --git a/src/gui/widgets/tab.cpp b/src/gui/widgets/tab.cpp index 64f49eac..b2779c4f 100644 --- a/src/gui/widgets/tab.cpp +++ b/src/gui/widgets/tab.cpp @@ -21,149 +21,90 @@ #include "gui/widgets/tab.h" -#include "configuration.h" #include "graphics.h" +#include "gui/gui.h" +#include "gui/widgets/label.h" #include "gui/widgets/tabbedarea.h" -#include "resources/image.h" #include "resources/theme.h" -#include "utils/dtor.h" - #include <guichan/widgets/label.hpp> -int Tab::mInstances = 0; -float Tab::mAlpha = 1.0; - -enum { - TAB_STANDARD, // 0 - TAB_HIGHLIGHTED, // 1 - TAB_SELECTED, // 2 - TAB_UNUSED, // 3 - TAB_COUNT // 4 - Must be last. -}; - -struct TabData -{ - char const *file; - int gridX[4]; - int gridY[4]; -}; - -static TabData const data[TAB_COUNT] = { - { "tab.png", {0, 9, 16, 25}, {0, 13, 19, 20} }, - { "tab_hilight.png", {0, 9, 16, 25}, {0, 13, 19, 20} }, - { "tabselected.png", {0, 9, 16, 25}, {0, 4, 12, 20} }, - { "tab.png", {0, 9, 16, 25}, {0, 13, 19, 20} } -}; - -ImageRect Tab::tabImg[TAB_COUNT]; - -Tab::Tab() : - mTabColor(&Theme::getThemeColor(Theme::TAB)) -{ - init(); -} - -Tab::~Tab() -{ - mInstances--; - - if (mInstances == 0) - { - for (auto &imgRect : tabImg) - { - std::for_each(imgRect.grid, imgRect.grid + 9, dtor<Image*>()); - } - } -} - -void Tab::init() +Tab::Tab() { setFocusable(false); - setFrameSize(0); - mFlash = false; - if (mInstances == 0) - { - // Load the skin - Image *tab[TAB_COUNT]; - - for (int mode = 0; mode < TAB_COUNT; mode++) - { - tab[mode] = Theme::getImageFromTheme(data[mode].file); - int a = 0; - for (int y = 0; y < 3; y++) - { - for (int x = 0; x < 3; x++) - { - tabImg[mode].grid[a] = tab[mode]->getSubImage( - data[mode].gridX[x], data[mode].gridY[y], - data[mode].gridX[x + 1] - data[mode].gridX[x] + 1, - data[mode].gridY[y + 1] - data[mode].gridY[y] + 1); - a++; - } - } - tabImg[mode].setAlpha(mAlpha); - tab[mode]->decRef(); - } - } - mInstances++; + // Replace the label with customized version + delete mLabel; + mLabel = new Label(); + add(mLabel); + + auto &skin = gui->getTheme()->getSkin(SkinType::Tab); + setFrameSize(skin.frameSize); + mPadding = skin.padding; + mLabel->setPosition(mPadding, mPadding); } -void Tab::updateAlpha() +void Tab::setCaption(const std::string &caption) { - float alpha = std::max(config.guiAlpha, - Theme::instance()->getMinimumOpacity()); + mLabel->setCaption(caption); + mLabel->adjustSize(); - // TODO We don't need to do this for every tab on every draw - // Maybe use a config listener to do it as the value changes. - if (alpha != mAlpha) - { - mAlpha = alpha; + setSize(mLabel->getWidth() + mPadding * 2, + mLabel->getHeight() + mPadding * 2); - for (auto &t : tabImg) - { - t.setAlpha(mAlpha); - } - } + if (mTabbedArea) + static_cast<TabbedArea*>(mTabbedArea)->adjustTabPositions(); } void Tab::draw(gcn::Graphics *graphics) { - int mode = TAB_STANDARD; + if (getFrameSize() == 0) + drawFrame(graphics); - // check which type of tab to draw - if (mTabbedArea) + // if tab is selected, it doesnt need to highlight activity + if (mTabbedArea && mTabbedArea->isTabSelected(this)) + mFlash = false; + + uint8_t flags = 0; + if (mHasMouse) + flags |= STATE_HOVERED; + if (mTabbedArea && mTabbedArea->isTabSelected(this)) + flags |= STATE_SELECTED; + + auto &skin = gui->getTheme()->getSkin(SkinType::Tab); + if (auto state = skin.getState(flags)) { - mLabel->setForegroundColor(*mTabColor); - if (mTabbedArea->isTabSelected(this)) - { - mode = TAB_SELECTED; - // if tab is selected, it doesnt need to highlight activity - mFlash = false; - } - else if (mHasMouse) - { - mode = TAB_HIGHLIGHTED; - } + gcn::Color foregroundColor = state->textFormat.color; if (mFlash) - { - mLabel->setForegroundColor(Theme::getThemeColor(Theme::TAB_FLASH)); - } + foregroundColor = Theme::getThemeColor(Theme::TAB_FLASH); + else if (mTabColor) + foregroundColor = *mTabColor; + + auto label = static_cast<Label*>(mLabel); + label->setForegroundColor(foregroundColor); + label->setOutlineColor(state->textFormat.outlineColor); + label->setShadowColor(state->textFormat.shadowColor); } - updateAlpha(); - - // draw tab - static_cast<Graphics*>(graphics)-> - drawImageRect(0, 0, getWidth(), getHeight(), tabImg[mode]); - // draw label drawChildren(graphics); } +void Tab::drawFrame(gcn::Graphics *graphics) +{ + WidgetState state(this); + state.width += getFrameSize() * 2; + state.height += getFrameSize() * 2; + if (mHasMouse) + state.flags |= STATE_HOVERED; + if (mTabbedArea && mTabbedArea->isTabSelected(this)) + state.flags |= STATE_SELECTED; + + gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), SkinType::Tab, state); +} + void Tab::setTabColor(const gcn::Color *color) { mTabColor = color; diff --git a/src/gui/widgets/tab.h b/src/gui/widgets/tab.h index 86650257..534abaff 100644 --- a/src/gui/widgets/tab.h +++ b/src/gui/widgets/tab.h @@ -19,12 +19,10 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef TAB_H -#define TAB_H +#pragma once #include <guichan/widgets/tab.hpp> -class ImageRect; class TabbedArea; /** @@ -35,19 +33,25 @@ class Tab : public gcn::Tab { public: Tab(); - ~Tab() override; /** - * Update the alpha value to the graphic components. + * Sets the caption of the tab. Shadowing gcn::Tab::setCaption, which + * shouldn't be used because it calls gcn::Tab::adjustSize, which does + * not take into account the padding. */ - static void updateAlpha(); + void setCaption(const std::string& caption); /** - * Draw the tabbed area. + * Draw the tab. */ void draw(gcn::Graphics *graphics) override; /** + * Draw the tab frame. + */ + void drawFrame(gcn::Graphics *graphics) override; + + /** * Set the normal color fo the tab's text. */ void setTabColor(const gcn::Color *color); @@ -62,15 +66,7 @@ class Tab : public gcn::Tab virtual void setCurrent() {} private: - /** Load images if no other instances exist yet */ - void init(); - - static ImageRect tabImg[4]; /**< Tab state graphics */ - static int mInstances; /**< Number of tab instances */ - static float mAlpha; - - const gcn::Color *mTabColor; - bool mFlash; + const gcn::Color *mTabColor = nullptr; + bool mFlash = false; + int mPadding = 8; }; - -#endif diff --git a/src/gui/widgets/tabbedarea.cpp b/src/gui/widgets/tabbedarea.cpp index af0c11cb..fb5436e0 100644 --- a/src/gui/widgets/tabbedarea.cpp +++ b/src/gui/widgets/tabbedarea.cpp @@ -21,6 +21,8 @@ #include "gui/widgets/tabbedarea.h" +#include "graphics.h" + #include "gui/widgets/tab.h" #include <guichan/widgets/container.hpp> @@ -30,13 +32,13 @@ TabbedArea::TabbedArea() mWidgetContainer->setOpaque(false); addWidgetListener(this); - mArrowButton[0] = new Button(std::string(), "shift_left", this); - mArrowButton[1] = new Button(std::string(), "shift_right", this); + mArrowButton[0] = std::make_unique<Button>(std::string(), "shift_left", this); + mArrowButton[1] = std::make_unique<Button>(std::string(), "shift_right", this); mArrowButton[0]->setButtonIcon("tab_arrows_left.png"); mArrowButton[1]->setButtonIcon("tab_arrows_right.png"); - add(mArrowButton[0]); - add(mArrowButton[1]); + add(mArrowButton[0].get()); + add(mArrowButton[1].get()); widgetResized(nullptr); } @@ -61,7 +63,12 @@ void TabbedArea::draw(gcn::Graphics *graphics) if (mTabs.empty()) return; + auto g = static_cast<Graphics*>(graphics); + g->pushClipRect(getChildrenArea()); + drawChildren(graphics); + + g->popClipRect(); } gcn::Widget *TabbedArea::getWidget(const std::string &name) const @@ -206,9 +213,8 @@ void TabbedArea::updateTabsWidth() { mTabsWidth = 0; for (const auto &[tab, _] : mTabs) - { mTabsWidth += tab->getWidth(); - } + updateVisibleTabsWidth(); } @@ -216,9 +222,7 @@ void TabbedArea::updateVisibleTabsWidth() { mVisibleTabsWidth = 0; for (unsigned int i = mTabScrollIndex; i < mTabs.size(); ++i) - { mVisibleTabsWidth += mTabs[i].first->getWidth(); - } } void TabbedArea::adjustTabPositions() @@ -265,7 +269,7 @@ void TabbedArea::action(const gcn::ActionEvent& actionEvent) { if (actionEvent.getId() == "shift_left") { - if (mTabScrollIndex) + if (mTabScrollIndex > 0) --mTabScrollIndex; } else if (actionEvent.getId() == "shift_right") @@ -282,33 +286,18 @@ void TabbedArea::action(const gcn::ActionEvent& actionEvent) void TabbedArea::updateArrowEnableState() { updateTabsWidth(); - if (mTabsWidth > getWidth() - 2) - { - mArrowButton[0]->setVisible(true); - mArrowButton[1]->setVisible(true); - } - else - { - mArrowButton[0]->setVisible(false); - mArrowButton[1]->setVisible(false); + + const bool arrowButtonsVisible = mTabsWidth > getWidth() - 2; + mArrowButton[0]->setVisible(arrowButtonsVisible); + mArrowButton[1]->setVisible(arrowButtonsVisible); + + if (!arrowButtonsVisible) mTabScrollIndex = 0; - } - // Left arrow consistency check - if (!mTabScrollIndex) - mArrowButton[0]->setEnabled(false); - else - mArrowButton[0]->setEnabled(true); + mArrowButton[0]->setEnabled(mTabScrollIndex > 0); // Right arrow consistency check - if (mVisibleTabsWidth < getWidth() - 2 - - mArrowButton[0]->getWidth() - - mArrowButton[1]->getWidth()) - { - mArrowButton[1]->setEnabled(false); - } - else - { - mArrowButton[1]->setEnabled(true); - } + const int availableWidth = getWidth() - 2 - mArrowButton[0]->getWidth() + - mArrowButton[1]->getWidth(); + mArrowButton[1]->setEnabled(mVisibleTabsWidth >= availableWidth); } diff --git a/src/gui/widgets/tabbedarea.h b/src/gui/widgets/tabbedarea.h index 8e6dcb5f..558b2696 100644 --- a/src/gui/widgets/tabbedarea.h +++ b/src/gui/widgets/tabbedarea.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef TABBEDAREA_H -#define TABBEDAREA_H +#pragma once #include <guichan/widget.hpp> #include <guichan/widgetlistener.hpp> @@ -29,6 +28,7 @@ #include "gui/widgets/button.h" +#include <memory> #include <string> class Tab; @@ -36,7 +36,7 @@ class Tab; /** * A tabbed area, the same as the guichan tabbed area in 0.8, but extended */ -class TabbedArea : public gcn::TabbedArea, public gcn::WidgetListener +class TabbedArea final : public gcn::TabbedArea, public gcn::WidgetListener { public: TabbedArea(); @@ -115,7 +115,7 @@ class TabbedArea : public gcn::TabbedArea, public gcn::WidgetListener private: /** The tab arrows */ - Button *mArrowButton[2]; + std::unique_ptr<Button> mArrowButton[2]; /** Check whether the arrow should be clickable */ void updateArrowEnableState(); @@ -151,5 +151,3 @@ class TabbedArea : public gcn::TabbedArea, public gcn::WidgetListener */ unsigned mTabScrollIndex = 0; }; - -#endif diff --git a/src/gui/widgets/table.cpp b/src/gui/widgets/table.cpp index 905bb166..7cddc1fa 100644 --- a/src/gui/widgets/table.cpp +++ b/src/gui/widgets/table.cpp @@ -21,8 +21,7 @@ #include "gui/widgets/table.h" -#include "configuration.h" - +#include "gui/gui.h" #include "gui/sdlinput.h" #include "resources/theme.h" @@ -33,13 +32,10 @@ #include <guichan/graphics.hpp> #include <guichan/key.hpp> -float GuiTable::mAlpha = 1.0; - class GuiTableActionListener : public gcn::ActionListener { public: - GuiTableActionListener(GuiTable *_table, gcn::Widget *_widget, int _row, int _column); - + GuiTableActionListener(GuiTable *table, gcn::Widget *widget, int row, int column); ~GuiTableActionListener() override; void action(const gcn::ActionEvent& actionEvent) override; @@ -269,13 +265,11 @@ void GuiTable::draw(gcn::Graphics* graphics) if (!mModel) return; - if (config.guiAlpha != mAlpha) - mAlpha = config.guiAlpha; + const auto guiAlpha = gui->getTheme()->getGuiAlpha(); if (mOpaque) { - graphics->setColor(Theme::getThemeColor(Theme::BACKGROUND, - (int)(mAlpha * 255.0f))); + graphics->setColor(Theme::getThemeColor(Theme::BACKGROUND, guiAlpha)); graphics->fillRectangle(gcn::Rectangle(0, 0, getWidth(), getHeight())); } @@ -320,8 +314,7 @@ void GuiTable::draw(gcn::Graphics* graphics) widget->setDimension(bounds); - graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT, - (int)(mAlpha * 255.0f))); + graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT, guiAlpha)); if (mLinewiseMode && r == mSelectedRow && c == 0) { @@ -497,14 +490,12 @@ gcn::Widget *GuiTable::getWidgetAt(int x, int y) const if (row > -1 && column > -1) { - gcn::Widget *w = mModel->getElementAt(row, column); - if (w && w->isFocusable()) - return w; - else - return nullptr; // Grab the event locally + if (gcn::Widget *w = mModel->getElementAt(row, column)) + if (w->isFocusable()) + return w; } - else - return nullptr; + + return nullptr; // Grab the event locally } int GuiTable::getRowForY(int y) const @@ -516,8 +507,8 @@ int GuiTable::getRowForY(int y) const if (row < 0 || row >= mModel->getRows()) return -1; - else - return row; + + return row; } int GuiTable::getColumnForX(int x) const @@ -534,24 +525,23 @@ int GuiTable::getColumnForX(int x) const if (column < 0 || column >= mModel->getColumns()) return -1; - else - return column; + + return column; } void GuiTable::_setFocusHandler(gcn::FocusHandler* focusHandler) { gcn::Widget::_setFocusHandler(focusHandler); - if (mModel) + if (!mModel) + return; + + for (int r = 0; r < mModel->getRows(); ++r) { - for (int r = 0; r < mModel->getRows(); ++r) + for (int c = 0; c < mModel->getColumns(); ++c) { - for (int c = 0; c < mModel->getColumns(); ++c) - { - gcn::Widget *w = mModel->getElementAt(r, c); - if (w) - w->_setFocusHandler(focusHandler); - } + if (gcn::Widget *w = mModel->getElementAt(r, c)) + w->_setFocusHandler(focusHandler); } } } diff --git a/src/gui/widgets/table.h b/src/gui/widgets/table.h index a9202022..81071267 100644 --- a/src/gui/widgets/table.h +++ b/src/gui/widgets/table.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef TABLE_H -#define TABLE_H +#pragma once #include "tablemodel.h" @@ -41,10 +40,10 @@ class GuiTableActionListener; * * \ingroup GUI */ -class GuiTable : public gcn::Widget, - public gcn::MouseListener, - public gcn::KeyListener, - public TableModelListener +class GuiTable final : public gcn::Widget, + public gcn::MouseListener, + public gcn::KeyListener, + public TableModelListener { // so that the action listener can call distributeActionEvent friend class GuiTableActionListener; @@ -103,6 +102,9 @@ public: // Inherited from Widget void draw(gcn::Graphics* graphics) override; + // Overridden to disable drawing of the frame + void drawFrame(gcn::Graphics* graphics) override {} + virtual gcn::Widget *getWidgetAt(int x, int y) const; void moveToTop(gcn::Widget *child) override; @@ -159,8 +161,6 @@ private: bool mWrappingEnabled; bool mOpaque; - static float mAlpha; - /** * Holds the background color of the table. */ @@ -180,6 +180,3 @@ private: /** Vector for compactness; used as a list in practice. */ std::vector<GuiTableActionListener *> mActionListeners; }; - - -#endif // TABLE_H diff --git a/src/gui/widgets/tablemodel.h b/src/gui/widgets/tablemodel.h index d4274e39..2ba36556 100644 --- a/src/gui/widgets/tablemodel.h +++ b/src/gui/widgets/tablemodel.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef TABLE_MODEL_H -#define TABLE_MODEL_H +#pragma once #include <guichanfwd.h> @@ -143,5 +142,3 @@ protected: std::vector<gcn::Widget *> mTableModel; std::vector<int> mWidths; }; - -#endif // TABLE_MODEL_H diff --git a/src/gui/widgets/textbox.h b/src/gui/widgets/textbox.h index bcf09ee2..e4596b9a 100644 --- a/src/gui/widgets/textbox.h +++ b/src/gui/widgets/textbox.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef TEXTBOX_H -#define TEXTBOX_H +#pragma once #include <guichan/widgets/textbox.hpp> @@ -62,5 +61,3 @@ class TextBox : public gcn::TextBox int mMinWidth; const gcn::Color *mTextColor; }; - -#endif diff --git a/src/gui/widgets/textfield.cpp b/src/gui/widgets/textfield.cpp index dd0adecd..9f35a5dd 100644 --- a/src/gui/widgets/textfield.cpp +++ b/src/gui/widgets/textfield.cpp @@ -21,107 +21,63 @@ #include "gui/widgets/textfield.h" -#include "configuration.h" #include "graphics.h" +#include "gui/gui.h" #include "gui/sdlinput.h" -#include "resources/image.h" #include "resources/theme.h" #include "utils/copynpaste.h" -#include "utils/dtor.h" #include "utils/stringutils.h" #include <guichan/font.hpp> #include <SDL.h> -#undef DELETE //Win32 compatibility hack - -int TextField::instances = 0; -float TextField::mAlpha = 1.0; -ImageRect TextField::skin; - -TextField::TextField(const std::string &text, bool loseFocusOnTab): - gcn::TextField(text) -{ - setFrameSize(2); - - mLoseFocusOnTab = loseFocusOnTab; - - if (instances == 0) - { - // Load the skin - Image *textbox = Theme::getImageFromTheme("deepbox.png"); - int gridx[4] = {0, 3, 28, 31}; - int gridy[4] = {0, 3, 28, 31}; - int a = 0; - - for (int y = 0; y < 3; y++) - { - for (int x = 0; x < 3; x++) - { - skin.grid[a] = textbox->getSubImage( - gridx[x], gridy[y], - gridx[x + 1] - gridx[x] + 1, - gridy[y + 1] - gridy[y] + 1); - a++; - } - } - skin.setAlpha(config.guiAlpha); - - textbox->decRef(); - } - - instances++; -} - -TextField::~TextField() +TextField::TextField(const std::string &text, bool loseFocusOnTab) + : gcn::TextField(text) + , mLoseFocusOnTab(loseFocusOnTab) { - instances--; - - if (instances == 0) - std::for_each(skin.grid, skin.grid + 9, dtor<Image*>()); -} + auto &skin = gui->getTheme()->getSkin(SkinType::TextField); + setFrameSize(skin.frameSize); + mPadding = skin.padding; -void TextField::updateAlpha() -{ - float alpha = std::max(config.guiAlpha, - Theme::instance()->getMinimumOpacity()); - - if (alpha != mAlpha) - { - mAlpha = alpha; - skin.setAlpha(mAlpha); - } + setWidth(getFont()->getWidth(mText) + 2 * mPadding); + setHeight(getFont()->getHeight() + 2 * mPadding); + fixScroll(); } void TextField::draw(gcn::Graphics *graphics) { - updateAlpha(); + if (getFrameSize() == 0) + drawFrame(graphics); + + auto g = static_cast<Graphics *>(graphics); + g->pushClipRect(gcn::Rectangle(0, 0, getWidth(), getHeight())); if (isFocused()) { drawCaret(graphics, - getFont()->getWidth(mText.substr(0, mCaretPosition)) - - mXScroll); + getFont()->getWidth(mText.substr(0, mCaretPosition)) - mXScroll); } graphics->setColor(Theme::getThemeColor(Theme::TEXT)); graphics->setFont(getFont()); - graphics->drawText(mText, 1 - mXScroll, 1); + graphics->drawText(mText, mPadding - mXScroll, mPadding); + + g->popClipRect(); } void TextField::drawFrame(gcn::Graphics *graphics) { - //updateAlpha(); -> Not useful... + const int bs = getFrameSize(); - int bs = getFrameSize(); - int w = getWidth() + bs * 2; - int h = getHeight() + bs * 2; + WidgetState state(this); + state.width += bs * 2; + state.height += bs * 2; - static_cast<Graphics*>(graphics)->drawImageRect(0, 0, w, h, skin); + gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), SkinType::TextField, state); } void TextField::setNumeric(bool numeric) @@ -156,6 +112,12 @@ int TextField::getValue() const return value; } +void TextField::drawCaret(gcn::Graphics *graphics, int x) +{ + graphics->setColor(getForegroundColor()); + graphics->drawLine(mPadding + x, mPadding, mPadding + x, getHeight() - mPadding); +} + void TextField::keyPressed(gcn::KeyEvent &keyEvent) { switch (keyEvent.getKey().getValue()) diff --git a/src/gui/widgets/textfield.h b/src/gui/widgets/textfield.h index 9235f4b8..b84dd723 100644 --- a/src/gui/widgets/textfield.h +++ b/src/gui/widgets/textfield.h @@ -19,16 +19,13 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef TEXTFIELD_H -#define TEXTFIELD_H +#pragma once #include <guichan/widgets/textfield.hpp> #include <vector> class TextInput; -class ImageRect; -class TextField; struct TextHistory { @@ -79,7 +76,6 @@ class TextField : public gcn::TextField */ TextField(const std::string &text = std::string(), bool loseFocusOnTab = true); - ~TextField() override; /** * Draws the text field. @@ -87,11 +83,6 @@ class TextField : public gcn::TextField void draw(gcn::Graphics *graphics) override; /** - * Update the alpha value to the graphic components. - */ - static void updateAlpha(); - - /** * Draws the background and border. */ void drawFrame(gcn::Graphics *graphics) override; @@ -139,42 +130,41 @@ class TextField : public gcn::TextField * Sets the TextField's source of autocomplete. Passing null will * disable autocomplete. */ - void setAutoComplete(AutoCompleteLister *lister) - { mAutoComplete = lister; } + void setAutoComplete(AutoCompleteLister *lister) + { mAutoComplete = lister; } - /** + /** * Returns the TextField's source of autocomplete. */ - AutoCompleteLister *getAutoComplete() const - { return mAutoComplete; } + AutoCompleteLister *getAutoComplete() const + { return mAutoComplete; } - /** + /** * Sets the TextField's source of input history. */ - void setHistory(TextHistory *history) - { mHistory = history; } + void setHistory(TextHistory *history) + { mHistory = history; } - /** + /** * Returns the TextField's source of input history. */ - TextHistory *getHistory() const - { return mHistory; } + TextHistory *getHistory() const + { return mHistory; } + + protected: + void drawCaret(gcn::Graphics *graphics, int x) override; private: void autoComplete(); void handlePaste(); - static int instances; - static float mAlpha; - static ImageRect skin; bool mNumeric = false; - int mMinimum; - int mMaximum; + int mMinimum = 0; + int mMaximum = 0; bool mLoseFocusOnTab; + int mPadding = 1; AutoCompleteLister *mAutoComplete = nullptr; TextHistory *mHistory = nullptr; /**< Text history. */ }; - -#endif diff --git a/src/gui/widgets/textpreview.h b/src/gui/widgets/textpreview.h index 7e88248f..da24b61e 100644 --- a/src/gui/widgets/textpreview.h +++ b/src/gui/widgets/textpreview.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef TEXTPREVIEW_H -#define TEXTPREVIEW_H +#pragma once #include <guichan/color.hpp> #include <guichan/font.hpp> @@ -138,5 +137,3 @@ class TextPreview : public gcn::Widget bool mShadow = false; bool mOutline = false; }; - -#endif diff --git a/src/gui/widgets/vertcontainer.h b/src/gui/widgets/vertcontainer.h index b66957d3..a684453f 100644 --- a/src/gui/widgets/vertcontainer.h +++ b/src/gui/widgets/vertcontainer.h @@ -18,8 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef GUI_VERTCONTAINER_H -#define GUI_VERTCONTAINER_H +#pragma once #include "gui/widgets/container.h" @@ -42,5 +41,3 @@ class VertContainer : public Container, public gcn::WidgetListener int mSpacing; int mCount = 0; }; - -#endif diff --git a/src/gui/widgets/whispertab.h b/src/gui/widgets/whispertab.h index 0f01bacc..1a0a4a0f 100644 --- a/src/gui/widgets/whispertab.h +++ b/src/gui/widgets/whispertab.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef WHISPERTAB_H -#define WHISPERTAB_H +#pragma once #include "chattab.h" @@ -63,5 +62,3 @@ class WhisperTab : public ChatTab private: std::string mNick; }; - -#endif // CHANNELTAB_H diff --git a/src/gui/widgets/window.cpp b/src/gui/widgets/window.cpp index 8bf9d081..61ed7896 100644 --- a/src/gui/widgets/window.cpp +++ b/src/gui/widgets/window.cpp @@ -23,6 +23,7 @@ #include "configuration.h" #include "log.h" +#include "textrenderer.h" #include "gui/gui.h" #include "gui/viewport.h" @@ -31,7 +32,6 @@ #include "gui/widgets/resizegrip.h" #include "gui/widgets/windowcontainer.h" -#include "resources/image.h" #include "resources/theme.h" #include <guichan/exception.hpp> @@ -43,13 +43,12 @@ int Window::instances = 0; int Window::mouseResize = 0; -Window::Window(const std::string &caption, bool modal, Window *parent, - const std::string &skin): - gcn::Window(caption), - mParent(parent), - mModal(modal), - mMaxWinWidth(graphics->getWidth()), - mMaxWinHeight(graphics->getHeight()) +Window::Window(const std::string &caption, bool modal, Window *parent) + : gcn::Window(caption) + , mParent(parent) + , mModal(modal) + , mMaxWinWidth(graphics->getWidth()) + , mMaxWinHeight(graphics->getHeight()) { logger->log("Window::Window(\"%s\")", caption.c_str()); @@ -58,19 +57,17 @@ Window::Window(const std::string &caption, bool modal, Window *parent, instances++; - setFrameSize(0); - setPadding(3); - setTitleBarHeight(20); - - // Loads the skin - mSkin = Theme::instance()->load(skin); + auto &skin = gui->getTheme()->getSkin(SkinType::Window); + setFrameSize(skin.frameSize); + setPadding(skin.padding); + setTitleBarHeight(skin.titleBarHeight); // Add this window to the window container windowContainer->add(this); if (mModal) { - gui->setCursorType(Cursor::POINTER); + gui->setCursorType(Cursor::Pointer); requestModalFocus(); } @@ -94,8 +91,6 @@ Window::~Window() removeWidgetListener(this); instances--; - - mSkin->instances--; } void Window::setWindowContainer(WindowContainer *wc) @@ -105,44 +100,58 @@ void Window::setWindowContainer(WindowContainer *wc) void Window::draw(gcn::Graphics *graphics) { - auto *g = static_cast<Graphics*>(graphics); + if (getFrameSize() == 0) + drawFrame(graphics); - g->drawImageRect(0, 0, getWidth(), getHeight(), mSkin->getBorder()); + auto g = static_cast<Graphics*>(graphics); - // Draw title - if (mShowTitle) - { - g->setColor(Theme::getThemeColor(Theme::TEXT)); - g->setFont(getFont()); - g->drawText(getCaption(), 7, 5, gcn::Graphics::LEFT); - } - - // Draw Close Button if (mCloseButton) { - g->drawImage(mSkin->getCloseImage(), - getWidth() - mSkin->getCloseImage()->getWidth() - getPadding(), - getPadding()); + WidgetState state(getCloseButtonRect(), mCloseButtonHovered ? STATE_HOVERED : 0); + gui->getTheme()->drawSkin(g, SkinType::ButtonClose, state); } - // Draw Sticky Button if (mStickyButton) { - Image *button = mSkin->getStickyImage(mSticky); - int x = getWidth() - button->getWidth() - getPadding(); - if (mCloseButton) - x -= mSkin->getCloseImage()->getWidth(); - - g->drawImage(button, x, getPadding()); + WidgetState state(getStickyButtonRect(), mSticky ? STATE_SELECTED : 0); + gui->getTheme()->drawSkin(g, SkinType::ButtonSticky, state); } drawChildren(graphics); } +void Window::drawFrame(gcn::Graphics *graphics) +{ + auto g = static_cast<Graphics*>(graphics); + auto theme = gui->getTheme(); + + WidgetState widgetState(this); + widgetState.width += getFrameSize() * 2; + widgetState.height += getFrameSize() * 2; + + auto &skin = theme->getSkin(SkinType::Window); + skin.draw(g, widgetState); + + if (mShowTitle) + { + if (auto skinState = skin.getState(widgetState.flags)) + { + auto &textFormat = skinState->textFormat; + TextRenderer::renderText(g, + getCaption(), + getFrameSize() + skin.titleOffsetX, + getFrameSize() + skin.titleOffsetY, + gcn::Graphics::LEFT, + textFormat.bold ? boldFont : getFont(), + textFormat); + } + } +} + void Window::setContentSize(int width, int height) { - width = width + 2 * getPadding(); - height = height + getPadding() + getTitleBarHeight(); + width += 2 * getPadding(); + height += getPadding() + getTitleBarHeight(); if (getMinWidth() > width) width = getMinWidth(); @@ -156,6 +165,16 @@ void Window::setContentSize(int width, int height) setSize(width, height); } +void Window::setMinimumContentSize(int width, int height) +{ + const int padding = getPadding(); + const int titleBarHeight = getTitleBarHeight(); + auto &skin = gui->getTheme()->getSkin(SkinType::Window); + + setMinWidth(std::max(skin.getMinWidth(), width + 2 * padding)); + setMinHeight(std::max(skin.getMinHeight(), height + padding + titleBarHeight)); +} + void Window::setLocationRelativeTo(gcn::Widget *widget) { int wx; @@ -218,13 +237,12 @@ void Window::setLocationRelativeTo(ImageRect::ImagePosition position, void Window::setMinWidth(int width) { - mMinWinWidth = width > mSkin->getMinWidth() ? width : mSkin->getMinWidth(); + mMinWinWidth = std::max(gui->getTheme()->getMinWidth(SkinType::Window), width); } void Window::setMinHeight(int height) { - mMinWinHeight = height > mSkin->getMinHeight() ? - height : mSkin->getMinHeight(); + mMinWinHeight = std::max(gui->getTheme()->getMinHeight(SkinType::Window), height); } void Window::setMaxWidth(int width) @@ -276,9 +294,7 @@ void Window::widgetResized(const gcn::Event &event) void Window::widgetHidden(const gcn::Event &event) { if (gui) - { - gui->setCursorType(Cursor::POINTER); - } + gui->setCursorType(Cursor::Pointer); WidgetListIterator it; @@ -341,38 +357,13 @@ void Window::mousePressed(gcn::MouseEvent &event) const int x = event.getX(); const int y = event.getY(); - // Handle close button - if (mCloseButton) - { - gcn::Rectangle closeButtonRect( - getWidth() - mSkin->getCloseImage()->getWidth() - getPadding(), - getPadding(), - mSkin->getCloseImage()->getWidth(), - mSkin->getCloseImage()->getHeight()); - - if (closeButtonRect.isPointInRect(x, y)) - { - close(); - } - } + if (mCloseButton && getCloseButtonRect().isPointInRect(x, y)) + close(); - // Handle sticky button - if (mStickyButton) - { - Image *button = mSkin->getStickyImage(mSticky); - int rx = getWidth() - button->getWidth() - getPadding(); - if (mCloseButton) - rx -= mSkin->getCloseImage()->getWidth(); - gcn::Rectangle stickyButtonRect(rx, getPadding(), - button->getWidth(), button->getHeight()); - - if (stickyButtonRect.isPointInRect(x, y)) - { - setSticky(!isSticky()); - } - } + if (mStickyButton && getStickyButtonRect().isPointInRect(x, y)) + setSticky(!isSticky()); - // Handle window resizing + // Update resizing state and disable moving if we're resizing the window mouseResize = getResizeHandles(event); if (mouseResize) mMoved = false; @@ -394,11 +385,15 @@ void Window::mouseReleased(gcn::MouseEvent &event) void Window::mouseExited(gcn::MouseEvent &event) { if (mGrip && !mouseResize) - gui->setCursorType(Cursor::POINTER); + gui->setCursorType(Cursor::Pointer); + + mCloseButtonHovered = false; } void Window::mouseMoved(gcn::MouseEvent &event) { + mCloseButtonHovered = false; + // Make sure BeingPopup is hidden (Viewport does not receive mouseExited) if (viewport) viewport->hideBeingPopup(); @@ -408,30 +403,36 @@ void Window::mouseMoved(gcn::MouseEvent &event) if (event.isConsumed()) return; - int resizeHandles = getResizeHandles(event); + mCloseButtonHovered = getCloseButtonRect().isPointInRect(event.getX(), event.getY()); + Cursor cursor = Cursor::Pointer; // Changes the custom mouse cursor based on its current position. - switch (resizeHandles) + if (!mCloseButtonHovered) { + switch (getResizeHandles(event)) + { case BOTTOM | RIGHT: case TOP | LEFT: - gui->setCursorType(Cursor::RESIZE_DOWN_RIGHT); + cursor = Cursor::ResizeDownRight; break; case BOTTOM | LEFT: case TOP | RIGHT: - gui->setCursorType(Cursor::RESIZE_DOWN_LEFT); + cursor = Cursor::ResizeDownLeft; break; case BOTTOM: case TOP: - gui->setCursorType(Cursor::RESIZE_DOWN); + cursor = Cursor::ResizeDown; break; case RIGHT: case LEFT: - gui->setCursorType(Cursor::RESIZE_ACROSS); + cursor = Cursor::ResizeAcross; break; default: - gui->setCursorType(Cursor::POINTER); + break; + } } + + gui->setCursorType(cursor); } void Window::mouseDragged(gcn::MouseEvent &event) @@ -675,6 +676,13 @@ int Window::getResizeHandles(gcn::MouseEvent &event) if (inPadding && event.getSource() == this) { + /** + * The width of the resize border. Is independent of the actual window + * border width, and determines mostly the size of the corner area + * where two borders are moved at the same time. + */ + const int resizeBorderWidth = std::max(mGrip->getWidth(), 10); + resizeHandles |= (x >= getWidth() - resizeBorderWidth) ? RIGHT : (x < resizeBorderWidth) ? LEFT : 0; resizeHandles |= (y >= getHeight() - resizeBorderWidth) ? BOTTOM : @@ -692,10 +700,55 @@ int Window::getResizeHandles(gcn::MouseEvent &event) return resizeHandles; } +gcn::Rectangle Window::getCloseButtonRect() const +{ + if (!mCloseButton) + return {}; + + auto theme = gui->getTheme(); + + auto &closeButtonSkin = theme->getSkin(SkinType::ButtonClose); + const int closeButtonWidth = closeButtonSkin.getMinWidth(); + const int closeButtonHeight = closeButtonSkin.getMinHeight(); + + return { + getWidth() - closeButtonWidth - closeButtonSkin.padding, + closeButtonSkin.padding, + closeButtonWidth, + closeButtonHeight + }; +} + +gcn::Rectangle Window::getStickyButtonRect() const +{ + if (!mStickyButton) + return {}; + + auto theme = gui->getTheme(); + + auto &closeButtonSkin = theme->getSkin(SkinType::ButtonClose); + const int closeButtonWidth = closeButtonSkin.getMinWidth(); + + auto &stickyButtonSkin = theme->getSkin(SkinType::ButtonSticky); + const int stickyButtonWidth = stickyButtonSkin.getMinWidth(); + const int stickyButtonHeight = stickyButtonSkin.getMinHeight(); + + int stickyButtonX = getWidth() - stickyButtonWidth - stickyButtonSkin.padding; + if (mCloseButton) + stickyButtonX -= closeButtonWidth + closeButtonSkin.padding; + + return { + stickyButtonX, + stickyButtonSkin.padding, + stickyButtonWidth, + stickyButtonHeight + }; +} + int Window::getGuiAlpha() { float alpha = std::max(config.guiAlpha, - Theme::instance()->getMinimumOpacity()); + gui->getTheme()->getMinimumOpacity()); return (int) (alpha * 255.0f); } diff --git a/src/gui/widgets/window.h b/src/gui/widgets/window.h index bf6f363c..bf459a32 100644 --- a/src/gui/widgets/window.h +++ b/src/gui/widgets/window.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef WINDOW_H -#define WINDOW_H +#pragma once #include "graphics.h" #include "guichanfwd.h" @@ -54,10 +53,9 @@ class Window : public gcn::Window, gcn::WidgetListener * @param parent The parent window. This is the window standing above * this one in the window hiearchy. When reordering, * a window will never go below its parent window. - * @param skin The location where the window's skin XML can be found. */ Window(const std::string &caption = "Window", bool modal = false, - Window *parent = nullptr, const std::string &skin = "window.xml"); + Window *parent = nullptr); /** * Destructor. Deletes all the added widgets. @@ -70,16 +68,26 @@ class Window : public gcn::Window, gcn::WidgetListener static void setWindowContainer(WindowContainer *windowContainer); /** - * Draws the window. + * Draws the window contents. */ void draw(gcn::Graphics *graphics) override; /** + * Draws the window frame. + */ + void drawFrame(gcn::Graphics *graphics) override; + + /** * Sets the size of this window. */ void setContentSize(int width, int height); /** + * Sets the minimum size of the window content. + */ + void setMinimumContentSize(int width, int height); + + /** * Sets the location relative to the given widget. */ void setLocationRelativeTo(gcn::Widget *widget); @@ -377,6 +385,9 @@ class Window : public gcn::Window, gcn::WidgetListener */ int getResizeHandles(gcn::MouseEvent &event); + gcn::Rectangle getCloseButtonRect() const; + gcn::Rectangle getStickyButtonRect() const; + ResizeGrip *mGrip = nullptr; /**< Resize grip */ Window *mParent; /**< The parent window */ Layout *mLayout = nullptr; /**< Layout handler */ @@ -384,6 +395,7 @@ class Window : public gcn::Window, gcn::WidgetListener bool mShowTitle = true; /**< Window has a title bar */ bool mModal; /**< Window is modal */ bool mCloseButton = false; /**< Window has a close button */ + bool mCloseButtonHovered = false; bool mDefaultVisible = false; /**< Window's default visibility */ bool mSaveVisible = false; /**< Window will save visibility */ bool mStickyButton = false; /**< Window has a sticky button */ @@ -398,15 +410,4 @@ class Window : public gcn::Window, gcn::WidgetListener int mDefaultHeight; /**< Default window height */ static int instances; /**< Number of Window instances */ - - Skin *mSkin; /**< Skin in use by this window */ - - /** - * The width of the resize border. Is independent of the actual window - * border width, and determines mostly the size of the corner area - * where two borders are moved at the same time. - */ - static const int resizeBorderWidth = 10; }; - -#endif diff --git a/src/gui/widgets/windowcontainer.cpp b/src/gui/widgets/windowcontainer.cpp index 36f0998f..4e350a9e 100644 --- a/src/gui/widgets/windowcontainer.cpp +++ b/src/gui/widgets/windowcontainer.cpp @@ -24,16 +24,15 @@ #include "gui/gui.h" #include "gui/widgets/window.h" -#include "utils/dtor.h" - #include <guichan/focushandler.hpp> WindowContainer *windowContainer = nullptr; void WindowContainer::logic() { - delete_all(mDeathList); - mDeathList.clear(); + for (auto widget : mScheduledDeletions) + delete widget; + mScheduledDeletions.clear(); gcn::Container::logic(); } @@ -48,7 +47,7 @@ void WindowContainer::draw(gcn::Graphics *graphics) void WindowContainer::scheduleDelete(gcn::Widget *widget) { - mDeathList.push_back(widget); + mScheduledDeletions.insert(widget); } void WindowContainer::adjustAfterResize(int oldScreenWidth, diff --git a/src/gui/widgets/windowcontainer.h b/src/gui/widgets/windowcontainer.h index 861839c9..ff03a903 100644 --- a/src/gui/widgets/windowcontainer.h +++ b/src/gui/widgets/windowcontainer.h @@ -19,11 +19,12 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef WINDOWCONTAINER_H -#define WINDOWCONTAINER_H +#pragma once #include "gui/widgets/container.h" +#include <set> + /** * A window container. This container adds functionality for more convenient * widget (windows in particular) destruction. @@ -65,11 +66,9 @@ class WindowContainer : public Container bool widgetIsVisible(gcn::Widget *widget); /** - * List of widgets that are scheduled to be deleted. + * Set of widgets that are scheduled to be deleted. */ - std::list<gcn::Widget *> mDeathList; + std::set<gcn::Widget *> mScheduledDeletions; }; extern WindowContainer *windowContainer; - -#endif diff --git a/src/gui/windowmenu.cpp b/src/gui/windowmenu.cpp index c6e52ee7..0b2d126f 100644 --- a/src/gui/windowmenu.cpp +++ b/src/gui/windowmenu.cpp @@ -25,7 +25,7 @@ #include "gui/emotepopup.h" #include "gui/skilldialog.h" -#include "gui/specialswindow.h" +#include "gui/abilitieswindow.h" #include "gui/widgets/button.h" #include "gui/widgets/window.h" @@ -61,8 +61,8 @@ WindowMenu::WindowMenu() addButton(N_("Skills"), x, h, "button-icon-skills.png", KeyboardConfig::KEY_WINDOW_SKILL); - if (specialsWindow->hasSpecials()) - addButton(N_("Specials"), x, h, "button-icon-specials.png"); + if (abilitiesWindow->hasAbilities()) + addButton(N_("Abilities"), x, h, "button-icon-abilities.png"); addButton(N_("Social"), x, h, "button-icon-social.png", KeyboardConfig::KEY_WINDOW_SOCIAL); @@ -122,9 +122,9 @@ void WindowMenu::action(const gcn::ActionEvent &event) { window = skillDialog; } - else if (event.getId() == "Specials") + else if (event.getId() == "Abilities") { - window = specialsWindow; + window = abilitiesWindow; } else if (event.getId() == "Social") { diff --git a/src/gui/windowmenu.h b/src/gui/windowmenu.h index 0eabc247..b80b62b0 100644 --- a/src/gui/windowmenu.h +++ b/src/gui/windowmenu.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef WINDOWMENU_H -#define WINDOWMENU_H +#pragma once #include "keyboardconfig.h" @@ -62,5 +61,3 @@ class WindowMenu : public Container, EmotePopup *mEmotePopup = nullptr; }; - -#endif diff --git a/src/gui/worldselectdialog.h b/src/gui/worldselectdialog.h index a1150cb8..702a5616 100644 --- a/src/gui/worldselectdialog.h +++ b/src/gui/worldselectdialog.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef WORLD_SELECT_DIALOG_H -#define WORLD_SELECT_DIALOG_H +#pragma once #include "gui/widgets/window.h" @@ -62,5 +61,3 @@ class WorldSelectDialog : public Window, public gcn::ActionListener, gcn::Button *mChangeLoginButton; gcn::Button *mChooseWorld; }; - -#endif // WORLD_SELECT_DIALOG_H diff --git a/src/guichanfwd.h b/src/guichanfwd.h index 7ef1a419..9c4d8b5f 100644 --- a/src/guichanfwd.h +++ b/src/guichanfwd.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef GUICHANFWD_H -#define GUICHANFWD_H +#pragma once namespace gcn { @@ -97,5 +96,3 @@ namespace gcn class WidgetListener; class Window; } - -#endif diff --git a/src/guild.h b/src/guild.h index e24893b4..cdd3a357 100644 --- a/src/guild.h +++ b/src/guild.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef GUILD_H -#define GUILD_H +#pragma once #include "avatar.h" @@ -170,5 +169,3 @@ private: short mId; bool mCanInviteUsers = false; }; - -#endif // GUILD_H diff --git a/src/imageparticle.cpp b/src/imageparticle.cpp index 9cde91d0..86f955c0 100644 --- a/src/imageparticle.cpp +++ b/src/imageparticle.cpp @@ -27,17 +27,12 @@ ImageParticle::ImageParticle(Map *map, Image *image): Particle(map), - mImage(image) + mImageRef(image) { - if (mImage) - mImage->incRef(); + mImage = mImageRef; } -ImageParticle::~ImageParticle() -{ - if (mImage) - mImage->decRef(); -} +ImageParticle::~ImageParticle() = default; bool ImageParticle::draw(Graphics *graphics, int offsetX, int offsetY) const { diff --git a/src/imageparticle.h b/src/imageparticle.h index ac677389..0f135c33 100644 --- a/src/imageparticle.h +++ b/src/imageparticle.h @@ -19,11 +19,12 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef IMAGEPARTICLE_H -#define IMAGEPARTICLE_H +#pragma once #include "particle.h" +#include "resources/resource.h" + class Image; class Map; @@ -50,6 +51,5 @@ class ImageParticle : public Particle protected: Image *mImage; /**< The image used for this particle. */ + ResourceRef<Image> mImageRef; }; - -#endif diff --git a/src/imagesprite.cpp b/src/imagesprite.cpp deleted file mode 100644 index 9ef27cd1..00000000 --- a/src/imagesprite.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/* - * The Mana Client - * Copyright (C) 2010-2012 The Mana Developers - * - * This file is part of The Mana Client. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "imagesprite.h" - -#include "graphics.h" - -ImageSprite::ImageSprite(Image *image): - mImage(image) -{ - mAlpha = mImage->getAlpha(); -} - -ImageSprite::~ImageSprite() = default; - -bool ImageSprite::draw(Graphics *graphics, int posX, int posY) const -{ - if (mImage->getAlpha() != mAlpha) - mImage->setAlpha(mAlpha); - - return graphics->drawImage(mImage, posX, posY); -} diff --git a/src/imagesprite.h b/src/imagesprite.h deleted file mode 100644 index 4fc69927..00000000 --- a/src/imagesprite.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * The Mana Client - * Copyright (C) 2010-2012 The Mana Developers - * - * This file is part of The Mana Client. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef IMAGESPRITE_H -#define IMAGESPRITE_H - -#include "sprite.h" - -#include "resources/image.h" - -class Graphics; - -class ImageSprite final : public Sprite -{ -public: - ImageSprite(Image *image); - - ~ImageSprite() override; - - bool reset() override - { return false; } - - bool play(const std::string &action) override - { return false; } - - bool update(int time) override - { return false; } - - bool draw(Graphics *graphics, int posX, int posY) const override; - - int getWidth() const override - { return mImage->getWidth(); } - - int getHeight() const override - { return mImage->getHeight(); } - - const Image *getImage() const override - { return mImage; } - - bool setDirection(SpriteDirection direction) override - { return false; } - - int getDuration() const override - { return 0; } - - // Hack to allow the ImageSprite to be used with SubImage instances, which - // are not reference counted. - void releaseImageRef() - { mImage.release(); } - -private: - ResourceRef<Image> mImage; -}; - -#endif // IMAGESPRITE_H diff --git a/src/inventory.cpp b/src/inventory.cpp index e689f8bb..6aa30c00 100644 --- a/src/inventory.cpp +++ b/src/inventory.cpp @@ -159,7 +159,5 @@ void Inventory::removeInventoryListener(InventoryListener* listener) void Inventory::distributeSlotsChangedEvent() { for (auto inventoryListener : mInventoryListeners) - { inventoryListener->slotsChanged(this); - } } diff --git a/src/inventory.h b/src/inventory.h index 7aea3013..40a9403a 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef INVENTORY_H -#define INVENTORY_H +#pragma once #include <list> @@ -125,7 +124,6 @@ class Inventory int getLastUsedSlot() const; void addInventoryListener(InventoryListener* listener); - void removeInventoryListener(InventoryListener* listener); int getType() const @@ -144,5 +142,3 @@ class Inventory int mSize; /**< The max number of inventory items */ int mUsed = 0; /**< THe number of slots in use */ }; - -#endif // INVENTORY_H diff --git a/src/item.cpp b/src/item.cpp index cae545b1..65fda05b 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -24,7 +24,6 @@ #include "configuration.h" #include "event.h" -#include "resources/image.h" #include "resources/iteminfo.h" #include "resources/resourcemanager.h" #include "resources/theme.h" @@ -36,8 +35,6 @@ Item::Item(int id, int quantity, bool equipped): setId(id); } -Item::~Item() = default; - void Item::setId(int id) { mId = id; @@ -48,12 +45,10 @@ void Item::setId(int id) mImage = resman->getImage(paths.getStringValue("itemIcons") + display.image); if (!mImage) + { mImage = Theme::getImageFromTheme(paths.getValue("unknownItemFile", "unknown-item.png")); - - // Remove the automatic reference added by the ResourceManager - if (mImage) - mImage->decRef(); + } } void Item::doEvent(Event::Type eventName) @@ -19,15 +19,12 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef ITEM_H -#define ITEM_H +#pragma once #include "event.h" +#include "resources/image.h" #include "resources/itemdb.h" -#include "resources/resource.h" - -class Image; const int ITEM_ICON_SIZE = 32; @@ -39,7 +36,7 @@ class Item public: Item(int id = -1, int quantity = 0, bool equipped = false); - virtual ~Item(); + virtual ~Item() = default; /** * Sets the item id, identifying the item type. @@ -82,16 +79,6 @@ class Item bool isEquipped() const { return mEquipped; } /** - * Sets whether this item is in equipment. - */ - void setInEquipment(bool inEquipment) { mInEquipment = inEquipment; } - - /** - * Returns whether this item is in equipment. - */ - bool isInEquipment() const { return mInEquipment; } - - /** * Returns whether this item is equippable. */ bool isEquippable() const; @@ -120,8 +107,5 @@ class Item ResourceRef<Image> mImage; /**< Item image. */ int mQuantity; /**< Number of items. */ bool mEquipped; /**< Item is equipped. */ - bool mInEquipment = false; /**< Item is in equipment */ int mInvIndex; /**< Inventory index. */ }; - -#endif // ITEM_H diff --git a/src/itemshortcut.h b/src/itemshortcut.h index 620973b6..fcec3d7d 100644 --- a/src/itemshortcut.h +++ b/src/itemshortcut.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef ITEMSHORTCUT_H -#define ITEMSHORTCUT_H +#pragma once #define SHORTCUT_ITEMS 12 @@ -115,5 +114,3 @@ class ItemShortcut }; extern ItemShortcut *itemShortcut; - -#endif diff --git a/src/joystick.h b/src/joystick.h index 0a5e51a4..c3dea9e1 100644 --- a/src/joystick.h +++ b/src/joystick.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef JOYSTICK_H -#define JOYSTICK_H +#pragma once #include <SDL.h> @@ -99,5 +98,3 @@ class Joystick void doCalibration(); }; - -#endif // JOYSTICK_H diff --git a/src/keyboardconfig.cpp b/src/keyboardconfig.cpp index c4d7a26d..ec5b5e37 100644 --- a/src/keyboardconfig.cpp +++ b/src/keyboardconfig.cpp @@ -153,24 +153,21 @@ void KeyboardConfig::store() void KeyboardConfig::makeDefault() { for (auto &key : mKey) - { key.value = key.defaultValue; - } } bool KeyboardConfig::hasConflicts() { - int i, j; /** * No need to parse the square matrix: only check one triangle * that's enough to detect conflicts */ - for (i = 0; i < KEY_TOTAL; i++) + for (int i = 0; i < KEY_TOTAL; i++) { if (mKey[i].value == KEY_NO_VALUE) continue; - for (j = i, j++; j < KEY_TOTAL; j++) + for (int j = i + 1; j < KEY_TOTAL; j++) { if (mKey[j].value == KEY_NO_VALUE) continue; @@ -220,25 +217,33 @@ const std::string &KeyboardConfig::getKeyCaption(int index) const int KeyboardConfig::getKeyIndex(SDL_Keycode keyValue) const { for (int i = 0; i < KEY_TOTAL; i++) - { if (keyValue == mKey[i].value) - { return i; - } - } + return KEY_NO_VALUE; } +std::string_view KeyboardConfig::getKeyName(std::string_view configName) const +{ + for (auto key : mKey) + { + if (configName == key.configField) + { + if (key.value == KEY_NO_VALUE) + return {}; + return SDL_GetKeyName(key.value); + } + } + + return {}; +} int KeyboardConfig::getKeyEmoteOffset(SDL_Keycode keyValue) const { for (int i = KEY_EMOTE_1; i <= KEY_EMOTE_12; i++) - { if (keyValue == mKey[i].value) - { return i - KEY_EMOTE_1; - } - } + return -1; } diff --git a/src/keyboardconfig.h b/src/keyboardconfig.h index e2d91e29..6fc79ced 100644 --- a/src/keyboardconfig.h +++ b/src/keyboardconfig.h @@ -20,8 +20,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef KEYBOARDCONFIG_H -#define KEYBOARDCONFIG_H +#pragma once #include <cstdint> #include <string> @@ -102,6 +101,11 @@ class KeyboardConfig int getKeyIndex(SDL_Keycode keyValue) const; /** + * Get the key name by providing the keys config name. + */ + std::string_view getKeyName(std::string_view configName) const; + + /** * Get the key function index for an emote by providing the offset value. */ int getKeyEmoteOffset(SDL_Keycode keyValue) const; @@ -235,5 +239,3 @@ class KeyboardConfig }; extern KeyboardConfig keyboard; - -#endif diff --git a/src/localplayer.cpp b/src/localplayer.cpp index e2af5fa3..043e9d25 100644 --- a/src/localplayer.cpp +++ b/src/localplayer.cpp @@ -55,24 +55,18 @@ constexpr unsigned ACTION_TIMEOUT = 182; LocalPlayer *local_player = nullptr; -LocalPlayer::LocalPlayer(int id, int subtype): - Being(id, PLAYER, subtype, nullptr) +LocalPlayer::LocalPlayer(int id, int subtype) + : Being(id, PLAYER, subtype, nullptr) + , mAwayListener(std::make_unique<AwayListener>()) { - listen(Event::AttributesChannel); - - mAwayListener = new AwayListener(); - setShowName(config.showOwnName); - listen(Event::ConfigChannel); listen(Event::ActorSpriteChannel); + listen(Event::AttributesChannel); + listen(Event::ConfigChannel); } -LocalPlayer::~LocalPlayer() -{ - delete mAwayDialog; - delete mAwayListener; -} +LocalPlayer::~LocalPlayer() = default; void LocalPlayer::logic() { @@ -910,7 +904,7 @@ void LocalPlayer::setAttackRange(int range) { mAttackRange = range; } - else if (Net::getNetworkType() == ServerType::TMWATHENA) + else if (Net::getNetworkType() == ServerType::TmwAthena) { // TODO: Fix this to be more generic Item *weapon = PlayerInfo::getEquipment(TmwAthena::EQUIP_FIGHT1_SLOT); @@ -932,7 +926,7 @@ bool LocalPlayer::withinRange(Actor *target, int range) const const Vector &pos = getPosition(); const int dx = abs(targetPos.x - pos.x); const int dy = abs(targetPos.y - pos.y); - return !(dx > range || dy > range); + return dx <= range && dy <= range; } void LocalPlayer::setGotoTarget(Being *target) @@ -1018,27 +1012,38 @@ void LocalPlayer::event(Event::Channel channel, const Event &event) Being::event(channel, event); } -void LocalPlayer::changeAwayMode() +void LocalPlayer::updateStatusEffect(int id, bool newStatus) { - mAwayMode = !mAwayMode; - mAfkTimer.reset(); + Event event(Event::UpdateStatusEffect); + event.setInt("index", id); + event.setBool("newStatus", newStatus); + event.trigger(Event::ActorSpriteChannel); - if (mAwayMode) - { - auto msg = config.afkMessage.empty() ? _("I am away from keyboard") - : config.afkMessage; - mAwayDialog = new OkDialog(_("Away"), msg); - mAwayDialog->addActionListener(mAwayListener); - } + Being::updateStatusEffect(id, newStatus); +} - mAwayDialog = nullptr; +static std::string afkMessage() +{ + return config.afkMessage.empty() ? _("I am away from keyboard") + : config.afkMessage; } -void LocalPlayer::setAway(const std::string &message) +void LocalPlayer::setAwayMode(bool away) { - if (!message.empty()) - config.afkMessage = message; - changeAwayMode(); + if (mAwayMode == away) + return; + + mAwayMode = away; + + if (mAwayMode) + { + mAwayListener->showDialog(afkMessage()); + mAfkTimer.reset(); + } + else + { + mAwayListener->closeDialog(); + } } void LocalPlayer::afkRespond(ChatTab *tab, const std::string &nick) @@ -1046,9 +1051,7 @@ void LocalPlayer::afkRespond(ChatTab *tab, const std::string &nick) if (!mAwayMode || !mAfkTimer.passed()) return; - auto msg = config.afkMessage.empty() ? _("I am away from keyboard") - : config.afkMessage; - msg = strprintf(_("*AFK*: %s"), msg.c_str()); + auto msg = strprintf(_("*AFK*: %s"), afkMessage().c_str()); Net::getChatHandler()->privateMessage(nick, msg); if (!tab) @@ -1064,10 +1067,40 @@ void LocalPlayer::afkRespond(ChatTab *tab, const std::string &nick) mAfkTimer.set(AWAY_MESSAGE_TIMEOUT); } -void AwayListener::action(const gcn::ActionEvent &event) +AwayListener::~AwayListener() { - if (event.getId() == "ok" && local_player->getAwayMode()) + if (mAwayDialog) { - local_player->changeAwayMode(); + mAwayDialog->removeActionListener(this); + mAwayDialog->removeDeathListener(this); + mAwayDialog->scheduleDelete(); } } + +void AwayListener::showDialog(const std::string &message) +{ + if (mAwayDialog) + return; + + mAwayDialog = new OkDialog(_("Away"), message); + mAwayDialog->addActionListener(this); + mAwayDialog->addDeathListener(this); +} + +void AwayListener::closeDialog() +{ + if (mAwayDialog) + mAwayDialog->scheduleDelete(); +} + +void AwayListener::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "ok") + local_player->setAwayMode(false); +} + +void AwayListener::death(const gcn::Event &event) +{ + if (mAwayDialog == event.getSource()) + mAwayDialog = nullptr; +} diff --git a/src/localplayer.h b/src/localplayer.h index 34021cbd..a5b08449 100644 --- a/src/localplayer.h +++ b/src/localplayer.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef LOCALPLAYER_H -#define LOCALPLAYER_H +#pragma once #include "being.h" @@ -29,6 +28,9 @@ #include "utils/time.h" #include <guichan/actionlistener.hpp> +#include <guichan/deathlistener.hpp> + +#include <memory> class ChatTab; class FloorItem; @@ -36,10 +38,20 @@ class ImageSet; class Map; class OkDialog; -class AwayListener : public gcn::ActionListener +class AwayListener : public gcn::ActionListener, public gcn::DeathListener { public: + AwayListener() = default; + ~AwayListener() override; + + void showDialog(const std::string &message); + void closeDialog(); + void action(const gcn::ActionEvent &event) override; + void death(const gcn::Event &event) override; + + private: + OkDialog *mAwayDialog = nullptr; }; /** @@ -62,8 +74,7 @@ enum class LocalPlayer final : public Being { public: - LocalPlayer(int id= 65535, int subtype = 0); - + LocalPlayer(int id = 65535, int subtype = 0); ~LocalPlayer() override; void logic() override; @@ -171,13 +182,10 @@ class LocalPlayer final : public Being bool isPathSetByMouse() const { return mPathSetByMouse; } - void changeAwayMode(); - + void setAwayMode(bool away); bool getAwayMode() const { return mAwayMode; } - void setAway(const std::string &message); - void afkRespond(ChatTab *tab, const std::string &nick); void addMessageToQueue(const std::string &message, @@ -186,6 +194,8 @@ class LocalPlayer final : public Being void event(Event::Channel channel, const Event &event) override; protected: + void updateStatusEffect(int id, bool newStatus) override; + /** Make the character starts to walk. */ void startWalking(unsigned char dir); @@ -229,12 +239,9 @@ class LocalPlayer final : public Being bool mShowIp = false; - AwayListener *mAwayListener; - OkDialog *mAwayDialog = nullptr; + std::unique_ptr<AwayListener> mAwayListener; Timer mAfkTimer; bool mAwayMode = false; }; extern LocalPlayer *local_player; - -#endif // LOCALPLAYER_H @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef LOG_H -#define LOG_H +#pragma once #include <fstream> @@ -69,5 +68,3 @@ class Logger }; extern Logger *logger; - -#endif diff --git a/src/main.cpp b/src/main.cpp index b27152e5..3531356f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,12 +23,13 @@ #include "client.h" +#include "utils/filesystem.h" #include "utils/gettext.h" +#include "utils/stringutils.h" #include "utils/xml.h" #include <getopt.h> #include <iostream> -#include <physfs.h> #ifdef __MINGW32__ #include <windows.h> @@ -168,7 +169,7 @@ static void parseOptions(int argc, char *argv[], Client::Options &options) break; case 'y': options.serverType = ServerInfo::parseType(optarg); - if (options.serverType == ServerType::UNKNOWN) + if (options.serverType == ServerType::Unknown) { std::cerr << _("Invalid server type, expected one of: tmwathena, manaserv") << std::endl; options.exitWithError = true; @@ -257,12 +258,12 @@ int main(int argc, char *argv[]) initInternationalization(); // Initialize PhysicsFS - if (!PHYSFS_init(argv[0])) { + if (!FS::init(argv[0])) { std::cout << "Error while initializing PhysFS: " - << PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()) << std::endl; + << FS::getLastError() << std::endl; return 1; } - atexit((void(*)()) PHYSFS_deinit); + atexit((void(*)()) FS::deinit); XML::init(); @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef MAIN_H -#define MAIN_H +#pragma once /** * \mainpage @@ -88,5 +87,3 @@ #ifndef PKG_DATADIR #define PKG_DATADIR "" #endif - -#endif diff --git a/src/map.cpp b/src/map.cpp index 929d66f8..908d6178 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -245,7 +245,7 @@ void Map::initializeAmbientLayers() auto addAmbientLayer = [=](const std::string &name, std::vector<AmbientLayer> &list) { - if (auto img = resman->getImageRef(getProperty(name + "image"))) + if (auto img = resman->getImage(getProperty(name + "image"))) { auto &ambientLayer = list.emplace_back(img); ambientLayer.mParallax = getFloatProperty(name + "parallax"); @@ -843,7 +843,7 @@ Path Map::findPath(int startX, int startY, int destX, int destY, // It costs extra to walk through a being (needs to be enough // to make it more attractive to walk around). // N.B.: Specific to TmwAthena for now. - if (Net::getNetworkType() == ServerType::TMWATHENA && + if (Net::getNetworkType() == ServerType::TmwAthena && occupied(x, y)) { Gcost += 3 * basicCost; @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef MAP_H -#define MAP_H +#pragma once #include "actor.h" #include "position.h" @@ -421,5 +420,3 @@ class Map : public Properties int mMask = 1; }; - -#endif diff --git a/src/net/specialhandler.h b/src/net/abilityhandler.h index fb8ffdfd..8e61d0c5 100644 --- a/src/net/specialhandler.h +++ b/src/net/abilityhandler.h @@ -19,25 +19,22 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SPECIALHANDLER_H -#define SPECIALHANDLER_H - -#include <iosfwd> +#pragma once namespace Net { -class SpecialHandler + +class AbilityHandler { public: - virtual ~SpecialHandler () {} + virtual ~AbilityHandler () {} virtual void use(int id) = 0; - virtual void use(int id, int level, int beingId) = 0; + virtual void useOn(int id, int beingId) = 0; - virtual void use(int id, int level, int x, int y) = 0; + virtual void useAt(int id, int x, int y) = 0; - virtual void use(int id, const std::string &map) = 0; + virtual void useInDirection(int id, int direction) = 0; }; -} -#endif // SPECIALHANDLER_H +} // namespace Net diff --git a/src/net/adminhandler.h b/src/net/adminhandler.h index 492918a7..87853317 100644 --- a/src/net/adminhandler.h +++ b/src/net/adminhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef ADMINHANDLER_H -#define ADMINHANDLER_H +#pragma once #include <string> @@ -39,5 +38,3 @@ class AdminHandler }; } // namespace Net - -#endif // ADMINHANDLER_H diff --git a/src/net/charhandler.h b/src/net/charhandler.h index 72e67739..3f0c096f 100644 --- a/src/net/charhandler.h +++ b/src/net/charhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef CHARHANDLER_H -#define CHARHANDLER_H +#pragma once #include "localplayer.h" #include "playerinfo.h" @@ -111,5 +110,3 @@ class CharHandler }; } // namespace Net - -#endif // CHARHANDLER_H diff --git a/src/net/chathandler.h b/src/net/chathandler.h index 382ac39a..5524f458 100644 --- a/src/net/chathandler.h +++ b/src/net/chathandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef CHATHANDLER_H -#define CHATHANDLER_H +#pragma once #include <string> @@ -62,5 +61,3 @@ class ChatHandler virtual void requestOnlineList() = 0; }; } - -#endif // CHATHANDLER_H diff --git a/src/net/download.cpp b/src/net/download.cpp index 8a41ebfa..7aab3b2f 100644 --- a/src/net/download.cpp +++ b/src/net/download.cpp @@ -31,7 +31,7 @@ #include <zlib.h> -const char *DOWNLOAD_ERROR_MESSAGE_THREAD = "Could not create download thread!"; +constexpr char DOWNLOAD_ERROR_MESSAGE_THREAD[] = "Could not create download thread!"; namespace Net { @@ -59,32 +59,26 @@ unsigned long Download::fadler32(FILE *file) return adler; } - -Download::Download(void *ptr, const std::string &url, - DownloadUpdate updateFunction): - mPtr(ptr), - mUrl(url), - mUpdateFunction(updateFunction) +Download::Download(const std::string &url) + : mUrl(url) { - mError = (char*) malloc(CURL_ERROR_SIZE); mError[0] = 0; - - mOptions.cancel = false; } Download::~Download() { - if (mHeaders) - curl_slist_free_all(mHeaders); + mCancel = true; + SDL_WaitThread(mThread, nullptr); - int status; - SDL_WaitThread(mThread, &status); - free(mError); + curl_slist_free_all(mHeaders); + free(mBuffer); } -void Download::addHeader(const std::string &header) +void Download::addHeader(const char *header) { - mHeaders = curl_slist_append(mHeaders, header.c_str()); + assert(!mThread); // Cannot add headers after starting download + + mHeaders = curl_slist_append(mHeaders, header); } void Download::noCache() @@ -96,19 +90,24 @@ void Download::noCache() void Download::setFile(const std::string &filename, std::optional<unsigned long> adler32) { - mOptions.memoryWrite = false; + assert(!mThread); // Cannot set file after starting download + + mMemoryWrite = false; mFileName = filename; mAdler = adler32; } -void Download::setWriteFunction(WriteFunction write) +void Download::setUseBuffer() { - mOptions.memoryWrite = true; - mWriteFunction = write; + assert(!mThread); // Cannot set write function after starting download + + mMemoryWrite = true; } bool Download::start() { + assert(!mThread); // Download already started + logger->log("Starting download: %s", mUrl.c_str()); mThread = SDL_CreateThread(downloadThread, "Download", this); @@ -116,9 +115,8 @@ bool Download::start() if (!mThread) { logger->log("%s", DOWNLOAD_ERROR_MESSAGE_THREAD); - strcpy(mError, DOWNLOAD_ERROR_MESSAGE_THREAD); - mUpdateFunction(mPtr, DOWNLOAD_STATUS_THREAD_ERROR, 0, 0); - + strncpy(mError, DOWNLOAD_ERROR_MESSAGE_THREAD, CURL_ERROR_SIZE - 1); + mState.lock()->status = DownloadStatus::Error; return false; } @@ -128,196 +126,186 @@ bool Download::start() void Download::cancel() { logger->log("Canceling download: %s", mUrl.c_str()); - - mOptions.cancel = true; - if (mThread && SDL_GetThreadID(mThread) != 0) - { - SDL_WaitThread(mThread, nullptr); - mThread = nullptr; - } + mCancel = true; } -const char *Download::getError() const +std::string_view Download::getBuffer() const { - return mError; + assert(mMemoryWrite); // Buffer not used + return std::string_view(mBuffer, mDownloadedBytes); } +/** + * A libcurl callback for reporting progress. + */ int Download::downloadProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { auto *d = reinterpret_cast<Download*>(clientp); - if (d->mOptions.cancel) + auto state = d->mState.lock(); + state->status = DownloadStatus::InProgress; + state->progress = 0.0f; + if (dltotal > 0) + state->progress = static_cast<float>(dlnow) / dltotal; + + return d->mCancel; +} + +/** + * A libcurl callback for writing to memory. + */ +size_t Download::writeBuffer(char *ptr, size_t size, size_t nmemb, void *stream) +{ + auto *d = reinterpret_cast<Download *>(stream); + + const size_t totalMem = size * nmemb; + d->mBuffer = (char *) realloc(d->mBuffer, d->mDownloadedBytes + totalMem); + if (d->mBuffer) { - return d->mUpdateFunction(d->mPtr, DOWNLOAD_STATUS_CANCELLED, - (size_t) dltotal, (size_t) dlnow); - return -5; + memcpy(d->mBuffer + d->mDownloadedBytes, ptr, totalMem); + d->mDownloadedBytes += totalMem; } - return d->mUpdateFunction(d->mPtr, DOWNLOAD_STATUS_IDLE, - (size_t) dltotal, (size_t) dlnow); + return totalMem; } int Download::downloadThread(void *ptr) { - int attempts = 0; - bool complete = false; auto *d = reinterpret_cast<Download*>(ptr); - CURLcode res; + bool complete = false; std::string outFilename; - if (!d) - { - return 0; - } - if (!d->mOptions.memoryWrite) - { + if (!d->mMemoryWrite) outFilename = d->mFileName + ".part"; - } - while (attempts < 3 && !complete && !d->mOptions.cancel) + for (int attempts = 0; attempts < 3 && !complete && !d->mCancel; ++attempts) { - FILE *file = nullptr; + CURL *curl = curl_easy_init(); + if (!curl) + break; - d->mUpdateFunction(d->mPtr, DOWNLOAD_STATUS_STARTING, 0, 0); + logger->log("Downloading: %s", d->mUrl.c_str()); - if (d->mOptions.cancel) - { - d->mThread = nullptr; - return 0; - } + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, d->mHeaders); - d->mCurl = curl_easy_init(); + FILE *file = nullptr; - if (d->mCurl && !d->mOptions.cancel) + if (d->mMemoryWrite) + { + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &Download::writeBuffer); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, ptr); + } + else { - logger->log("Downloading: %s", d->mUrl.c_str()); + file = fopen(outFilename.c_str(), "w+b"); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, file); + } - curl_easy_setopt(d->mCurl, CURLOPT_FOLLOWLOCATION, 1); - curl_easy_setopt(d->mCurl, CURLOPT_HTTPHEADER, d->mHeaders); + const std::string appShort = branding.getStringValue("appShort"); + const std::string userAgent = + strprintf(PACKAGE_EXTENDED_VERSION, appShort.c_str()); - if (d->mOptions.memoryWrite) - { - curl_easy_setopt(d->mCurl, CURLOPT_FAILONERROR, 1); - curl_easy_setopt(d->mCurl, CURLOPT_WRITEFUNCTION, d->mWriteFunction); - curl_easy_setopt(d->mCurl, CURLOPT_WRITEDATA, d->mPtr); - } - else - { - file = fopen(outFilename.c_str(), "w+b"); - curl_easy_setopt(d->mCurl, CURLOPT_WRITEDATA, file); - } + curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent.c_str()); + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, d->mError); + curl_easy_setopt(curl, CURLOPT_URL, d->mUrl.c_str()); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); + curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, &Download::downloadProgress); + curl_easy_setopt(curl, CURLOPT_XFERINFODATA, ptr); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 15); - const std::string appShort = branding.getStringValue("appShort"); - const std::string userAgent = - strprintf(PACKAGE_EXTENDED_VERSION, appShort.c_str()); + const CURLcode res = curl_easy_perform(curl); + curl_easy_cleanup(curl); - curl_easy_setopt(d->mCurl, CURLOPT_USERAGENT, userAgent.c_str()); - curl_easy_setopt(d->mCurl, CURLOPT_ERRORBUFFER, d->mError); - curl_easy_setopt(d->mCurl, CURLOPT_URL, d->mUrl.c_str()); - curl_easy_setopt(d->mCurl, CURLOPT_NOPROGRESS, 0); - curl_easy_setopt(d->mCurl, CURLOPT_XFERINFOFUNCTION, downloadProgress); - curl_easy_setopt(d->mCurl, CURLOPT_XFERINFODATA, ptr); - curl_easy_setopt(d->mCurl, CURLOPT_NOSIGNAL, 1); - curl_easy_setopt(d->mCurl, CURLOPT_CONNECTTIMEOUT, 15); + if (res == CURLE_ABORTED_BY_CALLBACK) + { + d->mCancel = true; - if ((res = curl_easy_perform(d->mCurl)) != 0 && !d->mOptions.cancel) + if (file) { - switch (res) - { - case CURLE_ABORTED_BY_CALLBACK: - d->mOptions.cancel = true; - break; - case CURLE_COULDNT_CONNECT: - default: - logger->log("curl error %d: %s host: %s", - res, d->mError, d->mUrl.c_str()); - break; - } + fclose(file); + ::remove(outFilename.c_str()); + } - if (d->mOptions.cancel) - { - break; - } + break; + } - d->mUpdateFunction(d->mPtr, DOWNLOAD_STATUS_ERROR, 0, 0); + if (res != CURLE_OK) + { + logger->log("curl error %d: %s host: %s", + res, d->mError, d->mUrl.c_str()); - if (!d->mOptions.memoryWrite) - { - fclose(file); - ::remove(outFilename.c_str()); - } - attempts++; - continue; + if (file) + { + fclose(file); + ::remove(outFilename.c_str()); } - curl_easy_cleanup(d->mCurl); + break; + } - if (!d->mOptions.memoryWrite) + if (!d->mMemoryWrite) + { + // Check the checksum if available + if (d->mAdler) { - // Don't check resources.xml checksum - if (d->mAdler) - { - unsigned long adler = fadler32(file); + unsigned long adler = fadler32(file); - if (d->mAdler != adler) - { + if (d->mAdler != adler) + { + if (file) fclose(file); - // Remove the corrupted file - ::remove(d->mFileName.c_str()); - logger->log("Checksum for file %s failed: (%lx/%lx)", - d->mFileName.c_str(), - adler, *d->mAdler); - attempts++; - continue; // Bail out here to avoid the renaming - } + // Remove the corrupted file + ::remove(outFilename.c_str()); + logger->log("Checksum for file %s failed: (%lx/%lx)", + d->mFileName.c_str(), + adler, *d->mAdler); + + continue; // Bail out here to avoid the renaming } + } + + if (file) fclose(file); - // Any existing file with this name is deleted first, otherwise - // the rename will fail on Windows. - ::remove(d->mFileName.c_str()); - ::rename(outFilename.c_str(), d->mFileName.c_str()); + // Any existing file with this name is deleted first, otherwise + // the rename will fail on Windows. + ::remove(d->mFileName.c_str()); + ::rename(outFilename.c_str(), d->mFileName.c_str()); - // Check if we can open it and no errors were encountered - // during renaming - file = fopen(d->mFileName.c_str(), "rb"); - if (file) - { - fclose(file); - complete = true; - } - } - else + // Check if we can open it and no errors were encountered + // during renaming + file = fopen(d->mFileName.c_str(), "rb"); + if (file) { - // It's stored in memory, we're done + fclose(file); + file = nullptr; complete = true; } } - if (d->mOptions.cancel) + else { - d->mThread = nullptr; - return 0; + // It's stored in memory, we're done + complete = true; } - attempts++; - } - if (d->mOptions.cancel) - { - // Nothing to do... - } - else if (!complete || attempts >= 3) - { - d->mUpdateFunction(d->mPtr, DOWNLOAD_STATUS_ERROR, 0, 0); + if (file) + fclose(file); } + + auto state = d->mState.lock(); + if (d->mCancel) + state->status = DownloadStatus::Canceled; + else if (complete) + state->status = DownloadStatus::Complete; else - { - d->mUpdateFunction(d->mPtr, DOWNLOAD_STATUS_COMPLETE, 0, 0); - } + state->status = DownloadStatus::Error; - d->mThread = nullptr; return 0; } diff --git a/src/net/download.h b/src/net/download.h index 0ce8cc8a..e9483fa5 100644 --- a/src/net/download.h +++ b/src/net/download.h @@ -18,44 +18,41 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include <cstdlib> // pulls in int64_t +#include "utils/mutex.h" + #include <cstdio> -#include <string> #include <optional> +#include <string> #include <curl/curl.h> -#ifndef NET_DOWNLOAD_H -#define NET_DOWNLOAD_H +#pragma once -enum DownloadStatus +enum class DownloadStatus { - DOWNLOAD_STATUS_CANCELLED = -3, - DOWNLOAD_STATUS_THREAD_ERROR = -2, - DOWNLOAD_STATUS_ERROR = -1, - DOWNLOAD_STATUS_STARTING = 0, - DOWNLOAD_STATUS_IDLE, - DOWNLOAD_STATUS_COMPLETE + InProgress, + Canceled, + Error, + Complete }; -using DownloadUpdate = int (*)(void *, DownloadStatus, size_t, size_t); - -// Matches what CURL expects -using WriteFunction = size_t (*)(void *, size_t, size_t, void *); - struct SDL_Thread; -using CURL = void; -struct curl_slist; namespace Net { + class Download { public: - Download(void *ptr, const std::string &url, DownloadUpdate updateFunction); + struct State + { + DownloadStatus status = DownloadStatus::InProgress; + float progress = 0.0f; + }; + Download(const std::string &url); ~Download(); - void addHeader(const std::string &header); + void addHeader(const char *header); /** * Convience method for adding no-cache headers. @@ -65,47 +62,66 @@ class Download void setFile(const std::string &filename, std::optional<unsigned long> adler32 = {}); - void setWriteFunction(WriteFunction write); + void setUseBuffer(); /** * Starts the download thread. - * @returns true if thread was created - * false if the thread could not be made or download wasn't - * properly setup + * @returns whether the thread could be created */ bool start(); /** - * Cancels the download. Returns immediately, the cancelled status will - * be noted in the next avialable update call. + * Cancels the download. Returns immediately, the canceled status will + * be noted in the next available update call. */ void cancel(); + /** + * Returns a view on the downloaded data. + */ + std::string_view getBuffer() const; + + State getState(); + const char *getError() const; static unsigned long fadler32(FILE *file); private: - static int downloadThread(void *ptr); static int downloadProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow); - void *mPtr; + + static size_t writeBuffer(char *ptr, size_t size, size_t nmemb, + void *stream); + + static int downloadThread(void *ptr); + + ThreadSafe<State> mState; std::string mUrl; - struct { - unsigned cancel : 1; - unsigned memoryWrite: 1; - } mOptions; + bool mCancel = false; + bool mMemoryWrite = false; std::string mFileName; - WriteFunction mWriteFunction = nullptr; std::optional<unsigned long> mAdler; - DownloadUpdate mUpdateFunction; SDL_Thread *mThread = nullptr; - CURL *mCurl = nullptr; curl_slist *mHeaders = nullptr; - char *mError; + char mError[CURL_ERROR_SIZE]; + + /** Byte count currently downloaded in mMemoryBuffer. */ + size_t mDownloadedBytes = 0; + + /** Buffer for files downloaded to memory. */ + char *mBuffer = nullptr; }; -} // namespace Net +inline Download::State Download::getState() +{ + return *mState.lock(); +} -#endif // NET_DOWNLOAD_H +inline const char *Download::getError() const +{ + return mError; +} + +} // namespace Net diff --git a/src/net/gamehandler.h b/src/net/gamehandler.h index 3f47aba5..08c6c33f 100644 --- a/src/net/gamehandler.h +++ b/src/net/gamehandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef MAPHANDLER_H -#define MAPHANDLER_H +#pragma once #include <iosfwd> @@ -58,5 +57,3 @@ class GameHandler }; } // namespace Net - -#endif // MAPHANDLER_H diff --git a/src/net/generalhandler.h b/src/net/generalhandler.h index 76cde7ce..93d76421 100644 --- a/src/net/generalhandler.h +++ b/src/net/generalhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef GENERALHANDLER_H -#define GENERALHANDLER_H +#pragma once namespace Net { @@ -36,10 +35,6 @@ class GeneralHandler virtual void unload() = 0; virtual void flushNetwork() = 0; - - virtual void clearHandlers() = 0; }; } // namespace Net - -#endif // GENERALHANDLER_H diff --git a/src/net/guildhandler.h b/src/net/guildhandler.h index 00eae99c..37127599 100644 --- a/src/net/guildhandler.h +++ b/src/net/guildhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef GUILDHANDLER_H -#define GUILDHANDLER_H +#pragma once #include "guild.h" @@ -64,5 +63,3 @@ class GuildHandler }; } - -#endif // GUILDHANDLER_H diff --git a/src/net/inventoryhandler.h b/src/net/inventoryhandler.h index 8a67a7db..323dec6c 100644 --- a/src/net/inventoryhandler.h +++ b/src/net/inventoryhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef INVENTORYHANDLER_H -#define INVENTORYHANDLER_H +#pragma once #include "equipment.h" #include "inventory.h" @@ -67,8 +66,6 @@ class InventoryHandler public: virtual ~InventoryHandler() {} - virtual bool canSplit(const Item *item) = 0; - // TODO: fix/remove me virtual size_t getSize(int type) const = 0; @@ -99,5 +96,3 @@ class InventoryHandler }; } // namespace Net - -#endif // INVENTORYHANDLER_H diff --git a/src/net/logindata.h b/src/net/logindata.h index 34b259fb..4af402ec 100644 --- a/src/net/logindata.h +++ b/src/net/logindata.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef LOGINDATA_H -#define LOGINDATA_H +#pragma once #include "being.h" @@ -40,7 +39,7 @@ public: std::string email; std::string captchaResponse; - Gender gender = Gender::UNSPECIFIED; + Gender gender = Gender::Unspecified; bool remember; /**< Whether to store the username. */ bool registerLogin; /**< Whether an account is being registered. */ @@ -64,9 +63,7 @@ public: updateHost.clear(); email.clear(); captchaResponse.clear(); - gender = Gender::UNSPECIFIED; + gender = Gender::Unspecified; resetCharacterSlots(); } }; - -#endif // LOGINDATA_H diff --git a/src/net/loginhandler.h b/src/net/loginhandler.h index 43d15b77..992e0709 100644 --- a/src/net/loginhandler.h +++ b/src/net/loginhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef LOGINHANDLER_H -#define LOGINHANDLER_H +#pragma once #include "net/logindata.h" #include "net/serverinfo.h" @@ -98,5 +97,3 @@ class LoginHandler }; } // namespace Net - -#endif // LOGINHANDLER_H diff --git a/src/net/manaserv/specialhandler.cpp b/src/net/manaserv/abilityhandler.cpp index 0a477ff8..a9ce8e37 100644 --- a/src/net/manaserv/specialhandler.cpp +++ b/src/net/manaserv/abilityhandler.cpp @@ -19,57 +19,60 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include "net/manaserv/specialhandler.h" +#include "net/manaserv/abilityhandler.h" #include "net/manaserv/connection.h" #include "net/manaserv/messagein.h" #include "net/manaserv/messageout.h" #include "net/manaserv/manaserv_protocol.h" -extern Net::SpecialHandler *specialHandler; +extern Net::AbilityHandler *abilityHandler; namespace ManaServ { extern Connection *gameServerConnection; -SpecialHandler::SpecialHandler() +AbilityHandler::AbilityHandler() { - specialHandler = this; + abilityHandler = this; } -void SpecialHandler::handleMessage(MessageIn &msg) +void AbilityHandler::handleMessage(MessageIn &msg) { // TODO } -void SpecialHandler::use(int id) +void AbilityHandler::use(int id) { - MessageOut msg(PGMSG_USE_SPECIAL_ON_BEING); + MessageOut msg(PGMSG_USE_ABILITY_ON_BEING); msg.writeInt8(id); msg.writeInt16(0); gameServerConnection->send(msg); } -void SpecialHandler::use(int id, int level, int beingId) +void AbilityHandler::useOn(int id, int beingId) { - MessageOut msg(PGMSG_USE_SPECIAL_ON_BEING); + MessageOut msg(PGMSG_USE_ABILITY_ON_BEING); msg.writeInt8(id); msg.writeInt16(beingId); gameServerConnection->send(msg); } -void SpecialHandler::use(int id, int level, int x, int y) +void AbilityHandler::useAt(int id, int x, int y) { - MessageOut msg(PGMSG_USE_SPECIAL_ON_POINT); + MessageOut msg(PGMSG_USE_ABILITY_ON_POINT); msg.writeInt8(id); msg.writeInt16(x); msg.writeInt16(y); gameServerConnection->send(msg); } -void SpecialHandler::use(int id, const std::string &map) +void AbilityHandler::useInDirection(int id, int direction) { - // TODO + MessageOut msg(PGMSG_USE_ABILITY_ON_DIRECTION); + msg.writeInt8(id); + msg.writeInt8(direction); + gameServerConnection->send(msg); } } // namespace ManaServ diff --git a/src/net/manaserv/specialhandler.h b/src/net/manaserv/abilityhandler.h index dbd203d8..e8263989 100644 --- a/src/net/manaserv/specialhandler.h +++ b/src/net/manaserv/abilityhandler.h @@ -19,31 +19,28 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_MANASERV_SKILLHANDLER_H -#define NET_MANASERV_SKILLHANDLER_H +#pragma once -#include "net/specialhandler.h" +#include "net/abilityhandler.h" #include "net/manaserv/messagehandler.h" namespace ManaServ { -class SpecialHandler final : public MessageHandler, public Net::SpecialHandler +class AbilityHandler final : public MessageHandler, public Net::AbilityHandler { public: - SpecialHandler(); + AbilityHandler(); void handleMessage(MessageIn &msg) override; void use(int id) override; - void use(int id, int level, int beingId) override; + void useOn(int id, int beingId) override; - void use(int id, int level, int x, int y) override; + void useAt(int id, int x, int y) override; - void use(int id, const std::string &map) override; + void useInDirection(int id, int direction) override; }; } // namespace ManaServ - -#endif // NET_MANASERV_SKILLHANDLER_H diff --git a/src/net/manaserv/adminhandler.cpp b/src/net/manaserv/adminhandler.cpp index 78e6acb2..ef155a38 100644 --- a/src/net/manaserv/adminhandler.cpp +++ b/src/net/manaserv/adminhandler.cpp @@ -31,17 +31,11 @@ extern Connection *chatServerConnection; AdminHandler::AdminHandler() { - static const uint16_t _messages[] = - { - 0 - }; - handledMessages = _messages; adminHandler = this; } void AdminHandler::handleMessage(MessageIn &msg) { - } void AdminHandler::kick(const std::string &name) diff --git a/src/net/manaserv/adminhandler.h b/src/net/manaserv/adminhandler.h index fd0c7de0..bc92872b 100644 --- a/src/net/manaserv/adminhandler.h +++ b/src/net/manaserv/adminhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_MANASERV_ADMINHANDLER_H -#define NET_MANASERV_ADMINHANDLER_H +#pragma once #include "net/adminhandler.h" @@ -43,5 +42,3 @@ class AdminHandler final : public Net::AdminHandler, public MessageHandler }; } // namespace ManaServ - -#endif diff --git a/src/net/manaserv/beinghandler.cpp b/src/net/manaserv/beinghandler.cpp index 186239c1..9bb52ac1 100644 --- a/src/net/manaserv/beinghandler.cpp +++ b/src/net/manaserv/beinghandler.cpp @@ -23,6 +23,7 @@ #include "actorspritemanager.h" #include "being.h" +#include "effectmanager.h" #include "localplayer.h" #include "gui/okdialog.h" @@ -34,6 +35,9 @@ #include "net/manaserv/playerhandler.h" #include "net/manaserv/manaserv_protocol.h" +#include "playerrelations.h" +#include "resources/abilitydb.h" +#include "resources/emotedb.h" #include "resources/hairdb.h" #include "utils/gettext.h" @@ -45,10 +49,13 @@ namespace ManaServ { BeingHandler::BeingHandler() { static const Uint16 _messages[] = { - GPMSG_BEING_ATTACK, GPMSG_BEING_ENTER, GPMSG_BEING_LEAVE, + GPMSG_BEING_EMOTE, GPMSG_BEINGS_MOVE, + GPMSG_BEING_ABILITY_POINT, + GPMSG_BEING_ABILITY_BEING, + GPMSG_BEING_ABILITY_DIRECTION, GPMSG_BEINGS_DAMAGE, GPMSG_BEING_ACTION_CHANGE, GPMSG_BEING_LOOKS_CHANGE, @@ -68,11 +75,20 @@ void BeingHandler::handleMessage(MessageIn &msg) case GPMSG_BEING_LEAVE: handleBeingLeaveMessage(msg); break; + case GPMSG_BEING_EMOTE: + handleBeingEmoteMessage(msg); + break; case GPMSG_BEINGS_MOVE: handleBeingsMoveMessage(msg); break; - case GPMSG_BEING_ATTACK: - handleBeingAttackMessage(msg); + case GPMSG_BEING_ABILITY_POINT: + handleBeingAbilityPointMessage(msg); + break; + case GPMSG_BEING_ABILITY_BEING: + handleBeingAbilityBeingMessage(msg); + break; + case GPMSG_BEING_ABILITY_DIRECTION: + handleBeingAbilityDirectionMessage(msg); break; case GPMSG_BEINGS_DAMAGE: handleBeingsDamageMessage(msg); @@ -91,17 +107,40 @@ void BeingHandler::handleMessage(MessageIn &msg) static void handleLooks(Being *being, MessageIn &msg) { - int lookChanges = msg.readInt8(); + const int hairStyle = msg.readInt8(); + const int hairColor = msg.readInt8(); + being->setSprite(SPRITE_LAYER_HAIR, hairStyle * -1, + hairDB.getHairColor(hairColor)); + + std::map<unsigned, int> equippedSlots; + + if (msg.getUnreadLength() > 1) { + int equippedSlotCount = msg.readInt8(); + while (equippedSlotCount-- > 0) { + unsigned slot = msg.readInt8(); + int itemId = msg.readInt16(); + equippedSlots[slot] = itemId; + } + } - if (lookChanges <= 0) - return; + unsigned endSlot = equippedSlots.empty() ? 0 : equippedSlots.rbegin()->first + 1; + if (being->getSpriteCount() > endSlot + FIXED_SPRITE_LAYER_SIZE) + endSlot = being->getSpriteCount() - FIXED_SPRITE_LAYER_SIZE; - while (lookChanges-- > 0) + for (unsigned slot = 0; slot < endSlot; ++slot) { - unsigned int slotTypeId = msg.readInt8(); - being->setSprite(slotTypeId + FIXED_SPRITE_LAYER_SIZE, - msg.readInt16(), "", - Net::getInventoryHandler()->isWeaponSlot(slotTypeId)); + auto it = equippedSlots.find(slot); + if (it == equippedSlots.end()) + { + being->setSprite(slot + FIXED_SPRITE_LAYER_SIZE, 0); + } + else + { + being->setSprite(slot + FIXED_SPRITE_LAYER_SIZE, + it->second, + std::string(), + Net::getInventoryHandler()->isWeaponSlot(slot)); + } } } @@ -113,14 +152,19 @@ void BeingHandler::handleBeingEnterMessage(MessageIn &msg) int px = msg.readInt16(); int py = msg.readInt16(); auto direction = (BeingDirection)msg.readInt8(); - Gender gender; - int genderAsInt = msg.readInt8(); - if (genderAsInt == GENDER_FEMALE) - gender = Gender::FEMALE; - else if (genderAsInt == GENDER_MALE) - gender = Gender::MALE; - else - gender = Gender::UNSPECIFIED; + + Gender gender = Gender::Unspecified; + switch (getGender(msg.readInt8())) { + case GENDER_MALE: + gender = Gender::Male; + break; + case GENDER_FEMALE: + gender = Gender::Female; + break; + case GENDER_UNSPECIFIED: + break; + } + Being *being; switch (type) @@ -139,9 +183,7 @@ void BeingHandler::handleBeingEnterMessage(MessageIn &msg) ActorSprite::PLAYER, 0); being->setName(name); } - int hs = msg.readInt8(), hc = msg.readInt8(); - being->setSprite(SPRITE_LAYER_HAIR, hs * -1, - hairDB.getHairColor(hc)); + handleLooks(being, msg); } break; @@ -152,7 +194,8 @@ void BeingHandler::handleBeingEnterMessage(MessageIn &msg) being = actorSpriteManager->createBeing(id, type == OBJECT_MONSTER ? ActorSprite::MONSTER : ActorSprite::NPC, subtype); std::string name = msg.readString(); - if (name.length() > 0) being->setName(name); + if (!name.empty()) + being->setName(name); } break; default: @@ -175,6 +218,19 @@ void BeingHandler::handleBeingLeaveMessage(MessageIn &msg) actorSpriteManager->destroyActor(being); } +void BeingHandler::handleBeingEmoteMessage(MessageIn &msg) +{ + Being *being = actorSpriteManager->findBeing(msg.readInt16()); + if (!being) + return; + + if (player_relations.hasPermission(being, PlayerPermissions::EMOTE)) + { + const int fx = EmoteDB::get(msg.readInt8() - 1).effectId; + effectManager->trigger(fx, being); + } +} + void BeingHandler::handleBeingsMoveMessage(MessageIn &msg) { while (msg.getUnreadLength()) @@ -235,18 +291,51 @@ void BeingHandler::handleBeingsMoveMessage(MessageIn &msg) } } -void BeingHandler::handleBeingAttackMessage(MessageIn &msg) +void BeingHandler::handleBeingAbilityPointMessage(MessageIn &msg) { Being *being = actorSpriteManager->findBeing(msg.readInt16()); - const auto direction = (BeingDirection) msg.readInt8(); - const int attackId = msg.readInt8(); + if (!being) + return; + + const int abilityId = msg.readInt8(); + const int x = msg.readInt16(); + const int y = msg.readInt16(); + + being->lookAt(Vector(x, y)); + + if (auto ability = AbilityDB::get(abilityId)) + being->setAction(ability->useAction); +} +void BeingHandler::handleBeingAbilityBeingMessage(MessageIn &msg) +{ + Being *being = actorSpriteManager->findBeing(msg.readInt16()); if (!being) return; + const int abilityId = msg.readInt8(); + const int targetId = msg.readInt16(); + + if (Being *target = actorSpriteManager->findBeing(targetId)) + being->lookAt(target->getPosition()); + + if (auto ability = AbilityDB::get(abilityId)) + being->setAction(ability->useAction); +} + +void BeingHandler::handleBeingAbilityDirectionMessage(MessageIn &msg) +{ + Being *being = actorSpriteManager->findBeing(msg.readInt16()); + if (!being) + return; + + const int abilityId = msg.readInt8(); + const int direction = msg.readInt8(); + being->setDirection(direction); - being->setAction(Being::ATTACK, attackId); + if (auto ability = AbilityDB::get(abilityId)) + being->setAction(ability->useAction); } void BeingHandler::handleBeingsDamageMessage(MessageIn &msg) @@ -256,9 +345,7 @@ void BeingHandler::handleBeingsDamageMessage(MessageIn &msg) Being *being = actorSpriteManager->findBeing(msg.readInt16()); int damage = msg.readInt16(); if (being) - { being->takeDamage(nullptr, damage, Being::HIT); - } } } @@ -306,14 +393,8 @@ void BeingHandler::handleBeingLooksChangeMessage(MessageIn &msg) Being *being = actorSpriteManager->findBeing(msg.readInt16()); if (!being || being->getType() != ActorSprite::PLAYER) return; + handleLooks(being, msg); - if (msg.getUnreadLength()) - { - int style = msg.readInt16(); - int color = msg.readInt16(); - being->setSprite(SPRITE_LAYER_HAIR, style * -1, - hairDB.getHairColor(color)); - } } void BeingHandler::handleBeingDirChangeMessage(MessageIn &msg) diff --git a/src/net/manaserv/beinghandler.h b/src/net/manaserv/beinghandler.h index 63424de9..9580c284 100644 --- a/src/net/manaserv/beinghandler.h +++ b/src/net/manaserv/beinghandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_MANASERV_BEINGHANDLER_H -#define NET_MANASERV_BEINGHANDLER_H +#pragma once #include "net/manaserv/messagehandler.h" @@ -44,10 +43,13 @@ class BeingHandler final : public MessageHandler void handleMessage(MessageIn &msg) override; private: - void handleBeingAttackMessage(MessageIn &msg); void handleBeingEnterMessage(MessageIn &msg); void handleBeingLeaveMessage(MessageIn &msg); + void handleBeingEmoteMessage(MessageIn &msg); void handleBeingsMoveMessage(MessageIn &msg); + void handleBeingAbilityPointMessage(MessageIn &msg); + void handleBeingAbilityBeingMessage(MessageIn &msg); + void handleBeingAbilityDirectionMessage(MessageIn &msg); void handleBeingsDamageMessage(MessageIn &msg); void handleBeingActionChangeMessage(MessageIn &msg); void handleBeingLooksChangeMessage(MessageIn &msg); @@ -55,5 +57,3 @@ class BeingHandler final : public MessageHandler }; } // namespace ManaServ - -#endif diff --git a/src/net/manaserv/buysellhandler.h b/src/net/manaserv/buysellhandler.h index 0629bd7b..c19090e1 100644 --- a/src/net/manaserv/buysellhandler.h +++ b/src/net/manaserv/buysellhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_MANASERV_BUYSELLHANDLER_H -#define NET_MANASERV_BUYSELLHANDLER_H +#pragma once #include "net/manaserv/messagehandler.h" @@ -35,5 +34,3 @@ class BuySellHandler final : public MessageHandler }; } // namespace ManaServ - -#endif diff --git a/src/net/manaserv/charhandler.cpp b/src/net/manaserv/charhandler.cpp index 8a922e70..98591669 100644 --- a/src/net/manaserv/charhandler.cpp +++ b/src/net/manaserv/charhandler.cpp @@ -44,6 +44,7 @@ #include "utils/dtor.h" #include "utils/gettext.h" +#include "utils/stringutils.h" extern Net::CharHandler *charHandler; extern ManaServ::GameHandler *gameHandler; @@ -99,28 +100,44 @@ void CharHandler::handleMessage(MessageIn &msg) void CharHandler::handleCharacterInfo(MessageIn &msg) { - CachedCharacterInfo info; - info.slot = msg.readInt8(); - info.name = msg.readString(); - info.gender = msg.readInt8() == ManaServ::GENDER_MALE ? Gender::MALE - : Gender::FEMALE; - info.hairStyle = msg.readInt8(); - info.hairColor = msg.readInt8(); - info.level = msg.readInt16(); - info.characterPoints = msg.readInt16(); - info.correctionPoints = msg.readInt16(); - while (msg.getUnreadLength() > 0) { - int id = msg.readInt32(); - CachedAttrbiute attr; - attr.base = msg.readInt32() / 256.0; - attr.mod = msg.readInt32() / 256.0; + CachedCharacterInfo &info = mCachedCharacterInfos.emplace_back(); - info.attribute[id] = attr; - } + info.slot = msg.readInt8(); + info.name = msg.readString(); + switch (getGender(msg.readInt8())) { + case GENDER_MALE: + info.gender = Gender::Male; + break; + case GENDER_FEMALE: + info.gender = Gender::Female; + break; + case GENDER_UNSPECIFIED: + info.gender = Gender::Unspecified; + break; + } + info.hairStyle = msg.readInt8(); + info.hairColor = msg.readInt8(); + info.characterPoints = msg.readInt16(); + info.correctionPoints = msg.readInt16(); - mCachedCharacterInfos.push_back(info); + int equipmentCount = msg.readInt8(); + while (equipmentCount--) + { + auto &slot = info.equipment.emplace_back(); + slot.id = msg.readInt16(); + slot.itemId = msg.readInt16(); + } + + int attributeCount = msg.readInt8(); + while (attributeCount--) + { + CachedAttribute &attr = info.attributes[msg.readInt32()]; + attr.base = msg.readInt32() / 256.0; + attr.mod = msg.readInt32() / 256.0; + } + } updateCharacters(); } @@ -182,6 +199,8 @@ void CharHandler::handleCharacterCreateResponse(MessageIn &msg) } else { + handleCharacterInfo(msg); + // Close the character create dialog if (mCharCreateDialog) { @@ -380,10 +399,8 @@ void CharHandler::updateCharacters() return; // Create new characters and initialize them from the cached infos - for (unsigned i = 0; i < mCachedCharacterInfos.size(); ++i) + for (const auto &info : mCachedCharacterInfos) { - const CachedCharacterInfo &info = mCachedCharacterInfos.at(i); - auto *character = new Net::Character; character->slot = info.slot; LocalPlayer *player = character->dummy = new LocalPlayer; @@ -391,14 +408,30 @@ void CharHandler::updateCharacters() player->setGender(info.gender); player->setSprite(SPRITE_LAYER_HAIR, info.hairStyle * -1, hairDB.getHairColor(info.hairColor)); - character->data.mAttributes[LEVEL] = info.level; + + for (auto &slot : info.equipment) + { + player->setSprite(slot.id + FIXED_SPRITE_LAYER_SIZE, + slot.itemId, + std::string(), + Net::getInventoryHandler()->isWeaponSlot(slot.id)); + } + character->data.mAttributes[CHAR_POINTS] = info.characterPoints; character->data.mAttributes[CORR_POINTS] = info.correctionPoints; - for (const auto &it : info.attribute) + for (const auto &[id, attr] : info.attributes) { - character->data.mStats[i].base = it.second.base; - character->data.mStats[i].mod = it.second.mod; + int playerInfoId = Attributes::getPlayerInfoIdFromAttrId(id); + if (playerInfoId > -1) + { + character->data.mAttributes[playerInfoId] = attr.mod; + } + else + { + character->data.mStats[id].base = attr.base; + character->data.mStats[id].mod = attr.mod; + } } mCharacters.push_back(character); diff --git a/src/net/manaserv/charhandler.h b/src/net/manaserv/charhandler.h index 9ec5cdbb..e962bdfa 100644 --- a/src/net/manaserv/charhandler.h +++ b/src/net/manaserv/charhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_MANASERV_CHARSERVERHANDLER_H -#define NET_MANASERV_CHARSERVERHANDLER_H +#pragma once #include "gui/charselectdialog.h" @@ -89,12 +88,15 @@ class CharHandler final : public MessageHandler, public Net::CharHandler * we have loaded the dynamic data, so we can't resolve load any * sprites yet. */ - struct CachedAttrbiute { + struct CachedAttribute { double base; double mod; }; - using CachedAttributes = std::map<int, CachedAttrbiute>; + struct EquipmentSlot { + int id; + int itemId; + }; struct CachedCharacterInfo { int slot; @@ -102,10 +104,10 @@ class CharHandler final : public MessageHandler, public Net::CharHandler Gender gender; int hairStyle; int hairColor; - int level; int characterPoints; int correctionPoints; - CachedAttributes attribute; + std::vector<EquipmentSlot> equipment; + std::map<int, CachedAttribute> attributes; }; void handleCharacterInfo(MessageIn &msg); @@ -120,5 +122,3 @@ class CharHandler final : public MessageHandler, public Net::CharHandler }; } // namespace ManaServ - -#endif diff --git a/src/net/manaserv/chathandler.h b/src/net/manaserv/chathandler.h index f17883ae..6099fe93 100644 --- a/src/net/manaserv/chathandler.h +++ b/src/net/manaserv/chathandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_MANASERV_CHATHANDLER_H -#define NET_MANASERV_CHATHANDLER_H +#pragma once #include "net/chathandler.h" @@ -127,5 +126,3 @@ class ChatHandler final : public MessageHandler, public Net::ChatHandler }; } // namespace ManaServ - -#endif diff --git a/src/net/manaserv/connection.cpp b/src/net/manaserv/connection.cpp index 896d86ad..1b6f757a 100644 --- a/src/net/manaserv/connection.cpp +++ b/src/net/manaserv/connection.cpp @@ -45,6 +45,8 @@ Connection::~Connection() bool Connection::connect(const std::string &address, enet_uint16 port) { logger->log("Net::Connection::connect(%s, %i)", address.c_str(), port); + if (mConnection) + disconnect(); if (address.empty()) { @@ -87,8 +89,7 @@ void Connection::disconnect() bool Connection::isConnected() { - return (mConnection) ? - (mConnection->state == ENET_PEER_STATE_CONNECTED) : false; + return mConnection && mConnection->state == ENET_PEER_STATE_CONNECTED; } void Connection::send(const ManaServ::MessageOut &msg) diff --git a/src/net/manaserv/connection.h b/src/net/manaserv/connection.h index dfd45e31..196d034b 100644 --- a/src/net/manaserv/connection.h +++ b/src/net/manaserv/connection.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_MANASERV_CONNECTION_H -#define NET_MANASERV_CONNECTION_H +#pragma once #include <enet/enet.h> #include "net/manaserv/network.h" @@ -76,5 +75,3 @@ namespace ManaServ State mState = OK; }; } - -#endif // NET_MANASERV_CONNECTION_H diff --git a/src/net/manaserv/defines.h b/src/net/manaserv/defines.h index f1d12339..e224c045 100644 --- a/src/net/manaserv/defines.h +++ b/src/net/manaserv/defines.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef MANASERV_DEFINES_H -#define MANASERV_DEFINES_H +#pragma once /** * Attributes used during combat. Available to all the beings. @@ -72,5 +71,3 @@ enum NB_CHARACTER_ATTRIBUTES = CHAR_ATTR_END }; - -#endif // MANASERV_DEFINES_H diff --git a/src/net/manaserv/effecthandler.h b/src/net/manaserv/effecthandler.h index 4ba711d7..9b4e7adf 100644 --- a/src/net/manaserv/effecthandler.h +++ b/src/net/manaserv/effecthandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_MANASERV_EFFECTSHANDLER_H -#define NET_MANASERV_EFFECTSHANDLER_H +#pragma once #include "net/manaserv/messagehandler.h" @@ -41,5 +40,3 @@ class EffectHandler final : public MessageHandler }; } // namespace ManaServ - -#endif // NET_MANASERV_EFFECTSHANDLER_H diff --git a/src/net/manaserv/gamehandler.cpp b/src/net/manaserv/gamehandler.cpp index 1bf4d69f..3dae5640 100644 --- a/src/net/manaserv/gamehandler.cpp +++ b/src/net/manaserv/gamehandler.cpp @@ -44,6 +44,7 @@ extern ServerInfo chatServer; GameHandler::GameHandler() { static const Uint16 _messages[] = { + GPMSG_CONNECT_RESPONSE, GPMSG_DISCONNECT_RESPONSE, 0 }; @@ -55,6 +56,9 @@ void GameHandler::handleMessage(MessageIn &msg) { switch (msg.getId()) { + case GPMSG_CONNECT_RESPONSE: + break; + case GPMSG_DISCONNECT_RESPONSE: { int errMsg = msg.readInt8(); diff --git a/src/net/manaserv/gamehandler.h b/src/net/manaserv/gamehandler.h index 019c2dfa..4db8a9ee 100644 --- a/src/net/manaserv/gamehandler.h +++ b/src/net/manaserv/gamehandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_MANASERV_MAPHANDLER_H -#define NET_MANASERV_MAPHANDLER_H +#pragma once #include "net/gamehandler.h" @@ -62,5 +61,3 @@ class GameHandler final : public MessageHandler, public Net::GameHandler }; } // namespace ManaServ - -#endif // NET_MANASERV_MAPHANDLER_H diff --git a/src/net/manaserv/generalhandler.cpp b/src/net/manaserv/generalhandler.cpp index 0692ee82..32141c29 100644 --- a/src/net/manaserv/generalhandler.cpp +++ b/src/net/manaserv/generalhandler.cpp @@ -23,7 +23,6 @@ #include "client.h" -#include "gui/inventorywindow.h" #include "gui/skilldialog.h" #include "net/manaserv/adminhandler.h" @@ -42,13 +41,11 @@ #include "net/manaserv/npchandler.h" #include "net/manaserv/partyhandler.h" #include "net/manaserv/playerhandler.h" -#include "net/manaserv/specialhandler.h" +#include "net/manaserv/abilityhandler.h" #include "net/manaserv/tradehandler.h" #include "resources/attributes.h" -extern Net::GeneralHandler *generalHandler; - extern ManaServ::LoginHandler *loginHandler; namespace ManaServ { @@ -76,7 +73,7 @@ GeneralHandler::GeneralHandler(): mPartyHandler(new PartyHandler), mPlayerHandler(new PlayerHandler), mTradeHandler(new TradeHandler), - mSpecialHandler(new SpecialHandler) + mAbilityHandler(new AbilityHandler) { initialize(); @@ -84,8 +81,6 @@ GeneralHandler::GeneralHandler(): gameServerConnection = getConnection(); chatServerConnection = getConnection(); - generalHandler = this; - listen(Event::ClientChannel); listen(Event::GameChannel); } @@ -129,18 +124,28 @@ void GeneralHandler::reload() void GeneralHandler::unload() { - clearHandlers(); + clearNetworkHandlers(); if (accountServerConnection) + { accountServerConnection->disconnect(); + delete accountServerConnection; + accountServerConnection = nullptr; + } + if (gameServerConnection) + { gameServerConnection->disconnect(); + delete gameServerConnection; + gameServerConnection = nullptr; + } + if (chatServerConnection) + { chatServerConnection->disconnect(); - - delete accountServerConnection; - delete gameServerConnection; - delete chatServerConnection; + delete chatServerConnection; + chatServerConnection = nullptr; + } finalize(); } @@ -157,11 +162,6 @@ void GeneralHandler::flushNetwork() } } -void GeneralHandler::clearHandlers() -{ - clearNetworkHandlers(); -} - void GeneralHandler::event(Event::Channel channel, const Event &event) { @@ -182,7 +182,6 @@ void GeneralHandler::event(Event::Channel channel, { if (event.getType() == Event::GuiWindowsLoaded) { - inventoryWindow->setSplitAllowed(true); skillDialog->loadSkills(); PlayerInfo::setAttribute(EXP_NEEDED, 100); diff --git a/src/net/manaserv/generalhandler.h b/src/net/manaserv/generalhandler.h index 582c1796..de0e3138 100644 --- a/src/net/manaserv/generalhandler.h +++ b/src/net/manaserv/generalhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_MANASERV_GENERALHANDLER_H -#define NET_MANASERV_GENERALHANDLER_H +#pragma once #include "eventlistener.h" @@ -44,8 +43,6 @@ class GeneralHandler : public Net::GeneralHandler, public EventListener void flushNetwork() override; - void clearHandlers() override; - void event(Event::Channel channel, const Event &event) override; protected: @@ -64,9 +61,7 @@ class GeneralHandler : public Net::GeneralHandler, public EventListener MessageHandlerPtr mPartyHandler; MessageHandlerPtr mPlayerHandler; MessageHandlerPtr mTradeHandler; - MessageHandlerPtr mSpecialHandler; + MessageHandlerPtr mAbilityHandler; }; } // namespace ManaServ - -#endif // NET_MANASERV_GENERALHANDLER_H diff --git a/src/net/manaserv/guildhandler.h b/src/net/manaserv/guildhandler.h index 666ae862..d7eb798c 100644 --- a/src/net/manaserv/guildhandler.h +++ b/src/net/manaserv/guildhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_MANASERV_GUILDHANDLER_H -#define NET_MANASERV_GUILDHANDLER_H +#pragma once #include "net/guildhandler.h" @@ -67,5 +66,3 @@ protected: }; } // namespace ManaServ - -#endif diff --git a/src/net/manaserv/internal.h b/src/net/manaserv/internal.h index 2961f4bb..701c9ee6 100644 --- a/src/net/manaserv/internal.h +++ b/src/net/manaserv/internal.h @@ -19,12 +19,9 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_MANASERV_INTERNAL_H -#define NET_MANASERV_INTERNAL_H +#pragma once namespace ManaServ { extern int connections; } - -#endif diff --git a/src/net/manaserv/inventoryhandler.cpp b/src/net/manaserv/inventoryhandler.cpp index 9754beb0..e1dc5dea 100644 --- a/src/net/manaserv/inventoryhandler.cpp +++ b/src/net/manaserv/inventoryhandler.cpp @@ -43,16 +43,6 @@ extern Net::InventoryHandler *inventoryHandler; namespace ManaServ { -struct EquipItemInfo -{ - - EquipItemInfo(int itemId, int slotTypeId, int amountUsed): - mItemId(itemId), mSlotTypeId(slotTypeId), mAmountUsed(amountUsed) - {} - - int mItemId, mSlotTypeId, mAmountUsed; -}; - extern Connection *gameServerConnection; EquipBackend::EquipBackend() @@ -61,15 +51,13 @@ EquipBackend::EquipBackend() mVisibleSlots = 0; } -EquipBackend::~EquipBackend() -{ - clear(); -} - Item *EquipBackend::getEquipment(int slotIndex) const { auto it = mSlots.find(slotIndex); - return it == mSlots.end() ? nullptr : it->second.item; + if (it == mSlots.end()) + return nullptr; + + return PlayerInfo::getInventory()->getItem(it->second.inventorySlot); } std::string EquipBackend::getSlotName(int slotIndex) const @@ -80,111 +68,55 @@ std::string EquipBackend::getSlotName(int slotIndex) const void EquipBackend::triggerUnequip(int slotIndex) const { - // First get the itemInstance - auto it = mSlots.find(slotIndex); - - if (it == mSlots.end() || it->second.itemInstance == 0 || !it->second.item) + auto item = getEquipment(slotIndex); + if (!item) return; Event event(Event::DoUnequip); - event.setItem("item", it->second.item); - event.setInt("itemInstance", it->second.itemInstance); + event.setItem("item", item); event.trigger(Event::ItemChannel); } - void EquipBackend::clear() { - for (auto &slot : mSlots) - { - if (slot.second.item) - { - delete slot.second.item; - slot.second.item = nullptr; - } - } - mSlots.clear(); + for (auto &[_, slot] : mSlots) + slot.inventorySlot = -1; } -void EquipBackend::equip(int itemId, int slotTypeId, int amountUsed, - int itemInstance) +void EquipBackend::equip(int inventorySlot, int equipmentSlot) { - if (itemInstance <= 0) - { - logger->log("ManaServ::EquipBackend: Equipment slot %i" - " has an invalid item instance.", slotTypeId); - return; - } - - auto it = mSlots.begin(); - auto it_end = mSlots.end(); - bool slotTypeFound = false; - for (; it != it_end; ++it) - if (it->second.slotTypeId == (unsigned)slotTypeId) - slotTypeFound = true; - - if (!slotTypeFound) + auto slotIt = mSlots.find(equipmentSlot); + if (slotIt == mSlots.end()) { logger->log("ManaServ::EquipBackend: Equipment slot %i" - " is not existing.", slotTypeId); + " is not existing.", + equipmentSlot); return; } - if (!itemDb->exists(itemId)) - { - logger->log("ManaServ::EquipBackend: No item with id %d", - itemId); - return; - } + slotIt->second.inventorySlot = inventorySlot; - // Place the item in the slots with corresponding id until - // the capacity requested has been reached - for (it = mSlots.begin(); it != it_end && amountUsed > 0; ++it) - { - // If we're on the right slot type and that its unit - // isn't already equipped, we can equip there. - // The slots are already sorted by id, and subId anyway. - if (it->second.slotTypeId == (unsigned)slotTypeId - && (!it->second.itemInstance) && (!it->second.item)) - { - it->second.itemInstance = itemInstance; - it->second.item = new Item(itemId, 1, true); - --amountUsed; - } - } + if (auto item = PlayerInfo::getInventory()->getItem(inventorySlot)) + item->setEquipped(true); } -void EquipBackend::unequip(int itemInstance) +void EquipBackend::unequip(int inventorySlot) { - auto it = mSlots.begin(); - auto it_end = mSlots.end(); - bool itemInstanceFound = false; - for (; it != it_end; ++it) - if (it->second.itemInstance == (unsigned)itemInstance) - itemInstanceFound = true; - - if (!itemInstanceFound) + for (auto &[_, slot] : mSlots) { - logger->log("ManaServ::EquipBackend: Equipment item instance %i" - " is not existing. The item couldn't be unequipped!", - itemInstance); - return; - } + if (slot.inventorySlot == inventorySlot) + { + slot.inventorySlot = -1; - for (it = mSlots.begin(); it != it_end; ++it) - { - if (it->second.itemInstance != (unsigned)itemInstance) - continue; + if (auto item = PlayerInfo::getInventory()->getItem(inventorySlot)) + item->setEquipped(false); - // We remove the item - it->second.itemInstance = 0; - // We also delete the item objects - if (it->second.item) - { - delete it->second.item; - it->second.item = nullptr; + return; } } + + logger->log("ManaServ::EquipBackend: No equipped item found at inventory " + "slot %i!", inventorySlot); } void EquipBackend::event(Event::Channel, const Event &event) @@ -195,7 +127,7 @@ void EquipBackend::event(Event::Channel, const Event &event) void EquipBackend::readEquipFile() { - clear(); + mSlots.clear(); XML::Document doc(EQUIP_FILE); XML::Node rootNode = doc.rootNode(); @@ -245,7 +177,7 @@ void EquipBackend::readEquipFile() } slot.subId = i; - mSlots.insert(std::make_pair(slotIndex, slot)); + mSlots.insert(std::make_pair(slotIndex, std::move(slot))); ++slotIndex; } } @@ -262,10 +194,10 @@ void EquipBackend::readBoxNode(XML::Node slotNode) if (boxNode.name() != "box") continue; - int x = boxNode.getProperty("x" , 0); - int y = boxNode.getProperty("y" , 0); + const int x = boxNode.getProperty("x" , 0); + const int y = boxNode.getProperty("y" , 0); - mBoxesPositions.push_back(Position(x, y)); + mBoxesPositions.emplace_back(x, y); std::string backgroundFile = boxNode.getProperty("background" , std::string()); @@ -313,6 +245,7 @@ InventoryHandler::InventoryHandler() GPMSG_INVENTORY_FULL, GPMSG_INVENTORY, GPMSG_EQUIP, + GPMSG_UNEQUIP, 0 }; handledMessages = _messages; @@ -331,87 +264,43 @@ void InventoryHandler::handleMessage(MessageIn &msg) int count = msg.readInt16(); while (count--) { - int slot = msg.readInt16(); - int id = msg.readInt16(); - int amount = msg.readInt16(); - PlayerInfo::setInventoryItem(slot, id, amount); + const int slot = msg.readInt16(); + const int itemId = msg.readInt16(); + const int amount = msg.readInt16(); + const int equipmentSlot = msg.readInt16(); + PlayerInfo::setInventoryItem(slot, itemId, amount); + + if (equipmentSlot > 0) + mEquipBackend.equip(slot, equipmentSlot); } - // A map of { item instance, {slot type id, item id, amount used}} - std::map<int, EquipItemInfo> equipItemsInfo; - std::map<int, EquipItemInfo>::iterator it; - while (msg.getUnreadLength()) - { - int slotTypeId = msg.readInt16(); - int itemId = msg.readInt16(); - int itemInstance = msg.readInt16(); - - // Turn the data received into a usable format - it = equipItemsInfo.find(itemInstance); - if (it == equipItemsInfo.end()) - { - // Add a new entry - equipItemsInfo.insert(std::make_pair(itemInstance, - EquipItemInfo(itemId, slotTypeId, 1))); - } - else - { - // Add amount to the existing entry - it->second.mAmountUsed++; - } - } - - for (it = equipItemsInfo.begin(); it != equipItemsInfo.end(); - ++it) - { - mEquipBackend.equip(it->second.mItemId, - it->second.mSlotTypeId, - it->second.mAmountUsed, - it->first); - } + inventoryWindow->updateButtons(); } break; case GPMSG_INVENTORY: while (msg.getUnreadLength()) { - unsigned int slot = msg.readInt16(); - int id = msg.readInt16(); - unsigned int amount = id ? msg.readInt16() : 0; + const unsigned int slot = msg.readInt16(); + const int id = msg.readInt16(); + const unsigned int amount = id ? msg.readInt16() : 0; PlayerInfo::setInventoryItem(slot, id, amount); } break; case GPMSG_EQUIP: { - int itemId = msg.readInt16(); - int equipSlotCount = msg.readInt16(); - - if (equipSlotCount <= 0) - break; - - // Otherwise equip the item in the given slots - while (equipSlotCount--) - { - unsigned int parameter = msg.readInt16(); - unsigned int amountUsed = msg.readInt16(); - - if (amountUsed == 0) - { - // No amount means to unequip this item - // Note that in that case, the parameter is - // in fact the itemInstanceId - mEquipBackend.unequip(parameter); - } - else - { - int itemInstance = msg.readInt16(); - // The parameter is in that case the slot type id. - mEquipBackend.equip(itemId, parameter, - amountUsed, itemInstance); - } - } + const int inventorySlot = msg.readInt16(); + const int equipmentSlot = msg.readInt16(); + mEquipBackend.equip(inventorySlot, equipmentSlot); + inventoryWindow->updateButtons(); + } + case GPMSG_UNEQUIP: + { + const int inventorySlot = msg.readInt16(); + mEquipBackend.unequip(inventorySlot); + inventoryWindow->updateButtons(); } break; } @@ -423,9 +312,7 @@ void InventoryHandler::event(Event::Channel channel, if (channel == Event::ItemChannel) { Item *item = event.getItem("item"); - int itemInstance = event.getInt("itemInstance", 0); - - if (!item && itemInstance == 0) + if (!item) return; int index = item->getInvIndex(); @@ -439,7 +326,7 @@ void InventoryHandler::event(Event::Channel channel, else if (event.getType() == Event::DoUnequip) { MessageOut msg(PGMSG_UNEQUIP); - msg.writeInt16(itemInstance); + msg.writeInt16(index); gameServerConnection->send(msg); } else if (event.getType() == Event::DoUse) @@ -457,53 +344,9 @@ void InventoryHandler::event(Event::Channel channel, msg.writeInt16(amount); gameServerConnection->send(msg); } - else if (event.getType() == Event::DoSplit) - { - int amount = event.getInt("amount", 1); - - int newIndex = PlayerInfo::getInventory()->getFreeSlot(); - if (newIndex > Inventory::NO_SLOT_INDEX) - { - MessageOut msg(PGMSG_MOVE_ITEM); - msg.writeInt16(index); - msg.writeInt16(newIndex); - msg.writeInt16(amount); - gameServerConnection->send(msg); - } - } - else if (event.getType() == Event::DoMove) - { - int newIndex = event.getInt("newIndex", -1); - - if (newIndex >= 0) - { - if (index == newIndex) - return; - - MessageOut msg(PGMSG_MOVE_ITEM); - msg.writeInt16(index); - msg.writeInt16(newIndex); - msg.writeInt16(item->getQuantity()); - gameServerConnection->send(msg); - } - else - { - /*int source = event.getInt("source"); - int destination = event.getInt("destination"); - int amount = event.getInt("amount", 1);*/ - - // TODO Support drag'n'drop to the map ground, or with other - // windows. - } - } } } -bool InventoryHandler::canSplit(const Item *item) -{ - return item && item->getQuantity() > 1; -} - size_t InventoryHandler::getSize(int type) const { switch (type) diff --git a/src/net/manaserv/inventoryhandler.h b/src/net/manaserv/inventoryhandler.h index e01bb5d8..02ce90df 100644 --- a/src/net/manaserv/inventoryhandler.h +++ b/src/net/manaserv/inventoryhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_MANASERV_INVENTORYHANDLER_H -#define NET_MANASERV_INVENTORYHANDLER_H +#pragma once #include "eventlistener.h" @@ -28,6 +27,7 @@ #include "net/manaserv/messagehandler.h" +#include <map> #include <vector> namespace ManaServ { @@ -37,15 +37,12 @@ class EquipBackend final : public Equipment::Backend, public EventListener public: EquipBackend(); - ~EquipBackend() override; - Item *getEquipment(int slotIndex) const override; std::string getSlotName(int slotIndex) const override; void clear() override; - void equip(int itemId, int slotTypeId, int amountUsed = 1, - int itemInstance = 0); - void unequip(int slotTypeId); + void equip(int inventorySlot, int equipmentSlot); + void unequip(int inventorySlot); void event(Event::Channel channel, const Event &event) override; @@ -73,9 +70,7 @@ class EquipBackend final : public Equipment::Backend, public EventListener // Generic info std::string name; - // The Item reference, used for graphical representation - // and info. - Item *item = nullptr; + int inventorySlot = 0; // Manaserv specific info @@ -89,10 +84,6 @@ class EquipBackend final : public Equipment::Backend, public EventListener // This is used to sort the multimap along with the slot id. unsigned int subId = 0; - // This is the (per character) unique item Id, used especially when - // equipping the same item multiple times on the same slot type. - unsigned int itemInstance = 0; - // Tell whether the slot is a weapon slot bool weaponSlot = false; @@ -103,8 +94,7 @@ class EquipBackend final : public Equipment::Backend, public EventListener unsigned int mVisibleSlots; // slot client index, slot info - using Slots = std::map<unsigned int, Slot>; - Slots mSlots; + std::map<unsigned int, Slot> mSlots; std::vector<Position> mBoxesPositions; std::vector<std::string> mBoxesBackgroundFile; }; @@ -119,8 +109,6 @@ class InventoryHandler final : public MessageHandler, Net::InventoryHandler, void event(Event::Channel channel, const Event &event) override; - bool canSplit(const Item *item) override; - size_t getSize(int type) const override; bool isWeaponSlot(unsigned int slotTypeId) const override @@ -146,5 +134,3 @@ class InventoryHandler final : public MessageHandler, Net::InventoryHandler, }; } // namespace ManaServ - -#endif // NET_MANASERV_INVENTORYHANDLER_H diff --git a/src/net/manaserv/itemhandler.cpp b/src/net/manaserv/itemhandler.cpp index de5b36fa..870e7a9f 100644 --- a/src/net/manaserv/itemhandler.cpp +++ b/src/net/manaserv/itemhandler.cpp @@ -26,8 +26,6 @@ #include "net/manaserv/manaserv_protocol.h" #include "net/manaserv/messagein.h" -#include "log.h" - namespace ManaServ { ItemHandler::ItemHandler() diff --git a/src/net/manaserv/itemhandler.h b/src/net/manaserv/itemhandler.h index 22adf8fb..c15d4638 100644 --- a/src/net/manaserv/itemhandler.h +++ b/src/net/manaserv/itemhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_MANASERV_ITEMHANDLER_H -#define NET_MANASERV_ITEMHANDLER_H +#pragma once #include "net/manaserv/messagehandler.h" @@ -35,5 +34,3 @@ class ItemHandler final : public MessageHandler }; } // namespace ManaServ - -#endif // NET_MANASERV_ITEMHANDLER_H diff --git a/src/net/manaserv/loginhandler.cpp b/src/net/manaserv/loginhandler.cpp index b9a56834..1c398990 100644 --- a/src/net/manaserv/loginhandler.cpp +++ b/src/net/manaserv/loginhandler.cpp @@ -399,8 +399,6 @@ void LoginHandler::loginAccount(LoginData *loginData) mTmpPassword = loginData->password; MessageOut msg(PAMSG_LOGIN_RNDTRGR); - msg.writeString(mLoginData->username); - accountServerConnection->send(msg); } diff --git a/src/net/manaserv/loginhandler.h b/src/net/manaserv/loginhandler.h index 87fbe9bc..e8d40835 100644 --- a/src/net/manaserv/loginhandler.h +++ b/src/net/manaserv/loginhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_MANASERV_LOGINHANDLER_H -#define NET_MANASERV_LOGINHANDLER_H +#pragma once #include "net/loginhandler.h" @@ -91,5 +90,3 @@ class LoginHandler final : public MessageHandler, public Net::LoginHandler }; } // namespace ManaServ - -#endif // NET_MANASERV_LOGINHANDLER_H diff --git a/src/net/manaserv/manaserv_protocol.h b/src/net/manaserv/manaserv_protocol.h index 760fe655..cb27d6f4 100644 --- a/src/net/manaserv/manaserv_protocol.h +++ b/src/net/manaserv/manaserv_protocol.h @@ -19,14 +19,14 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef MANASERV_PROTOCOL_H -#define MANASERV_PROTOCOL_H +#pragma once namespace ManaServ { enum { - PROTOCOL_VERSION = 3, - SUPPORTED_DB_VERSION = 21 + PROTOCOL_VERSION = 9, + MIN_PROTOCOL_VERSION = 9, + SUPPORTED_DB_VERSION = 27 }; /** @@ -69,19 +69,22 @@ enum { PAMSG_REQUEST_REGISTER_INFO = 0x0005, // APMSG_REGISTER_INFO_RESPONSE = 0x0006, // B byte registration Allowed, byte minNameLength, byte maxNameLength, string captchaURL, string captchaInstructions PAMSG_LOGIN = 0x0010, // D version, S username, S password - APMSG_LOGIN_RESPONSE = 0x0012, // B error, S updatehost, S Client data URL, B Character slots + APMSG_LOGIN_RESPONSE = 0x0012, // B error, S updatehost, S Client data URL, B Character slots, {content of APMSG_CHAR_CREATE_RESPONSE (without error code)}* PAMSG_LOGOUT = 0x0013, // - APMSG_LOGOUT_RESPONSE = 0x0014, // B error - PAMSG_LOGIN_RNDTRGR = 0x0015, // S username + PAMSG_LOGIN_RNDTRGR = 0x0015, // - APMSG_LOGIN_RNDTRGR_RESPONSE = 0x0016, // S random seed + PAMSG_STELLAR_LOGIN = 0x0017, // D version + APMSG_STELLAR_LOGIN_RESPONSE = 0x0018, // B error, S token, S url PAMSG_CHAR_CREATE = 0x0020, // S name, B hair style, B hair color, B gender, B slot, {W stats}* - APMSG_CHAR_CREATE_RESPONSE = 0x0021, // B error + APMSG_CHAR_CREATE_RESPONSE = 0x0021, // B error, on success: B slot, S name, B gender, B hair style, B hair color, + // W character points, W correction points, B amount of items equipped, + // { W slot, W itemId }* + // B attributeCount, + // {D attr id, D base value (in 1/256ths) D mod value (in 256ths) }* PAMSG_CHAR_DELETE = 0x0022, // B slot APMSG_CHAR_DELETE_RESPONSE = 0x0023, // B error - // B slot, S name, B gender, B hair style, B hair color, W level, - // W character points, W correction points, - // {D attr id, D base value (in 1/256ths) D mod value (in 256ths) }* - APMSG_CHAR_INFO = 0x0024, // ^ + APMSG_CHAR_INFO = 0x0024, // {content of APMSG_CHAR_CREATE_RESPONSE (without error code)}* PAMSG_CHAR_SELECT = 0x0026, // B slot APMSG_CHAR_SELECT_RESPONSE = 0x0027, // B error, B*32 token, S game address, W game port, S chat address, W chat port PAMSG_EMAIL_CHANGE = 0x0030, // S email @@ -109,26 +112,28 @@ enum { PGMSG_DROP = 0x0111, // W slot, W amount PGMSG_EQUIP = 0x0112, // W inventory slot PGMSG_UNEQUIP = 0x0113, // W item Instance id - PGMSG_MOVE_ITEM = 0x0114, // W slot1, W slot2, W amount GPMSG_INVENTORY = 0x0120, // { W slot, W item id [, W amount] (if item id is nonzero) }* - GPMSG_INVENTORY_FULL = 0x0121, // W inventory slot count { W slot, W itemId, W amount }, { W equip slot, W item Id, W item Instance}* - GPMSG_EQUIP = 0x0122, // W item Id, W equip slot type count //{ W equip slot, W capacity used}*//<- When equipping, //{ W item instance, W 0}*//<- When unequipping + GPMSG_INVENTORY_FULL = 0x0121, // W inventory slot count { W slot, W itemId, W amount, W equipmentSlot } + GPMSG_EQUIP = 0x0122, // W equipped inventory slot, W slot equipmentSlot + GPMSG_EQUIP_RESPONSE = 0x0123, // B error, W slot + GPMSG_UNEQUIP = 0x0124, // W equipped inventory slot + GPMSG_UNEQUIP_RESPONSE = 0x0125, // B error, W slot GPMSG_PLAYER_ATTRIBUTE_CHANGE = 0x0130, // { W attribute, D base value (in 1/256ths), D modified value (in 1/256ths)}* - GPMSG_PLAYER_EXP_CHANGE = 0x0140, // { W skill, D exp got, D exp needed, W skill level }* - GPMSG_LEVELUP = 0x0150, // W new level, W character points, W correction points - GPMSG_LEVEL_PROGRESS = 0x0151, // B percent completed to next levelup + GPMSG_ATTRIBUTE_POINTS_STATUS = 0x0140, // W character points, W correction points PGMSG_RAISE_ATTRIBUTE = 0x0160, // W attribute GPMSG_RAISE_ATTRIBUTE_RESPONSE = 0x0161, // B error, W attribute PGMSG_LOWER_ATTRIBUTE = 0x0170, // W attribute GPMSG_LOWER_ATTRIBUTE_RESPONSE = 0x0171, // B error, W attribute PGMSG_RESPAWN = 0x0180, // - GPMSG_BEING_ENTER = 0x0200, // B type, W being id, B action, W*2 position, B direction, B gender - // character: S name, B hair style, B hair color, B sprite layers changed, { B slot type, W item id }* + // character: S name, B hair style, B hair color [, B sprite layers changed, { B slot type, W item id }*] // monster: W type id // npc: W type id GPMSG_BEING_LEAVE = 0x0201, // W being id GPMSG_ITEM_APPEAR = 0x0202, // W item id, W*2 position - GPMSG_BEING_LOOKS_CHANGE = 0x0210, // B sprite layers changed, { B slot type, W item id }* + GPMSG_BEING_LOOKS_CHANGE = 0x0210, // B hairstyle, B haircolor [, B sprite layers changed, { B slot type, W item id }*] + GPMSG_BEING_EMOTE = 0x0211, // W being id, W emote id + PGMSG_BEING_EMOTE = 0x0212, // W emoticon id PGMSG_WALK = 0x0260, // W*2 destination PGMSG_ACTION_CHANGE = 0x0270, // B Action GPMSG_BEING_ACTION_CHANGE = 0x0271, // W being id, B action @@ -137,45 +142,50 @@ enum { GPMSG_BEING_HEALTH_CHANGE = 0x0274, // W being id, W hp, W max hp GPMSG_BEINGS_MOVE = 0x0280, // { W being id, B flags [, [W*2 position,] W*2 destination, B speed] }* GPMSG_ITEMS = 0x0281, // { W item id, W*2 position }* - PGMSG_ATTACK = 0x0290, // W being id - GPMSG_BEING_ATTACK = 0x0291, // W being id, B direction, B attack Id - PGMSG_USE_SPECIAL_ON_BEING = 0x0292, // B specialID, W being id - GPMSG_SPECIAL_STATUS = 0x0293, // { B specialID, D current, D max, D recharge } - PGMSG_USE_SPECIAL_ON_POINT = 0x0294, // B specialID, W*2 position - PGMSG_SAY = 0x02A0, // S text - GPMSG_SAY = 0x02A1, // W being id, S text - GPMSG_NPC_CHOICE = 0x02B0, // W being id, { S text }* - GPMSG_NPC_MESSAGE = 0x02B1, // W being id, B* text - PGMSG_NPC_TALK = 0x02B2, // W being id - PGMSG_NPC_TALK_NEXT = 0x02B3, // W being id - PGMSG_NPC_SELECT = 0x02B4, // W being id, B choice - GPMSG_NPC_BUY = 0x02B5, // W being id, { W item id, W amount, W cost }* - GPMSG_NPC_SELL = 0x02B6, // W being id, { W item id, W amount, W cost }* - PGMSG_NPC_BUYSELL = 0x02B7, // W item id, W amount - GPMSG_NPC_ERROR = 0x02B8, // B error - GPMSG_NPC_CLOSE = 0x02B9, // W being id + GPMSG_BEING_ABILITY_POINT = 0x0282, // W being id, B abilityId, W*2 point + GPMSG_BEING_ABILITY_BEING = 0x0283, // W being id, B abilityId, W target being id + GPMSG_BEING_ABILITY_DIRECTION = 0x0284, // W being id, B abilityId, B direction + PGMSG_USE_ABILITY_ON_BEING = 0x0290, // B abilityID, W being id + PGMSG_USE_ABILITY_ON_POINT = 0x0291, // B abilityID, W*2 position + PGMSG_USE_ABILITY_ON_DIRECTION = 0x0292, // B abilityID, B direction + GPMSG_ABILITY_STATUS = 0x02A0, // { B abilityID, D remainingTicks } + GPMSG_ABILITY_REMOVED = 0x02A1, // B abilityID + GPMSG_ABILITY_COOLDOWN = 0x02A2, // W ticks to wait + PGMSG_SAY = 0x02B0, // S text + GPMSG_SAY = 0x02B1, // W being id, S text + GPMSG_NPC_CHOICE = 0x02C0, // W being id, { S text }* + GPMSG_NPC_MESSAGE = 0x02C1, // W being id, B* text + PGMSG_NPC_TALK = 0x02C2, // W being id + PGMSG_NPC_TALK_NEXT = 0x02C3, // W being id + PGMSG_NPC_SELECT = 0x02C4, // W being id, B choice + GPMSG_NPC_BUY = 0x02C5, // W being id, { W item id, W amount, W cost }* + GPMSG_NPC_SELL = 0x02C6, // W being id, { W item id, W amount, W cost }* + PGMSG_NPC_BUYSELL = 0x02C7, // W item id, W amount + GPMSG_NPC_ERROR = 0x02C8, // B error + GPMSG_NPC_CLOSE = 0x02C9, // W being id GPMSG_NPC_POST = 0x02D0, // W being id PGMSG_NPC_POST_SEND = 0x02D1, // W being id, { S name, S text, W item id } GPMSG_NPC_POST_GET = 0x02D2, // W being id, { S name, S text, W item id } PGMSG_NPC_NUMBER = 0x02D3, // W being id, D number PGMSG_NPC_STRING = 0x02D4, // W being id, S string - GPMSG_NPC_NUMBER = 0x02D5, // W being id, D max, D min, D default + GPMSG_NPC_NUMBER = 0x02D5, // W being id, D min, D max, D default GPMSG_NPC_STRING = 0x02D6, // W being id - PGMSG_TRADE_REQUEST = 0x02C0, // W being id - GPMSG_TRADE_REQUEST = 0x02C1, // W being id - GPMSG_TRADE_START = 0x02C2, // - - GPMSG_TRADE_COMPLETE = 0x02C3, // - - PGMSG_TRADE_CANCEL = 0x02C4, // - - GPMSG_TRADE_CANCEL = 0x02C5, // - - PGMSG_TRADE_AGREED = 0x02C6, // - - GPMSG_TRADE_AGREED = 0x02C7, // - - PGMSG_TRADE_CONFIRM = 0x02C8, // - - GPMSG_TRADE_CONFIRM = 0x02C9, // - - PGMSG_TRADE_ADD_ITEM = 0x02CA, // B slot, B amount - GPMSG_TRADE_ADD_ITEM = 0x02CB, // W item id, B amount - PGMSG_TRADE_SET_MONEY = 0x02CC, // D amount - GPMSG_TRADE_SET_MONEY = 0x02CD, // D amount - GPMSG_TRADE_BOTH_CONFIRM = 0x02CE, // - + GPMSG_NPC_BUYSELL_RESPONSE = 0x02D7, // B error, W item id, W amount + PGMSG_TRADE_REQUEST = 0x02E0, // W being id + GPMSG_TRADE_REQUEST = 0x02E1, // W being id + GPMSG_TRADE_START = 0x02E2, // - + GPMSG_TRADE_COMPLETE = 0x02E3, // - + PGMSG_TRADE_CANCEL = 0x02E4, // - + GPMSG_TRADE_CANCEL = 0x02E5, // - + PGMSG_TRADE_AGREED = 0x02E6, // - + GPMSG_TRADE_AGREED = 0x02E7, // - + PGMSG_TRADE_CONFIRM = 0x02E8, // - + GPMSG_TRADE_CONFIRM = 0x02E9, // - + PGMSG_TRADE_ADD_ITEM = 0x02EA, // B slot, B amount + GPMSG_TRADE_ADD_ITEM = 0x02EB, // W item id, B amount + PGMSG_TRADE_SET_MONEY = 0x02EC, // D amount + GPMSG_TRADE_SET_MONEY = 0x02ED, // D amount + GPMSG_TRADE_BOTH_CONFIRM = 0x02EE, // - PGMSG_USE_ITEM = 0x0300, // B slot GPMSG_USE_RESPONSE = 0x0301, // B error GPMSG_BEINGS_DAMAGE = 0x0310, // { W being id, W amount }* @@ -243,8 +253,11 @@ enum { PCMSG_USER_MODE = 0x0465, // W channel id, S name, B mode PCMSG_KICK_USER = 0x0466, // W channel id, S name + // -- Questlog + GPMSG_QUESTLOG_STATUS = 0x0470, // {W quest id, B flags, [B status], [S questname], [S questdescription]}* + // Inter-server - GAMSG_REGISTER = 0x0500, // S address, W port, S password, D items db revision, { W map id }* + GAMSG_REGISTER = 0x0500, // S address, W port, S password, D items db revision AGMSG_REGISTER_RESPONSE = 0x0501, // W item version, W password response, { S globalvar_key, S globalvar_value } AGMSG_ACTIVE_MAP = 0x0502, // W map id, W Number of mapvar_key mapvar_value sent, { S mapvar_key, S mapvar_value }, W Number of map items, { D item Id, W amount, W posX, W posY } AGMSG_PLAYER_ENTER = 0x0510, // B*32 token, D id, S name, serialised character data @@ -263,7 +276,6 @@ enum { GAMSG_SET_VAR_WORLD = 0x0547, // S name, S value AGMSG_SET_VAR_WORLD = 0x0548, // S name, S value GAMSG_BAN_PLAYER = 0x0550, // D id, W duration - GAMSG_CHANGE_PLAYER_LEVEL = 0x0555, // D id, W level GAMSG_CHANGE_ACCOUNT_LEVEL = 0x0556, // D id, W level GAMSG_STATISTICS = 0x0560, // { W map id, W entity nb, W monster nb, W player nb, { D character id }* }* CGMSG_CHANGED_PARTY = 0x0590, // D character id, D party id @@ -295,7 +307,8 @@ enum { ERRMSG_TIME_OUT, // data failed to arrive in due time ERRMSG_LIMIT_REACHED, // limit reached ERRMSG_ADMINISTRATIVE_LOGOFF, // kicked by server administrator - ERRMSG_ALREADY_MEMBER // is already member of guild/party + ERRMSG_ALREADY_MEMBER, // is already member of guild/party + ERRMSG_LOGIN_WAS_TAKEN_OVER // a different connection took over }; // used in AGMSG_REGISTER_RESPONSE to show state of item db @@ -314,7 +327,6 @@ enum { enum { SYNC_CHARACTER_POINTS = 0x01, // D charId, D charPoints, D corrPoints SYNC_CHARACTER_ATTRIBUTE = 0x02, // D charId, D attrId, DF base, DF mod - SYNC_CHARACTER_SKILL = 0x03, // D charId, B skillId, D skill value SYNC_ONLINE_STATUS = 0x04 // D charId, B 0 = offline, 1 = online }; @@ -358,21 +370,18 @@ enum AttribmodResponseCode { enum EntityType { // A simple item. - OBJECT_ITEM = 0, - // An item that toggle map/quest actions (doors, switchs, ...) - // and can speak (map panels). - OBJECT_ACTOR, + OBJECT_ITEM = 0, // Non-Playable-Character is an actor capable of movement and maybe actions. - OBJECT_NPC, + OBJECT_NPC = 2, // A monster (moving actor with AI. Should be able to toggle map/quest // actions, too). - OBJECT_MONSTER, + OBJECT_MONSTER = 3, // A normal being. - OBJECT_CHARACTER, + OBJECT_CHARACTER = 4, // A effect to be shown. - OBJECT_EFFECT, + OBJECT_EFFECT = 5, // Server-only object. - OBJECT_OTHER + OBJECT_OTHER = 6 }; // Moving object flags @@ -406,6 +415,13 @@ enum { GUILD_EVENT_OFFLINE_PLAYER }; +enum { + QUESTLOG_UPDATE_STATE = 1, + QUESTLOG_UPDATE_TITLE = 2, + QUESTLOG_UPDATE_DESCRIPTION = 4, + QUESTLOG_SHOW_NOTIFICATION = 8 +}; + /** * Moves enum for beings and actors for others players vision. * WARNING: Has to be in sync with the same enum in the Being class @@ -415,27 +431,12 @@ enum BeingAction { STAND, WALK, - ATTACK, SIT, DEAD, HURT }; /** - * Moves enum for beings and actors for others players attack types. - * WARNING: Has to be in sync with the same enum in the Being class - * of the client! - */ -enum AttackType -{ - HIT = 0x00, - CRITICAL = 0x0a, - MULTI = 0x08, - REFLECT = 0x04, - FLEE = 0x0b -}; - -/** * Beings and actors directions * WARNING: Has to be in sync with the same enum in the Being class * of the client! @@ -458,6 +459,55 @@ enum BeingGender GENDER_UNSPECIFIED }; +// Helper functions for gender + +/** +* Helper function for getting gender by int +*/ +inline ManaServ::BeingGender getGender(int gender) +{ + switch (gender) + { + case 0: + return ManaServ::GENDER_MALE; + case 1: + return ManaServ::GENDER_FEMALE; + default: + return ManaServ::GENDER_UNSPECIFIED; + } +} + +/** + * Quest states + */ +enum QuestStatus +{ + STATUS_OPEN = 0, + STATUS_STARTED, + STATUS_FINISHED, + STATUS_INVALID +}; + +/** + * Helper function for getting quest status by id + * @param status id of the status + * @return the status as enum value + */ +inline ManaServ::QuestStatus getQuestStatus(int status) +{ + switch (status) + { + case 0: + return ManaServ::STATUS_OPEN; + case 1: + return ManaServ::STATUS_STARTED; + case 2: + return ManaServ::STATUS_FINISHED; + default: + return ManaServ::STATUS_INVALID; + } +} + /** The permited range to pick up an item */ const int PICKUP_RANGE = 32 + 16; @@ -465,5 +515,3 @@ const int PICKUP_RANGE = 32 + 16; const int NPC_TALK_RANGE = 32 * 7; } // namespace ManaServ - -#endif // MANASERV_PROTOCOL_H diff --git a/src/net/manaserv/messagehandler.h b/src/net/manaserv/messagehandler.h index f9ee09ab..2c68cde5 100644 --- a/src/net/manaserv/messagehandler.h +++ b/src/net/manaserv/messagehandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_MANASERV_MESSAGEHANDLER_H -#define NET_MANASERV_MESSAGEHANDLER_H +#pragma once #include "net/messagehandler.h" @@ -44,5 +43,3 @@ class MessageHandler : public Net::MessageHandler using MessageHandlerPtr = const std::unique_ptr<MessageHandler>; } // namespace ManaServ - -#endif // NET_MANASERV_MESSAGEHANDLER_H diff --git a/src/net/manaserv/messagein.h b/src/net/manaserv/messagein.h index 1edc4fe7..cd072fe0 100644 --- a/src/net/manaserv/messagein.h +++ b/src/net/manaserv/messagein.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_MANASERV_MESSAGEIN_H -#define NET_MANASERV_MESSAGEIN_H +#pragma once #include "net/manaserv/manaserv_protocol.h" @@ -95,5 +94,3 @@ class MessageIn }; } // namespace ManaServ - -#endif // NET_MANASERV_MESSAGEIN_H diff --git a/src/net/manaserv/messageout.h b/src/net/manaserv/messageout.h index d452f784..d9d2e68a 100644 --- a/src/net/manaserv/messageout.h +++ b/src/net/manaserv/messageout.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_MANASERV_MESSAGEOUT_H -#define NET_MANASERV_MESSAGEOUT_H +#pragma once #include "net/manaserv/manaserv_protocol.h" @@ -91,5 +90,3 @@ class MessageOut }; } // namespace ManaServ - -#endif // NET_MANASERV_MESSAGEOUT_H diff --git a/src/net/manaserv/network.cpp b/src/net/manaserv/network.cpp index b8d3fa93..d69d3397 100644 --- a/src/net/manaserv/network.cpp +++ b/src/net/manaserv/network.cpp @@ -88,17 +88,13 @@ Connection *getConnection() void registerHandler(MessageHandler *handler) { for (const uint16_t *i = handler->handledMessages; *i; i++) - { mMessageHandlers[*i] = handler; - } } void unregisterHandler(MessageHandler *handler) { for (const uint16_t *i = handler->handledMessages; *i; i++) - { mMessageHandlers.erase(*i); - } } void clearNetworkHandlers() diff --git a/src/net/manaserv/network.h b/src/net/manaserv/network.h index f484d54d..a51a8168 100644 --- a/src/net/manaserv/network.h +++ b/src/net/manaserv/network.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_MANASERV_NETWORK_H -#define NET_MANASERV_NETWORK_H +#pragma once #include <iosfwd> @@ -71,5 +70,3 @@ namespace ManaServ */ void flush(); } // namespace ManaServ - -#endif diff --git a/src/net/manaserv/npchandler.cpp b/src/net/manaserv/npchandler.cpp index f19bf134..44729654 100644 --- a/src/net/manaserv/npchandler.cpp +++ b/src/net/manaserv/npchandler.cpp @@ -47,6 +47,7 @@ NpcHandler::NpcHandler() GPMSG_NPC_CLOSE, GPMSG_NPC_NUMBER, GPMSG_NPC_STRING, + GPMSG_NPC_BUYSELL_RESPONSE, 0 }; handledMessages = _messages; @@ -122,6 +123,9 @@ void NpcHandler::handleMessage(MessageIn &msg) event->setInt("id", npcId); event->trigger(Event::NpcChannel); break; + + case GPMSG_NPC_BUYSELL_RESPONSE: + break; } delete event; diff --git a/src/net/manaserv/npchandler.h b/src/net/manaserv/npchandler.h index ee3a9a12..6c485650 100644 --- a/src/net/manaserv/npchandler.h +++ b/src/net/manaserv/npchandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_MANASERV_NPCHANDLER_H -#define NET_MANASERV_NPCHANDLER_H +#pragma once #include "net/npchandler.h" @@ -65,5 +64,3 @@ class NpcHandler final : public MessageHandler, public Net::NpcHandler }; } // namespace ManaServ - -#endif // NET_MANASERV_NPCHANDLER_H diff --git a/src/net/manaserv/partyhandler.h b/src/net/manaserv/partyhandler.h index ac4249a9..ab5aad9a 100644 --- a/src/net/manaserv/partyhandler.h +++ b/src/net/manaserv/partyhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_MANASERV_PARTYHANDLER_H -#define NET_MANASERV_PARTYHANDLER_H +#pragma once #include "net/partyhandler.h" @@ -70,5 +69,3 @@ private: }; } // namespace ManaServ - -#endif // NET_MANASERV_PARTYHANDLER_H diff --git a/src/net/manaserv/playerhandler.cpp b/src/net/manaserv/playerhandler.cpp index bf5694c6..8ee9ed80 100644 --- a/src/net/manaserv/playerhandler.cpp +++ b/src/net/manaserv/playerhandler.cpp @@ -29,17 +29,18 @@ #include "log.h" #include "particle.h" #include "playerinfo.h" -#include "configuration.h" #include "gui/viewport.h" #include "net/net.h" +#include "net/abilityhandler.h" #include "net/manaserv/connection.h" #include "net/manaserv/messagein.h" #include "net/manaserv/messageout.h" #include "net/manaserv/manaserv_protocol.h" +#include "resources/abilitydb.h" #include "resources/attributes.h" /** @@ -50,6 +51,7 @@ const int MAP_TELEPORT_SCROLL_DISTANCE = 256; extern Net::PlayerHandler *playerHandler; +extern Net::AbilityHandler *abilityHandler; namespace ManaServ { @@ -70,12 +72,11 @@ PlayerHandler::PlayerHandler() GPMSG_PLAYER_MAP_CHANGE, GPMSG_PLAYER_SERVER_CHANGE, GPMSG_PLAYER_ATTRIBUTE_CHANGE, - GPMSG_PLAYER_EXP_CHANGE, - GPMSG_LEVELUP, - GPMSG_LEVEL_PROGRESS, + GPMSG_ATTRIBUTE_POINTS_STATUS, GPMSG_RAISE_ATTRIBUTE_RESPONSE, GPMSG_LOWER_ATTRIBUTE_RESPONSE, - GPMSG_SPECIAL_STATUS, + GPMSG_ABILITY_STATUS, + GPMSG_ABILITY_REMOVED, 0 }; handledMessages = _messages; @@ -129,39 +130,10 @@ void PlayerHandler::handleMessage(MessageIn &msg) } } break; - case GPMSG_PLAYER_EXP_CHANGE: - { - logger->log("EXP Update"); - while (msg.getUnreadLength()) - { - int skill = msg.readInt16(); - int current = msg.readInt32(); - int next = msg.readInt32(); - int level = msg.readInt16(); - - PlayerInfo::setStatExperience(skill, current, next); - PlayerInfo::setStatBase(skill, level); - } - } break; - - case GPMSG_LEVELUP: - { - PlayerInfo::setAttribute(LEVEL, msg.readInt16()); + case GPMSG_ATTRIBUTE_POINTS_STATUS: PlayerInfo::setAttribute(CHAR_POINTS, msg.readInt16()); PlayerInfo::setAttribute(CORR_POINTS, msg.readInt16()); - Particle* effect = particleEngine->addEffect( - paths.getStringValue("particles") - + paths.getStringValue("levelUpEffectFile") - ,0, 0); - local_player->controlParticle(effect); - } break; - - - case GPMSG_LEVEL_PROGRESS: - { - PlayerInfo::setAttribute(EXP, msg.readInt8()); - } break; - + break; case GPMSG_RAISE_ATTRIBUTE_RESPONSE: { @@ -243,19 +215,24 @@ void PlayerHandler::handleMessage(MessageIn &msg) } break; - case GPMSG_SPECIAL_STATUS : + case GPMSG_ABILITY_STATUS: { - PlayerInfo::clearSpecialStatus(); while (msg.getUnreadLength()) { - // { B specialID, L current, L max, L recharge } int id = msg.readInt8(); int current = msg.readInt32(); int max = msg.readInt32(); int recharge = msg.readInt32(); - PlayerInfo::setSpecialStatus(id, current, max, recharge); + PlayerInfo::setAbilityStatus(id, current, max, recharge); } } break; + + case GPMSG_ABILITY_REMOVED: + { + int id = msg.readInt8(); + PlayerInfo::clearAbilityStatus(id); + } break; + /* case SMSG_PLAYER_ARROW_MESSAGE: { @@ -315,14 +292,31 @@ void PlayerHandler::handleMapChangeMessage(MessageIn &msg) void PlayerHandler::attack(int id) { - MessageOut msg(PGMSG_ATTACK); - msg.writeInt16(id); - gameServerConnection->send(msg); + auto ability = AbilityDB::find("Strike"); + if (!ability) + { + logger->log("PlayerHandler::attack: 'Strike' ability not found."); + return; + } + + switch (ability->targetMode) { + case AbilityInfo::TARGET_BEING: + abilityHandler->useOn(ability->id, id); + break; + case AbilityInfo::TARGET_POINT: + logger->log("PlayerHandler::attack: Unsupported target mode 'point' for 'Strike' ability."); + break; + case AbilityInfo::TARGET_DIRECTION: + abilityHandler->useInDirection(ability->id, local_player->getDirection()); + break; + } } void PlayerHandler::emote(int emoteId) { - // TODO + MessageOut msg(PGMSG_BEING_EMOTE); + msg.writeInt8(emoteId); + gameServerConnection->send(msg); } void PlayerHandler::increaseAttribute(int attr) diff --git a/src/net/manaserv/playerhandler.h b/src/net/manaserv/playerhandler.h index a6839112..134b449c 100644 --- a/src/net/manaserv/playerhandler.h +++ b/src/net/manaserv/playerhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_MANASERV_PLAYERHANDLER_H -#define NET_MANASERV_PLAYERHANDLER_H +#pragma once #include "net/playerhandler.h" @@ -77,5 +76,3 @@ class PlayerHandler final : public MessageHandler, public Net::PlayerHandler }; } // namespace ManaServ - -#endif // NET_MANASERV_PLAYERHANDLER_H diff --git a/src/net/manaserv/tradehandler.h b/src/net/manaserv/tradehandler.h index 928a62d9..374dde16 100644 --- a/src/net/manaserv/tradehandler.h +++ b/src/net/manaserv/tradehandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_MANASERV_TRADEHANDLER_H -#define NET_MANASERV_TRADEHANDLER_H +#pragma once #include "net/tradehandler.h" @@ -72,5 +71,3 @@ class TradeHandler final : public MessageHandler, public Net::TradeHandler }; } // namespace ManaServ - -#endif // NET_MANASERV_TRADEHANDLER_H diff --git a/src/net/messagehandler.h b/src/net/messagehandler.h index 3bf6c419..21973fa9 100644 --- a/src/net/messagehandler.h +++ b/src/net/messagehandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_MESSAGEHANDLER_H -#define NET_MESSAGEHANDLER_H +#pragma once #include <cstdint> @@ -32,11 +31,12 @@ namespace Net { class MessageHandler { public: - const uint16_t *handledMessages; + const uint16_t *handledMessages = _no_messages; virtual ~MessageHandler() {} + + private: + static constexpr uint16_t _no_messages[] = { 0 }; }; } // namespace Net - -#endif // NET_MESSAGEHANDLER_H diff --git a/src/net/net.cpp b/src/net/net.cpp index 3a389db2..1d157b3d 100644 --- a/src/net/net.cpp +++ b/src/net/net.cpp @@ -34,7 +34,7 @@ #include "net/npchandler.h" #include "net/partyhandler.h" #include "net/playerhandler.h" -#include "net/specialhandler.h" +#include "net/abilityhandler.h" #include "net/tradehandler.h" #include "net/tmwa/generalhandler.h" @@ -54,7 +54,7 @@ Net::GuildHandler *guildHandler = nullptr; Net::NpcHandler *npcHandler = nullptr; Net::PartyHandler *partyHandler = nullptr; Net::PlayerHandler *playerHandler = nullptr; -Net::SpecialHandler *specialHandler = nullptr; +Net::AbilityHandler *abilityHandler = nullptr; Net::TradeHandler *tradeHandler = nullptr; Net::AdminHandler *Net::getAdminHandler() @@ -112,9 +112,9 @@ Net::PlayerHandler *Net::getPlayerHandler() return playerHandler; } -Net::SpecialHandler *Net::getSpecialHandler() +Net::AbilityHandler *Net::getAbilityHandler() { - return specialHandler; + return abilityHandler; } Net::TradeHandler *Net::getTradeHandler() @@ -124,18 +124,19 @@ Net::TradeHandler *Net::getTradeHandler() namespace Net { -ServerType networkType = ServerType::UNKNOWN; + +static ServerType networkType = ServerType::Unknown; void connectToServer(ServerInfo &server) { - if (server.type == ServerType::UNKNOWN) + if (server.type == ServerType::Unknown) { // TODO: Query the server about itself and choose the netcode based on // that if (server.port == 6901) - server.type = ServerType::TMWATHENA; + server.type = ServerType::TmwAthena; else if (server.port == 9601) - server.type = ServerType::MANASERV; + server.type = ServerType::ManaServ; else logger->error(_("Unknown Server Type! Exiting.")); } @@ -146,20 +147,17 @@ void connectToServer(ServerInfo &server) } else { - if (networkType != ServerType::UNKNOWN && getGeneralHandler() != nullptr) - { - getGeneralHandler()->unload(); - } + unload(); switch (server.type) { #ifdef MANASERV_SUPPORT - case ServerType::MANASERV: - new ManaServ::GeneralHandler; + case ServerType::ManaServ: + generalHandler = new ManaServ::GeneralHandler; break; #endif - case ServerType::TMWATHENA: - new TmwAthena::GeneralHandler; + case ServerType::TmwAthena: + generalHandler = new TmwAthena::GeneralHandler; break; default: logger->error(_("Server protocol unsupported")); @@ -178,10 +176,25 @@ void connectToServer(ServerInfo &server) void unload() { - if (GeneralHandler *handler = getGeneralHandler()) - { - handler->unload(); - } + if (!generalHandler) + return; + + generalHandler->unload(); + delete generalHandler; + + adminHandler = nullptr; + charHandler = nullptr; + chatHandler = nullptr; + generalHandler = nullptr; + inventoryHandler = nullptr; + loginHandler = nullptr; + gameHandler = nullptr; + guildHandler = nullptr; + npcHandler = nullptr; + partyHandler = nullptr; + playerHandler = nullptr; + abilityHandler = nullptr; + tradeHandler = nullptr; } ServerType getNetworkType() diff --git a/src/net/net.h b/src/net/net.h index 135f2cb7..ced0f7ba 100644 --- a/src/net/net.h +++ b/src/net/net.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_H -#define NET_H +#pragma once /** * \namespace Net @@ -45,7 +44,7 @@ class LoginHandler; class NpcHandler; class PartyHandler; class PlayerHandler; -class SpecialHandler; +class AbilityHandler; class TradeHandler; AdminHandler *getAdminHandler(); @@ -59,7 +58,7 @@ LoginHandler *getLoginHandler(); NpcHandler *getNpcHandler(); PartyHandler *getPartyHandler(); PlayerHandler *getPlayerHandler(); -SpecialHandler *getSpecialHandler(); +AbilityHandler *getAbilityHandler(); TradeHandler *getTradeHandler(); ServerType getNetworkType(); @@ -72,5 +71,3 @@ void connectToServer(ServerInfo &server); void unload(); } // namespace Net - -#endif // NET_H diff --git a/src/net/npchandler.h b/src/net/npchandler.h index ac575605..7eeb54f1 100644 --- a/src/net/npchandler.h +++ b/src/net/npchandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NPCHANDLER_H -#define NPCHANDLER_H +#pragma once #include <iosfwd> @@ -61,5 +60,3 @@ class NpcHandler }; } // namespace Net - -#endif // NPCHANDLER_H diff --git a/src/net/partyhandler.h b/src/net/partyhandler.h index 4e14ddbd..58871358 100644 --- a/src/net/partyhandler.h +++ b/src/net/partyhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef PARTYHANDLER_H -#define PARTYHANDLER_H +#pragma once #include <string> @@ -74,5 +73,3 @@ class PartyHandler }; } // namespace Net - -#endif // PARTYHANDLER_H diff --git a/src/net/playerhandler.h b/src/net/playerhandler.h index 6187da2a..b9cf1abf 100644 --- a/src/net/playerhandler.h +++ b/src/net/playerhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef PLAYERHANDLER_H -#define PLAYERHANDLER_H +#pragma once #include "being.h" #include "flooritem.h" @@ -84,5 +83,3 @@ class PlayerHandler }; } // namespace Net - -#endif // PLAYERHANDLER_H diff --git a/src/net/serverinfo.h b/src/net/serverinfo.h index fd66c676..ad862427 100644 --- a/src/net/serverinfo.h +++ b/src/net/serverinfo.h @@ -19,10 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SERVERINFO_H -#define SERVERINFO_H - -#include "utils/stringutils.h" +#pragma once #include <cstdint> #include <deque> @@ -30,9 +27,9 @@ enum class ServerType { - UNKNOWN, - MANASERV, - TMWATHENA + Unknown, + ManaServ, + TmwAthena }; class ServerInfo @@ -40,7 +37,7 @@ class ServerInfo public: using VersionString = std::pair<int, std::string>; - ServerType type = ServerType::UNKNOWN; + ServerType type = ServerType::Unknown; std::string name; std::string hostname; uint16_t port = 0; @@ -53,7 +50,7 @@ public: bool isValid() const { - return !hostname.empty() && port != 0 && type != ServerType::UNKNOWN; + return !hostname.empty() && port != 0 && type != ServerType::Unknown; } void clear() @@ -75,14 +72,14 @@ public: static ServerType parseType(const std::string &type) { - if (compareStrI(type, "tmwathena") == 0) - return ServerType::TMWATHENA; + if (type == "tmwathena") + return ServerType::TmwAthena; // Used for backward compatibility - if (compareStrI(type, "eathena") == 0) - return ServerType::TMWATHENA; - if (compareStrI(type, "manaserv") == 0) - return ServerType::MANASERV; - return ServerType::UNKNOWN; + if (type == "eathena") + return ServerType::TmwAthena; + if (type == "manaserv") + return ServerType::ManaServ; + return ServerType::Unknown; } static uint16_t defaultPortForServerType(ServerType type) @@ -90,11 +87,11 @@ public: switch (type) { default: - case ServerType::UNKNOWN: + case ServerType::Unknown: return 0; - case ServerType::TMWATHENA: + case ServerType::TmwAthena: return 6901; - case ServerType::MANASERV: + case ServerType::ManaServ: return 9601; } } @@ -102,13 +99,11 @@ public: static ServerType defaultServerTypeForPort(uint16_t port) { if (port == 6901) - return ServerType::TMWATHENA; + return ServerType::TmwAthena; if (port == 9601) - return ServerType::MANASERV; - return ServerType::UNKNOWN; + return ServerType::ManaServ; + return ServerType::Unknown; } }; using ServerInfos = std::deque<ServerInfo>; - -#endif // SERVERINFO_H diff --git a/src/net/tmwa/specialhandler.cpp b/src/net/tmwa/abilityhandler.cpp index 2e22b00a..ab891b40 100644 --- a/src/net/tmwa/specialhandler.cpp +++ b/src/net/tmwa/abilityhandler.cpp @@ -19,7 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include "net/tmwa/specialhandler.h" +#include "net/tmwa/abilityhandler.h" #include "event.h" #include "log.h" @@ -64,11 +64,11 @@ /** should always be zero if failed */ #define SKILL_FAILED 0x00 -extern Net::SpecialHandler *specialHandler; +extern Net::AbilityHandler *abilityHandler; namespace TmwAthena { -SpecialHandler::SpecialHandler() +AbilityHandler::AbilityHandler() { static const Uint16 _messages[] = { SMSG_PLAYER_SKILLS, @@ -77,23 +77,20 @@ SpecialHandler::SpecialHandler() 0 }; handledMessages = _messages; - specialHandler = this; + abilityHandler = this; } -void SpecialHandler::handleMessage(MessageIn &msg) +void AbilityHandler::handleMessage(MessageIn &msg) { - int skillCount; - int skillId; - switch (msg.getId()) { - case SMSG_PLAYER_SKILLS: + case SMSG_PLAYER_SKILLS: { msg.readInt16(); // length - skillCount = (msg.getLength() - 4) / 37; + const int skillCount = (msg.getLength() - 4) / 37; for (int k = 0; k < skillCount; k++) { - skillId = msg.readInt16(); + int skillId = msg.readInt16(); msg.readInt16(); // target type msg.skip(2); // unused int level = msg.readInt16(); @@ -107,10 +104,11 @@ void SpecialHandler::handleMessage(MessageIn &msg) skillDialog->setModifiable(skillId, up); } break; + } case SMSG_PLAYER_SKILL_UP: { - skillId = msg.readInt16(); + int skillId = msg.readInt16(); int level = msg.readInt16(); msg.readInt16(); // sp msg.readInt16(); // range @@ -124,20 +122,20 @@ void SpecialHandler::handleMessage(MessageIn &msg) case SMSG_SKILL_FAILED: // Action failed (ex. sit because you have not reached the // right level) - skillId = msg.readInt16(); - short bskill = msg.readInt16(); - msg.readInt16(); // unknown - char success = msg.readInt8(); - char reason = msg.readInt8(); - if (success != SKILL_FAILED && bskill == BSKILL_EMOTE) + int skillId = msg.readInt16(); + auto btype = msg.readInt16(); + msg.readInt16(); // zero1 + msg.readInt8(); // zero2 + auto type = msg.readInt8(); + if (btype == BSKILL_EMOTE) { - logger->log("Action: %d/%d", bskill, success); + logger->log("Action: %d", btype); } std::string msg; - if (success == SKILL_FAILED && skillId == SKILL_BASIC) + if (skillId == SKILL_BASIC) { - switch (bskill) + switch (btype) { case BSKILL_TRADE: msg = _("Trade failed!"); @@ -161,7 +159,7 @@ void SpecialHandler::handleMessage(MessageIn &msg) msg += " "; - switch (reason) + switch (type) { case RFAIL_SKILLDEP: msg += _("You have not yet reached a high enough lvl!"); @@ -219,19 +217,19 @@ void SpecialHandler::handleMessage(MessageIn &msg) } } -void SpecialHandler::use(int id) +void AbilityHandler::use(int id) { } -void SpecialHandler::use(int id, int level, int beingId) +void AbilityHandler::useOn(int id, int beingId) { } -void SpecialHandler::use(int id, int level, int x, int y) +void AbilityHandler::useAt(int id, int x, int y) { } -void SpecialHandler::use(int id, const std::string &map) +void AbilityHandler::useInDirection(int id, int direction) { } diff --git a/src/net/tmwa/specialhandler.h b/src/net/tmwa/abilityhandler.h index 09b6ce8d..171f7d41 100644 --- a/src/net/tmwa/specialhandler.h +++ b/src/net/tmwa/abilityhandler.h @@ -19,32 +19,29 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_TA_SKILLHANDLER_H -#define NET_TA_SKILLHANDLER_H +#pragma once #include "net/net.h" -#include "net/specialhandler.h" +#include "net/abilityhandler.h" #include "net/tmwa/messagehandler.h" namespace TmwAthena { -class SpecialHandler final : public MessageHandler, public Net::SpecialHandler +class AbilityHandler final : public MessageHandler, public Net::AbilityHandler { public: - SpecialHandler(); + AbilityHandler(); void handleMessage(MessageIn &msg) override; void use(int id) override; - void use(int id, int level, int beingId) override; + void useOn(int id, int beingId) override; - void use(int id, int level, int x, int y) override; + void useAt(int id, int x, int y) override; - void use(int id, const std::string &map) override; + void useInDirection(int id, int direction) override; }; } // namespace TmwAthena - -#endif // NET_TA_SKILLHANDLER_H diff --git a/src/net/tmwa/adminhandler.cpp b/src/net/tmwa/adminhandler.cpp index c60050bd..4c4ecdad 100644 --- a/src/net/tmwa/adminhandler.cpp +++ b/src/net/tmwa/adminhandler.cpp @@ -68,10 +68,7 @@ void AdminHandler::handleMessage(MessageIn &msg) id = msg.readInt32(); int ip = msg.readInt32(); if (Being *player = actorSpriteManager->findBeing(id)) - { player->setIp(ip); - player->updateName(); - } break; } } diff --git a/src/net/tmwa/adminhandler.h b/src/net/tmwa/adminhandler.h index 17d547a8..6a233823 100644 --- a/src/net/tmwa/adminhandler.h +++ b/src/net/tmwa/adminhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_TA_ADMINHANDLER_H -#define NET_TA_ADMINHANDLER_H +#pragma once #include "net/adminhandler.h" #include "net/net.h" @@ -44,5 +43,3 @@ class AdminHandler final : public MessageHandler, public Net::AdminHandler }; } // namespace TmwAthena - -#endif // NET_TA_ADMINHANDLER_H diff --git a/src/net/tmwa/beinghandler.cpp b/src/net/tmwa/beinghandler.cpp index e2c4f158..c5979e9f 100644 --- a/src/net/tmwa/beinghandler.cpp +++ b/src/net/tmwa/beinghandler.cpp @@ -40,6 +40,7 @@ #include "resources/emotedb.h" #include "resources/hairdb.h" +#include "resources/statuseffectdb.h" #include <cmath> @@ -78,29 +79,51 @@ BeingHandler::BeingHandler(bool enableSync): handledMessages = _messages; } -static Being *createBeing(int id, short job) +static ActorSprite::Type typeFromJob(short job) { - ActorSprite::Type type = ActorSprite::UNKNOWN; if (job <= 25 || (job >= 4001 && job <= 4049)) - type = ActorSprite::PLAYER; - else if (job >= 46 && job <= 1000) - type = ActorSprite::NPC; - else if (job > 1000 && job <= 2000) - type = ActorSprite::MONSTER; - else if (job == 45) + return ActorSprite::PLAYER; + if (job >= 46 && job <= 1000) + return ActorSprite::NPC; + if (job > 1000 && job <= 2000) + return ActorSprite::MONSTER; + if (job == 45) + return ActorSprite::PORTAL; + + return ActorSprite::UNKNOWN; +} + +static Being *createBeing(int id, short job) +{ + const auto type = typeFromJob(job); + if (type == ActorSprite::PORTAL) return nullptr; // Skip portals Being *being = actorSpriteManager->createBeing(id, type, job); if (type == ActorSprite::PLAYER || type == ActorSprite::NPC) { - MessageOut outMsg(0x0094); - outMsg.writeInt32(id);//readLong(2)); + MessageOut outMsg(CMSG_NAME_REQUEST); + outMsg.writeInt32(id); } return being; } +static void updateBeingType(Being *being, short job) +{ + const auto type = typeFromJob(job); + const bool typeChanged = being->getType() != type; + + being->setType(type, job); + + if (typeChanged && type == ActorSprite::PLAYER) + { + MessageOut outMsg(CMSG_NAME_REQUEST); + outMsg.writeInt32(being->getId()); + } +} + static void handleMoveMessage(Map *map, Being *dstBeing, Uint16 srcX, Uint16 srcY, Uint16 dstX, Uint16 dstY) @@ -146,6 +169,42 @@ static void handlePosMessage(Map *map, Being *dstBeing, Uint16 x, Uint16 y, } } +static void applyStatusEffectsByOption1(Being *being, + const StatusEffectDB::OptionsMap &map, + uint16_t option) +{ + for (auto &[opt, id] : map) + being->setStatusEffect(id, option == opt); +} + +static void applyStatusEffectsByOption(Being *being, + const StatusEffectDB::OptionsMap &map, + uint16_t option) +{ + for (auto &[opt, id] : map) + { + const bool enabled = (option & opt) != 0; + being->setStatusEffect(id, enabled); + } +} + +/** + * Maps flags or indexes to their corresponding status effect index and + * updates the state of the given being. This is tmwAthena-specific. + */ +static void applyStatusEffects(Being *being, + uint16_t opt0, + uint16_t opt1, + uint16_t opt2, + std::optional<uint16_t> opt3 = {}) +{ + applyStatusEffectsByOption(being, StatusEffectDB::opt0ToIdMap(), opt0); + applyStatusEffectsByOption1(being, StatusEffectDB::opt1ToIdMap(), opt1); + applyStatusEffectsByOption(being, StatusEffectDB::opt2ToIdMap(), opt2); + if (opt3) + applyStatusEffectsByOption(being, StatusEffectDB::opt3ToIdMap(), *opt3); +} + void BeingHandler::handleMessage(MessageIn &msg) { if (!actorSpriteManager) @@ -159,8 +218,10 @@ void BeingHandler::handleMessage(MessageIn &msg) Uint16 weapon, shield; Uint16 gmstatus; int param1; - int stunMode; - Uint32 statusEffects; + uint16_t opt0; + uint16_t opt1; + uint16_t opt2; + uint16_t opt3; int type, guild; Uint16 status; Being *srcBeing, *dstBeing; @@ -176,9 +237,9 @@ void BeingHandler::handleMessage(MessageIn &msg) // Information about a being in range id = msg.readInt32(); speed = (float)msg.readInt16(); - stunMode = msg.readInt16(); // opt1 - statusEffects = msg.readInt16(); // opt2 - statusEffects |= ((Uint32)msg.readInt16()) << 16; // option + opt1 = msg.readInt16(); + opt2 = msg.readInt16(); + opt0 = msg.readInt16(); job = msg.readInt16(); // class dstBeing = actorSpriteManager->findBeing(id); @@ -209,7 +270,7 @@ void BeingHandler::handleMessage(MessageIn &msg) speed = 150.0f; // In ticks per tile * 10 dstBeing->setMoveSpeed(Vector(speed / 10, speed / 10)); - dstBeing->setSubtype(job); + updateBeingType(dstBeing, job); hairStyle = msg.readInt16(); weapon = msg.readInt16(); headBottom = msg.readInt16(); @@ -236,14 +297,14 @@ void BeingHandler::handleMessage(MessageIn &msg) } msg.readInt16(); // guild emblem msg.readInt16(); // manner - dstBeing->setStatusEffectBlock(32, msg.readInt16()); // opt3 + opt3 = msg.readInt16(); msg.readInt8(); // karma gender = msg.readInt8(); if (dstBeing->getType() == ActorSprite::PLAYER) { - dstBeing->setGender(gender == 0 ? Gender::FEMALE - : Gender::MALE); + dstBeing->setGender(gender == 0 ? Gender::Female + : Gender::Male); // Set these after the gender, as the sprites may be gender-specific dstBeing->setSprite(SPRITE_HAIR, hairStyle * -1, hairDB.getHairColor(hairColor)); @@ -274,9 +335,7 @@ void BeingHandler::handleMessage(MessageIn &msg) msg.readInt8(); // unknown msg.readInt8(); // unknown / sit - dstBeing->setStunMode(stunMode); - dstBeing->setStatusEffectBlock(0, (statusEffects >> 16) & 0xffff); - dstBeing->setStatusEffectBlock(16, statusEffects & 0xffff); + applyStatusEffects(dstBeing, opt0, opt1, opt2, opt3); break; case SMSG_BEING_SPAWN: @@ -417,6 +476,9 @@ void BeingHandler::handleMessage(MessageIn &msg) switch (type) { + case LOOK::BASE: + updateBeingType(dstBeing, id); + break; case LOOK::HAIR: { // const int look = id / 256; @@ -505,10 +567,9 @@ void BeingHandler::handleMessage(MessageIn &msg) // An update about a player, potentially including movement. id = msg.readInt32(); speed = msg.readInt16(); - stunMode = msg.readInt16(); // opt1; Aethyra use this as cape - statusEffects = msg.readInt16(); // opt2; Aethyra use this as misc1 - statusEffects |= ((Uint32) msg.readInt16()) - << 16; // status.options; Aethyra uses this as misc2 + opt1 = msg.readInt16(); + opt2 = msg.readInt16(); + opt0 = msg.readInt16(); job = msg.readInt16(); dstBeing = actorSpriteManager->findBeing(id); @@ -531,7 +592,7 @@ void BeingHandler::handleMessage(MessageIn &msg) else dstBeing->setMoveSpeed(Net::getPlayerHandler()->getDefaultMoveSpeed()); - dstBeing->setSubtype(job); + updateBeingType(dstBeing, job); hairStyle = msg.readInt16(); weapon = msg.readInt16(); shield = msg.readInt16(); @@ -545,15 +606,16 @@ void BeingHandler::handleMessage(MessageIn &msg) headTop = msg.readInt16(); headMid = msg.readInt16(); hairColor = msg.readInt16(); - shoes = msg.readInt16(); - gloves = msg.readInt16(); + msg.readInt16(); // clothes_color + msg.readInt8(); // head_dir + msg.readInt8(); // unused2 msg.readInt32(); // guild msg.readInt16(); // emblem msg.readInt16(); // manner - dstBeing->setStatusEffectBlock(32, msg.readInt16()); // opt3 + opt3 = msg.readInt16(); msg.readInt8(); // karma - dstBeing->setGender(msg.readInt8() == 0 ? Gender::FEMALE - : Gender::MALE); + dstBeing->setGender(msg.readInt8() == 0 ? Gender::Female + : Gender::Male); // Set these after the gender, as the sprites may be gender-specific dstBeing->setSprite(SPRITE_WEAPON, weapon, "", true); dstBeing->setSprite(SPRITE_SHIELD, shield); @@ -601,15 +663,13 @@ void BeingHandler::handleMessage(MessageIn &msg) } else if (msg.getId() == SMSG_PLAYER_MOVE) { - msg.readInt8(); // unknown + msg.readInt8(); // five } msg.readInt8(); // Lv - msg.readInt8(); // unknown + msg.readInt8(); // unused - dstBeing->setStunMode(stunMode); - dstBeing->setStatusEffectBlock(0, (statusEffects >> 16) & 0xffff); - dstBeing->setStatusEffectBlock(16, statusEffects & 0xffff); + applyStatusEffects(dstBeing, opt0, opt1, opt2, opt3); break; case SMSG_PLAYER_STOP: @@ -654,14 +714,12 @@ void BeingHandler::handleMessage(MessageIn &msg) if (!dstBeing) break; - stunMode = msg.readInt16(); - statusEffects = msg.readInt16(); - statusEffects |= ((Uint32) msg.readInt16()) << 16; - msg.readInt8(); // Unused? + opt1 = msg.readInt16(); + opt2 = msg.readInt16(); + opt0 = msg.readInt16(); + msg.readInt8(); // zero - dstBeing->setStunMode(stunMode); - dstBeing->setStatusEffectBlock(0, (statusEffects >> 16) & 0xffff); - dstBeing->setStatusEffectBlock(16, statusEffects & 0xffff); + applyStatusEffects(dstBeing, opt0, opt1, opt2); break; case SMSG_BEING_STATUS_CHANGE: diff --git a/src/net/tmwa/beinghandler.h b/src/net/tmwa/beinghandler.h index f1c8887f..fd8f08f5 100644 --- a/src/net/tmwa/beinghandler.h +++ b/src/net/tmwa/beinghandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_TA_BEINGHANDLER_H -#define NET_TA_BEINGHANDLER_H +#pragma once #include "net/tmwa/messagehandler.h" @@ -39,5 +38,3 @@ class BeingHandler final : public MessageHandler }; } // namespace TmwAthena - -#endif // NET_TA_BEINGHANDLER_H diff --git a/src/net/tmwa/buysellhandler.h b/src/net/tmwa/buysellhandler.h index 8f7a324f..cad9f943 100644 --- a/src/net/tmwa/buysellhandler.h +++ b/src/net/tmwa/buysellhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_TA_BUYSELLHANDLER_H -#define NET_TA_BUYSELLHANDLER_H +#pragma once #include "net/tmwa/messagehandler.h" @@ -41,5 +40,3 @@ class BuySellHandler final : public MessageHandler }; } // namespace TmwAthena - -#endif // NET_TA_BUYSELLHANDLER_H diff --git a/src/net/tmwa/charserverhandler.cpp b/src/net/tmwa/charserverhandler.cpp index 636b58ce..0ecbb135 100644 --- a/src/net/tmwa/charserverhandler.cpp +++ b/src/net/tmwa/charserverhandler.cpp @@ -28,7 +28,6 @@ #include "gui/charcreatedialog.h" #include "gui/okdialog.h" -#include "net/logindata.h" #include "net/net.h" #include "net/tmwa/gamehandler.h" @@ -210,21 +209,20 @@ void CharServerHandler::readPlayerData(MessageIn &msg, Net::Character *character const Token &token = static_cast<LoginHandler*>(Net::getLoginHandler())->getToken(); - auto *tempPlayer = new LocalPlayer(msg.readInt32(), 0); - tempPlayer->setGender(token.sex); + const int id = msg.readInt32(); character->data.mAttributes[EXP] = msg.readInt32(); character->data.mAttributes[MONEY] = msg.readInt32(); character->data.mStats[JOB].exp = msg.readInt32(); - int temp = msg.readInt32(); + const int temp = msg.readInt32(); character->data.mStats[JOB].base = temp; character->data.mStats[JOB].mod = temp; - tempPlayer->setSprite(SPRITE_SHOE, msg.readInt16()); - tempPlayer->setSprite(SPRITE_GLOVES, msg.readInt16()); - tempPlayer->setSprite(SPRITE_CAPE, msg.readInt16()); - tempPlayer->setSprite(SPRITE_MISC1, msg.readInt16()); + const int shoe = msg.readInt16(); + const int gloves = msg.readInt16(); + const int cape = msg.readInt16(); + const int misc1 = msg.readInt16(); msg.readInt32(); // option msg.readInt32(); // karma @@ -240,8 +238,15 @@ void CharServerHandler::readPlayerData(MessageIn &msg, Net::Character *character const uint16_t race = msg.readInt16(); // class (used for race) int hairStyle = msg.readInt8(); msg.readInt8(); // look - tempPlayer->setSubtype(race); - Uint16 weapon = msg.readInt16(); + const uint16_t weapon = msg.readInt16(); + + auto *tempPlayer = new LocalPlayer(id, race); + tempPlayer->setGender(token.sex); + + tempPlayer->setSprite(SPRITE_SHOE, shoe); + tempPlayer->setSprite(SPRITE_GLOVES, gloves); + tempPlayer->setSprite(SPRITE_CAPE, cape); + tempPlayer->setSprite(SPRITE_MISC1, misc1); tempPlayer->setSprite(SPRITE_WEAPON, weapon, "", true); character->data.mAttributes[LEVEL] = msg.readInt16(); @@ -263,7 +268,7 @@ void CharServerHandler::readPlayerData(MessageIn &msg, Net::Character *character character->slot = msg.readInt8(); // character slot const uint8_t sex = msg.readInt8(); - tempPlayer->setGender(sex ? Gender::MALE : Gender::FEMALE); + tempPlayer->setGender(sex ? Gender::Male : Gender::Female); } void CharServerHandler::setCharSelectDialog(CharSelectDialog *window) @@ -395,7 +400,7 @@ void CharServerHandler::connect() // [Fate] The next word is unused by the old char server, so we squeeze in // mana client version information outMsg.writeInt16(CLIENT_PROTOCOL_VERSION); - outMsg.writeInt8(token.sex == Gender::MALE ? 1 : 0); + outMsg.writeInt8(token.sex == Gender::Male ? 1 : 0); // We get 4 useless bytes before the real answer comes in (what are these?) mNetwork->skip(4); diff --git a/src/net/tmwa/charserverhandler.h b/src/net/tmwa/charserverhandler.h index 9633c8d9..b0d3e970 100644 --- a/src/net/tmwa/charserverhandler.h +++ b/src/net/tmwa/charserverhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_TA_CHARSERVERHANDLER_H -#define NET_TA_CHARSERVERHANDLER_H +#pragma once #include "net/charhandler.h" @@ -75,9 +74,7 @@ class CharServerHandler final : public MessageHandler, public Net::CharHandler void connect(); private: - void readPlayerData(MessageIn &msg, Net::Character *character); + static void readPlayerData(MessageIn &msg, Net::Character *character); }; } // namespace TmwAthena - -#endif // NET_TA_CHARSERVERHANDLER_H diff --git a/src/net/tmwa/chathandler.cpp b/src/net/tmwa/chathandler.cpp index 4f998922..f9061f63 100644 --- a/src/net/tmwa/chathandler.cpp +++ b/src/net/tmwa/chathandler.cpp @@ -271,7 +271,7 @@ void ChatHandler::handleMessage(MessageIn &msg) msg.readInt8(); // gm level msg.readInt8(); // gender - Avatar *avatar = new Avatar(nick); + auto *avatar = new Avatar(nick); avatar->setOnline(true); players.push_back(avatar); } diff --git a/src/net/tmwa/chathandler.h b/src/net/tmwa/chathandler.h index d545b221..b16b3dc4 100644 --- a/src/net/tmwa/chathandler.h +++ b/src/net/tmwa/chathandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_TA_CHATHANDLER_H -#define NET_TA_CHATHANDLER_H +#pragma once #include "net/chathandler.h" #include "net/net.h" @@ -73,5 +72,3 @@ class ChatHandler final : public MessageHandler, public Net::ChatHandler }; } // namespace TmwAthena - -#endif // NET_TA_CHATHANDLER_H diff --git a/src/net/tmwa/gamehandler.cpp b/src/net/tmwa/gamehandler.cpp index abc9c73c..0a3bb9d9 100644 --- a/src/net/tmwa/gamehandler.cpp +++ b/src/net/tmwa/gamehandler.cpp @@ -140,7 +140,7 @@ void GameHandler::connect() outMsg.writeInt32(mCharID); outMsg.writeInt32(token.session_ID1); outMsg.writeInt32(token.session_ID2); - outMsg.writeInt8(token.sex == Gender::MALE ? 1 : 0); + outMsg.writeInt8(token.sex == Gender::Male ? 1 : 0); // We get 4 useless bytes before the real answer comes in (what are these?) mNetwork->skip(4); diff --git a/src/net/tmwa/gamehandler.h b/src/net/tmwa/gamehandler.h index ecd8df2d..2f876b82 100644 --- a/src/net/tmwa/gamehandler.h +++ b/src/net/tmwa/gamehandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_TA_MAPHANDLER_H -#define NET_TA_MAPHANDLER_H +#pragma once #include "eventlistener.h" @@ -74,5 +73,3 @@ class GameHandler final : public MessageHandler, public Net::GameHandler, }; } // namespace TmwAthena - -#endif // NET_TA_MAPHANDLER_H diff --git a/src/net/tmwa/generalhandler.cpp b/src/net/tmwa/generalhandler.cpp index e1b1a9ea..d6eb3b34 100644 --- a/src/net/tmwa/generalhandler.cpp +++ b/src/net/tmwa/generalhandler.cpp @@ -25,7 +25,6 @@ #include "configuration.h" #include "log.h" -#include "gui/inventorywindow.h" #include "gui/skilldialog.h" #include "gui/socialwindow.h" #include "gui/statuswindow.h" @@ -49,7 +48,7 @@ #include "net/tmwa/playerhandler.h" #include "net/tmwa/protocol.h" #include "net/tmwa/tradehandler.h" -#include "net/tmwa/specialhandler.h" +#include "net/tmwa/abilityhandler.h" #include "net/tmwa/gui/guildtab.h" #include "net/tmwa/gui/partytab.h" @@ -60,8 +59,6 @@ #include <list> -extern Net::GeneralHandler *generalHandler; - namespace TmwAthena { ServerInfo charServer; @@ -84,7 +81,7 @@ GeneralHandler::GeneralHandler(): mNpcHandler(new NpcHandler), mPartyHandler(new PartyHandler), mPlayerHandler(new PlayerHandler), - mSpecialHandler(new SpecialHandler), + mAbilityHandler(new AbilityHandler), mTradeHandler(new TradeHandler) { static const Uint16 _messages[] = { @@ -92,7 +89,6 @@ GeneralHandler::GeneralHandler(): 0 }; handledMessages = _messages; - generalHandler = this; std::list<ItemStat> stats; stats.emplace_back("str", _("Strength %+d")); @@ -155,6 +151,7 @@ void GeneralHandler::handleMessage(MessageIn &msg) void GeneralHandler::load() { + // This sets mNetwork to the created Network instance (new Network)->registerHandler(this); mNetwork->registerHandler(mAdminHandler.get()); @@ -169,7 +166,7 @@ void GeneralHandler::load() mNetwork->registerHandler(mLoginHandler.get()); mNetwork->registerHandler(mNpcHandler.get()); mNetwork->registerHandler(mPlayerHandler.get()); - mNetwork->registerHandler(mSpecialHandler.get()); + mNetwork->registerHandler(mAbilityHandler.get()); mNetwork->registerHandler(mTradeHandler.get()); mNetwork->registerHandler(mPartyHandler.get()); } @@ -209,11 +206,6 @@ void GeneralHandler::flushNetwork() } } -void GeneralHandler::clearHandlers() -{ - mNetwork->clearHandlers(); -} - void GeneralHandler::event(Event::Channel channel, const Event &event) { @@ -221,7 +213,6 @@ void GeneralHandler::event(Event::Channel channel, { if (event.getType() == Event::GuiWindowsLoaded) { - inventoryWindow->setSplitAllowed(false); skillDialog->loadSkills(); statusWindow->addAttribute(STRENGTH, _("Strength"), true, ""); diff --git a/src/net/tmwa/generalhandler.h b/src/net/tmwa/generalhandler.h index f105da96..1da81ba8 100644 --- a/src/net/tmwa/generalhandler.h +++ b/src/net/tmwa/generalhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_TMWA_GENERALHANDLER_H -#define NET_TMWA_GENERALHANDLER_H +#pragma once #include "eventlistener.h" @@ -49,8 +48,6 @@ class GeneralHandler final : public MessageHandler, public Net::GeneralHandler, void flushNetwork() override; - void clearHandlers() override; - void event(Event::Channel channel, const Event &event) override; protected: @@ -67,10 +64,8 @@ class GeneralHandler final : public MessageHandler, public Net::GeneralHandler, MessageHandlerPtr mNpcHandler; MessageHandlerPtr mPartyHandler; MessageHandlerPtr mPlayerHandler; - MessageHandlerPtr mSpecialHandler; + MessageHandlerPtr mAbilityHandler; MessageHandlerPtr mTradeHandler; }; } // namespace TmwAthena - -#endif // NET_TMWA_GENERALHANDLER_H diff --git a/src/net/tmwa/gui/guildtab.h b/src/net/tmwa/gui/guildtab.h index 3f864748..c51e63d0 100644 --- a/src/net/tmwa/gui/guildtab.h +++ b/src/net/tmwa/gui/guildtab.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef TA_GUILDTAB_H -#define TA_GUILDTAB_H +#pragma once #include "gui/widgets/chattab.h" @@ -49,5 +48,3 @@ class GuildTab : public ChatTab extern GuildTab *guildTab; } // namespace TmwAthena - -#endif // TA_GUILDTAB_H diff --git a/src/net/tmwa/gui/partytab.h b/src/net/tmwa/gui/partytab.h index a6c52f58..39a1f978 100644 --- a/src/net/tmwa/gui/partytab.h +++ b/src/net/tmwa/gui/partytab.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef TA_PARTYTAB_H -#define TA_PARTYTAB_H +#pragma once #include "gui/widgets/chattab.h" @@ -49,5 +48,3 @@ class PartyTab : public ChatTab extern PartyTab *partyTab; } // namespace TmwAthena - -#endif // TA_PARTYTAB_H diff --git a/src/net/tmwa/guildhandler.cpp b/src/net/tmwa/guildhandler.cpp index 1877f0f1..e8ee4848 100644 --- a/src/net/tmwa/guildhandler.cpp +++ b/src/net/tmwa/guildhandler.cpp @@ -40,11 +40,6 @@ Guild *taGuild; GuildHandler::GuildHandler() { - static const Uint16 _messages[] = { - 0 - }; - handledMessages = _messages; - guildHandler = this; } diff --git a/src/net/tmwa/guildhandler.h b/src/net/tmwa/guildhandler.h index 9bbe9b4a..6faca0cc 100644 --- a/src/net/tmwa/guildhandler.h +++ b/src/net/tmwa/guildhandler.h @@ -18,8 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_TA_GUILDHANDLER_H -#define NET_TA_GUILDHANDLER_H +#pragma once #include "net/guildhandler.h" @@ -67,5 +66,3 @@ class GuildHandler final : public Net::GuildHandler, public MessageHandler }; } - -#endif // NET_TA_GUILDHANDLER_H diff --git a/src/net/tmwa/inventoryhandler.cpp b/src/net/tmwa/inventoryhandler.cpp index 496ce621..0d8e3005 100644 --- a/src/net/tmwa/inventoryhandler.cpp +++ b/src/net/tmwa/inventoryhandler.cpp @@ -521,11 +521,6 @@ void InventoryHandler::event(Event::Channel channel, } } -bool InventoryHandler::canSplit(const Item *item) -{ - return false; -} - size_t InventoryHandler::getSize(int type) const { switch (type) diff --git a/src/net/tmwa/inventoryhandler.h b/src/net/tmwa/inventoryhandler.h index 51a0fe51..47226bea 100644 --- a/src/net/tmwa/inventoryhandler.h +++ b/src/net/tmwa/inventoryhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_TA_INVENTORYHANDLER_H -#define NET_TA_INVENTORYHANDLER_H +#pragma once #include "eventlistener.h" #include "inventory.h" @@ -38,7 +37,7 @@ #include "utils/gettext.h" -#include <list> +#include <vector> namespace TmwAthena { @@ -166,8 +165,6 @@ class InventoryHandler final : public MessageHandler, public Net::InventoryHandl void event(Event::Channel channel, const Event &event) override; - bool canSplit(const Item *item) override; - size_t getSize(int type) const override; // Note the slot type id is equal to the slot Index for tA. @@ -195,5 +192,3 @@ class InventoryHandler final : public MessageHandler, public Net::InventoryHandl }; } // namespace TmwAthena - -#endif // NET_TA_INVENTORYHANDLER_H diff --git a/src/net/tmwa/itemhandler.h b/src/net/tmwa/itemhandler.h index 0c6175d8..5e42e0d8 100644 --- a/src/net/tmwa/itemhandler.h +++ b/src/net/tmwa/itemhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_TA_ITEMHANDLER_H -#define NET_TA_ITEMHANDLER_H +#pragma once #include "net/tmwa/messagehandler.h" @@ -35,5 +34,3 @@ class ItemHandler final : public MessageHandler }; } // namespace TmwAthena - -#endif // NET_TA_ITEMHANDLER_H diff --git a/src/net/tmwa/loginhandler.cpp b/src/net/tmwa/loginhandler.cpp index 24bc9ab2..a7162ee6 100644 --- a/src/net/tmwa/loginhandler.cpp +++ b/src/net/tmwa/loginhandler.cpp @@ -103,17 +103,15 @@ void LoginHandler::handleMessage(MessageIn &msg) } break; - case SMSG_UPDATE_HOST: - int len; - - len = msg.readInt16() - 4; + case SMSG_UPDATE_HOST: { + int len = msg.readInt16() - 4; mUpdateHost = msg.readString(len); loginData.updateHost = mUpdateHost; logger->log("Received update host \"%s\" from login server.", mUpdateHost.c_str()); break; - + } case SMSG_LOGIN_DATA: // Skip the length word msg.skip(2); @@ -126,7 +124,7 @@ void LoginHandler::handleMessage(MessageIn &msg) mToken.account_ID = msg.readInt32(); mToken.session_ID2 = msg.readInt32(); msg.skip(30); // unused - mToken.sex = msg.readInt8() ? Gender::MALE : Gender::FEMALE; + mToken.sex = msg.readInt8() ? Gender::Male : Gender::Female; for (int i = 0; i < worldCount; i++) { @@ -306,7 +304,7 @@ void LoginHandler::chooseServer(unsigned int server) void LoginHandler::registerAccount(LoginData *loginData) { std::string username = loginData->username; - username.append(loginData->gender == Gender::FEMALE ? "_F" : "_M"); + username.append(loginData->gender == Gender::Female ? "_F" : "_M"); sendLoginRegister(username, loginData->password); } diff --git a/src/net/tmwa/loginhandler.h b/src/net/tmwa/loginhandler.h index 25dd414d..f6b8a530 100644 --- a/src/net/tmwa/loginhandler.h +++ b/src/net/tmwa/loginhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_TMWA_LOGINHANDLER_H -#define NET_TMWA_LOGINHANDLER_H +#pragma once #include "net/loginhandler.h" @@ -94,5 +93,3 @@ class LoginHandler final : public MessageHandler, public Net::LoginHandler }; } // namespace TmwAthena - -#endif // NET_TA_LOGINHANDLER_H diff --git a/src/net/tmwa/messagehandler.h b/src/net/tmwa/messagehandler.h index 0d278c15..8e3c6a19 100644 --- a/src/net/tmwa/messagehandler.h +++ b/src/net/tmwa/messagehandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_TA_MESSAGEHANDLER_H -#define NET_TA_MESSAGEHANDLER_H +#pragma once #include "net/messagehandler.h" @@ -52,5 +51,3 @@ class MessageHandler : public Net::MessageHandler using MessageHandlerPtr = const std::unique_ptr<MessageHandler>; } - -#endif // NET_TA_MESSAGEHANDLER_H diff --git a/src/net/tmwa/messagein.cpp b/src/net/tmwa/messagein.cpp index 7c142619..c0db0fca 100644 --- a/src/net/tmwa/messagein.cpp +++ b/src/net/tmwa/messagein.cpp @@ -21,6 +21,9 @@ #include "net/tmwa/messagein.h" +#include "being.h" +#include "net/tmwa/protocol.h" + #include <SDL_endian.h> #define MAKEWORD(low,high) \ @@ -88,38 +91,20 @@ void MessageIn::readCoordinates(uint16_t &x, uint16_t &y, uint8_t &direction) direction = data[2] & 0x000f; // Translate from tmwAthena format - switch (direction) + switch (static_cast<DIR>(direction)) { - case 0: - direction = 1; - break; - case 1: - direction = 3; - break; - case 2: - direction = 2; - break; - case 3: - direction = 6; - break; - case 4: - direction = 4; - break; - case 5: - direction = 12; - break; - case 6: - direction = 8; - break; - case 7: - direction = 9; - break; - case 8: - direction = 8; - break; + case DIR::S: direction = Being::DOWN; break; + case DIR::SW: direction = Being::DOWN | Being::LEFT; break; + case DIR::W: direction = Being::LEFT; break; + case DIR::NW: direction = Being::UP | Being::LEFT; break; + case DIR::N: direction = Being::UP; break; + case DIR::NE: direction = Being::UP | Being::RIGHT; break; + case DIR::E: direction = Being::RIGHT; break; + case DIR::SE: direction = Being::DOWN | Being::RIGHT; break; default: // OOPSIE! Impossible or unknown direction = 0; + break; } } mPos += 3; @@ -162,7 +147,7 @@ std::string MessageIn::readString(int length) if (length < 0 || mPos + length > mLength) { mPos = mLength + 1; - return ""; + return std::string(); } // Read the string diff --git a/src/net/tmwa/messagein.h b/src/net/tmwa/messagein.h index 2f66ca28..b2fb6716 100644 --- a/src/net/tmwa/messagein.h +++ b/src/net/tmwa/messagein.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_TA_MESSAGEIN_H -#define NET_TA_MESSAGEIN_H +#pragma once #include <cstdint> #include <string> @@ -109,5 +108,3 @@ class MessageIn }; } // TmwAthena - -#endif // NET_TA_MESSAGEIN_H diff --git a/src/net/tmwa/messageout.h b/src/net/tmwa/messageout.h index b60644de..ba2d3c75 100644 --- a/src/net/tmwa/messageout.h +++ b/src/net/tmwa/messageout.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_TA_MESSAGEOUT_H -#define NET_TA_MESSAGEOUT_H +#pragma once #include <cstdint> #include <string> @@ -73,5 +72,3 @@ class MessageOut }; } // namespace TmwAthena - -#endif // NET_TA_MESSAGEOUT_H diff --git a/src/net/tmwa/network.cpp b/src/net/tmwa/network.cpp index 5a2dd0d0..353495da 100644 --- a/src/net/tmwa/network.cpp +++ b/src/net/tmwa/network.cpp @@ -331,20 +331,16 @@ void Network::disconnect() void Network::registerHandler(MessageHandler *handler) { - for (const Uint16 *i = handler->handledMessages; *i; ++i) - { + for (const uint16_t *i = handler->handledMessages; *i; ++i) mMessageHandlers[*i] = handler; - } handler->setNetwork(this); } void Network::unregisterHandler(MessageHandler *handler) { - for (const Uint16 *i = handler->handledMessages; *i; ++i) - { + for (const uint16_t *i = handler->handledMessages; *i; ++i) mMessageHandlers.erase(*i); - } handler->setNetwork(nullptr); } @@ -352,9 +348,8 @@ void Network::unregisterHandler(MessageHandler *handler) void Network::clearHandlers() { for (auto& [_, messageHandler] : mMessageHandlers) - { messageHandler->setNetwork(nullptr); - } + mMessageHandlers.clear(); } diff --git a/src/net/tmwa/network.h b/src/net/tmwa/network.h index fa0237dd..53f15761 100644 --- a/src/net/tmwa/network.h +++ b/src/net/tmwa/network.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_TA_NETWORK_H -#define NET_TA_NETWORK_H +#pragma once #include "utils/mutex.h" @@ -125,5 +124,3 @@ class Network }; } // namespace TmwAthena - -#endif // NET_TA_NETWORK_H diff --git a/src/net/tmwa/npchandler.h b/src/net/tmwa/npchandler.h index b55a8155..df23531f 100644 --- a/src/net/tmwa/npchandler.h +++ b/src/net/tmwa/npchandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_TA_NPCHANDLER_H -#define NET_TA_NPCHANDLER_H +#pragma once #include "net/npchandler.h" @@ -65,5 +64,3 @@ class NpcHandler final : public MessageHandler, public Net::NpcHandler }; } // namespace TmwAthena - -#endif // NET_TA_NPCHANDLER_H diff --git a/src/net/tmwa/partyhandler.h b/src/net/tmwa/partyhandler.h index 14c6d9f3..3f99de20 100644 --- a/src/net/tmwa/partyhandler.h +++ b/src/net/tmwa/partyhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_TA_PARTYHANDLER_H -#define NET_TA_PARTYHANDLER_H +#pragma once #include "net/net.h" #include "net/partyhandler.h" @@ -71,5 +70,3 @@ class PartyHandler final : public MessageHandler, public Net::PartyHandler }; } // namespace TmwAthena - -#endif // NET_TA_PARTYHANDLER_H diff --git a/src/net/tmwa/playerhandler.h b/src/net/tmwa/playerhandler.h index 1a90532e..f1a67e94 100644 --- a/src/net/tmwa/playerhandler.h +++ b/src/net/tmwa/playerhandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_TA_PLAYERHANDLER_H -#define NET_TA_PLAYERHANDLER_H +#pragma once #include "net/net.h" #include "net/playerhandler.h" @@ -67,5 +66,3 @@ class PlayerHandler final : public MessageHandler, public Net::PlayerHandler }; } // namespace TmwAthena - -#endif // NET_TA_PLAYERHANDLER_H diff --git a/src/net/tmwa/protocol.h b/src/net/tmwa/protocol.h index 609b18a5..532ac90e 100644 --- a/src/net/tmwa/protocol.h +++ b/src/net/tmwa/protocol.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef TA_PROTOCOL_H -#define TA_PROTOCOL_H +#pragma once #include <cstdint> @@ -80,6 +79,20 @@ enum class LOOK : uint8_t MISC2 = 13, }; +enum class DIR : uint8_t +{ + S = 0, + SW = 1, + W = 2, + NW = 3, + N = 4, + NE = 5, + E = 6, + SE = 7, + + COUNT, +}; + enum NpcCommand { NPC_REQUEST_LANG = 0, @@ -283,5 +296,3 @@ enum { }; } - -#endif diff --git a/src/net/tmwa/token.h b/src/net/tmwa/token.h index c0a2b3c8..b563bf65 100644 --- a/src/net/tmwa/token.h +++ b/src/net/tmwa/token.h @@ -21,8 +21,7 @@ #include "being.h" -#ifndef NET_TA_TOKEN_H -#define NET_TA_TOKEN_H +#pragma once struct Token { @@ -36,8 +35,6 @@ struct Token account_ID = 0; session_ID1 = 0; session_ID2 = 0; - sex = Gender::UNSPECIFIED; + sex = Gender::Unspecified; } }; - -#endif // NET_TA_TOKEN_H diff --git a/src/net/tmwa/tradehandler.cpp b/src/net/tmwa/tradehandler.cpp index 60732eef..c129cfd4 100644 --- a/src/net/tmwa/tradehandler.cpp +++ b/src/net/tmwa/tradehandler.cpp @@ -131,7 +131,7 @@ void TradeHandler::handleMessage(MessageIn &msg) "doesn't exist.")); break; case 2: // Invite request check failed... - serverNotice(_("Trade cancelled due to an unknown " + serverNotice(_("Trade canceled due to an unknown " "reason.")); break; case 3: // Trade accepted @@ -140,11 +140,11 @@ void TradeHandler::handleMessage(MessageIn &msg) tradePartnerName.c_str())); tradeWindow->setVisible(true); break; - case 4: // Trade cancelled + case 4: // Trade canceled if (player_relations.hasPermission(tradePartnerName, PlayerPermissions::SPEECH_LOG)) { - serverNotice(strprintf(_("Trade with %s cancelled."), + serverNotice(strprintf(_("Trade with %s canceled."), tradePartnerName.c_str())); } // otherwise ignore silently diff --git a/src/net/tmwa/tradehandler.h b/src/net/tmwa/tradehandler.h index fa7ec034..1a2cfcef 100644 --- a/src/net/tmwa/tradehandler.h +++ b/src/net/tmwa/tradehandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NET_TA_TRADEHANDLER_H -#define NET_TA_TRADEHANDLER_H +#pragma once #include "net/net.h" #include "net/tradehandler.h" @@ -57,5 +56,3 @@ class TradeHandler final : public MessageHandler, public Net::TradeHandler }; } // namespace TmwAthena - -#endif // NET_TA_TRADEHANDLER_H diff --git a/src/net/tradehandler.h b/src/net/tradehandler.h index 609f8087..605d122b 100644 --- a/src/net/tradehandler.h +++ b/src/net/tradehandler.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef TRADEHANDLER_H -#define TRADEHANDLER_H +#pragma once #include "being.h" @@ -49,5 +48,3 @@ class TradeHandler virtual void cancel() {} }; } - -#endif // TRADEHANDLER_H diff --git a/src/net/worldinfo.h b/src/net/worldinfo.h index d9596239..acc8a376 100644 --- a/src/net/worldinfo.h +++ b/src/net/worldinfo.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef WORLD_INFO_H -#define WORLD_INFO_H +#pragma once #include <string> #include <vector> @@ -34,5 +33,3 @@ struct WorldInfo { }; using Worlds = std::vector<WorldInfo *>; - -#endif // WORLD_INFO_H diff --git a/src/openglgraphics.cpp b/src/openglgraphics.cpp index cb242ff7..51d8cde3 100644 --- a/src/openglgraphics.cpp +++ b/src/openglgraphics.cpp @@ -34,6 +34,8 @@ #include <SDL.h> +#include <cmath> + #ifndef GL_TEXTURE_RECTANGLE_ARB #define GL_TEXTURE_RECTANGLE_ARB 0x84F5 #define GL_MAX_RECTANGLE_TEXTURE_SIZE_ARB 0x84F8 @@ -624,14 +626,6 @@ bool OpenGLGraphics::pushClipArea(gcn::Rectangle area) glPushMatrix(); glTranslatef(transX, transY, 0); - int x = (int) (mClipStack.top().x * mScaleX); - int y = (int) ((mHeight - mClipStack.top().y - - mClipStack.top().height) * mScaleY); - int width = (int) (mClipStack.top().width * mScaleX); - int height = (int) (mClipStack.top().height * mScaleY); - - glScissor(x, y, width, height); - return result; } @@ -640,17 +634,6 @@ void OpenGLGraphics::popClipArea() Graphics::popClipArea(); glPopMatrix(); - - if (mClipStack.empty()) - return; - - int x = (int) (mClipStack.top().x * mScaleX); - int y = (int) ((mHeight - mClipStack.top().y - - mClipStack.top().height) * mScaleY); - int width = (int) (mClipStack.top().width * mScaleX); - int height = (int) (mClipStack.top().height * mScaleY); - - glScissor(x, y, width, height); } void OpenGLGraphics::setColor(const gcn::Color &color) @@ -661,6 +644,25 @@ void OpenGLGraphics::setColor(const gcn::Color &color) mColorAlpha = (color.a != 255); } +void OpenGLGraphics::updateClipRect() +{ + if (mClipRects.empty()) + { + glDisable(GL_SCISSOR_TEST); + return; + } + + const gcn::Rectangle &clipRect = mClipRects.top(); + + const int x = (int) (clipRect.x * mScaleX); + const int y = (int) ((mHeight - clipRect.y - clipRect.height) * mScaleY); + const int width = (int) (clipRect.width * mScaleX); + const int height = (int) (clipRect.height * mScaleY); + + glEnable(GL_SCISSOR_TEST); + glScissor(x, y, width, height); +} + void OpenGLGraphics::drawPoint(int x, int y) { setTexturingAndBlending(false); diff --git a/src/openglgraphics.h b/src/openglgraphics.h index 7178128b..b342d9e5 100644 --- a/src/openglgraphics.h +++ b/src/openglgraphics.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef OPENGLGRAPHICS_H -#define OPENGLGRAPHICS_H +#pragma once #ifdef USE_OPENGL #include "graphics.h" @@ -116,6 +115,8 @@ class OpenGLGraphics final : public Graphics protected: void setTexturingAndBlending(bool enable); + void updateClipRect() override; + private: void drawQuadArrayfi(int size); @@ -135,5 +136,3 @@ class OpenGLGraphics final : public Graphics bool mReduceInputLag = true; }; #endif //USE_OPENGL - -#endif diff --git a/src/particle.cpp b/src/particle.cpp index 754cc43b..cb79c86f 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -74,7 +74,6 @@ void Particle::setupEngine() Particle::fastPhysics = config.particleFastPhysics; Particle::emitterSkip = config.particleEmitterSkip + 1; Particle::enabled = config.particleEffects; - disableAutoDelete(); logger->log("Particle engine set up"); } @@ -204,39 +203,35 @@ bool Particle::update() // Update child particles - for (auto p = mChildParticles.begin(); - p != mChildParticles.end();) + for (auto p = mChildParticles.begin(); p != mChildParticles.end(); ) { + auto particle = *p; //move particle with its parent if desired - if ((*p)->doesFollow()) + if (particle->doesFollow()) { - (*p)->moveBy(change); + particle->moveBy(change); } - //update particle - if ((*p)->update()) + if (particle->update()) { p++; } else { - delete (*p); + delete particle; p = mChildParticles.erase(p); } } - return mAlive == ALIVE || !mChildParticles.empty() || !mAutoDelete; + return isAlive() || !mChildParticles.empty() || !mAutoDelete; } void Particle::moveBy(const Vector &change) { mPos += change; + for (auto &childParticle : mChildParticles) - { if (childParticle->doesFollow()) - { childParticle->moveBy(change); - } - } } void Particle::moveTo(float x, float y) @@ -299,7 +294,7 @@ Particle *Particle::addEffect(const std::string &particleEffectFile, if (!imageSrc.empty() && !dyePalettes.empty()) Dye::instantiate(imageSrc, dyePalettes); - auto img = resman->getImageRef(imageSrc); + auto img = resman->getImage(imageSrc); newParticle = new ImageParticle(mMap, img); } // Other @@ -405,13 +400,11 @@ Particle *Particle::addTextRiseFadeOutEffect(const std::string &text, void Particle::adjustEmitterSize(int w, int h) { - if (mAllowSizeAdjust) - { - for (auto &childEmitter : mChildEmitters) - { - childEmitter->adjustSize(w, h); - } - } + if (!mAllowSizeAdjust) + return; + + for (auto &childEmitter : mChildEmitters) + childEmitter->adjustSize(w, h); } float Particle::getCurrentAlpha() const diff --git a/src/particle.h b/src/particle.h index 1c991878..a130a36e 100644 --- a/src/particle.h +++ b/src/particle.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef PARTICLE_H -#define PARTICLE_H +#pragma once #include "actor.h" #include "guichanfwd.h" @@ -77,7 +76,7 @@ class Particle : public Actor * Gives a particle the properties of an engine root particle and loads * the particle-related config settings. */ - void setupEngine(); + static void setupEngine(); /** * Updates particle position, returns false when the particle should @@ -292,6 +291,53 @@ class Particle : public Actor float mMomentum = 1.0f; /**< How much speed the particle retains after each game tick*/ }; -extern Particle *particleEngine; +/** + * A handle on a particle. The handle prevents automatic deletion of the + * particle by its parent and kills the particle when the handle is destroyed. + */ +class ParticleHandle +{ + public: + explicit ParticleHandle(Particle *particle = nullptr): + mParticle(particle) + { + if (mParticle) + mParticle->disableAutoDelete(); + } + + ParticleHandle(const ParticleHandle &) = delete; + + ParticleHandle(ParticleHandle &&other): + mParticle(other.mParticle) + { + other.mParticle = nullptr; + } -#endif + ~ParticleHandle() + { + if (mParticle) + mParticle->kill(); + } + + ParticleHandle &operator=(const ParticleHandle &) = delete; + + ParticleHandle &operator=(ParticleHandle &&other) + { + if (this != &other) + { + if (mParticle) + mParticle->kill(); + mParticle = other.mParticle; + other.mParticle = nullptr; + } + return *this; + } + + Particle *operator->() const { return mParticle; } + operator Particle *() const { return mParticle; } + + private: + Particle *mParticle; +}; + +extern Particle *particleEngine; diff --git a/src/particlecontainer.cpp b/src/particlecontainer.cpp deleted file mode 100644 index 60485c44..00000000 --- a/src/particlecontainer.cpp +++ /dev/null @@ -1,172 +0,0 @@ -/* - * The Mana Client - * Copyright (C) 2008-2009 The Mana World Development Team - * Copyright (C) 2009-2012 The Mana Developers - * - * This file is part of The Mana Client. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <cassert> - -#include "particle.h" -#include "particlecontainer.h" - -ParticleContainer::ParticleContainer(ParticleContainer *parent, - bool delParent): - mDelParent(delParent), - mNext(parent) -{} - -ParticleContainer::~ParticleContainer() -{ - clearLocally(); - if (mDelParent) - delete mNext; -} - -void ParticleContainer::clear() -{ - clearLocally(); - if (mNext) - mNext->clear(); -} - -void ParticleContainer::moveTo(float x, float y) -{ - if (mNext) - mNext->moveTo(x, y); -} - -// -- particle list ---------------------------------------- - -ParticleList::ParticleList(ParticleContainer *parent, bool delParent): - ParticleContainer(parent, delParent) -{} - -ParticleList::~ParticleList() = default; - -void ParticleList::addLocally(Particle *particle) -{ - if (particle) - { - // The effect may not die without the beings permission or we segfault - particle->disableAutoDelete(); - mElements.push_back(particle); - } -} - -void ParticleList::removeLocally(Particle *particle) -{ - for (auto it = mElements.begin(), it_end = mElements.end(); it != it_end;) - { - if (*it == particle) - { - (*it)->kill(); - it = mElements.erase(it); - } - else - it++; - } -} - -void ParticleList::clearLocally() -{ - for (auto &element : mElements) - element->kill(); - - mElements.clear(); -} - -void ParticleList::moveTo(float x, float y) -{ - ParticleContainer::moveTo(x, y); - - for (auto it = mElements.begin(); - it != mElements.end();) - { - (*it)->moveTo(x, y); - if ((*it)->isExtinct()) - { - (*it)->kill(); - it = mElements.erase(it); - } - else - it++; - } -} - -// -- particle vector ---------------------------------------- - -ParticleVector::ParticleVector(ParticleContainer *parent, bool delParent): - ParticleContainer(parent, delParent) -{} - -ParticleVector::~ParticleVector() = default; - -void ParticleVector::setLocally(int index, Particle *particle) -{ - assert(index >= 0); - - delLocally(index); - - if (mIndexedElements.size() <= (unsigned) index) - mIndexedElements.resize(index + 1); - - if (particle) - particle->disableAutoDelete(); - mIndexedElements[index] = particle; -} - -void ParticleVector::delLocally(int index) -{ - assert(index >= 0); - - if (mIndexedElements.size() <= (unsigned) index) - return; - - Particle *p = mIndexedElements[index]; - if (p) - { - mIndexedElements[index] = nullptr; - p->kill(); - } -} - -void ParticleVector::clearLocally() -{ - for (unsigned int i = 0; i < mIndexedElements.size(); i++) - delLocally(i); -} - -void ParticleVector::moveTo(float x, float y) -{ - ParticleContainer::moveTo(x, y); - - for (auto &indexedElement : mIndexedElements) - { - if (indexedElement) - { - indexedElement->moveTo(x, y); - - if (indexedElement->isExtinct()) - { - indexedElement->kill(); - indexedElement = nullptr; - } - } - } -} - diff --git a/src/particlecontainer.h b/src/particlecontainer.h deleted file mode 100644 index 3905ee49..00000000 --- a/src/particlecontainer.h +++ /dev/null @@ -1,121 +0,0 @@ -/* - * The Mana Client - * Copyright (C) 2008-2009 The Mana World Development Team - * Copyright (C) 2009-2012 The Mana Developers - * - * This file is part of The Mana Client. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef PARTICLE_CONTAINER_H -#define PARTICLE_CONTAINER_H - -#include <list> -#include <vector> - -class Particle; - -/** - * Set of particle effects. May be stacked with other ParticleContainers. All - * operations herein affect such stacked containers, unless the operations end - * in `Locally'. - */ -class ParticleContainer -{ -public: - /** - * Constructs a new particle container and assumes responsibility for - * its parent (for all operations defined herein, except when ending in `Locally') - * - * delParent means that the destructor should also free the parent. - */ - ParticleContainer(ParticleContainer *parent = nullptr, bool delParent = true); - virtual ~ParticleContainer(); - - /** - * Kills and removes all particle effects - */ - void clear(); - - /** - * Kills and removes all particle effects (only in this container) - */ - virtual void clearLocally() {} - - /** - * Sets the positions of all elements - */ - virtual void moveTo(float x, float y); - -protected: - bool mDelParent; /**< Delete mNext in destructor */ - ParticleContainer *mNext; /**< Contained container, if any */ -}; - -/** - * Linked list of particle effects. - */ -class ParticleList final : public ParticleContainer -{ -public: - ParticleList(ParticleContainer *parent = nullptr, bool delParent = true); - ~ParticleList() override; - - /** - * Takes control of and adds a particle - */ - void addLocally(Particle *); - - /** - * `kills' and removes a particle - */ - void removeLocally(Particle *); - - void clearLocally() override; - - void moveTo(float x, float y) override; - -protected: - std::list<Particle *> mElements; /**< Contained particle effects */ -}; - -/** - * Particle container with indexing facilities - */ -class ParticleVector final : public ParticleContainer -{ -public: - ParticleVector(ParticleContainer *parent = nullptr, bool delParent = true); - ~ParticleVector() override; - - /** - * Sets a particle at a specified index. Kills the previous particle - * there, if needed. - */ - void setLocally(int index, Particle *particle); - - /** - * Removes a particle at a specified index - */ - void delLocally(int index); - - void clearLocally() override; - void moveTo(float x, float y) override; - -protected: - std::vector<Particle *> mIndexedElements; -}; - -#endif diff --git a/src/particleemitter.cpp b/src/particleemitter.cpp index f45d39bd..4954d317 100644 --- a/src/particleemitter.cpp +++ b/src/particleemitter.cpp @@ -29,11 +29,8 @@ #include "resources/dye.h" #include "resources/image.h" -#include "resources/imageset.h" #include "resources/resourcemanager.h" -#include "utils/stringutils.h" - #include <cmath> #define SIN45 0.707106781f @@ -102,7 +99,7 @@ ParticleEmitter::ParticleEmitter(XML::Node emitterNode, Particle *target, Dye::instantiate(image, dyePalettes); ResourceManager *resman = ResourceManager::getInstance(); - mParticleImage = resman->getImageRef(image); + mParticleImage = resman->getImage(image); } } else if (name == "horizontal-angle") @@ -178,7 +175,7 @@ ParticleEmitter::ParticleEmitter(XML::Node emitterNode, Particle *target, } else if (name == "follow-parent") { - mParticleFollow = true; + mParticleFollow = propertyNode.getBoolProperty("value", true); } else { @@ -195,150 +192,13 @@ ParticleEmitter::ParticleEmitter(XML::Node emitterNode, Particle *target, } else if (propertyNode.name() == "rotation") { - ImageSet *imageset = ResourceManager::getInstance()->getImageSet( - propertyNode.getProperty("imageset", ""), - propertyNode.getProperty("width", 0), - propertyNode.getProperty("height", 0) - ); - - // Get animation frames - for (auto frameNode : propertyNode.children()) - { - int delay = frameNode.getProperty("delay", 0); - int offsetX = frameNode.getProperty("offsetX", 0); - int offsetY = frameNode.getProperty("offsetY", 0); - if (mMap) - { - offsetX -= imageset->getWidth() / 2 - - mMap->getTileWidth() / 2; - offsetY -= imageset->getHeight() - mMap->getTileHeight(); - } - - if (frameNode.name() == "frame") - { - int index = frameNode.getProperty("index", -1); - - if (index < 0) - { - logger->log("No valid value for 'index'"); - continue; - } - - Image *img = imageset->get(index); - - if (!img) - { - logger->log("No image at index %d", index); - continue; - } - - mParticleRotation.addFrame(img, delay, offsetX, offsetY); - } - else if (frameNode.name() == "sequence") - { - int start = frameNode.getProperty("start", -1); - int end = frameNode.getProperty("end", -1); - - if (start < 0 || end < 0) - { - logger->log("No valid value for 'start' or 'end'"); - continue; - } - - while (end >= start) - { - Image *img = imageset->get(start); - - if (!img) - { - logger->log("No image at index %d", start); - continue; - } - - mParticleRotation.addFrame(img, delay, offsetX, offsetY); - start++; - } - } - else if (frameNode.name() == "end") - { - mParticleRotation.addTerminator(); - } - } // for frameNode + mParticleRotation = Animation::fromXML(propertyNode); } else if (propertyNode.name() == "animation") { - std::string imagesetPath = - propertyNode.getProperty("imageset", ""); - ImageSet *imageset = ResourceManager::getInstance()->getImageSet( - imagesetPath, - propertyNode.getProperty("width", 0), - propertyNode.getProperty("height", 0) - ); - - if (!imageset) - logger->error(strprintf("Failed to load \"%s\"", - imagesetPath.c_str())); - - // Get animation frames - for (auto frameNode : propertyNode.children()) - { - int delay = frameNode.getProperty("delay", 0); - int offsetX = frameNode.getProperty("offsetX", 0); - int offsetY = frameNode.getProperty("offsetY", 0); - offsetY -= imageset->getHeight() - 32; - offsetX -= imageset->getWidth() / 2 - 16; - - if (frameNode.name() == "frame") - { - int index = frameNode.getProperty("index", -1); - - if (index < 0) - { - logger->log("No valid value for 'index'"); - continue; - } - - Image *img = imageset->get(index); - - if (!img) - { - logger->log("No image at index %d", index); - continue; - } - - mParticleAnimation.addFrame(img, delay, offsetX, offsetY); - } - else if (frameNode.name() == "sequence") - { - int start = frameNode.getProperty("start", -1); - int end = frameNode.getProperty("end", -1); - - if (start < 0 || end < 0) - { - logger->log("No valid value for 'start' or 'end'"); - continue; - } - - while (end >= start) - { - Image *img = imageset->get(start); - - if (!img) - { - logger->log("No image at index %d", start); - continue; - } - - mParticleAnimation.addFrame(img, delay, offsetX, offsetY); - start++; - } - } - else if (frameNode.name() == "end") - { - mParticleAnimation.addTerminator(); - } - } // for frameNode - } else if (propertyNode.name() == "deatheffect") + mParticleAnimation = Animation::fromXML(propertyNode); + } + else if (propertyNode.name() == "deatheffect") { mDeathEffect = propertyNode.textContent(); mDeathEffectConditions = 0x00; diff --git a/src/particleemitter.h b/src/particleemitter.h index 9d226209..ede5a71d 100644 --- a/src/particleemitter.h +++ b/src/particleemitter.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef PARTICLEEMITTER_H -#define PARTICLEEMITTER_H +#pragma once #include "particleemitterprop.h" @@ -140,4 +139,3 @@ class ParticleEmitter /** List of emitters the spawned particles are equipped with */ std::list<ParticleEmitter> mParticleChildEmitters; }; -#endif diff --git a/src/particleemitterprop.h b/src/particleemitterprop.h index eb719e57..78f349b8 100644 --- a/src/particleemitterprop.h +++ b/src/particleemitterprop.h @@ -19,6 +19,8 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#pragma once + #include <cmath> static const double PI = 3.14159265; diff --git a/src/party.cpp b/src/party.cpp index 92b6c22f..8db773fd 100644 --- a/src/party.cpp +++ b/src/party.cpp @@ -45,7 +45,7 @@ Party::~Party() PartyMember *Party::addMember(int id, const std::string &name) { PartyMember *m; - if (Net::getNetworkType() == ServerType::TMWATHENA && (m = getMember(id))) + if (Net::getNetworkType() == ServerType::TmwAthena && (m = getMember(id))) { return m; } diff --git a/src/party.h b/src/party.h index d96500ca..14d82352 100644 --- a/src/party.h +++ b/src/party.h @@ -18,8 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef PARTY_H -#define PARTY_H +#pragma once #include "avatar.h" @@ -171,5 +170,3 @@ private: short mId; bool mCanInviteUsers = false; }; - -#endif // PARTY_H diff --git a/src/playerinfo.cpp b/src/playerinfo.cpp index aacff3a1..a05ffaaa 100644 --- a/src/playerinfo.cpp +++ b/src/playerinfo.cpp @@ -50,8 +50,8 @@ static bool mNPCPostCount = false; static BuySellState mBuySellState = BUYSELL_NONE; -static std::map<int, Special> mSpecials; -static Timer mSpecialRechargeUpdateTimer; +static std::map<int, Ability> mAbilities; +static Timer mAbilityRechargeUpdateTimer; // --- Triggers --------------------------------------------------------------- @@ -278,25 +278,25 @@ void setBuySellState(BuySellState buySellState) } } -// --- Specials --------------------------------------------------------------- +// --- Abilities -------------------------------------------------------------- -void clearSpecialStatus() +void clearAbilityStatus(int id) { - mSpecials.clear(); + mAbilities.erase(id); } -void setSpecialStatus(int id, int current, int max, int recharge) +void setAbilityStatus(int id, int current, int max, int recharge) { - logger->log("SpecialUpdate Skill #%d -- (%d/%d) -> %d", id, current, max, + logger->log("AbilityUpdate Skill #%d -- (%d/%d) -> %d", id, current, max, recharge); - mSpecials[id].currentMana = current; - mSpecials[id].neededMana = max; - mSpecials[id].recharge = recharge; + mAbilities[id].currentMana = current; + mAbilities[id].neededMana = max; + mAbilities[id].recharge = recharge; } -const SpecialsMap &getSpecialStatus() +const std::map<int, Ability> &getAbilityStatus() { - return mSpecials; + return mAbilities; } // --- Misc ------------------------------------------------------------------- @@ -314,16 +314,16 @@ bool isTalking() void logic() { - if (mSpecialRechargeUpdateTimer.passed()) + if (mAbilityRechargeUpdateTimer.passed()) { - mSpecialRechargeUpdateTimer.set(100); + mAbilityRechargeUpdateTimer.set(100); - for (auto &special : mSpecials) + for (auto &[id, ability] : mAbilities) { - special.second.currentMana += special.second.recharge; - if (special.second.currentMana > special.second.neededMana) + ability.currentMana += ability.recharge; + if (ability.currentMana > ability.neededMana) { - special.second.currentMana = special.second.neededMana; + ability.currentMana = ability.neededMana; } } } diff --git a/src/playerinfo.h b/src/playerinfo.h index cbb6c748..492ccf42 100644 --- a/src/playerinfo.h +++ b/src/playerinfo.h @@ -18,8 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef PLAYERINFO_H -#define PLAYERINFO_H +#pragma once #include <map> @@ -71,17 +70,15 @@ enum BuySellState }; /** - * Special information storage structure. + * Ability information storage structure. */ -struct Special +struct Ability { int currentMana; int neededMana; int recharge; }; -using SpecialsMap = std::map<int, Special>; - /** * A database like namespace which holds global info about the localplayer * @@ -218,22 +215,22 @@ namespace PlayerInfo */ void setBuySellState(BuySellState buySellState); -// --- Specials --------------------------------------------------------------- +// --- Abilities -------------------------------------------------------------- /** - * Removes all specials. + * Removes the status for the given ability. */ - void clearSpecialStatus(); + void clearAbilityStatus(int id); /** - * Changes the status of the given special. + * Changes the status of the given ability. */ - void setSpecialStatus(int id, int current, int max, int recharge); + void setAbilityStatus(int id, int current, int max, int recharge); /** - * Returns the status of the given special. + * Returns the status all abilities. */ - const SpecialsMap &getSpecialStatus(); + const std::map<int, Ability> &getAbilityStatus(); // --- Misc ------------------------------------------------------------------- @@ -259,5 +256,3 @@ namespace PlayerInfo void init(); } // namespace PlayerInfo - -#endif diff --git a/src/playerrelations.cpp b/src/playerrelations.cpp index bec1d081..8da6c06b 100644 --- a/src/playerrelations.cpp +++ b/src/playerrelations.cpp @@ -80,11 +80,11 @@ unsigned int PlayerRelationsManager::checkPermissionSilently( switch (getRelation(playerName)) { - case PlayerRelation::NEUTRAL: + case PlayerRelation::Neutral: break; // widen permissions for friends - case PlayerRelation::FRIEND: + case PlayerRelation::Friend: permissions |= PlayerPermissions::EMOTE | PlayerPermissions::SPEECH_FLOAT | @@ -94,12 +94,12 @@ unsigned int PlayerRelationsManager::checkPermissionSilently( break; // narrow permissions for disregarded and ignored players - case PlayerRelation::DISREGARDED: + case PlayerRelation::Disregarded: permissions &= PlayerPermissions::EMOTE | PlayerPermissions::SPEECH_FLOAT; break; - case PlayerRelation::IGNORED: + case PlayerRelation::Ignored: permissions &= 0; break; } @@ -163,7 +163,7 @@ void PlayerRelationsManager::removePlayer(const std::string &name) PlayerRelation PlayerRelationsManager::getRelation(const std::string &name) const { auto it = mRelations.find(name); - return it != mRelations.end() ? it->second : PlayerRelation::NEUTRAL; + return it != mRelations.end() ? it->second : PlayerRelation::Neutral; } //////////////////////////////////////// diff --git a/src/playerrelations.h b/src/playerrelations.h index 38452c2f..0d2ee8d8 100644 --- a/src/playerrelations.h +++ b/src/playerrelations.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef PLAYER_RELATIONS_H -#define PLAYER_RELATIONS_H +#pragma once #include <list> #include <map> @@ -31,10 +30,10 @@ class Being; enum class PlayerRelation { - NEUTRAL = 0, - FRIEND = 1, - DISREGARDED = 2, - IGNORED = 3 + Neutral = 0, + Friend = 1, + Disregarded = 2, + Ignored = 3 }; constexpr unsigned int RELATIONS_NR = 4; @@ -201,6 +200,3 @@ private: extern PlayerRelationsManager player_relations; // singleton representation of player relations - - -#endif // PLAYER_RELATIONS_H diff --git a/src/position.h b/src/position.h index c9c8e6f2..7093351d 100644 --- a/src/position.h +++ b/src/position.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef POSITION_H -#define POSITION_H +#pragma once #include <iostream> #include <list> @@ -50,5 +49,3 @@ std::ostream& operator <<(std::ostream &os, const Position &p); * output stream. */ std::ostream& operator <<(std::ostream &os, const Path &path); - -#endif // POSITION_H diff --git a/src/properties.h b/src/properties.h index 7ccf86dc..5a7a19b3 100644 --- a/src/properties.h +++ b/src/properties.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef PROPERTIES_H -#define PROPERTIES_H +#pragma once #include <map> #include <sstream> @@ -140,5 +139,3 @@ class Properties using PropertyMap = std::map<std::string, std::string>; PropertyMap mProperties; }; - -#endif diff --git a/src/resources/abilitydb.cpp b/src/resources/abilitydb.cpp new file mode 100644 index 00000000..311ee9eb --- /dev/null +++ b/src/resources/abilitydb.cpp @@ -0,0 +1,106 @@ +/* + * The Mana Client + * Copyright (C) 2010-2013 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "resources/abilitydb.h" + +#include "log.h" + +#include "utils/dtor.h" + +#include <map> + +namespace +{ + std::map<int, AbilityInfo *> mAbilityInfos; + bool mLoaded = false; +} + +static AbilityInfo::TargetMode targetModeFromString(const std::string& str) +{ + if (str == "being") + return AbilityInfo::TARGET_BEING; + if (str == "point") + return AbilityInfo::TARGET_POINT; + if (str == "direction") + return AbilityInfo::TARGET_DIRECTION; + + logger->log("AbilityDB: Warning, unknown target mode \"%s\"", str.c_str() ); + return AbilityInfo::TARGET_BEING; +} + + +void AbilityDB::init() +{ + if (mLoaded) + unload(); +} + +void AbilityDB::readAbilityNode(XML::Node node, const std::string &filename) +{ + auto *info = new AbilityInfo(); + int id = node.getProperty("id", 0); + info->id = id; + info->name = node.getProperty("name", std::string()); + info->icon = node.getProperty("icon", std::string()); + info->useAction = node.getProperty("useaction", std::string()); + + info->targetMode = targetModeFromString(node.getProperty("target", "being")); + + info->rechargeable = node.getBoolProperty("rechargeable", true); + info->rechargeNeeded = 0; + info->rechargeCurrent = 0; + + if (mAbilityInfos.find(id) != mAbilityInfos.end()) + logger->log("AbilityDB: Duplicate ability ID %d in %s, ignoring", id, filename.c_str()); + else + mAbilityInfos[id] = info; +} + +void AbilityDB::checkStatus() +{ + mLoaded = true; +} + +void AbilityDB::unload() +{ + delete_all(mAbilityInfos); + mAbilityInfos.clear(); + + mLoaded = false; +} + +AbilityInfo *AbilityDB::get(int id) +{ + auto i = mAbilityInfos.find(id); + if (i != mAbilityInfos.end()) + return i->second; + + return nullptr; +} + +AbilityInfo *AbilityDB::find(std::string_view name) +{ + for (auto &[_, abilityInfo] : mAbilityInfos) + { + if (abilityInfo->name == name) + return abilityInfo; + } + return nullptr; +} diff --git a/src/resources/specialdb.h b/src/resources/abilitydb.h index 20ba0075..6b3de5a6 100644 --- a/src/resources/specialdb.h +++ b/src/resources/abilitydb.h @@ -18,50 +18,52 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SPECIAL_DB_H -#define SPECIAL_DB_H +#pragma once #include <string> #include "utils/xml.h" -struct SpecialInfo +struct AbilityInfo { enum TargetMode { - TARGET_BEING, // target any being - TARGET_POINT // target map location + TARGET_BEING, // target any being + TARGET_POINT, // target map location + TARGET_DIRECTION // target a direction }; int id; - std::string set; // tab on which the special is shown - std::string name; // displayed name of special + std::string name; // displayed name of ability std::string icon; // filename of graphical icon + std::string useAction; // action when using the ability - TargetMode targetMode; // target mode + TargetMode targetMode; - bool rechargeable; // true when the special has a recharge bar + bool rechargeable; // true when the ability has a recharge bar int rechargeNeeded; // maximum recharge when applicable int rechargeCurrent; // current recharge when applicable }; /** - * Special information database. + * Ability information database. */ -namespace SpecialDB +namespace AbilityDB { void init(); - void readSpecialSetNode(XML::Node node, const std::string &filename); + void readAbilityNode(XML::Node node, const std::string &filename); void checkStatus(); void unload(); - /** gets the special info for ID. Will return 0 when it is - * a server-specific special. + /** Gets the ability info for ID. Will return nullptr when it is + * a server-specific ability. */ - SpecialInfo *get(int id); + AbilityInfo *get(int id); - SpecialInfo::TargetMode targetModeFromString(const std::string& str); + /** + * Finds an ability by name. Returns nullptr when the ability could not be + * found. + */ + AbilityInfo *find(std::string_view name); } - -#endif diff --git a/src/resources/action.h b/src/resources/action.h index 37f29810..c3e47dbb 100644 --- a/src/resources/action.h +++ b/src/resources/action.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef ACTION_H -#define ACTION_H +#pragma once #include <map> @@ -43,5 +42,3 @@ class Action protected: std::map<int, Animation *> mAnimations; }; - -#endif diff --git a/src/resources/ambientlayer.h b/src/resources/ambientlayer.h index e62af33f..8e0137b1 100644 --- a/src/resources/ambientlayer.h +++ b/src/resources/ambientlayer.h @@ -18,8 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef RESOURCES_AMBIENTOVERLAY_H -#define RESOURCES_AMBIENTOVERLAY_H +#pragma once #include "resource.h" @@ -52,5 +51,3 @@ class AmbientLayer float mPosX = 0; /**< Current layer X position. */ float mPosY = 0; /**< Current layer Y position. */ }; - -#endif diff --git a/src/resources/animation.cpp b/src/resources/animation.cpp index b48e8cff..91c22236 100644 --- a/src/resources/animation.cpp +++ b/src/resources/animation.cpp @@ -21,6 +21,11 @@ #include "resources/animation.h" +#include "dye.h" +#include "game.h" +#include "log.h" +#include "resourcemanager.h" + void Animation::addFrame(Image *image, int delay, int offsetX, int offsetY) { auto &frame = mFrames.emplace_back(); @@ -41,3 +46,90 @@ bool Animation::isTerminator(const Frame &candidate) { return candidate.image == nullptr; } + +Animation Animation::fromXML(XML::Node node, const std::string &dyePalettes) +{ + Animation animation; + + std::string imagePath = node.getProperty("imageset", std::string()); + + // Instanciate the dye coloration. + Dye::instantiate(imagePath, dyePalettes); + + auto imageSet = ResourceManager::getInstance()->getImageSet( + imagePath, + node.getProperty("width", 0), + node.getProperty("height", 0) + ); + + if (!imageSet) + return animation; + + // Get animation frames + for (auto frameNode : node.children()) + { + int delay = frameNode.getProperty("delay", 0); + int offsetX = frameNode.getProperty("offsetX", 0); + int offsetY = frameNode.getProperty("offsetY", 0); + Game *game = Game::instance(); + if (game) + { + offsetX -= imageSet->getWidth() / 2 - game->getCurrentTileWidth() / 2; + offsetY -= imageSet->getHeight() - game->getCurrentTileHeight(); + } + + if (frameNode.name() == "frame") + { + int index = frameNode.getProperty("index", -1); + + if (index < 0) + { + logger->log("No valid value for 'index'"); + continue; + } + + Image *img = imageSet->get(index); + + if (!img) + { + logger->log("No image at index %d", index); + continue; + } + + animation.addFrame(img, delay, offsetX, offsetY); + } + else if (frameNode.name() == "sequence") + { + int start = frameNode.getProperty("start", -1); + int end = frameNode.getProperty("end", -1); + + if (start < 0 || end < 0) + { + logger->log("No valid value for 'start' or 'end'"); + continue; + } + + while (end >= start) + { + Image *img = imageSet->get(start); + + if (!img) + { + logger->log("No image at index %d", start); + continue; + } + + animation.addFrame(img, delay, offsetX, offsetY); + start++; + } + } + else if (frameNode.name() == "end") + { + animation.addTerminator(); + } + } + + animation.mImageSet = imageSet; + + return animation; +} diff --git a/src/resources/animation.h b/src/resources/animation.h index 812e0547..cc3abd58 100644 --- a/src/resources/animation.h +++ b/src/resources/animation.h @@ -19,8 +19,10 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef ANIMATION_H -#define ANIMATION_H +#pragma once + +#include "resources/imageset.h" +#include "utils/xml.h" #include <vector> @@ -77,9 +79,14 @@ class Animation final */ static bool isTerminator(const Frame &phase); + /** + * Loads an animation from XML. + */ + static Animation fromXML(XML::Node node, + const std::string &dyePalettes = {}); + protected: + ResourceRef<ImageSet> mImageSet; std::vector<Frame> mFrames; int mDuration = 0; }; - -#endif diff --git a/src/resources/attributes.cpp b/src/resources/attributes.cpp index ab270b65..7ec6b516 100644 --- a/src/resources/attributes.cpp +++ b/src/resources/attributes.cpp @@ -40,7 +40,7 @@ namespace Attributes { - using Attribute = struct + struct Attribute { unsigned int id; std::string name; @@ -94,11 +94,11 @@ namespace Attributes { { // Fill up the modifiable attribute label list. attributeLabels.clear(); - for (auto it = attributes.cbegin(), it_end = attributes.cend(); it != it_end; it++) + for (const auto &[_, attribute] : attributes) { - if (it->second.modifiable && - (it->second.scope == "character" || it->second.scope == "being")) - attributeLabels.push_back(it->second.name + ":"); + if (attribute.modifiable && + (attribute.scope == "character" || attribute.scope == "being")) + attributeLabels.push_back(attribute.name + ":"); } } @@ -228,7 +228,7 @@ namespace Attributes { void init() { - if (attributes.size()) + if (!attributes.empty()) unload(); } @@ -238,14 +238,14 @@ namespace Attributes { void readAttributeNode(XML::Node node, const std::string &filename) { int id = node.getProperty("id", 0); - if (!id) { logger->log("Attributes: Invalid or missing stat ID in " DEFAULT_ATTRIBUTESDB_FILE "!"); return; } - else if (attributes.find(id) != attributes.end()) + + if (attributes.find(id) != attributes.end()) { logger->log("Attributes: Redefinition of stat ID %d", id); } @@ -364,23 +364,23 @@ namespace Attributes { { std::list<ItemStat> dbStats; - for (auto it = tags.cbegin(), it_end = tags.cend(); it != it_end; ++it) - dbStats.emplace_back(it->first, it->second); + for (const auto &[tag, format] : tags) + dbStats.emplace_back(tag, format); setStatsList(std::move(dbStats)); } void informStatusWindow() { - for (auto it = attributes.cbegin(), it_end = attributes.cend(); it != it_end; it++) + for (const auto &[_, attribute] : attributes) { - if (it->second.playerInfoId == -1 && - (it->second.scope == "character" || it->second.scope == "being")) + if (attribute.playerInfoId == -1 && + (attribute.scope == "character" || attribute.scope == "being")) { - statusWindow->addAttribute(it->second.id, - it->second.name, - it->second.modifiable, - it->second.description); + statusWindow->addAttribute(attribute.id, + attribute.name, + attribute.modifiable, + attribute.description); } } } diff --git a/src/resources/attributes.h b/src/resources/attributes.h index 9071d6b8..e70a5435 100644 --- a/src/resources/attributes.h +++ b/src/resources/attributes.h @@ -18,8 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef RESOURCES_ATTRIBUTES_H -#define RESOURCES_ATTRIBUTES_H +#pragma once #include <string> #include <vector> @@ -73,5 +72,3 @@ namespace Attributes unsigned int getAttributeMaximum(); } // namespace Attributes - -#endif // RESOURCES_ATTRIBUTES_H diff --git a/src/resources/beinginfo.cpp b/src/resources/beinginfo.cpp index 17e270dc..20c24d4f 100644 --- a/src/resources/beinginfo.cpp +++ b/src/resources/beinginfo.cpp @@ -52,15 +52,15 @@ static std::optional<ActorSprite::TargetCursorSize> targetCursorSizeFromString(c static std::optional<Cursor> cursorFromString(const std::string &cursor) { - if (cursor == "pointer") return Cursor::POINTER; - if (cursor == "attack") return Cursor::FIGHT; - if (cursor == "pickup") return Cursor::PICKUP; - if (cursor == "talk") return Cursor::TALK; - if (cursor == "action") return Cursor::ACTION; - if (cursor == "left") return Cursor::LEFT; - if (cursor == "up") return Cursor::UP; - if (cursor == "right") return Cursor::RIGHT; - if (cursor == "down") return Cursor::DOWN; + if (cursor == "pointer") return Cursor::Pointer; + if (cursor == "attack") return Cursor::Fight; + if (cursor == "pickup") return Cursor::PickUp; + if (cursor == "talk") return Cursor::Talk; + if (cursor == "action") return Cursor::Action; + if (cursor == "left") return Cursor::Left; + if (cursor == "up") return Cursor::Up; + if (cursor == "right") return Cursor::Right; + if (cursor == "down") return Cursor::Down; return {}; } @@ -87,7 +87,7 @@ void BeingInfo::setHoverCursor(const std::string &cursorName) logger->log("Unknown hoverCursor value \"%s\" for %s", cursorName.c_str(), name.c_str()); } - hoverCursor = cursor.value_or(Cursor::POINTER); + hoverCursor = cursor.value_or(Cursor::Pointer); } void BeingInfo::addSound(SoundEvent event, const std::string &filename) diff --git a/src/resources/beinginfo.h b/src/resources/beinginfo.h index 2eac4237..91343d4f 100644 --- a/src/resources/beinginfo.h +++ b/src/resources/beinginfo.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef BEINGINFO_H -#define BEINGINFO_H +#pragma once #include "actorsprite.h" #include "map.h" @@ -43,10 +42,10 @@ struct Attack enum class SoundEvent { - HIT, - MISS, - HURT, - DIE + Hit, + Miss, + Hurt, + Die }; /** @@ -67,7 +66,7 @@ public: std::string name; SpriteDisplay display; ActorSprite::TargetCursorSize targetCursorSize = ActorSprite::TC_MEDIUM; - Cursor hoverCursor = Cursor::POINTER; + Cursor hoverCursor = Cursor::Pointer; unsigned char walkMask = Map::BLOCKMASK_ALL; Map::BlockType blockType = Map::BLOCKTYPE_CHARACTER; bool targetSelection = true; @@ -85,5 +84,3 @@ private: std::map<SoundEvent, std::vector<std::string>> mSounds; std::map<int, Attack> mAttacks; }; - -#endif // BEINGINFO_H diff --git a/src/resources/chardb.h b/src/resources/chardb.h index 10530b26..de49dad6 100644 --- a/src/resources/chardb.h +++ b/src/resources/chardb.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef RESOURCES_CHARDB_H -#define RESOURCES_CHARDB_H +#pragma once #include <vector> @@ -44,5 +43,3 @@ namespace CharDB const std::vector<int> &getDefaultItems(); } - -#endif // RESOURCES_CHARDB_H diff --git a/src/resources/dye.h b/src/resources/dye.h index ce5565f8..61ad4df2 100644 --- a/src/resources/dye.h +++ b/src/resources/dye.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef DYE_H -#define DYE_H +#pragma once #include <string> #include <vector> @@ -97,5 +96,3 @@ class Dye */ DyePalette *mDyePalettes[7]; }; - -#endif diff --git a/src/resources/emotedb.cpp b/src/resources/emotedb.cpp index 6f1ec6e4..d29483d1 100644 --- a/src/resources/emotedb.cpp +++ b/src/resources/emotedb.cpp @@ -22,10 +22,9 @@ #include "resources/emotedb.h" #include "log.h" -#include "imagesprite.h" -#include "resources/resourcemanager.h" #include "resources/imageset.h" +#include "resources/resourcemanager.h" #include <algorithm> #include <vector> @@ -44,8 +43,7 @@ void EmoteDB::init() mUnknown.name = "unknown"; mUnknown.effectId = -1; - mUnknown.sprite = std::make_unique<ImageSprite>( - ResourceManager::getInstance()->getImageRef("graphics/sprites/error.png")); + mUnknown.image = ResourceManager::getInstance()->getImage("graphics/sprites/error.png"); } void EmoteDB::readEmoteNode(XML::Node node, const std::string &filename) @@ -84,7 +82,6 @@ void EmoteDB::readEmoteNode(XML::Node node, const std::string &filename) emote.is = ResourceManager::getInstance()->getImageSet(imageName, width, height); - emote.is->decRef(); // clear automatic reference if (!emote.is || emote.is->size() == 0) { @@ -94,7 +91,7 @@ void EmoteDB::readEmoteNode(XML::Node node, const std::string &filename) } // For now we just use the first image in the animation - emote.sprite = std::make_unique<ImageSprite>(emote.is->get(0)); + emote.image = emote.is->get(0); mEmotes.push_back(std::move(emote)); } @@ -106,14 +103,12 @@ void EmoteDB::checkStatus() void EmoteDB::unload() { + // These images are owned by the ImageSet for (auto &emote : mEmotes) - emote.sprite->releaseImageRef(); + emote.image.release(); mEmotes.clear(); - - if (mUnknown.sprite) - mUnknown.sprite->releaseImageRef(); - + mUnknown.image = nullptr; mLoaded = false; } diff --git a/src/resources/emotedb.h b/src/resources/emotedb.h index 7c6f4644..b722c88b 100644 --- a/src/resources/emotedb.h +++ b/src/resources/emotedb.h @@ -19,26 +19,22 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef EMOTE_DB_H -#define EMOTE_DB_H +#pragma once -#include <memory> #include <string> -#include "resources/resource.h" +#include "resources/image.h" +#include "resources/imageset.h" #include "utils/xml.h" -class ImageSet; -class ImageSprite; - struct Emote { int id; int effectId; std::string name; ResourceRef<ImageSet> is; - std::unique_ptr<ImageSprite> sprite; + ResourceRef<Image> image; }; /** @@ -59,5 +55,3 @@ namespace EmoteDB int getEmoteCount(); } - -#endif // EMOTE_DB_H diff --git a/src/resources/hairdb.h b/src/resources/hairdb.h index 374f2e03..a228c72e 100644 --- a/src/resources/hairdb.h +++ b/src/resources/hairdb.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef HAIR_MANAGER_H -#define HAIR_MANAGER_H +#pragma once #include <map> #include <set> @@ -93,5 +92,3 @@ private: }; extern HairDB hairDB; - -#endif diff --git a/src/resources/image.cpp b/src/resources/image.cpp index 328ea9b8..158d5956 100644 --- a/src/resources/image.cpp +++ b/src/resources/image.cpp @@ -113,37 +113,40 @@ Resource *Image::load(SDL_RWops *rw) Resource *Image::load(SDL_RWops *rw, const Dye &dye) { - SDL_Surface *tmpImage = IMG_Load_RW(rw, 1); + SDL_Surface *surf = IMG_Load_RW(rw, 1); - if (!tmpImage) + if (!surf) { logger->log("Error, image load failed: %s", IMG_GetError()); return nullptr; } - SDL_PixelFormat rgba; - rgba.palette = nullptr; - rgba.BitsPerPixel = 32; - rgba.BytesPerPixel = 4; - rgba.Rmask = 0xFF000000; rgba.Rloss = 0; rgba.Rshift = 24; - rgba.Gmask = 0x00FF0000; rgba.Gloss = 0; rgba.Gshift = 16; - rgba.Bmask = 0x0000FF00; rgba.Bloss = 0; rgba.Bshift = 8; - rgba.Amask = 0x000000FF; rgba.Aloss = 0; rgba.Ashift = 0; + if (surf->format->format != SDL_PIXELFORMAT_ABGR8888) + { + logger->log("Warning: image format is %s, not SDL_PIXELFORMAT_ABGR8888. Converting...", + SDL_GetPixelFormatName(surf->format->format)); - SDL_Surface *surf = SDL_ConvertSurface(tmpImage, &rgba, 0); - SDL_FreeSurface(tmpImage); + SDL_Surface *convertedSurf = SDL_ConvertSurfaceFormat(surf, SDL_PIXELFORMAT_ABGR8888, 0); + SDL_FreeSurface(surf); + if (!convertedSurf) + { + logger->log("Error, image convert failed: %s", SDL_GetError()); + return nullptr; + } + surf = convertedSurf; + } - auto *pixels = static_cast< Uint32 * >(surf->pixels); - for (Uint32 *p_end = pixels + surf->w * surf->h; pixels != p_end; ++pixels) + auto *pixels = static_cast< uint32_t * >(surf->pixels); + for (uint32_t *p_end = pixels + surf->w * surf->h; pixels != p_end; ++pixels) { - int alpha = *pixels & 255; + const uint32_t alpha = (*pixels >> 24) & 255; if (!alpha) continue; int v[3]; - v[0] = (*pixels >> 24) & 255; - v[1] = (*pixels >> 16) & 255; - v[2] = (*pixels >> 8 ) & 255; + v[0] = (*pixels) & 255; + v[1] = (*pixels >> 8) & 255; + v[2] = (*pixels >> 16) & 255; dye.update(v); - *pixels = (v[0] << 24) | (v[1] << 16) | (v[2] << 8) | alpha; + *pixels = (alpha << 24) | (v[2] << 16) | (v[1] << 8) | v[0]; } Image *image = load(surf); diff --git a/src/resources/image.h b/src/resources/image.h index e2e240c3..37dd5e1d 100644 --- a/src/resources/image.h +++ b/src/resources/image.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef IMAGE_H -#define IMAGE_H +#pragma once #include "resources/resource.h" @@ -216,5 +215,3 @@ class SubImage : public Image private: ResourceRef<Image> mParent; }; - -#endif diff --git a/src/resources/imageset.h b/src/resources/imageset.h index a6501cc9..97dbec90 100644 --- a/src/resources/imageset.h +++ b/src/resources/imageset.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef IMAGESET_H -#define IMAGESET_H +#pragma once #include "resources/resource.h" @@ -78,5 +77,3 @@ class ImageSet : public Resource int mOffsetX = 0; int mOffsetY = 0; }; - -#endif diff --git a/src/resources/imagewriter.h b/src/resources/imagewriter.h index 23e85bd8..41ca267a 100644 --- a/src/resources/imagewriter.h +++ b/src/resources/imagewriter.h @@ -19,6 +19,8 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#pragma once + #include <iosfwd> struct SDL_Surface; diff --git a/src/resources/itemdb.cpp b/src/resources/itemdb.cpp index 68ddcd75..05f6ad0b 100644 --- a/src/resources/itemdb.cpp +++ b/src/resources/itemdb.cpp @@ -21,6 +21,7 @@ #include "resources/itemdb.h" +#include "configuration.h" #include "log.h" #include "resources/hairdb.h" @@ -29,10 +30,11 @@ #include "utils/dtor.h" #include "utils/gettext.h" #include "utils/stringutils.h" -#include "configuration.h" +#include "net/tmwa/protocol.h" #include <cassert> +#include <string_view> void setStatsList(std::list<ItemStat> stats) { @@ -60,14 +62,70 @@ static ItemType itemTypeFromString(const std::string &name, int id = 0) return ITEM_UNUSABLE; } +static uint8_t spriteFromString(std::string_view name) +{ + if (name.empty()) + return SPRITE_ALL; + if (name == "race" || name == "type") + return TmwAthena::SPRITE_BASE; + if (name == "shoes" || name == "boot" || name == "boots") + return TmwAthena::SPRITE_SHOE; + if (name == "bottomclothes" || name == "bottom" || name == "pants") + return TmwAthena::SPRITE_BOTTOMCLOTHES; + if (name == "topclothes" || name == "top" || name == "torso" || name == "body") + return TmwAthena::SPRITE_TOPCLOTHES; + if (name == "misc1") + return TmwAthena::SPRITE_MISC1; + if (name == "misc2" || name == "scarf" || name == "scarfs") + return TmwAthena::SPRITE_MISC2; + if (name == "hair") + return TmwAthena::SPRITE_HAIR; + if (name == "hat" || name == "hats") + return TmwAthena::SPRITE_HAT; + if (name == "wings") + return TmwAthena::SPRITE_CAPE; + if (name == "glove" || name == "gloves") + return TmwAthena::SPRITE_GLOVES; + if (name == "weapon" || name == "weapons") + return TmwAthena::SPRITE_WEAPON; + if (name == "shield" || name == "shields") + return TmwAthena::SPRITE_SHIELD; + if (name == "amulet" || name == "amulets") + return 12; + if (name == "ring" || name == "rings") + return 13; + + return SPRITE_UNKNOWN; +} + +static uint8_t directionFromString(std::string_view name) +{ + if (name.empty()) + return DIRECTION_ALL; + if (name == "down" || name == "downall") + return DIRECTION_DOWN; + if (name == "left") + return DIRECTION_LEFT; + if (name == "up" || name == "upall") + return DIRECTION_UP; + if (name == "right") + return DIRECTION_RIGHT; + + // hack for died action. + if (name == "died") + return DIRECTION_DEAD; + + return DIRECTION_UNKNOWN; +} + void ItemDB::loadEmptyItemDefinition() { mUnknown->name = _("Unknown item"); mUnknown->display = SpriteDisplay(); std::string errFile = paths.getStringValue("spriteErrorFile"); - mUnknown->setSprite(errFile, Gender::MALE, 0); - mUnknown->setSprite(errFile, Gender::FEMALE, 0); - mUnknown->setSprite(errFile, Gender::HIDDEN, 0); + mUnknown->setSprite(errFile, Gender::Male, 0); + mUnknown->setSprite(errFile, Gender::Female, 0); + mUnknown->setSprite(errFile, Gender::Hidden, 0); mUnknown->hitEffectId = paths.getIntValue("hitEffectId"); mUnknown->criticalHitEffectId = paths.getIntValue("criticalHitEffectId"); } @@ -122,11 +180,11 @@ void ItemDB::loadSpriteRef(ItemInfo &itemInfo, XML::Node node) const int race = node.getProperty("race", 0); if (gender == "male" || gender == "unisex") - itemInfo.setSprite(filename, Gender::MALE, race); + itemInfo.setSprite(filename, Gender::Male, race); if (gender == "female" || gender == "unisex") - itemInfo.setSprite(filename, Gender::FEMALE, race); + itemInfo.setSprite(filename, Gender::Female, race); if (gender == "hidden" || gender == "other" || gender == "unisex") - itemInfo.setSprite(filename, Gender::HIDDEN, race); + itemInfo.setSprite(filename, Gender::Hidden, race); } void ItemDB::loadSoundRef(ItemInfo &itemInfo, XML::Node node) @@ -136,11 +194,11 @@ void ItemDB::loadSoundRef(ItemInfo &itemInfo, XML::Node node) if (event == "hit") { - itemInfo.addSound(EquipmentSoundEvent::HIT, filename); + itemInfo.addSound(EquipmentSoundEvent::Hit, filename); } else if (event == "strike" || event == "miss") { - itemInfo.addSound(EquipmentSoundEvent::STRIKE, filename); + itemInfo.addSound(EquipmentSoundEvent::Strike, filename); } else { @@ -166,6 +224,46 @@ void ItemDB::loadFloorSprite(SpriteDisplay &display, XML::Node floorNode) } } +void ItemDB::loadReplacement(ItemInfo &info, XML::Node replaceNode) +{ + std::string_view spriteString; + std::string_view directionString; + + replaceNode.attribute("sprite", spriteString); + replaceNode.attribute("direction", directionString); + + const uint8_t sprite = spriteFromString(spriteString); + const uint8_t direction = directionFromString(directionString); + + if (sprite == SPRITE_UNKNOWN) + { + logger->log("ItemDB: Invalid sprite name '%s' in replace tag", + spriteString.data()); + return; + } + + if (direction == DIRECTION_UNKNOWN) + { + logger->log("ItemDB: Invalid direction name '%s' in replace tag", + directionString.data()); + return; + } + + Replacement &replace = info.replacements.emplace_back(); + replace.sprite = sprite; + replace.direction = direction; + + for (auto child : replaceNode.children()) + { + if (child.name() == "item") + { + Replacement::Item &item = replace.items.emplace_back(); + child.attribute("from", item.from); + child.attribute("to", item.to); + } + } +} + void ItemDB::unload() { logger->log("Unloading item database..."); @@ -228,6 +326,10 @@ void ItemDB::loadCommonRef(ItemInfo &itemInfo, XML::Node node, const std::string { loadFloorSprite(itemInfo.display, itemChild); } + else if (itemChild.name() == "replace") + { + loadReplacement(itemInfo, itemChild); + } } } diff --git a/src/resources/itemdb.h b/src/resources/itemdb.h index ef0985a3..69620122 100644 --- a/src/resources/itemdb.h +++ b/src/resources/itemdb.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef ITEM_MANAGER_H -#define ITEM_MANAGER_H +#pragma once #include <list> #include <map> @@ -134,13 +133,18 @@ class ItemDB /** * Loads the sound references contained in a <sound> tag. */ - void loadSoundRef(ItemInfo &itemInfo, XML::Node node); + void loadSoundRef(ItemInfo &itemInfo, XML::Node node); /** * Loads the floor item references contained in a <floor> tag. */ void loadFloorSprite(SpriteDisplay &display, XML::Node node); + /** + * Loads the <replace> tag. + */ + void loadReplacement(ItemInfo &info, XML::Node replaceNode); + // Items database std::map<int, ItemInfo *> mItemInfos; std::map<std::string, ItemInfo *> mNamedItemInfos; @@ -204,5 +208,3 @@ class ManaServItemDB : public ItemDB } // namespace ManaServ extern ItemDB *itemDb; - -#endif diff --git a/src/resources/iteminfo.h b/src/resources/iteminfo.h index 78c808da..62e4796d 100644 --- a/src/resources/iteminfo.h +++ b/src/resources/iteminfo.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef ITEMINFO_H -#define ITEMINFO_H +#pragma once #include "being.h" @@ -32,8 +31,8 @@ enum class EquipmentSoundEvent { - STRIKE, - HIT + Strike, + Hit }; /** @@ -68,6 +67,32 @@ namespace ManaServ { class ManaServItemDB; } +enum ReplacementDirection : uint8_t +{ + DIRECTION_ALL = DIRECTION_DEFAULT, + DIRECTION_DEAD = DIRECTION_INVALID, + DIRECTION_UNKNOWN, +}; + +enum ReplacementSprite : uint8_t +{ + SPRITE_UNKNOWN = 254, + SPRITE_ALL = 255, +}; + +struct Replacement +{ + struct Item + { + int from = 0; // ID to replace (0: any) + int to = 0; // Replace with this ID (0: remove) + }; + + uint8_t sprite = SPRITE_ALL; // sprite slot to replace + uint8_t direction = DIRECTION_ALL; // direction in which to replace + std::vector<Item> items; // specific items to replace (empty: remove) +}; + /** * Defines a class for storing generic item infos. */ @@ -110,6 +135,8 @@ public: ItemType type = ITEM_UNUSABLE; /**< Item type. */ + std::vector<Replacement> replacements; + const std::string &getSprite(Gender gender, int race) const; const std::string &getSound(EquipmentSoundEvent event) const; @@ -162,5 +189,3 @@ enum EquipmentSlot }; } // namespace TmwAthena - -#endif // ITEMINFO_H diff --git a/src/resources/mapreader.cpp b/src/resources/mapreader.cpp index b5a5e258..b952cdcc 100644 --- a/src/resources/mapreader.cpp +++ b/src/resources/mapreader.cpp @@ -527,7 +527,7 @@ static Tileset *readTileset(XML::Node node, const std::string &path, std::string sourceStr = resolveRelativePath(pathDir, source); ResourceManager *resman = ResourceManager::getInstance(); - auto tilebmp = resman->getImageRef(sourceStr); + auto tilebmp = resman->getImage(sourceStr); if (tilebmp) { diff --git a/src/resources/mapreader.h b/src/resources/mapreader.h index 105c5d1d..e646fb04 100644 --- a/src/resources/mapreader.h +++ b/src/resources/mapreader.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef MAPREADER_H -#define MAPREADER_H +#pragma once #include "utils/xml.h" @@ -45,5 +44,3 @@ public: */ static Map *readMap(XML::Node node, const std::string &path); }; - -#endif // MAPREADER_H diff --git a/src/resources/monsterdb.cpp b/src/resources/monsterdb.cpp index 215ca2f8..4963f93f 100644 --- a/src/resources/monsterdb.cpp +++ b/src/resources/monsterdb.cpp @@ -54,7 +54,7 @@ void MonsterDB::init() unload(); // This can be overridden by an 'offset' attribute on a 'monsters' root tag. - mMonsterIdOffset = Net::getNetworkType() == ServerType::TMWATHENA ? OLD_TMWATHENA_OFFSET : 0; + mMonsterIdOffset = Net::getNetworkType() == ServerType::TmwAthena ? OLD_TMWATHENA_OFFSET : 0; } void MonsterDB::setMonsterIdOffset(int offset) @@ -95,19 +95,19 @@ void MonsterDB::readMonsterNode(XML::Node node, const std::string &filename) if (event == "hit") { - currentInfo->addSound(SoundEvent::HIT, soundFile); + currentInfo->addSound(SoundEvent::Hit, soundFile); } else if (event == "miss") { - currentInfo->addSound(SoundEvent::MISS, soundFile); + currentInfo->addSound(SoundEvent::Miss, soundFile); } else if (event == "hurt") { - currentInfo->addSound(SoundEvent::HURT, soundFile); + currentInfo->addSound(SoundEvent::Hurt, soundFile); } else if (event == "die") { - currentInfo->addSound(SoundEvent::DIE, soundFile); + currentInfo->addSound(SoundEvent::Die, soundFile); } else { diff --git a/src/resources/monsterdb.h b/src/resources/monsterdb.h index ff709486..ec71952f 100644 --- a/src/resources/monsterdb.h +++ b/src/resources/monsterdb.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef MONSTER_DB_H -#define MONSTER_DB_H +#pragma once #include "utils/xml.h" @@ -43,5 +42,3 @@ namespace MonsterDB BeingInfo *get(int id); } - -#endif diff --git a/src/resources/music.cpp b/src/resources/music.cpp index 12c723bd..069af588 100644 --- a/src/resources/music.cpp +++ b/src/resources/music.cpp @@ -33,7 +33,7 @@ Music::~Music() Mix_FreeMusic(mMusic); } -Resource *Music::load(SDL_RWops *rw) +Music *Music::load(SDL_RWops *rw) { if (Mix_Music *music = Mix_LoadMUS_RW(rw, 1)) { diff --git a/src/resources/music.h b/src/resources/music.h index 0c445b2b..d22257da 100644 --- a/src/resources/music.h +++ b/src/resources/music.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef MUSIC_H -#define MUSIC_H +#pragma once #include "resources/resource.h" @@ -39,10 +38,10 @@ class Music : public Resource * * @param rw The SDL_RWops to load the music data from. * - * @return <code>NULL</code> if the an error occurred, a valid pointer - * otherwise. + * @return <code>nullptr</code> if the an error occurred, a valid + * pointer otherwise. */ - static Resource *load(SDL_RWops *rw); + static Music *load(SDL_RWops *rw); /** * Plays the music. @@ -61,5 +60,3 @@ class Music : public Resource Mix_Music *mMusic; }; - -#endif diff --git a/src/resources/npcdb.h b/src/resources/npcdb.h index 306167de..779f4919 100644 --- a/src/resources/npcdb.h +++ b/src/resources/npcdb.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef NPC_DB_H -#define NPC_DB_H +#pragma once #include <string> #include "utils/xml.h" @@ -42,5 +41,3 @@ namespace NPCDB BeingInfo *get(int id); } - -#endif diff --git a/src/resources/resource.h b/src/resources/resource.h index e1f37d73..ba8d17cc 100644 --- a/src/resources/resource.h +++ b/src/resources/resource.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef RESOURCE_H -#define RESOURCE_H +#pragma once #include <ctime> #include <string> @@ -151,11 +150,13 @@ public: * This is currently necessary to avoid calls to decRef on instances of * SubImage, which are not reference counted resources. */ - void release() - { mResource = nullptr; } + RESOURCE *release() + { + RESOURCE *resource = mResource; + mResource = nullptr; + return resource; + } private: RESOURCE *mResource; }; - -#endif diff --git a/src/resources/resourcemanager.cpp b/src/resources/resourcemanager.cpp index ff83f422..e62407e3 100644 --- a/src/resources/resourcemanager.cpp +++ b/src/resources/resourcemanager.cpp @@ -31,10 +31,7 @@ #include "resources/soundeffect.h" #include "resources/spritedef.h" -#include "utils/zlib.h" -#include "utils/physfsrwops.h" - -#include <physfs.h> +#include "utils/filesystem.h" #include <SDL_image.h> @@ -47,56 +44,42 @@ ResourceManager *ResourceManager::instance = nullptr; ResourceManager::ResourceManager() - : mOldestOrphan(0) { logger->log("Initializing resource manager..."); } ResourceManager::~ResourceManager() { - mResources.insert(mOrphanedResources.begin(), mOrphanedResources.end()); - - // Release any remaining spritedefs first because they depend on image sets - auto iter = mResources.begin(); - while (iter != mResources.end()) + auto cleanupResources = [&](auto match) { - if (dynamic_cast<SpriteDef*>(iter->second) != nullptr) - { - cleanUp(iter->second); - auto toErase = iter; - ++iter; - mResources.erase(toErase); - } - else - { - ++iter; - } - } + // Include any orphaned resources into the main list for cleanup + mResources.insert(mOrphanedResources.begin(), mOrphanedResources.end()); + mOrphanedResources.clear(); - // Release any remaining image sets first because they depend on images - iter = mResources.begin(); - while (iter != mResources.end()) - { - if (dynamic_cast<ImageSet*>(iter->second) != nullptr) - { - cleanUp(iter->second); - auto toErase = iter; - ++iter; - mResources.erase(toErase); - } - else + for (auto iter = mResources.begin(); iter != mResources.end(); ) { - ++iter; + if (match(iter->second)) + { + cleanUp(iter->second); + iter = mResources.erase(iter); + } + else + { + ++iter; + } } - } + }; - // Release remaining resources, logging the number of dangling references. - iter = mResources.begin(); - while (iter != mResources.end()) - { - cleanUp(iter->second); - ++iter; - } + // SpriteDef references ImageSet + cleanupResources([](Resource *res) { return dynamic_cast<SpriteDef *>(res); }); + + // ImageSet references Image + cleanupResources([](Resource *res) { return dynamic_cast<ImageSet *>(res); }); + + // Release remaining resources + cleanupResources([](Resource *res) { return true; }); + + assert(mOrphanedResources.empty()); } void ResourceManager::cleanUp(Resource *res) @@ -117,7 +100,7 @@ void ResourceManager::cleanOrphans() { // Delete orphaned resources after 30 seconds. time_t oldest = time(nullptr); - time_t threshold = oldest - 30; + const time_t threshold = oldest - 30; if (mOrphanedResources.empty() || mOldestOrphan >= threshold) return; @@ -144,17 +127,12 @@ void ResourceManager::cleanOrphans() mOldestOrphan = oldest; } -bool ResourceManager::setWriteDir(const std::string &path) -{ - return (bool) PHYSFS_setWriteDir(path.c_str()); -} - bool ResourceManager::addToSearchPath(const std::string &path, bool append) { logger->log("Adding to PhysicsFS: %s", path.c_str()); - if (!PHYSFS_mount(path.c_str(), nullptr, append ? 1 : 0)) + if (!FS::addToSearchPath(path, append)) { - logger->log("Error: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); + logger->log("Error: %s", FS::getLastError()); return false; } return true; @@ -164,58 +142,33 @@ void ResourceManager::searchAndAddArchives(const std::string &path, const std::string &ext, bool append) { - const char *dirSep = PHYSFS_getDirSeparator(); - char **list = PHYSFS_enumerateFiles(path.c_str()); + const char *dirSep = FS::getDirSeparator(); - for (char **i = list; *i; i++) + for (auto fileName : FS::enumerateFiles(path)) { - size_t len = strlen(*i); + const size_t len = strlen(fileName); - if (len > ext.length() && !ext.compare((*i)+(len - ext.length()))) + if (len > ext.length() && ext != (fileName + (len - ext.length()))) { - std::string file, realPath, archive; - - file = path + (*i); - realPath = std::string(PHYSFS_getRealDir(file.c_str())); - archive = realPath + dirSep + file; - - addToSearchPath(archive, append); + std::string file = path + fileName; + if (auto realDir = FS::getRealDir(file)) + { + std::string archive = std::string(*realDir) + dirSep + file; + addToSearchPath(archive, append); + } } } - - PHYSFS_freeList(list); -} - -bool ResourceManager::mkdir(const std::string &path) -{ - return (bool) PHYSFS_mkdir(path.c_str()); -} - -bool ResourceManager::exists(const std::string &path) -{ - return PHYSFS_exists(path.c_str()); -} - -bool ResourceManager::isDirectory(const std::string &path) -{ - PHYSFS_Stat stat; - if (PHYSFS_stat(path.c_str(), &stat) != 0) - { - return stat.filetype == PHYSFS_FILETYPE_DIRECTORY; - } - return false; } std::string ResourceManager::getPath(const std::string &file) { - // get the real path to the file - const char* tmp = PHYSFS_getRealDir(file.c_str()); + // Get the real directory of the file + auto realDir = FS::getRealDir(file); std::string path; - // if the file is not in the search path, then its NULL - if (tmp) + if (realDir) { - path = std::string(tmp) + "/" + file; + path = std::string(*realDir) + "/" + file; } else { @@ -226,11 +179,6 @@ std::string ResourceManager::getPath(const std::string &file) return path; } -SDL_RWops *ResourceManager::open(const std::string &path) -{ - return PHYSFSRWOPS_openRead(path.c_str()); -} - Resource *ResourceManager::get(const std::string &idPath, const std::function<Resource *()> &generator) { @@ -238,7 +186,6 @@ Resource *ResourceManager::get(const std::string &idPath, auto resIter = mResources.find(idPath); if (resIter != mResources.end()) { - resIter->second->incRef(); return resIter->second; } @@ -248,44 +195,41 @@ Resource *ResourceManager::get(const std::string &idPath, Resource *res = resIter->second; mResources.insert(*resIter); mOrphanedResources.erase(resIter); - res->incRef(); return res; } Resource *resource = generator(); - if (resource) { - resource->incRef(); resource->mIdPath = idPath; mResources[idPath] = resource; cleanOrphans(); } - // Returns NULL if the object could not be created. return resource; } -Resource *ResourceManager::get(const std::string &path, loader fun) +ResourceRef<Music> ResourceManager::getMusic(const std::string &path) { - return get(path, [&] () -> Resource * { - if (SDL_RWops *rw = open(path)) - return fun(rw); + return static_cast<Music*>(get(path, [&] () -> Resource * { + if (SDL_RWops *rw = FS::openBufferedRWops(path)) + return Music::load(rw); + return nullptr; - }); + })); } -Music *ResourceManager::getMusic(const std::string &idPath) +ResourceRef<SoundEffect> ResourceManager::getSoundEffect(const std::string &path) { - return static_cast<Music*>(get(idPath, Music::load)); -} + return static_cast<SoundEffect*>(get(path, [&] () -> Resource * { + if (SDL_RWops *rw = FS::openBufferedRWops(path)) + return SoundEffect::load(rw); -SoundEffect *ResourceManager::getSoundEffect(const std::string &idPath) -{ - return static_cast<SoundEffect*>(get(idPath, SoundEffect::load)); + return nullptr; + })); } -Image *ResourceManager::getImage(const std::string &idPath) +ResourceRef<Image> ResourceManager::getImage(const std::string &idPath) { return static_cast<Image*>(get(idPath, [&] () -> Resource * { std::string path = idPath; @@ -296,7 +240,7 @@ Image *ResourceManager::getImage(const std::string &idPath) d = std::make_unique<Dye>(path.substr(p + 1)); path = path.substr(0, p); } - SDL_RWops *rw = open(path); + SDL_RWops *rw = FS::openRWops(path); if (!rw) return nullptr; @@ -306,21 +250,14 @@ Image *ResourceManager::getImage(const std::string &idPath) })); } -ResourceRef<Image> ResourceManager::getImageRef(const std::string &idPath) -{ - ResourceRef<Image> img = getImage(idPath); - img->decRef(); // remove ref added by ResourceManager::get - return img; -} - -ImageSet *ResourceManager::getImageSet(const std::string &imagePath, - int w, int h) +ResourceRef<ImageSet> ResourceManager::getImageSet(const std::string &imagePath, + int w, int h) { std::stringstream ss; ss << imagePath << "[" << w << "x" << h << "]"; return static_cast<ImageSet*>(get(ss.str(), [&] () -> Resource * { - auto img = getImageRef(imagePath); + auto img = getImage(imagePath); if (!img) return nullptr; @@ -328,7 +265,7 @@ ImageSet *ResourceManager::getImageSet(const std::string &imagePath, })); } -SpriteDef *ResourceManager::getSprite(const std::string &path, int variant) +ResourceRef<SpriteDef> ResourceManager::getSprite(const std::string &path, int variant) { std::stringstream ss; ss << path << "[" << variant << "]"; @@ -373,101 +310,3 @@ void ResourceManager::deleteInstance() delete instance; instance = nullptr; } - -void *ResourceManager::loadFile(const std::string &filename, int &filesize, - bool inflate) -{ - // Attempt to open the specified file using PhysicsFS - PHYSFS_file *file = PHYSFS_openRead(filename.c_str()); - - // If the handler is an invalid pointer indicate failure - if (file == nullptr) - { - logger->log("Warning: Failed to load %s: %s", - filename.c_str(), PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - return nullptr; - } - - // Log the real dir of the file - logger->log("Loaded %s/%s", PHYSFS_getRealDir(filename.c_str()), - filename.c_str()); - - // Get the size of the file - filesize = PHYSFS_fileLength(file); - - // Allocate memory and load the file - void *buffer = malloc(filesize); - PHYSFS_readBytes(file, buffer, filesize); - - // Close the file and let the user deallocate the memory - PHYSFS_close(file); - - if (inflate && filename.find(".gz", filename.length() - 3) - != std::string::npos) - { - unsigned char *inflated; - - // Inflate the gzipped map data - filesize = inflateMemory((unsigned char*) buffer, filesize, inflated); - free(buffer); - - buffer = inflated; - - if (!buffer) - { - logger->log("Could not decompress file: %s", filename.c_str()); - } - } - - return buffer; -} - -bool ResourceManager::copyFile(const std::string &src, const std::string &dst) -{ - PHYSFS_file *srcFile = PHYSFS_openRead(src.c_str()); - if (!srcFile) - { - logger->log("Read error: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - return false; - } - PHYSFS_file *dstFile = PHYSFS_openWrite(dst.c_str()); - if (!dstFile) - { - logger->log("Write error: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - PHYSFS_close(srcFile); - return false; - } - - int fileSize = PHYSFS_fileLength(srcFile); - void *buf = malloc(fileSize); - PHYSFS_readBytes(srcFile, buf, fileSize); - PHYSFS_writeBytes(dstFile, buf, fileSize); - - PHYSFS_close(srcFile); - PHYSFS_close(dstFile); - free(buf); - return true; -} - -std::vector<std::string> ResourceManager::loadTextFile( - const std::string &fileName) -{ - int contentsLength; - char *fileContents = (char*)loadFile(fileName, contentsLength); - std::vector<std::string> lines; - - if (!fileContents) - { - logger->log("Couldn't load text file: %s", fileName.c_str()); - return lines; - } - - std::istringstream iss(std::string(fileContents, contentsLength)); - std::string line; - - while (getline(iss, line)) - lines.push_back(line); - - free(fileContents); - return lines; -} diff --git a/src/resources/resourcemanager.h b/src/resources/resourcemanager.h index d1c32d8c..728a9b74 100644 --- a/src/resources/resourcemanager.h +++ b/src/resources/resourcemanager.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef RESOURCE_MANAGER_H -#define RESOURCE_MANAGER_H +#pragma once #include "resources/resource.h" @@ -28,7 +27,6 @@ #include <functional> #include <map> #include <string> -#include <vector> class Image; class ImageSet; @@ -36,8 +34,6 @@ class Music; class SoundEffect; class SpriteDef; -struct SDL_RWops; - /** * A class for loading and managing resources. */ @@ -46,9 +42,6 @@ class ResourceManager friend class Resource; public: - - using loader = Resource *(*)(SDL_RWops *); - ResourceManager(); /** @@ -58,43 +51,20 @@ class ResourceManager ~ResourceManager(); /** - * Sets the write directory. - * - * @param path The path of the directory to be added. - * @return <code>true</code> on success, <code>false</code> otherwise. - */ - bool setWriteDir(const std::string &path); - - /** * Adds a directory or archive to the search path. If append is true * then the directory is added to the end of the search path, otherwise * it is added at the front. * * @return <code>true</code> on success, <code>false</code> otherwise. */ - bool addToSearchPath(const std::string &path, bool append); + static bool addToSearchPath(const std::string &path, bool append); /** * Searches for zip files and adds them to the search path. */ - void searchAndAddArchives(const std::string &path, - const std::string &ext, - bool append); - - /** - * Creates a directory in the write path - */ - bool mkdir(const std::string &path); - - /** - * Checks whether the given file or directory exists in the search path - */ - bool exists(const std::string &path); - - /** - * Checks whether the given path is a directory. - */ - bool isDirectory(const std::string &path); + static void searchAndAddArchives(const std::string &path, + const std::string &ext, + bool append); /** * Returns the real path to a file. Note that this method will always @@ -103,106 +73,34 @@ class ResourceManager * @param file The file to get the real path to. * @return The real path. */ - std::string getPath(const std::string &file); - - /** - * Opens a file for reading. The caller is responsible for closing the - * file. - * - * @param path The file name. - * @return A valid SDL_RWops pointer or <code>NULL</code> if the file - * could not be opened. - */ - SDL_RWops *open(const std::string &path); - - /** - * Creates a resource and adds it to the resource map. - * - * @param idPath The resource identifier path. - * @param fun A function for generating the resource. - * @param data Extra parameters for the generator. - * @return A valid resource or <code>NULL</code> if the resource could - * not be generated. - */ - Resource *get(const std::string &idPath, - const std::function<Resource *()> &generator); - - /** - * Loads a resource from a file and adds it to the resource map. - * - * @param path The file name. - * @param fun A function for parsing the file. - * @return A valid resource or <code>NULL</code> if the resource could - * not be loaded. - */ - Resource *get(const std::string &path, loader fun); - - /** - * Convenience wrapper around ResourceManager::get for loading - * images. - */ - Image *getImage(const std::string &idPath); + static std::string getPath(const std::string &file); /** - * Convenience wrapper around ResourceManager::get for loading - * images. Returns an automatically reference-counted resource. + * Loads the Image resource found at the given identifier path. The + * path can include a dye specification after a '|' character. */ - ResourceRef<Image> getImageRef(const std::string &idPath); + ResourceRef<Image> getImage(const std::string &idPath); /** - * Convenience wrapper around ResourceManager::get for loading - * songs. + * Loads the Music resource found at the given path. */ - Music *getMusic(const std::string &idPath); + ResourceRef<Music> getMusic(const std::string &path); /** - * Convenience wrapper around ResourceManager::get for loading - * samples. + * Loads the SoundEffect resource found at the given path. */ - SoundEffect *getSoundEffect(const std::string &idPath); + ResourceRef<SoundEffect> getSoundEffect(const std::string &path); /** - * Creates a image set based on the image referenced by the given - * path and the supplied sprite sizes + * Loads a image set based on the image referenced by the given path + * and the supplied sprite sizes. */ - ImageSet *getImageSet(const std::string &imagePath, int w, int h); + ResourceRef<ImageSet> getImageSet(const std::string &imagePath, int w, int h); /** - * Creates a sprite definition based on a given path and the supplied - * variant. + * Loads a SpriteDef based on a given path and the supplied variant. */ - SpriteDef *getSprite(const std::string &path, int variant = 0); - - /** - * Allocates data into a buffer pointer for raw data loading. The - * returned data is expected to be freed using <code>free()</code>. - * - * @param filename The name of the file to be loaded. - * @param filesize The size of the file that was loaded. - * @param inflate True to uncompress the file if the filename ends in - * ".gz", false to ignore that. - * - * @return An allocated byte array containing the data that was loaded, - * or <code>NULL</code> on fail. - */ - void *loadFile(const std::string &filename, int &filesize, - bool inflate = true); - - /** - * Copies a file from one place to another (useful for extracting - * raw files from a zip archive, for example) - * - * @param src Source file name - * @param dst Destination file name - * @return true on success, false on failure. An error message should be - * in the log file. - */ - bool copyFile(const std::string &src, const std::string &dst); - - /** - * Retrieves the contents of a text file. - */ - std::vector<std::string> loadTextFile(const std::string &fileName); + ResourceRef<SpriteDef> getSprite(const std::string &path, int variant = 0); /** * Returns an instance of the class, creating one if it does not @@ -217,6 +115,19 @@ class ResourceManager private: /** + * Looks up a resource, creating it with the generator function if it + * does not exist. Does not increment the reference count of the + * resource. + * + * @param idPath The resource identifier path. + * @param generator A function for generating the resource. + * @return A valid resource or <code>nullptr</code> if the resource could + * not be generated. + */ + Resource *get(const std::string &idPath, + const std::function<Resource *()> &generator); + + /** * Releases a resource, placing it in the set of orphaned resources. * Only called from Resource::decRef, */ @@ -238,7 +149,5 @@ class ResourceManager static ResourceManager *instance; std::map<std::string, Resource *> mResources; std::map<std::string, Resource *> mOrphanedResources; - time_t mOldestOrphan; + time_t mOldestOrphan = 0; }; - -#endif diff --git a/src/resources/settingsmanager.cpp b/src/resources/settingsmanager.cpp index 8966f976..9323d4d1 100644 --- a/src/resources/settingsmanager.cpp +++ b/src/resources/settingsmanager.cpp @@ -20,22 +20,23 @@ #include "resources/settingsmanager.h" -#include "configuration.h" #include "resources/attributes.h" +#include "resources/emotedb.h" #include "resources/hairdb.h" #include "resources/itemdb.h" #include "resources/monsterdb.h" -#include "resources/specialdb.h" #include "resources/npcdb.h" -#include "resources/emotedb.h" -#include "statuseffect.h" -#include "units.h" +#include "resources/abilitydb.h" +#include "resources/statuseffectdb.h" #include "net/net.h" #include "utils/xml.h" #include "utils/path.h" + +#include "configuration.h" #include "log.h" +#include "units.h" namespace SettingsManager { @@ -52,10 +53,10 @@ namespace SettingsManager hairDB.init(); itemDb->init(); MonsterDB::init(); - SpecialDB::init(); + AbilityDB::init(); NPCDB::init(); EmoteDB::init(); - StatusEffect::init(); + StatusEffectDB::init(); Units::init(); // load stuff from settings @@ -76,13 +77,13 @@ namespace SettingsManager hairDB.checkStatus(); itemDb->checkStatus(); MonsterDB::checkStatus(); - SpecialDB::checkStatus(); + AbilityDB::checkStatus(); NPCDB::checkStatus(); EmoteDB::checkStatus(); - StatusEffect::checkStatus(); + StatusEffectDB::checkStatus(); Units::checkStatus(); - if (Net::getNetworkType() == ServerType::MANASERV) + if (Net::getNetworkType() == ServerType::ManaServ) { Attributes::informItemDB(); } @@ -90,10 +91,10 @@ namespace SettingsManager void unload() { - StatusEffect::unload(); + StatusEffectDB::unload(); EmoteDB::unload(); NPCDB::unload(); - SpecialDB::unload(); + AbilityDB::unload(); MonsterDB::unload(); if (itemDb) itemDb->unload(); @@ -180,7 +181,6 @@ namespace SettingsManager } else if (childNode.name() == "attribute") { - // map config Attributes::readAttributeNode(childNode, filename); } else if (childNode.name() == "points") @@ -213,9 +213,9 @@ namespace SettingsManager { MonsterDB::readMonsterNode(childNode, filename); } - else if (childNode.name() == "special-set") + else if (childNode.name() == "ability") { - SpecialDB::readSpecialSetNode(childNode, filename); + AbilityDB::readAbilityNode(childNode, filename); } else if (childNode.name() == "npc") { @@ -225,23 +225,14 @@ namespace SettingsManager { EmoteDB::readEmoteNode(childNode, filename); } - else if (childNode.name() == "status-effect" || childNode.name() == "stun-effect") + else if (childNode.name() == "status-effect") { - StatusEffect::readStatusEffectNode(childNode, filename); + StatusEffectDB::readStatusEffectNode(childNode, filename); } else if (childNode.name() == "unit") { Units::readUnitNode(childNode, filename); } - else - { - // compatibility stuff with older configs/games - if (node.name() == "specials" && childNode.name() == "set") - { - // specials.xml:/specials/set - SpecialDB::readSpecialSetNode(childNode, filename); - } - } } mIncludedFiles.erase(filename); diff --git a/src/resources/settingsmanager.h b/src/resources/settingsmanager.h index 25feb86b..5b70f865 100644 --- a/src/resources/settingsmanager.h +++ b/src/resources/settingsmanager.h @@ -18,8 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SETTINGSMANAGER_HPP -#define SETTINGSMANAGER_HPP +#pragma once #include <string> #include <list> @@ -30,6 +29,3 @@ namespace SettingsManager void load(); void unload(); } - - -#endif // SETTINGSMANAGER_HPP diff --git a/src/resources/soundeffect.cpp b/src/resources/soundeffect.cpp index 8f8cdfc5..19d7a820 100644 --- a/src/resources/soundeffect.cpp +++ b/src/resources/soundeffect.cpp @@ -28,23 +28,20 @@ SoundEffect::~SoundEffect() Mix_FreeChunk(mChunk); } -Resource *SoundEffect::load(SDL_RWops *rw) +SoundEffect *SoundEffect::load(SDL_RWops *rw) { // Load the music data and free the RWops structure - Mix_Chunk *tmpSoundEffect = Mix_LoadWAV_RW(rw, 1); - - if (tmpSoundEffect) + if (Mix_Chunk *soundEffect = Mix_LoadWAV_RW(rw, 1)) { - return new SoundEffect(tmpSoundEffect); + return new SoundEffect(soundEffect); } logger->log("Error, failed to load sound effect: %s", Mix_GetError()); return nullptr; } -bool SoundEffect::play(int loops, int volume, int channel) +int SoundEffect::play(int loops, int volume, int channel) { Mix_VolumeChunk(mChunk, volume); - - return Mix_PlayChannel(channel, mChunk, loops) != -1; + return Mix_PlayChannel(channel, mChunk, loops); } diff --git a/src/resources/soundeffect.h b/src/resources/soundeffect.h index eada80b5..9ca8490d 100644 --- a/src/resources/soundeffect.h +++ b/src/resources/soundeffect.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SOUND_EFFECT_H -#define SOUND_EFFECT_H +#pragma once #include "resources/resource.h" @@ -37,12 +36,12 @@ class SoundEffect : public Resource /** * Loads a sample from a buffer in memory. * - * @param rw The SDL_RWops to load the sample data from. + * @param rw The SDL_RWops to load the sample data from. * - * @return <code>NULL</code> if the an error occurred, a valid pointer + * @return <code>nullptr</code> if the an error occurred, a valid pointer * otherwise. */ - static Resource *load(SDL_RWops *rw); + static SoundEffect *load(SDL_RWops *rw); /** * Plays the sample. @@ -51,15 +50,13 @@ class SoundEffect : public Resource * @param volume Sample playback volume. * @param channel Sample playback channel. * - * @return <code>true</code> if the playback started properly - * <code>false</code> otherwise. + * @return which channel was used to play the sound, or -1 if sound could not + * be played. */ - bool play(int loops, int volume, int channel = -1); + int play(int loops, int volume, int channel = -1); protected: SoundEffect(Mix_Chunk *soundEffect): mChunk(soundEffect) {} Mix_Chunk *mChunk; }; - -#endif // SOUND_EFFECT_H diff --git a/src/resources/specialdb.cpp b/src/resources/specialdb.cpp deleted file mode 100644 index ec0b3f2f..00000000 --- a/src/resources/specialdb.cpp +++ /dev/null @@ -1,115 +0,0 @@ -/* - * The Mana Client - * Copyright (C) 2010-2013 The Mana Developers - * - * This file is part of The Mana Client. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "resources/specialdb.h" - -#include "log.h" - -#include "utils/dtor.h" - -#include <map> - -namespace -{ - std::map<int, SpecialInfo *> mSpecialInfos; - bool mLoaded = false; -} - -SpecialInfo::TargetMode SpecialDB::targetModeFromString(const std::string& str) -{ - if (str == "being") - return SpecialInfo::TARGET_BEING; - if (str == "point") - return SpecialInfo::TARGET_POINT; - - logger->log("SpecialDB: Warning, unknown target mode \"%s\"", str.c_str() ); - return SpecialInfo::TARGET_BEING; -} - - -void SpecialDB::init() -{ - if (mLoaded) - unload(); -} - -void SpecialDB::readSpecialSetNode(XML::Node node, const std::string &filename) -{ - std::string setName = node.getProperty("name", "Actions"); - - for (auto special : node.children()) - { - if (special.name() == "special") - { - auto *info = new SpecialInfo(); - int id = special.getProperty("id", 0); - info->id = id; - info->set = setName; - info->name = special.getProperty("name", ""); - info->icon = special.getProperty("icon", ""); - - info->targetMode = targetModeFromString(special.getProperty("target", "being")); - - info->rechargeable = special.getBoolProperty("rechargeable", true); - info->rechargeNeeded = 0; - info->rechargeCurrent = 0; - - if (mSpecialInfos.find(id) != mSpecialInfos.end()) - { - logger->log("SpecialDB: Duplicate special ID %d in %s, ignoring", id, filename.c_str()); - } else { - mSpecialInfos[id] = info; - } - } - } - -} - -void SpecialDB::checkStatus() -{ - mLoaded = true; -} - - -void SpecialDB::unload() -{ - - delete_all(mSpecialInfos); - mSpecialInfos.clear(); - - mLoaded = false; -} - - -SpecialInfo *SpecialDB::get(int id) -{ - - auto i = mSpecialInfos.find(id); - - if (i == mSpecialInfos.end()) - { - return nullptr; - } - else - { - return i->second; - } - return nullptr; -} diff --git a/src/resources/spritedef.cpp b/src/resources/spritedef.cpp index f42e623e..85e5e566 100644 --- a/src/resources/spritedef.cpp +++ b/src/resources/spritedef.cpp @@ -28,7 +28,6 @@ #include "resources/animation.h" #include "resources/dye.h" #include "resources/image.h" -#include "resources/imageset.h" #include "resources/resourcemanager.h" #include "configuration.h" @@ -155,8 +154,7 @@ void SpriteDef::loadImageSet(XML::Node node, const std::string &palettes) Dye::instantiate(imageSrc, palettes); ResourceManager *resman = ResourceManager::getInstance(); - ImageSet *imageSet = resman->getImageSet(imageSrc, width, height); - + auto imageSet = resman->getImageSet(imageSrc, width, height); if (!imageSet) { logger->error(strprintf("Couldn't load imageset (%s)!", @@ -328,11 +326,6 @@ SpriteDef::~SpriteDef() { delete action; } - - for (auto &imageSet : mImageSets) - { - imageSet.second->decRef(); - } } SpriteDirection SpriteDef::makeSpriteDirection(const std::string &direction) diff --git a/src/resources/spritedef.h b/src/resources/spritedef.h index fa44deea..0d48d145 100644 --- a/src/resources/spritedef.h +++ b/src/resources/spritedef.h @@ -19,10 +19,9 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SPRITEDEF_H -#define SPRITEDEF_H +#pragma once -#include "resources/resource.h" +#include "resources/imageset.h" #include "utils/xml.h" @@ -50,7 +49,7 @@ struct SpriteDisplay * Remember those are the main action. * Action subtypes, e.g.: "attack_bow" are to be passed by items.xml after * an ACTION_ATTACK call. - * Which special to be use to to be passed with the USE_SPECIAL call. + * Which ability to be use to to be passed with the USE_ABILITY call. * Running, walking, ... is a sub-type of moving. * ... * Please don't add hard-coded subtypes here! @@ -65,7 +64,7 @@ namespace SpriteAction static const std::string MOVE = "walk"; static const std::string ATTACK = "attack"; static const std::string HURT = "hurt"; - static const std::string USE_SPECIAL = "special"; + static const std::string USE_ABILITY = "ability"; static const std::string CAST_MAGIC = "magic"; static const std::string USE_ITEM = "item"; static const std::string INVALID; @@ -147,8 +146,6 @@ class SpriteDef : public Resource */ void substituteAction(std::string complete, std::string with); - std::map<std::string, ImageSet *> mImageSets; + std::map<std::string, ResourceRef<ImageSet>> mImageSets; std::map<std::string, Action *> mActions; }; - -#endif // SPRITEDEF_H diff --git a/src/resources/statuseffectdb.cpp b/src/resources/statuseffectdb.cpp new file mode 100644 index 00000000..fe179191 --- /dev/null +++ b/src/resources/statuseffectdb.cpp @@ -0,0 +1,96 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2025 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "statuseffectdb.h" + +bool StatusEffectDB::mLoaded = false; +std::map<int, StatusEffect> StatusEffectDB::mStatusEffects; +StatusEffectDB::OptionsMap StatusEffectDB::mOpt0ToIdMap; +StatusEffectDB::OptionsMap StatusEffectDB::mOpt1ToIdMap; +StatusEffectDB::OptionsMap StatusEffectDB::mOpt2ToIdMap; +StatusEffectDB::OptionsMap StatusEffectDB::mOpt3ToIdMap; + + +const StatusEffect *StatusEffectDB::getStatusEffect(int id) +{ + auto it = mStatusEffects.find(id); + if (it == mStatusEffects.end()) + return nullptr; + return &it->second; +} + +void StatusEffectDB::init() +{ + if (mLoaded) + unload(); +} + +void StatusEffectDB::readStatusEffectNode(XML::Node node, const std::string &/* filename */) +{ + const int id = node.getProperty("id", -1); + + const int opt0 = node.getProperty("option", 0); + const int opt1 = node.getProperty("opt1", 0); + const int opt2 = node.getProperty("opt2", 0); + const int opt3 = node.getProperty("opt3", 0); + if (opt0 != 0 && opt0 <= UINT16_MAX) + mOpt0ToIdMap[opt0] = id; + if (opt1 != 0 && opt1 <= UINT16_MAX) + mOpt1ToIdMap[opt1] = id; + if (opt2 != 0 && opt2 <= UINT16_MAX) + mOpt2ToIdMap[opt2] = id; + if (opt3 != 0 && opt3 <= UINT16_MAX) + mOpt3ToIdMap[opt3] = id; + + auto &effect = mStatusEffects[id]; + + node.attribute("name", effect.name); + + node.attribute("start-message", effect.start.message); + node.attribute("start-audio", effect.start.sfx); + node.attribute("start-particle", effect.start.particleEffect); + + // For now we don't support separate particle effect for "already applied" + // status effects. + if (effect.start.particleEffect.empty()) + node.attribute("particle", effect.start.particleEffect); + + node.attribute("end-message", effect.end.message); + node.attribute("end-audio", effect.end.sfx); + node.attribute("end-particle", effect.end.particleEffect); + + node.attribute("icon", effect.icon); + node.attribute("persistent-particle-effect", effect.persistentParticleEffect); +} + +void StatusEffectDB::checkStatus() +{ + mLoaded = true; +} + +void StatusEffectDB::unload() +{ + if (!mLoaded) + return; + + mStatusEffects.clear(); + mLoaded = false; +} diff --git a/src/resources/statuseffectdb.h b/src/resources/statuseffectdb.h new file mode 100644 index 00000000..d1f1a6bf --- /dev/null +++ b/src/resources/statuseffectdb.h @@ -0,0 +1,67 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2025 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef STATUSEFFECTDB_H +#define STATUSEFFECTDB_H + +#include "statuseffect.h" +#include "utils/xml.h" + +#include <cstdint> +#include <map> + +class StatusEffectDB +{ +public: + /** + * Retrieves a status effect. + * + * \param id ID of the status effect. + */ + static const StatusEffect *getStatusEffect(int id); + + using OptionsMap = std::map<uint16_t, int>; + + /** + * These map flags or indexes to their corresponding status effect ID. + * This is tmwAthena-specific. + */ + static const OptionsMap &opt0ToIdMap() { return mOpt0ToIdMap; } + static const OptionsMap &opt1ToIdMap() { return mOpt1ToIdMap; } + static const OptionsMap &opt2ToIdMap() { return mOpt2ToIdMap; } + static const OptionsMap &opt3ToIdMap() { return mOpt3ToIdMap; } + + static void init(); + static void readStatusEffectNode(XML::Node node, const std::string &filename); + static void checkStatus(); + static void unload(); + +private: + static bool mLoaded; + + static std::map<int, StatusEffect> mStatusEffects; + static OptionsMap mOpt0ToIdMap; + static OptionsMap mOpt1ToIdMap; + static OptionsMap mOpt2ToIdMap; + static OptionsMap mOpt3ToIdMap; +}; + +#endif // STATUSEFFECTDB_H diff --git a/src/resources/theme.cpp b/src/resources/theme.cpp index ad686e19..694f2210 100644 --- a/src/resources/theme.cpp +++ b/src/resources/theme.cpp @@ -25,85 +25,191 @@ #include "configuration.h" #include "log.h" +#include "textrenderer.h" #include "resources/dye.h" #include "resources/image.h" #include "resources/imageset.h" #include "resources/resourcemanager.h" -#include "utils/dtor.h" -#include "utils/stringutils.h" -#include "utils/xml.h" +#include "utils/filesystem.h" -#include <physfs.h> +#include <guichan/font.hpp> +#include <guichan/widget.hpp> #include <algorithm> +#include <optional> +/** + * Initializes the directory in which the client looks for GUI themes, which at + * the same time functions as a fallback directory when looking up files + * relevant for the GUI theme. + */ static std::string defaultThemePath; -std::string Theme::mThemePath; -Theme *Theme::mInstance = nullptr; -// Set the theme path... static void initDefaultThemePath() { - ResourceManager *resman = ResourceManager::getInstance(); defaultThemePath = branding.getStringValue("guiThemePath"); - if (defaultThemePath.empty() || !resman->isDirectory(defaultThemePath)) + if (defaultThemePath.empty() || !FS::isDirectory(defaultThemePath)) defaultThemePath = "graphics/gui/"; } -Skin::Skin(ImageRect skin, Image *close, Image *stickyUp, Image *stickyDown): - mBorder(skin), - mCloseImage(close), - mStickyImageUp(stickyUp), - mStickyImageDown(stickyDown) +static std::optional<std::string> findThemePath(const std::string &theme) +{ + if (theme.empty()) + return {}; + + std::string themePath = defaultThemePath; + themePath += theme; + + if (FS::isDirectory(themePath)) + return themePath; + + return {}; +} + + +WidgetState::WidgetState(const gcn::Widget *widget) + : width(widget->getWidth()) + , height(widget->getHeight()) +{ + // x and y are not set based on the widget because the rendering usually + // happens in local coordinates. + + if (!widget->isEnabled()) + flags |= STATE_DISABLED; + if (widget->isFocused()) + flags |= STATE_FOCUSED; +} + +WidgetState::WidgetState(const gcn::Rectangle &dim, uint8_t flags) + : x(dim.x) + , y(dim.y) + , width(dim.width) + , height(dim.height) + , flags(flags) {} + Skin::~Skin() { - // Clean up static resources - for (auto img : mBorder.grid) - delete img; + // Raw Image* need explicit deletion + for (auto &state : mStates) + for (auto &part : state.parts) + if (auto image = std::get_if<Image *>(&part.data)) + delete *image; +} - mCloseImage->decRef(); - delete mStickyImageUp; - delete mStickyImageDown; +void Skin::addState(SkinState state) +{ + mStates.emplace_back(std::move(state)); } -void Skin::updateAlpha(float minimumOpacityAllowed) +void Skin::draw(Graphics *graphics, const WidgetState &state) const { - const float alpha = std::max(minimumOpacityAllowed, - config.guiAlpha); + // Only draw the first matching state + auto skinState = getState(state.flags); + if (!skinState) + return; - mBorder.setAlpha(alpha); + for (const auto &part : skinState->parts) + { + std::visit([&](const auto &data) { + using T = std::decay_t<decltype(data)>; - mCloseImage->setAlpha(alpha); - mStickyImageUp->setAlpha(alpha); - mStickyImageDown->setAlpha(alpha); + if constexpr (std::is_same_v<T, ImageRect>) + { + graphics->drawImageRect(state.x + part.offsetX, + state.y + part.offsetY, + state.width, + state.height, + data); + } + else if constexpr (std::is_same_v<T, Image*>) + { + graphics->drawImage(data, state.x + part.offsetX, state.y + part.offsetY); + } + else if constexpr (std::is_same_v<T, ColoredRectangle>) + { + graphics->setColor(data.color); + graphics->fillRectangle(gcn::Rectangle(state.x + part.offsetX, + state.y + part.offsetY, + state.width, + state.height)); + graphics->setColor(gcn::Color(255, 255, 255)); + } + }, part.data); + } +} + +const SkinState *Skin::getState(uint8_t flags) const +{ + for (const auto &skinState : mStates) + if (skinState.stateFlags == (skinState.setFlags & flags)) + return &skinState; + + return nullptr; } int Skin::getMinWidth() const { - return mBorder.grid[ImageRect::UPPER_LEFT]->getWidth() + - mBorder.grid[ImageRect::UPPER_RIGHT]->getWidth(); + int minWidth = 0; + + for (const auto &state : mStates) + { + for (const auto &part : state.parts) + { + if (auto imageRect = std::get_if<ImageRect>(&part.data)) + minWidth = std::max(minWidth, imageRect->minWidth()); + else if (auto img = std::get_if<Image *>(&part.data)) + minWidth = std::max(minWidth, (*img)->getWidth()); + } + } + + return minWidth; } int Skin::getMinHeight() const { - return mBorder.grid[ImageRect::UPPER_LEFT]->getHeight() + - mBorder.grid[ImageRect::LOWER_LEFT]->getHeight(); + int minHeight = 0; + + for (const auto &state : mStates) + { + for (const auto &part : state.parts) + { + if (auto imageRect = std::get_if<ImageRect>(&part.data)) + minHeight = std::max(minHeight, imageRect->minHeight()); + else if (auto img = std::get_if<Image *>(&part.data)) + minHeight = std::max(minHeight, (*img)->getHeight()); + } + } + + return minHeight; } -Theme::Theme(): - Palette(THEME_COLORS_END), - mMinimumOpacity(-1.0f), - mProgressColors(THEME_PROG_END) +void Skin::updateAlpha(float alpha) { - initDefaultThemePath(); + for (auto &state : mStates) + { + for (auto &part : state.parts) + { + if (auto rect = std::get_if<ImageRect>(&part.data)) + rect->setAlpha(alpha); + else if (auto img = std::get_if<Image *>(&part.data)) + (*img)->setAlpha(alpha); + } + } +} + +Theme::Theme(const std::string &path) + : Palette(THEME_COLORS_END) + , mThemePath(path) + , mProgressColors(THEME_PROG_END) +{ listen(Event::ConfigChannel); - loadColors(); + readTheme("theme.xml"); mColors[HIGHLIGHT].ch = 'H'; mColors[CHAT].ch = 'C'; @@ -118,72 +224,140 @@ Theme::Theme(): mColors[HYPERLINK].ch = '<'; } -Theme::~Theme() +Theme::~Theme() = default; + +std::string Theme::prepareThemePath() { - delete_all(mSkins); - delete_all(mProgressColors); + initDefaultThemePath(); + + // Try theme from settings + auto themePath = findThemePath(config.theme); + + // Try theme from branding + if (!themePath) + themePath = findThemePath(branding.getStringValue("theme")); + + return themePath.value_or(defaultThemePath); } -Theme *Theme::instance() +std::string Theme::resolvePath(const std::string &path) const { - if (!mInstance) - mInstance = new Theme; + // Need to strip off any dye info for the existence tests + int pos = path.find('|'); + std::string file; + if (pos > 0) + file = path.substr(0, pos); + else + file = path; + + // Try the theme + file = mThemePath + "/" + file; + if (FS::exists(file)) + return mThemePath + "/" + path; - return mInstance; + // Backup + return defaultThemePath + "/" + path; } -void Theme::deleteInstance() +ResourceRef<Image> Theme::getImage(const std::string &path) const { - delete mInstance; - mInstance = nullptr; + return ResourceManager::getInstance()->getImage(resolvePath(path)); } -gcn::Color Theme::getProgressColor(int type, float progress) +ResourceRef<Image> Theme::getImageFromTheme(const std::string &path) { - DyePalette *dye = mInstance->mProgressColors[type]; + return gui->getTheme()->getImage(path); +} +const gcn::Color &Theme::getThemeColor(int type, int alpha) +{ + return gui->getTheme()->getColor(type, alpha); +} + +const gcn::Color &Theme::getThemeColor(char c, bool &valid) +{ + return gui->getTheme()->getColor(c, valid); +} + +gcn::Color Theme::getProgressColor(int type, float progress) +{ int color[3] = {0, 0, 0}; - dye->getColor(progress, color); + + if (const auto &dye = gui->getTheme()->mProgressColors[type]) + dye->getColor(progress, color); return gcn::Color(color[0], color[1], color[2]); } -Skin *Theme::load(const std::string &filename, const std::string &defaultPath) +void Theme::drawSkin(Graphics *graphics, SkinType type, const WidgetState &state) const { - // Check if this skin was already loaded - auto skinIterator = mSkins.find(filename); - if (skinIterator != mSkins.end()) - { - Skin *skin = skinIterator->second; - skin->instances++; - return skin; - } + getSkin(type).draw(graphics, state); +} - Skin *skin = readSkin(filename); +void Theme::drawProgressBar(Graphics *graphics, const gcn::Rectangle &area, + const gcn::Color &color, float progress, + const std::string &text) const +{ + gcn::Font *oldFont = graphics->getFont(); + gcn::Color oldColor = graphics->getColor(); - if (!skin) - { - // Try falling back on the defaultPath if this makes sense - if (filename != defaultPath) - { - logger->log("Error loading skin '%s', falling back on default.", - filename.c_str()); + WidgetState widgetState; + widgetState.x = area.x; + widgetState.y = area.y; + widgetState.width = area.width; + widgetState.height = area.height; - skin = readSkin(defaultPath); - } + auto &skin = getSkin(SkinType::ProgressBar); + skin.draw(graphics, widgetState); + + // The bar + if (progress > 0) + { + graphics->setColor(color); + graphics->fillRectangle(gcn::Rectangle(area.x + 4, + area.y + 4, + (int) (progress * (area.width - 8)), + area.height - 8)); + } - if (!skin) + // The label + if (!text.empty()) + { + if (auto skinState = skin.getState(widgetState.flags)) { - logger->error(strprintf("Error: Loading default skin '%s' failed. " - "Make sure the skin file is valid.", - defaultPath.c_str())); + auto font = skinState->textFormat.bold ? boldFont : gui->getFont(); + const int textX = area.x + area.width / 2; + const int textY = area.y + (area.height - font->getHeight()) / 2; + + TextRenderer::renderText(graphics, + text, + textX, + textY, + gcn::Graphics::CENTER, + font, + skinState->textFormat); } } - // Add the skin to the loaded skins - mSkins[filename] = skin; + graphics->setFont(oldFont); + graphics->setColor(oldColor); +} + +const Skin &Theme::getSkin(SkinType skinType) const +{ + static Skin emptySkin; + const auto it = mSkins.find(skinType); + return it != mSkins.end() ? it->second : emptySkin; +} + +int Theme::getMinWidth(SkinType skinType) const +{ + return getSkin(skinType).getMinWidth(); +} - return skin; +int Theme::getMinHeight(SkinType skinType) const +{ + return getSkin(skinType).getMinHeight(); } void Theme::setMinimumOpacity(float minimumOpacity) @@ -197,8 +371,14 @@ void Theme::setMinimumOpacity(float minimumOpacity) void Theme::updateAlpha() { + const float alpha = std::max(config.guiAlpha, mMinimumOpacity); + if (mAlpha == alpha) + return; + + mAlpha = alpha; + for (auto &skin : mSkins) - skin.second->updateAlpha(mMinimumOpacity); + skin.second.updateAlpha(mAlpha); } void Theme::event(Event::Channel channel, const Event &event) @@ -211,189 +391,267 @@ void Theme::event(Event::Channel channel, const Event &event) } } -Skin *Theme::readSkin(const std::string &filename) +static bool check(bool value, const char *msg, ...) { - if (filename.empty()) - return nullptr; + if (!value) + { + va_list args; + va_start(args, msg); + logger->log(msg, args); + va_end(args); + } + return !value; +} - logger->log("Loading skin '%s'.", filename.c_str()); +bool Theme::readTheme(const std::string &filename) +{ + logger->log("Loading theme '%s'.", filename.c_str()); - XML::Document doc(resolveThemePath(filename)); + XML::Document doc(resolvePath(filename)); XML::Node rootNode = doc.rootNode(); - if (!rootNode || rootNode.name() != "skinset") - return nullptr; - - const std::string skinSetImage = rootNode.getProperty("image", ""); + if (!rootNode || rootNode.name() != "theme") + return false; - if (skinSetImage.empty()) + for (auto childNode : rootNode.children()) { - logger->log("Theme::readSkin(): Skinset does not define an image!"); - return nullptr; + if (childNode.name() == "skin") + readSkinNode(childNode); + else if (childNode.name() == "color") + readColorNode(childNode); + else if (childNode.name() == "progressbar") + readProgressBarNode(childNode); } - logger->log("Theme::load(): <skinset> defines '%s' as a skin image.", - skinSetImage.c_str()); + logger->log("Finished loading theme."); - Image *dBorders = Theme::getImageFromTheme(skinSetImage); - ImageRect border; - memset(&border, 0, sizeof(ImageRect)); + for (auto &[_, skin] : mSkins) + skin.updateAlpha(mAlpha); - // iterate <widget>'s - for (auto widgetNode : rootNode.children()) - { - if (widgetNode.name() != "widget") - continue; + return true; +} - const std::string widgetType = - widgetNode.getProperty("type", "unknown"); - if (widgetType == "Window") - { - // Iterate through <part>'s - // LEEOR / TODO: - // We need to make provisions to load in a CloseButton image. For - // now it can just be hard-coded. - for (auto partNode : widgetNode.children()) - { - if (partNode.name() != "part") - continue; - - const std::string partType = - partNode.getProperty("type", "unknown"); - // TOP ROW - const int xPos = partNode.getProperty("xpos", 0); - const int yPos = partNode.getProperty("ypos", 0); - const int width = partNode.getProperty("width", 1); - const int height = partNode.getProperty("height", 1); - - if (partType == "top-left-corner") - border.grid[0] = dBorders->getSubImage(xPos, yPos, width, height); - else if (partType == "top-edge") - border.grid[1] = dBorders->getSubImage(xPos, yPos, width, height); - else if (partType == "top-right-corner") - border.grid[2] = dBorders->getSubImage(xPos, yPos, width, height); - - // MIDDLE ROW - else if (partType == "left-edge") - border.grid[3] = dBorders->getSubImage(xPos, yPos, width, height); - else if (partType == "bg-quad") - border.grid[4] = dBorders->getSubImage(xPos, yPos, width, height); - else if (partType == "right-edge") - border.grid[5] = dBorders->getSubImage(xPos, yPos, width, height); - - // BOTTOM ROW - else if (partType == "bottom-left-corner") - border.grid[6] = dBorders->getSubImage(xPos, yPos, width, height); - else if (partType == "bottom-edge") - border.grid[7] = dBorders->getSubImage(xPos, yPos, width, height); - else if (partType == "bottom-right-corner") - border.grid[8] = dBorders->getSubImage(xPos, yPos, width, height); - - else - logger->log("Theme::readSkin(): Unknown part type '%s'", - partType.c_str()); - } - } - else - { - logger->log("Theme::readSkin(): Unknown widget type '%s'", - widgetType.c_str()); - } - } +static std::optional<SkinType> readSkinType(std::string_view type) +{ + if (type == "Window") return SkinType::Window; + if (type == "Popup") return SkinType::Popup; + if (type == "SpeechBubble") return SkinType::SpeechBubble; + if (type == "Button") return SkinType::Button; + if (type == "ButtonUp") return SkinType::ButtonUp; + if (type == "ButtonDown") return SkinType::ButtonDown; + if (type == "ButtonLeft") return SkinType::ButtonLeft; + if (type == "ButtonRight") return SkinType::ButtonRight; + if (type == "ButtonClose") return SkinType::ButtonClose; + if (type == "ButtonSticky") return SkinType::ButtonSticky; + if (type == "CheckBox") return SkinType::CheckBox; + if (type == "RadioButton") return SkinType::RadioButton; + if (type == "TextField") return SkinType::TextField; + if (type == "Tab") return SkinType::Tab; + if (type == "ScrollArea") return SkinType::ScrollArea; + if (type == "ScrollAreaHBar") return SkinType::ScrollAreaHBar; + if (type == "ScrollAreaHMarker") return SkinType::ScrollAreaHMarker; + if (type == "ScrollAreaVBar") return SkinType::ScrollAreaVBar; + if (type == "ScrollAreaVMarker") return SkinType::ScrollAreaVMarker; + if (type == "DropDownFrame") return SkinType::DropDownFrame; + if (type == "DropDownButton") return SkinType::DropDownButton; + if (type == "ProgressBar") return SkinType::ProgressBar; + if (type == "Slider") return SkinType::Slider; + if (type == "SliderHandle") return SkinType::SliderHandle; + if (type == "ResizeGrip") return SkinType::ResizeGrip; + if (type == "ShortcutBox") return SkinType::ShortcutBox; + return {}; +} - dBorders->decRef(); +void Theme::readSkinNode(XML::Node node) +{ + const auto skinTypeStr = node.getProperty("type", std::string()); + const auto skinType = readSkinType(skinTypeStr); + if (check(skinType.has_value(), "Theme: Unknown skin type '%s'", skinTypeStr.c_str())) + return; - logger->log("Finished loading skin."); + auto &skin = mSkins[*skinType]; - // Hard-coded for now until we update the above code to look for window buttons - Image *closeImage = Theme::getImageFromTheme("close_button.png"); - Image *sticky = Theme::getImageFromTheme("sticky_button.png"); - Image *stickyImageUp = sticky->getSubImage(0, 0, 15, 15); - Image *stickyImageDown = sticky->getSubImage(15, 0, 15, 15); - sticky->decRef(); + node.attribute("frameSize", skin.frameSize); + node.attribute("padding", skin.padding); + node.attribute("spacing", skin.spacing); + node.attribute("titleBarHeight", skin.titleBarHeight); + node.attribute("titleOffsetX", skin.titleOffsetX); + node.attribute("titleOffsetY", skin.titleOffsetY); - Skin *skin = new Skin(border, closeImage, stickyImageUp, stickyImageDown); - skin->updateAlpha(mMinimumOpacity); - return skin; + for (auto childNode : node.children()) + if (childNode.name() == "state") + readSkinStateNode(childNode, skin); } -bool Theme::tryThemePath(std::string themePath) +void Theme::readSkinStateNode(XML::Node node, Skin &skin) const { - if (!themePath.empty()) + SkinState state; + + auto readFlag = [&] (const char *name, int flag) { - themePath = defaultThemePath + themePath; + std::optional<bool> value; + node.attribute(name, value); - if (PHYSFS_exists(themePath.c_str())) + if (value.has_value()) { - mThemePath = themePath; - return true; + state.setFlags |= flag; + state.stateFlags |= *value ? flag : 0; } + }; + + readFlag("selected", STATE_SELECTED); + readFlag("disabled", STATE_DISABLED); + readFlag("hovered", STATE_HOVERED); + readFlag("focused", STATE_FOCUSED); + + for (auto childNode : node.children()) + { + if (childNode.name() == "img") + readSkinStateImgNode(childNode, state); + else if (childNode.name() == "rect") + readSkinStateRectNode(childNode, state); + else if (childNode.name() == "text") + readSkinStateTextNode(childNode, state); } - return false; + skin.addState(std::move(state)); } -void Theme::prepareThemePath() +void Theme::readSkinStateTextNode(XML::Node node, SkinState &state) const { - // Ensure the Theme object has been created - instance(); - - // Try theme from settings - if (!tryThemePath(config.theme)) - // Try theme from branding - if (!tryThemePath(branding.getStringValue("theme"))) - // Use default - mThemePath = defaultThemePath; + auto &textFormat = state.textFormat; + node.attribute("bold", textFormat.bold); + node.attribute("color", textFormat.color); + node.attribute("outlineColor", textFormat.outlineColor); + node.attribute("shadowColor", textFormat.shadowColor); +} - instance()->loadColors(mThemePath); +template<> +inline void fromString(const char *str, FillMode &value) +{ + if (strcmp(str, "repeat") == 0) + value = FillMode::Repeat; + else if (strcmp(str, "stretch") == 0) + value = FillMode::Stretch; } -std::string Theme::resolveThemePath(const std::string &path) +void Theme::readSkinStateImgNode(XML::Node node, SkinState &state) const { - // Need to strip off any dye info for the existence tests - int pos = path.find('|'); - std::string file; - if (pos > 0) - file = path.substr(0, pos); - else - file = path; + const std::string src = node.getProperty("src", std::string()); + if (check(!src.empty(), "Theme: 'img' element has empty 'src' attribute!")) + return; - // Might be a valid path already - if (PHYSFS_exists(file.c_str())) - return path; + auto image = getImage(src); + if (check(image, "Theme: Failed to load image '%s'!", src.c_str())) + return; - // Try the theme - file = getThemePath() + "/" + file; - if (PHYSFS_exists(file.c_str())) - return getThemePath() + "/" + path; + int left = 0; + int right = 0; + int top = 0; + int bottom = 0; + int x = 0; + int y = 0; + int width = image->getWidth(); + int height = image->getHeight(); + + node.attribute("left", left); + node.attribute("right", right); + node.attribute("top", top); + node.attribute("bottom", bottom); + node.attribute("x", x); + node.attribute("y", y); + node.attribute("width", width); + node.attribute("height", height); + + if (check(left >= 0 || right >= 0 || top >= 0 || bottom >= 0, "Theme: Invalid border value!")) + return; + if (check(x >= 0 || y >= 0, "Theme: Invalid position value!")) + return; + if (check(width >= 0 || height >= 0, "Theme: Invalid size value!")) + return; + if (check(x + width <= image->getWidth() || y + height <= image->getHeight(), "Theme: Image size out of bounds!")) + return; - // Backup - return std::string(defaultThemePath) + "/" + path; + auto &part = state.parts.emplace_back(); + + node.attribute("offsetX", part.offsetX); + node.attribute("offsetY", part.offsetY); + + if (left + right + top + bottom > 0) + { + auto &border = part.data.emplace<ImageRect>(); + + node.attribute("fill", border.fillMode); + + const int gridx[4] = {x, x + left, x + width - right, x + width}; + const int gridy[4] = {y, y + top, y + height - bottom, y + height}; + unsigned a = 0; + + for (unsigned y = 0; y < 3; y++) + { + for (unsigned x = 0; x < 3; x++) + { + border.grid[a] = image->getSubImage(gridx[x], + gridy[y], + gridx[x + 1] - gridx[x], + gridy[y + 1] - gridy[y]); + a++; + } + } + } + else + { + part.data = image->getSubImage(x, y, width, height); + } } -Image *Theme::getImageFromTheme(const std::string &path) +template<> +inline void fromString(const char *str, gcn::Color &value) { - ResourceManager *resman = ResourceManager::getInstance(); - return resman->getImage(resolveThemePath(path)); + if (strlen(str) < 7 || str[0] != '#') + { + error: + logger->log("Error, invalid theme color palette: %s", str); + value = Palette::BLACK; + return; + } + + int v = 0; + for (int i = 1; i < 7; ++i) + { + char c = str[i]; + int n; + + if ('0' <= c && c <= '9') + n = c - '0'; + else if ('A' <= c && c <= 'F') + n = c - 'A' + 10; + else if ('a' <= c && c <= 'f') + n = c - 'a' + 10; + else + goto error; + + v = (v << 4) | n; + } + + value = gcn::Color(v); } -ImageSet *Theme::getImageSetFromTheme(const std::string &path, - int w, int h) +void Theme::readSkinStateRectNode(XML::Node node, SkinState &state) const { - ResourceManager *resman = ResourceManager::getInstance(); - return resman->getImageSet(resolveThemePath(path), w, h); + auto &part = state.parts.emplace_back(); + auto &rect = part.data.emplace<ColoredRectangle>(); + + node.attribute("color", rect.color); + node.attribute("alpha", rect.color.a); } static int readColorType(const std::string &type) { - static std::string colors[] = { + static constexpr const char *colors[Theme::THEME_COLORS_END] = { "TEXT", "SHADOW", "OUTLINE", - "PROGRESS_BAR", - "BUTTON", - "BUTTON_DISABLED", - "TAB", "PARTY_CHAT_TAB", "PARTY_SOCIAL_TAB", "BACKGROUND", @@ -402,6 +660,7 @@ static int readColorType(const std::string &type) "SHOP_WARNING", "ITEM_EQUIPPED", "CHAT", + "BUBBLE_TEXT", "GM", "PLAYER", "WHISPER", @@ -432,51 +691,15 @@ static int readColorType(const std::string &type) return -1; for (int i = 0; i < Theme::THEME_COLORS_END; i++) - { - if (compareStrI(type, colors[i]) == 0) - { + if (type == colors[i]) return i; - } - } return -1; } -static gcn::Color readColor(const std::string &description) -{ - int size = description.length(); - if (size < 7 || description[0] != '#') - { - error: - logger->log("Error, invalid theme color palette: %s", - description.c_str()); - return Palette::BLACK; - } - - int v = 0; - for (int i = 1; i < 7; ++i) - { - char c = description[i]; - int n; - - if ('0' <= c && c <= '9') - n = c - '0'; - else if ('A' <= c && c <= 'F') - n = c - 'A' + 10; - else if ('a' <= c && c <= 'f') - n = c - 'a' + 10; - else - goto error; - - v = (v << 4) | n; - } - - return gcn::Color(v); -} - static Palette::GradientType readColorGradient(const std::string &grad) { - static std::string grads[] = { + static constexpr const char *grads[] = { "STATIC", "PULSE", "SPECTRUM", @@ -487,17 +710,30 @@ static Palette::GradientType readColorGradient(const std::string &grad) return Palette::STATIC; for (int i = 0; i < 4; i++) - { - if (compareStrI(grad, grads[i])) - return (Palette::GradientType) i; - } + if (grad == grads[i]) + return static_cast<Palette::GradientType>(i); return Palette::STATIC; } +void Theme::readColorNode(XML::Node node) +{ + const int type = readColorType(node.getProperty("id", std::string())); + if (check(type > 0, "Theme: 'color' element has invalid or no 'type' attribute!")) + return; + + gcn::Color color; + if (check(node.attribute("color", color), "Theme: 'color' element missing 'color' attribute!")) + return; + + const GradientType grad = readColorGradient(node.getProperty("effect", std::string())); + + mColors[type].set(type, color, grad, 10); +} + static int readProgressType(const std::string &type) { - static std::string colors[] = { + static constexpr const char *colors[Theme::THEME_PROG_END] = { "DEFAULT", "HP", "MP", @@ -512,62 +748,17 @@ static int readProgressType(const std::string &type) return -1; for (int i = 0; i < Theme::THEME_PROG_END; i++) - { - if (compareStrI(type, colors[i]) == 0) + if (type == colors[i]) return i; - } return -1; } -void Theme::loadColors(std::string file) +void Theme::readProgressBarNode(XML::Node node) { - if (file == defaultThemePath) - return; // No need to reload - - if (file.empty()) - file = defaultThemePath; - - file += "/colors.xml"; - - XML::Document doc(file); - XML::Node root = doc.rootNode(); - - if (!root || root.name() != "colors") - { - logger->log("Error loading colors file: %s", file.c_str()); + const int type = readProgressType(node.getProperty("id", std::string())); + if (type < 0) // invalid or no type given return; - } - - int type; - std::string temp; - gcn::Color color; - GradientType grad; - - for (auto node : root.children()) - { - if (node.name() == "color") - { - type = readColorType(node.getProperty("id", "")); - if (type < 0) // invalid or no type given - continue; - temp = node.getProperty("color", ""); - if (temp.empty()) // no color set, so move on - continue; - - color = readColor(temp); - grad = readColorGradient(node.getProperty("effect", "")); - - mColors[type].set(type, color, grad, 10); - } - else if (node.name() == "progressbar") - { - type = readProgressType(node.getProperty("id", "")); - if (type < 0) // invalid or no type given - continue; - - mProgressColors[type] = new DyePalette(node.getProperty( "color", "")); - } - } + mProgressColors[type] = std::make_unique<DyePalette>(node.getProperty("color", std::string())); } diff --git a/src/resources/theme.h b/src/resources/theme.h index 7edae416..6d71b067 100644 --- a/src/resources/theme.h +++ b/src/resources/theme.h @@ -21,44 +21,119 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SKIN_H -#define SKIN_H +#pragma once #include "graphics.h" #include "eventlistener.h" #include "gui/palette.h" +#include "resources/image.h" +#include "utils/xml.h" #include <map> +#include <memory> #include <string> +#include <variant> + +namespace gcn { +class Widget; +} class DyePalette; class Image; class ImageSet; class ProgressBar; +enum class SkinType +{ + Window, + Popup, + SpeechBubble, + Button, + ButtonUp, + ButtonDown, + ButtonLeft, + ButtonRight, + ButtonClose, + ButtonSticky, + CheckBox, + RadioButton, + TextField, + Tab, + ScrollArea, + ScrollAreaHBar, + ScrollAreaHMarker, + ScrollAreaVBar, + ScrollAreaVMarker, + DropDownFrame, + DropDownButton, + ProgressBar, + Slider, + SliderHandle, + ResizeGrip, + ShortcutBox, +}; + +enum StateFlags : uint8_t +{ + STATE_HOVERED = 0x01, + STATE_SELECTED = 0x02, + STATE_DISABLED = 0x04, + STATE_FOCUSED = 0x08, +}; + +struct ColoredRectangle +{ + gcn::Color color; +}; + +struct SkinPart +{ + int offsetX = 0; + int offsetY = 0; + std::variant<ImageRect, Image *, ColoredRectangle> data; +}; + +struct TextFormat +{ + bool bold = false; + gcn::Color color; + std::optional<gcn::Color> outlineColor; + std::optional<gcn::Color> shadowColor; +}; + +struct SkinState +{ + uint8_t stateFlags = 0; + uint8_t setFlags = 0; + TextFormat textFormat; + std::vector<SkinPart> parts; +}; + +struct WidgetState +{ + WidgetState() = default; + explicit WidgetState(const gcn::Widget *widget); + explicit WidgetState(const gcn::Rectangle &dim, uint8_t flags = 0); + + int x = 0; + int y = 0; + int width = 0; + int height = 0; + uint8_t flags = 0; +}; + class Skin { public: - Skin(ImageRect skin, Image *close, Image *stickyUp, Image *stickyDown); - + Skin() = default; ~Skin(); - /** - * Returns the background skin. - */ - const ImageRect &getBorder() const { return mBorder; } + void addState(SkinState state); - /** - * Returns the image used by a close button for this skin. - */ - Image *getCloseImage() const { return mCloseImage; } + void draw(Graphics *graphics, const WidgetState &state) const; - /** - * Returns the image used by a sticky button for this skin. - */ - Image *getStickyImage(bool state) const - { return state ? mStickyImageDown : mStickyImageUp; } + const SkinState *getState(uint8_t flags) const; /** * Returns the minimum width which can be used with this skin. @@ -73,44 +148,38 @@ class Skin /** * Updates the alpha value of the skin */ - void updateAlpha(float minimumOpacityAllowed = 0.0f); + void updateAlpha(float alpha); - int instances = 0; + int frameSize = 0; + int padding = 0; + int spacing = 0; + int titleBarHeight = 0; + int titleOffsetX = 0; + int titleOffsetY = 0; private: - ImageRect mBorder; /**< The window border and background */ - Image *mCloseImage; /**< Close Button Image */ - Image *mStickyImageUp; /**< Sticky Button Image */ - Image *mStickyImageDown; /**< Sticky Button Image */ + std::vector<SkinState> mStates; }; class Theme : public Palette, public EventListener { public: - static Theme *instance(); - static void deleteInstance(); + static std::string prepareThemePath(); - static void prepareThemePath(); - static const std::string &getThemePath() { return mThemePath; } + Theme(const std::string &path); + ~Theme() override; /** - * Returns the patch to the given gui resource relative to the theme + * Returns the patch to the given GUI resource relative to the theme * or, if it isn't in the theme, relative to 'graphics/gui'. */ - static std::string resolveThemePath(const std::string &path); - - static Image *getImageFromTheme(const std::string &path); - static ImageSet *getImageSetFromTheme(const std::string &path, - int w, int h); + std::string resolvePath(const std::string &path) const; + static ResourceRef<Image> getImageFromTheme(const std::string &path); enum ThemePalette { TEXT, SHADOW, OUTLINE, - PROGRESS_BAR, - BUTTON, - BUTTON_DISABLED, - TAB, PARTY_CHAT_TAB, PARTY_SOCIAL_TAB, BACKGROUND, @@ -119,6 +188,7 @@ class Theme : public Palette, public EventListener SHOP_WARNING, ITEM_EQUIPPED, CHAT, + BUBBLE_TEXT, GM, PLAYER, WHISPER, @@ -167,34 +237,32 @@ class Theme : public Palette, public EventListener * * @return the requested color */ - static const gcn::Color &getThemeColor(int type, int alpha = 255) - { - return mInstance->getColor(type, alpha); - } - - static const gcn::Color &getThemeColor(char c, bool &valid) - { - return mInstance->getColor(c, valid); - } + static const gcn::Color &getThemeColor(int type, int alpha = 255); + static const gcn::Color &getThemeColor(char c, bool &valid); static gcn::Color getProgressColor(int type, float progress); - /** - * Loads a skin. - */ - Skin *load(const std::string &filename, - const std::string &defaultPath = getThemePath()); + void drawSkin(Graphics *graphics, SkinType type, const WidgetState &state) const; + void drawProgressBar(Graphics *graphics, + const gcn::Rectangle &area, + const gcn::Color &color, + float progress, + const std::string &text = std::string()) const; + + const Skin &getSkin(SkinType skinType) const; + + int getMinWidth(SkinType skinType) const; + int getMinHeight(SkinType skinType) const; /** - * Updates the alpha values of all of the skins. + * Get the current GUI alpha value. */ - void updateAlpha(); + int getGuiAlpha() const { return static_cast<int>(mAlpha * 255.0f); } /** * Get the minimum opacity allowed to skins. */ - float getMinimumOpacity() const - { return mMinimumOpacity; } + float getMinimumOpacity() const { return mMinimumOpacity; } /** * Set the minimum opacity allowed to skins. @@ -205,28 +273,31 @@ class Theme : public Palette, public EventListener void event(Event::Channel channel, const Event &event) override; private: - Theme(); - ~Theme() override; - - Skin *readSkin(const std::string &filename); - - // Map containing all window skins - std::map<std::string, Skin *> mSkins; + /** + * Updates the alpha values of all of the skins and images. + */ + void updateAlpha(); - static std::string mThemePath; - static Theme *mInstance; + ResourceRef<Image> getImage(const std::string &path) const; - static bool tryThemePath(std::string themePath); + bool readTheme(const std::string &filename); + void readSkinNode(XML::Node node); + void readSkinStateNode(XML::Node node, Skin &skin) const; + void readSkinStateTextNode(XML::Node node, SkinState &state) const; + void readSkinStateImgNode(XML::Node node, SkinState &state) const; + void readSkinStateRectNode(XML::Node node, SkinState &state) const; + void readColorNode(XML::Node node); + void readProgressBarNode(XML::Node node); - void loadColors(std::string file = std::string()); + std::string mThemePath; + std::map<SkinType, Skin> mSkins; /** * Tells if the current skins opacity * should not get less than the given value */ - float mMinimumOpacity; + float mMinimumOpacity = 0.0f; + float mAlpha = 1.0; - std::vector<DyePalette *> mProgressColors; + std::vector<std::unique_ptr<DyePalette>> mProgressColors; }; - -#endif diff --git a/src/resources/userpalette.h b/src/resources/userpalette.h index 946a4725..347491f1 100644 --- a/src/resources/userpalette.h +++ b/src/resources/userpalette.h @@ -20,8 +20,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef USER_PALETTE_H -#define USER_PALETTE_H +#pragma once #include "gui/palette.h" @@ -185,5 +184,3 @@ class UserPalette : public Palette, public gcn::ListModel }; extern UserPalette *userPalette; - -#endif // USER_PALETTE_H diff --git a/src/resources/wallpaper.cpp b/src/resources/wallpaper.cpp index e8167b6b..2bdcd656 100644 --- a/src/resources/wallpaper.cpp +++ b/src/resources/wallpaper.cpp @@ -23,7 +23,7 @@ #include "configuration.h" -#include <physfs.h> +#include "utils/filesystem.h" #include <algorithm> #include <cstring> @@ -90,35 +90,24 @@ void Wallpaper::loadWallpapers() initWallpaperPaths(); - char **fileNames = PHYSFS_enumerateFiles(wallpaperPath.c_str()); - - for (char **fileName = fileNames; *fileName; fileName++) + for (auto fileName : FS::enumerateFiles(wallpaperPath)) { - int width; - int height; - // If the backup file is found, we tell it. - if (strncmp(*fileName, wallpaperFile.c_str(), strlen(*fileName)) == 0) + if (wallpaperFile == fileName) haveBackup = true; // If the image format is terminated by: "_<width>x<height>.png" // It is taken as a potential wallpaper. - - // First, get the base filename of the image: - std::string filename = *fileName; - filename = filename.substr(0, filename.rfind("_")); - - // Check that the base filename doesn't have any '%' markers. - if (filename.find("%") == std::string::npos) + if (auto sizeSuffix = strrchr(fileName, '_')) { - // Then, append the width and height search mask. - filename.append("_%dx%d.png"); + int width; + int height; - if (sscanf(*fileName, filename.c_str(), &width, &height) == 2) + if (sscanf(sizeSuffix, "_%dx%d.png", &width, &height) == 2) { WallpaperData wp; wp.filename = wallpaperPath; - wp.filename.append(*fileName); + wp.filename.append(fileName); wp.width = width; wp.height = height; wallpaperData.push_back(wp); @@ -126,8 +115,6 @@ void Wallpaper::loadWallpapers() } } - PHYSFS_freeList(fileNames); - std::sort(wallpaperData.begin(), wallpaperData.end(), wallpaperCompare); } diff --git a/src/resources/wallpaper.h b/src/resources/wallpaper.h index 532dfd38..7e72e2f0 100644 --- a/src/resources/wallpaper.h +++ b/src/resources/wallpaper.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef WALLPAPER_H -#define WALLPAPER_H +#pragma once #include <string> @@ -46,5 +45,3 @@ class Wallpaper */ static std::string getWallpaper(int width, int height); }; - -#endif // WALLPAPER_H diff --git a/src/rotationalparticle.cpp b/src/rotationalparticle.cpp index bcdb9bad..cdd7de61 100644 --- a/src/rotationalparticle.cpp +++ b/src/rotationalparticle.cpp @@ -35,11 +35,7 @@ RotationalParticle::RotationalParticle(Map *map, XML::Node animationNode, mAnimation(animationNode, dyePalettes) {} -RotationalParticle::~RotationalParticle() -{ - // Prevent ImageParticle from decreasing the reference count of the image - mImage = nullptr; -} +RotationalParticle::~RotationalParticle() = default; bool RotationalParticle::update() { diff --git a/src/rotationalparticle.h b/src/rotationalparticle.h index 4ddfa7a9..bc7d1a6d 100644 --- a/src/rotationalparticle.h +++ b/src/rotationalparticle.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef ROTATIONAL_PARTICLE_H -#define ROTATIONAL_PARTICLE_H +#pragma once #include "imageparticle.h" #include "simpleanimation.h" @@ -46,5 +45,3 @@ class RotationalParticle : public ImageParticle private: SimpleAnimation mAnimation; /**< Used animation for this particle */ }; - -#endif diff --git a/src/sdlgraphics.cpp b/src/sdlgraphics.cpp index 37643190..0fec9ab7 100644 --- a/src/sdlgraphics.cpp +++ b/src/sdlgraphics.cpp @@ -28,6 +28,41 @@ #include <guichan/exception.hpp> +#include <cmath> + +class SetColorAlphaMod +{ +public: + SetColorAlphaMod(SDL_Texture *texture, gcn::Color color, bool enabled) + : mTexture(texture) + , mEnabled(texture != nullptr && enabled) + { + if (mEnabled) + { + SDL_GetTextureColorMod(texture, &mOriginal.r, &mOriginal.g, &mOriginal.b); + SDL_GetTextureAlphaMod(texture, &mOriginal.a); + + SDL_SetTextureColorMod(texture, color.r, color.g, color.b); + SDL_SetTextureAlphaMod(texture, color.a * mOriginal.a / 255); + } + } + + ~SetColorAlphaMod() + { + if (mEnabled) + { + SDL_SetTextureAlphaMod(mTexture, mOriginal.a); + SDL_SetTextureColorMod(mTexture, mOriginal.r, mOriginal.g, mOriginal.b); + } + } + +private: + SDL_Texture *mTexture = nullptr; + SDL_Color mOriginal; + const bool mEnabled; +}; + + std::unique_ptr<Graphics> SDLGraphics::create(SDL_Window *window, const VideoSettings &settings) { int rendererFlags = 0; @@ -132,7 +167,8 @@ bool SDLGraphics::drawRescaledImage(const Image *image, dstRect.w = desiredWidth; dstRect.h = desiredHeight; - return !(SDL_RenderCopy(mRenderer, image->mTexture, &srcRect, &dstRect) < 0); + SetColorAlphaMod mod(image->mTexture, mColor, useColor); + return SDL_RenderCopy(mRenderer, image->mTexture, &srcRect, &dstRect) != 0; } #if SDL_VERSION_ATLEAST(2, 0, 10) @@ -162,7 +198,8 @@ bool SDLGraphics::drawRescaledImageF(const Image *image, dstRect.w = desiredWidth; dstRect.h = desiredHeight; - return !(SDL_RenderCopyF(mRenderer, image->mTexture, &srcRect, &dstRect) < 0); + SetColorAlphaMod mod(image->mTexture, mColor, useColor); + return SDL_RenderCopyF(mRenderer, image->mTexture, &srcRect, &dstRect) == 0; } #endif @@ -196,7 +233,8 @@ void SDLGraphics::drawRescaledImagePattern(const Image *image, SDL_Rect dstRect; dstRect.x = dstX; dstRect.y = dstY; dstRect.w = dw; dstRect.h = dh; - srcRect.w = dw; srcRect.h = dh; + srcRect.w = image->mBounds.w * dw / scaledWidth; + srcRect.h = image->mBounds.h * dh / scaledHeight; if (SDL_RenderCopy(mRenderer, image->mTexture, &srcRect, &dstRect)) return; @@ -255,33 +293,21 @@ SDL_Surface *SDLGraphics::getScreenshot() return screenshot; } -bool SDLGraphics::pushClipArea(gcn::Rectangle area) -{ - bool result = Graphics::pushClipArea(area); - updateSDLClipRect(); - return result; -} - -void SDLGraphics::popClipArea() +void SDLGraphics::updateClipRect() { - Graphics::popClipArea(); - updateSDLClipRect(); -} - -void SDLGraphics::updateSDLClipRect() -{ - if (mClipStack.empty()) + if (mClipRects.empty()) { SDL_RenderSetClipRect(mRenderer, nullptr); return; } - const gcn::ClipRectangle &carea = mClipStack.top(); - SDL_Rect rect; - rect.x = carea.x; - rect.y = carea.y; - rect.w = carea.width; - rect.h = carea.height; + const gcn::Rectangle &clipRect = mClipRects.top(); + const SDL_Rect rect = { + clipRect.x, + clipRect.y, + clipRect.width, + clipRect.height + }; SDL_RenderSetClipRect(mRenderer, &rect); } diff --git a/src/sdlgraphics.h b/src/sdlgraphics.h index e2d9b5a5..2b7635a5 100644 --- a/src/sdlgraphics.h +++ b/src/sdlgraphics.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SDLGRAPHICS_H -#define SDLGRAPHICS_H +#pragma once #include "graphics.h" @@ -70,10 +69,6 @@ public: SDL_Surface *getScreenshot() override; - bool pushClipArea(gcn::Rectangle area) override; - - void popClipArea() override; - void drawPoint(int x, int y) override; void drawLine(int x1, int y1, int x2, int y2) override; @@ -82,10 +77,9 @@ public: void fillRectangle(const gcn::Rectangle &rectangle) override; -private: - void updateSDLClipRect(); +protected: + void updateClipRect() override; +private: SDL_Renderer *mRenderer = nullptr; }; - -#endif // SDLGRAPHICS_H diff --git a/src/shopitem.h b/src/shopitem.h index dc1188e0..2bb5cb55 100644 --- a/src/shopitem.h +++ b/src/shopitem.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SHOPITEM_H -#define SHOPITEM_H +#pragma once #include "item.h" @@ -117,5 +116,3 @@ class ShopItem : public Item }; std::stack<DuplicateItem> mDuplicates; /** <-- Stores duplicates */ }; - -#endif diff --git a/src/simpleanimation.cpp b/src/simpleanimation.cpp index 506714d2..1ef22d84 100644 --- a/src/simpleanimation.cpp +++ b/src/simpleanimation.cpp @@ -21,28 +21,32 @@ #include "simpleanimation.h" -#include "game.h" #include "graphics.h" -#include "log.h" #include "resources/animation.h" -#include "resources/dye.h" #include "resources/image.h" #include "resources/imageset.h" #include "resources/resourcemanager.h" -SimpleAnimation::SimpleAnimation(Animation animation): - mAnimation(std::move(animation)), - mCurrentFrame(mAnimation.getFrame(0)), - mInitialized(true) +SimpleAnimation::SimpleAnimation(Animation animation) + : mAnimation(std::move(animation)) + , mInitialized(true) { + if (mAnimation.getLength() > 0) + mCurrentFrame = mAnimation.getFrame(0); } SimpleAnimation::SimpleAnimation(XML::Node animationNode, const std::string &dyePalettes) { - initializeAnimation(animationNode, dyePalettes); - mCurrentFrame = mAnimation.getFrame(0); + if (animationNode) + { + mAnimation = Animation::fromXML(animationNode, dyePalettes); + mInitialized = true; + } + + if (mAnimation.getLength() > 0) + mCurrentFrame = mAnimation.getFrame(0); } bool SimpleAnimation::draw(Graphics *graphics, int posX, int posY) const @@ -90,103 +94,9 @@ void SimpleAnimation::update(int dt) } } -int SimpleAnimation::getLength() const -{ - return mAnimation.getLength(); -} - Image *SimpleAnimation::getCurrentImage() const { if (mCurrentFrame) return mCurrentFrame->image; return nullptr; } - -void SimpleAnimation::initializeAnimation(XML::Node animationNode, - const std::string& dyePalettes) -{ - if (!animationNode) - return; - - std::string imagePath = animationNode.getProperty( "imageset", ""); - - // Instanciate the dye coloration. - if (!imagePath.empty() && !dyePalettes.empty()) - Dye::instantiate(imagePath, dyePalettes); - - ImageSet *imageset = ResourceManager::getInstance()->getImageSet( - animationNode.getProperty("imageset", ""), - animationNode.getProperty("width", 0), - animationNode.getProperty("height", 0) - ); - - if (!imageset) - return; - - // Get animation frames - for (auto frameNode : animationNode.children()) - { - int delay = frameNode.getProperty("delay", 0); - int offsetX = frameNode.getProperty("offsetX", 0); - int offsetY = frameNode.getProperty("offsetY", 0); - Game *game = Game::instance(); - if (game) - { - offsetX -= imageset->getWidth() / 2 - - game->getCurrentTileWidth() / 2; - offsetY -= imageset->getHeight() - game->getCurrentTileHeight(); - } - - if (frameNode.name() == "frame") - { - int index = frameNode.getProperty("index", -1); - - if (index < 0) - { - logger->log("No valid value for 'index'"); - continue; - } - - Image *img = imageset->get(index); - - if (!img) - { - logger->log("No image at index %d", index); - continue; - } - - mAnimation.addFrame(img, delay, offsetX, offsetY); - } - else if (frameNode.name() == "sequence") - { - int start = frameNode.getProperty("start", -1); - int end = frameNode.getProperty("end", -1); - - if (start < 0 || end < 0) - { - logger->log("No valid value for 'start' or 'end'"); - continue; - } - - while (end >= start) - { - Image *img = imageset->get(start); - - if (!img) - { - logger->log("No image at index %d", start); - continue; - } - - mAnimation.addFrame(img, delay, offsetX, offsetY); - start++; - } - } - else if (frameNode.name() == "end") - { - mAnimation.addTerminator(); - } - } - - mInitialized = true; -} diff --git a/src/simpleanimation.h b/src/simpleanimation.h index 33168fd9..299b243f 100644 --- a/src/simpleanimation.h +++ b/src/simpleanimation.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SIMPLEANIMAION_H -#define SIMPLEANIMAION_H +#pragma once #include "resources/animation.h" @@ -29,7 +28,7 @@ class Graphics; /** - * This class is a leightweight alternative to the AnimatedSprite class. + * This class is a leightweight alternative to the Sprite class. * It hosts a looping animation without actions and directions. */ class SimpleAnimation final @@ -49,7 +48,7 @@ class SimpleAnimation final void setFrame(int frame); - int getLength() const; + int getLength() const { return mAnimation.getLength(); } void update(int dt); @@ -63,9 +62,6 @@ class SimpleAnimation final Image *getCurrentImage() const; private: - void initializeAnimation(XML::Node animationNode, - const std::string& dyePalettes = std::string()); - /** The hosted animation. */ Animation mAnimation; @@ -81,5 +77,3 @@ class SimpleAnimation final /** Tell whether the animation is ready */ bool mInitialized = false; }; - -#endif diff --git a/src/sound.cpp b/src/sound.cpp index 60cc2c10..c97951a3 100644 --- a/src/sound.cpp +++ b/src/sound.cpp @@ -30,42 +30,32 @@ #include "resources/resourcemanager.h" #include "resources/soundeffect.h" -enum { - CHANNEL_NOTIFICATIONS = 0 -}; - /** - * This will be set to true, when a music can be freed after a fade out - * Currently used by fadeOutCallBack() + * This will be set to true when the music that was playing can be freed. */ -static bool sFadingOutEnded = false; +static bool sMusicFinished; +static bool sChannelFinished[Sound::CHANNEL_COUNT]; -/** - * Callback used at end of fadeout. - * It is called by Mix_MusicFadeFinished(). - */ -static void fadeOutCallBack() +static void musicFinishedCallBack() { - sFadingOutEnded = true; + sMusicFinished = true; } -Sound::Sound(): - mInstalled(false), - mSfxVolume(100), - mNotificationsVolume(100), - mMusicVolume(60), - mMusic(nullptr) +static void channelFinishedCallBack(int channel) { - // This set up our callback function used to - // handle fade outs endings. - sFadingOutEnded = false; - Mix_HookMusicFinished(fadeOutCallBack); + sChannelFinished[channel] = true; +} + +Sound::Sound() +{ + Mix_HookMusicFinished(musicFinishedCallBack); + Mix_ChannelFinished(channelFinishedCallBack); } Sound::~Sound() { - // Unlink the callback function. Mix_HookMusicFinished(nullptr); + Mix_ChannelFinished(nullptr); } void Sound::init() @@ -93,8 +83,8 @@ void Sound::init() return; } - Mix_AllocateChannels(16); - Mix_ReserveChannels(1); // reserve one channel for notification sounds + Mix_AllocateChannels(CHANNEL_COUNT); + Mix_ReserveChannels(CHANNEL_RESERVED_COUNT); Mix_VolumeMusic(mMusicVolume); Mix_Volume(-1, mSfxVolume); Mix_Volume(CHANNEL_NOTIFICATIONS, mNotificationsVolume); @@ -173,25 +163,9 @@ void Sound::setNotificationsVolume(int volume) Mix_Volume(CHANNEL_NOTIFICATIONS, mNotificationsVolume); } -static Music *loadMusic(const std::string &fileName) -{ - ResourceManager *resman = ResourceManager::getInstance(); - return resman->getMusic(paths.getStringValue("music") + fileName); -} - - void Sound::playMusic(const std::string &fileName) { - mCurrentMusicFile = fileName; - - if (!mInstalled) - return; - - haltMusic(); - - mMusic = loadMusic(fileName); - if (mMusic) - mMusic->play(); + fadeInMusic(fileName, 0); } void Sound::stopMusic() @@ -213,7 +187,9 @@ void Sound::fadeInMusic(const std::string &fileName, int ms) haltMusic(); - mMusic = loadMusic(fileName); + ResourceManager *resman = ResourceManager::getInstance(); + mMusic = resman->getMusic(paths.getStringValue("music") + fileName); + if (mMusic) mMusic->play(-1, ms); } @@ -230,12 +206,12 @@ void Sound::fadeOutMusic(int ms) if (mMusic) { Mix_FadeOutMusic(ms); - // Note: The fadeOutCallBack handler will take care about freeing + // Note: The musicFinishedCallBack will take care about freeing // the music file at fade out ending. } else { - sFadingOutEnded = true; + sMusicFinished = true; } } @@ -247,14 +223,10 @@ void Sound::fadeOutAndPlayMusic(const std::string &fileName, int ms) void Sound::logic() { - if (sFadingOutEnded) + if (sMusicFinished) { - if (mMusic) - { - mMusic->decRef(); - mMusic = nullptr; - } - sFadingOutEnded = false; + sMusicFinished = false; + mMusic = nullptr; if (!mNextMusicFile.empty()) { @@ -262,6 +234,15 @@ void Sound::logic() mNextMusicFile.clear(); } } + + for (int i = 0; i < CHANNEL_COUNT; i++) + { + if (sChannelFinished[i]) + { + sChannelFinished[i] = false; + mSounds[i] = nullptr; + } + } } void Sound::playSfx(const std::string &path, int x, int y) @@ -277,7 +258,7 @@ void Sound::playSfx(const std::string &path, int x, int y) ResourceManager *resman = ResourceManager::getInstance(); - if (SoundEffect *sample = resman->getSoundEffect(tmpPath)) + if (ResourceRef<SoundEffect> sound = resman->getSoundEffect(tmpPath)) { logger->log("Sound::playSfx() Playing: %s", path.c_str()); int vol = 120; @@ -293,7 +274,9 @@ void Sound::playSfx(const std::string &path, int x, int y) vol -= std::min(120, dist / 4); } - sample->play(0, vol); + int channel = sound->play(0, vol); + if (channel != -1) + mSounds[channel] = sound; } } @@ -302,9 +285,11 @@ void Sound::playNotification(const std::string &path) const std::string fullPath = paths.getValue("sfx", "sfx/") + path; ResourceManager *resman = ResourceManager::getInstance(); - if (SoundEffect *sample = resman->getSoundEffect(fullPath)) + if (ResourceRef<SoundEffect> sound = resman->getSoundEffect(fullPath)) { - sample->play(0, 128, CHANNEL_NOTIFICATIONS); + int channel = sound->play(0, MIX_MAX_VOLUME, CHANNEL_NOTIFICATIONS); + if (channel != -1) + mSounds[channel] = sound; } } @@ -326,6 +311,5 @@ void Sound::haltMusic() return; Mix_HaltMusic(); - mMusic->decRef(); mMusic = nullptr; } diff --git a/src/sound.h b/src/sound.h index d4d0e8da..98286d76 100644 --- a/src/sound.h +++ b/src/sound.h @@ -19,14 +19,16 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SOUND_H -#define SOUND_H +#pragma once + +#include "resources/resource.h" #include <SDL_mixer.h> #include <string> class Music; +class SoundEffect; /** Sound engine * @@ -108,11 +110,19 @@ class Sound /** * The sound logic. - * Currently used to check whether the music file can be freed after - * a fade out, and whether new music has to be played. + * + * Checks whether the music and sound effects can be freed after they + * finished playing, and whether new music has to be played. */ void logic(); + enum Channel { + CHANNEL_NOTIFICATIONS = 0, + CHANNEL_RESERVED_COUNT, + + CHANNEL_COUNT = 16, + }; + private: /** Logs various info about sound device. */ void info(); @@ -126,16 +136,15 @@ class Sound */ std::string mNextMusicFile; - bool mInstalled; + bool mInstalled = false; - int mSfxVolume; - int mNotificationsVolume; - int mMusicVolume; + int mSfxVolume = 100; + int mNotificationsVolume = 100; + int mMusicVolume = 60; std::string mCurrentMusicFile; - Music *mMusic; + ResourceRef<Music> mMusic; + ResourceRef<SoundEffect> mSounds[CHANNEL_COUNT]; }; extern Sound sound; - -#endif diff --git a/src/animatedsprite.cpp b/src/sprite.cpp index ec7aa1e3..c2e434e7 100644 --- a/src/animatedsprite.cpp +++ b/src/sprite.cpp @@ -19,7 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include "animatedsprite.h" +#include "sprite.h" #include "graphics.h" @@ -30,7 +30,7 @@ #include <cassert> -AnimatedSprite::AnimatedSprite(SpriteDef *sprite): +Sprite::Sprite(SpriteDef *sprite): mSprite(sprite) { assert(mSprite); @@ -39,30 +39,32 @@ AnimatedSprite::AnimatedSprite(SpriteDef *sprite): play(SpriteAction::STAND); } -AnimatedSprite *AnimatedSprite::load(const std::string &filename, int variant) +Sprite *Sprite::load(const std::string &filename, int variant) { ResourceManager *resman = ResourceManager::getInstance(); - SpriteDef *s = resman->getSprite(filename, variant); - if (!s) + auto spriteDef = resman->getSprite(filename, variant); + if (!spriteDef) return nullptr; - auto *as = new AnimatedSprite(s); - s->decRef(); - return as; + return new Sprite(spriteDef); } -AnimatedSprite::~AnimatedSprite() = default; +Sprite::~Sprite() = default; -bool AnimatedSprite::reset() +bool Sprite::reset() { bool ret = mFrameIndex !=0 || mFrameTime != 0; mFrameIndex = 0; mFrameTime = 0; + if (mAnimation) + mFrame = mAnimation->getFrame(0); + else + mFrame = nullptr; return ret; } -bool AnimatedSprite::play(const std::string &spriteAction) +bool Sprite::play(const std::string &spriteAction) { Action *action = mSprite->getAction(spriteAction); if (!action) @@ -74,8 +76,6 @@ bool AnimatedSprite::play(const std::string &spriteAction) if (animation && animation != mAnimation && animation->getLength() > 0) { mAnimation = animation; - mFrame = mAnimation->getFrame(0); - reset(); return true; @@ -84,7 +84,7 @@ bool AnimatedSprite::play(const std::string &spriteAction) return false; } -bool AnimatedSprite::update(int time) +bool Sprite::update(int dt) { if (!mAnimation) return false; @@ -92,7 +92,7 @@ bool AnimatedSprite::update(int time) Animation *animation = mAnimation; Frame *frame = mFrame; - if (!updateCurrentAnimation(time)) + if (!updateCurrentAnimation(dt)) { // Animation finished, reset to default play(SpriteAction::STAND); @@ -102,12 +102,12 @@ bool AnimatedSprite::update(int time) return animation != mAnimation || frame != mFrame; } -bool AnimatedSprite::updateCurrentAnimation(int time) +bool Sprite::updateCurrentAnimation(int dt) { if (!mFrame || Animation::isTerminator(*mFrame)) return false; - mFrameTime += time; + mFrameTime += dt; while (mFrameTime > mFrame->delay && mFrame->delay > 0) { @@ -130,13 +130,13 @@ bool AnimatedSprite::updateCurrentAnimation(int time) return true; } -bool AnimatedSprite::draw(Graphics *graphics, int posX, int posY) const +bool Sprite::draw(Graphics *graphics, int posX, int posY) const { if (!mFrame) return false; if (!mFrame->image) - return false; + return false; if (mFrame->image->getAlpha() != mAlpha) mFrame->image->setAlpha(mAlpha); @@ -146,7 +146,7 @@ bool AnimatedSprite::draw(Graphics *graphics, int posX, int posY) const posY + mFrame->offsetY); } -bool AnimatedSprite::setDirection(SpriteDirection direction) +bool Sprite::setDirection(SpriteDirection direction) { if (mDirection != direction) { @@ -160,7 +160,6 @@ bool AnimatedSprite::setDirection(SpriteDirection direction) if (animation && animation != mAnimation && animation->getLength() > 0) { mAnimation = animation; - mFrame = mAnimation->getFrame(0); reset(); } @@ -170,38 +169,38 @@ bool AnimatedSprite::setDirection(SpriteDirection direction) return false; } -int AnimatedSprite::getDuration() const +int Sprite::getDuration() const { if (mAnimation) return mAnimation->getDuration(); return 0; } -int AnimatedSprite::getWidth() const +int Sprite::getWidth() const { if (mFrame && mFrame->image) return mFrame->image->getWidth(); return 0; } -int AnimatedSprite::getHeight() const +int Sprite::getHeight() const { if (mFrame && mFrame->image) return mFrame->image->getHeight(); return 0; } -int AnimatedSprite::getOffsetX() const +int Sprite::getOffsetX() const { return mFrame ? mFrame->offsetX : 0; } -int AnimatedSprite::getOffsetY() const +int Sprite::getOffsetY() const { return mFrame ? mFrame->offsetY : 0; } -const Image *AnimatedSprite::getImage() const +const Image *Sprite::getImage() const { return mFrame ? mFrame->image : nullptr; } diff --git a/src/sprite.h b/src/sprite.h index d7f5f1d7..b82bf7bb 100644 --- a/src/sprite.h +++ b/src/sprite.h @@ -18,35 +18,54 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SPRITE_H -#define SPRITE_H +#pragma once #include "resources/spritedef.h" +class Animation; class Graphics; class Image; +struct Frame; // Default frame display delay in milliseconds const int DEFAULT_FRAME_DELAY = 75; +/** + * Animates a sprite by adding playback state. + */ class Sprite { public: - virtual ~Sprite() = default; + /** + * Constructor. + * @param sprite the sprite to animate + */ + Sprite(SpriteDef *sprite); + + /** + * An helper function, which will request the sprite to animate + * from the resource manager. + * + * @param filename the file of the sprite to animate + * @param variant the sprite variant + */ + static Sprite *load(const std::string &filename, int variant = 0); + + ~Sprite(); /** * Resets the sprite. * * @returns true if the sprite changed, false otherwise */ - virtual bool reset() = 0; + bool reset(); /** * Plays an action using the current direction. * * @returns true if the sprite changed, false otherwise */ - virtual bool play(const std::string &action) = 0; + bool play(const std::string &action); /** * Inform the animation of the passed time so that it can output the @@ -54,67 +73,73 @@ class Sprite * * @returns true if the sprite changed, false otherwise */ - virtual bool update(int time) = 0; + bool update(int time); /** * Draw the current animation frame at the coordinates given in screen * pixels. */ - virtual bool draw(Graphics *graphics, int posX, int posY) const = 0; + bool draw(Graphics *graphics, int posX, int posY) const; /** * Gets the width in pixels of the image */ - virtual int getWidth() const = 0; + int getWidth() const; /** * Gets the height in pixels of the image */ - virtual int getHeight() const = 0; + int getHeight() const; /** * Gets the horizontal offset that the sprite will be drawn at */ - virtual int getOffsetX() const - { return 0; } + int getOffsetX() const; /** * Gets the vertical offset that the sprite will be drawn at */ - virtual int getOffsetY() const - { return 0; } + int getOffsetY() const; /** * Returns a reference to the current image being drawn. */ - virtual const Image *getImage() const = 0; + const Image *getImage() const; /** * Sets the direction. * * @returns true if the sprite changed, false otherwise */ - virtual bool setDirection(SpriteDirection direction) = 0; + bool setDirection(SpriteDirection direction); /** - * Sets the alpha value of the animated sprite + * Sets the alpha value of the sprite. */ - virtual void setAlpha(float alpha) - { mAlpha = alpha; } + void setAlpha(float alpha) { mAlpha = alpha; } /** - * Returns the current alpha opacity of the animated sprite. + * Returns the current alpha opacity of the sprite. */ - virtual float getAlpha() const - { return mAlpha; } + float getAlpha() const { return mAlpha; } /** * Returns the duration of the current sprite animation in milliseconds. */ - virtual int getDuration() const = 0; + int getDuration() const; - protected: - float mAlpha = 1.0f; /**< The alpha opacity used to draw */ -}; + private: + bool updateCurrentAnimation(int dt); + + float mAlpha = 1.0f; /**< The alpha opacity used to draw */ -#endif // SPRITE_H + SpriteDirection mDirection = DIRECTION_DOWN; /**< The sprite direction. */ + + int mFrameIndex = 0; /**< The index of the current frame. */ + int mFrameTime = 0; /**< The time since start of frame. */ + + ResourceRef<SpriteDef> mSprite; /**< The sprite definition. */ + Action *mAction = nullptr; /**< The currently active action. */ + Animation *mAnimation = nullptr; /**< The currently active animation. */ + Frame *mFrame = nullptr; /**< The currently active frame. */ +}; diff --git a/src/statuseffect.cpp b/src/statuseffect.cpp index f06ab827..7cb035bd 100644 --- a/src/statuseffect.cpp +++ b/src/statuseffect.cpp @@ -1,7 +1,7 @@ /* * The Mana Client * Copyright (C) 2008-2009 The Mana World Development Team - * Copyright (C) 2009-2013 The Mana Developers + * Copyright (C) 2009-2025 The Mana Developers * * This file is part of The Mana Client. * @@ -22,152 +22,50 @@ #include "statuseffect.h" #include "event.h" +#include "particle.h" #include "sound.h" #include "configuration.h" -#include <map> - -#define STATUS_EFFECTS_FILE "status-effects.xml" - -bool StatusEffect::mLoaded = false; - -StatusEffect::StatusEffect() = default; -StatusEffect::~StatusEffect() = default; - -void StatusEffect::playSFX() +/** + * Plays the sound effect associated with this status effect, if possible. + */ +void StatusEffect::playSfx(bool enabled) const { - if (!mSFXEffect.empty()) - sound.playSfx(mSFXEffect); + auto &sfx = enabled ? start.sfx : end.sfx; + if (!sfx.empty()) + sound.playSfx(sfx); } -void StatusEffect::deliverMessage() +/** + * Delivers the chat message associated with this status effect, if + * possible. + */ +void StatusEffect::deliverMessage(bool enabled) const { - if (!mMessage.empty()) - serverNotice(mMessage); + auto &message = enabled ? start.message : end.message; + if (!message.empty()) + serverNotice(message); } -Particle *StatusEffect::getParticle() +/** + * Creates the particle effect associated with this status effect, if + * possible. + */ +Particle *StatusEffect::getParticle(bool enabled) const { - if (mParticleEffect.empty()) + auto &particleEffect = enabled ? start.particleEffect : end.particleEffect; + if (particleEffect.empty()) return nullptr; - return particleEngine->addEffect(mParticleEffect, 0, 0); + return particleEngine->addEffect(particleEffect, 0, 0); } -AnimatedSprite *StatusEffect::getIcon() +/** + * Retrieves the status icon for this effect, if applicable. + */ +Sprite *StatusEffect::getIconSprite() const { - if (mIcon.empty()) + if (icon.empty()) return nullptr; - - AnimatedSprite *sprite = AnimatedSprite::load( - paths.getStringValue("sprites") + mIcon); - if (false && sprite) - { - sprite->play(SpriteAction::DEFAULT); - sprite->reset(); - } - return sprite; -} - -std::string StatusEffect::getAction() const -{ - if (mAction.empty()) - return SpriteAction::INVALID; - return mAction; -} - - -// -- initialisation and static parts -- - - -typedef std::map<int, StatusEffect *> status_effect_map[2]; - -static status_effect_map statusEffects; -static status_effect_map stunEffects; -static std::map<int, int> blockEffectIndexMap; - -int StatusEffect::blockEffectIndexToEffectIndex(int blockIndex) -{ - if (blockEffectIndexMap.find(blockIndex) == blockEffectIndexMap.end()) - return -1; - return blockEffectIndexMap[blockIndex]; -} - -StatusEffect *StatusEffect::getStatusEffect(int index, bool enabling) -{ - return statusEffects[enabling][index]; -} - -StatusEffect *StatusEffect::getStunEffect(int index, bool enabling) -{ - return stunEffects[enabling][index]; -} - -void StatusEffect::init() -{ - if (mLoaded) - unload(); -} - -void StatusEffect::readStatusEffectNode(XML::Node node, const std::string &filename) -{ - status_effect_map *the_map = nullptr; - int index = atoi(node.getProperty("id", "-1").c_str()); - if (node.name() == "status-effect") - { - the_map = &statusEffects; - int block_index = atoi(node.getProperty("block-id", "-1").c_str()); - - if (index >= 0 && block_index >= 0) - blockEffectIndexMap[block_index] = index; - } - else if (node.name() == "stun-effect") - the_map = &stunEffects; - - if (the_map) - { - auto *startEffect = new StatusEffect; - auto *endEffect = new StatusEffect; - - startEffect->mMessage = node.getProperty("start-message", ""); - startEffect->mSFXEffect = node.getProperty("start-audio", ""); - startEffect->mParticleEffect = node.getProperty("start-particle", ""); - startEffect->mIcon = node.getProperty("icon", ""); - startEffect->mAction = node.getProperty("action", ""); - startEffect->mPersistentParticleEffect = (node.getProperty("persistent-particle-effect", "no")) != "no"; - - endEffect->mMessage = node.getProperty("end-message", ""); - endEffect->mSFXEffect = node.getProperty("end-audio", ""); - endEffect->mParticleEffect = node.getProperty("end-particle", ""); - - (*the_map)[1][index] = startEffect; - (*the_map)[0][index] = endEffect; - } - -} - -void StatusEffect::checkStatus() -{ - mLoaded = true; -} - -void unloadMap(std::map<int, StatusEffect *> map) -{ - for (auto &[_, effect] : map) - delete effect; - - map.clear(); -} - -void StatusEffect::unload() -{ - if (!mLoaded) - return; - - unloadMap(statusEffects[0]); - unloadMap(statusEffects[1]); - unloadMap(stunEffects[0]); - unloadMap(stunEffects[1]); - - mLoaded = false; + return Sprite::load(paths.getStringValue("sprites") + icon); } diff --git a/src/statuseffect.h b/src/statuseffect.h index 05b80e6b..2ab98d4f 100644 --- a/src/statuseffect.h +++ b/src/statuseffect.h @@ -1,7 +1,7 @@ /* * The Mana Client * Copyright (C) 2008-2009 The Mana World Development Team - * Copyright (C) 2009-2013 The Mana Developers + * Copyright (C) 2009-2025 The Mana Developers * * This file is part of The Mana Client. * @@ -19,97 +19,38 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef STATUS_EFFECT_H -#define STATUS_EFFECT_H +#pragma once -#include "particle.h" -#include "animatedsprite.h" +#include <string> -#include "utils/xml.h" +class Particle; +class Sprite; class StatusEffect { public: - StatusEffect(); - ~StatusEffect(); + struct Event + { + std::string message; + std::string sfx; + std::string particleEffect; + }; - /** - * Plays the sound effect associated with this status effect, if possible. - */ - void playSFX(); - - /** - * Delivers the chat message associated with this status effect, if - * possible. - */ - void deliverMessage(); - - /** - * Creates the particle effect associated with this status effect, if - * possible. - */ - Particle *getParticle(); - - /** - * Retrieves the status icon for this effect, if applicable - */ - AnimatedSprite *getIcon(); - - /** - * Retrieves an action to perform, or SpriteAction::INVALID - */ - std::string getAction() const; + std::string name; + Event start; + Event end; + std::string icon; /** * Determines whether the particle effect should be restarted when the - * being changes maps + * being changes maps. */ - bool particleEffectIsPersistent() const { return mPersistentParticleEffect; } + bool persistentParticleEffect = false; + StatusEffect() = default; - /** - * Retrieves a status effect. - * - * \param index Index of the status effect. - * \param enabling Whether to retrieve the activating effect (true) or - * the deactivating effect (false). - */ - static StatusEffect *getStatusEffect(int index, bool enabling); - - /** - * Retrieves a stun effect. - * - * \param index Index of the stun effect. - * \param enabling Whether to retrieve the activating effect (true) or - * the deactivating effect (false). - */ - static StatusEffect *getStunEffect(int index, bool enabling); - - /** - * Maps a block effect index to its corresponding effect index. Block - * effect indices are used for opt2/opt3/status.option blocks; their - * mapping to regular effect indices is handled in the config file. - * - * Returns -1 on failure. - */ - static int blockEffectIndexToEffectIndex(int blocKIndex); - - static void init(); - - static void readStatusEffectNode(XML::Node node, const std::string &filename); - - static void checkStatus(); - - static void unload(); -private: - static bool mLoaded; - - std::string mMessage; - std::string mSFXEffect; - std::string mParticleEffect; - std::string mIcon; - std::string mAction; - bool mPersistentParticleEffect = false; + void playSfx(bool enabled) const; + void deliverMessage(bool enabled) const; + Particle *getParticle(bool enabled) const; + Sprite *getIconSprite() const; }; - -#endif // !defined(STATUS_EFFECT_H) diff --git a/src/text.cpp b/src/text.cpp index e3776410..4698aa87 100644 --- a/src/text.cpp +++ b/src/text.cpp @@ -22,54 +22,34 @@ #include "text.h" -#include "configuration.h" #include "textmanager.h" #include "textrenderer.h" #include "gui/gui.h" -#include "resources/image.h" #include "resources/theme.h" #include <guichan/font.hpp> int Text::mInstances = 0; -ImageRect Text::mBubble; -Image *Text::mBubbleArrow; -Text::Text(const std::string &text, int x, int y, +Text::Text(const std::string &text, + int x, + int y, gcn::Graphics::Alignment alignment, - const gcn::Color* color, bool isSpeech, - gcn::Font *font) : - mText(text), - mColor(color), - mIsSpeech(isSpeech) + const gcn::Color *color, + bool isSpeech, + gcn::Font *font) + : mText(text) + , mColor(color) + , mFont(font ? font : gui->getFont()) + , mIsSpeech(isSpeech) { - if (!font) - mFont = gui->getFont(); - else - mFont = font; - if (textManager == nullptr) - { textManager = new TextManager; - Image *sbImage = Theme::getImageFromTheme("bubble.png|W:#" - + config.speechBubblecolor); - mBubble.grid[0] = sbImage->getSubImage(0, 0, 5, 5); - mBubble.grid[1] = sbImage->getSubImage(5, 0, 5, 5); - mBubble.grid[2] = sbImage->getSubImage(10, 0, 5, 5); - mBubble.grid[3] = sbImage->getSubImage(0, 5, 5, 5); - mBubble.grid[4] = sbImage->getSubImage(5, 5, 5, 5); - mBubble.grid[5] = sbImage->getSubImage(10, 5, 5, 5); - mBubble.grid[6] = sbImage->getSubImage(0, 10, 5, 5); - mBubble.grid[7] = sbImage->getSubImage(5, 10, 5, 5); - mBubble.grid[8] = sbImage->getSubImage(10, 10, 5, 5); - mBubbleArrow = sbImage->getSubImage(0, 15, 15, 10); - mBubble.setAlpha(config.speechBubbleAlpha); - mBubbleArrow->setAlpha(config.speechBubbleAlpha); - sbImage->decRef(); - } + ++mInstances; + mHeight = mFont->getHeight(); mWidth = mFont->getWidth(text); @@ -85,8 +65,10 @@ Text::Text(const std::string &text, int x, int y, mXOffset = mWidth; break; } + mX = x - mXOffset; mY = y; + textManager->addText(this); } @@ -97,9 +79,6 @@ Text::~Text() { delete textManager; textManager = nullptr; - for (auto img : mBubble.grid) - delete img; - delete mBubbleArrow; } } @@ -117,9 +96,15 @@ void Text::draw(gcn::Graphics *graphics, int xOff, int yOff) { if (mIsSpeech) { - static_cast<Graphics*>(graphics)->drawImageRect( - mX - xOff - 5, mY - yOff - 5, mWidth + 10, mHeight + 10, - mBubble); + WidgetState state; + state.x = mX - xOff - 5; + state.y = mY - yOff - 5; + state.width = mWidth + 10; + state.height = mHeight + 10; + + auto theme = gui->getTheme(); + theme->drawSkin(static_cast<Graphics *>(graphics), SkinType::SpeechBubble, state); + /* if (mWidth >= 15) { @@ -20,14 +20,13 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef TEXT_H -#define TEXT_H - -#include "graphics.h" +#pragma once #include "utils/time.h" #include <guichan/color.hpp> +#include <guichan/font.hpp> +#include <guichan/graphics.hpp> class TextManager; @@ -75,10 +74,6 @@ class Text const gcn::Color *mColor; /**< The color of the text. */ gcn::Font *mFont; /**< The font of the text */ bool mIsSpeech; /**< Is this text a speech bubble? */ - - protected: - static ImageRect mBubble; /**< Speech bubble graphic */ - static Image *mBubbleArrow; /**< Speech bubble arrow graphic */ }; class FlashText : public Text @@ -99,5 +94,3 @@ class FlashText : public Text private: Timer mTimer; /**< Time left for flashing */ }; - -#endif // TEXT_H diff --git a/src/textmanager.h b/src/textmanager.h index f736e87c..c4d38d82 100644 --- a/src/textmanager.h +++ b/src/textmanager.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef TEXTMANAGER_H -#define TEXTMANAGER_H +#pragma once #include <list> @@ -69,5 +68,3 @@ class TextManager }; extern TextManager *textManager; - -#endif // TEXTMANAGER_H diff --git a/src/textparticle.h b/src/textparticle.h index d70cc5b9..89a8e90c 100644 --- a/src/textparticle.h +++ b/src/textparticle.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef TEXTPARTICLE_H -#define TEXTPARTICLE_H +#pragma once #include "particle.h" @@ -53,5 +52,3 @@ class TextParticle : public Particle const gcn::Color *mColor; /**< Color used for drawing the text. */ bool mOutline; /**< Make the text better readable */ }; - -#endif diff --git a/src/textrenderer.h b/src/textrenderer.h index d402e146..c9cd9310 100644 --- a/src/textrenderer.h +++ b/src/textrenderer.h @@ -19,16 +19,15 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef TEXT_RENDERER_H -#define TEXT_RENDERER_H - -#include "graphics.h" +#pragma once #include "resources/theme.h" +#include <guichan/exception.hpp> +#include <guichan/font.hpp> + /** - * Class for text rendering. Used by the TextParticle, the Text and FlashText - * objects and the Preview in the color dialog. + * Class for text rendering which can apply an outline and shadow. */ class TextRenderer { @@ -39,48 +38,90 @@ public: static void renderText(gcn::Graphics *graphics, const std::string &text, int x, int y, - gcn::Graphics::Alignment align, + gcn::Graphics::Alignment alignment, const gcn::Color &color, gcn::Font *font, bool outline = false, - bool shadow = false) + bool shadow = false, + const std::optional<gcn::Color> &outlineColor = {}, + const std::optional<gcn::Color> &shadowColor = {}) { - graphics->setFont(font); + switch (alignment) + { + case gcn::Graphics::LEFT: + break; + case gcn::Graphics::CENTER: + x -= font->getWidth(text) / 2; + break; + case gcn::Graphics::RIGHT: + x -= font->getWidth(text); + break; + default: + throw GCN_EXCEPTION("Unknown alignment."); + } // Text shadow if (shadow) { - graphics->setColor(Theme::getThemeColor(Theme::SHADOW, - color.a / 2)); + if (shadowColor) + graphics->setColor(*shadowColor); + else + graphics->setColor(Theme::getThemeColor(Theme::SHADOW, color.a / 2)); + if (outline) - { - graphics->drawText(text, x + 2, y + 2, align); - } + font->drawString(graphics, text, x + 2, y + 2); else - { - graphics->drawText(text, x + 1, y + 1, align); - } + font->drawString(graphics, text, x + 1, y + 1); } - if (outline) { -/* graphics->setColor(guiPalette->getColor(Palette::OUTLINE, + if (outline) + { + /* + graphics->setColor(guiPalette->getColor(Palette::OUTLINE, alpha/4)); // TODO: Reanable when we can draw it nicely in software mode - graphics->drawText(text, x + 2, y + 2, align); - graphics->drawText(text, x + 1, y + 2, align); - graphics->drawText(text, x + 2, y + 1, align);*/ + font->drawString(graphics, text, x + 2, y + 2); + font->drawString(graphics, text, x + 1, y + 2); + font->drawString(graphics, text, x + 2, y + 1); + */ // Text outline - graphics->setColor(Theme::getThemeColor(Theme::OUTLINE, color.a)); - graphics->drawText(text, x + 1, y, align); - graphics->drawText(text, x - 1, y, align); - graphics->drawText(text, x, y + 1, align); - graphics->drawText(text, x, y - 1, align); + if (outlineColor) + graphics->setColor(*outlineColor); + else + graphics->setColor(Theme::getThemeColor(Theme::OUTLINE, color.a)); + + font->drawString(graphics, text, x + 1, y); + font->drawString(graphics, text, x - 1, y); + font->drawString(graphics, text, x, y + 1); + font->drawString(graphics, text, x, y - 1); } graphics->setColor(color); - graphics->drawText(text, x, y, align); + font->drawString(graphics, text, x, y); } -}; -#endif + /** + * Renders a specified text. + */ + static void renderText(gcn::Graphics *graphics, + const std::string &text, + int x, + int y, + gcn::Graphics::Alignment align, + gcn::Font *font, + const TextFormat &format) + { + renderText(graphics, + text, + x, + y, + align, + format.color, + font, + format.outlineColor.has_value(), + format.shadowColor.has_value(), + format.outlineColor, + format.shadowColor); + } +}; diff --git a/src/tileset.h b/src/tileset.h index 4ba9f016..daf8faf3 100644 --- a/src/tileset.h +++ b/src/tileset.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef TILESET_H -#define TILESET_H +#pragma once #include "resources/imageset.h" @@ -48,5 +47,3 @@ class Tileset : public ImageSet private: unsigned mFirstGid; }; - -#endif // TILESET_H diff --git a/src/units.cpp b/src/units.cpp index fd01afd8..ee8fa6ea 100644 --- a/src/units.cpp +++ b/src/units.cpp @@ -112,8 +112,7 @@ void Units::readUnitNode(XML::Node node, const std::string &filename) if (uLevel.name() == "level") { UnitLevel ul; - ul.symbol = uLevel.getProperty("symbol", - strprintf("¤%d",level)); + ul.symbol = uLevel.getProperty("symbol", strprintf("¤%d", level)); ul.count = uLevel.getProperty("count", -1); ul.round = uLevel.getProperty("round", bu.round); diff --git a/src/units.h b/src/units.h index eb6b6712..2a4bd458 100644 --- a/src/units.h +++ b/src/units.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef UNITS_H -#define UNITS_H +#pragma once #include <string> #include "utils/xml.h" @@ -44,5 +43,3 @@ class Units */ static std::string formatWeight(int value); }; - -#endif // UNITS_H diff --git a/src/utils/base64.h b/src/utils/base64.h index 92c23016..8fc7e79f 100644 --- a/src/utils/base64.h +++ b/src/utils/base64.h @@ -27,10 +27,7 @@ +----------------------------------------------------------------------+ */ -#ifndef BASE64_H -#define BASE64_H +#pragma once extern unsigned char *php3_base64_encode(const unsigned char *, int, int *); extern unsigned char *php3_base64_decode(const unsigned char *, int, int *); - -#endif /* BASE64_H */ diff --git a/src/utils/copynpaste.h b/src/utils/copynpaste.h index b6aa0de0..83e48891 100644 --- a/src/utils/copynpaste.h +++ b/src/utils/copynpaste.h @@ -18,6 +18,8 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#pragma once + #include <string> /** diff --git a/src/utils/dtor.h b/src/utils/dtor.h index 76c68725..79b374d0 100644 --- a/src/utils/dtor.h +++ b/src/utils/dtor.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef UTILS_DTOR_H -#define UTILS_DTOR_H +#pragma once #include <algorithm> #include <utility> @@ -48,5 +47,3 @@ inline void delete_all(Container &c) { std::for_each(c.begin(), c.end(), make_dtor(c)); } - -#endif diff --git a/src/utils/filesystem.h b/src/utils/filesystem.h new file mode 100644 index 00000000..3474e7c4 --- /dev/null +++ b/src/utils/filesystem.h @@ -0,0 +1,297 @@ +/* + * The Mana Client + * Copyright (C) 2024 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +// Suppress deprecation warnings for PHYSFS_getUserDir +#define PHYSFS_DEPRECATED + +#include "utils/physfsrwops.h" + +#include <optional> +#include <string> + +/** + * These functions wrap PHYSFS functions to provide a more user-friendly + * interface and to limit the direct use of the PHYSFS API to a single file. + */ +namespace FS { + +inline bool init(const char *argv0) +{ + return PHYSFS_init(argv0) != 0; +} + +inline void deinit() +{ + PHYSFS_deinit(); +} + +inline const char *getDirSeparator() +{ + return PHYSFS_getDirSeparator(); +} + +inline const char *getBaseDir() +{ + return PHYSFS_getBaseDir(); +} + +inline const char *getUserDir() +{ + return PHYSFS_getUserDir(); +} + +inline const char *getPrefDir(const char *org, const char *app) +{ + return PHYSFS_getPrefDir(org, app); +} + +/** + * Sets the write directory. + * + * @param path The path of the directory to be added. + * @return <code>true</code> on success, <code>false</code> otherwise. + */ +inline bool setWriteDir(const std::string &path) +{ + return PHYSFS_setWriteDir(path.c_str()) != 0; +} + +/** + * Adds a directory or archive to the search path. If append is true + * then the directory is added to the end of the search path, otherwise + * it is added at the front. + * + * @return <code>true</code> on success, <code>false</code> otherwise. + */ +inline bool addToSearchPath(const std::string &path, bool append) +{ + return PHYSFS_mount(path.c_str(), "/", append ? 1 : 0) != 0; +} + +/** + * Checks whether the given file or directory exists in the search path. + */ +inline bool exists(const std::string &path) +{ + return PHYSFS_exists(path.c_str()) != 0; +} + +inline std::optional<const char *> getRealDir(const std::string &path) +{ + auto dir = PHYSFS_getRealDir(path.c_str()); + return dir ? std::optional<const char *>(dir) : std::nullopt; +} + +/** + * Checks whether the given path is a directory. + */ +inline bool isDirectory(const std::string &path) +{ + PHYSFS_Stat stat; + if (PHYSFS_stat(path.c_str(), &stat) != 0) + { + return stat.filetype == PHYSFS_FILETYPE_DIRECTORY; + } + return false; +} + +/** + * Creates a directory in the write path. + */ +inline bool mkdir(const std::string &path) +{ + return PHYSFS_mkdir(path.c_str()) != 0; +} + +/** + * Helper class to iterate over the files in a directory. + * Based on https://stackoverflow.com/a/79051293/355419. + */ +class Files +{ +public: + struct End {}; + friend bool operator!=(const char *const *files, End) + { return *files != nullptr; } + + explicit Files(char **files) : mFiles(files) {} + ~Files() { PHYSFS_freeList(mFiles); } + + Files(const Files &) = delete; + Files &operator=(const Files &) = delete; + + // Relies on C++17 support for begin/end to not have the same return type + const char* const *begin() const { return mFiles; } + End end() const { return End(); } + +private: + char **mFiles; +}; + +/** + * Returns a list of files in the given directory. + */ +inline Files enumerateFiles(const std::string &dir) +{ + return Files(PHYSFS_enumerateFiles(dir.c_str())); +} + +/** + * File wrapper class to provide a more convenient API and automatic closing. + */ +class File +{ +public: + explicit File(PHYSFS_file *file) + : file(file) + {} + + ~File() + { + if (isOpen()) + close(); + } + + bool isOpen() const + { + return file != nullptr; + } + + operator bool() const + { + return isOpen(); + } + + bool close() + { + if (PHYSFS_close(file) != 0) + { + file = nullptr; + return true; + } + return false; + } + + std::optional<size_t> read(void *data, size_t size) + { + auto len = PHYSFS_readBytes(file, data, size); + return len >= 0 ? std::optional<size_t>(len) : std::nullopt; + } + + std::optional<size_t> write(const void *data, size_t size) + { + auto len = PHYSFS_writeBytes(file, data, size); + return len >= 0 ? std::optional<size_t>(len) : std::nullopt; + } + + bool flush() + { + return PHYSFS_flush(file) != 0; + } + + bool seek(size_t pos) + { + return PHYSFS_seek(file, pos) != 0; + } + + std::optional<size_t> fileLength() const + { + auto len = PHYSFS_fileLength(file); + return len >= 0 ? std::optional<size_t>(len) : std::nullopt; + } + + std::optional<size_t> tell() const + { + auto pos = PHYSFS_tell(file); + return pos >= 0 ? std::optional<size_t>(pos) : std::nullopt; + } + + bool eof() const + { + return PHYSFS_eof(file) != 0; + } + +private: + PHYSFS_file *file; +}; + +inline File openWrite(const std::string &path) +{ + return File(PHYSFS_openWrite(path.c_str())); +} + +inline File openAppend(const std::string &path) +{ + return File(PHYSFS_openAppend(path.c_str())); +} + +inline File openRead(const std::string &path) +{ + return File(PHYSFS_openRead(path.c_str())); +} + +inline const char *getLastError() +{ + return PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()); +} + +// +// Helper functions for loading files through SDL_RWops +// + +inline SDL_RWops *openRWops(const std::string &path) +{ + return PHYSFSRWOPS_openRead(path.c_str()); +} + +/** + * Creates a buffered SDL_RWops. + * + * Used to workaround a performance issue when SDL_mixer is using stb_vorbis, + * in which case the file is read one byte at a time. + * + * See https://github.com/libsdl-org/SDL_mixer/issues/670 + */ +inline SDL_RWops *openBufferedRWops(const std::string &path, + PHYSFS_uint64 bufferSize = 2048) +{ + if (auto file = PHYSFS_openRead(path.c_str())) + { + PHYSFS_setBuffer(file, bufferSize); + if (auto rw = PHYSFSRWOPS_makeRWops(file)) + return rw; + else + PHYSFS_close(file); + } + return nullptr; +} + +inline void *loadFile(const std::string &path, size_t &datasize) +{ + auto file = openRWops(path); + if (!file) + return nullptr; + + return SDL_LoadFile_RW(file, &datasize, 1); +} + +} // namespace FS diff --git a/src/utils/gettext.h b/src/utils/gettext.h index f3b12ada..1f40a4be 100644 --- a/src/utils/gettext.h +++ b/src/utils/gettext.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef UTILS_GETTEXT_H -#define UTILS_GETTEXT_H +#pragma once #ifdef HAVE_CONFIG_H #include "config.h" @@ -40,5 +39,3 @@ #define N_(s) ((char const *)s) #endif - -#endif // UTILS_GETTEXT_H diff --git a/src/utils/mathutils.h b/src/utils/mathutils.h index 21eba16f..0a169df5 100644 --- a/src/utils/mathutils.h +++ b/src/utils/mathutils.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef UTILS_MATHUTILS_H -#define UTILS_MATHUTILS_H +#pragma once /* A very fast function to calculate the approximate inverse square root of a * floating point value and a helper function that uses it for getting the @@ -57,5 +56,3 @@ inline float weightedAverage(float n1, float n2, float w) return w * n2 + (1.0f - w) * n1; } - -#endif // UTILS_MATHUTILS_H diff --git a/src/utils/mkdir.h b/src/utils/mkdir.h index 3bd76145..817c79df 100644 --- a/src/utils/mkdir.h +++ b/src/utils/mkdir.h @@ -18,9 +18,6 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef MKDIR_H -#define MKDIR_H +#pragma once int mkdir_r(const char *pathname); - -#endif diff --git a/src/utils/mutex.h b/src/utils/mutex.h index b4661c70..a0c72e95 100644 --- a/src/utils/mutex.h +++ b/src/utils/mutex.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef MUTEX_H -#define MUTEX_H +#pragma once #include "log.h" @@ -104,4 +103,41 @@ inline MutexLocker::~MutexLocker() mMutex->unlock(); } -#endif // MUTEX_H +/** + * A template class for wrapping data that is accessed by multiple threads. + */ +template <typename T> +class ThreadSafe +{ + class Locked : private MutexLocker + { + public: + Locked(T &data, Mutex &mutex) + : MutexLocker(&mutex) + , mData(data) + {} + + Locked(Locked&& rhs) = delete; + Locked(const Locked&) = delete; + Locked& operator=(const Locked&) = delete; + Locked& operator=(Locked&&) = delete; + + T &operator*() const { return mData; } + T *operator->() const { return &mData; } + + private: + T &mData; + }; + +public: + ThreadSafe() = default; + ThreadSafe(const T &data) + : mData(data) + {} + + Locked lock() { return { mData, mMutex }; } + +private: + T mData; + Mutex mMutex; +}; diff --git a/src/utils/path.h b/src/utils/path.h index 5338c74e..0b7019eb 100644 --- a/src/utils/path.h +++ b/src/utils/path.h @@ -18,8 +18,7 @@ * along with The Mana Server. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef UTILS_PATH_H -#define UTILS_PATH_H +#pragma once #include <string> @@ -29,5 +28,3 @@ namespace utils std::string joinPaths(std::string_view path1, std::string_view path2); std::string cleanPath(const std::string &path); } - -#endif // UTILS_PATH_H diff --git a/src/utils/sha256.h b/src/utils/sha256.h index 8a9c2b70..6c2c3107 100644 --- a/src/utils/sha256.h +++ b/src/utils/sha256.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef UTILS_SHA256_H -#define UTILS_SHA256_H +#pragma once #include <string> @@ -31,5 +30,3 @@ * @return the SHA-256 hash for the given string. */ std::string sha256(const std::string &string); - -#endif // UTILS_SHA256_H diff --git a/src/utils/specialfolder.h b/src/utils/specialfolder.h index 36a4e0c1..9e069310 100644 --- a/src/utils/specialfolder.h +++ b/src/utils/specialfolder.h @@ -18,8 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SPECIALFOLDER_H -#define SPECIALFOLDER_H +#pragma once #ifdef _WIN32 #include <shlobj.h> @@ -31,5 +30,3 @@ std::string getSpecialFolderLocation(const KNOWNFOLDERID &folderId); #include <string> std::string getResourcesLocation(); #endif - -#endif diff --git a/src/utils/stringutils.cpp b/src/utils/stringutils.cpp index 747243fd..ba5f575b 100644 --- a/src/utils/stringutils.cpp +++ b/src/utils/stringutils.cpp @@ -21,8 +21,6 @@ #include "utils/stringutils.h" -#include "log.h" - #include <cstring> #include <algorithm> #include <cstdarg> @@ -101,16 +99,15 @@ std::string strprintf(char const *format, ...) return res; } -std::string &removeBadChars(std::string &str) +std::string &replaceCharacters(std::string &str, + std::string_view chars, + char replacement) { - std::string::size_type pos; - do + for (auto &c : str) { - pos = str.find_first_of("@#[]"); - if (pos != std::string::npos) - str.erase(pos, 1); - } while (pos != std::string::npos); - + if (chars.find(c) != std::string::npos) + c = replacement; + } return str; } @@ -126,29 +123,6 @@ std::string removeColors(std::string msg) return msg; } -int compareStrI(const std::string &a, const std::string &b) -{ - std::string::const_iterator itA = a.begin(); - std::string::const_iterator endA = a.end(); - std::string::const_iterator itB = b.begin(); - std::string::const_iterator endB = b.end(); - - for (; itA < endA && itB < endB; ++itA, ++itB) - { - int comp = tolower(*itA) - tolower(*itB); - if (comp) - return comp; - } - - // Check string lengths - if (itA == endA && itB == endB) - return 0; - else if (itA == endA) - return -1; - else - return 1; -} - bool isWordSeparator(char chr) { return (chr == ' ' || chr == ',' || chr == '.' || chr == '"'); @@ -220,49 +194,27 @@ std::string normalize(const std::string &name) return toLower(trim(normalized)); } -std::string removeTrailingSymbol(const std::string &s, const char c) -{ - // Remove the trailing symblol at the end of the string - if (!s.empty() && s.at(s.size() - 1) == c) - return s.substr(0, s.size() - 1); - return std::string(s); -} - -std::string getHostNameFromURL(const std::string &url) +std::string getDirectoryFromURL(const std::string &url) { - std::string myHostName; + std::string directory = url; - // Don't go out of range in the next check - if (url.length() < 2) - return myHostName; + // Parse out any "http://", "ftp://", etc... + size_t pos = directory.find("://"); + if (pos != std::string::npos) + directory.erase(0, pos + 3); - // Remove any trailing slash at the end of the update host - myHostName = removeTrailingSymbol(url, '/'); + // Replace characters which are not valid or difficult in file system paths + replaceCharacters(directory, ":*?\"<>| ", '_'); - // Parse out any "http://", "ftp://", ect... - size_t pos = myHostName.find("://"); - if (pos == std::string::npos) + // Replace ".." (double dots) with "_" to avoid directory traversal. + pos = directory.find(".."); + while (pos != std::string::npos) { - logger->log("Warning: no protocol was specified for the url: %s", - url.c_str()); + directory.replace(pos, 2, "_"); + pos = directory.find(".."); } - if (myHostName.empty() || pos + 3 >= myHostName.length()) - { - logger->log("Error: Invalid url: %s", url.c_str()); - return myHostName; - } - myHostName = myHostName.substr(pos + 3); - - // Remove possible trailing port (i.e.: localhost:8000 -> localhost) - pos = myHostName.find(":"); - if (pos != std::string::npos) - myHostName = myHostName.substr(0, pos); - - // remove possible other junk - removeBadChars(myHostName); - - return myHostName; + return directory; } std::string join(const std::vector<std::string> &strings, const char *separator) diff --git a/src/utils/stringutils.h b/src/utils/stringutils.h index e7ff1332..1fa8e1e6 100644 --- a/src/utils/stringutils.h +++ b/src/utils/stringutils.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef UTILS_STRINGUTILS_H -#define UTILS_STRINGUTILS_H +#pragma once #include <optional> #include <sstream> @@ -96,12 +95,16 @@ std::string strprintf(char const *, ...) ; /** - * Removes bad characters from a string + * Replaces a set of characters with another character. * * @param str the string to remove the bad chars from + * @param chars the bad characters to remove + * @param replacement the character to replace the bad chars with * @return a reference to the string without bad chars */ -std::string &removeBadChars(std::string &str); +std::string &replaceCharacters(std::string &str, + std::string_view chars, + char replacement = '_'); /** * Removes colors from a string @@ -112,16 +115,6 @@ std::string &removeBadChars(std::string &str); std::string removeColors(std::string msg); /** - * Compares the two strings case-insensitively. - * - * @param a the first string in the comparison - * @param b the second string in the comparison - * @return 0 if the strings are equal, positive if the first is greater, - * negative if the second is greater - */ -int compareStrI(const std::string &a, const std::string &b); - -/** * Returns whether a string starts with a given prefix. */ inline bool startsWith(const std::string &str, const char *prefix) @@ -145,11 +138,31 @@ std::string findSameSubstring(const std::string &str1, */ bool getBoolFromString(std::string text, bool def = false); +/** + * This class can be partially specialized to provide custom string conversion. + * + * This is done instead of overloading the base function template to avoid + * ambiguity. + */ +template<typename T, typename Enable = void> +struct FromString; + +template<typename T> +void fromString(const char *str, T &value) +{ + FromString<T>()(str, value); +} + inline void fromString(const char *str, std::string &value) { value = str; } +inline void fromString(const char *str, std::string_view &value) +{ + value = str; +} + inline void fromString(const char *str, int &value) { value = atoi(str); @@ -180,19 +193,23 @@ inline void fromString(const char *str, bool &value) value = getBoolFromString(str); } -template<typename Enum, std::enable_if_t<std::is_enum_v<Enum>, bool> = true> -inline void fromString(const char *str, Enum &value) +template<typename T> +struct FromString<T, std::enable_if_t<std::is_enum_v<T>>> { - value = static_cast<Enum>(atoi(str)); -} + void operator() (const char *str, T &value) + { + fromString(str, reinterpret_cast<std::underlying_type_t<T>&>(value)); + } +}; template<typename T> -inline void fromString(const char *str, std::optional<T> &value) +struct FromString<std::optional<T>> { - T v; - fromString(str, v); - value = v; -} + void operator() (const char *str, std::optional<T> &value) + { + fromString(str, value.emplace()); + } +}; /** * Returns the most approaching string of base from candidates. @@ -206,20 +223,15 @@ std::string autocomplete(const std::vector<std::string> &candidates, std::string normalize(const std::string &name); /** - * Remove a potential trailing symbol from a string. - */ -std::string removeTrailingSymbol(const std::string &s, const char c); - -/** - * Gets the hostname out of the given URL. - * i.e.: http://www.manasource.org:9601 -> www.manasource.org + * Derives a directory from the given URL, stripping the schema and replacing + * certain invalid characters. + * + * i.e.: http://www.manasource.org:9601/updates/ -> www.manasource.org_9601/updates/ */ -std::string getHostNameFromURL(const std::string &url); +std::string getDirectoryFromURL(const std::string &url); /** * Joins a vector of strings into one string, separated by the given * separator. */ std::string join(const std::vector<std::string> &strings, const char *separator); - -#endif // UTILS_STRINGUTILS_H diff --git a/src/utils/time.h b/src/utils/time.h index 58b8164a..590041e3 100644 --- a/src/utils/time.h +++ b/src/utils/time.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - #ifndef UTILS_TIME_H - #define UTILS_TIME_H +#pragma once #include <cstdint> @@ -107,5 +106,3 @@ public: private: uint32_t mTimeout = 0; }; - -#endif // UTILS_TIME_H diff --git a/src/utils/xml.cpp b/src/utils/xml.cpp index 7ea5b9d6..c408c9c2 100644 --- a/src/utils/xml.cpp +++ b/src/utils/xml.cpp @@ -25,16 +25,13 @@ #include "log.h" -#include "resources/resourcemanager.h" - -#include "utils/zlib.h" +#include "utils/filesystem.h" namespace XML { struct XMLContext { std::string file; - bool resman; }; #if LIBXML_VERSION >= 21200 @@ -49,7 +46,7 @@ namespace XML logger->log("Error in XML file '%s' on line %d", context->file.c_str(), error->line); else - logger->log("Error in unknown xml file on line %d", + logger->log("Error in unknown XML file on line %d", error->line); logger->log("%s", error->message); @@ -63,32 +60,27 @@ namespace XML { XMLContext ctx; ctx.file = filename; - ctx.resman = useResman; xmlSetStructuredErrorFunc(&ctx, xmlLogger); - int size; + size_t size; char *data = nullptr; + if (useResman) - { - ResourceManager *resman = ResourceManager::getInstance(); - data = (char*) resman->loadFile(filename, size); - } + data = (char *) FS::loadFile(filename, size); else - { - data = (char *) loadCompressedFile(filename, size); - } + data = (char *) SDL_LoadFile(filename.c_str(), &size); if (data) { mDoc = xmlParseMemory(data, size); - free(data); + SDL_free(data); if (!mDoc) logger->log("Error parsing XML file %s", filename.c_str()); } else { - logger->log("Error loading %s", filename.c_str()); + logger->log("Error loading %s: %s", filename.c_str(), SDL_GetError()); } xmlSetStructuredErrorFunc(nullptr, xmlLogger); diff --git a/src/utils/xml.h b/src/utils/xml.h index 13c06988..787504bb 100644 --- a/src/utils/xml.h +++ b/src/utils/xml.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef XML_H -#define XML_H +#pragma once #include "utils/stringutils.h" @@ -295,5 +294,3 @@ namespace XML xmlTextWriterWriteString(mWriter, BAD_CAST text.c_str()); } } - -#endif // XML_H diff --git a/src/utils/zlib.cpp b/src/utils/zlib.cpp index 773c7484..f78b235e 100644 --- a/src/utils/zlib.cpp +++ b/src/utils/zlib.cpp @@ -25,8 +25,6 @@ #include <cassert> #include <cstdlib> -#include <fstream> -#include <iostream> #include <zlib.h> /** @@ -131,53 +129,3 @@ int inflateMemory(unsigned char *in, unsigned int inLength, return outLength; } - -void *loadCompressedFile(const std::string &filename, int &filesize) -{ - std::ifstream file; - file.open(filename, std::ios::in); - - if (file.is_open()) - { - // Get length of file - file.seekg (0, std::ios::end); - filesize = file.tellg(); - file.seekg(0, std::ios::beg); - - char *buffer = (char *) malloc(filesize); - - file.read(buffer, filesize); - file.close(); - - unsigned char *inflated; - unsigned int inflatedSize; - - if (filename.find(".gz", filename.length() - 3) != std::string::npos) - { - // Inflate the gzipped map data - inflatedSize = - inflateMemory((unsigned char*) buffer, filesize, inflated); - free(buffer); - - if (!inflated) - { - logger->log("Could not decompress file: %s", - filename.c_str()); - return nullptr; - } - - filesize = inflatedSize; - return inflated; - } - else - { - return buffer; - } - } - else - { - logger->log("Error loading file from drive: %s", filename.c_str()); - } - - return nullptr; -} diff --git a/src/utils/zlib.h b/src/utils/zlib.h index 53dfd613..46429c0b 100644 --- a/src/utils/zlib.h +++ b/src/utils/zlib.h @@ -19,10 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef UTILS_ZLIB_H -#define UTILS_ZLIB_H - -#include <string> +#pragma once /** * Inflates either zlib or gzip deflated memory. The inflated memory is @@ -33,16 +30,3 @@ int inflateMemory(unsigned char *in, unsigned int inLength, int inflateMemory(unsigned char *in, unsigned int inLength, unsigned char *&out); - -/** - * Loads the given file from the filesystem, uncompressing if it ends in ".gz". - * - * @param filename The name of the file to be loaded and uncompressed - * @param filesize The size of the file that was loaded and uncompressed. - * - * @return An allocated byte array containing the data that was loaded and - * uncompressed, or <code>NULL</code> on fail. - */ -void *loadCompressedFile(const std::string &filename, int &filesize); - -#endif // UTILS_ZLIB_H diff --git a/src/variabledata.h b/src/variabledata.h index 4135e115..4f6695a7 100644 --- a/src/variabledata.h +++ b/src/variabledata.h @@ -18,8 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef VARIABLEDATA_H -#define VARIABLEDATA_H +#pragma once #include <string> @@ -122,5 +121,3 @@ public: private: ActorSprite *mData; }; - -#endif diff --git a/src/vector.h b/src/vector.h index 6bf6d81a..142bb077 100644 --- a/src/vector.h +++ b/src/vector.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef VECTOR_H -#define VECTOR_H +#pragma once #include <cmath> @@ -183,5 +182,3 @@ inline bool operator == (const Vector &a, const Vector &b) * Appends a string representation of a vector to the output stream. */ std::ostream& operator <<(std::ostream &os, const Vector &v); - -#endif // VECTOR_H diff --git a/src/video.h b/src/video.h index c98434e6..47aed627 100644 --- a/src/video.h +++ b/src/video.h @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef VIDEO_H -#define VIDEO_H +#pragma once #include "graphics.h" @@ -113,5 +112,3 @@ private: std::unique_ptr<Graphics> mGraphics; SDL_Window *mWindow = nullptr; }; - -#endif // VIDEO_H |