canary/data/scripts/actions/items/exercise_training_weapons.lua
Beats 726199c5ab
fix: deplete the clicked exercise weapon, not the first in inventory (#3918)
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.
2026-05-04 08:23:42 -03:00

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()