diff options
author | Thorbjørn Lindeijer <bjorn@lindeijer.nl> | 2024-10-01 17:59:04 +0200 |
---|---|---|
committer | Thorbjørn Lindeijer <bjorn@lindeijer.nl> | 2024-10-08 21:01:45 +0200 |
commit | 59a7d5c58f8b3af21b3e19d4e78f5653bf011bfb (patch) | |
tree | 536f6b80e359922172e671fbbbeb2b7bc5d3f165 /src | |
parent | e115390f18734d9e3e0e2fc5e1ed05f44c9fdb60 (diff) | |
download | mana-59a7d5c58f8b3af21b3e19d4e78f5653bf011bfb.tar.gz mana-59a7d5c58f8b3af21b3e19d4e78f5653bf011bfb.tar.bz2 mana-59a7d5c58f8b3af21b3e19d4e78f5653bf011bfb.tar.xz mana-59a7d5c58f8b3af21b3e19d4e78f5653bf011bfb.zip |
Added convenient and efficient Timer class
The Timer is efficient because it does not depend on incrementing a
counter to keep track of time, nor does it call SDL_GetTicks every time
its state is checked (this happens once per frame instead).
Along with global functions Time::absoluteTimeMs() and
Time::deltaTimeMs(), this replaces previous globals tick_time, cur_time
and get_elapsed_time().
For now, there is still a fixed 100 times per second logic call rate,
but the new Time::deltaTimeMs() function should allow getting rid of
this.
Diffstat (limited to 'src')
-rw-r--r-- | src/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/actorsprite.cpp | 7 | ||||
-rw-r--r-- | src/being.cpp | 10 | ||||
-rw-r--r-- | src/being.h | 4 | ||||
-rw-r--r-- | src/client.cpp | 64 | ||||
-rw-r--r-- | src/client.h | 20 | ||||
-rw-r--r-- | src/game.cpp | 2 | ||||
-rw-r--r-- | src/gui/gui.cpp | 4 | ||||
-rw-r--r-- | src/gui/gui.h | 4 | ||||
-rw-r--r-- | src/gui/ministatuswindow.cpp | 5 | ||||
-rw-r--r-- | src/gui/palette.cpp | 146 | ||||
-rw-r--r-- | src/gui/palette.h | 10 | ||||
-rw-r--r-- | src/gui/socialwindow.cpp | 6 | ||||
-rw-r--r-- | src/gui/socialwindow.h | 4 | ||||
-rw-r--r-- | src/gui/viewport.cpp | 100 | ||||
-rw-r--r-- | src/gui/viewport.h | 8 | ||||
-rw-r--r-- | src/gui/widgets/browserbox.cpp | 8 | ||||
-rw-r--r-- | src/gui/widgets/browserbox.h | 4 | ||||
-rw-r--r-- | src/localplayer.cpp | 64 | ||||
-rw-r--r-- | src/localplayer.h | 10 | ||||
-rw-r--r-- | src/map.cpp | 15 | ||||
-rw-r--r-- | src/net/tmwa/playerhandler.cpp | 9 | ||||
-rw-r--r-- | src/resources/ambientlayer.cpp | 4 | ||||
-rw-r--r-- | src/resources/userpalette.h | 2 | ||||
-rw-r--r-- | src/utils/time.cpp | 63 | ||||
-rw-r--r-- | src/utils/time.h | 106 |
26 files changed, 388 insertions, 293 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9ced2c13..d5d512e8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -381,6 +381,8 @@ set(SRCS utils/specialfolder.h utils/stringutils.cpp utils/stringutils.h + utils/time.cpp + utils/time.h utils/mutex.h utils/mkdir.cpp utils/mkdir.h diff --git a/src/actorsprite.cpp b/src/actorsprite.cpp index 62c5d1dc..20e724f5 100644 --- a/src/actorsprite.cpp +++ b/src/actorsprite.cpp @@ -20,7 +20,6 @@ #include "actorsprite.h" -#include "client.h" #include "configuration.h" #include "event.h" #include "imagesprite.h" @@ -35,6 +34,8 @@ #include "resources/resourcemanager.h" #include "resources/theme.h" +#include "utils/time.h" + #include <cassert> #define EFFECTS_FILE "effects.xml" @@ -80,7 +81,7 @@ bool ActorSprite::draw(Graphics *graphics, int offsetX, int offsetY) const if (mUsedTargetCursor) { mUsedTargetCursor->reset(); - mUsedTargetCursor->update(tick_time * MILLISECONDS_IN_A_TICK); + mUsedTargetCursor->update(Time::absoluteTimeMs()); mUsedTargetCursor->draw(graphics, px, py); } @@ -101,7 +102,7 @@ bool ActorSprite::drawSpriteAt(Graphics *graphics, int x, int y) const void ActorSprite::logic() { // Update sprite animations - update(tick_time * MILLISECONDS_IN_A_TICK); + update(Time::absoluteTimeMs()); // Restart status/particle effects, if needed if (mMustResetParticles) diff --git a/src/being.cpp b/src/being.cpp index 95f7c0d5..88104d74 100644 --- a/src/being.cpp +++ b/src/being.cpp @@ -643,7 +643,7 @@ void Being::setAction(Action action, int attackId) } if (currentAction != SpriteAction::MOVE) - mActionTime = tick_time; + mActionTimer.set(); } void Being::lookAt(const Vector &destPos) @@ -865,13 +865,9 @@ 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() && - get_elapsed_time(mActionTime) > std::max(getDuration(), 1500)) - { - - if (getType() != PLAYER) + if (!isAlive() && Net::getGameHandler()->removeDeadBeings() && getType() != PLAYER) + if (mActionTimer.elapsed() > std::max(getDuration(), 1500)) actorSpriteManager->scheduleDelete(this); - } } void Being::drawSpeech(int offsetX, int offsetY) diff --git a/src/being.h b/src/being.h index 3bd66b06..c8e8a87b 100644 --- a/src/being.h +++ b/src/being.h @@ -29,6 +29,8 @@ #include "position.h" #include "vector.h" +#include "utils/time.h" + #include <guichan/color.hpp> #include <map> @@ -484,7 +486,7 @@ class Being : public ActorSprite, public EventListener const BeingInfo *mInfo; - int mActionTime = 0; /**< Time spent in current action. TODO: Remove use of it */ + Timer mActionTimer; /**< Time spent in current action. TODO: Remove use of it */ /** Time until the last speech sentence disappears */ int mSpeechTime = 0; diff --git a/src/client.cpp b/src/client.cpp index e98006a8..6137a88a 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -70,8 +70,11 @@ #include "utils/gettext.h" #include "utils/mkdir.h" +#if defined(_WIN32) || defined(__APPLE__) #include "utils/specialfolder.h" +#endif #include "utils/stringutils.h" +#include "utils/time.h" #include <physfs.h> #include <SDL_image.h> @@ -84,12 +87,6 @@ #include <sys/stat.h> #include <cassert> -/** - * Tells the max tick value, - * setting it back to zero (and start again). - */ -static const int MAX_TICK_VALUE = 10000; - // TODO: Get rid fo these globals std::string errorMessage; LoginData loginData; @@ -109,23 +106,8 @@ HairDB hairDB; /**< Hair styles and colors info database */ Sound sound; -volatile int tick_time; /**< Tick counter */ volatile int fps = 0; /**< Frames counted in the last second */ volatile int frame_count = 0; /**< Counts the frames during one second */ -volatile int cur_time; - -/** - * Advances game logic counter. - * Called every 10 milliseconds by SDL_AddTimer() - * @see MILLISECONDS_IN_A_TICK value - */ -Uint32 nextTick(Uint32 interval, void *param) -{ - tick_time++; - if (tick_time == MAX_TICK_VALUE) - tick_time = 0; - return interval; -} /** * Updates fps. @@ -139,27 +121,15 @@ Uint32 nextSecond(Uint32 interval, void *param) return interval; } -int get_elapsed_time(int startTime) -{ - if (startTime <= tick_time) - return (tick_time - startTime) * MILLISECONDS_IN_A_TICK; - - return (tick_time + (MAX_TICK_VALUE - startTime)) * MILLISECONDS_IN_A_TICK; -} - bool isDoubleClick(int selected) { - const Uint32 maximumDelay = 500; - static Uint32 lastTime = 0; + static Timer timer; static int lastSelected = -1; - if (selected == lastSelected && lastTime + maximumDelay >= SDL_GetTicks()) - { - lastTime = 0; + if (selected == lastSelected && !timer.passed()) return true; - } - lastTime = SDL_GetTicks(); + timer.set(500); lastSelected = selected; return false; } @@ -419,9 +389,7 @@ Client::Client(const Options &options): if (mState != STATE_ERROR) mState = STATE_CHOOSE_SERVER; - // Initialize logic and seconds counters - tick_time = 0; - mLogicCounterId = SDL_AddTimer(MILLISECONDS_IN_A_TICK, nextTick, nullptr); + // Initialize seconds counter mSecondsCounterId = SDL_AddTimer(1000, nextSecond, nullptr); listen(Event::ConfigChannel); @@ -437,7 +405,6 @@ Client::Client(const Options &options): Client::~Client() { - SDL_RemoveTimer(mLogicCounterId); SDL_RemoveTimer(mSecondsCounterId); // Unload XML databases @@ -473,12 +440,18 @@ Client::~Client() int Client::exec() { - int lastTickTime = tick_time; + Time::beginFrame(); // Prevent startup lag influencing the first frame + + // Tick timer, used until logic has been updated to use Time::deltaTimeMs + Timer tickTimer; + tickTimer.set(); SDL_Event event; while (mState != STATE_EXIT) { + Time::beginFrame(); + if (mGame) { // Let the game handle the events while it is active @@ -512,19 +485,16 @@ int Client::exec() if (Net::getGeneralHandler()) Net::getGeneralHandler()->flushNetwork(); - while (get_elapsed_time(lastTickTime) > 0) + while (tickTimer.passed()) { gui->logic(); if (mGame) mGame->logic(); - sound.logic(); - - ++lastTickTime; + tickTimer.extend(MILLISECONDS_IN_A_TICK); } - // This is done because at some point tick_time will wrap. - lastTickTime = tick_time; + sound.logic(); // Update the screen when application is active, delay otherwise. if (isActive()) diff --git a/src/client.h b/src/client.h index 49780a38..a2371a1c 100644 --- a/src/client.h +++ b/src/client.h @@ -40,35 +40,16 @@ class LoginData; class Window; class QuitDialog; -/** - * Set the milliseconds value of a tick time. - */ -static const int MILLISECONDS_IN_A_TICK = 10; - //manaserv uses 9601 //static const short DEFAULT_PORT = 9601; static const short DEFAULT_PORT = 6901; extern volatile int fps; -extern volatile int tick_time; -extern volatile int cur_time; extern std::string errorMessage; extern LoginData loginData; /** - * @param startTime The value to check in client ticks. - * - * @return the elapsed time in milliseconds. - * between startTime and the current client tick value. - * - * @warning This function can't handle delays > 100 seconds. - * @see MILLISECONDS_IN_A_TICK - * @see tick_time - */ -int get_elapsed_time(int startTime); - -/** * Returns whether this call and the last call were done for the same * selected index and within a short time. */ @@ -256,7 +237,6 @@ private: SDL_Surface *mIcon = nullptr; - SDL_TimerID mLogicCounterId = 0; SDL_TimerID mSecondsCounterId = 0; int mFpsLimit = 0; diff --git a/src/game.cpp b/src/game.cpp index a9199817..13928036 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -343,8 +343,6 @@ void Game::logic() if (mCurrentMap) mCurrentMap->update(); - cur_time = time(nullptr); - // Handle network stuff if (!Net::getGameHandler()->isConnected() && !mDisconnected) { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 26d0913e..2ff697c7 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -158,7 +158,7 @@ Gui::~Gui() void Gui::logic() { // Hide mouse cursor after extended inactivity - if (get_elapsed_time(mLastMouseActivityTime) > 15000) + if (mMouseActivityTimer.passed()) SDL_ShowCursor(SDL_DISABLE); Palette::advanceGradients(); @@ -239,7 +239,7 @@ void Gui::updateCursor() void Gui::handleMouseMoved(const gcn::MouseInput &mouseInput) { gcn::Gui::handleMouseMoved(mouseInput); - mLastMouseActivityTime = tick_time; + mMouseActivityTimer.set(15000); // Make sure the cursor is visible SDL_ShowCursor(SDL_ENABLE); diff --git a/src/gui/gui.h b/src/gui/gui.h index 7c3c3afd..fd1dcf94 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -25,6 +25,8 @@ #include "eventlistener.h" #include "guichanfwd.h" +#include "utils/time.h" + #include <guichan/gui.hpp> #include <SDL.h> @@ -137,7 +139,7 @@ class Gui final : public gcn::Gui, public EventListener float mCustomCursorScale = 1.0f; std::vector<SDL_Cursor *> mSystemMouseCursors; std::vector<SDL_Cursor *> mCustomMouseCursors; - int mLastMouseActivityTime = 0; + Timer mMouseActivityTimer; Cursor mCursorType = Cursor::POINTER; }; diff --git a/src/gui/ministatuswindow.cpp b/src/gui/ministatuswindow.cpp index d3e01bd2..e22172bc 100644 --- a/src/gui/ministatuswindow.cpp +++ b/src/gui/ministatuswindow.cpp @@ -43,8 +43,7 @@ #include "utils/gettext.h" #include "utils/stringutils.h" - -extern volatile int tick_time; +#include "utils/time.h" MiniStatusWindow::MiniStatusWindow(): Popup("MiniStatus") @@ -227,7 +226,7 @@ void MiniStatusWindow::logic() for (auto &icon : mIcons) if (icon) - icon->update(tick_time * 10); + icon->update(Time::absoluteTimeMs()); } void MiniStatusWindow::mouseMoved(gcn::MouseEvent &event) diff --git a/src/gui/palette.cpp b/src/gui/palette.cpp index aaa3165c..6c6e6f06 100644 --- a/src/gui/palette.cpp +++ b/src/gui/palette.cpp @@ -22,18 +22,11 @@ #include "palette.h" -#include "configuration.h" -#include "client.h" - -#include "gui/gui.h" - -#include "utils/gettext.h" -#include "utils/stringutils.h" - #include <cmath> -static const double PI = 3.14159265; +static constexpr double PI = 3.14159265; const gcn::Color Palette::BLACK = gcn::Color(0, 0, 0); +Timer Palette::mRainbowTimer; Palette::Palettes Palette::mInstances; const gcn::Color Palette::RAINBOW_COLORS[7] = { @@ -49,7 +42,6 @@ const gcn::Color Palette::RAINBOW_COLORS[7] = { const int Palette::RAINBOW_COLOR_COUNT = 7; Palette::Palette(int size) : - mRainbowTime(tick_time), mColors(size) { mInstances.insert(this); @@ -76,97 +68,85 @@ const gcn::Color &Palette::getColor(char c, bool &valid) void Palette::advanceGradients() { - auto it = mInstances.begin(); - auto it_end = mInstances.end(); + const int advance = mRainbowTimer.elapsed() / 5; + if (advance <= 0) + return; - for (; it != it_end; it++) - { - (*it)->advanceGradient(); - } + mRainbowTimer.extend(advance * 5); + + for (auto palette : mInstances) + palette->advanceGradient(advance); } -void Palette::advanceGradient() +void Palette::advanceGradient(int advance) { - if (get_elapsed_time(mRainbowTime) > 5) + for (auto elem : mGradVector) { - int pos, colIndex, colVal, delay, numOfColors; - // For slower systems, advance can be greater than one (advance > 1 - // skips advance-1 steps). Should make gradient look the same - // independent of the framerate. - int advance = get_elapsed_time(mRainbowTime) / 5; - double startColVal, destColVal; - - for (auto &elem : mGradVector) - { - delay = elem->delay; + int delay = elem->delay; - if (elem->grad == PULSE) - delay = delay / 20; + if (elem->grad == PULSE) + delay = delay / 20; - numOfColors = (elem->grad == SPECTRUM ? 6 : - elem->grad == PULSE ? 127 : - RAINBOW_COLOR_COUNT); + const int numOfColors = (elem->grad == SPECTRUM ? 6 : + elem->grad == PULSE ? 127 : + RAINBOW_COLOR_COUNT); - elem->gradientIndex = - (elem->gradientIndex + advance) % - (delay * numOfColors); + elem->gradientIndex = (elem->gradientIndex + advance) % + (delay * numOfColors); - pos = elem->gradientIndex % delay; - colIndex = elem->gradientIndex / delay; + const int pos = elem->gradientIndex % delay; + const int colIndex = elem->gradientIndex / delay; - if (elem->grad == PULSE) - { - colVal = (int) (255.0 * sin(PI * colIndex / numOfColors)); + if (elem->grad == PULSE) + { + const int colVal = (int) (255.0 * sin(PI * colIndex / numOfColors)); + const gcn::Color &col = elem->testColor; - const gcn::Color &col = elem->testColor; + elem->color.r = ((colVal * col.r) / 255) % (col.r + 1); + elem->color.g = ((colVal * col.g) / 255) % (col.g + 1); + elem->color.b = ((colVal * col.b) / 255) % (col.b + 1); + } + if (elem->grad == SPECTRUM) + { + int colVal; - elem->color.r = ((colVal * col.r) / 255) % (col.r + 1); - elem->color.g = ((colVal * col.g) / 255) % (col.g + 1); - elem->color.b = ((colVal * col.b) / 255) % (col.b + 1); + if (colIndex % 2) + { // falling curve + colVal = (int)(255.0 * (cos(PI * pos / delay) + 1) / 2); } - if (elem->grad == SPECTRUM) - { - if (colIndex % 2) - { // falling curve - colVal = (int)(255.0 * (cos(PI * pos / delay) + 1) / 2); - } - else - { // ascending curve - colVal = (int)(255.0 * (cos(PI * (delay - pos) / delay) + - 1) / 2); - } - - elem->color.r = - (colIndex == 0 || colIndex == 5) ? 255 : - (colIndex == 1 || colIndex == 4) ? colVal : 0; - elem->color.g = - (colIndex == 1 || colIndex == 2) ? 255 : - (colIndex == 0 || colIndex == 3) ? colVal : 0; - elem->color.b = - (colIndex == 3 || colIndex == 4) ? 255 : - (colIndex == 2 || colIndex == 5) ? colVal : 0; + else + { // ascending curve + colVal = (int)(255.0 * (cos(PI * (delay - pos) / delay) + + 1) / 2); } - else if (elem->grad == RAINBOW) - { - const gcn::Color &startCol = RAINBOW_COLORS[colIndex]; - const gcn::Color &destCol = - RAINBOW_COLORS[(colIndex + 1) % numOfColors]; - startColVal = (cos(PI * pos / delay) + 1) / 2; - destColVal = 1 - startColVal; + elem->color.r = + (colIndex == 0 || colIndex == 5) ? 255 : + (colIndex == 1 || colIndex == 4) ? colVal : 0; + elem->color.g = + (colIndex == 1 || colIndex == 2) ? 255 : + (colIndex == 0 || colIndex == 3) ? colVal : 0; + elem->color.b = + (colIndex == 3 || colIndex == 4) ? 255 : + (colIndex == 2 || colIndex == 5) ? colVal : 0; + } + else if (elem->grad == RAINBOW) + { + const gcn::Color &startCol = RAINBOW_COLORS[colIndex]; + const gcn::Color &destCol = + RAINBOW_COLORS[(colIndex + 1) % numOfColors]; - elem->color.r =(int)(startColVal * startCol.r + - destColVal * destCol.r); + const double startColVal = (cos(PI * pos / delay) + 1) / 2; + const double destColVal = 1 - startColVal; - elem->color.g =(int)(startColVal * startCol.g + - destColVal * destCol.g); + elem->color.r =(int)(startColVal * startCol.r + + destColVal * destCol.r); - elem->color.b =(int)(startColVal * startCol.b + - destColVal * destCol.b); - } - } + elem->color.g =(int)(startColVal * startCol.g + + destColVal * destCol.g); - if (advance) - mRainbowTime = tick_time; + elem->color.b =(int)(startColVal * startCol.b + + destColVal * destCol.b); + } } } diff --git a/src/gui/palette.h b/src/gui/palette.h index 41378a43..b81d8865 100644 --- a/src/gui/palette.h +++ b/src/gui/palette.h @@ -23,6 +23,8 @@ #ifndef PALETTE_H #define PALETTE_H +#include "utils/time.h" + #include <guichan/color.hpp> #include <cstdlib> @@ -121,8 +123,8 @@ class Palette static const gcn::Color RAINBOW_COLORS[]; static const int RAINBOW_COLOR_COUNT; - /** Time tick, that gradient-type colors were updated the last time. */ - int mRainbowTime; + /** Timer used when updating gradient-type colors. */ + static Timer mRainbowTimer; using Palettes = std::set<Palette *>; static Palettes mInstances; @@ -131,7 +133,7 @@ class Palette ~Palette(); - void advanceGradient(); + void advanceGradient(int advance); struct ColorElem { @@ -147,7 +149,7 @@ class Palette int delay; int committedDelay; - void set(int type, gcn::Color &color, GradientType grad, int delay) + void set(int type, const gcn::Color &color, GradientType grad, int delay) { ColorElem::type = type; ColorElem::color = color; diff --git a/src/gui/socialwindow.cpp b/src/gui/socialwindow.cpp index 76235306..79a0fd53 100644 --- a/src/gui/socialwindow.cpp +++ b/src/gui/socialwindow.cpp @@ -262,7 +262,7 @@ private: class PlayerListTab : public SocialTab { public: - PlayerListTab() + PlayerListTab() { mPlayerList = new PlayerList; @@ -679,10 +679,10 @@ void SocialWindow::setPlayersOnline(const std::vector<Avatar*> &players) void SocialWindow::logic() { - if (mLastOnlineListUpdate == 0 || get_elapsed_time(mLastOnlineListUpdate) >= 18000) + if (mOnlineListUpdateTimer.passed()) { Net::getChatHandler()->requestOnlineList(); - mLastOnlineListUpdate = tick_time; + mOnlineListUpdateTimer.set(18000); } Window::logic(); diff --git a/src/gui/socialwindow.h b/src/gui/socialwindow.h index b6865fb2..350f919c 100644 --- a/src/gui/socialwindow.h +++ b/src/gui/socialwindow.h @@ -21,6 +21,8 @@ #ifndef SOCIALWINDOW_H #define SOCIALWINDOW_H +#include "utils/time.h" + #include "gui/widgets/window.h" #include <guichan/actionevent.hpp> @@ -87,7 +89,7 @@ protected: void updateButtons(); int mGuildInvited = 0; - int mLastOnlineListUpdate = 0; + Timer mOnlineListUpdateTimer; ConfirmDialog *mGuildAcceptDialog = nullptr; TextDialog *mGuildCreateDialog = nullptr; diff --git a/src/gui/viewport.cpp b/src/gui/viewport.cpp index cdc4076e..8dbdf7c7 100644 --- a/src/gui/viewport.cpp +++ b/src/gui/viewport.cpp @@ -79,8 +79,6 @@ void Viewport::setMap(Map *map) void Viewport::draw(gcn::Graphics *gcnGraphics) { - static int lastTick = tick_time; - // Check whether map was successfully loaded since // the rest of this function relies on it if (!mMap || !local_player) @@ -95,12 +93,6 @@ void Viewport::draw(gcn::Graphics *gcnGraphics) auto *graphics = static_cast<Graphics*>(gcnGraphics); - // Avoid freaking out when tick_time overflows - if (tick_time < lastTick) - { - lastTick = tick_time; - } - // Calculate viewpoint int midTileX = (graphics->getWidth() + mScrollCenterOffsetX) / 2; int midTileY = (graphics->getHeight() + mScrollCenterOffsetX) / 2; @@ -109,49 +101,57 @@ void Viewport::draw(gcn::Graphics *gcnGraphics) const int player_x = (int) playerPos.x - midTileX; const int player_y = (int) playerPos.y - midTileY; - if (mScrollLaziness < 1) - mScrollLaziness = 1; // Avoids division by zero + const float ticks = Time::deltaTimeMs() / static_cast<float>(MILLISECONDS_IN_A_TICK); + float scrollFraction = 1.0f; - while (lastTick < tick_time) + if (mScrollLaziness > 1) { - // Apply lazy scrolling - if (player_x > mPixelViewX + mScrollRadius) - { - mPixelViewX += (player_x - mPixelViewX - mScrollRadius) / - mScrollLaziness; - } - if (player_x < mPixelViewX - mScrollRadius) - { - mPixelViewX += (player_x - mPixelViewX + mScrollRadius) / - mScrollLaziness; - } - if (player_y > mPixelViewY + mScrollRadius) - { - mPixelViewY += (player_y - mPixelViewY - mScrollRadius) / - mScrollLaziness; - } - if (player_y < mPixelViewY - mScrollRadius) - { - mPixelViewY += (player_y - mPixelViewY + mScrollRadius) / - mScrollLaziness; - } + // mScrollLaziness defines the fraction of the desired camera movement + // that is applied every 10ms. To make this work independently of the + // frame duration, we calculate the actual scroll fraction based on the + // time delta. + scrollFraction = 1.0f - std::pow(1.0f - 1.0f / mScrollLaziness, ticks); + } + + // Apply lazy scrolling + if (player_x > mPixelViewX + mScrollRadius) + { + mPixelViewX += (player_x - mPixelViewX - mScrollRadius) * + scrollFraction; + } + if (player_x < mPixelViewX - mScrollRadius) + { + mPixelViewX += (player_x - mPixelViewX + mScrollRadius) * + scrollFraction; + } + if (player_y > mPixelViewY + mScrollRadius) + { + mPixelViewY += (player_y - mPixelViewY - mScrollRadius) * + scrollFraction; + } + if (player_y < mPixelViewY - mScrollRadius) + { + mPixelViewY += (player_y - mPixelViewY + mScrollRadius) * + scrollFraction; + } + + // manage shake effect + for (auto i = mShakeEffects.begin(); i != mShakeEffects.end(); i++) + { + // The decay defines the reduction in amplitude per 10ms. Here + // we calculate the reduction based on the ticks. + const float decay = std::pow(i->decay, ticks); + + // apply the effect to viewport + mPixelViewX += i->x *= -decay; + mPixelViewY += i->y *= -decay; - // manage shake effect - for (auto i = mShakeEffects.begin(); - i != mShakeEffects.end(); - i++) + // check death conditions + if (std::abs(i->x) + std::abs(i->y) < 1.0f || + (i->timer.isSet() && i->timer.passed())) { - // apply the effect to viewport - mPixelViewX += i->x *= -i->decay; - mPixelViewY += i->y *= -i->decay; - // check death conditions - if (abs(i->x) + abs(i->y) < 1.0f || - (i->duration > 0 && --i->duration == 0)) - { - i = mShakeEffects.erase(i); - } + i = mShakeEffects.erase(i); } - lastTick++; } // Auto center when player is off screen @@ -264,7 +264,9 @@ void Viewport::shakeScreen(float x, float y, float decay, unsigned duration) effect.x = x; effect.y = y; effect.decay = decay; - effect.duration = duration; + + if (duration > 0) + effect.timer.set(duration * MILLISECONDS_IN_A_TICK); } void Viewport::logic() @@ -528,9 +530,9 @@ void Viewport::mouseDragged(gcn::MouseEvent &event) if (mPlayerFollowMouse && !event.isShiftPressed()) { - if (get_elapsed_time(mLocalWalkTime) >= walkingMouseDelay) + if (mLocalWalkTimer.passed()) { - mLocalWalkTime = tick_time; + mLocalWalkTimer.set(walkingMouseDelay); local_player->setDestination(event.getX() + (int) mPixelViewX, event.getY() + (int) mPixelViewY); local_player->pathSetByMouse(); diff --git a/src/gui/viewport.h b/src/gui/viewport.h index 32d9bd5c..53edb50f 100644 --- a/src/gui/viewport.h +++ b/src/gui/viewport.h @@ -25,6 +25,8 @@ #include "eventlistener.h" #include "position.h" +#include "utils/time.h" + #include "gui/widgets/windowcontainer.h" #include <guichan/mouselistener.hpp> @@ -207,15 +209,15 @@ class Viewport : public WindowContainer, public gcn::MouseListener, float x; float y; float decay; - unsigned duration; + Timer timer; }; std::list<ShakeEffect> mShakeEffects; bool mPlayerFollowMouse = false; - int mLocalWalkTime = -1; /**< Timestamp before the next walk can be sent. */ + Timer mLocalWalkTimer; /**< Timer for sending walk messages. */ - PopupMenu *mPopupMenu; /**< Popup menu. */ + PopupMenu *mPopupMenu; /**< Popup menu. */ Being *mHoverBeing = nullptr; /**< Being mouse is currently over. */ FloorItem *mHoverItem = nullptr; /**< FloorItem mouse is currently over. */ BeingPopup *mBeingPopup; /**< Being information popup. */ diff --git a/src/gui/widgets/browserbox.cpp b/src/gui/widgets/browserbox.cpp index 40c9b66a..9eee9448 100644 --- a/src/gui/widgets/browserbox.cpp +++ b/src/gui/widgets/browserbox.cpp @@ -22,8 +22,6 @@ #include "gui/widgets/browserbox.h" -#include "client.h" - #include "gui/gui.h" #include "gui/truetypefont.h" #include "gui/widgets/linkhandler.h" @@ -32,6 +30,8 @@ #include "resources/iteminfo.h" #include "resources/theme.h" +#include "utils/stringutils.h" + #include <guichan/graphics.hpp> #include <guichan/font.hpp> #include <guichan/cliprectangle.hpp> @@ -276,7 +276,7 @@ void BrowserBox::relayoutText() layoutTextRow(row, context); mLastLayoutWidth = getWidth(); - mLastLayoutTime = tick_time; + mLayoutTimer.set(100); setHeight(context.y); } @@ -507,7 +507,7 @@ void BrowserBox::maybeRelayoutText() { // Reduce relayouting frequency when there is a lot of text if (mTextRows.size() > 100) - if (mLastLayoutTime && std::abs(mLastLayoutTime - tick_time) < 10) + if (!mLayoutTimer.passed()) return; relayoutText(); diff --git a/src/gui/widgets/browserbox.h b/src/gui/widgets/browserbox.h index d7ff0428..7278bb59 100644 --- a/src/gui/widgets/browserbox.h +++ b/src/gui/widgets/browserbox.h @@ -23,6 +23,8 @@ #ifndef BROWSERBOX_H #define BROWSERBOX_H +#include "utils/time.h" + #include <guichan/mouselistener.hpp> #include <guichan/widget.hpp> @@ -193,7 +195,7 @@ class BrowserBox : public gcn::Widget, std::optional<BrowserLink> mHoveredLink; unsigned int mMaxRows = 0; int mLastLayoutWidth = 0; - int mLastLayoutTime = -1; + Timer mLayoutTimer; }; #endif diff --git a/src/localplayer.cpp b/src/localplayer.cpp index c6031e49..a7969fc3 100644 --- a/src/localplayer.cpp +++ b/src/localplayer.cpp @@ -21,7 +21,6 @@ #include "localplayer.h" -#include "client.h" #include "configuration.h" #include "event.h" #include "flooritem.h" @@ -50,7 +49,10 @@ #include "utils/gettext.h" #include "utils/stringutils.h" -const int AWAY_LIMIT_TIMER = 60; +constexpr unsigned AWAY_MESSAGE_TIMEOUT = 60 * 1000; + +// Actions are allowed at 5.5 per second +constexpr unsigned ACTION_TIMEOUT = 182; LocalPlayer *local_player = nullptr; @@ -75,10 +77,6 @@ LocalPlayer::~LocalPlayer() void LocalPlayer::logic() { - // Actions are allowed at 5.5 per second - if (get_elapsed_time(mLastActionTime) >= 182) - mLastActionTime = -1; - // Show XP messages if (!mMessages.empty()) { @@ -101,10 +99,6 @@ void LocalPlayer::logic() PlayerInfo::logic(); - // Targeting allowed 4 times a second - if (get_elapsed_time(mLastTargetTime) >= 250) - mLastTargetTime = -1; - if (mTarget) { ActorSprite::Type targetType = mTarget->getType(); @@ -173,7 +167,7 @@ void LocalPlayer::setAction(Action action, int attackId) { if (action == DEAD) { - mLastTargetTime = -1; + mLastTargetTimer.reset(); setTarget(nullptr); } @@ -625,24 +619,18 @@ Being *LocalPlayer::getTarget() const void LocalPlayer::setTarget(Being *target) { - if ((mLastTargetTime != -1 || target == this) && target) + if ((!mLastTargetTimer.passed() || target == this) && target) return; + // Targeting allowed 4 times a second if (target) - mLastTargetTime = tick_time; + mLastTargetTimer.set(250); if (target == mTarget) return; - if (target || mAction == ATTACK) - { - mTargetTime = tick_time; - } - else - { + if (!target && mAction != ATTACK) mKeepAttacking = false; - mTargetTime = -1; - } Being *oldTarget = nullptr; if (mTarget) @@ -793,9 +781,9 @@ void LocalPlayer::stopWalking(bool sendToServer) void LocalPlayer::toggleSit() { - if (mLastActionTime != -1) + if (!mLastActionTimer.passed()) return; - mLastActionTime = tick_time; + mLastActionTimer.set(ACTION_TIMEOUT); Being::Action newAction; switch (mAction) @@ -810,16 +798,16 @@ void LocalPlayer::toggleSit() void LocalPlayer::emote(int emoteId) { - if (mLastActionTime != -1) + if (!mLastActionTimer.passed()) return; - mLastActionTime = tick_time; + mLastActionTimer.set(ACTION_TIMEOUT); Net::getPlayerHandler()->emote(emoteId); } void LocalPlayer::attack(Being *target, bool keep) { - if (mLastActionTime != -1) + if (!mLastActionTimer.passed()) return; // Can only attack when standing still @@ -830,23 +818,23 @@ void LocalPlayer::attack(Being *target, bool keep) return; // Can't attack more times than its attack speed - static int lastAttackTime = 0; - if (get_elapsed_time(lastAttackTime) < mAttackSpeed) + static Timer lastAttackTimer; + if (!lastAttackTimer.passed()) return; - lastAttackTime = tick_time; + lastAttackTimer.set(mAttackSpeed); mKeepAttacking = keep; if (mTarget != target || !mTarget) { - mLastTargetTime = -1; + mLastTargetTimer.reset(); setTarget(target); } lookAt(mTarget->getPosition()); - mLastActionTime = tick_time; + mLastActionTimer.set(ACTION_TIMEOUT); setAction(ATTACK); @@ -872,7 +860,7 @@ void LocalPlayer::stopAttack() setAction(STAND); setTarget(nullptr); } - mLastTargetTime = -1; + mLastTargetTimer.reset(); cancelGoToTarget(); } @@ -964,7 +952,7 @@ void LocalPlayer::setGotoTarget(Being *target) if (!target) return; - mLastTargetTime = -1; + mLastTargetTimer.reset(); setTarget(target); mGoingToTarget = true; @@ -1039,7 +1027,8 @@ void LocalPlayer::event(Event::Channel channel, const Event &event) void LocalPlayer::changeAwayMode() { mAwayMode = !mAwayMode; - mAfkTime = 0; + mAfkTimer.reset(); + if (mAwayMode) { mAwayDialog = new OkDialog(_("Away"), @@ -1061,9 +1050,7 @@ void LocalPlayer::afkRespond(ChatTab *tab, const std::string &nick) { if (mAwayMode) { - if (mAfkTime == 0 - || cur_time < mAfkTime - || cur_time - mAfkTime > AWAY_LIMIT_TIMER) + if (mAfkTimer.passed()) { std::string msg = "*AFK*: " + config.getValue("afkMessage", "I am away from keyboard"); @@ -1078,7 +1065,8 @@ void LocalPlayer::afkRespond(ChatTab *tab, const std::string &nick) { tab->chatLog(getName(), msg); } - mAfkTime = cur_time; + + mAfkTimer.set(AWAY_MESSAGE_TIMEOUT); } } } diff --git a/src/localplayer.h b/src/localplayer.h index 4e3bbc88..9d844339 100644 --- a/src/localplayer.h +++ b/src/localplayer.h @@ -26,6 +26,8 @@ #include "resources/userpalette.h" +#include "utils/time.h" + #include <guichan/actionlistener.hpp> class ChatTab; @@ -217,9 +219,7 @@ class LocalPlayer final : public Being int mAttackRange = -1; - int mTargetTime = -1; /**< How long the being has been targeted **/ - /** Time stamp of last targeting action, -1 if none. */ - int mLastTargetTime = -1; + Timer mLastTargetTimer; /**< Timer for last targeting action. */ int mGMLevel = 0; @@ -229,7 +229,7 @@ class LocalPlayer final : public Being bool mGoingToTarget = false; bool mKeepAttacking = false; /**< Whether or not to continue to attack */ - int mLastActionTime = -1; /**< Time stamp of the last action, -1 if none. */ + Timer mLastActionTimer; /**< Timeout for the last action. */ int mWalkingDir = 0; /**< The direction the player is walking in. */ bool mPathSetByMouse = false; /**< Tells if the path was set using mouse */ @@ -241,7 +241,7 @@ class LocalPlayer final : public Being AwayListener *mAwayListener; OkDialog *mAwayDialog = nullptr; - int mAfkTime = 0; + Timer mAfkTimer; bool mAwayMode = false; }; diff --git a/src/map.cpp b/src/map.cpp index e9d29b61..c4e4c3be 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -22,7 +22,6 @@ #include "map.h" #include "actorspritemanager.h" -#include "client.h" #include "configuration.h" #include "graphics.h" #include "log.h" @@ -34,6 +33,8 @@ #include "resources/image.h" #include "resources/resourcemanager.h" +#include "utils/time.h" + #include "net/net.h" #include "utils/dtor.h" @@ -247,8 +248,8 @@ void Map::initializeAmbientLayers() { auto &ambientLayer = list.emplace_back(img); ambientLayer.mParallax = getFloatProperty(name + "parallax"); - ambientLayer.mSpeedX = getFloatProperty(name + "scrollX"); - ambientLayer.mSpeedY = getFloatProperty(name + "scrollY"); + ambientLayer.mSpeedX = getFloatProperty(name + "scrollX") / MILLISECONDS_IN_A_TICK; + ambientLayer.mSpeedY = getFloatProperty(name + "scrollY") / MILLISECONDS_IN_A_TICK; ambientLayer.mMask = getIntProperty(name + "mask", 1); ambientLayer.mKeepRatio = getBoolProperty(name + "keepratio"); } @@ -433,8 +434,6 @@ void Map::drawCollision(Graphics *graphics, int scrollX, int scrollY, void Map::updateAmbientLayers(float scrollX, float scrollY) { - static int lastTick = tick_time; // static = only initialized at first call - if (mLastScrollX == 0.0f && mLastScrollY == 0.0f) { // First call - initialisation @@ -445,25 +444,23 @@ void Map::updateAmbientLayers(float scrollX, float scrollY) // Update Overlays float dx = scrollX - mLastScrollX; float dy = scrollY - mLastScrollY; - int timePassed = get_elapsed_time(lastTick); for (auto &background : mBackgrounds) { if ((background.mMask & mMask) == 0) continue; - background.update(timePassed, dx, dy); + background.update(Time::deltaTimeMs(), dx, dy); } for (auto &foreground : mForegrounds) { if ((foreground.mMask & mMask) == 0) continue; - foreground.update(timePassed, dx, dy); + foreground.update(Time::deltaTimeMs(), dx, dy); } mLastScrollX = scrollX; mLastScrollY = scrollY; - lastTick = tick_time; } void Map::drawAmbientLayers(Graphics *graphics, LayerType type, diff --git a/src/net/tmwa/playerhandler.cpp b/src/net/tmwa/playerhandler.cpp index 431137fd..c9baf27b 100644 --- a/src/net/tmwa/playerhandler.cpp +++ b/src/net/tmwa/playerhandler.cpp @@ -40,8 +40,9 @@ #include "net/tmwa/messageout.h" #include "net/tmwa/protocol.h" -#include "utils/stringutils.h" #include "utils/gettext.h" +#include "utils/stringutils.h" +#include "utils/time.h" extern OkDialog *weightNotice; extern OkDialog *deathNotice; @@ -563,17 +564,17 @@ void PlayerHandler::increaseSkill(int skillId) void PlayerHandler::pickUp(FloorItem *floorItem) { - static Uint32 lastTime = 0; + static Timer lastPickupTimer; // Avoid spamming the server with pick-up requests to prevent the player // from being kicked. - if (!floorItem || SDL_GetTicks() < lastTime + 100) + if (!floorItem || !lastPickupTimer.passed()) return; MessageOut outMsg(CMSG_ITEM_PICKUP); outMsg.writeInt32(floorItem->getId()); - lastTime = SDL_GetTicks(); + lastPickupTimer.set(100); } void PlayerHandler::setDirection(char direction) diff --git a/src/resources/ambientlayer.cpp b/src/resources/ambientlayer.cpp index b292070b..a238afcc 100644 --- a/src/resources/ambientlayer.cpp +++ b/src/resources/ambientlayer.cpp @@ -34,8 +34,8 @@ AmbientLayer::~AmbientLayer() = default; void AmbientLayer::update(int timePassed, float dx, float dy) { // Self scrolling of the overlay - mPosX -= mSpeedX * timePassed / 10; - mPosY -= mSpeedY * timePassed / 10; + mPosX -= mSpeedX * timePassed; + mPosY -= mSpeedY * timePassed; // Parallax scrolling mPosX += dx * mParallax; diff --git a/src/resources/userpalette.h b/src/resources/userpalette.h index 93056867..6eba44b2 100644 --- a/src/resources/userpalette.h +++ b/src/resources/userpalette.h @@ -90,7 +90,7 @@ class UserPalette : public Palette, public gcn::ListModel * @param type the color type requested * @param color the color that should be tested */ - void setTestColor(int type, gcn::Color color) + void setTestColor(int type, const gcn::Color &color) { mColors[type].testColor = color; } diff --git a/src/utils/time.cpp b/src/utils/time.cpp new file mode 100644 index 00000000..c89914fa --- /dev/null +++ b/src/utils/time.cpp @@ -0,0 +1,63 @@ +/* + * 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/>. + */ + +#include "time.h" + +#include <algorithm> + +#include <SDL.h> + +namespace Time +{ + +static uint32_t s_absoluteTimeMs; +static unsigned s_deltaTimeMs; + +uint32_t absoluteTimeMs() +{ + return s_absoluteTimeMs; +} + +unsigned deltaTimeMs() +{ + return s_deltaTimeMs; +} + +static int32_t getElapsedTime(uint32_t timeMs) +{ + return static_cast<int32_t>(s_absoluteTimeMs - timeMs); +} + +void beginFrame() +{ + // todo: Use SDL_GetTicks64 once we require SDL 2.0.18 or newer. + + const uint32_t previousTime = s_absoluteTimeMs; + s_absoluteTimeMs = SDL_GetTicks(); + s_deltaTimeMs = std::clamp(getElapsedTime(previousTime), 0, 1000); +} + +} // namespace Time + +int32_t Timer::elapsed() const +{ + return Time::getElapsedTime(mTimeout); +} diff --git a/src/utils/time.h b/src/utils/time.h new file mode 100644 index 00000000..ac7cd351 --- /dev/null +++ b/src/utils/time.h @@ -0,0 +1,106 @@ +/* + * 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 UTILS_TIME_H + #define UTILS_TIME_H + +#include <cstdint> + +/** + * The amount of milliseconds in a logic tick. + */ +static constexpr unsigned MILLISECONDS_IN_A_TICK = 10; + +namespace Time +{ + +/** + * The time in milliseconds since the program started (wraps around after ~49 + * days). + */ +uint32_t absoluteTimeMs(); + +/** + * The time in milliseconds since the last frame, maximized to 1000ms. + */ +unsigned deltaTimeMs(); + +/** + * Called at the start of each frame, updates the above variables. + */ +void beginFrame(); + +} // namespace Time + +/** + * Simple timer that can be used to check if a certain amount of time + * has passed. + */ +class Timer +{ +public: + Timer() = default; + + /** + * Sets the timer with an optional duration in milliseconds. + */ + void set(uint32_t ms = 0) + { mTimeout = Time::absoluteTimeMs() + ms; } + + /** + * Reset the timer. + */ + void reset() + { mTimeout = 0; } + + /** + * Returns whether the timer has been set. + */ + bool isSet() const + { return mTimeout != 0; } + + /** + * Returns whether the timer has passed. + * A timer that wasn't set will always return true. + */ + bool passed() const + { return elapsed() >= 0; } + + /** + * Extend the timer by the given number of milliseconds. + */ + void extend(uint32_t ms) + { mTimeout += ms; } + + /** + * Returns the number of milliseconds elapsed since the set time, or a + * negative value if the timer hasn't passed. + * + * Due to wrapping, this function is not suitable for measuring long + * periods of time (tens of days). + */ + int32_t elapsed() const; + +private: + uint32_t mTimeout = 0; +}; + +#endif // UTILS_TIME_H |