Cain & Abel no longer forces dual-wielding and instead requires an empty offhand (#94661)

## About The Pull Request

Instead of forcing the player to dual wield it, Cain & Abel can now be
held with one hand, although doing so prohibits you from using it to
attack, throw/spin daggers or send out wisps.

## Why It's Good For The Game

Forcing dual wielding on a small pocketable item is a bit weird and
feels bad in gameplay, as it essentially prohibits you from being able
to use any healing items unless you keep an empty pocket, in which case
quick storage on all other items will default to it which is rather
impractical. Requiring an empty offhand essentially keeps the wielding
requirement without UX issues coming from inhand usage swapping modes or
inventory jank.

## Changelog
🆑
balance: Cain & Abel no longer forces dual-wielding and instead requires
an empty offhand
/🆑
This commit is contained in:
SmArtKar
2026-01-03 15:11:12 +01:00
committed by GitHub
parent 4aa281f63b
commit b80f8f1127
5 changed files with 47 additions and 6 deletions
@@ -6,7 +6,7 @@
desc = "I cry I pray mon Dieu."
icon = 'icons/obj/mining_zones/artefacts.dmi'
lefthand_file = 'icons/mob/inhands/equipment/kitchen_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/kitchen_lefthand.dmi' //same sprite as lefthand
righthand_file = 'icons/mob/inhands/equipment/kitchen_righthand.dmi'
icon_state = "cain_and_abel"
inhand_icon_state = "cain_and_abel"
attack_verb_continuous = list("attacks", "saws", "slices", "tears", "lacerates", "rips", "dices", "cuts")
@@ -36,12 +36,13 @@
var/throw_mode = THROW_MODE_CRYSTALS
/// Flames we have up!
var/list/current_wisps = list()
/// Have we thrown the second dagger yet?
var/dagger_thrown = FALSE
/// Cooldown till we can throw blades again
COOLDOWN_DECLARE(throw_cooldown)
/obj/item/cain_and_abel/Initialize(mapload)
. = ..()
AddComponent(/datum/component/two_handed, require_twohands = TRUE, force_unwielded = force, force_wielded = force)
if (length(animation_steps))
return
@@ -64,11 +65,10 @@
source.RemoveElement(/datum/element/relay_attackers)
set_combo(new_value = 0, user = source)
UnregisterSignal(source, list(COMSIG_ATOM_WAS_ATTACKED))
UnregisterSignal(source, list(COMSIG_ATOM_WAS_ATTACKED, COMSIG_MOB_UPDATE_HELD_ITEMS))
/obj/item/cain_and_abel/equipped(mob/user, slot)
. = ..()
if(!(slot & ITEM_SLOT_HANDS))
unset_user(user)
return
@@ -77,6 +77,7 @@
user.AddElement(/datum/element/relay_attackers)
RegisterSignal(user, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(on_attacked))
RegisterSignal(user, COMSIG_MOB_UPDATE_HELD_ITEMS, PROC_REF(on_updated_held_items))
/obj/item/cain_and_abel/proc/on_attacked(datum/source, atom/attacker, attack_flags)
SIGNAL_HANDLER
@@ -86,6 +87,10 @@
if(get_dist(interacting_with, user) > 9 || interacting_with.z != user.z)
return NONE
if(!check_wield(user))
user.balloon_alert(user, "offhand busy!")
return ITEM_INTERACT_BLOCKING
if(!length(current_wisps))
user.balloon_alert(user, "no wisps!")
return ITEM_INTERACT_BLOCKING
@@ -94,6 +99,14 @@
addtimer(CALLBACK(src, PROC_REF(fire_wisp), user, interacting_with), index * 0.15 SECONDS)
return ITEM_INTERACT_SUCCESS
/obj/item/cain_and_abel/pre_attack(atom/target, mob/living/user, list/modifiers, list/attack_modifiers)
. = ..()
if (.)
return
if(!check_wield(user))
user.balloon_alert(user, "offhand busy!")
return TRUE
/obj/item/cain_and_abel/attack(mob/living/target, mob/living/carbon/human/user)
if(!istype(target) || target.mob_size < MOB_SIZE_LARGE || target.stat == DEAD)
attack_speed = CLICK_CD_MELEE
@@ -129,6 +142,13 @@
combo_count = new_value
/obj/item/cain_and_abel/proc/on_updated_held_items(mob/living/source)
SIGNAL_HANDLER
update_dagger_icon()
/obj/item/cain_and_abel/proc/update_dagger_icon()
inhand_icon_state = "[src::inhand_icon_state][(dagger_thrown || !check_wield(loc)) ? "_thrown" : ""]"
/obj/item/cain_and_abel/proc/add_wisp(mob/living/user)
var/obj/effect/overlay/blood_wisp/new_wisp = new(src)
current_wisps += new_wisp
@@ -181,6 +201,18 @@
animate(wisp_to_remove, alpha = 0, time = 0.2 SECONDS)
QDEL_IN(wisp_to_remove, 0.2 SECONDS)
/obj/item/cain_and_abel/proc/check_wield(mob/living/user)
if (!istype(user))
return TRUE
var/active_item = (src == user.get_active_held_item())
var/obj/item/other_held = active_item ? user.get_inactive_held_item() : user.get_active_held_item()
if (!isnull(other_held))
return FALSE
var/obj/item/bodypart/hand = active_item ? user.get_inactive_hand() : user.get_active_hand()
if (!hand || hand.bodypart_disabled)
return FALSE
return TRUE
/// Frame data for cain & abel wisps so we don't have to make list monstrocities
/datum/abel_wisp_frame
var/x = 0
@@ -10,12 +10,16 @@
/datum/action/cooldown/dagger_swing/Activate(atom/target_atom)
var/mob/living/living_owner = owner
var/obj/item/cain_and_abel = target
var/obj/item/cain_and_abel/cain_and_abel = target
if(!living_owner.is_holding(cain_and_abel))
owner.balloon_alert(owner, "must be held")
return FALSE
if(!cain_and_abel.check_wield(owner))
owner.balloon_alert(owner, "offhand busy!")
return TRUE
living_owner.apply_status_effect(/datum/status_effect/dagger_swinging)
var/static/list/possible_sounds = list(
@@ -11,6 +11,10 @@
user.balloon_alert(user, "unable!")
return
if(!check_wield(user))
user.balloon_alert(user, "offhand busy!")
return
if(get_dist(target, user) > 9)
user.balloon_alert(user, "too far away!")
return
@@ -52,7 +56,8 @@
set_dagger_icon(thrown = FALSE)
/obj/item/cain_and_abel/proc/set_dagger_icon(thrown = FALSE)
inhand_icon_state = "[src::inhand_icon_state][thrown ? "_thrown" : ""]"
dagger_thrown = thrown
update_dagger_icon()
update_inhand_icon()
/obj/item/cain_and_abel/proc/reset_dagger_icon(datum/source)