ghostroles (#3961)

* files

* obj

* mass ports

* ah

* wack

* that

* todo refactor ui intieract calls jfc

* all

* test case

* compile

* compile

* auxtools time

* wack

* debugger hook

* I LOVE YOU SPACEMANIAC THE DEBUGGER JUST SAVED MY LIFE
This commit is contained in:
silicons
2022-05-01 01:26:52 -07:00
committed by GitHub
parent 0048e933a0
commit 8d4fd18cc2
59 changed files with 1948 additions and 29 deletions

View File

@@ -1,5 +1,10 @@
environment = "vorestation.dme"
[langserver]
dreamchecker = true
[dmdoc]
use_typepath_names = true
[debugger]
engine = "auxtools"

View File

@@ -0,0 +1,24 @@
/proc/auxtools_stack_trace(msg)
CRASH(msg)
GLOBAL_LIST_EMPTY(auxtools_initialized)
#define AUXTOOLS_CHECK(LIB)\
if (!GLOB.auxtools_initialized[LIB]) {\
if (fexists(LIB)) {\
var/string = call(LIB,"auxtools_init")();\
if(findtext(string, "SUCCESS")) {\
GLOB.auxtools_initialized[LIB] = TRUE;\
} else {\
CRASH(string);\
}\
} else {\
CRASH("No file named [LIB] found!")\
}\
}\
#define AUXTOOLS_SHUTDOWN(LIB)\
if (GLOB.auxtools_initialized[LIB] && fexists(LIB)){\
call(LIB,"auxtools_shutdown")();\
GLOB.auxtools_initialized[LIB] = FALSE;\
}\

View File

@@ -36,7 +36,7 @@
///? Skips the specific attack step, continuing for the next one to happen.
////#define COMPONENT_SKIP_ATTACK (1<<1)
/// From base of atom/attack_ghost(): (mob/dead/observer/ghost)
////#define COMSIG_ATOM_ATTACK_GHOST "atom_attack_ghost"
#define COMSIG_ATOM_ATTACK_GHOST "atom_attack_ghost"
/// From base of atom/attack_hand(): (mob/user, list/modifiers)
////#define COMSIG_ATOM_ATTACK_HAND "atom_attack_hand"
/// From base of atom/attack_paw(): (mob/user)

View File

@@ -0,0 +1,4 @@
// Notification action types
#define NOTIFY_JUMP "jump"
#define NOTIFY_ATTACK "attack"
#define NOTIFY_ORBIT "orbit"

View File

@@ -28,9 +28,8 @@
#define VAR_PROTECTED var
#endif
/*
/world/proc/enable_debugger()
var/dll = world.GetConfig("env", "EXTOOLS_DLL")
if (dll)
call(dll, "debug_initialize")()
*/
/proc/enable_debugging()
CRASH("Auxtools not found")
/proc/auxtools_expr_stub()
CRASH("Auxtools not found")

View File

@@ -203,6 +203,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define ORGAN_TRAIT "organ"
*/
#define ROUNDSTART_TRAIT "roundstart" //cannot be removed without admin intervention
#define GHOSTROLE_TRAIT "ghostrole"
#define JOB_TRAIT "job"
#define TRAIT_MIME "mime" //Mime trait.
/*

View File

@@ -0,0 +1,7 @@
/proc/safe_json_encode(list/L, default = "")
. = default
return json_encode(L)
/proc/safe_json_decode(string, default = list())
. = default
return json_decode(string)

View File

@@ -167,6 +167,7 @@
#define ui_ghost_teleport "SOUTH:6,CENTER+1:24"
#define ui_ghost_pai "SOUTH: 6,CENTER+2:24"
#define ui_ghost_updown "SOUTH: 6,CENTER+3:24"
#define ui_ghost_spawners "SOUTH: 6,CENTER+4:24"
// Rig panel
#define ui_rig_deco1 "WEST:-7, SOUTH+5"

View File

@@ -35,6 +35,8 @@
else
alert = new type
alert.owner = src
if(new_master)
alert.icon_state = "itembased"
var/image/I = image(icon = new_master.icon, icon_state = new_master.icon_state, dir = SOUTH)
@@ -82,16 +84,19 @@
var/severity = 0
var/alerttooltipstyle = ""
var/no_underlay // Don't underlay the UI style's blank template icon under this
/// mob that owns us
var/mob/owner
/atom/movable/screen/alert/Destroy()
owner = null
return ..()
/atom/movable/screen/alert/MouseEntered(location,control,params)
openToolTip(usr, src, params, title = name, content = desc, theme = alerttooltipstyle)
/atom/movable/screen/alert/MouseExited()
closeToolTip(usr)
//Gas alerts
/atom/movable/screen/alert/not_enough_oxy
name = "Choking (No O2)"
@@ -373,6 +378,32 @@ so as to remain in compliance with the most up-to-date laws."
var/mob/observer/dead/G = usr
G.reenter_corpse()
/atom/movable/screen/alert/notify_action
name = "Body created"
desc = "A body was created. You can enter it."
icon_state = "template"
timeout = 300
var/atom/target = null
var/action = NOTIFY_JUMP
/atom/movable/screen/alert/notify_action/Click()
if(!usr || !usr.client || usr != owner)
return
if(!target)
return
var/mob/observer/dead/G = usr
if(!istype(G))
return
switch(action)
if(NOTIFY_ATTACK)
target.attack_ghost(G)
if(NOTIFY_JUMP)
var/turf/T = get_turf(target)
if(T && isturf(T))
G.forceMove(T)
if(NOTIFY_ORBIT)
G.ManualFollow(target)
// /atom/movable/screen/alert/notify_jump
// name = "Body created"
// desc = "A body was created. You can enter it."

View File

@@ -1,5 +1,5 @@
/atom/movable/screen/ghost
icon = 'icons/mob/screen_ghost.dmi'
icon = 'icons/hud/common/observer.dmi'
/atom/movable/screen/ghost/MouseEntered(location,control,params)
flick(icon_state + "_anim", src)
@@ -91,6 +91,16 @@
var/mob/observer/dead/G = usr
G.zMove(DOWN)
/atom/movable/screen/ghost/spawners
name = "Ghost Roles/Spawners"
desc = "Open the ghostrole/spawner menu."
icon_state = "spawners"
/atom/movable/screen/ghost/spawners/Click()
. = ..()
GLOB.ghostrole_menu.ui_interact(usr)
// TODO; /datum/hud refactor
/datum/hud/proc/ghost_hud(apply_to_client = TRUE)
var/list/adding = list()
@@ -136,6 +146,11 @@
using.hud = src
adding += using
using = new /atom/movable/screen/ghost/spawners
using.screen_loc = ui_ghost_spawners
using.hud = src
adding += using
if(mymob.client && apply_to_client)
mymob.client.screen = list()
mymob.client.screen += adding

View File

@@ -43,13 +43,15 @@
A.attack_ghost(src)
// Oh by the way this didn't work with old click code which is why clicking shit didn't spam you
/atom/proc/attack_ghost(mob/observer/dead/user as mob)
/atom/proc/attack_ghost(mob/observer/dead/user)
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_GHOST, user)
// TODO: main ai interact bay code fucking disgusts me wtf
if(IsAdminGhost(user)) // admin AI interact
AdminAIInteract(user)
return
if(user.client && user.client.inquisitive_ghost)
user.examinate(src)
return
// defaults to just attack_ai
/atom/proc/AdminAIInteract(mob/user)
@@ -65,19 +67,22 @@
if(com.locked)
user.loc = get_turf(com.locked)
*/
/obj/effect/portal/attack_ghost(mob/user as mob)
/obj/effect/portal/attack_ghost(mob/user)
. = ..()
if(target)
user.loc = get_turf(target)
user.forceMove(get_turf(target))
/obj/machinery/gateway/centerstation/attack_ghost(mob/user as mob)
/obj/machinery/gateway/centerstation/attack_ghost(mob/user)
. = ..()
if(awaygate)
user.loc = awaygate.loc
user.forceMove(awaygate.loc)
else
to_chat(user, "[src] has no destination.")
/obj/machinery/gateway/centeraway/attack_ghost(mob/user as mob)
/obj/machinery/gateway/centeraway/attack_ghost(mob/user)
. = ..()
if(stationgate)
user.loc = stationgate.loc
user.forceMove(stationgate.loc)
else
to_chat(user, "[src] has no destination.")
@@ -95,7 +100,8 @@
*/
//VR FILE MERGE
/obj/item/paicard/attack_ghost(mob/user as mob)
/obj/item/paicard/attack_ghost(mob/user)
. = ..()
if(src.pai != null) //Have a person in them already?
user.examinate(src)
return

View File

@@ -483,7 +483,7 @@
return 0
//Initialisation procs
/mob/living/proc/mind_initialize()
/mob/proc/mind_initialize()
if(mind)
mind.key = key
else

View File

@@ -72,6 +72,7 @@
// /vg/: Don't let ghosts fuck with this.
/turf/unsimulated/wall/supermatter/attack_ghost(mob/user as mob)
. = ..()
user.examinate(src)
/turf/unsimulated/wall/supermatter/attack_ai(mob/user as mob)

View File

@@ -253,6 +253,7 @@
return attack_hand(user)
/obj/machinery/body_scanconsole/attack_ghost(user as mob)
. = ..()
return attack_hand(user)
/obj/machinery/body_scanconsole/attack_hand(user as mob)

View File

@@ -114,6 +114,7 @@
return src.attack_hand(user)
/obj/machinery/portable_atmospherics/powered/pump/attack_ghost(var/mob/user)
. = ..()
return src.attack_hand(user)
/obj/machinery/portable_atmospherics/powered/pump/attack_hand(var/mob/user)

View File

@@ -102,6 +102,7 @@
return src.attack_hand(user)
/obj/machinery/portable_atmospherics/powered/scrubber/attack_ghost(var/mob/user)
. = ..()
return src.attack_hand(user)
/obj/machinery/portable_atmospherics/powered/scrubber/attack_hand(var/mob/user)

View File

@@ -53,9 +53,9 @@
return
/obj/machinery/computer/aiupload/attack_ghost(user as mob)
. = ..()
return 1
/obj/machinery/computer/borgupload
name = "cyborg upload console"
desc = "Used to upload laws to Cyborgs."
@@ -89,4 +89,5 @@
return
/obj/machinery/computer/borgupload/attack_ghost(user as mob)
. = ..()
return 1

View File

@@ -841,6 +841,7 @@ About the new airlock wires panel:
ui_interact(user)
/obj/machinery/door/airlock/attack_ghost(mob/user)
. = ..()
ui_interact(user)
/obj/machinery/door/airlock/ui_interact(mob/user, datum/tgui/ui)

View File

@@ -52,6 +52,7 @@
/obj/machinery/tele_pad/attack_ghost(mob/user)
. = ..()
if (!computer?.active)
return
var/turf/T = get_turf(computer.target)

View File

@@ -131,6 +131,7 @@
attack_generic()
/obj/effect/catwalk_plated/attack_ghost()
. = ..()
attack_generic()
/obj/effect/catwalk_plated/attack_generic()

View File

@@ -375,6 +375,7 @@
to_chat(usr, "<span class='notice'>It won't budge!</span>")
/obj/structure/closet/attack_ghost(mob/ghost)
. = ..()
if(ghost.client && ghost.client.inquisitive_ghost)
ghost.examinate(src)
if (!src.opened)

View File

@@ -79,6 +79,7 @@
description_info = "A ghost can click on this to return to the round as whatever is contained inside this object."
/obj/structure/ghost_pod/ghost_activated/attack_ghost(var/mob/observer/dead/user)
. = ..()
if(used)
to_chat(user, "<span class='warning'>Another spirit appears to have gotten to \the [src] before you. Sorry.</span>")
return

View File

@@ -18,6 +18,7 @@
attack_generic()
/obj/effect/wingrille_spawn/attack_ghost()
. = ..()
attack_generic()
/obj/effect/wingrille_spawn/attack_generic()

View File

@@ -18,12 +18,16 @@ GLOBAL_LIST(topic_status_cache)
//This happens after the Master subsystem new(s) (it's a global datum)
//So subsystems globals exist, but are not initialised
/world/New()
// enable_debugger()
var/debug_server = world.GetConfig("env", "AUXTOOLS_DEBUG_DLL")
if (debug_server)
call(debug_server, "auxtools_init")()
enable_debugging()
log_world("World loaded at [TIME_STAMP("hh:mm:ss", FALSE)]!")
var/tempfile = "data/logs/config_error.[GUID()].log" //temporary file used to record errors with loading config, moved to log directory once logging is set
GLOB.config_error_log = GLOB.world_href_log = GLOB.world_runtime_log = GLOB.world_map_error_log = GLOB.world_attack_log = GLOB.world_game_log = tempfile
// citadel edit: world runtime log removed due to world.log shunt doing that for us
GLOB.config_error_log = GLOB.world_href_log = GLOB.world_map_error_log = GLOB.world_attack_log = GLOB.world_game_log = tempfile
world.Profile(PROFILE_START)
make_datum_reference_lists() //initialises global lists for referencing frequently used datums (so that we only ever do it once)
@@ -300,6 +304,12 @@ GLOBAL_REAL_VAR(world_log_redirected) = FALSE
shutdown_logging() // Past this point, no logging procs can be used, at risk of data loss.
..()
/world/Del()
var/debug_server = world.GetConfig("env", "AUXTOOLS_DEBUG_DLL")
if (debug_server)
call(debug_server, "auxtools_shutdown")()
..()
/hook/startup/proc/loadMode()
world.load_mode()
return 1

View File

@@ -228,6 +228,7 @@ Thus, the two variables affect pump operation are set in New():
return
/obj/machinery/atmospherics/binary/pump/attack_ghost(mob/user)
. = ..()
ui_interact(user)
/obj/machinery/atmospherics/binary/pump/attack_hand(mob/user)

View File

@@ -721,7 +721,6 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
using_perspective = null
if(!P)
eye = null
virtual_eye = null
lazy_eye = 0
perspective = EYE_PERSPECTIVE
return

View File

@@ -0,0 +1,152 @@
/**
* Handles mob creation, equip, and ckey transfer.
*/
/datum/ghostrole_instantiator
/// traits to add to mob : will be made with GHOSTROLE_TRAIT source
var/list/mob_traits
/datum/ghostrole_instantiator/proc/Run(client/C, atom/location, list/params)
RETURN_TYPE(/mob)
. = Create(C, location, params)
if(!.)
return
Equip(C, ., params)
/datum/ghostrole_instantiator/proc/Create(client/C, atom/location, list/params)
CRASH("Base Create() called on ghostrole instantiator datum.")
/datum/ghostrole_instantiator/proc/Equip(client/C, mob/M, list/params)
CRASH("Base Equip() called on ghostrole instantiator datum.")
/datum/ghostrole_instantiator/simple
var/mob_type
/datum/ghostrole_instantiator/simple/Create(client/C, atom/location, list/params)
var/type_to_make = GetMobType(location)
if(!ispath(type_to_make, /mob/living))
CRASH("Invalid path: [type_to_make]")
var/mob/living/L = new type_to_make(location)
for(var/trait in mob_traits)
ADD_TRAIT(L, trait, GHOSTROLE_TRAIT)
return L
/datum/ghostrole_instantiator/simple/proc/GetMobType(client/C, atom/location, list/params)
return params["mob"] || mob_type
/datum/ghostrole_instantiator/human
/// outfit to equip
var/equip_outfit
/datum/ghostrole_instantiator/human/Create(client/C, atom/location, list/params)
var/mob/living/carbon/human/H = new(location)
for(var/trait in mob_traits)
ADD_TRAIT(H, trait, GHOSTROLE_TRAIT)
return H
/datum/ghostrole_instantiator/human/Equip(client/C, mob/M, list/params)
var/mob/living/carbon/human/H = M
// H.dna.species.before_equip_job(null, H)
var/datum/outfit/O = GetOutfit(C, M, params)
if(ispath(O, /datum/outfit))
O = new O
O.equip(M)
H.species.equip_survival_gear(H, TRUE, TRUE)
// H.dna.species.after_equip_job(null, H)
/**
* Returns an outfit instance or a typepath
*/
/datum/ghostrole_instantiator/human/proc/GetOutfit(client/C, mob/M, list/params)
// allow for outfit override
if(params["outfit"])
var/override = params["outfit"]
if(istext(override))
override = text2path(override)
if(ispath(override, /datum/outfit))
return override
if(istype(override, /datum/outfit))
return override
if(ispath(equip_outfit, /datum/outfit))
return equip_outfit
if(istype(equip_outfit, /datum/outfit))
return equip_outfit
return new /datum/outfit
/datum/ghostrole_instantiator/human/random
/datum/ghostrole_instantiator/human/random/Create(client/C, atom/location, list/params)
var/mob/living/carbon/human/H = ..()
Randomize(H, params)
return H
/datum/ghostrole_instantiator/human/random/proc/Randomize(mob/living/carbon/human/H, list/params)
return // tgcode does this automatically
/datum/ghostrole_instantiator/human/random/species
/// allowed species types
var/list/possible_species = list(
/datum/species/human,
/datum/species/unathi,
/datum/species/tajaran,
/datum/species/skrell,
/datum/species/akula,
/datum/species/diona
// /datum/species/lizard,
// /datum/species/plasmaman,
// /datum/species/jelly,
// /datum/species/ipc
)
/datum/ghostrole_instantiator/human/random/species/proc/GetSpeciesPath(mob/living/carbon/human/H, list/params)
var/override = params["species"]
if(istext(override))
override = text2path(override)
if(ispath(override, /datum/species))
return override
return SAFEPICK(possible_species) || /datum/species/human
/datum/ghostrole_instantiator/human/random/species/Randomize(mob/living/carbon/human/H, list/params)
. = ..()
var/species = pick(GetSpeciesPath(H, params))
// todo species refactor
var/datum/species/S = species
H.set_species(initial(S.name))
var/new_name = random_name(H.gender, H.species.name)
// H.set_species(new species)
// var/new_name
// switch(H.dna.species.type)
// if(/datum/species/lizard)
// new_name = random_unique_lizard_name()
// if(/datum/species/ethereal)
// new_name = random_unique_ethereal_name()
// if(/datum/species/plasmaman)
// new_name = random_unique_plasmaman_name()
// if(/datum/species/insect)
// new_name = random_unique_moth_name()
// if(/datum/species/arachnid)
// new_name = random_unique_arachnid_name()
// else
// new_name = random_unique_name()
H.fully_replace_character_name(H.real_name, new_name)
/datum/ghostrole_instantiator/human/player_static
/// equip loadout
var/equip_loadout = TRUE
/// equip traits
var/equip_traits = TRUE
/datum/ghostrole_instantiator/human/player_static/Create(client/C, atom/location, list/params)
var/mob/living/carbon/human/H = ..()
LoadSavefile(C, H)
return H
/datum/ghostrole_instantiator/human/player_static/proc/LoadSavefile(client/C, mob/living/carbon/human/H)
C.prefs.copy_to(H)
job_master.EquipRank(H, USELESS_JOB)
// if(equip_loadout)
// SSjob.EquipLoadout(H, FALSE, null, C.prefs, C.ckey)
// if(equip_traits && CONFIG_GET(flag/roundstart_traits))
// SSquirks.AssignQuirks(H, C, TRUE, FALSE, null, FALSE, C)

View File

@@ -0,0 +1,66 @@
GLOBAL_DATUM_INIT(ghostrole_menu, /datum/ghostrole_menu, new)
/datum/ghostrole_menu
/datum/ghostrole_menu/ui_state(mob/user)
return GLOB.observer_state
/datum/ghostrole_menu/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "GhostRoleMenu")
ui.open()
/datum/ghostrole_menu/ui_static_data(mob/user)
. = ..()
var/list/spawners = list()
.["spawners"] = spawners
for(var/id in GLOB.ghostroles)
var/datum/ghostrole/role = GLOB.ghostroles[id]
if(!istype(role))
stack_trace("non ghostrole [role] ([id]) pruned from ghostroles list.")
GLOB.ghostroles -= id
continue
var/list/data = list()
data["id"] = role.id || role.type
data["name"] = role.name
data["short_desc"] = role.desc
data["flavor_text"] = role.spawntext
data["important_info"] = role.ImportantInfo()
var/slots = role.SpawnsLeft(user)
data["amount_left"] = slots == INFINITY? -1 : slots
spawners += list(data) // wrap
/datum/ghostrole_menu/ui_act(action, params)
if(..())
return
if(!isobserver(usr))
return
var/id = params["id"]
var/datum/ghostrole/role = get_ghostrole_datum(id)
if(!role)
return
switch(action)
if("jump")
if(role.spawnerless)
to_chat(usr, "<span class='warning'>[role] is spawnerless! You won't be able to find out where you spawn until you actually spawn in!</span>")
return
var/atom/A = role.GetSpawnLoc(usr.client, role.GetSpawnpoint(usr.client))
if(!A)
to_chat(usr, "<span class='warning'>Could not find a spawnpoint for [role]. This sometimes mean it isn't loaded in until someone attempts to spawn. Alternatively, you didn't pick one!</span>")
return
if(!A.loc)
to_chat(usr, "<span class='danger'>BUG: Spawnpoint was nullspace.</span>")
return
usr.forceMove(get_turf(A))
if("spawn")
var/client/C = usr.client
var/error = role.AttemptSpawn(C)
if(istext(error))
to_chat(C, SPAN_DANGER(error))
/**
* Call this whenever ghostrole data changes, we don't keep resending to save performance.
*/
/datum/ghostrole_menu/proc/queue_update()
addtimer(CALLBACK(src, /datum/proc/update_static_data), 0, TIMER_UNIQUE | TIMER_OVERRIDE)

View File

@@ -0,0 +1,215 @@
GLOBAL_LIST_INIT(ghostroles, init_ghostroles())
/proc/init_ghostroles()
. = list()
for(var/path in subtypesof(/datum/ghostrole))
var/datum/ghostrole/G = path
if(initial(G.abstract_type) == path)
continue
if(initial(G.lazy_init))
continue
.[path] = new path
/**
* This intentionally supports strings for badmin purposes.
*/
/proc/get_ghostrole_datum(path)
if(GLOB.ghostroles[path])
return GLOB.ghostroles[path]
var/is_this_a_path = ispath(path)? path : text2path(path)
if(ispath(is_this_a_path, /datum/ghostrole))
GLOB.ghostroles[is_this_a_path] = new is_this_a_path
return GLOB.ghostroles[is_this_a_path]
/**
* Ghostrole datums
*/
/datum/ghostrole
/// name
var/name = "Unnamed Role"
/// **short** description - use spawntext for long one.
var/desc = "Wow, a coder fucked up."
/// init on server load or only when needed
var/lazy_init = TRUE
/// allow selecting the spawner, or random? **If the spawner gets clicked by a player, they can still spawn from it!**
var/allow_pick_spawner = FALSE
/// abstract type
var/abstract_type = /datum/ghostrole
/// /datum/ghostrole_instantiator - handles mob creation, equip, and transfer. DOES NOT greet the ghostrole with role information.
var/datum/ghostrole_instantiator/instantiator
/// spawn count
var/spawns = 0
/// max spawns
var/slots = INFINITY
/// default message to show on greet(), also shows in spawners menu.
var/spawntext
/// important rules/policy info
var/important_info
/// should we show the standard ghostrole greeting?
var/show_standard_greeting = TRUE
/// snowflake ID for if we're not to be referred to by path - dynamically created ghostolres
var/id
/// spawnerless - advanced users only. This isn't for "load in spawners in PreInstantiate()", this is for true spawnpoint-less ghostroles.
var/spawnerless = FALSE
/// assigned role. defaults to name.
var/assigned_role
/// jobban name/id, if any
var/jobban_role
/// Automatically give them an objective and custom antag datum
var/automatic_objective
/// inject params during spawning
var/list/inject_params
/datum/ghostrole/New(_id)
if(ispath(instantiator, /datum/ghostrole_instantiator))
instantiator = new instantiator
id = _id || type
/datum/ghostrole/proc/Greet(mob/created, datum/component/ghostrole_spawnpoint/spawnpoint, list/params)
if(show_standard_greeting)
to_chat(created, {"<center><b><font size='16px'>You have spawned as a ghostrole.</font></b></center>
These roles are usually more roleplay oriented than standard hard-defined antagonist roles - besure to follow spawntext (if any), as well as server rules.
Spawntext as follows;"})
if(spawntext)
to_chat(created, spawntext)
if(spawnpoint.spawntext)
to_chat(created, spawntext)
/datum/ghostrole/proc/ImportantInfo()
// todo: policyconfig
return important_info
/**
* Master proc for spawning someone as this role.
*
* Return TRUe on success, or a string of why it failed.
*/
/datum/ghostrole/proc/AttemptSpawn(client/C, datum/component/ghostrole_spawnpoint/chosen_spawnpoint)
if(BanCheck(C))
return "You can't spawn as [src] due to an active job-ban."
if(!AllowSpawn(C))
return "You can't spawn as this role; Try refreshing the ghostrole/join menu."
if(!PreInstantiate(C))
return "PreInstantiate() failed."
var/datum/component/ghostrole_spawnpoint/spawnpoint = spawnerless? null : (chosen_spawnpoint || GetSpawnpoint(C))
var/list/params = islist(spawnpoint?.params)? spawnpoint.params.Copy() : list() // clone/new, because procs CAN MODIFY THIS.
if(inject_params)
params |= inject_params
if(!AllowSpawn(C, params)) // check again with params
return "The spawnpoint refused to let you spawn."
var/atom/location = GetSpawnLoc(C, spawnpoint)
if(!location)
return "Couldn't get a spawn location."
if(!instantiator)
return "BUG: No instantiator for [src][(id !=type) && ":[id]"] ([type])"
var/mob/created = Instantiate(C, location, params)
if(!created)
return "Mob instantiation failed."
if(!Transfer(C, created))
qdel(created)
return "Mob transfer failed."
PostInstantiate(created, spawnpoint, params)
// GLOB.join_menu.queue_update()
GLOB.ghostrole_menu.queue_update()
return TRUE
/datum/ghostrole/proc/Instantiate(client/C, atom/loc, list/params)
var/mob/living/L = instantiator.Run(C, loc, params)
. = istype(L) && L
if(.)
L.mind?.assigned_role = assigned_role || name
/datum/ghostrole/proc/Transfer(client/C, mob/created)
if(!isnewplayer(C.mob))
C.mob.ghostize(TRUE, TRUE)
created.ckey = C.ckey
return TRUE
/**
* Ran before anything else is at AttemptSpawn()
*/
/datum/ghostrole/proc/PreInstantiate(client/C)
return TRUE
/**
* Checks if the client is a valid user mob and if we can allow a spawn from them
*/
/datum/ghostrole/proc/AllowSpawn(client/C, list/params)
if(!isobserver(C.mob) && !isnewplayer(C.mob))
return FALSE
if(SpawnsLeft(C) <= 0)
return FALSE
return TRUE
/datum/ghostrole/proc/SpawnsLeft(client/C)
if(spawnerless)
return max(0, slots - spawns)
return min(max(0, slots - spawns), TallySpawnpointSlots(C))
/datum/ghostrole/proc/TallySpawnpointSlots(client/C)
var/list/datum/component/ghostrole_spawnpoint/spawnpoints = GLOB.ghostrole_spawnpoints[id]
. = 0
for(var/datum/component/ghostrole_spawnpoint/S as anything in spawnpoints)
. += S.SpawnsLeft(C)
/**
* Gets a spawnpoint for a client
*
* For spawnerless ghostroles, return null.
*/
/datum/ghostrole/proc/GetSpawnpoint(client/C)
if(!allow_pick_spawner)
return SAFEPICK(GLOB.ghostrole_spawnpoints[id])
var/list/datum/component/ghostrole_spawnpoint/spawnpoints = GLOB.ghostrole_spawnpoints[id]
var/list/inputlist = list()
for(var/datum/component/ghostrole_spawnpoint/spawnpoint as anything in spawnpoints)
var/atom/A = spawnpoint.Atom()
inputlist["[A] - [get_area(A)]"] = spawnpoint
var/picked = tgui_input_list(C.mob, "Spawner Selection", inputlist)
return inputlist[picked]
/**
* Gets a spawn location for a client.
*
* spawnpoint can be null for spawnerless ghostroles.
*/
/datum/ghostrole/proc/GetSpawnLoc(client/C, datum/component/ghostrole_spawnpoint/spawnpoint)
return spawnpoint?.Turf()
/**
* Spawnpoint can be null here, if we're not using a spawnpoint
*/
/datum/ghostrole/proc/PostInstantiate(mob/created, datum/component/ghostrole_spawnpoint/spawnpoint, list/params)
Greet(created, spawnpoint, params)
if(automatic_objective)
GiveCustomObjective(created, automatic_objective)
spawns++
spawnpoint?.OnSpawn(created, src)
/**
* Ban check.
*/
/datum/ghostrole/proc/BanCheck(client/C)
if(!jobban_role)
return FALSE
return jobban_isbanned(C.mob, jobban_role)
/datum/ghostrole/proc/GiveCustomObjective(mob/created, objective)
created.GhostroleGiveCustomObjective(src, objective)
/mob/proc/GhostroleGiveCustomObjective(datum/ghostrole/R, objective)
if(!mind)
mind_initialize()
if(!mind)
CRASH("No mind.")
store_memory("OBJECTIVE: [objective]", TRUE)
to_chat(src, SPAN_DANGER("An objective has been added to you by your ghostrole spawner. Remember that roleplay comes first - these are often freeform. Said objective is in your MEMORIES, due to the codebase lacking datum antagonists."))
// TODO: DATUM ANTAGS
// var/datum/antagonist/custom/A = mind.has_antag_datum(/datum/antagonist/custom) || mind.add_antag_datum(/datum/antagonist/custom)
// if(!A)
// CRASH("Failed to locate/make custom antagonist datum.")
// var/datum/objective/O = new(objective)
// O.owner = mind
// A.objectives += O

View File

@@ -0,0 +1,81 @@
/datum/ghostrole/ashwalker
name = "Ashwalker"
instantiator = /datum/ghostrole_instantiator/human/random/species/ashwalker
desc = "You are an ash walker. Your tribe worships the Necropolis."
spawntext = "The wastes are sacred ground, its monsters a blessed bounty. You would never willingly leave your homeland behind. \
You have seen lights in the distance... they foreshadow the arrival of outsiders to your domain. \
Ensure your nest remains protected at all costs."
assigned_role = "Ash Walker"
allow_pick_spawner = TRUE
jobban_role = ROLE_LAVALAND
/datum/ghostrole/ashwalker/Greet(mob/created)
. = ..()
var/turf/T = get_turf(created)
if(is_mining_level(T.z))
to_chat(created, "<b>Drag the corpses of men and beasts to your nest. It will absorb them to create more of your kind. Glory to the Necropolis!</b>")
to_chat(created, "<b>You can expand the weather proof area provided by your shelters by using the 'New Area' key near the bottom right of your HUD.</b>")
else
to_chat(created, "<span class='userdanger'>You have been born outside of your natural home! Whether you decide to return home, or make due with your new home is your own decision.</span>")
/datum/ghostrole/ashwalker/AllowSpawn(client/C, list/params)
if(params && params["team"])
var/datum/team/ashwalkers/team = params["team"]
if(C.ckey in team.players_spawned)
to_chat(C, span_warning("<b>You have exhausted your usefulness to the Necropolis</b>."))
return FALSE
return ..()
/datum/ghostrole/ashwalker/PostInstantiate(mob/created, datum/component/ghostrole_spawnpoint/spawnpoint, list/params)
. = ..()
if(params["team"])
var/datum/team/ashwalkers/team = spawnpoint.params["team"]
team.players_spawned += ckey(created.key)
created.mind.add_antag_datum(/datum/antagonist/ashwalker, team)
/datum/ghostrole_instantiator/human/random/species/ashwalker
possible_species = list(
/datum/species/lizard/ashwalker
)
equip_outfit = /datum/outfit/ashwalker
/datum/ghostrole_instantiator/human/random/species/ashwalker/Randomize(mob/living/carbon/human/H, list/params)
. = ..()
H.underwear = "Nude"
H.undershirt = "Nude"
H.socks = "Nude"
H.update_body()
H.real_name = random_unique_lizard_name(H.gender)
/obj/structure/ghost_role_spawner/ash_walker
name = "ash walker egg"
desc = "A man-sized yellow egg, spawned from some unfathomable creature. A humanoid silhouette lurks within. The egg shell looks resistant to temperature but otherwise rather brittle."
icon = 'icons/mob/lavaland/lavaland_monsters.dmi'
icon_state = "large_egg"
anchored = FALSE
move_resist = MOVE_FORCE_NORMAL
density = FALSE
role_type = /datum/ghostrole/ashwalker
role_spawns = 1
resistance_flags = LAVA_PROOF | FIRE_PROOF | FREEZE_PROOF
max_integrity = 80
var/datum/team/ashwalkers/team
/obj/structure/ghost_role_spawner/ash_walker/on_spawn(mob/created, datum/ghostrole/role, list/params)
. = ..()
qdel(src)
/obj/structure/ghost_role_spawner/ash_walker/Destroy()
var/mob/living/carbon/human/yolk = new /mob/living/carbon/human/(get_turf(src))
yolk.fully_replace_character_name(null,random_unique_lizard_name(gender))
yolk.set_species(/datum/species/lizard/ashwalker)
yolk.underwear = "Nude"
yolk.equipOutfit(/datum/outfit/ashwalker)//this is an authentic mess we're making
yolk.update_body()
yolk.gib()
return ..()
/datum/outfit/ashwalker
name ="Ashwalker"
head = /obj/item/clothing/head/helmet/gladiator
uniform = /obj/item/clothing/under/costume/gladiator/ash_walker

View File

@@ -0,0 +1,101 @@
/datum/ghostrole/cybersun
abstract_type = /datum/ghostrole/cybersun
assigned_role = "Space Syndicate"
/datum/ghostrole/cybersun/ship
name = "Cybersun Ship Operative"
name = "Syndicate Battlecruiser Ship Operative"
desc = "You are a crewmember aboard the syndicate flagship: the SBC Starfury."
spawntext = "Your job is to follow your captain's orders, maintain the ship, and keep the engine running. If you are not familiar with how the supermatter engine functions: do not attempt to start it. \
<br>Furthermore, the armory is not a candy store, and your role is not to assault the station directly, leave that work to the assault operatives."
instantiator = /datum/ghostrole_instantiator/human/random/cybersun/ship
/datum/ghostrole/cybersun/assault
name = "Cybersun Assault Operative"
desc = "You are an assault operative aboard the syndicate flagship: the SBC Starfury."
spawntext = "Your job is to follow your captain's orders, keep intruders out of the ship, and assault Space Station 13. There is an armory, multiple assault ships, and beam cannons to attack the station with. \
<br>Work as a team with your fellow operatives and work out a plan of attack. If you are overwhelmed, escape back to your ship!"
instantiator = /datum/ghostrole_instantiator/human/random/cybersun/assault
/datum/ghostrole/cybersun/captain
name = "Cybersun Ship Captain"
desc = "You are the captain aboard the syndicate flagship: the SBC Starfury."
spawntext = "Your job is to oversee your crew, defend the ship, and destroy Space Station 13. The ship has an armory, multiple ships, beam cannons, and multiple crewmembers to accomplish this goal. \
<br>As the captain, this whole operation falls on your shoulders. You do not need to nuke the station, causing sufficient damage and preventing your ship from being destroyed will be enough."
instantiator = /datum/ghostrole_instantiator/human/random/cybersun/captain
/datum/ghostrole_instantiator/human/random/cybersun
/datum/ghostrole_instantiator/human/random/cybersun/Create(client/C, atom/location)
. = ..()
if(!.)
return
var/mob/M = .
M.faction |= ROLE_SYNDICATE
/datum/ghostrole_instantiator/human/random/cybersun/ship
equip_outfit = /datum/outfit/syndicate_empty/SBC
/datum/ghostrole_instantiator/human/random/cybersun/assault
equip_outfit = /datum/outfit/syndicate_empty/SBC/assault
/datum/ghostrole_instantiator/human/random/cybersun/captain
equip_outfit = /datum/outfit/syndicate_empty/SBC/assault/captain
/obj/structure/ghost_role_spawner/syndicate
name = "Syndicate Operative"
icon = 'icons/obj/machines/sleeper.dmi'
icon_state = "sleeper_s"
qdel_on_deplete = TRUE
role_type = null
/obj/structure/ghost_role_spawner/syndicate/battlecruiser
role_type = /datum/ghostrole/cybersun/ship
/obj/structure/ghost_role_spawner/syndicate/battlecruiser/assault
role_type = /datum/ghostrole/cybersun/assault
/obj/structure/ghost_role_spawner/syndicate/battlecruiser/captain
role_type = /datum/ghostrole/cybersun/captain
/datum/outfit/syndicate_empty
name = "Syndicate Operative Empty"
uniform = /obj/item/clothing/under/syndicate
shoes = /obj/item/clothing/shoes/combat
gloves = /obj/item/clothing/gloves/tackler/combat/insulated
ears = /obj/item/radio/headset/syndicate/alt
back = /obj/item/storage/backpack
implants = list(/obj/item/implant/weapons_auth)
id = /obj/item/card/id/syndicate
/datum/outfit/syndicate_empty/SBC
name = "Syndicate Battlecruiser Ship Operative"
l_pocket = /obj/item/gun/ballistic/automatic/pistol
r_pocket = /obj/item/kitchen/knife/combat/survival
belt = /obj/item/storage/belt/military/assault
/datum/outfit/syndicate_empty/SBC/assault
name = "Syndicate Battlecruiser Assault Operative"
uniform = /obj/item/clothing/under/syndicate/combat
l_pocket = /obj/item/ammo_box/magazine/m10mm
r_pocket = /obj/item/kitchen/knife/combat/survival
belt = /obj/item/storage/belt/military
suit = /obj/item/clothing/suit/armor/vest
suit_store = /obj/item/gun/ballistic/automatic/pistol
back = /obj/item/storage/backpack/security
mask = /obj/item/clothing/mask/gas/syndicate
/datum/outfit/syndicate_empty/SBC/assault/captain
name = "Syndicate Battlecruiser Captain"
l_pocket = /obj/item/melee/transforming/energy/sword/saber/red
r_pocket = /obj/item/melee/classic_baton/telescopic
suit = /obj/item/clothing/suit/armor/vest/capcarapace/syndicate
suit_store = /obj/item/gun/ballistic/revolver/mateba
back = /obj/item/storage/backpack/satchel/leather
head = /obj/item/clothing/head/HoS/syndicate
mask = /obj/item/clothing/mask/cigarette/cigar/havana
glasses = /obj/item/clothing/glasses/thermal/eyepatch
access_add = list(
ACCESS_SYNDICATE,
ACCESS_SYNDICATE_LEADER
)

View File

@@ -0,0 +1,68 @@
/datum/ghostrole/demonic_friend
name = "Demonic Friend"
desc = "You are someone's demonic friend from hell."
instantiator = /datum/ghostrole_instantiator/human/random/demonic_friend
assigned_role = "SuperFriend"
/datum/ghostrole/demonic_friend/PostInstantiate(mob/created, datum/component/ghostrole_spawnpoint/spawnpoint, list/params)
. = ..()
if(params["spell"])
var/obj/effect/proc_holder/spell/targeted/summon_friend/S = spawnpoint?.params["spell"]
S.friend = created
S.charge_counter = S.charge_max
if(!created.mind)
CRASH("No mind")
created.mind.hasSoul = FALSE
if(params["owner"])
var/datum/mind/owner = spawnpoint.params["owner"]
soullink(/datum/soullink/oneway, owner.current, created)
created.name = created.real_name
created.real_name = "[owner.name]'s best friend"
if(QDELETED(owner.current) || owner.current.stat == DEAD)
addtimer(CALLBACK(created, /mob/proc/dust), 15 SECONDS)
else
addtimer(CALLBACK(created, /mob/proc/dust), 15 SECONDS)
/datum/ghostrole/demonic_friend/Greet(mob/created, datum/component/ghostrole_spawnpoint/spawnpoint, list/params)
. = ..()
if(params["owner"])
var/datum/mind/owner = spawnpoint?.params["owner"]
to_chat(created, "You have been given a reprieve from your eternity of torment, to be [owner.name]'s friend for their short mortal coil.")
to_chat(created, "Be aware that if you do not live up to their expectations, they can send you back to hell with a single thought. [owner.name]'s death will also return you to hell.")
if(QDELETED(owner.current) || owner.current.stat == DEAD)
to_chat(created, span_danger("Your owner is already dead! You will soon perish."))
else
GiveCustomObjective(created, "Be [owner.name]'s friend, and keep them alive, so you don't get sent back to hell.")
else
to_chat(created, span_danger("Your owner is already dead! You will soon perish."))
/datum/ghostrole_instantiator/human/random/demonic_friend
equip_outfit = /datum/outfit/demonic_friend
/datum/ghostrole_instantiator/human/random/demonic_friend/Equip(client/C, mob/M, list/params)
. = ..()
var/mob/living/carbon/human/H = .
if(!istype(H))
return
var/obj/item/card/id/ID = H.wear_id?.GetID()
if(ID && params["owner"])
var/datum/mind/_M = params["owner"]
ID.registered_name = "[_M.name]'s best friend"
ID.update_label()
/obj/structure/ghost_role_spawner/demonic_friend
name = "Essence of friendship"
desc = "Oh boy! Oh boy! A friend!"
icon = 'icons/obj/cardboard_cutout.dmi'
icon_state = "cutout_basic"
role_type = /datum/ghostrole/demonic_friend
/datum/outfit/demonic_friend
name = "Demonic Friend"
uniform = /obj/item/clothing/under/misc/assistantformal
shoes = /obj/item/clothing/shoes/laceup
r_pocket = /obj/item/radio/off
back = /obj/item/storage/backpack
implants = list(/obj/item/implant/mindshield) //No revolutionaries, he's MY friend.
id = /obj/item/card/id
access_clone = /datum/job/assistant

View File

@@ -0,0 +1,80 @@
/datum/ghostrole/fugitive_hunter
name = "Fugitive Hunter"
desc = "Independent bounty hunters sent after fugitives"
instantiator = /datum/ghostrole_instantiator/human/random/fugitive_hunter
/datum/ghostrole/fugitive_hunter/PostInstantiate(mob/created, datum/component/ghostrole_spawnpoint/spawnpoint, list/params)
. = ..()
var/datum/antagonist/fugitive_hunter/fughunter = new
fughunter.backstory = params["bcakstory"]
created.mind.add_antag_datum(fughunter)
fughunter.greet()
message_admins("[ADMIN_LOOKUPFLW(created)] has been made into a Fugitive Hunter by an event.")
log_game("[key_name(created)] was spawned as a Fugitive Hunter by an event.")
/datum/ghostrole_instantiator/human/random/fugitive_hunter
/datum/ghostrole_instantiator/human/random/fugitive_hunter/GetOutfit(client/C, mob/M, list/params)
switch(params["outfit"])
if("spacepol")
return new /datum/outfit/spacepol
if("russian")
return new /datum/outfit/russiancorpse/hunter
if("bountyarmor")
return new /datum/outfit/bountyarmor
if("bountygrapple")
return new /datum/outfit/bountygrapple
if("bountyhook")
return new /datum/outfit/bountyhook
if("bountysynth")
return new /datum/outfit/bountysynth
return ..()
/obj/structure/ghost_role_spawner/fugitive_hunter
role_type = /datum/ghostrole/fugitive_hunter
var/backstory
var/outfit
/obj/structure/ghost_role_spawner/fugitive_hunter/Initialize(mapload, params, spawns)
return ..(mapload, list(
"backstory" = backstory,
"outfit" = outfit
))
/obj/structure/ghost_role_spawner/fugitive_hunter/spacepol
name = "police pod"
desc = "A small sleeper typically used to put people to sleep for briefing on the mission."
backstory = "space cop"
outfit = "spacepol"
icon = 'icons/obj/machines/sleeper.dmi'
icon_state = "sleeper"
/obj/structure/ghost_role_spawner/fugitive_hunter/russian
name = "russian pod"
desc = "A small sleeper typically used to make long distance travel a bit more bearable."
backstory = "russian"
outfit = "russian"
icon = 'icons/obj/machines/sleeper.dmi'
icon_state = "sleeper"
/obj/structure/ghost_role_spawner/fugitive_hunter/bounty
name = "bounty hunter pod"
desc = "A small sleeper typically used to make long distance travel a bit more bearable."
backstory = "bounty hunters"
outfit = "bountyaromr"
icon = 'icons/obj/machines/sleeper.dmi'
icon_state = "sleeper"
/obj/structure/ghost_role_spawner/fugitive_hunter/bounty/Destroy()
var/obj/structure/fluff/empty_sleeper/S = new(drop_location())
S.setDir(dir)
return ..()
/obj/structure/ghost_role_spawner/fugitive_hunter/bounty/armor
outfit = "bountyarmor"
/obj/structure/ghost_role_spawner/fugitive_hunter/bounty/hook
outfit = "bountyhook"
/obj/structure/ghost_role_spawner/fugitive_hunter/bounty/synth
outfit = "bountysynth"

View File

@@ -0,0 +1,167 @@
/datum/ghostrole/ghost_cafe
name = "Ghost Cafe Visitor"
assigned_role = "Ghost Cafe Visitor"
desc = "Off-station area for ghosts to roleplay in."
spawntext = "You know one thing for sure. You aren't actually alive! Are you in a simulation?"
jobban_role = ROLE_GHOSTCAFE
instantiator = /datum/ghostrole_instantiator/human/player_static/ghost_cafe
/obj/structure/ghost_role_spawner/ghost_cafe
name = "Ghost Cafe Sleeper"
icon = 'icons/obj/machines/sleeper.dmi'
icon_state = "sleeper"
role_type = /datum/ghostrole/ghost_cafe
role_spawns = INFINITY
/datum/action/toggle_dead_chat_mob
icon_icon = 'icons/mob/mob.dmi'
button_icon_state = "ghost"
name = "Toggle deadchat"
desc = "Turn off or on your ability to hear ghosts."
/datum/action/toggle_dead_chat_mob/Trigger()
if(!..())
return 0
var/mob/M = target
if(HAS_TRAIT_FROM(M,TRAIT_SIXTHSENSE,GHOSTROLE_TRAIT))
REMOVE_TRAIT(M,TRAIT_SIXTHSENSE,GHOSTROLE_TRAIT)
to_chat(M,"<span class='notice'>You're no longer hearing deadchat.</span>")
else
ADD_TRAIT(M,TRAIT_SIXTHSENSE,GHOSTROLE_TRAIT)
to_chat(M,"<span class='notice'>You're once again longer hearing deadchat.</span>")
/datum/action/disguise
name = "Disguise"
button_icon_state = "ling_transform"
icon_icon = 'icons/mob/actions/actions_changeling.dmi'
background_icon_state = "bg_mime"
var/currently_disguised = FALSE
var/static/list/mob_blacklist = typecacheof(list(
/mob/living/simple_animal/pet,
/mob/living/simple_animal/hostile/retaliate/goose,
/mob/living/simple_animal/hostile/poison,
/mob/living/simple_animal/hostile/retaliate/goat,
/mob/living/simple_animal/cow,
/mob/living/simple_animal/chick,
/mob/living/simple_animal/chicken,
/mob/living/simple_animal/kiwi,
/mob/living/simple_animal/babyKiwi,
/mob/living/simple_animal/deer,
/mob/living/simple_animal/parrot,
/mob/living/simple_animal/hostile/lizard,
/mob/living/simple_animal/crab,
/mob/living/simple_animal/cockroach,
/mob/living/simple_animal/butterfly,
/mob/living/simple_animal/mouse,
/mob/living/simple_animal/sloth,
/mob/living/simple_animal/opossum,
/mob/living/simple_animal/hostile/bear,
/mob/living/simple_animal/hostile/asteroid/polarbear,
/mob/living/simple_animal/hostile/asteroid/wolf,
/mob/living/carbon/monkey,
/mob/living/simple_animal/hostile/gorilla,
/mob/living/carbon/alien/larva,
/mob/living/simple_animal/hostile/retaliate/frog
))
/datum/action/disguise/Trigger()
var/mob/living/carbon/human/H = owner
if(!currently_disguised)
var/user_object_type = input(H, "Disguising as OBJECT or MOB?") as null|anything in list("OBJECT", "MOB")
if(user_object_type)
var/search_term = stripped_input(H, "Enter the search term")
if(search_term)
var/list_to_search
if(user_object_type == "MOB")
list_to_search = subtypesof(/mob) - mob_blacklist
else
list_to_search = subtypesof(/obj)
var/list/filtered_results = list()
for(var/some_search_item in list_to_search)
if(findtext("[some_search_item]", search_term))
filtered_results += some_search_item
if(!length(filtered_results))
to_chat(H, "Nothing matched your search query!")
else
var/disguise_selection = input("Select item to disguise as") as null|anything in filtered_results
if(disguise_selection)
var/atom/disguise_item = disguise_selection
var/image/I = image(icon = initial(disguise_item.icon), icon_state = initial(disguise_item.icon_state), loc = H)
I.override = TRUE
I.layer = ABOVE_MOB_LAYER
H.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/everyone, "ghost_cafe_disguise", I)
currently_disguised = TRUE
else
H.remove_alt_appearance("ghost_cafe_disguise")
currently_disguised = FALSE
/datum/ghostrole/ghost_cafe/Greet(mob/created, datum/component/ghostrole_spawnpoint/spawnpoint, list/params)
. = ..()
to_chat(created,"<span class='boldwarning'>Ghosting is free!</span>")
/datum/ghostrole_instantiator/human/player_static/ghost_cafe
equip_outfit = /datum/outfit/ghostcafe
mob_traits = list(
TRAIT_SIXTHSENSE,
TRAIT_EXEMPT_HEALTH_EVENTS,
TRAIT_NO_MIDROUND_ANTAG
)
/datum/ghostrole_instantiator/human/player_static/ghost_cafe/Create(client/C, atom/location, list/params)
. = ..()
var/mob/living/carbon/human/H = .
H.AddElement(/datum/element/ghost_role_eligibility, free_ghosting = TRUE)
H.AddElement(/datum/element/dusts_on_catatonia)
var/area/A = get_area(H)
H.AddElement(/datum/element/dusts_on_leaving_area,list(A.type,/area/hilbertshotel))
var/datum/action/toggle_dead_chat_mob/D = new(H)
D.Grant(H)
var/datum/action/disguise/disguise_action = new(H)
disguise_action.Grant(H)
/datum/outfit/ghostcafe
name = "ID, jumpsuit and shoes"
uniform = /obj/item/clothing/under/color/random
shoes = /obj/item/clothing/shoes/sneakers/black
id = /obj/item/card/id/no_banking
r_hand = /obj/item/storage/box/syndie_kit/chameleon/ghostcafe
/datum/outfit/ghostcafe/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE, client/preference_source)
..()
if (isplasmaman(H))
head = /obj/item/clothing/head/helmet/space/plasmaman
uniform = /obj/item/clothing/under/plasmaman
l_hand= /obj/item/tank/internals/plasmaman/belt/full
mask = /obj/item/clothing/mask/breath
return
var/suited = !preference_source || preference_source.prefs.jumpsuit_style == PREF_SUIT
if (CONFIG_GET(flag/grey_assistants))
uniform = suited ? /obj/item/clothing/under/color/grey : /obj/item/clothing/under/color/jumpskirt/grey
else
if(SSevents.holidays && SSevents.holidays[PRIDE_MONTH])
uniform = suited ? /obj/item/clothing/under/color/rainbow : /obj/item/clothing/under/color/jumpskirt/rainbow
else
uniform = suited ? /obj/item/clothing/under/color/random : /obj/item/clothing/under/color/jumpskirt/random
/datum/outfit/ghostcafe/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE, client/preference_source)
H.internal = H.get_item_for_held_index(1)
H.update_internals_hud_icon(1)
/obj/item/storage/box/syndie_kit/chameleon/ghostcafe
name = "ghost cafe costuming kit"
desc = "Look just the way you did in life - or better!"
/obj/item/storage/box/syndie_kit/chameleon/ghostcafe/PopulateContents() // Doesn't contain a PDA, for isolation reasons.
new /obj/item/clothing/under/chameleon(src)
new /obj/item/clothing/suit/chameleon(src)
new /obj/item/clothing/gloves/chameleon(src)
new /obj/item/clothing/shoes/chameleon(src)
new /obj/item/clothing/glasses/chameleon(src)
new /obj/item/clothing/head/chameleon(src)
new /obj/item/clothing/mask/chameleon(src)
new /obj/item/storage/backpack/chameleon(src)
new /obj/item/clothing/neck/cloak/chameleon(src)

View File

@@ -0,0 +1,123 @@
/datum/ghostrole/golem
instantiator = /datum/ghostrole_instantiator/human/random/species/golem
/datum/ghostrole/golem/Greet(mob/created, datum/component/ghostrole_spawnpoint/spawnpoint, list/params)
. = ..()
var/mob/living/carbon/human/H = created
if(!istype(H))
return
var/datum/species/golem/G = H.dna.species
if(!istype(G))
return
to_chat(created, G.info_text)
/datum/ghostrole/golem/free
name = "Free Golem"
desc = "You are a Free Golem. Your family worships The Liberator."
spawntext = "In his infinite and divine wisdom, he set your clan free to \
travel the stars with a single declaration: \"Yeah go do whatever.\" Though you are bound to the one who created you, it is customary in your society to repeat those same words to newborn \
golems, so that no golem may ever be forced to serve again."
/datum/ghostrole/golem/free/Greet(mob/created, datum/component/ghostrole_spawnpoint/spawnpoint, list/params)
. = ..()
to_chat(created, span_boldwarning("Build golem shells in the autolathe, and feed refined mineral sheets to the shells to bring them to life! You are generally a peaceful group unless provoked."))
/datum/ghostrole/golem/servant
name = "Servant Golem"
desc = "You are a golem."
spawntext = "You move slowly, but are highly resistant to heat and cold as well as blunt trauma. You are unable to wear clothes, but can still use most tools."
inject_params = list(
"servant" = TRUE
)
/datum/ghostrole/golem/servant/PostInstantiate(mob/created, datum/component/ghostrole_spawnpoint/spawnpoint, list/params)
. = ..()
var/datum/mind/creator_mind = params["creator"]
if(!creator_mind)
return
var/creator_name = creator_mind.name
log_game("[key_name(created)] possessed a golem shell enslaved to [creator_mind.name]/[creator_mind.key].")
log_admin("[key_name(created)] possessed a golem shell enslaved to [creator_mind.name]/[creator_mind.key].")
created.mind.store_memory( "Serve [creator_name][creator_mind.current && " (currently [creator_mind.current.name])"], and assist them in completing their goals at any cost.")
to_chat(created, span_boldwarning("Serve [creator_name][creator_mind.current && " (currently [creator_mind.current.name])"], and assist them in completing their goals at any cost."))
var/mob/living/carbon/human/H = created
var/datum/species/golem/G = H.dna.species
G.owner = creator_mind
/datum/ghostrole_instantiator/human/random/species/golem
/datum/ghostrole_instantiator/human/random/species/golem/GetSpeciesPath(mob/living/carbon/human/H, list/params)
var/predestined = params["species"]
if(istext(predestined))
predestined = text2path(predestined)
if(!ispath(predestined, /datum/species/golem))
return pick(typesof(/datum/species/golem))
return predestined
/datum/ghostrole_instantiator/human/random/species/golem/Randomize(mob/living/carbon/human/H, list/params)
. = ..()
H.set_cloned_appearance()
var/datum/species/golem/G = H.dna.species
H.real_name = params["name"] || (params["servant"]? "[initial(G.prefix)] Golem ([rand(1,999)])" : H.dna.species.random_name())
//Golem shells: Spawns in Free Golem ships in lavaland. Ghosts become mineral golems and are advised to spread personal freedom.
/obj/structure/ghost_role_spawner/golem
name = "inert free golem shell"
desc = "A humanoid shape, empty, lifeless, and full of potential."
icon = 'icons/obj/wizard.dmi'
icon_state = "construct"
anchored = FALSE
move_resist = MOVE_FORCE_NORMAL
density = FALSE
/// can we possibly have an owner
var/has_owner = FALSE
/// can golems switch bodies to this shell
var/can_transfer = TRUE
/// override golem species?
var/golem_species_override
/obj/structure/ghost_role_spawner/golem/Initialize(mapload, datum/species/golem/species, mob/creator)
if(golem_species_override)
species = golem_species_override
if(species) //spawners list uses object name to register so this goes before ..()
name += " ([initial(species.prefix)])"
golem_species_override = species // yes semi-circular-reference sue me
return ..(mapload, list(
"species" = species,
"creator" = creator && (istype(creator, /datum/mind)? creator : creator.mind)
), (has_owner && creator)? /datum/ghostrole/golem/servant : /datum/ghostrole/golem/free)
/obj/structure/ghost_role_spawner/golem/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(isgolem(user) && can_transfer)
// this is a bit special
// we want them to keep their mind, so....
var/datum/ghostrole/G = get_ghostrole_datum(/datum/ghostrole/golem/free)
if(!G?.instantiator)
CRASH("Couldn't locate freegolem instantiator")
var/datum/ghostrole_instantiator/I = G.instantiator
var/transfer_choice = alert("Transfer your soul to [src]? (Warning, your old body will die!)",,"Yes","No")
if(transfer_choice != "Yes" || QDELETED(src) || !user.canUseTopic(src, BE_CLOSE, NO_DEXTERY, NO_TK))
return
log_game("[key_name(user)] golem-swapped into [src]")
user.visible_message("<span class='notice'>A faint light leaves [user], moving to [src] and animating it!</span>","<span class='notice'>You leave your old body behind, and transfer into [src]!</span>")
var/mob/living/created = I.Run(user.client, loc, list(
"species" = golem_species_override,
"name" = user.real_name
))
if(!created)
CRASH("Couldn't make a valid golem, cancelling.")
user.mind.transfer_to(created)
user.death()
qdel(src)
return
return ..()
/obj/structure/ghost_role_spawner/golem/servant
has_owner = TRUE
name = "inert servant golem shell"
/obj/structure/ghost_role_spawner/golem/adamantine
name = "dust-caked free golem shell"
desc = "A humanoid shape, empty, lifeless, and full of potential."
can_transfer = FALSE
golem_species_override = /datum/species/golem/adamantine

View File

@@ -0,0 +1,82 @@
/datum/ghostrole/hermit
name = "Space Hermit"
assigned_role = "Hermit"
desc = "A stranded cryo-occupant in deep space."
spawntext = "You've been late to awaken from your cryo slumber. Blasted machine, you set it to 10 days not 10 weeks!</span><b> Where have the others gone while we were out? Did they manage to survive?"
instantiator = /datum/ghostrole_instantiator/human/random/hermit
/datum/ghostrole/hermit/Instantiate(client/C, atom/loc, list/params)
var/rp = rand(1, 4)
switch(rp)
if(1)
params["fluff"] = "proper"
if(2)
params["fluff"] = "tiger"
if(3)
params["fluff"] = "exile"
if(4)
params["fluff"] = "tourist"
return ..()
/datum/ghostrole/hermit/Greet(mob/created, datum/component/ghostrole_spawnpoint/spawnpoint, list/params)
. = ..()
var/flavour_text = "Each day you barely scrape by, and between the terrible conditions of your makeshift shelter, \
the hostile creatures, and the ash drakes swooping down from the cloudless skies, all you can wish for is the feel of soft grass between your toes and \
the fresh air of Earth. These thoughts are dispelled by yet another recollection of how you got here... "
switch(params["fluff"])
if("proper")
flavour_text += "you were a [pick("arms dealer", "shipwright", "docking manager")]'s assistant on a small trading station several sectors from here. Raiders attacked, and there was \
only one pod left when you got to the escape bay. You took it and launched it alone, and the crowd of terrified faces crowding at the airlock door as your pod's engines burst to \
life and sent you to this hell are forever branded into your memory."
if("tiger")
flavour_text += "you're an exile from the Tiger Cooperative. Their technological fanaticism drove you to question the power and beliefs of the Exolitics, and they saw you as a \
heretic and subjected you to hours of horrible torture. You were hours away from execution when a high-ranking friend of yours in the Cooperative managed to secure you a pod, \
scrambled its destination's coordinates, and launched it. You awoke from stasis when you landed and have been surviving - barely - ever since."
if("exile")
flavour_text += "you were a doctor on one of Nanotrasen's space stations, but you left behind that damn corporation's tyranny and everything it stood for. From a metaphorical hell \
to a literal one, you find yourself nonetheless missing the recycled air and warm floors of what you left behind... but you'd still rather be here than there."
if("tourist")
flavour_text += "you were always joked about by your friends for \"not playing with a full deck\", as they so <i>kindly</i> put it. It seems that they were right when you, on a tour \
at one of Nanotrasen's state-of-the-art research facilities, were in one of the escape pods alone and saw the red button. It was big and shiny, and it caught your eye. You pressed \
it, and after a terrifying and fast ride for days, you landed here. You've had time to wisen up since then, and you think that your old friends wouldn't be laughing now."
to_chat(created, flavour_text)
/datum/ghostrole_instantiator/human/random/hermit
// mob_traits = list(
// TRAIT_EXEMPT_HEALTH_EVENTS
// )
/datum/ghostrole_instantiator/human/random/hermit/GetOutfit(client/C, mob/M, list/params)
var/datum/outfit/outfit = ..()
switch(params["fluff"])
if("proper")
outfit.uniform = /obj/item/clothing/under/assistantformal
outfit.shoes = /obj/item/clothing/shoes/black
outfit.back = /obj/item/storage/backpack
if("tiger")
outfit.uniform = /obj/item/clothing/under/color/prison
outfit.shoes = /obj/item/clothing/shoes/orange
outfit.back = /obj/item/storage/backpack
if("exile")
outfit.uniform = /obj/item/clothing/under/rank/medical
outfit.suit = /obj/item/clothing/suit/toggle/labcoat/paramedic
outfit.back = /obj/item/storage/backpack/medic
outfit.shoes = /obj/item/clothing/shoes/black
if("tourist")
outfit.uniform = /obj/item/clothing/under/color/grey
outfit.shoes = /obj/item/clothing/shoes/black
outfit.back = /obj/item/storage/backpack
return outfit
//Malfunctioning cryostasis sleepers: Spawns in makeshift shelters in lavaland. Ghosts become hermits with knowledge of how they got to where they are now.
/obj/structure/ghost_role_spawner/hermit
name = "malfunctioning cryostasis sleeper"
desc = "A humming sleeper with a silhouetted occupant inside. Its stasis function is broken and it's likely being used as a bed."
icon = 'icons/obj/spawners.dmi'
icon_state = "cryostasis_sleeper"
role_type = /datum/ghostrole/hermit
qdel_on_deplete = TRUE
/obj/structure/ghost_role_spawner/hermit/Destroy()
// new /obj/structure/fluff/empty_cryostasis_sleeper(get_turf(src))
return ..()

View File

@@ -0,0 +1,56 @@
/datum/ghostrole/space_hotel
instantiator = /datum/ghostrole_instantiator/human/random/space_hotel
name = "Space Hotel Staff"
desc = "You are a staff member of a top-of-the-line space hotel! Cater to guests and make sure the manager doesn't fire you."
automatic_objective = "You are a staff member of a top-of-the-line space hotel! Cater to guests and make sure the manager doesn't fire you."
assigned_role = "Hotel Staff"
/datum/ghostrole/space_hotel/security
instantiator = /datum/ghostrole_instantiator/human/random/space_hotel/security
name = "Space Hotel Security"
desc = "You have been assigned to this hotel to protect the interests of the company while keeping the peace between \
guests and the staff."
automatic_objective = "Do not leave your assigned hotel. Try and keep the peace between staff and guests, non-lethal force heavily advised if possible."
/datum/ghostrole_instantiator/human/random/space_hotel
equip_outfit = /datum/outfit/hotelstaff
mob_traits = list(
TRAIT_EXEMPT_HEALTH_EVENTS
)
/datum/ghostrole_instantiator/human/random/space_hotel/security
equip_outfit = /datum/outfit/hotelstaff/security
//Space Hotel Staff
/obj/structure/ghost_role_spawner/space_hotel //not free antag u little shits
name = "staff sleeper"
desc = "A sleeper designed for long-term stasis between guest visits."
role_type = /datum/ghostrole/space_hotel
icon = 'icons/obj/machines/sleeper.dmi'
icon_state = "sleeper_s"
/obj/structure/ghost_role_spawner/space_hotel/Destroy()
new/obj/structure/fluff/empty_sleeper/syndicate(get_turf(src))
return ..()
/obj/structure/ghost_role_spawner/space_hotel/security
name = "hotel security sleeper"
role_type = /datum/ghostrole/space_hotel/security
/datum/outfit/hotelstaff
name = "Hotel Staff"
uniform = /obj/item/clothing/under/suit/telegram
shoes = /obj/item/clothing/shoes/laceup
head = /obj/item/clothing/head/hotel
r_pocket = /obj/item/radio/off
back = /obj/item/storage/backpack
implants = list(/obj/item/implant/mindshield)
/datum/outfit/hotelstaff/security
name = "Hotel Secuirty"
uniform = /obj/item/clothing/under/rank/security/officer/blueshirt
shoes = /obj/item/clothing/shoes/jackboots
suit = /obj/item/clothing/suit/armor/vest/blueshirt
head = /obj/item/clothing/head/helmet/blueshirt
back = /obj/item/storage/backpack/security
belt = /obj/item/storage/belt/security/full

View File

@@ -0,0 +1,38 @@
/datum/ghostrole/seed_vault
name = "Lifebringer"
desc = "You are a sentient ecosystem, an example of the mastery over life that your creators possessed."
spawntext = "Your masters, benevolent as they were, created uncounted seed vaults and spread them across \
the universe to every planet they could chart. You are in one such seed vault. \
Your goal is to cultivate and spread life wherever it will go while waiting for contact from your creators. \
Estimated time of last contact: Deployment, 5000 millennia ago."
assigned_role = "Lifebringer"
instantiator = /datum/ghostrole_instantiator/human/random/seed_vault
/datum/ghostrole_instantiator/human/random/seed_vault
mob_traits = list(
TRAIT_EXEMPT_HEALTH_EVENTS
)
/datum/ghostrole_instantiator/human/random/seed_vault/Randomize(mob/living/carbon/human/H, list/params)
. = ..()
H.set_species(new /datum/species/pod)
var/plant_name = pick("Tomato", "Potato", "Broccoli", "Carrot", "Ambrosia", "Pumpkin", "Ivy", "Kudzu", "Banana", "Moss", "Flower", "Bloom", "Root", "Bark", "Glowshroom", "Petal", "Leaf", \
"Venus", "Sprout","Cocoa", "Strawberry", "Citrus", "Oak", "Cactus", "Pepper", "Juniper")
H.real_name = plant_name //why this works when moving it from one function to another is beyond me
H.underwear = "Nude" //You're a plant, partner
H.undershirt = "Nude" //changing underwear/shirt/socks doesn't seem to function correctly right now because of some bug elsewhere?
H.socks = "Nude"
H.update_body(TRUE)
H.language_holder.selected_language = /datum/language/sylvan
//Preserved terrarium/seed vault: Spawns in seed vault structures in lavaland. Ghosts become plantpeople and are advised to begin growing plants in the room near them.
/obj/structure/ghost_role_spawner/seed_vault
name = "preserved terrarium"
desc = "An ancient machine that seems to be used for storing plant matter. The glass is obstructed by a mat of vines."
icon = 'icons/obj/lavaland/spawners.dmi'
icon_state = "terrarium"
role_type = /datum/ghostrole/seed_vault
/obj/structure/ghost_role_spawner/seed_vault/Destroy()
new/obj/structure/fluff/empty_terrarium(get_turf(src))
return ..()

View File

@@ -0,0 +1,61 @@
/datum/ghostrole/old_research
name = "Oldstation Crew"
allow_pick_spawner = TRUE
desc = "You were a Nanotrasen employee from an era past, stationed upon a state of the art research station. \
You vaguely recall rushing into a cryogenics pod due to an oncoming radiation storm. \
The last thing you remember is the station's Artificial Program telling you that you would only be asleep for eight hours. As you open \
your eyes, everything seems rusted and broken, a dark feeling swells in your gut as you climb out of your pod."
automatic_objective = "Work as a team with your fellow survivors and do not abandon them."
assigned_role = "Ancient Crew"
instantiator = /datum/ghostrole_instantiator/human/random/old_research
/datum/ghostrole_instantiator/human/random/old_research/GetOutfit(client/C, mob/M, list/params)
. = ..()
switch(params["role"])
if("Officer")
return /datum/outfit/old_research/security
if("Engineer")
return /datum/outfit/old_research/engineer
if("Scientist")
return /datum/outfit/old_research/scientist
/obj/structure/ghost_role_spawner/old_research
role_type = /datum/ghostrole/old_research
name = "old cryogenics pod"
desc = "A humming cryo pod. You can barely recognise a security uniform underneath the built up ice. The machine is attempting to wake up its occupant."
icon = 'icons/obj/machines/sleeper.dmi'
icon_state = "sleeper"
/obj/structure/ghost_role_spawner/old_research/Destroy()
new /obj/structure/showcase/machinery/oldpod/used(drop_location())
return ..()
/obj/structure/ghost_role_spawner/old_research/security
role_params = list("role" = "Officer")
/obj/structure/ghost_role_spawner/old_research/engineer
role_params = list("role" = "Engineer")
/obj/structure/ghost_role_spawner/old_research/scientist
role_params = list("role" = "Scientist")
/datum/outfit/old_research/security
uniform = /obj/item/clothing/under/rank/security/officer
shoes = /obj/item/clothing/shoes/jackboots
id = /obj/item/card/id/away/old/sec
r_pocket = /obj/item/restraints/handcuffs
l_pocket = /obj/item/assembly/flash/handheld
/datum/outfit/old_research/engineer
uniform = /obj/item/clothing/under/rank/engineering/engineer
shoes = /obj/item/clothing/shoes/workboots
id = /obj/item/card/id/away/old/eng
gloves = /obj/item/clothing/gloves/color/fyellow/old
l_pocket = /obj/item/tank/internals/emergency_oxygen
/datum/outfit/old_research/scientist
uniform = /obj/item/clothing/under/rank/rnd/scientist
shoes = /obj/item/clothing/shoes/laceup
id = /obj/item/card/id/away/old/sci
l_pocket = /obj/item/stack/medical/suture

View File

@@ -0,0 +1,103 @@
/datum/ghostrole/pirate
name = "Space Pirate"
desc = "The station refused to pay for your protection, protect the ship, siphon the credits from the station and raid it for even more loot."
instantiator = /datum/ghostrole_instantiator/human/random/species/pirate
/datum/ghostrole_instantiator/human/random/species/pirate
possible_species = list(
/datum/species/skeleton/space
)
equip_outfit = /datum/outfit/pirate/space
/datum/ghostrole_instantiator/human/random/species/pirate/GetOutfit(client/C, mob/M, list/params)
. = ..()
switch(params["rank"])
if("Mate", "Gunner")
return /datum/outfit/pirate/space
if("Captain")
return /datum/outfit/pirate/space/captain
/datum/ghostrole_instantiator/human/random/species/pirate/Randomize(mob/living/carbon/human/H, list/params)
. = ..()
H.fully_replace_character_name(H.real_name,generate_pirate_name(params["rank"]))
/datum/ghostrole/pirate/PostInstantiate(mob/created, datum/component/ghostrole_spawnpoint/spawnpoint, list/params)
. = ..()
created.mind.add_antag_datum(/datum/antagonist/pirate)
/proc/generate_pirate_name(rank)
var/beggings = strings(PIRATE_NAMES_FILE, "beginnings")
var/endings = strings(PIRATE_NAMES_FILE, "endings")
return "[rank] [pick(beggings)][pick(endings)]"
/obj/structure/ghost_role_spawner/pirate
name = "space pirate sleeper"
desc = "A cryo sleeper smelling faintly of rum. The sleeper looks unstable. <i>Perhaps the pirate within can be killed with the right tools...</i>"
icon = 'icons/obj/machines/sleeper.dmi'
icon_state = "sleeper"
role_type = /datum/ghostrole/pirate
role_params = list(
"rank" = "Mate"
)
/obj/structure/ghost_role_spawner/pirate/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
. = ..()
if(.)
return
if(user.mind.has_antag_datum(/datum/antagonist/pirate))
to_chat(user, "<span class='notice'>Your shipmate sails within their dreams for now. Perhaps they may wake up eventually.</span>")
else
to_chat(user, "<span class='notice'>If you want to kill the pirate off, something to pry open the sleeper might be the best way to do it.</span>")
/obj/structure/ghost_role_spawner/pirate/attackby(obj/item/W, mob/user, params)
if(W.tool_behaviour == TOOL_CROWBAR && user.a_intent != INTENT_HARM)
if(user.mind.has_antag_datum(/datum/antagonist/pirate))
to_chat(user,"<span class='warning'>Why would you want to do that to your shipmate? That'd kill them.</span>")
return
user.visible_message("<span class='warning'>[user] start to pry open [src]...</span>",
"<span class='notice'>You start to pry open [src]...</span>",
"<span class='italics'>You hear prying...</span>")
W.play_tool_sound(src)
if(do_after(user, 100*W.toolspeed, target = src))
user.visible_message("<span class='warning'>[user] pries open [src], disrupting the sleep of the pirate within and killing them.</span>",
"<span class='notice'>You pry open [src], disrupting the sleep of the pirate within and killing them.</span>",
"<span class='italics'>You hear prying, followed by the death rattling of bones.</span>")
log_game("[key_name(user)] has successfully pried open [src] and disabled a space pirate spawner.")
W.play_tool_sound(src)
playsound(src.loc, 'modular_citadel/sound/voice/scream_skeleton.ogg', 50, 1, 4, 1.2)
if(role_params["rank"] == "Captain")
new /obj/effect/mob_spawn/human/pirate/corpse/captain(get_turf(src))
else
new /obj/effect/mob_spawn/human/pirate/corpse(get_turf(src))
qdel(src)
else
..()
/obj/effect/mob_spawn/human/pirate
mob_species = /datum/species/skeleton/space
outfit = /datum/outfit/pirate/space
/obj/effect/mob_spawn/human/pirate/corpse //occurs when someone pries a pirate out of their sleeper.
mob_name = "Dead Space Pirate"
death = TRUE
instant = TRUE
random = FALSE
/obj/effect/mob_spawn/human/pirate/corpse/captain
mob_name = "Dead Space Pirate Captain"
outfit = /datum/outfit/pirate/space/captain
/obj/structure/ghost_role_spawner/pirate/Destroy()
new /obj/structure/showcase/machinery/oldpod/used(drop_location())
return ..()
/obj/structure/ghost_role_spawner/pirate/captain
role_params = list(
"rank" = "Captain"
)
/obj/structure/ghost_role_spawner/pirate/gunner
role_params = list(
"rank" = "Gunner"
)

View File

@@ -0,0 +1,39 @@
/datum/ghostrole/lavaland_prisoner
name = "Lavaland Prisoner"
desc = "You're a prisoner, sentenced to hard work in one of Nanotrasen's labor camps, but it seems as though fate has other plans for you."
instantiator = /datum/ghostrole_instantiator/human/random/lavaland_prisoner
assigned_role = "Escaped Prisoner"
/datum/ghostrole/lavaland_prisoner/Greet(mob/created, datum/component/ghostrole_spawnpoint/spawnpoint)
. = ..()
var/list/crimes = list("murder", "larceny", "embezzlement", "unionization", "dereliction of duty", "kidnapping", "gross incompetence", "grand theft", "collaboration with the Syndicate", \
"worship of a forbidden deity", "interspecies relations", "mutiny")
to_chat(created, span_danger("Good. It seems as though your ship crashed. You remember that you were convicted of [pick(crimes)]. but regardless of that, it seems like your crime doesn't matter now. You don't know where you are, but you know that it's out to kill you, and you're not going \
to lose this opportunity. Find a way to get out of this mess and back to where you rightfully belong - your [pick("house", "apartment", "spaceship", "station")]."))
/datum/ghostrole_instantiator/human/random/lavaland_prisoner
equip_outfit = /datum/outfit/lavalandprisoner
/datum/ghostrole_instantiator/human/random/lavaland_prisoner/Randomize(mob/living/carbon/human/H, list/params)
. = ..()
H.real_name = "NTP #LL-0[rand(111,999)]" //Nanotrasen Prisoner #Lavaland-(numbers)
H.name = H.real_name
//Prisoner containment sleeper: Spawns in crashed prison ships in lavaland. Ghosts become escaped prisoners and are advised to find a way out of the mess they've gotten themselves into.
/obj/structure/ghost_role_spawner/prisoner_transport
name = "prisoner containment sleeper"
desc = "A sleeper designed to put its occupant into a deep coma, unbreakable until the sleeper turns off. This one's glass is cracked and you can see a pale, sleeping face staring out."
icon = 'icons/obj/machines/sleeper.dmi'
icon_state = "sleeper_s"
role_type = /datum/ghostrole/lavaland_prisoner
/datum/outfit/lavalandprisoner
name = "Lavaland Prisoner"
uniform = /obj/item/clothing/under/rank/prisoner
mask = /obj/item/clothing/mask/breath
shoes = /obj/item/clothing/shoes/sneakers/orange
r_pocket = /obj/item/tank/internals/emergency_oxygen
/obj/structure/ghost_role_spawner/prisoner_transport/Destroy()
new/obj/structure/fluff/empty_sleeper/syndicate(get_turf(src))
return ..()

View File

@@ -0,0 +1,40 @@
/datum/ghostrole/timeless_prison
name = "Timeless Prisoner"
desc = "Years ago, you sacrificed the lives of your trusted friends and the humanity of yourself to reach the Wish Granter. Though you \
did so, it has come at a cost: your very body rejects the light, dooming you to wander endlessly in this horrible wasteland."
instantiator = /datum/ghostrole_instantiator/human/random/species/shadow
assigned_role = "Exile"
/datum/ghostrole/timeless_prison/Greet(mob/created, datum/component/ghostrole_spawnpoint/spawnpoint, list/params)
. = ..()
var/wish = rand(1,4)
switch(wish)
if(1)
to_chat(created, "<b>You wished to kill, and kill you did. You've lost track of how many, but the spark of excitement that murder once held has winked out. You feel only regret.</b>")
if(2)
to_chat(created, "<b>You wished for unending wealth, but no amount of money was worth this existence. Maybe charity might redeem your soul?</b>")
if(3)
to_chat(created, "<b>You wished for power. Little good it did you, cast out of the light. You are the [created.gender == MALE ? "king" : "queen"] of a hell that holds no subjects. You feel only remorse.</b>")
if(4)
to_chat(created, "<b>You wished for immortality, even as your friends lay dying behind you. No matter how many times you cast yourself into the lava, you awaken in this room again within a few days. There is no escape.</b>")
/datum/ghostrole_instantiator/human/random/species/shadow
possible_species = list(
/datum/species/shadow
)
/datum/ghostrole_instantiator/human/random/species/shadow/timeless_prison/Randomize(mob/living/carbon/human/H, list/params)
. = ..()
H.real_name = "Wish Granter's Victim ([rand(1,999)])"
//Timeless prisons: Spawns in Wish Granter prisons in lavaland. Ghosts become age-old users of the Wish Granter and are advised to seek repentance for their past.
/obj/structure/ghost_role_spawner/exile
name = "timeless prison"
desc = "Although this stasis pod looks medicinal, it seems as though it's meant to preserve something for a very long time."
icon = 'icons/obj/machines/sleeper.dmi'
icon_state = "sleeper"
role_type = /datum/ghostrole/timeless_prison
/obj/structure/ghost_role_spawner/exile/Destroy()
new/obj/structure/fluff/empty_sleeper(get_turf(src))
return ..()

View File

@@ -0,0 +1,18 @@
/datum/ghostrole/lavaland_vet
name = "Lavaland Vet"
desc = "You are a animal doctor who just woke up in lavaland"
assigned_role = "Translocated Vet"
spawntext = "What...? Where are you? Where are the others? This is still the animal hospital - you should know, you've been an intern here for weeks - but \
you see them right now. So where is \
everyone? Where did they go? What happened to the hospital? And is that <i>smoke</i> you smell? You need to find someone else. Maybe they c everyone's gone. One of the cats scratched you just a few minutes ago. That's why you were in the pod - to heal the scratch. The scabs are still fresh; an tell you what happened."
//Broken rejuvenation pod: Spawns in animal hospitals in lavaland. Ghosts become disoriented interns and are advised to search for help.
/obj/structure/ghost_role_spawner/lavaland_vet
name = "broken rejuvenation pod"
desc = "A small sleeper typically used to instantly restore minor wounds. This one seems broken, and its occupant is comatose."
role_type = /datum/ghostrole/lavaland_vet
/obj/structure/ghost_role_spawner/lavaland_vet/Destroy()
var/obj/structure/fluff/empty_sleeper/S = new(drop_location())
S.setDir(dir)
return ..()

View File

@@ -0,0 +1,97 @@
/**
* Default implementation for ghost role spawners
*/
/obj/structure/ghost_role_spawner
name = "Ghost Role Spawner"
desc = "if you're seeing this a coder fucked up"
// TODO: atom damage
// resistance_flags = INDESTRUCTIBLE
unacidable = TRUE
density = TRUE
icon = 'icons/obj/spawners.dmi'
icon_state = "cryostasis_sleeper"
/// automatic handling - role type
var/role_type
/// automatic handling - allowed spawn count
var/role_spawns = 1
/// automatic handling - params. If this is a string at Init, it'll be json_decoded.
var/list/role_params
/// automatic handling - qdel on running out
var/qdel_on_deplete = FALSE
/// automatic handling - special spawntext
var/special_spawntext
/obj/structure/ghost_role_spawner/Initialize(mapload, params, type, spawns, spawntext)
. = ..()
if(type)
src.role_type = type
if(params)
role_params = params
else if(istext(role_params))
role_params = json_decode(role_params)
if(spawns)
role_spawns = spawns
if(spawntext)
special_spawntext = spawntext
if(!src.role_type)
if(mapload)
stack_trace("No role type")
else
AddComponent(/datum/component/ghostrole_spawnpoint, role_type, role_spawns, role_params, /obj/structure/ghost_role_spawner/proc/on_spawn, null, special_spawntext)
/obj/structure/ghost_role_spawner/proc/on_spawn(mob/created, datum/ghostrole/role, list/params, datum/component/ghostrole_spawnpoint/spawnpoint)
if(qdel_on_deplete && !spawnpoint.SpawnsLeft())
qdel(src)
/**
* for mappers
*/
/obj/structure/ghost_role_spawner/custom
/// json to convert to params
var/role_params_json
/// next id
var/static/id_next = 0
/// forced id
var/forced_id
/// mob path
var/mob_path = /mob/living/carbon/human
/// forced instantiator path
var/instantiator_path
/obj/structure/ghost_role_spawner/custom/Initialize(mapload, params, type, spawns, spawntext)
role_type = "[forced_id]" || "[++id_next]"
GenerateRole(role_type, mob_path)
role_params = GenerateParams()
return ..()
/obj/structure/ghost_role_spawner/custom/proc/GenerateRole(id = role_type, mob_path = src.mob_path)
var/datum/ghostrole/G = get_ghostrole_datum(id)
if(G)
return
G = new(id)
if(instantiator_path)
G.instantiator = new instantiator_path
else
G.instantiator = ispath(mob_path, /mob/living/carbon/human)? new /datum/ghostrole_instantiator/human/random/species : new /datum/ghostrole_instantiator/simple
return G
/obj/structure/ghost_role_spawner/custom/proc/GenerateParams()
. = list()
if(istext(role_params_json))
var/list/L = safe_json_decode(role_params_json)
if(L)
. = L
.["mob"] = mob_path
/obj/structure/ghost_role_spawner/custom/human
mob_path = /mob/living/carbon/human
/// outfit path
var/outfit_path = /datum/outfit/job/assistant
/// species path
var/species_path = /datum/species/human
/obj/structure/ghost_role_spawner/custom/human/GenerateParams()
. = ..()
.["outfit"] = outfit_path
.["species"] = species_path

View File

@@ -0,0 +1,114 @@
GLOBAL_LIST_EMPTY(ghostrole_spawnpoints)
/**
* Spawnpoint for a ghostrole
*/
/datum/component/ghostrole_spawnpoint
dupe_mode = COMPONENT_DUPE_ALLOWED
can_transfer = TRUE
/// allowed spawns
var/max_spawns
/// current spawns
var/spawns = 0
/// role type
var/role_type
/// params list
var/list/params
/// callback or proc type
var/datum/callback/proc_to_call_or_callback
/// custom HTML spawntext to show, if any
var/spawntext
/datum/component/ghostrole_spawnpoint/Initialize(role_type, allowed_spawns = INFINITY, list/params, datum/callback/proc_to_call_or_callback, notify_ghosts = TRUE, spawntext)
if((. = ..()) & COMPONENT_INCOMPATIBLE)
return
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
max_spawns = allowed_spawns
src.role_type = role_type
src.params = params
src.spawntext = spawntext
src.proc_to_call_or_callback = proc_to_call_or_callback
if(notify_ghosts)
var/datum/ghostrole/role = get_ghostrole_datum(role_type)
if(!role)
return
notify_ghosts("Ghostrole spawner created: [role.name] - [parent] - [get_area(parent)]", source = parent, ignore_mapload = TRUE, flashwindow = FALSE)
/datum/component/ghostrole_spawnpoint/RegisterWithParent()
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
. = ..()
RegisterSignal(parent, COMSIG_ATOM_ATTACK_GHOST, .proc/GhostInteract)
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/Examine)
RegisterGlobal()
/datum/component/ghostrole_spawnpoint/UnregisterFromParent()
. = ..()
UnregisterGlobal()
/datum/component/ghostrole_spawnpoint/PostTransfer()
if(ispath(proc_to_call_or_callback) && !hascall(parent, proc_to_call_or_callback))
stack_trace("[src] [parent] had a proc to call, [proc_to_call_or_callback], that is no longer valid after a component transfer. It will be removed. Please fix this!")
proc_to_call_or_callback = null
/datum/component/ghostrole_spawnpoint/proc/RegisterGlobal()
if(!islist(GLOB.ghostrole_spawnpoints[role_type]))
GLOB.ghostrole_spawnpoints[role_type] = list(src)
else
GLOB.ghostrole_spawnpoints[role_type] |= src
/datum/component/ghostrole_spawnpoint/proc/UnregisterGlobal()
GLOB.ghostrole_spawnpoints[role_type] -= src
/datum/component/ghostrole_spawnpoint/proc/Atom()
RETURN_TYPE(/atom)
return parent
/datum/component/ghostrole_spawnpoint/proc/Turf()
RETURN_TYPE(/turf)
return Atom().loc
/datum/component/ghostrole_spawnpoint/proc/SpawnsLeft(client/C)
return max(0, max_spawns - spawns)
/datum/component/ghostrole_spawnpoint/proc/OnSpawn(mob/created, datum/ghostrole/role)
if(istype(proc_to_call_or_callback))
proc_to_call_or_callback.Invoke(created, role, params, src)
spawns++
if(ispath(proc_to_call_or_callback))
if(!hascall(parent, proc_to_call_or_callback))
CRASH("Invalid proc [proc_to_call_or_callback] on [parent]")
call(parent, proc_to_call_or_callback)(created, role, params, src)
/datum/component/ghostrole_spawnpoint/vv_edit_var(var_name, var_value, massedit)
if(var_name == NAMEOF(src, proc_to_call_or_callback))
if(ispath(var_value) || istext(var_value))
return FALSE // security reasons, unwrapped proccall
. = ..()
/datum/component/ghostrole_spawnpoint/proc/Examine(datum/source, list/examine_list)
if(isobserver(source))
var/datum/ghostrole/role = get_ghostrole_datum(role_type)
if(!role)
return
examine_list += "<b>Click</> this ghostrole spawner to become a [role.name]!"
/datum/component/ghostrole_spawnpoint/proc/GhostInteract(datum/source, mob/user)
var/datum/ghostrole/role = get_ghostrole_datum(role_type)
if(!role)
to_chat(user, SPAN_DANGER("No ghostrole datum found: [role_type]. Contact a coder!"))
if(!(datum_flags & DF_VAR_EDITED))
stack_trace("Couldn't find role. Deleting self.")
qdel(src)
return
role.AttemptSpawn(user.client, src)
/datum/component/ghostrole_spawnpoint/vv_edit_var(var_name, var_value, massedit)
. = ..()
if(var_name == NAMEOF(src, proc_to_call_or_callback))
if(!istype(proc_to_call_or_callback, /datum/callback))
if(hascall(parent, proc_to_call_or_callback))
proc_to_call_or_callback = CALLBACK(parent, proc_to_call_or_callback)
else
proc_to_call_or_callback = null

View File

@@ -135,6 +135,7 @@
return ..()
/obj/machinery/portable_atmospherics/hydroponics/attack_ghost(var/mob/observer/dead/user)
. = ..()
if(!(harvest && seed && seed.has_mob_product))
return
@@ -145,7 +146,6 @@
var/response = alert(user, "Are you sure you want to harvest this [seed.display_name]?", "Living plant request", "Yes", "No")
if(response == "Yes")
harvest()
return
/obj/machinery/portable_atmospherics/hydroponics/attack_generic(var/mob/user)

View File

@@ -130,6 +130,7 @@
interact(user)
/obj/machinery/mineral/equipment_vendor/attack_ghost(mob/user)
. = ..()
interact(user)
/obj/machinery/mineral/equipment_vendor/interact(mob/user)

View File

@@ -358,6 +358,38 @@ proc/is_blind(A)
else
say_dead_direct("<span class='name'>[name]</span> no longer [pick("skulks","lurks","prowls","creeps","stalks")] in the realm of the dead. [message]")
/**
* WARNING: Proc direct ported from main
*
* ignore_key, ignore_dnr_observers will NOT work!
*/
/proc/notify_ghosts(message, ghost_sound, enter_link, atom/source, mutable_appearance/alert_overlay, action = NOTIFY_JUMP, flashwindow = TRUE, ignore_mapload = TRUE, ignore_key, ignore_dnr_observers = FALSE, header) //Easy notification of ghosts.
if(ignore_mapload && SSatoms.subsystem_initialized != INITIALIZATION_INNEW_REGULAR) //don't notify for objects created during a map load
return
for(var/mob/observer/dead/O in player_list)
if(!O.client)
continue
to_chat(O, "<span class='ghostalert'>[message][(enter_link) ? " [enter_link]" : ""]</span>")
if(ghost_sound)
SEND_SOUND(O, sound(ghost_sound))
if(flashwindow)
window_flash(O.client)
if(source)
var/atom/movable/screen/alert/notify_action/A = O.throw_alert("[REF(source)]_notify_action", /atom/movable/screen/alert/notify_action)
if(A)
if(O.client.prefs && O.client.prefs.UI_style)
A.icon = ui_style2icon(O.client.prefs.UI_style)
if (header)
A.name = header
A.desc = message
A.action = action
A.target = source
if(!alert_overlay)
alert_overlay = new(source)
alert_overlay.layer = FLOAT_LAYER
alert_overlay.plane = FLOAT_PLANE
A.add_overlay(alert_overlay)
/mob/proc/switch_to_camera(var/obj/machinery/camera/C)
if (!C.can_use() || stat || (get_dist(C, src) > 1 || machine != src || blinded || !canmove))
return 0

View File

@@ -98,6 +98,7 @@
update_uis()
/obj/item/modular_computer/attack_ghost(var/mob/observer/ghost/user)
. = ..()
if(enabled)
nano_ui_interact(user)
else if(check_rights(R_ADMIN, 0, user))

View File

@@ -51,13 +51,13 @@
climbLadder(M, target_ladder)
/obj/structure/ladder/attack_ghost(var/mob/M)
. = ..()
var/target_ladder = getTargetLadder(M)
if(target_ladder)
M.forceMove(get_turf(target_ladder))
/obj/structure/ladder/attack_robot(var/mob/M)
attack_hand(M)
return
/obj/structure/ladder/proc/getTargetLadder(var/mob/M)
if((!target_up && !target_down) || (target_up && !istype(target_up.loc, /turf) || (target_down && !istype(target_down.loc,/turf))))

View File

@@ -75,6 +75,7 @@
// After a recent rework this should mostly be safe.
/obj/machinery/computer/ship/attack_ghost(mob/user)
. = ..()
interface_interact(user)
// If you don't call parent in this proc, you must make all appropriate checks yourself.

View File

@@ -113,6 +113,7 @@ var/list/possible_cable_coil_colours = list(
// Ghost examining the cable -> tells him the power
/obj/structure/cable/attack_ghost(mob/user)
. = ..()
if(user.client && user.client.inquisitive_ghost)
user.examinate(src)
// following code taken from attackby (multitool)
@@ -120,8 +121,6 @@ var/list/possible_cable_coil_colours = list(
to_chat(user, "<span class='warning'>[powernet.avail]W in power network.</span>")
else
to_chat(user, "<span class='warning'>The cable is not powered.</span>")
return
// Rotating cables requires d1 and d2 to be rotated
/obj/structure/cable/setDir(new_dir)

View File

@@ -181,6 +181,7 @@
add_fingerprint(usr)
/obj/machinery/chemical_dispenser/attack_ghost(mob/user)
. = ..()
if(stat & BROKEN)
return
ui_interact(user)

View File

@@ -137,6 +137,7 @@
. = ..()
/obj/structure/lift/panel/attack_ghost(var/mob/user)
. = ..()
return interact(user)
/obj/structure/lift/panel/interact(var/mob/user)

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
icons/obj/spawners.dmi Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -0,0 +1,58 @@
import { useBackend } from '../backend';
import { Box, Button, Section } from '../components';
import { Window } from '../layouts';
export const GhostRoleMenu = (props, context) => {
const { act, data } = useBackend(context);
const spawners = data.spawners || [];
return (
<Window
title="Spawners Menu"
width={700}
height={600}>
<Window.Content scrollable>
<Section>
{spawners.map(spawner => (
<Section
key={spawner.name}
title={spawner.name + ((spawner.amount_left === -1) ? '' : (' (' + spawner.amount_left + ' left)'))}
level={2}
buttons={(
<>
<Button
content="Jump"
onClick={() => act('jump', {
id: spawner.id,
})} />
<Button
content="Spawn"
onClick={() => act('spawn', {
id: spawner.id,
})} />
</>
)}>
<Box
bold
mb={1}
fontSize="20px">
{spawner.short_desc}
</Box>
<Box>
{spawner.flavor_text}
</Box>
{!!spawner.important_info && (
<Box
mt={1}
bold
color="bad"
fontSize="26px">
{spawner.important_info}
</Box>
)}
</Section>
))}
</Section>
</Window.Content>
</Window>
);
};

File diff suppressed because one or more lines are too long

View File

@@ -29,6 +29,7 @@
#include "code\__DEFINES\access.dm"
#include "code\__DEFINES\admin.dm"
#include "code\__DEFINES\appearance.dm"
#include "code\__DEFINES\auxtools.dm"
#include "code\__DEFINES\belly_modes_vr.dm"
#include "code\__DEFINES\callbacks.dm"
#include "code\__DEFINES\chat.dm"
@@ -128,6 +129,7 @@
#include "code\__DEFINES\dcs\signals\signals_turf.dm"
#include "code\__DEFINES\dcs\signals\datums\signals_perspective.dm"
#include "code\__DEFINES\dcs\signals\items\signals_inducer.dm"
#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_attack.dm"
#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_lighting.dm"
#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_main.dm"
#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_mouse.dm"
@@ -142,6 +144,7 @@
#include "code\__DEFINES\mobs\cooldowns.dm"
#include "code\__DEFINES\mobs\intent.dm"
#include "code\__DEFINES\mobs\stat.dm"
#include "code\__DEFINES\rendering\alert.dm"
#include "code\__DEFINES\rendering\atom_huds.dm"
#include "code\__DEFINES\rendering\parallax.dm"
#include "code\__DEFINES\research\exosuit_fab.dm"
@@ -183,6 +186,7 @@
#include "code\__HELPERS\lists\bitflags.dm"
#include "code\__HELPERS\lists\copy.dm"
#include "code\__HELPERS\lists\counter.dm"
#include "code\__HELPERS\lists\json.dm"
#include "code\__HELPERS\lists\misc.dm"
#include "code\__HELPERS\lists\pick.dm"
#include "code\__HELPERS\lists\queue.dm"
@@ -2171,6 +2175,12 @@
#include "code\modules\games\tarot.dm"
#include "code\modules\games\unus.dm"
#include "code\modules\genetics\side_effects.dm"
#include "code\modules\ghostroles\instantiator.dm"
#include "code\modules\ghostroles\menu.dm"
#include "code\modules\ghostroles\role.dm"
#include "code\modules\ghostroles\spawner.dm"
#include "code\modules\ghostroles\spawnpoint.dm"
#include "code\modules\ghostroles\roles\hermit.dm"
#include "code\modules\ghosttrap\trap.dm"
#include "code\modules\holidays\_holiday.dm"
#include "code\modules\holidays\halloween.dm"