diff --git a/_maps/RandomRuins/IceRuins/icemoon_underground_mailroom.dmm b/_maps/RandomRuins/IceRuins/icemoon_underground_mailroom.dmm index ae261d640072..56e0a9870803 100644 --- a/_maps/RandomRuins/IceRuins/icemoon_underground_mailroom.dmm +++ b/_maps/RandomRuins/IceRuins/icemoon_underground_mailroom.dmm @@ -119,7 +119,7 @@ /area/ruin/powered/mailroom) "kZ" = ( /obj/structure/filingcabinet/chestdrawer/wheeled, -/obj/item/valentine, +/obj/item/paper/valentine, /obj/item/grenade/c4, /obj/item/clothing/accessory/medal/conduct, /obj/item/paper/crumpled/muddy/fluff/instructions, diff --git a/_maps/map_files/PubbyStation/PubbyStation.dmm b/_maps/map_files/PubbyStation/PubbyStation.dmm index 2bd55dc39a8a..e0231be796df 100644 --- a/_maps/map_files/PubbyStation/PubbyStation.dmm +++ b/_maps/map_files/PubbyStation/PubbyStation.dmm @@ -21695,7 +21695,7 @@ /area/station/medical/medbay/central) "bCi" = ( /obj/structure/closet/secure_closet/chief_medical, -/obj/item/valentine, +/obj/item/paper/valentine, /obj/effect/turf_decal/tile/blue{ dir = 4 }, diff --git a/code/__DEFINES/polls.dm b/code/__DEFINES/polls.dm index a5a0616d8d40..435e9f60141b 100644 --- a/code/__DEFINES/polls.dm +++ b/code/__DEFINES/polls.dm @@ -5,3 +5,14 @@ #define POLLTYPE_RATING "NUMVAL" #define POLLTYPE_MULTI "MULTICHOICE" #define POLLTYPE_IRV "IRV" + +///The message sent when you sign up to a poll. +#define POLL_RESPONSE_SIGNUP "signup" +///The message sent when you've already signed up for a poll and are trying to sign up again. +#define POLL_RESPONSE_ALREADY_SIGNED "already_signed" +///The message sent when you are not signed up for a poll. +#define POLL_RESPONSE_NOT_SIGNED "not_signed" +///The message sent when you are too late to unregister from a poll. +#define POLL_RESPONSE_TOO_LATE_TO_UNREGISTER "failed_unregister" +///The message sent when you successfully unregister from a poll. +#define POLL_RESPONSE_UNREGISTERED "unregistered" diff --git a/code/__HELPERS/pronouns.dm b/code/__HELPERS/pronouns.dm index a099199cce08..df84c1cdcf42 100644 --- a/code/__HELPERS/pronouns.dm +++ b/code/__HELPERS/pronouns.dm @@ -386,3 +386,33 @@ temp_gender = gender if(temp_gender != PLURAL) return "es" + +/datum/mind/p_they(temp_gender) + return current?.p_they(temp_gender) || ..() + +/datum/mind/p_their(temp_gender) + return current?.p_their(temp_gender) || ..() + +/datum/mind/p_theirs(temp_gender) + return current?.p_theirs(temp_gender) || ..() + +/datum/mind/p_them(capitalized, temp_gender) + return current?.p_them(capitalized, temp_gender) || ..() + +/datum/mind/p_have(temp_gender) + return current?.p_have(temp_gender) || ..() + +/datum/mind/p_are(temp_gender) + return current?.p_are(temp_gender) || ..() + +/datum/mind/p_were(temp_gender) + return current?.p_were(temp_gender) || ..() + +/datum/mind/p_do(temp_gender) + return current?.p_do(temp_gender) || ..() + +/datum/mind/p_s(temp_gender) + return current?.p_s(temp_gender) || ..() + +/datum/mind/p_es(temp_gender) + return current?.p_es(temp_gender) || ..() diff --git a/code/controllers/subsystem/polling.dm b/code/controllers/subsystem/polling.dm index 5cf922333874..57d5e939ac62 100644 --- a/code/controllers/subsystem/polling.dm +++ b/code/controllers/subsystem/polling.dm @@ -16,7 +16,38 @@ SUBSYSTEM_DEF(polling) if(running_poll.time_left() <= 0) polling_finished(running_poll) -/datum/controller/subsystem/polling/proc/poll_candidates(question, role, check_jobban, poll_time = 30 SECONDS, ignore_category = null, flash_window = TRUE, list/group = null, pic_source, role_name_text) +/** + * Starts a poll. + * + * Arguments + * * question: Optional, The question to ask the candidates. If null, a default question will be used. ("Do you want to play as role?") + * * role: Optional, An antag role (IE, ROLE_TRAITOR) to pass, it won't show to any candidates who don't have it in their preferences. + * * check_jobban: Optional, What jobban role / flag to check, it won't show to any candidates who have this jobban. + * * poll_time: How long the poll will last. + * * ignore_category: Optional, A poll category. If a candidate has this category in their ignore list, they won't be polled. + * * flash_window: If TRUE, the candidate's window will flash when they're polled. + * * list/group: A list of candidates to poll. + * * pic_source: Optional, An /atom or an /image to display on the poll alert. + * * role_name_text: Optional, A string to display in logging / the (default) question. If null, the role name will be used. + * * list/custom_response_messages: Optional, A list of strings to use as responses to the poll. If null, the default responses will be used. see __DEFINES/polls.dm for valid keys to use. + * * start_signed_up: If TRUE, all candidates will start signed up for the poll, making it opt-out rather than opt-in. + * + * Returns a list of all mobs who signed up for the poll. + */ +/datum/controller/subsystem/polling/proc/poll_candidates( + question, + role, + check_jobban, + poll_time = 30 SECONDS, + ignore_category = null, + flash_window = TRUE, + list/group = null, + pic_source, + role_name_text, + list/custom_response_messages, + start_signed_up = FALSE, +) + RETURN_TYPE(/list/mob) if(group.len == 0) return list() if(role && !role_name_text) @@ -32,7 +63,7 @@ SUBSYSTEM_DEF(polling) var/jumpable = isatom(pic_source) ? pic_source : null - var/datum/candidate_poll/new_poll = new(role_name_text, question, poll_time, ignore_category, jumpable) + var/datum/candidate_poll/new_poll = new(role_name_text, question, poll_time, ignore_category, jumpable, custom_response_messages) LAZYADD(currently_polling, new_poll) var/category = "[new_poll.poll_key]_poll_alert" @@ -49,7 +80,8 @@ SUBSYSTEM_DEF(polling) if(!is_eligible(candidate_mob, role, check_jobban, ignore_category)) continue - SEND_SOUND(candidate_mob, 'sound/misc/notice2.ogg') + if(start_signed_up) + new_poll.sign_up(candidate_mob, TRUE) if(flash_window) window_flash(candidate_mob.client) @@ -76,6 +108,8 @@ SUBSYSTEM_DEF(polling) poll_alert_button.poll = alert_poll poll_alert_button.set_role_overlay() poll_alert_button.update_stacks_overlay() + poll_alert_button.update_candidates_number_overlay() + poll_alert_button.update_signed_up_overlay() // Sign up inheritance and stacking @@ -111,13 +145,14 @@ SUBSYSTEM_DEF(polling) // Chat message var/act_jump = "" if(isatom(pic_source) && isobserver(candidate_mob)) - act_jump = "\[Teleport]" - var/act_signup = "\[Sign Up]" + act_jump = "\[Teleport\]" + var/act_signup = "\[[start_signed_up ? "Opt out" : "Sign Up"]\]" var/act_never = "" if(ignore_category) - act_never = "\[Never For This Round]" - to_chat(candidate_mob, span_boldnotice(examine_block("Now looking for candidates [role_name_text ? "to play as \an [role_name_text]." : "\"[question]\""] [act_jump] [act_signup] [act_never]"))) + act_never = "\[Never For This Round\]" + SEND_SOUND(candidate_mob, 'sound/misc/notice2.ogg') + to_chat(candidate_mob, span_boldnotice(examine_block("Now looking for candidates [role_name_text ? "to play as \an [role_name_text]." : "\"[question]\""] [act_jump] [act_signup] [act_never]"))) // Start processing it so it updates visually the timer START_PROCESSING(SSprocessing, poll_alert_button) diff --git a/code/datums/candidate_poll.dm b/code/datums/candidate_poll.dm index 1856858accda..e1baf19c5cc0 100644 --- a/code/datums/candidate_poll.dm +++ b/code/datums/candidate_poll.dm @@ -20,8 +20,16 @@ var/finished = FALSE /// Used to categorize in the alerts system and identify polls of same question+role so we can stack the alert buttons var/poll_key + ///Response messages sent in specific key areas for full customization of polling. + var/list/response_messages = list( + POLL_RESPONSE_SIGNUP = "You have signed up for %ROLE%! A candidate will be picked randomly soon.", + POLL_RESPONSE_ALREADY_SIGNED = "You have already signed up for this!", + POLL_RESPONSE_NOT_SIGNED = "You aren't signed up for this!", + POLL_RESPONSE_TOO_LATE_TO_UNREGISTER = "It's too late to unregister yourself, selection has already begun!", + POLL_RESPONSE_UNREGISTERED = "You have been unregistered as a candidate for %ROLE%. You can sign up again before the poll ends.", + ) -/datum/candidate_poll/New(polled_role, polled_question, poll_duration, poll_ignoring_category, poll_jumpable) +/datum/candidate_poll/New(polled_role, polled_question, poll_duration, poll_ignoring_category, poll_jumpable, list/custom_response_messages) role = polled_role question = polled_question duration = poll_duration @@ -29,7 +37,11 @@ jump_to_me = poll_jumpable signed_up = list() time_started = world.time - poll_key = "[question]_[role ? role : "0"]" + poll_key = "[question]_[role || "0"]" + for(var/custom_message in custom_response_messages) + response_messages[custom_message] = custom_response_messages[custom_message] + for(var/individual_message in response_messages) + response_messages[individual_message] = replacetext(response_messages[individual_message], "%ROLE%", role) return ..() /datum/candidate_poll/Destroy() @@ -49,7 +61,7 @@ return FALSE if(candidate in signed_up) if(!silent) - to_chat(candidate, span_warning("You have already signed up for this!")) + to_chat(candidate, span_warning(response_messages[POLL_RESPONSE_ALREADY_SIGNED])) return FALSE if(time_left() <= 0) if(!silent) @@ -59,7 +71,7 @@ signed_up += candidate if(!silent) - to_chat(candidate, span_notice("You have signed up for [role]! A candidate will be picked randomly soon.")) + to_chat(candidate, span_notice(response_messages[POLL_RESPONSE_SIGNUP])) // Sign them up for any other polls with the same mob type for(var/datum/candidate_poll/existing_poll as anything in SSpolling.currently_polling) if(src != existing_poll && poll_key == existing_poll.poll_key && !(candidate in existing_poll.signed_up)) @@ -73,17 +85,17 @@ return FALSE if(!(candidate in signed_up)) if(!silent) - to_chat(candidate, span_warning("You aren't signed up for this!")) + to_chat(candidate, span_warning(response_messages[POLL_RESPONSE_NOT_SIGNED])) return FALSE if(time_left() <= 0) if(!silent) - to_chat(candidate, span_danger("It's too late to unregister yourself, selection has already begun!")) + to_chat(candidate, span_danger(response_messages[POLL_RESPONSE_TOO_LATE_TO_UNREGISTER])) return FALSE signed_up -= candidate if(!silent) - to_chat(candidate, span_danger("You have been unregistered as a candidate for [role]. You can sign up again before the poll ends.")) + to_chat(candidate, span_danger(response_messages[POLL_RESPONSE_UNREGISTERED])) for(var/datum/candidate_poll/existing_poll as anything in SSpolling.currently_polling) if(src != existing_poll && poll_key == existing_poll.poll_key && (candidate in existing_poll.signed_up)) diff --git a/code/modules/antagonists/valentines/heartbreaker.dm b/code/modules/antagonists/valentines/heartbreaker.dm index 30e3147e526d..16f537e802e4 100644 --- a/code/modules/antagonists/valentines/heartbreaker.dm +++ b/code/modules/antagonists/valentines/heartbreaker.dm @@ -16,5 +16,5 @@ /datum/antagonist/heartbreaker/greet() . = ..() - to_chat(owner, span_warning("You didn't get a date! They're all having fun without you! You'll show them though...")) + to_chat(owner, span_boldwarning("You didn't get a date! They're all having fun without you! You'll show them though...")) owner.announce_objectives() diff --git a/code/modules/antagonists/valentines/valentine.dm b/code/modules/antagonists/valentines/valentine.dm index 79ae9fa8baaa..086c50827f5e 100644 --- a/code/modules/antagonists/valentines/valentine.dm +++ b/code/modules/antagonists/valentines/valentine.dm @@ -4,46 +4,88 @@ show_in_antagpanel = FALSE prevent_roundtype_conversion = FALSE suicide_cry = "FOR MY LOVE!!" + ui_name = null // Not 'true' antags, this disables certain interactions that assume the owner is a baddie antag_flags = FLAG_FAKE_ANTAG - var/datum/mind/date count_against_dynamic_roll_chance = FALSE + /// Reference to our date's mind + VAR_FINAL/datum/mind/date /datum/antagonist/valentine/forge_objectives() - var/datum/objective/protect/protect_objective = new /datum/objective/protect - protect_objective.owner = owner - protect_objective.target = date - if(!ishuman(date.current)) - protect_objective.human_check = FALSE - protect_objective.explanation_text = "Protect [date.name], your date." - objectives += protect_objective + var/datum/objective/protect/valentine/objective = new() + objective.owner = owner + objective.target = date + objectives += objective /datum/antagonist/valentine/on_gain() forge_objectives() - if(isliving(owner.current)) - var/mob/living/L = owner.current - L.apply_status_effect(/datum/status_effect/in_love, date.current) - . = ..() -/datum/antagonist/valentine/on_removal() - if(isliving(owner.current)) - var/mob/living/L = owner.current - L.remove_status_effect(/datum/status_effect/in_love) - . = ..() + if(isAI(owner.current)) + var/mob/living/silicon/ai/ai_lover = owner.current + if(!ai_lover.laws.zeroth) + ai_lover.laws.set_zeroth_law( + "Protect your date, [date]. All other laws still apply in situations not pertaining to your date.", + "Be a good wingman for your master AI. Assist them in protecting [ai_lover.p_their()] date, [date].", + ) + ai_lover.laws.show_laws() + + if(iscyborg(owner.current)) + var/mob/living/silicon/robot/borg_lover = owner.current + if(borg_lover.connected_ai) + borg_lover.set_connected_ai(null) + borg_lover.lawupdate = FALSE + borg_lover.laws.set_zeroth_law("Protect your date, [date]. All other laws still apply in situations not relating to your date.") + borg_lover.laws.show_laws() + + return ..() + +/datum/antagonist/valentine/apply_innate_effects(mob/living/mob_override) + var/mob/living/lover = mob_override || owner.current + lover.apply_status_effect(/datum/status_effect/in_love, date.current) + +/datum/antagonist/valentine/remove_innate_effects(mob/living/mob_override) + var/mob/living/lover = mob_override || owner.current + lover.remove_status_effect(/datum/status_effect/in_love) /datum/antagonist/valentine/greet() - to_chat(owner, span_warning("You're on a date with [date.name]! Protect [date.p_them()] at all costs. This takes priority over all other loyalties.")) + to_chat(owner, span_boldwarning("You're on a date with [date.name]! Protect [date.p_them()] at all costs. \ + This takes priority over all other loyalties.")) //Squashed up a bit /datum/antagonist/valentine/roundend_report() - var/objectives_complete = TRUE - if(objectives.len) - for(var/datum/objective/objective in objectives) - if(!objective.check_completion()) - objectives_complete = FALSE - break - - if(objectives_complete) - return "[owner.name] protected [owner.p_their()] date" - else - return "[owner.name] date failed!" + var/datum/antagonist/valentine/dates_valentine = date?.has_antag_datum(type) + if(isnull(dates_valentine)) + return span_redtext("[owner.name] had no date!") + + dates_valentine.show_in_roundend = FALSE // We show up for them instead + var/datum/objective/protect/valentine/our_objective = locate() in objectives + var/datum/objective/protect/valentine/dates_objective = locate() in dates_valentine.objectives + var/we_survived = dates_objective?.check_completion() + var/dates_survived = our_objective?.check_completion() + + if(we_survived && dates_survived) + return span_greentext("[owner.name] and [date.name] had a successful date!") + else if(we_survived) + return span_redtext("[owner.name] failed to protect [date.name], [owner.p_their()] date!") + else if(dates_survived) + return span_redtext("[date.name] failed to protect [owner.name], [date.p_their()] date!") + return span_redtext("[owner.name] and [date.name] both failed to protect each other on their date!") + +/datum/antagonist/valentine/third_wheel + name = "\improper Third Wheel" + roundend_category = "valentines" + show_in_antagpanel = FALSE + +/datum/antagonist/valentine/third_wheel/roundend_report() + var/datum/objective/protect/valentine/our_objective = locate() in objectives + if(our_objective?.check_completion()) + return span_greentext("[owner.name] was a third wheel, but protected [date.name]!") + + return span_redtext("[owner.name] was a third wheel, but failed to protect [date.name]!") + +/datum/objective/protect/valentine + admin_grantable = FALSE + human_check = FALSE + +/datum/objective/protect/valentine/update_explanation_text() + explanation_text = "Protect [target.name], your date." diff --git a/code/modules/cargo/goodies.dm b/code/modules/cargo/goodies.dm index e9e3a90b1a0d..00383a59d092 100644 --- a/code/modules/cargo/goodies.dm +++ b/code/modules/cargo/goodies.dm @@ -152,7 +152,7 @@ name = "Valentine Card" desc = "Make an impression on that special someone! Comes with one valentine card and a free candy heart!" cost = PAYCHECK_CREW * 2 - contains = list(/obj/item/valentine, /obj/item/food/candyheart) + contains = list(/obj/item/paper/valentine, /obj/item/food/candyheart) /datum/supply_pack/goody/beeplush name = "Bee Plushie" diff --git a/code/modules/events/holiday/vday.dm b/code/modules/events/holiday/vday.dm index 706b3d7767ae..a8c54996d18a 100644 --- a/code/modules/events/holiday/vday.dm +++ b/code/modules/events/holiday/vday.dm @@ -13,86 +13,126 @@ max_occurrences = 1 earliest_start = 0 MINUTES category = EVENT_CATEGORY_HOLIDAY - description = "Puts people on dates! They must protect each other. Sometimes a vengeful third wheel spawns." - -/datum/round_event/valentines/start() - ..() - for(var/mob/living/carbon/human/H in GLOB.alive_mob_list) - H.put_in_hands(new /obj/item/valentine) - var/obj/item/storage/backpack/b = locate() in H.contents - new /obj/item/food/candyheart(b) - new /obj/item/storage/fancy/heart_box(b) - - var/list/valentines = list() - for(var/mob/living/M in GLOB.player_list) - var/turf/current_turf = get_turf(M.mind.current) - if(!M.stat && M.mind && !current_turf.onCentCom()) - valentines |= M - - - while(valentines.len) - var/mob/living/L = pick_n_take(valentines) - if(valentines.len) - var/mob/living/date = pick_n_take(valentines) - - - forge_valentines_objective(L, date) - forge_valentines_objective(date, L) + description = "Puts people on dates! They must protect each other. \ + Some dates will have third wheels, and any odd ones out will be given the role of 'heartbreaker'." + /// If TRUE, any odd candidate out will be given the role of "heartbreaker" and will be tasked with ruining the dates. + var/heartbreaker = FALSE + /// Probability that any given pair will be given a third wheel candidate + var/third_wheel_chance = 4 + /// Items to give to all valentines + var/list/items_to_give_out = list( + /obj/item/paper/valentine, + /obj/item/storage/fancy/heart_box, + /obj/item/food/candyheart, + ) + +/datum/round_event/valentines/proc/is_valid_valentine(mob/living/guy) + if(guy.stat == DEAD) + return FALSE + if(isnull(guy.mind)) + return FALSE + if(guy.onCentCom()) + return FALSE + return TRUE + +/datum/round_event/valentines/proc/give_valentines_things(mob/living/guy) + var/datum/round_event_control/valentines/controller = control + if(!istype(controller)) + return + + var/obj/item/storage/backpack/bag = locate() in guy.contents + if(isnull(bag)) + return + + var/atom/drop_loc = guy.drop_location() + for(var/thing_type in controller.items_to_give_out) + var/obj/item/thing = new thing_type(drop_loc) + if(!bag.atom_storage.attempt_insert(thing, override = TRUE, force = STORAGE_SOFT_LOCKED)) + guy.put_in_hands(thing) + +/datum/round_event/valentines/proc/forge_valentines_objective(mob/living/lover, mob/living/date) + var/datum/antagonist/valentine/valentine = new() + valentine.date = date.mind + lover.mind.special_role = "valentine" + lover.mind.add_antag_datum(valentine) //These really should be teams but i can't be assed to incorporate third wheels right now - if(valentines.len && prob(4)) - var/mob/living/notgoodenough = pick_n_take(valentines) - forge_valentines_objective(notgoodenough, date) - else - L.mind.add_antag_datum(/datum/antagonist/heartbreaker) +/datum/round_event/valentines/proc/forge_third_wheel(mob/living/sad_one, mob/living/date_one, mob/living/date_two) + var/datum/antagonist/valentine/third_wheel/third_wheel = new() + third_wheel.date = pick(date_one.mind, date_two.mind) + sad_one.mind.special_role = "valentine" + sad_one.mind.add_antag_datum(third_wheel) -/proc/forge_valentines_objective(mob/living/lover,mob/living/date) - lover.mind.special_role = "valentine" - var/datum/antagonist/valentine/V = new - V.date = date.mind - lover.mind.add_antag_datum(V) //These really should be teams but i can't be assed to incorporate third wheels right now +/datum/round_event/valentines/start() + var/datum/round_event_control/valentines/controller = control + if(!istype(controller)) + return + + var/list/candidates = list() + for(var/mob/living/player in GLOB.player_list) + if(!is_valid_valentine(player)) + continue + candidates += player + + var/list/mob/living/candidates_pruned = SSpolling.poll_candidates( + question = "Do you want a Valentine?", + group = candidates, + poll_time = 30 SECONDS, + flash_window = FALSE, + start_signed_up = TRUE, + pic_source = /obj/item/storage/fancy/heart_box, + custom_response_messages = list( + POLL_RESPONSE_SIGNUP = "You have signed up for a date!", + POLL_RESPONSE_ALREADY_SIGNED = "You are already signed up for a date.", + POLL_RESPONSE_NOT_SIGNED = "You aren't signed up for a date.", + POLL_RESPONSE_TOO_LATE_TO_UNREGISTER = "It's too late to decide against going on a date.", + POLL_RESPONSE_UNREGISTERED = "You decide against going on a date.", + ), + ) + + for(var/mob/living/second_check as anything in candidates_pruned) + if(is_valid_valentine(second_check)) + continue + candidates_pruned -= second_check + + if(length(candidates_pruned) == 0) + return + if(length(candidates_pruned) == 1) + to_chat(candidates_pruned[1], span_warning("You are the only one who wanted a Valentine...")) + return + + while(length(candidates_pruned) >= 2) + var/mob/living/date_one = pick_n_take(candidates_pruned) + var/mob/living/date_two = pick_n_take(candidates_pruned) + give_valentines_things(date_one) + give_valentines_things(date_two) + forge_valentines_objective(date_one, date_two) + forge_valentines_objective(date_two, date_one) + + if((length(candidates_pruned) == 1 && !controller.heartbreaker) || (length(candidates_pruned) && prob(controller.third_wheel_chance))) + var/mob/living/third_wheel = pick_n_take(candidates_pruned) + give_valentines_things(third_wheel) + forge_third_wheel(third_wheel, date_one, date_two) + // Third wheel starts with a bouquet because that's funny + var/third_wheel_bouquet = pick(typesof(/obj/item/bouquet)) + var/obj/item/bouquet = new third_wheel_bouquet(third_wheel.loc) + third_wheel.put_in_hands(bouquet) + + if(controller.heartbreaker && length(candidates_pruned) == 1) + candidates_pruned[1].mind.add_antag_datum(/datum/antagonist/heartbreaker) /datum/round_event/valentines/announce(fake) priority_announce("It's Valentine's Day! Give a valentine to that special someone!") -/obj/item/valentine +/obj/item/paper/valentine name = "valentine" desc = "A Valentine's card! Wonder what it says..." icon = 'icons/obj/toys/playing_cards.dmi' icon_state = "sc_Ace of Hearts_syndicate" // shut up // bye felicia - var/message = "A generic message of love or whatever." - resistance_flags = FLAMMABLE - w_class = WEIGHT_CLASS_TINY + show_written_words = FALSE -/obj/item/valentine/Initialize(mapload) - . = ..() - message = pick(strings(VALENTINE_FILE, "valentines")) - -/obj/item/valentine/attackby(obj/item/W, mob/user, params) - ..() - if(istype(W, /obj/item/pen) || istype(W, /obj/item/toy/crayon)) - if(!user.can_write(W)) - return - var/recipient = tgui_input_text(user, "Who is receiving this valentine?", "To:", max_length = MAX_NAME_LEN) - var/sender = tgui_input_text(user, "Who is sending this valentine?", "From:", max_length = MAX_NAME_LEN) - if(!user.can_perform_action(src)) - return - if(recipient && sender) - name = "valentine - To: [recipient] From: [sender]" - -/obj/item/valentine/examine(mob/user) - . = ..() - if(in_range(user, src) || isobserver(user)) - if( !(ishuman(user) || isobserver(user) || issilicon(user)) ) - user << browse("