Skip to content

Commit

Permalink
[MIRROR] Different pen types have unique behavior when used in foam d…
Browse files Browse the repository at this point in the history
…arts. [MDB IGNORE] (#25183) (#773)

* Different pen types have unique behavior when used in foam darts. (#79587)

## About The Pull Request

This PR makes the following changes:
- Refactors inserting items into foam darts into a component on items
that can be inserted into darts
- Adds the aforementioned component to pens
- Provides an inspection tip for how to modify a foam dart
- Gives different pen types specific behavior when used in a foam dart

Pens typically give a foam dart 5 brute and 50% embed chance (affected
by falloff). The following types of pens give the specified properties
(usually directly derived from the pen's stats and additional
functions):
- Red pen (and four-color pen set to red): Slightly faster dart
- Captain's fountain pen: Slightly faster dart, and 75% base embed
chance
- Sleepypen: Tries to inject its reagents into the hit mob, but doesn't
penetrate thick clothing like syringe guns do
- Energy Dagger: 35 brute, 100% base embed chance, and slightly faster
dart
- Survival Pen: Mines rocks on impact
- Fine Tip Pen (if someone somehow manages to get one): 100 bare wound
bonus and 9000 demolition modifier

## Why It's Good For The Game

Expands the emergent gameplay possibilities of using pens in foam darts.

While there are balance risks involved with traitors being able to buy
the equivalent of reusable 45u syringe shots and 35 brute bullets, you
are not likely to get your pen back once it hits its target, unless you
somehow have the recall spell and have bound the pen to it. There are
probably more TC-efficient ways to achieve comparable projectile
weaponry, but foam dart guns have an air of subtlety to them... at least
until your skin is pierced by a pointy writing implement that may also
be something more deadly. If maintainers still have balance concerns,
please let me know.

## Changelog

:cl:
add: Certain types of pens now function like you expect they would when
inserted into a foam dart
qol: Examining a foam dart closely will show you how to modify it, or
what it is modified with
/:cl:

* Different pen types have unique behavior when used in foam darts.

---------

Co-authored-by: SkyratBot <[email protected]>
Co-authored-by: Y0SH1M4S73R <[email protected]>
  • Loading branch information
3 people authored Nov 23, 2023
1 parent fbdd283 commit 365bf82
Show file tree
Hide file tree
Showing 19 changed files with 403 additions and 56 deletions.
38 changes: 36 additions & 2 deletions code/__DEFINES/dcs/signals/signals_object.dm
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
/// from /obj/item/toy/crayon/spraycan/afterattack: (user, spraycan, color_is_dark)
#define COMSIG_OBJ_PAINTED "obj_painted"
#define DONT_USE_SPRAYCAN_CHARGES (1<<0)
/// from /obj/obj_reskin: (mob/user, skin)
#define COMSIG_OBJ_RESKIN "obj_reskin"

// /obj/machinery signals

Expand Down Expand Up @@ -361,9 +363,9 @@

// /obj/projectile signals (sent to the firer)

///from base of /obj/projectile/proc/on_hit(), like COMSIG_PROJECTILE_ON_HIT but on the projectile itself and with the hit limb (if any): (atom/movable/firer, atom/target, angle, hit_limb)
///from base of /obj/projectile/proc/on_hit(), like COMSIG_PROJECTILE_ON_HIT but on the projectile itself and with the hit limb (if any): (atom/movable/firer, atom/target, angle, hit_limb, blocked)
#define COMSIG_PROJECTILE_SELF_ON_HIT "projectile_self_on_hit"
///from base of /obj/projectile/proc/on_hit(): (atom/movable/firer, atom/target, angle, hit_limb)
///from base of /obj/projectile/proc/on_hit(): (atom/movable/firer, atom/target, angle, hit_limb, blocked)
#define COMSIG_PROJECTILE_ON_HIT "projectile_on_hit"
///from base of /obj/projectile/proc/fire(): (obj/projectile, atom/original_target)
#define COMSIG_PROJECTILE_BEFORE_FIRE "projectile_before_fire"
Expand All @@ -387,6 +389,9 @@
///sent to targets during the process_hit proc of projectiles
#define COMSIG_FIRE_CASING "fire_casing"

///from the base of /obj/item/ammo_casing/ready_proj() : (atom/target, mob/living/user, quiet, zone_override, atom/fired_from)
#define COMSIG_CASING_READY_PROJECTILE "casing_ready_projectile"

///sent to the projectile after an item is spawned by the projectile_drop element: (new_item)
#define COMSIG_PROJECTILE_ON_SPAWN_DROP "projectile_on_spawn_drop"
///sent to the projectile when spawning the item (shrapnel) that may be embedded: (new_item)
Expand Down Expand Up @@ -448,6 +453,12 @@
#define COMSIG_ITEM_AFTERATTACK_SECONDARY "item_afterattack_secondary"
///from base of obj/item/attack_qdeleted(): (atom/target, mob/user, params)
#define COMSIG_ITEM_ATTACK_QDELETED "item_attack_qdeleted"
///from base of obj/item/embedded(): (atom/target, obj/item/bodypart/part)
#define COMSIG_ITEM_EMBEDDED "item_embedded"
///from base of datum/component/embedded/safeRemove(): (mob/living/carbon/victim)
#define COMSIG_ITEM_UNEMBEDDED "item_unembedded"
/// from base of obj/item/failedEmbed()
#define COMSIG_ITEM_FAILED_EMBED "item_failed_embed"

///from /obj/item/assembly/proc/pulsed(mob/pulser)
#define COMSIG_ASSEMBLY_PULSED "assembly_pulsed"
Expand Down Expand Up @@ -481,3 +492,26 @@

/// from /obj/structure/cursed_slot_machine/determine_victor() when someone finally wins.
#define COMSIG_GLOB_CURSED_SLOT_MACHINE_WON "cursed_slot_machine_won"

/// from /datum/component/dart_insert/add_to_dart() : (obj/item/ammo_casing, mob/user)
#define COMSIG_DART_INSERT_ADDED "dart_insert_added"

/// from /datum/component/dart_insert/remove_from_dart() : (obj/ammo_casing/dart, mob/user)
#define COMSIG_DART_INSERT_REMOVED "dart_insert_removed"

/**
* from /datum/component/dart_insert/get_dart_var_modifiers() : (list/out_modifiers)
*
* valid indices for `out_modifiers` are:
* - `damage`: number
* - `speed`: number
* - `armour_penetration`: number
* - `wound_bonus`: number
* - `bare_wound_bonus`: number
* - `demolition_mod`: number
* - `embedding`: list with embedding params
*/
#define COMSIG_DART_INSERT_GET_VAR_MODIFIERS "dart_insert_get_var_modifiers"

/// from /datum/component/dart_insert/on_reskin()
#define COMSIG_DART_INSERT_PARENT_RESKINNED "dart_insert_parent_reskinned"
2 changes: 2 additions & 0 deletions code/__DEFINES/traits/declarations.dm
Original file line number Diff line number Diff line change
Expand Up @@ -976,4 +976,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// Trait given to mobs that we do not want to mindswap
#define TRAIT_NO_MINDSWAP "no_mindswap"

/// Trait given to foam darts that have an insert in them
#define TRAIT_DART_HAS_INSERT "dart_has_insert"
// END TRAIT DEFINES
5 changes: 5 additions & 0 deletions code/__HELPERS/maths.dm
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,8 @@
return max(new_value, threshold)
if(sign == -1)
return min(new_value, threshold * -1)

/// Takes two values x and y, and returns 1/((1/x) + y)
/// Useful for providing an additive modifier to a value that is used as a divisor, such as `/obj/projectile/var/speed`
/proc/reciprocal_add(x, y)
return 1/((1/x)+y)
3 changes: 3 additions & 0 deletions code/_globalvars/traits/_traits.dm
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,9 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_UNCATCHABLE" = TRAIT_UNCATCHABLE,
"TRAIT_WIELDED" = TRAIT_WIELDED,
),
/obj/item/ammo_casing = list(
"TRAIT_DART_HAS_INSERT" = TRAIT_DART_HAS_INSERT,
),
/obj/item/bodypart = list(
"TRAIT_DISABLED_BY_WOUND" = TRAIT_DISABLED_BY_WOUND,
"TRAIT_IGNORED_BY_LIVING_FLESH" = TRAIT_IGNORED_BY_LIVING_FLESH,
Expand Down
161 changes: 161 additions & 0 deletions code/datums/components/dart_insert.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/**
* Component for allowing items to be inserted into foam darts.
* The parent can register signal handlers for `COMSIG_DART_INSERT_ADDED`,
* `COMSIG_DART_INSERT_REMOVED` to define custom behavior for when the item
* is added to/removed from a dart, and `COMSIG_DART_INSERT_GET_VAR_MODIFIERS`
* to define the modifications the item makes to the vars of the fired projectile.
*/
/datum/component/dart_insert
/// List for tracking the modifications this component has made to the vars of the containing projectile
var/list/var_modifiers
/// A reference to the ammo casing this component's parent was inserted into
var/obj/item/ammo_casing/holder_casing
/// A reference to the projectile this component's parent was inserted into
var/obj/projectile/holder_projectile
/// The icon file used for the overlay applied over the containing ammo casing
var/casing_overlay_icon
/// The icon state used for the overlay applied over the containing ammo casing
var/casing_overlay_icon_state
/// The icon file used for the overlay applied over the containing projectile
var/projectile_overlay_icon
/// The icon state used for the overlay applied over the containing projectile
var/projectile_overlay_icon_state

/datum/component/dart_insert/Initialize(_casing_overlay_icon, _casing_overlay_icon_state, _projectile_overlay_icon, _projectile_overlay_icon_state)
if(!isitem(parent))
return COMPONENT_INCOMPATIBLE
casing_overlay_icon = _casing_overlay_icon
casing_overlay_icon_state = _casing_overlay_icon_state
projectile_overlay_icon = _projectile_overlay_icon
projectile_overlay_icon_state = _projectile_overlay_icon_state

/datum/component/dart_insert/RegisterWithParent()
. = ..()
RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, PROC_REF(on_preattack))
RegisterSignal(parent, COMSIG_OBJ_RESKIN, PROC_REF(on_reskin))

/datum/component/dart_insert/UnregisterFromParent()
. = ..()
var/obj/item/parent_item = parent
var/parent_loc = parent_item.loc
if(parent_loc && (parent_loc == holder_casing || parent_loc == holder_projectile))
parent_item.forceMove(get_turf(parent_item))
remove_from_dart(holder_casing, holder_projectile)
UnregisterSignal(parent, COMSIG_ITEM_PRE_ATTACK)

/datum/component/dart_insert/proc/on_preattack(datum/source, atom/target, mob/user, params)
SIGNAL_HANDLER
var/obj/item/ammo_casing/foam_dart/dart = target
if(!istype(dart))
return
if(!dart.modified)
to_chat(user, span_warning("The safety cap prevents you from inserting [parent] into [dart]."))
return COMPONENT_CANCEL_ATTACK_CHAIN
if(HAS_TRAIT(dart, TRAIT_DART_HAS_INSERT))
to_chat(user, span_warning("There's already something in [dart]."))
return COMPONENT_CANCEL_ATTACK_CHAIN
add_to_dart(dart, user)
return COMPONENT_CANCEL_ATTACK_CHAIN

/datum/component/dart_insert/proc/on_reskin(datum/source, mob/user, skin)
SIGNAL_HANDLER
SEND_SIGNAL(parent, COMSIG_DART_INSERT_PARENT_RESKINNED)

/datum/component/dart_insert/proc/add_to_dart(obj/item/ammo_casing/dart, mob/user)
var/obj/projectile/dart_projectile = dart.loaded_projectile
var/obj/item/parent_item = parent
if(user)
if(!user.transferItemToLoc(parent_item, dart_projectile))
return
to_chat(user, span_notice("You insert [parent_item] into [dart]."))
else
parent_item.forceMove(dart_projectile)
ADD_TRAIT(dart, TRAIT_DART_HAS_INSERT, REF(src))
RegisterSignal(dart, COMSIG_ITEM_ATTACK_SELF, PROC_REF(on_dart_attack_self))
RegisterSignal(dart, COMSIG_ATOM_EXAMINE_MORE, PROC_REF(on_dart_examine_more))
RegisterSignals(parent, list(COMSIG_QDELETING, COMSIG_MOVABLE_MOVED), PROC_REF(on_leave_dart))
RegisterSignal(dart, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_casing_update_overlays))
RegisterSignal(dart_projectile, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_projectile_update_overlays))
RegisterSignals(dart_projectile, list(COMSIG_PROJECTILE_ON_SPAWN_DROP, COMSIG_PROJECTILE_ON_SPAWN_EMBEDDED), PROC_REF(on_spawn_drop))
apply_var_modifiers(dart_projectile)
dart.harmful = dart_projectile.damage > 0 || dart_projectile.wound_bonus > 0 || dart_projectile.bare_wound_bonus > 0
SEND_SIGNAL(parent, COMSIG_DART_INSERT_ADDED, dart)
dart.update_appearance()
dart_projectile.update_appearance()
holder_casing = dart
holder_projectile = dart_projectile

/datum/component/dart_insert/proc/remove_from_dart(obj/item/ammo_casing/dart, obj/projectile/projectile, mob/user)
holder_casing = null
holder_projectile = null
if(istype(dart))
UnregisterSignal(dart, list(COMSIG_ITEM_ATTACK_SELF, COMSIG_ATOM_EXAMINE_MORE, COMSIG_ATOM_UPDATE_OVERLAYS))
REMOVE_TRAIT(dart, TRAIT_DART_HAS_INSERT, REF(src))
dart.update_appearance()
if(istype(projectile))
remove_var_modifiers(projectile)
UnregisterSignal(projectile, list(COMSIG_PROJECTILE_ON_SPAWN_DROP, COMSIG_PROJECTILE_ON_SPAWN_EMBEDDED, COMSIG_ATOM_UPDATE_OVERLAYS))
if(dart?.loaded_projectile == projectile)
dart.harmful = projectile.damage > 0 || projectile.wound_bonus > 0 || projectile.bare_wound_bonus > 0
projectile.update_appearance()
SEND_SIGNAL(parent, COMSIG_DART_INSERT_REMOVED, dart, projectile, user)
UnregisterSignal(parent, list(COMSIG_QDELETING, COMSIG_MOVABLE_MOVED))
if(user)
INVOKE_ASYNC(user, TYPE_PROC_REF(/mob, put_in_hands), parent)
to_chat(user, span_notice("You remove [parent] from [dart]."))

/datum/component/dart_insert/proc/on_dart_attack_self(datum/source, mob/user)
SIGNAL_HANDLER
remove_from_dart(holder_casing, holder_projectile, user)

/datum/component/dart_insert/proc/on_dart_examine_more(datum/source, mob/user, list/examine_list)
var/obj/item/parent_item = parent
examine_list += span_notice("You can see a [parent_item.name] inserted into it.")

/datum/component/dart_insert/proc/on_leave_dart()
SIGNAL_HANDLER
remove_from_dart(holder_casing, holder_projectile)

/datum/component/dart_insert/proc/on_spawn_drop(datum/source, obj/item/ammo_casing/new_casing)
SIGNAL_HANDLER
UnregisterSignal(parent, list(COMSIG_QDELETING, COMSIG_MOVABLE_MOVED))
add_to_dart(new_casing)

/datum/component/dart_insert/proc/on_casing_update_overlays(datum/source, list/new_overlays)
SIGNAL_HANDLER
new_overlays += mutable_appearance(casing_overlay_icon, casing_overlay_icon_state)

/datum/component/dart_insert/proc/on_projectile_update_overlays(datum/source, list/new_overlays)
SIGNAL_HANDLER
new_overlays += mutable_appearance(projectile_overlay_icon, projectile_overlay_icon_state)

/datum/component/dart_insert/proc/apply_var_modifiers(obj/projectile/projectile)
LAZYINITLIST(var_modifiers)
SEND_SIGNAL(parent, COMSIG_DART_INSERT_GET_VAR_MODIFIERS, var_modifiers)
projectile.damage += var_modifiers["damage"]
if(var_modifiers["speed"])
var_modifiers["speed"] = reciprocal_add(projectile.speed, var_modifiers["speed"]) - projectile.speed
projectile.speed += var_modifiers["speed"]
projectile.armour_penetration += var_modifiers["armour_penetration"]
projectile.wound_bonus += var_modifiers["wound_bonus"]
projectile.bare_wound_bonus += var_modifiers["bare_wound_bonus"]
projectile.demolition_mod += var_modifiers["demolition_mod"]
if(islist(var_modifiers["embedding"]))
var/list/embed_params = var_modifiers["embedding"]
for(var/embed_param in embed_params - "ignore_throwspeed_threshold")
LAZYADDASSOC(projectile.embedding, embed_param, embed_params[embed_param])
projectile.updateEmbedding()

/datum/component/dart_insert/proc/remove_var_modifiers(obj/projectile/projectile)
projectile.damage -= var_modifiers["damage"]
projectile.speed -= var_modifiers["speed"]
projectile.armour_penetration -= var_modifiers["armour_penetration"]
projectile.wound_bonus -= var_modifiers["wound_bonus"]
projectile.bare_wound_bonus -= var_modifiers["bare_wound_bonus"]
projectile.demolition_mod -= var_modifiers["demolition_mod"]
if(islist(var_modifiers["embedding"]))
var/list/embed_params = var_modifiers["embedding"]
for(var/embed_param in embed_params - "ignore_throwspeed_threshold")
LAZYADDASSOC(projectile.embedding, embed_param, -embed_params[embed_param])
projectile.updateEmbedding()
var_modifiers.Cut()
1 change: 1 addition & 0 deletions code/datums/components/embedded.dm
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@
limb._unembed_object(weapon)
UnregisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING)) // have to do it here otherwise we trigger weaponDeleted()

SEND_SIGNAL(weapon, COMSIG_ITEM_UNEMBEDDED, victim)
if(!weapon.unembedded()) // if it hasn't deleted itself due to drop del
UnregisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING))
if(to_hands)
Expand Down
10 changes: 9 additions & 1 deletion code/datums/elements/caseless.dm
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,23 @@
if(!isammocasing(target))
return ELEMENT_INCOMPATIBLE
src.reusable = reusable
RegisterSignal(target, COMSIG_CASING_READY_PROJECTILE, PROC_REF(on_ready_projectile))
RegisterSignal(target, COMSIG_FIRE_CASING, PROC_REF(on_fired_casing))

/datum/element/caseless/proc/on_fired_casing(obj/item/ammo_casing/shell, atom/target, mob/living/user, fired_from, randomspread, spread, zone_override, params, distro, obj/projectile/proj)
/datum/element/caseless/proc/on_ready_projectile(obj/item/ammo_casing/shell, atom/target, mob/living/user, quiet, zone_override, atom/fired_from)
SIGNAL_HANDLER
var/obj/projectile/proj = shell.loaded_projectile
if(isnull(proj))
return
if(reusable)
if(!ispath(proj.shrapnel_type))
proj.shrapnel_type = shell.type
proj.updateEmbedding()
proj.AddElement(/datum/element/projectile_drop, shell.type)

/datum/element/caseless/proc/on_fired_casing(obj/item/ammo_casing/shell, atom/target, mob/living/user, fired_from, randomspread, spread, zone_override, params, distro, obj/projectile/proj)
SIGNAL_HANDLER

if(isgun(fired_from))
var/obj/item/gun/shot_from = fired_from
if(shot_from.chambered == shell)
Expand Down
2 changes: 2 additions & 0 deletions code/datums/mutations/tongue_spike.dm
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
unembedded()

/obj/item/hardened_spike/embedded(atom/target)
. = ..()
if(isbodypart(target))
missed = FALSE

Expand Down Expand Up @@ -121,6 +122,7 @@
var/embedded_once_alread = FALSE

/obj/item/hardened_spike/chem/embedded(mob/living/carbon/human/embedded_mob)
. = ..()
if(embedded_once_alread)
return
embedded_once_alread = TRUE
Expand Down
5 changes: 4 additions & 1 deletion code/game/objects/items.dm
Original file line number Diff line number Diff line change
Expand Up @@ -1177,7 +1177,8 @@
return ..()

/obj/item/proc/embedded(atom/embedded_target, obj/item/bodypart/part)
return
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_ITEM_EMBEDDED, embedded_target, part)

/obj/item/proc/unembedded()
if(item_flags & DROPDEL && !QDELETED(src))
Expand Down Expand Up @@ -1206,6 +1207,8 @@

///In case we want to do something special (like self delete) upon failing to embed in something.
/obj/item/proc/failedEmbed()
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_ITEM_FAILED_EMBED)
if(item_flags & DROPDEL && !QDELETED(src))
qdel(src)

Expand Down
7 changes: 4 additions & 3 deletions code/game/objects/objs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ GLOBAL_LIST_EMPTY(objects_by_id_tag)
* Arguments:
* * M The mob choosing a reskin option
*/
/obj/proc/reskin_obj(mob/M)
/obj/proc/reskin_obj(mob/user)
if(!LAZYLEN(unique_reskin))
return

Expand All @@ -302,14 +302,15 @@ GLOBAL_LIST_EMPTY(objects_by_id_tag)
items += list("[reskin_option]" = item_image)
sort_list(items)

var/pick = show_radial_menu(M, src, items, custom_check = CALLBACK(src, PROC_REF(check_reskin_menu), M), radius = 38, require_near = TRUE)
var/pick = show_radial_menu(user, src, items, custom_check = CALLBACK(src, PROC_REF(check_reskin_menu), user), radius = 38, require_near = TRUE)
if(!pick)
return
if(!unique_reskin[pick])
return
current_skin = pick
icon_state = unique_reskin[pick]
to_chat(M, "[src] is now skinned as '[pick].'")
to_chat(user, "[src] is now skinned as '[pick].'")
SEND_SIGNAL(src, COMSIG_OBJ_RESKIN, user, pick)

/**
* Checks if we are allowed to interact with a radial menu for reskins
Expand Down
Loading

0 comments on commit 365bf82

Please sign in to comment.