Adds the Odyssey gamemode. (#18972)

https://forums.aurorastation.org/topic/20198-mission-briefing-auroras-gamemode-revolution

To-do:

- [x] Finish storyteller verbs.
- [x] Storyteller landmarks.
- [x] Proper storyteller spawning. Right now the gamemode system is
happy with just picking one storyteller and no actors.
- [x] Antagonist whitelists code.
- [x] Adding the Storyteller whitelist.
- [x] Mission map loading code.
- [x] Map in a bunch of missions.
- [ ] Storyteller adminhelps.

---------

Co-authored-by: Matt Atlas <liermattia@gmail.com>
Co-authored-by: DreamySkrell <>
This commit is contained in:
Matt Atlas
2024-11-26 20:18:43 +00:00
committed by GitHub
co-authored by Matt Atlas DreamySkrell <>
parent e1f5de6298
commit 1da20ad33f
194 changed files with 242505 additions and 1904 deletions
+6 -6
View File
@@ -3,27 +3,27 @@
// A mob that the AI controls to look around the station with.
// It streams chunks as it moves around, which will show it what the AI can and cannot see.
/mob/abstract/eye/cameranet
/mob/abstract/eye/freelook
// Generic version of the AI eye without the AI-specific handling, for things like the Camera MIU mask.
name = "Inactive Camera Eye"
name_suffix = "Camera Eye"
living_eye = FALSE
/mob/abstract/eye/cameranet/Initialize()
/mob/abstract/eye/freelook/Initialize()
. = ..()
visualnet = GLOB.cameranet
/mob/abstract/eye/aiEye
/mob/abstract/eye/freelook/aiEye
name = "Inactive AI Eye"
name_suffix = "AI Eye"
icon_state = "AI-eye"
living_eye = FALSE
/mob/abstract/eye/aiEye/Initialize()
/mob/abstract/eye/freelook/aiEye/Initialize()
. = ..()
visualnet = GLOB.cameranet
/mob/abstract/eye/aiEye/setLoc(var/T, var/cancel_tracking = 1)
/mob/abstract/eye/freelook/aiEye/setLoc(var/T, var/cancel_tracking = 1)
. = ..()
if(. && isAI(owner))
var/mob/living/silicon/ai/ai = owner
@@ -55,7 +55,7 @@
/mob/living/silicon/ai/proc/create_eyeobj(var/newloc = get_turf(src))
if(eyeobj) destroy_eyeobj()
eyeobj = new /mob/abstract/eye/aiEye(newloc)
eyeobj = new /mob/abstract/eye/freelook/aiEye(newloc)
eyeobj.possess(src)
/atom/proc/move_camera_by_click()
@@ -91,11 +91,13 @@
return
to_chat(owner, SPAN_NOTICE("You scrub [A.name] off the blueprints."))
log_and_message_admins("deleted area [A.name] via station blueprints.")
var/background_area = /area/space
var/background_area = world.area
var/obj/effect/overmap/visitable/sector/sector = GLOB.map_sectors["[A.z]"]
var/obj/effect/overmap/visitable/sector/exoplanet/exoplanet = sector
if(istype(exoplanet))
background_area = exoplanet.planetary_area
if(SSodyssey.scenario && (GET_Z(owner) in SSodyssey.scenario_zlevels))
background_area = SSodyssey.scenario.base_area
for(var/turf/T in A.contents)
ChangeArea(T, background_area)
if(!locate(/turf) in A)
@@ -309,11 +311,13 @@
return
to_chat(owner, SPAN_NOTICE("You scrub [A.name] off the blueprints."))
log_and_message_admins("deleted area [A.name] from [our_shuttle.name] via shuttle blueprints.")
var/background_area = /area/space
var/background_area = world.area
var/obj/effect/overmap/visitable/sector/sector = GLOB.map_sectors["[A.z]"]
var/obj/effect/overmap/visitable/sector/exoplanet/exoplanet = sector
if(istype(exoplanet))
background_area = exoplanet.planetary_area
if(SSodyssey.scenario && (GET_Z(owner) in SSodyssey.scenario_zlevels))
background_area = SSodyssey.scenario.base_area
for(var/turf/T in A.contents)
ChangeArea(T, background_area)
if(!(locate(/turf) in A))
+7 -2
View File
@@ -1,3 +1,8 @@
// CAMERA CHUNK
//
// A 16x16 grid of the map with a list of turfs that can be seen, are visible and are dimmed.
// Allows the Eye to stream these chunks and know what it can and cannot see.
#define UPDATE_BUFFER 25 // 2.5 seconds
// CHUNK
@@ -109,13 +114,13 @@
// Visualnet adds and removes eyes.
/datum/chunk/proc/add_eye(mob/abstract/eye/eye)
/datum/chunk/proc/add_eye(mob/abstract/eye/freelook/eye)
seenby += eye
eye.visibleChunks += src
if(eye.owner && eye.owner.client)
eye.owner.client.images += obscured
/datum/chunk/proc/remove_eye(mob/abstract/eye/eye)
/datum/chunk/proc/remove_eye(mob/abstract/eye/freelook/eye)
seenby -= eye
eye.visibleChunks -= src
if(eye.owner && eye.owner.client)
+2 -2
View File
@@ -90,7 +90,7 @@
if(click_handler_type)
owner.PushClickHandler(click_handler_type)
setLoc(owner)
visualnet.update_eye_chunks(src, TRUE)
visualnet?.update_eye_chunks(src, TRUE)
/mob/abstract/eye/proc/release(var/mob/user)
if(owner != user || !user)
@@ -125,7 +125,7 @@
if(owner_follows_eye)
owner.forceMove(loc)
visualnet.update_eye_chunks(src)
visualnet?.update_eye_chunks(src)
return TRUE
/mob/abstract/eye/proc/getLoc()
@@ -46,7 +46,7 @@
// Updates what the eye can see. It is recommended you use this when the eye moves or it's location is set.
/datum/visualnet/proc/update_eye_chunks(mob/abstract/eye/eye, var/full_update = FALSE)
/datum/visualnet/proc/update_eye_chunks(mob/abstract/eye/freelook/eye, var/full_update = FALSE)
. = list()
var/turf/T = get_turf(eye)
if(T)
@@ -74,7 +74,7 @@
var/datum/chunk/c = chunk
c.add_eye(eye)
/datum/visualnet/proc/remove_eye(mob/abstract/eye/eye)
/datum/visualnet/proc/remove_eye(mob/abstract/eye/freelook/eye)
for(var/chunk in eye.visibleChunks)
var/datum/chunk/c = chunk
c.remove_eye(eye)
+196
View File
@@ -0,0 +1,196 @@
/mob/abstract/ghost
stat = DEAD
layer = OBSERVER_LAYER
plane = OBSERVER_PLANE
/// Toggle darkness.
var/see_darkness = FALSE
/// Is the ghost able to see things humans can't?
var/ghostvision = FALSE
/// This variable generally controls whether a ghost has restrictions on where it can go or not (ex. if the ghost can bypass holy places).
var/has_ghost_restrictions = TRUE
/// If the ghost has antagHUD.
var/antagHUD = 0
/// Necessary for seeing wires.
var/obj/item/device/multitool/ghost_multitool
/// The POI we're orbiting.
var/orbiting_ref
/mob/abstract/ghost/Initialize(mapload)
. = ..()
sight |= SEE_TURFS | SEE_MOBS | SEE_OBJS | SEE_SELF
see_invisible = SEE_INVISIBLE_OBSERVER
add_verb(src, /mob/abstract/ghost/proc/dead_tele)
ghost_multitool = new(src)
/mob/abstract/ghost/Destroy()
QDEL_NULL(ghost_multitool)
return ..()
/mob/abstract/ghost/Topic(href, href_list)
if (href_list["track"])
if(istype(href_list["track"],/mob))
var/mob/target = locate(href_list["track"]) in GLOB.mob_list
if(target)
ManualFollow(target)
else
var/atom/target = locate(href_list["track"])
if(istype(target))
ManualFollow(target)
/mob/abstract/ghost/ClickOn(var/atom/A, var/params)
if(!canClick())
return
setClickCooldown(4)
// You are responsible for checking config.ghost_interaction when you override this function
// Not all of them require checking, see below
A.attack_ghost(src)
/mob/abstract/ghost/Post_Incorpmove()
orbiting?.end_orbit(src)
/mob/abstract/ghost/orbit()
set_dir(2)//reset dir so the right directional sprites show up
return ..()
/mob/abstract/ghost/verb/toggle_darkness()
set name = "Toggle Darkness"
set category = "Ghost"
see_darkness = !see_darkness
update_sight()
/mob/abstract/ghost/proc/update_sight()
set_sight(sight|SEE_TURFS|SEE_MOBS|SEE_OBJS)
set_see_invisible(SEE_INVISIBLE_LEVEL_TWO)
if (!see_darkness)
set_see_invisible(SEE_INVISIBLE_NOLIGHTING)
else
set_see_invisible(ghostvision ? SEE_INVISIBLE_OBSERVER : SEE_INVISIBLE_LIVING)
/mob/abstract/ghost/verb/toggle_ghostsee()
set name = "Toggle Ghost Vision"
set category = "Ghost"
set desc = "Toggles your ability to see things only ghosts can see, like ghosts."
ghostvision = !ghostvision
update_sight()
to_chat(usr, SPAN_NOTICE("You [(ghostvision ? "now" : "no longer")] have ghost vision."))
/mob/abstract/ghost/proc/dead_tele()
set name = "Teleport"
set category = "Ghost"
set desc= "Teleport to a location."
if(!istype(usr, /mob/abstract/ghost))
to_chat(usr, SPAN_WARNING("You need to be a ghost!"))
return
var/area_name = tgui_input_list(src, "Select an area to teleport to.", "Teleport", GLOB.ghostteleportlocs)
remove_verb(usr, /mob/abstract/ghost/proc/dead_tele)
ADD_VERB_IN(usr, 30, /mob/abstract/ghost/proc/dead_tele)
var/area/thearea = GLOB.ghostteleportlocs[area_name]
if(!thearea)
return
var/list/L = list()
var/holyblock = FALSE
if(usr.invisibility <= SEE_INVISIBLE_LIVING || (usr.mind in cult.current_antagonists))
for(var/turf/T in get_area_turfs(thearea))
if(!T.holy && has_ghost_restrictions)
L+=T
else
holyblock = TRUE
else
for(var/turf/T in get_area_turfs(thearea))
L+=T
if(!L || !L.len)
if(holyblock && has_ghost_restrictions)
to_chat(usr, SPAN_WARNING("This area has been entirely made into sacred grounds, you cannot enter it while you are in this plane of existence!"))
return
else
to_chat(usr, "No area available.")
return
var/turf/P = pick(L)
if(on_restricted_level(P.z) && has_ghost_restrictions)
to_chat(usr, "You can not teleport to this area.")
return
orbiting?.end_orbit(src)
usr.forceMove(pick(L))
/mob/abstract/ghost/verb/follow()
set name = "Follow"
set category = "Ghost"
set desc = "Follow and haunt a mob."
var/datum/tgui_module/follow_menu/GM = new /datum/tgui_module/follow_menu(usr)
GM.ui_interact(usr)
// This is the ghost's follow verb with an argument
/mob/abstract/ghost/proc/ManualFollow(var/atom/movable/target)
if(!target)
return
//Stops orbit if there's any; TG doesn't do this, but if you don't it breaks the orbiting reference
//if you are jumping from one mob to another, hence why we're doing it here
orbiting?.end_orbit(src)
var/list/icon_dimensions = get_icon_dimensions(target.icon)
var/orbitsize = (icon_dimensions["width"] + icon_dimensions["height"]) * 0.5
orbitsize -= (orbitsize/ICON_SIZE_ALL)*(ICON_SIZE_ALL*0.25)
var/rot_seg = 30 //Let's make it simple and it's just a circle
orbit(target,orbitsize, FALSE, 20, rot_seg)
to_chat(src, SPAN_NOTICE("Now following \the <b>[target]</b>."))
update_sight()
/mob/abstract/ghost/proc/on_restricted_level(var/check)
if(!check)
check = z
//Check if they are a staff member
if(check_rights(R_MOD|R_ADMIN|R_DEV, show_msg=FALSE, user=src))
return FALSE
//Check if the z level is in the restricted list
if (!(check in SSatlas.current_map.restricted_levels))
return FALSE
return TRUE
/mob/abstract/ghost/verb/analyze_air()
set name = "Analyze Air"
set category = "Ghost"
// Shamelessly copied from the Gas Analyzers
if(!isturf(loc))
return
var/datum/gas_mixture/environment = loc.return_air()
var/pressure = environment.return_pressure()
var/total_moles = environment.total_moles
to_chat(src, SPAN_NOTICE("<B>Results:</B>"))
if(abs(pressure - ONE_ATMOSPHERE) < 10)
to_chat(src, SPAN_NOTICE("Pressure: [round(pressure,0.1)] kPa"))
else
to_chat(src, SPAN_WARNING("Pressure: [round(pressure,0.1)] kPa"))
if(total_moles)
for(var/g in environment.gas)
to_chat(src, SPAN_NOTICE("[gas_data.name[g]]: [round((environment.gas[g] / total_moles) * 100)]% ([round(environment.gas[g], 0.01)] moles)"))
to_chat(src, SPAN_NOTICE("Temperature: [round(environment.temperature-T0C,0.1)]&deg;C ([round(environment.temperature,0.1)]K)"))
to_chat(src, SPAN_NOTICE("Heat Capacity: [round(environment.heat_capacity(),0.1)]"))
/mob/abstract/ghost/verb/view_manifest()
set name = "Show Crew Manifest"
set category = "Ghost"
SSrecords.open_manifest_tgui(usr)
@@ -1,4 +1,4 @@
/mob/abstract/observer/LateLogin()
/mob/abstract/ghost/observer/LateLogin()
..()
if (ghostimage)
ghostimage.icon_state = src.icon_state
@@ -1,4 +1,4 @@
/mob/abstract/observer/Logout()
/mob/abstract/ghost/observer/Logout()
..()
// This is one of very few spawn()s that we cannot get rid of
// Yes it needs to be like this
@@ -1,53 +1,44 @@
/mob/abstract/observer
/mob/abstract/ghost/observer
name = "ghost"
desc = "It's a g-g-g-g-ghooooost!" //jinkies!
icon = 'icons/mob/mob.dmi'
icon_state = "ghost"
layer = OBSERVER_LAYER
plane = OBSERVER_PLANE
stat = DEAD
density = 0
canmove = 0
blinded = 0
anchored = 1 // don't get pushed around
invisibility = INVISIBILITY_OBSERVER
simulated = FALSE
var/can_reenter_corpse
var/datum/hud/hud = null // hud
var/bootime = 0
var/started_as_observer //This variable is set to 1 when you enter the game as an observer.
//If you died in the game and are a ghsot - this will remain as null.
//Note that this is not a reliable way to determine if admins started as observers, since they change mobs a lot.
var/has_enabled_antagHUD = 0
var/medHUD = 0
var/antagHUD = 0
universal_speak = 1
// var/atom/movable/following = null
var/admin_ghosted = 0
var/anonsay = 0
var/image/ghostimage = null //this mobs ghost image, for deleting and stuff
var/ghostvision = 1 //is the ghost able to see things humans can't?
var/seedarkness = 1
var/is_manifest = 0
var/ghost_cooldown = 0
var/obj/item/device/multitool/ghost_multitool
incorporeal_move = INCORPOREAL_GHOST
mob_thinks = FALSE
/// The POI we're orbiting (orbit menu)
var/orbiting_ref
/// If the ghost can re-enter their corpse.
var/can_reenter_corpse
/// The ghost's HUD datum.
var/datum/hud/hud = null
/// This variable is set to 1 when you enter the game as an observer. Remains null if you died in the game and are a ghost. Not reliable for admins; they change mobs a lot.
var/started_as_observer
/// If the ghost has enabled antagHUD.
var/has_enabled_antagHUD = 0
/// If the ghost has enabled medHUD.
var/medHUD = 0
/// If this is an adminghost.
var/admin_ghosted = 0
/// If this ghost has enabled chat anonymization.
var/anonsay = 0
/// This mob's ghost image, for deleting and stuff.
var/image/ghostimage
/// If the ghost can be seen through cult shenanigans.
var/is_manifest = 0
/// Cooldown for ghost abilities, such as move_item().
var/ghost_cooldown = 0
/mob/abstract/observer/New(mob/body)
if (istype(body, /mob/abstract/observer))
/mob/abstract/ghost/observer/Initialize(mapload, mob/body)
. = ..()
if (istype(body, /mob/abstract/ghost/observer))
return//A ghost can't become a ghost.
sight |= SEE_TURFS | SEE_MOBS | SEE_OBJS | SEE_SELF
see_invisible = SEE_INVISIBLE_OBSERVER
add_verb(src, /mob/abstract/observer/proc/dead_tele)
set_stat(DEAD)
ghostimage = image(src.icon,src,src.icon_state)
@@ -96,32 +87,16 @@
name = capitalize(pick(first_names_male)) + " " + capitalize(pick(last_names))
real_name = name
ghost_multitool = new(src)
..()
/mob/abstract/observer/Destroy()
qdel(ghost_multitool)
ghost_multitool = null
/mob/abstract/ghost/observer/Destroy()
if (ghostimage)
SSmobs.ghost_darkness_images -= ghostimage
qdel(ghostimage)
ghostimage = null
updateallghostimages()
return ..()
/mob/abstract/observer/Topic(href, href_list)
if (href_list["track"])
if(istype(href_list["track"],/mob))
var/mob/target = locate(href_list["track"]) in GLOB.mob_list
if(target)
ManualFollow(target)
else
var/atom/target = locate(href_list["track"])
if(istype(target))
ManualFollow(target)
/mob/abstract/observer/proc/initialise_postkey(set_timers = TRUE)
/mob/abstract/ghost/observer/proc/initialise_postkey(set_timers = TRUE)
//This function should be run after a ghost has been created and had a ckey assigned
if (!set_timers)
return
@@ -135,19 +110,19 @@
if (!get_death_time(CREW))
set_death_time(CREW, world.time)
/mob/abstract/observer/attackby(obj/item/attacking_item, mob/user)
/mob/abstract/ghost/observer/attackby(obj/item/attacking_item, mob/user)
if(istype(attacking_item, /obj/item/book/tome))
var/mob/abstract/observer/M = src
var/mob/abstract/ghost/observer/M = src
M.manifest(user)
/mob/abstract/observer/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
/mob/abstract/ghost/observer/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
return 1
/*
Transfer_mind is there to check if mob is being deleted/not going to have a body.
Works together with spawning an observer, noted above.
*/
/mob/abstract/observer/Life(seconds_per_tick, times_fired)
/mob/abstract/ghost/observer/Life(seconds_per_tick, times_fired)
..()
if(!loc) return
if(!client) return 0
@@ -164,20 +139,7 @@ Works together with spawning an observer, noted above.
if(medHUD)
process_medHUD(src)
/mob/abstract/observer/proc/on_restricted_level(var/check)
if(!check)
check = z
//Check if they are a staff member
if(check_rights(R_MOD|R_ADMIN|R_DEV, show_msg=FALSE, user=src))
return FALSE
//Check if the z level is in the restricted list
if (!(check in SSatlas.current_map.restricted_levels))
return FALSE
return TRUE
/mob/abstract/observer/proc/teleport_to_spawn(var/message)
/mob/abstract/ghost/observer/proc/teleport_to_spawn(var/message)
if(!message)
message = "You can not freely observe on this z-level."
@@ -187,14 +149,13 @@ Works together with spawning an observer, noted above.
to_chat(src, SPAN_NOTICE("[message]"))
forceMove(O.loc)
/mob/abstract/observer/proc/process_medHUD(var/mob/M)
/mob/abstract/ghost/observer/proc/process_medHUD(var/mob/M)
var/client/C = M.client
for(var/mob/living/carbon/human/patient in oview(M, 14))
C.images += patient.hud_list[HEALTH_HUD]
C.images += patient.hud_list[STATUS_HUD_OOC]
/mob/abstract/observer/proc/assess_targets(list/target_list, mob/abstract/observer/U)
/mob/abstract/ghost/observer/proc/assess_targets(list/target_list, mob/abstract/ghost/observer/U)
var/client/C = U.client
for(var/mob/living/carbon/human/target in target_list)
C.images += target.hud_list[SPECIALROLE_HUD]
@@ -205,7 +166,7 @@ Works together with spawning an observer, noted above.
/mob/proc/ghostize(var/can_reenter_corpse = TRUE, var/should_set_timer = TRUE)
if(ckey)
CutOverlays(image('icons/effects/effects.dmi', "zzz_glow")) // not very efficient but ghostize isn't called /too/ often.
var/mob/abstract/observer/ghost = new(src) //Transfer safety to observer spawning proc.
var/mob/abstract/ghost/observer/ghost = new(src, src) //Transfer safety to observer spawning proc.
ghost.can_reenter_corpse = can_reenter_corpse
ghost.timeofdeath = src.stat == DEAD ? src.timeofdeath : world.time
@@ -213,7 +174,7 @@ Works together with spawning an observer, noted above.
ghost.initialise_postkey(should_set_timer)
if(ghost.client)
if(!ghost.client.holder && !GLOB.config.antag_hud_allowed) // For new ghosts we remove the verb from even showing up if it's not allowed.
remove_verb(ghost, /mob/abstract/observer/verb/toggle_antagHUD) // Poor guys, don't know what they are missing!
remove_verb(ghost, /mob/abstract/ghost/observer/verb/toggle_antagHUD) // Poor guys, don't know what they are missing!
ghost.client.init_verbs()
return ghost
@@ -243,20 +204,20 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
var/turf/location = get_turf(src)
message_admins("[key_name_admin(usr)] has ghosted. (<A HREF='?_src_=holder;adminplayerobservecoodjump=1;X=[location.x];Y=[location.y];Z=[location.z]'>JMP</a>)")
log_game("[key_name_admin(usr)] has ghosted.")
var/mob/abstract/observer/ghost = ghostize(0) //0 parameter is so we can never re-enter our body, "Charlie, you can never come baaaack~" :3
var/mob/abstract/ghost/observer/ghost = ghostize(0) //0 parameter is so we can never re-enter our body, "Charlie, you can never come baaaack~" :3
ghost.timeofdeath = world.time // Because the living mob won't have a time of death and we want the respawn timer to work properly.
announce_ghost_joinleave(ghost)
/mob/abstract/observer/can_use_hands()
/mob/abstract/ghost/observer/can_use_hands()
return FALSE
/mob/abstract/observer/is_active()
/mob/abstract/ghost/observer/is_active()
return FALSE
/mob/abstract/observer/set_stat()
/mob/abstract/ghost/observer/set_stat()
stat = DEAD // They are also always dead
/mob/abstract/observer/verb/reenter_corpse()
/mob/abstract/ghost/observer/verb/reenter_corpse()
set category = "Ghost"
set name = "Re-enter Corpse"
if(!client) return
@@ -284,7 +245,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
announce_ghost_joinleave(mind, 0, "They now occupy their body again.")
return 1
/mob/abstract/observer/verb/toggle_medHUD()
/mob/abstract/ghost/observer/verb/toggle_medHUD()
set category = "Ghost"
set name = "Toggle MedicHUD"
set desc = "Toggles Medical HUD allowing you to see how everyone is doing"
@@ -297,7 +258,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
medHUD = 1
to_chat(src, SPAN_NOTICE("<B>Medical HUD Enabled</B>"))
/mob/abstract/observer/verb/scan_target()
/mob/abstract/ghost/observer/verb/scan_target()
set category = "Ghost"
set name = "Medical Scan Target"
set desc = "Analyse the health of whatever you are following."
@@ -313,7 +274,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
else
to_chat(src, SPAN_WARNING("This isn't a scannable target."))
/mob/abstract/observer/verb/toggle_antagHUD()
/mob/abstract/ghost/observer/verb/toggle_antagHUD()
set category = "Ghost"
set name = "Toggle AntagHUD"
set desc = "Toggles AntagHUD allowing you to see who is the antagonist"
@@ -324,7 +285,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
if(!GLOB.config.antag_hud_allowed && (!client.holder || aux_staff))
to_chat(src, SPAN_WARNING("Admins have disabled this for this round."))
return
var/mob/abstract/observer/M = src
var/mob/abstract/ghost/observer/M = src
if(jobban_isbanned(M, "AntagHUD"))
to_chat(src, SPAN_DANGER("You have been banned from using this feature"))
return
@@ -341,145 +302,40 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
M.antagHUD = 1
to_chat(src, SPAN_NOTICE("<B>AntagHUD Enabled</B>"))
/mob/abstract/observer/proc/dead_tele(A in GLOB.ghostteleportlocs)
set category = "Ghost"
set name = "Teleport"
set desc= "Teleport to a location"
if(!istype(usr, /mob/abstract/observer))
to_chat(usr, "Not when you're not dead!")
return
remove_verb(usr, /mob/abstract/observer/proc/dead_tele)
ADD_VERB_IN(usr, 30, /mob/abstract/observer/proc/dead_tele)
var/area/thearea = GLOB.ghostteleportlocs[A]
if(!thearea) return
var/list/L = list()
var/holyblock = 0
if(usr.invisibility <= SEE_INVISIBLE_LIVING || (usr.mind in cult.current_antagonists))
for(var/turf/T in get_area_turfs(thearea))
if(!T.holy)
L+=T
else
holyblock = 1
else
for(var/turf/T in get_area_turfs(thearea))
L+=T
if(!L || !L.len)
if(holyblock)
to_chat(usr, SPAN_WARNING("This area has been entirely made into sacred grounds, you cannot enter it while you are in this plane of existence!"))
return
else
to_chat(usr, "No area available.")
return
var/turf/P = pick(L)
if(on_restricted_level(P.z))
to_chat(usr, "You can not teleport to this area")
return
usr.abstract_move(pick(L))
/mob/abstract/observer/verb/follow()
set category = "Ghost"
set name = "Follow" // "Haunt"
set desc = "Follow and haunt a mob."
var/datum/tgui_module/ghost_menu/GM = new /datum/tgui_module/ghost_menu(usr)
GM.ui_interact(usr)
// This is the ghost's follow verb with an argument
/mob/abstract/observer/proc/ManualFollow(atom/movable/target)
if(!target)
return
//Stops orbit if there's any; TG doesn't do this, but if you don't it breaks the orbiting reference
//if you are jumping from one mob to another, hence why we're doing it here
orbiting?.end_orbit(src)
var/list/icon_dimensions = get_icon_dimensions(target.icon)
var/orbitsize = (icon_dimensions["width"] + icon_dimensions["height"]) * 0.5
orbitsize -= (orbitsize/ICON_SIZE_ALL)*(ICON_SIZE_ALL*0.25)
var/rot_seg = 30 //Let's make it simple and it's just a circle
orbit(target,orbitsize, FALSE, 20, rot_seg)
to_chat(src, SPAN_NOTICE("Now following \the <b>[target]</b>."))
updateghostsight()
/mob/abstract/observer/orbit()
set_dir(2)//reset dir so the right directional sprites show up
return ..()
/mob/proc/check_holy(var/turf/T)
return 0
/mob/abstract/observer/check_holy(var/turf/T)
/mob/abstract/ghost/observer/check_holy(var/turf/T)
if(check_rights(R_ADMIN|R_FUN, 0, src))
return 0
return (T && T.holy) && (invisibility <= SEE_INVISIBLE_LIVING || (mind in cult.current_antagonists))
/mob/abstract/observer/verb/jumptomob(input in getmobs()) //Moves the ghost instead of just changing the ghosts's eye -Nodrak
/mob/abstract/ghost/observer/verb/jumptomob(input in getmobs()) //Moves the ghost instead of just changing the ghosts's eye -Nodrak
set category = "Ghost"
set name = "Jump to Mob"
set desc = "Teleport to a mob"
if(istype(src, /mob/abstract/observer)) //Make sure they're an observer!
var/mob/source_mob = src //Source mob
if(istype(usr, /mob/abstract/ghost/observer)) //Make sure they're an observer!
var/target = getmobs()[input]
if(!target) return
var/turf/T = get_turf(target) //Turf of the destination mob
if(T && isturf(T)) //Make sure the turf exists, then move the source to that destination.
source_mob.abstract_move(T)
abstract_move(T)
else
to_chat(src, "This mob is not located in the game world.")
/mob/abstract/observer/memory()
/mob/abstract/ghost/observer/memory()
set hidden = 1
to_chat(src, SPAN_WARNING("You are dead! You have no mind to store memory!"))
/mob/abstract/observer/add_memory()
/mob/abstract/ghost/observer/add_memory()
set hidden = 1
to_chat(src, SPAN_WARNING("You are dead! You have no mind to store memory!"))
/mob/abstract/observer/verb/analyze_air()
set name = "Analyze Air"
set category = "Ghost"
if(!istype(usr, /mob/abstract/observer)) return
// Shamelessly copied from the Gas Analyzers
if (!( istype(usr.loc, /turf) ))
return
var/datum/gas_mixture/environment = usr.loc.return_air()
var/pressure = environment.return_pressure()
var/total_moles = environment.total_moles
to_chat(src, SPAN_NOTICE("<B>Results:</B>"))
if(abs(pressure - ONE_ATMOSPHERE) < 10)
to_chat(src, SPAN_NOTICE("Pressure: [round(pressure,0.1)] kPa"))
else
to_chat(src, SPAN_WARNING("Pressure: [round(pressure,0.1)] kPa"))
if(total_moles)
for(var/g in environment.gas)
to_chat(src, SPAN_NOTICE("[gas_data.name[g]]: [round((environment.gas[g] / total_moles) * 100)]% ([round(environment.gas[g], 0.01)] moles)"))
to_chat(src, SPAN_NOTICE("Temperature: [round(environment.temperature-T0C,0.1)]&deg;C ([round(environment.temperature,0.1)]K)"))
to_chat(src, SPAN_NOTICE("Heat Capacity: [round(environment.heat_capacity(),0.1)]"))
/mob/abstract/observer/verb/view_manifest()
set name = "Show Crew Manifest"
set category = "Ghost"
SSrecords.open_manifest_tgui(usr)
//This is called when a ghost is drag clicked to something.
/mob/abstract/observer/MouseDrop(atom/over)
/mob/abstract/ghost/observer/MouseDrop(atom/over)
if(!usr || !over) return
if(isobserver(usr) && usr.client && isliving(over))
var/mob/living/M = over
@@ -496,7 +352,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
return ..()
/mob/abstract/observer/proc/try_possession(var/mob/living/M)
/mob/abstract/ghost/observer/proc/try_possession(var/mob/living/M)
if(!GLOB.config.ghosts_can_possess_animals)
to_chat(usr, SPAN_WARNING("Ghosts are not permitted to possess animals."))
return 0
@@ -505,7 +361,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
return M.do_possession(src)
//Used for drawing on walls with blood puddles as a spooky ghost.
/mob/abstract/observer/verb/bloody_doodle()
/mob/abstract/ghost/observer/verb/bloody_doodle()
set category = "Ghost"
set name = "Write in blood"
set desc = "If the round is sufficiently spooky, write a short message in blood on the floor or a wall. Remember, no IC in OOC or OOC in IC."
@@ -573,19 +429,19 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
W.add_hiddenprint(src)
W.visible_message(SPAN_WARNING("Invisible fingers crudely paint something in blood on [T]..."))
/mob/abstract/observer/pointed(atom/A as mob|obj|turf in view())
/mob/abstract/ghost/observer/pointed(atom/A as mob|obj|turf in view())
if(!..())
return 0
src.visible_message("<span class='deadsay'><b>[src]</b> points to [A]</span>")
return 1
/mob/abstract/observer/proc/manifest(mob/user)
/mob/abstract/ghost/observer/proc/manifest(mob/user)
is_manifest = 0
if(!is_manifest)
is_manifest = 1
add_verb(src, /mob/abstract/observer/proc/toggle_visibility_verb)
add_verb(src, /mob/abstract/observer/proc/ghost_whisper)
add_verb(src, /mob/abstract/observer/proc/move_item)
add_verb(src, /mob/abstract/ghost/observer/proc/toggle_visibility_verb)
add_verb(src, /mob/abstract/ghost/observer/proc/ghost_whisper)
add_verb(src, /mob/abstract/ghost/observer/proc/move_item)
if(src.invisibility != 0)
user.visible_message( \
@@ -600,7 +456,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
)
/mob/abstract/observer/proc/toggle_icon(var/icon)
/mob/abstract/ghost/observer/proc/toggle_icon(var/icon)
if(!client)
return
@@ -615,14 +471,14 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
var/image/J = image('icons/mob/mob.dmi', loc = src, icon_state = icon)
client.images += J
/mob/abstract/observer/proc/toggle_visibility_verb()
/mob/abstract/ghost/observer/proc/toggle_visibility_verb()
set category = "Ghost"
set name = "Toggle Visibility"
set desc = "Allows you to turn (in)visible (almost) at will."
toggle_visibility()
/mob/abstract/observer/proc/toggle_visibility(var/forced = 0)
/mob/abstract/ghost/observer/proc/toggle_visibility(var/forced = 0)
var/toggled_invisible
if(!forced && invisibility && world.time < toggled_invisible + 600)
to_chat(src, "You must gather strength before you can turn visible again...")
@@ -639,7 +495,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
// Give the ghost a cult icon which should be visible only to itself
toggle_icon("cult")
/mob/abstract/observer/proc/ghost_whisper()
/mob/abstract/ghost/observer/proc/ghost_whisper()
set category = "Ghost"
set name = "Spectral Whisper"
@@ -661,7 +517,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
else
to_chat(src, SPAN_DANGER("You have not been pulled past the veil!"))
/mob/abstract/observer/proc/move_item()
/mob/abstract/ghost/observer/proc/move_item()
set category = "Ghost"
set name = "Move item"
set desc = "Move a small item to where you are."
@@ -695,7 +551,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
ghost_cooldown = world.time + 500
/mob/abstract/observer/verb/toggle_anonsay()
/mob/abstract/ghost/observer/verb/toggle_anonsay()
set category = "Ghost"
set name = "Toggle Anonymous Chat"
set desc = "Toggles showing your key in dead chat."
@@ -706,30 +562,16 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
else
to_chat(src, "<span class='info'>Your key will be publicly visible again.</span>")
/mob/abstract/observer/canface()
/mob/abstract/ghost/observer/canface()
return 1
/mob/proc/can_admin_interact()
return 0
/mob/abstract/observer/can_admin_interact()
/mob/abstract/ghost/observer/can_admin_interact()
return check_rights(R_ADMIN, 0, src)
/mob/abstract/observer/verb/toggle_ghostsee()
set name = "Toggle Ghost Vision"
set desc = "Toggles your ability to see things only ghosts can see, like other ghosts"
set category = "Ghost"
ghostvision = !(ghostvision)
updateghostsight()
to_chat(usr, "You [(ghostvision ? "now" : "no longer")] have ghost vision.")
/mob/abstract/observer/verb/toggle_darkness()
set name = "Toggle Darkness"
set category = "Ghost"
seedarkness = !(seedarkness)
updateghostsight()
/mob/abstract/observer/proc/updateghostsight()
/mob/abstract/ghost/observer/update_sight()
//if they are on a restricted level, then set the ghost vision for them.
if(on_restricted_level())
//On the restricted level they have the same sight as the mob
@@ -740,7 +582,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
set_sight(sight|SEE_TURFS|SEE_MOBS|SEE_OBJS)
set_see_invisible(SEE_INVISIBLE_LEVEL_TWO)
if (!seedarkness)
if (!see_darkness)
set_see_invisible(SEE_INVISIBLE_NOLIGHTING)
else
set_see_invisible(ghostvision ? SEE_INVISIBLE_OBSERVER : SEE_INVISIBLE_LIVING)
@@ -748,13 +590,13 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
updateghostimages()
/proc/updateallghostimages()
for (var/mob/abstract/observer/O in GLOB.player_list)
for (var/mob/abstract/ghost/observer/O in GLOB.player_list)
O.updateghostimages()
/mob/abstract/observer/proc/updateghostimages()
/mob/abstract/ghost/observer/proc/updateghostimages()
if (!client)
return
if (seedarkness || !ghostvision)
if (see_darkness || !ghostvision)
client.images -= SSmobs.ghost_darkness_images
client.images |= SSmobs.ghost_sightless_images
else
@@ -764,7 +606,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
if (ghostimage)
client.images -= ghostimage //remove ourself
/mob/abstract/observer/MayRespawn(var/feedback = 0, var/respawn_type = null)
/mob/abstract/ghost/observer/MayRespawn(var/feedback = 0, var/respawn_type = null)
if(!client)
return 0
if(GLOB.config.antag_hud_restricted && has_enabled_antagHUD == 1)
@@ -798,7 +640,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
if(client && eyeobj)
return "|\[<a href='byond://?src=[REF(ghost)];track=[REF(eyeobj)]'>E</a>\]"
/mob/abstract/observer/extra_ghost_link(var/atom/ghost)
/mob/abstract/ghost/observer/extra_ghost_link(var/atom/ghost)
if(mind && mind.current)
return "|\[<a href='byond://?src=[REF(ghost)];track=[REF(mind.current)]'>B</a>\]"
@@ -809,7 +651,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
//Opens the Ghost Spawner Menu
/mob/abstract/observer/verb/ghost_spawner()
/mob/abstract/ghost/observer/verb/ghost_spawner()
set category = "Ghost"
set name = "Ghost Spawner"
@@ -819,7 +661,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
SSghostroles.ui_interact(usr)
/mob/abstract/observer/verb/submitpai()
/mob/abstract/ghost/observer/verb/submitpai()
set category = "Ghost"
set name = "Submit pAI personality"
set desc = "Submits you pAI personality to the pAI candidate pool."
@@ -829,7 +671,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
return
SSpai.recruitWindow(src)
/mob/abstract/observer/verb/revokepai()
/mob/abstract/ghost/observer/verb/revokepai()
set category = "Ghost"
set name = "Revoke pAI personality"
set desc = "Removes you from the pAI candidite pool."
@@ -837,10 +679,10 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
if(SSpai.revokeCandidancy(src))
to_chat(src, "You have been removed from the pAI candidate pool.")
/mob/abstract/observer/can_hear_radio(speaker_coverage = list())
/mob/abstract/ghost/observer/can_hear_radio(speaker_coverage = list())
if(client && (client.prefs.toggles & CHAT_GHOSTRADIO))
return TRUE
return ..()
/mob/abstract/observer/get_speech_bubble_state_modifier()
/mob/abstract/ghost/observer/get_speech_bubble_state_modifier()
return "ghost"
@@ -1,4 +1,4 @@
/mob/abstract/observer/say(var/message, var/datum/language/speaking = null, var/verb="says", var/alt_name="", var/ghost_hearing = GHOSTS_ALL_HEAR, var/whisper = FALSE)
/mob/abstract/ghost/observer/say(var/message, var/datum/language/speaking = null, var/verb="says", var/alt_name="", var/ghost_hearing = GHOSTS_ALL_HEAR, var/whisper = FALSE)
if (!message)
return
@@ -0,0 +1,158 @@
/mob/abstract/ghost/storyteller
name = "Storyteller"
desc = "You wonder what story will be told today. Hopefully one where your character with 200 euros' worth of custom art doesn't die. That would be a shame. Wouldn't it?"
icon = 'icons/mob/mob.dmi'
icon_state = "ghost"
status_flags = GODMODE
invisibility = INVISIBILITY_OBSERVER
see_invisible = SEE_INVISIBLE_OBSERVER
layer = OBSERVER_LAYER
incorporeal_move = INCORPOREAL_GHOST
simulated = FALSE
density = FALSE
mob_thinks = FALSE
universal_speak = TRUE
/mob/abstract/ghost/storyteller/Initialize()
. = ..()
SSghostroles.add_spawn_atom("storyteller", src)
/mob/abstract/ghost/storyteller/Destroy()
SSodyssey.remove_storyteller(src)
GLOB.storytellers.remove_antagonist(mind)
SSghostroles.remove_spawn_atom("storyteller", src)
return ..()
/mob/abstract/ghost/storyteller/LateLogin()
. = ..()
var/client/C = client
// if there's no mind, the initial setup hasn't run
if(C && !mind)
mind = new /datum/mind(key)
mind.active = TRUE
mind.current = src
real_name = "Storyteller ([client.ckey])"
name = "Storyteller ([client.ckey])"
SSodyssey.add_storyteller(src)
GLOB.storytellers.add_antagonist(mind)
/mob/abstract/ghost/storyteller/ghostize(can_reenter_corpse, should_set_timer)
. = ..()
if(!can_reenter_corpse)
SSodyssey.remove_storyteller(src)
/mob/abstract/ghost/storyteller/say(var/message, var/datum/language/speaking = null, var/verb="says", var/alt_name="", var/ghost_hearing = GHOSTS_ALL_HEAR, var/whisper = FALSE)
if (!message)
return
var/msg = sanitize(message)
if(!msg)
return
log_ooc("(STORYTELLER) [name]/[key] : [msg]")
var/list/turf/messageturfs = list() //List of turfs we broadcast to.
var/list/mob/messagemobs = list() //List of living mobs nearby who can hear it
for(var/turf in range(world.view, get_turf(src)))
messageturfs += turf
for(var/mob/M in GLOB.player_list)
if(!M.client || istype(M, /mob/abstract/new_player))
continue
if(isAI(M))
var/mob/living/silicon/ai/AI = M
if(get_turf(AI.eyeobj) in messageturfs)
messagemobs += M
continue
if(get_turf(M) in messageturfs)
messagemobs += M
var/display_name = key
if(client.holder && client.holder.fakekey)
display_name = client.holder.fakekey
msg = process_chat_markup(msg, list("*"))
var/prefix
var/admin_stuff
for(var/client/target in GLOB.clients)
admin_stuff = ""
var/display_remote = FALSE
if (target.holder && ((R_MOD|R_ADMIN) & target.holder.rights))
display_remote = TRUE
if(display_remote)
prefix = "(R)"
admin_stuff += "/([key])"
if(target != client)
admin_stuff += "(<A HREF='?src=[REF(target.holder)];adminplayerobservejump=[REF(src)]'>JMP</A>)"
if(target.mob in messagemobs)
prefix = ""
if((target.mob in messagemobs) || display_remote)
to_chat(target, SPAN_STORYTELLER("[create_text_tag("STORY", target)] [span("prefix", prefix)]<EM>[display_name][admin_stuff]:</EM> [span("message linkify", msg)]"))
/mob/abstract/ghost/storyteller/dust()
return
/mob/abstract/ghost/storyteller/gib()
return
/mob/abstract/ghost/storyteller/can_fall()
return FALSE
/mob/abstract/ghost/storyteller/conveyor_act()
return
/mob/abstract/ghost/storyteller/ex_act(var/severity = 2.0)
return
/mob/abstract/ghost/storyteller/singularity_act()
return
/mob/abstract/ghost/storyteller/singularity_pull()
return
/mob/abstract/ghost/storyteller/singuloCanEat()
return FALSE
/mob/abstract/ghost/storyteller/can_hear_radio(list/speaker_coverage)
return TRUE
/mob/abstract/ghost/storyteller/get_speech_bubble_state_modifier()
return "ghost"
/mob/abstract/ghost/storyteller/can_admin_interact()
return TRUE
/mob/abstract/ghost/storyteller/on_restricted_level(var/check)
return FALSE
/mob/abstract/ghost/storyteller/can_ztravel(direction)
return TRUE
/mob/abstract/ghost/storyteller/verb/odyssey_panel()
set name = "Odyssey Panel"
set category = "Storyteller"
SSodyssey.ui_interact(src)
/**
* Currently whitelisted in the config behind the head of staff whitelist.
* Let's see if they can play nice.
*/
/datum/ghostspawner/storyteller
name = "Storyteller"
short_name = "storyteller"
desc = "As the Storyteller, you have creative direction over the current round. You are trusted to change, mix up and make interesting experiences for the \
players. Use this power with responsibility!"
tags = list("Odyssey")
loc_type = GS_LOC_ATOM
enabled = TRUE
show_on_job_select = TRUE
spawn_mob = /mob/abstract/ghost/storyteller
atom_add_message = "A Storyteller position is available!"
@@ -0,0 +1,343 @@
// Okay, these verbs are a lot of ugly copypaste. The issue is that we don't really have any other choice.
// The alternative (and the first thing I tried) was to make the Storyteller into basically admin perms, but that introduces way too many issues.
/mob/abstract/ghost/storyteller/verb/storyteller_panel()
set name = "Storyteller Panel"
set category = "Storyteller"
open_storyteller_panel()
/mob/abstract/ghost/storyteller/proc/open_storyteller_panel()
var/dat = {"
<center><B>Storyteller Panel</B></center><hr>\n
"}
dat += {"
<BR>
<A href='?src=[REF(src)];create_object=1'>Create Object</A><br>
<A href='?src=[REF(src)];create_turf=1'>Create Turf</A><br>
<A href='?src=[REF(src)]];create_mob=1'>Create Mob</A><br>
"}
usr << browse(dat, "window=storytellerpanel;size=210x280")
/mob/abstract/ghost/storyteller/Topic(href, href_list)
. = ..()
if(href_list["create_object"])
if(usr != src)
return
return create_object(usr)
else if(href_list["create_turf"])
if(usr != src)
return
return create_turf(usr)
else if(href_list["create_mob"])
if(usr != src)
return
return create_mob(usr)
else if(href_list["object_list"])
if(!GLOB.config.allow_admin_spawning)
to_chat(usr, "Spawning of items is not allowed.")
return
var/atom/loc = usr.loc
var/dirty_paths
if (istext(href_list["object_list"]))
dirty_paths = list(href_list["object_list"])
else if (istype(href_list["object_list"], /list))
dirty_paths = href_list["object_list"]
var/paths = list()
var/removed_paths = list()
for(var/dirty_path in dirty_paths)
var/path = text2path(dirty_path)
if(!path)
removed_paths += dirty_path
continue
else if(!ispath(path, /obj) && !ispath(path, /turf) && !ispath(path, /mob))
removed_paths += dirty_path
continue
else if(ispath(path, /obj/effect/bhole))
if(!check_rights(R_FUN, 0))
removed_paths += dirty_path
continue
paths += path
if(!paths ||( length(paths) > 5))
return
var/atom/target
var/list/offset = text2list(href_list["offset"],",")
var/number = dd_range(1, 100, text2num(href_list["object_count"]))
var/X = offset.len > 0 ? text2num(offset[1]) : 0
var/Y = offset.len > 1 ? text2num(offset[2]) : 0
var/Z = offset.len > 2 ? text2num(offset[3]) : 0
var/tmp_dir = href_list["object_dir"]
var/obj_dir = tmp_dir ? text2num(tmp_dir) : 2
if(!obj_dir || !(obj_dir in list(1,2,4,8,5,6,9,10)))
obj_dir = 2
var/obj_name = sanitize(href_list["object_name"])
switch(href_list["offset_type"])
if ("absolute")
target = locate(0 + X, 0 + Y, 0 + Z)
if ("relative")
target = locate(loc.x + X, loc.y + Y, loc.z + Z)
if(target)
for (var/path in paths)
for (var/_ in 1 to number)
if(ispath(path, /turf))
var/turf/O = target
var/turf/N = O.ChangeTurf(path)
if(N)
if(obj_name)
N.name = obj_name
else
var/atom/O = new path(target)
if(O)
O.set_dir(obj_dir)
if(obj_name)
O.name = obj_name
if(ismob(O))
var/mob/M = O
M.set_name(obj_name)
log_and_message_admins("created [number] [english_list(paths)]")
/mob/abstract/ghost/storyteller/proc/create_object(var/mob/user)
if (!create_object_html)
var/objectjs = null
objectjs = jointext(typesof(/obj), ";")
create_object_html = file2text('html/create_object.html')
create_object_html = replacetext(create_object_html, "null /* object types */", "\"[objectjs]\"")
user << browse(replacetext(create_object_html, "/* ref src */", "[REF(src)]"), "window=create_object;size=700x700")
/mob/abstract/ghost/storyteller/proc/create_turf(var/mob/user)
if (!create_turf_html)
var/turfjs = null
turfjs = jointext(typesof(/turf), ";")
create_turf_html = file2text('html/create_object.html')
create_turf_html = replacetext(create_turf_html, "null /* object types */", "\"[turfjs]\"")
user << browse(replacetext(create_turf_html, "/* ref src */", "[REF(src)]"), "window=create_turf;size=700x700")
/mob/abstract/ghost/storyteller/proc/create_mob(var/mob/user)
if (!create_mob_html)
var/mobjs = null
mobjs = jointext(typesof(/mob), ";")
create_mob_html = file2text('html/create_object.html')
create_mob_html = replacetext(create_mob_html, "null /* object types */", "\"[mobjs]\"")
user << browse(replacetext(create_mob_html, "/* ref src */", "[REF(src)]"), "window=create_mob;size=700x700")
/mob/abstract/ghost/storyteller/verb/open_narrate_panel()
set name = "Narrate Panel"
set category = "Storyteller"
var/datum/tgui_module/narrate_panel/NP = new /datum/tgui_module/narrate_panel(usr)
NP.ui_interact(usr)
/mob/abstract/ghost/storyteller/verb/storyteller_direct_narrate(var/mob/M)
set name = "Direct Narrate"
set category = "Storyteller"
if(!M)
var/list/client_mobs = list()
for(var/mob/client_mob in get_mob_with_client_list())
client_mobs[client_mob.name] = client_mob
var/mob_name = tgui_input_list(src, "Who are you narrating to?", "Direct Narrate", client_mobs)
if(mob_name)
M = client_mobs[mob_name]
if(!M)
return
var/msg = html_decode(sanitize(tgui_input_text(src, "What do you want to narrate?", "Direct Narrate", max_length = MAX_PAPER_MESSAGE_LEN)))
if(!msg)
return
to_chat(M, msg)
log_admin("DirectNarrate: [key_name(usr)] to ([M.name]/[M.key]): [msg]")
message_admins(SPAN_NOTICE(SPAN_BOLD("DirectNarrate: [key_name(usr)] to ([M.name]/[M.key]): [msg]")), 1)
/mob/abstract/ghost/storyteller/verb/toggle_build_mode()
set name = "Toggle Build Mode"
set category = "Storyteller"
if(usr != src)
return
var/datum/click_handler/handler = GetClickHandler()
if(handler.type == /datum/click_handler/build_mode)
usr.PopClickHandler()
else
usr.PushClickHandler(/datum/click_handler/build_mode)
/mob/abstract/ghost/storyteller/verb/command_report()
set name = "Create Command Report"
set category = "Storyteller"
var/reporttitle = sanitizeSafe(tgui_input_text(usr, "Pick a title for the report.", "Title"))
if(!reporttitle)
reporttitle = "NanoTrasen Update"
var/reportbody = sanitize(tgui_input_text(usr, "Please enter anything you want. Anything. Serious.", "Body", multiline = TRUE), extra = FALSE)
if(!reportbody)
return
var/announce = tgui_alert(usr, "Should this be announced to the general population?", "Announcement", list("Yes","No"))
switch(announce)
if("Yes")
command_announcement.Announce("[reportbody]", reporttitle, new_sound = 'sound/AI/commandreport.ogg', msg_sanitized = 1);
if("No")
to_world(SPAN_WARNING("New [SSatlas.current_map.company_name] Update available at all communication consoles."))
sound_to_playing_players('sound/AI/commandreport.ogg')
log_admin("Storyteller [key_name(src)] has created a command report: [reportbody]")
message_admins("Storyteller [key_name_admin(src)] has created a command report", 1)
//New message handling
post_comm_message(reporttitle, reportbody)
/mob/abstract/ghost/storyteller/verb/send_odyssey_message()
set name = "Unrestrict Away Site Landing"
set category = "Storyteller"
if(!SSodyssey.site_landing_restricted)
to_chat(src, SPAN_WARNING("Site landing is already unrestricted!"))
return
var/reporttitle = sanitizeSafe(tgui_input_text(usr, "Pick a title for the message the Horizon will get.", "Title"))
if(!reporttitle)
reporttitle = "SCC Sensors Report"
var/reportbody = sanitize(tgui_input_text(usr, "Enter the message the Horizon will get. It should at least describe what they're doing here in a general sense, along with the reason why they can land now.", "Body", multiline = TRUE), extra = FALSE)
if(!reportbody)
return
var/announce = tgui_alert(usr, "Should this be announced to the general population?", "Announcement", list("Yes","No"))
switch(announce)
if("Yes")
command_announcement.Announce("[reportbody]", reporttitle, new_sound = 'sound/AI/commandreport.ogg', msg_sanitized = 1)
if("No")
command_announcement.Announce("New [SSatlas.current_map.company_name] update available at all communication consoles.", "[SSatlas.current_map.company_name] Report", new_sound = 'sound/AI/commandreport.ogg', msg_sanitized = 1)
SSodyssey.scenario.unrestrict_away_site_landing()
log_admin("Storyteller [key_name(src)] has lifted the away site landing restrictions: [reportbody]")
//New message handling
post_comm_message(reporttitle, reportbody)
/mob/abstract/ghost/storyteller/verb/change_mob_name(var/mob/victim)
set name = "Change Mob Name"
var/new_name = tgui_input_text(src, "Enter a new name.", "Change Mob Name", max_length = MAX_NAME_LEN)
if(!new_name)
return
log_admin("[key_name(src)] has renamed [victim] to [new_name].")
victim.set_name(new_name)
/mob/abstract/ghost/storyteller/verb/change_obj_name(var/obj/thing)
set name = "Change Object Name"
var/new_name = tgui_input_text(src, "Enter a new name.", "Change Object Name", max_length = MAX_NAME_LEN)
if(!new_name)
return
log_admin("[key_name(src)] has renamed [thing] to [new_name].")
thing.name = new_name
/mob/abstract/ghost/storyteller/verb/change_obj_desc(var/obj/thing)
set name = "Change Object Description"
var/new_desc = tgui_input_text(src, "Enter a new description.", "Change Object Description", max_length = MAX_MESSAGE_LEN)
if(!new_desc)
return
log_admin("[key_name(src)] has changed [thing]'s description to [new_desc].")
thing.desc = new_desc
/mob/abstract/ghost/storyteller/verb/set_outfit(var/mob/living/carbon/human/H)
set name = "Set Outfit"
set category = "Storyteller"
do_dressing(H)
/mob/abstract/ghost/storyteller/proc/show_traitor_panel(var/mob/M)
set name = "Edit Antagonist"
set category = "Storyteller"
if(!istype(M))
to_chat(usr, SPAN_WARNING("This can only be used on mobs!"))
return
if(!M.mind)
to_chat(usr, SPAN_WARNING("This mob has no mind!"))
return
M.mind.edit_memory()
/mob/abstract/ghost/storyteller/verb/create_explosion()
set name = "Create Explosion"
set category = "Storyteller"
var/turf/epicenter = get_turf(src)
var/choice = tgui_input_list(usr, "What size explosion would you like to produce?", "Drop Bomb", list("Small Bomb", "Medium Bomb", "Big Bomb", "Custom Bomb"))
switch(choice)
if(null)
return 0
if("Small Bomb")
explosion(epicenter, 1, 2, 3, 3)
if("Medium Bomb")
explosion(epicenter, 2, 3, 4, 4)
if("Big Bomb")
explosion(epicenter, 3, 5, 7, 5)
if("Custom Bomb")
var/devastation_range = tgui_input_number(usr, "Set the devastation range (in tiles).", "Devastation")
var/heavy_impact_range = tgui_input_number(usr, "Set the heavy impact range (in tiles).", "Heavy")
var/light_impact_range = tgui_input_number(usr, "Set the light impact range (in tiles).", "Light")
var/flash_range = tgui_input_number(usr, "Set the flash range (in tiles).", "Flash")
explosion(epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range)
message_admins(SPAN_NOTICE("[ckey] creating an explosion at [epicenter.loc]."))
/mob/abstract/ghost/storyteller/verb/delete_atom(atom/O as obj|mob|turf in range(world.view))
set name = "Delete"
set category = "Storyteller"
var/action = alert(src, "Are you sure you want to delete:\n[O]\nat ([O.x], [O.y], [O.z])?", "Confirmation", "Yes", "No", "Hard Delete")
if (action == "No" || !action)
return
if (istype(O, /mob/abstract/ghost/observer))
return
log_admin("[key_name(usr)] deleted [O] at ([O.x],[O.y],[O.z])")
message_admins("[key_name_admin(usr)] deleted [O] at ([O.x],[O.y],[O.z])", 1)
if (isturf(O)) // Can't qdel a turf.
var/turf/T = O
T.ChangeTurf(/turf/space)
return
if (action == "Yes")
qdel(O, TRUE)
else
// This is naughty, but sometimes necessary.
O.Destroy(TRUE) // Because direct del without this breaks things.
del(O)
/mob/abstract/ghost/storyteller/verb/rejuvenate(mob/living/M as mob in range(world.view))
set name = "Rejuvenate"
set category = "Storyteller"
if(!istype(M))
return
M.revive()
message_admins(SPAN_DANGER("Storyteller [key_name_admin(usr)] healed / revived [key_name_admin(M)]!"), 1)
+11 -3
View File
@@ -209,6 +209,10 @@
/atom/movable/screen/new_player/selection/join_game/Click()
var/mob/abstract/new_player/player = usr
if(SSticker.prevent_unready && player.ready)
tgui_alert(player, "You may not unready during Odyssey setup!", "Odyssey")
return
sound_to(player, click_sound)
if(SSticker.current_state <= GAME_STATE_SETTING_UP)
if(player.ready)
@@ -287,11 +291,15 @@
alert(src, "You can not ready up, because you have unacknowledged warnings or notifications. Acknowledge them in OOC->Warnings and Notifications.")
return
if(SSticker.prevent_unready && !readying)
tgui_alert(src, "You may not unready during Odyssey setup!", "Odyssey")
return
ready = readying
if(ready)
last_ready_name = client.prefs.real_name
SSticker.update_ready_list(src)
else
else if(!SSticker.prevent_unready)
ready = FALSE
/mob/abstract/new_player/proc/join_game(href, href_list)
@@ -315,7 +323,7 @@
if(alert(src, "Are you sure you wish to observe? You will have to wait [GLOB.config.respawn_delay] minutes before being able to respawn.", "Player Setup", "Yes", "No") != "Yes")
return FALSE
var/mob/abstract/observer/observer = new /mob/abstract/observer(src)
var/mob/abstract/ghost/observer/observer = new /mob/abstract/ghost/observer(null, src)
spawning = 1
src.stop_sound_channel(CHANNEL_LOBBYMUSIC) // stop the jams for observers
@@ -342,7 +350,7 @@
observer.real_name = client.prefs.real_name
observer.name = observer.real_name
if(!client.holder && !GLOB.config.antag_hud_allowed)
remove_verb(observer, /mob/abstract/observer/verb/toggle_antagHUD)
remove_verb(observer, /mob/abstract/ghost/observer/verb/toggle_antagHUD)
observer.ckey = ckey
observer.initialise_postkey()
observer.client.init_verbs()
@@ -107,7 +107,13 @@ INITIALIZE_IMMEDIATE(/mob/abstract/new_player)
alert(src, "You can not ready up, because you have unacknowledged warnings or notifications. Acknowledge them in OOC->Warnings and Notifications.")
return
ready = text2num(href_list["ready"])
var/new_ready_state = text2num(href_list["ready"])
if(SSticker.prevent_unready && new_ready_state == FALSE)
tgui_alert(src, "You may not unready during Odyssey setup!", "Odyssey")
return
ready = new_ready_state
else
ready = 0
+1 -1
View File
@@ -42,7 +42,7 @@
if (!M.client || isnewplayer(M))
continue
if(get_turf(M) in messageturfs)
if (isobserver(M))
if (isghost(M))
ghosts_nearby += M
continue
else if (isliving(M) && !(type == 2 && isdeaf(M)))
+1 -1
View File
@@ -1,5 +1,5 @@
/proc/examinate(mob/user, atom/target, show_extended)
if(user.is_blind() || (user.stat && !isobserver(user)))
if(user.is_blind() || (user.stat && !isghost(user)))
to_chat(user, SPAN_NOTICE("Something is there, but you cannot see it."))
return
+6 -6
View File
@@ -4,7 +4,7 @@
if(!istype(src, /mob/living/test) && cant_hear())
return
if(speaker && !istype(speaker, /mob/living/test) && (!speaker.client && istype(src,/mob/abstract/observer) && client.prefs.toggles & CHAT_GHOSTEARS && !(speaker in view(src))))
if(speaker && !istype(speaker, /mob/living/test) && (!speaker.client && istype(src,/mob/abstract/ghost/observer) && client.prefs.toggles & CHAT_GHOSTEARS && !(speaker in view(src))))
//Does the speaker have a client? It's either random stuff that observers won't care about (Experiment 97B says, 'EHEHEHEHEHEHEHE')
//Or someone snoring. So we make it where they won't hear it.
return
@@ -14,7 +14,7 @@
//make sure the air can transmit speech - hearer's side
var/turf/T = get_turf(src)
var/vacuum_proof = ((language && (language.flags & PRESSUREPROOF)) || isobserver(src))
var/vacuum_proof = ((language && (language.flags & PRESSUREPROOF)) || isghost(src))
if(T && !vacuum_proof) //Ghosts can hear even in vacuum.
var/datum/gas_mixture/environment = T.return_air()
var/pressure = (environment)? environment.return_pressure() : 0
@@ -31,7 +31,7 @@
//non-verbal languages are garbled if you can't see the speaker. Yes, this includes if they are inside a closet.
if (language && (language.flags & NONVERBAL))
if((!speaker || (src.sdisabilities & BLIND || src.blinded) || !(speaker in view(src))) && !isobserver(src))
if((!speaker || (src.sdisabilities & BLIND || src.blinded) || !(speaker in view(src))) && !isghost(src))
message = stars(message)
if(!(language && (language.flags & INNATE)) && !say_understands(speaker, language)) // skip understanding checks for INNATE languages
@@ -47,7 +47,7 @@
message = "<i>[message]</i>"
var/track = null
if(isobserver(src))
if(isghost(src))
if(speaker_name != speaker.real_name && speaker.real_name)
speaker_name = "[speaker.real_name] ([speaker_name])"
track = "[ghost_follow_link(speaker, src)] "
@@ -214,7 +214,7 @@
else
track = "<a class='ai_tracking' href='byond://?src=[REF(src)];trackname=[html_encode(speaker_name)];track=[REF(speaker)]'>[speaker_name] ([jobname])</a>"
if(istype(src, /mob/abstract/observer))
if(isghost(src))
if(speaker != null)
if(speaker_name != speaker.real_name && !isAI(speaker)) //Announce computer and various stuff that broadcasts doesn't use it's real name but AI's can't pretend to be other mobs.
speaker_name = "[speaker.real_name] ([speaker_name])"
@@ -240,7 +240,7 @@
if(vr_mob)
to_chat(vr_mob, "[part_a][speaker_name][part_b][formatted]")
/mob/abstract/observer/on_hear_radio(part_a, speaker_name, track, part_b, formatted)
/mob/abstract/ghost/observer/on_hear_radio(part_a, speaker_name, track, part_b, formatted)
to_chat(src, "[track][part_a][speaker_name][part_b][formatted]")
/mob/living/silicon/on_hear_radio(part_a, speaker_name, track, part_b, formatted)
+1 -1
View File
@@ -232,7 +232,7 @@
/mob/abstract/new_player/hear_broadcast(var/datum/language/language, var/mob/speaker, var/speaker_name, var/message)
return
/mob/abstract/observer/hear_broadcast(var/datum/language/language, var/mob/speaker, var/speaker_name, var/message)
/mob/abstract/ghost/hear_broadcast(var/datum/language/language, var/mob/speaker, var/speaker_name, var/message)
if(speaker.name == speaker_name || antagHUD)
to_chat(src, "[ghost_follow_link(speaker, src)] <i><span class='game say'>[language.name], <span class='name'>[speaker_name]</span> [message]</span></i>")
else
+1 -1
View File
@@ -126,7 +126,7 @@
return
for(var/mob/player in GLOB.player_list)
if(istype(player,/mob/abstract/observer) || check_special_condition(player))
if(isobserver(player) || check_special_condition(player))
if(!within_jamming_range(player))
to_chat(player, msg)
+1 -1
View File
@@ -256,7 +256,7 @@
for(var/mob/player in GLOB.player_list)
if(player == speaker)
to_chat(player, msg)
else if(isobserver(player))
else if(isghost(player))
to_chat(player, "[ghost_follow_link(speaker, player)] [msg]")
else if(!within_jamming_range(player) && check_special_condition(player))
if(speaker_encryption_key)
@@ -1187,7 +1187,7 @@
target.show_message(SPAN_NOTICE("You hear a voice that seems to echo around the room: [say]"))
usr.show_message(SPAN_NOTICE("You project your mind into [target.real_name]: [say]"))
log_say("[key_name(usr)] sent a telepathic message to [key_name(target)]: [say]")
for(var/mob/abstract/observer/G in GLOB.dead_mob_list)
for(var/mob/abstract/ghost/observer/G in GLOB.dead_mob_list)
G.show_message("<i>Telepathic message from <b>[src]</b> to <b>[target]</b>: [say]</i>")
/mob/living/carbon/human/proc/remoteobserve()
@@ -567,7 +567,7 @@
var/mob/M = targets[target]
if(istype(M, /mob/abstract/observer) || M.stat == DEAD)
if(isobserver(M) || M.stat == DEAD)
to_chat(src, SPAN_DANGER("[M]'s hivenet implant is inactive!"))
return
@@ -577,12 +577,12 @@
log_say("[key_name(src)] issued a hivenet order to [key_name(M)]: [text]")
if(istype(M, /mob/living/carbon/human) && isvaurca(M))
if(ishuman(M) && isvaurca(M))
to_chat(M, SPAN_DANGER("You feel a buzzing in the back of your head, and your mind fills with the authority of [src.real_name], your ruler:"))
to_chat(M, SPAN_NOTICE(" [text]"))
else
to_chat(M, SPAN_DANGER("Like lead slabs crashing into the ocean, alien thoughts drop into your mind: [text]"))
if(istype(M,/mob/living/carbon/human))
if(ishuman(M))
var/mob/living/carbon/human/H = M
if(H.species.name == src.species.name)
return
@@ -302,8 +302,8 @@
return ..()
/obj/effect/golemrune/process()
var/mob/abstract/observer/ghost
for(var/mob/abstract/observer/O in src.loc)
var/mob/abstract/ghost/observer/ghost
for(var/mob/abstract/ghost/observer/O in src.loc)
if(!O.client)
continue
if(O.mind && O.mind.current && O.mind.current.stat != DEAD)
+3 -3
View File
@@ -863,10 +863,10 @@ default behaviour is:
if(deaf >= 0)
ear_deaf = deaf
/mob/proc/can_be_possessed_by(var/mob/abstract/observer/possessor)
/mob/proc/can_be_possessed_by(var/mob/abstract/ghost/observer/possessor)
return istype(possessor) && possessor.client
/mob/living/can_be_possessed_by(var/mob/abstract/observer/possessor)
/mob/living/can_be_possessed_by(var/mob/abstract/ghost/observer/possessor)
if(!..())
return 0
if(!possession_candidate)
@@ -879,7 +879,7 @@ default behaviour is:
return 0
return 1
/mob/living/proc/do_possession(var/mob/abstract/observer/possessor)
/mob/living/proc/do_possession(var/mob/abstract/ghost/observer/possessor)
if(!(istype(possessor) && possessor.ckey))
return 0
@@ -35,6 +35,6 @@
/mob/proc/examine_laws(var/mob/living/silicon/S)
return
/mob/abstract/observer/examine_laws(var/mob/living/silicon/S)
/mob/abstract/ghost/observer/examine_laws(var/mob/living/silicon/S)
if(antagHUD || is_admin(src))
return S.laws.get_laws(src)
@@ -132,7 +132,7 @@
return ..()
/mob/living/silicon/robot/drone/can_be_possessed_by(var/mob/abstract/observer/possessor)
/mob/living/silicon/robot/drone/can_be_possessed_by(var/mob/abstract/ghost/observer/possessor)
if(!istype(possessor) || !possessor.client || !possessor.ckey)
return FALSE
if(!GLOB.config.allow_drone_spawn)
@@ -148,7 +148,7 @@
return FALSE
return TRUE
/mob/living/silicon/robot/drone/do_possession(var/mob/abstract/observer/possessor)
/mob/living/silicon/robot/drone/do_possession(var/mob/abstract/ghost/observer/possessor)
if(!(istype(possessor) && possessor.ckey))
return 0
if(src.ckey || src.client)
@@ -246,7 +246,7 @@
assign_drone_to_matrix(src, matrix_tag)
master_matrix.message_drones(MATRIX_NOTICE("Energy surges through your circuits. The matriarch has come online."))
/mob/living/silicon/robot/drone/construction/matriarch/do_possession(mob/abstract/observer/possessor)
/mob/living/silicon/robot/drone/construction/matriarch/do_possession(mob/abstract/ghost/observer/possessor)
. = ..()
SSghostroles.remove_spawn_atom("matriarchmaintdrone", src)
@@ -83,7 +83,7 @@
return FALSE
if(!R.key)
for(var/mob/abstract/observer/ghost in GLOB.player_list)
for(var/mob/abstract/ghost/observer/ghost in GLOB.player_list)
if(ghost.mind?.current == R)
R.key = ghost.key
@@ -26,6 +26,13 @@
scrambled_codes = TRUE
status_flags = GODMODE|NOFALL
/// The BST's original mob. Moved here from /datum/holder to support storytellers.
var/mob/original_mob
/mob/living/silicon/robot/bluespace/Destroy(force)
original_mob = null
return ..()
/mob/living/silicon/robot/bluespace/verb/antigrav()
set name = "Toggle Gravity"
set desc = "Use bluespace technology to ignore gravity."
@@ -67,10 +74,10 @@
animate(src, alpha = 0, time = 9, easing = QUAD_EASING)
if(key)
if(client.holder && client.holder.original_mob)
client.holder.original_mob.key = key
if(original_mob)
original_mob.key = key
else
var/mob/abstract/observer/ghost = new(src) //Transfer safety to observer spawning proc.
var/mob/abstract/ghost/observer/ghost = new(src, src) //Transfer safety to observer spawning proc.
ghost.key = key
ghost.mind.name = "[ghost.key] BSTech"
ghost.name = "[ghost.key] BSTech"
@@ -85,6 +92,11 @@
status_flags ^= GODMODE
to_chat(src, SPAN_NOTICE("God mode is now [status_flags & GODMODE ? "enabled" : "disabled"]"))
/mob/living/silicon/robot/bluespace/vv_edit_var(var_name, var_value)
if(var_name == NAMEOF(src, original_mob))
return FALSE
return ..()
/mob/living/silicon/robot/purpose
mod_type = "Purpose"
spawn_module = /obj/item/robot_module/purpose
@@ -76,7 +76,7 @@
GLOB.move_manager.stop_looping(src)
if(prob(2)) //spooky
var/mob/abstract/observer/spook = locate() in range(src,5)
var/mob/abstract/ghost/observer/spook = locate() in range(src,5)
if(spook)
var/turf/T = spook.loc
var/list/visible = list()
@@ -90,7 +90,7 @@
if(!B.brainmob.key)
var/ghost_can_reenter = 0
if(B.brainmob.mind)
for(var/mob/abstract/observer/G in GLOB.player_list)
for(var/mob/abstract/ghost/observer/G in GLOB.player_list)
if(G.can_reenter_corpse && G.mind == B.brainmob.mind)
ghost_can_reenter = 1
break
+4 -3
View File
@@ -41,7 +41,8 @@
//None of these mobs can 'die' in any sense, and none of them should be able to become ghosts.
//Ghosts are the only ones that even technically 'exist' and aren't just an abstraction using mob code for convenience
if (istype(src, /mob/living))
//Well, those and storytellers. This code is seriously shit and needs a rework, but who gives a fuck right now. Sue me.
if (istype(src, /mob/living) && !isstoryteller(src))
ghostize()
if (istype(src.loc, /atom/movable))
@@ -180,7 +181,7 @@
continue
if (!M.client || istype(M, /mob/abstract/new_player))
continue
if((get_turf(M) in messageturfs) || (show_observers && isobserver(M) && (M.client.prefs.toggles & CHAT_GHOSTSIGHT)))
if((get_turf(M) in messageturfs) || (show_observers && isghost(M) && (M.client.prefs.toggles & CHAT_GHOSTSIGHT)))
messagemobs += M
for(var/o in GLOB.listening_objects)
@@ -191,7 +192,7 @@
for(var/A in messagemobs)
var/mob/M = A
if(isobserver(M))
if(isghost(M))
M.show_message("[ghost_follow_link(src, M)] [message]", 1)
continue
if(self_message && M == src)
+3 -3
View File
@@ -582,8 +582,8 @@ var/list/intents = list(I_HELP,I_DISARM,I_GRAB,I_HURT)
follow = "[ghost_follow_link(subject, M)] "
if(M.stat != DEAD && M.client.holder)
follow = "([admin_jump_link(subject, M.client.holder)]) "
var/mob/abstract/observer/DM
if(istype(subject, /mob/abstract/observer))
var/mob/abstract/ghost/observer/DM
if(istype(subject, /mob/abstract/ghost/observer))
DM = subject
if(M.client.holder) // What admins see
lname = "[keyname][(DM && DM.anonsay) ? "*" : (DM ? "" : "^")] ([name])"
@@ -1092,7 +1092,7 @@ var/list/intents = list(I_HELP,I_DISARM,I_GRAB,I_HURT)
if(P?.ismultitool())
return P
/mob/abstract/observer/get_multitool()
/mob/abstract/ghost/observer/get_multitool()
return can_admin_interact() && ..(ghost_multitool)
/mob/living/carbon/human/get_multitool()
+1 -1
View File
@@ -250,7 +250,7 @@
if(mob.control_object)
Move_object(direct)
if(mob.incorporeal_move && isobserver(mob))
if(mob.incorporeal_move && isabstractmob(mob))
Process_Incorpmove(direct, mob)
return