diff --git a/BUGS b/BUGS index 85655f5d..1562fb74 100644 --- a/BUGS +++ b/BUGS @@ -1,7 +1,8 @@ 2x2 corpses is drawn above players animatedtext is displayed in non visible floors attack while walking cancels the walk -trying to walking in follow doesn't cancel de walk +trying to walking while following doesn't cancel de walk +attacked/followed creatures are not cleared when it goes out of range if a spell is used while pressing arrows keys the walk is canceled and it doesnt starts walking again game map text message boxes is not displayed like tibia name/shields doesnt follow the creature when walking on parcels diff --git a/TODO b/TODO index 1f9ff81f..2bc16ac0 100644 --- a/TODO +++ b/TODO @@ -61,6 +61,7 @@ rework win32 key input system [bart] check for recursive anchors and print a error instead of crashing [bart] make api to enable/disable capture of events to avoid massive event processing [bart] review style apply system +rework styles rendering order == Client make possible to reload modules diff --git a/modules/addon_terminal/commands.lua b/modules/addon_terminal/commands.lua index 6a5bd03c..8bd241f0 100644 --- a/modules/addon_terminal/commands.lua +++ b/modules/addon_terminal/commands.lua @@ -29,3 +29,7 @@ function debugContainersItems() end end end + +function quit() + exit() +end \ No newline at end of file diff --git a/modules/core_widgets/uiwindow.lua b/modules/core_widgets/uiwindow.lua index d80b23c1..1b563ce7 100644 --- a/modules/core_widgets/uiwindow.lua +++ b/modules/core_widgets/uiwindow.lua @@ -16,3 +16,11 @@ function UIWindow:onKeyPress(keyCode, keyboardModifiers, wouldFilter) end end end + +function UIWindow:onMousePress(mousePos, mouseButton) + +end + +function UIWindow:onGeometryChange(oldRect, newRect) + +end diff --git a/modules/game/game.lua b/modules/game/game.lua index 08287f07..5a98490d 100644 --- a/modules/game/game.lua +++ b/modules/game/game.lua @@ -71,7 +71,7 @@ end local function onApplicationClose() print('close app') if Game.isOnline() then - Game.logout(false) + Game.logout(true) else exit() end diff --git a/modules/game_textmessage/textmessage.lua b/modules/game_textmessage/textmessage.lua index a79587f1..dc69742b 100644 --- a/modules/game_textmessage/textmessage.lua +++ b/modules/game_textmessage/textmessage.lua @@ -81,8 +81,7 @@ end -- hooked events local function onGameDeath() - TextMessage.displayEventAdvance('You are dead.', 10) - scheduleEvent(function() Game.logout(true) end, 10*1000) + TextMessage.displayEventAdvance('You are dead.', 60) end local function onGameTextMessage(msgtype, msg) diff --git a/src/framework/core/declarations.h b/src/framework/core/declarations.h index ff5ae6e4..4b5eb364 100644 --- a/src/framework/core/declarations.h +++ b/src/framework/core/declarations.h @@ -26,6 +26,11 @@ #include class Module; +class Event; +class ScheduledEvent; + typedef std::shared_ptr ModulePtr; +typedef std::shared_ptr EventPtr; +typedef std::shared_ptr ScheduledEventPtr; #endif diff --git a/src/framework/core/eventdispatcher.cpp b/src/framework/core/eventdispatcher.cpp index f5b52878..92bdb989 100644 --- a/src/framework/core/eventdispatcher.cpp +++ b/src/framework/core/eventdispatcher.cpp @@ -37,29 +37,34 @@ void EventDispatcher::flush() void EventDispatcher::poll() { while(!m_scheduledEventList.empty()) { - if(g_clock.ticks() < m_scheduledEventList.top().ticks) + ScheduledEventPtr scheduledEvent = m_scheduledEventList.top(); + if(scheduledEvent->reamaningTicks() > 0) break; - SimpleCallback callback = std::move(m_scheduledEventList.top().callback); m_scheduledEventList.pop(); - callback(); + scheduledEvent->execute(); } while(!m_eventList.empty()) { - m_eventList.front()(); + EventPtr event = m_eventList.front(); m_eventList.pop_front(); + event->execute(); } } -void EventDispatcher::scheduleEvent(const SimpleCallback& callback, int delay) +ScheduledEventPtr EventDispatcher::scheduleEvent(const SimpleCallback& callback, int delay) { assert(delay >= 0); - m_scheduledEventList.push(ScheduledEvent(g_clock.ticksFor(delay), callback)); + ScheduledEventPtr scheduledEvent(new ScheduledEvent(callback, delay)); + m_scheduledEventList.push(scheduledEvent); + return scheduledEvent; } -void EventDispatcher::addEvent(const SimpleCallback& callback, bool pushFront) +EventPtr EventDispatcher::addEvent(const SimpleCallback& callback, bool pushFront) { + EventPtr event(new Event(callback)); if(pushFront) - m_eventList.push_front(callback); + m_eventList.push_front(event); else - m_eventList.push_back(callback); + m_eventList.push_back(event); + return event; } diff --git a/src/framework/core/eventdispatcher.h b/src/framework/core/eventdispatcher.h index a44367e5..46022c3e 100644 --- a/src/framework/core/eventdispatcher.h +++ b/src/framework/core/eventdispatcher.h @@ -24,12 +24,49 @@ #define EVENTDISPATCHER_H #include "declarations.h" +#include "clock.h" +#include -struct ScheduledEvent { - ScheduledEvent(ticks_t ticks, const SimpleCallback& callback) : ticks(ticks), callback(callback) { } - bool operator<(const ScheduledEvent& other) const { return ticks > other.ticks; } - ticks_t ticks; - SimpleCallback callback; +class Event : public LuaObject +{ +public: + Event(const SimpleCallback& callback) : m_callback(callback), m_canceled(false), m_executed(false) { } + + void execute() { + if(!m_canceled) { + m_callback(); + m_executed = true; + } + } + void cancel() { m_canceled = true; } + + bool isCanceled() { return m_canceled; } + bool isExecuted() { return m_executed; } + +protected: + SimpleCallback m_callback; + bool m_canceled; + bool m_executed; +}; + +class ScheduledEvent : public Event +{ +public: + ScheduledEvent(const SimpleCallback& callback, int delay) : Event(callback) { + m_ticks = g_clock.ticksFor(delay); + } + + int ticks() const { return m_ticks; } + int reamaningTicks() const { return m_ticks - g_clock.ticks(); } + +private: + ticks_t m_ticks; +}; + +struct lessScheduledEvent : std::binary_function { + bool operator()(const ScheduledEventPtr& a, const ScheduledEventPtr& b) const { + return b->ticks() < a->ticks(); + } }; class EventDispatcher @@ -38,12 +75,12 @@ public: void flush(); void poll(); - void addEvent(const SimpleCallback& callback, bool pushFront = false); - void scheduleEvent(const SimpleCallback& callback, int delay); + EventPtr addEvent(const SimpleCallback& callback, bool pushFront = false); + ScheduledEventPtr scheduleEvent(const SimpleCallback& callback, int delay); private: - std::list m_eventList; - std::priority_queue m_scheduledEventList; + std::list m_eventList; + std::priority_queue, lessScheduledEvent> m_scheduledEventList; }; extern EventDispatcher g_dispatcher; diff --git a/src/framework/luafunctions.cpp b/src/framework/luafunctions.cpp index 1fc593da..9dd02eaf 100644 --- a/src/framework/luafunctions.cpp +++ b/src/framework/luafunctions.cpp @@ -43,6 +43,14 @@ void Application::registerLuaFunctions() g_lua.bindGlobalFunction("colortostring", [](const Color& v) { return Fw::tostring(v); }); g_lua.bindGlobalFunction("sizetostring", [](const Size& v) { return Fw::tostring(v); }); + // Event + g_lua.registerClass(); + g_lua.bindClassMemberFunction("isCanceled", &Event::isCanceled); + g_lua.bindClassMemberFunction("isExecuted", &Event::isExecuted); + + // ScheduledEvent + g_lua.registerClass(); + // UIWidget g_lua.registerClass(); g_lua.bindClassStaticFunction("create", []{ return UIWidgetPtr(new UIWidget); }); diff --git a/src/framework/luascript/luaobject.h b/src/framework/luascript/luaobject.h index 7e119f07..e9f17276 100644 --- a/src/framework/luascript/luaobject.h +++ b/src/framework/luascript/luaobject.h @@ -119,4 +119,4 @@ T LuaObject::getLuaField(const std::string& key) { return g_lua.polymorphicPop(); } -#endif \ No newline at end of file +#endif diff --git a/src/framework/platform/win32window.cpp b/src/framework/platform/win32window.cpp index a4ef0428..6092bf74 100644 --- a/src/framework/platform/win32window.cpp +++ b/src/framework/platform/win32window.cpp @@ -351,16 +351,12 @@ void *WIN32Window::getExtensionProcAddress(const char *ext) void WIN32Window::move(const Point& pos) { - RECT windowRect = {pos.x, pos.y, m_pos.x + m_size.width(), m_pos.y + m_size.height()}; - AdjustWindowRectEx(&windowRect, WS_OVERLAPPEDWINDOW, FALSE, WS_EX_APPWINDOW | WS_EX_WINDOWEDGE); - MoveWindow(m_window, windowRect.left, windowRect.top, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, TRUE); + MoveWindow(m_window, pos.x, pos.y, m_size.width(), m_size.height(), TRUE); } void WIN32Window::resize(const Size& size) { - RECT windowRect = {m_pos.x, m_pos.y, m_pos.x + size.width(), m_pos.y + size.height()}; - AdjustWindowRectEx(&windowRect, WS_OVERLAPPEDWINDOW, FALSE, WS_EX_APPWINDOW | WS_EX_WINDOWEDGE); - MoveWindow(m_window, windowRect.left, windowRect.top, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, TRUE); + MoveWindow(m_window, m_pos.x, m_pos.y, size.width(), size.height(), TRUE); } void WIN32Window::show() @@ -400,7 +396,6 @@ void WIN32Window::poll() LRESULT WIN32Window::windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - m_inputEvent.reset(); switch(uMsg) { case WM_ACTIVATE: { @@ -411,6 +406,8 @@ LRESULT WIN32Window::windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPar if(wParam >= 32 && wParam <= 255) { m_inputEvent.reset(Fw::KeyTextInputEvent); m_inputEvent.keyText = wParam; + if(m_onInputEvent) + m_onInputEvent(m_inputEvent); } break; } @@ -429,31 +426,43 @@ LRESULT WIN32Window::windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPar case WM_LBUTTONDOWN: { m_inputEvent.reset(Fw::MousePressInputEvent); m_inputEvent.mouseButton = Fw::MouseLeftButton; + if(m_onInputEvent) + m_onInputEvent(m_inputEvent); break; } case WM_LBUTTONUP: { m_inputEvent.reset(Fw::MouseReleaseInputEvent); m_inputEvent.mouseButton = Fw::MouseLeftButton; + if(m_onInputEvent) + m_onInputEvent(m_inputEvent); break; } case WM_MBUTTONDOWN: { m_inputEvent.reset(Fw::MousePressInputEvent); m_inputEvent.mouseButton = Fw::MouseMidButton; + if(m_onInputEvent) + m_onInputEvent(m_inputEvent); break; } case WM_MBUTTONUP: { m_inputEvent.reset(Fw::MouseReleaseInputEvent); m_inputEvent.mouseButton = Fw::MouseMidButton; + if(m_onInputEvent) + m_onInputEvent(m_inputEvent); break; } case WM_RBUTTONDOWN: { m_inputEvent.reset(Fw::MousePressInputEvent); m_inputEvent.mouseButton = Fw::MouseRightButton; + if(m_onInputEvent) + m_onInputEvent(m_inputEvent); break; } case WM_RBUTTONUP: { m_inputEvent.reset(Fw::MouseReleaseInputEvent); m_inputEvent.mouseButton = Fw::MouseRightButton; + if(m_onInputEvent) + m_onInputEvent(m_inputEvent); break; } case WM_MOUSEMOVE: { @@ -461,11 +470,15 @@ LRESULT WIN32Window::windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPar Point newMousePos(LOWORD(lParam), HIWORD(lParam)); m_inputEvent.mouseMoved = newMousePos - m_inputEvent.mousePos; m_inputEvent.mousePos = newMousePos; + if(m_onInputEvent) + m_onInputEvent(m_inputEvent); break; } case WM_MOUSEWHEEL: { m_inputEvent.mouseButton = Fw::MouseMidButton; m_inputEvent.wheelDirection = HIWORD(wParam) > 0 ? Fw::MouseWheelUp : Fw::MouseWheelDown; + if(m_onInputEvent) + m_onInputEvent(m_inputEvent); break; } case WM_MOVE: { @@ -494,8 +507,6 @@ LRESULT WIN32Window::windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPar return DefWindowProc(hWnd, uMsg, wParam, lParam); } - if(m_onInputEvent && m_inputEvent.type != Fw::NoInputEvent) - m_onInputEvent(m_inputEvent); return 0; } diff --git a/src/framework/ui/uiwidget.cpp b/src/framework/ui/uiwidget.cpp index 42ddf4e4..499c0e6b 100644 --- a/src/framework/ui/uiwidget.cpp +++ b/src/framework/ui/uiwidget.cpp @@ -193,26 +193,27 @@ void UIWidget::removeChild(const UIWidgetPtr& child) void UIWidget::focusChild(const UIWidgetPtr& child, Fw::FocusReason reason) { + if(child == m_focusedChild) + return; + if(child && !hasChild(child)) { - logError("Attempt to focus an unknown child in a UIWidget"); + logError("attempt to focus an unknown child in a UIWidget"); return; } - if(child != m_focusedChild) { - UIWidgetPtr oldFocused = m_focusedChild; - m_focusedChild = child; + UIWidgetPtr oldFocused = m_focusedChild; + m_focusedChild = child; - if(child) { - child->setLastFocusReason(reason); - child->updateState(Fw::FocusState); - child->updateState(Fw::ActiveState); - } + if(child) { + child->setLastFocusReason(reason); + child->updateState(Fw::FocusState); + child->updateState(Fw::ActiveState); + } - if(oldFocused) { - oldFocused->setLastFocusReason(reason); - oldFocused->updateState(Fw::FocusState); - oldFocused->updateState(Fw::ActiveState); - } + if(oldFocused) { + oldFocused->setLastFocusReason(reason); + oldFocused->updateState(Fw::FocusState); + oldFocused->updateState(Fw::ActiveState); } } @@ -389,6 +390,8 @@ void UIWidget::addAnchor(Fw::AnchorEdge anchoredEdge, const std::string& hookedW { if(UIAnchorLayoutPtr anchorLayout = getAnchoredLayout()) anchorLayout->addAnchor(asUIWidget(), anchoredEdge, hookedWidgetId, hookedEdge); + else + logError("cannot add anchors to widget ", m_id, ": the parent doesn't use anchors layout"); } void UIWidget::centerIn(const std::string& hookedWidgetId) @@ -396,7 +399,8 @@ void UIWidget::centerIn(const std::string& hookedWidgetId) if(UIAnchorLayoutPtr anchorLayout = getAnchoredLayout()) { anchorLayout->addAnchor(asUIWidget(), Fw::AnchorHorizontalCenter, hookedWidgetId, Fw::AnchorHorizontalCenter); anchorLayout->addAnchor(asUIWidget(), Fw::AnchorVerticalCenter, hookedWidgetId, Fw::AnchorVerticalCenter); - } + } else + logError("cannot add anchors to widget ", m_id, ": the parent doesn't use anchors layout"); } void UIWidget::fill(const std::string& hookedWidgetId) @@ -406,7 +410,8 @@ void UIWidget::fill(const std::string& hookedWidgetId) anchorLayout->addAnchor(asUIWidget(), Fw::AnchorRight, hookedWidgetId, Fw::AnchorRight); anchorLayout->addAnchor(asUIWidget(), Fw::AnchorTop, hookedWidgetId, Fw::AnchorTop); anchorLayout->addAnchor(asUIWidget(), Fw::AnchorBottom, hookedWidgetId, Fw::AnchorBottom); - } + } else + logError("cannot add anchors to widget ", m_id, ": the parent doesn't use anchors layout"); } void UIWidget::breakAnchors() @@ -732,17 +737,10 @@ Rect UIWidget::getChildrenRect() UIAnchorLayoutPtr UIWidget::getAnchoredLayout() { UIWidgetPtr parent = getParent(); - if(!parent) { - logError("cannot add anchors to widget ", m_id, ": there is no parent"); + if(!parent) return nullptr; - } - UIAnchorLayoutPtr anchorLayout = parent->getLayout()->asUIAnchorLayout(); - if(!anchorLayout) { - logError("cannot add anchors to widget ", m_id, ": the parent doesn't use anchors layout"); - return nullptr; - } - return anchorLayout; + return parent->getLayout()->asUIAnchorLayout(); } UIWidgetPtr UIWidget::getRootParent() @@ -1190,38 +1188,38 @@ bool UIWidget::propagateOnKeyUp(uchar keyCode, int keyboardModifiers) bool UIWidget::propagateOnMousePress(const Point& mousePos, Fw::MouseButton button) { // do a backup of children list, because it may change while looping it - UIWidgetList children; + UIWidgetPtr clickedChild; for(const UIWidgetPtr& child : m_children) { // events on hidden or disabled widgets are discarded if(!child->isExplicitlyEnabled() || !child->isExplicitlyVisible()) continue; // mouse press events only go to children that contains the mouse position - if(child->containsPoint(mousePos) && child == getChildByPos(mousePos)) - children.push_back(child); + if(child->containsPoint(mousePos) && child == getChildByPos(mousePos)) { + clickedChild = child; + break; + } } - for(const UIWidgetPtr& child : children) { - // when a focusable item is focused it must gain focus - if(child->isFocusable()) - focusChild(child, Fw::MouseFocusReason); + if(clickedChild) { + // focusable child gains focus when clicked + if(clickedChild->isFocusable()) + focusChild(clickedChild, Fw::MouseFocusReason); - bool mustEnd = child->propagateOnMousePress(mousePos, button); - - if(button == Fw::MouseLeftButton && !child->isPressed()) { - UIWidgetPtr clickedChild = child->getChildByPos(mousePos); - if(!clickedChild || clickedChild->isPhantom()) - child->setPressed(true); - } - - if(mustEnd) + // stop propagating if the child accept the event + if(clickedChild->propagateOnMousePress(mousePos, button)) return true; } - if(!isPhantom()) - return onMousePress(mousePos, button); - else - return false; + // only non phatom widgets receives mouse press events + if(!isPhantom()) { + onMousePress(mousePos, button); + if(button == Fw::MouseLeftButton && !isPressed()) + setPressed(true); + return true; + } + + return false; } void UIWidget::propagateOnMouseRelease(const Point& mousePos, Fw::MouseButton button) @@ -1239,12 +1237,12 @@ void UIWidget::propagateOnMouseRelease(const Point& mousePos, Fw::MouseButton bu for(const UIWidgetPtr& child : children) { child->propagateOnMouseRelease(mousePos, button); - - if(child->isPressed() && button == Fw::MouseLeftButton) - child->setPressed(false); } onMouseRelease(mousePos, button); + + if(isPressed() && button == Fw::MouseLeftButton) + setPressed(false); } bool UIWidget::propagateOnMouseMove(const Point& mousePos, const Point& mouseMoved) diff --git a/src/otclient/core/creature.cpp b/src/otclient/core/creature.cpp index a862dd39..15b68230 100644 --- a/src/otclient/core/creature.cpp +++ b/src/otclient/core/creature.cpp @@ -44,17 +44,14 @@ Creature::Creature() : Thing() m_showVolatileSquare = false; m_showStaticSquare = false; m_direction = Otc::South; - m_walkTimePerPixel = 1000.0/32.0; - m_walking = false; - m_preWalking = false; - + m_walkInterval = 0; + m_walkTurnDirection = Otc::InvalidDirection; m_skull = Otc::SkullNone; m_shield = Otc::ShieldNone; m_emblem = Otc::EmblemNone; m_shieldBlink = false; m_showShieldTexture = true; - m_informationFont = g_fonts.getFont("verdana-11px-rounded"); } @@ -187,110 +184,133 @@ void Creature::drawInformation(int x, int y, bool useGray, const Rect& visibleRe } } -void Creature::walk(const Position& oldPos, const Position& newPos, bool preWalk) +void Creature::turn(Otc::Direction direction) +{ + // if is not walking change the direction right away + if(!m_walking) + setDirection(direction); + // schedules to set the new direction when walk ends + else + m_walkTurnDirection = direction; +} + +void Creature::walk(const Position& oldPos, const Position& newPos) { // get walk direction Otc::Direction direction = oldPos.getDirectionFromPosition(newPos); - // already pre walking to the same direction - if(m_preWalking && preWalk && direction == m_direction) - return; - - // pre walking was already going on, just change to normal waking - if(m_preWalking && !preWalk && direction == m_direction) { - m_preWalking = false; - m_walking = true; - updateWalk(); - return; - } - + // set current walking direction setDirection(direction); - // diagonal walking lasts 3 times more. - int walkTimeFactor = 1; - if(direction == Otc::NorthWest || direction == Otc::NorthEast || direction == Otc::SouthWest || direction == Otc::SouthEast) - walkTimeFactor = 3; - - // calculate walk interval - int groundSpeed = g_map.getTile(oldPos)->getGroundSpeed(); - float walkInterval = 1000.0 * (float)groundSpeed / m_speed; - walkInterval = (walkInterval == 0) ? 1000 : walkInterval; - walkInterval = std::ceil(walkInterval / g_game.getServerBeat()) * g_game.getServerBeat(); - - m_walkTimePerPixel = walkInterval / 32.0; - m_walkOffset = Point(); - m_walkStart = g_clock.ticks(); - m_walkEnd = m_walkStart + walkInterval * walkTimeFactor; + // starts counting walk m_walking = true; - m_preWalking = preWalk; - m_turnDirection = m_direction; - updateWalk(); + m_walkTimer.restart(); + + // calculates walk interval + float walkInterval = 1000; + int groundSpeed = g_map.getTile(oldPos)->getGroundSpeed(); + if(groundSpeed != 0) + walkInterval = (1000.0f * groundSpeed) / m_speed; + + // diagonal walking lasts 3 times more. + //if(direction == Otc::NorthWest || direction == Otc::NorthEast || direction == Otc::SouthWest || direction == Otc::SouthEast) + // walkInterval *= 3; + + m_walkInterval = (walkInterval / g_game.getServerBeat()) * g_game.getServerBeat(); + + // no direction needs to be changed when the walk ends + m_walkTurnDirection = Otc::InvalidDirection; + + // starts updating walk + nextWalkUpdate(); } -void Creature::turn(Otc::Direction direction) +void Creature::stopWalk() { if(!m_walking) - setDirection(direction); - else - m_turnDirection = direction; + return; + + // reset walk animation states + updateWalkAnimation(0); + updateWalkOffset(0); + + // stops the walk right away + terminateWalk(); +} + +void Creature::updateWalkAnimation(int totalPixelsWalked) +{ + // update outfit animation + if(m_outfit.getCategory() == ThingsType::Creature) { + if(totalPixelsWalked == 32 || totalPixelsWalked == 0 || m_type->dimensions[ThingType::AnimationPhases] <= 1) + m_animation = 0; + else if(m_type->dimensions[ThingType::AnimationPhases] > 1) + m_animation = 1 + ((totalPixelsWalked * 4) / Map::NUM_TILE_PIXELS) % (m_type->dimensions[ThingType::AnimationPhases] - 1); + } +} + +void Creature::updateWalkOffset(int totalPixelsWalked) +{ + m_walkOffset = Point(0,0); + if(m_direction == Otc::North || m_direction == Otc::NorthEast || m_direction == Otc::NorthWest) + m_walkOffset.y = 32 - totalPixelsWalked; + else if(m_direction == Otc::South || m_direction == Otc::SouthEast || m_direction == Otc::SouthWest) + m_walkOffset.y = totalPixelsWalked - 32; + + if(m_direction == Otc::East || m_direction == Otc::NorthEast || m_direction == Otc::SouthEast) + m_walkOffset.x = totalPixelsWalked - 32; + else if(m_direction == Otc::West || m_direction == Otc::NorthWest || m_direction == Otc::SouthWest) + m_walkOffset.x = 32 - totalPixelsWalked; +} + +void Creature::nextWalkUpdate() +{ + // remove any previous scheduled walk updates + if(m_walkUpdateEvent) + m_walkUpdateEvent->cancel(); + + // do the update + updateWalk(); + + // schedules next update + if(m_walking) { + auto self = asCreature(); + m_walkUpdateEvent = g_dispatcher.scheduleEvent([self] { + self->m_walkUpdateEvent = nullptr; + self->nextWalkUpdate(); + }, m_walkInterval / 32); + } } void Creature::updateWalk() { - if(!m_walking) - return; + float walkTicksPerPixel = m_walkInterval / 32.0f; + int totalPixelsWalked = std::min(m_walkTimer.ticksElapsed() / walkTicksPerPixel, 32.0f); - int elapsedTicks = g_clock.ticksElapsed(m_walkStart); - int totalPixelsWalked = std::min((int)round(elapsedTicks / m_walkTimePerPixel), 32); + // update walk animation and offsets + updateWalkAnimation(totalPixelsWalked); + updateWalkOffset(totalPixelsWalked); - if(!m_preWalking) { - if(m_direction == Otc::North || m_direction == Otc::NorthEast || m_direction == Otc::NorthWest) - m_walkOffset.y = 32 - totalPixelsWalked; - else if(m_direction == Otc::South || m_direction == Otc::SouthEast || m_direction == Otc::SouthWest) - m_walkOffset.y = totalPixelsWalked - 32; - - if(m_direction == Otc::East || m_direction == Otc::NorthEast || m_direction == Otc::SouthEast) - m_walkOffset.x = totalPixelsWalked - 32; - else if(m_direction == Otc::West || m_direction == Otc::NorthWest || m_direction == Otc::SouthWest) - m_walkOffset.x = 32 - totalPixelsWalked; - } else { - if(m_direction == Otc::North || m_direction == Otc::NorthEast || m_direction == Otc::NorthWest) - m_walkOffset.y = -totalPixelsWalked; - else if(m_direction == Otc::South || m_direction == Otc::SouthEast || m_direction == Otc::SouthWest) - m_walkOffset.y = totalPixelsWalked; - - if(m_direction == Otc::East || m_direction == Otc::NorthEast || m_direction == Otc::SouthEast) - m_walkOffset.x = totalPixelsWalked; - else if(m_direction == Otc::West || m_direction == Otc::NorthWest || m_direction == Otc::SouthWest) - m_walkOffset.x = -totalPixelsWalked; - } - - if(m_outfit.getCategory() == ThingsType::Creature) { - if(totalPixelsWalked == 32 || m_type->dimensions[ThingType::AnimationPhases] <= 1) - m_animation = 0; - else if(m_type->dimensions[ThingType::AnimationPhases] > 1) - m_animation = 1 + totalPixelsWalked * 4 / Map::NUM_TILE_PIXELS % (m_type->dimensions[ThingType::AnimationPhases] - 1); - } - - if(g_clock.ticks() > m_walkEnd) { - cancelWalk(m_turnDirection); - } else - g_dispatcher.scheduleEvent(std::bind(&Creature::updateWalk, asCreature()), m_walkTimePerPixel); + // terminate walk + if(m_walking && m_walkTimer.ticksElapsed() >= m_walkInterval) + terminateWalk(); } -void Creature::cancelWalk(Otc::Direction direction, bool force) +void Creature::terminateWalk() { - if(force) { - m_walkOffset = Point(); - m_preWalking = false; - } else if(!m_preWalking) - m_walkOffset = Point(); + // remove any scheduled walk update + if(m_walkUpdateEvent) { + m_walkUpdateEvent->cancel(); + m_walkUpdateEvent = nullptr; + } + + // now the walk has ended, do any scheduled turn + if(m_walkTurnDirection != Otc::InvalidDirection) { + setDirection(m_walkTurnDirection); + m_walkTurnDirection = Otc::InvalidDirection; + } + m_walking = false; - - if(direction != Otc::InvalidDirection) - setDirection(direction); - - m_animation = 0; } void Creature::setName(const std::string& name) diff --git a/src/otclient/core/creature.h b/src/otclient/core/creature.h index bdaff1c4..64247a59 100644 --- a/src/otclient/core/creature.h +++ b/src/otclient/core/creature.h @@ -25,6 +25,8 @@ #include "thing.h" #include "outfit.h" +#include +#include #include class Creature : public Thing @@ -79,18 +81,21 @@ public: ThingType *getType(); // walk related - void walk(const Position& oldPos, const Position& newPos, bool preWalk = false); void turn(Otc::Direction direction); - void cancelWalk(Otc::Direction direction = Otc::InvalidDirection, bool force = false); + virtual void walk(const Position& oldPos, const Position& newPos); + virtual void stopWalk(); Point getWalkOffset() { return m_walkOffset; } bool isWalking() { return m_walking; } - bool isPreWalking() { return m_preWalking; } CreaturePtr asCreature() { return std::static_pointer_cast(shared_from_this()); } protected: - void updateWalk(); + virtual void updateWalkAnimation(int totalPixelsWalked); + virtual void updateWalkOffset(int totalPixelsWalked); + virtual void nextWalkUpdate(); + virtual void updateWalk(); + virtual void terminateWalk(); std::string m_name; Size m_nameSize; @@ -98,7 +103,7 @@ protected: Otc::Direction m_direction; Outfit m_outfit; Light m_light; - uint16 m_speed; + int m_speed; uint8 m_skull, m_shield, m_emblem; TexturePtr m_skullTexture, m_shieldTexture, m_emblemTexture; bool m_showShieldTexture, m_shieldBlink; @@ -109,11 +114,13 @@ protected: FontPtr m_informationFont; Color m_informationColor; - ticks_t m_walkStart, m_walkEnd; - bool m_walking, m_preWalking; - float m_walkTimePerPixel; + // walk related + Timer m_walkTimer; + int m_walkInterval; + bool m_walking; + ScheduledEventPtr m_walkUpdateEvent; Point m_walkOffset; - Otc::Direction m_turnDirection; + Otc::Direction m_walkTurnDirection; }; class Npc : public Creature { diff --git a/src/otclient/core/game.cpp b/src/otclient/core/game.cpp index 4e089856..29ab0ecc 100644 --- a/src/otclient/core/game.cpp +++ b/src/otclient/core/game.cpp @@ -35,7 +35,6 @@ Game g_game; void Game::loginWorld(const std::string& account, const std::string& password, const std::string& worldHost, int worldPort, const std::string& characterName) { - m_online = false; m_dead = false; m_selectedThing = nullptr; m_protocolGame = ProtocolGamePtr(new ProtocolGame); @@ -49,7 +48,7 @@ void Game::cancelLogin() void Game::logout(bool force) { - if(!m_protocolGame || !m_online) + if(!m_protocolGame || !isOnline()) return; m_protocolGame->sendLogout(); @@ -77,7 +76,6 @@ void Game::processConnectionError(const boost::system::error_code& error) void Game::processLogin(const LocalPlayerPtr& localPlayer, int serverBeat) { m_localPlayer = localPlayer; - m_online = true; m_serverBeat = serverBeat; // NOTE: the entire map description is not known yet @@ -86,17 +84,18 @@ void Game::processLogin(const LocalPlayerPtr& localPlayer, int serverBeat) void Game::processLogout() { - if(m_online) { + if(isOnline()) { g_lua.callGlobalField("Game", "onLogout", m_localPlayer); - m_localPlayer.reset(); - m_online = false; + m_localPlayer = nullptr; } if(m_protocolGame) { m_protocolGame->disconnect(); - m_protocolGame.reset(); + m_protocolGame = nullptr; } + + g_map.clean(); } void Game::processDeath() @@ -145,8 +144,7 @@ void Game::processCreatureMove(const CreaturePtr& creature, const Position& oldP // teleport } else { // stop walking on teleport - if(creature->isWalking() || creature->isPreWalking()) - creature->cancelWalk(); + creature->stopWalk(); } } @@ -158,7 +156,8 @@ void Game::processAttackCancel() void Game::processWalkCancel(Otc::Direction direction) { - m_localPlayer->cancelWalk(direction, true); + logTraceDebug(); + m_localPlayer->cancelWalk(direction); } void Game::walk(Otc::Direction direction) @@ -208,7 +207,7 @@ void Game::forceWalk(Otc::Direction direction) void Game::turn(Otc::Direction direction) { - if(!m_online) + if(!isOnline()) return; switch(direction) { @@ -229,7 +228,7 @@ void Game::turn(Otc::Direction direction) void Game::look(const ThingPtr& thing) { - if(!m_online || !thing || !checkBotProtection()) + if(!isOnline() || !thing || !checkBotProtection()) return; int stackpos = getThingStackpos(thing); @@ -239,7 +238,7 @@ void Game::look(const ThingPtr& thing) void Game::open(const ThingPtr& thing, int containerId) { - if(!m_online || !thing || !checkBotProtection()) + if(!isOnline() || !thing || !checkBotProtection()) return; int stackpos = getThingStackpos(thing); @@ -249,9 +248,11 @@ void Game::open(const ThingPtr& thing, int containerId) void Game::use(const ThingPtr& thing) { - if(!m_online || !thing || !checkBotProtection()) + if(!isOnline() || !thing || !checkBotProtection()) return; + m_localPlayer->lockWalk(); + int stackpos = getThingStackpos(thing); if(stackpos != -1) m_protocolGame->sendUseItem(thing->getPos(), thing->getId(), stackpos, 0); @@ -259,7 +260,7 @@ void Game::use(const ThingPtr& thing) void Game::useWith(const ThingPtr& fromThing, const ThingPtr& toThing) { - if(!m_online || !fromThing || !toThing || !checkBotProtection()) + if(!isOnline() || !fromThing || !toThing || !checkBotProtection()) return; Position pos = fromThing->getPos(); @@ -267,6 +268,8 @@ void Game::useWith(const ThingPtr& fromThing, const ThingPtr& toThing) if(fromStackpos == -1) return; + m_localPlayer->lockWalk(); + if(CreaturePtr creature = toThing->asCreature()) { m_protocolGame->sendUseOnCreature(pos, fromThing->getId(), fromStackpos, creature->getId()); } else { @@ -280,9 +283,11 @@ void Game::useWith(const ThingPtr& fromThing, const ThingPtr& toThing) void Game::useInventoryItem(int itemId, const ThingPtr& toThing) { - if(!m_online || !toThing || !checkBotProtection()) + if(!isOnline() || !toThing || !checkBotProtection()) return; + m_localPlayer->lockWalk(); + Position pos = Position(0xFFFF, 0, 0); // means that is a item in inventory int toStackpos = getThingStackpos(toThing); if(toStackpos == -1) @@ -297,9 +302,11 @@ void Game::useInventoryItem(int itemId, const ThingPtr& toThing) void Game::attack(const CreaturePtr& creature) { - if(!m_online || !creature || !checkBotProtection()) + if(!isOnline() || !creature || !checkBotProtection()) return; + m_localPlayer->lockWalk(); + if(m_localPlayer->isFollowing()) cancelFollow(); @@ -309,13 +316,15 @@ void Game::attack(const CreaturePtr& creature) void Game::cancelAttack() { + m_localPlayer->lockWalk(); + m_localPlayer->setAttackingCreature(nullptr); m_protocolGame->sendAttack(0); } void Game::follow(const CreaturePtr& creature) { - if(!m_online || !creature || !checkBotProtection()) + if(!isOnline() || !creature || !checkBotProtection()) return; if(m_localPlayer->isAttacking()) @@ -333,7 +342,7 @@ void Game::cancelFollow() void Game::rotate(const ThingPtr& thing) { - if(!m_online || !thing || !checkBotProtection()) + if(!isOnline() || !thing || !checkBotProtection()) return; int stackpos = getThingStackpos(thing); @@ -361,42 +370,42 @@ void Game::talk(const std::string& message) void Game::talkChannel(const std::string& speakTypeDesc, int channelId, const std::string& message) { - if(!m_online || !checkBotProtection()) + if(!isOnline() || !checkBotProtection()) return; m_protocolGame->sendTalk(speakTypeDesc, channelId, "", message); } void Game::talkPrivate(const std::string& speakTypeDesc, const std::string& receiver, const std::string& message) { - if(!m_online || !checkBotProtection()) + if(!isOnline() || !checkBotProtection()) return; m_protocolGame->sendTalk(speakTypeDesc, 0, receiver, message); } void Game::requestChannels() { - if(!m_online || !checkBotProtection()) + if(!isOnline() || !checkBotProtection()) return; m_protocolGame->sendGetChannels(); } void Game::joinChannel(int channelId) { - if(!m_online || !checkBotProtection()) + if(!isOnline() || !checkBotProtection()) return; m_protocolGame->sendJoinChannel(channelId); } void Game::leaveChannel(int channelId) { - if(!m_online || !checkBotProtection()) + if(!isOnline() || !checkBotProtection()) return; m_protocolGame->sendLeaveChannel(channelId); } void Game::closeNpcChannel() { - if(!m_online || !checkBotProtection()) + if(!isOnline() || !checkBotProtection()) return; m_protocolGame->sendCloseNpcChannel(); } @@ -404,21 +413,21 @@ void Game::closeNpcChannel() void Game::partyInvite(int creatureId) { - if(!m_online || !checkBotProtection()) + if(!isOnline() || !checkBotProtection()) return; m_protocolGame->sendInviteToParty(creatureId); } void Game::partyJoin(int creatureId) { - if(!m_online || !checkBotProtection()) + if(!isOnline() || !checkBotProtection()) return; m_protocolGame->sendJoinParty(creatureId); } void Game::partyRevokeInvitation(int creatureId) { - if(!m_online || !checkBotProtection()) + if(!isOnline() || !checkBotProtection()) return; m_protocolGame->sendRevokeInvitation(creatureId); } @@ -426,49 +435,49 @@ void Game::partyRevokeInvitation(int creatureId) void Game::partyPassLeadership(int creatureId) { - if(!m_online || !checkBotProtection()) + if(!isOnline() || !checkBotProtection()) return; m_protocolGame->sendPassLeadership(creatureId); } void Game::partyLeave() { - if(!m_online || !checkBotProtection()) + if(!isOnline() || !checkBotProtection()) return; m_protocolGame->sendLeaveParty(); } void Game::partyShareExperience(bool active) { - if(!m_online || !checkBotProtection()) + if(!isOnline() || !checkBotProtection()) return; m_protocolGame->sendShareExperience(active, 0); } void Game::requestOutfit() { - if(!m_online || !checkBotProtection()) + if(!isOnline() || !checkBotProtection()) return; m_protocolGame->sendGetOutfit(); } void Game::setOutfit(const Outfit& outfit) { - if(!m_online || !checkBotProtection()) + if(!isOnline() || !checkBotProtection()) return; m_protocolGame->sendSetOutfit(outfit); } void Game::addVip(const std::string& name) { - if(!m_online || name.empty() || !checkBotProtection()) + if(!isOnline() || name.empty() || !checkBotProtection()) return; m_protocolGame->sendAddVip(name); } void Game::removeVip(int playerId) { - if(!m_online || !checkBotProtection()) + if(!isOnline() || !checkBotProtection()) return; m_protocolGame->sendRemoveVip(playerId); } diff --git a/src/otclient/core/game.h b/src/otclient/core/game.h index 1dac207b..6fafe365 100644 --- a/src/otclient/core/game.h +++ b/src/otclient/core/game.h @@ -102,7 +102,7 @@ public: bool checkBotProtection(); - bool isOnline() { return m_online; } + bool isOnline() { return !!m_localPlayer; } bool isDead() { return m_dead; } void setSelectedThing(const ThingPtr& thing) { m_selectedThing = thing; } @@ -118,7 +118,6 @@ public: private: LocalPlayerPtr m_localPlayer; ProtocolGamePtr m_protocolGame; - bool m_online; bool m_dead; int m_serverBeat; ThingPtr m_selectedThing; diff --git a/src/otclient/core/localplayer.cpp b/src/otclient/core/localplayer.cpp index 2156ac46..ad6581f9 100644 --- a/src/otclient/core/localplayer.cpp +++ b/src/otclient/core/localplayer.cpp @@ -25,24 +25,75 @@ #include "game.h" #include "tile.h" +LocalPlayer::LocalPlayer() +{ + m_preWalking = false; + m_canReportBugs = false; + m_known = false; + m_walkLocked = false; + m_lastPrewalkDone = true; + m_icons = 0; +} + +void LocalPlayer::lockWalk() +{ + // prevents double locks + if(m_walkLocked) + return; + + m_walkLocked = true; + m_walkLockTimer.restart(); +} + +void LocalPlayer::walk(const Position& oldPos, const Position& newPos) +{ + Otc::Direction direction = oldPos.getDirectionFromPosition(newPos); + + // a prewalk was going on + if(m_preWalking) { + // switch to normal walking + m_preWalking = false; + m_lastPrewalkDone = true; + // if is to the destination, updates it preserving the animation + if(newPos == m_lastPrewalkDestionation) { + // walk started by prewalk could already be finished by now + updateWalk(); + // was to another direction, replace the walk + } else + Creature::walk(oldPos, newPos); + } else + Creature::walk(oldPos, newPos); +} + void LocalPlayer::preWalk(Otc::Direction direction) { - // we're not walking, so start a client walk. + // start walking to direction Position newPos = m_pos + Position::getPosFromDirection(direction); - walk(m_pos, newPos, true); + m_preWalking = true; + m_lastPrewalkDone = false; + m_lastPrewalkDestionation = newPos; + Creature::walk(m_pos, newPos); } bool LocalPlayer::canWalk(Otc::Direction direction) { - if(m_walking || (m_preWalking && g_clock.ticksElapsed(m_walkEnd) < 1000)) + // cannot walk while already walking + if(m_walking) return false; + // avoid doing more walks than wanted when receiving a lot of walks from server + if(!m_lastPrewalkDone) + return false; + + // cannot walk while locked + if(m_walkLocked && m_walkLockTimer.ticksElapsed() <= WALK_LOCK_INTERVAL) + return false; + else + m_walkLocked = false; + // check for blockable tiles in the walk direction TilePtr tile = g_map.getTile(m_pos + Position::getPosFromDirection(direction)); - if(!tile) - return false; - - if(!tile->isWalkable()) { + if(!tile || !tile->isWalkable()) { g_game.processTextMessage("statusSmall", "Sorry, not possible."); return false; } @@ -50,6 +101,63 @@ bool LocalPlayer::canWalk(Otc::Direction direction) return true; } +void LocalPlayer::cancelWalk(Otc::Direction direction) +{ + // only cancel client side walks + if(m_walking && m_preWalking) + stopWalk(); + + m_lastPrewalkDone = true; + + // turn to the cancel direction + if(direction != Otc::InvalidDirection) + setDirection(direction); +} + +void LocalPlayer::stopWalk() +{ + Creature::stopWalk(); + m_lastPrewalkDestionation = Position(); +} + +void LocalPlayer::updateWalkOffset(int totalPixelsWalked) +{ + // pre walks offsets are calculated in the oposite direction + if(m_preWalking) { + m_walkOffset = Point(0,0); + if(m_direction == Otc::North || m_direction == Otc::NorthEast || m_direction == Otc::NorthWest) + m_walkOffset.y = -totalPixelsWalked; + else if(m_direction == Otc::South || m_direction == Otc::SouthEast || m_direction == Otc::SouthWest) + m_walkOffset.y = totalPixelsWalked; + + if(m_direction == Otc::East || m_direction == Otc::NorthEast || m_direction == Otc::SouthEast) + m_walkOffset.x = totalPixelsWalked; + else if(m_direction == Otc::West || m_direction == Otc::NorthWest || m_direction == Otc::SouthWest) + m_walkOffset.x = -totalPixelsWalked; + } else + Creature::updateWalkOffset(totalPixelsWalked); +} + +void LocalPlayer::updateWalk() +{ + float walkTicksPerPixel = m_walkInterval / 32.0f; + int totalPixelsWalked = std::min(m_walkTimer.ticksElapsed() / walkTicksPerPixel, 32.0f); + + // update walk animation and offsets + updateWalkAnimation(totalPixelsWalked); + updateWalkOffset(totalPixelsWalked); + + // terminate walk only when client and server side walk are complated + if(m_walking && !m_preWalking && m_walkTimer.ticksElapsed() >= m_walkInterval) + terminateWalk(); +} + +void LocalPlayer::terminateWalk() +{ + Creature::terminateWalk(); + m_preWalking = false; +} + void LocalPlayer::setAttackingCreature(const CreaturePtr& creature) { // clear current attacking creature diff --git a/src/otclient/core/localplayer.h b/src/otclient/core/localplayer.h index c5742b40..b27caf3d 100644 --- a/src/otclient/core/localplayer.h +++ b/src/otclient/core/localplayer.h @@ -27,13 +27,18 @@ class LocalPlayer : public Player { + enum { + WALK_LOCK_INTERVAL = 250 + }; public: + LocalPlayer(); + void setCanReportBugs(uint8 canReportBugs) { m_canReportBugs = (canReportBugs != 0); } void setSkill(Otc::Skill skill, Otc::SkillType skillType, int value) { m_skills[skill][skillType] = value; } void setStatistic(Otc::Statistic statistic, double value) { m_statistics[statistic] = value; } void setAttackingCreature(const CreaturePtr& creature); void setFollowingCreature(const CreaturePtr& creature); - void setIcons(Otc::PlayerIcons icons) { m_icons = icons; } + void setIcons(int icons) { m_icons = icons; } void setKnown(bool known) { m_known = known; } bool getCanReportBugs() { return m_canReportBugs; } @@ -41,14 +46,19 @@ public: double getStatistic(Otc::Statistic statistic) { return m_statistics[statistic]; } CreaturePtr getAttackingCreature() { return m_attackingCreature; } CreaturePtr getFollowingCreature() { return m_followingCreature; } - Otc::PlayerIcons getIcons() { return m_icons; } + int getIcons() { return m_icons; } bool isKnown() { return m_known; } bool isAttacking() { return m_attackingCreature != nullptr; } bool isFollowing() { return m_followingCreature != nullptr; } + void unlockWalk() { m_walkLocked = false; } + void lockWalk(); + void walk(const Position& oldPos, const Position& newPos); void preWalk(Otc::Direction direction); bool canWalk(Otc::Direction direction); + void cancelWalk(Otc::Direction direction = Otc::InvalidDirection); + void stopWalk(); LocalPlayerPtr asLocalPlayer() { return std::static_pointer_cast(shared_from_this()); } @@ -65,11 +75,24 @@ public: double getSoul() { return getStatistic(Otc::Soul); } double getStamina() { return getStatistic(Otc::Stamina); } +protected: + void updateWalkOffset(int totalPixelsWalked); + void updateWalk(); + void terminateWalk(); + private: + // walk related + bool m_preWalking; + bool m_lastPrewalkDone; + bool m_walkLocked; + Position m_lastPrewalkDestionation; + Timer m_walkLockTimer; + bool m_canReportBugs; bool m_known; - CreaturePtr m_attackingCreature, m_followingCreature; - Otc::PlayerIcons m_icons; + CreaturePtr m_attackingCreature; + CreaturePtr m_followingCreature; + int m_icons; int m_skills[Otc::LastSkill][Otc::LastSkillType]; double m_statistics[Otc::LastStatistic]; }; diff --git a/src/otclient/core/map.cpp b/src/otclient/core/map.cpp index 9586296c..3987a7be 100644 --- a/src/otclient/core/map.cpp +++ b/src/otclient/core/map.cpp @@ -150,6 +150,11 @@ void Map::draw(const Rect& rect) void Map::clean() { m_tiles.clear(); + m_creatures.clear(); + for(int i=0;isetEmblem(emblem); creature->setPassable(passable); - creature->cancelWalk(direction); + creature->setDirection(direction); if(creature == m_localPlayer) { m_localPlayer->setKnown(true);