Multi-protocol

Lots of chagnes to add multi protocol flexibility, not really
completed yet, still have to rework text messages opcodes and other stuff,
so this still a working in progress feature

* Rework dat reader, the dat reader can now
* dinamically detect dat version
* Split game into gamelib and game_interface
* Lots of other minor changes
This commit is contained in:
Eduardo Bart 2012-07-17 20:49:21 -03:00
parent 6fc11d2fa9
commit eb24d6776e
64 changed files with 536 additions and 588 deletions

View File

@ -29,17 +29,18 @@ g_configs.load("/config.otml")
g_modules.discoverModules()
-- core modules 0-99
g_modules.autoLoadModules(99)
-- libraries modules 0-99
g_modules.autoLoadModules(99);
g_modules.ensureModuleLoaded("corelib")
g_modules.ensureModuleLoaded("gamelib")
-- client modules 100-499
g_modules.autoLoadModules(499)
g_modules.ensureModuleLoaded("client")
-- game modules 500-999
g_modules.autoLoadModules(999)
g_modules.ensureModuleLoaded("game")
g_modules.autoLoadModules(999);
g_modules.ensureModuleLoaded("game_interface")
-- mods 1000-9999
g_modules.autoLoadModules(9999)

View File

@ -16,7 +16,6 @@ Module
- client_terminal
- client_modulemanager
- client_entergame
- game
@onLoad: |
dofile 'client'

View File

@ -11,7 +11,6 @@ function Background.init()
local clientVersionLabel = background:getChildById('clientVersionLabel')
clientVersionLabel:setText('OTClient ' .. g_app.getVersion() .. '\n' ..
'Rev ' .. g_app.getBuildRevision() .. ' ('.. g_app.getBuildCommit() .. ')\n' ..
'Protocol ' .. g_game.getProtocolVersion() .. '\n' ..
'Built on ' .. g_app.getBuildDate())
if not g_game.isOnline() then

View File

@ -54,7 +54,7 @@ local function updateWait(timeStart, timeEnd)
progressBar:setPercent(percent)
local label = waitingWindow:getChildById('timeLabel')
label:setText('Trying to reconnect in ' .. timeStr .. ' seconds.')
label:setText(tr('Trying to reconnect in %s seconds.', timeStr))
updateWaitEvent = scheduleEvent(function() updateWait(timeStart, timeEnd) end, 1000 * progressBar:getPercentPixels() / 100 * (timeEnd - timeStart))
return true

View File

@ -5,6 +5,7 @@ local loadBox
local enterGame
local motdButton
local enterGameButton
local protocolBox
-- private functions
local function onError(protocol, message, errorCode)
@ -66,6 +67,11 @@ function EnterGame.init()
local host = g_settings.get('host')
local port = g_settings.get('port')
local autologin = g_settings.getBoolean('autologin')
local protocol = g_settings.getInteger('protocol', 860)
if not protocol or protocol == 0 then
protocol = 860
end
if port == nil or port == 0 then port = 7171 end
@ -77,6 +83,12 @@ function EnterGame.init()
enterGame:getChildById('rememberPasswordBox'):setChecked(#account > 0)
enterGame:getChildById('accountNameTextEdit'):focus()
protocolBox = enterGame:getChildById('protocolComboBox')
for _i,proto in pairs(g_game.getSupportedProtocols()) do
protocolBox:addOption(proto)
end
protocolBox:setCurrentOption(protocol)
-- only open entergame when app starts
if not g_app.isRunning() then
if #host > 0 and #password > 0 and #account > 0 and autologin then
@ -95,6 +107,7 @@ function EnterGame.terminate()
enterGameButton = nil
motdButton:destroy()
motdButton = nil
protocolBox = nil
EnterGame = nil
end
@ -116,7 +129,6 @@ function EnterGame.openWindow()
end
end
function EnterGame.clearAccountFields()
enterGame:getChildById('accountNameTextEdit'):clearText()
enterGame:getChildById('accountPasswordTextEdit'):clearText()
@ -130,16 +142,18 @@ function EnterGame.doLogin()
G.password = enterGame:getChildById('accountPasswordTextEdit'):getText()
G.host = enterGame:getChildById('serverHostTextEdit'):getText()
G.port = tonumber(enterGame:getChildById('serverPortTextEdit'):getText())
local protocol = tonumber(protocolBox:getText())
EnterGame.hide()
if G.host == '' or G.port == nil or G.port == 0 then
local errorBox = displayErrorBox(tr('Login Error'), tr('Enter a valid server host and port to login.'))
if g_game.isOnline() then
local errorBox = displayErrorBox(tr('Login Error'), tr('Cannot login while already in game.'))
connect(errorBox, { onOk = EnterGame.show })
return
end
g_settings.set('host', G.host)
g_settings.set('port', G.port)
g_settings.set('protocol', protocol)
local protocolLogin = ProtocolLogin.create()
protocolLogin.onError = onError
@ -153,6 +167,8 @@ function EnterGame.doLogin()
EnterGame.show()
end })
g_game.chooseRsa(G.host)
g_game.setProtocolVersion(protocol)
protocolLogin:login(G.host, G.port, G.account, G.password)
end

View File

@ -1,7 +1,7 @@
MainWindow
id: enterGame
!text: tr('Enter Game')
size: 236 240
size: 236 274
@onEnter: EnterGame.doLogin()
@onEscape: EnterGame.hide()
@ -43,26 +43,43 @@ MainWindow
TextEdit
id: serverHostTextEdit
!tooltip: tr('Make sure that your client uses\nthe correct game protocol version')
anchors.left: serverLabel.left
anchors.left: parent.left
anchors.right: parent.right
anchors.top: serverLabel.bottom
margin-top: 2
width: 140
Label
id: protocolLabel
!text: tr('Protocol')
anchors.left: parent.left
anchors.top: serverHostTextEdit.bottom
anchors.right: portLabel.left
margin-right: 10
margin-top: 8
ComboBox
id: protocolComboBox
anchors.left: protocolLabel.left
anchors.right: protocolLabel.right
anchors.top: protocolLabel.bottom
margin-top: 2
width: 90
Label
id: portLabel
!text: tr('Port')
anchors.left: serverHostTextEdit.right
anchors.top: serverLabel.top
margin-left: 10
text-auto-resize: true
anchors.right: parent.right
anchors.top: serverHostTextEdit.bottom
margin-top: 8
width: 70
TextEdit
id: serverPortTextEdit
text: 7171
anchors.right: parent.right
anchors.left: portLabel.left
anchors.top: portLabel.bottom
margin-top: 2
width: 55
CheckBox
id: rememberPasswordBox

View File

@ -55,4 +55,4 @@ function Extended.unregister(opcode)
callbacks[opcode] = nil
return true
end
end

View File

@ -132,6 +132,7 @@ function getfsrcpath(depth)
end
function resolvepath(filePath, depth)
if not filePath then return nil end
depth = depth or 1
if filePath then
if filePath:sub(0, 1) ~= '/' then

View File

@ -1,44 +0,0 @@
Module
name: game
description: Contains game related classes
author: OTClient team
website: www.otclient.info
dependencies:
- client_extended
- client_background
- game_tibiafiles
load-later:
- game_interface
- game_hotkeys
- game_questlog
- game_textmessage
- game_console
- game_outfit
- game_healthinfo
- game_skills
- game_inventory
- game_combatcontrols
- game_containers
- game_viplist
- game_battle
- game_minimap
- game_npctrade
- game_textwindow
- game_playertrade
- game_ruleviolation
- game_bugreport
- game_shaders
- game_playerdeath
- game_playermount
- game_market
@onLoad: |
dofile 'const'
dofile 'protocollogin'
dofile 'creature'
dofile 'player'
dofile 'market'

View File

@ -224,7 +224,7 @@ function Battle.checkCreatureSkull(creature, skullId)
if creature:getSkull() ~= SkullNone then
skullWidget:setWidth(skullWidget:getHeight())
local imagePath = getSkullImagePath(creature:getSkull())
skullWidget:setImageSource('/game/' .. imagePath)
skullWidget:setImageSource(imagePath)
labelWidget:setMarginLeft(5)
else
skullWidget:setWidth(0)
@ -246,7 +246,7 @@ function Battle.checkCreatureEmblem(creature, emblemId)
if emblemId ~= EmblemNone then
emblemWidget:setWidth(emblemWidget:getHeight())
local imagePath = getEmblemImagePath(emblemId)
emblemWidget:setImageSource('/game/' .. imagePath)
emblemWidget:setImageSource(imagePath)
emblemWidget:setMarginLeft(5)
labelWidget:setMarginLeft(5)
else

View File

@ -4,6 +4,30 @@ Module
author: OTClient team
website: www.otclient.info
load-later:
- game_hotkeys
- game_questlog
- game_textmessage
- game_console
- game_outfit
- game_healthinfo
- game_skills
- game_inventory
- game_combatcontrols
- game_containers
- game_viplist
- game_battle
- game_minimap
- game_npctrade
- game_textwindow
- game_playertrade
- game_ruleviolation
- game_bugreport
- game_shaders
- game_playerdeath
- game_playermount
- game_market
@onLoad: |
dofile 'widgets/uigamemap'
dofile 'widgets/uiitem'

View File

@ -4,9 +4,6 @@ Module
author: BeniS
website: www.otclient.info
dependencies:
- game
@onLoad: |
dofile 'marketoffer'
dofile 'marketprotocol'

View File

@ -32,7 +32,7 @@ MarketOffer.new = function(offerId, action, itemId, amount, price, playerName, s
offer.player = playerName
state = tonumber(state)
if state ~= MarketOfferState.Active and state ~= MarketOfferState.Cancelled
if state ~= MarketOfferState.Active and state ~= MarketOfferState.Cancelled
and state ~= MarketOfferState.Expired and state ~= MarketOfferState.Accepted then
g_logger.error('MarketOffer.new - invalid state provided.')
end

View File

@ -3,27 +3,6 @@ MarketProtocol = {}
local market
-- private functions
local function parseOpcode(protocol, opcode, msg)
if not g_game.getFeature(GamePlayerMarket) then
return false
end
-- process msg
if opcode == GameServerOpcodes.GameServerMarketEnter then
parseMarketEnter(msg)
elseif opcode == GameServerOpcodes.GameServerMarketLeave then
parseMarketLeave(msg)
elseif opcode == GameServerOpcodes.GameServerMarketDetail then
parseMarketDetail(msg)
elseif opcode == GameServerOpcodes.GameServerMarketBrowse then
parseMarketBrowse(msg)
else
return false
end
return true
end
local function send(msg)
print(msg:getMessageSize())
g_game.getProtocolGame():safeSend(msg)
@ -49,12 +28,11 @@ local function readMarketOffer(msg, action, var)
else
playerName = msg:getString()
end
return MarketOffer.new({timestamp, counter}, action, itemId, amount, price, playerName, state)
end
-- parsing protocols
local function parseMarketEnter(msg)
local balance = msg:getU32()
local offers = msg:getU8()
@ -128,14 +106,18 @@ local function parseMarketBrowse(msg)
end
-- public functions
function MarketProtocol.init()
connect(ProtocolGame, { onOpcode = parseOpcode } )
ProtocolGame.registerOpcode(GameServerOpcodes.GameServerMarketEnter, parseMarketEnter)
ProtocolGame.registerOpcode(GameServerOpcodes.GameServerMarketLeave, parseMarketLeave)
ProtocolGame.registerOpcode(GameServerOpcodes.GameServerMarketDetail, parseMarketDetail)
ProtocolGame.registerOpcode(GameServerOpcodes.GameServerMarketBrowse, parseMarketBrowse)
end
function MarketProtocol.terminate()
disconnect(ProtocolGame, { onOpcode = parseOpcode } )
ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerMarketEnter, parseMarketEnter)
ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerMarketLeave, parseMarketLeave)
ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerMarketDetail, parseMarketDetail)
ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerMarketBrowse, parseMarketBrowse)
market = nil
MarketProtocol = nil

View File

@ -34,11 +34,6 @@ SouthEast = 5
SouthWest = 6
NorthWest = 7
LoginServerError = 10
LoginServerMotd = 20
LoginServerUpdateNeeded = 30
LoginServerCharacterList = 100
GameExtendedOpcode = 0
GameProtocolChecksum = 1
GameAccountNames = 2
@ -56,7 +51,7 @@ GameChannelPlayerList = 13
GamePlayerMounts = 14
GameEnvironmentEffect = 15
GameCreatureType = 16
GameCreatureAdditionalInfo = 17
GameCreatureEmblems = 17
GameCreaturePassableInfo = 18
GameItemAnimationPhase = 19
GameTrucatedPingOpcode = 20
@ -64,6 +59,25 @@ GameReverseCreatureStack = 21
GameMagicEffectU16 = 22
GamePlayerMarket = 23
OTSERV_RSA = "109120132967399429278860960508995541528237502902798129123468757937266291492576446330739696001110603907230888610072655818825358503429057592827629436413108566029093628212635953836686562675849720620786279431090218017681061521755056710823876476444260558147179707119674283982419152118103759076030616683978566631413"
OTSERV_RSA = "1091201329673994292788609605089955415282375029027981291234687579" ..
"3726629149257644633073969600111060390723088861007265581882535850" ..
"3429057592827629436413108566029093628212635953836686562675849720" ..
"6207862794310902180176810615217550567108238764764442605581471797" ..
"07119674283982419152118103759076030616683978566631413"
-- @}
CIPSOFT_RSA = "1321277432058722840622950990822933849527763264961655079678763618" ..
"4334395343554449668205332383339435179772895415509701210392836078" ..
"6959821132214473291575712138800495033169914814069637740318278150" ..
"2907336840325241747827401343576296990629870233111328210165697754" ..
"88792221429527047321331896351555606801473202394175817"
OsTypes = {
Linux = 1,
Windows = 2,
Flash = 3,
OtclientLinux = 10,
OtclientWindows = 11,
OtclientMac = 12
}
-- @}

View File

@ -30,73 +30,81 @@ EmblemBlue = 3
-- @}
function getSkullImagePath(skullId)
local path
if skullId == SkullYellow then
return 'icons/skull_yellow.png'
path = 'icons/skull_yellow.png'
elseif skullId == SkullGreen then
return 'icons/skull_green.png'
path = 'icons/skull_green.png'
elseif skullId == SkullWhite then
return 'icons/skull_white.png'
path = 'icons/skull_white.png'
elseif skullId == SkullRed then
return 'icons/skull_red.png'
path = 'icons/skull_red.png'
elseif skullId == SkullBlack then
return 'icons/skull_black.png'
path = 'icons/skull_black.png'
elseif skullId == SkullOrange then
return 'icons/skull_orange.png'
path = 'icons/skull_orange.png'
end
path = resolvepath(path)
return path
end
function getShieldImagePathAndBlink(shieldId)
local path
if shieldId == ShieldWhiteYellow then
return 'icons/shield_yellow_white.png', false
path = 'icons/shield_yellow_white.png', false
elseif shieldId == ShieldWhiteBlue then
return 'icons/shield_blue_white.png', false
path = 'icons/shield_blue_white.png', false
elseif shieldId == ShieldBlue then
return 'icons/shield_blue.png', false
path = 'icons/shield_blue.png', false
elseif shieldId == ShieldYellow then
return 'icons/shield_yellow.png', false
path = 'icons/shield_yellow.png', false
elseif shieldId == ShieldBlueSharedExp then
return 'icons/shield_blue_shared.png', false
path = 'icons/shield_blue_shared.png', false
elseif shieldId == ShieldYellowSharedExp then
return 'icons/shield_yellow_shared.png', false
path = 'icons/shield_yellow_shared.png', false
elseif shieldId == ShieldBlueNoSharedExpBlink then
return 'icons/shield_blue_not_shared.png', true
path = 'icons/shield_blue_not_shared.png', true
elseif shieldId == ShieldYellowNoSharedExpBlink then
return 'icons/shield_yellow_not_shared.png', true
path = 'icons/shield_yellow_not_shared.png', true
elseif shieldId == ShieldBlueNoSharedExp then
return 'icons/shield_blue_not_shared.png', false
path = 'icons/shield_blue_not_shared.png', false
elseif shieldId == ShieldYellowNoSharedExp then
return 'icons/shield_yellow_not_shared.png', false
path = 'icons/shield_yellow_not_shared.png', false
end
path = resolvepath(path)
return path
end
function getEmblemImagePath(emblemId)
local path
if emblemId == EmblemGreen then
return 'icons/emblem_green.png'
path = 'icons/emblem_green.png'
elseif emblemId == EmblemRed then
return 'icons/emblem_red.png'
path = 'icons/emblem_red.png'
elseif emblemId == EmblemBlue then
return 'icons/emblem_blue.png'
path = 'icons/emblem_blue.png'
end
path = resolvepath(path)
return path
end
function Creature:onSkullChange(skullId)
local imagePath = getSkullImagePath(skullId)
if imagePath then
self:setSkullTexture(resolvepath(imagePath))
self:setSkullTexture(imagePath)
end
end
function Creature:onShieldChange(shieldId)
local imagePath, blink = getShieldImagePathAndBlink(shieldId)
if imagePath then
self:setShieldTexture(resolvepath(imagePath), blink)
self:setShieldTexture(imagePath, blink)
end
end
function Creature:onEmblemChange(emblemId)
local imagePath = getEmblemImagePath(emblemId)
if imagePath then
self:setEmblemTexture(resolvepath(imagePath))
self:setEmblemTexture(imagePath)
end
end

47
modules/gamelib/game.lua Normal file
View File

@ -0,0 +1,47 @@
local currentRsa = OTSERV_RSA
function g_game.getRsa()
return currentRsa
end
function g_game.chooseRsa(host)
if host:match('.*\.tibia\.com') or host:match('.*\.cipsoft\.com') then
currentRsa = CIPSOFT_RSA
else
currentRsa = OTSERV_RSA
end
end
function g_game.setRsa(rsa)
currentRsa = rsa
end
function g_game.isOfficialTibia()
return currentRsa == CIPSOFT_RSA
end
function g_game.getOsType()
if g_game.isOfficialTibia() then
if g_app.getOs() == 'windows' then
return OsTypes.Windows
else
return OsTypes.Linux
end
else
if g_app.getOs() == 'windows' then
return OsTypes.OtclientWindows
elseif g_app.getOs() == 'mac' then
return OsTypes.OtclientMac
else
return OsTypes.OtclientLinux
end
end
end
function g_game.getSupportedProtocols()
return {
810, 853, 854, 860, 861, 862, 870, 940,
953, 954, 960
}
end

View File

@ -0,0 +1,20 @@
Module
name: gamelib
description: Contains game related classes
author: OTClient team
website: www.otclient.info
dependencies:
- client_extended
- game_tibiafiles
@onLoad: |
dofile 'const'
dofile 'protocollogin'
dofile 'protocolgame'
dofile 'game'
dofile 'creature'
dofile 'player'
dofile 'market'

View File

Before

Width:  |  Height:  |  Size: 385 B

After

Width:  |  Height:  |  Size: 385 B

View File

Before

Width:  |  Height:  |  Size: 381 B

After

Width:  |  Height:  |  Size: 381 B

View File

Before

Width:  |  Height:  |  Size: 386 B

After

Width:  |  Height:  |  Size: 386 B

View File

Before

Width:  |  Height:  |  Size: 352 B

After

Width:  |  Height:  |  Size: 352 B

View File

Before

Width:  |  Height:  |  Size: 522 B

After

Width:  |  Height:  |  Size: 522 B

View File

Before

Width:  |  Height:  |  Size: 516 B

After

Width:  |  Height:  |  Size: 516 B

View File

Before

Width:  |  Height:  |  Size: 404 B

After

Width:  |  Height:  |  Size: 404 B

View File

Before

Width:  |  Height:  |  Size: 377 B

After

Width:  |  Height:  |  Size: 377 B

View File

Before

Width:  |  Height:  |  Size: 512 B

After

Width:  |  Height:  |  Size: 512 B

View File

Before

Width:  |  Height:  |  Size: 494 B

After

Width:  |  Height:  |  Size: 494 B

View File

Before

Width:  |  Height:  |  Size: 407 B

After

Width:  |  Height:  |  Size: 407 B

View File

Before

Width:  |  Height:  |  Size: 482 B

After

Width:  |  Height:  |  Size: 482 B

View File

Before

Width:  |  Height:  |  Size: 438 B

After

Width:  |  Height:  |  Size: 438 B

View File

Before

Width:  |  Height:  |  Size: 445 B

After

Width:  |  Height:  |  Size: 445 B

View File

Before

Width:  |  Height:  |  Size: 421 B

After

Width:  |  Height:  |  Size: 421 B

View File

Before

Width:  |  Height:  |  Size: 437 B

After

Width:  |  Height:  |  Size: 437 B

View File

Before

Width:  |  Height:  |  Size: 437 B

After

Width:  |  Height:  |  Size: 437 B

View File

@ -52,4 +52,3 @@ function Player:hasVip(creatureName)
end
return false
end

View File

@ -0,0 +1,23 @@
local opcodeCallbacks = {}
function ProtocolGame:onOpcode(opcode, msg)
for i, callback in pairs(opcodeCallbacks) do
if i == opcode then
callback(msg)
return true
end
end
return false
end
function ProtocolGame.registerOpcode(opcode, callback)
if opcodeCallbacks[opcode] then
error('opcode ' .. opcode .. ' already registered will be overriden')
end
opcodeCallbacks[opcode] = callback
end
function ProtocolGame.unregisterOpcode(opcode)
opcodeCallbacks[opcode] = nil
end

View File

@ -1,16 +1,25 @@
-- @docclass
ProtocolLogin = extends(Protocol)
-- set to the latest Tibia.pic signature to make otclient compatible with official tibia
local PIC_SIGNATURE = 0
LoginServerError = 10
LoginServerMotd = 20
LoginServerUpdateNeeded = 30
LoginServerCharacterList = 100
-- private functions
local function sendLoginPacket(protocol)
local msg = OutputMessage.create()
msg:addU8(ClientOpcodes.ClientEnterAccount)
msg:addU16(1) -- todo: ClientOs
msg:addU16(g_game.getClientVersion())
msg:addU16(g_game.getOsType())
msg:addU16(g_game.getProtocolVersion())
msg:addU32(g_things.getDatSignature())
msg:addU32(g_sprites.getSprSignature())
msg:addU32(0) -- todo: pic signature
msg:addU32(PIC_SIGNATURE)
local paddingBytes = 128
msg:addU8(0) -- first RSA byte must be 0
@ -40,7 +49,7 @@ local function sendLoginPacket(protocol)
end
msg:addPaddingBytes(paddingBytes, 0)
msg:encryptRSA(128, OTSERV_RSA) -- todo: check whether to use cip or ot rsa
msg:encryptRsa(128, g_game.getRsa())
protocol:send(msg)
protocol:enableXteaEncryption()
@ -60,7 +69,7 @@ function ProtocolLogin:onRecv(msg)
elseif opcode == LoginServerMotd then
self:parseMotd(msg)
elseif opcode == LoginServerUpdateNeeded then
signalcall(self.onError, self, "Client needs update.")
signalcall(self.onError, self, tr("Client needs update."))
elseif opcode == LoginServerCharacterList then
self:parseCharacterList(msg)
else
@ -77,7 +86,11 @@ end
function ProtocolLogin:login(host, port, accountName, accountPassword)
if string.len(accountName) == 0 or string.len(accountPassword) == 0 then
signalcall(self.onError, self, "You must enter an account name and password.")
signalcall(self.onError, self, tr("You must enter an account name and password."))
return
end
if string.len(host) == 0 or port == nil or port == 0 then
signalcall(self.onError, self, tr("You must enter a valid server address and port."))
return
end

View File

@ -141,3 +141,14 @@ void Application::close()
if(!g_lua.callGlobalField<bool>("g_app", "onClose"))
exit();
}
std::string Application::getOs()
{
#if defined(WIN32)
return "windows";
#elif defined(__APPLE__)
return "mac";
#else
return "linux";
#endif
}

View File

@ -58,6 +58,7 @@ public:
std::string getBuildCommit() { return BUILD_COMMIT; }
std::string getBuildType() { return BUILD_TYPE; }
std::string getBuildArch() { return BUILD_ARCH; }
std::string getOs();
std::string getStartupOptions() { return m_startupOptions; }
protected:

View File

@ -22,22 +22,25 @@
#include <framework/core/application.h>
#include <framework/luaengine/luainterface.h>
#include <framework/graphics/fontmanager.h>
#include <framework/ui/ui.h>
#include <framework/net/protocol.h>
#include <framework/core/eventdispatcher.h>
#include <framework/core/configmanager.h>
#include <framework/otml/otml.h>
#include <framework/graphics/graphics.h>
#include <framework/platform/platformwindow.h>
#include <framework/core/modulemanager.h>
#include <framework/core/module.h>
#include <framework/sound/soundmanager.h>
#include <framework/util/crypt.h>
#include <framework/core/resourcemanager.h>
#include <framework/graphics/particlemanager.h>
#include <framework/graphics/texturemanager.h>
#ifdef FW_GRAPHICS
#include <framework/graphics/graphics.h>
#include <framework/platform/platformwindow.h>
#include <framework/graphics/particlemanager.h>
#include <framework/graphics/fontmanager.h>
#include <framework/ui/ui.h>
#endif
void Application::registerLuaFunctions()
{
// conversion globals
@ -67,6 +70,7 @@ void Application::registerLuaFunctions()
g_lua.bindSingletonFunction("g_app", "getBuildCommit", &Application::getBuildCommit, static_cast<Application*>(&g_app));
g_lua.bindSingletonFunction("g_app", "getBuildType", &Application::getBuildType, static_cast<Application*>(&g_app));
g_lua.bindSingletonFunction("g_app", "getBuildArch", &Application::getBuildArch, static_cast<Application*>(&g_app));
g_lua.bindSingletonFunction("g_app", "getOs", &Application::getOs, static_cast<Application*>(&g_app));
g_lua.bindSingletonFunction("g_app", "exit", &Application::exit, static_cast<Application*>(&g_app));
// Crypt
@ -644,7 +648,7 @@ void Application::registerLuaFunctions()
g_lua.bindClassMemberFunction<InputMessage>("peekU16", &InputMessage::peekU16);
g_lua.bindClassMemberFunction<InputMessage>("peekU32", &InputMessage::peekU32);
g_lua.bindClassMemberFunction<InputMessage>("peekU64", &InputMessage::peekU64);
g_lua.bindClassMemberFunction<InputMessage>("decryptRSA", &InputMessage::decryptRSA);
g_lua.bindClassMemberFunction<InputMessage>("decryptRsa", &InputMessage::decryptRsa);
g_lua.bindClassMemberFunction<InputMessage>("getReadSize", &InputMessage::getReadSize);
g_lua.bindClassMemberFunction<InputMessage>("getUnreadSize", &InputMessage::getUnreadSize);
g_lua.bindClassMemberFunction<InputMessage>("getMessageSize", &InputMessage::getMessageSize);
@ -661,7 +665,7 @@ void Application::registerLuaFunctions()
g_lua.bindClassMemberFunction<OutputMessage>("addU64", &OutputMessage::addU64);
g_lua.bindClassMemberFunction<OutputMessage>("addString", &OutputMessage::addString);
g_lua.bindClassMemberFunction<OutputMessage>("addPaddingBytes", &OutputMessage::addPaddingBytes);
g_lua.bindClassMemberFunction<OutputMessage>("encryptRSA", &OutputMessage::encryptRSA);
g_lua.bindClassMemberFunction<OutputMessage>("encryptRsa", &OutputMessage::encryptRsa);
g_lua.bindClassMemberFunction<OutputMessage>("getMessageSize", &OutputMessage::getMessageSize);
#endif

View File

@ -86,7 +86,7 @@ std::string InputMessage::getString()
return std::string(v, stringLength);
}
void InputMessage::decryptRSA(int size, const std::string& p, const std::string& q, const std::string& d)
void InputMessage::decryptRsa(int size, const std::string& p, const std::string& q, const std::string& d)
{
checkRead(size);
RSA::decrypt((char*)m_buffer + m_readPos, size, p.c_str(), q.c_str(), d.c_str());

View File

@ -40,6 +40,7 @@ public:
void setBuffer(const std::string& buffer);
void skipBytes(uint16 bytes) { m_readPos += bytes; }
void setReadPos(uint16 readPos) { m_readPos = readPos; }
uint8 getU8();
uint16 getU16();
uint32 getU32();
@ -51,9 +52,10 @@ public:
uint32 peekU32() { uint32 v = getU32(); m_readPos-=4; return v; }
uint64 peekU64() { uint64 v = getU64(); m_readPos-=8; return v; }
void decryptRSA(int size, const std::string& p, const std::string& q, const std::string& d);
void decryptRsa(int size, const std::string& p, const std::string& q, const std::string& d);
int getReadSize() { return m_readPos - m_headerPos; }
int getReadPos() { return m_readPos; }
int getUnreadSize() { return m_messageSize - (m_readPos - m_headerPos); }
uint16 getMessageSize() { return m_messageSize; }

View File

@ -89,7 +89,7 @@ void OutputMessage::addPaddingBytes(int bytes, uint8 byte)
m_messageSize += bytes;
}
void OutputMessage::encryptRSA(int size, const std::string& key)
void OutputMessage::encryptRsa(int size, const std::string& key)
{
if(m_messageSize < size)
throw stdext::exception("insufficient bytes in buffer to encrypt");

View File

@ -49,7 +49,7 @@ public:
void addString(const std::string& buffer);
void addPaddingBytes(int bytes, uint8 byte = 0);
void encryptRSA(int size, const std::string& key);
void encryptRsa(int size, const std::string& key);
uint16 getMessageSize() { return m_messageSize; }

View File

@ -286,31 +286,27 @@ namespace Otc
};
enum GameFeature {
GameExtendedOpcode = 0,
GameProtocolChecksum,
GameAccountNames,
GameChallangeOnLogin,
GameStackposOnTileAddThing,
GamePenalityOnDeath,
GameNameOnNpcTrade,
GameDoubleFreeCapacity,
GameDoubleExperience,
GameTotalCapacity,
GameSkillsBase,
GameAdditionalPlayerStats,
GameIdOnCancelAttack,
GameChannelPlayerList,
GamePlayerMounts,
GameEnvironmentEffect,
GameCreatureType,
GameCreatureAdditionalInfo,
GameCreaturePassableInfo,
GameItemAnimationPhase,
GameTrucatedPingOpcode,
GameReverseCreatureStack,
GameMagicEffectU16,
GamePlayerMarket,
LastGameFeature
// 1-50 defined in c++
GameProtocolChecksum = 1,
GameAccountNames = 2,
GameChallangeOnLogin = 3,
GamePenalityOnDeath = 5,
GameNameOnNpcTrade = 6,
GameDoubleFreeCapacity = 7,
GameDoubleExperience = 8,
GameTotalCapacity = 9,
GameSkillsBase = 10,
GamePlayerRegenerationTime = 11,
GameChannelPlayerList = 13,
GamePlayerMounts = 14,
GameEnvironmentEffect = 15,
GameCreatureEmblems = 17,
GameItemAnimationPhase = 19,
GameMagicEffectU16 = 22,
GamePlayerMarket = 23,
// 23-50 unused yet
// 51-100 reserved to be defined in lua
LastGameFeature = 101
};
enum PathFindResult {

View File

@ -461,6 +461,8 @@ void Creature::setDirection(Otc::Direction direction)
void Creature::setOutfit(const Outfit& outfit)
{
if(!g_things.isValidDatId(outfit.getId(), DatCreatureCategory))
return;
m_walkAnimationPhase = 0; // might happen when player is walking and outfit is changed.
m_outfit = outfit;
}

View File

@ -46,6 +46,8 @@ void Effect::startAnimation()
void Effect::setId(uint32 id)
{
if(!g_things.isValidDatId(id, DatEffectCategory))
id = 0;
m_id = id;
}

View File

@ -38,7 +38,7 @@ Game g_game;
Game::Game()
{
resetGameStates();
setClientVersion(860);
m_protocolVersion = 0;
}
void Game::resetGameStates()
@ -416,10 +416,11 @@ void Game::processWalkCancel(Otc::Direction 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)
{
if(m_protocolGame || isOnline()) {
g_logger.traceError("unable to login into a world while already online or logging");
return;
}
if(m_protocolGame || isOnline())
stdext::throw_exception("Unable to login into a world while already online or logging.");
if(m_protocolVersion == 0)
stdext::throw_exception("Must set a valid game protocol version before logging.");
m_protocolGame = ProtocolGamePtr(new ProtocolGame);
m_protocolGame->login(account, password, worldHost, (uint16)worldPort, characterName);
@ -1090,61 +1091,48 @@ bool Game::canPerformGameAction()
return m_localPlayer && !m_dead && m_protocolGame && m_protocolGame->isConnected() && checkBotProtection();
}
void Game::setClientVersion(int clientVersion)
void Game::setProtocolVersion(int version)
{
if(isOnline()) {
g_logger.error("Unable to change client version while online");
return;
}
if(isOnline())
stdext::throw_exception("Unable to change client version while online");
//TODO: check supported versions
if(version < 810 || version > 960)
stdext::throw_exception(stdext::format("Protocol version %d not supported", version));
m_features.reset();
if(clientVersion >= 854) {
if(version >= 854) {
enableFeature(Otc::GameProtocolChecksum);
enableFeature(Otc::GameAccountNames);
enableFeature(Otc::GameChallangeOnLogin);
enableFeature(Otc::GameStackposOnTileAddThing);
enableFeature(Otc::GameDoubleFreeCapacity);
enableFeature(Otc::GameCreatureAdditionalInfo);
enableFeature(Otc::GameReverseCreatureStack);
enableFeature(Otc::GameCreatureEmblems);
}
if(clientVersion >= 860) {
enableFeature(Otc::GameIdOnCancelAttack);
}
if(clientVersion >= 862) {
if(version >= 862) {
enableFeature(Otc::GamePenalityOnDeath);
}
if(clientVersion >= 870) {
if(version >= 870) {
enableFeature(Otc::GameDoubleExperience);
enableFeature(Otc::GamePlayerMounts);
}
if(clientVersion >= 910) {
if(version >= 910) {
enableFeature(Otc::GameNameOnNpcTrade);
enableFeature(Otc::GameTotalCapacity);
enableFeature(Otc::GameSkillsBase);
enableFeature(Otc::GameAdditionalPlayerStats);
enableFeature(Otc::GamePlayerRegenerationTime);
enableFeature(Otc::GameChannelPlayerList);
enableFeature(Otc::GameEnvironmentEffect);
enableFeature(Otc::GameCreatureType);
enableFeature(Otc::GameItemAnimationPhase);
}
if(clientVersion >= 940) {
if(version >= 940) {
enableFeature(Otc::GamePlayerMarket);
}
if(clientVersion >= 953) {
enableFeature(Otc::GameCreaturePassableInfo);
enableFeature(Otc::GameTrucatedPingOpcode);
}
m_clientVersion = clientVersion;
m_protocolVersion = version;
}
void Game::setAttackingCreature(const CreaturePtr& creature)

View File

@ -232,8 +232,8 @@ public:
void setFeature(Otc::GameFeature feature) { m_features.set(feature, false); }
bool getFeature(Otc::GameFeature feature) { return m_features.test(feature); }
void setClientVersion(int clientVersion);
int getClientVersion() { return m_clientVersion; }
void setProtocolVersion(int version);
int getProtocolVersion() { return m_protocolVersion; }
void setRSA(const std::string& rsa);
std::string getRSA() { return m_rsa; }
@ -256,7 +256,6 @@ public:
int getServerBeat() { return m_serverBeat; }
LocalPlayerPtr getLocalPlayer() { return m_localPlayer; }
ProtocolGamePtr getProtocolGame() { return m_protocolGame; }
int getProtocolVersion() { return PROTOCOL; }
std::string getCharacterName() { return m_characterName; }
std::string getWorldName() { return m_worldName; }
std::vector<uint8> getGMActions() { return m_gmActions; }
@ -288,7 +287,7 @@ private:
std::string m_characterName;
std::string m_worldName;
std::bitset<Otc::LastGameFeature> m_features;
int m_clientVersion;
int m_protocolVersion;
std::string m_rsa;
};

View File

@ -101,7 +101,7 @@ void Item::draw(const Point& dest, float scaleFactor, bool animate)
else if(tile->mustHookEast())
xPattern = getNumPatternX() >= 3 ? 2 : 0;
}
} else if(isFluid() || isFluidContainer()) {
} else if(isSplash() || isFluidContainer()) {
int color = Otc::FluidTransparent;
switch(m_countOrSubType) {
case Otc::FluidNone:
@ -176,15 +176,20 @@ void Item::draw(const Point& dest, float scaleFactor, bool animate)
void Item::setId(uint32 id)
{
m_otbId = g_things.findOtbForClientId(id)->getServerId();
if(!g_things.isValidDatId(id, DatItemCategory))
id = 0;
//m_otbId = g_things.findOtbForClientId(id)->getServerId();
m_id = id;
m_otbId = 0;
}
void Item::setOtbId(uint16 id)
{
if(!g_things.isValidOtbId(id))
id = 0;
auto otbType = g_things.getOtbType(id);
m_otbId = id;
m_id = otbType->getClientId();
m_otbId = id;
}
bool Item::isValid()

View File

@ -190,10 +190,10 @@ void OTClient::registerLuaFunctions()
g_lua.bindSingletonFunction("g_game", "getLocalPlayer", &Game::getLocalPlayer, &g_game);
g_lua.bindSingletonFunction("g_game", "getProtocolGame", &Game::getProtocolGame, &g_game);
g_lua.bindSingletonFunction("g_game", "getProtocolVersion", &Game::getProtocolVersion, &g_game);
g_lua.bindSingletonFunction("g_game", "setProtocolVersion", &Game::setProtocolVersion, &g_game);
g_lua.bindSingletonFunction("g_game", "getCharacterName", &Game::getCharacterName, &g_game);
g_lua.bindSingletonFunction("g_game", "getWorldName", &Game::getWorldName, &g_game);
g_lua.bindSingletonFunction("g_game", "getGMActions", &Game::getGMActions, &g_game);
g_lua.bindSingletonFunction("g_game", "getClientVersion", &Game::getClientVersion, &g_game);
g_lua.bindSingletonFunction("g_game", "getFeature", &Game::getFeature, &g_game);
g_lua.registerSingletonClass("g_shaders");

View File

@ -467,7 +467,7 @@ bool Map::loadOtcm(const std::string& fileName)
uint8 countOrSubType = fin->getU8();
ItemPtr item = Item::create(id);
if(item->isStackable() || item->isFluidContainer() || item->isFluid())
if(item->isStackable() || item->isFluidContainer() || item->isSplash() || item->isChargeable())
item->setCountOrSubType(countOrSubType);
if(item->isValid())

View File

@ -83,6 +83,8 @@ void Missile::setPath(const Position& fromPosition, const Position& toPosition)
void Missile::setId(uint32 id)
{
if(!g_things.isValidDatId(id, DatMissileCategory))
id = 0;
m_id = id;
}

View File

@ -25,7 +25,7 @@
Outfit::Outfit()
{
m_category = DatCreatureCategory;
m_id = 1;
m_id = 0;
resetClothes();
}

View File

@ -25,49 +25,7 @@
#include "global.h"
#if !(PROTOCOL == 810) && \
!(PROTOCOL == 854) && \
!(PROTOCOL >= 860 && PROTOCOL <= 862) && \
!(PROTOCOL >= 870 && PROTOCOL <= 871) && \
!(PROTOCOL >= 910 && PROTOCOL <= 953)
#error "the supplied protocol version is not supported"
#endif
namespace Proto {
#ifdef CIPSOFT_RSA
constexpr const char* RSA = "1321277432058722840622950990822933849527763264961655079678763618"
"4334395343554449668205332383339435179772895415509701210392836078"
"6959821132214473291575712138800495033169914814069637740318278150"
"2907336840325241747827401343576296990629870233111328210165697754"
"88792221429527047321331896351555606801473202394175817";
#else
constexpr const char* RSA = "1091201329673994292788609605089955415282375029027981291234687579"
"3726629149257644633073969600111060390723088861007265581882535850"
"3429057592827629436413108566029093628212635953836686562675849720"
"6207862794310902180176810615217550567108238764764442605581471797"
"07119674283982419152118103759076030616683978566631413";
#endif
constexpr int PicSignature = 0x4F8C231A; // 953 pic signature
constexpr int ClientVersion = PROTOCOL;
enum OsTypes {
OsLinux = 1,
OsWindows = 2,
OsFlash = 3,
OsOtclientLinux = 10,
OsOtclientWindows = 11,
OsOtclientMac = 12
};
#ifdef OSTYPE
constexpr int ClientOs = OSTYPE;
#elif defined WIN32
constexpr int ClientOs = OsOtclientWindows;
#else
constexpr int ClientOs = OsOtclientLinux;
#endif
enum LoginServerOpts {
LoginServerError = 10,
LoginServerMotd = 20,

View File

@ -43,8 +43,11 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
opcode = msg->getU8();
// try to parse in lua first
int readPos = msg->getReadPos();
if(callLuaField<bool>("onOpcode", opcode, msg))
continue;
else
msg->setReadPos(readPos); // restore read pos
if(!m_gameInitialized && opcode > Proto::GameServerFirstGameOpcode)
g_logger.warning("received a game opcode from the server, but the game is not initialized yet, this is a server side bug");
@ -66,16 +69,12 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
parseLoginWait(msg);
break;
case Proto::GameServerPing:
if(g_game.getFeature(Otc::GameTrucatedPingOpcode))
parsePingBack(msg);
else
parsePing(msg);
break;
case Proto::GameServerPingBack:
if(g_game.getFeature(Otc::GameTrucatedPingOpcode))
parsePing(msg);
else
if((opcode == Proto::GameServerPing && g_game.getProtocolVersion() >= 953) ||
(opcode == Proto::GameServerPingBack && g_game.getProtocolVersion() < 953))
parsePingBack(msg);
else
parsePing(msg);
break;
case Proto::GameServerChallange:
parseChallange(msg);
@ -334,9 +333,9 @@ void ProtocolGame::parseGMActions(const InputMessagePtr& msg)
int numViolationReasons;
if(g_game.getClientVersion() >= 860)
if(g_game.getProtocolVersion() >= 860)
numViolationReasons = 20;
else if(g_game.getClientVersion() >= 854)
else if(g_game.getProtocolVersion() >= 854)
numViolationReasons = 19;
else
numViolationReasons = 32;
@ -454,7 +453,7 @@ void ProtocolGame::parseTileAddThing(const InputMessagePtr& msg)
Position pos = getPosition(msg);
int stackPos = -1;
if(g_game.getFeature(Otc::GameStackposOnTileAddThing))
if(g_game.getProtocolVersion() >= 854)
stackPos = msg->getU8();
ThingPtr thing = getThing(msg);
@ -508,7 +507,7 @@ void ProtocolGame::parseCreatureMove(const InputMessagePtr& msg)
int stackPos = -2;
// older protocols stores creatures in reverse order
if(!g_game.getFeature(Otc::GameReverseCreatureStack))
if(!g_game.getProtocolVersion() >= 854)
stackPos = -1;
g_map.addThing(thing, newPos, stackPos);
@ -884,12 +883,11 @@ void ProtocolGame::parsePlayerStats(const InputMessagePtr& msg)
m_localPlayer->setStamina(stamina);
m_localPlayer->setSoul(soul);
if(g_game.getFeature(Otc::GameAdditionalPlayerStats)) {
int speed = msg->getU16();
msg->getU16(); // regeneration time
if(g_game.getProtocolVersion() >= 910)
m_localPlayer->setSpeed(msg->getU16());
m_localPlayer->setSpeed(speed);
}
if(g_game.getFeature(Otc::GamePlayerRegenerationTime))
msg->getU16(); // regeneration time
}
void ProtocolGame::parsePlayerSkills(const InputMessagePtr& msg)
@ -913,7 +911,7 @@ void ProtocolGame::parsePlayerState(const InputMessagePtr& msg)
void ProtocolGame::parsePlayerCancelAttack(const InputMessagePtr& msg)
{
if(g_game.getFeature(Otc::GameIdOnCancelAttack))
if(g_game.getProtocolVersion() >= 860)
msg->getU32(); // unknown
g_game.processAttackCancel();
@ -1389,7 +1387,7 @@ CreaturePtr ProtocolGame::getCreature(const InputMessagePtr& msg, int type)
uint id = msg->getU32();
int creatureType;
if(g_game.getFeature(Otc::GameCreatureType))
if(g_game.getProtocolVersion() >= 910)
creatureType = msg->getU8();
else {
if(id >= Proto::PlayerStartId && id < Proto::PlayerEndId)
@ -1441,12 +1439,11 @@ CreaturePtr ProtocolGame::getCreature(const InputMessagePtr& msg, int type)
int emblem = -1;
bool passable = false;
if(g_game.getFeature(Otc::GameCreatureAdditionalInfo)) {
if(!known)
emblem = msg->getU8();
if(g_game.getFeature(Otc::GameCreatureEmblems) && !known)
emblem = msg->getU8();
if(g_game.getProtocolVersion() >= 854)
passable = (msg->getU8() == 0);
}
if(creature) {
creature->setHealthPercent(healthPercent);
@ -1474,7 +1471,7 @@ CreaturePtr ProtocolGame::getCreature(const InputMessagePtr& msg, int type)
if(creature)
creature->turn(direction);
if(g_game.getFeature(Otc::GameCreaturePassableInfo)) {
if(g_game.getProtocolVersion() >= 953) {
bool passable = msg->getU8();
if(creature)
@ -1497,7 +1494,7 @@ ItemPtr ProtocolGame::getItem(const InputMessagePtr& msg, int id)
if(item->getId() == 0)
stdext::throw_exception("unable to create item with invalid id 0");
if(item->isStackable() || item->isFluidContainer() || item->isFluid())
if(item->isStackable() || item->isFluidContainer() || item->isSplash() || item->isChargeable())
item->setCountOrSubType(msg->getU8());
if(g_game.getFeature(Otc::GameItemAnimationPhase)) {

View File

@ -49,8 +49,9 @@ void ProtocolGame::sendLoginPacket(uint challangeTimestamp, uint8 challangeRando
OutputMessagePtr msg(new OutputMessage);
msg->addU8(Proto::ClientEnterGame);
msg->addU16(Proto::ClientOs);
msg->addU16(g_game.getClientVersion());
msg->addU16(g_lua.callGlobalField<int>("g_game", "getOs"));
msg->addU16(g_game.getProtocolVersion());
int paddingBytes = 128;
msg->addU8(0); // first RSA byte must be 0
@ -90,7 +91,7 @@ void ProtocolGame::sendLoginPacket(uint challangeTimestamp, uint8 challangeRando
msg->addPaddingBytes(paddingBytes);
// encrypt with RSA
msg->encryptRSA(128, Proto::RSA);
msg->encryptRsa(128, g_lua.callGlobalField<std::string>("g_game", "getRsa"));
send(msg);
@ -570,9 +571,10 @@ void ProtocolGame::sendShareExperience(bool active, int unknown)
OutputMessagePtr msg(new OutputMessage);
msg->addU8(Proto::ClientShareExperience);
msg->addU8(active ? 0x01 : 0x00);
#if PROTOCOL<910
msg->addU8(unknown);
#endif
if(g_game.getProtocolVersion() < 910)
msg->addU8(unknown);
send(msg);
}

View File

@ -28,12 +28,6 @@
#include "thingtypemanager.h"
#include <framework/luaengine/luaobject.h>
struct Light
{
uint8 intensity;
uint8 color;
};
// @bindclass
#pragma pack(push,1) // disable memory alignment
class Thing : public LuaObject
@ -95,8 +89,7 @@ public:
int getAnimationPhases() { return rawGetDatType()->getAnimationPhases(); }
int getGroundSpeed() { return rawGetDatType()->getGroundSpeed(); }
int getMaxTextLength() { return rawGetDatType()->getMaxTextLength(); }
int getLightLevel() { return rawGetDatType()->getLightLevel(); }
int getLightColor() { return rawGetDatType()->getLightColor(); }
Light getLight() { return rawGetDatType()->getLight(); }
int getMinimapColor() { return rawGetDatType()->getMinimapColor(); }
int getLensHelp() { return rawGetDatType()->getLensHelp(); }
int getClothSlot() { return rawGetDatType()->getClothSlot(); }
@ -110,9 +103,10 @@ public:
bool isForceUse() { return rawGetDatType()->isForceUse(); }
bool isMultiUse() { return rawGetDatType()->isMultiUse(); }
bool isWritable() { return rawGetDatType()->isWritable(); }
bool isChargeable() { return rawGetDatType()->isChargeable(); }
bool isWritableOnce() { return rawGetDatType()->isWritableOnce(); }
bool isFluidContainer() { return rawGetDatType()->isFluidContainer(); }
bool isFluid() { return rawGetDatType()->isFluid(); }
bool isSplash() { return rawGetDatType()->isSplash(); }
bool isNotWalkable() { return rawGetDatType()->isNotWalkable(); }
bool isNotMoveable() { return rawGetDatType()->isNotMoveable(); }
bool blockProjectile() { return rawGetDatType()->blockProjectile(); }
@ -134,6 +128,7 @@ public:
bool isFullGround() { return rawGetDatType()->isFullGround(); }
bool isIgnoreLook() { return rawGetDatType()->isIgnoreLook(); }
bool isCloth() { return rawGetDatType()->isCloth(); }
MarketData getMarketData() { return rawGetDatType()->getMarketData(); }
protected:
Position m_position;

View File

@ -22,6 +22,7 @@
#include "thingtypedat.h"
#include "spritemanager.h"
#include "game.h"
#include <framework/graphics/graphics.h>
#include <framework/graphics/texture.h>
@ -33,20 +34,11 @@ ThingTypeDat::ThingTypeDat()
{
m_category = DatInvalidCategory;
m_id = 0;
m_null = true;
m_exactSize = 0;
m_layers = 0;
m_numPatternX = 0;
m_numPatternY = 0;
m_numPatternZ = 0;
m_numPatternX = m_numPatternY = m_numPatternZ = 0;
m_animationPhases = 0;
m_groundSpeed = 0;
m_maxTextLenght = 0;
m_lightLevel = 0;
m_lightColor = 0;
m_miniMapColor = 0;
m_lensHelp = 0;
m_clothSlot = 0;
m_elevation = 0;
m_layers = 0;
}
void ThingTypeDat::unserialize(uint16 clientId, DatCategory category, const FileStreamPtr& fin)
@ -55,136 +47,68 @@ void ThingTypeDat::unserialize(uint16 clientId, DatCategory category, const File
m_id = clientId;
m_category = category;
static int datVersion;
if(clientId == 100 && category == DatItemCategory)
datVersion = 2;
bool done = false;
for(int i = 0 ; i < DatLastAttrib;++i) {
int property = fin->getU8();
if(property == DatLastAttrib) {
int attrib = fin->getU8();
if(attrib == DatLastAttrib) {
done = true;
break;
}
switch(property) {
case DatAttribIsGround:
m_isGround = true;
m_groundSpeed = fin->getU16();
if(m_groundSpeed == 0)
m_groundSpeed = 100;
// hacky way to detect if is older dat version or not
if(clientId == 100 && category == DatItemCategory && datVersion != 1 &&
(attrib == DatAttribNotPathable || attrib == DatAttribDontHide || attrib == DatAttribIgnoreLook)) {
datVersion = 1;
}
if(datVersion <= 1) {
if(attrib == DatAttribWritable) {
m_attribs.set(DatAttribChargeable, true);
continue;
} else if(attrib > DatAttribWritable)
attrib -= 1;
}
switch(attrib) {
case DatAttribDisplacement: {
m_displacement.x = fin->getU16();
m_displacement.y = fin->getU16();
m_attribs.set(attrib, true);
break;
case DatAttribIsGroundBorder:
m_isGroundBorder = true;
}
case DatAttribLight: {
Light light;
light.intensity = fin->getU16();
light.color = fin->getU16();
m_attribs.set(attrib, light);
break;
case DatAttribIsOnBottom:
m_isOnBottom = true;
break;
case DatAttribIsOnTop:
m_isOnTop = true;
break;
case DatAttribIsContainer:
m_isContainer = true;
break;
case DatAttribIsStackable:
m_isStackable = true;
break;
case DatAttribIsForceUse:
m_isForceUse = true;
break;
case DatAttribIsMultiUse:
m_isMultiUse = true;
break;
case DatAttribIsWritable:
m_isWritable = true;
m_maxTextLenght = fin->getU16();
break;
case DatAttribIsWritableOnce:
m_isWritableOnce = true;
m_maxTextLenght = fin->getU16();
break;
case DatAttribIsFluidContainer:
m_isFluidContainer = true;
break;
case DatAttribIsFluid:
m_isFluid = true;
break;
case DatAttribIsNotWalkable:
m_isNotWalkable = true;
break;
case DatAttribIsNotMoveable:
m_isNotMoveable = true;
break;
case DatAttribBlockProjectile:
m_blockProjectile = true;
break;
case DatAttribIsNotPathable:
m_isNotPathable = true;
break;
case DatAttribIsPickupable:
m_isPickupable = true;
break;
case DatAttribIsHangable:
m_isHangable = true;
break;
case DatAttribHookSouth:
m_isHookSouth = true;
break;
case DatAttribHookEast:
m_isHookEast = true;
break;
case DatAttribIsRotateable:
m_isRotateable = true;
break;
case DatAttribHasLight:
m_hasLight = true;
m_lightLevel = fin->getU16();
m_lightColor = fin->getU16();
break;
case DatAttribDontHide:
m_isDontHide = true;
break;
case DatAttribIsTranslucent:
m_isTranslucent = true;
break;
case DatAttribHasDisplacement:
m_hasDisplacement = true;
m_displacement = Point(fin->getU16(), fin->getU16());
break;
case DatAttribHasElevation:
m_hasElevation = true;
m_elevation = fin->getU16();
break;
case DatAttribIsLyingCorpse:
m_isLyingCorpse = true;
break;
case DatAttribAnimateAlways:
m_isAnimateAlways = true;
}
case DatAttribMarket: {
MarketData market;
market.category = fin->getU16();
market.showAs = fin->getU16();
market.tradeAs = fin->getU16();
market.name = fin->getString();
market.restrictProfession = fin->getU16();
market.requiredLevel = fin->getU16();
m_attribs.set(attrib, market);
break;
}
case DatAttribGround:
case DatAttribWritable:
case DatAttribWritableOnce:
case DatAttribElevation:
case DatAttribMiniMapColor:
m_miniMapColor = true;
m_miniMapColor = fin->getU16();
break;
case DatAttribLensHelp:
m_lensHelp = true;
m_lensHelp = fin->getU16();
break;
case DatAttribIsFullGround:
m_isFullGround = true;
break;
case DatAttribIgnoreLook:
m_isIgnoreLook = true;
break;
case DatAttribCloth:
m_isCloth = true;
m_clothSlot = fin->getU16();
break;
case DatAttribMarket:
fin->getU16(); // category
fin->getU16(); // trade as
fin->getU16(); // show as
fin->getString(); // name
fin->getU16(); // restrict profession
fin->getU16(); // level
case DatAttribLensHelp:
m_attribs.set(attrib, fin->getU16());
break;
default:
stdext::throw_exception("corrupt data, invalid type attribute");
m_attribs.set(attrib, true);
break;
};
}
@ -192,40 +116,18 @@ void ThingTypeDat::unserialize(uint16 clientId, DatCategory category, const File
if(!done)
stdext::throw_exception("corrupt data");
int totalSprites = 1;
for(int i = 0; i < DatLastDimension; ++i) {
switch(i) {
case DatWidth:
m_size.setWidth(fin->getU8());
break;
case DatHeight:
m_size.setHeight(fin->getU8());
break;
case DatExactSize:
if(m_size.width() <= 1 && m_size.height() <= 1)
m_exactSize = 32;
else
m_exactSize = std::min((int)fin->getU8(), std::max(m_size.width() * 32, m_size.height() * 32));
break;
case DatLayers:
m_layers = fin->getU8();
break;
case DatPatternX:
m_numPatternX = fin->getU8();
break;
case DatPatternY:
m_numPatternY = fin->getU8();
break;
case DatPatternZ:
m_numPatternZ = fin->getU8();
break;
case DatAnimationPhases:
m_animationPhases = fin->getU8();
break;
}
}
uint8 width = fin->getU8();
uint8 height = fin->getU8();
m_size = Size(width, height);
m_exactSize = (width > 1 || height > 1) ? std::min((int)fin->getU8(), std::max(width * 32, height * 32)) : 32;
m_layers = fin->getU8();
m_numPatternX = fin->getU8();
m_numPatternY = fin->getU8();
m_numPatternZ = fin->getU8();
m_animationPhases = fin->getU8();
int totalSprites = m_size.area() * m_layers * m_numPatternX * m_numPatternY * m_numPatternZ * m_animationPhases;
totalSprites = m_size.width() * m_size.height() * m_layers * m_numPatternX * m_numPatternY * m_numPatternZ * m_animationPhases;
if(totalSprites == 0)
stdext::throw_exception("a thing type has no sprites");
if(totalSprites > 4096)

View File

@ -29,6 +29,7 @@
#include <framework/graphics/coordsbuffer.h>
#include <framework/luaengine/luaobject.h>
#include <framework/net/server.h>
#include <framework/util/attribstorage.h>
enum DatCategory {
DatItemCategory = 0,
@ -48,53 +49,59 @@ enum DatSpriteMask {
};
enum DatAttrib {
DatAttribIsGround = 0,
DatAttribIsGroundBorder,
DatAttribIsOnBottom,
DatAttribIsOnTop,
DatAttribIsContainer,
DatAttribIsStackable,
DatAttribIsForceUse,
DatAttribIsMultiUse,
DatAttribIsWritable,
DatAttribIsWritableOnce,
DatAttribIsFluidContainer,
DatAttribIsFluid,
DatAttribIsNotWalkable,
DatAttribIsNotMoveable,
DatAttribGround = 0,
DatAttribGroundBorder,
DatAttribOnBottom,
DatAttribOnTop,
DatAttribContainer,
DatAttribStackable,
DatAttribForceUse,
DatAttribMultiUse,
//DatAttribRune
DatAttribWritable,
DatAttribWritableOnce,
DatAttribFluidContainer,
DatAttribSplash,
DatAttribNotWalkable,
DatAttribNotMoveable,
DatAttribBlockProjectile,
DatAttribIsNotPathable,
DatAttribIsPickupable,
DatAttribIsHangable,
DatAttribNotPathable,
DatAttribPickupable,
DatAttribHangable,
DatAttribHookSouth,
DatAttribHookEast,
DatAttribIsRotateable,
DatAttribHasLight,
DatAttribRotateable,
DatAttribLight,
DatAttribDontHide,
DatAttribIsTranslucent,
DatAttribHasDisplacement,
DatAttribHasElevation,
DatAttribIsLyingCorpse,
DatAttribTranslucent,
DatAttribDisplacement,
DatAttribElevation,
DatAttribLyingCorpse,
DatAttribAnimateAlways,
DatAttribMiniMapColor,
DatAttribLensHelp,
DatAttribIsFullGround,
DatAttribFullGround,
DatAttribIgnoreLook,
DatAttribCloth,
DatAttribMarket,
DatLastAttrib = 255
DatLastAttrib = 255,
// legacy attribs
DatAttribChargeable = 254
};
enum DatDimension {
DatWidth = 0,
DatHeight,
DatExactSize,
DatLayers,
DatPatternX,
DatPatternY,
DatPatternZ,
DatAnimationPhases,
DatLastDimension
struct MarketData {
std::string name;
int category;
uint16 requiredLevel;
uint16 restrictProfession;
uint16 showAs;
uint16 tradeAs;
};
struct Light {
uint8 intensity;
uint8 color;
};
class ThingTypeDat : public LuaObject
@ -113,56 +120,58 @@ public:
Size getSize() { return m_size; }
int getWidth() { return m_size.width(); }
int getHeight() { return m_size.height(); }
Point getDisplacement() { return m_displacement; }
int getDisplacementX() { return m_displacement.x; }
int getDisplacementY() { return m_displacement.y; }
int getExactSize() { return m_exactSize; }
int getLayers() { return m_layers; }
int getNumPatternX() { return m_numPatternX; }
int getNumPatternY() { return m_numPatternY; }
int getNumPatternZ() { return m_numPatternZ; }
int getAnimationPhases() { return m_animationPhases; }
int getGroundSpeed() { return m_groundSpeed; }
int getMaxTextLength() { return m_maxTextLenght; }
int getLightLevel() { return m_lightLevel; }
int getLightColor() { return m_lightColor; }
int getMinimapColor() { return m_miniMapColor; }
int getLensHelp() { return m_lensHelp; }
int getClothSlot() { return m_clothSlot; }
int getElevation() { return m_elevation; }
bool isGround() { return m_isGround; }
bool isGroundBorder() { return m_isGroundBorder; }
bool isOnBottom() { return m_isOnBottom; }
bool isOnTop() { return m_isOnTop; }
bool isContainer() { return m_isContainer; }
bool isStackable() { return m_isStackable; }
bool isForceUse() { return m_isForceUse; }
bool isMultiUse() { return m_isMultiUse; }
bool isWritable() { return m_isWritable; }
bool isWritableOnce() { return m_isWritableOnce; }
bool isFluidContainer() { return m_isFluidContainer; }
bool isFluid() { return m_isFluid; }
bool isNotWalkable() { return m_isNotWalkable; }
bool isNotMoveable() { return m_isNotMoveable; }
bool blockProjectile() { return m_blockProjectile; }
bool isNotPathable() { return m_isNotPathable; }
bool isPickupable() { return m_isPickupable; }
bool isHangable() { return m_isHangable; }
bool isHookSouth() { return m_isHookSouth; }
bool isHookEast() { return m_isHookEast; }
bool isRotateable() { return m_isRotateable; }
bool hasLight() { return m_hasLight; }
bool isDontHide() { return m_isDontHide; }
bool isTranslucent() { return m_isTranslucent; }
bool hasDisplacement() { return m_hasDisplacement; }
bool hasElevation() { return m_hasElevation; }
bool isLyingCorpse() { return m_isLyingCorpse; }
bool isAnimateAlways() { return m_isAnimateAlways; }
bool hasMiniMapColor() { return m_hasMiniMapColor; }
bool hasLensHelp() { return m_hasLensHelp; }
bool isFullGround() { return m_isFullGround; }
bool isIgnoreLook() { return m_isIgnoreLook; }
bool isCloth() { return m_isCloth; }
Point getDisplacement() { return m_displacement; }
int getDisplacementX() { return getDisplacement().x; }
int getDisplacementY() { return getDisplacement().y; }
int getGroundSpeed() { return m_attribs.get<uint16>(DatAttribGround); }
int getMaxTextLength() { return m_attribs.has(DatAttribWritableOnce) ? m_attribs.get<uint16>(DatAttribWritableOnce) : m_attribs.get<uint16>(DatAttribWritable); }
Light getLight() { return m_attribs.get<Light>(DatAttribLight); }
int getMinimapColor() { return m_attribs.get<uint16>(DatAttribMiniMapColor); }
int getLensHelp() { return m_attribs.get<uint16>(DatAttribLensHelp); }
int getClothSlot() { return m_attribs.get<uint16>(DatAttribCloth); }
int getElevation() { return m_attribs.get<uint16>(DatAttribElevation); }
MarketData getMarketData() { return m_attribs.get<MarketData>(DatAttribMarket); }
bool isGround() { return m_attribs.has(DatAttribGround); }
bool isGroundBorder() { return m_attribs.has(DatAttribGroundBorder); }
bool isOnBottom() { return m_attribs.has(DatAttribOnBottom); }
bool isOnTop() { return m_attribs.has(DatAttribOnTop); }
bool isContainer() { return m_attribs.has(DatAttribContainer); }
bool isStackable() { return m_attribs.has(DatAttribStackable); }
bool isForceUse() { return m_attribs.has(DatAttribForceUse); }
bool isMultiUse() { return m_attribs.has(DatAttribMultiUse); }
bool isWritable() { return m_attribs.has(DatAttribWritable); }
bool isChargeable() { return m_attribs.has(DatAttribChargeable); }
bool isWritableOnce() { return m_attribs.has(DatAttribWritableOnce); }
bool isFluidContainer() { return m_attribs.has(DatAttribFluidContainer); }
bool isSplash() { return m_attribs.has(DatAttribSplash); }
bool isNotWalkable() { return m_attribs.has(DatAttribNotWalkable); }
bool isNotMoveable() { return m_attribs.has(DatAttribNotMoveable); }
bool blockProjectile() { return m_attribs.has(DatAttribBlockProjectile); }
bool isNotPathable() { return m_attribs.has(DatAttribNotPathable); }
bool isPickupable() { return m_attribs.has(DatAttribPickupable); }
bool isHangable() { return m_attribs.has(DatAttribHangable); }
bool isHookSouth() { return m_attribs.has(DatAttribHookSouth); }
bool isHookEast() { return m_attribs.has(DatAttribHookEast); }
bool isRotateable() { return m_attribs.has(DatAttribRotateable); }
bool hasLight() { return m_attribs.has(DatAttribLight); }
bool isDontHide() { return m_attribs.has(DatAttribDontHide); }
bool isTranslucent() { return m_attribs.has(DatAttribTranslucent); }
bool hasDisplacement() { return m_attribs.has(DatAttribDisplacement); }
bool hasElevation() { return m_attribs.has(DatAttribElevation); }
bool isLyingCorpse() { return m_attribs.has(DatAttribLyingCorpse); }
bool isAnimateAlways() { return m_attribs.has(DatAttribAnimateAlways); }
bool hasMiniMapColor() { return m_attribs.has(DatAttribMiniMapColor); }
bool hasLensHelp() { return m_attribs.has(DatAttribLensHelp); }
bool isFullGround() { return m_attribs.has(DatAttribFullGround); }
bool isIgnoreLook() { return m_attribs.has(DatAttribIgnoreLook); }
bool isCloth() { return m_attribs.has(DatAttribCloth); }
private:
const TexturePtr& getTexture(int animationPhase);
@ -172,64 +181,21 @@ private:
DatCategory m_category;
uint16 m_id;
Boolean<true> m_null;
bool m_null;
AttribStorage m_attribs;
Size m_size;
Point m_displacement;
int m_exactSize;
int m_numPatternX, m_numPatternY, m_numPatternZ;
int m_animationPhases;
int m_layers;
std::vector<int> m_spritesIndex;
std::vector<TexturePtr> m_textures;
std::vector<std::vector<Rect>> m_texturesFramesRects;
std::vector<std::vector<Rect>> m_texturesFramesOriginRects;
std::vector<std::vector<Point>> m_texturesFramesOffsets;
// dat stuff
Size m_size;
Point m_displacement;
int m_exactSize;
int m_layers;
int m_numPatternX;
int m_numPatternY;
int m_numPatternZ;
int m_animationPhases;
int m_groundSpeed;
int m_maxTextLenght;
int m_lightLevel;
int m_lightColor;
int m_miniMapColor;
int m_lensHelp;
int m_clothSlot;
int m_elevation;
Boolean<false> m_isGround;
Boolean<false> m_isGroundBorder;
Boolean<false> m_isOnBottom;
Boolean<false> m_isOnTop;
Boolean<false> m_isContainer;
Boolean<false> m_isStackable;
Boolean<false> m_isForceUse;
Boolean<false> m_isMultiUse;
Boolean<false> m_isWritable;
Boolean<false> m_isWritableOnce;
Boolean<false> m_isFluidContainer;
Boolean<false> m_isFluid;
Boolean<false> m_isNotWalkable;
Boolean<false> m_isNotMoveable;
Boolean<false> m_blockProjectile;
Boolean<false> m_isNotPathable;
Boolean<false> m_isPickupable;
Boolean<false> m_isHangable;
Boolean<false> m_isHookSouth;
Boolean<false> m_isHookEast;
Boolean<false> m_isRotateable;
Boolean<false> m_hasLight;
Boolean<false> m_isDontHide;
Boolean<false> m_isTranslucent;
Boolean<false> m_hasDisplacement;
Boolean<false> m_hasElevation;
Boolean<false> m_isLyingCorpse;
Boolean<false> m_isAnimateAlways;
Boolean<false> m_hasMiniMapColor;
Boolean<false> m_hasLensHelp;
Boolean<false> m_isFullGround;
Boolean<false> m_isIgnoreLook;
Boolean<false> m_isCloth;
};
#endif

View File

@ -367,7 +367,7 @@ ThingPtr Tile::getTopMultiUseThing()
for(uint i = 0; i < m_things.size(); ++i) {
ThingPtr thing = m_things[i];
if(thing->isForceUse() || (!thing->isGround() && !thing->isGroundBorder() && !thing->isOnBottom() && !thing->isOnTop())) {
if(i > 0 && thing->isFluid())
if(i > 0 && thing->isSplash())
return m_things[i-1];
return thing;
}

View File

@ -58,7 +58,7 @@ void UIItem::drawSelf(Fw::DrawPane drawPane)
g_painter->setColor(Color::white);
m_item->draw(dest, scaleFactor, true);
if(m_font && m_item->isStackable() && m_item->getCount() > 1) {
if(m_font && (m_item->isStackable() || m_item->isChargeable()) && m_item->getCount() > 1) {
std::string count = stdext::to_string(m_item->getCount());
g_painter->setColor(Color(231, 231, 231));
m_font->drawText(count, Rect(m_rect.topLeft(), m_rect.bottomRight() - Point(3, 0)), Fw::AlignBottomRight);