Files
Bubberstation/code/datums/hud.dm
zxaber b36986d993 Compromised APCs now flicker their darkblue screen, and it cannot be seen from cameras. (#79929)
## About The Pull Request
- APCs now show the dark blue effect periodically, once every six to ten
seconds.
- This flicker is not visible from cameras, except if the mob is anyway
within view.
- Malf AIs will see their own APCs flicker through cameras, but other
AIs will not see them, except when the AI mob itself is nearby.
## Why It's Good For The Game
This is one of those "sacrificing soul for gameplay" things.

Malf AI does not win against an aware and uninjured crew. That's just
the nature of the game. It's not even a competition, humans have too
many tools that silicons simply have no answer for. That's fine, it
makes the fight asymmetric (which is cool), and plays further into Malf
AIs (and AIs in general) using underhanded tactics to quietly unravel
their disadvantages. It makes stealth the greatest asset for an AI.

Which is why the current hacked APC sprites are not great. The AI, by
its own nature, needs to be able to make the first move. But the game is
full of tools that allow the crew to, either accidentally or on purpose,
seek out the telltale sign of a Malf AI. It's poor design that the AI is
encouraged to not use any of it's antag tools because even the process
of preparing to buy them can place them in a losing position.
## Changelog
🆑
balance: Emagged and Hacked APCs now occasionally flicker a blue effect,
and the effect is not visible with cameras.
/🆑

---------

Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
2023-12-15 00:36:48 +01:00

441 lines
17 KiB
Plaintext

/* HUD DATUMS */
GLOBAL_LIST_EMPTY(all_huds)
///gets filled by each /datum/atom_hud/New().
///associative list of the form: list(hud category = list(all global atom huds that use that category))
GLOBAL_LIST_EMPTY(huds_by_category)
//GLOBAL HUD LIST
GLOBAL_LIST_INIT(huds, list(
DATA_HUD_SECURITY_BASIC = new/datum/atom_hud/data/human/security/basic(),
DATA_HUD_SECURITY_ADVANCED = new/datum/atom_hud/data/human/security/advanced(),
DATA_HUD_MEDICAL_BASIC = new/datum/atom_hud/data/human/medical/basic(),
DATA_HUD_MEDICAL_ADVANCED = new/datum/atom_hud/data/human/medical/advanced(),
DATA_HUD_DIAGNOSTIC_BASIC = new/datum/atom_hud/data/diagnostic/basic(),
DATA_HUD_DIAGNOSTIC_ADVANCED = new/datum/atom_hud/data/diagnostic/advanced(),
DATA_HUD_ABDUCTOR = new/datum/atom_hud/abductor(),
DATA_HUD_SENTIENT_DISEASE = new/datum/atom_hud/sentient_disease(),
DATA_HUD_AI_DETECT = new/datum/atom_hud/ai_detector(),
DATA_HUD_FAN = new/datum/atom_hud/data/human/fan_hud(),
DATA_HUD_MALF_APC = new/datum/atom_hud/data/malf_apc(),
))
/datum/atom_hud
///associative list of the form: list(z level = list(hud atom)).
///tracks what hud atoms for this hud exists in what z level so we can only give users
///the hud images that they can actually see.
var/list/atom/hud_atoms = list()
///associative list of the form: list(z level = list(hud user client mobs)).
///tracks mobs that can "see" us
// by z level so when they change z's we can adjust what images they see from this hud.
var/list/hud_users = list()
///used for signal tracking purposes, associative list of the form: list(hud atom = TRUE) that isnt separated by z level
var/list/atom/hud_atoms_all_z_levels = list()
///used for signal tracking purposes, associative list of the form: list(hud user = number of times this hud was added to this user).
///that isnt separated by z level
var/list/mob/hud_users_all_z_levels = list()
///these will be the indexes for the atom's hud_list
var/list/hud_icons = list()
///mobs associated with the next time this hud can be added to them
var/list/next_time_allowed = list()
///mobs that have triggered the cooldown and are queued to see the hud, but do not yet
var/list/queued_to_see = list()
/// huduser = list(atoms with their hud hidden) - aka everyone hates targeted invisiblity
var/list/hud_exceptions = list()
///whether or not this atom_hud type updates the global huds_by_category list.
///some subtypes cant work like this since theyre supposed to "belong" to
///one target atom each. it will still go in the other global hud lists.
var/uses_global_hud_category = TRUE
/datum/atom_hud/New()
GLOB.all_huds += src
for(var/z_level in 1 to world.maxz)
hud_atoms += list(list())
hud_users += list(list())
RegisterSignal(SSdcs, COMSIG_GLOB_NEW_Z, PROC_REF(add_z_level_huds))
if(uses_global_hud_category)
for(var/hud_icon in hud_icons)
GLOB.huds_by_category[hud_icon] += list(src)
/datum/atom_hud/Destroy()
for(var/mob/mob as anything in hud_users_all_z_levels)
hide_from(mob)
for(var/atom/atom as anything in hud_atoms_all_z_levels)
remove_atom_from_hud(atom)
if(uses_global_hud_category)
for(var/hud_icon in hud_icons)
LAZYREMOVEASSOC(GLOB.huds_by_category, hud_icon, src)
GLOB.all_huds -= src
return ..()
/datum/atom_hud/proc/add_z_level_huds()
SIGNAL_HANDLER
hud_atoms += list(list())
hud_users += list(list())
///returns a list of all hud atoms in the given z level and linked lower z levels (because hud users in higher z levels can see below)
/datum/atom_hud/proc/get_hud_atoms_for_z_level(z_level)
if(z_level <= 0)
return FALSE
if(z_level > length(hud_atoms))
stack_trace("get_hud_atoms_for_z_level() was given a z level index out of bounds of hud_atoms!")
return FALSE
. = list()
. += hud_atoms[z_level]
var/max_number_of_linked_z_levels_i_care_to_support_here = 10
while(max_number_of_linked_z_levels_i_care_to_support_here)
var/lower_z_level_exists = SSmapping.level_trait(z_level, ZTRAIT_DOWN)
if(lower_z_level_exists)
z_level--
. += hud_atoms[z_level]
max_number_of_linked_z_levels_i_care_to_support_here--
continue
else
break
///returns a list of all hud users in the given z level and linked upper z levels (because hud users in higher z levels can see below)
/datum/atom_hud/proc/get_hud_users_for_z_level(z_level)
if(z_level > length(hud_users) || z_level <= 0)
stack_trace("get_hud_atoms_for_z_level() was given a z level index [z_level] out of bounds 1->[length(hud_users)] of hud_atoms!")
return FALSE
. = list()
. += hud_users[z_level]
var/max_number_of_linked_z_levels_i_care_to_support_here = 10
while(max_number_of_linked_z_levels_i_care_to_support_here)
var/upper_level_exists = SSmapping.level_trait(z_level, ZTRAIT_UP)
if(upper_level_exists)
z_level++
. += hud_users[z_level]
max_number_of_linked_z_levels_i_care_to_support_here--
continue
else
break
///show this hud to the passed in user
/datum/atom_hud/proc/show_to(mob/new_viewer)
if(!new_viewer)
return
if(!hud_users_all_z_levels[new_viewer])
hud_users_all_z_levels[new_viewer] = 1
RegisterSignal(new_viewer, COMSIG_QDELETING, PROC_REF(unregister_atom), override = TRUE) //both hud users and hud atoms use these signals
RegisterSignal(new_viewer, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(on_atom_or_user_z_level_changed), override = TRUE)
var/turf/their_turf = get_turf(new_viewer)
if(!their_turf)
return
hud_users[their_turf.z][new_viewer] = TRUE
if(next_time_allowed[new_viewer] > world.time)
if(!queued_to_see[new_viewer])
addtimer(CALLBACK(src, PROC_REF(show_hud_images_after_cooldown), new_viewer), next_time_allowed[new_viewer] - world.time)
queued_to_see[new_viewer] = TRUE
else
next_time_allowed[new_viewer] = world.time + ADD_HUD_TO_COOLDOWN
for(var/atom/hud_atom_to_add as anything in get_hud_atoms_for_z_level(their_turf.z))
add_atom_to_single_mob_hud(new_viewer, hud_atom_to_add)
else
hud_users_all_z_levels[new_viewer] += 1 //increment the number of times this hud has been added to this hud user
///Hides the images in this hud from former_viewer
///If absolute is set to true, this will forcefully remove the hud, even if sources in theory remain
/datum/atom_hud/proc/hide_from(mob/former_viewer, absolute = FALSE)
if(!former_viewer || !hud_users_all_z_levels[former_viewer])
return
hud_users_all_z_levels[former_viewer] -= 1//decrement number of sources for this hud on this user (bad way to track i know)
if (absolute || hud_users_all_z_levels[former_viewer] <= 0)//if forced or there arent any sources left, remove the user
if(!hud_atoms_all_z_levels[former_viewer])//make sure we arent unregistering changes on a mob thats also a hud atom for this hud
UnregisterSignal(former_viewer, COMSIG_MOVABLE_Z_CHANGED)
UnregisterSignal(former_viewer, COMSIG_QDELETING)
hud_users_all_z_levels -= former_viewer
if(next_time_allowed[former_viewer])
next_time_allowed -= former_viewer
var/turf/their_turf = get_turf(former_viewer)
if(their_turf)
hud_users[their_turf.z] -= former_viewer
if(queued_to_see[former_viewer])
queued_to_see -= former_viewer
else if (their_turf)
for(var/atom/hud_atom as anything in get_hud_atoms_for_z_level(their_turf.z))
remove_atom_from_single_hud(former_viewer, hud_atom)
/// add new_hud_atom to this hud
/datum/atom_hud/proc/add_atom_to_hud(atom/new_hud_atom)
if(!new_hud_atom)
return FALSE
// No matter where or who you are, you matter to me :)
RegisterSignal(new_hud_atom, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(on_atom_or_user_z_level_changed), override = TRUE)
RegisterSignal(new_hud_atom, COMSIG_QDELETING, PROC_REF(unregister_atom), override = TRUE) //both hud atoms and hud users use these signals
hud_atoms_all_z_levels[new_hud_atom] = TRUE
var/turf/atom_turf = get_turf(new_hud_atom)
if(!atom_turf)
return TRUE
hud_atoms[atom_turf.z] |= new_hud_atom
for(var/mob/mob_to_show as anything in get_hud_users_for_z_level(atom_turf.z))
if(!queued_to_see[mob_to_show])
add_atom_to_single_mob_hud(mob_to_show, new_hud_atom)
return TRUE
/// remove this atom from this hud completely
/datum/atom_hud/proc/remove_atom_from_hud(atom/hud_atom_to_remove)
if(!hud_atom_to_remove || !hud_atoms_all_z_levels[hud_atom_to_remove])
return FALSE
//make sure we arent unregistering a hud atom thats also a hud user mob
if(!hud_users_all_z_levels[hud_atom_to_remove])
UnregisterSignal(hud_atom_to_remove, COMSIG_MOVABLE_Z_CHANGED)
UnregisterSignal(hud_atom_to_remove, COMSIG_QDELETING)
for(var/mob/mob_to_remove as anything in hud_users_all_z_levels)
remove_atom_from_single_hud(mob_to_remove, hud_atom_to_remove)
hud_atoms_all_z_levels -= hud_atom_to_remove
var/turf/atom_turf = get_turf(hud_atom_to_remove)
if(!atom_turf)
return TRUE
hud_atoms[atom_turf.z] -= hud_atom_to_remove
return TRUE
///adds a newly active hud category's image on a hud atom to every mob that could see it
/datum/atom_hud/proc/add_single_hud_category_on_atom(atom/hud_atom, hud_category_to_add)
if(!hud_atom?.active_hud_list?[hud_category_to_add] || QDELING(hud_atom) || !(hud_category_to_add in hud_icons))
return FALSE
if(!hud_atoms_all_z_levels[hud_atom])
add_atom_to_hud(hud_atom)
return TRUE
var/turf/atom_turf = get_turf(hud_atom)
if(!atom_turf)
return FALSE
for(var/mob/hud_user as anything in get_hud_users_for_z_level(atom_turf.z))
if(!hud_user.client)
continue
if(!hud_exceptions[hud_user] || !(hud_atom in hud_exceptions[hud_user]))
hud_user.client.images |= hud_atom.active_hud_list[hud_category_to_add]
return TRUE
///removes the image or images in hud_atom.hud_list[hud_category_to_remove] from every mob that can see it but leaves every other image
///from that atom there.
/datum/atom_hud/proc/remove_single_hud_category_on_atom(atom/hud_atom, hud_category_to_remove)
if(QDELETED(hud_atom) || !(hud_category_to_remove in hud_icons) || !hud_atoms_all_z_levels[hud_atom])
return FALSE
if(!hud_atom.active_hud_list)
remove_atom_from_hud(hud_atom)
return TRUE
var/turf/atom_turf = get_turf(hud_atom)
if(!atom_turf)
return FALSE
for(var/mob/hud_user as anything in get_hud_users_for_z_level(atom_turf.z))
if(!hud_user.client)
continue
hud_user.client.images -= hud_atom.active_hud_list[hud_category_to_remove]//by this point it shouldnt be in active_hud_list
return TRUE
///when a hud atom or hud user changes z levels this makes sure it gets the images it needs and removes the images it doesnt need.
///because of how signals work we need the same proc to handle both use cases because being a hud atom and being a hud user arent mutually exclusive
/datum/atom_hud/proc/on_atom_or_user_z_level_changed(atom/movable/moved_atom, turf/old_turf, turf/new_turf)
SIGNAL_HANDLER
if(old_turf)
if(hud_users_all_z_levels[moved_atom])
hud_users[old_turf.z] -= moved_atom
remove_all_atoms_from_single_hud(moved_atom, get_hud_atoms_for_z_level(old_turf.z))
if(hud_atoms_all_z_levels[moved_atom])
hud_atoms[old_turf.z] -= moved_atom
//this wont include moved_atom since its removed
remove_atom_from_all_huds(get_hud_users_for_z_level(old_turf.z), moved_atom)
if(new_turf)
if(hud_users_all_z_levels[moved_atom])
hud_users[new_turf.z][moved_atom] = TRUE //hud users is associative, hud atoms isnt
add_all_atoms_to_single_mob_hud(moved_atom, get_hud_atoms_for_z_level(new_turf.z))
if(hud_atoms_all_z_levels[moved_atom])
hud_atoms[new_turf.z] |= moved_atom
add_atom_to_all_mob_huds(get_hud_users_for_z_level(new_turf.z), moved_atom)
/// add just hud_atom's hud images (that are part of this atom_hud) to requesting_mob's client.images list
/datum/atom_hud/proc/add_atom_to_single_mob_hud(mob/requesting_mob, atom/hud_atom) //unsafe, no sanity apart from client
if(!requesting_mob || !requesting_mob.client || !hud_atom)
return
for(var/hud_category in (hud_icons & hud_atom.active_hud_list))
if(!hud_exceptions[requesting_mob] || !(hud_atom in hud_exceptions[requesting_mob]))
requesting_mob.client.images |= hud_atom.active_hud_list[hud_category]
/// all passed in hud_atoms's hud images (that are part of this atom_hud) to requesting_mob's client.images list
/// optimization of [/datum/atom_hud/proc/add_atom_to_single_mob_hud] for hot cases, we assert that no nulls will be passed in via the list
/datum/atom_hud/proc/add_all_atoms_to_single_mob_hud(mob/requesting_mob, list/atom/hud_atoms) //unsafe, no sanity apart from client
if(!requesting_mob || !requesting_mob.client)
return
// Hud entries this mob ignores
var/list/mob_exceptions = hud_exceptions[requesting_mob]
for(var/hud_category in hud_icons)
for(var/atom/hud_atom as anything in hud_atoms)
if(mob_exceptions && (hud_atom in hud_exceptions[requesting_mob]))
continue
var/image/output = hud_atom.active_hud_list?[hud_category]
// byond throws a fit if you try to add null to the images list
if(!output)
continue
requesting_mob.client.images |= output
/// add just hud_atom's hud images (that are part of this atom_hud) to all the requesting_mobs's client.images list
/// optimization of [/datum/atom_hud/proc/add_atom_to_single_mob_hud] for hot cases, we assert that no nulls will be passed in via the list
/datum/atom_hud/proc/add_atom_to_all_mob_huds(list/mob/requesting_mobs, atom/hud_atom) //unsafe, no sanity apart from client
if(!hud_atom?.active_hud_list)
return
var/list/images_to_add = list()
for(var/hud_category in (hud_icons & hud_atom.active_hud_list))
images_to_add |= hud_atom.active_hud_list[hud_category]
// Cache for sonic speed, lists are structs
var/list/exceptions = hud_exceptions
for(var/mob/requesting_mob as anything in requesting_mobs)
if(!requesting_mob.client)
continue
if(!exceptions[requesting_mob] || !(hud_atom in exceptions[requesting_mob]))
requesting_mob.client.images |= images_to_add
/// remove every hud image for this hud on atom_to_remove from client_mob's client.images list
/datum/atom_hud/proc/remove_atom_from_single_hud(mob/client_mob, atom/atom_to_remove)
if(!client_mob || !client_mob.client || !atom_to_remove?.active_hud_list)
return
for(var/hud_image in hud_icons)
client_mob.client.images -= atom_to_remove.active_hud_list[hud_image]
/// remove every hud image for this hud pulled from atoms_to_remove from client_mob's client.images list
/// optimization of [/datum/atom_hud/proc/remove_atom_from_single_hud] for hot cases, we assert that no nulls will be passed in via the list
/datum/atom_hud/proc/remove_all_atoms_from_single_hud(mob/client_mob, list/atom/atoms_to_remove)
if(!client_mob || !client_mob.client)
return
for(var/hud_image in hud_icons)
for(var/atom/atom_to_remove as anything in atoms_to_remove)
client_mob.client.images -= atom_to_remove.active_hud_list?[hud_image]
/// remove every hud image for this hud on atom_to_remove from client_mobs's client.images list
/// optimization of [/datum/atom_hud/proc/remove_atom_from_single_hud] for hot cases, we assert that no nulls will be passed in via the list
/datum/atom_hud/proc/remove_atom_from_all_huds(list/mob/client_mobs, atom/atom_to_remove)
if(!atom_to_remove?.active_hud_list)
return
var/list/images_to_remove = list()
for(var/hud_image in hud_icons)
images_to_remove |= atom_to_remove.active_hud_list[hud_image]
for(var/mob/client_mob as anything in client_mobs)
if(!client_mob.client)
continue
client_mob.client.images -= images_to_remove
/datum/atom_hud/proc/unregister_atom(datum/source, force)
SIGNAL_HANDLER
hide_from(source, TRUE)
remove_atom_from_hud(source)
/datum/atom_hud/proc/hide_single_atomhud_from(mob/hud_user, atom/hidden_atom)
if(hud_users_all_z_levels[hud_user])
remove_atom_from_single_hud(hud_user, hidden_atom)
if(!hud_exceptions[hud_user])
hud_exceptions[hud_user] = list(hidden_atom)
else
hud_exceptions[hud_user] += hidden_atom
/datum/atom_hud/proc/unhide_single_atomhud_from(mob/hud_user, atom/hidden_atom)
hud_exceptions[hud_user] -= hidden_atom
var/turf/hud_atom_turf = get_turf(hidden_atom)
if(!hud_atom_turf)
return
if(hud_users[hud_atom_turf.z][hud_user])
add_atom_to_single_mob_hud(hud_user, hidden_atom)
/datum/atom_hud/proc/show_hud_images_after_cooldown(mob/queued_hud_user)
if(!queued_to_see[queued_hud_user])
return
queued_to_see -= queued_hud_user
next_time_allowed[queued_hud_user] = world.time + ADD_HUD_TO_COOLDOWN
var/turf/user_turf = get_turf(queued_hud_user)
if(!user_turf)
return
for(var/atom/hud_atom_to_show as anything in get_hud_atoms_for_z_level(user_turf.z))
add_atom_to_single_mob_hud(queued_hud_user, hud_atom_to_show)
//MOB PROCS
/mob/proc/reload_huds()
var/turf/our_turf = get_turf(src)
if(!our_turf)
return
for(var/datum/atom_hud/hud in GLOB.all_huds)
if(hud?.hud_users_all_z_levels[src])
for(var/atom/hud_atom as anything in hud.get_hud_atoms_for_z_level(our_turf.z))
hud.add_atom_to_single_mob_hud(src, hud_atom)
/mob/dead/new_player/reload_huds()
return
/mob/proc/add_click_catcher()
client.screen += client.void
/mob/dead/new_player/add_click_catcher()
return