diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..e1ba3f5f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +from ubuntu:latest + +WORKDIR /app + +RUN apt-get update; apt-get install -y \ + build-essential \ + cmake \ + git-core \ + libboost-all-dev \ + libglew-dev \ + liblua5.1-0-dev \ + libopenal-dev \ + libphysfs-dev \ + libssl-dev \ + libvorbis-dev \ + zlib1g-dev + +RUN apt-get install -y \ + libncurses5-dev \ + mercurial; \ + hg clone -r stable-2.0 http://hg.icculus.org/icculus/physfs/; \ + cd physfs; \ + mkdir build && cd build && cmake .. && make && make install; \ + mv /usr/local/lib/libphysfs.a /usr/lib/x86_64-linux-gnu/. + +ADD . /app + +# Build application +RUN mkdir -p build && cd build && cmake .. && make -j$(grep -c ^process /proc/cpuinfo); + +CMD cd build; ./otclient diff --git a/README.md b/README.md index ca7a77a6..7dd03b80 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,15 @@ In short, if you need to compile OTClient, follow these tutorials: * [Compiling on Linux](https://github.com/edubart/otclient/wiki/Compiling-on-Linux) * [Compiling on OS X](https://github.com/edubart/otclient/wiki/Compiling-on-Mac-OS-X) +### Build and run with Docker + +To build and run the client: +``` +./build.sh +./run.sh +``` + +The build step should be run just when something on implementation changes. ### Need help? diff --git a/build.sh b/build.sh new file mode 100755 index 00000000..35ce50fd --- /dev/null +++ b/build.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +docker build -t edubart/otclient . diff --git a/modules/corelib/keyboard.lua b/modules/corelib/keyboard.lua index 9485535d..000076f4 100644 --- a/modules/corelib/keyboard.lua +++ b/modules/corelib/keyboard.lua @@ -26,6 +26,11 @@ local function retranslateKeyComboDesc(keyComboDesc) if keyComboDesc == nil then error('Unable to translate key combo \'' .. keyComboDesc .. '\'') end + + if type(keyComboDesc) == 'number' then + keyComboDesc = tostring(keyComboDesc) + end + local keyCombo = {} for i,currentKeyDesc in ipairs(keyComboDesc:split('+')) do for keyCode, keyDesc in pairs(KeyCodeDescs) do diff --git a/modules/corelib/ui/uicombobox.lua b/modules/corelib/ui/uicombobox.lua index f6c94e9a..a9f54f4c 100644 --- a/modules/corelib/ui/uicombobox.lua +++ b/modules/corelib/ui/uicombobox.lua @@ -19,13 +19,14 @@ function UIComboBox:clearOptions() self:clearText() end -function UIComboBox:getOption(text) - if not self.options then return nil end +function UIComboBox:isOption(text) + if not self.options then return false end for i,v in ipairs(self.options) do if v.text == text then - return nil + return true end end + return false end function UIComboBox:setOption(text, dontSignal) diff --git a/modules/corelib/ui/uimovabletabbar.lua b/modules/corelib/ui/uimovabletabbar.lua index 1d398196..d4991cc0 100644 --- a/modules/corelib/ui/uimovabletabbar.lua +++ b/modules/corelib/ui/uimovabletabbar.lua @@ -385,6 +385,11 @@ function UIMoveableTabBar:selectTab(tab) tab:setOn(false) tab.blinking = false + if tab.blinkEvent then + removeEvent(tab.blinkEvent) + tab.blinkEvent = nil + end + local parent = tab:getParent() parent:focusChild(tab, MouseFocusReason) updateNavigation(self) diff --git a/modules/corelib/ui/uiscrollbar.lua b/modules/corelib/ui/uiscrollbar.lua index 387ba313..76c2d07c 100644 --- a/modules/corelib/ui/uiscrollbar.lua +++ b/modules/corelib/ui/uiscrollbar.lua @@ -126,8 +126,8 @@ end function UIScrollBar:onSetup() self.setupDone = true local sliderButton = self:getChildById('sliderButton') - g_mouse.bindAutoPress(self:getChildById('decrementButton'), function() self:decrement() end, 300) - g_mouse.bindAutoPress(self:getChildById('incrementButton'), function() self:increment() end, 300) + g_mouse.bindAutoPress(self:getChildById('decrementButton'), function() self:onDecrement() end, 300) + g_mouse.bindAutoPress(self:getChildById('incrementButton'), function() self:onIncrement() end, 300) g_mouse.bindPressMove(sliderButton, function(mousePos, mouseMoved) parseSliderPos(self, sliderButton, mousePos, mouseMoved) end) g_mouse.bindPress(sliderButton, function(mousePos, mouseButton) parseSliderPress(self, sliderButton, mousePos, mouseButton) end) @@ -158,6 +158,26 @@ function UIScrollBar:onStyleApply(styleName, styleNode) end end +function UIScrollBar:onDecrement() + if g_keyboard.isCtrlPressed() then + self:decrement(self.value) + elseif g_keyboard.isShiftPressed() then + self:decrement(10) + else + self:decrement() + end +end + +function UIScrollBar:onIncrement() + if g_keyboard.isCtrlPressed() then + self:increment(self.maximum) + elseif g_keyboard.isShiftPressed() then + self:increment(10) + else + self:increment() + end +end + function UIScrollBar:decrement(count) count = count or self.step self:setValue(self.value - count) @@ -264,4 +284,4 @@ function UIScrollBar:getStep() return self.step end function UIScrollBar:getOrientation() return self.orientation end function UIScrollBar:getShowValue() return self.showValue end function UIScrollBar:getSymbol() return self.symbol end -function UIScrollBar:getMouseScroll() return self.mouseScroll end \ No newline at end of file +function UIScrollBar:getMouseScroll() return self.mouseScroll end diff --git a/modules/game_console/console.lua b/modules/game_console/console.lua index a0171732..1a068286 100644 --- a/modules/game_console/console.lua +++ b/modules/game_console/console.lua @@ -52,6 +52,13 @@ SayModes = { [3] = { speakTypeDesc = 'yell', icon = '/images/game/console/yell' } } +ChannelEventFormats = { + [ChannelEvent.Join] = '%s joined the channel.', + [ChannelEvent.Leave] = '%s left the channel.', + [ChannelEvent.Invite] = '%s has been invited to the channel.', + [ChannelEvent.Exclude] = '%s has been removed from the channel.', +} + MAX_HISTORY = 500 MAX_LINES = 100 HELP_CHANNEL = 9 @@ -98,7 +105,8 @@ function init() onRuleViolationCancel = onRuleViolationCancel, onRuleViolationLock = onRuleViolationLock, onGameStart = online, - onGameEnd = offline + onGameEnd = offline, + onChannelEvent = onChannelEvent, }) consolePanel = g_ui.loadUI('console', modules.game_interface.getBottomPanel()) @@ -240,7 +248,8 @@ function terminate() onRuleViolationCancel = onRuleViolationCancel, onRuleViolationLock = onRuleViolationLock, onGameStart = online, - onGameEnd = offline + onGameEnd = offline, + onChannelEvent = onChannelEvent, }) if g_game.isOnline() then clear() end @@ -1437,3 +1446,19 @@ function offline() end clear() end + +function onChannelEvent(channelId, name, type) + local fmt = ChannelEventFormats[type] + if not fmt then + print(('Unknown channel event type (%d).'):format(type)) + return + end + + local channel = channels[channelId] + if channel then + local tab = getTab(channel) + if tab then + addTabText(fmt:format(name), SpeakTypesSettings.channelOrange, tab) + end + end +end diff --git a/modules/game_skills/skills.lua b/modules/game_skills/skills.lua index 0fe7fee5..2f554aa4 100644 --- a/modules/game_skills/skills.lua +++ b/modules/game_skills/skills.lua @@ -120,7 +120,7 @@ function setSkillTooltip(id, value) widget:setTooltip(value) end -function setSkillPercent(id, percent, tooltip) +function setSkillPercent(id, percent, tooltip, color) local skill = skillsWindow:recursiveGetChildById(id) local widget = skill:getChildById('percent') if widget then @@ -129,6 +129,10 @@ function setSkillPercent(id, percent, tooltip) if tooltip then widget:setTooltip(tooltip) end + + if color then + widget:setBackgroundColor(color) + end end end @@ -333,10 +337,34 @@ function onStaminaChange(localPlayer, stamina) if minutes < 10 then minutes = '0' .. minutes end - local percent = math.floor(100 * stamina / (42 * 60)) -- max is 42 hours + local percent = math.floor(100 * stamina / (42 * 60)) -- max is 42 hours --TODO not in all client versions setSkillValue('stamina', hours .. ":" .. minutes) - setSkillPercent('stamina', percent, tr('You have %s percent', percent)) + + --TODO not all client versions have premium time + if stamina > 2400 and g_game.getClientVersion() >= 1038 and localPlayer:isPremium() then + local text = tr("You have %s hours and %s minutes left", hours, minutes) .. '\n' .. + tr("Now you will gain 50%% more experience") + setSkillPercent('stamina', percent, text, 'green') + elseif stamina > 2400 and g_game.getClientVersion() >= 1038 and not localPlayer:isPremium() then + local text = tr("You have %s hours and %s minutes left", hours, minutes) .. '\n' .. + tr("You will not gain 50%% more experience because you aren't premium player, now you receive only 1x experience points") + setSkillPercent('stamina', percent, text, '#89F013') + elseif stamina > 2400 and g_game.getClientVersion() < 1038 then + local text = tr("You have %s hours and %s minutes left", hours, minutes) .. '\n' .. + tr("If you are premium player, you will gain 50%% more experience") + setSkillPercent('stamina', percent, text, 'green') + elseif stamina <= 2400 and stamina > 840 then + setSkillPercent('stamina', percent, tr("You have %s hours and %s minutes left", hours, minutes), 'orange') + elseif stamina <= 840 and stamina > 0 then + local text = tr("You have %s hours and %s minutes left", hours, minutes) .. "\n" .. + tr("You gain only 50%% experience and you don't may gain loot from monsters") + setSkillPercent('stamina', percent, text, 'red') + elseif stamina == 0 then + local text = tr("You have %s hours and %s minutes left", hours, minutes) .. "\n" .. + tr("You don't may receive experience and loot from monsters") + setSkillPercent('stamina', percent, text, 'black') + end end function onOfflineTrainingChange(localPlayer, offlineTrainingTime) diff --git a/modules/game_textmessage/textmessage.otui b/modules/game_textmessage/textmessage.otui index 32cd2b62..85804f52 100644 --- a/modules/game_textmessage/textmessage.otui +++ b/modules/game_textmessage/textmessage.otui @@ -8,7 +8,7 @@ TextMessageLabel < UILabel Panel anchors.fill: gameMapPanel - anchors.bottom: gameBottomPanel.top + anchors.bottom: gameMapPanel.bottom focusable: false Panel diff --git a/modules/gamelib/const.lua b/modules/gamelib/const.lua index dbd18531..29df1f1b 100644 --- a/modules/gamelib/const.lua +++ b/modules/gamelib/const.lua @@ -333,4 +333,11 @@ SubscriptionStatus = { Premium = 1, } +ChannelEvent = { + Join = 0, + Leave = 1, + Invite = 2, + Exclude = 3, +} + -- @} diff --git a/run.sh b/run.sh new file mode 100755 index 00000000..288c22bd --- /dev/null +++ b/run.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +SE_enforcing=`getenforce` || true + +sudo setenforce Permissive || true + +# Enable any host to connect on X Org +xhost + + +docker run -ti --rm \ + -e DISPLAY \ + -v /tmp/.X11-unix:/tmp/.X11-unix \ + --device /dev/dri \ + edubart/otclient + +# Enable any host to connect on X Org +xhost - + +sudo setenforce $SE_enforcing || true diff --git a/src/client/animator.cpp b/src/client/animator.cpp index c81077ba..0382f291 100644 --- a/src/client/animator.cpp +++ b/src/client/animator.cpp @@ -123,6 +123,24 @@ int Animator::getPhase() return m_phase; } +int Animator::getPhaseAt(ticks_t time) +{ + int index = 0; + ticks_t total = 0; + + for(const auto &pair: m_phaseDurations) { + total += std::get<1>(pair); + + if (time < total) { + return index; + } + + ++index; + } + + return std::min(index, m_animationPhases - 1); +} + int Animator::getStartPhase() { if(m_startPhase > -1) @@ -157,7 +175,7 @@ int Animator::getLoopPhase() if(m_loopCount == 0) return 0; - + if(m_currentLoop < (m_loopCount - 1)) { m_currentLoop++; return 0; @@ -182,7 +200,7 @@ void Animator::calculateSynchronous() int totalDuration = 0; for(int i = 0; i < m_animationPhases; i++) totalDuration += getPhaseDuration(i); - + ticks_t ticks = g_clock.millis(); int elapsedTicks = (int)(ticks % totalDuration); int totalTime = 0; @@ -197,3 +215,13 @@ void Animator::calculateSynchronous() } m_lastPhaseTicks = ticks; } + +ticks_t Animator::getTotalDuration() +{ + ticks_t time = 0; + for (const auto &pair: m_phaseDurations) { + time += std::get<1>(pair); + } + + return time; +} diff --git a/src/client/animator.h b/src/client/animator.h index 93d0e1ef..2bf7f845 100644 --- a/src/client/animator.h +++ b/src/client/animator.h @@ -50,12 +50,15 @@ public: void setPhase(int phase); int getPhase(); + int getPhaseAt(ticks_t time); int getStartPhase(); int getAnimationPhases() { return m_animationPhases; } bool isAsync() { return m_async; } bool isComplete() { return m_isComplete; } + ticks_t getTotalDuration(); + void resetAnimation(); private: diff --git a/src/client/creature.cpp b/src/client/creature.cpp index 00d23d54..d29eac56 100644 --- a/src/client/creature.cpp +++ b/src/client/creature.cpp @@ -405,7 +405,7 @@ void Creature::updateJump() int nextT, i = 1; do { - nextT = stdext::round((-b + std::sqrt(std::max(b*b + 4*a*(roundHeight+diff*i), 0.0)) * diff) / (2*a)); + nextT = stdext::round((-b + std::sqrt(std::max(b*b + 4*a*(roundHeight+diff*i), 0.0)) * diff) / (2*a)); ++i; if(nextT < halfJumpDuration) diff --git a/src/client/effect.cpp b/src/client/effect.cpp index 0ea74a6a..60f254bd 100644 --- a/src/client/effect.cpp +++ b/src/client/effect.cpp @@ -22,6 +22,7 @@ #include "effect.h" #include "map.h" +#include "game.h" #include void Effect::drawEffect(const Point& dest, float scaleFactor, bool animate, int offsetX, int offsetY, LightView *lightView) @@ -30,8 +31,20 @@ void Effect::drawEffect(const Point& dest, float scaleFactor, bool animate, int return; int animationPhase = 0; - if(animate) - animationPhase = std::min((int)(m_animationTimer.ticksElapsed() / m_phaseDuration), getAnimationPhases() - 1); + if(animate) { + if(g_game.getFeature(Otc::GameEnhancedAnimations)) { + // This requires a separate getPhaseAt method as using getPhase would make all magic effects use the same phase regardless of their appearance time + animationPhase = rawGetThingType()->getAnimator()->getPhaseAt(m_animationTimer.ticksElapsed()); + } else { + // hack to fix some animation phases duration, currently there is no better solution + int ticks = EFFECT_TICKS_PER_FRAME; + if (m_id == 33) { + ticks <<= 2; + } + + animationPhase = std::min((int)(m_animationTimer.ticksElapsed() / ticks), getAnimationPhases() - 1); + } + } int xPattern = offsetX % getNumPatternX(); if(xPattern < 0) @@ -47,15 +60,24 @@ void Effect::drawEffect(const Point& dest, float scaleFactor, bool animate, int void Effect::onAppear() { m_animationTimer.restart(); - m_phaseDuration = EFFECT_TICKS_PER_FRAME; - // hack to fix some animation phases duration, currently there is no better solution - if(m_id == 33) - m_phaseDuration <<= 2; + int duration = 0; + if(g_game.getFeature(Otc::GameEnhancedAnimations)) { + duration = getThingType()->getAnimator()->getTotalDuration(); + } else { + duration = EFFECT_TICKS_PER_FRAME; + + // hack to fix some animation phases duration, currently there is no better solution + if(m_id == 33) { + duration <<= 2; + } + + duration *= getAnimationPhases(); + } // schedule removal auto self = asEffect(); - g_dispatcher.scheduleEvent([self]() { g_map.removeThing(self); }, m_phaseDuration * getAnimationPhases()); + g_dispatcher.scheduleEvent([self]() { g_map.removeThing(self); }, duration); } void Effect::setId(uint32 id) diff --git a/src/client/effect.h b/src/client/effect.h index 74e757cd..00a9cdfb 100644 --- a/src/client/effect.h +++ b/src/client/effect.h @@ -51,7 +51,6 @@ protected: private: Timer m_animationTimer; - uint m_phaseDuration; uint16 m_id; }; diff --git a/src/client/protocolgameparse.cpp b/src/client/protocolgameparse.cpp index 5bb49816..caf0fad5 100644 --- a/src/client/protocolgameparse.cpp +++ b/src/client/protocolgameparse.cpp @@ -1296,7 +1296,7 @@ void ProtocolGame::parsePremiumTrigger(const InputMessagePtr& msg) for(int i=0;igetU8()); } - + if(g_game.getClientVersion() <= 1096) { bool something = msg->getU8() == 1; } @@ -1903,9 +1903,11 @@ void ProtocolGame::parseQuestLine(const InputMessagePtr& msg) void ProtocolGame::parseChannelEvent(const InputMessagePtr& msg) { - msg->getU16(); // channel id - g_game.formatCreatureName(msg->getString()); // player name - msg->getU8(); // event type + uint16 channelId = msg->getU16(); + std::string name = g_game.formatCreatureName(msg->getString()); + uint8 type = msg->getU8(); + + g_lua.callGlobalField("g_game", "onChannelEvent", channelId, name, type); } void ProtocolGame::parseItemInfo(const InputMessagePtr& msg) diff --git a/src/framework/platform/win32crashhandler.cpp b/src/framework/platform/win32crashhandler.cpp index 2fa9fbd1..5721d717 100644 --- a/src/framework/platform/win32crashhandler.cpp +++ b/src/framework/platform/win32crashhandler.cpp @@ -29,8 +29,20 @@ #include #include #include + +#ifdef _MSC_VER + +#pragma warning (push) +#pragma warning (disable:4091) // warning C4091: 'typedef ': ignored on left of '' when no variable is declared +#include +#pragma warning (pop) + +#else + #include +#endif + const char *getExceptionName(DWORD exceptionCode) { switch (exceptionCode) { diff --git a/src/framework/stdext/demangle.cpp b/src/framework/stdext/demangle.cpp index 3866ed04..59def575 100644 --- a/src/framework/stdext/demangle.cpp +++ b/src/framework/stdext/demangle.cpp @@ -23,13 +23,21 @@ #include "demangle.h" #ifdef _MSC_VER + #include #include + +#pragma warning (push) +#pragma warning (disable:4091) // warning C4091: 'typedef ': ignored on left of '' when no variable is declared #include +#pragma warning (pop) + #else + #include #include #include + #endif namespace stdext { diff --git a/vc14/settings.props b/vc14/settings.props index 901332ea..45160ebc 100644 --- a/vc14/settings.props +++ b/vc14/settings.props @@ -25,7 +25,7 @@ BUILD_TYPE="RelWithDebInfo"; BUILD_COMMIT="devel"; BUILD_REVISION="0"; - VERSION="0.6.3"; + VERSION="0.6.6"; AB @@ -85,8 +85,8 @@ - glew32.lib; - zlib.lib; + glew32d.lib; + zlibd.lib; libeay32.lib; physfs.lib; openal32.lib;