Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Valentines Day #663

Merged
merged 3 commits into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion _maps/map_files/PubbyStation/PubbyStation.dmm
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
Expand Down
11 changes: 11 additions & 0 deletions code/__DEFINES/polls.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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"
30 changes: 30 additions & 0 deletions code/__HELPERS/pronouns.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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) || ..()
49 changes: 42 additions & 7 deletions code/controllers/subsystem/polling.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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"
Expand All @@ -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)

Expand All @@ -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
Expand Down Expand Up @@ -111,13 +145,14 @@ SUBSYSTEM_DEF(polling)
// Chat message
var/act_jump = ""
if(isatom(pic_source) && isobserver(candidate_mob))
act_jump = "<a href='?src=[REF(poll_alert_button)];jump=1'>\[Teleport]</a>"
var/act_signup = "<a href='?src=[REF(poll_alert_button)];signup=1'>\[Sign Up]</a>"
act_jump = "<a href='?src=[REF(poll_alert_button)];jump=1'>\[Teleport\]</a>"
var/act_signup = "<a href='?src=[REF(poll_alert_button)];signup=1'>\[[start_signed_up ? "Opt out" : "Sign Up"]\]</a>"
var/act_never = ""
if(ignore_category)
act_never = "<a href='?src=[REF(poll_alert_button)];never=1'>\[Never For This Round]</a>"
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 = "<a href='?src=[REF(poll_alert_button)];never=1'>\[Never For This Round\]</a>"

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)

Expand Down
26 changes: 19 additions & 7 deletions code/datums/candidate_poll.dm
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,28 @@
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
ignoring_category = poll_ignoring_category
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()
Expand All @@ -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)
Expand All @@ -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))
Expand All @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion code/modules/antagonists/valentines/heartbreaker.dm
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@

/datum/antagonist/heartbreaker/greet()
. = ..()
to_chat(owner, span_warning("<B>You didn't get a date! They're all having fun without you! You'll show them though...</B>"))
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()
100 changes: 71 additions & 29 deletions code/modules/antagonists/valentines/valentine.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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("<B>You're on a date with [date.name]! Protect [date.p_them()] at all costs. This takes priority over all other loyalties.</B>"))
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 "<span class='greentext big'>[owner.name] protected [owner.p_their()] date</span>"
else
return "<span class='redtext big'>[owner.name] date failed!</span>"
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."
2 changes: 1 addition & 1 deletion code/modules/cargo/goodies.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading
Loading