From ccbcfaead116be62699068fabf358a648e6c175f Mon Sep 17 00:00:00 2001 From: mrmdbeng Date: Tue, 4 Feb 2025 14:09:14 -0500 Subject: [PATCH 01/11] TibiaDrome 99% Map isnt released and im missing one mechanic with monsters. --- .../monster/drome/Domestikion.lua | 95 +++ .../monster/drome/Hoodinion.lua | 102 +++ .../monster/drome/Mearidion.lua | 96 +++ .../monster/drome/Murmillion.lua | 94 +++ .../monster/drome/Scissorion.lua | 95 +++ .../scripts/systems/drome_mechanics.lua | 797 ++++++++++++++++++ .../scripts/systems/tibia_drome.lua | 520 ++++++++++++ data/items/appearances.dat | Bin 4439297 -> 4439321 bytes 8 files changed, 1799 insertions(+) create mode 100644 data-otservbr-global/monster/drome/Domestikion.lua create mode 100644 data-otservbr-global/monster/drome/Hoodinion.lua create mode 100644 data-otservbr-global/monster/drome/Mearidion.lua create mode 100644 data-otservbr-global/monster/drome/Murmillion.lua create mode 100644 data-otservbr-global/monster/drome/Scissorion.lua create mode 100644 data-otservbr-global/scripts/systems/drome_mechanics.lua create mode 100644 data-otservbr-global/scripts/systems/tibia_drome.lua diff --git a/data-otservbr-global/monster/drome/Domestikion.lua b/data-otservbr-global/monster/drome/Domestikion.lua new file mode 100644 index 00000000000..f7deac0531b --- /dev/null +++ b/data-otservbr-global/monster/drome/Domestikion.lua @@ -0,0 +1,95 @@ +local mType = Game.createMonsterType("Domestikion") +local monster = {} + +monster.description = "Domestikion" +monster.experience = 0 +monster.outfit = { + lookType = 1426, +} + +monster.events = { + "DromeMonsterDeath", + "ExplodingCorpses", + "TargetedExplodingCorpses", +} + +monster.health = 800 +monster.maxHealth = 800 +monster.race = "undead" +monster.corpse = 36914 +monster.speed = 180 +monster.manaCost = 0 + +monster.changeTarget = { + interval = 4000, + chance = 10 +} + +monster.strategiesTarget = { + nearest = 80, + health = 10, + damage = 10, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = false, + canWalkOnFire = true, + canWalkOnPoison = true +} + +monster.light = { + level = 0, + color = 0 +} + +monster.voices = { + +} + +monster.attacks = { + {name ="melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -550}, + {name ="combat", interval = 1000, chance = 8, type = COMBAT_ENERGYDAMAGE, minDamage = -150, maxDamage = -600, range = 7, radius = 3, shootEffect = CONST_ANI_ENERGY, effect = CONST_ME_ENERGYHIT, target = true}, + {name ="combat", interval = 3000, chance = 13, type = COMBAT_HOLYDAMAGE, minDamage = -120, maxDamage = -650, range = 7, length = 3, effect = CONST_ME_HOLYAREA, target = true}, + {name ="combat", interval = 3000, chance = 8, type = COMBAT_ENERGYDAMAGE, minDamage = -160, maxDamage = -500, range = 7, radius = 4, effect = CONST_ME_ENERGYAREA, target = false} +} + +monster.defenses = { + defense = 110, + armor = 110 +} + +monster.elements = { + {type = COMBAT_PHYSICALDAMAGE, percent = 0}, + {type = COMBAT_ENERGYDAMAGE, percent = 0}, + {type = COMBAT_EARTHDAMAGE, percent = 0}, + {type = COMBAT_FIREDAMAGE, percent = 0}, + {type = COMBAT_LIFEDRAIN, percent = 0}, + {type = COMBAT_MANADRAIN, percent = 0}, + {type = COMBAT_DROWNDAMAGE, percent = 0}, + {type = COMBAT_ICEDAMAGE, percent = 0}, + {type = COMBAT_HOLYDAMAGE , percent = 0}, + {type = COMBAT_DEATHDAMAGE , percent = 0} +} + +monster.immunities = { + {type = "paralyze", condition = true}, + {type = "outfit", condition = false}, + {type = "invisible", condition = true}, + {type = "bleed", condition = false} +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/drome/Hoodinion.lua b/data-otservbr-global/monster/drome/Hoodinion.lua new file mode 100644 index 00000000000..4f70fce1356 --- /dev/null +++ b/data-otservbr-global/monster/drome/Hoodinion.lua @@ -0,0 +1,102 @@ +local mType = Game.createMonsterType("Hoodinion") +local monster = {} + +monster.description = "Hoodinion" +monster.experience = 0 +monster.outfit = { + lookType = 1424, +} + +monster.events = { + "DromeMonsterDeath", + "ExplodingCorpses", + "TargetedExplodingCorpses", +} + +monster.health = 800 +monster.maxHealth = 800 +monster.race = "undead" +monster.corpse = 36906 +monster.speed = 180 +monster.manaCost = 0 + +monster.changeTarget = { + interval = 4000, + chance = 10 +} + +monster.strategiesTarget = { + nearest = 80, + health = 10, + damage = 10, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 4, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = false, + canWalkOnFire = true, + canWalkOnPoison = true +} + +monster.light = { + level = 0, + color = 0 +} + +monster.voices = { + +} + +monster.attacks = { + {name ="melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -400}, + {name ="combat", interval = 1000, chance = 12, type = COMBAT_ICEDAMAGE, minDamage = -100, maxDamage = -500, range = 7, shootEffect = CONST_ANI_SMALLICE, effect = CONST_ME_ICEATTACK, target = true}, + {name ="combat", interval = 2000, chance = 12, type = COMBAT_ICEDAMAGE, minDamage = -100, maxDamage = -500, range = 7, radius = 6, shootEffect = CONST_ANI_SMALLICE, effect = CONST_ME_ICETORNADO, target = true}, + {name ="combat", interval = 3000, chance = 13, type = COMBAT_ICEDAMAGE, minDamage = -100, maxDamage = -600, range = 7, range = 7, shootEffect = CONST_ANI_SMALLICE, effect = CONST_ANI_TARSALARROW, target = true}, + {name ="combat", interval = 3000, chance = 16, type = COMBAT_HOLYDAMAGE, minDamage = -100, maxDamage = -550, range = 7, radius = 6, effect = CONST_ME_HITBYFIRE, target = false} +} + +monster.defenses = { + defense = 110, + armor = 110 +} + +monster.elements = { + {type = COMBAT_PHYSICALDAMAGE, percent = 0}, + {type = COMBAT_ENERGYDAMAGE, percent = 0}, + {type = COMBAT_EARTHDAMAGE, percent = 0}, + {type = COMBAT_FIREDAMAGE, percent = 0}, + {type = COMBAT_LIFEDRAIN, percent = 0}, + {type = COMBAT_MANADRAIN, percent = 0}, + {type = COMBAT_DROWNDAMAGE, percent = 0}, + {type = COMBAT_ICEDAMAGE, percent = 0}, + {type = COMBAT_HOLYDAMAGE , percent = 0}, + {type = COMBAT_DEATHDAMAGE , percent = 0} +} + +monster.immunities = { + {type = "paralyze", condition = true}, + {type = "outfit", condition = false}, + {type = "invisible", condition = true}, + {type = "bleed", condition = false} +} + +mType.onAppear = function(creature) + -- Retrieve drome level from the creature (not player) + local dromeLevel = getDromeLevel(creature) -- Ensure this function returns a valid drome level + print(monster:getName() .. " spawned with drome level: " .. dromeLevel) +end + +mType:register(monster) diff --git a/data-otservbr-global/monster/drome/Mearidion.lua b/data-otservbr-global/monster/drome/Mearidion.lua new file mode 100644 index 00000000000..48e955a1140 --- /dev/null +++ b/data-otservbr-global/monster/drome/Mearidion.lua @@ -0,0 +1,96 @@ +local mType = Game.createMonsterType("Mearidion") +local monster = {} + +monster.description = "Mearidion" +monster.experience = 0 +monster.outfit = { + lookType = 1425, +} + +monster.events = { + "DromeMonsterDeath", + "ExplodingCorpses", + "TargetedExplodingCorpses", +} + +monster.health = 800 +monster.maxHealth = 800 +monster.race = "undead" +monster.corpse = 36910 +monster.speed = 180 +monster.manaCost = 0 + +monster.changeTarget = { + interval = 4000, + chance = 10 +} + +monster.strategiesTarget = { + nearest = 80, + health = 10, + damage = 10, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 4, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = false, + canWalkOnFire = true, + canWalkOnPoison = true +} + +monster.light = { + level = 0, + color = 0 +} + +monster.voices = { + +} + +monster.attacks = { + {name ="melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -500}, + {name ="combat", interval = 1000, chance = 12, type = COMBAT_FIREDAMAGE, minDamage = -100, maxDamage = -550, range = 7, shootEffect = CONST_ANI_FIRE, effect = CONST_ME_HITBYFIRE, target = true}, + {name ="combat", interval = 2000, chance = 12, type = COMBAT_DEATHDAMAGE, minDamage = -100, maxDamage = -600, range = 7, shootEffect = CONST_ANI_BOLT, effect = CONST_ME_BLACKSMOKE, target = true}, + {name ="combat", interval = 3000, chance = 13, type = COMBAT_DEATHDAMAGE, minDamage = -100, maxDamage = -700, range = 7, radius = 7, effect = CONST_ME_BLACKSMOKE, target = false}, + {name ="combat", interval = 3000, chance = 15, type = COMBAT_HOLYDAMAGE, minDamage = -100, maxDamage = -500, range = 7, radius = 2, shootEffect = CONST_ANI_SUDDENDEATH, effect = CONST_ME_MORTAREA, target = true} +} + +monster.defenses = { + defense = 110, + armor = 110 +} + +monster.elements = { + {type = COMBAT_PHYSICALDAMAGE, percent = 0}, + {type = COMBAT_ENERGYDAMAGE, percent = 0}, + {type = COMBAT_EARTHDAMAGE, percent = 0}, + {type = COMBAT_FIREDAMAGE, percent = 0}, + {type = COMBAT_LIFEDRAIN, percent = 0}, + {type = COMBAT_MANADRAIN, percent = 0}, + {type = COMBAT_DROWNDAMAGE, percent = 0}, + {type = COMBAT_ICEDAMAGE, percent = 0}, + {type = COMBAT_HOLYDAMAGE , percent = 0}, + {type = COMBAT_DEATHDAMAGE , percent = 0} +} + +monster.immunities = { + {type = "paralyze", condition = true}, + {type = "outfit", condition = false}, + {type = "invisible", condition = true}, + {type = "bleed", condition = false} +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/drome/Murmillion.lua b/data-otservbr-global/monster/drome/Murmillion.lua new file mode 100644 index 00000000000..76e14a18616 --- /dev/null +++ b/data-otservbr-global/monster/drome/Murmillion.lua @@ -0,0 +1,94 @@ +local mType = Game.createMonsterType("Murmillion") +local monster = {} + +monster.description = "Murmillion" +monster.experience = 0 +monster.outfit = { + lookType = 1422, +} + +monster.events = { + "DromeMonsterDeath", + "ExplodingCorpses", + "TargetedExplodingCorpses", +} + +monster.health = 800 +monster.maxHealth = 800 +monster.race = "undead" +monster.corpse = 36898 +monster.speed = 180 +monster.manaCost = 0 + +monster.changeTarget = { + interval = 4000, + chance = 10 +} + +monster.strategiesTarget = { + nearest = 80, + health = 10, + damage = 10, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = false, + canWalkOnFire = true, + canWalkOnPoison = true +} + +monster.light = { + level = 0, + color = 0 +} + +monster.voices = {} + +monster.attacks = { + {name = "melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -500}, + {name = "combat", interval = 1000, chance = 8, type = COMBAT_PHYSICALDAMAGE, minDamage = -100, maxDamage = -500, radius = 4, effect = CONST_ME_SLASH, target = false}, + {name = "combat", interval = 2500, chance = 13, type = COMBAT_PHYSICALDAMAGE, minDamage = -100, maxDamage = -550, range = 5, radius = 7, effect = CONST_ME_EXPLOSIONAREA, target = false}, + {name = "combat", interval = 2000, chance = 8, type = COMBAT_DEATHDAMAGE, minDamage = -100, maxDamage = -600, range = 7, length = 3, effect = CONST_ME_GROUNDSHAKER, target = false}, + { name = "root", interval = 3000, chance = 1, target = true } +} + +monster.defenses = { + defense = 110, + armor = 110 +} + +monster.elements = { + {type = COMBAT_PHYSICALDAMAGE, percent = 0}, + {type = COMBAT_ENERGYDAMAGE, percent = 0}, + {type = COMBAT_EARTHDAMAGE, percent = 0}, + {type = COMBAT_FIREDAMAGE, percent = 0}, + {type = COMBAT_LIFEDRAIN, percent = 0}, + {type = COMBAT_MANADRAIN, percent = 0}, + {type = COMBAT_DROWNDAMAGE, percent = 0}, + {type = COMBAT_ICEDAMAGE, percent = 0}, + {type = COMBAT_HOLYDAMAGE , percent = 0}, + {type = COMBAT_DEATHDAMAGE , percent = 0} +} + +monster.immunities = { + {type = "paralyze", condition = true}, + {type = "outfit", condition = false}, + {type = "invisible", condition = true}, + {type = "bleed", condition = false} +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/drome/Scissorion.lua b/data-otservbr-global/monster/drome/Scissorion.lua new file mode 100644 index 00000000000..418fc38ecdf --- /dev/null +++ b/data-otservbr-global/monster/drome/Scissorion.lua @@ -0,0 +1,95 @@ +local mType = Game.createMonsterType("Scissorion") +local monster = {} + +monster.description = "Scissorion" +monster.experience = 0 +monster.outfit = { + lookType = 1423, +} + +monster.events = { + "DromeMonsterDeath", + "ExplodingCorpses", + "TargetedExplodingCorpses", +} + +monster.health = 800 +monster.maxHealth = 800 +monster.race = "undead" +monster.corpse = 36902 +monster.speed = 180 +monster.manaCost = 0 + +monster.changeTarget = { + interval = 4000, + chance = 10 +} + +monster.strategiesTarget = { + nearest = 80, + health = 10, + damage = 10, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = false, + canWalkOnFire = true, + canWalkOnPoison = true +} + +monster.light = { + level = 0, + color = 0 +} + +monster.voices = { + +} + +monster.attacks = { + {name ="melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -500}, + {name ="combat", interval = 1000, chance = 8, type = COMBAT_EARTHDAMAGE, minDamage = -100, maxDamage = -700, range = 7, radius = 4, effect = CONST_ME_GREENSMOKE, target = false}, + {name ="combat", interval = 3000, chance = 13, type = COMBAT_FIREDAMAGE, minDamage = -100, maxDamage = -500, range = 7, effect = CONST_ME_CRITICAL_DAMAGE, target = true}, + {name ="combat", interval = 3000, chance = 8, type = COMBAT_FIREDAMAGE, minDamage = -100, maxDamage = -400, range = 7, length = 3, effect = CONST_ME_FIREATTACK, target = false} +} + +monster.defenses = { + defense = 110, + armor = 110 +} + +monster.elements = { + {type = COMBAT_PHYSICALDAMAGE, percent = 0}, + {type = COMBAT_ENERGYDAMAGE, percent = 0}, + {type = COMBAT_EARTHDAMAGE, percent = 0}, + {type = COMBAT_FIREDAMAGE, percent = 0}, + {type = COMBAT_LIFEDRAIN, percent = 0}, + {type = COMBAT_MANADRAIN, percent = 0}, + {type = COMBAT_DROWNDAMAGE, percent = 0}, + {type = COMBAT_ICEDAMAGE, percent = 0}, + {type = COMBAT_HOLYDAMAGE , percent = 0}, + {type = COMBAT_DEATHDAMAGE , percent = 0} +} + +monster.immunities = { + {type = "paralyze", condition = true}, + {type = "outfit", condition = false}, + {type = "invisible", condition = true}, + {type = "bleed", condition = false} +} + +mType:register(monster) diff --git a/data-otservbr-global/scripts/systems/drome_mechanics.lua b/data-otservbr-global/scripts/systems/drome_mechanics.lua new file mode 100644 index 00000000000..67ec3aa7d9f --- /dev/null +++ b/data-otservbr-global/scripts/systems/drome_mechanics.lua @@ -0,0 +1,797 @@ +-- by arah +local generalArea = { + from = Position(32246, 32177, 12), + to = Position(32264, 32195, 12) +} + +local mechanics = {} +local restrictedPosition = Position(32259, 32178, 12) +local activeMechanics = {} + +function activateRandomMechanics() + for _, mechanic in pairs(activeMechanics) do + if type(mechanic) == "function" then + mechanic("stop") + end + end + + activeMechanics = {} + + local keys = {} + for key in pairs(mechanics) do + table.insert(keys, key) + end + + if #keys < 2 then + return + end + + math.randomseed(os.time()) + + while true do + local firstIndex = math.random(#keys) + local firstKey = keys[firstIndex] + table.remove(keys, firstIndex) + + if #keys == 0 then + return + end + + local secondKey = keys[math.random(#keys)] + + if not ((firstKey == "startFearMechanics" and secondKey == "startBadRootsMechanics") or + (firstKey == "startBadRootsMechanics" and secondKey == "startFearMechanics") or + (firstKey == "startLavaEvent" and (secondKey == "startBeamMeUpEvent" or secondKey == "startTankedUpEvent")) or + (firstKey == "startBeamMeUpEvent" and (secondKey == "startLavaEvent" or secondKey == "startTankedUpEvent")) or + (firstKey == "startTankedUpEvent" and (secondKey == "startLavaEvent" or secondKey == "startBeamMeUpEvent"))) then + + table.insert(activeMechanics, mechanics[firstKey]) + table.insert(activeMechanics, mechanics[secondKey]) + + if type(mechanics[firstKey]) == "function" then + mechanics[firstKey]("start") + end + if type(mechanics[secondKey]) == "function" then + mechanics[secondKey]("start") + end + + print("Activated mechanics:", firstKey, secondKey) + break + end + + table.insert(keys, firstKey) + end +end + +function teleportSingleMonsterToPlayer(action) + if action == "stop" then + return + end + + local monstersInArea = {} + for x = generalArea.from.x, generalArea.to.x do + for y = generalArea.from.y, generalArea.to.y do + local position = Position(x, y, generalArea.from.z) + local tile = Tile(position) + if tile then + for _, creature in pairs(tile:getCreatures()) do + if creature:isMonster() then + table.insert(monstersInArea, creature) + end + end + end + end + end + + if #monstersInArea > 0 then + if math.random(100) <= 15 then + local randomMonster = monstersInArea[math.random(#monstersInArea)] + local playersInArea = {} + for _, player in pairs(Game.getPlayers()) do + if player:getPosition():isInRange(generalArea.from, generalArea.to) then + table.insert(playersInArea, player) + end + end + + if #playersInArea > 0 then + local randomPlayer = playersInArea[math.random(#playersInArea)] + local playerPosition = randomPlayer:getPosition() + randomMonster:teleportTo(playerPosition) + playerPosition:sendMagicEffect(CONST_ME_TELEPORT) + end + end + end + addEvent(teleportSingleMonsterToPlayer, 2000) +end + +lavaFields = {} +function startLavaEvent(action) + if action == "stop" then + lavaFields = {} + return + end + + addEvent(function() + placeLavaFields() + startLavaEvent() + end, 15000) +end + +function placeLavaFields() + if not arePlayersInArea() then + return + end + + lavaFields = {} + for _ = 1, 100 do + local position = getRandomWalkablePosition() + if position and position ~= restrictedPosition then + local item = Game.createItem(36927, 1, position) + table.insert(lavaFields, { position = position, created_time = os.time() }) + addEvent(function() item:remove() end, 5000) + end + end + + addEvent(applyLavaDamage, 3000) +end + +function applyLavaDamage() + local current_time = os.time() + local players = Game.getPlayers() + for _, player in ipairs(players) do + local player_position = player:getPosition() + for _, field in ipairs(lavaFields) do + if player_position == field.position then + if current_time - field.created_time >= 3 then + local damage = player:getHealth() * 0.6 + player:addHealth(-damage) + end + end + end + end +end + +beamFields = {} +function startBeamMeUpEvent(action) + if action == "stop" then + beamFields = {} + return + end + + addEvent(function() + if not arePlayersInArea() then + return + end + + placeBeamFields() + startBeamMeUpEvent() + end, 15000) +end + +function placeBeamFields() + beamFields = {} + for _ = 1, 100 do + local position = getRandomWalkablePosition() + if position and position ~= restrictedPosition then + local item = Game.createItem(36925, 1, position) + table.insert(beamFields, { position = position, created_time = os.time() }) + addEvent(function() item:remove() end, 5000) + end + end + addEvent(teleportPlayers, 1000) +end + +function teleportPlayers() + local players = Game.getPlayers() + for _, player in ipairs(players) do + local playerPosition = player:getPosition() + for _, field in ipairs(beamFields) do + if playerPosition == field.position then + local randomPos = getRandomWalkablePosition() + player:teleportTo(randomPos) + break + end + end + end +end + +tankedUpFields = {} +function startTankedUpEvent(action) + if action == "stop" then + tankedUpFields = {} + return + end + + addEvent(function() + placeTankedUpFields() + startTankedUpEvent() + end, 15000) +end + +function placeTankedUpFields() + if not arePlayersInArea() then + return + end + + tankedUpFields = {} + for _ = 1, 100 do + local position = getRandomWalkablePosition() + if position and position ~= restrictedPosition then + local item = Game.createItem(36926, 1, position) + table.insert(tankedUpFields, { position = position, created_time = os.time() }) + addEvent(function() item:remove() end, 5000) + end + end + addEvent(applySuperdrunkEffect, 500) +end + +function applySuperdrunkEffect() + local players = Game.getPlayers() + for _, player in ipairs(players) do + local playerPosition = player:getPosition() + for _, field in ipairs(tankedUpFields) do + if playerPosition == field.position then + local drunk = Condition(CONDITION_DRUNK) + drunk:setParameter(CONDITION_PARAM_TICKS, 40000) + player:addCondition(drunk) + break + end + end + end +end + +function getRandomWalkablePosition() + local minX, minY, maxX, maxY = generalArea.from.x, generalArea.from.y, generalArea.to.x, generalArea.to.y + local position + for _ = 1, 10 do + local x = math.random(minX, maxX) + local y = math.random(minY, maxY) + position = Position(x, y, generalArea.from.z) + if isWalkable(position) then + return position + end + end + return nil +end + +function isWalkable(position) + if position == restrictedPosition then + return false + end + + local tile = Tile(position) + return tile and tile:isWalkable() +end + +function arePlayersInArea() + local players = Game.getPlayers() + for _, player in ipairs(players) do + if isInSpecArea(player:getPosition()) then + return true + end + end + return false +end + +function isInSpecArea(position) + return position.x >= generalArea.from.x and position.x <= generalArea.to.x and + position.y >= generalArea.from.y and position.y <= generalArea.to.y and + position.z == generalArea.from.z +end +-- end beam + +-- Targeted Exploding Corpses Mechanic +local activeMechanic = nil +targetedExplodingCorpses = {} +function startTargetedExplodingCorpsesMechanic(action) + if action == "stop" then + activeMechanic = nil + targetedExplodingCorpses = {} + return + elseif action == "start" then + activeMechanic = "targeted" + end +end + +local TARGETED_DAMAGE_PERCENTAGE = 0.40 +local targetedSpellArea = { + from = Position(32246, 32177, 12), + to = Position(32264, 32195, 12) +} + +local targetedValidMonsters = { + ["Domestikion"] = true, + ["Hoodinion"] = true, + ["Mearidion"] = true, + ["Murmillion"] = true, + ["Scissorion"] = true +} + +local targetSpellPattern = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 1, 1, 1, 1, 1, 0, 0 }, + { 0, 0, 1, 1, 2, 1, 1, 0, 0 }, + { 0, 0, 1, 1, 1, 1, 1, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, +} + +local function findNearestTargetedPlayer(monsterPosition) + local playersInArea = Game.getSpectators(monsterPosition, false, true, 8, 8, 8, 8) + local nearestPlayer = nil + local shortestDistance = math.huge + + for _, player in ipairs(playersInArea) do + if player:isPlayer() then + local distance = monsterPosition:getDistance(player:getPosition()) + if distance < shortestDistance then + nearestPlayer = player + shortestDistance = distance + end + end + end + + return nearestPlayer +end + +local function isInTargetedSpellArea(position) + return position.x >= targetedSpellArea.from.x and position.x <= targetedSpellArea.to.x and + position.y >= targetedSpellArea.from.y and position.y <= targetedSpellArea.to.y and + position.z == targetedSpellArea.from.z +end + +local function getTargetedAffectedPositions(centerPosition) + local affectedPositions = {} + local offset = math.floor(#targetSpellPattern / 2) + for y = 1, #targetSpellPattern do + for x = 1, #targetSpellPattern[y] do + if targetSpellPattern[y][x] > 0 then + local pos = Position(centerPosition.x + (x - offset - 1), centerPosition.y + (y - offset - 1), centerPosition.z) + table.insert(affectedPositions, pos) + end + end + end + return affectedPositions +end + +local function handleTargetedSpell(monster) + local monsterPosition = monster:getPosition() + + if not isInTargetedSpellArea(monsterPosition) then + return + end + + local targetPlayer = findNearestTargetedPlayer(monsterPosition) + if not targetPlayer then + return + end + + local targetPosition = targetPlayer:getPosition() + for _, pos in ipairs(getTargetedAffectedPositions(targetPosition)) do + pos:sendMagicEffect(CONST_ME_FIREAREA) + end + + for _, pos in ipairs(getTargetedAffectedPositions(targetPosition)) do + local playersInTile = Tile(pos):getCreatures() + if playersInTile then + for _, player in ipairs(playersInTile) do + if player:isPlayer() then + local currentHealth = player:getHealth() + local damage = math.floor(currentHealth * TARGETED_DAMAGE_PERCENTAGE) + player:addHealth(-damage) + end + end + end + end +end + +local targetedExplodingCorpses = CreatureEvent("TargetedExplodingCorpses") + +function targetedExplodingCorpses.onDeath(creature, corpse, killer, mostDamageKiller, unjustified) + if activeMechanic ~= "targeted" then + return false + end + if creature and creature:isMonster() and targetedValidMonsters[creature:getName()] then + handleTargetedSpell(creature) + end + return true +end + +targetedExplodingCorpses:register() + + +-----------------------------------separate mechanic +--explodingCorpses +explodingCorpses = {} +function startExplodingCorpsesMechanic(action) + if action == "stop" then + activeMechanic = nil + explodingCorpses = {} + return + elseif action == "start" then + activeMechanic = "exploding" + end +end + +local DAMAGE_PERCENTAGE = 0.25 +local explosionArea = { + from = Position(32246, 32177, 12), + to = Position(32264, 32195, 12) +} + +local validMonsters = { + ["Domestikion"] = true, + ["Hoodinion"] = true, + ["Mearidion"] = true, + ["Murmillion"] = true, + ["Scissorion"] = true +} + +local explosionPattern = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 1, 1, 1, 1, 1, 0, 0 }, + { 0, 0, 1, 1, 2, 1, 1, 0, 0 }, + { 0, 0, 1, 1, 1, 1, 1, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, +} + +local function isInExplosionArea(position) + return position.x >= explosionArea.from.x and position.x <= explosionArea.to.x and + position.y >= explosionArea.from.y and position.y <= explosionArea.to.y and + position.z == explosionArea.from.z +end + +local function getAffectedPositions(centerPosition) + local affectedPositions = {} + local offset = math.floor(#explosionPattern / 2) + for y = 1, #explosionPattern do + for x = 1, #explosionPattern[y] do + if explosionPattern[y][x] > 0 then + local pos = Position(centerPosition.x + (x - offset - 1), centerPosition.y + (y - offset - 1), centerPosition.z) + table.insert(affectedPositions, pos) + end + end + end + return affectedPositions +end + +local function showExplosionEffect(centerPosition) + for _, pos in ipairs(getAffectedPositions(centerPosition)) do + if isInExplosionArea(pos) then + pos:sendMagicEffect(CONST_ME_FIREAREA) + end + end +end + +local function handleExplosion(monster) + local monsterPosition = monster:getPosition() + + if not isInExplosionArea(monsterPosition) then + return + end + + showExplosionEffect(monsterPosition) + + local affectedPositions = getAffectedPositions(monsterPosition) + for _, pos in ipairs(affectedPositions) do + local playersInTile = Tile(pos):getCreatures() + if playersInTile then + for _, player in ipairs(playersInTile) do + if player:isPlayer() then + local currentHealth = player:getHealth() + local damage = math.floor(currentHealth * DAMAGE_PERCENTAGE) + player:addHealth(-damage) + end + end + end + end +end + +local explodingCorpses = CreatureEvent("ExplodingCorpses") + +function explodingCorpses.onDeath(creature, corpse, killer, mostDamageKiller, unjustified) + if activeMechanic ~= "exploding" then + return false + end + if creature and creature:isMonster() and validMonsters[creature:getName()] then + handleExplosion(creature) + end + return true +end + +explodingCorpses:register() + +------------------------------------- fear mechanic -------------------------- +fearMechanic = {} +function startFearMechanics(action) + if action == "stop" then + fearMechanic = {} + return + elseif action == "start" then + createFearAfterPlayerDetection() + end +end + +local function isTileWalkable(position) + + if position == restrictedPosition then + return false + end + + local tile = Tile(position) + if not tile then + return false + end + return not tile:hasFlag(TILESTATE_BLOCKSOLID) and not tile:hasFlag(TILESTATE_PROTECTIONZONE) +end + +local function getRandomWalkablePosition(from, to) + local attempts = 100 + while attempts > 0 do + local randomX = math.random(from.x, to.x) + local randomY = math.random(from.y, to.y) + local position = Position(randomX, randomY, from.z) + if isWalkable(position) and position ~= restrictedPosition then + return position + end + attempts = attempts - 1 + end + return nil +end + +local function isPlayerInArea(from, to) + for x = from.x, to.x do + for y = from.y, to.y do + local tile = Tile(Position(x, y, from.z)) + if tile then + local creatures = tile:getCreatures() + for _, creature in ipairs(creatures) do + if creature:isPlayer() then + return true + end + end + end + end + end + return false +end + +local fear = {36934, 36935} +local playersSteppedOnItem = {} + +local fearSeedStepIn = MoveEvent("fearSeedStepIn") +function fearSeedStepIn.onStepIn(creature, item, position, fromPosition) + if item:getId() == 36934 or item:getId() == 36935 then + if creature and creature:isPlayer() then + item:remove() + playersSteppedOnItem[creature:getId()] = true + end + end + return true +end + +fearSeedStepIn:type("stepin") +fearSeedStepIn:id(36934, 36935) +fearSeedStepIn:register() + +local function applyFearConditionToPlayers(from, to) + for x = from.x, to.x do + for y = from.y, to.y do + local tile = Tile(Position(x, y, from.z)) + if tile then + local creatures = tile:getCreatures() + for _, creature in ipairs(creatures) do + if creature:isPlayer() then + if not playersSteppedOnItem[creature:getId()] then + local player = creature + local condition = Condition(CONDITION_FEARED) + condition:setTicks(3000) + player:addCondition(condition) + player:getPosition():sendMagicEffect(CONST_ME_GHOST_SMOKE) + end + end + end + end + end + end +end + +function createFearAfterPlayerDetection() + local spawnAreaFrom = Position(32246, 32177, 12) + local spawnAreaTo = Position(32264, 32195, 12) + + if isPlayerInArea(spawnAreaFrom, spawnAreaTo) then + addEvent(function() + local position = getRandomWalkablePosition(spawnAreaFrom, spawnAreaTo) + if position then + local item = Game.createItem(36934, 1, position) + if item then + addEvent(function() + local tile = Tile(position) + if tile then + local seedItem = tile:getItemById(36934) + if seedItem then + seedItem:remove() + Game.createItem(36935, 1, position) + addEvent(function() + local tile = Tile(position) + if tile then + local transformedItem = tile:getItemById(36935) + if transformedItem then + transformedItem:remove() + end + + applyFearConditionToPlayers(spawnAreaFrom, spawnAreaTo) + end + end, 6000) + end + end + end, 3000) + end + end + end, 20000) + end + + addEvent(function() + playersSteppedOnItem = {} + end, 20000) + + addEvent(createFearAfterPlayerDetection, 20000) +end + +--------------------------------------- bad roots ------------------- + +rootMechanic = {} +function startBadRootsMechanics(action) + if action == "stop" then + rootMechanic = {} + return + elseif action == "start" then + createBadRootAfterPlayerDetection() + end +end + +local function isTileWalkable(position) + if position == restrictedPosition then + return false + end + + local tile = Tile(position) + if not tile then + return false + end + return not tile:hasFlag(TILESTATE_BLOCKSOLID) and not tile:hasFlag(TILESTATE_PROTECTIONZONE) +end + +local function getRandomWalkablePosition(from, to) + local attempts = 100 + while attempts > 0 do + local randomX = math.random(from.x, to.x) + local randomY = math.random(from.y, to.y) + local position = Position(randomX, randomY, from.z) + if position ~= restrictedPosition and isTileWalkable(position) then + return position + end + attempts = attempts - 1 + end + return nil +end + +local function isPlayerInArea(from, to) + for x = from.x, to.x do + for y = from.y, to.y do + local tile = Tile(Position(x, y, from.z)) + if tile then + local creatures = tile:getCreatures() + for _, creature in ipairs(creatures) do + if creature:isPlayer() then + return true + end + end + end + end + end + return false +end + +local eggs = {36936, 36937} +local playersSteppedOnItem = {} + +local eggsSeedStepIn = MoveEvent("eggsSeedStepIn") +function eggsSeedStepIn.onStepIn(creature, item, position, fromPosition) + if item:getId() == 36936 or item:getId() == 36937 then + if creature and creature:isPlayer() then + item:remove() + playersSteppedOnItem[creature:getId()] = true + end + end + return true +end + +eggsSeedStepIn:type("stepin") +eggsSeedStepIn:id(36936, 36937) +eggsSeedStepIn:register() + +local function applyRootedConditionToPlayers(from, to) + for x = from.x, to.x do + for y = from.y, to.y do + local tile = Tile(Position(x, y, from.z)) + if tile then + local creatures = tile:getCreatures() + for _, creature in ipairs(creatures) do + if creature:isPlayer() then + if not playersSteppedOnItem[creature:getId()] then + local player = creature + local condition = Condition(CONDITION_ROOTED) + condition:setTicks(5000) + player:addCondition(condition) + player:getPosition():sendMagicEffect(CONST_ME_ROOTS) + end + end + end + end + end + end +end + +function createBadRootAfterPlayerDetection() + local spawnAreaFrom = Position(32246, 32177, 12) + local spawnAreaTo = Position(32264, 32195, 12) + + if isPlayerInArea(spawnAreaFrom, spawnAreaTo) then + addEvent(function() + local position = getRandomWalkablePosition(spawnAreaFrom, spawnAreaTo) + if position then + local item = Game.createItem(36936, 1, position) + if item then + addEvent(function() + local tile = Tile(position) + if tile then + local seedItem = tile:getItemById(36936) + if seedItem then + seedItem:remove() + Game.createItem(36937, 1, position) + addEvent(function() + local tile = Tile(position) + if tile then + local transformedItem = tile:getItemById(36937) + if transformedItem then + transformedItem:remove() + end + + applyRootedConditionToPlayers(spawnAreaFrom, spawnAreaTo) + end + end, 6000) + end + end + end, 3000) + end + end + end, 20000) + end + + addEvent(function() + playersSteppedOnItem = {} + end, 20000) + + addEvent(createBadRootAfterPlayerDetection, 20000) +end + + +mechanics.startFearMechanics = startFearMechanics +mechanics.startBadRootsMechanics = startBadRootsMechanics +mechanics.startExplodingCorpsesMechanic = startExplodingCorpsesMechanic +mechanics.startTargetedExplodingCorpsesMechanic = startTargetedExplodingCorpsesMechanic +mechanics.startLavaEvent = startLavaEvent +mechanics.startBeamMeUpEvent = startBeamMeUpEvent +mechanics.startTankedUpEvent = startTankedUpEvent +mechanics.teleportSingleMonsterToPlayer = teleportSingleMonsterToPlayer + +-- by Arah \ No newline at end of file diff --git a/data-otservbr-global/scripts/systems/tibia_drome.lua b/data-otservbr-global/scripts/systems/tibia_drome.lua new file mode 100644 index 00000000000..cea000713b8 --- /dev/null +++ b/data-otservbr-global/scripts/systems/tibia_drome.lua @@ -0,0 +1,520 @@ +-- Im planning to move the database creation to schema once everything is good to go, for the time being im leaving it +-- here so i can test without messing around with the original code + +db.query([[ + CREATE TABLE IF NOT EXISTS drome_highscores ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT, + player_id INT NOT NULL, + player_name VARCHAR(255) NOT NULL, + highscore INT NOT NULL DEFAULT 0, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (id), + UNIQUE KEY (player_id), + FOREIGN KEY (player_id) REFERENCES players (id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +]]) + +db.query([[ +CREATE TABLE IF NOT EXISTS drome_reset ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT, + last_reset TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +]]) + +db.query([[ +CREATE TABLE IF NOT EXISTS drome_offline_rewards ( + player_id INT NOT NULL, + rewards TEXT NOT NULL, + PRIMARY KEY (player_id) +); +]]) + +local leverAction = Action() +local deathEvent = CreatureEvent("DromeMonsterDeath") +local configDrome = { + requiredLevel = 50, + playerPositions = { + { pos = Position(32253, 32199, 12), teleport = Position(32254, 32193, 12), effect = CONST_ME_MAGIC_BLUE }, + { pos = Position(32253, 32200, 12), teleport = Position(32254, 32193, 12), effect = CONST_ME_MAGIC_BLUE }, + { pos = Position(32253, 32201, 12), teleport = Position(32254, 32193, 12), effect = CONST_ME_MAGIC_BLUE }, + { pos = Position(32253, 32202, 12), teleport = Position(32254, 32193, 12), effect = CONST_ME_MAGIC_BLUE }, + { pos = Position(32253, 32203, 12), teleport = Position(32254, 32193, 12), effect = CONST_ME_MAGIC_BLUE }, + }, + specPos = { + from = Position(32246, 32177, 12), + to = Position(32264, 32195, 12), + }, + exit = Position(32259, 32178, 12), + timeToFightAgain = 14400, +} + +local lastFightTime = 0 +local dromeLevel = 0 +local monstersKilled = 0 +local playerWaveData = {} + +local creaturePool = { + "Domestikion", + "Hoodinion", + "Mearidion", + "Murmillion", + "Scissorion" +} + +function deathEvent.onDeath(creature, corpse, killer, mostDamageKiller) + if creature:isMonster() then + monstersKilled = monstersKilled + 1 + checkWaveCompletion(killer) + end + return true +end + +deathEvent:register() + +function calculateMonsters(playerCount) + local monstersPerPlayer = { + [1] = 4, + [2] = 8, + [3] = 11, + [4] = 14, + [5] = 17 + } + return monstersPerPlayer[playerCount] or 4 +end + +function spawnMonsters(playerCount) + local monsterCount = calculateMonsters(playerCount) + local baseHP = 1000 + local scaledHP = baseHP * (1.039 ^ dromeLevel) + + local spawnedCount = 0 + + while spawnedCount < monsterCount do + local spawnPos = Position(math.random(32246, 32264), math.random(32177, 32195), 12) + local tile = Tile(spawnPos) + if tile and tile:getGround() and not tile:hasProperty(CONST_PROP_BLOCKSOLID) then + local creatureName = creaturePool[math.random(#creaturePool)] + local monster = Game.createMonster(creatureName, spawnPos) + if monster then + monster:setMaxHealth(scaledHP) + monster:setHealth(scaledHP) + monster:registerEvent("DromeMonsterDeath") + spawnPos:sendMagicEffect(CONST_ME_MAGIC_BLUE) + spawnedCount = spawnedCount + 1 + end + end + end +end + +local waveTimer = nil +local waveTimeLimit = 120 + +function startWaveTimer() + if waveTimer then + stopEvent(waveTimer) + end + + waveTimer = addEvent(function() + local playerCount = countPlayersInArea() + + if playerCount > 0 then + for _, targetPlayer in pairs(Game.getPlayers()) do + if targetPlayer:getPosition():isInRange(configDrome.specPos.from, configDrome.specPos.to) then + targetPlayer:teleportTo(Position(32255, 32205, 11)) -- Position for players who failed + targetPlayer:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You failed to complete the wave in time! You have been kicked out.") + end + end + else + end + end, waveTimeLimit * 1000) +end + +function checkWaveCompletion(player) + + local playerCount = countPlayersInArea() + local requiredMonsters = calculateMonsters(playerCount) + + if monstersKilled >= requiredMonsters then + + if waveTimer then + stopEvent(waveTimer) + end + + for _, targetPlayer in pairs(Game.getPlayers()) do + if targetPlayer:getPosition():isInRange(configDrome.specPos.from, configDrome.specPos.to) then + local playerId = targetPlayer:getGuid() + local playerName = targetPlayer:getName() + local playerHighscore = playerWaveData[playerId] or 0 + + playerWaveData[playerId] = playerHighscore + 1 + + db.query(string.format([[ + INSERT INTO drome_highscores (player_id, player_name, highscore) + VALUES (%d, %s, %d) + ON DUPLICATE KEY UPDATE + highscore = GREATEST(highscore, %d), + updated_at = NOW(); + ]], playerId, db.escapeString(playerName), playerWaveData[playerId], playerWaveData[playerId])) + end + end + + dromeLevel = dromeLevel + 1 + monstersKilled = 0 + + local message = "Next wave will start in 8 seconds. Current level: " .. dromeLevel + local playersInArea = Game.getPlayers() + for _, targetPlayer in pairs(playersInArea) do + if targetPlayer:getPosition():isInRange(configDrome.specPos.from, configDrome.specPos.to) then + targetPlayer:say(message, TALKTYPE_YELL) + end + end + + addEvent(function() + local playerCount = countPlayersInArea() + if playerCount > 0 then + spawnMonsters(playerCount) + end + end, 8000) + + storeWaveInItemAtPosition() + storeLeaderboardInItemAtPosition() + startWaveTimer() + else + end +end + +function leverAction.onUse(player, item, fromPosition, target, isHotkey) + if item:getActionId() == 46985 then + local isInPosition = false + for _, posData in ipairs(configDrome.playerPositions) do + if player:getPosition() == posData.pos then + isInPosition = true + break + end + end + + if not isInPosition then + return true + end + + local currentTime = os.time() + if currentTime - lastFightTime < configDrome.timeToFightAgain then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You must wait before entering the Tibiadrome again.") + return true + end + + lastFightTime = currentTime + + local playerId = player:getGuid() + local query = string.format("SELECT highscore FROM drome_highscores WHERE player_id = %d", playerId) + local resultId = db.storeQuery(query) + + local playerHighscore = 0 + if resultId then + playerHighscore = result.getNumber(resultId, "highscore") or 0 + result.free(resultId) + end + + local startingWave = math.max(playerHighscore - 5, 0) + playerWaveData[playerId] = startingWave + + local entryPos = configDrome.playerPositions[math.random(#configDrome.playerPositions)].teleport + player:teleportTo(entryPos) + player:getPosition():sendMagicEffect(configDrome.playerPositions[math.random(#configDrome.playerPositions)].effect) + + if waveTimer then + stopEvent(waveTimer) + end + + dromeLevel = startingWave + spawnMonsters(countPlayersInArea()) + waveTimer = nil + startWaveTimer() + return true + end + return false +end + +function countPlayersInArea() + local playerCount = 0 + for _, targetPlayer in pairs(Game.getPlayers()) do + local playerPos = targetPlayer:getPosition() + + if playerPos:isInRange(configDrome.specPos.from, configDrome.specPos.to) then + playerCount = playerCount + 1 + end + end + return playerCount +end + +function removeMonstersFromArea() + for x = configDrome.specPos.from.x, configDrome.specPos.to.x do + for y = configDrome.specPos.from.y, configDrome.specPos.to.y do + local position = Position(x, y, 12) + local tile = Tile(position) + + if tile then + for _, creature in pairs(tile:getCreatures()) do + if creature:isMonster() then + local monsterName = creature:getName() + local validMonsters = { + "Domestikion", "Hoodinion", "Mearidion", "Murmillion", "Scissorion" + } + + for _, validMonster in ipairs(validMonsters) do + if monsterName == validMonster then + creature:remove() + break + end + end + end + end + end + end + end +end + +function checkForMonstersRemoval() + if countPlayersInArea() == 0 then + removeMonstersFromArea() + if waveTimer then + stopEvent(waveTimer) + end + waveTimer = nil + monstersKilled = 0 + dromeLevel = 0 + end + addEvent(checkForMonstersRemoval, 1000) +end + +checkForMonstersRemoval() + +function storeLeaderboardInItemAtPosition() + local itemPositions = { + Position(32255, 32203, 11), + Position(33378, 31820, 9), + Position(33154, 32972, 8), + Position(33537, 31820, 9), + Position(32706, 31820, 12) + } + + for _, itemPosition in ipairs(itemPositions) do + local tile = Tile(itemPosition) + + if not tile then + return + end + + local item = tile:getItemById(36828) + if not item then + return + end + + local query = "SELECT player_name, highscore FROM drome_highscores ORDER BY highscore DESC LIMIT 10" + local resultId = db.storeQuery(query) + + if not resultId then + item:setAttribute(ITEM_ATTRIBUTE_TEXT, "The current drome leaderboard:\nNo records found.") + return + end + + local leaderboardText = "The current drome leaderboard:\n" + local rank = 1 + + repeat + local playerName = result.getString(resultId, "player_name") + local highscore = result.getNumber(resultId, "highscore") + leaderboardText = leaderboardText .. rank .. ". " .. playerName .. ": " .. highscore .. "\n" + rank = rank + 1 + until not result.next(resultId) + + result.free(resultId) + + item:setAttribute(ITEM_ATTRIBUTE_TEXT, leaderboardText) + end +end + +function storeWaveInItemAtPosition() + local itemPosition = Position(32251, 32196, 11) + local tile = Tile(itemPosition) + + if not tile then + return + end + + local item = tile:getItemById(36870) + if not item then + return + end + + local waveInfoText = "Current Wave: " .. dromeLevel + item:setAttribute(ITEM_ATTRIBUTE_TEXT, waveInfoText) +end + +leverAction:aid(46985) +leverAction:register() + +function resetHighScores() + local query = "SELECT last_reset FROM drome_reset WHERE id = 1" + local resultId = db.storeQuery(query) + + local lastReset = 0 + if resultId then + local lastResetString = result.getString(resultId, "last_reset") or "" + result.free(resultId) + + if lastResetString ~= "" then + lastReset = os.time{ + year = tonumber(lastResetString:sub(1, 4)), + month = tonumber(lastResetString:sub(6, 7)), + day = tonumber(lastResetString:sub(9, 10)), + hour = tonumber(lastResetString:sub(12, 13)), + min = tonumber(lastResetString:sub(15, 16)), + sec = tonumber(lastResetString:sub(18, 19)) + } + end + end + + local currentTime = os.time() + local resetInterval = 5 * 24 * 60 * 60 + + if currentTime - lastReset >= resetInterval then + local rewards = { + {itemId = 36725, count = 1}, -- stamina + {itemId = 36727, count = 1}, -- wealth duplex + {itemId = 36742, count = 1}, -- physical amplification + {itemId = 36735, count = 1}, -- physical resilience + {itemId = 36737, count = 1}, -- ice amp + {itemId = 36730, count = 1}, -- ice res + {itemId = 36740, count = 1}, -- holy amp + {itemId = 36733, count = 1}, -- holy res + {itemId = 36736, count = 1}, -- fire amp + {itemId = 36729, count = 1}, -- fire res + {itemId = 36739, count = 1}, -- energy amp + {itemId = 36732, count = 1}, -- energy res + {itemId = 36738, count = 1}, -- earth amp + {itemId = 36731, count = 1}, -- earth res + {itemId = 36741, count = 1}, -- death amp + {itemId = 36734, count = 1} -- death res + } + + local topPlayersQuery = "SELECT player_id, player_name FROM drome_highscores ORDER BY highscore DESC LIMIT 10" + local topPlayersResult = db.storeQuery(topPlayersQuery) + + if topPlayersResult then + repeat + local playerId = result.getNumber(topPlayersResult, "player_id") + local playerName = result.getString(topPlayersResult, "player_name") + local player = Player(playerId) + + if player then + local inbox = player:getStoreInbox() + if inbox then + local randomReward = rewards[math.random(1, #rewards)] + inbox:addItem(randomReward.itemId, randomReward.count) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Congratulations, " .. playerName .. "! You have received a reward for being in the top 10 of the Drome leaderboard!") + end + else + local storedRewards = "" + local rewardCount = 0 + local randomReward = rewards[math.random(1, #rewards)] + storedRewards = storedRewards .. randomReward.itemId .. ":" .. randomReward.count .. "," + db.query("INSERT INTO drome_offline_rewards (player_id, rewards) VALUES (" .. playerId .. ", '" .. storedRewards .. "') ON DUPLICATE KEY UPDATE rewards = '" .. storedRewards .. "'") + end + until not result.next(topPlayersResult) + result.free(topPlayersResult) + end + + db.query([[TRUNCATE TABLE drome_highscores]]) + db.query([[UPDATE drome_reset SET last_reset = NOW() WHERE id = 1]]) + + for _, player in pairs(Game.getPlayers()) do + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The Drome high scores have been reset, and the top players have received their rewards!") + end + + local itemPosition = Position(32255, 32203, 11) + local tile = Tile(itemPosition) + if tile then + local item = tile:getItemById(36828) + if item then + item:setAttribute(ITEM_ATTRIBUTE_TEXT, "The current drome leaderboard:\nAll records have been cleared.") + end + end + activateRandomMechanics() + end + addEvent(resetHighScores, resetInterval * 1000) +end + +local offlinereward = CreatureEvent('RewardDrome') +function offlinereward.onLogin(player) + if not player then + return true + end + + local playerId = player:getGuid() + local query = "SELECT rewards FROM drome_offline_rewards WHERE player_id = " .. playerId .. " LIMIT 1" + local resultId, err = db.storeQuery(query) + if not resultId then + return true + end + + local rewardsString = result.getString(resultId, "rewards") + result.free(resultId) + + if rewardsString and rewardsString ~= "" then + local rewards = {} + for reward in rewardsString:gmatch("([^,]+)") do + local itemId, count = reward:match("(%d+):(%d+)") + if itemId and count then + table.insert(rewards, {itemId = tonumber(itemId), count = tonumber(count)}) + end + end + + local inbox = player:getStoreInbox() + if inbox then + for _, reward in ipairs(rewards) do + if not inbox:addItem(reward.itemId, reward.count) then + else + end + end + + db.query("DELETE FROM drome_offline_rewards WHERE player_id = " .. playerId) + else + end + else + end + + return true +end + +offlinereward:register() + +-- save it to retrieve data ( not finished function ) +function getDromeLevel(player) + local playerId = player:getId() + local dromeLevel = dromeLevel + 1 + local query = string.format("SELECT highscore FROM drome_highscores WHERE player_id = %d", playerId) + local resultId = db.storeQuery(query) + if resultId and result:getID() ~= -1 then + dromeLevel = result:getNumber("highscore") + end + + return dromeLevel +end + +function onServerStart() + local query = "SELECT COUNT(*) AS count FROM drome_reset" + local resultId = db.storeQuery(query) + + if resultId and result.getNumber(resultId, "count") == 0 then + db.query([[INSERT INTO drome_reset (last_reset) VALUES (NOW())]]) + end + + result.free(resultId) + storeLeaderboardInItemAtPosition() + addEvent(resetHighScores, 1000) + activateRandomMechanics() +end + +addEvent(onServerStart, 1) diff --git a/data/items/appearances.dat b/data/items/appearances.dat index e26d139005db44977508022013fe2ae98a484a4e..d4484cd48a408370e4c3e764d97e63c6d3d68c89 100644 GIT binary patch delta 432 zcmY++yGp}Q00!WkMD1y8+BESJYi(l^FYy-ZwO(88CUzA;@EQ6D3J0<|Rf2AUAktNc zgW{%Egk;R>6Syfj`IE&ueA9n9{W;?=;DiyzGl6xnZq~yRjItyXSuac7Nk&9&JTsE^ z$&uHHBvbf&dUTtS1Zlq+W!hil(mM|1I;sEVVIaFdsl+3df$pIr;{R2xdyrCiCCg9-bVs~soF|s;Lz82 zQFu5{Rk=M%Ihkm>Ud;U)x8t#X{hldXajPKbHAA;LTZi;5esWu?4O1`;GcXHt;J`eT jU;!3k36^06R-p`Qunrrr30trY6{x}v?D{RWc5wR($}OR2 delta 384 zcmYkyIZne+07X%KCj=*8aQvCa5Hs1Bd7g)qK#QP)C1eR8eaRi7ieUj1^hjPnBp@_y zTzC~oX(*uKih!Uv&3pIm<667Ir4mY0DV0`DDx;c}uCgkpT2$+!QB1^rU?;|rI19>& zVT(7vk{DgGpcj4U#{dQ~gkcmgf+B2; zVhrP$2;*onUrM?9Q&86&8GKrfnN1f&S>!zZIjCpYJn2|Jo2$Pxx^wxjt2MftzwT6j x{qFp#bl_qN9;PvaS Date: Tue, 4 Feb 2025 15:12:28 -0500 Subject: [PATCH 02/11] Added Damage Scaling got to see further improvements for nerf or buff --- .../monster/drome/Domestikion.lua | 28 +++++++++++++++--- .../monster/drome/Hoodinion.lua | 29 ++++++++++++++----- .../monster/drome/Mearidion.lua | 29 +++++++++++++++---- .../monster/drome/Murmillion.lua | 27 ++++++++++++++--- .../monster/drome/Scissorion.lua | 27 ++++++++++++++--- 5 files changed, 115 insertions(+), 25 deletions(-) diff --git a/data-otservbr-global/monster/drome/Domestikion.lua b/data-otservbr-global/monster/drome/Domestikion.lua index f7deac0531b..cbc8a360496 100644 --- a/data-otservbr-global/monster/drome/Domestikion.lua +++ b/data-otservbr-global/monster/drome/Domestikion.lua @@ -61,10 +61,10 @@ monster.voices = { } monster.attacks = { - {name ="melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -550}, - {name ="combat", interval = 1000, chance = 8, type = COMBAT_ENERGYDAMAGE, minDamage = -150, maxDamage = -600, range = 7, radius = 3, shootEffect = CONST_ANI_ENERGY, effect = CONST_ME_ENERGYHIT, target = true}, - {name ="combat", interval = 3000, chance = 13, type = COMBAT_HOLYDAMAGE, minDamage = -120, maxDamage = -650, range = 7, length = 3, effect = CONST_ME_HOLYAREA, target = true}, - {name ="combat", interval = 3000, chance = 8, type = COMBAT_ENERGYDAMAGE, minDamage = -160, maxDamage = -500, range = 7, radius = 4, effect = CONST_ME_ENERGYAREA, target = false} + {name ="melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -100}, + {name ="combat", interval = 1000, chance = 8, type = COMBAT_ENERGYDAMAGE, minDamage = -100, maxDamage = -100, range = 7, radius = 3, shootEffect = CONST_ANI_ENERGY, effect = CONST_ME_ENERGYHIT, target = true}, + {name ="combat", interval = 3000, chance = 13, type = COMBAT_HOLYDAMAGE, minDamage = -100, maxDamage = -100, range = 7, length = 3, effect = CONST_ME_HOLYAREA, target = true}, + {name ="combat", interval = 3000, chance = 8, type = COMBAT_ENERGYDAMAGE, minDamage = -100, maxDamage = -100, range = 7, radius = 4, effect = CONST_ME_ENERGYAREA, target = false} } monster.defenses = { @@ -92,4 +92,24 @@ monster.immunities = { {type = "bleed", condition = false} } +local function applyDamageScalingCondition(creature) + local dromeLevel = getDromeLevel(creature) + local condition = Condition(CONDITION_ATTRIBUTES) + local scaleFactor = 1 + (dromeLevel * 0.5) + local scaledBuff = math.floor(100 * scaleFactor) + + condition:setParameter(CONDITION_PARAM_TICKS, -1) + condition:setParameter(CONDITION_PARAM_BUFF_DAMAGEDEALT, scaledBuff) + creature:addCondition(condition) +end + +mType.onAppear = function(creature) + applyDamageScalingCondition(creature) + + local creatureName = creature:getName() + + local dromeLevel = getDromeLevel(creature) +end + mType:register(monster) + diff --git a/data-otservbr-global/monster/drome/Hoodinion.lua b/data-otservbr-global/monster/drome/Hoodinion.lua index 4f70fce1356..f68ddf4426a 100644 --- a/data-otservbr-global/monster/drome/Hoodinion.lua +++ b/data-otservbr-global/monster/drome/Hoodinion.lua @@ -61,11 +61,11 @@ monster.voices = { } monster.attacks = { - {name ="melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -400}, - {name ="combat", interval = 1000, chance = 12, type = COMBAT_ICEDAMAGE, minDamage = -100, maxDamage = -500, range = 7, shootEffect = CONST_ANI_SMALLICE, effect = CONST_ME_ICEATTACK, target = true}, - {name ="combat", interval = 2000, chance = 12, type = COMBAT_ICEDAMAGE, minDamage = -100, maxDamage = -500, range = 7, radius = 6, shootEffect = CONST_ANI_SMALLICE, effect = CONST_ME_ICETORNADO, target = true}, - {name ="combat", interval = 3000, chance = 13, type = COMBAT_ICEDAMAGE, minDamage = -100, maxDamage = -600, range = 7, range = 7, shootEffect = CONST_ANI_SMALLICE, effect = CONST_ANI_TARSALARROW, target = true}, - {name ="combat", interval = 3000, chance = 16, type = COMBAT_HOLYDAMAGE, minDamage = -100, maxDamage = -550, range = 7, radius = 6, effect = CONST_ME_HITBYFIRE, target = false} + {name ="melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -100}, + {name ="combat", interval = 1000, chance = 12, type = COMBAT_ICEDAMAGE, minDamage = -100, maxDamage = -100, range = 7, shootEffect = CONST_ANI_SMALLICE, effect = CONST_ME_ICEATTACK, target = true}, + {name ="combat", interval = 2000, chance = 12, type = COMBAT_ICEDAMAGE, minDamage = -100, maxDamage = -100, range = 7, radius = 6, shootEffect = CONST_ANI_SMALLICE, effect = CONST_ME_ICETORNADO, target = true}, + {name ="combat", interval = 3000, chance = 13, type = COMBAT_ICEDAMAGE, minDamage = -100, maxDamage = -100, range = 7, range = 7, shootEffect = CONST_ANI_SMALLICE, effect = CONST_ANI_TARSALARROW, target = true}, + {name ="combat", interval = 3000, chance = 16, type = COMBAT_HOLYDAMAGE, minDamage = -100, maxDamage = -100, range = 7, radius = 6, effect = CONST_ME_HITBYFIRE, target = false} } monster.defenses = { @@ -93,10 +93,23 @@ monster.immunities = { {type = "bleed", condition = false} } +local function applyDamageScalingCondition(creature) + local dromeLevel = getDromeLevel(creature) + local condition = Condition(CONDITION_ATTRIBUTES) + local scaleFactor = 1 + (dromeLevel * 0.5) + local scaledBuff = math.floor(100 * scaleFactor) + + condition:setParameter(CONDITION_PARAM_TICKS, -1) + condition:setParameter(CONDITION_PARAM_BUFF_DAMAGEDEALT, scaledBuff) + creature:addCondition(condition) +end + mType.onAppear = function(creature) - -- Retrieve drome level from the creature (not player) - local dromeLevel = getDromeLevel(creature) -- Ensure this function returns a valid drome level - print(monster:getName() .. " spawned with drome level: " .. dromeLevel) + applyDamageScalingCondition(creature) + + local creatureName = creature:getName() + + local dromeLevel = getDromeLevel(creature) end mType:register(monster) diff --git a/data-otservbr-global/monster/drome/Mearidion.lua b/data-otservbr-global/monster/drome/Mearidion.lua index 48e955a1140..dbae89dd56d 100644 --- a/data-otservbr-global/monster/drome/Mearidion.lua +++ b/data-otservbr-global/monster/drome/Mearidion.lua @@ -61,11 +61,11 @@ monster.voices = { } monster.attacks = { - {name ="melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -500}, - {name ="combat", interval = 1000, chance = 12, type = COMBAT_FIREDAMAGE, minDamage = -100, maxDamage = -550, range = 7, shootEffect = CONST_ANI_FIRE, effect = CONST_ME_HITBYFIRE, target = true}, - {name ="combat", interval = 2000, chance = 12, type = COMBAT_DEATHDAMAGE, minDamage = -100, maxDamage = -600, range = 7, shootEffect = CONST_ANI_BOLT, effect = CONST_ME_BLACKSMOKE, target = true}, - {name ="combat", interval = 3000, chance = 13, type = COMBAT_DEATHDAMAGE, minDamage = -100, maxDamage = -700, range = 7, radius = 7, effect = CONST_ME_BLACKSMOKE, target = false}, - {name ="combat", interval = 3000, chance = 15, type = COMBAT_HOLYDAMAGE, minDamage = -100, maxDamage = -500, range = 7, radius = 2, shootEffect = CONST_ANI_SUDDENDEATH, effect = CONST_ME_MORTAREA, target = true} + {name ="melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -100}, + {name ="combat", interval = 1000, chance = 12, type = COMBAT_FIREDAMAGE, minDamage = -100, maxDamage = -100, range = 7, shootEffect = CONST_ANI_FIRE, effect = CONST_ME_HITBYFIRE, target = true}, + {name ="combat", interval = 2000, chance = 12, type = COMBAT_DEATHDAMAGE, minDamage = -100, maxDamage = -100, range = 7, shootEffect = CONST_ANI_BOLT, effect = CONST_ME_BLACKSMOKE, target = true}, + {name ="combat", interval = 3000, chance = 13, type = COMBAT_DEATHDAMAGE, minDamage = -100, maxDamage = -100, range = 7, radius = 7, effect = CONST_ME_BLACKSMOKE, target = false}, + {name ="combat", interval = 3000, chance = 15, type = COMBAT_HOLYDAMAGE, minDamage = -100, maxDamage = -100, range = 7, radius = 2, shootEffect = CONST_ANI_SUDDENDEATH, effect = CONST_ME_MORTAREA, target = true} } monster.defenses = { @@ -93,4 +93,23 @@ monster.immunities = { {type = "bleed", condition = false} } +local function applyDamageScalingCondition(creature) + local dromeLevel = getDromeLevel(creature) + local condition = Condition(CONDITION_ATTRIBUTES) + local scaleFactor = 1 + (dromeLevel * 0.5) + local scaledBuff = math.floor(100 * scaleFactor) + + condition:setParameter(CONDITION_PARAM_TICKS, -1) + condition:setParameter(CONDITION_PARAM_BUFF_DAMAGEDEALT, scaledBuff) + creature:addCondition(condition) +end + +mType.onAppear = function(creature) + applyDamageScalingCondition(creature) + + local creatureName = creature:getName() + + local dromeLevel = getDromeLevel(creature) +end + mType:register(monster) diff --git a/data-otservbr-global/monster/drome/Murmillion.lua b/data-otservbr-global/monster/drome/Murmillion.lua index 76e14a18616..af0496328e9 100644 --- a/data-otservbr-global/monster/drome/Murmillion.lua +++ b/data-otservbr-global/monster/drome/Murmillion.lua @@ -59,10 +59,10 @@ monster.light = { monster.voices = {} monster.attacks = { - {name = "melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -500}, - {name = "combat", interval = 1000, chance = 8, type = COMBAT_PHYSICALDAMAGE, minDamage = -100, maxDamage = -500, radius = 4, effect = CONST_ME_SLASH, target = false}, - {name = "combat", interval = 2500, chance = 13, type = COMBAT_PHYSICALDAMAGE, minDamage = -100, maxDamage = -550, range = 5, radius = 7, effect = CONST_ME_EXPLOSIONAREA, target = false}, - {name = "combat", interval = 2000, chance = 8, type = COMBAT_DEATHDAMAGE, minDamage = -100, maxDamage = -600, range = 7, length = 3, effect = CONST_ME_GROUNDSHAKER, target = false}, + {name = "melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -100}, + {name = "combat", interval = 1000, chance = 8, type = COMBAT_PHYSICALDAMAGE, minDamage = -100, maxDamage = -100, radius = 4, effect = CONST_ME_SLASH, target = false}, + {name = "combat", interval = 2500, chance = 13, type = COMBAT_PHYSICALDAMAGE, minDamage = -100, maxDamage = -100, range = 5, radius = 7, effect = CONST_ME_EXPLOSIONAREA, target = false}, + {name = "combat", interval = 2000, chance = 8, type = COMBAT_DEATHDAMAGE, minDamage = -100, maxDamage = -100, range = 7, length = 3, effect = CONST_ME_GROUNDSHAKER, target = false}, { name = "root", interval = 3000, chance = 1, target = true } } @@ -91,4 +91,23 @@ monster.immunities = { {type = "bleed", condition = false} } +local function applyDamageScalingCondition(creature) + local dromeLevel = getDromeLevel(creature) + local condition = Condition(CONDITION_ATTRIBUTES) + local scaleFactor = 1 + (dromeLevel * 0.5) + local scaledBuff = math.floor(100 * scaleFactor) + + condition:setParameter(CONDITION_PARAM_TICKS, -1) + condition:setParameter(CONDITION_PARAM_BUFF_DAMAGEDEALT, scaledBuff) + creature:addCondition(condition) +end + +mType.onAppear = function(creature) + applyDamageScalingCondition(creature) + + local creatureName = creature:getName() + + local dromeLevel = getDromeLevel(creature) +end + mType:register(monster) diff --git a/data-otservbr-global/monster/drome/Scissorion.lua b/data-otservbr-global/monster/drome/Scissorion.lua index 418fc38ecdf..1e13b617e88 100644 --- a/data-otservbr-global/monster/drome/Scissorion.lua +++ b/data-otservbr-global/monster/drome/Scissorion.lua @@ -61,10 +61,10 @@ monster.voices = { } monster.attacks = { - {name ="melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -500}, - {name ="combat", interval = 1000, chance = 8, type = COMBAT_EARTHDAMAGE, minDamage = -100, maxDamage = -700, range = 7, radius = 4, effect = CONST_ME_GREENSMOKE, target = false}, - {name ="combat", interval = 3000, chance = 13, type = COMBAT_FIREDAMAGE, minDamage = -100, maxDamage = -500, range = 7, effect = CONST_ME_CRITICAL_DAMAGE, target = true}, - {name ="combat", interval = 3000, chance = 8, type = COMBAT_FIREDAMAGE, minDamage = -100, maxDamage = -400, range = 7, length = 3, effect = CONST_ME_FIREATTACK, target = false} + {name ="melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -100}, + {name ="combat", interval = 1000, chance = 8, type = COMBAT_EARTHDAMAGE, minDamage = -100, maxDamage = -100, range = 7, radius = 4, effect = CONST_ME_GREENSMOKE, target = false}, + {name ="combat", interval = 3000, chance = 13, type = COMBAT_FIREDAMAGE, minDamage = -100, maxDamage = -100, range = 7, effect = CONST_ME_CRITICAL_DAMAGE, target = true}, + {name ="combat", interval = 3000, chance = 8, type = COMBAT_FIREDAMAGE, minDamage = -100, maxDamage = -100, range = 7, length = 3, effect = CONST_ME_FIREATTACK, target = false} } monster.defenses = { @@ -92,4 +92,23 @@ monster.immunities = { {type = "bleed", condition = false} } +local function applyDamageScalingCondition(creature) + local dromeLevel = getDromeLevel(creature) + local condition = Condition(CONDITION_ATTRIBUTES) + local scaleFactor = 1 + (dromeLevel * 0.5) + local scaledBuff = math.floor(100 * scaleFactor) + + condition:setParameter(CONDITION_PARAM_TICKS, -1) + condition:setParameter(CONDITION_PARAM_BUFF_DAMAGEDEALT, scaledBuff) + creature:addCondition(condition) +end + +mType.onAppear = function(creature) + applyDamageScalingCondition(creature) + + local creatureName = creature:getName() + + local dromeLevel = getDromeLevel(creature) +end + mType:register(monster) From 25689fe0bce2415e0622dda266f6595ad97173b8 Mon Sep 17 00:00:00 2001 From: mrmdbeng Date: Tue, 4 Feb 2025 15:22:34 -0500 Subject: [PATCH 03/11] Drome Highscores. --- data/items/items.xml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/data/items/items.xml b/data/items/items.xml index 9b228d3882a..15621c36561 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -80338,5 +80338,14 @@ Granted by TibiaGoals.com"/> + + + + + + + + + + - From 7cb64133ca0ac21ad9dcaf1242d6968b0be92565 Mon Sep 17 00:00:00 2001 From: mrmdbeng Date: Tue, 4 Feb 2025 16:18:33 -0500 Subject: [PATCH 04/11] Lever improvements. --- .../scripts/systems/drome_mechanics.lua | 1 + .../scripts/systems/tibia_drome.lua | 72 +++++++++++-------- 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/data-otservbr-global/scripts/systems/drome_mechanics.lua b/data-otservbr-global/scripts/systems/drome_mechanics.lua index 67ec3aa7d9f..812dc2d6eeb 100644 --- a/data-otservbr-global/scripts/systems/drome_mechanics.lua +++ b/data-otservbr-global/scripts/systems/drome_mechanics.lua @@ -521,6 +521,7 @@ end local function isTileWalkable(position) + -- Check if the position matches the restricted position if position == restrictedPosition then return false end diff --git a/data-otservbr-global/scripts/systems/tibia_drome.lua b/data-otservbr-global/scripts/systems/tibia_drome.lua index cea000713b8..05eb2165f3b 100644 --- a/data-otservbr-global/scripts/systems/tibia_drome.lua +++ b/data-otservbr-global/scripts/systems/tibia_drome.lua @@ -1,6 +1,3 @@ --- Im planning to move the database creation to schema once everything is good to go, for the time being im leaving it --- here so i can test without messing around with the original code - db.query([[ CREATE TABLE IF NOT EXISTS drome_highscores ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, @@ -184,53 +181,68 @@ function checkWaveCompletion(player) end end +local playerCooldowns = {} + function leverAction.onUse(player, item, fromPosition, target, isHotkey) if item:getActionId() == 46985 then - local isInPosition = false - for _, posData in ipairs(configDrome.playerPositions) do - if player:getPosition() == posData.pos then - isInPosition = true - break - end - end - - if not isInPosition then - return true - end - + local playerId = player:getGuid() + local lastFightTime = playerCooldowns[playerId] or 0 local currentTime = os.time() + if currentTime - lastFightTime < configDrome.timeToFightAgain then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You must wait before entering the Tibiadrome again.") return true end - lastFightTime = currentTime + playerCooldowns[playerId] = currentTime - local playerId = player:getGuid() - local query = string.format("SELECT highscore FROM drome_highscores WHERE player_id = %d", playerId) - local resultId = db.storeQuery(query) + local isAnyPlayerInPosition = false + local playersToTeleport = {} - local playerHighscore = 0 - if resultId then - playerHighscore = result.getNumber(resultId, "highscore") or 0 - result.free(resultId) + for _, targetPlayer in pairs(Game.getPlayers()) do + for _, posData in ipairs(configDrome.playerPositions) do + if targetPlayer:getPosition() == posData.pos then + table.insert(playersToTeleport, targetPlayer) + isAnyPlayerInPosition = true + break + end + end + end + + if not isAnyPlayerInPosition then + return true end - local startingWave = math.max(playerHighscore - 5, 0) - playerWaveData[playerId] = startingWave + for _, targetPlayer in ipairs(playersToTeleport) do + local playerId = targetPlayer:getGuid() + local query = string.format("SELECT highscore FROM drome_highscores WHERE player_id = %d", playerId) + local resultId = db.storeQuery(query) - local entryPos = configDrome.playerPositions[math.random(#configDrome.playerPositions)].teleport - player:teleportTo(entryPos) - player:getPosition():sendMagicEffect(configDrome.playerPositions[math.random(#configDrome.playerPositions)].effect) + local playerHighscore = 0 + if resultId then + playerHighscore = result.getNumber(resultId, "highscore") or 0 + result.free(resultId) + end + + local startingWave = math.max(playerHighscore - 5, 0) + playerWaveData[playerId] = startingWave + end + + for _, targetPlayer in ipairs(playersToTeleport) do + local entryPos = configDrome.playerPositions[math.random(#configDrome.playerPositions)].teleport + targetPlayer:teleportTo(entryPos) + targetPlayer:getPosition():sendMagicEffect(configDrome.playerPositions[math.random(#configDrome.playerPositions)].effect) + end if waveTimer then stopEvent(waveTimer) end - dromeLevel = startingWave + dromeLevel = 0 spawnMonsters(countPlayersInArea()) waveTimer = nil startWaveTimer() + return true end return false @@ -490,7 +502,7 @@ end offlinereward:register() --- save it to retrieve data ( not finished function ) +-- save it to retrieve data function getDromeLevel(player) local playerId = player:getId() local dromeLevel = dromeLevel + 1 From 43e997653f2978f5e48763ddb0bd9e2c0e1c515b Mon Sep 17 00:00:00 2001 From: mrmdbeng Date: Tue, 4 Feb 2025 21:14:38 -0500 Subject: [PATCH 05/11] bad roots / bad fear remove ground tile fix. --- data/items/appearances.dat | Bin 4439321 -> 4439304 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/data/items/appearances.dat b/data/items/appearances.dat index d4484cd48a408370e4c3e764d97e63c6d3d68c89..506f2504b7f1fbf015fd1b383c945f119102b18a 100644 GIT binary patch delta 295 zcmX}jxlRI60EOYqfN&X6ajxL5xO;~|-1imU!PxlG;kjU!g1H(q-?;3u5z{bTZmv>%zY+GH>1P(*Acf^WN?5 z)aA23-)G+oE=>D3y=hq|kyIp>s;o#&R%K1rWkWV)OSWZ4c4bfYj6?&vkzj2WJQVZ4S1 zNK4~PswjTdIcLt@&%OO1H(1zY8DNkcLm2Wn3{zl)A|+f#@$eaAoCzi=Gew1Is?1Ph zmVi0x%oEaJfhLR3(YKe4&0E^FBR~E1BDa`#Y?HaRUTw>1nfJ7tQ_%Dyuh197Y2rtX z|J9FPHF*3gwZkNHVNA`fmg_-S>DyhI&)(B*$B$%5mSsg&WldsPmzHeErfkW!?8vUP ZWl#3yKn~?dj^#uWIhC`Qj(>jn^arKSV9Edh From 37cc5ec994eeccbf560c4c2d22a72be0254ddbf2 Mon Sep 17 00:00:00 2001 From: mrmdbeng Date: Tue, 4 Feb 2025 21:22:47 -0500 Subject: [PATCH 06/11] faster beam me up --- data-otservbr-global/scripts/systems/drome_mechanics.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data-otservbr-global/scripts/systems/drome_mechanics.lua b/data-otservbr-global/scripts/systems/drome_mechanics.lua index 812dc2d6eeb..20f40048c0a 100644 --- a/data-otservbr-global/scripts/systems/drome_mechanics.lua +++ b/data-otservbr-global/scripts/systems/drome_mechanics.lua @@ -178,7 +178,7 @@ function placeBeamFields() addEvent(function() item:remove() end, 5000) end end - addEvent(teleportPlayers, 1000) + addEvent(teleportPlayers, 500) end function teleportPlayers() From ad10c01054bad5ded6a49b4f1872b9804a7c0f1a Mon Sep 17 00:00:00 2001 From: mrmdbeng Date: Wed, 5 Feb 2025 23:06:58 -0500 Subject: [PATCH 07/11] global variable / better iteration for drome --- .../scripts/systems/drome_mechanics.lua | 56 +++++++----- .../scripts/systems/tibia_drome.lua | 85 +++++++++++++------ 2 files changed, 94 insertions(+), 47 deletions(-) diff --git a/data-otservbr-global/scripts/systems/drome_mechanics.lua b/data-otservbr-global/scripts/systems/drome_mechanics.lua index 20f40048c0a..2683641f7be 100644 --- a/data-otservbr-global/scripts/systems/drome_mechanics.lua +++ b/data-otservbr-global/scripts/systems/drome_mechanics.lua @@ -87,11 +87,14 @@ function teleportSingleMonsterToPlayer(action) if math.random(100) <= 15 then local randomMonster = monstersInArea[math.random(#monstersInArea)] local playersInArea = {} - for _, player in pairs(Game.getPlayers()) do - if player:getPosition():isInRange(generalArea.from, generalArea.to) then - table.insert(playersInArea, player) + local centerPosition = Position(generalArea.from.x + (rangeX / 2), generalArea.from.y + (rangeY / 2), generalArea.from.z) + local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) + + for _, spectator in ipairs(spectators) do + if spectator:getPosition():isInRange(generalArea.from, generalArea.to) then + table.insert(playersInArea, spectator) end - end + end if #playersInArea > 0 then local randomPlayer = playersInArea[math.random(#playersInArea)] @@ -137,14 +140,16 @@ end function applyLavaDamage() local current_time = os.time() - local players = Game.getPlayers() - for _, player in ipairs(players) do - local player_position = player:getPosition() + local centerPosition = Position(generalArea.from.x + (rangeX / 2), generalArea.from.y + (rangeY / 2), generalArea.from.z) + local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) + + for _, spectator in ipairs(spectators) do + local player_position = spectator:getPosition() for _, field in ipairs(lavaFields) do if player_position == field.position then if current_time - field.created_time >= 3 then - local damage = player:getHealth() * 0.6 - player:addHealth(-damage) + local damage = spectator:getHealth() * 0.6 + spectator:addHealth(-damage) end end end @@ -178,17 +183,19 @@ function placeBeamFields() addEvent(function() item:remove() end, 5000) end end - addEvent(teleportPlayers, 500) + addEvent(teleportPlayers, 1000) end function teleportPlayers() - local players = Game.getPlayers() - for _, player in ipairs(players) do - local playerPosition = player:getPosition() + local centerPosition = Position(generalArea.from.x + (rangeX / 2), generalArea.from.y + (rangeY / 2), generalArea.from.z) + local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) + + for _, spectator in ipairs(spectators) do + local playerPosition = spectator:getPosition() for _, field in ipairs(beamFields) do if playerPosition == field.position then local randomPos = getRandomWalkablePosition() - player:teleportTo(randomPos) + spectator:teleportTo(randomPos) break end end @@ -222,18 +229,20 @@ function placeTankedUpFields() addEvent(function() item:remove() end, 5000) end end - addEvent(applySuperdrunkEffect, 500) + addEvent(applySuperdrunkEffect, 1000) end function applySuperdrunkEffect() - local players = Game.getPlayers() - for _, player in ipairs(players) do - local playerPosition = player:getPosition() + local centerPosition = Position(generalArea.from.x + (rangeX / 2), generalArea.from.y + (rangeY / 2), generalArea.from.z) + local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) + + for _, spectator in ipairs(spectators) do + local playerPosition = spectator:getPosition() for _, field in ipairs(tankedUpFields) do if playerPosition == field.position then local drunk = Condition(CONDITION_DRUNK) drunk:setParameter(CONDITION_PARAM_TICKS, 40000) - player:addCondition(drunk) + spectator:addCondition(drunk) break end end @@ -264,9 +273,11 @@ function isWalkable(position) end function arePlayersInArea() - local players = Game.getPlayers() - for _, player in ipairs(players) do - if isInSpecArea(player:getPosition()) then + local centerPosition = Position(generalArea.from.x + (rangeX / 2), generalArea.from.y + (rangeY / 2), generalArea.from.z) + local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) + + for _, spectator in ipairs(spectators) do + if isInSpecArea(spectator:getPosition()) then return true end end @@ -521,7 +532,6 @@ end local function isTileWalkable(position) - -- Check if the position matches the restricted position if position == restrictedPosition then return false end diff --git a/data-otservbr-global/scripts/systems/tibia_drome.lua b/data-otservbr-global/scripts/systems/tibia_drome.lua index 05eb2165f3b..af52378e17a 100644 --- a/data-otservbr-global/scripts/systems/tibia_drome.lua +++ b/data-otservbr-global/scripts/systems/tibia_drome.lua @@ -58,6 +58,9 @@ local creaturePool = { "Murmillion", "Scissorion" } +rangeX = math.abs(configDrome.specPos.to.x - configDrome.specPos.from.x) +rangeY = math.abs(configDrome.specPos.to.y - configDrome.specPos.from.y) + function deathEvent.onDeath(creature, corpse, killer, mostDamageKiller) if creature:isMonster() then @@ -116,30 +119,34 @@ function startWaveTimer() local playerCount = countPlayersInArea() if playerCount > 0 then - for _, targetPlayer in pairs(Game.getPlayers()) do - if targetPlayer:getPosition():isInRange(configDrome.specPos.from, configDrome.specPos.to) then - targetPlayer:teleportTo(Position(32255, 32205, 11)) -- Position for players who failed - targetPlayer:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You failed to complete the wave in time! You have been kicked out.") + local centerPosition = Position(configDrome.specPos.from.x, configDrome.specPos.from.y, configDrome.specPos.from.z) + local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) + + if #spectators > 0 then + for _, spectator in pairs(spectators) do + spectator:teleportTo(Position(32255, 32205, 11)) + spectator:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You failed to complete the wave in time! You have been kicked out.") end end - else end end, waveTimeLimit * 1000) end function checkWaveCompletion(player) - local playerCount = countPlayersInArea() local requiredMonsters = calculateMonsters(playerCount) if monstersKilled >= requiredMonsters then - if waveTimer then stopEvent(waveTimer) end - for _, targetPlayer in pairs(Game.getPlayers()) do - if targetPlayer:getPosition():isInRange(configDrome.specPos.from, configDrome.specPos.to) then + local centerPosition = Position(configDrome.specPos.from.x, configDrome.specPos.from.y, configDrome.specPos.from.z) + local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) + + -- Check if there are any players in the area + if #spectators > 0 then + for _, targetPlayer in pairs(spectators) do local playerId = targetPlayer:getGuid() local playerName = targetPlayer:getName() local playerHighscore = playerWaveData[playerId] or 0 @@ -160,11 +167,8 @@ function checkWaveCompletion(player) monstersKilled = 0 local message = "Next wave will start in 8 seconds. Current level: " .. dromeLevel - local playersInArea = Game.getPlayers() - for _, targetPlayer in pairs(playersInArea) do - if targetPlayer:getPosition():isInRange(configDrome.specPos.from, configDrome.specPos.to) then - targetPlayer:say(message, TALKTYPE_YELL) - end + for _, targetPlayer in pairs(spectators) do + targetPlayer:say(message, TALKTYPE_YELL) end addEvent(function() @@ -194,21 +198,53 @@ function leverAction.onUse(player, item, fromPosition, target, isHotkey) return true end + local isInValidPosition = false + for _, posData in ipairs(configDrome.playerPositions) do + if player:getPosition() == posData.pos then + isInValidPosition = true + break + end + end + + if not isInValidPosition then + -- player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You must be in a valid position to use the lever.") + return true + end + + local isAnyoneInSpecPos = false + local centerPosition = Position(configDrome.specPos.from.x, configDrome.specPos.from.y, configDrome.specPos.from.z) + local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) + + for _, targetPlayer in pairs(spectators) do + if targetPlayer:getPosition():isInRange(configDrome.specPos.from, configDrome.specPos.to) then + isAnyoneInSpecPos = true + break + end + end + + if isAnyoneInSpecPos then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Someone is already inside the Tibiadrome. Please wait until they leave.") + return true + end + playerCooldowns[playerId] = currentTime local isAnyPlayerInPosition = false local playersToTeleport = {} - for _, targetPlayer in pairs(Game.getPlayers()) do - for _, posData in ipairs(configDrome.playerPositions) do - if targetPlayer:getPosition() == posData.pos then + for _, posData in ipairs(configDrome.playerPositions) do + local centerPosition = Position(posData.pos.x, posData.pos.y, posData.pos.z) + local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) + + for _, targetPlayer in pairs(spectators) do + if targetPlayer:getPosition():isInRange(posData.pos, posData.pos) then table.insert(playersToTeleport, targetPlayer) isAnyPlayerInPosition = true break end end end - + if not isAnyPlayerInPosition then return true end @@ -248,15 +284,16 @@ function leverAction.onUse(player, item, fromPosition, target, isHotkey) return false end + function countPlayersInArea() local playerCount = 0 - for _, targetPlayer in pairs(Game.getPlayers()) do - local playerPos = targetPlayer:getPosition() + local centerPosition = Position(configDrome.specPos.from.x, configDrome.specPos.from.y, configDrome.specPos.from.z) + local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) - if playerPos:isInRange(configDrome.specPos.from, configDrome.specPos.to) then - playerCount = playerCount + 1 - end + for _, targetPlayer in pairs(spectators) do + playerCount = playerCount + 1 end + return playerCount end @@ -502,7 +539,7 @@ end offlinereward:register() --- save it to retrieve data +-- save it to retrieve data for monsters and future development function getDromeLevel(player) local playerId = player:getId() local dromeLevel = dromeLevel + 1 From 414b05e7a022b9253d317d10335a44fa8cc19a69 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sat, 8 Feb 2025 06:03:25 +0000 Subject: [PATCH 08/11] Lua code format - (Stylua) --- .../monster/drome/Domestikion.lua | 71 +- .../monster/drome/Hoodinion.lua | 72 +- .../monster/drome/Mearidion.lua | 72 +- .../monster/drome/Murmillion.lua | 120 +- .../monster/drome/Scissorion.lua | 70 +- .../scripts/systems/drome_mechanics.lua | 1616 ++++++++--------- .../scripts/systems/tibia_drome.lua | 1146 ++++++------ 7 files changed, 1583 insertions(+), 1584 deletions(-) diff --git a/data-otservbr-global/monster/drome/Domestikion.lua b/data-otservbr-global/monster/drome/Domestikion.lua index cbc8a360496..f148d645b3b 100644 --- a/data-otservbr-global/monster/drome/Domestikion.lua +++ b/data-otservbr-global/monster/drome/Domestikion.lua @@ -22,7 +22,7 @@ monster.manaCost = 0 monster.changeTarget = { interval = 4000, - chance = 10 + chance = 10, } monster.strategiesTarget = { @@ -48,68 +48,65 @@ monster.flags = { isBlockable = false, canWalkOnEnergy = false, canWalkOnFire = true, - canWalkOnPoison = true + canWalkOnPoison = true, } monster.light = { level = 0, - color = 0 + color = 0, } -monster.voices = { - -} +monster.voices = {} monster.attacks = { - {name ="melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -100}, - {name ="combat", interval = 1000, chance = 8, type = COMBAT_ENERGYDAMAGE, minDamage = -100, maxDamage = -100, range = 7, radius = 3, shootEffect = CONST_ANI_ENERGY, effect = CONST_ME_ENERGYHIT, target = true}, - {name ="combat", interval = 3000, chance = 13, type = COMBAT_HOLYDAMAGE, minDamage = -100, maxDamage = -100, range = 7, length = 3, effect = CONST_ME_HOLYAREA, target = true}, - {name ="combat", interval = 3000, chance = 8, type = COMBAT_ENERGYDAMAGE, minDamage = -100, maxDamage = -100, range = 7, radius = 4, effect = CONST_ME_ENERGYAREA, target = false} + { name = "melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -100 }, + { name = "combat", interval = 1000, chance = 8, type = COMBAT_ENERGYDAMAGE, minDamage = -100, maxDamage = -100, range = 7, radius = 3, shootEffect = CONST_ANI_ENERGY, effect = CONST_ME_ENERGYHIT, target = true }, + { name = "combat", interval = 3000, chance = 13, type = COMBAT_HOLYDAMAGE, minDamage = -100, maxDamage = -100, range = 7, length = 3, effect = CONST_ME_HOLYAREA, target = true }, + { name = "combat", interval = 3000, chance = 8, type = COMBAT_ENERGYDAMAGE, minDamage = -100, maxDamage = -100, range = 7, radius = 4, effect = CONST_ME_ENERGYAREA, target = false }, } monster.defenses = { defense = 110, - armor = 110 + armor = 110, } monster.elements = { - {type = COMBAT_PHYSICALDAMAGE, percent = 0}, - {type = COMBAT_ENERGYDAMAGE, percent = 0}, - {type = COMBAT_EARTHDAMAGE, percent = 0}, - {type = COMBAT_FIREDAMAGE, percent = 0}, - {type = COMBAT_LIFEDRAIN, percent = 0}, - {type = COMBAT_MANADRAIN, percent = 0}, - {type = COMBAT_DROWNDAMAGE, percent = 0}, - {type = COMBAT_ICEDAMAGE, percent = 0}, - {type = COMBAT_HOLYDAMAGE , percent = 0}, - {type = COMBAT_DEATHDAMAGE , percent = 0} + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 0 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, } monster.immunities = { - {type = "paralyze", condition = true}, - {type = "outfit", condition = false}, - {type = "invisible", condition = true}, - {type = "bleed", condition = false} + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, } local function applyDamageScalingCondition(creature) - local dromeLevel = getDromeLevel(creature) - local condition = Condition(CONDITION_ATTRIBUTES) - local scaleFactor = 1 + (dromeLevel * 0.5) - local scaledBuff = math.floor(100 * scaleFactor) - - condition:setParameter(CONDITION_PARAM_TICKS, -1) - condition:setParameter(CONDITION_PARAM_BUFF_DAMAGEDEALT, scaledBuff) - creature:addCondition(condition) + local dromeLevel = getDromeLevel(creature) + local condition = Condition(CONDITION_ATTRIBUTES) + local scaleFactor = 1 + (dromeLevel * 0.5) + local scaledBuff = math.floor(100 * scaleFactor) + + condition:setParameter(CONDITION_PARAM_TICKS, -1) + condition:setParameter(CONDITION_PARAM_BUFF_DAMAGEDEALT, scaledBuff) + creature:addCondition(condition) end mType.onAppear = function(creature) - applyDamageScalingCondition(creature) + applyDamageScalingCondition(creature) - local creatureName = creature:getName() + local creatureName = creature:getName() - local dromeLevel = getDromeLevel(creature) + local dromeLevel = getDromeLevel(creature) end mType:register(monster) - diff --git a/data-otservbr-global/monster/drome/Hoodinion.lua b/data-otservbr-global/monster/drome/Hoodinion.lua index f68ddf4426a..e67af8fcea7 100644 --- a/data-otservbr-global/monster/drome/Hoodinion.lua +++ b/data-otservbr-global/monster/drome/Hoodinion.lua @@ -22,7 +22,7 @@ monster.manaCost = 0 monster.changeTarget = { interval = 4000, - chance = 10 + chance = 10, } monster.strategiesTarget = { @@ -48,68 +48,66 @@ monster.flags = { isBlockable = false, canWalkOnEnergy = false, canWalkOnFire = true, - canWalkOnPoison = true + canWalkOnPoison = true, } monster.light = { level = 0, - color = 0 + color = 0, } -monster.voices = { - -} +monster.voices = {} monster.attacks = { - {name ="melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -100}, - {name ="combat", interval = 1000, chance = 12, type = COMBAT_ICEDAMAGE, minDamage = -100, maxDamage = -100, range = 7, shootEffect = CONST_ANI_SMALLICE, effect = CONST_ME_ICEATTACK, target = true}, - {name ="combat", interval = 2000, chance = 12, type = COMBAT_ICEDAMAGE, minDamage = -100, maxDamage = -100, range = 7, radius = 6, shootEffect = CONST_ANI_SMALLICE, effect = CONST_ME_ICETORNADO, target = true}, - {name ="combat", interval = 3000, chance = 13, type = COMBAT_ICEDAMAGE, minDamage = -100, maxDamage = -100, range = 7, range = 7, shootEffect = CONST_ANI_SMALLICE, effect = CONST_ANI_TARSALARROW, target = true}, - {name ="combat", interval = 3000, chance = 16, type = COMBAT_HOLYDAMAGE, minDamage = -100, maxDamage = -100, range = 7, radius = 6, effect = CONST_ME_HITBYFIRE, target = false} + { name = "melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -100 }, + { name = "combat", interval = 1000, chance = 12, type = COMBAT_ICEDAMAGE, minDamage = -100, maxDamage = -100, range = 7, shootEffect = CONST_ANI_SMALLICE, effect = CONST_ME_ICEATTACK, target = true }, + { name = "combat", interval = 2000, chance = 12, type = COMBAT_ICEDAMAGE, minDamage = -100, maxDamage = -100, range = 7, radius = 6, shootEffect = CONST_ANI_SMALLICE, effect = CONST_ME_ICETORNADO, target = true }, + { name = "combat", interval = 3000, chance = 13, type = COMBAT_ICEDAMAGE, minDamage = -100, maxDamage = -100, range = 7, range = 7, shootEffect = CONST_ANI_SMALLICE, effect = CONST_ANI_TARSALARROW, target = true }, + { name = "combat", interval = 3000, chance = 16, type = COMBAT_HOLYDAMAGE, minDamage = -100, maxDamage = -100, range = 7, radius = 6, effect = CONST_ME_HITBYFIRE, target = false }, } monster.defenses = { defense = 110, - armor = 110 + armor = 110, } monster.elements = { - {type = COMBAT_PHYSICALDAMAGE, percent = 0}, - {type = COMBAT_ENERGYDAMAGE, percent = 0}, - {type = COMBAT_EARTHDAMAGE, percent = 0}, - {type = COMBAT_FIREDAMAGE, percent = 0}, - {type = COMBAT_LIFEDRAIN, percent = 0}, - {type = COMBAT_MANADRAIN, percent = 0}, - {type = COMBAT_DROWNDAMAGE, percent = 0}, - {type = COMBAT_ICEDAMAGE, percent = 0}, - {type = COMBAT_HOLYDAMAGE , percent = 0}, - {type = COMBAT_DEATHDAMAGE , percent = 0} + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 0 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, } monster.immunities = { - {type = "paralyze", condition = true}, - {type = "outfit", condition = false}, - {type = "invisible", condition = true}, - {type = "bleed", condition = false} + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, } local function applyDamageScalingCondition(creature) - local dromeLevel = getDromeLevel(creature) - local condition = Condition(CONDITION_ATTRIBUTES) - local scaleFactor = 1 + (dromeLevel * 0.5) - local scaledBuff = math.floor(100 * scaleFactor) - - condition:setParameter(CONDITION_PARAM_TICKS, -1) - condition:setParameter(CONDITION_PARAM_BUFF_DAMAGEDEALT, scaledBuff) - creature:addCondition(condition) + local dromeLevel = getDromeLevel(creature) + local condition = Condition(CONDITION_ATTRIBUTES) + local scaleFactor = 1 + (dromeLevel * 0.5) + local scaledBuff = math.floor(100 * scaleFactor) + + condition:setParameter(CONDITION_PARAM_TICKS, -1) + condition:setParameter(CONDITION_PARAM_BUFF_DAMAGEDEALT, scaledBuff) + creature:addCondition(condition) end mType.onAppear = function(creature) - applyDamageScalingCondition(creature) + applyDamageScalingCondition(creature) - local creatureName = creature:getName() + local creatureName = creature:getName() - local dromeLevel = getDromeLevel(creature) + local dromeLevel = getDromeLevel(creature) end mType:register(monster) diff --git a/data-otservbr-global/monster/drome/Mearidion.lua b/data-otservbr-global/monster/drome/Mearidion.lua index dbae89dd56d..bd078d56559 100644 --- a/data-otservbr-global/monster/drome/Mearidion.lua +++ b/data-otservbr-global/monster/drome/Mearidion.lua @@ -22,7 +22,7 @@ monster.manaCost = 0 monster.changeTarget = { interval = 4000, - chance = 10 + chance = 10, } monster.strategiesTarget = { @@ -48,68 +48,66 @@ monster.flags = { isBlockable = false, canWalkOnEnergy = false, canWalkOnFire = true, - canWalkOnPoison = true + canWalkOnPoison = true, } monster.light = { level = 0, - color = 0 + color = 0, } -monster.voices = { - -} +monster.voices = {} monster.attacks = { - {name ="melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -100}, - {name ="combat", interval = 1000, chance = 12, type = COMBAT_FIREDAMAGE, minDamage = -100, maxDamage = -100, range = 7, shootEffect = CONST_ANI_FIRE, effect = CONST_ME_HITBYFIRE, target = true}, - {name ="combat", interval = 2000, chance = 12, type = COMBAT_DEATHDAMAGE, minDamage = -100, maxDamage = -100, range = 7, shootEffect = CONST_ANI_BOLT, effect = CONST_ME_BLACKSMOKE, target = true}, - {name ="combat", interval = 3000, chance = 13, type = COMBAT_DEATHDAMAGE, minDamage = -100, maxDamage = -100, range = 7, radius = 7, effect = CONST_ME_BLACKSMOKE, target = false}, - {name ="combat", interval = 3000, chance = 15, type = COMBAT_HOLYDAMAGE, minDamage = -100, maxDamage = -100, range = 7, radius = 2, shootEffect = CONST_ANI_SUDDENDEATH, effect = CONST_ME_MORTAREA, target = true} + { name = "melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -100 }, + { name = "combat", interval = 1000, chance = 12, type = COMBAT_FIREDAMAGE, minDamage = -100, maxDamage = -100, range = 7, shootEffect = CONST_ANI_FIRE, effect = CONST_ME_HITBYFIRE, target = true }, + { name = "combat", interval = 2000, chance = 12, type = COMBAT_DEATHDAMAGE, minDamage = -100, maxDamage = -100, range = 7, shootEffect = CONST_ANI_BOLT, effect = CONST_ME_BLACKSMOKE, target = true }, + { name = "combat", interval = 3000, chance = 13, type = COMBAT_DEATHDAMAGE, minDamage = -100, maxDamage = -100, range = 7, radius = 7, effect = CONST_ME_BLACKSMOKE, target = false }, + { name = "combat", interval = 3000, chance = 15, type = COMBAT_HOLYDAMAGE, minDamage = -100, maxDamage = -100, range = 7, radius = 2, shootEffect = CONST_ANI_SUDDENDEATH, effect = CONST_ME_MORTAREA, target = true }, } monster.defenses = { defense = 110, - armor = 110 + armor = 110, } monster.elements = { - {type = COMBAT_PHYSICALDAMAGE, percent = 0}, - {type = COMBAT_ENERGYDAMAGE, percent = 0}, - {type = COMBAT_EARTHDAMAGE, percent = 0}, - {type = COMBAT_FIREDAMAGE, percent = 0}, - {type = COMBAT_LIFEDRAIN, percent = 0}, - {type = COMBAT_MANADRAIN, percent = 0}, - {type = COMBAT_DROWNDAMAGE, percent = 0}, - {type = COMBAT_ICEDAMAGE, percent = 0}, - {type = COMBAT_HOLYDAMAGE , percent = 0}, - {type = COMBAT_DEATHDAMAGE , percent = 0} + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 0 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, } monster.immunities = { - {type = "paralyze", condition = true}, - {type = "outfit", condition = false}, - {type = "invisible", condition = true}, - {type = "bleed", condition = false} + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, } local function applyDamageScalingCondition(creature) - local dromeLevel = getDromeLevel(creature) - local condition = Condition(CONDITION_ATTRIBUTES) - local scaleFactor = 1 + (dromeLevel * 0.5) - local scaledBuff = math.floor(100 * scaleFactor) - - condition:setParameter(CONDITION_PARAM_TICKS, -1) - condition:setParameter(CONDITION_PARAM_BUFF_DAMAGEDEALT, scaledBuff) - creature:addCondition(condition) + local dromeLevel = getDromeLevel(creature) + local condition = Condition(CONDITION_ATTRIBUTES) + local scaleFactor = 1 + (dromeLevel * 0.5) + local scaledBuff = math.floor(100 * scaleFactor) + + condition:setParameter(CONDITION_PARAM_TICKS, -1) + condition:setParameter(CONDITION_PARAM_BUFF_DAMAGEDEALT, scaledBuff) + creature:addCondition(condition) end mType.onAppear = function(creature) - applyDamageScalingCondition(creature) + applyDamageScalingCondition(creature) - local creatureName = creature:getName() + local creatureName = creature:getName() - local dromeLevel = getDromeLevel(creature) + local dromeLevel = getDromeLevel(creature) end mType:register(monster) diff --git a/data-otservbr-global/monster/drome/Murmillion.lua b/data-otservbr-global/monster/drome/Murmillion.lua index af0496328e9..feb1605985b 100644 --- a/data-otservbr-global/monster/drome/Murmillion.lua +++ b/data-otservbr-global/monster/drome/Murmillion.lua @@ -4,13 +4,13 @@ local monster = {} monster.description = "Murmillion" monster.experience = 0 monster.outfit = { - lookType = 1422, + lookType = 1422, } monster.events = { - "DromeMonsterDeath", - "ExplodingCorpses", - "TargetedExplodingCorpses", + "DromeMonsterDeath", + "ExplodingCorpses", + "TargetedExplodingCorpses", } monster.health = 800 @@ -21,93 +21,93 @@ monster.speed = 180 monster.manaCost = 0 monster.changeTarget = { - interval = 4000, - chance = 10 + interval = 4000, + chance = 10, } monster.strategiesTarget = { - nearest = 80, - health = 10, - damage = 10, + nearest = 80, + health = 10, + damage = 10, } monster.flags = { - summonable = false, - attackable = true, - hostile = true, - convinceable = false, - pushable = false, - rewardBoss = false, - illusionable = false, - canPushItems = true, - canPushCreatures = true, - staticAttackChance = 90, - targetDistance = 1, - runHealth = 0, - healthHidden = false, - isBlockable = false, - canWalkOnEnergy = false, - canWalkOnFire = true, - canWalkOnPoison = true + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = false, + canWalkOnFire = true, + canWalkOnPoison = true, } monster.light = { - level = 0, - color = 0 + level = 0, + color = 0, } monster.voices = {} monster.attacks = { - {name = "melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -100}, - {name = "combat", interval = 1000, chance = 8, type = COMBAT_PHYSICALDAMAGE, minDamage = -100, maxDamage = -100, radius = 4, effect = CONST_ME_SLASH, target = false}, - {name = "combat", interval = 2500, chance = 13, type = COMBAT_PHYSICALDAMAGE, minDamage = -100, maxDamage = -100, range = 5, radius = 7, effect = CONST_ME_EXPLOSIONAREA, target = false}, - {name = "combat", interval = 2000, chance = 8, type = COMBAT_DEATHDAMAGE, minDamage = -100, maxDamage = -100, range = 7, length = 3, effect = CONST_ME_GROUNDSHAKER, target = false}, - { name = "root", interval = 3000, chance = 1, target = true } + { name = "melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -100 }, + { name = "combat", interval = 1000, chance = 8, type = COMBAT_PHYSICALDAMAGE, minDamage = -100, maxDamage = -100, radius = 4, effect = CONST_ME_SLASH, target = false }, + { name = "combat", interval = 2500, chance = 13, type = COMBAT_PHYSICALDAMAGE, minDamage = -100, maxDamage = -100, range = 5, radius = 7, effect = CONST_ME_EXPLOSIONAREA, target = false }, + { name = "combat", interval = 2000, chance = 8, type = COMBAT_DEATHDAMAGE, minDamage = -100, maxDamage = -100, range = 7, length = 3, effect = CONST_ME_GROUNDSHAKER, target = false }, + { name = "root", interval = 3000, chance = 1, target = true }, } monster.defenses = { - defense = 110, - armor = 110 + defense = 110, + armor = 110, } monster.elements = { - {type = COMBAT_PHYSICALDAMAGE, percent = 0}, - {type = COMBAT_ENERGYDAMAGE, percent = 0}, - {type = COMBAT_EARTHDAMAGE, percent = 0}, - {type = COMBAT_FIREDAMAGE, percent = 0}, - {type = COMBAT_LIFEDRAIN, percent = 0}, - {type = COMBAT_MANADRAIN, percent = 0}, - {type = COMBAT_DROWNDAMAGE, percent = 0}, - {type = COMBAT_ICEDAMAGE, percent = 0}, - {type = COMBAT_HOLYDAMAGE , percent = 0}, - {type = COMBAT_DEATHDAMAGE , percent = 0} + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 0 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, } monster.immunities = { - {type = "paralyze", condition = true}, - {type = "outfit", condition = false}, - {type = "invisible", condition = true}, - {type = "bleed", condition = false} + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, } local function applyDamageScalingCondition(creature) - local dromeLevel = getDromeLevel(creature) - local condition = Condition(CONDITION_ATTRIBUTES) - local scaleFactor = 1 + (dromeLevel * 0.5) - local scaledBuff = math.floor(100 * scaleFactor) - - condition:setParameter(CONDITION_PARAM_TICKS, -1) - condition:setParameter(CONDITION_PARAM_BUFF_DAMAGEDEALT, scaledBuff) - creature:addCondition(condition) + local dromeLevel = getDromeLevel(creature) + local condition = Condition(CONDITION_ATTRIBUTES) + local scaleFactor = 1 + (dromeLevel * 0.5) + local scaledBuff = math.floor(100 * scaleFactor) + + condition:setParameter(CONDITION_PARAM_TICKS, -1) + condition:setParameter(CONDITION_PARAM_BUFF_DAMAGEDEALT, scaledBuff) + creature:addCondition(condition) end mType.onAppear = function(creature) - applyDamageScalingCondition(creature) + applyDamageScalingCondition(creature) - local creatureName = creature:getName() + local creatureName = creature:getName() - local dromeLevel = getDromeLevel(creature) + local dromeLevel = getDromeLevel(creature) end mType:register(monster) diff --git a/data-otservbr-global/monster/drome/Scissorion.lua b/data-otservbr-global/monster/drome/Scissorion.lua index 1e13b617e88..9f05dbea034 100644 --- a/data-otservbr-global/monster/drome/Scissorion.lua +++ b/data-otservbr-global/monster/drome/Scissorion.lua @@ -22,7 +22,7 @@ monster.manaCost = 0 monster.changeTarget = { interval = 4000, - chance = 10 + chance = 10, } monster.strategiesTarget = { @@ -48,67 +48,65 @@ monster.flags = { isBlockable = false, canWalkOnEnergy = false, canWalkOnFire = true, - canWalkOnPoison = true + canWalkOnPoison = true, } monster.light = { level = 0, - color = 0 + color = 0, } -monster.voices = { - -} +monster.voices = {} monster.attacks = { - {name ="melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -100}, - {name ="combat", interval = 1000, chance = 8, type = COMBAT_EARTHDAMAGE, minDamage = -100, maxDamage = -100, range = 7, radius = 4, effect = CONST_ME_GREENSMOKE, target = false}, - {name ="combat", interval = 3000, chance = 13, type = COMBAT_FIREDAMAGE, minDamage = -100, maxDamage = -100, range = 7, effect = CONST_ME_CRITICAL_DAMAGE, target = true}, - {name ="combat", interval = 3000, chance = 8, type = COMBAT_FIREDAMAGE, minDamage = -100, maxDamage = -100, range = 7, length = 3, effect = CONST_ME_FIREATTACK, target = false} + { name = "melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -100 }, + { name = "combat", interval = 1000, chance = 8, type = COMBAT_EARTHDAMAGE, minDamage = -100, maxDamage = -100, range = 7, radius = 4, effect = CONST_ME_GREENSMOKE, target = false }, + { name = "combat", interval = 3000, chance = 13, type = COMBAT_FIREDAMAGE, minDamage = -100, maxDamage = -100, range = 7, effect = CONST_ME_CRITICAL_DAMAGE, target = true }, + { name = "combat", interval = 3000, chance = 8, type = COMBAT_FIREDAMAGE, minDamage = -100, maxDamage = -100, range = 7, length = 3, effect = CONST_ME_FIREATTACK, target = false }, } monster.defenses = { defense = 110, - armor = 110 + armor = 110, } monster.elements = { - {type = COMBAT_PHYSICALDAMAGE, percent = 0}, - {type = COMBAT_ENERGYDAMAGE, percent = 0}, - {type = COMBAT_EARTHDAMAGE, percent = 0}, - {type = COMBAT_FIREDAMAGE, percent = 0}, - {type = COMBAT_LIFEDRAIN, percent = 0}, - {type = COMBAT_MANADRAIN, percent = 0}, - {type = COMBAT_DROWNDAMAGE, percent = 0}, - {type = COMBAT_ICEDAMAGE, percent = 0}, - {type = COMBAT_HOLYDAMAGE , percent = 0}, - {type = COMBAT_DEATHDAMAGE , percent = 0} + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 0 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, } monster.immunities = { - {type = "paralyze", condition = true}, - {type = "outfit", condition = false}, - {type = "invisible", condition = true}, - {type = "bleed", condition = false} + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, } local function applyDamageScalingCondition(creature) - local dromeLevel = getDromeLevel(creature) - local condition = Condition(CONDITION_ATTRIBUTES) - local scaleFactor = 1 + (dromeLevel * 0.5) - local scaledBuff = math.floor(100 * scaleFactor) - - condition:setParameter(CONDITION_PARAM_TICKS, -1) - condition:setParameter(CONDITION_PARAM_BUFF_DAMAGEDEALT, scaledBuff) - creature:addCondition(condition) + local dromeLevel = getDromeLevel(creature) + local condition = Condition(CONDITION_ATTRIBUTES) + local scaleFactor = 1 + (dromeLevel * 0.5) + local scaledBuff = math.floor(100 * scaleFactor) + + condition:setParameter(CONDITION_PARAM_TICKS, -1) + condition:setParameter(CONDITION_PARAM_BUFF_DAMAGEDEALT, scaledBuff) + creature:addCondition(condition) end mType.onAppear = function(creature) - applyDamageScalingCondition(creature) + applyDamageScalingCondition(creature) - local creatureName = creature:getName() + local creatureName = creature:getName() - local dromeLevel = getDromeLevel(creature) + local dromeLevel = getDromeLevel(creature) end mType:register(monster) diff --git a/data-otservbr-global/scripts/systems/drome_mechanics.lua b/data-otservbr-global/scripts/systems/drome_mechanics.lua index 2683641f7be..c87626f59a5 100644 --- a/data-otservbr-global/scripts/systems/drome_mechanics.lua +++ b/data-otservbr-global/scripts/systems/drome_mechanics.lua @@ -1,808 +1,808 @@ --- by arah -local generalArea = { - from = Position(32246, 32177, 12), - to = Position(32264, 32195, 12) -} - -local mechanics = {} -local restrictedPosition = Position(32259, 32178, 12) -local activeMechanics = {} - -function activateRandomMechanics() - for _, mechanic in pairs(activeMechanics) do - if type(mechanic) == "function" then - mechanic("stop") - end - end - - activeMechanics = {} - - local keys = {} - for key in pairs(mechanics) do - table.insert(keys, key) - end - - if #keys < 2 then - return - end - - math.randomseed(os.time()) - - while true do - local firstIndex = math.random(#keys) - local firstKey = keys[firstIndex] - table.remove(keys, firstIndex) - - if #keys == 0 then - return - end - - local secondKey = keys[math.random(#keys)] - - if not ((firstKey == "startFearMechanics" and secondKey == "startBadRootsMechanics") or - (firstKey == "startBadRootsMechanics" and secondKey == "startFearMechanics") or - (firstKey == "startLavaEvent" and (secondKey == "startBeamMeUpEvent" or secondKey == "startTankedUpEvent")) or - (firstKey == "startBeamMeUpEvent" and (secondKey == "startLavaEvent" or secondKey == "startTankedUpEvent")) or - (firstKey == "startTankedUpEvent" and (secondKey == "startLavaEvent" or secondKey == "startBeamMeUpEvent"))) then - - table.insert(activeMechanics, mechanics[firstKey]) - table.insert(activeMechanics, mechanics[secondKey]) - - if type(mechanics[firstKey]) == "function" then - mechanics[firstKey]("start") - end - if type(mechanics[secondKey]) == "function" then - mechanics[secondKey]("start") - end - - print("Activated mechanics:", firstKey, secondKey) - break - end - - table.insert(keys, firstKey) - end -end - -function teleportSingleMonsterToPlayer(action) - if action == "stop" then - return - end - - local monstersInArea = {} - for x = generalArea.from.x, generalArea.to.x do - for y = generalArea.from.y, generalArea.to.y do - local position = Position(x, y, generalArea.from.z) - local tile = Tile(position) - if tile then - for _, creature in pairs(tile:getCreatures()) do - if creature:isMonster() then - table.insert(monstersInArea, creature) - end - end - end - end - end - - if #monstersInArea > 0 then - if math.random(100) <= 15 then - local randomMonster = monstersInArea[math.random(#monstersInArea)] - local playersInArea = {} - local centerPosition = Position(generalArea.from.x + (rangeX / 2), generalArea.from.y + (rangeY / 2), generalArea.from.z) - local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) - - for _, spectator in ipairs(spectators) do - if spectator:getPosition():isInRange(generalArea.from, generalArea.to) then - table.insert(playersInArea, spectator) - end - end - - if #playersInArea > 0 then - local randomPlayer = playersInArea[math.random(#playersInArea)] - local playerPosition = randomPlayer:getPosition() - randomMonster:teleportTo(playerPosition) - playerPosition:sendMagicEffect(CONST_ME_TELEPORT) - end - end - end - addEvent(teleportSingleMonsterToPlayer, 2000) -end - -lavaFields = {} -function startLavaEvent(action) - if action == "stop" then - lavaFields = {} - return - end - - addEvent(function() - placeLavaFields() - startLavaEvent() - end, 15000) -end - -function placeLavaFields() - if not arePlayersInArea() then - return - end - - lavaFields = {} - for _ = 1, 100 do - local position = getRandomWalkablePosition() - if position and position ~= restrictedPosition then - local item = Game.createItem(36927, 1, position) - table.insert(lavaFields, { position = position, created_time = os.time() }) - addEvent(function() item:remove() end, 5000) - end - end - - addEvent(applyLavaDamage, 3000) -end - -function applyLavaDamage() - local current_time = os.time() - local centerPosition = Position(generalArea.from.x + (rangeX / 2), generalArea.from.y + (rangeY / 2), generalArea.from.z) - local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) - - for _, spectator in ipairs(spectators) do - local player_position = spectator:getPosition() - for _, field in ipairs(lavaFields) do - if player_position == field.position then - if current_time - field.created_time >= 3 then - local damage = spectator:getHealth() * 0.6 - spectator:addHealth(-damage) - end - end - end - end -end - -beamFields = {} -function startBeamMeUpEvent(action) - if action == "stop" then - beamFields = {} - return - end - - addEvent(function() - if not arePlayersInArea() then - return - end - - placeBeamFields() - startBeamMeUpEvent() - end, 15000) -end - -function placeBeamFields() - beamFields = {} - for _ = 1, 100 do - local position = getRandomWalkablePosition() - if position and position ~= restrictedPosition then - local item = Game.createItem(36925, 1, position) - table.insert(beamFields, { position = position, created_time = os.time() }) - addEvent(function() item:remove() end, 5000) - end - end - addEvent(teleportPlayers, 1000) -end - -function teleportPlayers() - local centerPosition = Position(generalArea.from.x + (rangeX / 2), generalArea.from.y + (rangeY / 2), generalArea.from.z) - local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) - - for _, spectator in ipairs(spectators) do - local playerPosition = spectator:getPosition() - for _, field in ipairs(beamFields) do - if playerPosition == field.position then - local randomPos = getRandomWalkablePosition() - spectator:teleportTo(randomPos) - break - end - end - end -end - -tankedUpFields = {} -function startTankedUpEvent(action) - if action == "stop" then - tankedUpFields = {} - return - end - - addEvent(function() - placeTankedUpFields() - startTankedUpEvent() - end, 15000) -end - -function placeTankedUpFields() - if not arePlayersInArea() then - return - end - - tankedUpFields = {} - for _ = 1, 100 do - local position = getRandomWalkablePosition() - if position and position ~= restrictedPosition then - local item = Game.createItem(36926, 1, position) - table.insert(tankedUpFields, { position = position, created_time = os.time() }) - addEvent(function() item:remove() end, 5000) - end - end - addEvent(applySuperdrunkEffect, 1000) -end - -function applySuperdrunkEffect() - local centerPosition = Position(generalArea.from.x + (rangeX / 2), generalArea.from.y + (rangeY / 2), generalArea.from.z) - local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) - - for _, spectator in ipairs(spectators) do - local playerPosition = spectator:getPosition() - for _, field in ipairs(tankedUpFields) do - if playerPosition == field.position then - local drunk = Condition(CONDITION_DRUNK) - drunk:setParameter(CONDITION_PARAM_TICKS, 40000) - spectator:addCondition(drunk) - break - end - end - end -end - -function getRandomWalkablePosition() - local minX, minY, maxX, maxY = generalArea.from.x, generalArea.from.y, generalArea.to.x, generalArea.to.y - local position - for _ = 1, 10 do - local x = math.random(minX, maxX) - local y = math.random(minY, maxY) - position = Position(x, y, generalArea.from.z) - if isWalkable(position) then - return position - end - end - return nil -end - -function isWalkable(position) - if position == restrictedPosition then - return false - end - - local tile = Tile(position) - return tile and tile:isWalkable() -end - -function arePlayersInArea() - local centerPosition = Position(generalArea.from.x + (rangeX / 2), generalArea.from.y + (rangeY / 2), generalArea.from.z) - local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) - - for _, spectator in ipairs(spectators) do - if isInSpecArea(spectator:getPosition()) then - return true - end - end - return false -end - -function isInSpecArea(position) - return position.x >= generalArea.from.x and position.x <= generalArea.to.x and - position.y >= generalArea.from.y and position.y <= generalArea.to.y and - position.z == generalArea.from.z -end --- end beam - --- Targeted Exploding Corpses Mechanic -local activeMechanic = nil -targetedExplodingCorpses = {} -function startTargetedExplodingCorpsesMechanic(action) - if action == "stop" then - activeMechanic = nil - targetedExplodingCorpses = {} - return - elseif action == "start" then - activeMechanic = "targeted" - end -end - -local TARGETED_DAMAGE_PERCENTAGE = 0.40 -local targetedSpellArea = { - from = Position(32246, 32177, 12), - to = Position(32264, 32195, 12) -} - -local targetedValidMonsters = { - ["Domestikion"] = true, - ["Hoodinion"] = true, - ["Mearidion"] = true, - ["Murmillion"] = true, - ["Scissorion"] = true -} - -local targetSpellPattern = { - { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 1, 1, 1, 0, 0, 0 }, - { 0, 0, 1, 1, 1, 1, 1, 0, 0 }, - { 0, 0, 1, 1, 2, 1, 1, 0, 0 }, - { 0, 0, 1, 1, 1, 1, 1, 0, 0 }, - { 0, 0, 0, 1, 1, 1, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, -} - -local function findNearestTargetedPlayer(monsterPosition) - local playersInArea = Game.getSpectators(monsterPosition, false, true, 8, 8, 8, 8) - local nearestPlayer = nil - local shortestDistance = math.huge - - for _, player in ipairs(playersInArea) do - if player:isPlayer() then - local distance = monsterPosition:getDistance(player:getPosition()) - if distance < shortestDistance then - nearestPlayer = player - shortestDistance = distance - end - end - end - - return nearestPlayer -end - -local function isInTargetedSpellArea(position) - return position.x >= targetedSpellArea.from.x and position.x <= targetedSpellArea.to.x and - position.y >= targetedSpellArea.from.y and position.y <= targetedSpellArea.to.y and - position.z == targetedSpellArea.from.z -end - -local function getTargetedAffectedPositions(centerPosition) - local affectedPositions = {} - local offset = math.floor(#targetSpellPattern / 2) - for y = 1, #targetSpellPattern do - for x = 1, #targetSpellPattern[y] do - if targetSpellPattern[y][x] > 0 then - local pos = Position(centerPosition.x + (x - offset - 1), centerPosition.y + (y - offset - 1), centerPosition.z) - table.insert(affectedPositions, pos) - end - end - end - return affectedPositions -end - -local function handleTargetedSpell(monster) - local monsterPosition = monster:getPosition() - - if not isInTargetedSpellArea(monsterPosition) then - return - end - - local targetPlayer = findNearestTargetedPlayer(monsterPosition) - if not targetPlayer then - return - end - - local targetPosition = targetPlayer:getPosition() - for _, pos in ipairs(getTargetedAffectedPositions(targetPosition)) do - pos:sendMagicEffect(CONST_ME_FIREAREA) - end - - for _, pos in ipairs(getTargetedAffectedPositions(targetPosition)) do - local playersInTile = Tile(pos):getCreatures() - if playersInTile then - for _, player in ipairs(playersInTile) do - if player:isPlayer() then - local currentHealth = player:getHealth() - local damage = math.floor(currentHealth * TARGETED_DAMAGE_PERCENTAGE) - player:addHealth(-damage) - end - end - end - end -end - -local targetedExplodingCorpses = CreatureEvent("TargetedExplodingCorpses") - -function targetedExplodingCorpses.onDeath(creature, corpse, killer, mostDamageKiller, unjustified) - if activeMechanic ~= "targeted" then - return false - end - if creature and creature:isMonster() and targetedValidMonsters[creature:getName()] then - handleTargetedSpell(creature) - end - return true -end - -targetedExplodingCorpses:register() - - ------------------------------------separate mechanic ---explodingCorpses -explodingCorpses = {} -function startExplodingCorpsesMechanic(action) - if action == "stop" then - activeMechanic = nil - explodingCorpses = {} - return - elseif action == "start" then - activeMechanic = "exploding" - end -end - -local DAMAGE_PERCENTAGE = 0.25 -local explosionArea = { - from = Position(32246, 32177, 12), - to = Position(32264, 32195, 12) -} - -local validMonsters = { - ["Domestikion"] = true, - ["Hoodinion"] = true, - ["Mearidion"] = true, - ["Murmillion"] = true, - ["Scissorion"] = true -} - -local explosionPattern = { - { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 1, 1, 1, 0, 0, 0 }, - { 0, 0, 1, 1, 1, 1, 1, 0, 0 }, - { 0, 0, 1, 1, 2, 1, 1, 0, 0 }, - { 0, 0, 1, 1, 1, 1, 1, 0, 0 }, - { 0, 0, 0, 1, 1, 1, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, -} - -local function isInExplosionArea(position) - return position.x >= explosionArea.from.x and position.x <= explosionArea.to.x and - position.y >= explosionArea.from.y and position.y <= explosionArea.to.y and - position.z == explosionArea.from.z -end - -local function getAffectedPositions(centerPosition) - local affectedPositions = {} - local offset = math.floor(#explosionPattern / 2) - for y = 1, #explosionPattern do - for x = 1, #explosionPattern[y] do - if explosionPattern[y][x] > 0 then - local pos = Position(centerPosition.x + (x - offset - 1), centerPosition.y + (y - offset - 1), centerPosition.z) - table.insert(affectedPositions, pos) - end - end - end - return affectedPositions -end - -local function showExplosionEffect(centerPosition) - for _, pos in ipairs(getAffectedPositions(centerPosition)) do - if isInExplosionArea(pos) then - pos:sendMagicEffect(CONST_ME_FIREAREA) - end - end -end - -local function handleExplosion(monster) - local monsterPosition = monster:getPosition() - - if not isInExplosionArea(monsterPosition) then - return - end - - showExplosionEffect(monsterPosition) - - local affectedPositions = getAffectedPositions(monsterPosition) - for _, pos in ipairs(affectedPositions) do - local playersInTile = Tile(pos):getCreatures() - if playersInTile then - for _, player in ipairs(playersInTile) do - if player:isPlayer() then - local currentHealth = player:getHealth() - local damage = math.floor(currentHealth * DAMAGE_PERCENTAGE) - player:addHealth(-damage) - end - end - end - end -end - -local explodingCorpses = CreatureEvent("ExplodingCorpses") - -function explodingCorpses.onDeath(creature, corpse, killer, mostDamageKiller, unjustified) - if activeMechanic ~= "exploding" then - return false - end - if creature and creature:isMonster() and validMonsters[creature:getName()] then - handleExplosion(creature) - end - return true -end - -explodingCorpses:register() - -------------------------------------- fear mechanic -------------------------- -fearMechanic = {} -function startFearMechanics(action) - if action == "stop" then - fearMechanic = {} - return - elseif action == "start" then - createFearAfterPlayerDetection() - end -end - -local function isTileWalkable(position) - - if position == restrictedPosition then - return false - end - - local tile = Tile(position) - if not tile then - return false - end - return not tile:hasFlag(TILESTATE_BLOCKSOLID) and not tile:hasFlag(TILESTATE_PROTECTIONZONE) -end - -local function getRandomWalkablePosition(from, to) - local attempts = 100 - while attempts > 0 do - local randomX = math.random(from.x, to.x) - local randomY = math.random(from.y, to.y) - local position = Position(randomX, randomY, from.z) - if isWalkable(position) and position ~= restrictedPosition then - return position - end - attempts = attempts - 1 - end - return nil -end - -local function isPlayerInArea(from, to) - for x = from.x, to.x do - for y = from.y, to.y do - local tile = Tile(Position(x, y, from.z)) - if tile then - local creatures = tile:getCreatures() - for _, creature in ipairs(creatures) do - if creature:isPlayer() then - return true - end - end - end - end - end - return false -end - -local fear = {36934, 36935} -local playersSteppedOnItem = {} - -local fearSeedStepIn = MoveEvent("fearSeedStepIn") -function fearSeedStepIn.onStepIn(creature, item, position, fromPosition) - if item:getId() == 36934 or item:getId() == 36935 then - if creature and creature:isPlayer() then - item:remove() - playersSteppedOnItem[creature:getId()] = true - end - end - return true -end - -fearSeedStepIn:type("stepin") -fearSeedStepIn:id(36934, 36935) -fearSeedStepIn:register() - -local function applyFearConditionToPlayers(from, to) - for x = from.x, to.x do - for y = from.y, to.y do - local tile = Tile(Position(x, y, from.z)) - if tile then - local creatures = tile:getCreatures() - for _, creature in ipairs(creatures) do - if creature:isPlayer() then - if not playersSteppedOnItem[creature:getId()] then - local player = creature - local condition = Condition(CONDITION_FEARED) - condition:setTicks(3000) - player:addCondition(condition) - player:getPosition():sendMagicEffect(CONST_ME_GHOST_SMOKE) - end - end - end - end - end - end -end - -function createFearAfterPlayerDetection() - local spawnAreaFrom = Position(32246, 32177, 12) - local spawnAreaTo = Position(32264, 32195, 12) - - if isPlayerInArea(spawnAreaFrom, spawnAreaTo) then - addEvent(function() - local position = getRandomWalkablePosition(spawnAreaFrom, spawnAreaTo) - if position then - local item = Game.createItem(36934, 1, position) - if item then - addEvent(function() - local tile = Tile(position) - if tile then - local seedItem = tile:getItemById(36934) - if seedItem then - seedItem:remove() - Game.createItem(36935, 1, position) - addEvent(function() - local tile = Tile(position) - if tile then - local transformedItem = tile:getItemById(36935) - if transformedItem then - transformedItem:remove() - end - - applyFearConditionToPlayers(spawnAreaFrom, spawnAreaTo) - end - end, 6000) - end - end - end, 3000) - end - end - end, 20000) - end - - addEvent(function() - playersSteppedOnItem = {} - end, 20000) - - addEvent(createFearAfterPlayerDetection, 20000) -end - ---------------------------------------- bad roots ------------------- - -rootMechanic = {} -function startBadRootsMechanics(action) - if action == "stop" then - rootMechanic = {} - return - elseif action == "start" then - createBadRootAfterPlayerDetection() - end -end - -local function isTileWalkable(position) - if position == restrictedPosition then - return false - end - - local tile = Tile(position) - if not tile then - return false - end - return not tile:hasFlag(TILESTATE_BLOCKSOLID) and not tile:hasFlag(TILESTATE_PROTECTIONZONE) -end - -local function getRandomWalkablePosition(from, to) - local attempts = 100 - while attempts > 0 do - local randomX = math.random(from.x, to.x) - local randomY = math.random(from.y, to.y) - local position = Position(randomX, randomY, from.z) - if position ~= restrictedPosition and isTileWalkable(position) then - return position - end - attempts = attempts - 1 - end - return nil -end - -local function isPlayerInArea(from, to) - for x = from.x, to.x do - for y = from.y, to.y do - local tile = Tile(Position(x, y, from.z)) - if tile then - local creatures = tile:getCreatures() - for _, creature in ipairs(creatures) do - if creature:isPlayer() then - return true - end - end - end - end - end - return false -end - -local eggs = {36936, 36937} -local playersSteppedOnItem = {} - -local eggsSeedStepIn = MoveEvent("eggsSeedStepIn") -function eggsSeedStepIn.onStepIn(creature, item, position, fromPosition) - if item:getId() == 36936 or item:getId() == 36937 then - if creature and creature:isPlayer() then - item:remove() - playersSteppedOnItem[creature:getId()] = true - end - end - return true -end - -eggsSeedStepIn:type("stepin") -eggsSeedStepIn:id(36936, 36937) -eggsSeedStepIn:register() - -local function applyRootedConditionToPlayers(from, to) - for x = from.x, to.x do - for y = from.y, to.y do - local tile = Tile(Position(x, y, from.z)) - if tile then - local creatures = tile:getCreatures() - for _, creature in ipairs(creatures) do - if creature:isPlayer() then - if not playersSteppedOnItem[creature:getId()] then - local player = creature - local condition = Condition(CONDITION_ROOTED) - condition:setTicks(5000) - player:addCondition(condition) - player:getPosition():sendMagicEffect(CONST_ME_ROOTS) - end - end - end - end - end - end -end - -function createBadRootAfterPlayerDetection() - local spawnAreaFrom = Position(32246, 32177, 12) - local spawnAreaTo = Position(32264, 32195, 12) - - if isPlayerInArea(spawnAreaFrom, spawnAreaTo) then - addEvent(function() - local position = getRandomWalkablePosition(spawnAreaFrom, spawnAreaTo) - if position then - local item = Game.createItem(36936, 1, position) - if item then - addEvent(function() - local tile = Tile(position) - if tile then - local seedItem = tile:getItemById(36936) - if seedItem then - seedItem:remove() - Game.createItem(36937, 1, position) - addEvent(function() - local tile = Tile(position) - if tile then - local transformedItem = tile:getItemById(36937) - if transformedItem then - transformedItem:remove() - end - - applyRootedConditionToPlayers(spawnAreaFrom, spawnAreaTo) - end - end, 6000) - end - end - end, 3000) - end - end - end, 20000) - end - - addEvent(function() - playersSteppedOnItem = {} - end, 20000) - - addEvent(createBadRootAfterPlayerDetection, 20000) -end - - -mechanics.startFearMechanics = startFearMechanics -mechanics.startBadRootsMechanics = startBadRootsMechanics -mechanics.startExplodingCorpsesMechanic = startExplodingCorpsesMechanic -mechanics.startTargetedExplodingCorpsesMechanic = startTargetedExplodingCorpsesMechanic -mechanics.startLavaEvent = startLavaEvent -mechanics.startBeamMeUpEvent = startBeamMeUpEvent -mechanics.startTankedUpEvent = startTankedUpEvent -mechanics.teleportSingleMonsterToPlayer = teleportSingleMonsterToPlayer - --- by Arah \ No newline at end of file +-- by arah +local generalArea = { + from = Position(32246, 32177, 12), + to = Position(32264, 32195, 12), +} + +local mechanics = {} +local restrictedPosition = Position(32259, 32178, 12) +local activeMechanics = {} + +function activateRandomMechanics() + for _, mechanic in pairs(activeMechanics) do + if type(mechanic) == "function" then + mechanic("stop") + end + end + + activeMechanics = {} + + local keys = {} + for key in pairs(mechanics) do + table.insert(keys, key) + end + + if #keys < 2 then + return + end + + math.randomseed(os.time()) + + while true do + local firstIndex = math.random(#keys) + local firstKey = keys[firstIndex] + table.remove(keys, firstIndex) + + if #keys == 0 then + return + end + + local secondKey = keys[math.random(#keys)] + + if + not ( + (firstKey == "startFearMechanics" and secondKey == "startBadRootsMechanics") + or (firstKey == "startBadRootsMechanics" and secondKey == "startFearMechanics") + or (firstKey == "startLavaEvent" and (secondKey == "startBeamMeUpEvent" or secondKey == "startTankedUpEvent")) + or (firstKey == "startBeamMeUpEvent" and (secondKey == "startLavaEvent" or secondKey == "startTankedUpEvent")) + or (firstKey == "startTankedUpEvent" and (secondKey == "startLavaEvent" or secondKey == "startBeamMeUpEvent")) + ) + then + table.insert(activeMechanics, mechanics[firstKey]) + table.insert(activeMechanics, mechanics[secondKey]) + + if type(mechanics[firstKey]) == "function" then + mechanics[firstKey]("start") + end + if type(mechanics[secondKey]) == "function" then + mechanics[secondKey]("start") + end + + print("Activated mechanics:", firstKey, secondKey) + break + end + + table.insert(keys, firstKey) + end +end + +function teleportSingleMonsterToPlayer(action) + if action == "stop" then + return + end + + local monstersInArea = {} + for x = generalArea.from.x, generalArea.to.x do + for y = generalArea.from.y, generalArea.to.y do + local position = Position(x, y, generalArea.from.z) + local tile = Tile(position) + if tile then + for _, creature in pairs(tile:getCreatures()) do + if creature:isMonster() then + table.insert(monstersInArea, creature) + end + end + end + end + end + + if #monstersInArea > 0 then + if math.random(100) <= 15 then + local randomMonster = monstersInArea[math.random(#monstersInArea)] + local playersInArea = {} + local centerPosition = Position(generalArea.from.x + (rangeX / 2), generalArea.from.y + (rangeY / 2), generalArea.from.z) + local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) + + for _, spectator in ipairs(spectators) do + if spectator:getPosition():isInRange(generalArea.from, generalArea.to) then + table.insert(playersInArea, spectator) + end + end + + if #playersInArea > 0 then + local randomPlayer = playersInArea[math.random(#playersInArea)] + local playerPosition = randomPlayer:getPosition() + randomMonster:teleportTo(playerPosition) + playerPosition:sendMagicEffect(CONST_ME_TELEPORT) + end + end + end + addEvent(teleportSingleMonsterToPlayer, 2000) +end + +lavaFields = {} +function startLavaEvent(action) + if action == "stop" then + lavaFields = {} + return + end + + addEvent(function() + placeLavaFields() + startLavaEvent() + end, 15000) +end + +function placeLavaFields() + if not arePlayersInArea() then + return + end + + lavaFields = {} + for _ = 1, 100 do + local position = getRandomWalkablePosition() + if position and position ~= restrictedPosition then + local item = Game.createItem(36927, 1, position) + table.insert(lavaFields, { position = position, created_time = os.time() }) + addEvent(function() + item:remove() + end, 5000) + end + end + + addEvent(applyLavaDamage, 3000) +end + +function applyLavaDamage() + local current_time = os.time() + local centerPosition = Position(generalArea.from.x + (rangeX / 2), generalArea.from.y + (rangeY / 2), generalArea.from.z) + local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) + + for _, spectator in ipairs(spectators) do + local player_position = spectator:getPosition() + for _, field in ipairs(lavaFields) do + if player_position == field.position then + if current_time - field.created_time >= 3 then + local damage = spectator:getHealth() * 0.6 + spectator:addHealth(-damage) + end + end + end + end +end + +beamFields = {} +function startBeamMeUpEvent(action) + if action == "stop" then + beamFields = {} + return + end + + addEvent(function() + if not arePlayersInArea() then + return + end + + placeBeamFields() + startBeamMeUpEvent() + end, 15000) +end + +function placeBeamFields() + beamFields = {} + for _ = 1, 100 do + local position = getRandomWalkablePosition() + if position and position ~= restrictedPosition then + local item = Game.createItem(36925, 1, position) + table.insert(beamFields, { position = position, created_time = os.time() }) + addEvent(function() + item:remove() + end, 5000) + end + end + addEvent(teleportPlayers, 1000) +end + +function teleportPlayers() + local centerPosition = Position(generalArea.from.x + (rangeX / 2), generalArea.from.y + (rangeY / 2), generalArea.from.z) + local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) + + for _, spectator in ipairs(spectators) do + local playerPosition = spectator:getPosition() + for _, field in ipairs(beamFields) do + if playerPosition == field.position then + local randomPos = getRandomWalkablePosition() + spectator:teleportTo(randomPos) + break + end + end + end +end + +tankedUpFields = {} +function startTankedUpEvent(action) + if action == "stop" then + tankedUpFields = {} + return + end + + addEvent(function() + placeTankedUpFields() + startTankedUpEvent() + end, 15000) +end + +function placeTankedUpFields() + if not arePlayersInArea() then + return + end + + tankedUpFields = {} + for _ = 1, 100 do + local position = getRandomWalkablePosition() + if position and position ~= restrictedPosition then + local item = Game.createItem(36926, 1, position) + table.insert(tankedUpFields, { position = position, created_time = os.time() }) + addEvent(function() + item:remove() + end, 5000) + end + end + addEvent(applySuperdrunkEffect, 1000) +end + +function applySuperdrunkEffect() + local centerPosition = Position(generalArea.from.x + (rangeX / 2), generalArea.from.y + (rangeY / 2), generalArea.from.z) + local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) + + for _, spectator in ipairs(spectators) do + local playerPosition = spectator:getPosition() + for _, field in ipairs(tankedUpFields) do + if playerPosition == field.position then + local drunk = Condition(CONDITION_DRUNK) + drunk:setParameter(CONDITION_PARAM_TICKS, 40000) + spectator:addCondition(drunk) + break + end + end + end +end + +function getRandomWalkablePosition() + local minX, minY, maxX, maxY = generalArea.from.x, generalArea.from.y, generalArea.to.x, generalArea.to.y + local position + for _ = 1, 10 do + local x = math.random(minX, maxX) + local y = math.random(minY, maxY) + position = Position(x, y, generalArea.from.z) + if isWalkable(position) then + return position + end + end + return nil +end + +function isWalkable(position) + if position == restrictedPosition then + return false + end + + local tile = Tile(position) + return tile and tile:isWalkable() +end + +function arePlayersInArea() + local centerPosition = Position(generalArea.from.x + (rangeX / 2), generalArea.from.y + (rangeY / 2), generalArea.from.z) + local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) + + for _, spectator in ipairs(spectators) do + if isInSpecArea(spectator:getPosition()) then + return true + end + end + return false +end + +function isInSpecArea(position) + return position.x >= generalArea.from.x and position.x <= generalArea.to.x and position.y >= generalArea.from.y and position.y <= generalArea.to.y and position.z == generalArea.from.z +end +-- end beam + +-- Targeted Exploding Corpses Mechanic +local activeMechanic = nil +targetedExplodingCorpses = {} +function startTargetedExplodingCorpsesMechanic(action) + if action == "stop" then + activeMechanic = nil + targetedExplodingCorpses = {} + return + elseif action == "start" then + activeMechanic = "targeted" + end +end + +local TARGETED_DAMAGE_PERCENTAGE = 0.40 +local targetedSpellArea = { + from = Position(32246, 32177, 12), + to = Position(32264, 32195, 12), +} + +local targetedValidMonsters = { + ["Domestikion"] = true, + ["Hoodinion"] = true, + ["Mearidion"] = true, + ["Murmillion"] = true, + ["Scissorion"] = true, +} + +local targetSpellPattern = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 1, 1, 1, 1, 1, 0, 0 }, + { 0, 0, 1, 1, 2, 1, 1, 0, 0 }, + { 0, 0, 1, 1, 1, 1, 1, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, +} + +local function findNearestTargetedPlayer(monsterPosition) + local playersInArea = Game.getSpectators(monsterPosition, false, true, 8, 8, 8, 8) + local nearestPlayer = nil + local shortestDistance = math.huge + + for _, player in ipairs(playersInArea) do + if player:isPlayer() then + local distance = monsterPosition:getDistance(player:getPosition()) + if distance < shortestDistance then + nearestPlayer = player + shortestDistance = distance + end + end + end + + return nearestPlayer +end + +local function isInTargetedSpellArea(position) + return position.x >= targetedSpellArea.from.x and position.x <= targetedSpellArea.to.x and position.y >= targetedSpellArea.from.y and position.y <= targetedSpellArea.to.y and position.z == targetedSpellArea.from.z +end + +local function getTargetedAffectedPositions(centerPosition) + local affectedPositions = {} + local offset = math.floor(#targetSpellPattern / 2) + for y = 1, #targetSpellPattern do + for x = 1, #targetSpellPattern[y] do + if targetSpellPattern[y][x] > 0 then + local pos = Position(centerPosition.x + (x - offset - 1), centerPosition.y + (y - offset - 1), centerPosition.z) + table.insert(affectedPositions, pos) + end + end + end + return affectedPositions +end + +local function handleTargetedSpell(monster) + local monsterPosition = monster:getPosition() + + if not isInTargetedSpellArea(monsterPosition) then + return + end + + local targetPlayer = findNearestTargetedPlayer(monsterPosition) + if not targetPlayer then + return + end + + local targetPosition = targetPlayer:getPosition() + for _, pos in ipairs(getTargetedAffectedPositions(targetPosition)) do + pos:sendMagicEffect(CONST_ME_FIREAREA) + end + + for _, pos in ipairs(getTargetedAffectedPositions(targetPosition)) do + local playersInTile = Tile(pos):getCreatures() + if playersInTile then + for _, player in ipairs(playersInTile) do + if player:isPlayer() then + local currentHealth = player:getHealth() + local damage = math.floor(currentHealth * TARGETED_DAMAGE_PERCENTAGE) + player:addHealth(-damage) + end + end + end + end +end + +local targetedExplodingCorpses = CreatureEvent("TargetedExplodingCorpses") + +function targetedExplodingCorpses.onDeath(creature, corpse, killer, mostDamageKiller, unjustified) + if activeMechanic ~= "targeted" then + return false + end + if creature and creature:isMonster() and targetedValidMonsters[creature:getName()] then + handleTargetedSpell(creature) + end + return true +end + +targetedExplodingCorpses:register() + +-----------------------------------separate mechanic +--explodingCorpses +explodingCorpses = {} +function startExplodingCorpsesMechanic(action) + if action == "stop" then + activeMechanic = nil + explodingCorpses = {} + return + elseif action == "start" then + activeMechanic = "exploding" + end +end + +local DAMAGE_PERCENTAGE = 0.25 +local explosionArea = { + from = Position(32246, 32177, 12), + to = Position(32264, 32195, 12), +} + +local validMonsters = { + ["Domestikion"] = true, + ["Hoodinion"] = true, + ["Mearidion"] = true, + ["Murmillion"] = true, + ["Scissorion"] = true, +} + +local explosionPattern = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 1, 1, 1, 1, 1, 0, 0 }, + { 0, 0, 1, 1, 2, 1, 1, 0, 0 }, + { 0, 0, 1, 1, 1, 1, 1, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, +} + +local function isInExplosionArea(position) + return position.x >= explosionArea.from.x and position.x <= explosionArea.to.x and position.y >= explosionArea.from.y and position.y <= explosionArea.to.y and position.z == explosionArea.from.z +end + +local function getAffectedPositions(centerPosition) + local affectedPositions = {} + local offset = math.floor(#explosionPattern / 2) + for y = 1, #explosionPattern do + for x = 1, #explosionPattern[y] do + if explosionPattern[y][x] > 0 then + local pos = Position(centerPosition.x + (x - offset - 1), centerPosition.y + (y - offset - 1), centerPosition.z) + table.insert(affectedPositions, pos) + end + end + end + return affectedPositions +end + +local function showExplosionEffect(centerPosition) + for _, pos in ipairs(getAffectedPositions(centerPosition)) do + if isInExplosionArea(pos) then + pos:sendMagicEffect(CONST_ME_FIREAREA) + end + end +end + +local function handleExplosion(monster) + local monsterPosition = monster:getPosition() + + if not isInExplosionArea(monsterPosition) then + return + end + + showExplosionEffect(monsterPosition) + + local affectedPositions = getAffectedPositions(monsterPosition) + for _, pos in ipairs(affectedPositions) do + local playersInTile = Tile(pos):getCreatures() + if playersInTile then + for _, player in ipairs(playersInTile) do + if player:isPlayer() then + local currentHealth = player:getHealth() + local damage = math.floor(currentHealth * DAMAGE_PERCENTAGE) + player:addHealth(-damage) + end + end + end + end +end + +local explodingCorpses = CreatureEvent("ExplodingCorpses") + +function explodingCorpses.onDeath(creature, corpse, killer, mostDamageKiller, unjustified) + if activeMechanic ~= "exploding" then + return false + end + if creature and creature:isMonster() and validMonsters[creature:getName()] then + handleExplosion(creature) + end + return true +end + +explodingCorpses:register() + +------------------------------------- fear mechanic -------------------------- +fearMechanic = {} +function startFearMechanics(action) + if action == "stop" then + fearMechanic = {} + return + elseif action == "start" then + createFearAfterPlayerDetection() + end +end + +local function isTileWalkable(position) + if position == restrictedPosition then + return false + end + + local tile = Tile(position) + if not tile then + return false + end + return not tile:hasFlag(TILESTATE_BLOCKSOLID) and not tile:hasFlag(TILESTATE_PROTECTIONZONE) +end + +local function getRandomWalkablePosition(from, to) + local attempts = 100 + while attempts > 0 do + local randomX = math.random(from.x, to.x) + local randomY = math.random(from.y, to.y) + local position = Position(randomX, randomY, from.z) + if isWalkable(position) and position ~= restrictedPosition then + return position + end + attempts = attempts - 1 + end + return nil +end + +local function isPlayerInArea(from, to) + for x = from.x, to.x do + for y = from.y, to.y do + local tile = Tile(Position(x, y, from.z)) + if tile then + local creatures = tile:getCreatures() + for _, creature in ipairs(creatures) do + if creature:isPlayer() then + return true + end + end + end + end + end + return false +end + +local fear = { 36934, 36935 } +local playersSteppedOnItem = {} + +local fearSeedStepIn = MoveEvent("fearSeedStepIn") +function fearSeedStepIn.onStepIn(creature, item, position, fromPosition) + if item:getId() == 36934 or item:getId() == 36935 then + if creature and creature:isPlayer() then + item:remove() + playersSteppedOnItem[creature:getId()] = true + end + end + return true +end + +fearSeedStepIn:type("stepin") +fearSeedStepIn:id(36934, 36935) +fearSeedStepIn:register() + +local function applyFearConditionToPlayers(from, to) + for x = from.x, to.x do + for y = from.y, to.y do + local tile = Tile(Position(x, y, from.z)) + if tile then + local creatures = tile:getCreatures() + for _, creature in ipairs(creatures) do + if creature:isPlayer() then + if not playersSteppedOnItem[creature:getId()] then + local player = creature + local condition = Condition(CONDITION_FEARED) + condition:setTicks(3000) + player:addCondition(condition) + player:getPosition():sendMagicEffect(CONST_ME_GHOST_SMOKE) + end + end + end + end + end + end +end + +function createFearAfterPlayerDetection() + local spawnAreaFrom = Position(32246, 32177, 12) + local spawnAreaTo = Position(32264, 32195, 12) + + if isPlayerInArea(spawnAreaFrom, spawnAreaTo) then + addEvent(function() + local position = getRandomWalkablePosition(spawnAreaFrom, spawnAreaTo) + if position then + local item = Game.createItem(36934, 1, position) + if item then + addEvent(function() + local tile = Tile(position) + if tile then + local seedItem = tile:getItemById(36934) + if seedItem then + seedItem:remove() + Game.createItem(36935, 1, position) + addEvent(function() + local tile = Tile(position) + if tile then + local transformedItem = tile:getItemById(36935) + if transformedItem then + transformedItem:remove() + end + + applyFearConditionToPlayers(spawnAreaFrom, spawnAreaTo) + end + end, 6000) + end + end + end, 3000) + end + end + end, 20000) + end + + addEvent(function() + playersSteppedOnItem = {} + end, 20000) + + addEvent(createFearAfterPlayerDetection, 20000) +end + +--------------------------------------- bad roots ------------------- + +rootMechanic = {} +function startBadRootsMechanics(action) + if action == "stop" then + rootMechanic = {} + return + elseif action == "start" then + createBadRootAfterPlayerDetection() + end +end + +local function isTileWalkable(position) + if position == restrictedPosition then + return false + end + + local tile = Tile(position) + if not tile then + return false + end + return not tile:hasFlag(TILESTATE_BLOCKSOLID) and not tile:hasFlag(TILESTATE_PROTECTIONZONE) +end + +local function getRandomWalkablePosition(from, to) + local attempts = 100 + while attempts > 0 do + local randomX = math.random(from.x, to.x) + local randomY = math.random(from.y, to.y) + local position = Position(randomX, randomY, from.z) + if position ~= restrictedPosition and isTileWalkable(position) then + return position + end + attempts = attempts - 1 + end + return nil +end + +local function isPlayerInArea(from, to) + for x = from.x, to.x do + for y = from.y, to.y do + local tile = Tile(Position(x, y, from.z)) + if tile then + local creatures = tile:getCreatures() + for _, creature in ipairs(creatures) do + if creature:isPlayer() then + return true + end + end + end + end + end + return false +end + +local eggs = { 36936, 36937 } +local playersSteppedOnItem = {} + +local eggsSeedStepIn = MoveEvent("eggsSeedStepIn") +function eggsSeedStepIn.onStepIn(creature, item, position, fromPosition) + if item:getId() == 36936 or item:getId() == 36937 then + if creature and creature:isPlayer() then + item:remove() + playersSteppedOnItem[creature:getId()] = true + end + end + return true +end + +eggsSeedStepIn:type("stepin") +eggsSeedStepIn:id(36936, 36937) +eggsSeedStepIn:register() + +local function applyRootedConditionToPlayers(from, to) + for x = from.x, to.x do + for y = from.y, to.y do + local tile = Tile(Position(x, y, from.z)) + if tile then + local creatures = tile:getCreatures() + for _, creature in ipairs(creatures) do + if creature:isPlayer() then + if not playersSteppedOnItem[creature:getId()] then + local player = creature + local condition = Condition(CONDITION_ROOTED) + condition:setTicks(5000) + player:addCondition(condition) + player:getPosition():sendMagicEffect(CONST_ME_ROOTS) + end + end + end + end + end + end +end + +function createBadRootAfterPlayerDetection() + local spawnAreaFrom = Position(32246, 32177, 12) + local spawnAreaTo = Position(32264, 32195, 12) + + if isPlayerInArea(spawnAreaFrom, spawnAreaTo) then + addEvent(function() + local position = getRandomWalkablePosition(spawnAreaFrom, spawnAreaTo) + if position then + local item = Game.createItem(36936, 1, position) + if item then + addEvent(function() + local tile = Tile(position) + if tile then + local seedItem = tile:getItemById(36936) + if seedItem then + seedItem:remove() + Game.createItem(36937, 1, position) + addEvent(function() + local tile = Tile(position) + if tile then + local transformedItem = tile:getItemById(36937) + if transformedItem then + transformedItem:remove() + end + + applyRootedConditionToPlayers(spawnAreaFrom, spawnAreaTo) + end + end, 6000) + end + end + end, 3000) + end + end + end, 20000) + end + + addEvent(function() + playersSteppedOnItem = {} + end, 20000) + + addEvent(createBadRootAfterPlayerDetection, 20000) +end + +mechanics.startFearMechanics = startFearMechanics +mechanics.startBadRootsMechanics = startBadRootsMechanics +mechanics.startExplodingCorpsesMechanic = startExplodingCorpsesMechanic +mechanics.startTargetedExplodingCorpsesMechanic = startTargetedExplodingCorpsesMechanic +mechanics.startLavaEvent = startLavaEvent +mechanics.startBeamMeUpEvent = startBeamMeUpEvent +mechanics.startTankedUpEvent = startTankedUpEvent +mechanics.teleportSingleMonsterToPlayer = teleportSingleMonsterToPlayer + +-- by Arah diff --git a/data-otservbr-global/scripts/systems/tibia_drome.lua b/data-otservbr-global/scripts/systems/tibia_drome.lua index af52378e17a..0e8b16c7afc 100644 --- a/data-otservbr-global/scripts/systems/tibia_drome.lua +++ b/data-otservbr-global/scripts/systems/tibia_drome.lua @@ -1,569 +1,577 @@ -db.query([[ - CREATE TABLE IF NOT EXISTS drome_highscores ( - id INT UNSIGNED NOT NULL AUTO_INCREMENT, - player_id INT NOT NULL, - player_name VARCHAR(255) NOT NULL, - highscore INT NOT NULL DEFAULT 0, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (id), - UNIQUE KEY (player_id), - FOREIGN KEY (player_id) REFERENCES players (id) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8; -]]) - -db.query([[ -CREATE TABLE IF NOT EXISTS drome_reset ( - id INT UNSIGNED NOT NULL AUTO_INCREMENT, - last_reset TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (id) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; -]]) - -db.query([[ -CREATE TABLE IF NOT EXISTS drome_offline_rewards ( - player_id INT NOT NULL, - rewards TEXT NOT NULL, - PRIMARY KEY (player_id) -); -]]) - -local leverAction = Action() -local deathEvent = CreatureEvent("DromeMonsterDeath") -local configDrome = { - requiredLevel = 50, - playerPositions = { - { pos = Position(32253, 32199, 12), teleport = Position(32254, 32193, 12), effect = CONST_ME_MAGIC_BLUE }, - { pos = Position(32253, 32200, 12), teleport = Position(32254, 32193, 12), effect = CONST_ME_MAGIC_BLUE }, - { pos = Position(32253, 32201, 12), teleport = Position(32254, 32193, 12), effect = CONST_ME_MAGIC_BLUE }, - { pos = Position(32253, 32202, 12), teleport = Position(32254, 32193, 12), effect = CONST_ME_MAGIC_BLUE }, - { pos = Position(32253, 32203, 12), teleport = Position(32254, 32193, 12), effect = CONST_ME_MAGIC_BLUE }, - }, - specPos = { - from = Position(32246, 32177, 12), - to = Position(32264, 32195, 12), - }, - exit = Position(32259, 32178, 12), - timeToFightAgain = 14400, -} - -local lastFightTime = 0 -local dromeLevel = 0 -local monstersKilled = 0 -local playerWaveData = {} - -local creaturePool = { - "Domestikion", - "Hoodinion", - "Mearidion", - "Murmillion", - "Scissorion" -} -rangeX = math.abs(configDrome.specPos.to.x - configDrome.specPos.from.x) -rangeY = math.abs(configDrome.specPos.to.y - configDrome.specPos.from.y) - - -function deathEvent.onDeath(creature, corpse, killer, mostDamageKiller) - if creature:isMonster() then - monstersKilled = monstersKilled + 1 - checkWaveCompletion(killer) - end - return true -end - -deathEvent:register() - -function calculateMonsters(playerCount) - local monstersPerPlayer = { - [1] = 4, - [2] = 8, - [3] = 11, - [4] = 14, - [5] = 17 - } - return monstersPerPlayer[playerCount] or 4 -end - -function spawnMonsters(playerCount) - local monsterCount = calculateMonsters(playerCount) - local baseHP = 1000 - local scaledHP = baseHP * (1.039 ^ dromeLevel) - - local spawnedCount = 0 - - while spawnedCount < monsterCount do - local spawnPos = Position(math.random(32246, 32264), math.random(32177, 32195), 12) - local tile = Tile(spawnPos) - if tile and tile:getGround() and not tile:hasProperty(CONST_PROP_BLOCKSOLID) then - local creatureName = creaturePool[math.random(#creaturePool)] - local monster = Game.createMonster(creatureName, spawnPos) - if monster then - monster:setMaxHealth(scaledHP) - monster:setHealth(scaledHP) - monster:registerEvent("DromeMonsterDeath") - spawnPos:sendMagicEffect(CONST_ME_MAGIC_BLUE) - spawnedCount = spawnedCount + 1 - end - end - end -end - -local waveTimer = nil -local waveTimeLimit = 120 - -function startWaveTimer() - if waveTimer then - stopEvent(waveTimer) - end - - waveTimer = addEvent(function() - local playerCount = countPlayersInArea() - - if playerCount > 0 then - local centerPosition = Position(configDrome.specPos.from.x, configDrome.specPos.from.y, configDrome.specPos.from.z) - local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) - - if #spectators > 0 then - for _, spectator in pairs(spectators) do - spectator:teleportTo(Position(32255, 32205, 11)) - spectator:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You failed to complete the wave in time! You have been kicked out.") - end - end - end - end, waveTimeLimit * 1000) -end - -function checkWaveCompletion(player) - local playerCount = countPlayersInArea() - local requiredMonsters = calculateMonsters(playerCount) - - if monstersKilled >= requiredMonsters then - if waveTimer then - stopEvent(waveTimer) - end - - local centerPosition = Position(configDrome.specPos.from.x, configDrome.specPos.from.y, configDrome.specPos.from.z) - local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) - - -- Check if there are any players in the area - if #spectators > 0 then - for _, targetPlayer in pairs(spectators) do - local playerId = targetPlayer:getGuid() - local playerName = targetPlayer:getName() - local playerHighscore = playerWaveData[playerId] or 0 - - playerWaveData[playerId] = playerHighscore + 1 - - db.query(string.format([[ - INSERT INTO drome_highscores (player_id, player_name, highscore) - VALUES (%d, %s, %d) - ON DUPLICATE KEY UPDATE - highscore = GREATEST(highscore, %d), - updated_at = NOW(); - ]], playerId, db.escapeString(playerName), playerWaveData[playerId], playerWaveData[playerId])) - end - end - - dromeLevel = dromeLevel + 1 - monstersKilled = 0 - - local message = "Next wave will start in 8 seconds. Current level: " .. dromeLevel - for _, targetPlayer in pairs(spectators) do - targetPlayer:say(message, TALKTYPE_YELL) - end - - addEvent(function() - local playerCount = countPlayersInArea() - if playerCount > 0 then - spawnMonsters(playerCount) - end - end, 8000) - - storeWaveInItemAtPosition() - storeLeaderboardInItemAtPosition() - startWaveTimer() - else - end -end - -local playerCooldowns = {} - -function leverAction.onUse(player, item, fromPosition, target, isHotkey) - if item:getActionId() == 46985 then - local playerId = player:getGuid() - local lastFightTime = playerCooldowns[playerId] or 0 - local currentTime = os.time() - - if currentTime - lastFightTime < configDrome.timeToFightAgain then - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You must wait before entering the Tibiadrome again.") - return true - end - - local isInValidPosition = false - for _, posData in ipairs(configDrome.playerPositions) do - if player:getPosition() == posData.pos then - isInValidPosition = true - break - end - end - - if not isInValidPosition then - -- player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You must be in a valid position to use the lever.") - return true - end - - local isAnyoneInSpecPos = false - local centerPosition = Position(configDrome.specPos.from.x, configDrome.specPos.from.y, configDrome.specPos.from.z) - local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) - - for _, targetPlayer in pairs(spectators) do - if targetPlayer:getPosition():isInRange(configDrome.specPos.from, configDrome.specPos.to) then - isAnyoneInSpecPos = true - break - end - end - - if isAnyoneInSpecPos then - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Someone is already inside the Tibiadrome. Please wait until they leave.") - return true - end - - playerCooldowns[playerId] = currentTime - - local isAnyPlayerInPosition = false - local playersToTeleport = {} - - for _, posData in ipairs(configDrome.playerPositions) do - local centerPosition = Position(posData.pos.x, posData.pos.y, posData.pos.z) - local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) - - for _, targetPlayer in pairs(spectators) do - if targetPlayer:getPosition():isInRange(posData.pos, posData.pos) then - table.insert(playersToTeleport, targetPlayer) - isAnyPlayerInPosition = true - break - end - end - end - - if not isAnyPlayerInPosition then - return true - end - - for _, targetPlayer in ipairs(playersToTeleport) do - local playerId = targetPlayer:getGuid() - local query = string.format("SELECT highscore FROM drome_highscores WHERE player_id = %d", playerId) - local resultId = db.storeQuery(query) - - local playerHighscore = 0 - if resultId then - playerHighscore = result.getNumber(resultId, "highscore") or 0 - result.free(resultId) - end - - local startingWave = math.max(playerHighscore - 5, 0) - playerWaveData[playerId] = startingWave - end - - for _, targetPlayer in ipairs(playersToTeleport) do - local entryPos = configDrome.playerPositions[math.random(#configDrome.playerPositions)].teleport - targetPlayer:teleportTo(entryPos) - targetPlayer:getPosition():sendMagicEffect(configDrome.playerPositions[math.random(#configDrome.playerPositions)].effect) - end - - if waveTimer then - stopEvent(waveTimer) - end - - dromeLevel = 0 - spawnMonsters(countPlayersInArea()) - waveTimer = nil - startWaveTimer() - - return true - end - return false -end - - -function countPlayersInArea() - local playerCount = 0 - local centerPosition = Position(configDrome.specPos.from.x, configDrome.specPos.from.y, configDrome.specPos.from.z) - local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) - - for _, targetPlayer in pairs(spectators) do - playerCount = playerCount + 1 - end - - return playerCount -end - -function removeMonstersFromArea() - for x = configDrome.specPos.from.x, configDrome.specPos.to.x do - for y = configDrome.specPos.from.y, configDrome.specPos.to.y do - local position = Position(x, y, 12) - local tile = Tile(position) - - if tile then - for _, creature in pairs(tile:getCreatures()) do - if creature:isMonster() then - local monsterName = creature:getName() - local validMonsters = { - "Domestikion", "Hoodinion", "Mearidion", "Murmillion", "Scissorion" - } - - for _, validMonster in ipairs(validMonsters) do - if monsterName == validMonster then - creature:remove() - break - end - end - end - end - end - end - end -end - -function checkForMonstersRemoval() - if countPlayersInArea() == 0 then - removeMonstersFromArea() - if waveTimer then - stopEvent(waveTimer) - end - waveTimer = nil - monstersKilled = 0 - dromeLevel = 0 - end - addEvent(checkForMonstersRemoval, 1000) -end - -checkForMonstersRemoval() - -function storeLeaderboardInItemAtPosition() - local itemPositions = { - Position(32255, 32203, 11), - Position(33378, 31820, 9), - Position(33154, 32972, 8), - Position(33537, 31820, 9), - Position(32706, 31820, 12) - } - - for _, itemPosition in ipairs(itemPositions) do - local tile = Tile(itemPosition) - - if not tile then - return - end - - local item = tile:getItemById(36828) - if not item then - return - end - - local query = "SELECT player_name, highscore FROM drome_highscores ORDER BY highscore DESC LIMIT 10" - local resultId = db.storeQuery(query) - - if not resultId then - item:setAttribute(ITEM_ATTRIBUTE_TEXT, "The current drome leaderboard:\nNo records found.") - return - end - - local leaderboardText = "The current drome leaderboard:\n" - local rank = 1 - - repeat - local playerName = result.getString(resultId, "player_name") - local highscore = result.getNumber(resultId, "highscore") - leaderboardText = leaderboardText .. rank .. ". " .. playerName .. ": " .. highscore .. "\n" - rank = rank + 1 - until not result.next(resultId) - - result.free(resultId) - - item:setAttribute(ITEM_ATTRIBUTE_TEXT, leaderboardText) - end -end - -function storeWaveInItemAtPosition() - local itemPosition = Position(32251, 32196, 11) - local tile = Tile(itemPosition) - - if not tile then - return - end - - local item = tile:getItemById(36870) - if not item then - return - end - - local waveInfoText = "Current Wave: " .. dromeLevel - item:setAttribute(ITEM_ATTRIBUTE_TEXT, waveInfoText) -end - -leverAction:aid(46985) -leverAction:register() - -function resetHighScores() - local query = "SELECT last_reset FROM drome_reset WHERE id = 1" - local resultId = db.storeQuery(query) - - local lastReset = 0 - if resultId then - local lastResetString = result.getString(resultId, "last_reset") or "" - result.free(resultId) - - if lastResetString ~= "" then - lastReset = os.time{ - year = tonumber(lastResetString:sub(1, 4)), - month = tonumber(lastResetString:sub(6, 7)), - day = tonumber(lastResetString:sub(9, 10)), - hour = tonumber(lastResetString:sub(12, 13)), - min = tonumber(lastResetString:sub(15, 16)), - sec = tonumber(lastResetString:sub(18, 19)) - } - end - end - - local currentTime = os.time() - local resetInterval = 5 * 24 * 60 * 60 - - if currentTime - lastReset >= resetInterval then - local rewards = { - {itemId = 36725, count = 1}, -- stamina - {itemId = 36727, count = 1}, -- wealth duplex - {itemId = 36742, count = 1}, -- physical amplification - {itemId = 36735, count = 1}, -- physical resilience - {itemId = 36737, count = 1}, -- ice amp - {itemId = 36730, count = 1}, -- ice res - {itemId = 36740, count = 1}, -- holy amp - {itemId = 36733, count = 1}, -- holy res - {itemId = 36736, count = 1}, -- fire amp - {itemId = 36729, count = 1}, -- fire res - {itemId = 36739, count = 1}, -- energy amp - {itemId = 36732, count = 1}, -- energy res - {itemId = 36738, count = 1}, -- earth amp - {itemId = 36731, count = 1}, -- earth res - {itemId = 36741, count = 1}, -- death amp - {itemId = 36734, count = 1} -- death res - } - - local topPlayersQuery = "SELECT player_id, player_name FROM drome_highscores ORDER BY highscore DESC LIMIT 10" - local topPlayersResult = db.storeQuery(topPlayersQuery) - - if topPlayersResult then - repeat - local playerId = result.getNumber(topPlayersResult, "player_id") - local playerName = result.getString(topPlayersResult, "player_name") - local player = Player(playerId) - - if player then - local inbox = player:getStoreInbox() - if inbox then - local randomReward = rewards[math.random(1, #rewards)] - inbox:addItem(randomReward.itemId, randomReward.count) - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Congratulations, " .. playerName .. "! You have received a reward for being in the top 10 of the Drome leaderboard!") - end - else - local storedRewards = "" - local rewardCount = 0 - local randomReward = rewards[math.random(1, #rewards)] - storedRewards = storedRewards .. randomReward.itemId .. ":" .. randomReward.count .. "," - db.query("INSERT INTO drome_offline_rewards (player_id, rewards) VALUES (" .. playerId .. ", '" .. storedRewards .. "') ON DUPLICATE KEY UPDATE rewards = '" .. storedRewards .. "'") - end - until not result.next(topPlayersResult) - result.free(topPlayersResult) - end - - db.query([[TRUNCATE TABLE drome_highscores]]) - db.query([[UPDATE drome_reset SET last_reset = NOW() WHERE id = 1]]) - - for _, player in pairs(Game.getPlayers()) do - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The Drome high scores have been reset, and the top players have received their rewards!") - end - - local itemPosition = Position(32255, 32203, 11) - local tile = Tile(itemPosition) - if tile then - local item = tile:getItemById(36828) - if item then - item:setAttribute(ITEM_ATTRIBUTE_TEXT, "The current drome leaderboard:\nAll records have been cleared.") - end - end - activateRandomMechanics() - end - addEvent(resetHighScores, resetInterval * 1000) -end - -local offlinereward = CreatureEvent('RewardDrome') -function offlinereward.onLogin(player) - if not player then - return true - end - - local playerId = player:getGuid() - local query = "SELECT rewards FROM drome_offline_rewards WHERE player_id = " .. playerId .. " LIMIT 1" - local resultId, err = db.storeQuery(query) - if not resultId then - return true - end - - local rewardsString = result.getString(resultId, "rewards") - result.free(resultId) - - if rewardsString and rewardsString ~= "" then - local rewards = {} - for reward in rewardsString:gmatch("([^,]+)") do - local itemId, count = reward:match("(%d+):(%d+)") - if itemId and count then - table.insert(rewards, {itemId = tonumber(itemId), count = tonumber(count)}) - end - end - - local inbox = player:getStoreInbox() - if inbox then - for _, reward in ipairs(rewards) do - if not inbox:addItem(reward.itemId, reward.count) then - else - end - end - - db.query("DELETE FROM drome_offline_rewards WHERE player_id = " .. playerId) - else - end - else - end - - return true -end - -offlinereward:register() - --- save it to retrieve data for monsters and future development -function getDromeLevel(player) - local playerId = player:getId() - local dromeLevel = dromeLevel + 1 - local query = string.format("SELECT highscore FROM drome_highscores WHERE player_id = %d", playerId) - local resultId = db.storeQuery(query) - if resultId and result:getID() ~= -1 then - dromeLevel = result:getNumber("highscore") - end - - return dromeLevel -end - -function onServerStart() - local query = "SELECT COUNT(*) AS count FROM drome_reset" - local resultId = db.storeQuery(query) - - if resultId and result.getNumber(resultId, "count") == 0 then - db.query([[INSERT INTO drome_reset (last_reset) VALUES (NOW())]]) - end - - result.free(resultId) - storeLeaderboardInItemAtPosition() - addEvent(resetHighScores, 1000) - activateRandomMechanics() -end - -addEvent(onServerStart, 1) +db.query([[ + CREATE TABLE IF NOT EXISTS drome_highscores ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT, + player_id INT NOT NULL, + player_name VARCHAR(255) NOT NULL, + highscore INT NOT NULL DEFAULT 0, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (id), + UNIQUE KEY (player_id), + FOREIGN KEY (player_id) REFERENCES players (id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +]]) + +db.query([[ +CREATE TABLE IF NOT EXISTS drome_reset ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT, + last_reset TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +]]) + +db.query([[ +CREATE TABLE IF NOT EXISTS drome_offline_rewards ( + player_id INT NOT NULL, + rewards TEXT NOT NULL, + PRIMARY KEY (player_id) +); +]]) + +local leverAction = Action() +local deathEvent = CreatureEvent("DromeMonsterDeath") +local configDrome = { + requiredLevel = 50, + playerPositions = { + { pos = Position(32253, 32199, 12), teleport = Position(32254, 32193, 12), effect = CONST_ME_MAGIC_BLUE }, + { pos = Position(32253, 32200, 12), teleport = Position(32254, 32193, 12), effect = CONST_ME_MAGIC_BLUE }, + { pos = Position(32253, 32201, 12), teleport = Position(32254, 32193, 12), effect = CONST_ME_MAGIC_BLUE }, + { pos = Position(32253, 32202, 12), teleport = Position(32254, 32193, 12), effect = CONST_ME_MAGIC_BLUE }, + { pos = Position(32253, 32203, 12), teleport = Position(32254, 32193, 12), effect = CONST_ME_MAGIC_BLUE }, + }, + specPos = { + from = Position(32246, 32177, 12), + to = Position(32264, 32195, 12), + }, + exit = Position(32259, 32178, 12), + timeToFightAgain = 14400, +} + +local lastFightTime = 0 +local dromeLevel = 0 +local monstersKilled = 0 +local playerWaveData = {} + +local creaturePool = { + "Domestikion", + "Hoodinion", + "Mearidion", + "Murmillion", + "Scissorion", +} +rangeX = math.abs(configDrome.specPos.to.x - configDrome.specPos.from.x) +rangeY = math.abs(configDrome.specPos.to.y - configDrome.specPos.from.y) + +function deathEvent.onDeath(creature, corpse, killer, mostDamageKiller) + if creature:isMonster() then + monstersKilled = monstersKilled + 1 + checkWaveCompletion(killer) + end + return true +end + +deathEvent:register() + +function calculateMonsters(playerCount) + local monstersPerPlayer = { + [1] = 4, + [2] = 8, + [3] = 11, + [4] = 14, + [5] = 17, + } + return monstersPerPlayer[playerCount] or 4 +end + +function spawnMonsters(playerCount) + local monsterCount = calculateMonsters(playerCount) + local baseHP = 1000 + local scaledHP = baseHP * (1.039 ^ dromeLevel) + + local spawnedCount = 0 + + while spawnedCount < monsterCount do + local spawnPos = Position(math.random(32246, 32264), math.random(32177, 32195), 12) + local tile = Tile(spawnPos) + if tile and tile:getGround() and not tile:hasProperty(CONST_PROP_BLOCKSOLID) then + local creatureName = creaturePool[math.random(#creaturePool)] + local monster = Game.createMonster(creatureName, spawnPos) + if monster then + monster:setMaxHealth(scaledHP) + monster:setHealth(scaledHP) + monster:registerEvent("DromeMonsterDeath") + spawnPos:sendMagicEffect(CONST_ME_MAGIC_BLUE) + spawnedCount = spawnedCount + 1 + end + end + end +end + +local waveTimer = nil +local waveTimeLimit = 120 + +function startWaveTimer() + if waveTimer then + stopEvent(waveTimer) + end + + waveTimer = addEvent(function() + local playerCount = countPlayersInArea() + + if playerCount > 0 then + local centerPosition = Position(configDrome.specPos.from.x, configDrome.specPos.from.y, configDrome.specPos.from.z) + local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) + + if #spectators > 0 then + for _, spectator in pairs(spectators) do + spectator:teleportTo(Position(32255, 32205, 11)) + spectator:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You failed to complete the wave in time! You have been kicked out.") + end + end + end + end, waveTimeLimit * 1000) +end + +function checkWaveCompletion(player) + local playerCount = countPlayersInArea() + local requiredMonsters = calculateMonsters(playerCount) + + if monstersKilled >= requiredMonsters then + if waveTimer then + stopEvent(waveTimer) + end + + local centerPosition = Position(configDrome.specPos.from.x, configDrome.specPos.from.y, configDrome.specPos.from.z) + local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) + + -- Check if there are any players in the area + if #spectators > 0 then + for _, targetPlayer in pairs(spectators) do + local playerId = targetPlayer:getGuid() + local playerName = targetPlayer:getName() + local playerHighscore = playerWaveData[playerId] or 0 + + playerWaveData[playerId] = playerHighscore + 1 + + db.query(string.format( + [[ + INSERT INTO drome_highscores (player_id, player_name, highscore) + VALUES (%d, %s, %d) + ON DUPLICATE KEY UPDATE + highscore = GREATEST(highscore, %d), + updated_at = NOW(); + ]], + playerId, + db.escapeString(playerName), + playerWaveData[playerId], + playerWaveData[playerId] + )) + end + end + + dromeLevel = dromeLevel + 1 + monstersKilled = 0 + + local message = "Next wave will start in 8 seconds. Current level: " .. dromeLevel + for _, targetPlayer in pairs(spectators) do + targetPlayer:say(message, TALKTYPE_YELL) + end + + addEvent(function() + local playerCount = countPlayersInArea() + if playerCount > 0 then + spawnMonsters(playerCount) + end + end, 8000) + + storeWaveInItemAtPosition() + storeLeaderboardInItemAtPosition() + startWaveTimer() + else + end +end + +local playerCooldowns = {} + +function leverAction.onUse(player, item, fromPosition, target, isHotkey) + if item:getActionId() == 46985 then + local playerId = player:getGuid() + local lastFightTime = playerCooldowns[playerId] or 0 + local currentTime = os.time() + + if currentTime - lastFightTime < configDrome.timeToFightAgain then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You must wait before entering the Tibiadrome again.") + return true + end + + local isInValidPosition = false + for _, posData in ipairs(configDrome.playerPositions) do + if player:getPosition() == posData.pos then + isInValidPosition = true + break + end + end + + if not isInValidPosition then + -- player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You must be in a valid position to use the lever.") + return true + end + + local isAnyoneInSpecPos = false + local centerPosition = Position(configDrome.specPos.from.x, configDrome.specPos.from.y, configDrome.specPos.from.z) + local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) + + for _, targetPlayer in pairs(spectators) do + if targetPlayer:getPosition():isInRange(configDrome.specPos.from, configDrome.specPos.to) then + isAnyoneInSpecPos = true + break + end + end + + if isAnyoneInSpecPos then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Someone is already inside the Tibiadrome. Please wait until they leave.") + return true + end + + playerCooldowns[playerId] = currentTime + + local isAnyPlayerInPosition = false + local playersToTeleport = {} + + for _, posData in ipairs(configDrome.playerPositions) do + local centerPosition = Position(posData.pos.x, posData.pos.y, posData.pos.z) + local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) + + for _, targetPlayer in pairs(spectators) do + if targetPlayer:getPosition():isInRange(posData.pos, posData.pos) then + table.insert(playersToTeleport, targetPlayer) + isAnyPlayerInPosition = true + break + end + end + end + + if not isAnyPlayerInPosition then + return true + end + + for _, targetPlayer in ipairs(playersToTeleport) do + local playerId = targetPlayer:getGuid() + local query = string.format("SELECT highscore FROM drome_highscores WHERE player_id = %d", playerId) + local resultId = db.storeQuery(query) + + local playerHighscore = 0 + if resultId then + playerHighscore = result.getNumber(resultId, "highscore") or 0 + result.free(resultId) + end + + local startingWave = math.max(playerHighscore - 5, 0) + playerWaveData[playerId] = startingWave + end + + for _, targetPlayer in ipairs(playersToTeleport) do + local entryPos = configDrome.playerPositions[math.random(#configDrome.playerPositions)].teleport + targetPlayer:teleportTo(entryPos) + targetPlayer:getPosition():sendMagicEffect(configDrome.playerPositions[math.random(#configDrome.playerPositions)].effect) + end + + if waveTimer then + stopEvent(waveTimer) + end + + dromeLevel = 0 + spawnMonsters(countPlayersInArea()) + waveTimer = nil + startWaveTimer() + + return true + end + return false +end + +function countPlayersInArea() + local playerCount = 0 + local centerPosition = Position(configDrome.specPos.from.x, configDrome.specPos.from.y, configDrome.specPos.from.z) + local spectators = Game.getSpectators(centerPosition, false, true, rangeX, rangeX, rangeY, rangeY) + + for _, targetPlayer in pairs(spectators) do + playerCount = playerCount + 1 + end + + return playerCount +end + +function removeMonstersFromArea() + for x = configDrome.specPos.from.x, configDrome.specPos.to.x do + for y = configDrome.specPos.from.y, configDrome.specPos.to.y do + local position = Position(x, y, 12) + local tile = Tile(position) + + if tile then + for _, creature in pairs(tile:getCreatures()) do + if creature:isMonster() then + local monsterName = creature:getName() + local validMonsters = { + "Domestikion", + "Hoodinion", + "Mearidion", + "Murmillion", + "Scissorion", + } + + for _, validMonster in ipairs(validMonsters) do + if monsterName == validMonster then + creature:remove() + break + end + end + end + end + end + end + end +end + +function checkForMonstersRemoval() + if countPlayersInArea() == 0 then + removeMonstersFromArea() + if waveTimer then + stopEvent(waveTimer) + end + waveTimer = nil + monstersKilled = 0 + dromeLevel = 0 + end + addEvent(checkForMonstersRemoval, 1000) +end + +checkForMonstersRemoval() + +function storeLeaderboardInItemAtPosition() + local itemPositions = { + Position(32255, 32203, 11), + Position(33378, 31820, 9), + Position(33154, 32972, 8), + Position(33537, 31820, 9), + Position(32706, 31820, 12), + } + + for _, itemPosition in ipairs(itemPositions) do + local tile = Tile(itemPosition) + + if not tile then + return + end + + local item = tile:getItemById(36828) + if not item then + return + end + + local query = "SELECT player_name, highscore FROM drome_highscores ORDER BY highscore DESC LIMIT 10" + local resultId = db.storeQuery(query) + + if not resultId then + item:setAttribute(ITEM_ATTRIBUTE_TEXT, "The current drome leaderboard:\nNo records found.") + return + end + + local leaderboardText = "The current drome leaderboard:\n" + local rank = 1 + + repeat + local playerName = result.getString(resultId, "player_name") + local highscore = result.getNumber(resultId, "highscore") + leaderboardText = leaderboardText .. rank .. ". " .. playerName .. ": " .. highscore .. "\n" + rank = rank + 1 + until not result.next(resultId) + + result.free(resultId) + + item:setAttribute(ITEM_ATTRIBUTE_TEXT, leaderboardText) + end +end + +function storeWaveInItemAtPosition() + local itemPosition = Position(32251, 32196, 11) + local tile = Tile(itemPosition) + + if not tile then + return + end + + local item = tile:getItemById(36870) + if not item then + return + end + + local waveInfoText = "Current Wave: " .. dromeLevel + item:setAttribute(ITEM_ATTRIBUTE_TEXT, waveInfoText) +end + +leverAction:aid(46985) +leverAction:register() + +function resetHighScores() + local query = "SELECT last_reset FROM drome_reset WHERE id = 1" + local resultId = db.storeQuery(query) + + local lastReset = 0 + if resultId then + local lastResetString = result.getString(resultId, "last_reset") or "" + result.free(resultId) + + if lastResetString ~= "" then + lastReset = os.time({ + year = tonumber(lastResetString:sub(1, 4)), + month = tonumber(lastResetString:sub(6, 7)), + day = tonumber(lastResetString:sub(9, 10)), + hour = tonumber(lastResetString:sub(12, 13)), + min = tonumber(lastResetString:sub(15, 16)), + sec = tonumber(lastResetString:sub(18, 19)), + }) + end + end + + local currentTime = os.time() + local resetInterval = 5 * 24 * 60 * 60 + + if currentTime - lastReset >= resetInterval then + local rewards = { + { itemId = 36725, count = 1 }, -- stamina + { itemId = 36727, count = 1 }, -- wealth duplex + { itemId = 36742, count = 1 }, -- physical amplification + { itemId = 36735, count = 1 }, -- physical resilience + { itemId = 36737, count = 1 }, -- ice amp + { itemId = 36730, count = 1 }, -- ice res + { itemId = 36740, count = 1 }, -- holy amp + { itemId = 36733, count = 1 }, -- holy res + { itemId = 36736, count = 1 }, -- fire amp + { itemId = 36729, count = 1 }, -- fire res + { itemId = 36739, count = 1 }, -- energy amp + { itemId = 36732, count = 1 }, -- energy res + { itemId = 36738, count = 1 }, -- earth amp + { itemId = 36731, count = 1 }, -- earth res + { itemId = 36741, count = 1 }, -- death amp + { itemId = 36734, count = 1 }, -- death res + } + + local topPlayersQuery = "SELECT player_id, player_name FROM drome_highscores ORDER BY highscore DESC LIMIT 10" + local topPlayersResult = db.storeQuery(topPlayersQuery) + + if topPlayersResult then + repeat + local playerId = result.getNumber(topPlayersResult, "player_id") + local playerName = result.getString(topPlayersResult, "player_name") + local player = Player(playerId) + + if player then + local inbox = player:getStoreInbox() + if inbox then + local randomReward = rewards[math.random(1, #rewards)] + inbox:addItem(randomReward.itemId, randomReward.count) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Congratulations, " .. playerName .. "! You have received a reward for being in the top 10 of the Drome leaderboard!") + end + else + local storedRewards = "" + local rewardCount = 0 + local randomReward = rewards[math.random(1, #rewards)] + storedRewards = storedRewards .. randomReward.itemId .. ":" .. randomReward.count .. "," + db.query("INSERT INTO drome_offline_rewards (player_id, rewards) VALUES (" .. playerId .. ", '" .. storedRewards .. "') ON DUPLICATE KEY UPDATE rewards = '" .. storedRewards .. "'") + end + until not result.next(topPlayersResult) + result.free(topPlayersResult) + end + + db.query([[TRUNCATE TABLE drome_highscores]]) + db.query([[UPDATE drome_reset SET last_reset = NOW() WHERE id = 1]]) + + for _, player in pairs(Game.getPlayers()) do + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The Drome high scores have been reset, and the top players have received their rewards!") + end + + local itemPosition = Position(32255, 32203, 11) + local tile = Tile(itemPosition) + if tile then + local item = tile:getItemById(36828) + if item then + item:setAttribute(ITEM_ATTRIBUTE_TEXT, "The current drome leaderboard:\nAll records have been cleared.") + end + end + activateRandomMechanics() + end + addEvent(resetHighScores, resetInterval * 1000) +end + +local offlinereward = CreatureEvent("RewardDrome") +function offlinereward.onLogin(player) + if not player then + return true + end + + local playerId = player:getGuid() + local query = "SELECT rewards FROM drome_offline_rewards WHERE player_id = " .. playerId .. " LIMIT 1" + local resultId, err = db.storeQuery(query) + if not resultId then + return true + end + + local rewardsString = result.getString(resultId, "rewards") + result.free(resultId) + + if rewardsString and rewardsString ~= "" then + local rewards = {} + for reward in rewardsString:gmatch("([^,]+)") do + local itemId, count = reward:match("(%d+):(%d+)") + if itemId and count then + table.insert(rewards, { itemId = tonumber(itemId), count = tonumber(count) }) + end + end + + local inbox = player:getStoreInbox() + if inbox then + for _, reward in ipairs(rewards) do + if not inbox:addItem(reward.itemId, reward.count) then + else + end + end + + db.query("DELETE FROM drome_offline_rewards WHERE player_id = " .. playerId) + else + end + else + end + + return true +end + +offlinereward:register() + +-- save it to retrieve data for monsters and future development +function getDromeLevel(player) + local playerId = player:getId() + local dromeLevel = dromeLevel + 1 + local query = string.format("SELECT highscore FROM drome_highscores WHERE player_id = %d", playerId) + local resultId = db.storeQuery(query) + if resultId and result:getID() ~= -1 then + dromeLevel = result:getNumber("highscore") + end + + return dromeLevel +end + +function onServerStart() + local query = "SELECT COUNT(*) AS count FROM drome_reset" + local resultId = db.storeQuery(query) + + if resultId and result.getNumber(resultId, "count") == 0 then + db.query([[INSERT INTO drome_reset (last_reset) VALUES (NOW())]]) + end + + result.free(resultId) + storeLeaderboardInItemAtPosition() + addEvent(resetHighScores, 1000) + activateRandomMechanics() +end + +addEvent(onServerStart, 1) From bb13ef991a955679f031c5c35a034d92ea06fbfd Mon Sep 17 00:00:00 2001 From: mrmdbeng Date: Sat, 8 Feb 2025 01:03:27 -0500 Subject: [PATCH 09/11] Apparances back to the stock version --- data/items/appearances.dat | Bin 4439304 -> 4439297 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/data/items/appearances.dat b/data/items/appearances.dat index 506f2504b7f1fbf015fd1b383c945f119102b18a..e26d139005db44977508022013fe2ae98a484a4e 100644 GIT binary patch delta 337 zcmXxZxlY1x9Kdn^fLPH=wH^qFwXH`L?+Z^*HjmZl<|FV3n*5SFIZ_`$2UlX!XTX?r z>CmSzVR3NsyEuHN&(rU7>Jbm3BqeFdNWWxdKr|VYoD9kEhi)h8EpQV3O5FvmM0eD; z-%fOlgzY5X{ZTK$ePmp#%m0ld!;hm;jKRb>CNPO96flhf- zhH2ITV#ZP}mAlnGgI>xr{f_Bj87rt_6>C_> j1~##UZR}tdd)P+}2ROtLj&Xuh)X~5h&clw`ym|ivx4({0 delta 344 zcmYMpyGp}Q0Dxgnq9#^jqw!K}jWLOrc&m5oz1mIeDuUoM^br&e#L1};a1#WPu0mYI zxgsQER-eGl!O4Gf_@?jX_fEORwG>j3s_c|qvRi6Wmpw8kdu88?;b!6~@-pL0Tt Date: Sat, 8 Feb 2025 02:03:55 -0500 Subject: [PATCH 10/11] Database Creation / Migration --- data-otservbr-global/migrations/50.lua | 32 +++++++++++++++++++ .../scripts/systems/tibia_drome.lua | 29 ----------------- schema.sql | 26 +++++++++++++++ 3 files changed, 58 insertions(+), 29 deletions(-) create mode 100644 data-otservbr-global/migrations/50.lua diff --git a/data-otservbr-global/migrations/50.lua b/data-otservbr-global/migrations/50.lua new file mode 100644 index 00000000000..7d78887a94c --- /dev/null +++ b/data-otservbr-global/migrations/50.lua @@ -0,0 +1,32 @@ +function onUpdateDatabase() + logger.info("Updating database to version 50 (Drome Highscores and Rewards)") + + db.query([[ + CREATE TABLE IF NOT EXISTS drome_highscores ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT, + player_id INT NOT NULL, + player_name VARCHAR(255) NOT NULL, + highscore INT NOT NULL DEFAULT 0, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (id), + UNIQUE KEY (player_id), + FOREIGN KEY (player_id) REFERENCES players (id) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + ]]) + + db.query([[ + CREATE TABLE IF NOT EXISTS drome_reset ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT, + last_reset TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + ]]) + + db.query([[ + CREATE TABLE IF NOT EXISTS drome_offline_rewards ( + player_id INT NOT NULL, + rewards TEXT NOT NULL, + PRIMARY KEY (player_id) + ); + ]]) +end diff --git a/data-otservbr-global/scripts/systems/tibia_drome.lua b/data-otservbr-global/scripts/systems/tibia_drome.lua index 0e8b16c7afc..d61f14b9c54 100644 --- a/data-otservbr-global/scripts/systems/tibia_drome.lua +++ b/data-otservbr-global/scripts/systems/tibia_drome.lua @@ -1,32 +1,3 @@ -db.query([[ - CREATE TABLE IF NOT EXISTS drome_highscores ( - id INT UNSIGNED NOT NULL AUTO_INCREMENT, - player_id INT NOT NULL, - player_name VARCHAR(255) NOT NULL, - highscore INT NOT NULL DEFAULT 0, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (id), - UNIQUE KEY (player_id), - FOREIGN KEY (player_id) REFERENCES players (id) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8; -]]) - -db.query([[ -CREATE TABLE IF NOT EXISTS drome_reset ( - id INT UNSIGNED NOT NULL AUTO_INCREMENT, - last_reset TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (id) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; -]]) - -db.query([[ -CREATE TABLE IF NOT EXISTS drome_offline_rewards ( - player_id INT NOT NULL, - rewards TEXT NOT NULL, - PRIMARY KEY (player_id) -); -]]) - local leverAction = Action() local deathEvent = CreatureEvent("DromeMonsterDeath") local configDrome = { diff --git a/schema.sql b/schema.sql index 10efc5dce00..00084d462c4 100644 --- a/schema.sql +++ b/schema.sql @@ -840,6 +840,32 @@ CREATE TABLE IF NOT EXISTS `kv_store` ( PRIMARY KEY (`key_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- Drome Highscores Table +CREATE TABLE IF NOT EXISTS drome_highscores ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT, + player_id INT NOT NULL, + player_name VARCHAR(255) NOT NULL, + highscore INT NOT NULL DEFAULT 0, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (id), + UNIQUE KEY (player_id), + FOREIGN KEY (player_id) REFERENCES players (id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- Drome Reset Table +CREATE TABLE IF NOT EXISTS drome_reset ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT, + last_reset TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- Drome Offline Rewards Table +CREATE TABLE IF NOT EXISTS drome_offline_rewards ( + player_id INT NOT NULL, + rewards TEXT NOT NULL, + PRIMARY KEY (player_id) +); + -- Create Account god/god INSERT INTO `accounts` (`id`, `name`, `email`, `password`, `type`) VALUES From bd1c80ae94dc68d371fa66263ab2b9a6dafa5d14 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sat, 8 Feb 2025 07:04:33 +0000 Subject: [PATCH 11/11] Lua code format - (Stylua) --- data-otservbr-global/migrations/50.lua | 64 +++++++++++++------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/data-otservbr-global/migrations/50.lua b/data-otservbr-global/migrations/50.lua index 7d78887a94c..8818df3cb3e 100644 --- a/data-otservbr-global/migrations/50.lua +++ b/data-otservbr-global/migrations/50.lua @@ -1,32 +1,32 @@ -function onUpdateDatabase() - logger.info("Updating database to version 50 (Drome Highscores and Rewards)") - - db.query([[ - CREATE TABLE IF NOT EXISTS drome_highscores ( - id INT UNSIGNED NOT NULL AUTO_INCREMENT, - player_id INT NOT NULL, - player_name VARCHAR(255) NOT NULL, - highscore INT NOT NULL DEFAULT 0, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (id), - UNIQUE KEY (player_id), - FOREIGN KEY (player_id) REFERENCES players (id) ON DELETE CASCADE - ) ENGINE=InnoDB DEFAULT CHARSET=utf8; - ]]) - - db.query([[ - CREATE TABLE IF NOT EXISTS drome_reset ( - id INT UNSIGNED NOT NULL AUTO_INCREMENT, - last_reset TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (id) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8; - ]]) - - db.query([[ - CREATE TABLE IF NOT EXISTS drome_offline_rewards ( - player_id INT NOT NULL, - rewards TEXT NOT NULL, - PRIMARY KEY (player_id) - ); - ]]) -end +function onUpdateDatabase() + logger.info("Updating database to version 50 (Drome Highscores and Rewards)") + + db.query([[ + CREATE TABLE IF NOT EXISTS drome_highscores ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT, + player_id INT NOT NULL, + player_name VARCHAR(255) NOT NULL, + highscore INT NOT NULL DEFAULT 0, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (id), + UNIQUE KEY (player_id), + FOREIGN KEY (player_id) REFERENCES players (id) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + ]]) + + db.query([[ + CREATE TABLE IF NOT EXISTS drome_reset ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT, + last_reset TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + ]]) + + db.query([[ + CREATE TABLE IF NOT EXISTS drome_offline_rewards ( + player_id INT NOT NULL, + rewards TEXT NOT NULL, + PRIMARY KEY (player_id) + ); + ]]) +end