Server upload

RD Core / Commit #2

Commit Server upload

Alex Alex committed 18/10/2026 23:38 main Patch
1,106 files +223,404 -0
A data/scripts/customs/custom_sounds/player_attacks.lua +30-0 View file
@@ -0,0 +1,30 @@
1+local playerAttack = CreatureEvent("playerAttackSound")
2+
3+
4+function playerAttack.onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin)
5+ if attacker and attacker:isPlayer() then
6+ local player = attacker
7+ if origin == ORIGIN_MELEE or origin == ORIGIN_RANGED then
8+ local weapon = player:getSlotItem(CONST_SLOT_LEFT)
9+ if weapon then
10+ local weaponType = weapon:getType():getWeaponType()
11+
12+ if weaponType == WEAPON_AXE or weaponType == WEAPON_SWORD or weaponType == WEAPON_CLUB then
13+ playSound(player, "weapon_attack.ogg")
14+ elseif weaponType == WEAPON_WAND then
15+ playSound(player, "wand_attack.ogg")
16+ elseif weaponType == WEAPON_DISTANCE then
17+ playSound(player, "distance_attack.ogg")
18+ elseif weaponType == WEAPON_NONE or weaponType == WEAPON_SHIELD then
19+ playSound(player, "fist_attack.ogg")
20+ end
21+ else
22+ playSound(player, "fist_attack.ogg")
23+ end
24+ end
25+ end
26+
27+ return primaryDamage, primaryType, secondaryDamage, secondaryType
28+end
29+
30+playerAttack:register()
A data/scripts/customs/guildTitle/guildTitle.lua +41-0 View file
@@ -0,0 +1,41 @@
1+
2+
3+
4+local LoginEvent = CreatureEvent("guildTitle_Login")
5+function LoginEvent.onLogin(player)
6+ player:registerEvent("guildTitle")
7+ return true
8+end
9+LoginEvent:register()
10+local OPCODE_GUILDTITLE = 134
11+
12+local guildTitle = CreatureEvent("guildTitle")
13+guildTitle:type("extendedopcode")
14+function guildTitle.onExtendedOpcode(player, opcode, buffer)
15+ if opcode and opcode == OPCODE_GUILDTITLE then
16+ local json_status, json_data =
17+ pcall(
18+ function()
19+ return json.decode(buffer)
20+ end
21+ )
22+ local creatureID = json_data.creature
23+ local creature = Creature(creatureID)
24+ if creature and creature:isPlayer() then
25+ local guild = creature:getGuild()
26+ if guild ~= nil then
27+ local data = {
28+ response = "SetGuildName",
29+ creatureId = creatureID,
30+ name = creature:getName(),
31+ guildNick = creature:getGuildNick(),
32+ guildName = guild:getName(),
33+ }
34+ player:sendExtendedOpcode(OPCODE_GUILDTITLE,json.encode(data))
35+ end
36+ end
37+ end
38+end
39+guildTitle:register()
40+
41+
A data/scripts/customs/tp_shop.lua +158-0 View file
@@ -0,0 +1,158 @@
1+local savedPosition = nil
2+
3+local forwardTeleport = MoveEvent()
4+local returnTeleport = MoveEvent()
5+
6+-- Configuración para TP de ida
7+local forwardConfig = {
8+ fromPosition = Position(4407, 1346, 7), -- Posición activadora
9+ toPosition = Position(3999, 991, 8), -- Destino
10+ effect = CONST_ME_TELEPORT, -- Efecto de teletransporte
11+ delay = 3000, -- Tiempo de carga (ms)
12+ opcode = 125 -- Código de comunicación con OTClient
13+}
14+
15+-- Configuración para TP de vuelta
16+local returnConfig = {
17+ fromPositions = {
18+ Position(3999, 995, 7),
19+ Position(32795, 31248, 7)
20+ },
21+ exitPositions = {
22+ Position(3999, 996, 8), -- Posición de salida 1
23+ Position(32797, 31247, 7) -- Posición de salida 2
24+ },
25+ delay = 3000, -- Tiempo de carga (ms)
26+ opcode = 125 -- Código de comunicación con OTClient
27+}
28+
29+-- Offsets for magic effects
30+local effectOffset = { x = 1, y = 1, z = 0 } -- Standard offset for final (248) effect
31+local disappearOffset = { x = effectOffset.x - 1, y = effectOffset.y - 1, z = effectOffset.z } -- Adjusted offset for effect 244
32+
33+-- Función de teletransporte de ida
34+function forwardTeleport.onStepIn(creature, item, position, fromPosition)
35+ if not creature:isPlayer() then return true end
36+ local player = creature:getPlayer()
37+ if not player then return true end
38+
39+ savedPosition = player:getPosition()
40+ print("DEBUG SERVIDOR: Se ha guardado la posición original: " .. tostring(savedPosition))
41+
42+ local success = player:sendExtendedOpcode(forwardConfig.opcode, '{"action": "show_loading"}')
43+ if success then
44+ print("DEBUG SERVIDOR: Opcode 125 enviado exitosamente a " .. player:getName())
45+ else
46+ print("ERROR SERVIDOR: No se pudo enviar opcode 125 a " .. player:getName())
47+ end
48+
49+ addEvent(function()
50+ if player then
51+ if forwardConfig.effect == CONST_ME_TELEPORT then
52+ local originalOutfit = player:getOutfit() -- Save current outfit
53+ local originalName = player:getName() -- Save current name
54+ local pos = player:getPosition()
55+ local posOffset = Position(pos.x + disappearOffset.x, pos.y + disappearOffset.y, pos.z + disappearOffset.z)
56+ -- Immediately hide the character and its name:
57+ posOffset:sendMagicEffect(244) -- Disappear effect at adjusted offset
58+ player:setOutfit({lookType = 0})
59+ if player.setName then player:setName("") end -- Hide name
60+ -- Teleport the player:
61+ player:teleportTo(forwardConfig.toPosition, true)
62+ -- After a short delay, play final effect (248)
63+ addEvent(function()
64+ if player then
65+ local newPos = player:getPosition()
66+ local newPosOffset = Position(newPos.x + effectOffset.x, newPos.y + effectOffset.y, newPos.z + effectOffset.z)
67+ newPosOffset:sendMagicEffect(248)
68+ end
69+ end, 500)
70+ -- Then, after final effect nearly finishes, restore outfit and name:
71+ addEvent(function()
72+ if player then
73+ player:setOutfit(originalOutfit)
74+ if player.setName then player:setName(originalName) end
75+ end
76+ end, 1000) -- Delay increased to ensure invisibility period is hidden
77+ addEvent(function()
78+ if player then
79+ player:sendExtendedOpcode(126, '{"action": "hide_loading"}')
80+ print("DEBUG SERVIDOR: Opcode 126 enviado para ocultar loading")
81+ end
82+ end, 1100)
83+ else
84+ player:teleportTo(forwardConfig.toPosition, true)
85+ player:getPosition():sendMagicEffect(forwardConfig.effect)
86+ addEvent(function()
87+ if player then
88+ player:sendExtendedOpcode(126, '{"action": "hide_loading"}')
89+ end
90+ end, 500)
91+ end
92+ end
93+ end, forwardConfig.delay)
94+ return true
95+end
96+
97+-- Función de teletransporte de regreso
98+function returnTeleport.onStepIn(creature, item, position, fromPosition)
99+ if not creature:isPlayer() then return true end
100+ local player = creature:getPlayer()
101+ if not player then return true end
102+ local exits = returnConfig.exitPositions
103+ local exitPos = exits[ math.random(#exits) ]
104+ print("DEBUG: Regresando a la posición de salida: " .. tostring(exitPos))
105+ player:sendExtendedOpcode(returnConfig.opcode, '{"action": "show_loading"}')
106+
107+ addEvent(function()
108+ if player then
109+ if forwardConfig.effect == CONST_ME_TELEPORT then -- Same condition as forward
110+ local originalOutfit = player:getOutfit()
111+ local originalName = player:getName()
112+ local pos = player:getPosition()
113+ local posOffset = Position(pos.x + disappearOffset.x, pos.y + disappearOffset.y, pos.z + disappearOffset.z)
114+ posOffset:sendMagicEffect(244)
115+ player:setOutfit({lookType = 0})
116+ if player.setName then player:setName("") end
117+ player:teleportTo(exitPos, true)
118+ addEvent(function()
119+ if player then
120+ local newPos = player:getPosition()
121+ local newPosOffset = Position(newPos.x + effectOffset.x, newPos.y + effectOffset.y, newPos.z + effectOffset.z)
122+ newPosOffset:sendMagicEffect(248)
123+ end
124+ end, 500)
125+ addEvent(function()
126+ if player then
127+ player:setOutfit(originalOutfit)
128+ if player.setName then player:setName(originalName) end
129+ end
130+ end, 1200)
131+ addEvent(function()
132+ if player then
133+ player:sendExtendedOpcode(126, '{"action": "hide_loading"}')
134+ end
135+ end, 1300)
136+ else
137+ player:teleportTo(exitPos, true)
138+ player:getPosition():sendMagicEffect(returnConfig.effect or CONST_ME_TELEPORT)
139+ addEvent(function()
140+ if player then
141+ player:sendExtendedOpcode(126, '{"action": "hide_loading"}')
142+ end
143+ end, 500)
144+ end
145+ savedPosition = nil
146+ end
147+ end, returnConfig.delay)
148+ return true
149+end
150+
151+-- Registrar eventos de teletransporte
152+forwardTeleport:position(forwardConfig.fromPosition)
153+forwardTeleport:register()
154+
155+for _, pos in ipairs(returnConfig.fromPositions) do
156+ returnTeleport:position(pos)
157+end
158+returnTeleport:register()
A data/scripts/lib/create_functions.lua +2-0 View file
@@ -0,0 +1,2 @@
1+createFunctions(MonsterType) -- creates get/set functions for MonsterType
2+createFunctions(Spell) -- creates get/set functions for Spell
A data/scripts/lib/defaults_move_event.lua +25-0 View file
@@ -0,0 +1,25 @@
1+-- default callbacks
2+
3+function defaultStepIn(creature, item, position, fromPosition)
4+ return true
5+end
6+
7+function defaultStepOut(creature, item, position, fromPosition)
8+ return true
9+end
10+
11+function defaultAddItem(moveitem, tileitem, pos)
12+ return true
13+end
14+
15+function defaultRemoveItem(moveitem, tileitem, pos)
16+ return true
17+end
18+
19+function defaultEquip(player, item, slot, isCheck)
20+ return true
21+end
22+
23+function defaultDeEquip(player, item, slot, isCheck)
24+ return true
25+end
A data/scripts/lib/helper_constructors.lua +52-0 View file
@@ -0,0 +1,52 @@
1+local classes = {Action, CreatureEvent, Spell, TalkAction, MoveEvent, GlobalEvent, Weapon}
2+
3+for _, class in ipairs(classes) do
4+ local MT = getmetatable(class)
5+ local DefaultConstructor = MT.__call
6+
7+ MT.__call = function(self, def, ...)
8+ -- Backwards compatibility for default obj() constructor
9+ if type(def) ~= "table" then
10+ return DefaultConstructor(self, def, ...)
11+ end
12+
13+ local obj = nil
14+ if def.init then
15+ obj = DefaultConstructor(self, unpack(def.init))
16+ else
17+ obj = DefaultConstructor(self)
18+ end
19+
20+ -- Call each method from definition table with the value as params
21+ local hasCallback = false
22+
23+ for methodName, value in pairs(def) do
24+ -- Strictly check if a correct callback is passed
25+ if methodName:sub(1, 2) == "on" and type(value) == "function" and rawget(class, methodName) then
26+ hasCallback = true
27+ end
28+
29+ if methodName ~= "register" then
30+ local method = rawget(self, methodName)
31+ if method then
32+ if type(value) == "table" then
33+ method(obj, unpack(value))
34+ else
35+ method(obj, value)
36+ end
37+ end
38+ end
39+ end
40+
41+ -- Only register if callback has already been defined, otherwise defining afterwards will not work
42+ if def.register then
43+ if not hasCallback then
44+ print("Warning: Event not registered due to there being no callback.")
45+ else
46+ obj:register()
47+ end
48+ end
49+
50+ return obj
51+ end
52+end
A data/scripts/lib/Notmove_function.lua +7-0 View file
@@ -0,0 +1,7 @@
1+function onPlayerMove(player, fromPosition, toPosition)
2+ if player:getStorageValue(STORAGE_NO_MOVE) == 1 then
3+ player:sendCancelMessage("Você não pode se mover enquanto conjura esta magia!")
4+ return false
5+ end
6+ return true
7+end
A data/scripts/lib/register_monster_type.lua +471-0 View file
@@ -0,0 +1,471 @@
1+registerMonsterType = {}
2+setmetatable(registerMonsterType,
3+{
4+ __call =
5+ function(self, mtype, mask)
6+ for _,parse in pairs(self) do
7+ parse(mtype, mask)
8+ end
9+ end
10+})
11+
12+MonsterType.register = function(self, mask)
13+ return registerMonsterType(self, mask)
14+end
15+
16+registerMonsterType.description = function(mtype, mask)
17+ if mask.description then
18+ mtype:nameDescription(mask.description)
19+ end
20+end
21+registerMonsterType.experience = function(mtype, mask)
22+ if mask.experience then
23+ mtype:experience(mask.experience)
24+ end
25+end
26+registerMonsterType.skull = function(mtype, mask)
27+ if mask.skull then
28+ mtype:skull(mask.skull)
29+ end
30+end
31+registerMonsterType.outfit = function(mtype, mask)
32+ if mask.outfit then
33+ mtype:outfit(mask.outfit)
34+ end
35+end
36+registerMonsterType.maxHealth = function(mtype, mask)
37+ if mask.maxHealth then
38+ mtype:maxHealth(mask.maxHealth)
39+ end
40+end
41+registerMonsterType.health = function(mtype, mask)
42+ if mask.health then
43+ mtype:health(mask.health)
44+ end
45+end
46+registerMonsterType.runHealth = function(mtype, mask)
47+ if mask.runHealth then
48+ mtype:runHealth(mask.runHealth)
49+ end
50+end
51+registerMonsterType.maxSummons = function(mtype, mask)
52+ if mask.maxSummons then
53+ mtype:maxSummons(mask.maxSummons)
54+ end
55+end
56+registerMonsterType.race = function(mtype, mask)
57+ if mask.race then
58+ mtype:race(mask.race)
59+ end
60+end
61+registerMonsterType.manaCost = function(mtype, mask)
62+ if mask.manaCost then
63+ mtype:manaCost(mask.manaCost)
64+ end
65+end
66+registerMonsterType.speed = function(mtype, mask)
67+ if mask.speed then
68+ mtype:baseSpeed(mask.speed)
69+ end
70+end
71+registerMonsterType.corpse = function(mtype, mask)
72+ if mask.corpse then
73+ mtype:corpseId(mask.corpse)
74+ end
75+end
76+registerMonsterType.flags = function(mtype, mask)
77+ if mask.flags then
78+ if mask.flags.attackable ~= nil then
79+ mtype:isAttackable(mask.flags.attackable)
80+ end
81+ if mask.flags.healthHidden ~= nil then
82+ mtype:isHealthHidden(mask.flags.healthHidden)
83+ end
84+ if mask.flags.boss ~= nil then
85+ mtype:isBoss(mask.flags.boss)
86+ end
87+ if mask.flags.convinceable ~= nil then
88+ mtype:isConvinceable(mask.flags.convinceable)
89+ end
90+ if mask.flags.illusionable ~= nil then
91+ mtype:isIllusionable(mask.flags.illusionable)
92+ end
93+ if mask.flags.hostile ~= nil then
94+ mtype:isHostile(mask.flags.hostile)
95+ end
96+ if mask.flags.pushable ~= nil then
97+ mtype:isPushable(mask.flags.pushable)
98+ end
99+ if mask.flags.canPushItems ~= nil then
100+ mtype:canPushItems(mask.flags.canPushItems)
101+ end
102+ if mask.flags.canPushCreatures ~= nil then
103+ mtype:canPushCreatures(mask.flags.canPushCreatures)
104+ end
105+ if mask.flags.targetDistance then
106+ mtype:targetDistance(mask.flags.targetDistance)
107+ end
108+ if mask.flags.staticAttackChance then
109+ mtype:staticAttackChance(mask.flags.staticAttackChance)
110+ end
111+ end
112+end
113+registerMonsterType.light = function(mtype, mask)
114+ if mask.light then
115+ if mask.light.color then
116+ local color = mask.light.color
117+ end
118+ if mask.light.level then
119+ mtype:light(color, mask.light.level)
120+ end
121+ end
122+end
123+registerMonsterType.changeTarget = function(mtype, mask)
124+ if mask.changeTarget then
125+ if mask.changeTarget.chance then
126+ mtype:changeTargetChance(mask.changeTarget.chance)
127+ end
128+ if mask.changeTarget.interval then
129+ mtype:changeTargetSpeed(mask.changeTarget.interval)
130+ end
131+ end
132+end
133+registerMonsterType.voices = function(mtype, mask)
134+ if type(mask.voices) == "table" then
135+ local interval, chance
136+ if mask.voices.interval then
137+ interval = mask.voices.interval
138+ end
139+ if mask.voices.chance then
140+ chance = mask.voices.chance
141+ end
142+ for k, v in pairs(mask.voices) do
143+ if type(v) == "table" then
144+ mtype:addVoice(v.text, interval, chance, v.yell)
145+ end
146+ end
147+ end
148+end
149+registerMonsterType.summons = function(mtype, mask)
150+ if type(mask.summons) == "table" then
151+ for k, v in pairs(mask.summons) do
152+ mtype:addSummon(v.name, v.interval, v.chance)
153+ end
154+ end
155+end
156+registerMonsterType.events = function(mtype, mask)
157+ if type(mask.events) == "table" then
158+ for k, v in pairs(mask.events) do
159+ mtype:registerEvent(v)
160+ end
161+ end
162+end
163+registerMonsterType.loot = function(mtype, mask)
164+ if type(mask.loot) == "table" then
165+ local lootError = false
166+ for _, loot in pairs(mask.loot) do
167+ local parent = Loot()
168+ if not parent:setId(loot.id) then
169+ lootError = true
170+ end
171+ if loot.chance then
172+ parent:setChance(loot.chance)
173+ end
174+ if loot.maxCount then
175+ parent:setMaxCount(loot.maxCount)
176+ end
177+ if loot.aid or loot.actionId then
178+ parent:setActionId(loot.aid or loot.actionId)
179+ end
180+ if loot.subType or loot.charges then
181+ parent:setSubType(loot.subType or loot.charges)
182+ end
183+ if loot.text or loot.description then
184+ parent:setDescription(loot.text or loot.description)
185+ end
186+ if loot.child then
187+ for _, children in pairs(loot.child) do
188+ local child = Loot()
189+ if not child:setId(children.id) then
190+ lootError = true
191+ end
192+ if children.chance then
193+ child:setChance(children.chance)
194+ end
195+ if children.maxCount then
196+ child:setMaxCount(children.maxCount)
197+ end
198+ if children.aid or children.actionId then
199+ child:setActionId(children.aid or children.actionId)
200+ end
201+ if children.subType or children.charges then
202+ child:setSubType(children.subType or children.charges)
203+ end
204+ if children.text or children.description then
205+ child:setDescription(children.text or children.description)
206+ end
207+ parent:addChildLoot(child)
208+ end
209+ end
210+ mtype:addLoot(parent)
211+ end
212+ if lootError then
213+ print("[Warning - end] Monster: \"".. mtype:name() .. "\" loot could not correctly be load.")
214+ end
215+ end
216+end
217+registerMonsterType.elements = function(mtype, mask)
218+ if type(mask.elements) == "table" then
219+ for _, element in pairs(mask.elements) do
220+ if element.type and element.percent then
221+ mtype:addElement(element.type, element.percent)
222+ end
223+ end
224+ end
225+end
226+registerMonsterType.immunities = function(mtype, mask)
227+ if type(mask.immunities) == "table" then
228+ for _, immunity in pairs(mask.immunities) do
229+ if immunity.type and immunity.combat then
230+ mtype:combatImmunities(immunity.type)
231+ end
232+ if immunity.type and immunity.condition then
233+ mtype:conditionImmunities(immunity.type)
234+ end
235+ end
236+ end
237+end
238+registerMonsterType.attacks = function(mtype, mask)
239+ if type(mask.attacks) == "table" then
240+ for _, attack in pairs(mask.attacks) do
241+ local spell = MonsterSpell()
242+ if attack.name then
243+ if attack.name == "melee" then
244+ spell:setType("melee")
245+ if attack.attack and attack.skill then
246+ spell:setAttackValue(attack.attack, attack.skill)
247+ end
248+ if attack.interval then
249+ spell:setInterval(attack.interval)
250+ end
251+ if attack.effect then
252+ spell:setCombatEffect(attack.effect)
253+ end
254+ if attack.condition then
255+ if attack.condition.type then
256+ spell:setConditionType(attack.condition.type)
257+ end
258+ local startDamnage = 0
259+ if attack.condition.startDamage then
260+ startDamage = attack.condition.startDamage
261+ end
262+ if attack.condition.minDamage and attack.condition.maxDamage then
263+ spell:setConditionDamage(attack.condition.minDamage, attack.condition.maxDamage, startDamage)
264+ end
265+ if attack.condition.duration then
266+ spell:setConditionDuration(attack.condition.duration)
267+ end
268+ if attack.condition.interval then
269+ spell:setConditionTickInterval(attack.condition.interval)
270+ end
271+ end
272+ else
273+ spell:setType(attack.name)
274+ if attack.type then
275+ if attack.name == "combat" then
276+ spell:setCombatType(attack.type)
277+ else
278+ spell:setConditionType(attack.type)
279+ end
280+ end
281+ if attack.interval then
282+ spell:setInterval(attack.interval)
283+ end
284+ if attack.chance then
285+ spell:setChance(attack.chance)
286+ end
287+ if attack.range then
288+ spell:setRange(attack.range)
289+ end
290+ if attack.duration then
291+ spell:setConditionDuration(attack.duration)
292+ end
293+ if attack.speed then
294+ if type(attack.speed) ~= "table" then
295+ spell:setConditionSpeedChange(attack.speed)
296+ elseif type(attack.speed) == "table" then
297+ if attack.speed.min and attack.speed.max then
298+ spell:setConditionSpeedChange(attack.speed.min, attack.speed.max)
299+ end
300+ end
301+ end
302+ if attack.target then
303+ spell:setNeedTarget(attack.target)
304+ end
305+ if attack.length then
306+ spell:setCombatLength(attack.length)
307+ end
308+ if attack.spread then
309+ spell:setCombatSpread(attack.spread)
310+ end
311+ if attack.radius then
312+ spell:setCombatRadius(attack.radius)
313+ end
314+ if attack.minDamage and attack.maxDamage then
315+ if attack.name == "combat" then
316+ spell:setCombatValue(attack.minDamage, attack.maxDamage)
317+ else
318+ local startDamage = 0
319+ if attack.startDamage then
320+ startDamage = attack.startDamage
321+ end
322+ spell:setConditionDamage(attack.minDamage, attack.maxDamage, startDamage)
323+ end
324+ end
325+ if attack.effect then
326+ spell:setCombatEffect(attack.effect)
327+ end
328+ if attack.shootEffect then
329+ spell:setCombatShootEffect(attack.shootEffect)
330+ end
331+ end
332+ elseif attack.script then
333+ spell:setScriptName(attack.script)
334+ if attack.interval then
335+ spell:setInterval(attack.interval)
336+ end
337+ if attack.chance then
338+ spell:setChance(attack.chance)
339+ end
340+ if attack.minDamage and attack.maxDamage then
341+ spell:setCombatValue(attack.minDamage, attack.maxDamage)
342+ end
343+ if attack.target then
344+ spell:setNeedTarget(attack.target)
345+ end
346+ end
347+ mtype:addAttack(spell)
348+ end
349+ end
350+end
351+registerMonsterType.defenses = function(mtype, mask)
352+ if type(mask.defenses) == "table" then
353+ if mask.defenses.defense then
354+ mtype:defense(mask.defenses.defense)
355+ end
356+ if mask.defenses.armor then
357+ mtype:armor(mask.defenses.armor)
358+ end
359+ for _, defense in pairs(mask.defenses) do
360+ if type(defense) == "table" then
361+ local spell = MonsterSpell()
362+ if defense.name then
363+ if defense.name == "melee" then
364+ spell:setType("melee")
365+ if defense.attack and defense.skill then
366+ spell:setAttackValue(defense.attack, defense.skill)
367+ end
368+ if defense.interval then
369+ spell:setInterval(defense.interval)
370+ end
371+ if defense.effect then
372+ spell:setCombatEffect(defense.effect)
373+ end
374+ if defense.condition then
375+ if defense.condition.type then
376+ spell:setConditionType(defense.condition.type)
377+ end
378+ local startDamnage = 0
379+ if defense.condition.startDamage then
380+ startDamage = defense.condition.startDamage
381+ end
382+ if defense.condition.minDamage and defense.condition.maxDamage then
383+ spell:setConditionDamage(defense.condition.minDamage, defense.condition.maxDamage, startDamage)
384+ end
385+ if defense.condition.duration then
386+ spell:setConditionDuration(defense.condition.duration)
387+ end
388+ if defense.condition.interval then
389+ spell:setConditionTickInterval(defense.condition.interval)
390+ end
391+ end
392+ else
393+ spell:setType(defense.name)
394+ if defense.type then
395+ if defense.name == "combat" then
396+ spell:setCombatType(defense.type)
397+ else
398+ spell:setConditionType(defense.type)
399+ end
400+ end
401+ if defense.interval then
402+ spell:setInterval(defense.interval)
403+ end
404+ if defense.chance then
405+ spell:setChance(defense.chance)
406+ end
407+ if defense.range then
408+ spell:setRange(defense.range)
409+ end
410+ if defense.duration then
411+ spell:setConditionDuration(defense.duration)
412+ end
413+ if defense.speed then
414+ if type(defense.speed) ~= "table" then
415+ spell:setConditionSpeedChange(defense.speed)
416+ elseif type(defense.speed) == "table" then
417+ if defense.speed.min and defense.speed.max then
418+ spell:setConditionSpeedChange(defense.speed.min, defense.speed.max)
419+ end
420+ end
421+ end
422+ if defense.target then
423+ spell:setNeedTarget(defense.target)
424+ end
425+ if defense.length then
426+ spell:setCombatLength(defense.length)
427+ end
428+ if defense.spread then
429+ spell:setCombatSpread(defense.spread)
430+ end
431+ if defense.radius then
432+ spell:setCombatRadius(defense.radius)
433+ end
434+ if defense.minDamage and defense.maxDamage then
435+ if defense.name == "combat" then
436+ spell:setCombatValue(defense.minDamage, defense.maxDamage)
437+ else
438+ local startDamage = 0
439+ if defense.startDamage then
440+ startDamage = defense.startDamage
441+ end
442+ spell:setConditionDamage(defense.minDamage, defense.maxDamage, startDamage)
443+ end
444+ end
445+ if defense.effect then
446+ spell:setCombatEffect(defense.effect)
447+ end
448+ if defense.shootEffect then
449+ spell:setCombatShootEffect(defense.shootEffect)
450+ end
451+ end
452+ elseif defense.script then
453+ spell:setScriptName(defense.script)
454+ if defense.interval then
455+ spell:setInterval(defense.interval)
456+ end
457+ if defense.chance then
458+ spell:setChance(defense.chance)
459+ end
460+ if defense.minDamage and defense.maxDamage then
461+ spell:setCombatValue(defense.minDamage, defense.maxDamage)
462+ end
463+ if defense.target then
464+ spell:setNeedTarget(defense.target)
465+ end
466+ end
467+ mtype:addDefense(spell)
468+ end
469+ end
470+ end
471+end
A data/scripts/lib/titles_data.lua +65-0 View file
@@ -0,0 +1,65 @@
1+local STORAGE_TITLE_BASE = 2000
2+local OPCODE_TITLE = 100
3+local OPCODE_SEND_TITLES = 101
4+
5+local rainbowColors = {
6+ "#FF0000", "#FF7F00", "#FFFF00", "#00FF00",
7+ "#0000FF", "#4B0082", "#8B00FF"
8+}
9+
10+local function rainbowTitleStep(playerId, title, font, idx, storageId)
11+ local player = Player(playerId)
12+ if not player or player:getStorageValue(storageId) < 1 then return end
13+ local color = rainbowColors[((idx - 1) % #rainbowColors) + 1]
14+ player:sendFirstTitle(title, font, color)
15+ addEvent(rainbowTitleStep, 1, playerId, title, font, idx + 1, storageId)
16+end
17+
18+local customTitles = {
19+ teste = { title = "Matheus Gay, Caloteiro", font = "sono_bold_border_12", color = "rainbow", storageId = 2000 },
20+ heroi = { title = "Herói da Luz", font = "sono_bold_border_12", color = "rainbow", storageId = 2001 },
21+ vip = { title = "VIP", font = "sono_bold_border_12", color = "#fa763e", storageId = 2002 },
22+ dev = { title = "<dev>", font = "sono_bold_border_12", color = "rainbow", storageId = 2003 },
23+ hokage = { title = "Sétimo Hokage", font = "sono_bold_border_12", color = "#FFA500", storageId = 2004 },
24+ reiPirata = { title = "Rei dos Piratas", font = "sono_bold_border_12", color = "#E8C0ED", storageId = 2005 },
25+ espadachim = { title = "Espadachim de Alabasta", font = "sono_bold_border_12", color = "rainbow", storageId = 2006 },
26+ sayajin = { title = "Príncipe Sayajin", font = "sono_bold_border_12", color = "#FFD700", storageId = 2007 },
27+ shinigami = { title = "Ceifador de Almas", font = "sono_bold_border_12", color = "#BA2BE2", storageId = 2008 },
28+ hunter = { title = "Caçador Supremo", font = "sono_bold_border_12", color = "#52CD32", storageId = 2009 },
29+ heroiMonstro = { title = "Matador de Titãs", font = "sono_bold_border_12", color = "#DC143C", storageId = 2010 },
30+ alquimista = { title = "Alquimista de Aço", font = "sono_bold_border_12", color = "rainbow", storageId = 2011 },
31+ sono_bold_22 = { title = "Caçador de Demônios", font = "sono_bold_border_12", color = "#FF1493", storageId = 2013 },
32+ kyuubiNinchuriki = { title = "Jinchuuriki da Nove-Caudas", font = "sono_bold_border_12", color = "#FF456E", storageId = 2014 },
33+ soulReaperSubstitute = { title = "Substituto Soul Reaper", font = "sono_bold_border_12", color = "rainbow", storageId = 2015 },
34+ espadaArrancar = { title = "Espada Arrancar", font = "sono_bold_border_12", color = "#8B0000", storageId = 2016 },
35+ almiranteMarinha = { title = "Almirante da Marinha", font = "sono_bold_border_12", color = "#1E90FF", storageId = 2017 },
36+ corsarioRei = { title = "Rei dos Corsários", font = "sono_bold_border_12", color = "rainbow", storageId = 2018 },
37+ superSayajin = { title = "Super Sayajin", font = "sono_bold_border_12", color = "#FFD700", storageId = 2019 },
38+ deusDestruicao = { title = "Deus da Destruição", font = "sono_bold_border_12", color = "#9400D3", storageId = 2020 },
39+ anciao = { title = "Ancião Supremo", font = "sono_bold_border_12", color = "#00FFFF", storageId = 2021 },
40+ titanColossal = { title = "Titã Colossal", font = "sono_bold_border_12", color = "#8B0000", storageId = 2022 },
41+ titanBestial = { title = "Titã Bestial", font = "sono_bold_border_12", color = "#A52A2A", storageId = 2023 },
42+ heroiNumeroUm = { title = "Herói Número 1", font = "sono_bold_border_12", color = "#DC143C", storageId = 2024 },
43+ estagiarioUA = { title = "Estagiário U.A.", font = "sono_bold_border_12", color = "#00BFFF", storageId = 2025 },
44+ vilaosNumeroUm = { title = "Vilão Número 1", font = "sono_bold_border_12", color = "#2E8B57", storageId = 2026 },
45+ professorUA = { title = "Professor U.A.", font = "sono_bold_border_12", color = "#228B22", storageId = 2027 },
46+ sClassHero = { title = "Herói S-Class", font = "sono_bold_border_12", color = "#FF8C00", storageId = 2028 },
47+ meioGhoul = { title = "Meio Ghoul", font = "sono_bold_border_12", color = "#BF8CBF", storageId = 2043 },
48+ hashiraAgua = { title = "Hashira da Água", font = "sono_bold_border_12", color = "#00BFFF", storageId = 2030 },
49+ hashiraSombras = { title = "Hashira das Sombras", font = "sono_bold_border_12", color = "#4B0082", storageId = 2031 },
50+ homunculo = { title = "Homúnculo", font = "sono_bold_border_12", color = "#9932CC", storageId = 2032 },
51+ pioneiroAlquimia = { title = "Pioneiro da Alquimia", font = "sono_bold_border_12", color = "#DB5A5F", storageId = 2033 },
52+ hunterClassA = { title = "Caçador Classe A", font = "sono_bold_border_12", color = "rainbow", storageId = 2034 },
53+ mageCelestial = { title = "Mago Celestial", font = "sono_bold_border_12", color = "#7FFFDA", storageId = 2035 },
54+ dragaoCelestial = { title = "Dragão Celestial", font = "sono_bold_border_12", color = "#FFD904", storageId = 2036 },
55+ mestreExceed = { title = "Mestre Exceed", font = "sono_bold_border_12", color = "rainbow", storageId = 2037 },
56+ shinigamiKira = { title = "Shinigami Kira", font = "sono_bold_border_12", color = "#7F0000", storageId = 2038 },
57+ bountyHunter = { title = "Caçador de Recompensas", font = "sono_bold_border_12", color = "#DAA706", storageId = 2039 },
58+ capitaoBebop = { title = "Capitão de Bebop", font = "sono_bold_border_12", color = "rainbow", storageId = 2040 },
59+ heroiIncrivel = { title = "Herói Incrível", font = "sono_bold_border_12", color = "#ADDFDF", storageId = 2041 },
60+}
61+
62+titleById = {}
63+for _, data in pairs(customTitles) do
64+ titleById[data.storageId] = data
65+end
A data/scripts/sex_system.lua +590-0 View file
@@ -0,0 +1,590 @@
1+--[[
2+ This is the virtual sex system created by 𝓜𝓲𝓵𝓵𝓱𝓲𝓸𝓻𝓮 𝓑𝓣
3+ For TFS-master 1.5 server
4+
5+ The virtual sex system is a system that allows players to have sexual relations with other players, this system is fully configurable and can be modified to suit the user.
6+
7+ -- WARNING --
8+ > If you are a programmer and you want to modify the system, you can do it, surely you know what you are doing.
9+ > If you're not a programmer, don't modify anything you don't understand, as it may cause system errors.
10+]]--
11+
12+local talkActionWords = "!sex"
13+local sexSystem = {
14+ types = {"oral", "anal", "vaginal"},
15+ intensities = {"mild", "normal", "strong", "extreme", "painful"},
16+ conditionTimes = { -- Depending on the intensity the benefits will last longer active.
17+ mild = 60000, -- 1 Minute
18+ normal = 120000, -- 2 Minutes
19+ strong = 180000, -- 3 Minutes
20+ extreme = 240000, -- 4 Minutes
21+ painful = 300000 -- 5 Minutes
22+ },
23+
24+ childrenId = 6579,
25+ childrenMaintance = 10, -- 10% gold from father to mother.
26+ childrenChance = 50, -- 50% chance of getting pregnant if the condom fails.
27+
28+ whiteListStorageBase = 670000,
29+ whiteListStorageMax = 100,
30+ blackListStorageBase = 670100,
31+ blackListStorageMax = 100
32+}
33+
34+local sexPoses = {
35+ [PLAYERSEX_FEMALE] = {
36+ oral = {"Dressed and seated", "The 69, but on the side", "On knees", "Above his mouth"},
37+ anal = {"The doggy", "Side", "Face down", "Riding"},
38+ vaginal = {"The back lying", "Romantic landscape", "The amazon", "The missionary"}
39+ },
40+
41+ [PLAYERSEX_MALE] = {
42+ oral = {"The lazy", "The supported", "The king of the world", "The automatic"},
43+ anal = {"At the border", "Standing", "The immersion", "The little spoon"},
44+ vaginal = {"The rabbit", "Legs to shoulder", "The cowgirl", "Forklift truck"}
45+ }
46+}
47+
48+local sexCondons = {
49+ { type = "Free", security = 0.25, price = 0 },
50+ { type = "Latex", security = 0.99, price = 100 },
51+ { type = "Polyurethane", security = 0.75, price = 50 },
52+ { type = "Animal skin", security = 0.50, price = 10 }
53+}
54+
55+local boostTypes = {
56+ regeneration = CONDITION_REGENERATION,
57+ skills = CONDITION_ATTRIBUTES,
58+ stats = CONDITION_ATTRIBUTES,
59+ speed = CONDITION_HASTE,
60+ critical = CONDITION_ATTRIBUTES,
61+ slowness = CONDITION_PARALYZE
62+}
63+
64+local boostValues = {
65+ regeneration = {"HEALTHGAIN", "MANAGAIN"},
66+ skills = {"SKILL_AXE", "SKILL_CLUB", "SKILL_SWORD"},
67+ stats = {"STAT_MAGICPOINTS"},
68+ speed = {"SPEED"},
69+ critical = {"SPECIALSKILL_CRITICALHITCHANCE", "SPECIALSKILL_CRITICALHITAMOUNT"},
70+ slowness = {"SPEED"}
71+}
72+
73+local defaultValues = {
74+ regeneration = {
75+ HEALTHTICKS = 1000,
76+ MANATICKS = 1000
77+ }
78+}
79+
80+local sexBoosts = {
81+ [PLAYERSEX_FEMALE] = {
82+ oral = {regeneration = 100, skills = 10, stats = 10, speed = 10},
83+ anal = {regeneration = 100, skills = 30, stats = 10 , slowness = 10},
84+ vaginal = {regeneration = 200, skills = 20, stats = 20, speed = 20}
85+ },
86+
87+ [PLAYERSEX_MALE] = {
88+ oral = {regeneration = 100, skills = 10, stats = 10, speed = 10},
89+ anal = {regeneration = 100, skills = 30, stats = 10 , critical = 10},
90+ vaginal = {regeneration = 200, skills = 20, stats = 20, speed = 20}
91+ }
92+}
93+
94+function sexSystem.applyBoosts(player, request)
95+ local sexBoost = sexBoosts[player:getSex()][request.type]
96+ if not sexBoost then
97+ return
98+ end
99+
100+ for type, value in pairs(sexBoost) do
101+ local condition = Condition(boostTypes[type])
102+ condition:setParameter(CONDITION_PARAM_TICKS, sexSystem.conditionTimes[request.intensity])
103+ condition:setParameter(CONDITION_PARAM_SUBID, request.playerId)
104+ for _, boost in pairs(boostValues[type] or {}) do
105+ condition:setParameter(_G[string.format("CONDITION_PARAM_%s", boost)], value)
106+ end
107+
108+ for boostType, boostValue in pairs(defaultValues[type] or {}) do
109+ condition:setParameter(_G[string.format("CONDITION_PARAM_%s", boostType)], boostValue)
110+ end
111+
112+ player:addCondition(condition)
113+ end
114+
115+ addEvent(function (playerId)
116+ local player = Player(playerId)
117+ if player then
118+ player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, string.format("You have received a boost for %s seconds.", math.floor(sexSystem.conditionTimes[request.intensity] / 1000)))
119+ end
120+ end, 3000, player:getId())
121+end
122+
123+local function getIndexByList(player, listName, targetId)
124+ targetId = targetId or -1
125+ for key = sexSystem[string.format("%sStorageBase", listName)], sexSystem[string.format("%sStorageMax", listName)] - 1 do
126+ if player.storage[key] == targetId then
127+ return key
128+ end
129+ end
130+end
131+
132+local function setIndexByList(player, listName, targetId, value)
133+ local key = getIndexByList(player, listName, targetId)
134+ if key then
135+ player.storage[key] = value or -1
136+ return true
137+ end
138+end
139+
140+local function hasWhiteList(player, targetId) return getIndexByList(player, "whiteList", targetId) end
141+local function addWhiteList(player, targetId) return setIndexByList(player, "whiteList", -1, targetId) end
142+local function removeWhiteList(player, targetId) return setIndexByList(player, "whiteList", targetId) end
143+
144+local function hasBlackList(player, targetId) return getIndexByList(player, "blackList", targetId) end
145+local function addWhiteList(player, targetId) return setIndexByList(player, "blackList", -1, targetId) end
146+local function removeWhiteList(player, targetId) return setIndexByList(player, "blackList", targetId) end
147+
148+local function createChildren(player, target)
149+ local children = Game.createItem(sexSystem.childrenId, 1)
150+ if children then
151+ children:setCustomAttribute("fatherGuid", player:getGuid())
152+ children:setCustomAttribute("motherGuid", target:getGuid())
153+ children:setAttribute(ITEM_ATTRIBUTE_NAME, string.format("%s and %s's child", player:getName(), target:getName()))
154+ children:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, "Use the child to claim your maintenance.")
155+ local returnValue = target:addItemEx(children)
156+ if returnValue ~= RETURNVALUE_NOERROR then
157+ target:sendCancelMessage(returnValue)
158+ children:moveTo(target:getPosition())
159+ end
160+ end
161+ return true
162+end
163+
164+local requests = {}
165+
166+local function resetRequest(playerId, targetId)
167+ requests[playerId] = nil
168+ if targetId then
169+ requests[targetId] = nil
170+ end
171+end
172+
173+local talkAction = TalkAction(talkActionWords)
174+
175+function talkAction.onSay(player, words, param)
176+ local pos = player:getPosition()
177+ pos:getNextPosition(player:getDirection())
178+ local tile = Tile(pos)
179+ if not tile then
180+ player:sendCancelMessage("There is nothing in front of you.")
181+ return false
182+ end
183+
184+ local target = tile:getTopVisibleCreature(player)
185+ if not target then
186+ player:sendCancelMessage("There is no one in front of you.")
187+ return false
188+ end
189+
190+ local targetId = target:getId()
191+ if hasBlackList(player, targetId) and not hasWhiteList(player, targetId) then
192+ player:sendCancelMessage("You have this player in your blacklist.")
193+ return false
194+ end
195+
196+ local request = requests[player:getId()]
197+ if request then
198+ if type(request) == "table" then
199+ sexSystem.handleRequest(player, target, request)
200+ return false
201+ end
202+
203+ player:sendCancelMessage("You already have a request.")
204+ return false
205+ end
206+
207+ local targetRequest = requests[target:getId()]
208+ if targetRequest then
209+ player:sendCancelMessage("The player already has a pending request.")
210+ return false
211+ end
212+
213+ requests[player:getId()] = target:getId()
214+ sexSystem.sendSexTypesWindow(player, target)
215+ return false
216+end
217+
218+talkAction:register()
219+
220+function sexSystem.handleRequest(player, target, request)
221+ if player:getId() == request.playerId then
222+ player:sendCancelMessage("You already have a pending request.")
223+ return false
224+ end
225+
226+ local target = Player(request.playerId)
227+ if not target then
228+ player:sendCancelMessage("The requesting player is not online.")
229+ return false
230+ elseif target:getId() ~= request.playerId then
231+ player:sendCancelMessage("The player you are looking at was not the one who requested the sex.")
232+ return false
233+ end
234+
235+ return sexSystem.sendSexHandleRequestWindow(player, target, request)
236+end
237+
238+function sexSystem.sendSexHandleRequestWindow(player, target, request)
239+ local modalWindow = ModalWindow{
240+ title = "Sex System - Request",
241+ message = string.format("The player %s wants to have sex with you, do you accept?", target:getName())
242+ }
243+
244+ modalWindow:addChoice("No")
245+ modalWindow:addChoice("Yes")
246+ if not hasBlackList(player, request.playerId) then
247+ modalWindow:addChoice("Add to blacklist")
248+ end
249+
250+ if not hasWhiteList(player, request.playerId) then
251+ modalWindow:addChoice("Add to whitelist")
252+ end
253+
254+ modalWindow:addButton("Select", function (player, button, choice)
255+ local target = Player(request.playerId)
256+ if not target then
257+ player:sendCancelMessage("The requesting player is not online.")
258+ resetRequest(player:getId(), request.playerId)
259+ return false
260+ end
261+
262+ if not choice or not table.contains({"Yes", "Add to whitelist"}, choice.text) then
263+ if choice.text == "Add to blacklist" then
264+ if not hasBlackList(player, request.playerId) then
265+ addBlackList(player, request.playerId)
266+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, string.format("You have added %s to your blacklist.", target:getName()))
267+ end
268+ end
269+
270+ player:sendCancelMessage("You have declined the request.")
271+ resetRequest(player:getId(), request.playerId)
272+ return false
273+ end
274+
275+ if choice.text == "Add to whitelist" then
276+ if not hasWhiteList(player, request.playerId) then
277+ addWhiteList(player, request.playerId)
278+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, string.format("You have added %s to your whitelist.", target:getName()))
279+ end
280+ end
281+
282+ sexSystem.startSex(target, player, request)
283+ resetRequest(player:getId(), request.playerId)
284+ end)
285+ modalWindow:addButton("Close")
286+
287+ modalWindow:setDefaultEnterButton("Select")
288+ modalWindow:setDefaultEscapeButton("Close")
289+ return modalWindow:sendToPlayer(player)
290+end
291+
292+function sexSystem.startSex(player, target, request)
293+ local con = request.con
294+ if player:getTotalMoney() < con.price then
295+ resetRequest(request.playerId, request.targetId)
296+ return false
297+ end
298+
299+ player:removeTotalMoney(con.price)
300+ player:getPosition():sendMagicEffect(CONST_ME_HEARTS)
301+ target:getPosition():sendMagicEffect(CONST_ME_HEARTS)
302+ player:sendTextMessage(MESSAGE_LOOT, string.format("You have had {33780|%s} {33952|%s} sex {3035|%s} with {33780|%s}.", request.intensity, request.type, request.pose, target:getName()))
303+ target:sendTextMessage(MESSAGE_LOOT, string.format("You have had {33780|%s} {33952|%s} sex {3035|%s} with {33780|%s}.", request.intensity, request.type, request.pose, player:getName()))
304+
305+ local childrenGenerated = nil
306+ if math.random() > con.security then
307+ if math.random(1, 100) <= sexSystem.childrenChance then
308+ local playerSex = player:getSex()
309+ if playerSex ~= target:getSex() then
310+ if playerSex == PLAYERSEX_MALE then
311+ childrenGenerated = createChildren(player, target)
312+ else
313+ childrenGenerated = createChildren(target, player)
314+ end
315+ end
316+ end
317+ end
318+
319+ if not childrenGenerated then
320+ local fluid = Game.createItem(2016, FLUID_MILK, player:getPosition())
321+ if fluid then
322+ fluid:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, string.format("%s Fluids", player:getName()))
323+ fluid:decay(0)
324+ end
325+
326+ local fluid = Game.createItem(2016, FLUID_MILK, target:getPosition())
327+ if fluid then
328+ fluid:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, string.format("%s Fluids", target:getName()))
329+ fluid:decay(0)
330+ end
331+ end
332+
333+ sexSystem.applyBoosts(player, request)
334+ sexSystem.applyBoosts(target, request)
335+ resetRequest(request.playerId, request.targetId)
336+end
337+
338+function sexSystem.sendSexTypesWindow(player, target)
339+ local modalWindow = ModalWindow{
340+ title = "Sex System - Type",
341+ message = string.format("Select the type of sex you want to have with %s", target:getName())
342+ }
343+
344+ local playerSex = player:getSex()
345+ local targetSex = target:getSex()
346+
347+ for _, sexType in pairs(sexSystem.types) do
348+ repeat
349+ if sexType == "vaginal" and playerSex == PLAYERSEX_MALE and targetSex == PLAYERSEX_MALE then
350+ break
351+ end
352+
353+ modalWindow:addChoice(string.format("%s sex", sexType))
354+ until true
355+ end
356+
357+ modalWindow:addButton("Accept", function (player, button, choice)
358+ local playerId = player:getId()
359+ if not choice or choice.text == "" then
360+ resetRequest(playerId)
361+ return false
362+ end
363+
364+ local targetId = requests[playerId]
365+ if not targetId then
366+ resetRequest(playerId)
367+ return false
368+ end
369+
370+ local target = Player(targetId)
371+ if not target then
372+ resetRequest(playerId)
373+ return false
374+ end
375+
376+ sexSystem.sendSexIntensitiesWindow(player, target, sexSystem.types[choice.id])
377+ return true
378+ end)
379+ modalWindow:addButton("Cancel", function (player, button, choice)
380+ resetRequest(player:getId())
381+ end)
382+
383+ modalWindow:setDefaultEnterButton("Accept")
384+ modalWindow:setDefaultEscapeButton("Cancel")
385+ return modalWindow:sendToPlayer(player)
386+end
387+
388+function sexSystem.sendSexIntensitiesWindow(player, target, type)
389+ local modalWindow = ModalWindow{
390+ title = "Sex System - Intensity",
391+ message = string.format("Select the intensity with which you want to have %s sex with %s.", type, target:getName())
392+ }
393+
394+ for _, intensity in pairs(sexSystem.intensities) do
395+ modalWindow:addChoice(intensity)
396+ end
397+
398+ modalWindow:addButton("Accept", function (player, button, choice)
399+ local playerId = player:getId()
400+ if not choice or choice.text == "" then
401+ resetRequest(playerId)
402+ return false
403+ end
404+
405+ local targetId = requests[playerId]
406+ if not targetId then
407+ resetRequest(playerId)
408+ return false
409+ end
410+
411+ local target = Player(targetId)
412+ if not target then
413+ resetRequest(playerId)
414+ return false
415+ end
416+
417+ sexSystem.sendSexPosesWindow(player, target, type, sexSystem.intensities[choice.id])
418+ return true
419+ end)
420+ modalWindow:addButton("Cancel", function (player, button, choice)
421+ resetRequest(player:getId())
422+ end)
423+
424+ modalWindow:setDefaultEnterButton("Accept")
425+ modalWindow:setDefaultEscapeButton("Cancel")
426+ return modalWindow:sendToPlayer(player)
427+end
428+
429+function sexSystem.sendSexPosesWindow(player, target, type, intensity)
430+ local modalWindow = ModalWindow{
431+ title = "Sex System - Poses",
432+ message = string.format("Select the position with which you want to have %s %s sex with %s.", intensity, type, target:getName())
433+ }
434+
435+ local playerId = player:getId()
436+ local poses = sexPoses[player:getSex()]
437+ if not poses then
438+ resetRequest(playerId)
439+ return false
440+ end
441+
442+ local poses = poses[type]
443+ if not poses then
444+ resetRequest(playerId)
445+ return false
446+ end
447+
448+ for _, pose in pairs(poses) do
449+ modalWindow:addChoice(pose)
450+ end
451+
452+ modalWindow:addButton("Accept", function (player, button, choice)
453+ local playerId = player:getId()
454+ if not choice or choice.text == "" then
455+ resetRequest(playerId)
456+ return false
457+ end
458+
459+ local targetId = requests[playerId]
460+ if not targetId then
461+ resetRequest(playerId)
462+ return false
463+ end
464+
465+ local target = Player(targetId)
466+ if not target then
467+ resetRequest(playerId)
468+ return false
469+ end
470+
471+ sexSystem.sendSexCondonsWindow(player, target, type, intensity, poses[choice.id])
472+ return true
473+ end)
474+ modalWindow:addButton("Cancel", function (player, button, choice)
475+ resetRequest(player:getId())
476+ end)
477+
478+ modalWindow:setDefaultEnterButton("Accept")
479+ modalWindow:setDefaultEscapeButton("Cancel")
480+ return modalWindow:sendToPlayer(player)
481+end
482+
483+function sexSystem.sendSexCondonsWindow(player, target, type, intensity, pose)
484+ local modalWindow = ModalWindow{
485+ title = "Sex System - Condons",
486+ message = "Select the condom you want to use:"
487+ }
488+
489+ for _, con in pairs(sexCondons) do
490+ modalWindow:addChoice(string.format("%s (%d%%) - %d gold", con.type, con.security * 100, con.price))
491+ end
492+
493+ modalWindow:addButton("Accept", function (player, button, choice)
494+ local playerId = player:getId()
495+ if not choice or choice.text == "" then
496+ resetRequest(playerId)
497+ return false
498+ end
499+
500+ local targetId = requests[playerId]
501+ if not targetId then
502+ resetRequest(playerId)
503+ return false
504+ end
505+
506+ local target = Player(targetId)
507+ if not target then
508+ resetRequest(playerId)
509+ return false
510+ end
511+
512+ local con = sexCondons[choice.id]
513+ if not con then
514+ resetRequest(playerId)
515+ return false
516+ end
517+
518+ if player:getTotalMoney() < con.price then
519+ player:sendTextMessage(MESSAGE_INFO_DESCR, "You don't have enough money.")
520+ resetRequest(playerId)
521+ return false
522+ end
523+
524+ sexSystem.createRequest(player, target, type, intensity, pose, con)
525+ return true
526+ end)
527+ modalWindow:addButton("Cancel", function (player, button, choice)
528+ resetRequest(player:getId())
529+ end)
530+
531+ modalWindow:setDefaultEnterButton("Accept")
532+ modalWindow:setDefaultEscapeButton("Cancel")
533+ return modalWindow:sendToPlayer(player)
534+end
535+
536+function sexSystem.createRequest(player, target, type, intensity, pose, con)
537+ local request = {
538+ playerId = player:getId(),
539+ targetId = target:getId(),
540+ type = type,
541+ intensity = intensity,
542+ pose = pose,
543+ con = con
544+ }
545+
546+ requests[player:getId()] = request
547+ requests[target:getId()] = request
548+
549+ player:sendTextMessage(MESSAGE_LOOT, string.format("You have requested to have {33780|%s} {33952|%s} sex {3035|%s} with {33780|%s}.\nWait for it to respond to your request.", intensity, type, pose, target:getName()))
550+ target:sendTextMessage(MESSAGE_LOOT, string.format("{3038|%s} has asked you to have {33780|%s} {33952|%s} sex {3035|%s}.\nTo handle the request use the command {3035|%s}.", player:getName(), intensity, type, pose, talkActionWords))
551+ return true
552+end
553+
554+local action = Action()
555+
556+function action.onUse(player, item, fromPos, target, toPos, isHotkey)
557+ local fatherGuid = item:getCustomAttribute("fatherGuid")
558+ local motherGuid = item:getCustomAttribute("motherGuid")
559+ if not fatherGuid or not motherGuid then
560+ return false
561+ end
562+
563+ local mother = Player(motherGuid)
564+ if not mother or motherGuid ~= player:getGuid() then
565+ player:sendTextMessage(MESSAGE_INFO_DESCR, "You are not the mother of this child.")
566+ return true
567+ end
568+
569+ local father = Player(fatherGuid)
570+ if not father then
571+ player:sendTextMessage(MESSAGE_INFO_DESCR, "The father of this child is offline.")
572+ return true
573+ end
574+
575+ local maintance = math.ceil(father:getTotalMoney() * (sexSystem.childrenMaintance / 100))
576+ if player:getTotalMoney() < maintance then
577+ player:sendTextMessage(MESSAGE_INFO_DESCR, "The father of this child doesn't have enough money to pay the maintance.")
578+ return true
579+ end
580+
581+ father:removeTotalMoney(maintance)
582+ player:setBankBalance(player:getBankBalance() + maintance)
583+ player:sendTextMessage(MESSAGE_INFO_DESCR, string.format("You have received %d gold from the father of this child.", maintance))
584+ child:getPosition():sendMagicEffect(CONST_ME_GIFT_WRAPS)
585+ child:remove()
586+ return true
587+end
588+
589+action:id(sexSystem.childrenId)
590+action:register()
A data/scripts/shadersitem/addshader.lua +25-0 View file
@@ -0,0 +1,25 @@
1+local ITEM_ID = 7777 -- Clickable item ID to add aura
2+local AURA_ID = 3 -- ID of the aura you want to add
3+
4+local addAuraSystem = Action()
5+
6+function addAuraSystem.onUse(player, item, fromPosition, target, toPosition, isHotkey)
7+ if item.itemid ~= ITEM_ID then
8+ return true
9+ end
10+
11+ local defaultOutfit = player:getOutfit()
12+ if defaultOutfit.lookAura ~= 0 then
13+ player:sendTextMessage(MESSAGE_INFO_DESCR, "You already have this aura!")
14+ return true
15+ end
16+
17+ player:addAura(AURA_ID)
18+ player:getPosition():sendMagicEffect(CONST_ME_MAGIC_RED)
19+ player:sendTextMessage(MESSAGE_INFO_DESCR, 'You have received a new aura!')
20+ item:remove(1)
21+ return true
22+end
23+
24+addAuraSystem:id(ITEM_ID)
25+addAuraSystem:register()
A data/scripts/stats_inspect.lua +95-0 View file
@@ -0,0 +1,95 @@
1+local LoginEvent = CreatureEvent("InspectLogin")
2+
3+function LoginEvent.onLogin(player)
4+ player:registerEvent("InspectExtended")
5+ return true
6+end
7+
8+local ExtendedEvent = CreatureEvent("InspectExtended")
9+
10+function ExtendedEvent.onExtendedOpcode(player, opcode, buffer)
11+ if opcode == OPCODE_TESTPROFILE then
12+ local status, json_data =
13+ pcall(
14+ function()
15+ return json.decode(buffer)
16+ end
17+ )
18+ if not status then
19+ return
20+ end
21+
22+ local action = json_data.action
23+ local data = json_data.data
24+
25+ if action == "inspect" then
26+ inspectPlayer(player, data)
27+ print("Inspect")
28+ end
29+ end
30+end
31+
32+function inspectPlayer(player, targetName)
33+ local exhaustionTime = player:getExhaustion(PlayerStorageKeys.inspectProfileOpcodeExhaustion)
34+
35+ if exhaustionTime > 0 then
36+ local message = "[pt-BR] Espere " .. exhaustionTime .. " segundos antes de fazer isso novamente.\n\n" ..
37+ "[en-US] Please wait " .. exhaustionTime .. " seconds before doing this again."
38+
39+ player:popupFYI(message)
40+ return false
41+ end
42+
43+ local target = Player(targetName)
44+ if target then
45+ local stats = {}
46+ stats.name = target:getName()
47+ local guild = target:getGuild()
48+ stats.guild = guild and guild:getName() or "None"
49+ stats.vocation = target:getVocation():getName()
50+ stats.health = target:getHealth()
51+ stats.maxHealth = target:getMaxHealth()
52+ stats.mana = target:getMana()
53+ stats.maxMana = target:getMaxMana()
54+ stats.level = target:getLevel()
55+ stats.exp = target:getExperience()
56+ stats.skills = {}
57+ for i = SKILL_FIST, SKILL_FISHING do
58+ local totalSkill = target:getEffectiveSkillLevel(i)
59+ local baseSkill = target:getSkillLevel(i)
60+ local skillPercent = target:getSkillPercent(i)
61+ local bonus = totalSkill - baseSkill
62+ stats.skills[i + 1] = {
63+ total = totalSkill,
64+ bonus = bonus,
65+-- percent = skillPercent
66+ }
67+ end
68+ local baseMagic = target:getBaseMagicLevel()
69+ local totalMagic = target:getMagicLevel()
70+ stats.skills[#stats.skills + 1] = {
71+ total = totalMagic,
72+ bonus = totalMagic - baseMagic,
73+-- percent = target:getMagicLevelPercent()
74+ }
75+ stats.stats = {}
76+
77+ player:sendExtendedOpcode(OPCODE_TESTPROFILE, json.encode({action = "stats", data = stats}))
78+ player:setExhaustion(PlayerStorageKeys.inspectProfileOpcodeExhaustion, 4)
79+
80+ for slot = CONST_SLOT_HEAD, CONST_SLOT_RING do
81+ local item = target:getSlotItem(slot)
82+ if item then
83+ player:sendExtendedOpcode(OPCODE_TESTPROFILE, json.encode({action = "item", data = {slot = slot, item = item:getType():getClientId()}}))
84+ else
85+ player:sendExtendedOpcode(OPCODE_TESTPROFILE, json.encode({action = "item", data = {slot = slot}}))
86+ end
87+ end
88+
89+ end
90+end
91+
92+LoginEvent:type("login")
93+LoginEvent:register()
94+ExtendedEvent:type("extendedopcode")
95+ExtendedEvent:register()
A data/spells/lib/spells.lua +343-0 View file
@@ -0,0 +1,343 @@
1+--Pre-made areas
2+
3+--Waves
4+AREA_WAVE3 = {
5+{1, 1, 1},
6+{1, 1, 1},
7+{0, 3, 0}
8+}
9+
10+AREA_WAVE4 = {
11+{1, 1, 1, 1, 1},
12+{0, 1, 1, 1, 0},
13+{0, 1, 1, 1, 0},
14+{0, 0, 3, 0, 0}
15+}
16+
17+AREA_WAVE6 = {
18+{0, 0, 0, 0, 0},
19+{0, 1, 3, 1, 0},
20+{0, 0, 0, 0, 0}
21+}
22+
23+AREA_SQUAREWAVE5 = {
24+{1, 1, 1},
25+{1, 1, 1},
26+{1, 1, 1},
27+{0, 1, 0},
28+{0, 3, 0}
29+}
30+
31+AREA_SQUAREWAVE6 = {
32+{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0},
33+{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0},
34+{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0},
35+{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0},
36+{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0},
37+{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0},
38+{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0},
39+{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0},
40+{0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0},
41+{0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0},
42+{0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0},
43+{0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0}
44+}
45+
46+AREA_SQUAREWAVE7 = {
47+{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0},
48+{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0},
49+{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0},
50+{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0},
51+{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0},
52+{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0},
53+{0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0},
54+{0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0},
55+{0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0},
56+{0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0},
57+{0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0}
58+}
59+
60+--Diagonal waves
61+AREADIAGONAL_WAVE4 = {
62+{0, 0, 0, 0, 1, 0},
63+{0, 0, 0, 1, 1, 0},
64+{0, 0, 1, 1, 1, 0},
65+{0, 1, 1, 1, 1, 0},
66+{1, 1, 1, 1, 1, 0},
67+{0, 0, 0, 0, 0, 3}
68+}
69+
70+AREADIAGONAL_SQUAREWAVE5 = {
71+{1, 1, 1, 0, 0},
72+{1, 1, 1, 0, 0},
73+{1, 1, 1, 0, 0},
74+{0, 0, 0, 1, 0},
75+{0, 0, 0, 0, 3}
76+}
77+
78+AREADIAGONAL_WAVE6 = {
79+{0, 0, 1},
80+{0, 3, 0},
81+{1, 0, 0}
82+}
83+
84+--Beams
85+AREA_BEAM1 = {
86+{3}
87+}
88+
89+AREA_BEAM5 = {
90+{1},
91+{1},
92+{1},
93+{1},
94+{3}
95+}
96+
97+AREA_BEAM7 = {
98+{1},
99+{1},
100+{1},
101+{1},
102+{1},
103+{1},
104+{3}
105+}
106+
107+AREA_BEAM8 = {
108+{1},
109+{1},
110+{1},
111+{1},
112+{1},
113+{1},
114+{1},
115+{3}
116+}
117+
118+--Diagonal Beams
119+AREADIAGONAL_BEAM5 = {
120+{1, 0, 0, 0, 0},
121+{0, 1, 0, 0, 0},
122+{0, 0, 1, 0, 0},
123+{0, 0, 0, 1, 0},
124+{0, 0, 0, 0, 3}
125+}
126+
127+AREADIAGONAL_BEAM7 = {
128+{1, 0, 0, 0, 0, 0, 0},
129+{0, 1, 0, 0, 0, 0, 0},
130+{0, 0, 1, 0, 0, 0, 0},
131+{0, 0, 0, 1, 0, 0, 0},
132+{0, 0, 0, 0, 1, 0, 0},
133+{0, 0, 0, 0, 0, 1, 0},
134+{0, 0, 0, 0, 0, 0, 3}
135+}
136+
137+--Circles
138+AREA_CIRCLE2X2 = {
139+{0, 1, 1, 1, 0},
140+{1, 1, 1, 1, 1},
141+{1, 1, 3, 1, 1},
142+{1, 1, 1, 1, 1},
143+{0, 1, 1, 1, 0}
144+}
145+
146+AREA_CIRCLE3X3 = {
147+{0, 0, 1, 1, 1, 0, 0},
148+{0, 1, 1, 1, 1, 1, 0},
149+{1, 1, 1, 1, 1, 1, 1},
150+{1, 1, 1, 3, 1, 1, 1},
151+{1, 1, 1, 1, 1, 1, 1},
152+{0, 1, 1, 1, 1, 1, 0},
153+{0, 0, 1, 1, 1, 0, 0}
154+}
155+
156+-- Crosses
157+AREA_CROSS1X1 = {
158+{0, 1, 0},
159+{1, 3, 1},
160+{0, 1, 0}
161+}
162+
163+AREA_CIRCLE5X5 = {
164+{0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0},
165+{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0},
166+{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0},
167+{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0},
168+{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0},
169+{1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1},
170+{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0},
171+{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0},
172+{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0},
173+{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0},
174+{0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0}
175+}
176+
177+AREA_CIRCLE6X6 = {
178+{0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0},
179+{0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0},
180+{0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0},
181+{0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0},
182+{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0},
183+{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0},
184+{1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1},
185+{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0},
186+{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0},
187+{0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0},
188+{0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0},
189+{0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0},
190+{0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}
191+}
192+
193+--Squares
194+AREA_SQUARE1X1 = {
195+{1, 1, 1},
196+{1, 3, 1},
197+{1, 1, 1}
198+}
199+
200+-- Walls
201+AREA_WALLFIELD = {
202+{1, 1, 3, 1, 1}
203+}
204+
205+AREADIAGONAL_WALLFIELD = {
206+{0, 0, 0, 0, 1},
207+{0, 0, 0, 1, 1},
208+{0, 1, 3, 1, 0},
209+{1, 1, 0, 0, 0},
210+{1, 0, 0, 0, 0},
211+}
212+
213+-- Spells-only arrays
214+
215+--This HUGE array contains all corpses of the game, until protocol 8.0
216+-- It is used on animate dead rune and on undead legion spell. No unmoveable corpses are there.
217+CORPSES = {
218+2806,2807,2808,2809,2810,2811,2812,2813,2814,2815,2816,2817,2818,2819,2820,2821,2822,2823,
219+2824,2825,2826,2827,2828,2829,2830,2831,2832,2833,2834,2835,2836,2837,2838,2839,2840,2841,
220+2842,2843,2844,2845,2846,2847,2848,2849,2850,2851,2852,2853,2854,2855,2856,2857,2858,2859,
221+2879,2880,2881,2882,2883,2884,2885,2886,2887,2888,2889,2890,2891,2892,2893,2894,2895,2896,
222+2897,2898,2899,2900,2901,2902,2903,2904,2905,2906,2907,2908,2909,2910,2911,2912,2913,2914,
223+2915,2916,2917,2918,2919,2920,2921,2922,2923,2924,2925,2926,2927,2928,2929,2930,2931,2932,
224+2933,2934,2935,2936,2937,2938,2939,2940,2941,2942,2943,2944,2945,2946,2947,2948,2949,2950,
225+2951,2952,2953,2954,2955,2956,2957,2958,2959,2960,2961,2962,2963,2964,2965,2966,2967,2968,
226+2969,2970,2971,2972,2973,2974,2975,2976,2977,2978,2979,2980,2981,2982,2983,2984,2985,2986,
227+2987,2988,2989,2990,2991,2992,2993,2994,2995,2996,2997,2998,2999,3000,3001,3002,3003,3004,
228+3005,3006,3007,3008,3009,3010,3011,3012,3013,3014,3015,3016,3017,3018,3019,3020,3021,3022,
229+3023,3024,3025,3026,3027,3028,3029,3030,3031,3032,3033,3034,3035,3036,3037,3038,3039,3040,
230+3041,3042,3043,3044,3045,3046,3047,3048,3049,3050,3051,3052,3053,3054,3055,3056,3057,3058,
231+3059,3060,3061,3062,3063,3064,3065,3066,3067,3068,3069,3070,3071,3072,3073,3074,3075,3076,
232+3077,3078,3079,3080,3081,3082,3083,3084,3085,3086,3087,3088,3089,3090,3091,3092,3093,3094,
233+3095,3096,3097,3098,3099,3100,3101,3102,3103,3104,3105,3106,3107,3108,3109,3110,3111,3112,
234+3113,3114,3115,3116,3117,3118,3119,3120,3121,3128,3129,3130,3131,3132,3133,3134,4252,4253,
235+4254,4255,4256,4257,4258,4259,4260,4261,4262,4263,4264,4265,4266,4267,4268,4269,4270,4271,
236+4272,4273,4274,4275,4276,4277,4278,4279,4280,4281,4282,4283,4284,4285,4286,4287,4288,4289,
237+4290,4291,4292,4293,4294,4295,4296,4297,4298,4299,4300,4301,4302,4303,4304,4305,4306,4307,
238+4308,4309,4310,4311,4312,4313,4314,4315,4316,4317,4318,4319,4320,4321,4322,4323,4324,4325,
239+4326,4327,5522,5523,5524,5525,5526,5527,5528,5529,5530,5531,5532,5533,5534,5535,5536,5537,
240+5538,5540,5541,5542,5565,5566,5567,5568,5625,5626,5627,5628,5629,5630,5666,5667,5668,5688,
241+5689,5690,5727,5728,5729,5762,5765,5766,5767,5931,5932,5933,5934,5935,5936,5965,6022,6082,
242+6083,6084,6303,6304,6305,6307,6308,6309,6310,6313,6314,6315,6317,6318,6319,6321,6322,6323,
243+6325,6326,6327,6328,6329,6330,6333,6334,6335,6337,6338,6339,6341,6342,6343,6345,6346,6347,
244+6349,6350,6351,6355,6365,6366,6367,6520,6521,6522,6560,7092,7093,7094,7256,7257,7258,7283,
245+7284,7285,7317,7318,7319,7321,7322,7323,7325,7326,7328,7329,7331,7332,7333,7335,7336,7337,
246+7339,7340,7341,7345,7346,7347,7623,7624,7625,7626,7627,7629,7630,7631,7638,7639,7640,7741,
247+7742,7743,7848,7849,7908,7927,7928,7929,7931,7970,7971,8272}
248+
249+-- This array contains all destroyable field items
250+FIELDS = {1487,1488,1489,1490,1491,1492,1493,1494,1495,1496,1500,1501,1502,1503,1504}
251+
252+function Player:addPartyCondition(combat, variant, condition, baseMana)
253+ local party = self:getParty()
254+ if not party then
255+ self:sendCancelMessage(RETURNVALUE_NOPARTYMEMBERSINRANGE)
256+ self:getPosition():sendMagicEffect(CONST_ME_POFF)
257+ return false
258+ end
259+
260+ local positions = combat:getPositions(self, variant)
261+ local members = party:getMembers()
262+ members[#members + 1] = party:getLeader()
263+
264+ local affectedMembers = {}
265+ for _, member in ipairs(members) do
266+ local memberPosition = member:getPosition()
267+ for _, position in ipairs(positions) do
268+ if memberPosition == position then
269+ affectedMembers[#affectedMembers + 1] = member
270+ end
271+ end
272+ end
273+
274+ if #affectedMembers <= 1 then
275+ self:sendCancelMessage(RETURNVALUE_NOPARTYMEMBERSINRANGE)
276+ self:getPosition():sendMagicEffect(CONST_ME_POFF)
277+ return false
278+ end
279+
280+ local mana = math.ceil(#affectedMembers * math.pow(0.9, #affectedMembers - 1) * baseMana)
281+ if self:getMana() < mana then
282+ self:sendCancelMessage(RETURNVALUE_NOTENOUGHMANA)
283+ self:getPosition():sendMagicEffect(CONST_ME_POFF)
284+ return false
285+ end
286+
287+ self:addMana(-mana)
288+ self:addManaSpent(mana)
289+
290+ for _, member in ipairs(affectedMembers) do
291+ member:addCondition(condition)
292+ end
293+
294+ for _, position in ipairs(positions) do
295+ position:sendMagicEffect(CONST_ME_MAGIC_BLUE)
296+ end
297+ return true
298+end
299+
300+function Player:conjureItem(reagentId, conjureId, conjureCount, effect)
301+ if not conjureCount and conjureId ~= 0 then
302+ local itemType = ItemType(conjureId)
303+ if itemType:getId() == 0 then
304+ return false
305+ end
306+
307+ local charges = itemType:getCharges()
308+ if charges ~= 0 then
309+ conjureCount = charges
310+ end
311+ end
312+
313+ if reagentId ~= 0 and not self:removeItem(reagentId, 1, -1) then
314+ self:sendCancelMessage(RETURNVALUE_YOUNEEDAMAGICITEMTOCASTSPELL)
315+ self:getPosition():sendMagicEffect(CONST_ME_POFF)
316+ return false
317+ end
318+
319+ local item = self:addItem(conjureId, conjureCount)
320+ if not item then
321+ self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE)
322+ self:getPosition():sendMagicEffect(CONST_ME_POFF)
323+ return false
324+ end
325+
326+ if item:hasAttribute(ITEM_ATTRIBUTE_DURATION) then
327+ item:decay()
328+ end
329+
330+ self:getPosition():sendMagicEffect(item:getType():isRune() and CONST_ME_MAGIC_RED or effect)
331+ return true
332+end
333+
334+function Creature:addAttributeCondition(parameters)
335+ local condition = Condition(CONDITION_ATTRIBUTES)
336+ for _, parameter in ipairs(parameters) do
337+ if parameter.key and parameter.value then
338+ condition:setParameter(parameter.key, parameter.value)
339+ end
340+ end
341+
342+ self:addCondition(condition)
343+end
A data/spells/scripts/arcane_nova.lua +158-0 View file
@@ -0,0 +1,158 @@
1+SPELL_ID = 00
2+local min = 0
3+local max = 0
4+
5+-- =============== COMBAT VARS ===============
6+-- Areas/Combat for 400ms
7+local combat4_Brush_2 = createCombatObject()
8+setCombatParam(combat4_Brush_2, COMBAT_PARAM_EFFECT, CONST_ME_ENERGYAREA)
9+setCombatParam(combat4_Brush_2, COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE)
10+setCombatArea(combat4_Brush_2,createCombatArea({{0, 1, 0},
11+{1, 2, 1},
12+{0, 1, 0}}))
13+function getDmg_Brush_2(cid, level, maglevel)
14+
15+ return -min, -max
16+end
17+setCombatCallback(combat4_Brush_2, CALLBACK_PARAM_LEVELMAGICVALUE, "getDmg_Brush_2")
18+
19+-- Areas/Combat for 0ms
20+local combat0_Brush = createCombatObject()
21+setCombatParam(combat0_Brush, COMBAT_PARAM_EFFECT, CONST_ME_YALAHARIGHOST)
22+setCombatParam(combat0_Brush, COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE)
23+setCombatArea(combat0_Brush,createCombatArea({{0, 0, 1, 0, 0},
24+{0, 0, 0, 0, 0},
25+{1, 0, 2, 0, 1},
26+{0, 0, 0, 0, 0},
27+{0, 0, 1, 0, 0}}))
28+function getDmg_Brush(cid, level, maglevel)
29+
30+ return -min, -max
31+end
32+setCombatCallback(combat0_Brush, CALLBACK_PARAM_LEVELMAGICVALUE, "getDmg_Brush")
33+
34+-- Areas/Combat for 1300ms
35+local combat13_Brush = createCombatObject()
36+setCombatParam(combat13_Brush, COMBAT_PARAM_EFFECT, CONST_ME_YALAHARIGHOST)
37+setCombatParam(combat13_Brush, COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE)
38+setCombatArea(combat13_Brush,createCombatArea({{0, 0, 0, 1, 0, 0, 0},
39+{0, 0, 0, 0, 0, 0, 0},
40+{0, 0, 0, 0, 0, 0, 0},
41+{1, 0, 0, 2, 0, 0, 1},
42+{0, 0, 0, 0, 0, 0, 0},
43+{0, 0, 0, 0, 0, 0, 0},
44+{0, 0, 0, 1, 0, 0, 0}}))
45+function getDmg_Brush(cid, level, maglevel)
46+
47+ return -min, -max
48+end
49+setCombatCallback(combat13_Brush, CALLBACK_PARAM_LEVELMAGICVALUE, "getDmg_Brush")
50+
51+-- Areas/Combat for 1600ms
52+local combat16_Brush_2 = createCombatObject()
53+setCombatParam(combat16_Brush_2, COMBAT_PARAM_EFFECT, CONST_ME_ENERGYAREA)
54+setCombatParam(combat16_Brush_2, COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE)
55+setCombatArea(combat16_Brush_2,createCombatArea({{0, 0, 1, 0, 0},
56+{0, 1, 1, 1, 0},
57+{1, 1, 2, 1, 1},
58+{0, 1, 1, 1, 0},
59+{0, 0, 1, 0, 0}}))
60+function getDmg_Brush_2(cid, level, maglevel)
61+
62+ return -min, -max
63+end
64+setCombatCallback(combat16_Brush_2, CALLBACK_PARAM_LEVELMAGICVALUE, "getDmg_Brush_2")
65+
66+-- Areas/Combat for 2600ms
67+local combat26_Brush = createCombatObject()
68+setCombatParam(combat26_Brush, COMBAT_PARAM_EFFECT, CONST_ME_YALAHARIGHOST)
69+setCombatParam(combat26_Brush, COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE)
70+setCombatArea(combat26_Brush,createCombatArea({{0, 0, 0, 0, 1, 0, 0, 0, 0},
71+{0, 0, 0, 0, 0, 0, 0, 0, 0},
72+{0, 0, 0, 0, 0, 0, 0, 0, 0},
73+{0, 0, 0, 0, 0, 0, 0, 0, 0},
74+{1, 0, 0, 0, 2, 0, 0, 0, 1},
75+{0, 0, 0, 0, 0, 0, 0, 0, 0},
76+{0, 0, 0, 0, 0, 0, 0, 0, 0},
77+{0, 0, 0, 0, 0, 0, 0, 0, 0},
78+{0, 0, 0, 0, 1, 0, 0, 0, 0}}))
79+function getDmg_Brush(cid, level, maglevel)
80+
81+ return -min, -max
82+end
83+setCombatCallback(combat26_Brush, CALLBACK_PARAM_LEVELMAGICVALUE, "getDmg_Brush")
84+
85+-- Areas/Combat for 2800ms
86+local combat28_Brush_2 = createCombatObject()
87+setCombatParam(combat28_Brush_2, COMBAT_PARAM_EFFECT, CONST_ME_ENERGYAREA)
88+setCombatParam(combat28_Brush_2, COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE)
89+setCombatArea(combat28_Brush_2,createCombatArea({{0, 0, 0, 1, 0, 0, 0},
90+{0, 0, 1, 1, 1, 0, 0},
91+{0, 1, 1, 1, 1, 1, 0},
92+{1, 1, 1, 2, 1, 1, 1},
93+{0, 1, 1, 1, 1, 1, 0},
94+{0, 0, 1, 1, 1, 0, 0},
95+{0, 0, 0, 1, 0, 0, 0}}))
96+function getDmg_Brush_2(cid, level, maglevel)
97+
98+ return -min, -max
99+end
100+setCombatCallback(combat28_Brush_2, CALLBACK_PARAM_LEVELMAGICVALUE, "getDmg_Brush_2")
101+
102+-- =============== CORE FUNCTIONS ===============
103+local function RunPart(c,cid,var,dirList,dirEmitPos) -- Part
104+ if (isCreature(cid)) then
105+ doCombat(cid, c, var)
106+ if (dirList ~= nil) then -- Emit distance effects
107+ local i = 2;
108+ while (i < #dirList) do
109+ doSendDistanceShoot(dirEmitPos,{x=dirEmitPos.x-dirList[i],y=dirEmitPos.y-dirList[i+1],z=dirEmitPos.z},dirList[1])
110+ i = i + 2
111+ end
112+ end
113+ end
114+end
115+function checkSpell64(cid)
116+ local GUN_ID = cid:getStorageValue(_SYSTEM.INVENTARIO[04].UID_01)
117+ local CHECK_SLOT_01 = cid:getStorageValue(_SYSTEM.INVENTARIO[04].UID_04)
118+ local CHECK_SLOT_02 = cid:getStorageValue(_SYSTEM.INVENTARIO[04].UID_05)
119+ local CHECK_SLOT_03 = cid:getStorageValue(_SYSTEM.INVENTARIO[04].UID_06)
120+ SPELL_ID = 64
121+
122+ if GUN_ID > 0 then
123+ if CHECK_SLOT_01 == SPELL_ID then
124+ cid:sendCancelMessage("@SpellCooldown 01")
125+ return true
126+ elseif CHECK_SLOT_02 == SPELL_ID then
127+ cid:sendCancelMessage("@SpellCooldown 02")
128+ return true
129+ elseif CHECK_SLOT_03 == SPELL_ID then
130+ cid:sendCancelMessage("@SpellCooldown 03")
131+ return true
132+ else
133+ return false
134+ end
135+ else
136+ return false
137+ end
138+end
139+function onCastSpell(cid, var)
140+ if checkSpell64(cid) or cid:getGroup():getAccess() then
141+ local startPos = getCreaturePosition(cid)
142+ local GUN_ID = cid:getStorageValue(_SYSTEM.INVENTARIO[04].UID_01)
143+ local starGun = cid:getStorageValue(_SYSTEM.INVENTARIO[04].UID_03)
144+ local levelGun = cid:getStorageValue(_SYSTEM.INVENTARIO[04].UID_07)
145+ min = onDamage(cid, GUN_ID, levelGun, starGun, "SKILL_MIN", SPELL_ID)
146+ max = min
147+ addEvent(RunPart,400,combat4_Brush_2,cid.uid,var)
148+ RunPart(combat0_Brush,cid,var)
149+ addEvent(RunPart,1300,combat13_Brush,cid.uid,var)
150+ addEvent(RunPart,1600,combat16_Brush_2,cid.uid,var)
151+ addEvent(RunPart,2600,combat26_Brush,cid.uid,var)
152+ addEvent(RunPart,2800,combat28_Brush_2,cid.uid,var)
153+ return true
154+ else
155+ cid:getPosition():sendMagicEffect(03)
156+ return false
157+ end
158+end
A data/spells/scripts/arcane_rage.lua +183-0 View file
@@ -0,0 +1,183 @@
1+SPELL_ID = 00
2+local min = 0
3+local max = 0
4+
5+-- =============== COMBAT VARS ===============
6+-- Areas/Combat for 100ms
7+local combat1_Brush = createCombatObject()
8+setCombatParam(combat1_Brush, COMBAT_PARAM_EFFECT, CONST_ME_ENERGYAREA)
9+setCombatParam(combat1_Brush, COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE)
10+setCombatArea(combat1_Brush,createCombatArea({{2},
11+{1}}))
12+function getDmg_Brush(cid, level, maglevel)
13+
14+ return -min, -max
15+end
16+setCombatCallback(combat1_Brush, CALLBACK_PARAM_LEVELMAGICVALUE, "getDmg_Brush")
17+local dfcombat1_Brush = {CONST_ANI_SMALLICE,0,1}
18+
19+-- Areas/Combat for 0ms
20+local combat0_Brush = createCombatObject()
21+setCombatParam(combat0_Brush, COMBAT_PARAM_EFFECT, CONST_ME_ENERGYAREA)
22+setCombatParam(combat0_Brush, COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE)
23+setCombatArea(combat0_Brush,createCombatArea({{2, 0},
24+{0, 1}}))
25+function getDmg_Brush(cid, level, maglevel)
26+
27+ return -min, -max
28+end
29+setCombatCallback(combat0_Brush, CALLBACK_PARAM_LEVELMAGICVALUE, "getDmg_Brush")
30+local dfcombat0_Brush = {CONST_ANI_SMALLICE,1,1}
31+
32+-- Areas/Combat for 200ms
33+local combat2_Brush = createCombatObject()
34+setCombatParam(combat2_Brush, COMBAT_PARAM_EFFECT, CONST_ME_ENERGYAREA)
35+setCombatParam(combat2_Brush, COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE)
36+setCombatArea(combat2_Brush,createCombatArea({{0, 2},
37+{1, 0}}))
38+function getDmg_Brush(cid, level, maglevel)
39+
40+ return -min, -max
41+end
42+setCombatCallback(combat2_Brush, CALLBACK_PARAM_LEVELMAGICVALUE, "getDmg_Brush")
43+local dfcombat2_Brush = {CONST_ANI_SMALLICE,-1,1}
44+
45+-- Areas/Combat for 300ms
46+local combat3_Brush = createCombatObject()
47+setCombatParam(combat3_Brush, COMBAT_PARAM_EFFECT, CONST_ME_ENERGYAREA)
48+setCombatParam(combat3_Brush, COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE)
49+setCombatArea(combat3_Brush,createCombatArea({{1, 2}}))
50+function getDmg_Brush(cid, level, maglevel)
51+
52+ return -min, -max
53+end
54+setCombatCallback(combat3_Brush, CALLBACK_PARAM_LEVELMAGICVALUE, "getDmg_Brush")
55+local dfcombat3_Brush = {CONST_ANI_SMALLICE,-1,0}
56+
57+-- Areas/Combat for 400ms
58+local combat4_Brush = createCombatObject()
59+setCombatParam(combat4_Brush, COMBAT_PARAM_EFFECT, CONST_ME_ENERGYAREA)
60+setCombatParam(combat4_Brush, COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE)
61+setCombatArea(combat4_Brush,createCombatArea({{1, 0},
62+{0, 2}}))
63+function getDmg_Brush(cid, level, maglevel)
64+
65+ return -min, -max
66+end
67+setCombatCallback(combat4_Brush, CALLBACK_PARAM_LEVELMAGICVALUE, "getDmg_Brush")
68+local dfcombat4_Brush = {CONST_ANI_SMALLICE,-1,-1}
69+
70+-- Areas/Combat for 500ms
71+local combat5_Brush = createCombatObject()
72+setCombatParam(combat5_Brush, COMBAT_PARAM_EFFECT, CONST_ME_ENERGYAREA)
73+setCombatParam(combat5_Brush, COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE)
74+setCombatArea(combat5_Brush,createCombatArea({{1},
75+{2}}))
76+function getDmg_Brush(cid, level, maglevel)
77+
78+ return -min, -max
79+end
80+setCombatCallback(combat5_Brush, CALLBACK_PARAM_LEVELMAGICVALUE, "getDmg_Brush")
81+local dfcombat5_Brush = {CONST_ANI_SMALLICE,0,-1}
82+
83+-- Areas/Combat for 600ms
84+local combat6_Brush = createCombatObject()
85+setCombatParam(combat6_Brush, COMBAT_PARAM_EFFECT, CONST_ME_ENERGYAREA)
86+setCombatParam(combat6_Brush, COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE)
87+setCombatArea(combat6_Brush,createCombatArea({{0, 1},
88+{2, 0}}))
89+function getDmg_Brush(cid, level, maglevel)
90+
91+ return -min, -max
92+end
93+setCombatCallback(combat6_Brush, CALLBACK_PARAM_LEVELMAGICVALUE, "getDmg_Brush")
94+local dfcombat6_Brush = {CONST_ANI_SMALLICE,1,-1}
95+
96+-- Areas/Combat for 700ms
97+local combat7_Brush = createCombatObject()
98+setCombatParam(combat7_Brush, COMBAT_PARAM_EFFECT, CONST_ME_ENERGYAREA)
99+setCombatParam(combat7_Brush, COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE)
100+setCombatArea(combat7_Brush,createCombatArea({{2, 1}}))
101+function getDmg_Brush(cid, level, maglevel)
102+
103+ return -min, -max
104+end
105+setCombatCallback(combat7_Brush, CALLBACK_PARAM_LEVELMAGICVALUE, "getDmg_Brush")
106+local dfcombat7_Brush = {CONST_ANI_SMALLICE,1,0}
107+
108+-- Areas/Combat for 800ms
109+local combat8_Brush = createCombatObject()
110+setCombatParam(combat8_Brush, COMBAT_PARAM_EFFECT, CONST_ME_ENERGYAREA)
111+setCombatParam(combat8_Brush, COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE)
112+setCombatArea(combat8_Brush,createCombatArea({{2, 0},
113+{0, 1}}))
114+function getDmg_Brush(cid, level, maglevel)
115+
116+ return -min, -max
117+end
118+setCombatCallback(combat8_Brush, CALLBACK_PARAM_LEVELMAGICVALUE, "getDmg_Brush")
119+local dfcombat8_Brush = {CONST_ANI_SMALLICE,1,1}
120+
121+-- =============== CORE FUNCTIONS ===============
122+local function RunPart(c,cid,var,dirList,dirEmitPos) -- Part
123+ if (isCreature(cid)) then
124+ doCombat(cid, c, var)
125+ if (dirList ~= nil) then -- Emit distance effects
126+ local i = 2;
127+ while (i < #dirList) do
128+ doSendDistanceShoot(dirEmitPos,{x=dirEmitPos.x-dirList[i],y=dirEmitPos.y-dirList[i+1],z=dirEmitPos.z},dirList[1])
129+ i = i + 2
130+ end
131+ end
132+ end
133+end
134+
135+function checkSpell10(cid)
136+ local GUN_ID = cid:getStorageValue(_SYSTEM.INVENTARIO[04].UID_01)
137+ local CHECK_SLOT_01 = cid:getStorageValue(_SYSTEM.INVENTARIO[04].UID_04)
138+ local CHECK_SLOT_02 = cid:getStorageValue(_SYSTEM.INVENTARIO[04].UID_05)
139+ local CHECK_SLOT_03 = cid:getStorageValue(_SYSTEM.INVENTARIO[04].UID_06)
140+ SPELL_ID = 10
141+
142+ if GUN_ID > 0 then
143+ if CHECK_SLOT_01 == SPELL_ID then
144+ cid:sendCancelMessage("@SpellCooldown 01")
145+ return true
146+ elseif CHECK_SLOT_02 == SPELL_ID then
147+ cid:sendCancelMessage("@SpellCooldown 02")
148+ return true
149+ elseif CHECK_SLOT_03 == SPELL_ID then
150+ cid:sendCancelMessage("@SpellCooldown 03")
151+ return true
152+ else
153+ return false
154+ end
155+ else
156+ return false
157+ end
158+ return true
159+end
160+
161+function onCastSpell(cid, var)
162+ if checkSpell10(cid) or cid:getGroup():getAccess() then
163+ local startPos = getCreaturePosition(cid)
164+ local GUN_ID = cid:getStorageValue(_SYSTEM.INVENTARIO[04].UID_01)
165+ local starGun = cid:getStorageValue(_SYSTEM.INVENTARIO[04].UID_03)
166+ local levelGun = cid:getStorageValue(_SYSTEM.INVENTARIO[04].UID_07)
167+ min = onDamage(cid, GUN_ID, levelGun, starGun, "SKILL_MIN", SPELL_ID)
168+ max = min
169+ addEvent(RunPart,100,combat1_Brush,cid.uid,var,dfcombat1_Brush,startPos)
170+ RunPart(combat0_Brush,cid,var,dfcombat0_Brush,startPos)
171+ addEvent(RunPart,200,combat2_Brush,cid.uid,var,dfcombat2_Brush,startPos)
172+ addEvent(RunPart,300,combat3_Brush,cid.uid,var,dfcombat3_Brush,startPos)
173+ addEvent(RunPart,400,combat4_Brush,cid.uid,var,dfcombat4_Brush,startPos)
174+ addEvent(RunPart,500,combat5_Brush,cid.uid,var,dfcombat5_Brush,startPos)
175+ addEvent(RunPart,600,combat6_Brush,cid.uid,var,dfcombat6_Brush,startPos)
176+ addEvent(RunPart,700,combat7_Brush,cid.uid,var,dfcombat7_Brush,startPos)
177+ addEvent(RunPart,800,combat8_Brush,cid.uid,var,dfcombat8_Brush,startPos)
178+ return true
179+ else
180+ cid:getPosition():sendMagicEffect(03)
181+ return false
182+ end
183+end
A data/spells/scripts/attacks_moves/attack_spell.7z +0-0 View file
Binary file not shown.
A data/spells/scripts/attacks_moves/attack_spell.lua +521-0 View file
@@ -0,0 +1,521 @@
1+-- Configuración general
2+local 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
66+local combats = {}
67+for 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)
73+end
74+
75+-- Funciones auxiliares
76+local 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
87+end
88+
89+local 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
100+end
101+
102+local function adjustEffectPosition(position)
103+ return Position(position.x + 1, position.y + 1, position.z)
104+end
105+
106+local 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
118+end
119+
120+local 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
132+end
133+
134+local function sendEffect(position, effectId, effectEnabled)
135+ if effectEnabled then
136+ position:sendMagicEffect(effectId)
137+ end
138+end
139+
140+local 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
163+end
164+
165+local 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)
169+end
170+
171+local 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)
175+end
176+
177+local 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
180+end
181+
182+local 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
275+end
276+
277+local 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
303+end
304+
305+local 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
322+end
323+
324+local 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
361+end
362+
363+local 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
383+end
384+
385+-- Función principal de lanzamiento de hechizo
386+function 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
521+end
Top