Files
Bubberstation/code/datums/components/transforming.dm

372 lines
14 KiB
Plaintext

/*
* Transforming weapon component. For weapons that swap between states.
* For example: Energy swords, cleaving saws, switch blades.
*
* Used to easily make an item that can be attack_self'd to gain force or change mode.
*
* Only values passed on initialize will update when the item is activated (except the icon_state).
* The icon_state of the item will swap between "[icon_state]" and "[icon_state]_on".
*/
/datum/component/transforming
/// Whether the weapon is transformed
var/active = FALSE
/// Cooldown on transforming this item back and forth
var/transform_cooldown_time
/// Force of the weapon when active
var/force_on
/// Throwforce of the weapon when active
var/throwforce_on
/// Throw speed of the weapon when active
var/throw_speed_on
/// Weight class of the weapon when active
var/w_class_on
/// The sharpness of the weapon when active
var/sharpness_on
/// Hitsound played when active
var/hitsound_on
/// Force when the weapon is inactive
var/force_off
/// Throwforce when the weapon is inactive
var/throwforce_off
/// Throw speed of the weapon when inactive
var/throw_speed_off
/// Weight class of the weapon when inactive
var/w_class_off
/// The sharpness of the weapon when inactive
var/sharpness_off
/// List of the original continuous attack verbs the item has.
var/list/attack_verb_continuous_off
/// List of the original simple attack verbs the item has.
var/list/attack_verb_simple_off
/// List of continuous attack verbs used when the weapon is enabled
var/list/attack_verb_continuous_on
/// List of simple attack verbs used when the weapon is enabled
var/list/attack_verb_simple_on
/// Whether clumsy people need to succeed an RNG check to turn it on without hurting themselves
var/clumsy_check
/// Amount of damage to deal to clumsy people
var/clumsy_damage
/// If we get sharpened with a whetstone, save the bonus here for later use if we un/redeploy
var/sharpened_bonus = 0
/// Dictate whether we change inhands or not
var/inhand_icon_change = TRUE
/// Cooldown in between transforms
COOLDOWN_DECLARE(transform_cooldown)
/datum/component/transforming/Initialize(
start_transformed = FALSE,
transform_cooldown_time = 0 SECONDS,
force_on = 0,
throwforce_on = 0,
throw_speed_on = 2,
sharpness_on = NONE,
hitsound_on = 'sound/items/weapons/blade1.ogg',
w_class_on = WEIGHT_CLASS_BULKY,
clumsy_check = TRUE,
clumsy_damage = 10,
list/attack_verb_continuous_on,
list/attack_verb_simple_on,
inhand_icon_change = TRUE,
)
if(!isitem(parent))
return COMPONENT_INCOMPATIBLE
var/obj/item/item_parent = parent
src.transform_cooldown_time = transform_cooldown_time
src.force_on = force_on
src.throwforce_on = throwforce_on
src.throw_speed_on = throw_speed_on
src.sharpness_on = sharpness_on
src.hitsound_on = hitsound_on
src.w_class_on = w_class_on
src.clumsy_check = clumsy_check
src.clumsy_damage = clumsy_damage
src.inhand_icon_change = inhand_icon_change
src.force_off = item_parent.force
src.throwforce_off = item_parent.throwforce
src.throw_speed_off = item_parent.throw_speed
src.sharpness_off = item_parent.sharpness // Raw value, not via the getter
src.w_class_off = item_parent.w_class
if(attack_verb_continuous_on)
src.attack_verb_continuous_on = attack_verb_continuous_on
attack_verb_continuous_off = item_parent.attack_verb_continuous
if(attack_verb_simple_on)
src.attack_verb_simple_on = attack_verb_simple_on
attack_verb_simple_off = item_parent.attack_verb_simple
if(start_transformed)
toggle_active(parent)
/datum/component/transforming/RegisterWithParent()
var/obj/item/item_parent = parent
RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, PROC_REF(on_attack_self))
if(item_parent.sharpness || sharpness_on)
RegisterSignal(parent, COMSIG_ITEM_SHARPEN_ACT, PROC_REF(on_sharpen))
RegisterSignal(parent, COMSIG_DETECTIVE_SCANNED, PROC_REF(on_scan))
RegisterSignal(parent, COMSIG_ITEM_APPLY_FANTASY_BONUSES, PROC_REF(apply_fantasy_bonuses))
RegisterSignal(parent, COMSIG_ITEM_REMOVE_FANTASY_BONUSES, PROC_REF(remove_fantasy_bonuses))
RegisterSignal(parent, COMSIG_ATOM_FINALIZE_MATERIAL_EFFECTS, PROC_REF(on_materials_updated))
RegisterSignal(parent, COMSIG_ATOM_FINALIZE_REMOVE_MATERIAL_EFFECTS, PROC_REF(on_materials_updated))
RegisterSignal(parent, COMSIG_ATOM_SINGLE_MATERIAL_EFFECT_APPLY, PROC_REF(on_material_apply))
RegisterSignal(parent, COMSIG_ATOM_SINGLE_MATERIAL_EFFECT_REMOVE, PROC_REF(on_material_remove))
/datum/component/transforming/proc/apply_fantasy_bonuses(obj/item/source, bonus)
SIGNAL_HANDLER
active = FALSE
set_inactive(source)
force_on = source.modify_fantasy_variable("force_on", force_on, bonus)
throwforce_on = source.modify_fantasy_variable("throwforce_on", throwforce_on, bonus)
/datum/component/transforming/proc/remove_fantasy_bonuses(obj/item/source, bonus)
SIGNAL_HANDLER
active = FALSE
set_inactive(source)
force_on = source.reset_fantasy_variable("force_on", force_on)
throwforce_on = source.reset_fantasy_variable("throwforce_on", throwforce_on)
/datum/component/transforming/proc/on_material_apply(obj/item/source, datum/material/material, amount, multiplier)
SIGNAL_HANDLER
// Opposite state's force needs to be calculated for each material's effect
if (active)
force_off *= GET_MATERIAL_MODIFIER(source.get_material_force_modifier(material, initial(source.sharpness)), multiplier)
throwforce_off *= GET_MATERIAL_MODIFIER(source.get_material_throwforce_modifier(material, initial(source.sharpness)), multiplier)
else
force_on *= GET_MATERIAL_MODIFIER(source.get_material_force_modifier(material, sharpness_on), multiplier)
throwforce_on *= GET_MATERIAL_MODIFIER(source.get_material_throwforce_modifier(material, sharpness_on), multiplier)
/datum/component/transforming/proc/on_material_remove(obj/item/source, datum/material/material, amount, multiplier)
SIGNAL_HANDLER
// Same as appliation but inversed
if (active)
force_off /= GET_MATERIAL_MODIFIER(source.get_material_force_modifier(material, initial(source.sharpness)), multiplier)
throwforce_off /= GET_MATERIAL_MODIFIER(source.get_material_throwforce_modifier(material, initial(source.sharpness)), multiplier)
else
force_on /= GET_MATERIAL_MODIFIER(source.get_material_force_modifier(material, sharpness_on), multiplier)
throwforce_on /= GET_MATERIAL_MODIFIER(source.get_material_throwforce_modifier(material, sharpness_on), multiplier)
/datum/component/transforming/proc/on_materials_updated(obj/item/source, list/materials, datum/material/main_material)
SIGNAL_HANDLER
// Current force can be set directly
if (active)
force_on = source.force
throwforce_on = source.throwforce
else
force_off = source.force
throwforce_off = source.throwforce
/datum/component/transforming/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_ITEM_ATTACK_SELF, COMSIG_ITEM_SHARPEN_ACT, COMSIG_DETECTIVE_SCANNED, COMSIG_ATOM_FINALIZE_MATERIAL_EFFECTS, COMSIG_ATOM_FINALIZE_REMOVE_MATERIAL_EFFECTS))
/datum/component/transforming/proc/on_scan(datum/source, mob/user, datum/detective_scanner_log/entry)
SIGNAL_HANDLER
entry.add_data_entry(DETSCAN_CATEGORY_NOTES, "Readings suggest some form of state changing.")
/*
* Called on [COMSIG_ITEM_ATTACK_SELF].
*
* Check if we can transform our weapon, and if so, call [do_transform].
* Sends signal [COMSIG_TRANSFORMING_PRE_TRANSFORM], and stops the transform action if it returns [COMPONENT_BLOCK_TRANSFORM].
* And, if [do_transform] was successful, do a clumsy effect from [clumsy_transform_effect].
*
* source - source of the signal, the item being transformed / parent
* user - the mob transforming the weapon
*/
/datum/component/transforming/proc/on_attack_self(obj/item/source, mob/user)
SIGNAL_HANDLER
if(!COOLDOWN_FINISHED(src, transform_cooldown))
to_chat(user, span_warning("Wait a bit before trying to use [source] again!"))
return
if(SEND_SIGNAL(source, COMSIG_TRANSFORMING_PRE_TRANSFORM, user, active) & COMPONENT_BLOCK_TRANSFORM)
return
if(do_transform(source, user))
clumsy_transform_effect(user)
return COMPONENT_CANCEL_ATTACK_CHAIN
/*
* Transform the weapon into its alternate form, calling [toggle_active].
*
* Sends signal [COMSIG_TRANSFORMING_ON_TRANSFORM], and calls [default_transform_message] if it does not return [COMPONENT_NO_DEFAULT_MESSAGE].
* Also starts the [transform_cooldown] if we have a set [transform_cooldown_time].
*
* source - the item being transformed / parent
* user - the mob transforming the item (can be null)
*
* returns TRUE.
*/
/datum/component/transforming/proc/do_transform(obj/item/source, mob/user)
toggle_active(source)
if(!(SEND_SIGNAL(source, COMSIG_TRANSFORMING_ON_TRANSFORM, user, active) & COMPONENT_NO_DEFAULT_MESSAGE))
default_transform_message(source, user)
if(!isnull(user))
SEND_SIGNAL(user, COMSIG_MOB_TRANSFORMING_ITEM, source, active)
if(isnum(transform_cooldown_time))
COOLDOWN_START(src, transform_cooldown, transform_cooldown_time)
if(user)
source.add_fingerprint(user)
return TRUE
/*
* The default feedback message and sound effect for an item transforming.
*
* source - the item being transformed / parent
* user - the mob transforming the item
*/
/datum/component/transforming/proc/default_transform_message(obj/item/source, mob/user)
if(user)
source.balloon_alert(user, "[active ? "enabled" : "disabled"] [source]")
playsound(source, 'sound/items/weapons/batonextend.ogg', 50, TRUE)
/*
* Toggle active between true and false, and call
* either set_active or set_inactive depending on whichever state is toggled.
*
* source - the item being transformed / parent
*/
/datum/component/transforming/proc/toggle_active(obj/item/source)
active = !active
if(active)
set_active(source)
else
set_inactive(source)
/*
* Set our transformed item into its active state.
* Updates all the values that were passed from init and the icon_state.
*
* source - the item being transformed / parent
*/
/datum/component/transforming/proc/set_active(obj/item/source)
ADD_TRAIT(source, TRAIT_TRANSFORM_ACTIVE, REF(src))
if(!isnull(sharpness_on))
source.sharpness = sharpness_on
if(!isnull(force_on))
source.force = force_on
if(!isnull(throwforce_on))
source.throwforce = throwforce_on
if(!isnull(throw_speed_on))
source.throw_speed = throw_speed_on
if(LAZYLEN(attack_verb_continuous_on))
source.attack_verb_continuous = attack_verb_continuous_on
if(LAZYLEN(attack_verb_simple_on))
source.attack_verb_simple = attack_verb_simple_on
source.hitsound = hitsound_on
source.update_weight_class(w_class_on)
source.icon_state = "[source.icon_state]_on"
if(inhand_icon_change && source.inhand_icon_state)
source.inhand_icon_state = "[source.inhand_icon_state]_on"
source.update_appearance()
source.update_inhand_icon()
/*
* Set our transformed item into its inactive state.
* Updates all the values back to the item's initial values.
*
* source - the item being un-transformed / parent
*/
/datum/component/transforming/proc/set_inactive(obj/item/source)
REMOVE_TRAIT(source, TRAIT_TRANSFORM_ACTIVE, REF(src))
if(!isnull(sharpness_on))
source.sharpness = sharpness_off
if(!isnull(force_on))
source.force = force_off
if(!isnull(throwforce_on))
source.throwforce = throwforce_off
if(!isnull(throw_speed_on))
source.throw_speed = throwforce_off
if(LAZYLEN(attack_verb_continuous_on))
source.attack_verb_continuous = attack_verb_continuous_off
if(LAZYLEN(attack_verb_simple_off))
source.attack_verb_simple = attack_verb_simple_off
source.hitsound = initial(source.hitsound)
source.update_weight_class(initial(source.w_class))
if(source.post_init_icon_state) // BUBBER CHANGE START : Bug Fix.
source.icon_state = initial(source.post_init_icon_state)
else
source.icon_state = initial(source.icon_state) // BUBBER CHANGE END
source.update_weight_class(w_class_off)
source.icon_state = initial(source.icon_state)
source.inhand_icon_state = initial(source.inhand_icon_state)
source.update_appearance()
source.update_inhand_icon()
/*
* If [clumsy_check] is set to TRUE, attempt to cause a side effect for clumsy people activating this item.
* Called after the transform is done, meaning [active] var has already updated.
*
* user - the clumsy mob, transforming our item (parent)
*
* Returns TRUE if side effects happened, FALSE otherwise
*/
/datum/component/transforming/proc/clumsy_transform_effect(mob/living/user)
if(!clumsy_check)
return FALSE
if(!user || !HAS_TRAIT(user, TRAIT_CLUMSY))
return FALSE
if(active && prob(50))
var/hurt_self_verb_simple = LAZYLEN(attack_verb_simple_on) ? pick(attack_verb_simple_on) : "hit"
var/hurt_self_verb_continuous = LAZYLEN(attack_verb_continuous_on) ? pick(attack_verb_continuous_on) : "hits"
user.visible_message(
span_warning("[user] triggers [parent] while holding it backwards and [hurt_self_verb_continuous] themself, like a doofus!"),
span_warning("You trigger [parent] while holding it backwards and [hurt_self_verb_simple] yourself, like a doofus!"),
)
var/obj/item/item_parent = parent
switch(item_parent.damtype)
if(STAMINA)
user.adjust_stamina_loss(clumsy_damage)
if(OXY)
user.adjust_oxy_loss(clumsy_damage)
if(TOX)
user.adjust_tox_loss(clumsy_damage)
if(BRUTE)
user.take_bodypart_damage(brute=clumsy_damage)
if(BURN)
user.take_bodypart_damage(burn=clumsy_damage)
return TRUE
return FALSE
/*
* Called on [COMSIG_ITEM_SHARPEN_ACT].
* We need to track our sharpened bonus here, so we correctly apply and unapply it
* if our item's sharpness state changes from transforming.
*
* source - the item being sharpened / parent
* increment - the amount of force added
* max - the maximum force that the item can be adjusted to.
*
* Does not return naturally [COMPONENT_BLOCK_SHARPEN_APPLIED] as this is only to track our sharpened bonus between transformation.
*/
/datum/component/transforming/proc/on_sharpen(obj/item/source, increment, max)
SIGNAL_HANDLER
if(sharpened_bonus)
return COMPONENT_BLOCK_SHARPEN_ALREADY
if(force_on + increment > max)
return COMPONENT_BLOCK_SHARPEN_MAXED
sharpened_bonus = increment
force_on += sharpened_bonus
throwforce_on += sharpened_bonus
force_off += sharpened_bonus
throwforce_off += sharpened_bonus
// Mimics base whetstone effect for the on state
sharpness_on = SHARP_EDGED
if (!active)
return COMPONENT_BLOCK_SHARPEN_SHARPNESS