315 lines
9.4 KiB
Lua
315 lines
9.4 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/>.
|
|
]]
|
|
local common = require("base.common")
|
|
local quests = require("monster.base.quests")
|
|
local hooks = require("monster.base.hooks")
|
|
local treasure = require("item.base.treasure")
|
|
local arena = require("base.arena")
|
|
local mugWithLid = require("item.id_310_mug_with_lid")
|
|
local magicPuzzle = require("item.magicSphere")
|
|
local character = require("base.character")
|
|
local M = {}
|
|
|
|
local killers = {}
|
|
|
|
local function _isNumber(value)
|
|
return type(value) == "number"
|
|
end
|
|
|
|
local function _isTable(value)
|
|
return type(value) == "table"
|
|
end
|
|
|
|
local function _isRange(value)
|
|
if not _isTable(value) then
|
|
return false
|
|
end
|
|
|
|
if _isNumber(value.min) and _isNumber(value.max) then
|
|
return true
|
|
elseif _isNumber(value[1]) and _isNumber(value[2]) then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
local function _getValueFromRange(value)
|
|
if not _isRange(value) then
|
|
error("This function requires a range.")
|
|
end
|
|
|
|
local min = value.min or value[1]
|
|
local max = value.max or value[2]
|
|
|
|
if min > max then
|
|
error("The minimal value of the range is larger then the maximal value.")
|
|
end
|
|
|
|
return math.random() * (max - min) + min
|
|
end
|
|
|
|
local function performRandomTalk(monster, msgs)
|
|
local langSkill = monster:getSkill(Character.commonLanguage)
|
|
if langSkill < 100 then
|
|
monster:increaseSkill(Character.commonLanguage, 100 - langSkill)
|
|
end
|
|
|
|
local germanMessage, englishMessage = msgs:getRandomMessage() --choses a random message
|
|
common.TalkNLS(monster, Character.say, germanMessage, englishMessage ) --does the talking in both languages
|
|
end
|
|
|
|
local function performRegeneration(monster)
|
|
if (math.random() < 0.3) and character.GetHP(monster) < 10000 then
|
|
local con = monster:increaseAttrib("constitution", 0)
|
|
local healAmount = 2 * con
|
|
character.ChangeHP(monster, healAmount)
|
|
end
|
|
end
|
|
|
|
local function reportAttack(monster, enemy)
|
|
killers[monster.id] = enemy
|
|
end
|
|
|
|
local function cleanupMonster(monster)
|
|
killers[monster.id] = nil
|
|
hooks.cleanHooks(monster)
|
|
end
|
|
|
|
local function reportMonsterDeath(monster)
|
|
local killer = killers[monster.id]
|
|
if killer ~= nil and (not isValidChar(killer) or not killer:isInRange(monster, 12)) then
|
|
killer = nil
|
|
end
|
|
|
|
if killer ~= nil and not arena.isArenaMonster(monster) then
|
|
quests.checkQuest(killer, monster)
|
|
end
|
|
hooks.executeOnDeath(monster, killer)
|
|
end
|
|
|
|
local function copyMergeTables(table1, table2)
|
|
local dataCopy = {}
|
|
for key, value in pairs(table1) do
|
|
dataCopy[key] = value
|
|
end
|
|
for key, value in pairs(table2) do
|
|
dataCopy[key] = value
|
|
end
|
|
return dataCopy
|
|
end
|
|
|
|
local function dropLootItem(monster, lootItemData)
|
|
local amount = math.random(lootItemData.minAmount, lootItemData.maxAmount)
|
|
local quality = math.random(lootItemData.minQuality, lootItemData.maxQuality)
|
|
local durability = math.random(lootItemData.minDurability, lootItemData.maxDurability)
|
|
|
|
local data = lootItemData.data
|
|
if lootItemData.itemId == 505 then
|
|
-- It's a treasure map! Populate it with a valid location.
|
|
local mapData = treasure.createMapData()
|
|
if _isTable(mapData) then
|
|
data = copyMergeTables(data, mapData)
|
|
end
|
|
|
|
elseif lootItemData.itemId == 310 then -- mug with lid; give it a proper collector's mug id
|
|
data = {mugId = mugWithLid.getRandomMugId()}
|
|
end
|
|
|
|
local createdItem = world:createItemFromId(lootItemData.itemId, amount, monster.pos, true, quality * 100 + durability, data)
|
|
createdItem.wear = 4
|
|
world:changeItem(createdItem)
|
|
end
|
|
|
|
local function dropLootCategory(monster, lootData)
|
|
local randomTry = math.random()
|
|
for _, itemInfo in pairs(lootData) do
|
|
if itemInfo.probability >= randomTry then
|
|
dropLootItem(monster, itemInfo)
|
|
return
|
|
else
|
|
randomTry = randomTry - itemInfo.probability
|
|
end
|
|
end
|
|
end
|
|
|
|
local function performDrop(monster)
|
|
if not arena.isArenaMonster(monster) and not hooks.isNoDrop(monster) and not magicPuzzle.isBunbun(monster) then
|
|
local loot = monster:getLoot()
|
|
|
|
for _, category in pairs(loot) do
|
|
dropLootCategory(monster, category)
|
|
end
|
|
end
|
|
end
|
|
|
|
function M.generateCallbacks(msgs)
|
|
local t = {}
|
|
|
|
function t.enemyNear(monster, _)
|
|
if math.random() < 3e-4 then --once each 5 minutes (3e-4) in average a message is spoken (is called very often)
|
|
performRandomTalk(monster, msgs)
|
|
end
|
|
return false
|
|
end
|
|
|
|
function t.enemyOnSight(monster, _)
|
|
performRegeneration(monster)
|
|
if math.random() < 3e-3 then --once each 5 minutes (3e-3) in average a message is spoken
|
|
performRandomTalk(monster, msgs)
|
|
end
|
|
return false
|
|
end
|
|
|
|
t.onAttacked = reportAttack
|
|
t.onCasted = reportAttack
|
|
|
|
function t.onDeath(monster)
|
|
performDrop(monster)
|
|
reportMonsterDeath(monster)
|
|
cleanupMonster(monster)
|
|
end
|
|
|
|
return t
|
|
end
|
|
|
|
M.SKIN_COLOR = 0
|
|
M.HAIR_COLOR = 1
|
|
|
|
function M.white()
|
|
return {255, 255, 255}
|
|
end
|
|
|
|
function M.setColor(params)
|
|
if not _isTable(params) then
|
|
error("The parameter for the setColor function are not set.")
|
|
end
|
|
|
|
if params.monster == nil or not isValidChar(params.monster) then
|
|
error("The referenced monster is not set or not valid.")
|
|
end
|
|
|
|
if not _isNumber(params.target) then
|
|
error("The required target value is not set.")
|
|
end
|
|
|
|
local colorTarget = tonumber(params.target)
|
|
|
|
if colorTarget ~= M.SKIN_COLOR and colorTarget ~= M.HAIR_COLOR then
|
|
error("The required target is not set to a valid target.")
|
|
end
|
|
|
|
local red, green, blue, alpha
|
|
if params.hue ~= nil and params.saturation ~= nil and params.value ~= nil then
|
|
-- HSV Mode
|
|
local hue, saturation, value
|
|
if _isRange(params.hue) then
|
|
hue = _getValueFromRange(params.hue)
|
|
elseif _isNumber(params.hue) then
|
|
hue = tonumber(params.hue)
|
|
else
|
|
error("Hue parameter contains a unexpected value.")
|
|
end
|
|
|
|
if _isRange(params.saturation) then
|
|
saturation = _getValueFromRange(params.saturation)
|
|
elseif _isNumber(params.saturation) then
|
|
saturation = tonumber(params.saturation)
|
|
else
|
|
error("Saturation parameter contains a unexpected value.")
|
|
end
|
|
|
|
if _isRange(params.value) then
|
|
value = _getValueFromRange(params.value)
|
|
elseif _isNumber(params.value) then
|
|
value = tonumber(params.value)
|
|
else
|
|
error("Value parameter contains a unexpected value.")
|
|
end
|
|
|
|
red, green, blue = common.HSVtoRGB(hue, saturation, value)
|
|
elseif params.red ~= nil and params.green ~= nil and params.blue ~= nil then
|
|
if _isRange(params.red) then
|
|
red = _getValueFromRange(params.red)
|
|
elseif _isNumber(params.red) then
|
|
red = tonumber(params.red)
|
|
else
|
|
error("Red parameter contains a unexpected value.")
|
|
end
|
|
|
|
if _isRange(params.green) then
|
|
green = _getValueFromRange(params.green)
|
|
elseif _isNumber(params.green) then
|
|
green = tonumber(params.green)
|
|
else
|
|
error("Green parameter contains a unexpected value.")
|
|
end
|
|
|
|
if _isRange(params.blue) then
|
|
blue = _getValueFromRange(params.blue)
|
|
elseif _isNumber(params.blue) then
|
|
blue = tonumber(params.blue)
|
|
else
|
|
error("Blue parameter contains a unexpected value.")
|
|
end
|
|
elseif _isTable(params.color) then
|
|
if _isNumber(params.color[1]) then
|
|
red = tonumber(params.color[1])
|
|
else
|
|
error("Color parameter is not a valid color.")
|
|
end
|
|
|
|
if _isNumber(params.color[2]) then
|
|
green = tonumber(params.color[2])
|
|
else
|
|
error("Color parameter is not a valid color.")
|
|
end
|
|
|
|
if _isNumber(params.color[3]) then
|
|
blue = tonumber(params.color[3])
|
|
else
|
|
error("Color parameter is not a valid color.")
|
|
end
|
|
else
|
|
error("No colour was set.")
|
|
end
|
|
|
|
if params.alpha ~= nil then
|
|
if _isRange(params.alpha) then
|
|
alpha = _getValueFromRange(params.alpha)
|
|
elseif _isNumber(params.alpha) then
|
|
alpha = tonumber(params.alpha)
|
|
else
|
|
error("Alpha parameter contains a unexpected value.")
|
|
end
|
|
else
|
|
alpha = 255
|
|
end
|
|
|
|
red = math.floor(red + 0.5)
|
|
green = math.floor(green + 0.5)
|
|
blue = math.floor(blue + 0.5)
|
|
alpha = math.floor(alpha + 0.5)
|
|
|
|
if colorTarget == M.SKIN_COLOR then
|
|
params.monster:setSkinColour(colour(red, green, blue, alpha))
|
|
else
|
|
params.monster:setHairColour(colour(red, green, blue, alpha))
|
|
end
|
|
end
|
|
|
|
return M
|