| @@ -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() |
| @@ -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 | + |
| @@ -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() |
| @@ -0,0 +1,2 @@ | |||
| 1 | + | createFunctions(MonsterType) -- creates get/set functions for MonsterType | |
| 2 | + | createFunctions(Spell) -- creates get/set functions for Spell |
| @@ -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 |
| @@ -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 |
| @@ -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 |
| @@ -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 |
| @@ -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 |
| @@ -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() |
| @@ -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() |
| @@ -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() |
| @@ -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 |
| @@ -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 |
| @@ -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 |
| @@ -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 |