-- Configuración general local config = { attackAnimationEffectIds = { {effectId = 253}, {effectId = 241}, {effectId = 247}, {effectId = 245} }, disappearEffectId = 244, attackAnimationTotalTick = 1200, attackAnimationDamageTime = 450, damage = {min = -100, max = -200}, extraDamagePerTarget = -50, cooldownStorage = 12345, cooldownTime = 2, offsetEffectId = 249, offsetEffectEnabled = false, jumpEffectId = 244, finalDamageEffectId = 248, finalDamageEffectOffset = { x = 1, y = 1, z = 0 }, attackAnimationEffectOffsets = { [253] = {x = 1, y = 1, z = 0}, [241] = {x = 1, y = 1, z = 0}, [247] = {x = 1, y = 1, z = 0}, [245] = {x = 3, y = 3, z = 0} }, maxTargets = 9, -- Número máximo de objetivos alcanzados effectsEnabled = { attackAnimation = true, disappear = true, jump = true, finalDamage = true, splashDamage = true }, elementalDamage = { target = { energy = {enabled = false, damage = {min = -50, max = -100}}, fire = {enabled = false, damage = {min = -60, max = -120}}, ice = {enabled = true, damage = {min = -40, max = -80}}, earth = {enabled = false, damage = {min = -70, max = -140}} }, around = { energy = {enabled = false, damage = {min = -50, max = -100}}, fire = {enabled = false, damage = {min = -60, max = -120}}, ice = {enabled = true, damage = {min = -40, max = -80}}, earth = {enabled = false, damage = {min = -70, max = -140}} } }, effectsAround = { enabled = true, effects = { {effectId = 243, offset = {x = 1, y = 1, z = 0}}, {effectId = 254, offset = {x = 0.5, y = 0.5, z = 0}} } }, splashDamageEffectId = 245, -- Efecto que desencadena el dano a las criaturas alrededor allowPlayerReposition = true, -- Permitir al jugador elegir la posición final } -- Define combat objects para diferentes etapas local combats = {} for i, effectConfig in ipairs(config.attackAnimationEffectIds) do local combat = createCombatObject() setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) setCombatParam(combat, COMBAT_PARAM_EFFECT, effectConfig.effectId) setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, config.damage.min, config.damage.max, config.damage.min, config.damage.max) table.insert(combats, combat) end -- Funciones auxiliares local function getDirectionToTarget(creature, target) local creaturePos, targetPos = creature:getPosition(), target:getPosition() if targetPos.x > creaturePos.x then return DIRECTION_EAST elseif targetPos.x < creaturePos.x then return DIRECTION_WEST elseif targetPos.y > creaturePos.y then return DIRECTION_SOUTH else return DIRECTION_NORTH end end local function getFrontPosition(target, direction) local targetPos = target:getPosition() if direction == DIRECTION_NORTH then return Position(targetPos.x, targetPos.y - 1, targetPos.z) elseif direction == DIRECTION_SOUTH then return Position(targetPos.x, targetPos.y + 1, targetPos.z) elseif direction == DIRECTION_EAST then return Position(targetPos.x + 1, targetPos.y, targetPos.z) else return Position(targetPos.x - 1, targetPos.y, targetPos.z) end end local function adjustEffectPosition(position) return Position(position.x + 1, position.y + 1, position.z) end local function isTileWalkable(position) local tile = Tile(position) if not tile or not tile:isWalkable() then return false end local items = tile:getItems() for _, item in ipairs(items) do if item and item.isBlocking and item:isBlocking() then return false end end return true end local function findFreeTileAround(position) local directions = { {x = 1, y = 0}, {x = -1, y = 0}, {x = 0, y = 1}, {x = 0, y = -1}, {x = 1, y = 1}, {x = -1, y = -1}, {x = 1, y = -1}, {x = -1, y = 1} } for _, dir in ipairs(directions) do local newPos = Position(position.x + dir.x, position.y + dir.y, position.z) if isTileWalkable(newPos) then return newPos end end return nil end local function sendEffect(position, effectId, effectEnabled) if effectEnabled then position:sendMagicEffect(effectId) end end local function handleOccupiedTile(creature, target, frontPos, originalPos) local occupant = Tile(frontPos):getTopCreature() if occupant then local freeTile = findFreeTileAround(frontPos) if freeTile then occupant:teleportTo(freeTile) else occupant:teleportTo(originalPos) sendEffect(originalPos, config.disappearEffectId, config.effectsEnabled.disappear) occupant:setOutfit({lookType = 0}) addEvent(function() if occupant:isRemoved() then return end local newFreeTile = findFreeTileAround(frontPos) if newFreeTile then occupant:teleportTo(newFreeTile) else occupant:teleportTo(frontPos) sendEffect(frontPos, config.disappearEffectId, config.effectsEnabled.disappear) occupant:setOutfit(creature:getOutfit()) end end, config.attackAnimationTotalTick) end end end local function preventLogout(creature) local condition = Condition(CONDITION_INFIGHT) condition:setParameter(CONDITION_PARAM_TICKS, config.attackAnimationTotalTick + 500) -- Prevent logout for the duration of the spell + buffer creature:addCondition(condition) end local function applyFightingCondition(creature) local condition = Condition(CONDITION_INFIGHT) condition:setParameter(CONDITION_PARAM_TICKS, config.attackAnimationTotalTick + 500) -- Apply fighting condition for the duration of the spell + buffer creature:addCondition(condition) end local function hasPlayerMoved(creature, originalPos) local currentPos = creature:getPosition() return currentPos.x ~= originalPos.x or currentPos.y ~= originalPos.y or currentPos.z ~= originalPos.z end local function jumpToFrontAndBack(creature, target, duration) if not config.effectsEnabled.jump then return end local originalPos = creature:getPosition() local direction = getDirectionToTarget(creature, target) local targetPos = target:getPosition() local frontPos = getFrontPosition(target, direction) local outfitBeforeJump = creature:getOutfit() local creatureId = creature:getId() local targetId = target:getId() applyFightingCondition(creature) if isTileWalkable(frontPos) then handleOccupiedTile(creature, target, frontPos, originalPos) -- Efecto y desaparición if config.effectsEnabled.disappear then sendEffect(creature:getPosition(), config.disappearEffectId, config.effectsEnabled.disappear) addEvent(function(creatureId) local creature = Creature(creatureId) if not creature then return end creature:setOutfit({lookType = 0}) -- Hace invisible al jugador end, 150, creatureId) end -- Teleport y aparición addEvent(function(creatureId) local creature = Creature(creatureId) if not creature then return end creature:setDirection(direction) creature:teleportTo(frontPos) sendEffect(frontPos, config.disappearEffectId, config.effectsEnabled.disappear) sendEffect(frontPos, config.jumpEffectId, config.effectsEnabled.jump) end, 200, creatureId) -- Retorno addEvent(function(creatureId) local creature = Creature(creatureId) if not creature then return end -- Efecto y desaparición antes de volver if config.effectsEnabled.disappear then sendEffect(frontPos, config.disappearEffectId, config.effectsEnabled.disappear) creature:setOutfit({lookType = 0}) end -- Teleport y aparición final addEvent(function(creatureId) local creature = Creature(creatureId) if not creature then return end local finalPos = originalPos if config.allowPlayerReposition and target:isRemoved() then if hasPlayerMoved(creature, originalPos) then finalPos = creature:getPosition() -- Usar la posición actual del jugador end end creature:teleportTo(finalPos) sendEffect(finalPos, config.disappearEffectId, config.effectsEnabled.disappear) if config.offsetEffectEnabled then sendEffect(Position(finalPos.x + 3, finalPos.y + 3, finalPos.z), config.offsetEffectId, config.effectsEnabled.jump) end sendEffect(finalPos, config.jumpEffectId, config.effectsEnabled.jump) sendEffect(finalPos, config.effectsEnabled.finalDamage) addEvent(function(creatureId) local creature = Creature(creatureId) if not creature then return end creature:setOutfit(outfitBeforeJump) creature:setDirection(direction) end, 250, creatureId) -- Reaparecer el outfit después de 250ms end, 100, creatureId) end, duration - 250, creatureId) else creature:sendCancelMessage("La posición frente al objetivo no es válida.") -- Teleport to the target's position if the front position is not walkable creature:teleportTo(targetPos) -- Ensure the creature returns to its original position addEvent(function(creatureId) local creature = Creature(creatureId) if not creature then return end creature:teleportTo(originalPos) sendEffect(originalPos, config.disappearEffectId, config.effectsEnabled.disappear) if config.offsetEffectEnabled then sendEffect(Position(originalPos.x + 3, originalPos.y + 3, originalPos.z), config.offsetEffectId, config.effectsEnabled.jump) end sendEffect(originalPos, config.jumpEffectId, config.effectsEnabled.jump) sendEffect(originalPos, config.effectsEnabled.finalDamage) addEvent(function(creatureId) local creature = Creature(creatureId) if not creature then return end creature:setOutfit(outfitBeforeJump) creature:setDirection(direction) end, 250, creatureId) -- Reaparecer el outfit después de 250ms end, duration - 250, creatureId) end end local function getTargetsAround(target, radius) if not target then return {} end local targets = {} local targetPos = target:getPosition() if not targetPos then return targets end local positions = {} for x = -radius, radius do for y = -radius, radius do if not (x == 0 and y == 0) then table.insert(positions, Position(targetPos.x + x, targetPos.y + y, targetPos.z)) end end end for _, pos in ipairs(positions) do local tile = Tile(pos) if tile then local creature = tile:getTopCreature() if creature then table.insert(targets, creature) end end end return targets end local function getTargetsAroundPosition(position, radius) local targets = {} for x = -radius, radius do for y = -radius, radius do if not (x == 0 and y == 0) then local pos = Position(position.x + x, position.y + y, position.z) local tile = Tile(pos) if tile then local creature = tile:getTopCreature() if creature and (creature:isMonster() or creature:isPlayer()) then table.insert(targets, creature) end end end end end return targets end local function getLimitedTargetsAround(targetOrPos, radius, maxTargets) local targets = {} local positions = {} local centerPos if type(targetOrPos) == "userdata" and targetOrPos.getPosition then -- Si es una criatura centerPos = targetOrPos:getPosition() else -- Si es una posición centerPos = targetOrPos end if not centerPos then return targets end for x = -radius, radius do for y = -radius, radius do if not (x == 0 and y == 0) then table.insert(positions, Position(centerPos.x + x, centerPos.y + y, centerPos.z)) end end end for _, pos in ipairs(positions) do if #targets >= maxTargets then break end local tile = Tile(pos) if tile then local creature = tile:getTopCreature() if creature then table.insert(targets, creature) end end end return targets end local function getLimitedTargetsAroundPosition(position, radius, maxTargets) local targets = {} for x = -radius, radius do for y = -radius, radius do if not (x == 0 and y == 0) then local pos = Position(position.x + x, position.y + y, position.z) local tile = Tile(pos) if tile then local creature = tile:getTopCreature() if creature and (creature:isMonster() or creature:isPlayer()) then table.insert(targets, creature) if #targets >= maxTargets then return targets end end end end end end return targets end -- Función principal de lanzamiento de hechizo function onCastSpell(creature, variant) if not creature then return false end if os.time() < creature:getStorageValue(config.cooldownStorage) then creature:sendCancelMessage("Debes esperar antes de usar esta magia nuevamente.") return false end local target = Creature(variant:getNumber()) if not target then return false end local direction = getDirectionToTarget(creature, target) creature:setDirection(direction) creature:setStorageValue(config.cooldownStorage, os.time() + config.cooldownTime) applyFightingCondition(creature) jumpToFrontAndBack(creature, target, config.attackAnimationTotalTick) local targetPos = target:getPosition() if not targetPos then return false end local offset = adjustEffectPosition(targetPos) local targetsAround = getTargetsAround(target, 1) local numTargets = #targetsAround local totalDamageDealt = 0 local comboMultiplier = 1 + (numTargets * 0.2) -- 20% más de dano por objetivo local creatureId = creature:getId() local targetId = target:getId() -- Secuencia de efectos y dano combo for i, effectConfig in ipairs(config.attackAnimationEffectIds) do local delay = config.attackAnimationDamageTime + (i - 1) * 200 addEvent(function(creatureId, targetId, lastKnownPos) local creature = Creature(creatureId) if not creature then return end local target = Creature(targetId) local currentPos = lastKnownPos if target then currentPos = target:getPosition() end local effectId = effectConfig.effectId local offset = config.attackAnimationEffectOffsets[effectId] local effectPosition = Position(currentPos.x + offset.x, currentPos.y + offset.y, currentPos.z + offset.z) sendEffect(effectPosition, effectId, config.effectsEnabled.attackAnimation) -- Asegurar que el dano sea negativo local comboDamage = { min = math.min(config.damage.min * comboMultiplier * i, -1), max = math.min(config.damage.max * comboMultiplier * i, -1) } -- Dano principal al objetivo if target and not target:isRemoved() then local damageDealt = doTargetCombatHealth(creature:getId(), target, COMBAT_PHYSICALDAMAGE, comboDamage.min, comboDamage.max, CONST_ME_NONE) if type(damageDealt) == "number" then totalDamageDealt = totalDamageDealt + math.abs(damageDealt) end -- Verificar si el objetivo sigue con vida if target:getHealth() <= 0 then lastKnownPos = target:getPosition() target:remove() return -- Salir si el objetivo muere end -- Dano elemental al objetivo for damageType, elementalConfig in pairs(config.elementalDamage.target) do if elementalConfig.enabled then local elementalDamage = { min = math.min(elementalConfig.damage.min * comboMultiplier * i, -1), max = math.min(elementalConfig.damage.max * comboMultiplier * i, -1) } if target and not target:isRemoved() then doTargetCombatHealth(creature:getId(), target, _G["COMBAT_" .. damageType:upper() .. "DAMAGE"], elementalDamage.min, elementalDamage.max, CONST_ME_NONE) end end end end -- Efectos y daño a objetivos cercanos solo si el objetivo original existía if currentPos then if config.effectsEnabled.splashDamage and effectId == config.splashDamageEffectId then local targetsAround = getLimitedTargetsAround(currentPos, 1, config.maxTargets) for _, nearby in ipairs(targetsAround) do if nearby and not nearby:isRemoved() then local splashDamage = { min = math.min(comboDamage.min * 0.7, -1), max = math.min(comboDamage.max * 0.7, -1) } doTargetCombatHealth(creature:getId(), nearby, COMBAT_PHYSICALDAMAGE, splashDamage.min, splashDamage.max, CONST_ME_NONE) end end end end end, delay, creatureId, targetId, targetPos) end -- Dano final (asegurar que sea negativo) addEvent(function(creatureId, targetId, lastKnownPos) local creature = Creature(creatureId) if not creature then return end local finalDamage = -math.abs(totalDamageDealt * 0.3) local pos = creature:getPosition() sendEffect(Position( pos.x + config.finalDamageEffectOffset.x, pos.y + config.finalDamageEffectOffset.y, pos.z + config.finalDamageEffectOffset.z ), config.finalDamageEffectId, config.effectsEnabled.finalDamage) local target = Creature(targetId) if target and not target:isRemoved() then doTargetCombatHealth(creature:getId(), target, COMBAT_PHYSICALDAMAGE, finalDamage, finalDamage, CONST_ME_NONE) end -- Dano final a objetivos cercanos usando la última posición conocida if lastKnownPos and config.effectsEnabled.splashDamage then local finalTargetsAround = getLimitedTargetsAround(lastKnownPos, 2, config.maxTargets) for _, nearby in ipairs(finalTargetsAround) do if nearby and not nearby:isRemoved() then local splashFinalDamage = finalDamage * 0.5 doTargetCombatHealth(creature:getId(), nearby, COMBAT_PHYSICALDAMAGE, splashFinalDamage, splashFinalDamage, CONST_ME_NONE) end end end end, config.attackAnimationTotalTick, creatureId, targetId, targetPos) return true end