diff --git a/data/images/game/skull_socket.png b/data/images/game/skull_socket.png new file mode 100644 index 00000000..a2d30e2d Binary files /dev/null and b/data/images/game/skull_socket.png differ diff --git a/data/images/topbuttons/unjustifiedpoints.png b/data/images/topbuttons/unjustifiedpoints.png new file mode 100644 index 00000000..67245fa6 Binary files /dev/null and b/data/images/topbuttons/unjustifiedpoints.png differ diff --git a/modules/client_entergame/characterlist.lua b/modules/client_entergame/characterlist.lua index 79648077..eb639559 100644 --- a/modules/client_entergame/characterlist.lua +++ b/modules/client_entergame/characterlist.lua @@ -27,7 +27,7 @@ local function tryLogin(charInfo, tries) CharacterList.hide() - g_game.loginWorld(G.account, G.password, charInfo.worldName, charInfo.worldHost, charInfo.worldPort, charInfo.characterName) + g_game.loginWorld(G.account, G.password, charInfo.worldName, charInfo.worldHost, charInfo.worldPort, charInfo.characterName, G.authenticatorToken) loadBox = displayCancelBox(tr('Please wait'), tr('Connecting to game server...')) connect(loadBox, { onCancel = function() @@ -109,6 +109,16 @@ function onGameLoginError(message) end end +function onGameLoginToken(unknown) + CharacterList.destroyLoadBox() + -- TODO: make it possible to enter a new token here / prompt token + errorBox = displayErrorBox(tr("Two-Factor Authentification"), 'A new authentification token is required.\nPlease login again.') + errorBox.onOk = function() + errorBox = nil + EnterGame.show() + end +end + function onGameConnectionError(message, code) CharacterList.destroyLoadBox() local text = translateNetworkError(code, g_game.getProtocolGame() and g_game.getProtocolGame():isConnecting(), message) @@ -131,6 +141,7 @@ end -- public functions function CharacterList.init() connect(g_game, { onLoginError = onGameLoginError }) + connect(g_game, { onLoginToken = onGameLoginToken }) connect(g_game, { onUpdateNeeded = onGameUpdateNeeded }) connect(g_game, { onConnectionError = onGameConnectionError }) connect(g_game, { onGameStart = CharacterList.destroyLoadBox }) @@ -144,6 +155,7 @@ end function CharacterList.terminate() disconnect(g_game, { onLoginError = onGameLoginError }) + disconnect(g_game, { onLoginToken = onGameLoginToken }) disconnect(g_game, { onUpdateNeeded = onGameUpdateNeeded }) disconnect(g_game, { onConnectionError = onGameConnectionError }) disconnect(g_game, { onGameStart = CharacterList.destroyLoadBox }) diff --git a/modules/client_entergame/characterlist.otui b/modules/client_entergame/characterlist.otui index e93cb385..d310b1fe 100644 --- a/modules/client_entergame/characterlist.otui +++ b/modules/client_entergame/characterlist.otui @@ -16,7 +16,7 @@ CharacterWidget < UIWidget Label id: name - color: #aaaaaa + color: #bbbbbb anchors.top: parent.top anchors.left: parent.left font: verdana-11px-monochrome @@ -29,8 +29,7 @@ CharacterWidget < UIWidget Label id: worldName - color: #ffffff - color: #aaaaaa + color: #bbbbbb anchors.top: parent.top anchors.right: parent.right margin-right: 5 @@ -59,6 +58,7 @@ MainWindow TextList id: characters + background-color: #565656 anchors.top: parent.top anchors.left: parent.left anchors.right: characterListScrollBar.left diff --git a/modules/client_entergame/entergame.lua b/modules/client_entergame/entergame.lua index d7a4119a..312f0658 100644 --- a/modules/client_entergame/entergame.lua +++ b/modules/client_entergame/entergame.lua @@ -111,7 +111,7 @@ function EnterGame.init() local port = g_settings.get('port') local autologin = g_settings.getBoolean('autologin') local clientVersion = g_settings.getInteger('client-version') - if clientVersion == 0 then clientVersion = 1071 end + if clientVersion == 0 then clientVersion = 1072 end if port == nil or port == 0 then port = 7171 end @@ -122,7 +122,10 @@ function EnterGame.init() enterGame:getChildById('serverPortTextEdit'):setText(port) enterGame:getChildById('autoLoginBox'):setChecked(autologin) + clientBox = enterGame:getChildById('clientComboBox') + connect(clientBox, { onOptionChange = EnterGame.onClientVersionChange }) + for _, proto in pairs(g_game.getSupportedClients()) do clientBox:addOption(proto) end @@ -152,6 +155,7 @@ end function EnterGame.terminate() g_keyboard.unbindKeyDown('Ctrl+G') + disconnect(clientBox, { onOptionChange = EnterGame.onClientVersionChange }) enterGame:destroy() enterGame = nil enterGameButton:destroy() @@ -210,14 +214,55 @@ end function EnterGame.clearAccountFields() enterGame:getChildById('accountNameTextEdit'):clearText() enterGame:getChildById('accountPasswordTextEdit'):clearText() + enterGame:getChildById('authenticatorTokenTextEdit'):clearText() enterGame:getChildById('accountNameTextEdit'):focus() g_settings.remove('account') g_settings.remove('password') end +function EnterGame.toggleAuthenticatorToken(enabled) + if enabled == enterGame.authenticatorEnabled then + return + end + + if enabled then + enterGame:getChildById('authenticatorTokenLabel'):setVisible(true) + enterGame:getChildById('authenticatorTokenTextEdit'):setVisible(true) + + local serverLabel = enterGame:getChildById('serverLabel') + serverLabel:setMarginTop(serverLabel:getMarginTop() + enterGame.authenticatorHeight) + + enterGame:breakAnchors() + enterGame:setY(enterGame:getY() - enterGame.authenticatorHeight) + enterGame:bindRectToParent() + + enterGame:setHeight(enterGame:getHeight() + enterGame.authenticatorHeight) + else + enterGame:getChildById('authenticatorTokenLabel'):setVisible(false) + enterGame:getChildById('authenticatorTokenTextEdit'):setVisible(false) + + local serverLabel = enterGame:getChildById('serverLabel') + serverLabel:setMarginTop(serverLabel:getMarginTop() - enterGame.authenticatorHeight) + + enterGame:breakAnchors() + enterGame:setY(enterGame:getY() + enterGame.authenticatorHeight) + enterGame:bindRectToParent() + + enterGame:setHeight(enterGame:getHeight() - enterGame.authenticatorHeight) + end + + enterGame.authenticatorEnabled = enabled +end + +function EnterGame.onClientVersionChange(comboBox, text, data) + local clientVersion = tonumber(text) + EnterGame.toggleAuthenticatorToken(clientVersion >= 1072) +end + function EnterGame.doLogin() G.account = enterGame:getChildById('accountNameTextEdit'):getText() G.password = enterGame:getChildById('accountPasswordTextEdit'):getText() + G.authenticatorToken = enterGame:getChildById('authenticatorTokenTextEdit'):getText() G.host = enterGame:getChildById('serverHostTextEdit'):getText() G.port = tonumber(enterGame:getChildById('serverPortTextEdit'):getText()) local clientVersion = tonumber(clientBox:getText()) @@ -251,7 +296,7 @@ function EnterGame.doLogin() g_game.chooseRsa(G.host) if modules.game_things.isLoaded() then - protocolLogin:login(G.host, G.port, G.account, G.password) + protocolLogin:login(G.host, G.port, G.account, G.password, G.authenticatorToken) else loadBox:destroy() loadBox = nil @@ -272,6 +317,7 @@ function EnterGame.setDefaultServer(host, port, protocol) local clientLabel = enterGame:getChildById('clientLabel') local accountTextEdit = enterGame:getChildById('accountNameTextEdit') local passwordTextEdit = enterGame:getChildById('accountPasswordTextEdit') + local authenticatorTokenTextEdit = enterGame:getChildById('authenticatorTokenTextEdit') if hostTextEdit:getText() ~= host then hostTextEdit:setText(host) @@ -279,6 +325,7 @@ function EnterGame.setDefaultServer(host, port, protocol) clientBox:setCurrentOption(protocol) accountTextEdit:setText('') passwordTextEdit:setText('') + authenticatorTokenTextEdit:setText('') end end @@ -291,6 +338,13 @@ function EnterGame.setUniqueServer(host, port, protocol, windowWidth, windowHeig portTextEdit:setText(port) portTextEdit:setVisible(false) portTextEdit:setHeight(0) + local authenticatorTokenTextEdit = enterGame:getChildById('authenticatorTokenTextEdit') + authenticatorTokenTextEdit:setText('') + authenticatorTokenTextEdit:setVisible(false) + authenticatorTokenTextEdit:setHeight(0) + local authenticatorTokenLabel = enterGame:getChildById('authenticatorTokenLabel') + authenticatorTokenLabel:setVisible(false) + authenticatorTokenLabel:setHeight(0) clientBox:setCurrentOption(protocol) clientBox:setVisible(false) @@ -312,7 +366,7 @@ function EnterGame.setUniqueServer(host, port, protocol, windowWidth, windowHeig serverListButton:setWidth(0) local rememberPasswordBox = enterGame:getChildById('rememberPasswordBox') - rememberPasswordBox:setMarginTop(-5) + rememberPasswordBox:setMarginTop(-14) if not windowWidth then windowWidth = 236 end enterGame:setWidth(windowWidth) diff --git a/modules/client_entergame/entergame.otui b/modules/client_entergame/entergame.otui index 52c60e01..debb0ea9 100644 --- a/modules/client_entergame/entergame.otui +++ b/modules/client_entergame/entergame.otui @@ -21,6 +21,8 @@ ServerListButton < UIButton EnterGameWindow id: enterGame + &authenticatorEnabled: false + &authenticatorHeight: 44 @onEnter: EnterGame.doLogin() MenuLabel @@ -50,12 +52,30 @@ EnterGameWindow anchors.top: prev.bottom margin-top: 2 + MenuLabel + id: authenticatorTokenLabel + !text: tr('Authenticator Token') + anchors.left: prev.left + anchors.top: prev.bottom + text-auto-resize: true + margin-top: 8 + visible: false + + TextEdit + id: authenticatorTokenTextEdit + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 2 + visible: false + max-length: 8 + MenuLabel id: serverLabel !text: tr('Server') anchors.left: prev.left anchors.top: prev.bottom - margin-top: 8 + margin-top: -36 text-auto-resize: true ServerListButton diff --git a/modules/game_interface/interface.otmod b/modules/game_interface/interface.otmod index 91f0973c..bdb40fd4 100644 --- a/modules/game_interface/interface.otmod +++ b/modules/game_interface/interface.otmod @@ -30,5 +30,6 @@ Module - game_spelllist - game_cooldown - game_modaldialog + - game_unjustifiedpoints @onLoad: init() @onUnload: terminate() diff --git a/modules/game_unjustifiedpoints/unjustifiedpoints.lua b/modules/game_unjustifiedpoints/unjustifiedpoints.lua new file mode 100644 index 00000000..ad01e15d --- /dev/null +++ b/modules/game_unjustifiedpoints/unjustifiedpoints.lua @@ -0,0 +1,135 @@ +unjustifiedPointsWindow = nil +unjustifiedPointsButton = nil +contentsPanel = nil + +openPvpSituationsLabel = nil +currentSkullWidget = nil +skullTimeLabel = nil + +dayProgressBar = nil +weekProgressBar = nil +monthProgressBar = nil + +daySkullWidget = nil +weekSkullWidget = nil +monthSkullWidget = nil + +function init() + connect(g_game, { onGameStart = online, + onUnjustifiedPointsChange = onUnjustifiedPointsChange, + onOpenPvpSituationsChange = onOpenPvpSituationsChange }) + connect(LocalPlayer, { onSkullChange = onSkullChange } ) + + unjustifiedPointsButton = modules.client_topmenu.addRightGameToggleButton('unjustifiedPointsButton', + tr('Unjustified Points'), '/images/topbuttons/unjustifiedpoints', toggle) + unjustifiedPointsButton:setOn(true) + unjustifiedPointsButton:hide() + + unjustifiedPointsWindow = g_ui.loadUI('unjustifiedpoints', modules.game_interface.getRightPanel()) + unjustifiedPointsWindow:disableResize() + unjustifiedPointsWindow:setup() + + contentsPanel = unjustifiedPointsWindow:getChildById('contentsPanel') + + openPvpSituationsLabel = contentsPanel:getChildById('openPvpSituationsLabel') + currentSkullWidget = contentsPanel:getChildById('currentSkullWidget') + skullTimeLabel = contentsPanel:getChildById('skullTimeLabel') + + dayProgressBar = contentsPanel:getChildById('dayProgressBar') + weekProgressBar = contentsPanel:getChildById('weekProgressBar') + monthProgressBar = contentsPanel:getChildById('monthProgressBar') + daySkullWidget = contentsPanel:getChildById('daySkullWidget') + weekSkullWidget = contentsPanel:getChildById('weekSkullWidget') + monthSkullWidget = contentsPanel:getChildById('monthSkullWidget') + + if g_game.isOnline() then + online() + end +end + +function terminate() + disconnect(g_game, { onGameStart = online, + onUnjustifiedPointsChange = onUnjustifiedPointsChange, + onOpenPvpSituationsChange = onOpenPvpSituationsChange }) + disconnect(LocalPlayer, { onSkullChange = onSkullChange } ) + + unjustifiedPointsWindow:destroy() + unjustifiedPointsButton:destroy() +end + +function onMiniWindowClose() + unjustifiedPointsButton:setOn(false) +end + +function toggle() + if unjustifiedPointsButton:isOn() then + unjustifiedPointsWindow:close() + unjustifiedPointsButton:setOn(false) + else + unjustifiedPointsWindow:open() + unjustifiedPointsButton:setOn(true) + end +end + +function online() + if g_game.getFeature(GameUnjustifiedPoints) then + unjustifiedPointsButton:show() + else + unjustifiedPointsButton:hide() + unjustifiedPointsWindow:close() + end + + refresh() +end + +function refresh() + local localPlayer = g_game.getLocalPlayer() + + local unjustifiedPoints = g_game.getUnjustifiedPoints() + onUnjustifiedPointsChange(unjustifiedPoints) + + onSkullChange(localPlayer, localPlayer:getSkull()) + onOpenPvpSituationsChange(g_game.getOpenPvpSituations()) +end + +function onSkullChange(localPlayer, skull) + if not localPlayer:isLocalPlayer() then return end + + if skull == SkullRed or skull == SkullBlack then + currentSkullWidget:setIcon(getSkullImagePath(skull)) + currentSkullWidget:setTooltip('Remaining skull time') + else + currentSkullWidget:setIcon('') + currentSkullWidget:setTooltip('You have no skull') + end + + daySkullWidget:setIcon(getSkullImagePath(getNextSkullId(skull))) + weekSkullWidget:setIcon(getSkullImagePath(getNextSkullId(skull))) + monthSkullWidget:setIcon(getSkullImagePath(getNextSkullId(skull))) +end + +function onOpenPvpSituationsChange(amount) + openPvpSituationsLabel:setText(amount) +end + +function onUnjustifiedPointsChange(unjustifiedPoints) + if unjustifiedPoints.skullTime == 0 then + skullTimeLabel:setText('No skull') + skullTimeLabel:setTooltip('You have no skull') + else + skullTimeLabel:setText(unjustifiedPoints.skullTime .. ' days') + skullTimeLabel:setTooltip('Remaining skull time') + end + + dayProgressBar:setValue(unjustifiedPoints.killsDay, 0, 100) + dayProgressBar:setTooltip(string.format('Unjustified points gained during the last 24 hours.\n%i kills left.', unjustifiedPoints.killsDayRemaining)) + dayProgressBar:setText(unjustifiedPoints.killsDayRemaining .. ' kills left') + + weekProgressBar:setValue(unjustifiedPoints.killsWeek, 0, 100) + weekProgressBar:setTooltip(string.format('Unjustified points gained during the last 7 days.\n%i kills left.', unjustifiedPoints.killsWeekRemaining)) + weekProgressBar:setText(unjustifiedPoints.killsWeekRemaining .. ' kills left') + + monthProgressBar:setValue(unjustifiedPoints.killsMonth, 0, 100) + monthProgressBar:setTooltip(string.format('Unjustified points gained during the last 30 days.\n%i kills left.', unjustifiedPoints.killsMonthRemaining)) + monthProgressBar:setText(unjustifiedPoints.killsMonthRemaining .. ' kills left') +end diff --git a/modules/game_unjustifiedpoints/unjustifiedpoints.otmod b/modules/game_unjustifiedpoints/unjustifiedpoints.otmod new file mode 100644 index 00000000..178a414d --- /dev/null +++ b/modules/game_unjustifiedpoints/unjustifiedpoints.otmod @@ -0,0 +1,8 @@ +Module + name: game_unjustifiedpoints + description: View unjustified points + author: Summ + sandboxed: true + scripts: [ unjustifiedpoints ] + @onLoad: init() + @onUnload: terminate() diff --git a/modules/game_unjustifiedpoints/unjustifiedpoints.otui b/modules/game_unjustifiedpoints/unjustifiedpoints.otui new file mode 100644 index 00000000..dc052464 --- /dev/null +++ b/modules/game_unjustifiedpoints/unjustifiedpoints.otui @@ -0,0 +1,80 @@ +SkullProgressBar < ProgressBar + height: 13 + margin: 4 18 0 10 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + +SkullWidget < UIWidget + size: 13 13 + margin-right: 2 + anchors.right: parent.right + image-source: /images/game/skull_socket + +MiniWindow + id: unjustifiedPointsWindow + !text: tr('Unjustified Points') + height: 114 + icon: /images/topbuttons/unjustifiedpoints + @onClose: modules.game_unjustifiedpoints.onMiniWindowClose() + &save: true + + MiniWindowContents + Label + anchors.top: parent.top + anchors.left: parent.left + !text: tr('Open PvP') + !tooltip: tr('Open PvP Situations') + phantom: false + margin-top: 2 + margin-left: 10 + + Label + id: openPvpSituationsLabel + anchors.top: prev.bottom + anchors.left: parent.left + font: verdana-11px-rounded + margin-left: 12 + phantom: false + + Label + anchors.top: parent.top + anchors.right: parent.right + !text: tr('Skull Time') + margin-top: 2 + margin-right: 10 + + SkullWidget + id: currentSkullWidget + anchors.top: prev.bottom + margin-right: 10 + + Label + id: skullTimeLabel + anchors.top: prev.top + anchors.right: prev.left + font: verdana-11px-rounded + margin-right: 6 + phantom: false + + SkullProgressBar + id: dayProgressBar + margin-top: 10 + + SkullWidget + id: daySkullWidget + anchors.top: prev.top + + SkullProgressBar + id: weekProgressBar + + SkullWidget + id: weekSkullWidget + anchors.top: prev.top + + SkullProgressBar + id: monthProgressBar + + SkullWidget + id: monthSkullWidget + anchors.top: prev.top diff --git a/modules/gamelib/const.lua b/modules/gamelib/const.lua index 16f1cfaf..a27b1fa4 100644 --- a/modules/gamelib/const.lua +++ b/modules/gamelib/const.lua @@ -127,6 +127,8 @@ GameLoginPacketEncryption = 63 GameClientVersion = 64 GameContentRevision = 65 GameExperienceBonus = 66 +GameAuthenticator = 67 +GameUnjustifiedPoints = 68 TextColors = { red = '#f55e5e', --'#c83200' diff --git a/modules/gamelib/creature.lua b/modules/gamelib/creature.lua index 5a734008..382c468c 100644 --- a/modules/gamelib/creature.lua +++ b/modules/gamelib/creature.lua @@ -35,6 +35,13 @@ NpcIconTradeQuest = 4 -- @} +function getNextSkullId(skullId) + if skullId == SkullRed or skullId == SkullBlack then + return SkullBlack + end + return SkullRed +end + function getSkullImagePath(skullId) local path if skullId == SkullYellow then diff --git a/modules/gamelib/game.lua b/modules/gamelib/game.lua index cc283d38..cc273b54 100644 --- a/modules/gamelib/game.lua +++ b/modules/gamelib/game.lua @@ -74,7 +74,7 @@ function g_game.getSupportedClients() 1040, 1041, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, - 1063, 1064, 1070, 1071 + 1063, 1064, 1070, 1071, 1072 } end diff --git a/modules/gamelib/protocollogin.lua b/modules/gamelib/protocollogin.lua index e3d20630..5d107eca 100644 --- a/modules/gamelib/protocollogin.lua +++ b/modules/gamelib/protocollogin.lua @@ -2,13 +2,15 @@ ProtocolLogin = extends(Protocol, "ProtocolLogin") LoginServerError = 10 +LoginServerTokenSuccess = 12 +LoginServerTokenError = 13 LoginServerUpdate = 17 LoginServerMotd = 20 LoginServerUpdateNeeded = 30 LoginServerCharacterList = 100 LoginServerExtendedCharacterList = 101 -function ProtocolLogin:login(host, port, accountName, accountPassword) +function ProtocolLogin:login(host, port, accountName, accountPassword, authenticatorToken) if string.len(host) == 0 or port == nil or port == 0 then signalcall(self.onLoginError, self, tr("You must enter a valid server address and port.")) return @@ -16,6 +18,7 @@ function ProtocolLogin:login(host, port, accountName, accountPassword) self.accountName = accountName self.accountPassword = accountPassword + self.authenticatorToken = authenticatorToken self.connectCallback = self.sendLoginPacket self:connect(host, port) @@ -78,7 +81,10 @@ function ProtocolLogin:sendLoginPacket() local paddingBytes = g_crypt.rsaGetSize() - (msg:getMessageSize() - offset) assert(paddingBytes >= 0) - msg:addPaddingBytes(paddingBytes, 0) + for i = 1, paddingBytes do + msg:addU8(math.random(0, 0xff)) + end + if g_game.getFeature(GameLoginPacketEncryption) then msg:encryptRsa() end @@ -86,10 +92,32 @@ function ProtocolLogin:sendLoginPacket() if g_game.getFeature(GameOGLInformation) then msg:addU8(1) --unknown msg:addU8(1) --unknown - msg:addString(g_graphics.getRenderer()) + + if g_game.getClientVersion() >= 1072 then + msg:addString(string.format('%s %s', g_graphics.getVendor(), g_graphics.getRenderer())) + else + msg:addString(g_graphics.getRenderer()) + end msg:addString(g_graphics.getVersion()) end + -- add RSA encrypted auth token + if g_game.getFeature(GameAuthenticator) then + offset = msg:getMessageSize() + + -- first RSA byte must be 0 + msg:addU8(0) + msg:addString(self.authenticatorToken) + + paddingBytes = g_crypt.rsaGetSize() - (msg:getMessageSize() - offset) + assert(paddingBytes >= 0) + for i = 1, paddingBytes do + msg:addU8(math.random(0, 0xff)) + end + + msg:encryptRsa() + end + if g_game.getFeature(GameProtocolChecksum) then self:enableChecksum() end @@ -116,6 +144,9 @@ function ProtocolLogin:onRecv(msg) self:parseMotd(msg) elseif opcode == LoginServerUpdateNeeded then signalcall(self.onLoginError, self, tr("Client needs update.")) + elseif opcode == LoginServerTokenError then + -- TODO: prompt for token here + signalcall(self.onLoginError, self, tr("Invalid authentification token.")) elseif opcode == LoginServerCharacterList then self:parseCharacterList(msg) elseif opcode == LoginServerExtendedCharacterList then diff --git a/src/client/const.h b/src/client/const.h index 902cb7d5..18ffa33b 100644 --- a/src/client/const.h +++ b/src/client/const.h @@ -399,6 +399,8 @@ namespace Otc GameClientVersion = 64, GameContentRevision = 65, GameExperienceBonus = 66, + GameAuthenticator = 67, + GameUnjustifiedPoints = 68, LastGameFeature = 101 }; diff --git a/src/client/game.cpp b/src/client/game.cpp index dde46699..fe133e3f 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -84,6 +84,7 @@ void Game::resetGameStates() m_localPlayer = nullptr; m_pingSent = 0; m_pingReceived = 0; + m_unjustifiedPoints = UnjustifiedPoints(); for(auto& it : m_containers) { const ContainerPtr& container = it.second; @@ -155,6 +156,11 @@ void Game::processLoginWait(const std::string& message, int time) g_lua.callGlobalField("g_game", "onLoginWait", message, time); } +void Game::processLoginToken(bool unknown) +{ + g_lua.callGlobalField("g_game", "onLoginToken", unknown); +} + void Game::processLogin() { g_lua.callGlobalField("g_game", "onLogin"); @@ -528,7 +534,7 @@ void Game::processWalkCancel(Otc::Direction direction) m_localPlayer->cancelWalk(direction); } -void Game::loginWorld(const std::string& account, const std::string& password, const std::string& worldName, const std::string& worldHost, int worldPort, const std::string& characterName) +void Game::loginWorld(const std::string& account, const std::string& password, const std::string& worldName, const std::string& worldHost, int worldPort, const std::string& characterName, const std::string& authenticatorToken) { if(m_protocolGame || isOnline()) stdext::throw_exception("Unable to login into a world while already online or logging."); @@ -543,7 +549,7 @@ void Game::loginWorld(const std::string& account, const std::string& password, c m_localPlayer->setName(characterName); m_protocolGame = ProtocolGamePtr(new ProtocolGame); - m_protocolGame->login(account, password, worldHost, (uint16)worldPort, characterName); + m_protocolGame->login(account, password, worldHost, (uint16)worldPort, characterName, authenticatorToken); m_characterName = characterName; m_worldName = worldName; } @@ -1204,6 +1210,31 @@ void Game::setPVPMode(Otc::PVPModes pvpMode) g_lua.callGlobalField("g_game", "onPVPModeChange", pvpMode); } +void Game::setUnjustifiedPoints(UnjustifiedPoints unjustifiedPoints) +{ + if(!canPerformGameAction()) + return; + if(!getFeature(Otc::GameUnjustifiedPoints)) + return; + if(m_unjustifiedPoints == unjustifiedPoints) + return; + + m_unjustifiedPoints = unjustifiedPoints; + g_lua.callGlobalField("g_game", "onUnjustifiedPointsChange", unjustifiedPoints); +} + +void Game::setOpenPvpSituations(int openPvpSituations) +{ + if(!canPerformGameAction()) + return; + if(m_openPvpSituations == openPvpSituations) + return; + + m_openPvpSituations = openPvpSituations; + g_lua.callGlobalField("g_game", "onOpenPvpSituationsChange", openPvpSituations); +} + + void Game::inspectNpcTrade(const ItemPtr& item) { if(!canPerformGameAction() || !item) @@ -1425,7 +1456,7 @@ void Game::setProtocolVersion(int version) if(isOnline()) stdext::throw_exception("Unable to change protocol version while online"); - if(version != 0 && (version < 740 || version > 1071)) + if(version != 0 && (version < 740 || version > 1072)) stdext::throw_exception(stdext::format("Protocol version %d not supported", version)); m_protocolVersion = version; @@ -1443,7 +1474,7 @@ void Game::setClientVersion(int version) if(isOnline()) stdext::throw_exception("Unable to change client version while online"); - if(version != 0 && (version < 740 || version > 1071)) + if(version != 0 && (version < 740 || version > 1072)) stdext::throw_exception(stdext::format("Client version %d not supported", version)); m_features.reset(); @@ -1563,6 +1594,10 @@ void Game::setClientVersion(int version) enableFeature(Otc::GameEnhancedAnimations); } + if(version >= 1053) { + enableFeature(Otc::GameUnjustifiedPoints); + } + if(version >= 1054) { enableFeature(Otc::GameExperienceBonus); } @@ -1575,6 +1610,10 @@ void Game::setClientVersion(int version) enableFeature(Otc::GameContentRevision); } + if(version >= 1072) { + enableFeature(Otc::GameAuthenticator); + } + m_clientVersion = version; g_lua.callGlobalField("g_game", "onClientVersionChange", version); diff --git a/src/client/game.h b/src/client/game.h index 1bf3228f..8d3a6a9b 100644 --- a/src/client/game.h +++ b/src/client/game.h @@ -36,6 +36,25 @@ #include +struct UnjustifiedPoints { + bool operator==(const UnjustifiedPoints& other) { + return killsDay == other.killsDay && + killsDayRemaining == other.killsDayRemaining && + killsWeek == other.killsWeek && + killsWeekRemaining == other.killsWeekRemaining && + killsMonth == other.killsMonth && + killsMonthRemaining == other.killsMonthRemaining && + skullTime == other.skullTime; + } + uint8 killsDay; + uint8 killsDayRemaining; + uint8 killsWeek; + uint8 killsWeekRemaining; + uint8 killsMonth; + uint8 killsMonthRemaining; + uint8 skullTime; +}; + typedef std::tuple Vip; //@bindsingleton g_game @@ -60,6 +79,7 @@ protected: void processLoginError(const std::string& error); void processLoginAdvice(const std::string& message); void processLoginWait(const std::string& message, int time); + void processLoginToken(bool unknown); void processLogin(); void processPendingGame(); void processEnterGame(); @@ -139,7 +159,7 @@ protected: public: // login related - void loginWorld(const std::string& account, const std::string& password, const std::string& worldName, const std::string& worldHost, int worldPort, const std::string& characterName); + void loginWorld(const std::string& account, const std::string& password, const std::string& worldName, const std::string& worldHost, int worldPort, const std::string& characterName, const std::string& authenticatorToken); void cancelLogin(); void forceLogout(); void safeLogout(); @@ -218,6 +238,12 @@ public: bool isSafeFight() { return m_safeFight; } Otc::PVPModes getPVPMode() { return m_pvpMode; } + // pvp related + void setUnjustifiedPoints(UnjustifiedPoints unjustifiedPoints); + UnjustifiedPoints getUnjustifiedPoints() { return m_unjustifiedPoints; }; + void setOpenPvpSituations(int openPvpSitations); + int getOpenPvpSituations() { return m_openPvpSituations; } + // npc trade related void inspectNpcTrade(const ItemPtr& item); void buyItem(const ItemPtr& item, int amount, bool ignoreCapacity, bool buyWithBackpack); @@ -304,6 +330,8 @@ public: int getServerBeat() { return m_serverBeat; } void setCanReportBugs(bool enable) { m_canReportBugs = enable; } bool canReportBugs() { return m_canReportBugs; } + void setExpertPvpMode(bool enable) { m_expertPvpMode = enable; } + bool getExpertPvpMode() { return m_expertPvpMode; } LocalPlayerPtr getLocalPlayer() { return m_localPlayer; } ProtocolGamePtr getProtocolGame() { return m_protocolGame; } std::string getCharacterName() { return m_characterName; } @@ -333,6 +361,7 @@ private: bool m_online; bool m_denyBotCall; bool m_dead; + bool m_expertPvpMode; int m_serverBeat; ticks_t m_ping; uint m_pingSent; @@ -345,6 +374,8 @@ private: Otc::ChaseModes m_chaseMode; Otc::PVPModes m_pvpMode; Otc::Direction m_lastWalkDir; + UnjustifiedPoints m_unjustifiedPoints; + int m_openPvpSituations; bool m_safeFight; bool m_canReportBugs; std::vector m_gmActions; diff --git a/src/client/luafunctions.cpp b/src/client/luafunctions.cpp index f8cdcccb..e4f8b60f 100644 --- a/src/client/luafunctions.cpp +++ b/src/client/luafunctions.cpp @@ -238,6 +238,10 @@ void Client::registerLuaFunctions() g_lua.bindSingletonFunction("g_game", "getChaseMode", &Game::getChaseMode, &g_game); g_lua.bindSingletonFunction("g_game", "getFightMode", &Game::getFightMode, &g_game); g_lua.bindSingletonFunction("g_game", "getPVPMode", &Game::getPVPMode, &g_game); + g_lua.bindSingletonFunction("g_game", "setUnjustifiedPoints", &Game::setUnjustifiedPoints, &g_game); + g_lua.bindSingletonFunction("g_game", "getUnjustifiedPoints", &Game::getUnjustifiedPoints, &g_game); + g_lua.bindSingletonFunction("g_game", "setOpenPvpSituations", &Game::setOpenPvpSituations, &g_game); + g_lua.bindSingletonFunction("g_game", "getOpenPvpSituations", &Game::getOpenPvpSituations, &g_game); g_lua.bindSingletonFunction("g_game", "isSafeFight", &Game::isSafeFight, &g_game); g_lua.bindSingletonFunction("g_game", "inspectNpcTrade", &Game::inspectNpcTrade, &g_game); g_lua.bindSingletonFunction("g_game", "buyItem", &Game::buyItem, &g_game); diff --git a/src/client/luavaluecasts.cpp b/src/client/luavaluecasts.cpp index a4c4e524..c2041a9e 100644 --- a/src/client/luavaluecasts.cpp +++ b/src/client/luavaluecasts.cpp @@ -165,3 +165,45 @@ bool luavalue_cast(int index, Light& light) } return false; } + +int push_luavalue(const UnjustifiedPoints& unjustifiedPoints) +{ + g_lua.createTable(0, 7); + g_lua.pushInteger(unjustifiedPoints.killsDay); + g_lua.setField("killsDay"); + g_lua.pushInteger(unjustifiedPoints.killsDayRemaining); + g_lua.setField("killsDayRemaining"); + g_lua.pushInteger(unjustifiedPoints.killsWeek); + g_lua.setField("killsWeek"); + g_lua.pushInteger(unjustifiedPoints.killsWeekRemaining); + g_lua.setField("killsWeekRemaining"); + g_lua.pushInteger(unjustifiedPoints.killsMonth); + g_lua.setField("killsMonth"); + g_lua.pushInteger(unjustifiedPoints.killsMonthRemaining); + g_lua.setField("killsMonthRemaining"); + g_lua.pushInteger(unjustifiedPoints.skullTime); + g_lua.setField("skullTime"); + return 1; +} + +bool luavalue_cast(int index, UnjustifiedPoints& unjustifiedPoints) +{ + if(g_lua.isTable(index)) { + g_lua.getField("killsDay", index); + unjustifiedPoints.killsDay = g_lua.popInteger(); + g_lua.getField("killsDayRemaining", index); + unjustifiedPoints.killsDayRemaining = g_lua.popInteger(); + g_lua.getField("killsWeek", index); + unjustifiedPoints.killsWeek = g_lua.popInteger(); + g_lua.getField("killsWeekRemaining", index); + unjustifiedPoints.killsWeekRemaining = g_lua.popInteger(); + g_lua.getField("killsMonth", index); + unjustifiedPoints.killsMonth = g_lua.popInteger(); + g_lua.getField("killsMonthRemaining", index); + unjustifiedPoints.killsMonthRemaining = g_lua.popInteger(); + g_lua.getField("skullTime", index); + unjustifiedPoints.skullTime = g_lua.popInteger(); + return true; + } + return false; +} diff --git a/src/client/luavaluecasts.h b/src/client/luavaluecasts.h index 8d35b19b..d4548bfb 100644 --- a/src/client/luavaluecasts.h +++ b/src/client/luavaluecasts.h @@ -44,4 +44,8 @@ bool luavalue_cast(int index, MarketData& data); int push_luavalue(const Light& light); bool luavalue_cast(int index, Light& light); +// unjustified points +int push_luavalue(const UnjustifiedPoints& unjustifiedPoints); +bool luavalue_cast(int index, UnjustifiedPoints& unjustifiedPoints); + #endif diff --git a/src/client/protocolcodes.h b/src/client/protocolcodes.h index 69780587..26fda831 100644 --- a/src/client/protocolcodes.h +++ b/src/client/protocolcodes.h @@ -50,6 +50,7 @@ namespace Proto { GameServerLoginAdvice = 21, GameServerLoginWait = 22, GameServerLoginSuccess = 23, + GameServerLoginToken = 24, GameServerPingBack = 29, GameServerPing = 30, GameServerChallenge = 31, diff --git a/src/client/protocolgame.cpp b/src/client/protocolgame.cpp index 0d3ba266..bab30ef1 100644 --- a/src/client/protocolgame.cpp +++ b/src/client/protocolgame.cpp @@ -26,10 +26,11 @@ #include "item.h" #include "localplayer.h" -void ProtocolGame::login(const std::string& accountName, const std::string& accountPassword, const std::string& host, uint16 port, const std::string& characterName) +void ProtocolGame::login(const std::string& accountName, const std::string& accountPassword, const std::string& host, uint16 port, const std::string& characterName, const std::string& authenticatorToken) { m_accountName = accountName; m_accountPassword = accountPassword; + m_authenticatorToken = authenticatorToken; m_characterName = characterName; connect(host, port); diff --git a/src/client/protocolgame.h b/src/client/protocolgame.h index 7be5b44c..6d65e8f9 100644 --- a/src/client/protocolgame.h +++ b/src/client/protocolgame.h @@ -31,7 +31,7 @@ class ProtocolGame : public Protocol { public: - void login(const std::string& accountName, const std::string& accountPassword, const std::string& host, uint16 port, const std::string& characterName); + void login(const std::string& accountName, const std::string& accountPassword, const std::string& host, uint16 port, const std::string& characterName, const std::string& authenticatorToken); void send(const OutputMessagePtr& outputMessage); void sendExtendedOpcode(uint8 opcode, const std::string& buffer); @@ -143,6 +143,7 @@ private: void parseLoginError(const InputMessagePtr& msg); void parseLoginAdvice(const InputMessagePtr& msg); void parseLoginWait(const InputMessagePtr& msg); + void parseLoginToken(const InputMessagePtr& msg); void parsePing(const InputMessagePtr& msg); void parsePingBack(const InputMessagePtr& msg); void parseChallenge(const InputMessagePtr& msg); @@ -246,6 +247,7 @@ private: stdext::boolean m_firstRecv; std::string m_accountName; std::string m_accountPassword; + std::string m_authenticatorToken; std::string m_characterName; LocalPlayerPtr m_localPlayer; }; diff --git a/src/client/protocolgameparse.cpp b/src/client/protocolgameparse.cpp index 0fd626cc..8853c150 100644 --- a/src/client/protocolgameparse.cpp +++ b/src/client/protocolgameparse.cpp @@ -79,6 +79,9 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg) case Proto::GameServerLoginWait: parseLoginWait(msg); break; + case Proto::GameServerLoginToken: + parseLoginToken(msg); + break; case Proto::GameServerPing: case Proto::GameServerPingBack: if((opcode == Proto::GameServerPing && g_game.getFeature(Otc::GameClientPing)) || @@ -385,11 +388,13 @@ void ProtocolGame::parseLogin(const InputMessagePtr& msg) } bool canReportBugs = msg->getU8(); - if(g_game.getClientVersion() >= 1053) + if(g_game.getClientVersion() >= 1054) msg->getU8(); // can change pvp frame option - if(g_game.getClientVersion() >= 1058) - msg->getU8(); // expert mode enabled + if(g_game.getClientVersion() >= 1058) { + int expertModeEnabled = msg->getU8(); + g_game.setExpertPvpMode(expertModeEnabled); + } m_localPlayer->setId(playerId); g_game.setServerBeat(serverBeat); @@ -428,19 +433,23 @@ void ProtocolGame::parsePreset(const InputMessagePtr& msg) void ProtocolGame::parseUnjustifiedStats(const InputMessagePtr& msg) { - // Unjustified Kills display since 10.55 - msg->getU8(); - msg->getU8(); - msg->getU8(); - msg->getU8(); - msg->getU8(); - msg->getU8(); - msg->getU8(); + UnjustifiedPoints unjustifiedPoints; + unjustifiedPoints.killsDay = msg->getU8(); + unjustifiedPoints.killsDayRemaining = msg->getU8(); + unjustifiedPoints.killsWeek = msg->getU8(); + unjustifiedPoints.killsWeekRemaining = msg->getU8(); + unjustifiedPoints.killsMonth = msg->getU8(); + unjustifiedPoints.killsMonthRemaining = msg->getU8(); + unjustifiedPoints.skullTime = msg->getU8(); + + g_game.setUnjustifiedPoints(unjustifiedPoints); } void ProtocolGame::parsePvpSituations(const InputMessagePtr& msg) { - msg->getU8(); // amount of open pvp situations + uint8 openPvpSituations = msg->getU8(); + + g_game.setOpenPvpSituations(openPvpSituations); } void ProtocolGame::parsePlayerHelpers(const InputMessagePtr& msg) @@ -501,6 +510,12 @@ void ProtocolGame::parseLoginWait(const InputMessagePtr& msg) g_game.processLoginWait(message, time); } +void ProtocolGame::parseLoginToken(const InputMessagePtr& msg) +{ + bool unknown = (msg->getU8() == 0); + g_game.processLoginToken(unknown); +} + void ProtocolGame::parsePing(const InputMessagePtr& msg) { g_game.processPing(); diff --git a/src/client/protocolgamesend.cpp b/src/client/protocolgamesend.cpp index a32ee396..f0e2d167 100644 --- a/src/client/protocolgamesend.cpp +++ b/src/client/protocolgamesend.cpp @@ -87,6 +87,9 @@ void ProtocolGame::sendLoginPacket(uint challengeTimestamp, uint8 challengeRando msg->addString(m_characterName); msg->addString(m_accountPassword); + if(g_game.getFeature(Otc::GameAuthenticator)) + msg->addString(m_authenticatorToken); + if(g_game.getFeature(Otc::GameChallengeOnLogin)) { msg->addU32(challengeTimestamp); msg->addU8(challengeRandom);