From 365bf82f961830fe6c7b9cb4dcc96001963a2482 Mon Sep 17 00:00:00 2001 From: Iajret Creature <122297233+Steals-The-PRs@users.noreply.github.com> Date: Thu, 23 Nov 2023 22:36:51 +0300 Subject: [PATCH] [MIRROR] Different pen types have unique behavior when used in foam darts. [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 <59378654+SkyratBot@users.noreply.github.com> Co-authored-by: Y0SH1M4S73R --- code/__DEFINES/dcs/signals/signals_object.dm | 38 ++++- code/__DEFINES/traits/declarations.dm | 2 + code/__HELPERS/maths.dm | 5 + code/_globalvars/traits/_traits.dm | 3 + code/datums/components/dart_insert.dm | 161 ++++++++++++++++++ code/datums/components/embedded.dm | 1 + code/datums/elements/caseless.dm | 10 +- code/datums/mutations/tongue_spike.dm | 2 + code/game/objects/items.dm | 5 +- code/game/objects/objs.dm | 7 +- code/modules/paperwork/pen.dm | 153 ++++++++++++++++- .../modules/projectiles/ammunition/_firing.dm | 1 + .../projectiles/ammunition/ballistic/foam.dm | 47 +++-- .../guns/ballistic/bows/bow_arrows.dm | 3 +- code/modules/projectiles/projectile.dm | 4 +- .../projectile/bullets/foam_dart.dm | 13 +- .../projectiles/projectile/bullets/rifle.dm | 3 +- icons/obj/weapons/guns/toy.dmi | Bin 869 -> 2855 bytes tgstation.dme | 1 + 19 files changed, 403 insertions(+), 56 deletions(-) create mode 100644 code/datums/components/dart_insert.dm diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index 4998f10849e..c1d9e88786a 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -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 @@ -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" @@ -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) @@ -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" @@ -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" diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 536b1023a5a..d9db85b32da 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -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 diff --git a/code/__HELPERS/maths.dm b/code/__HELPERS/maths.dm index 116fb34fad5..c28357eb478 100644 --- a/code/__HELPERS/maths.dm +++ b/code/__HELPERS/maths.dm @@ -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) diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index b1d1c5eaac3..6af47e3ab10 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -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, diff --git a/code/datums/components/dart_insert.dm b/code/datums/components/dart_insert.dm new file mode 100644 index 00000000000..03d1d689357 --- /dev/null +++ b/code/datums/components/dart_insert.dm @@ -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() diff --git a/code/datums/components/embedded.dm b/code/datums/components/embedded.dm index f4d7b5d7369..b62121d30d0 100644 --- a/code/datums/components/embedded.dm +++ b/code/datums/components/embedded.dm @@ -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) diff --git a/code/datums/elements/caseless.dm b/code/datums/elements/caseless.dm index a8a1d3df3e4..587a32f2b30 100644 --- a/code/datums/elements/caseless.dm +++ b/code/datums/elements/caseless.dm @@ -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) diff --git a/code/datums/mutations/tongue_spike.dm b/code/datums/mutations/tongue_spike.dm index b33b78d7d1b..e6249041250 100644 --- a/code/datums/mutations/tongue_spike.dm +++ b/code/datums/mutations/tongue_spike.dm @@ -73,6 +73,7 @@ unembedded() /obj/item/hardened_spike/embedded(atom/target) + . = ..() if(isbodypart(target)) missed = FALSE @@ -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 diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index d592cd0790b..55cdf19f960 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -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)) @@ -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) diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 1c34e75c3d1..a7263520ad8 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -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 @@ -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 diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm index 69af56d3419..3750acddc42 100644 --- a/code/modules/paperwork/pen.dm +++ b/code/modules/paperwork/pen.dm @@ -31,6 +31,32 @@ var/requires_gravity = TRUE // can you use this to write in zero-g embedding = list(embed_chance = 50) sharpness = SHARP_POINTY + var/dart_insert_icon = 'icons/obj/weapons/guns/toy.dmi' + var/dart_insert_casing_icon_state = "overlay_pen" + var/dart_insert_projectile_icon_state = "overlay_pen_proj" + +/obj/item/pen/Initialize(mapload) + . = ..() + AddComponent(/datum/component/dart_insert, dart_insert_icon, dart_insert_casing_icon_state, dart_insert_icon, dart_insert_projectile_icon_state) + RegisterSignal(src, COMSIG_DART_INSERT_ADDED, PROC_REF(on_inserted_into_dart)) + RegisterSignal(src, COMSIG_DART_INSERT_REMOVED, PROC_REF(on_removed_from_dart)) + RegisterSignal(src, COMSIG_DART_INSERT_GET_VAR_MODIFIERS, PROC_REF(get_dart_var_modifiers)) + +/obj/item/pen/proc/on_inserted_into_dart(datum/source, obj/projectile/dart, mob/user, embedded = FALSE) + SIGNAL_HANDLER + +/obj/item/pen/proc/get_dart_var_modifiers(datum/source, list/modifiers) + SIGNAL_HANDLER + modifiers["damage"] = max(5, throwforce) + modifiers["speed"] = max(0, throw_speed - 3) + modifiers["embedding"] = embedding + modifiers["armour_penetration"] = armour_penetration + modifiers["wound_bonus"] = wound_bonus + modifiers["bare_wound_bonus"] = bare_wound_bonus + modifiers["demolition_mod"] = demolition_mod + +/obj/item/pen/proc/on_removed_from_dart(datum/source, obj/projectile/dart, mob/user) + SIGNAL_HANDLER /obj/item/pen/suicide_act(mob/living/user) user.visible_message(span_suicide("[user] is scribbling numbers all over [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit sudoku...")) @@ -69,7 +95,7 @@ if("#FF0000") colour = "#00FF00" chosen_color = "green" - throw_speed = initial(throw_speed) + throw_speed-- if("#00FF00") colour = "#0000FF" chosen_color = "blue" @@ -84,6 +110,8 @@ icon_state = "pen-fountain" font = FOUNTAIN_PEN_FONT requires_gravity = FALSE // fancy spess pens + dart_insert_casing_icon_state = "overlay_fountainpen" + dart_insert_projectile_icon_state = "overlay_fountainpen_proj" /obj/item/pen/charcoal name = "charcoal stylus" @@ -113,13 +141,23 @@ custom_materials = list(/datum/material/gold = SMALL_MATERIAL_AMOUNT*7.5) sharpness = SHARP_EDGED resistance_flags = FIRE_PROOF - unique_reskin = list("Oak" = "pen-fountain-o", - "Gold" = "pen-fountain-g", - "Rosewood" = "pen-fountain-r", - "Black and Silver" = "pen-fountain-b", - "Command Blue" = "pen-fountain-cb" - ) + unique_reskin = list( + "Oak" = "pen-fountain-o", + "Gold" = "pen-fountain-g", + "Rosewood" = "pen-fountain-r", + "Black and Silver" = "pen-fountain-b", + "Command Blue" = "pen-fountain-cb" + ) embedding = list("embed_chance" = 75) + dart_insert_casing_icon_state = "overlay_fountainpen_gold" + dart_insert_projectile_icon_state = "overlay_fountainpen_gold_proj" + var/list/overlay_reskin = list( + "Oak" = "overlay_fountainpen_gold", + "Gold" = "overlay_fountainpen_gold", + "Rosewood" = "overlay_fountainpen_gold", + "Black and Silver" = "overlay_fountainpen", + "Command Blue" = "overlay_fountainpen_gold" + ) /obj/item/pen/fountain/captain/Initialize(mapload) . = ..() @@ -128,12 +166,19 @@ effectiveness = 115, \ ) //the pen is mightier than the sword + RegisterSignal(src, COMSIG_DART_INSERT_PARENT_RESKINNED, PROC_REF(reskin_dart_insert)) /obj/item/pen/fountain/captain/reskin_obj(mob/M) ..() if(current_skin) desc = "It's an expensive [current_skin] fountain pen. The nib is quite sharp." +/obj/item/pen/fountain/captain/proc/reskin_dart_insert(datum/component/dart_insert/insert_comp) + if(!istype(insert_comp)) //You really shouldn't be sending this signal from anything other than a dart_insert component + return + insert_comp.casing_overlay_icon_state = overlay_reskin[current_skin] + insert_comp.projectile_overlay_icon_state = "[overlay_reskin[current_skin]]_proj" + /obj/item/pen/attack_self(mob/living/carbon/user) . = ..() if(.) @@ -247,6 +292,23 @@ reagents.add_reagent(/datum/reagent/toxin/mutetoxin, 15) reagents.add_reagent(/datum/reagent/toxin/staminatoxin, 10) +/obj/item/pen/sleepy/on_inserted_into_dart(datum/source, obj/item/ammo_casing/dart, mob/user) + . = ..() + var/obj/projectile/proj = dart.loaded_projectile + RegisterSignal(proj, COMSIG_PROJECTILE_SELF_ON_HIT, PROC_REF(on_dart_hit)) + +/obj/item/pen/sleepy/on_removed_from_dart(datum/source, obj/item/ammo_casing/dart, obj/projectile/proj, mob/user) + . = ..() + if(istype(proj)) + UnregisterSignal(proj, COMSIG_PROJECTILE_SELF_ON_HIT) + +/obj/item/pen/sleepy/proc/on_dart_hit(datum/source, atom/movable/firer, atom/target, angle, hit_limb, blocked) + SIGNAL_HANDLER + var/mob/living/carbon/carbon_target = target + if(!istype(carbon_target) || blocked == 100) + return + if(carbon_target.can_inject(target_zone = hit_limb)) + reagents.trans_to(carbon_target, reagents.total_volume, transferred_by = firer, methods = INJECT) /* * (Alan) Edaggers */ @@ -262,6 +324,7 @@ light_power = 0.75 light_color = COLOR_SOFT_RED light_on = FALSE + dart_insert_projectile_icon_state = "overlay_edagger" /// The real name of our item when extended. var/hidden_name = "energy dagger" /// The real desc of our item when extended. @@ -287,6 +350,62 @@ RegisterSignal(src, COMSIG_TRANSFORMING_ON_TRANSFORM, PROC_REF(on_transform)) RegisterSignal(src, COMSIG_DETECTIVE_SCANNED, PROC_REF(on_scan)) +/obj/item/pen/edagger/on_inserted_into_dart(datum/source, obj/item/ammo_casing/dart, mob/user) + . = ..() + var/datum/component/transforming/transform_comp = GetComponent(/datum/component/transforming) + if(HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE)) + transform_comp.do_transform(src, user) + RegisterSignal(dart.loaded_projectile, COMSIG_PROJECTILE_FIRE, PROC_REF(on_containing_dart_fired)) + RegisterSignal(dart.loaded_projectile, COMSIG_PROJECTILE_ON_SPAWN_DROP, PROC_REF(on_containing_dart_drop)) + RegisterSignal(dart.loaded_projectile, COMSIG_PROJECTILE_ON_SPAWN_EMBEDDED, PROC_REF(on_containing_dart_embedded)) + +/obj/item/pen/edagger/on_removed_from_dart(datum/source, obj/item/ammo_casing/dart, obj/projectile/projectile, mob/user) + . = ..() + if(istype(dart)) + UnregisterSignal(dart, list(COMSIG_ITEM_UNEMBEDDED, COMSIG_ITEM_FAILED_EMBED)) + if(istype(projectile)) + UnregisterSignal(projectile, list(COMSIG_PROJECTILE_FIRE, COMSIG_PROJECTILE_ON_SPAWN_DROP, COMSIG_PROJECTILE_ON_SPAWN_EMBEDDED)) + +/obj/item/pen/edagger/get_dart_var_modifiers(datum/source, list/modifiers) + . = ..() + var/datum/component/transforming/transform_comp = GetComponent(/datum/component/transforming) + modifiers["damage"] = max(5, transform_comp.throwforce_on) + modifiers["speed"] = max(0, transform_comp.throw_speed_on - 3) + var/list/embed_params = modifiers["embedding"] + embed_params["embed_chance"] = 100 + +/obj/item/pen/edagger/proc/on_containing_dart_fired(obj/projectile/source) + SIGNAL_HANDLER + playsound(source, 'sound/weapons/saberon.ogg', 5, TRUE) + var/datum/component/transforming/transform_comp = GetComponent(/datum/component/transforming) + source.hitsound = transform_comp.hitsound_on + source.set_light(light_range, light_power, light_color, l_on = TRUE) + +/obj/item/pen/edagger/proc/on_containing_dart_drop(datum/source, obj/item/ammo_casing/new_casing) + SIGNAL_HANDLER + playsound(new_casing, 'sound/weapons/saberoff.ogg', 5, TRUE) + +/obj/item/pen/edagger/proc/on_containing_dart_embedded(datum/source, obj/item/ammo_casing/new_casing) + SIGNAL_HANDLER + RegisterSignal(new_casing, COMSIG_ITEM_UNEMBEDDED, PROC_REF(on_embedded_removed)) + RegisterSignal(new_casing, COMSIG_ITEM_FAILED_EMBED, PROC_REF(on_containing_dart_failed_embed)) + +/obj/item/pen/edagger/proc/on_containing_dart_failed_embed(obj/item/ammo_casing/source) + SIGNAL_HANDLER + playsound(source, 'sound/weapons/saberoff.ogg', 5, TRUE) + UnregisterSignal(source, list(COMSIG_ITEM_UNEMBEDDED, COMSIG_ITEM_FAILED_EMBED)) + +/obj/item/pen/edagger/proc/on_embedded_removed(obj/item/ammo_casing/source, mob/living/carbon/victim) + SIGNAL_HANDLER + playsound(source, 'sound/weapons/saberoff.ogg', 5, TRUE) + UnregisterSignal(source, list(COMSIG_ITEM_UNEMBEDDED, COMSIG_ITEM_FAILED_EMBED)) + victim.visible_message( + message = span_warning("The blade of the [hidden_name] retracts as the [source.name] is removed from [victim]!"), + self_message = span_warning("The blade of the [hidden_name] retracts as the [source.name] is removed from you!"), + blind_message = span_warning("You hear an energy blade retract!"), + vision_distance = 1 + ) + /obj/item/pen/edagger/suicide_act(mob/living/user) if(HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE)) user.visible_message(span_suicide("[user] forcefully rams the pen into their mouth!")) @@ -348,6 +467,25 @@ toolspeed = 10 //You will never willingly choose to use one of these over a shovel. font = FOUNTAIN_PEN_FONT colour = "#0000FF" + dart_insert_casing_icon_state = "overlay_survivalpen" + dart_insert_projectile_icon_state = "overlay_survivalpen_proj" + +/obj/item/pen/survival/on_inserted_into_dart(datum/source, obj/item/ammo_casing/dart, mob/user) + . = ..() + RegisterSignal(dart.loaded_projectile, COMSIG_PROJECTILE_SELF_ON_HIT, PROC_REF(on_dart_hit)) + +/obj/item/pen/survival/on_removed_from_dart(datum/source, obj/item/ammo_casing/dart, obj/projectile/proj, mob/user) + . = ..() + if(istype(proj)) + UnregisterSignal(proj, COMSIG_PROJECTILE_SELF_ON_HIT) + +/obj/item/pen/survival/proc/on_dart_hit(obj/projectile/source, atom/movable/firer, atom/target) + var/turf/target_turf = get_turf(target) + if(!target_turf) + target_turf = get_turf(src) + if(ismineralturf(target_turf)) + var/turf/closed/mineral/mineral_turf = target_turf + mineral_turf.gets_drilled(firer, TRUE) /obj/item/pen/destroyer name = "Fine Tipped Pen" @@ -362,6 +500,7 @@ desc = "A pen with an extendable screwdriver tip. This one has a yellow cap." icon_state = "pendriver" toolspeed = 1.2 // gotta have some downside + dart_insert_projectile_icon_state = "overlay_pendriver" /obj/item/pen/screwdriver/get_all_tool_behaviours() return list(TOOL_SCREWDRIVER) diff --git a/code/modules/projectiles/ammunition/_firing.dm b/code/modules/projectiles/ammunition/_firing.dm index dee2c087c68..009c52b227e 100644 --- a/code/modules/projectiles/ammunition/_firing.dm +++ b/code/modules/projectiles/ammunition/_firing.dm @@ -66,6 +66,7 @@ if(reagents && loaded_projectile.reagents) reagents.trans_to(loaded_projectile, reagents.total_volume, transferred_by = user) //For chemical darts/bullets qdel(reagents) + SEND_SIGNAL(src, COMSIG_CASING_READY_PROJECTILE, target, user, quiet, zone_override, fired_from) /obj/item/ammo_casing/proc/throw_proj(atom/target, turf/targloc, mob/living/user, params, spread, atom/fired_from) var/turf/curloc = get_turf(fired_from) diff --git a/code/modules/projectiles/ammunition/ballistic/foam.dm b/code/modules/projectiles/ammunition/ballistic/foam.dm index 21ceeb6918b..2895d74555b 100644 --- a/code/modules/projectiles/ammunition/ballistic/foam.dm +++ b/code/modules/projectiles/ammunition/ballistic/foam.dm @@ -9,6 +9,7 @@ custom_materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 0.1125) harmful = FALSE var/modified = FALSE + var/static/list/insertable_items_hint = list(/obj/item/pen) /obj/item/ammo_casing/foam_dart/Initialize(mapload) . = ..() @@ -18,47 +19,37 @@ . = ..() if(modified) icon_state = "[base_icon_state]_empty" - loaded_projectile?.icon_state = "[base_icon_state]_empty" + loaded_projectile?.icon_state = "[loaded_projectile.base_icon_state]_empty_proj" return icon_state = "[base_icon_state]" - loaded_projectile?.icon_state = "[loaded_projectile.base_icon_state]" + loaded_projectile?.icon_state = "[loaded_projectile.base_icon_state]_proj" /obj/item/ammo_casing/foam_dart/update_desc() . = ..() desc = "It's Donk or Don't! [modified ? "... Although, this one doesn't look too safe." : "Ages 8 and up."]" -/obj/item/ammo_casing/foam_dart/attackby(obj/item/A, mob/user, params) - var/obj/projectile/bullet/foam_dart/FD = loaded_projectile - if (A.tool_behaviour == TOOL_SCREWDRIVER && !modified) +/obj/item/ammo_casing/foam_dart/examine_more(mob/user) + . = ..() + if(!HAS_TRAIT(src, TRAIT_DART_HAS_INSERT)) + var/list/type_initial_names = list() + for(var/type in insertable_items_hint) + var/obj/item/type_item = type + type_initial_names += "\a [initial(type_item.name)]" + . += span_notice("[modified ? "You can" : "If you removed the safety cap with a screwdriver, you could"] insert a small item\ + [length(type_initial_names) ? ", such as [english_list(type_initial_names, and_text = "or ", final_comma_text = ", ")]" : ""].") + + +/obj/item/ammo_casing/foam_dart/attackby(obj/item/attacking_item, mob/user, params) + var/obj/projectile/bullet/foam_dart/dart = loaded_projectile + if (attacking_item.tool_behaviour == TOOL_SCREWDRIVER && !modified) modified = TRUE - FD.modified = TRUE - FD.damage_type = BRUTE + dart.modified = TRUE + dart.damage_type = BRUTE to_chat(user, span_notice("You pop the safety cap off [src].")) update_appearance() - else if (istype(A, /obj/item/pen)) - if(modified) - if(!FD.pen) - harmful = TRUE - if(!user.transferItemToLoc(A, FD)) - return - FD.pen = A - FD.damage = 5 - to_chat(user, span_notice("You insert [A] into [src].")) - else - to_chat(user, span_warning("There's already something in [src].")) - else - to_chat(user, span_warning("The safety cap prevents you from inserting [A] into [src].")) else return ..() -/obj/item/ammo_casing/foam_dart/attack_self(mob/living/user) - var/obj/projectile/bullet/foam_dart/FD = loaded_projectile - if(FD.pen) - FD.damage = initial(FD.damage) - user.put_in_hands(FD.pen) - to_chat(user, span_notice("You remove [FD.pen] from [src].")) - FD.pen = null - /obj/item/ammo_casing/foam_dart/riot name = "riot foam dart" desc = "Whose smart idea was it to use toys as crowd control? Ages 18 and up." diff --git a/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm b/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm index a3bb54a07e0..713790049b5 100644 --- a/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm +++ b/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm @@ -32,6 +32,7 @@ damage = 50 speed = 1 range = 25 + shrapnel_type = null embedding = list( embed_chance = 90, fall_chance = 2, @@ -42,7 +43,6 @@ jostle_pain_mult = 3, rip_time = 1 SECONDS ) - shrapnel_type = /obj/item/ammo_casing/arrow /// holy arrows /obj/item/ammo_casing/arrow/holy @@ -59,7 +59,6 @@ desc = "Here it comes, cultist scum!" icon_state = "holy_arrow_projectile" damage = 20 //still a lot but this is roundstart gear so far less - shrapnel_type =/obj/item/ammo_casing/arrow/holy embedding = list( embed_chance = 50, fall_chance = 2, diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 18c0b557f88..a7beb1a7102 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -282,8 +282,8 @@ var/mob/living/L = target hit_limb_zone = L.check_hit_limb_zone_name(def_zone) if(fired_from) - SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_ON_HIT, firer, target, Angle, hit_limb_zone) - SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_ON_HIT, firer, target, Angle, hit_limb_zone) + SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_ON_HIT, firer, target, Angle, hit_limb_zone, blocked) + SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_ON_HIT, firer, target, Angle, hit_limb_zone, blocked) if(QDELETED(src)) // in case one of the above signals deleted the projectile for whatever reason return BULLET_ACT_BLOCK diff --git a/code/modules/projectiles/projectile/bullets/foam_dart.dm b/code/modules/projectiles/projectile/bullets/foam_dart.dm index 6d4cffd4524..3f086166e6a 100644 --- a/code/modules/projectiles/projectile/bullets/foam_dart.dm +++ b/code/modules/projectiles/projectile/bullets/foam_dart.dm @@ -5,27 +5,24 @@ damage_type = OXY icon = 'icons/obj/weapons/guns/toy.dmi' icon_state = "foamdart_proj" - base_icon_state = "foamdart_proj" + base_icon_state = "foamdart" range = 10 + shrapnel_type = null embedding = null var/modified = FALSE var/obj/item/pen/pen = null /obj/projectile/bullet/foam_dart/Initialize(mapload) . = ..() - RegisterSignal(src, COMSIG_PROJECTILE_ON_SPAWN_DROP, PROC_REF(handle_drop)) + RegisterSignals(src, list(COMSIG_PROJECTILE_ON_SPAWN_DROP, COMSIG_PROJECTILE_ON_SPAWN_EMBEDDED), PROC_REF(handle_drop)) /obj/projectile/bullet/foam_dart/proc/handle_drop(datum/source, obj/item/ammo_casing/foam_dart/newcasing) SIGNAL_HANDLER newcasing.modified = modified + newcasing.update_appearance() var/obj/projectile/bullet/foam_dart/newdart = newcasing.loaded_projectile newdart.modified = modified newdart.damage_type = damage_type - if(pen) - newdart.pen = pen - pen.forceMove(newdart) - pen = null - newdart.damage = 5 newdart.update_appearance() /obj/projectile/bullet/foam_dart/Destroy() @@ -35,5 +32,5 @@ /obj/projectile/bullet/foam_dart/riot name = "riot foam dart" icon_state = "foamdart_riot_proj" - base_icon_state = "foamdart_riot_proj" + base_icon_state = "foamdart_riot" stamina = 25 diff --git a/code/modules/projectiles/projectile/bullets/rifle.dm b/code/modules/projectiles/projectile/bullets/rifle.dm index de7e59facc3..d76b2de9d6a 100644 --- a/code/modules/projectiles/projectile/bullets/rifle.dm +++ b/code/modules/projectiles/projectile/bullets/rifle.dm @@ -46,7 +46,7 @@ bare_wound_bonus = 80 embedding = list(embed_chance=100, fall_chance=3, jostle_chance=4, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.4, pain_mult=5, jostle_pain_mult=6, rip_time=10) wound_falloff_tile = -5 - shrapnel_type = /obj/item/ammo_casing/harpoon + shrapnel_type = null // Rebar (Rebar Crossbow) /obj/projectile/bullet/rebar @@ -75,4 +75,3 @@ embedding = list(embed_chance=80, fall_chance=1, jostle_chance=3, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.4, pain_mult=3, jostle_pain_mult=2, rip_time=14) embed_falloff_tile = -3 shrapnel_type = /obj/item/stack/rods - diff --git a/icons/obj/weapons/guns/toy.dmi b/icons/obj/weapons/guns/toy.dmi index 83a85e7e447c793b1c5e295f3d85fceeb5388302..3f7e509b699e69710cd172f4642260940dcbb3b6 100644 GIT binary patch literal 2855 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 literal 869 zcmV-r1DgDaP)005u}0{{R3yb+fl0000yP)t-sz`(#2 zSPwIg5*T|10A~L)ng3=I5`ZWuSPl*xF&bxQXCWLMHZLz67#RPVGylvp06qXTE-wH7 zGr+*WA?R{500001bW%=J06^y0W&i*HsCrabbVOxyV{&P5bZKvH004NLQ&w!zk*nG@wu6K znM5lFIff*~#2XLtEJC3P5url7YH;w8W;lL5$_lQ2F5tul0Ky@_XTVRWrvLx~BS}O- zRCt{2n(dB)Fc3wpAFv1r>-#_K;;LD@bmmrD=os%w_*I5EK^fC4n++j^q~n5=w)(?u zcLBC`3@Mn(GlKiWUcFXTf0Y$%j*WV)w(Ik+yKRu5;AuFi4Hj7sPnqzXDIz$AKH=SN zv?SQiuqD`k&*-fQPV>V%%n45O!#m6gPAlFa2oC2#ZLrAt=t6=nC|Cpvrt&K|efZvC zPH;8{O$oaBFNrfGxEX_#wjzWOLhN85zf-$MWG748&lLRbP*AU@;3o;9ZK;$`B0-ej zMGK;hy_8SR71T#`_N40dPY6ou`jo@@UG$WERdB8_`l}$n)9dpHD(NB4@AUdCg1y}R zx?0thcl;C7>N5$-=^@VVE@z1Lxdi3(5a)M#{R8N44oy%_4{`dW*FS*%D3S*GMM2KK zA_Ud?47`P)9M0kVPOr}-D2H=6ztiip2>J<~`zMIec2|zf%4uoq+n+-AehNbOP$X^c{pm^oN*#NX+Md2=jxS5JCtcgb+eVNz88>ng69y vH2;gCX#N*N(flt4=6|U$|09GDLWsu?W2#u;HO+7C00000NkvXXu0mjf