mirror of
https://github.com/opentibiabr/canary
synced 2026-08-16 06:26:09 -04:00
Fixes #3855. When a player carries multiple exercise weapons of the same type, using the second (or any non-first) weapon on a training dummy was draining charges from the first weapon in the inventory instead of the one the player actually clicked. Bug Fixes • Improved training weapon tracking during exercise events to ensure the weapon remains valid and properly positioned throughout the training session. • Training will now correctly stop if the weapon is removed or moved during active training.
227 lines
7.8 KiB
Lua
227 lines
7.8 KiB
Lua
local exhaustionTime = 10
|
|
|
|
local exerciseWeaponsTable = {
|
|
-- MELE
|
|
[50292] = { skill = SKILL_FIST },
|
|
[50293] = { skill = SKILL_FIST },
|
|
[50294] = { skill = SKILL_FIST },
|
|
[50295] = { skill = SKILL_FIST },
|
|
[28540] = { skill = SKILL_SWORD },
|
|
[28552] = { skill = SKILL_SWORD },
|
|
[35279] = { skill = SKILL_SWORD },
|
|
[35285] = { skill = SKILL_SWORD },
|
|
[28553] = { skill = SKILL_AXE },
|
|
[28541] = { skill = SKILL_AXE },
|
|
[35280] = { skill = SKILL_AXE },
|
|
[35286] = { skill = SKILL_AXE },
|
|
[28554] = { skill = SKILL_CLUB },
|
|
[28542] = { skill = SKILL_CLUB },
|
|
[35281] = { skill = SKILL_CLUB },
|
|
[35287] = { skill = SKILL_CLUB },
|
|
[44064] = { skill = SKILL_SHIELD },
|
|
[44065] = { skill = SKILL_SHIELD },
|
|
[44066] = { skill = SKILL_SHIELD },
|
|
[44067] = { skill = SKILL_SHIELD },
|
|
-- ROD
|
|
[28544] = { skill = SKILL_MAGLEVEL, effect = CONST_ANI_SMALLICE, allowFarUse = true },
|
|
[28556] = { skill = SKILL_MAGLEVEL, effect = CONST_ANI_SMALLICE, allowFarUse = true },
|
|
[35283] = { skill = SKILL_MAGLEVEL, effect = CONST_ANI_SMALLICE, allowFarUse = true },
|
|
[35289] = { skill = SKILL_MAGLEVEL, effect = CONST_ANI_SMALLICE, allowFarUse = true },
|
|
-- RANGE
|
|
[28543] = { skill = SKILL_DISTANCE, effect = CONST_ANI_SIMPLEARROW, allowFarUse = true },
|
|
[28555] = { skill = SKILL_DISTANCE, effect = CONST_ANI_SIMPLEARROW, allowFarUse = true },
|
|
[35282] = { skill = SKILL_DISTANCE, effect = CONST_ANI_SIMPLEARROW, allowFarUse = true },
|
|
[35288] = { skill = SKILL_DISTANCE, effect = CONST_ANI_SIMPLEARROW, allowFarUse = true },
|
|
-- WAND
|
|
[28545] = { skill = SKILL_MAGLEVEL, effect = CONST_ANI_FIRE, allowFarUse = true },
|
|
[28557] = { skill = SKILL_MAGLEVEL, effect = CONST_ANI_FIRE, allowFarUse = true },
|
|
[35284] = { skill = SKILL_MAGLEVEL, effect = CONST_ANI_FIRE, allowFarUse = true },
|
|
[35290] = { skill = SKILL_MAGLEVEL, effect = CONST_ANI_FIRE, allowFarUse = true },
|
|
}
|
|
|
|
local dummies = Game.getDummies()
|
|
|
|
local function leaveExerciseTraining(playerId, targetItem)
|
|
if _G.OnExerciseTraining[playerId] then
|
|
stopEvent(_G.OnExerciseTraining[playerId].event)
|
|
_G.OnExerciseTraining[playerId] = nil
|
|
end
|
|
|
|
local player = Player(playerId)
|
|
if player then
|
|
player:setTraining(false)
|
|
if targetItem then
|
|
targetItem:actor(false)
|
|
end
|
|
end
|
|
return
|
|
end
|
|
|
|
local function exerciseTrainingEvent(playerId, tilePosition, weaponId, dummyId)
|
|
local player = Player(playerId)
|
|
if not player then
|
|
return leaveExerciseTraining(playerId)
|
|
end
|
|
|
|
local targetItem = Tile(tilePosition):getItemById(dummyId)
|
|
if not targetItem then
|
|
player:sendTextMessage(MESSAGE_FAILURE, "Someone has moved the dummy, the training has stopped.")
|
|
leaveExerciseTraining(playerId, targetItem)
|
|
return false
|
|
end
|
|
|
|
if player:isTraining() == 0 then
|
|
player:sendTextMessage(MESSAGE_FAILURE, "You have stopped training.")
|
|
return leaveExerciseTraining(playerId, targetItem)
|
|
end
|
|
|
|
local playerPosition = player:getPosition()
|
|
if not playerPosition:isProtectionZoneTile() then
|
|
player:sendTextMessage(MESSAGE_FAILURE, "You are no longer in a protection zone, the training has stopped.")
|
|
leaveExerciseTraining(playerId)
|
|
return false
|
|
end
|
|
|
|
local trainingData = _G.OnExerciseTraining[playerId]
|
|
local weapon = trainingData and trainingData.weapon
|
|
if not weapon or not weapon:isItem() or weapon:getPosition() ~= playerPosition then
|
|
player:sendTextMessage(MESSAGE_FAILURE, "You need the training weapon in the backpack, the training has stopped.")
|
|
leaveExerciseTraining(playerId, targetItem)
|
|
return false
|
|
end
|
|
|
|
if not weapon:hasAttribute(ITEM_ATTRIBUTE_CHARGES) then
|
|
player:sendTextMessage(MESSAGE_FAILURE, "The selected item is not a training weapon, the training has stopped.")
|
|
leaveExerciseTraining(playerId, targetItem)
|
|
return false
|
|
end
|
|
|
|
local weaponCharges = weapon:getAttribute(ITEM_ATTRIBUTE_CHARGES)
|
|
if not weaponCharges or weaponCharges <= 0 then
|
|
weapon:remove(1) -- ??
|
|
player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your training weapon has disappeared.")
|
|
leaveExerciseTraining(playerId, targetItem)
|
|
return false
|
|
end
|
|
|
|
if not dummies[dummyId] then
|
|
return false
|
|
end
|
|
|
|
local rate = dummies[dummyId] / 100
|
|
local isMagic = exerciseWeaponsTable[weaponId].skill == SKILL_MAGLEVEL
|
|
if isMagic then
|
|
player:addManaSpent(600 * rate)
|
|
else
|
|
player:addSkillTries(exerciseWeaponsTable[weaponId].skill, 7 * rate)
|
|
end
|
|
|
|
weapon:setAttribute(ITEM_ATTRIBUTE_CHARGES, (weaponCharges - 1))
|
|
tilePosition:sendMagicEffect(CONST_ME_HITAREA)
|
|
|
|
if exerciseWeaponsTable[weaponId].effect then
|
|
playerPosition:sendDistanceEffect(tilePosition, exerciseWeaponsTable[weaponId].effect)
|
|
end
|
|
|
|
if weapon:getAttribute(ITEM_ATTRIBUTE_CHARGES) <= 0 then
|
|
weapon:remove(1)
|
|
player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your training weapon has disappeared.")
|
|
leaveExerciseTraining(playerId, targetItem)
|
|
return false
|
|
end
|
|
|
|
local eventSpeedMultiplier = 1
|
|
local scopedFastExercise = KV.scoped("eventscheduler"):get("fast-exercise")
|
|
if scopedFastExercise then
|
|
eventSpeedMultiplier = 0.5
|
|
logger.debug("Fast exercise is enabled.")
|
|
end
|
|
|
|
local vocation = player:getVocation()
|
|
_G.OnExerciseTraining[playerId].event = addEvent(exerciseTrainingEvent, (vocation:getBaseAttackSpeed() / configManager.getFloat(configKeys.RATE_EXERCISE_TRAINING_SPEED)) * eventSpeedMultiplier, playerId, tilePosition, weaponId, dummyId)
|
|
return true
|
|
end
|
|
|
|
local function isDummy(id)
|
|
return dummies[id] and dummies[id] > 0
|
|
end
|
|
|
|
local exerciseTraining = Action()
|
|
|
|
function exerciseTraining.onUse(player, item, fromPosition, target, toPosition, isHotkey)
|
|
if not target or type(target) ~= "userdata" or not target:isItem() then
|
|
return true
|
|
end
|
|
|
|
local playerId = player:getId()
|
|
local targetId = target:getId()
|
|
|
|
local targetItem = Item(target.uid)
|
|
if targetItem and isDummy(targetId) then
|
|
if _G.OnExerciseTraining[playerId] then
|
|
player:sendTextMessage(MESSAGE_FAILURE, "You are already training!")
|
|
return true
|
|
end
|
|
|
|
local playerPos = player:getPosition()
|
|
if not exerciseWeaponsTable[item.itemid].allowFarUse and (playerPos:getDistance(target:getPosition()) > 1) then
|
|
player:sendTextMessage(MESSAGE_FAILURE, "Get closer to the dummy.")
|
|
return true
|
|
end
|
|
|
|
if not playerPos:isProtectionZoneTile() then
|
|
player:sendTextMessage(MESSAGE_FAILURE, "You need to be in a protection zone.")
|
|
return true
|
|
end
|
|
|
|
local playerHouse = player:getTile():getHouse()
|
|
local targetPos = target:getPosition()
|
|
local targetHouse = Tile(targetPos):getHouse()
|
|
|
|
if targetHouse and isDummy(targetId) then
|
|
if playerHouse ~= targetHouse then
|
|
player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You must be inside the house to use this dummy.")
|
|
return true
|
|
end
|
|
|
|
local playersOnDummy = 0
|
|
for _, playerTraining in pairs(_G.OnExerciseTraining) do
|
|
if playerTraining.dummyPos == targetPos then
|
|
playersOnDummy = playersOnDummy + 1
|
|
end
|
|
|
|
if playersOnDummy >= configManager.getNumber(configKeys.MAX_ALLOWED_ON_A_DUMMY) then
|
|
player:sendTextMessage(MESSAGE_FAILURE, "That exercise dummy is busy.")
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
if player:hasExhaustion("training-exhaustion") then
|
|
player:sendTextMessage(MESSAGE_FAILURE, "This exercise dummy can only be used after a " .. exhaustionTime .. " seconds cooldown.")
|
|
return true
|
|
end
|
|
|
|
_G.OnExerciseTraining[playerId] = {}
|
|
if not _G.OnExerciseTraining[playerId].event then
|
|
_G.OnExerciseTraining[playerId].weapon = item
|
|
_G.OnExerciseTraining[playerId].event = addEvent(exerciseTrainingEvent, 0, playerId, targetPos, item.itemid, targetId)
|
|
_G.OnExerciseTraining[playerId].dummyPos = targetPos
|
|
targetItem:actor(true)
|
|
player:setTraining(true)
|
|
player:setExhaustion("training-exhaustion", exhaustionTime)
|
|
player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have started training on an exercise dummy.")
|
|
end
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
for weaponId, weapon in pairs(exerciseWeaponsTable) do
|
|
exerciseTraining:id(weaponId)
|
|
if weapon.allowFarUse then
|
|
exerciseTraining:allowFarUse(true)
|
|
end
|
|
end
|
|
|
|
exerciseTraining:register()
|