1-- Configuración general
2local config = {
3 attackAnimationEffectIds = {
4 {effectId = 253},
5 {effectId = 241},
6 {effectId = 247},
7 {effectId = 245}
8 },
9 disappearEffectId = 244,
10 attackAnimationTotalTick = 1200,
11 attackAnimationDamageTime = 450,
12 damage = {min = -100, max = -200},
13 extraDamagePerTarget = -50,
14 cooldownStorage = 12345,
15 cooldownTime = 2,
16 offsetEffectId = 249,
17 offsetEffectEnabled = false,
18 jumpEffectId = 244,
19 finalDamageEffectId = 248,
20 finalDamageEffectOffset = {
21 x = 1,
22 y = 1,
23 z = 0
24 },
25
26 attackAnimationEffectOffsets = {
27 [253] = {x = 1, y = 1, z = 0},
28 [241] = {x = 1, y = 1, z = 0},
29 [247] = {x = 1, y = 1, z = 0},
30 [245] = {x = 3, y = 3, z = 0}
31 },
32 maxTargets = 9, -- Número máximo de objetivos alcanzados
33 effectsEnabled = {
34 attackAnimation = true,
35 disappear = true,
36 jump = true,
37 finalDamage = true,
38 splashDamage = true
39 },
40 elementalDamage = {
41 target = {
42 energy = {enabled = false, damage = {min = -50, max = -100}},
43 fire = {enabled = false, damage = {min = -60, max = -120}},
44 ice = {enabled = true, damage = {min = -40, max = -80}},
45 earth = {enabled = false, damage = {min = -70, max = -140}}
46 },
47 around = {
48 energy = {enabled = false, damage = {min = -50, max = -100}},
49 fire = {enabled = false, damage = {min = -60, max = -120}},
50 ice = {enabled = true, damage = {min = -40, max = -80}},
51 earth = {enabled = false, damage = {min = -70, max = -140}}
52 }
53 },
54 effectsAround = {
55 enabled = true,
56 effects = {
57 {effectId = 243, offset = {x = 1, y = 1, z = 0}},
58 {effectId = 254, offset = {x = 0.5, y = 0.5, z = 0}}
59 }
60 },
61 splashDamageEffectId = 245, -- Efecto que desencadena el dano a las criaturas alrededor
62 allowPlayerReposition = true, -- Permitir al jugador elegir la posición final
63}
64
65-- Define combat objects para diferentes etapas
66local combats = {}
67for i, effectConfig in ipairs(config.attackAnimationEffectIds) do
68 local combat = createCombatObject()
69 setCombatParam(combat, COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE)
70 setCombatParam(combat, COMBAT_PARAM_EFFECT, effectConfig.effectId)
71 setCombatFormula(combat, COMBAT_FORMULA_LEVELMAGIC, config.damage.min, config.damage.max, config.damage.min, config.damage.max)
72 table.insert(combats, combat)
73end
74
75-- Funciones auxiliares
76local function getDirectionToTarget(creature, target)
77 local creaturePos, targetPos = creature:getPosition(), target:getPosition()
78 if targetPos.x > creaturePos.x then
79 return DIRECTION_EAST
80 elseif targetPos.x < creaturePos.x then
81 return DIRECTION_WEST
82 elseif targetPos.y > creaturePos.y then
83 return DIRECTION_SOUTH
84 else
85 return DIRECTION_NORTH
86 end
87end
88
89local function getFrontPosition(target, direction)
90 local targetPos = target:getPosition()
91 if direction == DIRECTION_NORTH then
92 return Position(targetPos.x, targetPos.y - 1, targetPos.z)
93 elseif direction == DIRECTION_SOUTH then
94 return Position(targetPos.x, targetPos.y + 1, targetPos.z)
95 elseif direction == DIRECTION_EAST then
96 return Position(targetPos.x + 1, targetPos.y, targetPos.z)
97 else
98 return Position(targetPos.x - 1, targetPos.y, targetPos.z)
99 end
100end
101
102local function adjustEffectPosition(position)
103 return Position(position.x + 1, position.y + 1, position.z)
104end
105
106local function isTileWalkable(position)
107 local tile = Tile(position)
108 if not tile or not tile:isWalkable() then
109 return false
110 end
111 local items = tile:getItems()
112 for _, item in ipairs(items) do
113 if item and item.isBlocking and item:isBlocking() then
114 return false
115 end
116 end
117 return true
118end
119
120local function findFreeTileAround(position)
121 local directions = {
122 {x = 1, y = 0}, {x = -1, y = 0}, {x = 0, y = 1}, {x = 0, y = -1},
123 {x = 1, y = 1}, {x = -1, y = -1}, {x = 1, y = -1}, {x = -1, y = 1}
124 }
125 for _, dir in ipairs(directions) do
126 local newPos = Position(position.x + dir.x, position.y + dir.y, position.z)
127 if isTileWalkable(newPos) then
128 return newPos
129 end
130 end
131 return nil
132end
133
134local function sendEffect(position, effectId, effectEnabled)
135 if effectEnabled then
136 position:sendMagicEffect(effectId)
137 end
138end
139
140local function handleOccupiedTile(creature, target, frontPos, originalPos)
141 local occupant = Tile(frontPos):getTopCreature()
142 if occupant then
143 local freeTile = findFreeTileAround(frontPos)
144 if freeTile then
145 occupant:teleportTo(freeTile)
146 else
147 occupant:teleportTo(originalPos)
148 sendEffect(originalPos, config.disappearEffectId, config.effectsEnabled.disappear)
149 occupant:setOutfit({lookType = 0})
150 addEvent(function()
151 if occupant:isRemoved() then return end
152 local newFreeTile = findFreeTileAround(frontPos)
153 if newFreeTile then
154 occupant:teleportTo(newFreeTile)
155 else
156 occupant:teleportTo(frontPos)
157 sendEffect(frontPos, config.disappearEffectId, config.effectsEnabled.disappear)
158 occupant:setOutfit(creature:getOutfit())
159 end
160 end, config.attackAnimationTotalTick)
161 end
162 end
163end
164
165local function preventLogout(creature)
166 local condition = Condition(CONDITION_INFIGHT)
167 condition:setParameter(CONDITION_PARAM_TICKS, config.attackAnimationTotalTick + 500) -- Prevent logout for the duration of the spell + buffer
168 creature:addCondition(condition)
169end
170
171local function applyFightingCondition(creature)
172 local condition = Condition(CONDITION_INFIGHT)
173 condition:setParameter(CONDITION_PARAM_TICKS, config.attackAnimationTotalTick + 500) -- Apply fighting condition for the duration of the spell + buffer
174 creature:addCondition(condition)
175end
176
177local function hasPlayerMoved(creature, originalPos)
178 local currentPos = creature:getPosition()
179 return currentPos.x ~= originalPos.x or currentPos.y ~= originalPos.y or currentPos.z ~= originalPos.z
180end
181
182local function jumpToFrontAndBack(creature, target, duration)
183 if not config.effectsEnabled.jump then return end
184
185 local originalPos = creature:getPosition()
186 local direction = getDirectionToTarget(creature, target)
187 local targetPos = target:getPosition()
188 local frontPos = getFrontPosition(target, direction)
189 local outfitBeforeJump = creature:getOutfit()
190 local creatureId = creature:getId()
191 local targetId = target:getId()
192
193 applyFightingCondition(creature)
194
195 if isTileWalkable(frontPos) then
196 handleOccupiedTile(creature, target, frontPos, originalPos)
197 -- Efecto y desaparición
198 if config.effectsEnabled.disappear then
199 sendEffect(creature:getPosition(), config.disappearEffectId, config.effectsEnabled.disappear)
200 addEvent(function(creatureId)
201 local creature = Creature(creatureId)
202 if not creature then return end
203 creature:setOutfit({lookType = 0}) -- Hace invisible al jugador
204 end, 150, creatureId)
205 end
206
207 -- Teleport y aparición
208 addEvent(function(creatureId)
209 local creature = Creature(creatureId)
210 if not creature then return end
211 creature:setDirection(direction)
212 creature:teleportTo(frontPos)
213 sendEffect(frontPos, config.disappearEffectId, config.effectsEnabled.disappear)
214 sendEffect(frontPos, config.jumpEffectId, config.effectsEnabled.jump)
215 end, 200, creatureId)
216
217 -- Retorno
218 addEvent(function(creatureId)
219 local creature = Creature(creatureId)
220 if not creature then return end
221 -- Efecto y desaparición antes de volver
222 if config.effectsEnabled.disappear then
223 sendEffect(frontPos, config.disappearEffectId, config.effectsEnabled.disappear)
224 creature:setOutfit({lookType = 0})
225 end
226
227 -- Teleport y aparición final
228 addEvent(function(creatureId)
229 local creature = Creature(creatureId)
230 if not creature then return end
231 local finalPos = originalPos
232 if config.allowPlayerReposition and target:isRemoved() then
233 if hasPlayerMoved(creature, originalPos) then
234 finalPos = creature:getPosition() -- Usar la posición actual del jugador
235 end
236 end
237 creature:teleportTo(finalPos)
238 sendEffect(finalPos, config.disappearEffectId, config.effectsEnabled.disappear)
239 if config.offsetEffectEnabled then
240 sendEffect(Position(finalPos.x + 3, finalPos.y + 3, finalPos.z), config.offsetEffectId, config.effectsEnabled.jump)
241 end
242 sendEffect(finalPos, config.jumpEffectId, config.effectsEnabled.jump)
243 sendEffect(finalPos, config.effectsEnabled.finalDamage)
244 addEvent(function(creatureId)
245 local creature = Creature(creatureId)
246 if not creature then return end
247 creature:setOutfit(outfitBeforeJump)
248 creature:setDirection(direction)
249 end, 250, creatureId) -- Reaparecer el outfit después de 250ms
250 end, 100, creatureId)
251 end, duration - 250, creatureId)
252 else
253 creature:sendCancelMessage("La posición frente al objetivo no es válida.")
254 -- Teleport to the target's position if the front position is not walkable
255 creature:teleportTo(targetPos)
256 -- Ensure the creature returns to its original position
257 addEvent(function(creatureId)
258 local creature = Creature(creatureId)
259 if not creature then return end
260 creature:teleportTo(originalPos)
261 sendEffect(originalPos, config.disappearEffectId, config.effectsEnabled.disappear)
262 if config.offsetEffectEnabled then
263 sendEffect(Position(originalPos.x + 3, originalPos.y + 3, originalPos.z), config.offsetEffectId, config.effectsEnabled.jump)
264 end
265 sendEffect(originalPos, config.jumpEffectId, config.effectsEnabled.jump)
266 sendEffect(originalPos, config.effectsEnabled.finalDamage)
267 addEvent(function(creatureId)
268 local creature = Creature(creatureId)
269 if not creature then return end
270 creature:setOutfit(outfitBeforeJump)
271 creature:setDirection(direction)
272 end, 250, creatureId) -- Reaparecer el outfit después de 250ms
273 end, duration - 250, creatureId)
274 end
275end
276
277local function getTargetsAround(target, radius)
278 if not target then return {} end
279 local targets = {}
280 local targetPos = target:getPosition()
281 if not targetPos then return targets end
282 local positions = {}
283
284 for x = -radius, radius do
285 for y = -radius, radius do
286 if not (x == 0 and y == 0) then
287 table.insert(positions, Position(targetPos.x + x, targetPos.y + y, targetPos.z))
288 end
289 end
290 end
291
292 for _, pos in ipairs(positions) do
293 local tile = Tile(pos)
294 if tile then
295 local creature = tile:getTopCreature()
296 if creature then
297 table.insert(targets, creature)
298 end
299 end
300 end
301
302 return targets
303end
304
305local function getTargetsAroundPosition(position, radius)
306 local targets = {}
307 for x = -radius, radius do
308 for y = -radius, radius do
309 if not (x == 0 and y == 0) then
310 local pos = Position(position.x + x, position.y + y, position.z)
311 local tile = Tile(pos)
312 if tile then
313 local creature = tile:getTopCreature()
314 if creature and (creature:isMonster() or creature:isPlayer()) then
315 table.insert(targets, creature)
316 end
317 end
318 end
319 end
320 end
321 return targets
322end
323
324local function getLimitedTargetsAround(targetOrPos, radius, maxTargets)
325 local targets = {}
326 local positions = {}
327 local centerPos
328
329 if type(targetOrPos) == "userdata" and targetOrPos.getPosition then
330 -- Si es una criatura
331 centerPos = targetOrPos:getPosition()
332 else
333 -- Si es una posición
334 centerPos = targetOrPos
335 end
336
337 if not centerPos then return targets end
338
339 for x = -radius, radius do
340 for y = -radius, radius do
341 if not (x == 0 and y == 0) then
342 table.insert(positions, Position(centerPos.x + x, centerPos.y + y, centerPos.z))
343 end
344 end
345 end
346
347 for _, pos in ipairs(positions) do
348 if #targets >= maxTargets then
349 break
350 end
351 local tile = Tile(pos)
352 if tile then
353 local creature = tile:getTopCreature()
354 if creature then
355 table.insert(targets, creature)
356 end
357 end
358 end
359
360 return targets
361end
362
363local function getLimitedTargetsAroundPosition(position, radius, maxTargets)
364 local targets = {}
365 for x = -radius, radius do
366 for y = -radius, radius do
367 if not (x == 0 and y == 0) then
368 local pos = Position(position.x + x, position.y + y, position.z)
369 local tile = Tile(pos)
370 if tile then
371 local creature = tile:getTopCreature()
372 if creature and (creature:isMonster() or creature:isPlayer()) then
373 table.insert(targets, creature)
374 if #targets >= maxTargets then
375 return targets
376 end
377 end
378 end
379 end
380 end
381 end
382 return targets
383end
384
385-- Función principal de lanzamiento de hechizo
386function onCastSpell(creature, variant)
387 if not creature then return false end
388
389 if os.time() < creature:getStorageValue(config.cooldownStorage) then
390 creature:sendCancelMessage("Debes esperar antes de usar esta magia nuevamente.")
391 return false
392 end
393
394 local target = Creature(variant:getNumber())
395 if not target then return false end
396
397 local direction = getDirectionToTarget(creature, target)
398 creature:setDirection(direction)
399 creature:setStorageValue(config.cooldownStorage, os.time() + config.cooldownTime)
400
401 applyFightingCondition(creature)
402 jumpToFrontAndBack(creature, target, config.attackAnimationTotalTick)
403
404 local targetPos = target:getPosition()
405 if not targetPos then return false end
406 local offset = adjustEffectPosition(targetPos)
407 local targetsAround = getTargetsAround(target, 1)
408 local numTargets = #targetsAround
409 local totalDamageDealt = 0
410 local comboMultiplier = 1 + (numTargets * 0.2) -- 20% más de dano por objetivo
411 local creatureId = creature:getId()
412 local targetId = target:getId()
413
414 -- Secuencia de efectos y dano combo
415 for i, effectConfig in ipairs(config.attackAnimationEffectIds) do
416 local delay = config.attackAnimationDamageTime + (i - 1) * 200
417 addEvent(function(creatureId, targetId, lastKnownPos)
418 local creature = Creature(creatureId)
419 if not creature then return end
420
421 local target = Creature(targetId)
422 local currentPos = lastKnownPos
423 if target then
424 currentPos = target:getPosition()
425 end
426
427 local effectId = effectConfig.effectId
428 local offset = config.attackAnimationEffectOffsets[effectId]
429 local effectPosition = Position(currentPos.x + offset.x, currentPos.y + offset.y, currentPos.z + offset.z)
430 sendEffect(effectPosition, effectId, config.effectsEnabled.attackAnimation)
431
432 -- Asegurar que el dano sea negativo
433 local comboDamage = {
434 min = math.min(config.damage.min * comboMultiplier * i, -1),
435 max = math.min(config.damage.max * comboMultiplier * i, -1)
436 }
437
438 -- Dano principal al objetivo
439 if target and not target:isRemoved() then
440 local damageDealt = doTargetCombatHealth(creature:getId(), target, COMBAT_PHYSICALDAMAGE,
441 comboDamage.min, comboDamage.max, CONST_ME_NONE)
442 if type(damageDealt) == "number" then
443 totalDamageDealt = totalDamageDealt + math.abs(damageDealt)
444 end
445
446 -- Verificar si el objetivo sigue con vida
447 if target:getHealth() <= 0 then
448 lastKnownPos = target:getPosition()
449 target:remove()
450 return -- Salir si el objetivo muere
451 end
452
453 -- Dano elemental al objetivo
454 for damageType, elementalConfig in pairs(config.elementalDamage.target) do
455 if elementalConfig.enabled then
456 local elementalDamage = {
457 min = math.min(elementalConfig.damage.min * comboMultiplier * i, -1),
458 max = math.min(elementalConfig.damage.max * comboMultiplier * i, -1)
459 }
460 if target and not target:isRemoved() then
461 doTargetCombatHealth(creature:getId(), target, _G["COMBAT_" .. damageType:upper() .. "DAMAGE"],
462 elementalDamage.min, elementalDamage.max, CONST_ME_NONE)
463 end
464 end
465 end
466 end
467
468 -- Efectos y daño a objetivos cercanos solo si el objetivo original existía
469 if currentPos then
470 if config.effectsEnabled.splashDamage and effectId == config.splashDamageEffectId then
471 local targetsAround = getLimitedTargetsAround(currentPos, 1, config.maxTargets)
472 for _, nearby in ipairs(targetsAround) do
473 if nearby and not nearby:isRemoved() then
474 local splashDamage = {
475 min = math.min(comboDamage.min * 0.7, -1),
476 max = math.min(comboDamage.max * 0.7, -1)
477 }
478 doTargetCombatHealth(creature:getId(), nearby, COMBAT_PHYSICALDAMAGE,
479 splashDamage.min, splashDamage.max, CONST_ME_NONE)
480 end
481 end
482 end
483 end
484
485 end, delay, creatureId, targetId, targetPos)
486 end
487
488 -- Dano final (asegurar que sea negativo)
489 addEvent(function(creatureId, targetId, lastKnownPos)
490 local creature = Creature(creatureId)
491 if not creature then return end
492
493 local finalDamage = -math.abs(totalDamageDealt * 0.3)
494 local pos = creature:getPosition()
495 sendEffect(Position(
496 pos.x + config.finalDamageEffectOffset.x,
497 pos.y + config.finalDamageEffectOffset.y,
498 pos.z + config.finalDamageEffectOffset.z
499 ), config.finalDamageEffectId, config.effectsEnabled.finalDamage)
500
501 local target = Creature(targetId)
502 if target and not target:isRemoved() then
503 doTargetCombatHealth(creature:getId(), target, COMBAT_PHYSICALDAMAGE,
504 finalDamage, finalDamage, CONST_ME_NONE)
505 end
506
507 -- Dano final a objetivos cercanos usando la última posición conocida
508 if lastKnownPos and config.effectsEnabled.splashDamage then
509 local finalTargetsAround = getLimitedTargetsAround(lastKnownPos, 2, config.maxTargets)
510 for _, nearby in ipairs(finalTargetsAround) do
511 if nearby and not nearby:isRemoved() then
512 local splashFinalDamage = finalDamage * 0.5
513 doTargetCombatHealth(creature:getId(), nearby, COMBAT_PHYSICALDAMAGE,
514 splashFinalDamage, splashFinalDamage, CONST_ME_NONE)
515 end
516 end
517 end
518 end, config.attackAnimationTotalTick, creatureId, targetId, targetPos)
519
520 return true
521end
522