diff --git a/README.md b/README.md
index b759405..fa86406 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# ORTS, a real map datapack
[](https://gitter.im/orts/server?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
- * Client version 10.77
+ * Client version 10.98
* Map version 10.76
# Contributions
@@ -26,7 +26,7 @@ When [creating a pull request](https://github.com/orts/server/pulls) please mind
The map can be found in its own repository over at [https://github.com/orts/world/](https://github.com/orts/world/).
## Engine
-ORTS uses The Forgotten Server 1.2 as an engine. [The Forgotten Server 1.2](https://github.com/otland/forgottenserver) is being developed by [Mark Samman](https://github.com/marksamman). Nightly builds of The Fogotten Server can be found at [http://nightlies.otland.net](http://nightlies.otland.net).
+ORTS uses The Forgotten Server 1.3 as an engine. [The Forgotten Server 1.3](https://github.com/otland/forgottenserver) is being developed by [Mark Samman](https://github.com/marksamman). Nightly builds of The Fogotten Server can be found at [http://nightlies.otland.net](http://nightlies.otland.net).
## Client
In order to login [Otclient](https://github.com/edubart/otclient) by [edubart](https://github.com/edubart) can be used.
diff --git a/data/XML/commands.xml b/data/XML/commands.xml
deleted file mode 100644
index 9b1630a..0000000
--- a/data/XML/commands.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/data/XML/mounts.xml b/data/XML/mounts.xml
index 23926eb..ca94505 100644
--- a/data/XML/mounts.xml
+++ b/data/XML/mounts.xml
@@ -20,13 +20,13 @@
-
+
-
+
@@ -64,14 +64,42 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/data/XML/outfits.xml b/data/XML/outfits.xml
index ca0483a..3cab650 100644
--- a/data/XML/outfits.xml
+++ b/data/XML/outfits.xml
@@ -1,96 +1,116 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/data/actions/actions.xml b/data/actions/actions.xml
index 236d9df..8c8f4a5 100644
--- a/data/actions/actions.xml
+++ b/data/actions/actions.xml
@@ -402,84 +402,29 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -649,10 +594,6 @@
-
-
-
-
diff --git a/data/actions/lib/actions.lua b/data/actions/lib/actions.lua
index 3dcf629..1ebcf1f 100644
--- a/data/actions/lib/actions.lua
+++ b/data/actions/lib/actions.lua
@@ -445,7 +445,7 @@ function onUseCrowbar(player, item, fromPosition, target, toPosition, isHotkey)
-- In Service Of Yalahar Quest
if targetUniqueId == 3071 then
if player:getStorageValue(Storage.InServiceofYalahar.SewerPipe01) < 1 then
- doSetMonsterOutfit(player, 'skeleton', 3 * 1000)
+ player:setMonsterOutfit('skeleton', 3 * 1000)
fromPosition:sendMagicEffect(CONST_ME_ENERGYHIT)
player:setStorageValue(Storage.InServiceofYalahar.SewerPipe01, 1)
player:setStorageValue(Storage.InServiceofYalahar.Mission01, player:getStorageValue(Storage.InServiceofYalahar.Mission01) + 1) -- StorageValue for Questlog 'Mission 01: Something Rotten'
@@ -483,7 +483,7 @@ function onUseCrowbar(player, item, fromPosition, target, toPosition, isHotkey)
elseif targetUniqueId == 3074 then
if player:getStorageValue(Storage.InServiceofYalahar.SewerPipe04) < 1 then
- doSetMonsterOutfit(player, 'bog raider', 5 * 1000)
+ player:setMonsterOutfit('bog raider', 5 * 1000)
player:say('You have used the crowbar on a knot.', TALKTYPE_MONSTER_SAY)
player:setStorageValue(Storage.InServiceofYalahar.SewerPipe04, 1)
player:setStorageValue(Storage.InServiceofYalahar.Mission01, player:getStorageValue(Storage.InServiceofYalahar.Mission01) + 1) -- StorageValue for Questlog 'Mission 01: Something Rotten'
diff --git a/data/actions/scripts/other/costumebags.lua b/data/actions/scripts/other/costumebags.lua
index 7159908..8673ea7 100644
--- a/data/actions/scripts/other/costumebags.lua
+++ b/data/actions/scripts/other/costumebags.lua
@@ -10,7 +10,7 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey)
return true
end
- doSetMonsterOutfit(player, monsterNames[math.random(#monsterNames)], 5 * 60 * 60 * 1000)
+ player:setMonsterOutfit(monsterNames[math.random(#monsterNames)], 5 * 60 * 60 * 1000)
player:addAchievementProgress('Masquerader', 100)
item:getPosition():sendMagicEffect(CONST_ME_MAGIC_BLUE)
item:remove()
diff --git a/data/actions/scripts/other/decayto.lua b/data/actions/scripts/other/decayto.lua
index f456a0b..02b2699 100644
--- a/data/actions/scripts/other/decayto.lua
+++ b/data/actions/scripts/other/decayto.lua
@@ -1,17 +1,27 @@
+-- keep in sync with actions.xml
local decayItems = {
- [2041] = 2042, [2042] = 2041, [2044] = 2045, [2045] = 2044, [2047] = 2048,
- [2048] = 2047, [2050] = 2051, [2051] = 2050, [2052] = 2053, [2053] = 2052,
- [2054] = 2055, [2055] = 2054
+ [1479] = 1480, [1480] = 1479, [1634] = 1635, [1635] = 1634, [1636] = 1637,
+ [1637] = 1636, [1638] = 1639, [1639] = 1638, [1640] = 1641, [1641] = 1640,
+ [1786] = 1787, [1787] = 1786, [1788] = 1789, [1789] = 1788, [1790] = 1791,
+ [1791] = 1790, [1792] = 1793, [1793] = 1792, [1873] = 1874, [1874] = 1873,
+ [1875] = 1876, [1876] = 1875, [1945] = 1946, [1946] = 1945, [2037] = 2038,
+ [2038] = 2037, [2039] = 2040, [2040] = 2039, [2041] = 2042, [2042] = 2041,
+ [2044] = 2045, [2045] = 2044, [2047] = 2048, [2048] = 2047, [2050] = 2051,
+ [2051] = 2050, [2052] = 2053, [2053] = 2052, [2054] = 2055, [2055] = 2054,
+ [2058] = 2059, [2059] = 2058, [2060] = 2061, [2061] = 2060, [2064] = 2065,
+ [2065] = 2064, [2066] = 2067, [2067] = 2066, [2068] = 2069, [2069] = 2068,
+ [2096] = 2097, [2097] = 2096, [2162] = 2163, [2163] = 2162, [2578] = 2579,
+ [3947] = 3948, [3948] = 3947, [5812] = 5813, [5813] = 5812, [6489] = 6490,
+ [6490] = 6489, [7058] = 7059, [7059] = 7058, [8684] = 8685, [8685] = 8684,
+ [8686] = 8687, [8687] = 8686, [8688] = 8689, [8689] = 8688, [8690] = 8691,
+ [8691] = 8690, [9575] = 9576, [9576] = 9575, [9577] = 9578, [9578] = 9577,
+ [9579] = 9580, [9580] = 9579, [9581] = 9582, [9582] = 9581, [9747] = 9748,
+ [9748] = 9747, [9749] = 9750, [9750] = 9749, [19691] = 19692,
+ [19692] = 19691,
}
function onUse(player, item, fromPosition, target, toPosition, isHotkey)
- local decayItemId = decayItems[item.itemid]
- if not decayItemId then
- return false
- end
-
- item:transform(decayItemId)
+ item:transform(decayItems[item.itemid])
item:decay()
-
return true
end
diff --git a/data/actions/scripts/other/spellbook.lua b/data/actions/scripts/other/spellbook.lua
index 24d1705..14f3fa3 100644
--- a/data/actions/scripts/other/spellbook.lua
+++ b/data/actions/scripts/other/spellbook.lua
@@ -1,21 +1,19 @@
function onUse(player, item, fromPosition, target, toPosition, isHotkey)
- local count = getPlayerInstantSpellCount(player)
local text = ""
- local t = {}
- for i = 0, count - 1 do
- local spell = getPlayerInstantSpellInfo(player, i)
+ local spells = {}
+ for _, spell in ipairs(player:getInstantSpells()) do
if spell.level ~= 0 then
if spell.manapercent > 0 then
spell.mana = spell.manapercent .. "%"
end
- t[#t + 1] = spell
+ spells[#spells + 1] = spell
end
end
- table.sort(t, function(a, b) return a.level < b.level end)
+
+ table.sort(spells, function(a, b) return a.level < b.level end)
+
local prevLevel = -1
- local spell
- for i = 1, #t do
- spell = t[i]
+ for i, spell in ipairs(spells) do
local line = ""
if prevLevel ~= spell.level then
if i ~= 1 then
@@ -26,6 +24,7 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey)
end
text = text .. line .. " " .. spell.words .. " - " .. spell.name .. " : " .. spell.mana .. "\n"
end
- player:showTextDialog(item.itemid, text)
+
+ player:showTextDialog(item:getId(), text)
return true
end
diff --git a/data/events/scripts/player.lua b/data/events/scripts/player.lua
index d430c49..53bb9c2 100644
--- a/data/events/scripts/player.lua
+++ b/data/events/scripts/player.lua
@@ -130,6 +130,34 @@ function Player:onMoveCreature(creature, fromPosition, toPosition)
return true
end
+function Player:onReport(message, position, category)
+ if self:getAccountType() == ACCOUNT_TYPE_NORMAL then
+ return false
+ end
+
+ local name = self:getName()
+ local file = io.open("data/reports/" .. name .. " report.txt", "a")
+
+ if not file then
+ self:sendTextMessage(MESSAGE_EVENT_DEFAULT, "There was an error when processing your report, please contact a gamemaster.")
+ return true
+ end
+
+ io.output(file)
+ io.write("------------------------------\n")
+ io.write("Name: " .. name)
+ if category == BUG_CATEGORY_MAP then
+ io.write(" [Map position: " .. position.x .. ", " .. position.y .. ", " .. position.z .. "]")
+ end
+ local playerPosition = self:getPosition()
+ io.write(" [Player Position: " .. playerPosition.x .. ", " .. playerPosition.y .. ", " .. playerPosition.z .. "]\n")
+ io.write("Comment: " .. message .. "\n")
+ io.close(file)
+
+ self:sendTextMessage(MESSAGE_EVENT_DEFAULT, "Your report has been sent to " .. configManager.getString(configKeys.SERVER_NAME) .. ".")
+ return true
+end
+
function Player:onTurn(direction)
return true
end
diff --git a/data/lib/compat/compat.lua b/data/lib/compat/compat.lua
index b97eded..d4878e6 100644
--- a/data/lib/compat/compat.lua
+++ b/data/lib/compat/compat.lua
@@ -546,7 +546,7 @@ function doConvinceCreature(cid, target)
return false
end
- targetCreature:setMaster(creature)
+ creature:addSummon(targetCreature)
return true
end
@@ -757,7 +757,7 @@ function getTileInfo(position)
ret.nopz = ret.protection
ret.nologout = t:hasFlag(TILESTATE_NOLOGOUT)
ret.refresh = t:hasFlag(TILESTATE_REFRESH)
- ret.house = t:hasFlag(TILESTATE_HOUSE)
+ ret.house = t:getHouse() ~= nil
ret.bed = t:hasFlag(TILESTATE_BED)
ret.depot = t:hasFlag(TILESTATE_DEPOT)
@@ -875,6 +875,7 @@ function getThingfromPos(pos)
end
local thing
+ local stackpos = pos.stackpos or 0
if stackpos == STACKPOS_TOP_MOVEABLE_ITEM_OR_CREATURE then
thing = tile:getTopCreature()
if thing == nil then
@@ -888,7 +889,7 @@ function getThingfromPos(pos)
elseif stackpos == STACKPOS_TOP_CREATURE then
thing = tile:getTopCreature()
else
- thing = tile:getThing(pos.stackpos)
+ thing = tile:getThing(stackpos)
end
return pushThing(thing)
end
@@ -1005,3 +1006,61 @@ end
function Guild.removeMember(self, player)
return player:getGuild() == self and player:setGuild(nil)
end
+
+function getPlayerInstantSpellCount(cid) local p = Player(cid) return p ~= nil and p:getInstantSpellCount() end
+function getPlayerInstantSpellInfo(cid, spellId)
+ local player = Player(cid)
+ if not player then
+ return false
+ end
+
+ local spell = Spell(spellId)
+ if not spell or not player:canCast(spell) then
+ return false
+ end
+
+ return spell
+end
+
+function doSetItemOutfit(cid, item, time) local c = Creature(cid) return c ~= nil and c:setItemOutfit(item, time) end
+function doSetMonsterOutfit(cid, name, time) local c = Creature(cid) return c ~= nil and c:setMonsterOutfit(name, time) end
+function doSetCreatureOutfit(cid, outfit, time)
+ local creature = Creature(cid)
+ if not creature then
+ return false
+ end
+
+ local condition = Condition(CONDITION_OUTFIT)
+ condition:setOutfit({
+ lookTypeEx = itemType:getId()
+ })
+ condition:setTicks(time)
+ creature:addCondition(condition)
+
+ return true
+end
+
+function isInArray(array, value) return table.contains(array, value) end
+
+function doCreateItem(itemid, count, pos)
+ local tile = Tile(pos)
+ if not tile then
+ return false
+ end
+
+ local item = Game.createItem(itemid, count, pos)
+ if item then
+ return item:getUniqueId()
+ end
+ return false
+end
+
+function doCreateItemEx(itemid, count)
+ local item = Game.createItem(itemid, count)
+ if item then
+ return item:getUniqueId()
+ end
+ return false
+end
+
+function doMoveCreature(cid, direction) local c = Creature(cid) return c ~= nil and c:move(direction) end
diff --git a/data/lib/core/creature.lua b/data/lib/core/creature.lua
index 9e2b75e..fc9a7e3 100644
--- a/data/lib/core/creature.lua
+++ b/data/lib/core/creature.lua
@@ -1,6 +1,6 @@
function Creature.getClosestFreePosition(self, position, extended)
local usePosition = Position(position)
- local tiles = { usePosition:getTile() }
+ local tiles = { Tile(usePosition) }
local length = extended and 2 or 1
local tile
@@ -10,7 +10,7 @@ function Creature.getClosestFreePosition(self, position, extended)
usePosition.x = position.x + x
usePosition.y = position.y + y
- tile = usePosition:getTile()
+ tile = Tile(usePosition)
if tile then
tiles[#tiles + 1] = tile
end
@@ -20,7 +20,7 @@ function Creature.getClosestFreePosition(self, position, extended)
for i = 1, #tiles do
tile = tiles[i]
- if tile:getCreatureCount() == 0 and not tile:hasProperty(CONST_PROP_BLOCKINGANDNOTMOVEABLE) then
+ if tile:getCreatureCount() == 0 and not tile:hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID) then
return tile:getPosition()
end
end
@@ -54,3 +54,67 @@ end
function Creature.isTile(self)
return false
end
+
+function Creature:setMonsterOutfit(monster, time)
+ local monsterType = MonsterType(monster)
+ if not monsterType then
+ return false
+ end
+
+ if self:isPlayer() and not (getPlayerFlagValue(self, PlayerFlag_CanIllusionAll) or monsterType:isIllusionable()) then
+ return false
+ end
+
+ local condition = Condition(CONDITION_OUTFIT)
+ condition:setOutfit(monsterType:getOutfit())
+ condition:setTicks(time)
+ self:addCondition(condition)
+
+ return true
+end
+
+function Creature:setItemOutfit(item, time)
+ local itemType = ItemType(item)
+ if not itemType then
+ return false
+ end
+
+ local condition = Condition(CONDITION_OUTFIT)
+ condition:setOutfit({
+ lookTypeEx = itemType:getId()
+ })
+ condition:setTicks(time)
+ self:addCondition(condition)
+
+ return true
+end
+
+function Creature:addSummon(monster)
+ local summon = Monster(monster)
+ if not summon then
+ return false
+ end
+
+ summon:setTarget(nil)
+ summon:setFollowCreature(nil)
+ summon:setDropLoot(false)
+ summon:setSkillLoss(false)
+ summon:setMaster(self)
+
+ return true
+end
+
+function Creature:removeSummon(monster)
+ local summon = Monster(monster)
+ if not summon or summon:getMaster() ~= self then
+ return false
+ end
+
+ summon:setTarget(nil)
+ summon:setFollowCreature(nil)
+ summon:setDropLoot(true)
+ summon:setSkillLoss(true)
+ summon:setMaster(nil)
+
+ return true
+end
diff --git a/data/lib/core/tables.lua b/data/lib/core/tables.lua
index a2d225c..28da275 100644
--- a/data/lib/core/tables.lua
+++ b/data/lib/core/tables.lua
@@ -13,13 +13,12 @@ table.find = function (table, value)
return nil
end
-table.contains = function (txt, str)
- for i, v in pairs(str) do
- if(txt:find(v) and not txt:find('(%w+)' .. v) and not txt:find(v .. '(%w+)')) then
+table.contains = function(array, value)
+ for _, targetColumn in pairs(array) do
+ if targetColumn == value then
return true
end
end
-
return false
end
table.isStrIn = table.contains
diff --git a/data/migrations/18.lua b/data/migrations/18.lua
index d0ffd9c..d84c3b3 100644
--- a/data/migrations/18.lua
+++ b/data/migrations/18.lua
@@ -1,3 +1,5 @@
function onUpdateDatabase()
- return false
+ print("> Updating database to version 19 (authenticator token support)")
+ db.query("ALTER TABLE `accounts` ADD COLUMN `secret` CHAR(16) NULL AFTER `password`")
+ return true
end
diff --git a/data/migrations/19.lua b/data/migrations/19.lua
new file mode 100644
index 0000000..d0ffd9c
--- /dev/null
+++ b/data/migrations/19.lua
@@ -0,0 +1,3 @@
+function onUpdateDatabase()
+ return false
+end
diff --git a/data/movements/scripts/swimming.lua b/data/movements/scripts/swimming.lua
index 1c56ab5..8964f44 100644
--- a/data/movements/scripts/swimming.lua
+++ b/data/movements/scripts/swimming.lua
@@ -1,19 +1,21 @@
-local outfit = {lookType = 267, lookHead = 0, lookBody = 0, lookLegs = 0, lookFeet = 0, lookTypeEx = 0, lookAddons = 0}
+local condition = Condition(CONDITION_OUTFIT)
+condition:setOutfit({lookType = 267})
+condition:setTicks(-1)
function onStepIn(creature, item, position, fromPosition)
- local player = creature:getPlayer()
- if not player then
- return true
+ if not creature:isPlayer() then
+ return false
end
- doSetCreatureOutfit(player, outfit, -1)
+ creature:addCondition(condition)
+ return true
end
function onStepOut(creature, item, position, fromPosition)
- local player = creature:getPlayer()
- if not player then
- return true
+ if not creature:isPlayer() then
+ return false
end
- player:removeCondition(CONDITION_OUTFIT)
+ creature:removeCondition(CONDITION_OUTFIT)
+ return true
end
diff --git a/data/reports/.gitignore b/data/reports/.gitignore
new file mode 100644
index 0000000..5e7d273
--- /dev/null
+++ b/data/reports/.gitignore
@@ -0,0 +1,4 @@
+# Ignore everything in this directory
+*
+# Except this file
+!.gitignore
diff --git a/data/spells/scripts/house/edit_door_list.lua b/data/spells/scripts/house/edit_door_list.lua
new file mode 100644
index 0000000..c05879e
--- /dev/null
+++ b/data/spells/scripts/house/edit_door_list.lua
@@ -0,0 +1,16 @@
+function onCastSpell(player, variant)
+ local house = player:getTile():getHouse()
+ if not house then
+ return false
+ end
+
+ local doorId = house:getDoorIdByPosition(variant:getPosition())
+ if doorId ~= nil and house:canEditAccessList(doorId, player) then
+ player:setEditHouse(house, doorId)
+ player:sendHouseWindow(house, doorId)
+ else
+ player:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE)
+ player:getPosition():sendMagicEffect(CONST_ME_POFF)
+ end
+ return true
+end
diff --git a/data/spells/scripts/house/edit_guest_list.lua b/data/spells/scripts/house/edit_guest_list.lua
new file mode 100644
index 0000000..f5f904c
--- /dev/null
+++ b/data/spells/scripts/house/edit_guest_list.lua
@@ -0,0 +1,15 @@
+function onCastSpell(player, variant)
+ local house = player:getTile():getHouse()
+ if not house then
+ return false
+ end
+
+ if house:canEditAccessList(GUEST_LIST, player) then
+ player:setEditHouse(house, GUEST_LIST)
+ player:sendHouseWindow(house, GUEST_LIST)
+ else
+ player:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE)
+ player:getPosition():sendMagicEffect(CONST_ME_POFF)
+ end
+ return true
+end
diff --git a/data/spells/scripts/house/edit_subowner_list.lua b/data/spells/scripts/house/edit_subowner_list.lua
new file mode 100644
index 0000000..d955e84
--- /dev/null
+++ b/data/spells/scripts/house/edit_subowner_list.lua
@@ -0,0 +1,15 @@
+function onCastSpell(player, variant)
+ local house = player:getTile():getHouse()
+ if not house then
+ return false
+ end
+
+ if house:canEditAccessList(SUBOWNER_LIST, player) then
+ player:setEditHouse(house, SUBOWNER_LIST)
+ player:sendHouseWindow(house, SUBOWNER_LIST)
+ else
+ player:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE)
+ player:getPosition():sendMagicEffect(CONST_ME_POFF)
+ end
+ return true
+end
diff --git a/data/spells/scripts/house/kick.lua b/data/spells/scripts/house/kick.lua
new file mode 100644
index 0000000..ec15e60
--- /dev/null
+++ b/data/spells/scripts/house/kick.lua
@@ -0,0 +1,10 @@
+function onCastSpell(player, variant)
+ local targetPlayer = Player(variant:getString()) or player
+ local house = targetPlayer:getTile():getHouse()
+ if not house or not house:kickPlayer(player, targetPlayer) then
+ player:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE)
+ player:getPosition():sendMagicEffect(CONST_ME_POFF)
+ return false
+ end
+ return true
+end
diff --git a/data/spells/scripts/support/find_person.lua b/data/spells/scripts/support/find_person.lua
new file mode 100644
index 0000000..e2decbb
--- /dev/null
+++ b/data/spells/scripts/support/find_person.lua
@@ -0,0 +1,102 @@
+local LEVEL_LOWER = 1
+local LEVEL_SAME = 2
+local LEVEL_HIGHER = 3
+
+local DISTANCE_BESIDE = 1
+local DISTANCE_CLOSE = 2
+local DISTANCE_FAR = 3
+local DISTANCE_VERYFAR = 4
+
+local directions = {
+ [DIRECTION_NORTH] = "north",
+ [DIRECTION_SOUTH] = "south",
+ [DIRECTION_EAST] = "east",
+ [DIRECTION_WEST] = "west",
+ [DIRECTION_NORTHEAST] = "north-east",
+ [DIRECTION_NORTHWEST] = "north-west",
+ [DIRECTION_SOUTHEAST] = "south-east",
+ [DIRECTION_SOUTHWEST] = "south-west"
+}
+
+local messages = {
+ [DISTANCE_BESIDE] = {
+ [LEVEL_LOWER] = " is below you.",
+ [LEVEL_SAME] = " is standing next to you.",
+ [LEVEL_HIGHER] = " is above you."
+ },
+ [DISTANCE_CLOSE] = {
+ [LEVEL_LOWER] = " is on a lower level to the ",
+ [LEVEL_SAME] = " is to the ",
+ [LEVEL_HIGHER] = " is on a higher level to the "
+ },
+ [DISTANCE_FAR] = " is far to the ",
+ [DISTANCE_VERYFAR] = " is very far to the "
+}
+
+function onCastSpell(creature, variant)
+ local targetPlayer = Player(variant:getString())
+ if not targetPlayer then
+ return false
+ end
+
+ if targetPlayer:getGroup():getAccess() and not creature:getGroup():getAccess() then
+ creature:sendCancelMessage(RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE)
+ creature:getPosition():sendMagicEffect(CONST_ME_POFF)
+ return false
+ end
+
+ local playerPosition = creature:getPosition()
+ local targetPosition = targetPlayer:getPosition()
+ local offset = {
+ x = playerPosition.x - targetPosition.x,
+ y = playerPosition.y - targetPosition.y,
+ z = playerPosition.z - targetPosition.z
+ }
+
+ local level = LEVEL_SAME
+ if offset.z > 0 then
+ level = LEVEL_HIGHER
+ elseif offset.z < 0 then
+ level = LEVEL_LOWER
+ end
+
+ local direction
+ local distanceOutput = DISTANCE_VERYFAR
+ if math.abs(offset.x) < 4 and math.abs(offset.y) < 4 then
+ distanceOutput = DISTANCE_BESIDE
+ else
+ local distanceFormula = math.pow(offset.x, 2) + math.pow(offset.y, 2)
+ if distanceFormula < 1000 then
+ distanceOutput = DISTANCE_CLOSE
+ elseif distanceFormula < math.pow(274, 2) then
+ distanceOutput = DISTANCE_FAR
+ end
+
+ local distanceValue = offset.x ~= 0 and (offset.y / offset.x) or 10
+ local absValue = math.abs(distanceValue)
+ if absValue < 0.4142 then
+ direction = offset.x > 0 and DIRECTION_WEST or DIRECTION_EAST
+ elseif absValue < 2.4142 then
+ if distanceValue > 0 then
+ direction = offset.y > 0 and DIRECTION_NORTHWEST or DIRECTION_SOUTHEAST
+ else
+ direction = offset.x > 0 and DIRECTION_SOUTHWEST or DIRECTION_NORTHEAST
+ end
+ else
+ direction = offset.y > 0 and DIRECTION_NORTH or DIRECTION_SOUTH
+ end
+ end
+
+ local returnMessage = targetPlayer:getName()
+ if distanceOutput == DISTANCE_BESIDE then
+ returnMessage = returnMessage .. messages[distanceOutput][level]
+ elseif distanceOutput == DISTANCE_CLOSE then
+ returnMessage = returnMessage .. messages[distanceOutput][level] .. directions[direction] .. "."
+ else
+ returnMessage = returnMessage .. messages[distanceOutput] .. directions[direction] .. "."
+ end
+
+ creature:sendTextMessage(MESSAGE_INFO_DESCR, returnMessage)
+ playerPosition:sendMagicEffect(CONST_ME_MAGIC_BLUE)
+ return true
+end
diff --git a/data/spells/scripts/support/illusion.lua b/data/spells/scripts/support/illusion.lua
new file mode 100644
index 0000000..3969974
--- /dev/null
+++ b/data/spells/scripts/support/illusion.lua
@@ -0,0 +1,23 @@
+local condition = Condition(CONDITION_OUTFIT)
+condition:setTicks(180000)
+
+function onCastSpell(creature, variant)
+ local returnValue = RETURNVALUE_NOERROR
+ local monsterType = MonsterType(variant:getString())
+ if not monsterType then
+ returnValue = RETURNVALUE_CREATUREDOESNOTEXIST
+ elseif not getPlayerFlagValue(creature, PlayerFlag_CanIllusionAll) and not monsterType:isIllusionable() then
+ returnValue = RETURNVALUE_NOTPOSSIBLE
+ end
+
+ if returnValue ~= RETURNVALUE_NOERROR then
+ creature:sendCancelMessage(returnValue)
+ creature:getPosition():sendMagicEffect(CONST_ME_POFF)
+ return false
+ end
+
+ condition:setOutfit(monsterType:getOutfit())
+ creature:addCondition(condition)
+ creature:getPosition():sendMagicEffect(CONST_ME_MAGIC_RED)
+ return true
+end
diff --git a/data/spells/scripts/support/levitate.lua b/data/spells/scripts/support/levitate.lua
new file mode 100644
index 0000000..6cf401f
--- /dev/null
+++ b/data/spells/scripts/support/levitate.lua
@@ -0,0 +1,30 @@
+local function levitate(creature, parameter)
+ local fromPosition = creature:getPosition()
+
+ if parameter == "up" and fromPosition.z ~= 8 or parameter == "down" and fromPosition.z ~= 7 then
+ local toPosition = creature:getPosition()
+ toPosition:getNextPosition(creature:getDirection())
+
+ local tile = Tile(parameter == "up" and Position(fromPosition.x, fromPosition.y, fromPosition.z - 1) or toPosition)
+ if not tile or not tile:getGround() and not tile:hasFlag(parameter == "up" and TILESTATE_IMMOVABLEBLOCKSOLID or TILESTATE_BLOCKSOLID) then
+ tile = Tile(toPosition.x, toPosition.y, toPosition.z + (parameter == "up" and -1 or 1))
+
+ if tile and tile:getGround() and not tile:hasFlag(bit.bor(TILESTATE_IMMOVABLEBLOCKSOLID, TILESTATE_FLOORCHANGE)) then
+ return creature:move(tile, bit.bor(FLAG_IGNOREBLOCKITEM, FLAG_IGNOREBLOCKCREATURE))
+ end
+ end
+ end
+ return RETURNVALUE_NOTPOSSIBLE
+end
+
+function onCastSpell(creature, variant)
+ local returnValue = levitate(creature, variant:getString())
+ if returnValue ~= RETURNVALUE_NOERROR then
+ creature:sendCancelMessage(returnValue)
+ creature:getPosition():sendMagicEffect(CONST_ME_POFF)
+ return false
+ end
+
+ creature:getPosition():sendMagicEffect(CONST_ME_TELEPORT)
+ return true
+end
diff --git a/data/spells/scripts/support/summon_creature.lua b/data/spells/scripts/support/summon_creature.lua
new file mode 100644
index 0000000..53b3d79
--- /dev/null
+++ b/data/spells/scripts/support/summon_creature.lua
@@ -0,0 +1,46 @@
+function onCastSpell(creature, variant)
+ local monsterName = variant:getString()
+ local monsterType = MonsterType(monsterName)
+
+ if not monsterType then
+ creature:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE)
+ creature:getPosition():sendMagicEffect(CONST_ME_POFF)
+ return false
+ end
+
+ if not getPlayerFlagValue(creature, PlayerFlag_CanSummonAll) then
+ if not monsterType:isSummonable() then
+ creature:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE)
+ creature:getPosition():sendMagicEffect(CONST_ME_POFF)
+ return false
+ end
+
+ if #creature:getSummons() >= 2 then
+ creature:sendCancelMessage("You cannot summon more creatures.")
+ creature:getPosition():sendMagicEffect(CONST_ME_POFF)
+ return false
+ end
+ end
+
+ local manaCost = monsterType:getManaCost()
+ if creature:getMana() < manaCost and not getPlayerFlagValue(creature, PlayerFlag_HasInfiniteMana) then
+ creature:sendCancelMessage(RETURNVALUE_NOTENOUGHMANA)
+ creature:getPosition():sendMagicEffect(CONST_ME_POFF)
+ return false
+ end
+
+ local position = creature:getPosition()
+ local summon = Game.createMonster(monsterName, position, true)
+ if not summon then
+ creature:sendCancelMessage(RETURNVALUE_NOTENOUGHROOM)
+ position:sendMagicEffect(CONST_ME_POFF)
+ return false
+ end
+
+ creature:addMana(-manaCost)
+ creature:addManaSpent(manaCost)
+ creature:addSummon(summon)
+ position:sendMagicEffect(CONST_ME_MAGIC_BLUE)
+ summon:getPosition():sendMagicEffect(CONST_ME_TELEPORT)
+ return true
+end
diff --git a/data/spells/spells.xml b/data/spells/spells.xml
index 57d1fad..f37a9ad 100644
--- a/data/spells/spells.xml
+++ b/data/spells/spells.xml
@@ -33,7 +33,7 @@
-
+
@@ -125,7 +125,7 @@
-
+
@@ -239,7 +239,7 @@
-
+
@@ -357,7 +357,7 @@
-
+
@@ -687,10 +687,10 @@
-
-
-
-
+
+
+
+
diff --git a/data/talkactions/lib/talkactions.lua b/data/talkactions/lib/talkactions.lua
index 585eb19..c962dd4 100644
--- a/data/talkactions/lib/talkactions.lua
+++ b/data/talkactions/lib/talkactions.lua
@@ -1 +1,12 @@
--- Nothing --
+local logFormat = "[%s] %s %s"
+
+function logCommand(player, words, param)
+ local file = io.open("data/logs/" .. player:getName() .. " commands.log", "a")
+ if not file then
+ return
+ end
+
+ io.output(file)
+ io.write(logFormat:format(os.date("%d/%m/%Y %H:%M"), words, param):trim() .. "\n")
+ io.close(file)
+end
diff --git a/data/talkactions/scripts/force_raid.lua b/data/talkactions/scripts/force_raid.lua
new file mode 100644
index 0000000..ebdf162
--- /dev/null
+++ b/data/talkactions/scripts/force_raid.lua
@@ -0,0 +1,20 @@
+function onSay(player, words, param)
+ if not player:getGroup():getAccess() then
+ return true
+ end
+
+ if player:getAccountType() < ACCOUNT_TYPE_GAMEMASTER then
+ return false
+ end
+
+ logCommand(player, words, param)
+
+ local returnValue = Game.startRaid(param)
+ if returnValue ~= RETURNVALUE_NOERROR then
+ player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, Game.getReturnMessage(returnValue))
+ else
+ player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Raid started.")
+ end
+
+ return false
+end
diff --git a/data/talkactions/scripts/ipban.lua b/data/talkactions/scripts/ipban.lua
index 2489765..42a78fc 100644
--- a/data/talkactions/scripts/ipban.lua
+++ b/data/talkactions/scripts/ipban.lua
@@ -5,32 +5,35 @@ function onSay(player, words, param)
return true
end
- local resultId = db.storeQuery("SELECT `account_id`, `lastip` FROM `players` WHERE `name` = " .. db.escapeString(param))
+ local resultId = db.storeQuery("SELECT `name`, `lastip` FROM `players` WHERE `name` = " .. db.escapeString(param))
if resultId == false then
return false
end
- local ip = result.getNumber(resultId, "lastip")
+ local targetName = result.getString(resultId, "name")
+ local targetIp = result.getNumber(resultId, "lastip")
result.free(resultId)
local targetPlayer = Player(param)
if targetPlayer then
- ip = targetPlayer:getIp()
+ targetIp = targetPlayer:getIp()
targetPlayer:remove()
end
- if ip == 0 then
+ if targetIp == 0 then
return false
end
- resultId = db.storeQuery("SELECT 1 FROM `ip_bans` WHERE `ip` = " .. ip)
+ resultId = db.storeQuery("SELECT 1 FROM `ip_bans` WHERE `ip` = " .. targetIp)
if resultId ~= false then
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, targetName .. " is already IP banned.")
result.free(resultId)
return false
end
local timeNow = os.time()
db.query("INSERT INTO `ip_bans` (`ip`, `reason`, `banned_at`, `expires_at`, `banned_by`) VALUES (" ..
- ip .. ", '', " .. timeNow .. ", " .. timeNow + (ipBanDays * 86400) .. ", " .. player:getGuid() .. ")")
+ targetIp .. ", '', " .. timeNow .. ", " .. timeNow + (ipBanDays * 86400) .. ", " .. player:getGuid() .. ")")
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, targetName .. " has been IP banned.")
return false
end
diff --git a/data/talkactions/scripts/reload.lua b/data/talkactions/scripts/reload.lua
new file mode 100644
index 0000000..0aeed12
--- /dev/null
+++ b/data/talkactions/scripts/reload.lua
@@ -0,0 +1,76 @@
+local reloadTypes = {
+ ["all"] = { targetType = RELOAD_TYPE_ALL, name = "all" },
+
+ ["action"] = { targetType = RELOAD_TYPE_ACTIONS, name = "actions" },
+ ["actions"] = { targetType = RELOAD_TYPE_ACTIONS, name = "actions" },
+
+ ["chat"] = { targetType = RELOAD_TYPE_CHAT, name = "chatchannels" },
+ ["channel"] = { targetType = RELOAD_TYPE_CHAT, name = "chatchannels" },
+ ["chatchannels"] = { targetType = RELOAD_TYPE_CHAT, name = "chatchannels" },
+
+ ["config"] = { targetType = RELOAD_TYPE_CONFIG, name = "config" },
+ ["configuration"] = { targetType = RELOAD_TYPE_CONFIG, name = "config" },
+
+ ["creaturescript"] = { targetType = RELOAD_TYPE_CREATURESCRIPTS, name = "creature scripts" },
+ ["creaturescripts"] = { targetType = RELOAD_TYPE_CREATURESCRIPTS, name = "creature scripts" },
+
+ ["events"] = { targetType = RELOAD_TYPE_EVENTS, name = "events" },
+
+ ["global"] = { targetType = RELOAD_TYPE_GLOBAL, name = "global.lua" },
+
+ ["globalevent"] = { targetType = RELOAD_TYPE_GLOBALEVENTS, name = "globalevents" },
+ ["globalevents"] = { targetType = RELOAD_TYPE_GLOBALEVENTS, name = "globalevents" },
+
+ ["items"] = { targetType = RELOAD_TYPE_ITEMS, name = "items" },
+
+ ["monster"] = { targetType = RELOAD_TYPE_MONSTERS, name = "monsters" },
+ ["monsters"] = { targetType = RELOAD_TYPE_MONSTERS, name = "monsters" },
+
+ ["mount"] = { targetType = RELOAD_TYPE_MOUNTS, name = "mounts" },
+ ["mounts"] = { targetType = RELOAD_TYPE_MOUNTS, name = "mounts" },
+
+ ["move"] = { targetType = RELOAD_TYPE_MOVEMENTS, name = "movements" },
+ ["movement"] = { targetType = RELOAD_TYPE_MOVEMENTS, name = "movements" },
+ ["movements"] = { targetType = RELOAD_TYPE_MOVEMENTS, name = "movements" },
+
+ ["npc"] = { targetType = RELOAD_TYPE_NPCS, name = "npcs" },
+ ["npcs"] = { targetType = RELOAD_TYPE_NPCS, name = "npcs" },
+
+ ["quest"] = { targetType = RELOAD_TYPE_QUESTS, name = "quests" },
+ ["quests"] = { targetType = RELOAD_TYPE_QUESTS, name = "quests" },
+
+ ["raid"] = { targetType = RELOAD_TYPE_RAIDS, name = "raids" },
+ ["raids"] = { targetType = RELOAD_TYPE_RAIDS, name = "raids" },
+
+ ["spell"] = { targetType = RELOAD_TYPE_SPELLS, name = "spells" },
+ ["spells"] = { targetType = RELOAD_TYPE_SPELLS, name = "spells" },
+
+ ["talk"] = { targetType = RELOAD_TYPE_TALKACTIONS, name = "talk actions" },
+ ["talkaction"] = { targetType = RELOAD_TYPE_TALKACTIONS, name = "talk actions" },
+ ["talkactions"] = { targetType = RELOAD_TYPE_TALKACTIONS, name = "talk actions" },
+
+ ["weapon"] = { targetType = RELOAD_TYPE_WEAPONS, name = "weapons" },
+ ["weapons"] = { targetType = RELOAD_TYPE_WEAPONS, name = "weapons" }
+}
+
+function onSay(player, words, param)
+ if not player:getGroup():getAccess() then
+ return true
+ end
+
+ if player:getAccountType() < ACCOUNT_TYPE_GOD then
+ return false
+ end
+
+ logCommand(player, words, param)
+
+ local reloadType = reloadTypes[param and param:lower()]
+ if not reloadType then
+ player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reload type not found.")
+ return false
+ end
+
+ Game.reload(reloadType.targetType)
+ player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, string.format("Reloaded %s.", reloadType.name))
+ return false
+end
diff --git a/data/talkactions/scripts/sellhouse.lua b/data/talkactions/scripts/sellhouse.lua
new file mode 100644
index 0000000..525d71c
--- /dev/null
+++ b/data/talkactions/scripts/sellhouse.lua
@@ -0,0 +1,19 @@
+function onSay(player, words, param)
+ local tradePartner = Player(param)
+ if not tradePartner or tradePartner == player then
+ player:sendCancelMessage("Trade player not found.")
+ return false
+ end
+
+ local house = player:getTile():getHouse()
+ if not house then
+ player:sendCancelMessage("You must stand in your house to initiate the trade.")
+ return false
+ end
+
+ local returnValue = house:startTrade(player, tradePartner)
+ if returnValue ~= RETURNVALUE_NOERROR then
+ player:sendCancelMessage(returnValue)
+ end
+ return false
+end
diff --git a/data/talkactions/talkactions.xml b/data/talkactions/talkactions.xml
index 4c9c95f..f53ef1b 100644
--- a/data/talkactions/talkactions.xml
+++ b/data/talkactions/talkactions.xml
@@ -14,6 +14,7 @@
+
@@ -40,6 +41,7 @@
+
@@ -48,6 +50,7 @@
+