GLOBAL_LIST_EMPTY(ghost_images_default) //this is a list of the default (non-accessorized, non-dir) images of the ghosts themselves GLOBAL_LIST_EMPTY(ghost_images_simple) //this is a list of all ghost images as the simple white ghost GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER) /mob/dead/observer name = "ghost" desc = "It's a g-g-g-g-ghooooost!" //jinkies! icon = 'icons/mob/mob.dmi' icon_state = "ghost" layer = GHOST_LAYER stat = DEAD density = FALSE canmove = 0 anchored = TRUE // don't get pushed around see_invisible = SEE_INVISIBLE_OBSERVER see_in_dark = 100 invisibility = INVISIBILITY_OBSERVER hud_type = /datum/hud/ghost var/can_reenter_corpse var/can_reenter_round = TRUE var/datum/hud/living/carbon/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/atom/movable/following = null var/fun_verbs = 0 var/image/ghostimage_default = null //this mobs ghost image without accessories and dirs var/image/ghostimage_simple = null //this mob with the simple white ghost sprite var/ghostvision = 1 //is the ghost able to see things humans can't? var/mob/observetarget = null //The target mob that the ghost is observing. Used as a reference in logout() var/ghost_hud_enabled = 1 //did this ghost disable the on-screen HUD? var/data_huds_on = 0 //Are data HUDs currently enabled? var/health_scan = FALSE //Are health scans currently enabled? var/list/datahuds = list(DATA_HUD_SECURITY_ADVANCED, DATA_HUD_MEDICAL_ADVANCED, DATA_HUD_DIAGNOSTIC_ADVANCED) //list of data HUDs shown to ghosts. var/ghost_orbit = GHOST_ORBIT_CIRCLE //These variables store hair data if the ghost originates from a species with head and/or facial hair. var/hair_style var/hair_color var/mutable_appearance/hair_overlay var/facial_hair_style var/facial_hair_color var/mutable_appearance/facial_hair_overlay var/updatedir = 1 //Do we have to update our dir as the ghost moves around? var/lastsetting = null //Stores the last setting that ghost_others was set to, for a little more efficiency when we update ghost images. Null means no update is necessary //We store copies of the ghost display preferences locally so they can be referred to even if no client is connected. //If there's a bug with changing your ghost settings, it's probably related to this. var/ghost_accs = GHOST_ACCS_DEFAULT_OPTION var/ghost_others = GHOST_OTHERS_DEFAULT_OPTION // Used for displaying in ghost chat, without changing the actual name // of the mob var/deadchat_name var/datum/spawners_menu/spawners_menu /mob/dead/observer/Initialize() set_invisibility(GLOB.observer_default_invisibility) verbs += list( /mob/dead/observer/proc/dead_tele, /mob/dead/observer/proc/open_spawners_menu, /mob/dead/observer/proc/view_gas) if(icon_state in GLOB.ghost_forms_with_directions_list) ghostimage_default = image(src.icon,src,src.icon_state + "_nodir") else ghostimage_default = image(src.icon,src,src.icon_state) ghostimage_default.override = TRUE GLOB.ghost_images_default |= ghostimage_default ghostimage_simple = image(src.icon,src,"ghost_nodir") ghostimage_simple.override = TRUE GLOB.ghost_images_simple |= ghostimage_simple updateallghostimages() var/turf/T var/mob/body = loc if(ismob(body)) T = get_turf(body) //Where is the body located? gender = body.gender if(body.mind && body.mind.name) name = body.mind.name else if(body.real_name) name = body.real_name else name = random_unique_name(gender) mind = body.mind //we don't transfer the mind but we keep a reference to it. suiciding = body.suiciding // Transfer whether they committed suicide. if(ishuman(body)) var/mob/living/carbon/human/body_human = body if(HAIR in body_human.dna.species.species_traits) hair_style = body_human.hair_style hair_color = brighten_color(body_human.hair_color) if(FACEHAIR in body_human.dna.species.species_traits) facial_hair_style = body_human.facial_hair_style facial_hair_color = brighten_color(body_human.facial_hair_color) update_icon() if(!T) var/list/turfs = get_area_turfs(/area/shuttle/arrival) if(turfs.len) T = pick(turfs) else T = SSmapping.get_station_center() forceMove(T) if(!name) //To prevent nameless ghosts name = random_unique_name(gender) real_name = name if(!fun_verbs) verbs -= /mob/dead/observer/verb/boo verbs -= /mob/dead/observer/verb/possess animate(src, pixel_y = 2, time = 10, loop = -1) GLOB.dead_mob_list += src for(var/v in GLOB.active_alternate_appearances) if(!v) continue var/datum/atom_hud/alternate_appearance/AA = v AA.onNewMob(src) . = ..() grant_all_languages() /mob/dead/observer/get_photo_description(obj/item/camera/camera) if(!invisibility || camera.see_ghosts) return "You can also see a g-g-g-g-ghooooost!" /mob/dead/observer/narsie_act() var/old_color = color color = "#960000" animate(src, color = old_color, time = 10, flags = ANIMATION_PARALLEL) addtimer(CALLBACK(src, /atom/proc/update_atom_colour), 10) /mob/dead/observer/ratvar_act() var/old_color = color color = "#FAE48C" animate(src, color = old_color, time = 10, flags = ANIMATION_PARALLEL) addtimer(CALLBACK(src, /atom/proc/update_atom_colour), 10) /mob/dead/observer/Destroy() GLOB.ghost_images_default -= ghostimage_default QDEL_NULL(ghostimage_default) GLOB.ghost_images_simple -= ghostimage_simple QDEL_NULL(ghostimage_simple) updateallghostimages() QDEL_NULL(spawners_menu) return ..() /mob/dead/CanPass(atom/movable/mover, turf/target) return 1 /* * This proc will update the icon of the ghost itself, with hair overlays, as well as the ghost image. * Please call update_icon(icon_state) from now on when you want to update the icon_state of the ghost, * or you might end up with hair on a sprite that's not supposed to get it. * Hair will always update its dir, so if your sprite has no dirs the haircut will go all over the place. * |- Ricotez */ /mob/dead/observer/proc/update_icon(new_form) if(client) //We update our preferences in case they changed right before update_icon was called. ghost_accs = client.prefs.ghost_accs ghost_others = client.prefs.ghost_others if(hair_overlay) cut_overlay(hair_overlay) hair_overlay = null if(facial_hair_overlay) cut_overlay(facial_hair_overlay) facial_hair_overlay = null if(new_form) icon_state = new_form if(icon_state in GLOB.ghost_forms_with_directions_list) ghostimage_default.icon_state = new_form + "_nodir" //if this icon has dirs, the default ghostimage must use its nodir version or clients with the preference set to default sprites only will see the dirs else ghostimage_default.icon_state = new_form if(ghost_accs >= GHOST_ACCS_DIR && icon_state in GLOB.ghost_forms_with_directions_list) //if this icon has dirs AND the client wants to show them, we make sure we update the dir on movement updatedir = 1 else updatedir = 0 //stop updating the dir in case we want to show accessories with dirs on a ghost sprite without dirs setDir(2 )//reset the dir to its default so the sprites all properly align up if(ghost_accs == GHOST_ACCS_FULL && icon_state in GLOB.ghost_forms_with_accessories_list) //check if this form supports accessories and if the client wants to show them var/datum/sprite_accessory/S if(facial_hair_style) S = GLOB.facial_hair_styles_list[facial_hair_style] if(S) facial_hair_overlay = mutable_appearance(S.icon, "[S.icon_state]", -HAIR_LAYER) if(facial_hair_color) facial_hair_overlay.color = "#" + facial_hair_color facial_hair_overlay.alpha = 200 add_overlay(facial_hair_overlay) if(hair_style) S = GLOB.hair_styles_list[hair_style] if(S) hair_overlay = mutable_appearance(S.icon, "[S.icon_state]", -HAIR_LAYER) if(hair_color) hair_overlay.color = "#" + hair_color hair_overlay.alpha = 200 add_overlay(hair_overlay) /* * Increase the brightness of a color by calculating the average distance between the R, G and B values, * and maximum brightness, then adding 30% of that average to R, G and B. * * I'll make this proc global and move it to its own file in a future update. |- Ricotez */ /mob/proc/brighten_color(input_color) var/r_val var/b_val var/g_val var/color_format = lentext(input_color) if(color_format == 3) r_val = hex2num(copytext(input_color, 1, 2))*16 g_val = hex2num(copytext(input_color, 2, 3))*16 b_val = hex2num(copytext(input_color, 3, 0))*16 else if(color_format == 6) r_val = hex2num(copytext(input_color, 1, 3)) g_val = hex2num(copytext(input_color, 3, 5)) b_val = hex2num(copytext(input_color, 5, 0)) else return 0 //If the color format is not 3 or 6, you're using an unexpected way to represent a color. r_val += (255 - r_val) * 0.4 if(r_val > 255) r_val = 255 g_val += (255 - g_val) * 0.4 if(g_val > 255) g_val = 255 b_val += (255 - b_val) * 0.4 if(b_val > 255) b_val = 255 return num2hex(r_val, 2) + num2hex(g_val, 2) + num2hex(b_val, 2) /* 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/proc/ghostize(can_reenter_corpse = 1) if(key) if(!cmptext(copytext(key,1,2),"@")) // Skip aghosts. stop_sound_channel(CHANNEL_HEARTBEAT) //Stop heartbeat sounds because You Are A Ghost Now var/mob/dead/observer/ghost = new(src) // Transfer safety to observer spawning proc. SStgui.on_transfer(src, ghost) // Transfer NanoUIs. ghost.can_reenter_corpse = can_reenter_corpse ghost.can_reenter_round = (can_reenter_corpse && !suiciding) ghost.key = key return ghost /* This is the proc mobs get to turn into a ghost. Forked from ghostize due to compatibility issues. */ /mob/living/verb/ghost() set category = "OOC" set name = "Ghost" set desc = "Relinquish your life and enter the land of the dead." // CITADEL EDIT if(istype(loc, /obj/machinery/cryopod)) var/response = alert(src, "Are you -sure- you want to ghost?\n(You are alive. If you ghost whilst still alive you won't be able to re-enter this round! You can't change your mind so choose wisely!!)","Are you sure you want to ghost?","Ghost","Stay in body") if(response != "Ghost")//darn copypaste return var/obj/machinery/cryopod/C = loc C.despawn_occupant() return // END EDIT if(stat != DEAD) succumb() if(stat == DEAD) ghostize(1) else var/response = alert(src, "Are you -sure- you want to ghost?\n(You are alive. If you ghost whilst still alive you won't be able to re-enter this round! You can't change your mind so choose wisely!!)","Are you sure you want to ghost?","Ghost","Stay in body") if(response != "Ghost") return //didn't want to ghost after-all ghostize(0) //0 parameter is so we can never re-enter our body, "Charlie, you can never come baaaack~" :3 suicide_log(TRUE) /mob/camera/verb/ghost() set category = "OOC" set name = "Ghost" set desc = "Relinquish your life and enter the land of the dead." var/response = alert(src, "Are you -sure- you want to ghost?\n(You are alive. If you ghost whilst still alive you won't be able to re-enter this round! You can't change your mind so choose wisely!!)","Are you sure you want to ghost?","Ghost","Stay in body") if(response != "Ghost") return ghostize(0) /mob/dead/observer/Move(NewLoc, direct) if(updatedir) setDir(direct)//only update dir if we actually need it, so overlays won't spin on base sprites that don't have directions of their own var/oldloc = loc if(NewLoc) forceMove(NewLoc) update_parallax_contents() else forceMove(get_turf(src)) //Get out of closets and such as a ghost if((direct & NORTH) && y < world.maxy) y++ else if((direct & SOUTH) && y > 1) y-- if((direct & EAST) && x < world.maxx) x++ else if((direct & WEST) && x > 1) x-- Moved(oldloc, direct) /mob/dead/observer/verb/reenter_corpse() set category = "Ghost" set name = "Re-enter Corpse" if(!client) return if(!mind || QDELETED(mind.current)) to_chat(src, "You have no body.") return if(!can_reenter_corpse) to_chat(src, "You cannot re-enter your body.") return if(mind.current.key && copytext(mind.current.key,1,2)!="@") //makes sure we don't accidentally kick any clients to_chat(usr, "Another consciousness is in your body...It is resisting you.") return client.change_view(CONFIG_GET(string/default_view)) SStgui.on_transfer(src, mind.current) // Transfer NanoUIs. mind.current.key = key return 1 /mob/dead/observer/proc/notify_cloning(var/message, var/sound, var/atom/source, flashwindow = TRUE) if(flashwindow) window_flash(client) if(message) to_chat(src, "[message]") if(source) var/obj/screen/alert/A = throw_alert("[REF(source)]_notify_cloning", /obj/screen/alert/notify_cloning) if(A) if(client && client.prefs && client.prefs.UI_style) A.icon = ui_style2icon(client.prefs.UI_style) A.desc = message var/old_layer = source.layer var/old_plane = source.plane source.layer = FLOAT_LAYER source.plane = FLOAT_PLANE A.add_overlay(source) source.layer = old_layer source.plane = old_plane to_chat(src, "(Click to re-enter)") if(sound) SEND_SOUND(src, sound(sound)) /mob/dead/observer/proc/dead_tele() set category = "Ghost" set name = "Teleport" set desc= "Teleport to a location" if(!isobserver(usr)) to_chat(usr, "Not when you're not dead!") return var/list/filtered = list() for(var/V in GLOB.sortedAreas) var/area/A = V if(!A.hidden) filtered += A var/area/thearea = input("Area to jump to", "BOOYEA") as null|anything in filtered if(!thearea) return var/list/L = list() for(var/turf/T in get_area_turfs(thearea.type)) L+=T if(!L || !L.len) to_chat(usr, "No area available.") return usr.forceMove(pick(L)) update_parallax_contents() /mob/dead/observer/proc/view_gas() set category = "Ghost" set name = "View Gases" set desc= "View the atmospheric conditions in a location" var/turf/loc = get_turf(src) show_air_status_to(loc, usr) /mob/dead/observer/verb/follow() set category = "Ghost" set name = "Orbit" // "Haunt" set desc = "Follow and orbit a mob." var/list/mobs = getpois(skip_mindless=1) var/input = input("Please, select a mob!", "Haunt", null, null) as null|anything in mobs var/mob/target = mobs[input] ManualFollow(target) // This is the ghost's follow verb with an argument /mob/dead/observer/proc/ManualFollow(atom/movable/target) if (!istype(target)) return var/icon/I = icon(target.icon,target.icon_state,target.dir) var/orbitsize = (I.Width()+I.Height())*0.5 orbitsize -= (orbitsize/world.icon_size)*(world.icon_size*0.25) var/rot_seg switch(ghost_orbit) if(GHOST_ORBIT_TRIANGLE) rot_seg = 3 if(GHOST_ORBIT_SQUARE) rot_seg = 4 if(GHOST_ORBIT_PENTAGON) rot_seg = 5 if(GHOST_ORBIT_HEXAGON) rot_seg = 6 else //Circular rot_seg = 36 //360/10 bby, smooth enough aproximation of a circle orbit(target,orbitsize, FALSE, 20, rot_seg) /mob/dead/observer/orbit() setDir(2)//reset dir so the right directional sprites show up return ..() /mob/dead/observer/stop_orbit(datum/component/orbiter/orbits) . = ..() //restart our floating animation after orbit is done. pixel_y = 0 animate(src, pixel_y = 2, time = 10, loop = -1) /mob/dead/observer/verb/jumptomob() //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(isobserver(usr)) //Make sure they're an observer! var/list/dest = list() //List of possible destinations (mobs) var/target = null //Chosen target. dest += getpois(mobs_only=1) //Fill list, prompt user with list target = input("Please, select a player!", "Jump to Mob", null, null) as null|anything in dest if (!target)//Make sure we actually have a target return else var/mob/M = dest[target] //Destination mob var/mob/A = src //Source mob var/turf/T = get_turf(M) //Turf of the destination mob if(T && isturf(T)) //Make sure the turf exists, then move the source to that destination. A.forceMove(T) A.update_parallax_contents() else to_chat(A, "This mob is not located in the game world.") /mob/dead/observer/verb/change_view_range() set category = "Ghost" set name = "View Range" set desc = "Change your view range." var/max_view = client.prefs.unlock_content ? GHOST_MAX_VIEW_RANGE_MEMBER : GHOST_MAX_VIEW_RANGE_DEFAULT if(client.view == CONFIG_GET(string/default_view)) var/list/views = list() for(var/i in 7 to max_view) views |= i var/new_view = input("Choose your new view", "Modify view range", 7) as null|anything in views if(new_view) client.change_view(CLAMP(new_view, 7, max_view)) else client.change_view(CONFIG_GET(string/default_view)) /mob/dead/observer/verb/add_view_range(input as num) set name = "Add View Range" set hidden = TRUE var/max_view = client.prefs.unlock_content ? GHOST_MAX_VIEW_RANGE_MEMBER : GHOST_MAX_VIEW_RANGE_DEFAULT if(input) client.rescale_view(input, 15, (max_view*2)+1) /mob/dead/observer/verb/boo() set category = "Ghost" set name = "Boo!" set desc= "Scare your crew members because of boredom!" if(bootime > world.time) return var/obj/machinery/light/L = locate(/obj/machinery/light) in view(1, src) if(L) L.flicker() bootime = world.time + 600 return //Maybe in the future we can add more spooky code here! return /mob/dead/observer/memory() set hidden = 1 to_chat(src, "You are dead! You have no mind to store memory!") /mob/dead/observer/add_memory() set hidden = 1 to_chat(src, "You are dead! You have no mind to store memory!") /mob/dead/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) update_sight() to_chat(usr, "You [(ghostvision?"now":"no longer")] have ghost vision.") /mob/dead/observer/verb/toggle_darkness() set name = "Toggle Darkness" set category = "Ghost" switch(lighting_alpha) if (LIGHTING_PLANE_ALPHA_VISIBLE) lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE if (LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE) lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE if (LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE) lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE else lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE update_sight() /mob/dead/observer/update_sight() if(client) ghost_others = client.prefs.ghost_others //A quick update just in case this setting was changed right before calling the proc if (!ghostvision) see_invisible = SEE_INVISIBLE_LIVING else see_invisible = SEE_INVISIBLE_OBSERVER updateghostimages() ..() /proc/updateallghostimages() listclearnulls(GLOB.ghost_images_default) listclearnulls(GLOB.ghost_images_simple) for (var/mob/dead/observer/O in GLOB.player_list) O.updateghostimages() /mob/dead/observer/proc/updateghostimages() if (!client) return if(lastsetting) switch(lastsetting) //checks the setting we last came from, for a little efficiency so we don't try to delete images from the client that it doesn't have anyway if(GHOST_OTHERS_DEFAULT_SPRITE) client.images -= GLOB.ghost_images_default if(GHOST_OTHERS_SIMPLE) client.images -= GLOB.ghost_images_simple lastsetting = client.prefs.ghost_others if(!ghostvision) return if(client.prefs.ghost_others != GHOST_OTHERS_THEIR_SETTING) switch(client.prefs.ghost_others) if(GHOST_OTHERS_DEFAULT_SPRITE) client.images |= (GLOB.ghost_images_default-ghostimage_default) if(GHOST_OTHERS_SIMPLE) client.images |= (GLOB.ghost_images_simple-ghostimage_simple) /mob/dead/observer/verb/possess() set category = "Ghost" set name = "Possess!" set desc= "Take over the body of a mindless creature!" var/list/possessible = list() for(var/mob/living/L in GLOB.alive_mob_list) if(istype(L,/mob/living/carbon/human/dummy) || !get_turf(L)) //Haha no. continue if(!(L in GLOB.player_list) && !L.mind) possessible += L var/mob/living/target = input("Your new life begins today!", "Possess Mob", null, null) as null|anything in possessible if(!target) return 0 if(ismegafauna(target)) to_chat(src, "This creature is too powerful for you to possess!") return 0 if(istype (target, /mob/living/simple_animal/hostile/spawner)) to_chat(src, "This isn't really a creature, now is it!") return 0 if(!can_reenter_round) to_chat(src, "You are unable to re-enter the round.") return FALSE if(can_reenter_corpse && mind && mind.current) if(alert(src, "Your soul is still tied to your former life as [mind.current.name], if you go forward there is no going back to that life. Are you sure you wish to continue?", "Move On", "Yes", "No") == "No") return 0 if(target.key) to_chat(src, "Someone has taken this body while you were choosing!") return 0 target.key = key target.faction = list("neutral") return 1 //this is a mob verb instead of atom for performance reasons //see /mob/verb/examinate() in mob.dm for more info //overridden here and in /mob/living for different point span classes and sanity checks /mob/dead/observer/pointed(atom/A as mob|obj|turf in view()) if(!..()) return 0 usr.visible_message("[src] points to [A].") return 1 /mob/dead/observer/verb/view_manifest() set name = "View Crew Manifest" set category = "Ghost" var/dat dat += "