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
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
-- If you don't use updater or other service, set it to updater = ""

View File

@ -125,11 +125,13 @@ end
function onGameConnectionError(message, code)
CharacterList.destroyLoadBox()
local text = translateNetworkError(code, g_game.getProtocolGame() and g_game.getProtocolGame():isConnecting(), message)
errorBox = displayErrorBox(tr("Connection Error"), text)
errorBox.onOk = function()
errorBox = nil
CharacterList.showAgain()
if (not g_game.isOnline() or code ~= 2) and not errorBox then -- code 2 is normal disconnect, end of file
local text = translateNetworkError(code, g_game.getProtocolGame() and g_game.getProtocolGame():isConnecting(), message)
errorBox = displayErrorBox(tr("Connection Error"), text)
errorBox.onOk = function()
errorBox = nil
CharacterList.showAgain()
end
end
scheduleAutoReconnect()
end
@ -144,8 +146,8 @@ function onGameUpdateNeeded(signature)
end
function onGameEnd()
CharacterList.showAgain()
scheduleAutoReconnect()
CharacterList.showAgain()
end
function onLogout()
@ -163,7 +165,7 @@ function scheduleAutoReconnect()
end
function executeAutoReconnect()
if not autoReconnectButton or not autoReconnectButton:isOn() then
if not autoReconnectButton or not autoReconnectButton:isOn() or g_game.isOnline() then
return
end
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.'))
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)
for feature_id, value in pairs(features) do
if value == "1" or value == "true" or value == true then
@ -468,6 +474,7 @@ function EnterGame.doLogin()
protocolLogin.onSessionKey = onSessionKey
protocolLogin.onCharacterList = onCharacterList
protocolLogin.onUpdateNeeded = onUpdateNeeded
protocolLogin.onProxyList = onProxyList
EnterGame.hide()
loadBox = displayCancelBox(tr('Please wait'), tr('Connecting to login server...'))

View File

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

View File

@ -17,6 +17,8 @@ local enableButton = nil
local executeEvent = nil
local statusLabel = nil
local configManagerUrl = "http://otclient.ovh/configs.php"
function init()
dofile("executor")
@ -263,6 +265,13 @@ function onError(message)
end
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:focus()
editWindow:raise()
@ -306,6 +315,102 @@ function createDefaultConfigs()
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
function message(category, msg)
local widget = g_ui.createWidget('BotLabel', botMessages)

View File

@ -1,85 +1,210 @@
MainWindow
id: editWindow
size: 550 580
!text: tr("Config editor")
!text: tr("Config editor & manager")
@onEscape: self:hide()
@onEnter: self:hide()
size: 550 570
$mobile:
size: 550 240
Label
Panel
id: manager
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
text-auto-resize: true
text-align: center
text-wrap: true
!text: tr("Bot configs are stored in:")
height: 152
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("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
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
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("Upload config")
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
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("Select config to upload")
Label
margin-top: 5
ComboBox
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.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: 5
margin-top: 3
height: 2
Panel
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
anchors.right: parent.right
margin-top: 5
anchors.top: prev.bottom
anchors.horizontalCenter: parent.horizontalCenter
height: 150
image-source: scripts.png
image-fixed-ratio: true
image-size: 500 150
height: 330
$mobile:
visible: false
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("To reload configs just press On and Off in bot window.\nTo learn more about bot click Tutorials button.")
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("Bot configs are stored in:")
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
!text: tr('Documentation')

View File

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

View File

@ -8,17 +8,25 @@ context.zoomOut = function() modules.game_interface.getMapPanel():zoomOut() end
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 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 string then it's used for getSpectatorsByPattern
]]--
local pos = context.player:getPosition()
local direction = context.player:getDirection()
if type(param1) == 'table' then
pos = param1
direction = 8 -- invalid direction
param1 = param2
end
if type(param1) == 'userdata' then
pos = param1:getPosition()
direction = param1:getDirection()
param1 = param2
end
if type(param1) == 'string' then
return g_map.getSpectatorsByPattern(pos, param1)
return g_map.getSpectatorsByPattern(pos, param1, direction)
end
local multifloor = false
@ -221,4 +229,13 @@ end
context.getTileUnderCursor = function()
if not modules.game_interface.gameMapPanel.mousePos then return end
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

View File

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

View File

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

View File

@ -10,6 +10,7 @@ LoginServerUpdateNeeded = 30
LoginServerSessionKey = 40
LoginServerCharacterList = 100
LoginServerExtendedCharacterList = 101
LoginServerProxyList = 110
-- Since 10.76
LoginServerRetry = 10
@ -182,9 +183,19 @@ function ProtocolLogin:onRecv(msg)
self:parseExtendedCharacterList(msg)
elseif opcode == LoginServerUpdate then
local signature = msg:getString()
signalcall(self.onUpdateNeeded, self, signature)
signalcall(self.onUpdateNeeded, self, signature)
elseif opcode == LoginServerSessionKey then
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
self:parseOpcode(opcode, msg)
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", "isPatchable", &Map::isPatchable, &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.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>("isRemoved", &Creature::isRemoved);
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>("getPrewalkingPosition", &Creature::getPrewalkingPosition);
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>("isFullyOpaque", &Tile::isFullyOpaque);
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>("hasBlockingCreature", &Tile::hasBlockingCreature);
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>("hasElevation", &Tile::hasElevation);
g_lua.bindClassMemberFunction<Tile>("isBlocking", &Tile::isBlocking);
g_lua.bindClassMemberFunction<Tile>("canShoot", &Tile::canShoot);
// for bot
g_lua.bindClassMemberFunction<Tile>("setText", &Tile::setText);
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);
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
g_lua.registerSingletonClass("g_platform");
#ifdef UNSAFE_LUA_FUNCTIONS
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", "fileExists", &Platform::fileExists, &g_platform);
g_lua.bindSingletonFunction("g_platform", "removeFile", &Platform::removeFile, &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", "openUrl", &Platform::openUrl, &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", "wsClose", &Http::wsClose, &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.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", "updateData", &ResourceManager::updateData, &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", "getLayout", &ResourceManager::getLayout, &g_resources);