diff --git a/code/__DEFINES/_flags/obj_flags.dm b/code/__DEFINES/_flags/obj_flags.dm
index ac772c6e8e..5f5ea12aaa 100644
--- a/code/__DEFINES/_flags/obj_flags.dm
+++ b/code/__DEFINES/_flags/obj_flags.dm
@@ -16,6 +16,7 @@
#define SHOVABLE_ONTO (1<<13)//called on turf.shove_act() to consider whether an object should have a niche effect (defined in their own shove_act()) when someone is pushed onto it, or do a sanity CanPass() check.
#define EXAMINE_SKIP (1<<14) /// Makes the Examine proc not read out this item.
#define IN_STORAGE (1<<15) //is this item in the storage item, such as backpack? used for tooltips
+#define HAND_ITEM (1<<16) // If an item is just your hand (circled hand, slapper) and shouldn't block things like riding
/// Integrity defines for clothing (not flags but close enough)
#define CLOTHING_PRISTINE 0 // We have no damage on the clothing
diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index e92fa3ce34..060e8a2d4c 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -455,11 +455,11 @@
#define COMSIG_MINE_TRIGGERED "minegoboom" ///from [/obj/effect/mine/proc/triggermine]:
// Uncovered information
#define COMPONENT_DEEPSCAN_UNCOVERED_INFORMATION 1
-///Called when an item is being offered, from [/obj/item/proc/on_offered(mob/living/carbon/giver)]
+///Called when an item is being offered, from [/obj/item/proc/on_offered(mob/living/carbon/offerer)]
#define COMSIG_ITEM_OFFERING "item_offering"
///Interrupts the offer proc
#define COMPONENT_OFFER_INTERRUPT (1<<0)
-///Called when an someone tries accepting an offered item, from [/obj/item/proc/on_offer_taken(mob/living/carbon/giver, mob/living/carbon/taker)]
+///Called when an someone tries accepting an offered item, from [/obj/item/proc/on_offer_taken(mob/living/carbon/offer, mob/living/carbon/taker)]
#define COMSIG_ITEM_OFFER_TAKEN "item_offer_taken"
///Interrupts the offer acceptance
#define COMPONENT_OFFER_TAKE_INTERRUPT (1<<0)
diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm
index 3c0cdc2d39..3204a39b89 100644
--- a/code/__DEFINES/role_preferences.dm
+++ b/code/__DEFINES/role_preferences.dm
@@ -36,7 +36,7 @@
#define ROLE_DEATHSQUAD "deathsquad"
#define ROLE_LAVALAND "lavaland"
#define ROLE_INTERNAL_AFFAIRS "internal affairs agent"
-#define ROLE_FAMILIES "gangster"
+#define ROLE_FAMILIES "family boss"
#define ROLE_FAMILY_HEAD_ASPIRANT "family head aspirant"
#define ROLE_BLOODSUCKER "bloodsucker"
#define ROLE_SPACE_DRAGON "Space Dragon"
diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm
index 5accbd959f..01d7794638 100644
--- a/code/__DEFINES/status_effects.dm
+++ b/code/__DEFINES/status_effects.dm
@@ -134,6 +134,8 @@
#define STATUS_EFFECT_INLOVE /datum/status_effect/in_love //Displays you as being in love with someone else, and makes hearts appear around them.
+#define STATUS_EFFECT_OFFERING /datum/status_effect/offering // you are offering up an item to people
+
#define STATUS_EFFECT_HANDSHAKE /datum/status_effect/offering/secret_handshake // you are attempting to perform a secret Family handshake
/////////////
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index 4693041f18..5026fe1030 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -461,3 +461,6 @@ GLOBAL_LIST_EMPTY(species_datums)
/// Gets the client of the mob, allowing for mocking of the client.
/// You only need to use this if you know you're going to be mocking clients somewhere else.
#define GET_CLIENT(mob) (##mob.client || ##mob.mock_client)
+
+//check if the person is dead, not sure where to put this
+#define IS_DEAD_OR_INCAP(source) (source.incapacitated() || source.stat)
diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm
index 54e4478cae..b6324d75f0 100644
--- a/code/_onclick/hud/alert.dm
+++ b/code/_onclick/hud/alert.dm
@@ -2,9 +2,6 @@
//PUBLIC - call these wherever you want
-
-/mob/proc/throw_alert(category, type, severity, obj/new_master, override = FALSE)
-
/* Proc to create or update an alert. Returns the alert if the alert is new or updated, 0 if it was thrown already
category is a text string. Each mob may only have one alert per category; the previous one will be replaced
path is a type path of the actual alert type to throw
@@ -14,6 +11,7 @@
Clicks are forwarded to master
Override makes it so the alert is not replaced until cleared by a clear_alert with clear_override, and it's used for hallucinations.
*/
+/mob/proc/throw_alert(category, type, severity, obj/new_master, override = FALSE)
if(!category || QDELETED(src))
return
@@ -309,37 +307,33 @@ or shoot a gun to move around via Newton's 3rd Law of Motion."
/atom/movable/screen/alert/give // information set when the give alert is made
icon_state = "default"
- var/mob/living/carbon/giver
+ var/mob/living/carbon/offerer
var/obj/item/receiving
/**
- * Handles assigning most of the variables for the alert that pops up when an item is offered
- *
- * Handles setting the name, description and icon of the alert and tracking the person giving
- * and the item being offered, also registers a signal that removes the alert from anyone who moves away from the giver
- * Arguments:
- * * taker - The person receiving the alert
- * * giver - The person giving the alert and item
- * * receiving - The item being given by the giver
- */
-/atom/movable/screen/alert/give/proc/setup(mob/living/carbon/taker, mob/living/carbon/giver, obj/item/receiving)
- name = "[giver] is offering [receiving]"
- desc = "[giver] is offering [receiving]. Click this alert to take it."
+ * Handles assigning most of the variables for the alert that pops up when an item is offered
+ *
+ * Handles setting the name, description and icon of the alert and tracking the person giving
+ * and the item being offered, also registers a signal that removes the alert from anyone who moves away from the offerer
+ * Arguments:
+ * * taker - The person receiving the alert
+ * * offerer - The person giving the alert and item
+ * * receiving - The item being given by the offerer
+ */
+/atom/movable/screen/alert/give/proc/setup(mob/living/carbon/taker, mob/living/carbon/offerer, obj/item/receiving)
+ name = "[offerer] is offering [receiving]"
+ desc = "[offerer] is offering [receiving]. Click this alert to take it."
icon_state = "template"
cut_overlays()
add_overlay(receiving)
src.receiving = receiving
- src.giver = giver
- RegisterSignal(taker, COMSIG_MOVABLE_MOVED, .proc/removeAlert)
-
-/atom/movable/screen/alert/give/proc/removeAlert()
- to_chat(usr, "You moved out of range of [giver]!")
- usr.clear_alert("[giver]")
+ src.offerer = offerer
+ RegisterSignal(taker, COMSIG_MOVABLE_MOVED, .proc/check_in_range, override = TRUE) //Override to prevent runtimes when people offer a item multiple times
/atom/movable/screen/alert/give/Click(location, control, params)
. = ..()
- var/mob/living/carbon/C = usr
- C.take(giver, receiving)
+ if(!.)
+ return
if(!iscarbon(usr))
CRASH("User for [src] is of type \[[usr.type]\]. This should never happen.")
@@ -349,27 +343,27 @@ or shoot a gun to move around via Newton's 3rd Law of Motion."
/// An overrideable proc used simply to hand over the item when claimed, this is a proc so that high-fives can override them since nothing is actually transferred
/atom/movable/screen/alert/give/proc/handle_transfer()
var/mob/living/carbon/taker = owner
- taker.take(giver, receiving)
+ taker.take(offerer, receiving)
/// Simply checks if the other person is still in range
/atom/movable/screen/alert/give/proc/check_in_range(atom/taker)
SIGNAL_HANDLER
-
- if(!giver.CanReach(taker))
- to_chat(owner, span_warning("You moved out of range of [giver]!"))
- owner.clear_alert("[giver]")
+
+ if(!offerer.CanReach(taker))
+ to_chat(owner, span_warning("You moved out of range of [offerer]!"))
+ owner.clear_alert("[offerer]")
/atom/movable/screen/alert/give/secret_handshake
icon_state = "default"
-/atom/movable/screen/alert/give/secret_handshake/setup(mob/living/carbon/taker, mob/living/carbon/giver, obj/item/receiving)
- name = "[giver] is offering a Handshake"
- desc = "[giver] wants to teach you the Secret Handshake for their Family and induct you! Click on this alert to accept."
+/atom/movable/screen/alert/give/secret_handshake/setup(mob/living/carbon/taker, mob/living/carbon/offerer, obj/item/receiving)
+ name = "[offerer] is offering a Handshake"
+ desc = "[offerer] wants to teach you the Secret Handshake for their Family and induct you! Click on this alert to accept."
icon_state = "template"
cut_overlays()
add_overlay(receiving)
src.receiving = receiving
- src.giver = giver
+ src.offerer = offerer
RegisterSignal(taker, COMSIG_MOVABLE_MOVED, .proc/check_in_range, override = TRUE) //Override to prevent runtimes when people offer a item multiple times
//ALIENS
diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm
index 22cafd5f36..291761a197 100644
--- a/code/datums/status_effects/neutral.dm
+++ b/code/datums/status_effects/neutral.dm
@@ -84,6 +84,88 @@
REMOVE_TRAIT(owner, TRAIT_SOOTHED_THROAT, "[STATUS_EFFECT_TRAIT]_[id]")
return ..()
+// this status effect is used to negotiate the high-fiving capabilities of all concerned parties
+/datum/status_effect/offering
+ id = "offering"
+ duration = -1
+ tick_interval = -1
+ status_type = STATUS_EFFECT_UNIQUE
+ alert_type = null
+ /// The people who were offered this item at the start
+ var/list/possible_takers
+ /// The actual item being offered
+ var/obj/item/offered_item
+ /// The type of alert given to people when offered, in case you need to override some behavior (like for high-fives)
+ var/give_alert_type = /atom/movable/screen/alert/give
+
+/datum/status_effect/offering/on_creation(mob/living/new_owner, obj/item/offer, give_alert_override)
+ . = ..()
+ if(!.)
+ return
+
+ offered_item = offer
+ if(give_alert_override)
+ give_alert_type = give_alert_override
+
+ for(var/mob/living/carbon/possible_taker in orange(1, owner))
+ if(!owner.CanReach(possible_taker) || IS_DEAD_OR_INCAP(possible_taker) || !possible_taker.can_hold_items())
+ continue
+ register_candidate(possible_taker)
+
+ if(!possible_takers) // no one around
+ qdel(src)
+ return
+
+ RegisterSignal(owner, COMSIG_MOVABLE_MOVED, .proc/check_owner_in_range)
+ RegisterSignal(offered_item, list(COMSIG_PARENT_QDELETING, COMSIG_ITEM_DROPPED), .proc/dropped_item)
+ //RegisterSignal(owner, COMSIG_PARENT_EXAMINE_MORE, .proc/check_fake_out)
+
+/datum/status_effect/offering/Destroy()
+ for(var/i in possible_takers)
+ var/mob/living/carbon/removed_taker = i
+ remove_candidate(removed_taker)
+ LAZYCLEARLIST(possible_takers)
+ return ..()
+
+/// Hook up the specified carbon mob to be offered the item in question, give them the alert and signals and all
+/datum/status_effect/offering/proc/register_candidate(mob/living/carbon/possible_candidate)
+ var/atom/movable/screen/alert/give/G = possible_candidate.throw_alert("[owner]", give_alert_type)
+ if(!G)
+ return
+ LAZYADD(possible_takers, possible_candidate)
+ RegisterSignal(possible_candidate, COMSIG_MOVABLE_MOVED, .proc/check_taker_in_range)
+ G.setup(possible_candidate, owner, offered_item)
+
+/// Remove the alert and signals for the specified carbon mob. Automatically removes the status effect when we lost the last taker
+/datum/status_effect/offering/proc/remove_candidate(mob/living/carbon/removed_candidate)
+ removed_candidate.clear_alert("[owner]")
+ LAZYREMOVE(possible_takers, removed_candidate)
+ UnregisterSignal(removed_candidate, COMSIG_MOVABLE_MOVED)
+ if(!possible_takers && !QDELING(src))
+ qdel(src)
+
+/// One of our possible takers moved, see if they left us hanging
+/datum/status_effect/offering/proc/check_taker_in_range(mob/living/carbon/taker)
+ SIGNAL_HANDLER
+ if(owner.CanReach(taker) && !IS_DEAD_OR_INCAP(taker))
+ return
+
+ remove_candidate(taker)
+
+/// The offerer moved, see if anyone is out of range now
+/datum/status_effect/offering/proc/check_owner_in_range(mob/living/carbon/source)
+ SIGNAL_HANDLER
+
+ for(var/i in possible_takers)
+ var/mob/living/carbon/checking_taker = i
+ if(!istype(checking_taker) || !owner.CanReach(checking_taker) || IS_DEAD_OR_INCAP(checking_taker))
+ remove_candidate(checking_taker)
+
+/// We lost the item, give it up
+/datum/status_effect/offering/proc/dropped_item(obj/item/source)
+ SIGNAL_HANDLER
+ qdel(src)
+
/datum/status_effect/offering/secret_handshake
id = "secret_handshake"
alert_type = /atom/movable/screen/alert/give/secret_handshake
diff --git a/code/datums/status_effects/status_effect.dm b/code/datums/status_effects/status_effect.dm
index fe605ba4a6..f5ceff09aa 100644
--- a/code/datums/status_effects/status_effect.dm
+++ b/code/datums/status_effects/status_effect.dm
@@ -25,11 +25,11 @@
/datum/status_effect/proc/on_creation(mob/living/new_owner, ...)
if(new_owner)
owner = new_owner
- if(owner)
- LAZYADD(owner.status_effects, src)
- if(!owner || !on_apply())
+ if(QDELETED(owner) || !on_apply())
qdel(src)
return
+ if(owner)
+ LAZYADD(owner.status_effects, src)
if(duration != -1)
duration = world.time + duration
next_tick = world.time + tick_interval
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index aab06c875b..1420a8c078 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -1196,6 +1196,28 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb
embed_chance_turf_mod = (!isnull(embedding["embed_chance_turf_mod"]) ? embedding["embed_chance_turf_mod"] : EMBED_CHANCE_TURF_MOD))
return TRUE
-/obj/item/proc/on_offer_taken(mob/living/carbon/giver, mob/living/carbon/taker)
- if(SEND_SIGNAL(src, COMSIG_ITEM_OFFER_TAKEN, giver, taker) & COMPONENT_OFFER_INTERRUPT)
+
+/**
+ * * An interrupt for offering an item to other people, called mainly from [/mob/living/carbon/proc/give], in case you want to run your own offer behavior instead.
+ *
+ * * Return TRUE if you want to interrupt the offer.
+ *
+ * * Arguments:
+ * * offerer - the person offering the item
+ */
+/obj/item/proc/on_offered(mob/living/carbon/offerer)
+ if(SEND_SIGNAL(src, COMSIG_ITEM_OFFERING, offerer) & COMPONENT_OFFER_INTERRUPT)
+ return TRUE
+
+/**
+ * * An interrupt for someone trying to accept an offered item, called mainly from [/mob/living/carbon/proc/take], in case you want to run your own take behavior instead.
+ *
+ * * Return TRUE if you want to interrupt the taking.
+ *
+ * * Arguments:
+ * * offerer - the person offering the item
+ * * taker - the person trying to accept the offer
+ */
+/obj/item/proc/on_offer_taken(mob/living/carbon/offerer, mob/living/carbon/taker)
+ if(SEND_SIGNAL(src, COMSIG_ITEM_OFFER_TAKEN, offerer, taker) & COMPONENT_OFFER_INTERRUPT)
return TRUE
diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm
index 5e5c55eab4..c89c0ce735 100644
--- a/code/game/objects/items/weaponry.dm
+++ b/code/game/objects/items/weaponry.dm
@@ -1105,7 +1105,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
icon_state = "madeyoulook"
force = 0
throwforce = 0
- item_flags = DROPDEL | ABSTRACT // | HAND_ITEM
+ item_flags = DROPDEL | ABSTRACT | HAND_ITEM
attack_verb = list("bopped")
/obj/item/circlegame/Initialize()
@@ -1207,7 +1207,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
item_state = "nothing"
force = 0
throwforce = 0
- item_flags = DROPDEL | ABSTRACT // | HAND_ITEM
+ item_flags = DROPDEL | ABSTRACT | HAND_ITEM
attack_verb = list("slapped")
hitsound = 'sound/effects/snap.ogg'
@@ -1227,6 +1227,12 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
return FALSE
return TRUE
+/obj/item/slapper/on_offered(mob/living/carbon/offerer)
+ . = TRUE
+
+/obj/item/slapper/on_offer_taken(mob/living/carbon/offerer, mob/living/carbon/taker)
+ . = TRUE
+
/// Gangster secret handshakes.
/obj/item/slapper/secret_handshake
name = "Secret Handshake"
@@ -1268,10 +1274,10 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
user.mind.remove_antag_datum(/datum/antagonist/gang)
add_to_gang(user, real_name_backup)
-/obj/item/slapper/secret_handshake/on_offer_taken(mob/living/carbon/giver, mob/living/carbon/taker)
+/obj/item/slapper/secret_handshake/on_offer_taken(mob/living/carbon/offerer, mob/living/carbon/taker)
. = TRUE
if (!(null in taker.held_items))
- to_chat(taker, span_warning("You can't get taught the secret handshake if [giver] has no free hands!"))
+ to_chat(taker, span_warning("You can't get taught the secret handshake if [offerer] has no free hands!"))
return
if(HAS_TRAIT(taker, TRAIT_MINDSHIELD))
@@ -1286,9 +1292,9 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
to_chat(taker, "You started your family. You can't turn your back on it now.")
return
- giver.visible_message(span_notice("[taker] is taught the secret handshake by [giver]!"), span_nicegreen("All right! You've taught the secret handshake to [taker]!"), span_hear("You hear a bunch of weird shuffling and flesh slapping sounds!"), ignored_mobs=taker)
- to_chat(taker, span_nicegreen("You get taught the secret handshake by [giver]!"))
- var/datum/antagonist/gang/owner_gang_datum = giver.mind.has_antag_datum(/datum/antagonist/gang)
+ offerer.visible_message(span_notice("[taker] is taught the secret handshake by [offerer]!"), span_nicegreen("All right! You've taught the secret handshake to [taker]!"), span_hear("You hear a bunch of weird shuffling and flesh slapping sounds!"), ignored_mobs=taker)
+ to_chat(taker, span_nicegreen("You get taught the secret handshake by [offerer]!"))
+ var/datum/antagonist/gang/owner_gang_datum = offerer.mind.has_antag_datum(/datum/antagonist/gang)
handler = owner_gang_datum.handler
gang_to_use = owner_gang_datum.type
team_to_use = owner_gang_datum.my_gang
diff --git a/code/modules/mob/living/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm
index 48bb0ead0e..13f811196e 100644
--- a/code/modules/mob/living/carbon/inventory.dm
+++ b/code/modules/mob/living/carbon/inventory.dm
@@ -161,57 +161,58 @@
return index && hand_bodyparts[index]
/**
- * Proc called when giving an item to another player
+ * Proc called when offering an item to another player
*
* This handles creating an alert and adding an overlay to it
*/
/mob/living/carbon/proc/give(target)
- var/obj/item/receiving = get_active_held_item()
- if(!receiving)
+ var/obj/item/offered_item = get_active_held_item()
+ if(!offered_item)
to_chat(src, "You're not holding anything to give!")
return
- visible_message("[src] is offering [receiving]", \
- "You offer [receiving]", null, 2)
- var/mob/living/carbon/targets = list()
- if(!target)
- for(var/mob/living/carbon/C in orange(1, src))
- if(!CanReach(C))
- return
- targets += C
- else
- targets += target
- if(!targets)
+
+ if(IS_DEAD_OR_INCAP(src))
+ to_chat(src, span_warning("You're unable to offer anything in your current state!"))
return
- for(var/mob/living/carbon/C in targets)
- var/atom/movable/screen/alert/give/G = C.throw_alert("[src]", /atom/movable/screen/alert/give)
- if(!G)
- return
- G.setup(C, src, receiving)
+
+ if(has_status_effect(STATUS_EFFECT_OFFERING))
+ to_chat(src, span_warning("You're already offering up something!"))
+ return
+
+ if(offered_item.on_offered(src)) // see if the item interrupts with its own behavior
+ return
+
+ visible_message(span_notice("[src] is offering [offered_item]."), \
+ span_notice("You offer [offered_item]."), null, 2)
+
+ apply_status_effect(STATUS_EFFECT_OFFERING, offered_item)
/**
* Proc called when the player clicks the give alert
*
- * Handles checking if the player taking the item has open slots and is in range of the giver
+ * Handles checking if the player taking the item has open slots and is in range of the offerer
* Also deals with the actual transferring of the item to the players hands
* Arguments:
- * * giver - The person giving the original item
- * * I - The item being given by the giver
+ * * offerer - The person giving the original item
+ * * I - The item being given by the offerer
*/
-/mob/living/carbon/proc/take(mob/living/carbon/giver, obj/item/I)
- clear_alert("[giver]")
- if(get_dist(src, giver) > 1)
- to_chat(src, "[giver] is out of range! ")
+/mob/living/carbon/proc/take(mob/living/carbon/offerer, obj/item/I)
+ clear_alert("[offerer]")
+ if(get_dist(src, offerer) > 1)
+ to_chat(src, span_warning("[offerer] is out of range!"))
return
- if(!I || giver.get_active_held_item() != I)
- to_chat(src, "[giver] is no longer holding the item they were offering! ")
+ if(!I || offerer.get_active_held_item() != I)
+ to_chat(src, span_warning("[offerer] is no longer holding the item they were offering!"))
return
if(!get_empty_held_indexes())
to_chat(src, "You have no empty hands!")
return
- if(I.on_offer_taken(giver, src)) // see if the item has special behavior for being accepted
+ if(I.on_offer_taken(offerer, src)) // see if the item has special behavior for being accepted
return
- if(!giver.temporarilyRemoveItemFromInventory(I))
- visible_message("[src] tries to hand over [I] but it's stuck to them....", \
+ if(!offerer.temporarilyRemoveItemFromInventory(I))
+ visible_message("[offerer] tries to hand over [I] but it's stuck to them....", \
" You make a fool of yourself trying to give away an item stuck to your hands")
return
+ visible_message(span_notice("[src] takes [I] from [offerer]"), \
+ span_notice("You take [I] from [offerer]"))
put_in_hands(I)