diff --git a/init.lua b/init.lua index c24bc29..b1f1bb4 100644 --- a/init.lua +++ b/init.lua @@ -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 = "" diff --git a/modules/client_entergame/characterlist.lua b/modules/client_entergame/characterlist.lua index dd6da80..ef5ec74 100644 --- a/modules/client_entergame/characterlist.lua +++ b/modules/client_entergame/characterlist.lua @@ -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 diff --git a/modules/client_entergame/entergame.lua b/modules/client_entergame/entergame.lua index f81bead..5de6488 100644 --- a/modules/client_entergame/entergame.lua +++ b/modules/client_entergame/entergame.lua @@ -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...')) diff --git a/modules/game_battle/battle.lua b/modules/game_battle/battle.lua index a5ce972..6c66f7f 100644 --- a/modules/game_battle/battle.lua +++ b/modules/game_battle/battle.lua @@ -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) diff --git a/modules/game_bot/bot.lua b/modules/game_bot/bot.lua index e94b09f..3809080 100644 --- a/modules/game_bot/bot.lua +++ b/modules/game_bot/bot.lua @@ -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) diff --git a/modules/game_bot/edit.otui b/modules/game_bot/edit.otui index 07de46f..e45ad6e 100644 --- a/modules/game_bot/edit.otui +++ b/modules/game_bot/edit.otui @@ -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') diff --git a/modules/game_bot/executor.lua b/modules/game_bot/executor.lua index b9e4916..8be9f83 100644 --- a/modules/game_bot/executor.lua +++ b/modules/game_bot/executor.lua @@ -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 diff --git a/modules/game_bot/functions/map.lua b/modules/game_bot/functions/map.lua index 9961234..198478a 100644 --- a/modules/game_bot/functions/map.lua +++ b/modules/game_bot/functions/map.lua @@ -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 \ No newline at end of file diff --git a/modules/game_outfit/outfit.lua b/modules/game_outfit/outfit.lua index 433e474..d7eea90 100644 --- a/modules/game_outfit/outfit.lua +++ b/modules/game_outfit/outfit.lua @@ -227,6 +227,7 @@ function accept() end end end + g_game.changeOutfit(outfit) destroy() end diff --git a/modules/game_shaders/shaders.lua b/modules/game_shaders/shaders.lua index e607dc0..c24a3c9 100644 --- a/modules/game_shaders/shaders.lua +++ b/modules/game_shaders/shaders.lua @@ -5,3 +5,5 @@ end function terminate() end + + diff --git a/modules/gamelib/protocollogin.lua b/modules/gamelib/protocollogin.lua index 85206b3..165ed54 100644 --- a/modules/gamelib/protocollogin.lua +++ b/modules/gamelib/protocollogin.lua @@ -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 diff --git a/otclient_dx.exe b/otclient_dx.exe index b9965d3..059dd57 100644 Binary files a/otclient_dx.exe and b/otclient_dx.exe differ diff --git a/otclient_gl.exe b/otclient_gl.exe index ede23ff..0f379be 100644 Binary files a/otclient_gl.exe and b/otclient_gl.exe differ diff --git a/otclient_linux b/otclient_linux index 264c122..c7f1f8d 100644 Binary files a/otclient_linux and b/otclient_linux differ diff --git a/otclientv8.apk b/otclientv8.apk index 761d79c..2590db2 100644 Binary files a/otclientv8.apk and b/otclientv8.apk differ diff --git a/pdb/pdb.7z b/pdb/pdb.7z index dc24d10..1d1fa3d 100644 Binary files a/pdb/pdb.7z and b/pdb/pdb.7z differ diff --git a/src/client/luafunctions_client.cpp b/src/client/luafunctions_client.cpp index 7069a10..86dcab9 100644 --- a/src/client/luafunctions_client.cpp +++ b/src/client/luafunctions_client.cpp @@ -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("isDead", &Creature::isDead); g_lua.bindClassMemberFunction("isRemoved", &Creature::isRemoved); g_lua.bindClassMemberFunction("canBeSeen", &Creature::canBeSeen); + g_lua.bindClassMemberFunction("canShoot", &Creature::canShoot); g_lua.bindClassMemberFunction("jump", &Creature::jump); g_lua.bindClassMemberFunction("getPrewalkingPosition", &Creature::getPrewalkingPosition); g_lua.bindClassMemberFunction("setInformationColor", &Creature::setInformationColor); @@ -772,6 +775,7 @@ void Client::registerLuaFunctions() g_lua.bindClassMemberFunction("isFullGround", &Tile::isFullGround); g_lua.bindClassMemberFunction("isFullyOpaque", &Tile::isFullyOpaque); g_lua.bindClassMemberFunction("isLookPossible", &Tile::isLookPossible); + g_lua.bindClassMemberFunction("isBlockingProjectile", &Tile::isBlockingProjectile); g_lua.bindClassMemberFunction("hasCreature", &Tile::hasCreature); g_lua.bindClassMemberFunction("hasBlockingCreature", &Tile::hasBlockingCreature); g_lua.bindClassMemberFunction("isEmpty", &Tile::isEmpty); @@ -789,6 +793,7 @@ void Client::registerLuaFunctions() g_lua.bindClassMemberFunction("getElevation", &Tile::getElevation); g_lua.bindClassMemberFunction("hasElevation", &Tile::hasElevation); g_lua.bindClassMemberFunction("isBlocking", &Tile::isBlocking); + g_lua.bindClassMemberFunction("canShoot", &Tile::canShoot); // for bot g_lua.bindClassMemberFunction("setText", &Tile::setText); g_lua.bindClassMemberFunction("getText", &Tile::getText); diff --git a/src/client/map.cpp b/src/client/map.cpp index 8a9cbfd..13c67de 100644 --- a/src/client/map.cpp +++ b/src/client/map.cpp @@ -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 Map::getSpectatorsByPattern(const Position& centerPos, const std::string& pattern, Otc::Direction direction) +{ + std::vector finalPattern(pattern.size(), false); + std::vector 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); +} \ No newline at end of file diff --git a/src/framework/luafunctions.cpp b/src/framework/luafunctions.cpp index 2aa8b01..c0079a4 100644 --- a/src/framework/luafunctions.cpp +++ b/src/framework/luafunctions.cpp @@ -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);