Files
Bubberstation/code/controllers/subsystem/polling.dm
SkyratBot b4d3b18bae [MIRROR] Fixing the constant poll alert runtimes, and the selector outline not being updated. [MDB IGNORE] (#25891)
* Fixing the constant poll alert runtimes, and the selector outline not being updated. (#80610)

* Fixing the constant poll alert runtimes, and the selector outline not being updated.

---------

Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
2023-12-29 18:14:38 +00:00

236 lines
10 KiB
Plaintext

SUBSYSTEM_DEF(polling)
name = "Polling"
flags = SS_BACKGROUND | SS_NO_INIT
wait = 1 SECONDS
runlevels = RUNLEVEL_GAME
/// List of polls currently ongoing, to be checked on next fire()
var/list/datum/candidate_poll/currently_polling
/// Number of polls performed since the start
var/total_polls = 0
/datum/controller/subsystem/polling/fire()
if(!currently_polling) // if polls_active is TRUE then this shouldn't happen, but still..
currently_polling = list()
for(var/datum/candidate_poll/running_poll as anything in currently_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)
if(group.len == 0)
return list()
if(role && !role_name_text)
role_name_text = role
if(role_name_text && !question)
question = "Do you want to play as [full_capitalize(role_name_text)]?"
if(!question)
question = "Do you want to play as a special role?"
log_game("Polling candidates [role_name_text ? "for [role_name_text]" : "\"[question]\""] for [DisplayTimeText(poll_time)] seconds")
// Start firing
total_polls++
var/jumpable = isatom(pic_source) ? pic_source : null
var/datum/candidate_poll/new_poll = new(role_name_text, question, poll_time, ignore_category, jumpable)
LAZYADD(currently_polling, new_poll)
var/category = "[new_poll.poll_key]_poll_alert"
for(var/mob/candidate_mob as anything in group)
if(!candidate_mob.client)
continue
// Universal opt-out for all players.
if((!candidate_mob.client.prefs.read_preference(/datum/preference/toggle/ghost_roles)))
continue
// Opt-out for admins whom are currently adminned.
if((!candidate_mob.client.prefs.read_preference(/datum/preference/toggle/ghost_roles_as_admin)) && candidate_mob.client.holder)
continue
if(!is_eligible(candidate_mob, role, check_jobban, ignore_category))
continue
SEND_SOUND(candidate_mob, 'sound/misc/notice2.ogg')
if(flash_window)
window_flash(candidate_mob.client)
// If we somehow send two polls for the same mob type, but with a duration on the second one shorter than the time left on the first one,
// we need to keep the first one's timeout rather than use the shorter one
var/atom/movable/screen/alert/poll_alert/current_alert = LAZYACCESS(candidate_mob.alerts, category)
var/alert_time = poll_time
var/datum/candidate_poll/alert_poll = new_poll
if(current_alert && current_alert.timeout > (world.time + poll_time - world.tick_lag))
alert_time = current_alert.timeout - world.time + world.tick_lag
alert_poll = current_alert.poll
// Send them an on-screen alert
var/atom/movable/screen/alert/poll_alert/poll_alert_button = candidate_mob.throw_alert(category, /atom/movable/screen/alert/poll_alert, timeout_override = alert_time, no_anim = TRUE)
if(!poll_alert_button)
continue
new_poll.alert_buttons += poll_alert_button
new_poll.RegisterSignal(poll_alert_button, COMSIG_QDELETING, TYPE_PROC_REF(/datum/candidate_poll, clear_alert_ref))
poll_alert_button.icon = ui_style2icon(candidate_mob.client?.prefs?.read_preference(/datum/preference/choiced/ui_style))
poll_alert_button.desc = "[question]"
poll_alert_button.show_time_left = TRUE
poll_alert_button.poll = alert_poll
poll_alert_button.set_role_overlay()
poll_alert_button.update_stacks_overlay()
// Sign up inheritance and stacking
for(var/datum/candidate_poll/other_poll as anything in currently_polling)
if(new_poll == other_poll || new_poll.poll_key != other_poll.poll_key)
continue
// If there's already a poll for an identical mob type ongoing and the client is signed up for it, sign them up for this one
if((candidate_mob in other_poll.signed_up) && new_poll.sign_up(candidate_mob, TRUE))
break
// Image to display
var/image/poll_image
if(pic_source)
if(!ispath(pic_source))
var/atom/the_pic_source = pic_source
var/old_layer = the_pic_source.layer
var/old_plane = the_pic_source.plane
the_pic_source.plane = poll_alert_button.plane
the_pic_source.layer = FLOAT_LAYER
poll_alert_button.add_overlay(the_pic_source)
the_pic_source.layer = old_layer
the_pic_source.plane = old_plane
else
poll_image = image(pic_source, layer = FLOAT_LAYER)
else
// Just use a generic image
poll_image = image('icons/effects/effects.dmi', icon_state = "static", layer = FLOAT_LAYER)
if(poll_image)
poll_image.plane = poll_alert_button.plane
poll_alert_button.add_overlay(poll_image)
// 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>"
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]")))
// Start processing it so it updates visually the timer
START_PROCESSING(SSprocessing, poll_alert_button)
// Sleep until the time is up
UNTIL(new_poll.finished)
return new_poll.signed_up
/datum/controller/subsystem/polling/proc/poll_ghost_candidates(question, role, check_jobban, poll_time = 30 SECONDS, ignore_category = null, flashwindow = TRUE, pic_source, role_name_text)
var/list/candidates = list()
if(!(GLOB.ghost_role_flags & GHOSTROLE_STATION_SENTIENCE))
return candidates
for(var/mob/dead/observer/ghost_player in GLOB.player_list)
candidates += ghost_player
return poll_candidates(question, role, check_jobban, poll_time, ignore_category, flashwindow, candidates, pic_source, role_name_text)
/datum/controller/subsystem/polling/proc/poll_ghost_candidates_for_mob(question, role, check_jobban, poll_time = 30 SECONDS, mob/target_mob, ignore_category = null, flashwindow = TRUE, pic_source, role_name_text)
var/static/list/mob/currently_polling_mobs = list()
if(currently_polling_mobs.Find(target_mob))
return list()
currently_polling_mobs += target_mob
var/list/possible_candidates = poll_ghost_candidates(question, role, check_jobban, poll_time, ignore_category, flashwindow, pic_source, role_name_text)
currently_polling_mobs -= target_mob
if(!target_mob || QDELETED(target_mob) || !target_mob.loc)
return list()
return possible_candidates
/datum/controller/subsystem/polling/proc/poll_ghost_candidates_for_mobs(question, role, check_jobban, poll_time = 30 SECONDS, list/mobs, ignore_category = null, flashwindow = TRUE, pic_source, role_name_text)
var/list/candidate_list = poll_ghost_candidates(question, role, check_jobban, poll_time, ignore_category, flashwindow, pic_source, role_name_text)
for(var/mob/potential_mob as anything in mobs)
if(QDELETED(potential_mob) || !potential_mob.loc)
mobs -= potential_mob
if(!length(mobs))
return list()
return candidate_list
/datum/controller/subsystem/polling/proc/is_eligible(mob/potential_candidate, role, check_jobban, the_ignore_category)
if(isnull(potential_candidate.key) || isnull(potential_candidate.client))
return FALSE
if(the_ignore_category)
if(potential_candidate.ckey in GLOB.poll_ignore[the_ignore_category])
return FALSE
if(role)
if(!(role in potential_candidate.client.prefs.be_special))
return FALSE
var/required_time = GLOB.special_roles[role] || 0
if(potential_candidate.client && potential_candidate.client.get_remaining_days(required_time) > 0)
return FALSE
if(check_jobban)
if(is_banned_from(potential_candidate.ckey, list(check_jobban, ROLE_SYNDICATE)))
return FALSE
//SKYRAT EDIT ADDITION BEGIN
if(is_banned_from(potential_candidate.ckey, BAN_GHOST_TAKEOVER) || is_banned_from(potential_candidate.ckey, BAN_ANTAGONIST))
to_chat(potential_candidate, "There was a ghost prompt for: [role], unfortunately you are banned from ghost takeovers.")
return FALSE
//SKYRAT EDIT END
return TRUE
/datum/controller/subsystem/polling/proc/polling_finished(datum/candidate_poll/finishing_poll)
currently_polling -= finishing_poll
// Trim players who aren't eligible anymore
var/length_pre_trim = length(finishing_poll.signed_up)
finishing_poll.trim_candidates()
log_game("Candidate poll [finishing_poll.role ? "for [finishing_poll.role]" : "\"[finishing_poll.question]\""] finished. [length_pre_trim] players signed up, [length(finishing_poll.signed_up)] after trimming")
finishing_poll.finished = TRUE
// Take care of updating the remaining screen alerts if a similar poll is found, or deleting them.
if(length(finishing_poll.alert_buttons))
var/polls_of_same_type_left = FALSE
for(var/datum/candidate_poll/running_poll as anything in currently_polling)
if(running_poll.poll_key == finishing_poll.poll_key && running_poll.time_left() > 0)
polls_of_same_type_left = TRUE
break
for(var/atom/movable/screen/alert/poll_alert/alert as anything in finishing_poll.alert_buttons)
if(polls_of_same_type_left)
alert.update_stacks_overlay()
else
alert.owner.clear_alert("[finishing_poll.poll_key]_poll_alert")
//More than enough time for the the `UNTIL()` stopping loop in `poll_candidates()` to be over, and the results to be turned in.
QDEL_IN(finishing_poll, 0.5 SECONDS)
/datum/controller/subsystem/polling/stat_entry(msg)
msg += "Active: [length(currently_polling)] | Total: [total_polls]"
var/datum/candidate_poll/soonest_to_complete = get_next_poll_to_finish()
if(soonest_to_complete)
msg += " | Next: [DisplayTimeText(soonest_to_complete.time_left())] ([length(soonest_to_complete.signed_up)] candidates)"
return ..()
/datum/controller/subsystem/polling/proc/get_next_poll_to_finish()
var/lowest_time_left = INFINITY
var/next_poll_to_finish
for(var/datum/candidate_poll/poll as anything in currently_polling)
var/time_left = poll.time_left()
if(time_left >= lowest_time_left)
continue
lowest_time_left = time_left
next_poll_to_finish = poll
if(isnull(next_poll_to_finish))
return FALSE
return next_poll_to_finish