This commit is contained in:
OTCv8 2020-07-23 02:37:11 +02:00
parent a65844f182
commit 929ab400ed
19 changed files with 489 additions and 84 deletions

View File

@ -1,6 +1,6 @@
-- CONFIG -- CONFIG
APP_NAME = "otclientv8" -- important, change it, it's name for config dir and files in appdata APP_NAME = "otclientv8" -- important, change it, it's name for config dir and files in appdata
APP_VERSION = 1340 -- client version for updater and login to identify outdated client APP_VERSION = 1342 -- client version for updater and login to identify outdated client
DEFAULT_LAYOUT = "retro" -- on android it's forced to "mobile", check code bellow DEFAULT_LAYOUT = "retro" -- on android it's forced to "mobile", check code bellow
-- If you don't use updater or other service, set it to updater = "" -- If you don't use updater or other service, set it to updater = ""

View File

@ -125,11 +125,13 @@ end
function onGameConnectionError(message, code) function onGameConnectionError(message, code)
CharacterList.destroyLoadBox() CharacterList.destroyLoadBox()
local text = translateNetworkError(code, g_game.getProtocolGame() and g_game.getProtocolGame():isConnecting(), message) if (not g_game.isOnline() or code ~= 2) and not errorBox then -- code 2 is normal disconnect, end of file
errorBox = displayErrorBox(tr("Connection Error"), text) local text = translateNetworkError(code, g_game.getProtocolGame() and g_game.getProtocolGame():isConnecting(), message)
errorBox.onOk = function() errorBox = displayErrorBox(tr("Connection Error"), text)
errorBox = nil errorBox.onOk = function()
CharacterList.showAgain() errorBox = nil
CharacterList.showAgain()
end
end end
scheduleAutoReconnect() scheduleAutoReconnect()
end end
@ -144,8 +146,8 @@ function onGameUpdateNeeded(signature)
end end
function onGameEnd() function onGameEnd()
CharacterList.showAgain()
scheduleAutoReconnect() scheduleAutoReconnect()
CharacterList.showAgain()
end end
function onLogout() function onLogout()
@ -163,7 +165,7 @@ function scheduleAutoReconnect()
end end
function executeAutoReconnect() function executeAutoReconnect()
if not autoReconnectButton or not autoReconnectButton:isOn() then if not autoReconnectButton or not autoReconnectButton:isOn() or g_game.isOnline() then
return return
end end
if errorBox then if errorBox then

View File

@ -63,6 +63,12 @@ local function onUpdateNeeded(protocol, signature)
return EnterGame.onError(tr('Your client needs updating, try redownloading it.')) return EnterGame.onError(tr('Your client needs updating, try redownloading it.'))
end end
local function onProxyList(protocol, proxies)
for _, proxy in ipairs(proxies) do
g_proxy.addProxy(proxy["host"], proxy["port"], proxy["priority"])
end
end
local function parseFeatures(features) local function parseFeatures(features)
for feature_id, value in pairs(features) do for feature_id, value in pairs(features) do
if value == "1" or value == "true" or value == true then if value == "1" or value == "true" or value == true then
@ -468,6 +474,7 @@ function EnterGame.doLogin()
protocolLogin.onSessionKey = onSessionKey protocolLogin.onSessionKey = onSessionKey
protocolLogin.onCharacterList = onCharacterList protocolLogin.onCharacterList = onCharacterList
protocolLogin.onUpdateNeeded = onUpdateNeeded protocolLogin.onUpdateNeeded = onUpdateNeeded
protocolLogin.onProxyList = onProxyList
EnterGame.hide() EnterGame.hide()
loadBox = displayCancelBox(tr('Please wait'), tr('Connecting to login server...')) loadBox = displayCancelBox(tr('Please wait'), tr('Connecting to login server...'))

View File

@ -13,6 +13,7 @@ prevCreature = nil
battleButtons = {} battleButtons = {}
local ageNumber = 1 local ageNumber = 1
local ages = {}
function init() function init()
g_ui.importStyle('battlebutton') g_ui.importStyle('battlebutton')
@ -239,8 +240,12 @@ function checkCreatures()
local creatures = {} local creatures = {}
for _, creature in ipairs(spectators) do for _, creature in ipairs(spectators) do
if doCreatureFitFilters(creature) and #creatures < maxCreatures then if doCreatureFitFilters(creature) and #creatures < maxCreatures then
if not creature.age then if not ages[creature:getId()] then
creature.age = ageNumber if ageNumber > 10000 then
ageNumber = 1
ages = {}
end
ages[creature:getId()] = ageNumber
ageNumber = ageNumber + 1 ageNumber = ageNumber + 1
end end
table.insert(creatures, creature) table.insert(creatures, creature)
@ -324,23 +329,23 @@ function sortCreatures(creatures)
local playerPos = player:getPosition() local playerPos = player:getPosition()
table.sort(creatures, function(a, b) table.sort(creatures, function(a, b)
if getDistanceBetween(playerPos, a:getPosition()) == getDistanceBetween(playerPos, b:getPosition()) then if getDistanceBetween(playerPos, a:getPosition()) == getDistanceBetween(playerPos, b:getPosition()) then
return a.age > b.age return ages[a:getId()] > ages[b:getId()]
end end
return getDistanceBetween(playerPos, a:getPosition()) > getDistanceBetween(playerPos, b:getPosition()) return getDistanceBetween(playerPos, a:getPosition()) > getDistanceBetween(playerPos, b:getPosition())
end) end)
elseif getSortType() == 'health' then elseif getSortType() == 'health' then
table.sort(creatures, function(a, b) table.sort(creatures, function(a, b)
if a:getHealthPercent() == b:getHealthPercent() then if a:getHealthPercent() == b:getHealthPercent() then
return a.age > b.age return ages[a:getId()] > ages[b:getId()]
end end
return a:getHealthPercent() > b:getHealthPercent() return a:getHealthPercent() > b:getHealthPercent()
end) end)
elseif getSortType() == 'age' then elseif getSortType() == 'age' then
table.sort(creatures, function(a, b) return a.age > b.age end) table.sort(creatures, function(a, b) return ages[a:getId()] > ages[b:getId()] end)
else -- name else -- name
table.sort(creatures, function(a, b) table.sort(creatures, function(a, b)
if a:getName():lower() == b:getName():lower() then if a:getName():lower() == b:getName():lower() then
return a.age > b.age return ages[a:getId()] > ages[b:getId()]
end end
return a:getName():lower() > b:getName():lower() return a:getName():lower() > b:getName():lower()
end) end)

View File

@ -17,6 +17,8 @@ local enableButton = nil
local executeEvent = nil local executeEvent = nil
local statusLabel = nil local statusLabel = nil
local configManagerUrl = "http://otclient.ovh/configs.php"
function init() function init()
dofile("executor") dofile("executor")
@ -263,6 +265,13 @@ function onError(message)
end end
function edit() function edit()
local configs = g_resources.listDirectoryFiles("/bot", false, false)
editWindow.manager.upload.config:clearOptions()
for i=1,#configs do
editWindow.manager.upload.config:addOption(configs[i])
end
editWindow.manager.download.config:setText("")
editWindow:show() editWindow:show()
editWindow:focus() editWindow:focus()
editWindow:raise() editWindow:raise()
@ -306,6 +315,102 @@ function createDefaultConfigs()
end end
end end
function uploadConfig()
local config = editWindow.manager.upload.config:getCurrentOption().text
local archive = compressConfig(config)
if not archive then
return displayErrorBox(tr("Config upload failed"), tr("Config %s is invalid (can't be compressed)", config))
end
if archive:len() > 64 * 1024 then
return displayErrorBox(tr("Config upload failed"), tr("Config %s is too big, maximum size is 64KB. Now it has %s KB.", config, math.floor(archive / 1024)))
end
local infoBox = displayInfoBox(tr("Uploading config"), tr("Uploading config %s. Please wait.", config))
HTTP.postJSON(configManagerUrl .. "?config=" .. config:gsub("%s+", "_"), archive, function(data, err)
if infoBox then
infoBox:destroy()
end
if err or data["error"] then
return displayErrorBox(tr("Config upload failed"), tr("Error while upload config %s:\n%s", config, err or data["error"]))
end
displayInfoBox(tr("Succesful config upload"), tr("Config %s has been uploaded.\n%s", config, data["message"]))
end)
end
function downloadConfig()
local hash = editWindow.manager.download.config:getText()
if hash:len() == 0 then
return displayErrorBox(tr("Config download error"), tr("Enter correct config hash"))
end
local infoBox = displayInfoBox(tr("Downloading config"), tr("Downloading config with hash %s. Please wait.", hash))
HTTP.download(configManagerUrl .. "?hash=" .. hash, hash .. ".zip", function(path, checksum, err)
if infoBox then
infoBox:destroy()
end
if err then
return displayErrorBox(tr("Config download error"), tr("Config with hash %s cannot be downloaded", hash))
end
modules.client_textedit.show("", {
title="Enter name for downloaded config",
description="Config with hash " .. hash .. " has been downloaded. Enter name for new config.\nWarning: if config with same name already exist, it will be overwritten!",
width=500
}, function(configName)
decompressConfig(configName, "/downloads/" .. path)
refresh()
edit()
end)
end)
end
function compressConfig(configName)
if not g_resources.directoryExists("/bot/" .. configName) then
return onError("Config " .. configName .. " doesn't exist")
end
local forArchive = {}
for _, file in ipairs(g_resources.listDirectoryFiles("/bot/" .. configName)) do
local fullPath = "/bot/" .. configName .. "/" .. file
if g_resources.fileExists(fullPath) then -- regular file
forArchive[file] = g_resources.readFileContents(fullPath)
else -- dir
for __, file2 in ipairs(g_resources.listDirectoryFiles(fullPath)) do
local fullPath2 = fullPath .. "/" .. file2
if g_resources.fileExists(fullPath2) then -- regular file
forArchive[file .. "/" .. file2] = g_resources.readFileContents(fullPath2)
end
end
end
end
return g_resources.createArchive(forArchive)
end
function decompressConfig(configName, archive)
if g_resources.directoryExists("/bot/" .. configName) then
g_resources.deleteFile("/bot/" .. configName) -- also delete dirs
end
local files = g_resources.decompressArchive(archive)
g_resources.makeDir("/bot/" .. configName)
if not g_resources.directoryExists("/bot/" .. configName) then
return onError("Can't create /bot/" .. configName .. " directory in " .. g_resources.getWriteDir())
end
for file, contents in pairs(files) do
local split = file:split("/")
split[#split] = nil -- remove file name
local dirPath = "/bot/" .. configName
for _, s in ipairs(split) do
dirPath = dirPath .. "/" .. s
if not g_resources.directoryExists(dirPath) then
g_resources.makeDir(dirPath)
if not g_resources.directoryExists(dirPath) then
return onError("Can't create " .. dirPath .. " directory in " .. g_resources.getWriteDir())
end
end
end
g_resources.writeFileContents("/bot/" .. configName .. file, contents)
end
end
-- Executor -- Executor
function message(category, msg) function message(category, msg)
local widget = g_ui.createWidget('BotLabel', botMessages) local widget = g_ui.createWidget('BotLabel', botMessages)

View File

@ -1,85 +1,210 @@
MainWindow MainWindow
id: editWindow id: editWindow
size: 550 580 !text: tr("Config editor & manager")
!text: tr("Config editor")
@onEscape: self:hide() @onEscape: self:hide()
@onEnter: self:hide() size: 550 570
$mobile:
size: 550 240
Label Panel
id: manager
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
text-auto-resize: true height: 152
text-align: center
text-wrap: true Label
!text: tr("Bot configs are stored in:") anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
text-auto-resize: true
text-align: center
text-wrap: true
!text: tr("Config Manager\nYou can use config manager to share configs between different machines, especially smartphones. After you configure your config, you can upload it, then you'll get unique hash code which you can use on diffent machinge (for eg. mobile phone) to download it.")
HorizontalSeparator
anchors.top: prev.bottom
anchors.left: parent.left
anchors.right: parent.right
margin-top: 3
height: 2
Panel
id: upload
anchors.top: prev.bottom
anchors.left: parent.left
anchors.right: parent.horizontalCenter
anchors.bottom: parent.bottom
margin-top: 3
TextEdit Label
anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top
anchors.top: prev.bottom anchors.left: parent.left
height: 20 anchors.right: parent.right
width: 400 text-auto-resize: true
margin-top: 5 text-align: center
editable: false text-wrap: true
!text: g_resources.getWriteDir() .. "bot" !text: tr("Upload config")
text-align: center
Button Label
id: documentationButton anchors.top: prev.bottom
!text: tr('Click here to open bot directory') anchors.left: parent.left
anchors.horizontalCenter: parent.horizontalCenter anchors.right: parent.right
anchors.top: prev.bottom margin-top: 7
margin-top: 5 text-auto-resize: true
width: 250 text-align: center
@onClick: g_platform.openDir(g_resources.getWriteDir() .. "bot") text-wrap: true
!text: tr("Select config to upload")
Label ComboBox
margin-top: 5 id: config
anchors.top: prev.bottom
anchors.left: parent.left
anchors.right: parent.right
margin-top: 4
margin-left: 20
margin-right: 20
text-offset: 3 0
Button
id: submit
anchors.top: prev.bottom
anchors.left: parent.left
anchors.right: parent.right
!text: tr('Upload config')
margin-top: 4
margin-left: 40
margin-right: 40
@onClick: modules.game_bot.uploadConfig()
Panel
id: download
anchors.top: prev.top
anchors.left: parent.horizontalCenter
anchors.right: parent.right
anchors.bottom: parent.bottom
Label
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
text-auto-resize: true
text-align: center
text-wrap: true
!text: tr("Download config")
Label
anchors.top: prev.bottom
anchors.left: parent.left
anchors.right: parent.right
margin-top: 7
text-auto-resize: true
text-align: center
text-wrap: true
!text: tr("Enter config hash code")
TextEdit
id: config
anchors.top: prev.bottom
anchors.left: parent.left
anchors.right: parent.right
margin-top: 4
margin-left: 20
margin-right: 20
Button
id: submit
anchors.top: prev.bottom
anchors.left: parent.left
anchors.right: parent.right
!text: tr('Download config')
margin-top: 4
margin-left: 40
margin-right: 40
@onClick: modules.game_bot.downloadConfig()
HorizontalSeparator
anchors.top: prev.bottom anchors.top: prev.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
text-auto-resize: true margin-top: 3
text-align: center height: 2
text-wrap: true
!text: tr("Every directory in bot directory is treated as different config.\nTo create new config just create new directory.") Panel
Label
margin-top: 5
anchors.top: prev.bottom
anchors.horizontalCenter: parent.horizontalCenter
height: 175
image-source: configs.png
image-fixed-ratio: true
image-size: 500 175
Label
margin-top: 5
anchors.top: prev.bottom anchors.top: prev.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
text-auto-resize: true
text-align: center
text-wrap: true
!text: tr("Inside config directory put .lua and .otui files.\nEvery file will be loaded and executed in alphabetical order, .otui first and then .lua.")
Label
margin-top: 5 margin-top: 5
anchors.top: prev.bottom height: 330
anchors.horizontalCenter: parent.horizontalCenter $mobile:
height: 150 visible: false
image-source: scripts.png
image-fixed-ratio: true
image-size: 500 150
Label Label
margin-top: 5 anchors.top: parent.top
anchors.top: prev.bottom anchors.left: parent.left
anchors.left: parent.left anchors.right: parent.right
anchors.right: parent.right text-auto-resize: true
text-auto-resize: true text-align: center
text-align: center text-wrap: true
text-wrap: true !text: tr("Bot configs are stored in:")
!text: tr("To reload configs just press On and Off in bot window.\nTo learn more about bot click Tutorials button.")
TextEdit
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: prev.bottom
height: 20
width: 400
margin-top: 5
editable: false
!text: g_resources.getWriteDir() .. "bot"
text-align: center
Button
id: documentationButton
!text: tr('Click here to open bot directory')
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: prev.bottom
margin-top: 5
width: 250
@onClick: g_platform.openDir(g_resources.getWriteDir() .. "bot")
Label
margin-top: 5
anchors.top: prev.bottom
anchors.left: parent.left
anchors.right: parent.right
text-auto-resize: true
text-align: center
text-wrap: true
!text: tr("Every directory in bot directory is treated as different config.\nTo create new config just create new directory.")
Label
margin-top: 5
anchors.top: prev.bottom
anchors.horizontalCenter: parent.horizontalCenter
height: 175
image-source: configs.png
image-fixed-ratio: true
image-size: 500 175
Label
margin-top: 3
anchors.top: prev.bottom
anchors.left: parent.left
anchors.right: parent.right
text-auto-resize: true
text-align: center
text-wrap: true
!text: tr("Inside config directory put .lua and .otui files.\nEvery file will be loaded and executed in alphabetical order, .otui first and then .lua.")
Label
margin-top: 3
anchors.top: prev.bottom
anchors.left: parent.left
anchors.right: parent.right
text-auto-resize: true
text-align: center
text-wrap: true
!text: tr("To reload configs just press On and Off in bot window.\nTo learn more about bot click Tutorials button.")
Button Button
!text: tr('Documentation') !text: tr('Documentation')

View File

@ -86,17 +86,22 @@ function executeBot(config, storage, tabs, msgCallback, saveConfigCallback, relo
context.getDistanceBetween = function(p1, p2) context.getDistanceBetween = function(p1, p2)
return math.max(math.abs(p1.x - p2.x), math.abs(p1.y - p2.y)) return math.max(math.abs(p1.x - p2.x), math.abs(p1.y - p2.y))
end end
context.isMobile = g_app.isMobile
context.getVersion = g_app.getVersion
-- classes -- classes
context.g_resources = g_resources context.g_resources = g_resources
context.g_game = g_game context.g_game = g_game
context.g_map = g_map context.g_map = g_map
context.g_ui = g_ui context.g_ui = g_ui
context.g_platform = g_platform
context.g_sounds = g_sounds context.g_sounds = g_sounds
context.g_window = g_window context.g_window = g_window
context.g_mouse = g_mouse context.g_mouse = g_mouse
context.g_things = g_things context.g_things = g_things
context.g_platform = {
openUrl = g_platform.openUrl,
openDir = g_platform.openDir,
}
context.Item = Item context.Item = Item
context.Creature = Creature context.Creature = Creature

View File

@ -8,17 +8,25 @@ context.zoomOut = function() modules.game_interface.getMapPanel():zoomOut() end
context.getSpectators = function(param1, param2) context.getSpectators = function(param1, param2)
--[[ --[[
if param1 is table (position) then it's used for central position, then param2 is used as param1 if param1 is table (position) then it's used for central position, then param2 is used as param1
if param1 is creature, then creature position and direction of creature is used, then param2 is used as param1
if param1 is true/false then it's used for multifloor, example: getSpectators(true) if param1 is true/false then it's used for multifloor, example: getSpectators(true)
if param1 is string then it's used for getSpectatorsByPattern if param1 is string then it's used for getSpectatorsByPattern
]]-- ]]--
local pos = context.player:getPosition() local pos = context.player:getPosition()
local direction = context.player:getDirection()
if type(param1) == 'table' then if type(param1) == 'table' then
pos = param1 pos = param1
direction = 8 -- invalid direction
param1 = param2
end
if type(param1) == 'userdata' then
pos = param1:getPosition()
direction = param1:getDirection()
param1 = param2 param1 = param2
end end
if type(param1) == 'string' then if type(param1) == 'string' then
return g_map.getSpectatorsByPattern(pos, param1) return g_map.getSpectatorsByPattern(pos, param1, direction)
end end
local multifloor = false local multifloor = false
@ -221,4 +229,13 @@ end
context.getTileUnderCursor = function() context.getTileUnderCursor = function()
if not modules.game_interface.gameMapPanel.mousePos then return end if not modules.game_interface.gameMapPanel.mousePos then return end
return modules.game_interface.gameMapPanel:getTile(modules.game_interface.gameMapPanel.mousePos) return modules.game_interface.gameMapPanel:getTile(modules.game_interface.gameMapPanel.mousePos)
end
context.canShoot = function(pos, distance)
if not distance then distance = 5 end
local tile = g_map.getTile(pos, distance)
if tile then
return tile:canShoot(distance)
end
return false
end end

View File

@ -227,6 +227,7 @@ function accept()
end end
end end
end end
g_game.changeOutfit(outfit) g_game.changeOutfit(outfit)
destroy() destroy()
end end

View File

@ -5,3 +5,5 @@ end
function terminate() function terminate()
end end

View File

@ -10,6 +10,7 @@ LoginServerUpdateNeeded = 30
LoginServerSessionKey = 40 LoginServerSessionKey = 40
LoginServerCharacterList = 100 LoginServerCharacterList = 100
LoginServerExtendedCharacterList = 101 LoginServerExtendedCharacterList = 101
LoginServerProxyList = 110
-- Since 10.76 -- Since 10.76
LoginServerRetry = 10 LoginServerRetry = 10
@ -182,9 +183,19 @@ function ProtocolLogin:onRecv(msg)
self:parseExtendedCharacterList(msg) self:parseExtendedCharacterList(msg)
elseif opcode == LoginServerUpdate then elseif opcode == LoginServerUpdate then
local signature = msg:getString() local signature = msg:getString()
signalcall(self.onUpdateNeeded, self, signature) signalcall(self.onUpdateNeeded, self, signature)
elseif opcode == LoginServerSessionKey then elseif opcode == LoginServerSessionKey then
self:parseSessionKey(msg) self:parseSessionKey(msg)
elseif opcode == LoginServerProxyList then
local proxies = {}
local proxiesCount = msg:getU8()
for i=1, proxiesCount do
local host = msg:getString()
local port = msg:getU16()
local priority = msg:getU16()
table.insert(proxies, {host=host, port=port, priority=priority})
end
signalcall(self.onProxyList, self, proxies)
else else
self:parseOpcode(opcode, msg) self:parseOpcode(opcode, msg)
end end

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -169,6 +169,8 @@ void Client::registerLuaFunctions()
g_lua.bindSingletonFunction("g_map", "getMinimapColor", &Map::getMinimapColor, &g_map); g_lua.bindSingletonFunction("g_map", "getMinimapColor", &Map::getMinimapColor, &g_map);
g_lua.bindSingletonFunction("g_map", "isPatchable", &Map::isPatchable, &g_map); g_lua.bindSingletonFunction("g_map", "isPatchable", &Map::isPatchable, &g_map);
g_lua.bindSingletonFunction("g_map", "isWalkable", &Map::isWalkable, &g_map); g_lua.bindSingletonFunction("g_map", "isWalkable", &Map::isWalkable, &g_map);
g_lua.bindSingletonFunction("g_map", "checkSightLine", &Map::checkSightLine, &g_map);
g_lua.bindSingletonFunction("g_map", "isSightClear", &Map::isSightClear, &g_map);
g_lua.registerSingletonClass("g_minimap"); g_lua.registerSingletonClass("g_minimap");
g_lua.bindSingletonFunction("g_minimap", "clean", &Minimap::clean, &g_minimap); g_lua.bindSingletonFunction("g_minimap", "clean", &Minimap::clean, &g_minimap);
@ -523,6 +525,7 @@ void Client::registerLuaFunctions()
g_lua.bindClassMemberFunction<Creature>("isDead", &Creature::isDead); g_lua.bindClassMemberFunction<Creature>("isDead", &Creature::isDead);
g_lua.bindClassMemberFunction<Creature>("isRemoved", &Creature::isRemoved); g_lua.bindClassMemberFunction<Creature>("isRemoved", &Creature::isRemoved);
g_lua.bindClassMemberFunction<Creature>("canBeSeen", &Creature::canBeSeen); g_lua.bindClassMemberFunction<Creature>("canBeSeen", &Creature::canBeSeen);
g_lua.bindClassMemberFunction<Creature>("canShoot", &Creature::canShoot);
g_lua.bindClassMemberFunction<Creature>("jump", &Creature::jump); g_lua.bindClassMemberFunction<Creature>("jump", &Creature::jump);
g_lua.bindClassMemberFunction<Creature>("getPrewalkingPosition", &Creature::getPrewalkingPosition); g_lua.bindClassMemberFunction<Creature>("getPrewalkingPosition", &Creature::getPrewalkingPosition);
g_lua.bindClassMemberFunction<Creature>("setInformationColor", &Creature::setInformationColor); g_lua.bindClassMemberFunction<Creature>("setInformationColor", &Creature::setInformationColor);
@ -772,6 +775,7 @@ void Client::registerLuaFunctions()
g_lua.bindClassMemberFunction<Tile>("isFullGround", &Tile::isFullGround); g_lua.bindClassMemberFunction<Tile>("isFullGround", &Tile::isFullGround);
g_lua.bindClassMemberFunction<Tile>("isFullyOpaque", &Tile::isFullyOpaque); g_lua.bindClassMemberFunction<Tile>("isFullyOpaque", &Tile::isFullyOpaque);
g_lua.bindClassMemberFunction<Tile>("isLookPossible", &Tile::isLookPossible); g_lua.bindClassMemberFunction<Tile>("isLookPossible", &Tile::isLookPossible);
g_lua.bindClassMemberFunction<Tile>("isBlockingProjectile", &Tile::isBlockingProjectile);
g_lua.bindClassMemberFunction<Tile>("hasCreature", &Tile::hasCreature); g_lua.bindClassMemberFunction<Tile>("hasCreature", &Tile::hasCreature);
g_lua.bindClassMemberFunction<Tile>("hasBlockingCreature", &Tile::hasBlockingCreature); g_lua.bindClassMemberFunction<Tile>("hasBlockingCreature", &Tile::hasBlockingCreature);
g_lua.bindClassMemberFunction<Tile>("isEmpty", &Tile::isEmpty); g_lua.bindClassMemberFunction<Tile>("isEmpty", &Tile::isEmpty);
@ -789,6 +793,7 @@ void Client::registerLuaFunctions()
g_lua.bindClassMemberFunction<Tile>("getElevation", &Tile::getElevation); g_lua.bindClassMemberFunction<Tile>("getElevation", &Tile::getElevation);
g_lua.bindClassMemberFunction<Tile>("hasElevation", &Tile::hasElevation); g_lua.bindClassMemberFunction<Tile>("hasElevation", &Tile::hasElevation);
g_lua.bindClassMemberFunction<Tile>("isBlocking", &Tile::isBlocking); g_lua.bindClassMemberFunction<Tile>("isBlocking", &Tile::isBlocking);
g_lua.bindClassMemberFunction<Tile>("canShoot", &Tile::canShoot);
// for bot // for bot
g_lua.bindClassMemberFunction<Tile>("setText", &Tile::setText); g_lua.bindClassMemberFunction<Tile>("setText", &Tile::setText);
g_lua.bindClassMemberFunction<Tile>("getText", &Tile::getText); g_lua.bindClassMemberFunction<Tile>("getText", &Tile::getText);

View File

@ -967,3 +967,118 @@ bool Map::isWalkable(const Position& pos, bool ignoreCreatures)
const MinimapTile& mtile = g_minimap.getTile(pos); const MinimapTile& mtile = g_minimap.getTile(pos);
return !mtile.hasFlag(MinimapTileNotPathable); return !mtile.hasFlag(MinimapTileNotPathable);
} }
std::vector<CreaturePtr> Map::getSpectatorsByPattern(const Position& centerPos, const std::string& pattern, Otc::Direction direction)
{
std::vector<bool> finalPattern(pattern.size(), false);
std::vector<CreaturePtr> creatures;
int width = 0, height = 0, lineLength = 0, p = 0;
for (auto& c : pattern) {
lineLength += 1;
if (c == '0' || c == '-') {
p += 1;
} else if (c == '1' || c == '+') {
finalPattern[p++] = true;
} else if (c == 'N' || c == 'n') {
finalPattern[p++] = direction == Otc::North;
} else if (c == 'E' || c == 'e') {
finalPattern[p++] = direction == Otc::East;
} else if (c == 'W' || c == 'w') {
finalPattern[p++] = direction == Otc::West;
} else if (c == 'S' || c == 's') {
finalPattern[p++] = direction == Otc::South;
} else {
lineLength -= 1;
if (lineLength > 1) {
if (width == 0)
width = lineLength;
if (width != lineLength) {
g_logger.error(stdext::format("Invalid pattern for getSpectatorsByPattern: %s", pattern));
return creatures;
}
height += 1;
lineLength = 0;
}
}
}
if (lineLength > 0) {
if (width == 0)
width = lineLength;
if (width != lineLength) {
g_logger.error(stdext::format("Invalid pattern for getSpectatorsByPattern: %s", pattern));
return creatures;
}
height += 1;
}
if (width % 2 != 1 || height % 2 != 1) {
g_logger.error(stdext::format("Invalid pattern for getSpectatorsByPattern, width and height should be odd (height: %i width: %i)", height, width));
return creatures;
}
p = 0;
for (int y = centerPos.y - height / 2, endy = centerPos.y + height / 2; y <= endy; ++y) {
for (int x = centerPos.x - width / 2, endx = centerPos.x + width / 2; x <= endx; ++x) {
if (!finalPattern[p++])
continue;
TilePtr tile = getTile(Position(x, y, centerPos.z));
if (!tile)
continue;
auto tileCreatures = tile->getCreatures();
creatures.insert(creatures.end(), tileCreatures.rbegin(), tileCreatures.rend());
}
}
return creatures;
}
bool Map::isSightClear(const Position& fromPos, const Position& toPos)
{
if (fromPos == toPos) {
return true;
}
Position start(fromPos.z > toPos.z ? toPos : fromPos);
Position destination(fromPos.z > toPos.z ? fromPos : toPos);
const int8_t mx = start.x < destination.x ? 1 : start.x == destination.x ? 0 : -1;
const int8_t my = start.y < destination.y ? 1 : start.y == destination.y ? 0 : -1;
int32_t A = destination.y - start.y;
int32_t B = start.x - destination.x;
int32_t C = -(A * destination.x + B * destination.y);
while (start.x != destination.x || start.y != destination.y) {
int32_t move_hor = std::abs(A * (start.x + mx) + B * (start.y) + C);
int32_t move_ver = std::abs(A * (start.x) + B * (start.y + my) + C);
int32_t move_cross = std::abs(A * (start.x + mx) + B * (start.y + my) + C);
if (start.y != destination.y && (start.x == destination.x || move_hor > move_ver || move_hor > move_cross)) {
start.y += my;
}
if (start.x != destination.x && (start.y == destination.y || move_ver > move_hor || move_ver > move_cross)) {
start.x += mx;
}
auto tile = getTile(Position(start.x, start.y, start.z));
if (tile && tile->isBlockingProjectile()) {
return false;
}
}
while (start.z != destination.z) {
auto tile = getTile(Position(start.x, start.y, start.z));
if (tile && tile->getThingCount() > 0) {
return false;
}
start.z++;
}
return true;
}
bool Map::checkSightLine(const Position& fromPos, const Position& toPos)
{
if (fromPos.z != toPos.z)
return false;
return checkSightLine(fromPos, toPos) || checkSightLine(toPos, fromPos);
}

View File

@ -109,13 +109,15 @@ void Application::registerLuaFunctions()
// Platform // Platform
g_lua.registerSingletonClass("g_platform"); g_lua.registerSingletonClass("g_platform");
#ifdef UNSAFE_LUA_FUNCTIONS
g_lua.bindSingletonFunction("g_platform", "spawnProcess", &Platform::spawnProcess, &g_platform); g_lua.bindSingletonFunction("g_platform", "spawnProcess", &Platform::spawnProcess, &g_platform);
g_lua.bindSingletonFunction("g_platform", "getProcessId", &Platform::getProcessId, &g_platform);
g_lua.bindSingletonFunction("g_platform", "isProcessRunning", &Platform::isProcessRunning, &g_platform);
g_lua.bindSingletonFunction("g_platform", "copyFile", &Platform::copyFile, &g_platform); g_lua.bindSingletonFunction("g_platform", "copyFile", &Platform::copyFile, &g_platform);
g_lua.bindSingletonFunction("g_platform", "fileExists", &Platform::fileExists, &g_platform); g_lua.bindSingletonFunction("g_platform", "fileExists", &Platform::fileExists, &g_platform);
g_lua.bindSingletonFunction("g_platform", "removeFile", &Platform::removeFile, &g_platform); g_lua.bindSingletonFunction("g_platform", "removeFile", &Platform::removeFile, &g_platform);
g_lua.bindSingletonFunction("g_platform", "killProcess", &Platform::killProcess, &g_platform); g_lua.bindSingletonFunction("g_platform", "killProcess", &Platform::killProcess, &g_platform);
#endif
g_lua.bindSingletonFunction("g_platform", "getProcessId", &Platform::getProcessId, &g_platform);
g_lua.bindSingletonFunction("g_platform", "isProcessRunning", &Platform::isProcessRunning, &g_platform);
g_lua.bindSingletonFunction("g_platform", "getTempPath", &Platform::getTempPath, &g_platform); g_lua.bindSingletonFunction("g_platform", "getTempPath", &Platform::getTempPath, &g_platform);
g_lua.bindSingletonFunction("g_platform", "openUrl", &Platform::openUrl, &g_platform); g_lua.bindSingletonFunction("g_platform", "openUrl", &Platform::openUrl, &g_platform);
g_lua.bindSingletonFunction("g_platform", "openDir", &Platform::openDir, &g_platform); g_lua.bindSingletonFunction("g_platform", "openDir", &Platform::openDir, &g_platform);
@ -226,6 +228,7 @@ void Application::registerLuaFunctions()
g_lua.bindSingletonFunction("g_http", "wsSend", &Http::wsSend, &g_http); g_lua.bindSingletonFunction("g_http", "wsSend", &Http::wsSend, &g_http);
g_lua.bindSingletonFunction("g_http", "wsClose", &Http::wsClose, &g_http); g_lua.bindSingletonFunction("g_http", "wsClose", &Http::wsClose, &g_http);
g_lua.bindSingletonFunction("g_http", "cancel", &Http::cancel, &g_http); g_lua.bindSingletonFunction("g_http", "cancel", &Http::cancel, &g_http);
g_lua.bindSingletonFunction("g_http", "setUserAgent", &Http::setUserAgent, &g_http);
g_lua.registerSingletonClass("g_atlas"); g_lua.registerSingletonClass("g_atlas");
g_lua.bindSingletonFunction("g_atlas", "getStats", &Atlas::getStats, &g_atlas); g_lua.bindSingletonFunction("g_atlas", "getStats", &Atlas::getStats, &g_atlas);
@ -273,6 +276,8 @@ void Application::registerLuaFunctions()
g_lua.bindSingletonFunction("g_resources", "selfChecksum", &ResourceManager::selfChecksum, &g_resources); g_lua.bindSingletonFunction("g_resources", "selfChecksum", &ResourceManager::selfChecksum, &g_resources);
g_lua.bindSingletonFunction("g_resources", "updateData", &ResourceManager::updateData, &g_resources); g_lua.bindSingletonFunction("g_resources", "updateData", &ResourceManager::updateData, &g_resources);
g_lua.bindSingletonFunction("g_resources", "updateExecutable", &ResourceManager::updateExecutable, &g_resources); g_lua.bindSingletonFunction("g_resources", "updateExecutable", &ResourceManager::updateExecutable, &g_resources);
g_lua.bindSingletonFunction("g_resources", "createArchive", &ResourceManager::createArchive, &g_resources);
g_lua.bindSingletonFunction("g_resources", "decompressArchive", &ResourceManager::decompressArchive, &g_resources);
g_lua.bindSingletonFunction("g_resources", "setLayout", &ResourceManager::setLayout, &g_resources); g_lua.bindSingletonFunction("g_resources", "setLayout", &ResourceManager::setLayout, &g_resources);
g_lua.bindSingletonFunction("g_resources", "getLayout", &ResourceManager::getLayout, &g_resources); g_lua.bindSingletonFunction("g_resources", "getLayout", &ResourceManager::getLayout, &g_resources);