• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

[INCOMPLETE] Dynamic Combats

Mkalo

ボーカロイド
Senator
Joined
Jun 1, 2011
Messages
1,118
Solutions
55
Reaction score
946
Location
Japan
I won't have enough time to complete this lib, it was supposed to replace the Combat() and createCombatArea() functions as you can only use them at the start of the script, with this lib you would be able to modify the area and have more flexibility to damages, effects, area etc.

Well this works for somethings but it have bugs like:
Its iterating over all the tiles not having in mind if its reachable from the center or not.)
getCombatDamage is incomplete and its not using the parameter values, only the callback, it should look like this: https://github.com/otland/forgottenserver/blob/master/src/combat.cpp#L42-L92

So this is mainly for people to look/learn and maybe get something out of it:
Code:
function table.copy(t, out)
    out = out or {}
    if type(t) ~= "table" then
        return false
    end

    for i, x in pairs(t) do
        if type(x) == "table" then
            out[i] = {}
            table.copy(t[i], out[i])
        else
            out[i] = x
        end
    end
    return out
end

VARIANT_NONE = 0
VARIANT_NUMBER = 1
VARIANT_POSITION = 2
VARIANT_TARGETPOSITION = 3
VARIANT_STRING = 4

DynamicCombatArea = {}
DynamicCombat = {}
setmetatable(DynamicCombat,
{__call =
    function()
        return setmetatable({parameters = {COMBAT_PARAM_TYPE = COMBAT_NONE}}, {__index = DynamicCombat})
    end
})

setmetatable(DynamicCombatArea,
{__call =
    function(_, area)
        if type(area) ~= "table" then
            return false
        end
        return setmetatable({area = table.copy(area)}, {__index = DynamicCombatArea})
    end
})

function DynamicCombatArea:getCenter()
    if self.centerx and self.centery then
        return self.centerx, self.centery
    end

    for y = 1, #self.area do
        for x = 1, #self.area[y] do
            if self.area[y][x] == 3 then
                self.centerx = x
                self.centery = y
                return x, y
            end
        end
    end

    return false
end

function DynamicCombatArea:iterateArea(mapcenter, func, combat, caster, damage)
    local centerx, centery = self:getCenter()
    if not centerx then
        print("[Error - iterateArea] Can't find area center.")
        return false
    end

    for y = 1, #self.area do
        for x = 1, #self.area[y] do
            if self.area[y][x] == 1 or self.area[y][x] == 3 then
                local offsetx, offsety = x-centerx, y-centery
                func(combat, caster, Tile(mapcenter.x+offsetx, mapcenter.y+offsety, mapcenter.z), damage)
            end
        end
    end
end

function DynamicCombat:setParameter(param, value)
    self.parameters[param] = value
    return true
end

function DynamicCombat:setDamageCallback(callback)
    self.damagecallback = callback
    return true
end

function DynamicCombat:setArea(area)
    self.area = area
    return true
end

function DynamicCombat:getCombatDamage(caster, target)
    if self.damagecallback then
        return self.damagecallback(self, caster, target)
    end
    return -100, -200 -- This is incomplete only handled by callback
end

function DynamicCombat:doCombat(caster, param, damage)
    if not param then
        return false
    end

    if param.isCreature and param:isCreature() then
        local target = param
        local min, max
        if damage then
            min, max = unpack(damage)
        else
            min, max = self:getCombatDamage()
        end

        local func = doTargetCombatHealth
        if self.parameters[COMBAT_PARAM_TYPE] == COMBAT_MANADRAIN then
            func = doTargetCombatMana
        end
        if self.parameters[COMBAT_PARAM_DISTANCEEFFECT] then
            caster:getPosition():sendDistanceEffect(target:getPosition(), self.parameters[COMBAT_PARAM_DISTANCEEFFECT])
        end
        func(caster, target, self.parameters[COMBAT_PARAM_TYPE], min, max, self.parameters[COMBAT_PARAM_EFFECT] or CONST_ME_NONE)
    elseif type(param) == "table" and param.x and param.y and param.z then
        local min, max = self:getCombatDamage()
        self.area:iterateArea(param, DynamicCombat.doCombat, self, caster, {min, max})
    elseif param.isTile and param:isTile() then
        local tile = param
        local creatures = tile:getCreatures()
        if creatures and #creatures > 0 then
            for i = 1, #creatures do
                self:doCombat(caster, creatures[i], damage)
            end
        elseif self.parameters[COMBAT_PARAM_EFFECT] then
            tile:getPosition():sendMagicEffect(self.parameters[COMBAT_PARAM_EFFECT])
        end
    end
end

function DynamicCombat:execute(creature, variant)
    if variant.type == VARIANT_NUMBER then
        local target = Creature(variant.number)
        if not target then
            return false
        end

        if self.area then
            self:doCombat(creature, target:getPosition())
        else
            self:doCombat(creature, target)
        end
    end
end

Talkaction example:
Code:
local damageCallback = function(combat, caster, target)
    local v = math.random(100, 200)
    return -v, -v
end

local combat = DynamicCombat()
combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE)
combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_ICEAREA)
combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ICE)
combat:setDamageCallback(damageCallback)
local area = DynamicCombatArea(AREA_CIRCLE3X3)
combat:setArea(area)

function onSay(player)
    local target = player:getTarget()
    if target then
        combat:execute(player, {type=VARIANT_NUMBER, number=target:getId()})
    end
end

In this case i'm forcing variant to be targeted but in onCastSpell the variant would come in the callback and you would use that instead of that table.
 
Back
Top