3155 lines
102 KiB
Lua
3155 lines
102 KiB
Lua
--[[
|
||
Illarion Server
|
||
|
||
This program is free software: you can redistribute it and/or modify it under
|
||
the terms of the GNU Affero General Public License as published by the Free
|
||
Software Foundation, either version 3 of the License, or (at your option) any
|
||
later version.
|
||
|
||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||
PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||
details.
|
||
|
||
You should have received a copy of the GNU Affero General Public License along
|
||
with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
]]
|
||
-- Generic Routine Collection
|
||
local M = {}
|
||
|
||
|
||
--- Select the proper text upon the language flag of the character
|
||
-- @param User The character who's language flag matters
|
||
-- @param textInDe german text
|
||
-- @param textInEn english text
|
||
-- @return Text german or english text version
|
||
|
||
function M.GetNLS(User, textInDe, textInEn)
|
||
return (User:getPlayerLanguage() == Player.german and textInDe or textInEn)
|
||
end
|
||
|
||
--- Send a inform to the player in the language the player speaks
|
||
-- @param User The character who receives the message
|
||
-- @param textInDe german text
|
||
-- @param textInEn english text
|
||
-- @param informPriority Player.[low|medium|high]Priority
|
||
|
||
-- Default: Medium priority
|
||
function M.InformNLS(User, textInDe, textInEn)
|
||
User:inform(textInDe, textInEn)
|
||
end
|
||
|
||
-- Temp: Low priority
|
||
function M.TempInformNLS(User, textInDe, textInEn)
|
||
User:inform(textInDe, textInEn, Player.lowPriority)
|
||
end
|
||
|
||
-- High: High priority
|
||
function M.HighInformNLS(User, textInDe, textInEn)
|
||
User:inform(textInDe, textInEn, Player.highPriority)
|
||
end
|
||
|
||
function M.TalkNLS(User, method, textInDe, textInEN)
|
||
User:talk(method, textInDe, textInEN)
|
||
end
|
||
|
||
---
|
||
-- Notify user about an error, write it to log and raise exception (stop the execution)
|
||
-- @param User The character who receives the message
|
||
-- @param message The error message as string. In English. Should end with "."
|
||
function M.informError(User, message)
|
||
User:inform("[ERROR] " .. message .. " Please inform a developer.")
|
||
error(message)
|
||
end
|
||
|
||
---
|
||
-- Show selection dialog with defined properties
|
||
-- @param User - the character to receive the dialog
|
||
-- @param title - string - the dialog title
|
||
-- @param description - string - the text below the title
|
||
-- @param buttons - a list of tables that define dialog options, with following fields:
|
||
-- icon - itemId of the icon to be displayed
|
||
-- text - text to be displayed
|
||
-- func - function to be called if this button is pressed, if nil than nothing is done
|
||
-- args - arguments to be passed to func
|
||
-- @param onclose - a table that defines what happens if dialog is closed, if nil than nothing is done
|
||
-- func - function to be called
|
||
-- args - arguments to be passed to func
|
||
-- @param closeOnMove - whether dialog closes when char moves, default is `true`
|
||
--
|
||
function M.selectionDialogWrapper(User, title, description, buttons, onclose, closeOnMove)
|
||
onclose = onclose or { func = function() end, args = {} }
|
||
if closeOnMove == nil then
|
||
closeOnMove = true
|
||
end
|
||
-- User:inform("#buttons = " .. #buttons)
|
||
buttons = buttons or {}
|
||
local callback = function(dialog)
|
||
if (not dialog:getSuccess()) then
|
||
onclose.func(table.unpack(onclose.args))
|
||
return
|
||
end
|
||
local index = dialog:getSelectedIndex() + 1
|
||
if index >= 1 and index <= #buttons then
|
||
if buttons[index].func ~= nil then
|
||
buttons[index].func(table.unpack(buttons[index].args))
|
||
end
|
||
else
|
||
User:inform("[ERROR] No valid function. Please inform a developer.")
|
||
end
|
||
end
|
||
local sd = SelectionDialog(title, description, callback)
|
||
for _, btn in ipairs(buttons) do
|
||
local icon = btn.icon or 0
|
||
local text = btn.text or ""
|
||
sd:addOption(icon, text)
|
||
end
|
||
if closeOnMove then
|
||
sd:setCloseOnMove()
|
||
end
|
||
User:requestSelectionDialog(sd)
|
||
end
|
||
|
||
function M.tableToString(user, theTable)
|
||
local theString = ""
|
||
|
||
for _, tableEntry in ipairs(theTable) do
|
||
|
||
if theString ~= "" then
|
||
theString = theString..","
|
||
end
|
||
|
||
theString = theString..tableEntry.key..","..tableEntry.value
|
||
end
|
||
|
||
return theString
|
||
end
|
||
|
||
|
||
function M.convertTableToItemData(user, theItem, theTable, dataKey, maxData)
|
||
|
||
local theString = M.tableToString(user, theTable)
|
||
|
||
for i = 1, maxData do
|
||
theItem:setData(dataKey..i, string.sub(theString, 1+(250*(i-1)), 250*i))
|
||
end
|
||
|
||
world:changeItem(theItem)
|
||
|
||
end
|
||
|
||
function M.convertItemDataToTable(user, theItem, dataKey, maxData, data)
|
||
|
||
local theString = ""
|
||
|
||
if theItem then
|
||
for i = 1, maxData do
|
||
theString = theString..theItem:getData(dataKey..i)
|
||
end
|
||
elseif data then
|
||
for i = 1, maxData do
|
||
theString = theString..data[dataKey..i]
|
||
end
|
||
end
|
||
|
||
local retTable = {}
|
||
local parts = {}
|
||
|
||
for part in string.gmatch(theString, "([^,]+)") do
|
||
table.insert(parts, part)
|
||
end
|
||
|
||
for i = 1, #parts, 2 do
|
||
|
||
local foundKey = parts[i]
|
||
local foundValue = parts[i+1]
|
||
|
||
if foundKey and foundValue then
|
||
if tonumber(foundValue) then
|
||
foundValue = tonumber(foundValue)
|
||
end
|
||
table.insert(retTable, {key = foundKey, value = foundValue })
|
||
end
|
||
end
|
||
|
||
return retTable
|
||
end
|
||
|
||
|
||
--- Get a text based on the gender of the character.
|
||
-- @param User The character used to choose the text
|
||
-- @param textMale The text returned in case the character is male
|
||
-- @param textFemale The text returned in case the character is female
|
||
-- @return textMale or textFemale, based on the gender of User
|
||
function M.GetGenderText(User, textMale, textFemale)
|
||
return (User:increaseAttrib("sex", 0) == 1 and textFemale or textMale)
|
||
end
|
||
|
||
--[[ Get the frst letter in a text uppercase.
|
||
@param text: "the text"
|
||
@return text: "The text" ]]--
|
||
function M.firstToUpper(text)
|
||
if M.IsNilOrEmpty(text) then
|
||
return text
|
||
end
|
||
text = tostring(text)
|
||
return (text:gsub("^%l", string.upper))
|
||
end
|
||
|
||
function M.checkIfBookInHand(user, alchemy, cooking)
|
||
|
||
local bookList = {1061, 105, 106, 129, 2609, 2610, 114, 115, 2608, 2615, 107, 108, 111, 112, 2605, 2617, 109, 110, 117, 128, 130, 2604, 131, 2602, 2620, 116, 2621, 2607, 127, 2598, 2606, 2616, 2619}
|
||
|
||
local leftTool = user:getItemAt(Character.left_tool)
|
||
local rightTool = user:getItemAt(Character.right_tool)
|
||
|
||
local potentialBooks = {leftTool, rightTool}
|
||
|
||
for _, book in pairs(potentialBooks) do
|
||
|
||
local isBook = M.isInList(book.id, bookList)
|
||
local isCookBook = not M.IsNilOrEmpty(book:getData("cookBook"))
|
||
local isAlchemyBook = not M.IsNilOrEmpty(book:getData("alchemyBook"))
|
||
local availableSlots = M.IsNilOrEmpty(book:getData("recipe100"))
|
||
local isEmpty = M.IsNilOrEmpty(book:getData("sheet1")) and M.IsNilOrEmpty(book:getData("page1")) and M.IsNilOrEmpty(book:getData("magicBook")) and M.IsNilOrEmpty(book:getData("book")) and M.IsNilOrEmpty(book:getData("alchemyBook"))
|
||
|
||
if isBook and ((isAlchemyBook and alchemy) or (isCookBook and cooking) or isEmpty) and availableSlots then -- it is a book
|
||
|
||
if book.number > 1 then
|
||
user:inform("Du kannst nur in einem Buch gleichzeitig schreiben.", "You can only write in one book at a time.")
|
||
return false
|
||
end
|
||
return book
|
||
end
|
||
|
||
end
|
||
|
||
user:inform("Du brauchst ein Buch in der Hand, wenn du es beschriften oder darin schreiben m<>chtest.","You need a book in your hand if you want to label or write in one.")
|
||
|
||
return false
|
||
end
|
||
|
||
--[[ Replace Umlaute by possible character.
|
||
@param text: "the text with an <20>."
|
||
@return text: "the text with an ae."]]--
|
||
function M.replaceUmlaut(text)
|
||
if M.IsNilOrEmpty(text) then
|
||
return text
|
||
end
|
||
text = tostring(text)
|
||
text = text:gsub("<EFBFBD>","ae")
|
||
text = text:gsub("<EFBFBD>","oe")
|
||
text = text:gsub("<EFBFBD>","ue")
|
||
text = text:gsub("<EFBFBD>","Ae")
|
||
text = text:gsub("<EFBFBD>","Oe")
|
||
text = text:gsub("<EFBFBD>","Ue")
|
||
text = text:gsub("<EFBFBD>","ss")
|
||
return text
|
||
end
|
||
|
||
--- This function uses the random value generator to produce a random chance.
|
||
-- This function returns true in a specified amount of cases. The optional
|
||
-- Base parameter is used to set the value of 100%. By default this value is 1.
|
||
-- For example in case the value is set to 80 and the base is set to 100 the
|
||
-- function will return true in 80% of the calls. The same result would be
|
||
-- produced in case the value is set to 8 and the base is set to 10 or in case
|
||
-- the Value is set to 0.8 and the base is not set, or set to 1.
|
||
-- @param Value The chance value
|
||
-- @param Base The value of 100% (optional)
|
||
-- @return true in (Value/Base*100)% of the calls
|
||
function M.Chance(Value, Base)
|
||
if (Base ~= nil and Value >= Base) or (Base == nil and Value >= 1) then
|
||
return true
|
||
end
|
||
if (Value <= 0) then
|
||
return false
|
||
end
|
||
|
||
if (Base ~= nil) and (Base ~= 1) then
|
||
Value = Value / Base
|
||
end
|
||
|
||
return (math.random() <= Value)
|
||
end
|
||
|
||
|
||
---This function returns a value based on a binomial distribution.
|
||
-- Consider this function rolling dices against a minimum value
|
||
-- Mean value = probability * rolls
|
||
function M.BinomialByProbability(probability, rolls)
|
||
local result = 0
|
||
for i=1,rolls do
|
||
if math.random()<probability then
|
||
result = result + 1
|
||
end
|
||
end
|
||
return result
|
||
end
|
||
|
||
function M.BinomialByMean(mean, rolls)
|
||
local probability = mean / rolls
|
||
return M.BinomialByProbability(probability, rolls)
|
||
end
|
||
|
||
--- Determine if a character is looking at a position or not
|
||
-- @param User The character whos looking direction matters
|
||
-- @param Location The position the character should look at
|
||
-- @return true in case the character looks at the position, else false
|
||
function M.IsLookingAt(User, Location)
|
||
if not User or not Location then
|
||
return false
|
||
end
|
||
|
||
if User.pos == Location then
|
||
return true
|
||
end
|
||
|
||
local richtung = User:getFaceTo()
|
||
|
||
return (((richtung == Character.north) and (Location.y < User.pos.y)) or
|
||
((richtung == Character.northeast) and
|
||
((Location.y < User.pos.y) and (Location.x > User.pos.x))) or
|
||
((richtung == Character.east) and (Location.x > User.pos.x)) or
|
||
((richtung == Character.southeast) and
|
||
((Location.y > User.pos.y) and (Location.x > User.pos.x))) or
|
||
((richtung == Character.south) and (Location.y > User.pos.y)) or
|
||
((richtung == Character.southwest) and
|
||
((Location.y > User.pos.y) and (Location.x < User.pos.x))) or
|
||
((richtung == Character.west) and (Location.x < User.pos.x)) or
|
||
((richtung == Character.northwest) and
|
||
((Location.y < User.pos.y) and (Location.x < User.pos.x))))
|
||
end
|
||
|
||
--- Check if a character sequence (string) is nil or empty.
|
||
-- @param the variable to check
|
||
-- @return true in case the text is nil or equal to a empty string
|
||
function M.IsNilOrEmpty(text)
|
||
return ((text == nil) or (text == ""))
|
||
end
|
||
|
||
--- Determine the direction from one position to another one
|
||
-- @param StartPosition The start position. The place you get the direction you have to look towards to see the target location
|
||
-- @param TargetPosition The target position
|
||
-- @return value for the direction, possible values are: Character.dir_north, Character.dir_northeast, Character.dir_east, Character.dir_southeast, Character.dir_south, Character.dir_southwest, Character.dir_west, Character.dir_northwest
|
||
function M.GetDirection(StartPosition, TargetPosition)
|
||
if (StartPosition.z < TargetPosition.z) then
|
||
return Character.dir_up
|
||
elseif (StartPosition.z > TargetPosition.z) then
|
||
return Character.dir_down
|
||
end
|
||
|
||
local phi = M.GetPhi(StartPosition, TargetPosition)
|
||
|
||
if (phi < math.pi / 8) then
|
||
return Character.dir_east
|
||
elseif (phi < 3 * math.pi / 8) then
|
||
return Character.dir_southeast
|
||
elseif (phi < 5 * math.pi / 8) then
|
||
return Character.dir_south
|
||
elseif (phi < 7 * math.pi / 8) then
|
||
return Character.dir_southwest
|
||
elseif (phi < 9 * math.pi / 8) then
|
||
return Character.dir_west
|
||
elseif (phi < 11 * math.pi / 8) then
|
||
return Character.dir_northwest
|
||
elseif (phi < 13 * math.pi / 8) then
|
||
return Character.dir_north
|
||
elseif (phi < 15 * math.pi / 8) then
|
||
return Character.dir_northeast
|
||
else
|
||
return Character.dir_east
|
||
end
|
||
end
|
||
|
||
--- Turn a character towards a location
|
||
-- @param User The character who shall be turned
|
||
-- @param Location The position the character shall turn to
|
||
function M.TurnTo(User, Location)
|
||
local oldDir = User:getFaceTo()
|
||
local newDir = M.GetDirection(User.pos, Location)
|
||
|
||
if (newDir ~= oldDir) then
|
||
User:turn(newDir)
|
||
end
|
||
end
|
||
|
||
function M.GetDirectionVector(dir)
|
||
if dir == Character.dir_up then
|
||
return 0, 0, 1
|
||
elseif dir == Character.dir_down then
|
||
return 0, 0, -1
|
||
elseif dir == Character.north then
|
||
return 0, -1, 0
|
||
elseif dir == Character.northeast then
|
||
return 1, -1, 0
|
||
elseif dir == Character.east then
|
||
return 1, 0, 0
|
||
elseif dir == Character.southeast then
|
||
return 1, 1, 0
|
||
elseif dir == Character.south then
|
||
return 0, 1, 0
|
||
elseif dir == Character.southwest then
|
||
return -1, 1, 0
|
||
elseif dir == Character.west then
|
||
return -1, 0, 0
|
||
elseif dir == Character.northwest then
|
||
return -1, -1, 0
|
||
else
|
||
error("Unexpected value for parameter \"dir\"")
|
||
end
|
||
end
|
||
|
||
--- Get the position right in front of a character in looking direction
|
||
-- @param User The character the front position is wanted
|
||
-- @return The position in front of the character
|
||
function M.GetFrontPosition(User, distance, dir)
|
||
local direct = dir or User:getFaceTo()
|
||
local d = distance or 1
|
||
local vX, vY = M.GetDirectionVector(direct)
|
||
|
||
return position(User.pos.x + vX * d, User.pos.y + vY * d, User.pos.z)
|
||
end
|
||
|
||
--- Get the item that is in front of the character in case there is one
|
||
-- @param User The character whos front area is searched
|
||
-- @return The item that was found or nil
|
||
function M.GetFrontItem(User, dir)
|
||
local Posi = M.GetFrontPosition(User, 1, dir)
|
||
|
||
if world:isItemOnField(Posi) then
|
||
return world:getItemOnField(Posi)
|
||
else
|
||
return nil
|
||
end
|
||
end
|
||
|
||
--- Get the ID of the item that is in front of the character in case there is one
|
||
-- @param User The character whos front area is searched
|
||
-- @return The ID of the item in front of the character or 0 in case there is none
|
||
function M.GetFrontItemID(User, dir)
|
||
local theItem = M.GetFrontItem(User, dir)
|
||
|
||
if not theItem then
|
||
return 0
|
||
else
|
||
return theItem.id
|
||
end
|
||
end
|
||
|
||
--- Get character who is in front of the character in case there is one
|
||
-- @param User The character whos front area is searched
|
||
-- @return The character in front of the parameter character or nil
|
||
function M.GetFrontCharacter(User)
|
||
local Posi = M.GetFrontPosition(User)
|
||
|
||
if world:isCharacterOnField(Posi) then
|
||
return world:getCharacterOnField(Posi)
|
||
else
|
||
return nil
|
||
end
|
||
end
|
||
|
||
--- Get the position right behind a character
|
||
-- @param User The character the back position is wanted
|
||
-- @param distance How far behind the char? Defaults to 1.
|
||
-- @return The position behind the character
|
||
function M.GetBehindPosition(User, distance)
|
||
|
||
local d = distance or 1
|
||
local direct = User:getFaceTo()
|
||
|
||
if (direct == Character.north) then
|
||
return position(User.pos.x, User.pos.y + d, User.pos.z)
|
||
elseif (direct == Character.northeast) then
|
||
return position(User.pos.x - d, User.pos.y + d, User.pos.z)
|
||
elseif (direct == Character.east) then
|
||
return position(User.pos.x - d, User.pos.y, User.pos.z)
|
||
elseif (direct == Character.southeast) then
|
||
return position(User.pos.x + d, User.pos.y - d, User.pos.z)
|
||
elseif (direct == Character.south) then
|
||
return position(User.pos.x, User.pos.y - d, User.pos.z)
|
||
elseif (direct == Character.southwest) then
|
||
return position(User.pos.x + d, User.pos.y - d, User.pos.z)
|
||
elseif (direct == Character.west) then
|
||
return position(User.pos.x + d, User.pos.y, User.pos.z)
|
||
elseif (direct == Character.northwest) then
|
||
return position(User.pos.x + d, User.pos.y + d, User.pos.z)
|
||
end
|
||
|
||
debug("Invalid user direction")
|
||
end
|
||
|
||
--- Get the item that is behind the character in case there is one
|
||
-- @param User The character whos back area is searched
|
||
-- @return The item that was found or nil
|
||
function M.GetBehindItem(User)
|
||
local Posi = M.GetBehindPosition(User)
|
||
|
||
if world:isItemOnField(Posi) then
|
||
return world:getItemOnField(Posi)
|
||
else
|
||
return nil
|
||
end
|
||
end
|
||
|
||
--- Get the ID of the item that is behind the character in case there is one
|
||
-- @param User The character whos back area is searched
|
||
-- @return The ID of the item in front of the character or 0 in case there is none
|
||
function M.GetBehindItemID(User)
|
||
local theItem = M.GetBehindItem(User)
|
||
|
||
if not theItem then
|
||
return 0
|
||
else
|
||
return theItem.id
|
||
end
|
||
end
|
||
|
||
--- Get character who is behind the character in case there is one
|
||
-- @param User The character whos front area is searched
|
||
-- @return The character in front of the parameter character or nil
|
||
function M.GetBehindCharacter(User)
|
||
local Posi = M.GetBehindPosition(User)
|
||
|
||
if world:isCharacterOnField(Posi) then
|
||
return world:getCharacterOnField(Posi)
|
||
else
|
||
return nil
|
||
end
|
||
end
|
||
|
||
--- Check if a character is within a area determined with 2 positions. The
|
||
-- rectangle of the area is determined by the grid of the tiles
|
||
-- @param User The character who is checked for being within the area
|
||
-- @param Pos1 The first position to determine the area
|
||
-- @param Pos2 The second position to determine the area
|
||
-- @return True if the character is in the area, false if not
|
||
function M.GetInArea(User, Pos1, Pos2)
|
||
if (User.pos.x <= math.min(Pos1.x, Pos2.x) or
|
||
User.pos.x >= math.max(Pos1.x, Pos2.x)) then
|
||
return false
|
||
elseif (User.pos.y <= math.min(Pos1.y, Pos2.y) or
|
||
User.pos.y >= math.max(Pos1.y, Pos2.y)) then
|
||
return false
|
||
elseif (User.pos.z <= math.min(Pos1.z, Pos2.z) or
|
||
User.pos.z >= math.max(Pos1.z, Pos2.z)) then
|
||
return false
|
||
end
|
||
return true
|
||
end
|
||
|
||
--- Get all free positions at and around a center location. The search is performed with a square mask. So the search
|
||
-- area is actually a rectangle with the with a side length of searchRadius + 1
|
||
-- @param centerPosition the center of the search
|
||
-- @param searchRadius the radius of the search
|
||
-- @param allowPassableItems this has to be set to true in case tiles that contain items that are passable are valid.
|
||
-- in case this is set to false. tiles that do contain items are in general invalid
|
||
-- (optional, defaults to false)
|
||
-- @param shuffle randomize the order the tiles are returned in (optional, defaults to false)
|
||
-- @return a function that can be used as iterator that gives all the free locations one by one
|
||
function M.GetFreePositions(centerPosition, searchRadius, allowPassableItems, shuffle)
|
||
local coords = {}
|
||
for x = -searchRadius, searchRadius do
|
||
for y = -searchRadius, searchRadius do
|
||
table.insert(coords, {centerPosition.x + x, centerPosition.y + y})
|
||
end
|
||
end
|
||
if shuffle then
|
||
coords = M.Shuffle(coords)
|
||
end
|
||
|
||
local currentIndex = 1
|
||
local lastIndex = #coords
|
||
return function()
|
||
while currentIndex <= lastIndex do
|
||
local xyCoords = coords[currentIndex]
|
||
local targetPos = position(xyCoords[1], xyCoords[2], centerPosition.z)
|
||
|
||
currentIndex = currentIndex + 1
|
||
|
||
local field = world:getField(targetPos)
|
||
if field ~= nil then
|
||
if field:isPassable() and not world:isCharacterOnField(targetPos) then
|
||
if allowPassableItems or not world:isItemOnField(targetPos) then
|
||
return targetPos
|
||
end
|
||
end
|
||
end
|
||
end
|
||
return nil
|
||
end
|
||
end
|
||
|
||
-- Get one random free location
|
||
function M.getFreePos(CenterPos, Rad)
|
||
local pos = M.GetFreePositions(CenterPos, Rad, false, true)()
|
||
if pos == nil then
|
||
return CenterPos
|
||
end
|
||
return pos
|
||
end
|
||
|
||
--Counts passable fields around a center location. The search is performed with a square mask. So the search
|
||
-- area is actually a rectangle with the with a side length of searchRadius + 2
|
||
-- @param centerPosition the center of the search
|
||
-- @param searchRadius the radius of the search
|
||
-- @return number of passable fields
|
||
function M.getNumberOfPassableFieldsInArea(centerPos, searchRadius)
|
||
local countPos = 0
|
||
for x = -searchRadius, searchRadius do
|
||
for y = -searchRadius, searchRadius do
|
||
local pos = position(centerPos.x + x, centerPos.y + y, centerPos.z)
|
||
local field = world:getField(pos)
|
||
if field ~= nil then
|
||
if field:isPassable() then
|
||
countPos = countPos + 1
|
||
end
|
||
end
|
||
end
|
||
end
|
||
return countPos
|
||
end
|
||
|
||
--Counts passable fields around a center location. The search is performed with a given relativ mask.
|
||
-- @param centerPos the center of the search
|
||
-- @param relativePoss table of reltive positions
|
||
-- @return number of passable fields
|
||
function M.getNumberOfPassableFieldsFromList(centerPos, relativePos)
|
||
local countPos = 0
|
||
if relativePos == nil or #relativePos == 0 then
|
||
return 0
|
||
else
|
||
for i=1, #relativePos do
|
||
local pos = position(centerPos.x + relativePos[i][1], centerPos.y + relativePos[i][2], centerPos.z + relativePos[i][3])
|
||
local field = world:getField(pos)
|
||
if field ~= nil then
|
||
if field:isPassable() then
|
||
countPos = countPos + 1
|
||
end
|
||
end
|
||
end
|
||
end
|
||
return countPos
|
||
end
|
||
|
||
--- Check if a ItemStruct is valid for a special character
|
||
-- @param User Character who should own the item
|
||
-- @param Item Item that shall be checked if its still valid
|
||
-- @param altIDs alternative ItemIDs the item could have changed to and is still valid
|
||
-- @return True if everything is fine, else false
|
||
function M.CheckItem(User, Item, altIDs)
|
||
local ItemCheck
|
||
if Item:getType() == scriptItem.field then
|
||
if world:isItemOnField(Item.pos) then
|
||
ItemCheck = world:getItemOnField(Item.pos)
|
||
end
|
||
elseif Item:getType() == scriptItem.container then
|
||
local found
|
||
found, ItemCheck = User:getBackPack():viewItemNr(Item.itempos)
|
||
if not found then
|
||
ItemCheck = nil
|
||
end
|
||
else
|
||
ItemCheck = User:getItemAt(Item.itempos)
|
||
end
|
||
if ItemCheck then
|
||
if Item.id == ItemCheck.id then
|
||
return true
|
||
elseif altIDs ~= nil then
|
||
for _, altID in pairs(altIDs) do
|
||
if ItemCheck.id == altID then
|
||
return true
|
||
end
|
||
end
|
||
end
|
||
end
|
||
return false
|
||
end
|
||
|
||
--- Checks if the Character has a minimal default amount of food points. If not
|
||
-- it prints out a inform message.
|
||
-- @param User The character whos foodpoints are checked
|
||
-- @return True in case the character has enougth food points, false if not
|
||
function M.FitForWork(User)
|
||
return M.FitForHardWork(User, 1000)
|
||
end
|
||
|
||
--- Checks if the Character has a minimal amount of food points. If not it
|
||
-- prints out a inform message-
|
||
-- @param User The character whos foodpoints are checked
|
||
-- @param required Amount of foodpoints that are required
|
||
-- @return True in case the character has enougth food points, false if not
|
||
function M.FitForHardWork(User, required)
|
||
|
||
if (User:increaseAttrib("foodlevel", 0) < required) or (User:increaseAttrib("foodlevel", 0) < 1000) then
|
||
M.InformNLS(User,
|
||
"Du bist daf<61>r zu ersch<63>pft. Du solltest etwas essen.",
|
||
"You are too exhausted for that. You should eat something.")
|
||
return false
|
||
end
|
||
|
||
return true
|
||
|
||
end
|
||
|
||
--- Decrease the foodpoints of a character and show a warning if the foodpoints
|
||
-- are running low.
|
||
-- @param User The character that loses foodpoints
|
||
-- @param units The amount of foodpoints that are removed
|
||
function M.GetHungry(User, units)
|
||
|
||
local food = User:increaseAttrib("foodlevel", -units)
|
||
|
||
if (food <= 6000) and ((food + units) > 6000) then
|
||
M.InformNLS(User,
|
||
"Die Arbeit macht dich langsam m<>de und hungrig.",
|
||
"You are getting tired and hungry from your work.")
|
||
end
|
||
|
||
end
|
||
|
||
--[[function set for item quality and durability
|
||
item.quality contains quality and durability
|
||
637 means: quality=6, durability=37
|
||
default value is 333
|
||
]]--
|
||
M.ITEM_MAX_QUALITY = 9
|
||
M.ITEM_DEFAULT_QUALITY = 3
|
||
M.ITEM_MAX_DURABILITY = 99
|
||
M.ITEM_DEFAULT_DURABILITY = 33
|
||
|
||
-- Get durability of an item
|
||
-- @param item the item to be checked
|
||
-- @return durability (1-99)
|
||
function M.getItemDurability(item)
|
||
local durability = math.fmod(item.quality, 100)
|
||
return durability
|
||
end
|
||
|
||
-- Check if an item is broken (durability 0)
|
||
-- @param item the item to be checked
|
||
-- @return true or false
|
||
function M.isBroken(item)
|
||
local durability = math.fmod(item.quality, 100)
|
||
if durability == 0 then
|
||
return true
|
||
else
|
||
return false
|
||
end
|
||
end
|
||
|
||
-- Set durability of an item
|
||
-- @param item the item to be changed
|
||
-- @param newDurability (1-99)
|
||
function M.setItemDurability(item, newDurability)
|
||
local quality = M.getItemQuality(item)
|
||
item.quality = M.calculateItemQualityDurability(quality, newDurability)
|
||
world:changeItem(item)
|
||
end
|
||
|
||
-- Get quality of an item
|
||
-- @param Item the item to be checked
|
||
-- @return quality (1-9)
|
||
function M.getItemQuality(item)
|
||
local durability = math.fmod(item.quality, 100)
|
||
local quality = (item.quality - durability) / 100
|
||
return quality
|
||
end
|
||
|
||
-- Set quality of an item
|
||
-- @param item the item to be changed
|
||
-- @param newQuality (1-9)
|
||
function M.setItemQuality(item, newQuality)
|
||
local durability = M.getItemDurability(item)
|
||
item.quality = M.calculateItemQualityDurability(newQuality, durability)
|
||
world:changeItem(item)
|
||
end
|
||
|
||
-- Set quality and durability of an item
|
||
-- @param item the item to be checked
|
||
-- @param newQuality (1-9)
|
||
-- @param newDurability (1-99)
|
||
function M.setItemQualityDurability(item, newQuality, newDurability)
|
||
item.quality = M.calculateItemQualityDurability(newQuality, newDurability)
|
||
world:changeItem(item)
|
||
end
|
||
|
||
-- calculates and verify the item state for item.quality
|
||
-- @param quality (1-9)
|
||
-- @param durability (1-99)
|
||
-- @return value for item.quality
|
||
-- default value is 333
|
||
-- calculateItemQualityDurability (nil, nil) = 333
|
||
function M.calculateItemQualityDurability (quality, durability)
|
||
local qualityNumber
|
||
if M.IsNilOrEmpty(quality) then
|
||
qualityNumber = M.ITEM_DEFAULT_QUALITY
|
||
else
|
||
qualityNumber= tonumber(quality)
|
||
end
|
||
if qualityNumber < 1 or qualityNumber > M.ITEM_MAX_QUALITY then
|
||
qualityNumber = M.ITEM_DEFAULT_QUALITY
|
||
end
|
||
|
||
local durabilityNumber
|
||
if M.IsNilOrEmpty(durability) then
|
||
durabilityNumber = M.ITEM_DEFAULT_DURABILITY
|
||
else
|
||
durabilityNumber= tonumber(durability)
|
||
end
|
||
if durabilityNumber < 0 or durabilityNumber > M.ITEM_MAX_DURABILITY then --We allow 0 now for broken items
|
||
durabilityNumber = M.ITEM_DEFAULT_DURABILITY
|
||
end
|
||
|
||
return qualityNumber * 100 + durabilityNumber
|
||
end
|
||
|
||
|
||
-- Check if a cooldown for an item is expired (e.g. if using it)
|
||
-- Set a new cooldown if the old one is expired (or none set yet)
|
||
-- @param Item the item to be checked
|
||
-- @param dataKey the key of the data the timestamp is saved in (and the new one is saved in if cooldown ran up)
|
||
-- @param cooldownDuration length of the cooldown in seconds
|
||
-- @return true if the cooldown is expired (or there was none yet) and new one has been set;
|
||
-- false if the cooldown is still valid
|
||
function M.ItemCooldown(User,Item, dataKey, cooldownDuration)
|
||
local timeNow = M.GetCurrentTimestamp()
|
||
local timeThen = tonumber(Item:getData(dataKey))
|
||
if (timeThen == nil) or ((timeNow - timeThen) >= cooldownDuration) then
|
||
Item:setData(dataKey,timeNow)
|
||
world:changeItem(Item)
|
||
return true
|
||
else
|
||
return false
|
||
end
|
||
end
|
||
|
||
--- Get a timestamp based on the current server time. Resolution in RL seconds.
|
||
-- @return The current timestamp
|
||
function M.GetCurrentTimestamp()
|
||
return M.GetCurrentTimestampForDate(world:getTime("year"),
|
||
world:getTime("month"),
|
||
world:getTime("day"),
|
||
world:getTime("hour"),
|
||
world:getTime("minute"),
|
||
world:getTime("second"))
|
||
end
|
||
|
||
--- Converts a date into a timestamp in with the resolution in RL seconds.
|
||
-- @param year the year of the date to convert
|
||
-- @param month the month of the date to convert
|
||
-- @param day the day of the date to convert
|
||
-- @param hour the hour of the date to convert
|
||
-- @param minute the minute of the date to convert
|
||
-- @param second the second of the date to convert
|
||
-- @return The timestamp of the date
|
||
function M.GetCurrentTimestampForDate(year, month, day, hour, minute, second)
|
||
return math.floor(
|
||
(( year - 1 ) * 31536000 + -- (year-1)*((15*24) + 5)*24*60*60;
|
||
( month - 1 ) * 2073600 + -- (month-1)*24*24*60*60;
|
||
( day - 1 ) * 86400 + -- (day-1)*24*60*60;
|
||
hour * 3600 + -- hour*60*60
|
||
minute * 60 + -- minute*60
|
||
second
|
||
)/3
|
||
)
|
||
end
|
||
|
||
|
||
--- Convert a timestamp back into a full data. This is the inverse function of
|
||
-- GetCurrentTimestamp() and GetCurrentTimestampForData(...).
|
||
-- @param timestamp The timestamp that shall be converted
|
||
-- @return The year of the resulting data and time
|
||
-- @return The month of the resulting data and time
|
||
-- @return The day of the resulting data and time
|
||
-- @return The hour of the resulting data and time
|
||
-- @return The minute of the resulting data and time
|
||
-- @return The second of the resulting data and time
|
||
function M.TimestampToDate(timestamp)
|
||
local year = math.floor(timestamp / 31536000)
|
||
timestamp = timestamp - (year * 31536000)
|
||
|
||
--Calculating day
|
||
--86400 = 60*60*24
|
||
local day = math.floor(timestamp / 86400)
|
||
timestamp = timestamp - (day * 86400)
|
||
day = day + 1
|
||
|
||
-- Calculating month
|
||
-- 24 days per month
|
||
local month = math.floor(day / 24)
|
||
day = day - (month * 24)
|
||
|
||
-- checking for range borders and fixing the date
|
||
if (day == 0) then
|
||
if (month > 0 and month < 16) then
|
||
day = 24
|
||
else
|
||
day = 5
|
||
end
|
||
else
|
||
month=month+1
|
||
end
|
||
|
||
if (month == 0) then
|
||
month = 16
|
||
year = year - 1
|
||
end
|
||
|
||
|
||
-- Calculate the time of day
|
||
-- Calculating hour
|
||
-- 3600 = 60 * 60
|
||
local hour = math.floor(timestamp / 3600)
|
||
timestamp = timestamp - (hour * 3600)
|
||
--Calculating minute
|
||
local minute = math.floor(timestamp / 60)
|
||
--seconds
|
||
timestamp = timestamp - (minute * 60)
|
||
return year,month,day,hour,minute,timestamp
|
||
end
|
||
|
||
--- Determines the type of ground a tile has.
|
||
-- @param TileID ID of the tile that should be checked
|
||
-- @return The ground typ as one of the following constants: GroundType.unknown, GroundType.field, GroundType.forest, GroundType.sand, GroundType.gras, GroundType.rocks, GroundType.water, GroundType.dirt
|
||
function M.GetGroundType(TileID)
|
||
if (TileID == 4) then -- field
|
||
return M.GroundType.field
|
||
elseif (TileID == 6) then -- water
|
||
return M.GroundType.water
|
||
elseif (TileID == 9) then -- forest
|
||
return M.GroundType.forest
|
||
elseif (TileID == 3 or TileID == 12) then -- sand
|
||
return M.GroundType.sand
|
||
elseif (TileID == 11) then -- grass
|
||
return M.GroundType.grass
|
||
elseif (TileID == 2 or TileID == 15) then -- rocks
|
||
return M.GroundType.rocks
|
||
elseif (TileID == 8 or TileID == 16) then -- dirt
|
||
return M.GroundType.dirt
|
||
elseif (TileID == 10) then -- snow
|
||
return M.GroundType.snow
|
||
end
|
||
|
||
return M.GroundType.unknown
|
||
end
|
||
|
||
--- This list contains the constants that are returned by GetGroundType.
|
||
-- @class Enumerator
|
||
M.GroundType = {
|
||
["unknown"] = 0,
|
||
["field"] = 1,
|
||
["forest"] = 2,
|
||
["sand"] = 3,
|
||
["grass"] = 4,
|
||
["rocks"] = 5,
|
||
["water"] = 6,
|
||
["dirt"] = 7,
|
||
["snow"] = 8
|
||
}
|
||
|
||
--- Create random number with normal distribution.
|
||
-- @param minVal Minimal value of the random number range
|
||
-- @param maxVal Maximal value of the random number range
|
||
-- @return The random number
|
||
function M.NormalRnd(minVal, maxVal)
|
||
return M.NormalRnd2(minVal, maxVal, 10)
|
||
end
|
||
|
||
--- Create random number with normal distribution.
|
||
-- @param minVal Minimal value of the random number range
|
||
-- @param maxVal Maximal value of the random number range
|
||
-- @param count How often will be diced
|
||
-- @return The random number
|
||
function M.NormalRnd2(minVal, maxVal, count)
|
||
local base = 0
|
||
for _ = 1, count do
|
||
base = base + math.random(maxVal - minVal + 1) - 1
|
||
end
|
||
return math.ceil(base / count) + minVal
|
||
end
|
||
|
||
--- Sample from categorical distribution, e.g. rolling an N-sided die
|
||
-- @param cumulativeProb - array of legth N - cumulative sum of probabilities
|
||
-- should be monotonic non-decreasing, first element is the probability of first category
|
||
-- every other element is sum of previous element and probability of current category
|
||
-- last element should be 1
|
||
-- @return int - the index of category that won
|
||
--
|
||
function M.randomCategorical(cumulativeProb)
|
||
local r = math.random()
|
||
local category
|
||
for i,v in ipairs(cumulativeProb) do
|
||
if r <= v then
|
||
category = i
|
||
break
|
||
end
|
||
end
|
||
assert(category, "Failed sampling categorical distribution, last cumulativeProbability was " .. cumulativeProb[#cumulativeProb])
|
||
return category
|
||
end
|
||
|
||
--- Sample from multinomial distribution, i.e. repeated categorical trials
|
||
-- @param numTrials - number of categorical trials to simulate. Sum of values in returned array will be equal to this.
|
||
-- @param cumulativeProb - cumulative sum of probabilities, same as in randomCategorical function
|
||
-- @return array of integers - number of times each category won
|
||
--
|
||
function M.randomMultinomial(numTrials, cumulativeProb)
|
||
local result = {}
|
||
for i = 1, #cumulativeProb do
|
||
result[i] = 0
|
||
end
|
||
for _ = 1, numTrials do
|
||
local category = M.randomCategorical(cumulativeProb)
|
||
result[category] = result[category] + 1
|
||
end
|
||
return result
|
||
end
|
||
|
||
|
||
local function _isNumber(value)
|
||
return type(value) == "number"
|
||
end
|
||
|
||
local function _isTable(value)
|
||
return type(value) == "table"
|
||
end
|
||
|
||
local TimeList = {}
|
||
function M.spamProtect(character, delay)
|
||
if not isValidChar(character) then
|
||
error("The parameter 'character' is not a valid character as it was expected.")
|
||
end
|
||
|
||
delay = delay or 1
|
||
if not _isNumber(delay) then
|
||
error("The parameter 'delay' is not a number as it was expected.")
|
||
elseif delay < 1 then
|
||
error("The parameter 'delay' must be a positive number.")
|
||
end
|
||
|
||
if TimeList[character.id] ~= nil then
|
||
if (math.abs(world:getTime("unix") - TimeList[character.id])) <= delay then
|
||
return true
|
||
end
|
||
end
|
||
TimeList[character.id] = world:getTime("unix")
|
||
return false
|
||
end
|
||
|
||
--[[
|
||
CreateItem
|
||
Safely create an item
|
||
@return boolean - true if all fits in player's inventory, false if something was created on the ground
|
||
]]
|
||
function M.CreateItem(character, id, amount, quality, data)
|
||
if not isValidChar(character) then
|
||
error("The parameter 'character' is not a valid character as it was expected.")
|
||
end
|
||
|
||
if not _isNumber(id) then
|
||
error("The parameter 'id' is not a number as it was expected.")
|
||
elseif id < 1 then
|
||
error("The parameter 'id' must be a positive number.")
|
||
end
|
||
|
||
if not _isNumber(amount) then
|
||
error("The parameter 'amount' is not a number as it was expected.")
|
||
elseif amount < 1 then
|
||
error("The parameter 'amount' must be a positive number.")
|
||
end
|
||
|
||
if not _isNumber(quality) then
|
||
error("The parameter 'quality' is not a number as it was expected.")
|
||
elseif quality < 100 or quality > 999 then
|
||
error("The parameter 'quality' must be a number between 100 and 999.")
|
||
end
|
||
|
||
if data ~= nil and not _isTable(data) then
|
||
error("The parameter 'data' is not a table as it was expected.")
|
||
end
|
||
|
||
local notCreated = character:createItem(id, amount, quality, data)
|
||
if notCreated == 0 then
|
||
return true
|
||
end
|
||
|
||
local maxStack = world:getItemStatsFromId(id).MaxStack
|
||
while (notCreated > 0) do
|
||
-- work around an issue to prevent creation of stacks of unstackable items
|
||
local minimum = math.min(notCreated, maxStack)
|
||
world:createItemFromId(id, minimum, character.pos, true, quality, data)
|
||
notCreated = notCreated - minimum
|
||
end
|
||
|
||
if not M.spamProtect(character) then
|
||
character:inform(
|
||
"Du kannst nichts mehr halten und der Rest f<>llt zu Boden.",
|
||
"You can't carry any more and the rest drops to the ground.",
|
||
Player.highPriority)
|
||
end
|
||
return false
|
||
end
|
||
|
||
--[[
|
||
DeleteItemFromStack
|
||
Searches for an item with given properties in a stack and deletes it.
|
||
@param PositionStruct - Position of the stack
|
||
@param List - A list containing the properties of the item to be deleted
|
||
@return boolean
|
||
|
||
]]
|
||
function M.DeleteItemFromStack(stackPosition, itemProperties)
|
||
|
||
if not world:isItemOnField(stackPosition) then
|
||
return false
|
||
end
|
||
|
||
local theField = world:getField(stackPosition)
|
||
local counter = 1
|
||
local foundItem = false
|
||
while counter <= theField:countItems() do
|
||
local checkItem = theField:getStackItem(theField:countItems()- counter )
|
||
if (itemProperties.itemId == checkItem.id) and (not itemProperties.deleteAmount or checkItem.number <= itemProperties.deleteAmount) and (not itemProperties.quality or checkItem.quality == itemProperties.quality) then
|
||
if itemProperties.data then
|
||
for i=1,#itemProperties.data do
|
||
if not checkItem:getData(itemProperties["data"][i]["dataKey"]) == itemProperties["data"][i]["dataValue"] then
|
||
break
|
||
end
|
||
end
|
||
end
|
||
foundItem = true
|
||
break
|
||
end
|
||
counter = counter + 1
|
||
end
|
||
|
||
if not foundItem then
|
||
return false
|
||
end
|
||
|
||
local deletedItems = {}
|
||
for i=1,counter do
|
||
local deleteItem = world:getItemOnField(stackPosition)
|
||
if i ~= counter then
|
||
table.insert(deletedItems,1,deleteItem)
|
||
world:erase(deleteItem, deleteItem.number)
|
||
else
|
||
world:increase(deleteItem, -(itemProperties.deleteAmount or deleteItem.number))
|
||
end
|
||
end
|
||
for i=1,#deletedItems do
|
||
world:createItemFromItem(deletedItems[i],stackPosition,true)
|
||
end
|
||
|
||
return true
|
||
|
||
end
|
||
|
||
--[[
|
||
GetItemsOnField
|
||
Get a list of all items on a field
|
||
IMPORTAINT: All items but the one on top are read only
|
||
@param PositionStruct - The position of the tile that shall be checked
|
||
@return List of ItemStructs - The list of all items on that field
|
||
]]
|
||
function M.GetItemsOnField(Fieldpos)
|
||
local Field = world:getField(Fieldpos)
|
||
if (Field == nil) then
|
||
return {}
|
||
end
|
||
local ItemsCount = Field:countItems()
|
||
local retList = {}
|
||
if (ItemsCount > 0) then
|
||
for i=0, ItemsCount-1 do
|
||
local Item = Field:getStackItem(i)
|
||
table.insert(retList, Item)
|
||
end
|
||
end
|
||
return retList
|
||
end
|
||
|
||
--[[
|
||
GetItemInInventory
|
||
Search the whole Inventory of a character for a Item and a Data value
|
||
@param CharacterStruct - The characters who's inventory is checked
|
||
@param integer - The item ID we are looking for
|
||
@param list(list(string)) - Optional list of key-value pairs of the sought item. Default is an empty list.
|
||
E.g. { {"amount", "4"}, {"valid", "true"} }. NOTE: key & value have to be both strings.
|
||
@return mixed - The itemstruct of the first found item or nil.
|
||
]]
|
||
function M.GetItemInInventory(User, ItemID, DataValues)
|
||
local ItemList = User:getItemList(ItemID)
|
||
local dataOK
|
||
local hasData = true
|
||
if (DataValues == nil or #DataValues == 0) then
|
||
hasData = false
|
||
end
|
||
for _, item in pairs(ItemList) do
|
||
dataOK = true
|
||
if hasData then
|
||
for _, data in pairs(DataValues) do
|
||
if (tostring(item:getData(data[1])) ~= tostring(data[2])) then
|
||
dataOK = false
|
||
end
|
||
end
|
||
end
|
||
if dataOK then
|
||
return item
|
||
end
|
||
end
|
||
return nil
|
||
end
|
||
|
||
--[[
|
||
GetStiffness
|
||
Generate the sum of all stiffness values of the armor parts a character wears
|
||
@param CharacterStruct - The character whos stiffness is checked
|
||
@return integer - the stiffness value
|
||
Reworked by Flux
|
||
]]
|
||
function M.GetStiffness(Character)
|
||
|
||
local StiffnessVal = 0
|
||
local Item
|
||
local found
|
||
local Armor
|
||
|
||
local Equipmentposition = {1, 3, 4, 9, 10, 11}
|
||
|
||
for counter = 1, #Equipmentposition do
|
||
Item = Character:getItemAt(Equipmentposition[counter])
|
||
if Item and (Item.id ~= 0 ) then
|
||
found, Armor=world:getArmorStruct(Item.id)
|
||
if found then
|
||
StiffnessVal = StiffnessVal + Armor.Stiffness
|
||
end
|
||
end
|
||
|
||
end
|
||
|
||
return StiffnessVal
|
||
end
|
||
|
||
local interruptTable
|
||
|
||
--[[
|
||
ResetInterruption
|
||
Resets the interruption counter state, in case the action did not success
|
||
@param CharacterStruct - The character whos state is resetted
|
||
@param LongTimeActionState - The state of the current action
|
||
]]
|
||
function M.ResetInterruption(Character, ltstate)
|
||
if (ltstate ~= Action.success) and interruptTable then
|
||
interruptTable[Character.id] = nil
|
||
end
|
||
end
|
||
|
||
--[[
|
||
IsInterrupted
|
||
Checks if a characters action is interrupted has a minimal amount of actions that
|
||
will succeed all time before a interruption is possible.
|
||
@param CharacterStruct - The character who is checked for a interruption
|
||
@return boolean - true in case the action is interruped, false if not
|
||
]]
|
||
function M.IsInterrupted(Character)
|
||
if not interruptTable then
|
||
interruptTable = {}
|
||
interruptTable[Character.id] = 2
|
||
return false
|
||
elseif not interruptTable[Character.id] then
|
||
interruptTable[Character.id] = 2
|
||
return false
|
||
end
|
||
local rnd = math.random(20, 50)
|
||
if ( interruptTable[Character.id] > rnd ) then
|
||
interruptTable[Character.id] = nil
|
||
return true
|
||
end
|
||
interruptTable[Character.id] = interruptTable[Character.id] + 1
|
||
return false
|
||
end
|
||
|
||
--[[
|
||
ParalyseCharacter
|
||
Paralyses a character with a lte or by the actionpoints for setted time
|
||
@param CharacterStruct - The character that shall be paralysed
|
||
@param integer - The time in seconds the character shall be paralysed
|
||
@param boolean - true if the time shall be added to an exsisting paralizing, false if the old values shall just be overwritten
|
||
@param boolean - true(default) if a LTE shall be used to stunn the character. In this case its not intended to release the stunn any time ealier. False if not
|
||
@return boolean - true if anything was done. false if not.
|
||
]]
|
||
function M.ParalyseCharacter(Target, Time, Cumulative, forced)
|
||
|
||
if not Time or (Time == 1) then
|
||
return false
|
||
end
|
||
|
||
if (forced == nil) then
|
||
forced = true
|
||
end
|
||
|
||
if not forced then
|
||
if Cumulative then
|
||
Target.actionpoints = Target.actionpoints - ( Time * 10 )
|
||
else
|
||
Target.actionpoints = 30 - ( Time * 10 )
|
||
end
|
||
return true
|
||
end
|
||
|
||
local foundEffect, Paralysis = Target.effects:find(23)
|
||
local timeLeft = 0
|
||
|
||
if not foundEffect then
|
||
if (Time < 1) then
|
||
return false
|
||
end
|
||
Paralysis = LongTimeEffect(23, 1)
|
||
Target.effects:addEffect(Paralysis)
|
||
else
|
||
local foundTime
|
||
foundTime, timeLeft = Paralysis:findValue("timeLeft")
|
||
if not foundTime then
|
||
timeLeft=0
|
||
end
|
||
end
|
||
|
||
if (0 < Time and Cumulative) or (Time < 0) then
|
||
Time = Time + timeLeft
|
||
else
|
||
Time = math.max(Time, timeLeft)
|
||
end
|
||
|
||
if not _G.extendedCastingTimeForParalysis then
|
||
_G.extendedCastingTimeForParalysis = {}
|
||
end
|
||
|
||
_G.extendedCastingTimeForParalysis[Target.id] = math.max(0,Time)
|
||
|
||
if (Time ~= timeLeft) then
|
||
Paralysis:addValue("timeLeft", math.max(0,Time))
|
||
return true
|
||
end
|
||
|
||
return false
|
||
end
|
||
|
||
--[[
|
||
IsCharacterParalysed
|
||
Checks if the character is stunned by the stunning LTE
|
||
@param CharacterStruct - The character that shall be checked
|
||
@return integer - The amount of setting remaining in the stunn or nil if not stunned
|
||
]]
|
||
function M.IsCharacterParalysed(Character)
|
||
|
||
local foundEffect, Paralysis = Character.effects:find(23)
|
||
if not foundEffect then
|
||
return nil
|
||
else
|
||
local foundTime, timeLeft = Paralysis:findValue("timeLeft")
|
||
if foundTime then
|
||
return timeLeft
|
||
end
|
||
end
|
||
return nil
|
||
end
|
||
|
||
local circleCache = {}
|
||
--[[
|
||
CreateCircle
|
||
Calculates a circle based on a center position and a radius. It triggers a Event function
|
||
on every position of the circle with the position of the circle as parameter
|
||
@param PositionStruct - Center position of the circle
|
||
@param integer - Radius of the circle
|
||
@param function(PositionStruct) - Event function that is triggered for every position
|
||
]]
|
||
function M.CreateCircle(CenterPos, Radius, Event, monster)
|
||
if not Event then
|
||
return
|
||
end
|
||
if not circleCache[Radius] then
|
||
local irad = math.ceil(Radius)
|
||
local map = {}
|
||
circleCache[Radius] = {}
|
||
|
||
for x = -irad - 1, irad do
|
||
map[x] = {}
|
||
for y = -irad - 1, irad do
|
||
map[x][y] = (x + 0.5) * (x + 0.5) + (y + 0.5) * (y + 0.5) - irad * irad > 0
|
||
end
|
||
end
|
||
|
||
for x = -irad, irad do
|
||
for y = -irad, irad do
|
||
if not (map[x][y] and map[x-1][y] and map[x][y-1] and map[x-1][y-1])
|
||
and (map[x][y] or map[x-1][y] or map[x][y-1] or map[x-1][y-1]) then
|
||
table.insert(circleCache[Radius], position(x, y, 0))
|
||
end
|
||
end
|
||
end
|
||
end
|
||
local go_on
|
||
for _, posi in pairs(circleCache[Radius]) do
|
||
go_on = Event(position(CenterPos.x + posi.x, CenterPos.y + posi.y, CenterPos.z), monster)
|
||
if (go_on == false and go_on ~= nil) then
|
||
return true
|
||
end
|
||
end
|
||
end
|
||
|
||
--[[
|
||
CreateLine
|
||
Calculates a straight line between 2 position and triggers a Event function for every position
|
||
@param PositionStruct - First position of the line
|
||
@param PositionStruct - Second position of the line
|
||
@param function(PositionStruct) - Event function that is triggered for every position
|
||
]]
|
||
function M.CreateLine(StartPos, TargetPos, Event)
|
||
local XDiff2 = math.abs(StartPos.x - TargetPos.x)
|
||
local YDiff2 = math.abs(StartPos.y - TargetPos.y)
|
||
local PriDiff = math.max(XDiff2, YDiff2)
|
||
local SecDiff = math.min(YDiff2, XDiff2)
|
||
local XMod = (StartPos.x < TargetPos.x and -1 or 1)
|
||
local YMod = (StartPos.y < TargetPos.y and -1 or 1)
|
||
local PathPos
|
||
local go_on
|
||
for i = 1, PriDiff do
|
||
if (PriDiff == XDiff2) then
|
||
PathPos = position(StartPos.x - XMod * i, StartPos.y - YMod * math.floor(i / PriDiff * SecDiff), StartPos.z )
|
||
else
|
||
PathPos = position(StartPos.x - XMod * math.floor(i / PriDiff * SecDiff), StartPos.y - YMod * i, StartPos.z )
|
||
end
|
||
go_on = Event(PathPos)
|
||
if (go_on == false and go_on ~= nil) then
|
||
return true
|
||
end
|
||
end
|
||
end
|
||
|
||
--[[
|
||
CreateTangentLine
|
||
Calculates a tangent line from the perspective of the center pos with the middle on the TargetPos
|
||
@param PositionStruct - The center position
|
||
@param PositionStruct - the target position (center of this line is on this position)
|
||
@param integer - length of one arm of the line. Effective length is ArmLength*2+1
|
||
@param function(PositionStruct) - Event function that is triggered for every position
|
||
]]
|
||
function M.CreateTangentLine(CenterPos, TargetPos, ArmLength, Event)
|
||
local phi = M.GetPhi( CenterPos, TargetPos ) + math.pi/2
|
||
|
||
local xoffset = math.cos( phi )
|
||
local yoffset = math.sin( phi )
|
||
local maxoffset = math.max( math.abs( xoffset ), math.abs( yoffset ) )
|
||
|
||
yoffset = yoffset * ( 1 / maxoffset )
|
||
xoffset = xoffset * ( 1 / maxoffset )
|
||
|
||
local go_on = Event( TargetPos )
|
||
if (go_on == false and go_on ~= nil) then
|
||
return
|
||
end
|
||
|
||
local first_go_on = true
|
||
local second_go_on = true
|
||
for i=1, ArmLength do
|
||
if first_go_on then
|
||
first_go_on = Event(position(M.Round(TargetPos.x + i * xoffset),
|
||
M.Round(TargetPos.y + i * yoffset), TargetPos.z))
|
||
first_go_on = (first_go_on ~= nil and first_go_on or true)
|
||
end
|
||
if second_go_on then
|
||
second_go_on = Event(position(M.Round(TargetPos.x - i * xoffset),
|
||
M.Round(TargetPos.y - i * yoffset), TargetPos.z))
|
||
second_go_on = ( second_go_on ~= nil and second_go_on or true )
|
||
end
|
||
if not first_go_on and not second_go_on then
|
||
return
|
||
end
|
||
end
|
||
end
|
||
|
||
--[[
|
||
Scale
|
||
Calculates a linear interpolation between two values based upon a third value
|
||
@param integer - Start of the linear interpolation. Value at 0
|
||
@param integer - End of the linear interpolation. Value at 100
|
||
@param integer - Value for the interpolation. Causes the function to return the first value if its set to 0 and the second if its set to 100
|
||
For any value between 0 and 100 it returns a value between the start and the end value
|
||
@return integer - interpolated value
|
||
]]
|
||
function M.Scale(ScBegin, ScEnd, value)
|
||
return M.ScaleUnlimited(ScBegin, ScEnd, M.Limit(value, 0, 100))
|
||
end
|
||
|
||
--[[
|
||
ScaleUnlimited
|
||
Calculates a linear interpolation between two values based upon a third value
|
||
In case the value is aboth 100 the return value will be aboth the end value
|
||
@param integer - Start of the linear interpolation. Value at 0
|
||
@param integer - End of the linear interpolation. Value at 100
|
||
@param integer - Value for the interpolation. Causes the function to return the first value if its set to 0 and the second if its set to 100
|
||
For any value between 0 and 100 it returns a value between the start and the end value
|
||
@return integer - interpolated value
|
||
]]
|
||
function M.ScaleUnlimited(ScBegin, ScEnd, value)
|
||
return (ScEnd - ScBegin) / 100 * value + ScBegin
|
||
end
|
||
|
||
--[[
|
||
Limit
|
||
Sets the value into the given borders. If the borders are set to nil or false, they are interpreted as non-exsistant
|
||
@param integer - The value that shall be checked
|
||
@param integer - The minimal border. The return value will be not smaller then this
|
||
@param integer - The maximal border. The return value will not be bigger then this
|
||
@return integer - the value or the border in case the value is bigger or smaller then the given range
|
||
]]
|
||
function M.Limit(value, min, max)
|
||
if min and (value < min) then
|
||
return min
|
||
end
|
||
if max and (value > max) then
|
||
return max
|
||
end
|
||
return value
|
||
end
|
||
|
||
--[[
|
||
Round
|
||
Round a value at a given precision up at x.5 and bigger, down in any other cases
|
||
@param integer - the value that shall be rounded
|
||
@param integer - the precision, default 0
|
||
@return integer - the rounded value
|
||
]]
|
||
function M.Round(value, precision)
|
||
if not precision then
|
||
precision = 0
|
||
end
|
||
|
||
value = value * 10 ^ precision
|
||
|
||
if (math.fmod(value, 1) >= 0.5) then
|
||
value = math.ceil( value )
|
||
else
|
||
value = math.floor( value )
|
||
end
|
||
|
||
value = value / 10 ^ precision
|
||
return value
|
||
end
|
||
|
||
--[[
|
||
GetPhi
|
||
Calculate phi between 2 positions
|
||
@param PositionStruct - The start position
|
||
@param PositionStruct - The target position
|
||
@return float - phi
|
||
]]
|
||
function M.GetPhi(StartPos, TargetPos)
|
||
if StartPos == TargetPos then
|
||
return math.random()*2
|
||
end
|
||
local dx = TargetPos.x - StartPos.x
|
||
local dy = TargetPos.y - StartPos.y
|
||
local phi
|
||
if (dx == 0) then
|
||
if (dy > 0) then
|
||
phi = math.pi / 2
|
||
else
|
||
phi = math.pi + math.pi / 2
|
||
end
|
||
elseif (dx > 0) then
|
||
if (dy >= 0) then
|
||
phi = math.atan(dy / dx)
|
||
else
|
||
phi = math.atan(dy / dx) + 2 * math.pi
|
||
end
|
||
else
|
||
phi = math.atan(dy / dx) + math.pi
|
||
end
|
||
return phi
|
||
end
|
||
|
||
--[[
|
||
ChangeAttribute
|
||
Change a attribute by a given value and return the effective change
|
||
@param CharacterStruct - The character whos attribute shall be changed
|
||
@param string - The attribute that shall be changed
|
||
@param integer - The value that the attribute shall be changed by
|
||
@param integer - The bottom border [optional]
|
||
@param integer - The top border [optional]
|
||
@return integer - Change Value
|
||
]]
|
||
function M.ChangeAttribute( Character, attrib, value, bottomBorder, topBorder )
|
||
if (value < -255 or value > 255) then
|
||
return 0
|
||
end
|
||
if (bottomBorder <= 0 or bottomBorder >= 255) then
|
||
bottomBorder = 1
|
||
end
|
||
if (topBorder <= 0 or topBorder >= 255) then
|
||
topBorder = 255
|
||
end
|
||
if bottomBorder >= topBorder then
|
||
bottomBorder = 1
|
||
topBorder = 255
|
||
end
|
||
local oldValue = Character:increaseAttrib(attrib, 0)
|
||
local newValue = oldValue + value
|
||
if (newValue < bottomBorder) then
|
||
value = bottomBorder - newValue
|
||
elseif (newValue > topBorder) then
|
||
value = newValue - topBorder
|
||
end
|
||
Character:increaseAttrib(attrib, value)
|
||
return (value + 255)
|
||
end
|
||
|
||
--[[
|
||
RevertAttribute
|
||
Revert the change of a attribute done with ChangeAttribute
|
||
@param CharacterStruct - The character whos attribute shall be changed
|
||
@param string - The attribute that shall be changed
|
||
@param integer - The value returned by ChangeAttribute
|
||
@return boolean - success or not
|
||
]]
|
||
function M.RevertAttribute(Character, attrib, value)
|
||
if (value < 0 or value > 510) then
|
||
return false
|
||
else
|
||
value = value - 255
|
||
end
|
||
|
||
Character:increaseAttrib(attrib, -value)
|
||
return true
|
||
end
|
||
|
||
--[[
|
||
CopyPosition
|
||
Copies a position struct to a new one
|
||
@param position - the position that shall be copied
|
||
@return position - the new position struct
|
||
]]
|
||
function M.CopyPosition(Posi)
|
||
return position(Posi.x, Posi.y, Posi.z)
|
||
end
|
||
|
||
--[[
|
||
split
|
||
splits a string into different parts
|
||
@param str - the string which should be splitted
|
||
@return pat - the pattern around what the string should be splittet
|
||
example = split("hello:my:split:world",":") returns {"hello","my","split","world"}
|
||
|
||
In case the pattern is only one character it is better to use string.gmatch.
|
||
]]
|
||
function M.split(str, pat)
|
||
local t = {}
|
||
if (string.len(pat) == 1) then
|
||
for element in string.gmatch(str, "([^" .. pat .. "]+)[" .. pat .. "]?") do
|
||
table.insert(t, element)
|
||
end
|
||
else
|
||
local fpat = "(.-)" .. pat
|
||
local last_end = 1
|
||
local s, e, cap = str:find(fpat, 1)
|
||
while s do
|
||
if s ~= 1 or cap ~= "" then
|
||
table.insert(t, cap)
|
||
end
|
||
last_end = e + 1
|
||
s, e, cap = str:find(fpat, last_end)
|
||
end
|
||
if (last_end <= str:len()) then
|
||
cap = str:sub(last_end)
|
||
table.insert(t, cap)
|
||
end
|
||
end
|
||
return t
|
||
end
|
||
|
||
|
||
--[[
|
||
Split_number
|
||
Splits a digit chain into digits and returns it in a table
|
||
Example: split_number(3015,4) returns following table: table = {3, 0, 1, 5}
|
||
@param Number - the number chain that shall be splitted
|
||
@param AmountOfDigits - the amount of digits the number chain has
|
||
|
||
@return table - the splitted number chain into digits
|
||
]]
|
||
function M.Split_number(Number, AmountOfDigits)
|
||
|
||
local temptable = {}
|
||
local tempcnt = 0
|
||
local tempnumber
|
||
|
||
tempnumber = math.floor(Number / (10^AmountOfDigits))
|
||
Number = Number - tempnumber * (10^AmountOfDigits)
|
||
|
||
for i = (AmountOfDigits-1), 0, -1 do
|
||
tempcnt = tempcnt + 1
|
||
tempnumber = math.floor(Number / (10^i))
|
||
temptable[tempcnt] = tempnumber
|
||
Number = Number - tempnumber * (10^i)
|
||
end
|
||
return temptable
|
||
end
|
||
|
||
|
||
--[[
|
||
Converts a given hour of day to a string like: early morning, morning,
|
||
@param hour the hour which should be converted
|
||
@return ger, eng strings
|
||
]]
|
||
function M.Hour_To_String(hour)
|
||
if (hour >= 0 and hour < 2 or hour == 24) then
|
||
return "gegen Mitternacht", "around midnight"
|
||
elseif (hour >= 2 and hour < 4) then
|
||
return "nach Mitternacht", "after midnight"
|
||
elseif (hour >= 4 and hour < 6) then
|
||
return "vor Sonnenaufgang", "before sunrise"
|
||
elseif (hour >= 6 and hour < 8) then
|
||
return "fr<EFBFBD>her Morgen", "in the early morning"
|
||
elseif (hour >= 8 and hour < 10) then
|
||
return "morgens", "in the morning"
|
||
elseif (hour >= 10 and hour < 12) then
|
||
return "vormittags", "before noon"
|
||
elseif (hour >= 12 and hour < 14) then
|
||
return "gegen Mittag", "around noon"
|
||
elseif (hour >= 14 and hour < 16) then
|
||
return "nachmittags", "in the afternoon"
|
||
elseif (hour >= 16 and hour < 18) then
|
||
return "fr<EFBFBD>her Abend", "in the early evening"
|
||
elseif (hour >= 18 and hour < 20) then
|
||
return "abends","in the evening"
|
||
elseif (hour >= 20 and hour < 22) then
|
||
return "sp<EFBFBD>ter Abend", "in the late evening"
|
||
else
|
||
return "vor Mitternacht", "before midnight"
|
||
end
|
||
end
|
||
|
||
--[[
|
||
Converts a numeric month to a string like: Elos, Tanos,...
|
||
@param month the moth which should be converted
|
||
@return string
|
||
]]
|
||
function M.Month_To_String(month)
|
||
|
||
local MonthNames = {"Elos", "Tanos", "Zhas", "Ushos", "Siros", "Ronas", "Bras", "Eldas", "Irmas", "Malas", "Findos", "Olos", "Adras", "Naras", "Chos", "Mas"} --List of our months
|
||
|
||
if (month >= 1) and (month <= 16) then --only valid months
|
||
return MonthNames[month] --return the month as string
|
||
else
|
||
return "[ERROR] Corrupted date, please inform a developer."
|
||
end
|
||
|
||
end
|
||
|
||
function M.fold(ar, f, neutral)
|
||
local result = neutral
|
||
for i,v in ipairs(ar) do
|
||
result = f(result, v)
|
||
end
|
||
return result
|
||
end
|
||
|
||
function M.map(ar, f)
|
||
for i,v in ipairs(ar) do
|
||
ar[i] = f(v)
|
||
end
|
||
return ar
|
||
end
|
||
|
||
---
|
||
-- Turn a list into a "set"
|
||
-- Use set[item] to check if an item is contained in set
|
||
-- Taken from: http://lua-users.org/wiki/SetOperations
|
||
-- For more operaions (union, intersection) see the above and also https://www.lua.org/pil/13.1.html
|
||
--@param t list of values to be included in the set
|
||
function M.setFromList(t)
|
||
local s = {}
|
||
for _,v in pairs(t) do s[v] = true end
|
||
return s
|
||
end
|
||
|
||
---
|
||
-- Take two sets and create a union (set containing all items from the sets, without repetition)
|
||
-- @param a - a set
|
||
-- @param b - a set
|
||
function M.setUnion(a, b)
|
||
local s = {}
|
||
for v, _ in pairs(a) do s[v] = true end
|
||
for v, _ in pairs(b) do s[v] = true end
|
||
return s
|
||
end
|
||
|
||
|
||
--[[
|
||
This is some strange method written by Vilarion. Its extremly inefficient,
|
||
very creepy and most likely totally buggy.
|
||
Usage is not adviced.
|
||
--]]
|
||
function M.isItemIdInFieldStack( id, pos )
|
||
|
||
local stack = {}
|
||
local i = -1
|
||
local found = false
|
||
local itm
|
||
while( world:isItemOnField(pos) and (not found) ) do
|
||
itm = world:getItemOnField(pos)
|
||
if( itm.id == id ) then
|
||
found = true
|
||
else
|
||
i = i + 1
|
||
stack[i] = itm
|
||
world:erase( itm, itm.number )
|
||
end
|
||
end
|
||
while( i >= 0 ) do
|
||
world:createItemFromItem( stack[i], pos, true )
|
||
i = i - 1
|
||
end
|
||
return found
|
||
end
|
||
|
||
--[[
|
||
This is some strange method written by Vilarion. Its extremly inefficient,
|
||
very creepy and most likely totally buggy.
|
||
Usage is not adviced.
|
||
--]]
|
||
function M.removeItemIdFromFieldStack( id, pos )
|
||
|
||
local stack = {}
|
||
local i = -1
|
||
local found = false
|
||
local itm
|
||
while( world:isItemOnField(pos) and (not found) ) do
|
||
itm = world:getItemOnField(pos)
|
||
if( itm.id == id ) then
|
||
found = true
|
||
else
|
||
i = i + 1
|
||
stack[i] = itm
|
||
end
|
||
world:erase( itm, itm.number )
|
||
end
|
||
while( i >= 0 ) do
|
||
world:createItemFromItem( stack[i], pos, true )
|
||
i = i - 1
|
||
end
|
||
end
|
||
|
||
--[[
|
||
ExtgetPlayersInRangeOf
|
||
An extended Version of the world - function getPlayersInRangeOf
|
||
includes players who are in the diagonal direction in range of the radius
|
||
|
||
@param pos - the center position struct from where there shall be looked at
|
||
@param radius - the range at which we want to look for players
|
||
|
||
@return struct table - a table with players or nil
|
||
]]
|
||
function M.ExtgetPlayersInRangeOf(posi, radius)
|
||
|
||
local ext = 2
|
||
local plyList = world:getPlayersInRangeOf(posi, radius+ext)
|
||
|
||
for i, player in pairs(plyList) do
|
||
-- player not in rect, remove from list
|
||
if not M.isInRect(player.pos, posi, radius) then
|
||
plyList[i] = nil
|
||
end
|
||
end
|
||
|
||
return plyList
|
||
|
||
end
|
||
|
||
--[[
|
||
isInRect
|
||
checks whether targetpos is within a rect with distance "range"
|
||
and center position "posi"
|
||
|
||
@param targetpos - the target pos that shall be looked for
|
||
@param posi - center position of the rect
|
||
@param range - distance of the rect borders from the center
|
||
|
||
@return bool - true when targetpos is in rect else false
|
||
]]
|
||
function M.isInRect(targetpos, posi, range)
|
||
|
||
if targetpos.x>=(posi.x-range) and targetpos.x<=(posi.x+range) then --checks the x-Coordinates with the borders
|
||
if targetpos.y>=(posi.y-range) and targetpos.y<=(posi.y+range) then --checks the y-Coordinates with the borders
|
||
return true
|
||
else
|
||
return false
|
||
end
|
||
else
|
||
return false
|
||
end
|
||
end
|
||
|
||
--- Check if a given monster is docile
|
||
-- @param id The ID of the monster in question
|
||
-- @return true if monster is docile
|
||
function M.IsMonsterDocile( id )
|
||
local docileList = {6,16,26,36,46,56,96,106,116,181,182,241,242,243,244,371,372,373,396,581,584,591,615,616,621,1054,1131,1132,1133,1134,1135,1136,1151,1152,1161,1162,1163,1164,1165}
|
||
for i,v in pairs(docileList) do
|
||
if id == v then
|
||
return true
|
||
end
|
||
end
|
||
return false
|
||
end
|
||
|
||
--- Checks if an item is in the hand tools slots
|
||
-- @param item The item to check
|
||
-- @return true if the item is in a hand tools slot, false otherwise
|
||
function M.IsItemInHands( item )
|
||
if item:getType() == 4 then
|
||
local itempos = item.itempos
|
||
if itempos == 5 or itempos == 6 then
|
||
return true
|
||
end
|
||
end
|
||
return false
|
||
end
|
||
|
||
--[[Check if a char holds an item from a list in hand
|
||
@return true if item with id is in any hand slot
|
||
If true, returns the item in question]]--
|
||
function M.hasItemIdInHand(user, itemIds)
|
||
if type(itemIds) ~= "table" then
|
||
itemIds = {itemIds}
|
||
end
|
||
local leftTool = user:getItemAt(Character.left_tool)
|
||
if M.isInList(leftTool.id, itemIds) then
|
||
return true, leftTool
|
||
end
|
||
local rightTool = user:getItemAt(Character.right_tool)
|
||
if M.isInList(rightTool.id, itemIds) then
|
||
return true, rightTool
|
||
end
|
||
return false
|
||
end
|
||
|
||
--[[Check if a char holds an item from a list in hand
|
||
@return item if item with id is in any hand slot]]--
|
||
function M.getItemInHand(user, itemIds)
|
||
if type(itemIds) ~= "table" then
|
||
itemIds = {itemIds}
|
||
end
|
||
local leftTool = user:getItemAt(Character.left_tool)
|
||
if M.isInList(leftTool.id, itemIds) then
|
||
return leftTool
|
||
end
|
||
local rightTool = user:getItemAt(Character.right_tool)
|
||
if M.isInList(rightTool.id, itemIds) then
|
||
return rightTool
|
||
end
|
||
return nil
|
||
end
|
||
|
||
|
||
--- Gets the target item for Use-With like commands. Both, source and target items have to be in hand tool slots.
|
||
-- @param character The character who wants to use the items
|
||
-- @param source The source item that is "used with" the target item
|
||
-- @return The target item (in the other hand slot than the source) or nil if no target is found
|
||
function M.GetTargetItem( character, source )
|
||
if not M.IsItemInHands(source) then
|
||
return nil
|
||
end
|
||
local tpos = 11 - source.itempos -- either 5 or 6
|
||
local target = character:getItemAt(tpos)
|
||
if target.id == 0 then
|
||
return nil
|
||
end
|
||
return target
|
||
end
|
||
|
||
--[[ Gets the target item for Use-With like commands.
|
||
@param character The character who wants to use the items
|
||
@param items The items, searched for
|
||
Sucess:
|
||
one of the items in hand
|
||
else#
|
||
one of the items in belt
|
||
else
|
||
one of the items in bag
|
||
itemNumber > 1 means in one step more than 1 possible item was found
|
||
@return itemNumber (0 if not found), foundItem last found item]]--
|
||
function M.GetTargetItemAnywhere(user, itemList)
|
||
local tmpItem
|
||
if type(itemList) ~= "table" then
|
||
itemList = {itemList}
|
||
end
|
||
local countItems = 0
|
||
local foundItem = nil
|
||
for i = 5, 6 do -- hands
|
||
tmpItem = user:getItemAt(i)
|
||
if M.isInList(tmpItem.id, itemList) then
|
||
countItems = countItems + 1
|
||
foundItem = tmpItem
|
||
end
|
||
end
|
||
if countItems > 0 then
|
||
return countItems, foundItem
|
||
end
|
||
for i = 12, 17 do -- belt
|
||
tmpItem = user:getItemAt(i)
|
||
if M.isInList(tmpItem.id, itemList) then
|
||
countItems = countItems + 1
|
||
foundItem = tmpItem
|
||
end
|
||
end
|
||
if countItems > 0 then
|
||
return countItems, foundItem
|
||
end
|
||
local backpack = user:getBackPack()
|
||
if backpack then
|
||
for i = 0, 99 do
|
||
local isItem, tmpItemb, _ = backpack:viewItemNr(i)
|
||
if isItem then
|
||
if M.isInList(tmpItemb.id, itemList) then
|
||
countItems = countItems + 1
|
||
foundItem = tmpItemb
|
||
end
|
||
end
|
||
end
|
||
end
|
||
return countItems, foundItem
|
||
end
|
||
|
||
--- Returns the real date and time as a String
|
||
-- @return date and time in format: YYYY-MM-DD | hh:mm:ss
|
||
function M.GetRealDateTimeString()
|
||
local year, month, day, hour, minute, second = M.GetRealDate()
|
||
local timeString =
|
||
function(int)
|
||
if int < 10 then
|
||
return "0"..int
|
||
end
|
||
return ""..int
|
||
end
|
||
return timeString(year) .."-".. timeString(month) .."-"..
|
||
timeString(day) .." | ".. timeString(hour) ..":".. timeString(minute) ..":".. timeString(second)
|
||
end
|
||
|
||
--- Returns the real date as a String
|
||
-- @return date in format: YYYY-MM-DD
|
||
function M.GetRealDateString()
|
||
local year, month, day, _, _, _ = M.GetRealDate()
|
||
local timeString =
|
||
function(int)
|
||
if int < 10 then
|
||
return "0"..int
|
||
end
|
||
return ""..int
|
||
end
|
||
return timeString(year) .."-".. timeString(month) .."-".. timeString(day)
|
||
end
|
||
|
||
--- Returns the real time as a String
|
||
-- @return time in format: hh:mm:ss
|
||
function M.GetRealTimeString()
|
||
local _, _, _, hour, minute, second = M.GetRealDate()
|
||
local timeString =
|
||
function(int)
|
||
if int < 10 then
|
||
return "0"..int
|
||
end
|
||
return ""..int
|
||
end
|
||
return timeString(hour) ..":".. timeString(minute) ..":".. timeString(second)
|
||
end
|
||
|
||
--- Converts the unix timestamp to a real date (GMT 0)
|
||
-- @return year
|
||
-- @return month
|
||
-- @return day
|
||
-- @return hour
|
||
-- @return minute
|
||
-- @return second
|
||
function M.GetRealDate()
|
||
local timestamp = world:getTime("unix")
|
||
|
||
local year, month, day, hour, minute, second, tmp
|
||
year = math.floor(timestamp / 31557600) -- (365.25*24*60*60)
|
||
local leapDays = math.floor( (year+1) / 4 ) -- without the current year
|
||
timestamp = timestamp - (year*365 + leapDays)*86400 -- 24*60*60
|
||
|
||
local leapYear = 0
|
||
if (year % 4) == 2 then
|
||
leapYear = 1 -- this year is a leap year
|
||
end
|
||
year = year + 1970 -- unix time starts there
|
||
|
||
local dayList = {0,31,59,90,120,151,181,212,243,273,304,334}
|
||
tmp = math.floor(timestamp / 86400) -- days so far this year
|
||
month = 1
|
||
for i=12,2,-1 do
|
||
local check = tmp-dayList[i]
|
||
if i>2 then
|
||
check = check - leapYear
|
||
end
|
||
if check > 0 then
|
||
month = i
|
||
break
|
||
end
|
||
end
|
||
tmp = dayList[month] -- days without current month
|
||
if month > 2 then
|
||
tmp = tmp + leapYear
|
||
end
|
||
timestamp = timestamp - ((tmp-1) * 86400) -- days this month (in seconds)
|
||
day = math.floor(timestamp / 86400)
|
||
timestamp = timestamp - (day * 86400)
|
||
hour = math.floor(timestamp / 3600)
|
||
timestamp = timestamp - (hour * 3600)
|
||
minute = math.floor(timestamp / 60)
|
||
second = timestamp - (minute * 60)
|
||
return year, month, day, hour, minute, second
|
||
end
|
||
|
||
-- Returns an NPC in case it is found within a given range of a give position
|
||
-- @param centerPosition Position structure that is the center of the search area.
|
||
-- @param radius Radius within which is searched
|
||
-- @param npcName Name of the npc to search for
|
||
-- @return Returns the NPC in case it was found else false
|
||
function M.getNpc(centerPosition, radius, npcName)
|
||
|
||
local npcs = world:getNPCSInRangeOf(centerPosition, radius)
|
||
for _, candidateNpc in pairs(npcs) do
|
||
if (npcName and candidateNpc.name == npcName) then
|
||
return candidateNpc
|
||
end
|
||
end
|
||
|
||
return false
|
||
|
||
end
|
||
|
||
function M.PositionToText(pos)
|
||
if pos ~= nil then
|
||
local retString = "("..pos.x..","..pos.y..","..pos.z..")"
|
||
return retString
|
||
end
|
||
return "nil","nil","nil"
|
||
end
|
||
|
||
--[[
|
||
\fn: IsInList
|
||
\brief: returns true if an valueChecked is in the valueList
|
||
|
||
\attention: valueList must be a list!
|
||
]]
|
||
function M.isInList(valueChecked, valueList)
|
||
if valueChecked == nil then
|
||
return false
|
||
end
|
||
if type(valueList) ~= "table" then
|
||
valueList = {valueList}
|
||
end
|
||
for _, value in pairs(valueList) do
|
||
if valueChecked == value then
|
||
return true
|
||
end
|
||
end
|
||
return false
|
||
end
|
||
|
||
--[[
|
||
\fn: posInList
|
||
\brief: returns position of an valueChecked in the valueList
|
||
|
||
\attention: valueList must be a list!
|
||
]]
|
||
function M.posInList(valueChecked, valueList)
|
||
if valueChecked == nil then
|
||
return 0
|
||
end
|
||
if type(valueList) ~= "table" then
|
||
return 0
|
||
end
|
||
for i, value in pairs(valueList) do
|
||
if valueChecked == value then
|
||
return i
|
||
end
|
||
end
|
||
return 0
|
||
end
|
||
|
||
--[[
|
||
\fn: getOneOutOfList
|
||
\brief: returns one random row out of valueList
|
||
if valueList isn't a table, valueList is returned
|
||
]]
|
||
function M.getOneOutOfList(valueList)
|
||
if type(valueList) ~= "table" then
|
||
return valueList
|
||
end
|
||
local tmpList = {}
|
||
for i, row in pairs(valueList) do
|
||
table.insert(tmpList, i)
|
||
end
|
||
return valueList[math.random(1,#tmpList)]
|
||
end
|
||
|
||
--[[
|
||
\fn: ListToNumber
|
||
\brief: transforms a number list into a number sequence which can be stored for example as quest status
|
||
\usage: list = {5,2,7,9,1};
|
||
ListToNumber(list) returns following number: 52791
|
||
|
||
@param NumberList - the number list that shall be transformed into a number
|
||
@return result - the list as number
|
||
|
||
\attention: Only Lists holding elements greater 1 are allowed!
|
||
]]
|
||
function M.ListToNumber(NumberList)
|
||
|
||
local result=NumberList[1]
|
||
|
||
for i = 2, #NumberList do
|
||
|
||
result = result ..NumberList[i]
|
||
|
||
|
||
end
|
||
result = tonumber(result) --convert the string result into a number
|
||
return result
|
||
|
||
end
|
||
|
||
--[[
|
||
\fn: Shuffle
|
||
\brief: Shuffles the elements of a list (with consecutively number indices, no key strings!) using the modern Fisher-Yates algorithm.
|
||
\usage: list = {5,2,7,9,1};
|
||
list = Shuffle(list) shuffles the list and could return: list ={1,7,5,9,2}
|
||
|
||
@param List The list that shall be shuffled
|
||
@return The shuffled list
|
||
]]
|
||
function M.Shuffle(List)
|
||
local minIndex = 1
|
||
local maxIndex = #List
|
||
if (List[0] ~= nil) then -- check if zero index is used
|
||
minIndex = 0
|
||
maxIndex = maxIndex - 1
|
||
end
|
||
for i = maxIndex, minIndex+1, -1 do -- shuffle all elements
|
||
local j = math.random(minIndex, i)
|
||
List[i], List[j] = List[j], List[i]
|
||
end
|
||
return List
|
||
end
|
||
|
||
--[[
|
||
\fn: CreateRandomNumberList
|
||
\brief: creates a random number list with AmntElements elements
|
||
\usage: list = CreateRandomNumberList(6, 1, 4);
|
||
returned list could hold: list = {3,1,4,2,2,4}
|
||
|
||
@param AmntElements - the amount of elements the list shall have
|
||
@param minval - the smallest value an element can have
|
||
@param maxval - the highest value an element can have
|
||
@return reslist - a list holding AmntElements elements with values between minval and maxval
|
||
]]
|
||
function M.CreateRandomNumberList(AmntElements, minval, maxval)
|
||
|
||
local reslist = {}
|
||
|
||
for i = 1, AmntElements do
|
||
|
||
reslist[i] = math.random(minval, maxval)
|
||
|
||
if (reslist[i] == reslist[i-1]) then -- same number like before, try getting a new number
|
||
reslist[i] = math.random(minval, maxval)
|
||
end
|
||
|
||
end
|
||
|
||
return reslist
|
||
end
|
||
|
||
--[[Searches the Online List for a Player by name
|
||
if a player was found it returns: true, Char Struct
|
||
-- if not, nil]]
|
||
|
||
function M.CheckIfOnline(playername)
|
||
local playerlist = world:getPlayersOnline()
|
||
|
||
for i = 1, #(playerlist) do
|
||
if playerlist[i].name == playername then
|
||
return playerlist[i]
|
||
end
|
||
end
|
||
return nil
|
||
end
|
||
|
||
--- Looks up the name of the defined lead attribute of a given skill name.
|
||
-- @param Skill The name of the skill.
|
||
-- @return String The name of the corresponding lead attribute.
|
||
-- NOTE: in case there is no lead attribute, nil will be returned.
|
||
local leadAttribTable = {}
|
||
--Dexterity: All crafting skills for final products
|
||
leadAttribTable[Character.tailoring]= {first = "intelligence", second = "dexterity"}
|
||
leadAttribTable[Character.blacksmithing]= {first = "constitution", second = "dexterity"}
|
||
leadAttribTable[Character.gemcutting]= {first = "essence", second = "dexterity"}
|
||
leadAttribTable[Character.carpentry]= {first = "willpower", second = "dexterity"}
|
||
leadAttribTable[Character.cookingAndBaking]= {first = "perception", second = "dexterity"}
|
||
leadAttribTable[Character.finesmithing]= {first = "essence", second = "dexterity"}
|
||
leadAttribTable[Character.glassBlowing]= {first = "willpower", second = "dexterity"}
|
||
leadAttribTable[Character.pottery]= {first = "agility", second = "dexterity"}
|
||
leadAttribTable[Character.armourer]= {first = "strength", second = "dexterity"}
|
||
leadAttribTable[Character.brewing]= {first = "perception", second = "dexterity"}
|
||
|
||
--Dexterity: Instruments
|
||
leadAttribTable[Character.harp]="dexterity"
|
||
leadAttribTable[Character.horn]="dexterity"
|
||
leadAttribTable[Character.flute]="dexterity"
|
||
leadAttribTable[Character.lute]="dexterity"
|
||
leadAttribTable[Character.panpipe]="dexterity"
|
||
leadAttribTable[Character.clavichord]="dexterity"
|
||
|
||
--Constitution: All gathering skills have it as their second attrib, with varying first attribs
|
||
leadAttribTable[Character.herblore]= {first = "perception", second = "constitution"}
|
||
leadAttribTable[Character.mining]= {first = "strength", second = "constitution"}
|
||
leadAttribTable[Character.fishing]= {first = "intelligence", second = "constitution"}
|
||
leadAttribTable[Character.farming]= {first = "dexterity", second = "constitution"}
|
||
leadAttribTable[Character.woodcutting]= {first = "agility", second = "constitution"}
|
||
leadAttribTable[Character.tanningAndWeaving]= {first = "willpower", second = "constitution"}
|
||
leadAttribTable[Character.husbandry]= {first = "essence", second = "constitution"}
|
||
leadAttribTable[Character.digging]= {first = "strength", second = "constitution"}
|
||
leadAttribTable[Character.natureResistance]= {first = "willpower", second = "constitution"}
|
||
|
||
--Agility: Defensive fighting skills
|
||
leadAttribTable[Character.parry]="agility"
|
||
leadAttribTable[Character.heavyArmour]="agility"
|
||
leadAttribTable[Character.mediumArmour]="agility"
|
||
leadAttribTable[Character.lightArmour]="agility"
|
||
|
||
--Perception: Archery, poison weapons and druids
|
||
leadAttribTable[Character.distanceWeapons]="perception"
|
||
leadAttribTable[Character.poisoning]="perception"
|
||
leadAttribTable[Character.witherweave]="perception"
|
||
leadAttribTable[Character.bloomweave]="perception"
|
||
leadAttribTable[Character.deepweave]="perception"
|
||
leadAttribTable[Character.stoneweave]="perception"
|
||
leadAttribTable[Character.wildweave]="perception"
|
||
|
||
--Strength: Offensive fighting skills
|
||
leadAttribTable[Character.slashingWeapons]="strength"
|
||
leadAttribTable[Character.wrestling]="strength"
|
||
leadAttribTable[Character.concussionWeapons]="strength"
|
||
leadAttribTable[Character.punctureWeapons]="strength"
|
||
|
||
--Essence: alchemy and enchanting
|
||
leadAttribTable[Character.alchemy]= {first = "essence", second = "perception"}
|
||
leadAttribTable[Character.enchanting]= {first = "essence", second = "intelligence"}
|
||
|
||
--Intelligence: Magic and transmutation
|
||
|
||
leadAttribTable[Character.transmutation]= {first = "intelligence", second = "perception"}
|
||
leadAttribTable[Character.fireMagic]="intelligence"
|
||
leadAttribTable[Character.spiritMagic]="intelligence"
|
||
leadAttribTable[Character.windMagic]="intelligence"
|
||
leadAttribTable[Character.earthMagic]="intelligence"
|
||
leadAttribTable[Character.waterMagic]="intelligence"
|
||
leadAttribTable[Character.spatialMagic]= {first = "willpower", second = "intelligence"}
|
||
|
||
--Willpower: Priests and magic resistance
|
||
leadAttribTable[Character.blessing]="willpower"
|
||
leadAttribTable[Character.praying]="willpower"
|
||
leadAttribTable[Character.vowing]="willpower"
|
||
leadAttribTable[Character.confessing]="willpower"
|
||
leadAttribTable[Character.ceremony]="willpower"
|
||
leadAttribTable[Character.consecrateWeapons]="willpower"
|
||
leadAttribTable[Character.consecrateArmours]="willpower"
|
||
leadAttribTable[Character.magicResistance]="willpower"
|
||
|
||
function M.GetLeadAttributeName(Skill)
|
||
return leadAttribTable[Skill]
|
||
end
|
||
|
||
--- Looks up the lead attribute value of a given skill name.
|
||
-- @param Skill The name of the skill.
|
||
-- @param user The character whose attributes are evaluated
|
||
-- @return The value of the corresponding lead attribute.
|
||
-- NOTE: in case there is no lead attribute, 10 will be returned.
|
||
|
||
function M.GetLeadAttrib(user, Skill)
|
||
|
||
local leadAttribName = M.GetLeadAttributeName(Skill)
|
||
|
||
|
||
if leadAttribName ~= nil then
|
||
|
||
if type(leadAttribName) == "table" then --Currently only crafts/gathering have two attribs instead of one
|
||
local leadAttribValue1 = user:increaseAttrib(leadAttribName.first, 0) * 0.6 -- 60% of the impact dex had on its own in the past
|
||
local leadAttribValue2 = user:increaseAttrib(leadAttribName.second, 0) * 0.4 -- 40% of the impact dex had on its own in the past
|
||
return leadAttribValue1 + leadAttribValue2
|
||
else
|
||
return user:increaseAttrib(leadAttribName, 0)
|
||
end
|
||
|
||
end
|
||
|
||
return 10 --10 should be default
|
||
|
||
end
|
||
|
||
--- Calculates an universal attribute bonus
|
||
-- Recommendation: Use together with GetLeadAttrib to calculate the bonus of a lead attribute on a skill related action
|
||
-- @param attributeValue The value of the attribute
|
||
-- @param range The range of the bonus (+/- range)
|
||
-- @return The value of the bonus
|
||
-- NOTE: 0 will be returned if something goes wrong
|
||
|
||
function M.GetAttributeBonus(attributeValue, range, skillName, user)
|
||
|
||
if skillName then -- In this case, attributeValue is left blank and skillName/user is instead filled in to fetch the lead attribute on its own
|
||
local leadAttribName = M.GetLeadAttributeName(skillName)
|
||
if type(leadAttribName) == "table" then --Currently only crafts/gathering have two attribs instead of one
|
||
local leadAttribValue1 = user:increaseAttrib(leadAttribName.first, 0) * 0.6 -- 60% of the impact dex had on its own in the past
|
||
local leadAttribValue2 = user:increaseAttrib(leadAttribName.second, 0) * 0.4 -- 40% of the impact dex had on its own in the past
|
||
attributeValue = leadAttribValue1 + leadAttribValue2
|
||
else
|
||
attributeValue = user:increaseAttrib(leadAttribName, 0)
|
||
end
|
||
end
|
||
|
||
local bonus
|
||
if attributeValue ~= nil and attributeValue ~= 0 and range < 1 and range > 0 then
|
||
bonus=range * ((attributeValue-1) / 9.5) - range -- +/- range for attributes 1-20. Bonus capped at attribute 30. Neutral attribute (bonus = 0) at 10.5
|
||
else
|
||
bonus=0 --default
|
||
end
|
||
|
||
return bonus
|
||
|
||
end
|
||
|
||
function M.GetAttributeBonusLow(attributeValue, skillName, user)
|
||
return M.GetAttributeBonus(attributeValue, 0.1, skillName, user)
|
||
end
|
||
|
||
function M.GetAttributeBonusMedium(attributeValue, skillName, user)
|
||
return M.GetAttributeBonus(attributeValue, 0.2, skillName, user)
|
||
end
|
||
|
||
function M.GetAttributeBonusHigh(attributeValue, skillName, user)
|
||
return M.GetAttributeBonus(attributeValue, 0.5, skillName, user)
|
||
end
|
||
|
||
--- Calculates an universal quality bonus
|
||
-- @param item The item for which the bonus shall be calculated
|
||
-- @param range The range of the bonus (+/- range)
|
||
-- @return The value of the bonus
|
||
-- NOTE: 0 will be returned if something goes wrong
|
||
|
||
function M.GetQualityBonus(item, range)
|
||
|
||
local bonus
|
||
local quality
|
||
if item ~= nil and item.quality ~= 0 and item.quality < 1000 and range < 1 and range > 0 then
|
||
quality = math.floor(item.quality/100)
|
||
bonus=range * ((quality-1) / 4) - range -- +/- range for quality 1-9. Neutral quality (bonus = 0) at 5
|
||
else
|
||
bonus=0 --default
|
||
end
|
||
|
||
return bonus
|
||
|
||
end
|
||
|
||
function M.GetQualityBonusStandard(item)
|
||
return M.GetQualityBonus(item, 0.1)
|
||
end
|
||
|
||
--- Searches in an area for an item id.
|
||
-- @param CenterPos Position struct of the center position of the area.
|
||
-- @param ItemId The ID of the sought item.
|
||
-- @param Radius Around the CenterPos, an area of +/-Radius is queried in x and y direction.
|
||
-- Default value is 1.
|
||
-- @param OnlyWriteable Bool that defines if only writeable items should be returned.
|
||
-- Default value is false.
|
||
-- @return scrItem If an item with ItemId is found, this is the first one found. Otherwise it is nil.
|
||
-- @return bool Writeable flag. If there are multiple items on the field, only the top one is writeable.
|
||
-- If none is found, nil is returned.
|
||
function M.GetItemInArea(CenterPos, ItemId, Radius, OnlyWriteable, itemData)
|
||
|
||
if (Radius == nil) then
|
||
Radius = 1
|
||
end
|
||
|
||
if (OnlyWriteable == nil) then
|
||
OnlyWriteable = false
|
||
end
|
||
|
||
for x=-Radius,Radius do
|
||
for y=-Radius,Radius do
|
||
|
||
local field = world:getField(position(CenterPos.x + x, CenterPos.y + y, CenterPos.z))
|
||
|
||
if field ~= nil then
|
||
|
||
local itemCount = field:countItems()
|
||
|
||
if (itemCount > 0) then
|
||
|
||
if (OnlyWriteable) then
|
||
itemCount = 1
|
||
end
|
||
|
||
for i=0,itemCount-1 do
|
||
local item = field:getStackItem(i)
|
||
if (item.id == ItemId) then
|
||
item.pos.x = CenterPos.x + x
|
||
item.pos.y = CenterPos.y + y
|
||
item.pos.z = CenterPos.z
|
||
|
||
if itemData then
|
||
local dataValue = item:getData(itemData.key)
|
||
|
||
if dataValue == itemData.value then
|
||
|
||
return item, (i==0)
|
||
end
|
||
else
|
||
return item, (i==0)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
return nil, nil
|
||
end
|
||
|
||
-- Checks if a given position is located in the prison mine.
|
||
-- @param posStruct Pos The position to check.
|
||
-- @return bool True if position is located in Prison mine, false otherwise.
|
||
function M.isInPrison(Pos)
|
||
if (Pos.z == -40) then
|
||
return true
|
||
end
|
||
return false
|
||
end
|
||
|
||
--- Convert a RGB colour to a HSV color.
|
||
-- @param red the red colour component in a range from 0 to 255
|
||
-- @param green the green colour component in a range from 0 to 255
|
||
-- @param blue the blue colour component in a range from 0 to 255
|
||
-- @return hue, saturation, value colour components. Hue in a range from 0<> to 360<36>. Saturation and value in a range
|
||
-- from 0 to 1
|
||
function M.RGBtoHSV(red, green, blue)
|
||
local max = math.max(red, green, blue)
|
||
local min = math.min(red, green, blue)
|
||
if max > 255 or min < 0 then
|
||
error("The colour values are rgb to hsv conversation are out of bounds.")
|
||
end
|
||
|
||
local delta = max - min
|
||
local hue
|
||
if delta == 0 then
|
||
hue = 0
|
||
elseif max == red and green >= blue then
|
||
hue = 60 * (green - blue) / delta
|
||
elseif max == red and green < blue then
|
||
hue = 60 * (green - blue) / delta + 360
|
||
elseif max == green then
|
||
hue = 60 * (blue - red) / delta + 120
|
||
elseif max == blue then
|
||
hue = 60 * (red - green) / delta + 240
|
||
end
|
||
|
||
local saturation
|
||
if max == 0 then
|
||
saturation = 0
|
||
else
|
||
saturation = 1 - min/max
|
||
end
|
||
|
||
return hue, saturation, max / 255
|
||
end
|
||
|
||
--- Convert a HSV colour value to a RGB colour value.
|
||
-- @param hue The hue value in degrees. Values larger then 360<36> are wrapped until they fit the range from 0<> to 360<36>
|
||
-- @param saturation The saturation value in a range from 0 to 1
|
||
-- @param value The colour value in a range from 0 to 1
|
||
-- @return red, green, blue colour value in a range from 0 to 255 as integer value
|
||
function M.HSVtoRGB(hue, saturation, value)
|
||
local realHue = hue % 360
|
||
if saturation < 0 or saturation > 1 then
|
||
error("Saturation is out of bounds: " + saturation)
|
||
end
|
||
if value < 0 or value > 1 then
|
||
error("Saturation is out of bounds: " + value)
|
||
end
|
||
|
||
local c = saturation * value
|
||
local x = c * (1 - math.abs(((realHue / 60) % 2) - 1))
|
||
local m = value - c
|
||
local red = 0
|
||
local green = 0
|
||
local blue = 0
|
||
if realHue >= 0 and realHue < 60 then
|
||
red = c
|
||
green = x
|
||
elseif realHue >= 60 and realHue < 120 then
|
||
red = x
|
||
green = c
|
||
elseif realHue >= 120 and realHue < 180 then
|
||
green = c
|
||
blue = x
|
||
elseif realHue >= 180 and realHue < 240 then
|
||
green = x
|
||
blue = c
|
||
elseif realHue >= 240 and realHue < 300 then
|
||
red = x
|
||
blue = c
|
||
elseif realHue >= 300 and realHue < 360 then
|
||
red = c
|
||
blue = x
|
||
end
|
||
|
||
local function fixValue(v)
|
||
return math.floor((v + m) * 255 + 0.5)
|
||
end
|
||
|
||
return fixValue(red), fixValue(green), fixValue(blue)
|
||
end
|
||
|
||
-- Checks wether an item has a special name, description or the specialItem data set
|
||
-- @param the item to be checked
|
||
-- @return bool True if it is special, else false
|
||
function M.isSpecialItem(item)
|
||
if item:getData("nameEn") ~= "" or item:getData("descriptionEn") ~= "" or item:getData("specialItem") ~= "" then
|
||
return true
|
||
end
|
||
return false
|
||
end
|
||
|
||
--[[Binary functions
|
||
Checks whether bit n is set or not
|
||
bitN must be a number in between 1 and 15 (limit 16 bit integer)
|
||
@return bool: true if set
|
||
]]--
|
||
function M.isBitSet(checkedValue, bitN)
|
||
local bitNumber = tonumber(bitN)
|
||
local valueNumber = tonumber(checkedValue)
|
||
if bitNumber == nil or valueNumber == nil then
|
||
return false
|
||
end
|
||
if bitNumber < 1 or bitNumber > 15 then
|
||
return false
|
||
end
|
||
local bitValue = math.pow( 2, bitNumber - 1 )
|
||
if bit32.band(valueNumber, bitValue) == 0 then
|
||
return false
|
||
end
|
||
return true
|
||
end
|
||
|
||
--[[Binary functions
|
||
Set bit n
|
||
bitN must be a number in between 1 and 15 (limit 16 bit integer)
|
||
setBit must be a number
|
||
@return int: setValue
|
||
]]--
|
||
function M.addBit(setValue, bitN)
|
||
local bitNumber = tonumber(bitN)
|
||
local valueNumber = tonumber(setValue)
|
||
if bitNumber == nil or valueNumber == nil then
|
||
return nil
|
||
end
|
||
if bitNumber < 1 or bitNumber > 15 then
|
||
return valueNumber
|
||
end
|
||
if not M.isBitSet(valueNumber, bitNumber) then
|
||
valueNumber = valueNumber + math.pow( 2, bitNumber - 1 )
|
||
end
|
||
return valueNumber
|
||
end
|
||
|
||
--[[Binary functions
|
||
Remove bit n
|
||
bitN must be a number in between 1 and 15 (limit 16 bit integer)
|
||
setBit must be a number
|
||
@return int: setValue
|
||
]]--
|
||
function M.removeBit(setValue, bitN)
|
||
local bitNumber = tonumber(bitN)
|
||
local valueNumber = tonumber(setValue)
|
||
if bitNumber == nil or valueNumber == nil then
|
||
return nil
|
||
end
|
||
if bitNumber < 1 or bitNumber > 15 then
|
||
return valueNumber
|
||
end
|
||
if M.isBitSet(valueNumber, bitNumber) then
|
||
valueNumber = valueNumber - math.pow( 2, bitNumber - 1 )
|
||
end
|
||
return valueNumber
|
||
end
|
||
|
||
--[[Binary functions
|
||
Returns the number of set bits.
|
||
setBit must be a number
|
||
@return int: number of bits = 1
|
||
]]--
|
||
function M.countBit(checkedValue)
|
||
local bitNumber = 1
|
||
local valueNumber = tonumber(checkedValue)
|
||
local countBit = 0
|
||
if valueNumber == nil then
|
||
return 0
|
||
end
|
||
for i=1 , 15 do
|
||
if bit32.band(valueNumber, bitNumber) > 0 then
|
||
countBit = countBit + 1
|
||
end
|
||
bitNumber = bitNumber * 2
|
||
end
|
||
return countBit
|
||
end
|
||
|
||
--[[Item name
|
||
Considers special item names
|
||
@return string]]--
|
||
function M.getItemName(item, lang)
|
||
local specialname = ( lang == Player.german and item:getData("nameDe") or item:getData("nameEn") )
|
||
if ( M.IsNilOrEmpty(specialname) ) then
|
||
return world:getItemName( item.id, lang )
|
||
else
|
||
return specialname
|
||
end
|
||
end
|
||
|
||
--[[warp a character back
|
||
@return: real distance (0 if not warped)]]--
|
||
function M.pushBack(user, distance, centerPos)
|
||
|
||
local directionX = 0
|
||
local directionY = 0
|
||
local xDiff = 0
|
||
local yDiff = 0
|
||
|
||
if user.pos.x > centerPos.x then
|
||
xDiff = user.pos.x - centerPos.x
|
||
directionX = 1
|
||
elseif user.pos.x < centerPos.x then
|
||
xDiff = centerPos.x - user.pos.x
|
||
directionX = -1
|
||
end
|
||
|
||
if user.pos.y > centerPos.y then
|
||
yDiff = user.pos.y - centerPos.y
|
||
directionY = 1
|
||
elseif user.pos.y < centerPos.y then
|
||
yDiff = centerPos.y - user.pos.y
|
||
directionY = -1
|
||
end
|
||
|
||
local distY = 0
|
||
local distX = 0
|
||
|
||
if yDiff >= xDiff/2 and xDiff >= yDiff/2 then -- diagonal pushback when they are both more or the same as half of the other
|
||
distY = directionY*(distance/2)
|
||
distX = directionX*(distance - (distance/2))
|
||
elseif yDiff >= xDiff/2 then -- Only pushback on y axis when X is less than half the Y diff
|
||
distY = directionY*distance
|
||
elseif xDiff >= yDiff/2 then -- same as above but x/y inverted
|
||
distX = directionX*distance
|
||
end
|
||
|
||
local posX = user.pos.x + distX
|
||
local posY = user.pos.y + distY
|
||
local posZ = user.pos.z
|
||
|
||
local targetPos = position(posX, posY, posZ)
|
||
|
||
local isNotBlocked = function(pos)
|
||
if world:getField(pos):isPassable() then
|
||
targetPos = pos
|
||
return true
|
||
else
|
||
return false
|
||
end
|
||
end
|
||
|
||
M.CreateLine(user.pos, position(posX,posY,posZ), isNotBlocked)
|
||
|
||
local realDistance = user:distanceMetricToPosition(targetPos)
|
||
user:warp(targetPos)
|
||
return realDistance
|
||
end
|
||
|
||
--[[Find items out of a list from inventory and bag
|
||
user: The cheched character
|
||
itemIdList: Table of item ID's or item ID
|
||
return: hasItem, returnList
|
||
hasItem: true if all items are in inventory
|
||
returnList: all found items
|
||
]]--
|
||
function M.userHasItems(user,itemIdList)
|
||
local idItem
|
||
local numberItem
|
||
local numberExists
|
||
local returnList = {}
|
||
local allItems = false
|
||
if type(itemIdList) ~= "table" then
|
||
itemIdList = {itemIdList}
|
||
end
|
||
if user:getType() == Character.player and #itemIdList > 0 then
|
||
allItems = true
|
||
for _, row in pairs(itemIdList) do
|
||
if type(row) == "table" then
|
||
idItem = row[1]
|
||
numberItem = row[2] or 1
|
||
else
|
||
idItem = row
|
||
numberItem = 1
|
||
end
|
||
local l = user:getItemList(idItem)
|
||
numberExists = 0
|
||
for _,i in pairs(l) do
|
||
numberExists = numberExists + i.number
|
||
table.insert(returnList,i)
|
||
end
|
||
if numberExists < numberItem then
|
||
allItems = false
|
||
end
|
||
end
|
||
end
|
||
return allItems, returnList
|
||
end
|
||
|
||
|
||
--[[ Drop a blood spot on the ground at a specified location.
|
||
@param Posi The location where the blood spot is placed]]--
|
||
function M.dropBlood(Posi, bloodWear)
|
||
local usedWear = bloodWear or 2
|
||
if world:isItemOnField(Posi) then
|
||
return --no blood on tiles with items on them!
|
||
end
|
||
local field = world:getField(Posi)
|
||
local tileId = field:tile()
|
||
if tileId == 6 or tileId == 0 or tileId == 34 then
|
||
return -- no blood on water and invisible tiles
|
||
end
|
||
|
||
local Blood = world:createItemFromId(3101, 1, Posi, true, 333, nil)
|
||
Blood.wear = usedWear
|
||
world:changeItem(Blood)
|
||
end
|
||
|
||
--[[ Drop some of blood. This function drops blood on every tile around the centerPos with a certain probability
|
||
@param centerPos: The center of the bloody area
|
||
@param: bloodProbability 0-1
|
||
@param: bllodWear Time the blood remains x 3 min
|
||
]]--
|
||
function M.dropSomeBlood(centerPos, bloodProbability, bloodWear)
|
||
local usedProbability = bloodProbability or 0.33
|
||
local usedWear = bloodWear or 1
|
||
local centerPosX = centerPos.x - 1
|
||
local centerPosY = centerPos.y - 1
|
||
local centerPosZ = centerPos.z
|
||
for i = 1, 3 do
|
||
for j = 1, 3 do
|
||
if math.random() < usedProbability then
|
||
M.dropBlood(position(centerPosX,centerPosY,centerPosZ), usedWear)
|
||
end
|
||
centerPosX = centerPosX + 1
|
||
end
|
||
centerPosX = centerPosX - 3
|
||
centerPosY = centerPosY + 1
|
||
end
|
||
end
|
||
|
||
--[[ Drop much of blood. This function drops blood on about every 3rd tile around the position set as center.
|
||
@param centerPos The center of the bloody area]]--
|
||
function M.dropMuchBlood(centerPos)
|
||
M.dropSomeBlood(centerPos, 1, 2)
|
||
end
|
||
|
||
--[[Counts player around a position including the levels -2, -1, +1, +2
|
||
center pos: center of search
|
||
range: range of search
|
||
@return: number of character]]--
|
||
function M.countPlayersInRangeOf(centerPos, range)
|
||
local users = world:getPlayersInRangeOf(centerPos, range)
|
||
local count=0
|
||
for _, user in pairs(users) do
|
||
count = count + 1
|
||
end
|
||
return count
|
||
end
|
||
|
||
--[[Direction hints]]--
|
||
--[[Translate a distance into text
|
||
distance: number of tiles
|
||
@return: textDe,textEn]]--
|
||
function M.getTextForDistance(distance)
|
||
distance = tonumber(distance)
|
||
if distance == nil then
|
||
return "unscharf", "diffuse"
|
||
elseif distance < 20 then
|
||
return "sehr nah", "very close"
|
||
elseif distance < 60 then
|
||
return "nah", "close"
|
||
elseif distance < 200 then
|
||
return "fern", "far"
|
||
elseif distance < 500 then
|
||
return "sehr fern", "very far"
|
||
else
|
||
return "<EFBFBD>u<EFBFBD>erst fern", "extremely far"
|
||
end
|
||
end
|
||
|
||
--[[Translate a direction into text
|
||
direction: direction value
|
||
@return: textDe,textEn]]--
|
||
function M.getTextForDirection(direction)
|
||
if direction == Character.dir_north then
|
||
return "Norden", "north"
|
||
elseif direction == Character.dir_northeast then
|
||
return "Nordosten", "northeast"
|
||
elseif direction == Character.dir_east then
|
||
return "Osten", "east"
|
||
elseif direction == Character.dir_southeast then
|
||
return "S<EFBFBD>dosten", "southeast"
|
||
elseif direction == Character.dir_south then
|
||
return "S<EFBFBD>den", "south"
|
||
elseif direction == Character.dir_southwest then
|
||
return "S<EFBFBD>dwesten", "southwest"
|
||
elseif direction == Character.dir_west then
|
||
return "Westen", "west"
|
||
elseif direction == Character.dir_northwest then
|
||
return "Nordwesten", "northwest"
|
||
else
|
||
return false, false
|
||
end
|
||
end
|
||
|
||
|
||
--[[Distance hint, a raw expectation where the target position might be
|
||
user: start position
|
||
targetPos: target position
|
||
@return structure with German and English distance and direction]]--
|
||
function M.getDistanceHint(user, targetPos)
|
||
if not targetPos then
|
||
return false
|
||
end
|
||
|
||
local metricDistance = user:distanceMetricToPosition(targetPos);
|
||
local dir = M.GetDirection(user.pos, targetPos);
|
||
|
||
local result = {}
|
||
result.direction = {}
|
||
result.direction.de, result.direction.en = M.getTextForDirection(dir)
|
||
result.distance = {}
|
||
result.distance.de, result.distance.en = M.getTextForDistance(metricDistance)
|
||
return result
|
||
end
|
||
|
||
|
||
--[[Number to text
|
||
number: integer 0-20
|
||
languag: Player language
|
||
cardinal: false or nil: number, true: cardinals
|
||
@return text]]--
|
||
function M.numberToText(number, language, cardinal)
|
||
local usedCardinal = cardinal or false
|
||
local numberTexts = {}
|
||
numberTexts[0] = {"kein", "no", "kein", "no"}
|
||
numberTexts[1] = {"ein", "one", "erste", "first"}
|
||
numberTexts[2] = {"zwei", "two", "zweite", "second"}
|
||
numberTexts[3] = {"drei", "three", "dritte", "third"}
|
||
numberTexts[4] = {"vier", "four", "vierte", "fourth"}
|
||
numberTexts[5] = {"f<EFBFBD>nf", "five", "f<EFBFBD>nfte", "fivth"}
|
||
numberTexts[6] = {"sechs", "six", "sechste", "sixth"}
|
||
numberTexts[7] = {"sieben", "seven", "siebente", "seventh"}
|
||
numberTexts[8] = {"acht", "eigth", "achte", "eighth"}
|
||
numberTexts[9] = {"neun", "nine", "neunte", "ninth"}
|
||
numberTexts[10] = {"zehn", "ten", "zehnte", "tenth"}
|
||
numberTexts[11] = {"elf", "eleven", "elfe", "eleventh"}
|
||
numberTexts[12] = {"zw<EFBFBD>lf", "twelf", "zw<EFBFBD>lfte", "twelfth"}
|
||
numberTexts[13] = {"dreizehn", "thirteen", "dreizehnte", "thirteenth"}
|
||
numberTexts[14] = {"vierzehn", "fourteen", "vierzehnte", "fourteenth"}
|
||
numberTexts[15] = {"f<EFBFBD>nfzehn", "fiveteen", "f<EFBFBD>nfzehnte", "fiveteenth"}
|
||
numberTexts[16] = {"sechzehn", "sixteen", "sechzehnte", "sixteenth"}
|
||
numberTexts[17] = {"siebzehn", "seventeen", "siebzehnte", "seventeenth"}
|
||
numberTexts[18] = {"achtzehn", "eightteen", "achtzehnte", "eightteenth"}
|
||
numberTexts[19] = {"neunzehn", "ninteen", "neunzehnte", "ninteenth"}
|
||
numberTexts[20] = {"zwanzig", "twenty", "zwanzigste", "twentieth"}
|
||
local inumber = tonumber(number)
|
||
if M.IsNilOrEmpty(inumber) then
|
||
inumber = 0
|
||
end
|
||
if inumber > 20 then
|
||
if usedCardinal == true then
|
||
return tostring(inumber) .. "."
|
||
else
|
||
return tostring(inumber)
|
||
end
|
||
else
|
||
if usedCardinal == true then
|
||
return (language == Player.german and numberTexts[inumber][3] or numberTexts[inumber][4])
|
||
else
|
||
return (language == Player.german and numberTexts[inumber][1] or numberTexts[inumber][2])
|
||
end
|
||
end
|
||
end
|
||
|
||
--[[Position to number
|
||
number: integer
|
||
position: related position]]--
|
||
function M.positionToNumber(pos)
|
||
local number = pos.x + 1000
|
||
number = number + (pos.y +1000) * 10000
|
||
number = number + (pos.z +100) * 10000000
|
||
return number
|
||
end
|
||
|
||
--[[Number to postion
|
||
number: integer
|
||
position: related position]]--
|
||
function M.numberToPosition(number)
|
||
number = tonumber(number)
|
||
if number == nil then
|
||
return position(0,0,0)
|
||
end
|
||
local posz = math.floor (number / 10000000 - 100)
|
||
number = number - ((posz +1000) * 100000000)
|
||
local posy = math.floor (number / 10000 - 1000)
|
||
number = number - ((posy +1000) * 10000)
|
||
local posx = math.floor (number - 1000)
|
||
return position(posx, posy, posz)
|
||
end
|
||
|
||
function M.isContainerFull(container, containerId)
|
||
|
||
local containerSlots = 100
|
||
|
||
if containerId == Item.wickerBasket then
|
||
containerSlots = 58
|
||
end
|
||
|
||
for i = 1, containerSlots do
|
||
local success = container:viewItemNr(i-1)
|
||
|
||
if not success then --No item was found in that slot
|
||
return false
|
||
end
|
||
|
||
end
|
||
|
||
return true
|
||
|
||
end
|
||
|
||
function M.moveItemToBackpack(user, theItem)
|
||
local backpack = user:getBackPack()
|
||
local backpackId = user:getItemAt(Character.backpack).id
|
||
|
||
if not backpack or M.isContainerFull(backpack, backpackId) then -- if there is no backpack or it is full, items fall on the ground instead
|
||
local itemStats = world:getItemStatsFromId(theItem.id)
|
||
user:inform("Da du keinen Platz im Rucksack hast, f<>llt dieser Gegenstand zu Boden: "..itemStats.German,"Having no backpack/no space in your backpack to put them in, the pieces of your broken "..itemStats.English.." fall to the ground.")
|
||
return false
|
||
end
|
||
|
||
world:erase(theItem, theItem.number)
|
||
backpack:insertItem(theItem)
|
||
|
||
return true
|
||
end
|
||
|
||
|
||
-- functions for storage of bitwise flags in quest IDs, see alchemy.teaching.playerInventedPotions for an example of its usage
|
||
|
||
local function questprogressToIndexedObject(qpg)
|
||
if qpg<0 then
|
||
return 2^31-1-qpg
|
||
else
|
||
return qpg
|
||
end
|
||
end
|
||
|
||
local function indexedObjectToQuestprogress(number)
|
||
if number < 2^31 then
|
||
return number
|
||
else
|
||
return 2^31- 1 - number
|
||
end
|
||
end
|
||
|
||
function M.writeBitwise(user, index, questIdStart)
|
||
|
||
local base = questIdStart + math.floor((index-1)/32)
|
||
|
||
local baseOffset = math.fmod(index-1,32)
|
||
|
||
local current = questprogressToIndexedObject(user:getQuestProgress(base))
|
||
|
||
user:setQuestProgress(base, indexedObjectToQuestprogress(bit32.bor(2^baseOffset, current)))
|
||
end
|
||
|
||
function M.readBitwise(user, index, questIdStart)
|
||
|
||
|
||
local base = questIdStart + math.floor((index-1)/32)
|
||
|
||
local baseOffset = math.fmod(index - 1, 32)
|
||
|
||
local current = questprogressToIndexedObject(user:getQuestProgress(base))
|
||
|
||
|
||
if bit32.btest(bit32.lshift(1, baseOffset), current) then
|
||
return true
|
||
end
|
||
|
||
return false
|
||
end
|
||
|
||
-- bitwise flag functions end
|
||
|
||
function M.getCharactersInRangeOfMultiplePositions(characterType, tableOfPosRange)
|
||
|
||
local charactersToReturn = {}
|
||
|
||
for _, posRange in pairs(tableOfPosRange) do
|
||
local characters
|
||
if characterType == "monsters" then
|
||
characters = world:getMonstersInRangeOf(posRange.pos, posRange.range)
|
||
elseif characterType == "players" then
|
||
characters = world:getPlayersInRangeOf(posRange.pos, posRange.range)
|
||
elseif characterType == "npcs" then
|
||
characters = world:getNPCSInRangeOf(posRange.pos, posRange.range)
|
||
else
|
||
characters = world:getCharactersInRangeOf(posRange.pos, posRange.range)
|
||
end
|
||
|
||
for _, character in pairs(characters) do
|
||
local alreadyFoundCharacter = false
|
||
for _, alreadyFound in pairs(charactersToReturn) do
|
||
if alreadyFound == character then
|
||
alreadyFoundCharacter = true
|
||
break
|
||
end
|
||
end
|
||
if not alreadyFoundCharacter then
|
||
table.insert(charactersToReturn, character)
|
||
end
|
||
end
|
||
end
|
||
|
||
return charactersToReturn
|
||
end
|
||
|
||
return M
|