From 2026d7fb819f1c0e479079c470f3dbc68ca63d04 Mon Sep 17 00:00:00 2001
From: Alexander Sedov <alex0player@gmail.com>
Date: Tue, 28 Apr 2015 07:34:31 +0300
Subject: [PATCH] factored out archery pre-use functions

Better error messages, check for nonzero ammo (so NPCs won't try to
shoot while being low on arrows).
---
 .../talents/chronomancy/bow-threading.lua     |  9 ++-
 .../data/talents/chronomancy/chronomancer.lua | 13 ++--
 .../talents/chronomancy/temporal-archery.lua  |  8 ++-
 .../data/talents/cunning/called-shots.lua     | 16 ++---
 .../tome/data/talents/psionic/psi-archery.lua |  7 ++-
 .../tome/data/talents/techniques/archery.lua  | 63 ++++++++++++++-----
 .../tome/data/talents/techniques/bow.lua      |  8 ++-
 .../talents/techniques/buckler-training.lua   | 21 ++++---
 .../data/talents/techniques/excellence.lua    | 10 +--
 .../talents/techniques/skirmisher-slings.lua  | 16 ++---
 .../tome/data/talents/techniques/sling.lua    |  8 ++-
 .../data/talents/techniques/techniques.lua    |  2 +-
 game/modules/tome/data/talents/uber/dex.lua   |  2 +-
 13 files changed, 109 insertions(+), 74 deletions(-)

diff --git a/game/modules/tome/data/talents/chronomancy/bow-threading.lua b/game/modules/tome/data/talents/chronomancy/bow-threading.lua
index e78a290bcf..c0af01427b 100644
--- a/game/modules/tome/data/talents/chronomancy/bow-threading.lua
+++ b/game/modules/tome/data/talents/chronomancy/bow-threading.lua
@@ -19,6 +19,9 @@
 
 -- EDGE TODO: Particles, Timed Effect Particles
 
+
+local wardenPreUse = Talents.wardenPreUse
+
 newTalent{
 	name = "Arrow Stitching",
 	type = {"chronomancy/bow-threading", 1},
@@ -35,7 +38,7 @@ newTalent{
 	target = function(self, t)
 		return {type="bolt", range=self:getTalentRange(t), talent=t, friendlyfire=false, friendlyblock=false}
 	end,
-	on_pre_use = function(self, t, silent) if not doWardenPreUse(self, "bow") then if not silent then game.logPlayer(self, "You require a bow to use this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return wardenPreUse(self, t, silent, "bow") end,
 	passives = function(self, t, p)
 		self:talentTemporaryValue(p,"archery_pass_friendly", 1)
 	end,
@@ -117,7 +120,7 @@ newTalent{
 	target = function(self, t)
 		return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t), talent=t, stop_block=true, friendlyfire=false, friendlyblock=false}
 	end,
-	on_pre_use = function(self, t, silent) if not doWardenPreUse(self, "bow") then if not silent then game.logPlayer(self, "You require a bow to use this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return wardenPreUse(self, t, silent, "bow") end,
 	archery_onreach = function(self, t, x, y)
 		game:onTickEnd(function() -- Let the arrow hit first
 			local tg = self:getTalentTarget(t)
@@ -212,7 +215,7 @@ newTalent{
 	target = function(self, t)
 		return {type="bolt", range=self:getTalentRange(t), talent=t, friendlyfire=false, friendlyblock=false}
 	end,
-	on_pre_use = function(self, t, silent) if not doWardenPreUse(self, "bow") then if not silent then game.logPlayer(self, "You require a bow to use this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return wardenPreUse(self, t, silent, "bow") end,
 	getDuration = function(self, t) return getExtensionModifier(self, t, 4) end,
 	getDamage = function(self, t) return self:combatTalentWeaponDamage(t, 0.5, 1.3) end,
 	doEcho = function(self, t, eff)
diff --git a/game/modules/tome/data/talents/chronomancy/chronomancer.lua b/game/modules/tome/data/talents/chronomancy/chronomancer.lua
index 42ed00e1d5..fc074c1146 100644
--- a/game/modules/tome/data/talents/chronomancy/chronomancer.lua
+++ b/game/modules/tome/data/talents/chronomancy/chronomancer.lua
@@ -217,16 +217,19 @@ end
 -- Checks for weapons in main and quickslot
 doWardenPreUse = function(self, weapon, silent)
 	if weapon == "bow" then
-		if not self:hasArcheryWeapon("bow") and not self:hasArcheryWeaponQS("bow") then
-			return false
+		local bow, ammo = self:hasArcheryWeapon("bow")
+		if not bow then
+			bow, ammo = self:hasArcheryWeaponQS("bow")
 		end
+		return bow, ammo
 	end
 	if weapon == "dual" then
-		if not self:hasDualWeapon() and not self:hasDualWeaponQS() then
-			return false
+		local mh, oh = self:hasDualWeapon()
+		if not mh then
+			mh, oh = self:hasDualWeaponQS()
 		end
+		return mh, oh
 	end
-	return true
 end
 
 -- Swaps weapons if needed
diff --git a/game/modules/tome/data/talents/chronomancy/temporal-archery.lua b/game/modules/tome/data/talents/chronomancy/temporal-archery.lua
index d4ba21b73f..4892e076ab 100644
--- a/game/modules/tome/data/talents/chronomancy/temporal-archery.lua
+++ b/game/modules/tome/data/talents/chronomancy/temporal-archery.lua
@@ -17,6 +17,8 @@
 -- Nicolas Casalini "DarkGod"
 -- darkgod@te4.org
 
+local archerPreUse = Talents.archerPreUse
+
 newTalent{
 	name = "Phase Shot",
 	type = {"chronomancy/temporal-archery", 1},
@@ -27,7 +29,7 @@ newTalent{
 	no_energy = "fake",
 	range = 10,
 	tactical = { ATTACK = {TEMPORAL = 2} },
-	on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon() then if not silent then game.logPlayer(self, "You require a bow or sling for this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent) end,
 	requires_target = true,
 	action = function(self, t)
 		local tg = {type="bolt"}
@@ -53,7 +55,7 @@ newTalent{
 	no_energy = "fake",
 	range = 10,
 	tactical = { ATTACK = {PHYSICAL = 2} },
-	on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon() then if not silent then game.logPlayer(self, "You require a bow or sling for this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent) end,
 	requires_target = true,
 	action = function(self, t)
 		local tg = {type="bolt"}
@@ -112,7 +114,7 @@ newTalent{
 	no_energy = true,
 	range = 10,
 	tactical = { ATTACK = {PHYSICAL = 2} },
-	on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon() then if not silent then game.logPlayer(self, "You require a bow or sling for this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent) end,
 	requires_target = true,
 	action = function(self, t)
 		local old = self.energy.value
diff --git a/game/modules/tome/data/talents/cunning/called-shots.lua b/game/modules/tome/data/talents/cunning/called-shots.lua
index b91ad604ff..dcba9b7009 100644
--- a/game/modules/tome/data/talents/cunning/called-shots.lua
+++ b/game/modules/tome/data/talents/cunning/called-shots.lua
@@ -13,15 +13,7 @@
 -- You should have received a copy of the GNU General Public License
 -- along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-local sling_equipped = function(self, silent)
-	if not self:hasArcheryWeapon("sling") then
-		if not silent then
-			game.logPlayer(self, "You must wield a sling!")
-		end
-		return false
-	end
-	return true
-end
+local archerPreUse = Talents.archerPreUse
 
 -- calc_all is so the info can show all the effects.
 local sniper_bonuses = function(self, calc_all)
@@ -89,7 +81,7 @@ newTalent {
 	cooldown = shot_cooldown,
 	requires_target = true,
 	range = archery_range,
-	on_pre_use = function(self, t, silent) return sling_equipped(self, silent) end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent, "sling") end,
 	pin_duration = function(self, t)
 		return math.floor(self:combatTalentScale(t, 1, 2))
 	end,
@@ -144,7 +136,7 @@ newTalent {
 	no_npc_use = true, -- Numbers overtuned to make sure the class has a satisfying high damage shot
 	requires_target = true,
 	range = archery_range,
-	on_pre_use = function(self, t, silent) return sling_equipped(self, silent) end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent, "sling") end,
 	getDistanceBonus = function(self, t, range)
 		return self:combatScale(range, -.5, 1, 2.5, 10, 0.25) --Slow scaling to allow for greater range variability
 	end,
@@ -207,7 +199,7 @@ newTalent {
 	cooldown = shot_cooldown,
 	requires_target = true,
 	range = archery_range,
-	on_pre_use = function(self, t, silent) return sling_equipped(self, silent) end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent, "sling") end,
 	damage_multiplier = function(self, t)
 		return self:combatTalentWeaponDamage(t, 0.3, 0.75)
 	end,
diff --git a/game/modules/tome/data/talents/psionic/psi-archery.lua b/game/modules/tome/data/talents/psionic/psi-archery.lua
index 779ba3b79c..c1ae0ffccc 100644
--- a/game/modules/tome/data/talents/psionic/psi-archery.lua
+++ b/game/modules/tome/data/talents/psionic/psi-archery.lua
@@ -17,6 +17,7 @@
 -- Nicolas Casalini "DarkGod"
 -- darkgod@te4.org
 
+local archerPreUse = Talents.archerPreUse
 
 newTalent{
 	name = "Guided Shot",
@@ -30,7 +31,7 @@ newTalent{
 	tactical = { ATTACK = { PHYSICAL = 2 } },
 	range = archery_range,
 	requires_target = true,
-	on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon("bow") then if not silent then game.logPlayer(self, "You require a bow for this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent, "bow") end,
 	shot_boost = function(self, t) return self:combatTalentScale(t, 40, 80, 0.75) end,
 	use_psi_archery = function(self, t)
 		local pf_weapon = self:getInven("PSIONIC_FOCUS")[1]
@@ -64,7 +65,7 @@ newTalent{
 	range = archery_range,
 	requires_target = true,
 	tactical = { ATTACK = { PHYSICAL = 2 } },
-	on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon("bow") then if not silent then game.logPlayer(self, "You require a bow for this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent, "bow") end,
 	apr_boost = function(self, t) return math.floor(self:combatTalentScale(t, 20, 60)) end,
 	dam_mult = function(self, t)
 		return self:combatTalentWeaponDamage(t, 1.5, 2.5)
@@ -102,7 +103,7 @@ newTalent{
 	tactical = { ATTACK = { PHYSICAL = 2 } },
 	range = archery_range,
 	requires_target = true,
-	on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon("bow") then if not silent then game.logPlayer(self, "You require a bow for this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent, "bow") end,
 	use_psi_archery = function(self, t)
 		local pf_weapon = self:getInven("PSIONIC_FOCUS")[1]
 		if pf_weapon and pf_weapon.archery then
diff --git a/game/modules/tome/data/talents/techniques/archery.lua b/game/modules/tome/data/talents/techniques/archery.lua
index c98639a121..7e6ba313b1 100644
--- a/game/modules/tome/data/talents/techniques/archery.lua
+++ b/game/modules/tome/data/talents/techniques/archery.lua
@@ -18,6 +18,46 @@
 -- darkgod@te4.org
 
 -- Default archery attack
+
+local weaponCheck = function(self, weapon, ammo, silent, weapon_type)
+	if not weapon then
+		if not silent then
+			-- ammo contains error message
+			game.logPlayer(self, ({
+				["disarmed"] = "You are currently disarmed and cannot use this talent.",
+				["no shooter"] = "You require a missile weapon to use this talent.",
+				["no ammo"] = "You require ammo to use this talent.",
+				["bad ammo"] = "Your ammo type does not match your weapon type.",
+				["bad type"] = ("You require a %s to use this talent."):format(weapon_type or "bow"), --  warden hack
+			})[ammo] or "You require a bow or sling and ammo for this talent.")
+		end
+		return false
+	else
+		local infinite = ammo and ammo.infinite or self:attr("infinite_ammo")
+		if not ammo or (ammo.combat.shots_left <= 0 and not infinite) then
+			if not silent then game.logPlayer(self, "You do not have enough ammo left!") end
+			return false
+		end
+	end
+	return true
+end
+
+local archerPreUse = function(self, t, silent, weapon_type)
+	local weapon, ammo = self:hasArcheryWeapon(weapon_type)
+	return weaponCheck(self, weapon, ammo, silent, weapon_type)
+end
+
+local wardenPreUse = function(self, t, silent, weapon_type)
+	local weapon, ammo = self:hasArcheryWeapon(weapon_type)
+	if self:attr("warden_swap") and not weapon and weapon_type == nil or weapon_type == "bow" then
+		weapon, ammo = doWardenPreUse(self, "bow")
+	end
+	return weaponCheck(self, weapon, ammo, silent, weapon_type)
+end
+
+Talents.archerPreUse = archerPreUse
+Talents.wardenPreUse = wardenPreUse
+
 newTalent{
 	name = "Shoot",
 	type = {"technique/archery-base", 1},
@@ -37,7 +77,7 @@ newTalent{
 	message = "@Source@ shoots!",
 	requires_target = true,
 	tactical = { ATTACK = { weapon = 1 } },
-	on_pre_use = function(self, t, silent) if not (self:attr("warden_swap") and doWardenPreUse(self, "bow")) and not self:hasArcheryWeapon() then if not silent then game.logPlayer(self, "You require a bow or sling and ammo for this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return wardenPreUse(self, t, silent) end,
 	no_unlearn_last = true,
 	use_psi_archery = function(self, t)
 		local inven = self:getInven("PSIONIC_FOCUS")
@@ -62,11 +102,6 @@ newTalent{
 		
 		local weapon, ammo, offweapon = self:hasArcheryWeapon()
 		if not weapon then return nil end
-		local infinite = ammo.infinite or self:attr("infinite_ammo")
-		if not ammo or (ammo.combat.shots_left <= 0 and not infinite) then
-			game.logPlayer(self, "You do not have enough ammo left!")
-			return nil
-		end
 
 		-- Bombardment.
 		local weapon = self:hasArcheryWeapon("sling")
@@ -113,7 +148,7 @@ newTalent{
 	range = archery_range,
 	requires_target = true,
 	tactical = { ATTACK = { weapon = 2 } },
-	on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon() then if not silent then game.logPlayer(self, "You require a bow or sling for this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent) end,
 	action = function(self, t)
 		local targets = self:archeryAcquireTargets(nil, {one_shot=true})
 		if not targets then return end
@@ -136,7 +171,7 @@ newTalent{
 	no_energy = true,
 	tactical = { BUFF = 2 },
 	no_npc_use = true,
-	on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon() then if not silent then game.logPlayer(self, "You require a bow or sling for this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent) end,
 	getCombatVals = function(self, t)
 		local vals = {speed = -self:combatTalentLimit(t, 0.5, 0.05, 0.25), -- Limit < 50% speed loss
 			crit =  self:combatScale(self:getTalentLevel(t) * self:getDex(10, true), 7, 0, 57, 50),
@@ -188,7 +223,7 @@ newTalent{
 	sustain_stamina = 20,
 	no_energy = true,
 	tactical = { BUFF = 2 },
-	on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon() then if not silent then game.logPlayer(self, "You require a bow or sling for this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent) end,
 	getCombatVals = function(self, t)
 		local vals = {speed = self:combatTalentScale(t, 0.1, 0.5, 0.75),
 			crit = -self:combatTalentScale(t, 10.4, 20),
@@ -238,7 +273,7 @@ newTalent{
 	range = archery_range,
 	requires_target = true,
 	tactical = { ATTACK = { weapon = 1 }, STAMINA = 1 },
-	on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon() then if not silent then game.logPlayer(self, "You require a bow or sling for this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent) end,
 	action = function(self, t)
 		local targets = self:archeryAcquireTargets(nil, {one_shot=true})
 		if not targets then return end
@@ -270,7 +305,7 @@ newTalent{
 	end,
 	require = techs_dex_req1,
 	tactical = { ATTACKAREA = { FIRE = 2 }, DISABLE = { blind = 2 } },
-	on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon() then if not silent then game.logPlayer(self, "You require a bow or sling for this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent) end,
 	requires_target = true,
 	target = function(self, t)
 		return {type="ball", x=x, y=y, radius=self:getTalentRadius(t), range=self:getTalentRange(t)}
@@ -312,7 +347,7 @@ newTalent{
 	range = archery_range,
 	tactical = { ATTACK = { weapon = 1 }, DISABLE = 1 },
 	requires_target = true,
-	on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon() then if not silent then game.logPlayer(self, "You require a bow or sling for this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent) end,
 	archery_onhit = function(self, t, target, x, y)
 		target:setEffect(target.EFF_SLOW, 7, {power=util.bound((self:combatAttack() * 0.15 * self:getTalentLevel(t)) / 100, 0.1, 0.4), apply_power=self:combatAttack()})
 	end,
@@ -341,7 +376,7 @@ newTalent{
 	tactical = { ATTACK = { weapon = 1 }, DISABLE = { pin = 2 } },
 	requires_target = true,
 	getDur = function(self, t) return math.floor(self:combatTalentScale(t, 2.3, 5.5)) end,
-	on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon() then if not silent then game.logPlayer(self, "You require a bow or sling for this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent) end,
 	archery_onhit = function(self, t, target, x, y)
 		if target:canBe("pin") then
 			target:setEffect(target.EFF_PINNED, t.getDur(self, t), {apply_power=self:combatAttack()})
@@ -380,7 +415,7 @@ newTalent{
 		local weapon, ammo = self:hasArcheryWeapon()
 		return {type="ball", radius=self:getTalentRadius(t), range=self:getTalentRange(t), display=self:archeryDefaultProjectileVisual(weapon, ammo)}
 	end,
-	on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon() then if not silent then game.logPlayer(self, "You require a bow or sling for this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent) end,
 	getStunDur = function(self, t) return math.floor(self:combatTalentScale(t, 3, 7)) end,
 	archery_onhit = function(self, t, target, x, y)
 		if target:canBe("stun") then
diff --git a/game/modules/tome/data/talents/techniques/bow.lua b/game/modules/tome/data/talents/techniques/bow.lua
index ed1e14a3dd..283c86f383 100644
--- a/game/modules/tome/data/talents/techniques/bow.lua
+++ b/game/modules/tome/data/talents/techniques/bow.lua
@@ -17,6 +17,8 @@
 -- Nicolas Casalini "DarkGod"
 -- darkgod@te4.org
 
+local archerPreUse = Talents.archerPreUse
+
 newTalent{
 	name = "Bow Mastery",
 	type = {"technique/archery-bow", 1},
@@ -51,7 +53,7 @@ newTalent{
 	range = archery_range,
 	tactical = { ATTACK = { weapon = 2 } },
 	requires_target = true,
-	on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon("bow") then if not silent then game.logPlayer(self, "You require a bow for this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent, "bow") end,
 	action = function(self, t)
 		if not self:hasArcheryWeapon("bow") then game.logPlayer(self, "You must wield a bow!") return nil end
 
@@ -79,7 +81,7 @@ newTalent{
 	target = function(self, t)
 		return {type="ball", radius=self:getTalentRadius(t), range=self:getTalentRange(t)}
 	end,
-	on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon("bow") then if not silent then game.logPlayer(self, "You require a bow for this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent, "bow") end,
 	action = function(self, t)
 		if not self:hasArcheryWeapon("bow") then game.logPlayer(self, "You must wield a bow!") return nil end
 
@@ -111,7 +113,7 @@ newTalent{
 	target = function(self, t)
 		return {type="ball", radius=self:getTalentRadius(t), range=self:getTalentRange(t), selffire=false}
 	end,
-	on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon("bow") then if not silent then game.logPlayer(self, "You require a bow for this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent, "bow") end,
 	action = function(self, t)
 		if not self:hasArcheryWeapon("bow") then game.logPlayer(self, "You must wield a bow!") return nil end
 
diff --git a/game/modules/tome/data/talents/techniques/buckler-training.lua b/game/modules/tome/data/talents/techniques/buckler-training.lua
index a60192b305..c556b98935 100644
--- a/game/modules/tome/data/talents/techniques/buckler-training.lua
+++ b/game/modules/tome/data/talents/techniques/buckler-training.lua
@@ -13,6 +13,15 @@
 -- You should have received a copy of the GNU General Public License
 -- along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+local archerPreUse = Talents.archerPreUse
+
+local preUse = function(self, t, silent)
+	if not self:hasShield() or not archerPreUse(self, t, true) then
+		if not silent then game.logPlayer("You require a ranged weapon and a shield to use this talent.") end
+		return false
+	end
+	return true
+end
 
 newTalent {
 	short_name = "SKIRMISHER_BUCKLER_EXPERTISE",
@@ -70,11 +79,7 @@ newTalent {
 	range = 1,
 	is_special_melee = true,
 	on_pre_use = function(self, t, silent)
-		if not self:hasShield() or not self:hasArcheryWeapon() then
-			if not silent then game.logPlayer(self, "You require a ranged weapon and a shield to use this talent.") end
-			return false
-		end
-		return true
+		return preUse(self, t, silent)
 	end,
 	getDist = function(self, t)
 		if self:getTalentLevelRaw(t) >= 3 then
@@ -203,11 +208,7 @@ newTalent {
 	require = techs_dex_req4,
 	tactical = { BUFF = 2 },
 	on_pre_use = function(self, t, silent)
-		if not self:hasShield() or not self:hasArcheryWeapon() then
-			if not silent then game.logPlayer(self, "You require a ranged weapon and a shield to use this talent.") end
-			return false
-		end
-		return true
+		return preUse(self, t, silent)
 	end,
 	activate = function(self, t)
 		return {}
diff --git a/game/modules/tome/data/talents/techniques/excellence.lua b/game/modules/tome/data/talents/techniques/excellence.lua
index 42aeee4e39..e53ade5de1 100644
--- a/game/modules/tome/data/talents/techniques/excellence.lua
+++ b/game/modules/tome/data/talents/techniques/excellence.lua
@@ -17,6 +17,8 @@
 -- Nicolas Casalini "DarkGod"
 -- darkgod@te4.org
 
+local archerPreUse = Talents.archerPreUse
+
 newTalent{
 	name = "Shoot Down",
 	type = {"technique/archery-excellence", 1},
@@ -36,7 +38,7 @@ newTalent{
 		if #tgts > 0 then return tgts[1].x, tgts[1].y, tgts[1].tgt end
 	end,
 	on_pre_use_ai = function(self, t, silent) return t.onAIGetTarget(self, t) and true or false end,
-	on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon() then if not silent then game.logPlayer(self, "You require a bow or sling for this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent) end,
 	requires_target = true,
 	getNb = function(self, t) return math.floor(self:combatTalentScale(t, 1, 5, "log")) end,
 	target = function(self, t)
@@ -80,7 +82,7 @@ newTalent{
 	tactical = { ATTACK = { weapon = 1 } },
 	requires_target = true,
 	on_pre_use = function(self, t, silent)
-		if not self:hasArcheryWeapon() then if not silent then game.logPlayer(self, "You require a bow or sling for this talent.") end return false end
+		if not archerPreUse(self, t, silent) then return false end
 		if self:attr("never_move") then return false end
 		return true
 	end,
@@ -166,7 +168,7 @@ newTalent{
 		self:archeryShoot(targets, t, nil, {atk = xatk, mult=self:combatTalentWeaponDamage(t, 0.4, 0.9)})
 		return ret
 	end,
-	on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon() then if not silent then game.logPlayer(self, "You require a bow or sling for this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent) end,
 	activate = function(self, t)
 		return {}
 	end,
@@ -196,7 +198,7 @@ newTalent{
 		local weapon, ammo = self:hasArcheryWeapon()
 		return {type="bolt", range=self:getTalentRange(t), display=self:archeryDefaultProjectileVisual(weapon, ammo)}
 	end,
-	on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon() then if not silent then game.logPlayer(self, "You require a bow or sling for this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent) end,
 	getDur = function(self, t) return math.floor(self:combatTalentScale(t, 2, 6)) end,
 	archery_onhit = function(self, t, target, x, y)
 		if target:canBe("silence") then
diff --git a/game/modules/tome/data/talents/techniques/skirmisher-slings.lua b/game/modules/tome/data/talents/techniques/skirmisher-slings.lua
index 0ff6f95c1f..90719e1891 100644
--- a/game/modules/tome/data/talents/techniques/skirmisher-slings.lua
+++ b/game/modules/tome/data/talents/techniques/skirmisher-slings.lua
@@ -13,15 +13,7 @@
 -- You should have received a copy of the GNU General Public License
 -- along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-local sling_equipped = function(self, silent)
-	if not self:hasArcheryWeapon("sling") then
-		if not silent then
-			game.logPlayer(self, "You must wield a sling!")
-		end
-		return false
-	end
-	return true
-end
+local archerPreUse = Talents.archerPreUse
 
 -- Currently just a copy of Sling Mastery.
 newTalent {
@@ -62,7 +54,7 @@ newTalent {
 	cooldown = 5,
 	stamina = 15,
 	requires_target = true,
-	on_pre_use = function(self, t, silent) return sling_equipped(self, silent) end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent, "sling") end,
 	getDamage = function(self, t)
 		return self:combatTalentWeaponDamage(t, 0.4, 1.6)
 	end,
@@ -129,7 +121,7 @@ newTalent {
 			--cone_angle = 50, -- degrees
 		}
 	end,
-	on_pre_use = function(self, t, silent) return sling_equipped(self, silent) end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent, "sling") end,
 	damage_multiplier = function(self, t)
 		return self:combatTalentWeaponDamage(t, 0.2, 0.8)
 	end,
@@ -198,7 +190,7 @@ newTalent {
 	mode = "sustained",
 	no_energy = true,
 	tactical = { BUFF = 2 },
-	on_pre_use = function(self, t, silent) return sling_equipped(self, silent) end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent, "sling") end,
 	cooldown = function(self, t)
 		return 10
 	end,
diff --git a/game/modules/tome/data/talents/techniques/sling.lua b/game/modules/tome/data/talents/techniques/sling.lua
index 6a706fdb24..02bb16daeb 100644
--- a/game/modules/tome/data/talents/techniques/sling.lua
+++ b/game/modules/tome/data/talents/techniques/sling.lua
@@ -17,6 +17,8 @@
 -- Nicolas Casalini "DarkGod"
 -- darkgod@te4.org
 
+local archerPreUse = Talents.archerPreUse
+
 newTalent{
 	name = "Sling Mastery",
 	type = {"technique/archery-sling", 1},
@@ -52,7 +54,7 @@ newTalent{
 	requires_target = true,
 	tactical = { ATTACK = { weapon = 2 }, DISABLE = { blind = 2 } },
 	getBlindDur = function(self, t) return math.floor(self:combatTalentScale(t, 3, 7)) end,
-	on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon("sling") then if not silent then game.logPlayer(self, "You require a sling for this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent, "sling") end,
 	archery_onhit = function(self, t, target, x, y)
 		if target:canBe("blind") then
 			target:setEffect(target.EFF_BLINDED, t.getBlindDur(self, t), {apply_power=self:combatAttack()})
@@ -86,7 +88,7 @@ newTalent{
 	range = archery_range,
 	requires_target = true,
 	tactical = { ATTACK = { weapon = 2 }, DISABLE = { knockback = 2 }, ESCAPE = { knockback = 1 } },
-	on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon("sling") then if not silent then game.logPlayer(self, "You require a sling for this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent, "sling") end,
 	archery_onhit = function(self, t, target, x, y)
 		if target:checkHit(self:combatAttack(), target:combatPhysicalResist(), 0, 95, 15) and target:canBe("knockback") then
 			target:knockback(self.x, self.y, 4)
@@ -126,7 +128,7 @@ newTalent{
 		if fake then return count end
 		return math.floor(count) + (rng.percent(100*(count - math.floor(count))) and 1 or 0)
 	end,
-	on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon("sling") then if not silent then game.logPlayer(self, "You require a sling for this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent, "sling") end,
 	action = function(self, t)
 		if not self:hasArcheryWeapon("sling") then game.logPlayer(self, "You must wield a sling!") return nil end
 		local targets = self:archeryAcquireTargets(nil, {multishots=t.getShots(self, t)})
diff --git a/game/modules/tome/data/talents/techniques/techniques.lua b/game/modules/tome/data/talents/techniques/techniques.lua
index 45c5a7c4c3..a129a0700c 100644
--- a/game/modules/tome/data/talents/techniques/techniques.lua
+++ b/game/modules/tome/data/talents/techniques/techniques.lua
@@ -326,9 +326,9 @@ load("/data/talents/techniques/battle-tactics.lua")
 load("/data/talents/techniques/field-control.lua")
 load("/data/talents/techniques/combat-techniques.lua")
 load("/data/talents/techniques/combat-training.lua")
+load("/data/talents/techniques/archery.lua")
 load("/data/talents/techniques/bow.lua")
 load("/data/talents/techniques/sling.lua")
-load("/data/talents/techniques/archery.lua")
 load("/data/talents/techniques/excellence.lua")
 load("/data/talents/techniques/magical-combat.lua")
 load("/data/talents/techniques/mobility.lua")
diff --git a/game/modules/tome/data/talents/uber/dex.lua b/game/modules/tome/data/talents/uber/dex.lua
index eb0cb04d10..938dfaf625 100644
--- a/game/modules/tome/data/talents/uber/dex.lua
+++ b/game/modules/tome/data/talents/uber/dex.lua
@@ -237,7 +237,7 @@ uberTalent{
 	require = { special={desc="Have dealt over 50000 damage with ranged weapons", fct=function(self) return self.damage_log and self.damage_log.weapon.archery and self.damage_log.weapon.archery >= 50000 end} },
 	tactical = { ATTACK = { weapon = 3 }, DISABLE = 3 },
 	requires_target = true,
-	on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon() then if not silent then game.logPlayer(self, "You require a bow or sling for this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) return Talents.archerPreUse(self, t, silent) end,
 	archery_onhit = function(self, t, target, x, y)
 		if target:canBe("stun") then
 			target:setEffect(target.EFF_STUNNED, 5, {apply_power=self:combatAttack()})
-- 
GitLab