diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index e2ccb39f534..d9f5f22f79c 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -133,3 +133,5 @@ #define STATUS_EFFECT_SYPHONMARK /datum/status_effect/syphon_mark //tracks kills for the KA death syphon module #define STATUS_EFFECT_ADAPTIVELEARNING /datum/status_effect/adaptive_learning //tracks the total bonus damage needed to be done to target +/// Status effect given when someone uses the Give Item command to offer an item to another player. +#define STATUS_EFFECT_OFFERING_ITEM /datum/status_effect/offering_item diff --git a/code/__HELPERS/qdel.dm b/code/__HELPERS/qdel.dm index 020077a4e5c..99ccce4719b 100644 --- a/code/__HELPERS/qdel.dm +++ b/code/__HELPERS/qdel.dm @@ -8,3 +8,15 @@ /proc/______qdel_list_wrapper(list/L) //the underscores are to encourage people not to use this directly. QDEL_LIST(L) + +/** + * # Signal qdel + * + * Proc intended to be used when someone wants the src datum to be qdeled when a certain signal is sent to them. + * + * Example usage: + * RegisterSignal(item, COMSIG_PARENT_QDELETING, /datum/proc/signal_qdel) + */ +/datum/proc/signal_qdel() + SIGNAL_HANDLER + qdel(src) diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index 0e70c02b05d..1038ed2c18f 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -3,19 +3,21 @@ //PUBLIC - call these wherever you want -/mob/proc/throw_alert(category, type, severity, obj/new_master, override = FALSE, timeout_override, no_anim, icon_override) - -/* - 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 - severity is an optional number that will be placed at the end of the icon_state for this alert - For example, high pressure's icon_state is "highpressure" and can be serverity 1 or 2 to get "highpressure1" or "highpressure2" - new_master is optional and sets the alert's icon state to "template" in the ui_style icons with the master as an overlay. - 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. +/** + * Proc to create or update an alert. Returns the alert if the alert is new or updated, 0 if it was thrown already. + * Each mob may only have one alert per category. + * + * Arguments: + * * category - a text string corresponding to what type of alert it is + * * type - a type path of the actual alert type to throw + * * severity - is an optional number that will be placed at the end of the icon_state for this alert + For example, high pressure's icon_state is "highpressure" and can be serverity 1 or 2 to get "highpressure1" or "highpressure2" + * * obj/new_master - optional argument. Sets the alert's icon state to "template" in the ui_style icons with the master as an overlay. Clicks are forwarded to master + * * no_anim - whether the alert should play a small sliding animation when created on the player's screen + * * icon_override - makes it so the alert is not replaced until cleared by a clear_alert with clear_override, and it's used for hallucinations. + * * list/alert_args - a list of arguments to pass to the alert when creating it */ - +/mob/proc/throw_alert(category, type, severity, obj/new_master, override = FALSE, timeout_override, no_anim, icon_override, list/alert_args) if(!category) return @@ -37,7 +39,11 @@ else //no need to update return 0 else - alert = new type() + if(alert_args) + alert_args.Insert(1, null) // So it's still created in nullspace. + alert = new type(arglist(alert_args)) + else + alert = new type() alert.override_alerts = override if(override) alert.timeout = null diff --git a/code/datums/keybindings/carbon.dm b/code/datums/keybindings/carbon.dm index b27a59fbc73..69aaa35b136 100644 --- a/code/datums/keybindings/carbon.dm +++ b/code/datums/keybindings/carbon.dm @@ -22,6 +22,15 @@ var/mob/living/carbon/M = C.mob M.throw_mode_off() +/datum/keybinding/carbon/give_item + name = "Give Item (Toggle)" + keys = null + +/datum/keybinding/carbon/give_item/down(client/C) + . = ..() + var/mob/living/carbon/user = C.mob + user.toggle_give() + /datum/keybinding/carbon/intent /// The intent to switch to. var/intent diff --git a/code/modules/mob/living/carbon/give.dm b/code/modules/mob/living/carbon/give.dm index 6795ec8643c..59d06a9fdd5 100644 --- a/code/modules/mob/living/carbon/give.dm +++ b/code/modules/mob/living/carbon/give.dm @@ -1,52 +1,189 @@ -/mob/living/carbon/verb/give(mob/living/carbon/target in oview(1)) +/** + * Toggles the [/datum/click_intercept/give] on or off for the src mob. + */ +/mob/living/carbon/verb/toggle_give() + set name = "Give Item" set category = "IC" - set name = "Give" - if(!iscarbon(target)) //something is bypassing the give arguments, no clue what, adding a sanity check JIC - to_chat(usr, "Wait a second... \the [target] HAS NO HANDS! AHH!")//cheesy messages ftw + if(has_status_effect(STATUS_EFFECT_OFFERING_ITEM)) + to_chat(src, "You're already offering an item to someone!") return - - if(target.incapacitated() || usr.incapacitated() || target.client == null) + if(istype(client.click_intercept, /datum/click_intercept/give)) + QDEL_NULL(client.click_intercept) return - var/obj/item/I = get_active_hand() - if(!I) - to_chat(usr, " You don't have anything in your hand to give to [target.name]") + to_chat(src, "You don't have anything in your hand to give!") return - if((I.flags & NODROP) || (I.flags & ABSTRACT)) - to_chat(usr, "That's not exactly something you can give.") + if(I.flags & NODROP) + to_chat(src, "[I] is stuck to your hand, you can't give it away!") return - if(target.r_hand == null || target.l_hand == null) - var/ans = alert(target,"[usr] wants to give you \a [I]?",,"Yes","No") - if(!I || !target) - return - switch(ans) - if("Yes") - if(target.incapacitated() || usr.incapacitated()) - return - if(!Adjacent(target)) - to_chat(usr, " You need to stay in reaching distance while giving an object.") - to_chat(target, " [usr.name] moved too far away.") - return - if((I.flags & NODROP) || (I.flags & ABSTRACT)) - to_chat(usr, "[I] stays stuck to your hand when you try to give it!") - to_chat(target, "[I] stays stuck to [usr.name]'s hand when you try to take it!") - return - if(I != get_active_hand()) - to_chat(usr, " You need to keep the item in your active hand.") - to_chat(target, " [usr.name] seem to have given up on giving [I] to you.") - return - if(target.r_hand != null && target.l_hand != null) - to_chat(target, " Your hands are full.") - to_chat(usr, " Their hands are full.") - return - usr.unEquip(I) - target.put_in_hands(I) - I.add_fingerprint(target) - target.visible_message(" [usr.name] handed [I] to [target.name].") - I.on_give(usr, target) - if("No") - target.visible_message(" [usr.name] tried to hand [I] to [target.name] but [target.name] didn't want it.") - else - to_chat(usr, " [target.name]'s hands are full.") + if(I.flags & ABSTRACT) + to_chat(src, "That's not exactly something you can give.") + return + + new /datum/click_intercept/give(client) + +/** + * # Offering Item status effect + * + * Status effect given to mobs after they've offered an item to another player using the Give Item action ([/datum/click_intercept/give]). + */ +/datum/status_effect/offering_item + id = "offering item" + duration = 10 SECONDS + alert_type = /obj/screen/alert/status_effect/offering_item + +/datum/status_effect/offering_item/on_creation(mob/living/new_owner, receiver_UID, item_UID) + . = ..() + var/obj/screen/alert/status_effect/offering_item/offer = linked_alert + offer.item_UID = item_UID + offer.receiver_UID = receiver_UID + + +/obj/screen/alert/status_effect/offering_item + name = "Offering Item" + desc = "You're currently offering an item someone. Make sure to keep the item in your hand so they can accept it! Click to stop offering your item." + icon_state = "offering_item" + /// UID of the mob who's being offered the item. + var/receiver_UID + /// UID of the item being given. + var/item_UID + +/obj/screen/alert/status_effect/offering_item/Click(location, control, params) + var/mob/living/carbon/receiver = locateUID(receiver_UID) + var/mob/living/carbon/giver = attached_effect.owner + var/obj/item/I = locateUID(item_UID) + to_chat(giver, "You decide against giving [I] to [receiver].") + to_chat(receiver, "[giver] seems to have given up on giving you [I].") + receiver.clear_alert("take item [item_UID]") // This cancels *everything* related to the giving/item offering. + + +/** + * # Give click intercept + * + * While a mob has this intercept, left clicking on a carbon mob will attempt to offer their currently held item to that mob. + */ +/datum/click_intercept/give + /// If the intercept user has succesfully offered the item to another player. + var/item_offered = FALSE + +/datum/click_intercept/give/New(client/C) + ..() + holder.mouse_pointer_icon = 'icons/mouse_icons/give_item.dmi' + to_chat(holder, "You can now left click on someone to give them your held item.") + RegisterSignal(holder.mob.get_active_hand(), list(COMSIG_PARENT_QDELETING, COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED), /datum/proc/signal_qdel) + RegisterSignal(holder.mob, list(SIGNAL_ADDTRAIT(TRAIT_HANDS_BLOCKED), COMSIG_CARBON_SWAP_HANDS), /datum/proc/signal_qdel) + + +/datum/click_intercept/give/Destroy(force = FALSE, ...) + holder.mouse_pointer_icon = initial(holder.mouse_pointer_icon) + if(!item_offered) + to_chat(holder.mob, "You're no longer trying to give someone your held item.") + return ..() + + +/datum/click_intercept/give/InterceptClickOn(mob/user, params, atom/object) + if(user == object || !iscarbon(object)) + return + var/mob/living/carbon/receiver = object + if(receiver.stat != CONSCIOUS) + to_chat(user, "[receiver] can't accept any items because they're not conscious!") + return + var/obj/item/I = user.get_active_hand() + if(!user.Adjacent(receiver)) + to_chat(user, "You need to be closer to [receiver] to offer them [I].") + return + if(!receiver.client) + to_chat(user, "You offer [I] to [receiver], but they don't seem to respond...") + return + // We use UID() here so that the receiver can have more then one give request at one time. + // Otherwise, throwing a new "take item" alert would override any current one also named "take item". + receiver.throw_alert("take item [I.UID()]", /obj/screen/alert/take_item, alert_args = list(user, receiver, I)) + item_offered = TRUE // TRUE so we don't give them the default chat message in Destroy. + to_chat(user, "You offer [I] to [receiver].") + qdel(src) + + +/** + * # Take Item alert + * + * Alert which appears for a user when another player is attempting to offer them an item. + * The user can click the alert to accept, or simply do nothing to not take the item. + */ +/obj/screen/alert/take_item + name = "Take Item" + desc = "someone wants to hand you an item!" + icon_state = "template" + timeout = 10 SECONDS + /// UID of the mob offering the receiver an item. + var/giver_UID + /// UID of the mob who has this alert. + var/receiver_UID + /// UID of the item being given. + var/item_UID + + +/obj/screen/alert/take_item/Initialize(mapload, mob/living/giver, mob/living/receiver, obj/item/I) + . = ..() + desc = "[giver] wants to hand you \a [I]. Click here to accept it!" + giver_UID = giver.UID() + receiver_UID = receiver.UID() + item_UID = I.UID() + giver.apply_status_effect(STATUS_EFFECT_OFFERING_ITEM, receiver_UID, item_UID) + add_overlay(icon(I.icon, I.icon_state, SOUTH)) + add_overlay("alert_flash") + RegisterSignal(I, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED), .proc/cancel_give) + RegisterSignal(giver, list(SIGNAL_ADDTRAIT(TRAIT_HANDS_BLOCKED), COMSIG_CARBON_SWAP_HANDS), .proc/cancel_give) + // If either of these atoms are deleted, we need to cancel everything. Also saves having to do null checks before interacting with these atoms. + RegisterSignal(I, COMSIG_PARENT_QDELETING, /datum/proc/signal_qdel) + RegisterSignal(giver, COMSIG_PARENT_QDELETING, /datum/proc/signal_qdel) + + +/obj/screen/alert/take_item/Destroy() + var/mob/living/giver = locateUID(giver_UID) + giver.remove_status_effect(STATUS_EFFECT_OFFERING_ITEM) + return ..() + + +/obj/screen/alert/take_item/proc/cancel_give() + SIGNAL_HANDLER + var/mob/living/giver = locateUID(giver_UID) + to_chat(giver, "You need to keep the item in your active hand if you want to hand it to someone!") + to_chat(locateUID(receiver_UID), "[giver] seems to have given up on giving you [locateUID(item_UID)].") + qdel(src) + + +/obj/screen/alert/take_item/Click(location, control, params) + var/mob/living/receiver = locateUID(receiver_UID) + if(receiver.stat != CONSCIOUS) + return + var/obj/item/I = locateUID(item_UID) + if(HAS_TRAIT(receiver, TRAIT_HANDS_BLOCKED) || receiver.r_hand && receiver.l_hand) + to_chat(receiver, "You need to have your hands free to accept [I]!") + return + var/mob/living/giver = locateUID(giver_UID) + if(!giver.Adjacent(receiver)) + to_chat(receiver, "You need to stay in reaching distance of [giver] to take [I]!") + return + if(I.flags & NODROP) + to_chat(giver, "[I] stays stuck to your hand when [receiver] tries to take it!") + to_chat(receiver, "[I] stays stuck to [giver]'s hand when you try to take it!") + return + giver.unEquip(I) + receiver.put_in_hands(I) + I.add_fingerprint(receiver) + I.on_give(giver, receiver) + receiver.visible_message("[giver] handed [I] to [receiver].") + receiver.clear_alert("give item [item_UID]") + + +/obj/screen/alert/take_item/do_timeout(mob/M, category) + var/mob/living/giver = locateUID(giver_UID) + var/mob/living/receiver = locateUID(receiver_UID) + // Make sure we're still nearby. We don't want to show a message if the giver not near us. + if(giver in view(3, receiver)) + var/obj/item/I = locateUID(item_UID) + to_chat(giver, "You tried to hand [I] to [receiver], but they didn't want it.") + to_chat(receiver, "[giver] seems to have given up on giving you [I].") + ..() diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index d6ba5a8e59e..5b03e5959ed 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -9,6 +9,8 @@ mobspellremove(src) QDEL_LIST(viruses) QDEL_LIST(actions) + for(var/alert in alerts) + clear_alert(alert) ghostize() QDEL_LIST_ASSOC_VAL(tkgrabbed_objects) for(var/I in tkgrabbed_objects) diff --git a/icons/mob/screen_alert.dmi b/icons/mob/screen_alert.dmi index 5b65a8175f1..4ef228dcd80 100644 Binary files a/icons/mob/screen_alert.dmi and b/icons/mob/screen_alert.dmi differ diff --git a/icons/mouse_icons/give_item.dmi b/icons/mouse_icons/give_item.dmi new file mode 100644 index 00000000000..6b4944261a6 Binary files /dev/null and b/icons/mouse_icons/give_item.dmi differ