This commit is contained in:
Gesche 2019-02-02 11:44:17 +01:00
commit 7fc3229610
21 changed files with 257 additions and 30 deletions

31
Dockerfile Normal file
View File

@ -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

View File

@ -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 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) * [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? ### Need help?

3
build.sh Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
docker build -t edubart/otclient .

View File

@ -26,6 +26,11 @@ local function retranslateKeyComboDesc(keyComboDesc)
if keyComboDesc == nil then if keyComboDesc == nil then
error('Unable to translate key combo \'' .. keyComboDesc .. '\'') error('Unable to translate key combo \'' .. keyComboDesc .. '\'')
end end
if type(keyComboDesc) == 'number' then
keyComboDesc = tostring(keyComboDesc)
end
local keyCombo = {} local keyCombo = {}
for i,currentKeyDesc in ipairs(keyComboDesc:split('+')) do for i,currentKeyDesc in ipairs(keyComboDesc:split('+')) do
for keyCode, keyDesc in pairs(KeyCodeDescs) do for keyCode, keyDesc in pairs(KeyCodeDescs) do

View File

@ -19,13 +19,14 @@ function UIComboBox:clearOptions()
self:clearText() self:clearText()
end end
function UIComboBox:getOption(text) function UIComboBox:isOption(text)
if not self.options then return nil end if not self.options then return false end
for i,v in ipairs(self.options) do for i,v in ipairs(self.options) do
if v.text == text then if v.text == text then
return nil return true
end end
end end
return false
end end
function UIComboBox:setOption(text, dontSignal) function UIComboBox:setOption(text, dontSignal)

View File

@ -385,6 +385,11 @@ function UIMoveableTabBar:selectTab(tab)
tab:setOn(false) tab:setOn(false)
tab.blinking = false tab.blinking = false
if tab.blinkEvent then
removeEvent(tab.blinkEvent)
tab.blinkEvent = nil
end
local parent = tab:getParent() local parent = tab:getParent()
parent:focusChild(tab, MouseFocusReason) parent:focusChild(tab, MouseFocusReason)
updateNavigation(self) updateNavigation(self)

View File

@ -126,8 +126,8 @@ end
function UIScrollBar:onSetup() function UIScrollBar:onSetup()
self.setupDone = true self.setupDone = true
local sliderButton = self:getChildById('sliderButton') local sliderButton = self:getChildById('sliderButton')
g_mouse.bindAutoPress(self:getChildById('decrementButton'), function() self:decrement() end, 300) g_mouse.bindAutoPress(self:getChildById('decrementButton'), function() self:onDecrement() end, 300)
g_mouse.bindAutoPress(self:getChildById('incrementButton'), function() self:increment() 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.bindPressMove(sliderButton, function(mousePos, mouseMoved) parseSliderPos(self, sliderButton, mousePos, mouseMoved) end)
g_mouse.bindPress(sliderButton, function(mousePos, mouseButton) parseSliderPress(self, sliderButton, mousePos, mouseButton) 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
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) function UIScrollBar:decrement(count)
count = count or self.step count = count or self.step
self:setValue(self.value - count) 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:getOrientation() return self.orientation end
function UIScrollBar:getShowValue() return self.showValue end function UIScrollBar:getShowValue() return self.showValue end
function UIScrollBar:getSymbol() return self.symbol end function UIScrollBar:getSymbol() return self.symbol end
function UIScrollBar:getMouseScroll() return self.mouseScroll end function UIScrollBar:getMouseScroll() return self.mouseScroll end

View File

@ -52,6 +52,13 @@ SayModes = {
[3] = { speakTypeDesc = 'yell', icon = '/images/game/console/yell' } [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_HISTORY = 500
MAX_LINES = 100 MAX_LINES = 100
HELP_CHANNEL = 9 HELP_CHANNEL = 9
@ -98,7 +105,8 @@ function init()
onRuleViolationCancel = onRuleViolationCancel, onRuleViolationCancel = onRuleViolationCancel,
onRuleViolationLock = onRuleViolationLock, onRuleViolationLock = onRuleViolationLock,
onGameStart = online, onGameStart = online,
onGameEnd = offline onGameEnd = offline,
onChannelEvent = onChannelEvent,
}) })
consolePanel = g_ui.loadUI('console', modules.game_interface.getBottomPanel()) consolePanel = g_ui.loadUI('console', modules.game_interface.getBottomPanel())
@ -240,7 +248,8 @@ function terminate()
onRuleViolationCancel = onRuleViolationCancel, onRuleViolationCancel = onRuleViolationCancel,
onRuleViolationLock = onRuleViolationLock, onRuleViolationLock = onRuleViolationLock,
onGameStart = online, onGameStart = online,
onGameEnd = offline onGameEnd = offline,
onChannelEvent = onChannelEvent,
}) })
if g_game.isOnline() then clear() end if g_game.isOnline() then clear() end
@ -1437,3 +1446,19 @@ function offline()
end end
clear() clear()
end 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

View File

@ -120,7 +120,7 @@ function setSkillTooltip(id, value)
widget:setTooltip(value) widget:setTooltip(value)
end end
function setSkillPercent(id, percent, tooltip) function setSkillPercent(id, percent, tooltip, color)
local skill = skillsWindow:recursiveGetChildById(id) local skill = skillsWindow:recursiveGetChildById(id)
local widget = skill:getChildById('percent') local widget = skill:getChildById('percent')
if widget then if widget then
@ -129,6 +129,10 @@ function setSkillPercent(id, percent, tooltip)
if tooltip then if tooltip then
widget:setTooltip(tooltip) widget:setTooltip(tooltip)
end end
if color then
widget:setBackgroundColor(color)
end
end end
end end
@ -333,10 +337,34 @@ function onStaminaChange(localPlayer, stamina)
if minutes < 10 then if minutes < 10 then
minutes = '0' .. minutes minutes = '0' .. minutes
end 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) 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 end
function onOfflineTrainingChange(localPlayer, offlineTrainingTime) function onOfflineTrainingChange(localPlayer, offlineTrainingTime)

View File

@ -8,7 +8,7 @@ TextMessageLabel < UILabel
Panel Panel
anchors.fill: gameMapPanel anchors.fill: gameMapPanel
anchors.bottom: gameBottomPanel.top anchors.bottom: gameMapPanel.bottom
focusable: false focusable: false
Panel Panel

View File

@ -333,4 +333,11 @@ SubscriptionStatus = {
Premium = 1, Premium = 1,
} }
ChannelEvent = {
Join = 0,
Leave = 1,
Invite = 2,
Exclude = 3,
}
-- @} -- @}

19
run.sh Executable file
View File

@ -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

View File

@ -123,6 +123,24 @@ int Animator::getPhase()
return m_phase; 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<int>(index, m_animationPhases - 1);
}
int Animator::getStartPhase() int Animator::getStartPhase()
{ {
if(m_startPhase > -1) if(m_startPhase > -1)
@ -157,7 +175,7 @@ int Animator::getLoopPhase()
if(m_loopCount == 0) if(m_loopCount == 0)
return 0; return 0;
if(m_currentLoop < (m_loopCount - 1)) { if(m_currentLoop < (m_loopCount - 1)) {
m_currentLoop++; m_currentLoop++;
return 0; return 0;
@ -182,7 +200,7 @@ void Animator::calculateSynchronous()
int totalDuration = 0; int totalDuration = 0;
for(int i = 0; i < m_animationPhases; i++) for(int i = 0; i < m_animationPhases; i++)
totalDuration += getPhaseDuration(i); totalDuration += getPhaseDuration(i);
ticks_t ticks = g_clock.millis(); ticks_t ticks = g_clock.millis();
int elapsedTicks = (int)(ticks % totalDuration); int elapsedTicks = (int)(ticks % totalDuration);
int totalTime = 0; int totalTime = 0;
@ -197,3 +215,13 @@ void Animator::calculateSynchronous()
} }
m_lastPhaseTicks = ticks; m_lastPhaseTicks = ticks;
} }
ticks_t Animator::getTotalDuration()
{
ticks_t time = 0;
for (const auto &pair: m_phaseDurations) {
time += std::get<1>(pair);
}
return time;
}

View File

@ -50,12 +50,15 @@ public:
void setPhase(int phase); void setPhase(int phase);
int getPhase(); int getPhase();
int getPhaseAt(ticks_t time);
int getStartPhase(); int getStartPhase();
int getAnimationPhases() { return m_animationPhases; } int getAnimationPhases() { return m_animationPhases; }
bool isAsync() { return m_async; } bool isAsync() { return m_async; }
bool isComplete() { return m_isComplete; } bool isComplete() { return m_isComplete; }
ticks_t getTotalDuration();
void resetAnimation(); void resetAnimation();
private: private:

View File

@ -405,7 +405,7 @@ void Creature::updateJump()
int nextT, i = 1; int nextT, i = 1;
do { do {
nextT = stdext::round((-b + std::sqrt(std::max<int>(b*b + 4*a*(roundHeight+diff*i), 0.0)) * diff) / (2*a)); nextT = stdext::round((-b + std::sqrt(std::max<double>(b*b + 4*a*(roundHeight+diff*i), 0.0)) * diff) / (2*a));
++i; ++i;
if(nextT < halfJumpDuration) if(nextT < halfJumpDuration)

View File

@ -22,6 +22,7 @@
#include "effect.h" #include "effect.h"
#include "map.h" #include "map.h"
#include "game.h"
#include <framework/core/eventdispatcher.h> #include <framework/core/eventdispatcher.h>
void Effect::drawEffect(const Point& dest, float scaleFactor, bool animate, int offsetX, int offsetY, LightView *lightView) 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; return;
int animationPhase = 0; int animationPhase = 0;
if(animate) if(animate) {
animationPhase = std::min<int>((int)(m_animationTimer.ticksElapsed() / m_phaseDuration), getAnimationPhases() - 1); 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>((int)(m_animationTimer.ticksElapsed() / ticks), getAnimationPhases() - 1);
}
}
int xPattern = offsetX % getNumPatternX(); int xPattern = offsetX % getNumPatternX();
if(xPattern < 0) if(xPattern < 0)
@ -47,15 +60,24 @@ void Effect::drawEffect(const Point& dest, float scaleFactor, bool animate, int
void Effect::onAppear() void Effect::onAppear()
{ {
m_animationTimer.restart(); m_animationTimer.restart();
m_phaseDuration = EFFECT_TICKS_PER_FRAME;
// hack to fix some animation phases duration, currently there is no better solution int duration = 0;
if(m_id == 33) if(g_game.getFeature(Otc::GameEnhancedAnimations)) {
m_phaseDuration <<= 2; 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 // schedule removal
auto self = asEffect(); 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) void Effect::setId(uint32 id)

View File

@ -51,7 +51,6 @@ protected:
private: private:
Timer m_animationTimer; Timer m_animationTimer;
uint m_phaseDuration;
uint16 m_id; uint16 m_id;
}; };

View File

@ -1296,7 +1296,7 @@ void ProtocolGame::parsePremiumTrigger(const InputMessagePtr& msg)
for(int i=0;i<triggerCount;++i) { for(int i=0;i<triggerCount;++i) {
triggers.push_back(msg->getU8()); triggers.push_back(msg->getU8());
} }
if(g_game.getClientVersion() <= 1096) { if(g_game.getClientVersion() <= 1096) {
bool something = msg->getU8() == 1; bool something = msg->getU8() == 1;
} }
@ -1903,9 +1903,11 @@ void ProtocolGame::parseQuestLine(const InputMessagePtr& msg)
void ProtocolGame::parseChannelEvent(const InputMessagePtr& msg) void ProtocolGame::parseChannelEvent(const InputMessagePtr& msg)
{ {
msg->getU16(); // channel id uint16 channelId = msg->getU16();
g_game.formatCreatureName(msg->getString()); // player name std::string name = g_game.formatCreatureName(msg->getString());
msg->getU8(); // event type uint8 type = msg->getU8();
g_lua.callGlobalField("g_game", "onChannelEvent", channelId, name, type);
} }
void ProtocolGame::parseItemInfo(const InputMessagePtr& msg) void ProtocolGame::parseItemInfo(const InputMessagePtr& msg)

View File

@ -29,7 +29,19 @@
#include <winsock2.h> #include <winsock2.h>
#include <windows.h> #include <windows.h>
#include <process.h> #include <process.h>
#ifdef _MSC_VER
#pragma warning (push)
#pragma warning (disable:4091) // warning C4091: 'typedef ': ignored on left of '' when no variable is declared
#include <imagehlp.h> #include <imagehlp.h>
#pragma warning (pop)
#else
#include <imagehlp.h>
#endif
const char *getExceptionName(DWORD exceptionCode) const char *getExceptionName(DWORD exceptionCode)
{ {

View File

@ -23,13 +23,21 @@
#include "demangle.h" #include "demangle.h"
#ifdef _MSC_VER #ifdef _MSC_VER
#include <winsock2.h> #include <winsock2.h>
#include <windows.h> #include <windows.h>
#pragma warning (push)
#pragma warning (disable:4091) // warning C4091: 'typedef ': ignored on left of '' when no variable is declared
#include <dbghelp.h> #include <dbghelp.h>
#pragma warning (pop)
#else #else
#include <cxxabi.h> #include <cxxabi.h>
#include <cstring> #include <cstring>
#include <cstdlib> #include <cstdlib>
#endif #endif
namespace stdext { namespace stdext {

View File

@ -25,7 +25,7 @@
BUILD_TYPE="RelWithDebInfo"; BUILD_TYPE="RelWithDebInfo";
BUILD_COMMIT="devel"; BUILD_COMMIT="devel";
BUILD_REVISION="0"; BUILD_REVISION="0";
VERSION="0.6.3"; VERSION="0.6.6";
AB AB
</PREPROCESSOR_DEFS> </PREPROCESSOR_DEFS>
@ -85,8 +85,8 @@
</OTCLIENT_LIBDEPS> </OTCLIENT_LIBDEPS>
<OTCLIENT_LIBDEPS_D> <OTCLIENT_LIBDEPS_D>
glew32.lib; glew32d.lib;
zlib.lib; zlibd.lib;
libeay32.lib; libeay32.lib;
physfs.lib; physfs.lib;
openal32.lib; openal32.lib;