mirror of
https://github.com/VOREStation/VOREStation.git
synced 2026-07-11 15:15:32 +01:00
simple ghost pod find (#17909)
* simple ghost spawn rewrite * neutral * . * don't close * migrate vore spawn * use a searchable list * move some more verbs * allow special role spawns * add key * . * fix that part * announce for logging * filter those * typ o * . * . * Update SelectionList.tsx * . * that * . --------- Co-authored-by: Cameron Lennox <killer65311@gmail.com>
This commit is contained in:
@@ -92,3 +92,8 @@ _ADMIN_VERB(verb_path_name, verb_permissions, verb_name, verb_desc, verb_categor
|
||||
|
||||
// Visibility flags
|
||||
#define ADMIN_VERB_VISIBLITY_FLAG_MAPPING_DEBUG "Map-Debug"
|
||||
|
||||
// Tgui input list options
|
||||
#define TGUI_VIEW_ATTACK_LOGS "Check Player Attack Logs"
|
||||
#define TGUI_VIEW_DIALOG_LOGS "Check Player Dialogue Logs"
|
||||
#define TGUI_RESIZE "Resize player"
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
#define OBSERVER_EVENT_DESTROY "OnDestroy"
|
||||
|
||||
#define MOUSE_VENT_NETWORK_LENGTH 20
|
||||
|
||||
@@ -3,6 +3,7 @@ GLOBAL_DATUM(data_core, /datum/datacore)
|
||||
|
||||
//I would upgrade all instances of global.machines to SSmachines.all_machines but it's used in so many places and a search returns so many matches for 'machines' that isn't a use of the global...
|
||||
GLOBAL_LIST_INIT(machines, SSmachines.all_machines)
|
||||
GLOBAL_LIST_EMPTY(all_drone_fabricators)
|
||||
|
||||
GLOBAL_LIST_EMPTY(active_diseases)
|
||||
GLOBAL_LIST_EMPTY(hud_icon_reference)
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
sorter.cmp = cmp; \
|
||||
sorter.associative = associative;
|
||||
|
||||
//This is a global instance to allow much of this code to be reused. The interfaces are kept separately
|
||||
//This is a global instance to allow much of this code to be reused. The interfaces are kept separately
|
||||
GLOBAL_DATUM_INIT(sortInstance, /datum/sort_instance, new())
|
||||
/datum/sort_instance
|
||||
//The array being sorted.
|
||||
|
||||
@@ -100,7 +100,30 @@
|
||||
/obj/screen/ghost/vr/Click()
|
||||
..()
|
||||
var/mob/observer/dead/G = usr
|
||||
G.fake_enter_vr()
|
||||
var/datum/data/record/record_found
|
||||
record_found = find_general_record("name", G.client.prefs.real_name)
|
||||
// Found their record, they were spawned previously. Remind them corpses cannot play games.
|
||||
if(record_found)
|
||||
var/answer = tgui_alert(G, "You seem to have previously joined this round. If you are currently dead, you should not enter VR as this character. Would you still like to proceed?", "Previously spawned",list("Yes", "No"))
|
||||
if(answer != "Yes")
|
||||
return
|
||||
|
||||
var/S = null
|
||||
var/list/vr_landmarks = list()
|
||||
for(var/obj/effect/landmark/virtual_reality/sloc in GLOB.landmarks_list)
|
||||
vr_landmarks += sloc.name
|
||||
if(!LAZYLEN(vr_landmarks))
|
||||
to_chat(G, "There are no available spawn locations in virtual reality.")
|
||||
return
|
||||
S = tgui_input_list(G, "Please select a location to spawn your avatar at:", "Spawn location", vr_landmarks)
|
||||
if(!S)
|
||||
return 0
|
||||
for(var/obj/effect/landmark/virtual_reality/i in GLOB.landmarks_list)
|
||||
if(i.name == S)
|
||||
S = i
|
||||
break
|
||||
|
||||
G.fake_enter_vr(S)
|
||||
|
||||
/mob/observer/dead/create_mob_hud(datum/hud/HUD, apply_to_client = TRUE)
|
||||
..()
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
#define GHOST_POD_TAB 0
|
||||
#define GHOST_JOIN 1
|
||||
#define GHOST_VORE_SPAWN 2
|
||||
|
||||
GLOBAL_VAR_INIT(allowed_ghost_spawns, 2)
|
||||
|
||||
// Ghost spawn menu
|
||||
/datum/tgui_module/ghost_spawn_menu
|
||||
name = "Ghost Spawn Menu"
|
||||
tgui_id = "GhostSpawn"
|
||||
var/active_tab = GHOST_POD_TAB
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/tgui_state(mob/user)
|
||||
return GLOB.tgui_self_state
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/tgui_close(mob/user)
|
||||
. = ..()
|
||||
if(isobserver(user))
|
||||
var/mob/observer/dead/observer = user
|
||||
observer.selecting_ghostrole = FALSE
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/tgui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui)
|
||||
. = ..()
|
||||
if(isobserver(user) && ui)
|
||||
var/mob/observer/dead/observer = user
|
||||
observer.selecting_ghostrole = TRUE
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/tgui_data(mob/user)
|
||||
var/list/data = ..()
|
||||
|
||||
if(active_tab == GHOST_POD_TAB)
|
||||
data["all_ghost_pods"] = compile_pod_data()
|
||||
|
||||
if(active_tab == GHOST_JOIN)
|
||||
data["all_ghost_join_options"] = compile_ghost_join_data(user)
|
||||
|
||||
if(active_tab == GHOST_VORE_SPAWN)
|
||||
data["all_vore_spawns"] = compile_vorespawn_data()
|
||||
|
||||
data["active_tab"] = active_tab
|
||||
data["user_z"] = user.z
|
||||
return data
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/tgui_act(action, params, datum/tgui/ui)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
|
||||
var/mob/observer/dead/observer = ui.user
|
||||
|
||||
switch(action)
|
||||
if("select_pod")
|
||||
if(istype(observer))
|
||||
jump_to_pod(ui.user, params["selected_pod"])
|
||||
. = TRUE
|
||||
if("set_tab")
|
||||
var/new_tab = text2num(params["val"])
|
||||
if(isnum(new_tab))
|
||||
active_tab = new_tab
|
||||
. = TRUE
|
||||
if("soulcatcher_spawn")
|
||||
soulcatcher_spawn(ui.user, params["selected_player"])
|
||||
close_ui()
|
||||
. = TRUE
|
||||
if("soulcatcher_vore_spawn")
|
||||
soulcatcher_vore_spawn(ui.user, params["selected_player"])
|
||||
close_ui()
|
||||
. = TRUE
|
||||
if("bellyspawn")
|
||||
vore_belly_spawn(ui.user, params["selected_player"])
|
||||
close_ui()
|
||||
. = TRUE
|
||||
if("mouse_spawn")
|
||||
become_mouse(ui.user)
|
||||
. = TRUE
|
||||
if("drone_spawn")
|
||||
become_drone(ui.user, params["fabricator"])
|
||||
. = TRUE
|
||||
if("vr_spawn")
|
||||
join_vr(ui.user, params["landmark"])
|
||||
. = TRUE
|
||||
if("corgi_spawn")
|
||||
join_corgi(ui.user)
|
||||
. = TRUE
|
||||
if("lost_drone_spawn")
|
||||
join_lost(ui.user)
|
||||
. = TRUE
|
||||
if("maintpred_spawn")
|
||||
join_maintpred(ui.user)
|
||||
. = TRUE
|
||||
if("gravekeeper_spawn")
|
||||
join_grave(ui.user)
|
||||
. = TRUE
|
||||
if("morph_spawn")
|
||||
join_morpth(ui.user)
|
||||
. = TRUE
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/proc/compile_pod_data()
|
||||
var/list/compiled_pods = list()
|
||||
for(var/atom/movable/spawn_object in GLOB.active_ghost_pods)
|
||||
var/enabled = TRUE
|
||||
if(istype(spawn_object, /obj/structure/ghost_pod/manual))
|
||||
var/obj/structure/ghost_pod/manual/man = spawn_object
|
||||
if(!man.remains_active)
|
||||
enabled = FALSE
|
||||
var/type = "Other"
|
||||
if(ismouse(spawn_object))
|
||||
type = "Mouse"
|
||||
else if(ismob(spawn_object))
|
||||
type = "Mob"
|
||||
else if(isstructure(spawn_object))
|
||||
type = "Structure"
|
||||
UNTYPED_LIST_ADD(compiled_pods, list("pod_type" = type, "pod_name" = spawn_object.name, "z_level" = spawn_object.z, "ref" = REF(spawn_object), "remains_active" = enabled))
|
||||
return compiled_pods
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/proc/compile_ghost_join_data(mob/user)
|
||||
var/ghost_spawn_exists = FALSE
|
||||
for(var/obj/effect/landmark/L in GLOB.landmarks_list)
|
||||
if(L.name == JOB_GHOSTROLES)
|
||||
ghost_spawn_exists = TRUE
|
||||
break
|
||||
var/deathtime = world.time - user.timeofdeath
|
||||
var/time_diff = 15 MINUTES - deathtime
|
||||
var/timedifference_text = time_diff > 0 ? time2text(time_diff, "mm:ss") : ""
|
||||
var/list/ghost_join_data = list(
|
||||
"mouse_data" = get_mouse_data(user),
|
||||
"drone_data" = get_drone_data(user),
|
||||
"vr_data" = get_vr_data(user),
|
||||
"ghost_banned" = jobban_isbanned(user, JOB_CYBORG),
|
||||
"cyborg_banned" = jobban_isbanned(user, JOB_GHOSTROLES),
|
||||
"may_respawn" = user.MayRespawn(),
|
||||
"special_role_respawn" = timedifference_text,
|
||||
"existing_ghost_spawnpoints" = ghost_spawn_exists,
|
||||
"remaining_ghost_roles" = GLOB.allowed_ghost_spawns
|
||||
)
|
||||
return ghost_join_data
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/proc/get_mouse_data(mob/user)
|
||||
var/turf/T = get_turf(user)
|
||||
var/timedifference_mouse = world.time - user.client.time_died_as_mouse
|
||||
var/timedifference_mouse_text = ""
|
||||
if(user.client.time_died_as_mouse && timedifference_mouse <= CONFIG_GET(number/mouse_respawn_time) MINUTES)
|
||||
timedifference_mouse_text = time2text(CONFIG_GET(number/mouse_respawn_time) MINUTES - timedifference_mouse,"mm:ss")
|
||||
var/found_vents = FALSE
|
||||
for(var/obj/machinery/atmospherics/unary/vent_pump/v in GLOB.machines)
|
||||
if(!v.welded && v.z == T.z && v.network && v.network.normal_members.len > MOUSE_VENT_NETWORK_LENGTH)
|
||||
found_vents = TRUE
|
||||
break
|
||||
|
||||
return list(
|
||||
"disabled" = CONFIG_GET(flag/disable_player_mice),
|
||||
"bad_turf" = (!T || (T.z in using_map.admin_levels)),
|
||||
"respawn_time" = timedifference_mouse_text,
|
||||
"found_vents" = found_vents
|
||||
)
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/proc/get_drone_data(mob/user)
|
||||
var/time_till_play
|
||||
if(CONFIG_GET(flag/use_age_restriction_for_jobs) && isnum(user.client.player_age))
|
||||
time_till_play = max(0, 3 - user.client.player_age)
|
||||
var/deathtime = world.time - user.timeofdeath
|
||||
var/time_diff = 5 MINUTES - deathtime
|
||||
var/timedifference_text = time_diff > 0 ? time2text(time_diff, "mm:ss") : ""
|
||||
var/list/all_fabricators = list()
|
||||
for(var/obj/machinery/drone_fabricator/DF in GLOB.all_drone_fabricators)
|
||||
if(DF.stat & NOPOWER || !DF.produce_drones)
|
||||
continue
|
||||
if(DF.drone_progress >= 100)
|
||||
all_fabricators += list(REF(DF) = DF.fabricator_tag)
|
||||
|
||||
return list(
|
||||
"disabled" = !CONFIG_GET(flag/allow_drone_spawn),
|
||||
"days_to_play" = time_till_play,
|
||||
"respawn_time" = timedifference_text,
|
||||
"fabricators" = all_fabricators
|
||||
)
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/proc/get_vr_data(mob/user)
|
||||
var/datum/data/record/record_found = find_general_record("name", user.client.prefs.real_name)
|
||||
var/list/vr_landmarks = list()
|
||||
for(var/obj/effect/landmark/virtual_reality/sloc in GLOB.landmarks_list)
|
||||
vr_landmarks += list(REF(sloc) = sloc.name)
|
||||
|
||||
return list(
|
||||
"record_found" = !!record_found,
|
||||
"vr_landmarks" = vr_landmarks
|
||||
)
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/proc/compile_vorespawn_data()
|
||||
var/list/compiled_spawn_data = list()
|
||||
for(var/mob/living/player in GLOB.player_list)
|
||||
if(!player.client || player.stat)
|
||||
continue
|
||||
var/soulcatcher_active = FALSE
|
||||
var/soulcatcher_vore_active = FALSE
|
||||
var/vorespawn_active = FALSE
|
||||
if(ishuman(player))
|
||||
var/mob/living/carbon/human/H = player
|
||||
if(H.nif)
|
||||
var/datum/nifsoft/soulcatcher/SC = H.nif.imp_check(NIF_SOULCATCHER)
|
||||
if(SC)
|
||||
soulcatcher_active = TRUE
|
||||
if(player.soulgem?.flag_check(SOULGEM_ACTIVE | SOULGEM_CATCHING_GHOSTS, TRUE))
|
||||
soulcatcher_vore_active = TRUE
|
||||
|
||||
if(!player.no_vore && (get_z(player) in using_map.station_levels))
|
||||
if(ishuman(player))
|
||||
var/mob/living/carbon/human/H = player
|
||||
if(H.latejoin_vore)
|
||||
vorespawn_active = TRUE
|
||||
else if(issilicon(player))
|
||||
var/mob/living/silicon/S = player
|
||||
if(!isAI(S) && S.latejoin_vore)
|
||||
vorespawn_active = TRUE
|
||||
if(isanimal(player))
|
||||
var/mob/living/simple_mob/SM = player
|
||||
if(SM.vore_active && SM.latejoin_vore)
|
||||
vorespawn_active = TRUE
|
||||
compiled_spawn_data += list(REF(player) = list("player" = player.name, "soulcatcher" = soulcatcher_active, "soulcatcher_vore" = soulcatcher_vore_active, "vorespawn" = vorespawn_active))
|
||||
return compiled_spawn_data
|
||||
|
||||
#undef GHOST_POD_TAB
|
||||
#undef GHOST_JOIN
|
||||
#undef GHOST_VORE_SPAWN
|
||||
@@ -0,0 +1,329 @@
|
||||
/datum/tgui_module/ghost_spawn_menu/proc/jump_to_pod(mob/observer/dead/user, selected_pod)
|
||||
var/atom/movable/target = locate(selected_pod) in GLOB.active_ghost_pods
|
||||
if(!target)
|
||||
to_chat(user, span_warning("Invalid ghost pod selected!"))
|
||||
return
|
||||
|
||||
var/turf/T = get_turf(target) //Turf of the destination mob
|
||||
|
||||
if(T && isturf(T)) //Make sure the turf exists, then move the source to that destination.
|
||||
user.stop_following()
|
||||
user.forceMove(T)
|
||||
else
|
||||
to_chat(user, span_filter_notice("This ghost pod is not located in the game world."))
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/proc/become_mouse(mob/observer/dead/user)
|
||||
if(CONFIG_GET(flag/disable_player_mice))
|
||||
to_chat(user, span_warning("Spawning as a mouse is currently disabled."))
|
||||
return
|
||||
|
||||
if(jobban_isbanned(user, JOB_GHOSTROLES))
|
||||
to_chat(user, span_warning("You cannot become a mouse because you are banned from playing ghost roles."))
|
||||
return
|
||||
|
||||
if(!user.MayRespawn(1))
|
||||
return
|
||||
|
||||
var/turf/T = get_turf(user)
|
||||
if(!T || (T.z in using_map.admin_levels))
|
||||
to_chat(user, span_warning("You may not spawn as a mouse on this Z-level."))
|
||||
return
|
||||
|
||||
var/timedifference = world.time - user.client.time_died_as_mouse
|
||||
if(user.client.time_died_as_mouse && timedifference <= CONFIG_GET(number/mouse_respawn_time) MINUTES)
|
||||
var/timedifference_text = time2text(CONFIG_GET(number/mouse_respawn_time) MINUTES - timedifference,"mm:ss")
|
||||
to_chat(user, span_warning("You may only spawn again as a mouse more than [CONFIG_GET(number/mouse_respawn_time)] minutes after your death. You have [timedifference_text] left."))
|
||||
return
|
||||
|
||||
//find a viable mouse candidate
|
||||
var/mob/living/simple_mob/animal/passive/mouse/host
|
||||
var/obj/machinery/atmospherics/unary/vent_pump/vent_found
|
||||
var/list/found_vents = list()
|
||||
for(var/obj/machinery/atmospherics/unary/vent_pump/v in GLOB.machines)
|
||||
if(!v.welded && v.z == T.z && v.network && v.network.normal_members.len > MOUSE_VENT_NETWORK_LENGTH)
|
||||
found_vents.Add(v)
|
||||
if(found_vents.len)
|
||||
vent_found = pick(found_vents)
|
||||
host = new /mob/living/simple_mob/animal/passive/mouse(vent_found)
|
||||
else
|
||||
to_chat(user, span_warning("Unable to find any unwelded vents to spawn mice at."))
|
||||
|
||||
if(host)
|
||||
if(CONFIG_GET(flag/uneducated_mice))
|
||||
host.universal_understand = 0
|
||||
announce_ghost_joinleave(user, 0, "They are now a mouse.")
|
||||
host.ckey = user.ckey
|
||||
host.add_ventcrawl(vent_found)
|
||||
to_chat(host, span_info("You are now a mouse. Try to avoid interaction with players, and do not give hints away that you are more than a simple rodent."))
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/proc/become_drone(mob/observer/dead/user, fabricator)
|
||||
if(ticker.current_state < GAME_STATE_PLAYING)
|
||||
to_chat(user, span_danger("The game hasn't started yet!"))
|
||||
return
|
||||
|
||||
if(!CONFIG_GET(flag/allow_drone_spawn))
|
||||
to_chat(user, span_danger("That verb is not currently permitted."))
|
||||
return
|
||||
|
||||
if(jobban_isbanned(user, JOB_CYBORG))
|
||||
to_chat(user, span_danger("You are banned from playing synthetics and cannot spawn as a drone."))
|
||||
return
|
||||
|
||||
if(CONFIG_GET(flag/use_age_restriction_for_jobs) && isnum(user.client.player_age))
|
||||
var/time_till_play = max(0, 3 - user.client.player_age)
|
||||
if(time_till_play)
|
||||
to_chat(user, span_danger("You have not been playing on the server long enough to join as drone."))
|
||||
return
|
||||
|
||||
if(!user.MayRespawn(1))
|
||||
return
|
||||
|
||||
var/deathtime = world.time - user.timeofdeath
|
||||
var/deathtimeminutes = round(deathtime / (1 MINUTE))
|
||||
var/pluralcheck = "minute"
|
||||
if(deathtimeminutes == 0)
|
||||
pluralcheck = ""
|
||||
else if(deathtimeminutes == 1)
|
||||
pluralcheck = " [deathtimeminutes] minute and"
|
||||
else if(deathtimeminutes > 1)
|
||||
pluralcheck = " [deathtimeminutes] minutes and"
|
||||
var/deathtimeseconds = round((deathtime - deathtimeminutes * 1 MINUTE) / 10,1)
|
||||
|
||||
if (deathtime < 5 MINUTES)
|
||||
to_chat(user, "You have been dead for[pluralcheck] [deathtimeseconds] seconds.")
|
||||
to_chat(user, "You must wait 5 minutes to respawn as a drone!")
|
||||
return
|
||||
|
||||
var/obj/machinery/drone_fabricator/chosen_fabricator = locate(fabricator) in GLOB.all_drone_fabricators
|
||||
|
||||
if(!chosen_fabricator)
|
||||
return
|
||||
if(chosen_fabricator.stat & NOPOWER || !chosen_fabricator.produce_drones)
|
||||
return
|
||||
if(!chosen_fabricator.drone_progress >= 100)
|
||||
return
|
||||
|
||||
chosen_fabricator.create_drone(user.client)
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/proc/join_vr(mob/observer/dead/user, landmark)
|
||||
var/S = locate(landmark) in GLOB.landmarks_list
|
||||
|
||||
user.fake_enter_vr(S)
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/proc/soulcatcher_spawn(mob/observer/dead/user, selected_player)
|
||||
var/mob/living/target = locate(selected_player) in GLOB.player_list
|
||||
//Didn't pick anyone or picked a null
|
||||
if(!target)
|
||||
to_chat(user, span_warning("Invalid player selected!"))
|
||||
return
|
||||
|
||||
//Good choice testing and some instance-grabbing
|
||||
if(!ishuman(target))
|
||||
to_chat(user, span_warning("[target] isn't in a humanoid mob at the moment."))
|
||||
return
|
||||
|
||||
var/mob/living/carbon/human/H = target
|
||||
|
||||
if(H.stat || !H.client)
|
||||
to_chat(user, span_warning("[H] isn't awake/alive at the moment."))
|
||||
return
|
||||
|
||||
if(!H.nif)
|
||||
to_chat(user, span_warning("[H] doesn't have a NIF installed."))
|
||||
return
|
||||
|
||||
var/datum/nifsoft/soulcatcher/SC = H.nif.imp_check(NIF_SOULCATCHER)
|
||||
if(!SC)
|
||||
to_chat(user, span_warning("[H] doesn't have the Soulcatcher NIFSoft installed, or their NIF is unpowered."))
|
||||
return
|
||||
|
||||
//Fine fine, we can ask.
|
||||
var/obj/item/nif/nif = H.nif
|
||||
to_chat(user, span_notice("Request sent to [H]."))
|
||||
|
||||
var/req_time = world.time
|
||||
nif.notify("Transient mindstate detected, analyzing...")
|
||||
addtimer(CALLBACK(src, PROC_REF(finish_soulcatcher_spawn), user, H, SC, req_time), 1.5 SECONDS, TIMER_DELETE_ME)
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/proc/finish_soulcatcher_spawn(mob/observer/dead/user, mob/living/carbon/human/H, datum/nifsoft/soulcatcher/SC, req_time)
|
||||
var/response = tgui_alert(H,"[user] ([user.key]) wants to join into your Soulcatcher.","Soulcatcher Request", list("Deny", "Allow"), timeout = 1 MINUTE)
|
||||
|
||||
if(!response || response == "Deny")
|
||||
to_chat(user, span_warning("[H] denied your request."))
|
||||
return
|
||||
|
||||
if((world.time - req_time) > 1 MINUTE)
|
||||
to_chat(H, span_warning("The request had already expired. (1 minute waiting max)"))
|
||||
return
|
||||
|
||||
//Final check since we waited for input a couple times.
|
||||
if(H && user && user.key && !H.stat && H.nif && SC)
|
||||
if(!user.mind) //No mind yet, aka haven't played in this round.
|
||||
user.mind = new(user.key)
|
||||
|
||||
user.mind.name = name
|
||||
user.mind.current = user
|
||||
user.mind.active = TRUE
|
||||
|
||||
SC.catch_mob(user) //This will result in us being deleted so...
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/proc/soulcatcher_vore_spawn(mob/observer/dead/user, selected_player)
|
||||
var/mob/living/target = locate(selected_player) in GLOB.player_list
|
||||
if(!target)
|
||||
to_chat(user, span_warning("Invalid player selected!"))
|
||||
return
|
||||
|
||||
if(!ismob(target))
|
||||
to_chat(user, span_warning("Target is no mob."))
|
||||
return
|
||||
|
||||
var/mob/M = target
|
||||
|
||||
var/obj/soulgem/gem = M.soulgem
|
||||
|
||||
if(!gem?.flag_check(SOULGEM_ACTIVE))
|
||||
to_chat(user, span_warning("[M] has no enabled Soulcatcher."))
|
||||
return
|
||||
|
||||
var/req_time = world.time
|
||||
gem.notify_holder("Transient mindstate detected, analyzing...")
|
||||
addtimer(CALLBACK(src, PROC_REF(finish_soulcatcher_vore_spawn), user, M, gem, req_time), 1.5 SECONDS, TIMER_DELETE_ME)
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/proc/finish_soulcatcher_vore_spawn(mob/observer/dead/user, mob/M, obj/soulgem/gem, req_time)
|
||||
if(tgui_alert(M, "[user.name] wants to join into your Soulcatcher.","Soulcatcher Request",list("Deny", "Allow"), timeout=1 MINUTES) != "Allow")
|
||||
to_chat(user, span_warning("[M] has denied your request."))
|
||||
return
|
||||
|
||||
if((world.time - req_time) > 1 MINUTES)
|
||||
to_chat(M, span_warning("The request had already expired. (1 minute waiting max)"))
|
||||
return
|
||||
|
||||
//Final check since we waited for input a couple times.
|
||||
if(M && user && user.key && !M.stat && gem?.flag_check(SOULGEM_ACTIVE | SOULGEM_CATCHING_GHOSTS, TRUE))
|
||||
if(!user.mind) //No mind yet, aka haven't played in this round.
|
||||
user.mind = new(user.key)
|
||||
|
||||
user.mind.name = name
|
||||
user.mind.current = user
|
||||
user.mind.active = TRUE
|
||||
|
||||
gem.catch_mob(user) //This will result in us being deleted so...
|
||||
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/proc/vore_belly_spawn(mob/observer/dead/user, selected_player)
|
||||
var/mob/living/target = locate(selected_player) in GLOB.player_list
|
||||
|
||||
if(!target)
|
||||
to_chat(user, span_warning("Invalid player selected!"))
|
||||
return
|
||||
|
||||
to_chat(user, span_notice("Inbelly spawn request sent to predator."))
|
||||
to_chat(target, span_notice("Incoming belly spawn request."))
|
||||
addtimer(CALLBACK(src, PROC_REF(finish_vore_belly_spawn), user, target), 1.5 SECONDS, TIMER_DELETE_ME)
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/proc/finish_vore_belly_spawn(mob/observer/dead/user, mob/living/L)
|
||||
L.inbelly_spawn_prompt(user.client) // Hand reins over to them
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/proc/join_corgi(mob/observer/dead/user)
|
||||
if(jobban_isbanned(user, JOB_GHOSTROLES))
|
||||
to_chat(user, span_danger("You are banned from playing ghost roles and cannot spawn as a corgi."))
|
||||
return
|
||||
|
||||
if(GLOB.allowed_ghost_spawns <= 0)
|
||||
to_chat(user, span_warning("There're no free ghost join slots."))
|
||||
return
|
||||
|
||||
var/obj/effect/landmark/spawnspot = get_ghost_role_spawn()
|
||||
if(!spawnspot)
|
||||
to_chat(user, span_warning("No spawnpoint available."))
|
||||
return
|
||||
|
||||
GLOB.allowed_ghost_spawns--
|
||||
announce_ghost_joinleave(user, 0, "They are now a corgi.")
|
||||
var/obj/structure/ghost_pod/manual/corgi/corg = new(get_turf(spawnspot))
|
||||
corg.create_occupant(user)
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/proc/join_lost(mob/observer/dead/user)
|
||||
if(jobban_isbanned(user, JOB_CYBORG))
|
||||
to_chat(user, span_danger("You are banned from playing synthetics and cannot spawn as a drone."))
|
||||
return
|
||||
|
||||
if(GLOB.allowed_ghost_spawns <= 0)
|
||||
to_chat(user, span_warning("There're no free ghost join slots."))
|
||||
return
|
||||
|
||||
var/obj/effect/landmark/spawnspot = get_ghost_role_spawn()
|
||||
if(!spawnspot)
|
||||
to_chat(user, span_warning("No spawnpoint available."))
|
||||
return
|
||||
|
||||
GLOB.allowed_ghost_spawns--
|
||||
announce_ghost_joinleave(user, 0, "They are now a lost drone.")
|
||||
var/obj/structure/ghost_pod/manual/lost_drone/dogborg/lost = new(get_turf(spawnspot))
|
||||
lost.create_occupant(user)
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/proc/join_maintpred(mob/observer/dead/user)
|
||||
if(jobban_isbanned(user, JOB_GHOSTROLES))
|
||||
to_chat(user, span_danger("You are banned from playing ghost roles and cannot spawn as a maint pred."))
|
||||
return
|
||||
|
||||
if(GLOB.allowed_ghost_spawns <= 0)
|
||||
to_chat(user, span_warning("There're no free ghost join slots."))
|
||||
return
|
||||
|
||||
var/obj/effect/landmark/spawnspot = get_ghost_role_spawn()
|
||||
if(!spawnspot)
|
||||
to_chat(user, span_warning("No spawnpoint available."))
|
||||
return
|
||||
|
||||
GLOB.allowed_ghost_spawns--
|
||||
announce_ghost_joinleave(user, 0, "They are now a maint pred.")
|
||||
var/obj/structure/ghost_pod/ghost_activated/maintpred/no_announce/mpred = new(get_turf(spawnspot))
|
||||
mpred.create_occupant(user)
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/proc/join_grave(mob/observer/dead/user)
|
||||
if(jobban_isbanned(user, JOB_CYBORG))
|
||||
to_chat(user, span_danger("You are banned from playing synthetics and cannot spawn as a gravekeeper."))
|
||||
return
|
||||
|
||||
if(GLOB.allowed_ghost_spawns <= 0)
|
||||
to_chat(user, span_warning("There're no free ghost join slots."))
|
||||
return
|
||||
|
||||
var/obj/effect/landmark/spawnspot = get_ghost_role_spawn()
|
||||
if(!spawnspot)
|
||||
to_chat(user, span_warning("No spawnpoint available."))
|
||||
return
|
||||
|
||||
GLOB.allowed_ghost_spawns--
|
||||
announce_ghost_joinleave(user, 0, "They are now a gravekeeper drone.")
|
||||
var/obj/structure/ghost_pod/automatic/gravekeeper_drone/grave = new(get_turf(spawnspot))
|
||||
grave.create_occupant(user)
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/proc/join_morpth(mob/observer/dead/user)
|
||||
if(jobban_isbanned(user, JOB_GHOSTROLES))
|
||||
to_chat(user, span_danger("You are banned from playing ghost roles and cannot spawn as a morph."))
|
||||
return
|
||||
|
||||
if(GLOB.allowed_ghost_spawns <= 0)
|
||||
to_chat(user, span_warning("There're no free ghost join slots."))
|
||||
return
|
||||
|
||||
var/obj/effect/landmark/spawnspot = get_ghost_role_spawn()
|
||||
if(!spawnspot)
|
||||
to_chat(user, span_warning("No spawnpoint available."))
|
||||
return
|
||||
|
||||
GLOB.allowed_ghost_spawns--
|
||||
announce_ghost_joinleave(user, 0, "They are now a morph.")
|
||||
var/obj/structure/ghost_pod/ghost_activated/morphspawn/no_announce/morph = new(get_turf(spawnspot))
|
||||
morph.create_occupant(user)
|
||||
|
||||
/datum/tgui_module/ghost_spawn_menu/proc/get_ghost_role_spawn()
|
||||
var/list/possibleSpawnspots = list()
|
||||
for(var/obj/effect/landmark/L in GLOB.landmarks_list)
|
||||
if(L.name == JOB_GHOSTROLES)
|
||||
possibleSpawnspots += L
|
||||
if(possibleSpawnspots.len)
|
||||
return pick(possibleSpawnspots)
|
||||
return null
|
||||
@@ -102,12 +102,12 @@
|
||||
var/list/cameras = list()
|
||||
|
||||
/mob/living/silicon/ai/proc/trackable_mobs()
|
||||
if(usr.stat == 2)
|
||||
if(src.stat == 2)
|
||||
return list()
|
||||
|
||||
var/datum/trackable/TB = new()
|
||||
for(var/mob/living/M in GLOB.mob_list)
|
||||
if(M == usr)
|
||||
if(M == src)
|
||||
continue
|
||||
if(M.tracking_status() != TRACKING_POSSIBLE)
|
||||
continue
|
||||
|
||||
@@ -54,44 +54,11 @@
|
||||
drop_from_inventory(I)
|
||||
qdel(src)
|
||||
|
||||
/mob/observer/dead/verb/fake_enter_vr()
|
||||
set name = "Join virtual reality"
|
||||
set category = "Ghost.Join"
|
||||
set desc = "Log into NanoTrasen's local virtual reality server."
|
||||
|
||||
/* Temp removal while I figure out how to reduce the respawn time to 1 minute
|
||||
var/time_till_respawn = time_till_respawn()
|
||||
if(time_till_respawn == -1) // Special case, never allowed to respawn
|
||||
to_chat(usr, span_warning("Respawning is not allowed!"))
|
||||
/mob/observer/dead/proc/fake_enter_vr(landmark)
|
||||
if(!landmark)
|
||||
return
|
||||
if(time_till_respawn) // Nonzero time to respawn
|
||||
to_chat(usr, span_warning("You can't do that yet! You died too recently. You need to wait another [round(time_till_respawn/10/60, 0.1)] minutes."))
|
||||
return
|
||||
*/
|
||||
var/datum/data/record/record_found
|
||||
record_found = find_general_record("name", client.prefs.real_name)
|
||||
// Found their record, they were spawned previously. Remind them corpses cannot play games.
|
||||
if(record_found)
|
||||
var/answer = tgui_alert(src,"You seem to have previously joined this round. If you are currently dead, you should not enter VR as this character. Would you still like to proceed?","Previously spawned",list("Yes","No"))
|
||||
if(answer != "Yes")
|
||||
return
|
||||
|
||||
var/S = null
|
||||
var/list/vr_landmarks = list()
|
||||
for(var/obj/effect/landmark/virtual_reality/sloc in GLOB.landmarks_list)
|
||||
vr_landmarks += sloc.name
|
||||
if(!LAZYLEN(vr_landmarks))
|
||||
to_chat(src, "There are no available spawn locations in virtual reality.")
|
||||
return
|
||||
S = tgui_input_list(usr, "Please select a location to spawn your avatar at:", "Spawn location", vr_landmarks)
|
||||
if(!S)
|
||||
return 0
|
||||
for(var/obj/effect/landmark/virtual_reality/i in GLOB.landmarks_list)
|
||||
if(i.name == S)
|
||||
S = i
|
||||
break
|
||||
|
||||
var/mob/living/carbon/human/avatar = new(get_turf(S), client.prefs.species)
|
||||
var/mob/living/carbon/human/avatar = new(get_turf(landmark), client.prefs.species)
|
||||
if(!avatar)
|
||||
to_chat(src, "Something went wrong and spawning failed.")
|
||||
return
|
||||
|
||||
@@ -96,3 +96,6 @@
|
||||
name = JOB_TALON_PILOT
|
||||
/obj/effect/landmark/start/talonminer
|
||||
name = JOB_TALON_MINER
|
||||
|
||||
/obj/effect/landmark/start/ghost_roles
|
||||
name = JOB_GHOSTROLES
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
set name = "Check Player Attack Logs"
|
||||
set desc = "Check a player's attack logs."
|
||||
|
||||
show_cmd_admin_check_player_logs(M)
|
||||
|
||||
//Views specific attack logs belonging to one player.
|
||||
/client/proc/show_cmd_admin_check_player_logs(mob/living/M)
|
||||
var/dat = span_bold("[M]'s Attack Log:<HR>")
|
||||
dat += span_bold("Viewing attack logs of [M]") + " - (Played by ([key_name(M)]).<br>"
|
||||
if(M.mind)
|
||||
@@ -33,8 +36,10 @@
|
||||
set category = "Admin.Logs"
|
||||
set name = "Check Player Dialogue Logs"
|
||||
set desc = "Check a player's dialogue logs."
|
||||
show_cmd_admin_check_dialogue_logs(M)
|
||||
|
||||
//Views specific dialogue logs belonging to one player.
|
||||
/client/proc/show_cmd_admin_check_dialogue_logs(mob/living/M)
|
||||
var/dat = span_bold("[M]'s Dialogue Log:<HR>")
|
||||
dat += span_bold("Viewing say and emote logs of [M]") + " - (Played by ([key_name(M)]).<br>"
|
||||
if(M.mind)
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
ADMIN_VERB_AND_CONTEXT_MENU(resize, (R_ADMIN|R_FUN|R_VAREDIT), "Resize", "Resizes any living mob without any restrictions on size.", "Fun.Event Kit", mob/living/L in GLOB.mob_list)
|
||||
user.do_resize(L)
|
||||
|
||||
/client/proc/do_resize(var/mob/living/L)
|
||||
var/size_multiplier = tgui_input_number(usr, "Input size multiplier.", "Resize", 1, round_value=FALSE)
|
||||
if(!size_multiplier)
|
||||
return //cancelled
|
||||
@@ -8,11 +11,11 @@ ADMIN_VERB_AND_CONTEXT_MENU(resize, (R_ADMIN|R_FUN|R_VAREDIT), "Resize", "Resize
|
||||
var/very_big = is_extreme_size(size_multiplier)
|
||||
|
||||
if(very_big && can_be_big) // made an extreme size in an area that allows it, don't assume adminbuse
|
||||
to_chat(user, span_warning("[L] will lose this size upon moving into an area where this size is not allowed."))
|
||||
to_chat(src, span_warning("[L] will lose this size upon moving into an area where this size is not allowed."))
|
||||
else if(very_big) // made an extreme size in an area that doesn't allow it, assume adminbuse
|
||||
to_chat(user, span_warning("[L] will retain this normally unallowed size outside this area."))
|
||||
to_chat(src, span_warning("[L] will retain this normally unallowed size outside this area."))
|
||||
|
||||
L.resize(size_multiplier, animate = TRUE, uncapped = TRUE, ignore_prefs = TRUE)
|
||||
|
||||
log_and_message_admins("has changed [key_name(L)]'s size multiplier to [size_multiplier].", user)
|
||||
log_and_message_admins("has changed [key_name(L)]'s size multiplier to [size_multiplier].", src)
|
||||
feedback_add_details("admin_verb","RESIZE")
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
//Allows some list search inputs
|
||||
/client/proc/tgui_admin_lists()
|
||||
set name = "TGUI Admin Lists"
|
||||
set desc = "Allows to have some procs with searchable lists."
|
||||
set category = "Admin.Game"
|
||||
if(!check_rights(R_ADMIN|R_EVENT))
|
||||
return
|
||||
|
||||
var/list/modification_options = list(TGUI_VIEW_ATTACK_LOGS, TGUI_VIEW_DIALOG_LOGS, TGUI_RESIZE)
|
||||
|
||||
|
||||
var/tgui_list_choice = tgui_input_list(src, "Select the verb you would like to use with a tgui input","Choice", modification_options)
|
||||
if(!tgui_list_choice || tgui_list_choice == "Cancel")
|
||||
return
|
||||
|
||||
log_and_message_admins("[key_name(src)] has used TGUI Admin Lists with ([tgui_list_choice])].")
|
||||
feedback_add_details("admin_verb","TGUIADL") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
switch(tgui_list_choice)
|
||||
if(TGUI_VIEW_ATTACK_LOGS)
|
||||
var/mob/living/L = tgui_input_list(src, "Check a player's attack logs.", "Check Player Attack Logs", GLOB.mob_list)
|
||||
show_cmd_admin_check_player_logs(L)
|
||||
if(TGUI_VIEW_DIALOG_LOGS)
|
||||
var/mob/living/L = tgui_input_list(src, "Check a player's dialogue logs.", "Check Player Dialogue Logs", GLOB.mob_list)
|
||||
show_cmd_admin_check_dialogue_logs(L)
|
||||
if(TGUI_RESIZE)
|
||||
var/mob/living/L = tgui_input_list(src, "Resizes any living mob without any restrictions on size.", "Resize", GLOB.mob_list)
|
||||
if(L)
|
||||
do_resize(L)
|
||||
@@ -1,4 +1,4 @@
|
||||
//GLOBAL_LIST_EMPTY(event_viruses) // so that event viruses are kept around for admin logs, rather than being GCed
|
||||
GLOBAL_LIST_EMPTY(event_viruses) // so that event viruses are kept around for admin logs, rather than being GCed
|
||||
|
||||
/datum/event/viral_infection
|
||||
var/list/viruses = list()
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
var/ghost_sprite = null
|
||||
var/last_revive_notification = null // world.time of last notification, used to avoid spamming players from defibs or cloners.
|
||||
var/cleanup_timer // Refernece to a timer that will delete this mob if no client returns
|
||||
var/selecting_ghostrole = FALSE
|
||||
|
||||
invisibility = INVISIBILITY_OBSERVER
|
||||
layer = BELOW_MOB_LAYER
|
||||
@@ -648,61 +649,6 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
|
||||
var/rads = SSradiation.get_rads_at_turf(t)
|
||||
to_chat(src, span_notice("Radiation level: [rads ? rads : "0"] Bq."))
|
||||
|
||||
|
||||
/mob/observer/dead/verb/become_mouse()
|
||||
set name = "Become mouse"
|
||||
set category = "Ghost.Join"
|
||||
|
||||
if(CONFIG_GET(flag/disable_player_mice))
|
||||
to_chat(src, span_warning("Spawning as a mouse is currently disabled."))
|
||||
return
|
||||
|
||||
//VOREStation Add Start
|
||||
if(jobban_isbanned(src, JOB_GHOSTROLES))
|
||||
to_chat(src, span_warning("You cannot become a mouse because you are banned from playing ghost roles."))
|
||||
return
|
||||
//VOREStation Add End
|
||||
|
||||
if(!MayRespawn(1))
|
||||
return
|
||||
|
||||
var/turf/T = get_turf(src)
|
||||
if(!T || (T.z in using_map.admin_levels))
|
||||
to_chat(src, span_warning("You may not spawn as a mouse on this Z-level."))
|
||||
return
|
||||
|
||||
var/timedifference = world.time - client.time_died_as_mouse
|
||||
if(client.time_died_as_mouse && timedifference <= CONFIG_GET(number/mouse_respawn_time) MINUTES)
|
||||
var/timedifference_text
|
||||
timedifference_text = time2text(CONFIG_GET(number/mouse_respawn_time) MINUTES - timedifference,"mm:ss")
|
||||
to_chat(src, span_warning("You may only spawn again as a mouse more than [CONFIG_GET(number/mouse_respawn_time)] minutes after your death. You have [timedifference_text] left."))
|
||||
return
|
||||
|
||||
var/response = tgui_alert(src, "Are you -sure- you want to become a mouse?","Are you sure you want to squeek?",list("Squeek!","Nope!"))
|
||||
if(response != "Squeek!") return //Hit the wrong key...again.
|
||||
|
||||
|
||||
//find a viable mouse candidate
|
||||
var/mob/living/simple_mob/animal/passive/mouse/host
|
||||
var/obj/machinery/atmospherics/unary/vent_pump/vent_found
|
||||
var/list/found_vents = list()
|
||||
for(var/obj/machinery/atmospherics/unary/vent_pump/v in GLOB.machines)
|
||||
if(!v.welded && v.z == T.z && v.network && v.network.normal_members.len > 20)
|
||||
found_vents.Add(v)
|
||||
if(found_vents.len)
|
||||
vent_found = pick(found_vents)
|
||||
host = new /mob/living/simple_mob/animal/passive/mouse(vent_found)
|
||||
else
|
||||
to_chat(src, span_warning("Unable to find any unwelded vents to spawn mice at."))
|
||||
|
||||
if(host)
|
||||
if(CONFIG_GET(flag/uneducated_mice))
|
||||
host.universal_understand = 0
|
||||
announce_ghost_joinleave(src, 0, "They are now a mouse.")
|
||||
host.ckey = src.ckey
|
||||
host.add_ventcrawl(vent_found)
|
||||
to_chat(host, span_info("You are now a mouse. Try to avoid interaction with players, and do not give hints away that you are more than a simple rodent."))
|
||||
|
||||
/mob/observer/dead/verb/view_manfiest()
|
||||
set name = "Show Crew Manifest"
|
||||
set category = "Ghost.Game"
|
||||
@@ -916,7 +862,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
|
||||
return 0
|
||||
if(mind && mind.current && mind.current.stat != DEAD && can_reenter_corpse)
|
||||
if(feedback)
|
||||
to_chat(src, span_warning("Your non-dead body prevent you from respawning."))
|
||||
to_chat(src, span_warning("Your non-dead body prevents you from respawning."))
|
||||
return 0
|
||||
if(CONFIG_GET(flag/antag_hud_restricted) && has_enabled_antagHUD == 1)
|
||||
if(feedback)
|
||||
|
||||
@@ -1,62 +1,3 @@
|
||||
/mob/observer/dead/verb/nifjoin()
|
||||
set category = "Ghost.Join"
|
||||
set name = "Join Into Soulcatcher"
|
||||
set desc = "Select a player with a working NIF + Soulcatcher NIFSoft to join into it."
|
||||
|
||||
var/picked = tgui_input_list(src, "Pick a friend with NIF and Soulcatcher to join into. Harrass strangers, get banned. Not everyone has a NIF w/ Soulcatcher.","Select a player", GLOB.player_list)
|
||||
|
||||
//Didn't pick anyone or picked a null
|
||||
if(!picked)
|
||||
return
|
||||
|
||||
//Good choice testing and some instance-grabbing
|
||||
if(!ishuman(picked))
|
||||
to_chat(src,span_warning("[picked] isn't in a humanoid mob at the moment."))
|
||||
return
|
||||
|
||||
var/mob/living/carbon/human/H = picked
|
||||
|
||||
if(H.stat || !H.client)
|
||||
to_chat(src,span_warning("[H] isn't awake/alive at the moment."))
|
||||
return
|
||||
|
||||
if(!H.nif)
|
||||
to_chat(src,span_warning("[H] doesn't have a NIF installed."))
|
||||
return
|
||||
|
||||
var/datum/nifsoft/soulcatcher/SC = H.nif.imp_check(NIF_SOULCATCHER)
|
||||
if(!SC)
|
||||
to_chat(src,span_warning("[H] doesn't have the Soulcatcher NIFSoft installed, or their NIF is unpowered."))
|
||||
return
|
||||
|
||||
//Fine fine, we can ask.
|
||||
var/obj/item/nif/nif = H.nif
|
||||
to_chat(src,span_notice("Request sent to [H]."))
|
||||
|
||||
var/req_time = world.time
|
||||
nif.notify("Transient mindstate detected, analyzing...")
|
||||
sleep(15) //So if they are typing they get interrupted by sound and message, and don't type over the box
|
||||
var/response = tgui_alert(H,"[src] ([src.key]) wants to join into your Soulcatcher.","Soulcatcher Request",list("Deny","Allow"), timeout = 1 MINUTE)
|
||||
|
||||
if(!response || response == "Deny")
|
||||
to_chat(src,span_warning("[H] denied your request."))
|
||||
return
|
||||
|
||||
if((world.time - req_time) > 1 MINUTE)
|
||||
to_chat(H,span_warning("The request had already expired. (1 minute waiting max)"))
|
||||
return
|
||||
|
||||
//Final check since we waited for input a couple times.
|
||||
if(H && src && src.key && !H.stat && nif && SC)
|
||||
if(!mind) //No mind yet, aka haven't played in this round.
|
||||
mind = new(key)
|
||||
|
||||
mind.name = name
|
||||
mind.current = src
|
||||
mind.active = TRUE
|
||||
|
||||
SC.catch_mob(src) //This will result in us being deleted so...
|
||||
|
||||
/mob/observer/dead/verb/backup_ping()
|
||||
set category = "Ghost.Join"
|
||||
set name = "Notify Transcore"
|
||||
@@ -106,30 +47,17 @@
|
||||
*/
|
||||
/mob/observer/dead/verb/findghostpod() //Moves the ghost instead of just changing the ghosts's eye -Nodrak
|
||||
set category = "Ghost.Join"
|
||||
set name = "Find Ghost Pod"
|
||||
set desc = "Find an active ghost pod"
|
||||
set popup_menu = FALSE
|
||||
set name = "Ghost Spawn"
|
||||
set desc = "Open Ghost Spawn Menu"
|
||||
|
||||
if(!isobserver(src)) //Make sure they're an observer!
|
||||
return
|
||||
|
||||
var/input = tgui_input_list(src, "Select a ghost pod:", "Ghost Jump", observe_list_format(GLOB.active_ghost_pods))
|
||||
if(!input)
|
||||
to_chat(src, span_filter_notice("No active ghost pods detected."))
|
||||
if(selecting_ghostrole)
|
||||
return
|
||||
|
||||
var/target = observe_list_format(GLOB.active_ghost_pods)[input]
|
||||
if (!target)//Make sure we actually have a target
|
||||
return
|
||||
else
|
||||
var/obj/O = target //Destination mob
|
||||
var/turf/T = get_turf(O) //Turf of the destination mob
|
||||
|
||||
if(T && isturf(T)) //Make sure the turf exists, then move the source to that destination.
|
||||
forceMove(T)
|
||||
stop_following()
|
||||
else
|
||||
to_chat(src, span_filter_notice("This ghost pod is not located in the game world."))
|
||||
var/datum/tgui_module/ghost_spawn_menu/ui = new(src)
|
||||
ui.tgui_interact(src)
|
||||
|
||||
/mob/observer/dead/verb/findautoresleever()
|
||||
set category = "Ghost.Join"
|
||||
|
||||
@@ -34,6 +34,15 @@
|
||||
fabricator_tag = "Upper Level Mining"
|
||||
drone_type = /mob/living/silicon/robot/drone/mining
|
||||
|
||||
/obj/machinery/drone_fabricator/Initialize(mapload)
|
||||
. = ..()
|
||||
GLOB.all_drone_fabricators += src
|
||||
|
||||
|
||||
/obj/machinery/drone_fabricator/Destroy()
|
||||
GLOB.all_drone_fabricators -= src
|
||||
. = ..()
|
||||
|
||||
/obj/machinery/drone_fabricator/power_change()
|
||||
..()
|
||||
if (stat & NOPOWER)
|
||||
@@ -88,70 +97,3 @@
|
||||
new_drone.transfer_personality(player)
|
||||
|
||||
return new_drone
|
||||
|
||||
/mob/observer/dead/verb/join_as_drone()
|
||||
|
||||
set category = "Ghost.Join"
|
||||
set name = "Join As Drone"
|
||||
set desc = "If there is a powered, enabled fabricator in the game world with a prepared chassis, join as a maintenance drone."
|
||||
|
||||
if(ticker.current_state < GAME_STATE_PLAYING)
|
||||
to_chat(src, span_danger("The game hasn't started yet!"))
|
||||
return
|
||||
|
||||
if(!CONFIG_GET(flag/allow_drone_spawn))
|
||||
to_chat(src, span_danger("That verb is not currently permitted."))
|
||||
return
|
||||
|
||||
if (!src.stat)
|
||||
return
|
||||
|
||||
if (usr != src)
|
||||
return 0 //something is terribly wrong
|
||||
|
||||
if(jobban_isbanned(src,JOB_CYBORG))
|
||||
to_chat(src, span_danger("You are banned from playing synthetics and cannot spawn as a drone."))
|
||||
return
|
||||
|
||||
// VOREStation Addition Start
|
||||
if(CONFIG_GET(flag/use_age_restriction_for_jobs) && isnum(src.client.player_age))
|
||||
var/time_till_play = max(0, 3 - src.client.player_age)
|
||||
if(time_till_play)
|
||||
to_chat(src, span_danger("You have not been playing on the server long enough to join as drone."))
|
||||
return
|
||||
// VOREStation Addition End
|
||||
|
||||
if(!MayRespawn(1))
|
||||
return
|
||||
|
||||
var/deathtime = world.time - src.timeofdeath
|
||||
var/deathtimeminutes = round(deathtime / (1 MINUTE))
|
||||
var/pluralcheck = "minute"
|
||||
if(deathtimeminutes == 0)
|
||||
pluralcheck = ""
|
||||
else if(deathtimeminutes == 1)
|
||||
pluralcheck = " [deathtimeminutes] minute and"
|
||||
else if(deathtimeminutes > 1)
|
||||
pluralcheck = " [deathtimeminutes] minutes and"
|
||||
var/deathtimeseconds = round((deathtime - deathtimeminutes * 1 MINUTE) / 10,1)
|
||||
|
||||
if (deathtime < 5 MINUTES)
|
||||
to_chat(src, "You have been dead for[pluralcheck] [deathtimeseconds] seconds.")
|
||||
to_chat(src, "You must wait 5 minutes to respawn as a drone!")
|
||||
return
|
||||
|
||||
var/list/all_fabricators = list()
|
||||
for(var/obj/machinery/drone_fabricator/DF in GLOB.machines)
|
||||
if(DF.stat & NOPOWER || !DF.produce_drones)
|
||||
continue
|
||||
if(DF.drone_progress >= 100)
|
||||
all_fabricators[DF.fabricator_tag] = DF
|
||||
|
||||
if(!all_fabricators.len)
|
||||
to_chat(src, span_danger("There are no available drone spawn points, sorry."))
|
||||
return
|
||||
|
||||
var/choice = tgui_input_list(src, "Which fabricator do you wish to use?", "Fabricator Choice", all_fabricators)
|
||||
if(choice)
|
||||
var/obj/machinery/drone_fabricator/chosen_fabricator = all_fabricators[choice]
|
||||
chosen_fabricator.create_drone(src.client)
|
||||
|
||||
@@ -1,68 +1,3 @@
|
||||
/mob/observer/dead/verb/spawn_in_belly()
|
||||
set category = "Ghost.Join"
|
||||
set name = "Spawn In Belly"
|
||||
set desc = "Spawn in someone's belly."
|
||||
|
||||
if(!client)
|
||||
return
|
||||
|
||||
// If any ghost-side restrictions are desired, they'll go here
|
||||
|
||||
tgui_alert(src,{"
|
||||
This verb allows you to spawn inside someone's belly when they are in round.
|
||||
Make sure you to coordinate with your predator OOCly as well as roleplay approprietly.
|
||||
You are considered to have been in the belly entire time the predator was around and are not added to crew lists.
|
||||
This is not intended to be used for mechanical advantage or providing assistance, but for facilitating longterm scenes.
|
||||
Please do not abuse this ability.
|
||||
"},"OOC Warning") // Warning.
|
||||
|
||||
var/list/eligible_targets = list()
|
||||
|
||||
for(var/mob/living/pred in GLOB.living_mob_list)
|
||||
if(!istype(pred) || !pred.client) // Ignore preds that aren't living mobs or player controlled
|
||||
continue
|
||||
if(pred.no_vore) // No vore, no bellies, no inbelly spawning
|
||||
continue
|
||||
if(!(get_z(pred) in using_map.station_levels)) // No explo reinforcements
|
||||
continue
|
||||
if(ishuman(pred))
|
||||
var/mob/living/carbon/human/H = pred
|
||||
if(!H.latejoin_vore)
|
||||
continue
|
||||
eligible_targets += H
|
||||
continue
|
||||
if(issilicon(pred))
|
||||
var/mob/living/silicon/S = pred
|
||||
if(isAI(S))
|
||||
continue // Sorry, AI buddies. Your vore works too differently.
|
||||
if(!S.latejoin_vore)
|
||||
continue
|
||||
eligible_targets += S
|
||||
continue
|
||||
if(isanimal(pred))
|
||||
var/mob/living/simple_mob/SM = pred
|
||||
if(!SM.vore_active) // No vore, no bellies, no inbelly spawning
|
||||
continue
|
||||
if(!SM.latejoin_vore)
|
||||
continue
|
||||
eligible_targets += SM
|
||||
continue
|
||||
|
||||
// Only humans, simple_mobs and non-AI silicons are included. Obscure stuff like bots is skipped.
|
||||
|
||||
if(!eligible_targets.len)
|
||||
to_chat(src, span_notice("No eligible preds were found.")) // :(
|
||||
return
|
||||
|
||||
var/mob/living/target = tgui_input_list(src, "Please specify which character you want to spawn inside of.", "Predator", eligible_targets) // Offer the list of things we gathered.
|
||||
|
||||
if(!target || !client) // Did out target cease to exist? Or did we?
|
||||
return
|
||||
|
||||
// Notify them that its now pred's turn
|
||||
to_chat(src, span_notice("Inbelly spawn request sent to predator."))
|
||||
target.inbelly_spawn_prompt(client) // Hand reins over to them
|
||||
|
||||
/mob/living/proc/inbelly_spawn_prompt(client/potential_prey)
|
||||
if(!potential_prey || !istype(potential_prey)) // Did our prey cease to exist?
|
||||
return
|
||||
@@ -127,7 +62,6 @@ Please do not abuse this ability.
|
||||
else
|
||||
to_chat(potential_prey, span_notice("Inbelly spawn cancelled."))
|
||||
to_chat(src, span_notice("Prey cancelled their inbelly spawn request."))
|
||||
return
|
||||
|
||||
/proc/inbelly_spawn(client/prey, mob/living/pred, obj/belly/target_belly, var/absorbed = FALSE)
|
||||
// All this is basically admin late spawn-in, but skipping all parts related to records and equipment and with predteremined location
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
/mob/observer/dead/verb/soulcatcherjoin()
|
||||
set category = "Ghost.Join"
|
||||
set name = "Join Into Soulcatcher(Vore)"
|
||||
set desc = "Select a player with enabled Soulcatcher to join."
|
||||
|
||||
var/list/valid_players = list()
|
||||
for(var/mob/player in GLOB.player_list)
|
||||
if(player.soulgem?.flag_check(SOULGEM_ACTIVE | SOULGEM_CATCHING_GHOSTS, TRUE))
|
||||
valid_players += player
|
||||
|
||||
if(!valid_players.len)
|
||||
to_chat(src, span_warning("No valid players found."))
|
||||
return
|
||||
|
||||
var/picked = tgui_input_list(usr, "Pick a friend with enabled Soulcatcher to join into. Harrass strangers, get banned.","Select a player", valid_players)
|
||||
|
||||
if(!picked)
|
||||
return
|
||||
|
||||
if(!ismob(picked))
|
||||
to_chat(src, span_warning("Target is no mob."))
|
||||
return
|
||||
|
||||
var/mob/M = picked
|
||||
|
||||
var/obj/soulgem/gem = M.soulgem
|
||||
|
||||
if(!gem?.flag_check(SOULGEM_ACTIVE))
|
||||
to_chat(src, span_warning("[M] has no enabled Soulcatcher."))
|
||||
return
|
||||
|
||||
var/req_time = world.time
|
||||
gem.notify_holder("Transient mindstate detected, analyzing...")
|
||||
sleep(15) //So if they are typing they get interrupted by sound and message, and don't type over the box
|
||||
if(tgui_alert(M,"[src.name] wants to join into your Soulcatcher.","Soulcatcher Request",list("Deny","Allow"), timeout=1 MINUTES) != "Allow")
|
||||
to_chat(src, span_warning("[M] has denied your request."))
|
||||
return
|
||||
|
||||
if((world.time - req_time) > 1 MINUTES)
|
||||
to_chat(M, span_warning("The request had already expired. (1 minute waiting max)"))
|
||||
return
|
||||
|
||||
//Final check since we waited for input a couple times.
|
||||
if(M && src && src.key && !M.stat && gem?.flag_check(SOULGEM_ACTIVE | SOULGEM_CATCHING_GHOSTS, TRUE))
|
||||
if(!mind) //No mind yet, aka haven't played in this round.
|
||||
mind = new(key)
|
||||
|
||||
mind.name = name
|
||||
mind.current = src
|
||||
mind.active = TRUE
|
||||
|
||||
gem.catch_mob(src) //This will result in us being deleted so...
|
||||
@@ -0,0 +1,189 @@
|
||||
import { useState } from 'react';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
LabeledList,
|
||||
NoticeBox,
|
||||
Section,
|
||||
Stack,
|
||||
} from 'tgui-core/components';
|
||||
|
||||
import {
|
||||
describeDroneData,
|
||||
describeMouseData,
|
||||
describeSpecialData,
|
||||
} from '../functions';
|
||||
import type { describeReturnData, GhostJoinData } from '../types';
|
||||
import { SpawnElement } from './SpawnElement';
|
||||
|
||||
export const GhostJoin = (props: { all_ghost_join_options: GhostJoinData }) => {
|
||||
const { act } = useBackend();
|
||||
const { all_ghost_join_options } = props;
|
||||
|
||||
const {
|
||||
mouse_data,
|
||||
drone_data,
|
||||
vr_data,
|
||||
may_respawn,
|
||||
ghost_banned,
|
||||
cyborg_banned,
|
||||
existing_ghost_spawnpoints,
|
||||
remaining_ghost_roles,
|
||||
special_role_respawn,
|
||||
} = all_ghost_join_options;
|
||||
|
||||
const [selectedFab, setSelectedFab] = useState('');
|
||||
const [selectedLandmark, setSelectedLandmark] = useState('');
|
||||
|
||||
const describedMouse = describeMouseData(mouse_data, !!ghost_banned);
|
||||
const describeDrone = describeDroneData(drone_data, !!cyborg_banned);
|
||||
|
||||
const specialRoles: describeReturnData[] = [];
|
||||
|
||||
specialRoles[0] = describeSpecialData(
|
||||
'Corgi',
|
||||
!!ghost_banned,
|
||||
remaining_ghost_roles,
|
||||
!!existing_ghost_spawnpoints,
|
||||
special_role_respawn,
|
||||
'corgi_spawn',
|
||||
);
|
||||
specialRoles[1] = describeSpecialData(
|
||||
'Lost Drone',
|
||||
!!cyborg_banned,
|
||||
remaining_ghost_roles,
|
||||
!!existing_ghost_spawnpoints,
|
||||
special_role_respawn,
|
||||
'lost_drone_spawn',
|
||||
);
|
||||
specialRoles[2] = describeSpecialData(
|
||||
'Maint Pred',
|
||||
!!ghost_banned,
|
||||
remaining_ghost_roles,
|
||||
!!existing_ghost_spawnpoints,
|
||||
special_role_respawn,
|
||||
'maintpred_spawn',
|
||||
);
|
||||
specialRoles[3] = describeSpecialData(
|
||||
'Gravekeeper Drone',
|
||||
!!cyborg_banned,
|
||||
remaining_ghost_roles,
|
||||
!!existing_ghost_spawnpoints,
|
||||
special_role_respawn,
|
||||
'gravekeeper_spawn',
|
||||
);
|
||||
specialRoles[4] = describeSpecialData(
|
||||
'Morph',
|
||||
!!ghost_banned,
|
||||
remaining_ghost_roles,
|
||||
!!existing_ghost_spawnpoints,
|
||||
special_role_respawn,
|
||||
'morph_spawn',
|
||||
);
|
||||
|
||||
const playerDropdown = Object.entries(drone_data.fabricators).map((entry) => {
|
||||
return { displayText: entry[1], value: entry[0] };
|
||||
});
|
||||
|
||||
const landmarkDropdown = Object.entries(vr_data.vr_landmarks).map((entry) => {
|
||||
return { displayText: entry[1], value: entry[0] };
|
||||
});
|
||||
return (
|
||||
<Stack fill vertical>
|
||||
<Stack.Item>
|
||||
<Section fill title="Spawn options">
|
||||
<Stack vertical>
|
||||
{!!vr_data.record_found && (
|
||||
<Stack.Item>
|
||||
<NoticeBox danger>
|
||||
You seem to have previously joined this round. If you are
|
||||
currently dead, you should not enter VR as this character.
|
||||
</NoticeBox>
|
||||
</Stack.Item>
|
||||
)}
|
||||
<Stack.Item grow>
|
||||
<LabeledList>
|
||||
<SpawnElement
|
||||
title="Mouse"
|
||||
disabled={!describedMouse.state || !may_respawn}
|
||||
tooltip={describedMouse.text}
|
||||
action="mouse_spawn"
|
||||
/>
|
||||
<LabeledList.Item label="Drone Spawn">
|
||||
<Stack>
|
||||
<Stack.Item basis="200px">
|
||||
<Button.Confirm
|
||||
fluid
|
||||
disabled={
|
||||
!describeDrone.state || !may_respawn || !selectedFab
|
||||
}
|
||||
color={
|
||||
describeDrone.state && may_respawn ? 'green' : 'red'
|
||||
}
|
||||
tooltip={describeDrone.text}
|
||||
tooltipPosition="top"
|
||||
onClick={() =>
|
||||
act('drone_spawn', { fabricator: selectedFab })
|
||||
}
|
||||
>
|
||||
Become Drone?
|
||||
</Button.Confirm>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Dropdown
|
||||
onSelected={(value) => setSelectedFab(value)}
|
||||
options={playerDropdown}
|
||||
selected={drone_data.fabricators[selectedFab]}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Enter VR">
|
||||
<Stack>
|
||||
<Stack.Item basis="200px">
|
||||
<Button.Confirm
|
||||
fluid
|
||||
disabled={!selectedLandmark}
|
||||
color={selectedLandmark ? 'green' : 'red'}
|
||||
tooltip="Log into NanoTrasen's local virtual reality server."
|
||||
tooltipPosition="top"
|
||||
onClick={() =>
|
||||
act('vr_spawn', { landmark: selectedLandmark })
|
||||
}
|
||||
>
|
||||
Enter VR?
|
||||
</Button.Confirm>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Dropdown
|
||||
onSelected={(value) => setSelectedLandmark(value)}
|
||||
options={landmarkDropdown}
|
||||
selected={vr_data.vr_landmarks[selectedLandmark]}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Section fill title={'Special roles (' + remaining_ghost_roles + ')'}>
|
||||
<LabeledList>
|
||||
{specialRoles.map((role) => (
|
||||
<SpawnElement
|
||||
key={role.name}
|
||||
title={role.name || ''}
|
||||
disabled={!role.state || !may_respawn}
|
||||
tooltip={role.text}
|
||||
action={role.action || ''}
|
||||
/>
|
||||
))}
|
||||
</LabeledList>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Button, LabeledList, Stack } from 'tgui-core/components';
|
||||
|
||||
export const SpawnElement = (props: {
|
||||
title: string;
|
||||
disabled: boolean;
|
||||
tooltip: string;
|
||||
action: string;
|
||||
}) => {
|
||||
const { act } = useBackend();
|
||||
const { title, disabled, tooltip, action } = props;
|
||||
|
||||
return (
|
||||
<LabeledList.Item label={title + ' Spawn'}>
|
||||
<Stack>
|
||||
<Stack.Item basis="200px">
|
||||
<Button.Confirm
|
||||
fluid
|
||||
disabled={disabled}
|
||||
color={!disabled ? 'green' : 'red'}
|
||||
tooltip={tooltip}
|
||||
tooltipPosition="top"
|
||||
onClick={() => act(action)}
|
||||
>
|
||||
{'Become ' + title + '?'}
|
||||
</Button.Confirm>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</LabeledList.Item>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Stack } from 'tgui-core/components';
|
||||
|
||||
import type { PodData } from '../types';
|
||||
import { PodSelectionList } from './PodSelectionList';
|
||||
import { SelectionList } from './SelectionList';
|
||||
|
||||
export const GhostPod = (props: {
|
||||
all_ghost_pods: PodData[];
|
||||
user_z: number;
|
||||
}) => {
|
||||
const [selectedType, setSelectedType] = useState<string>('');
|
||||
const [selectedPod, setSelectedPod] = useState<string>('');
|
||||
|
||||
const { all_ghost_pods, user_z } = props;
|
||||
|
||||
const podTypes = [...new Set(all_ghost_pods.map((pod) => pod.pod_type))].sort(
|
||||
(a, b) => a.localeCompare(b),
|
||||
);
|
||||
const selectedGhostPods = all_ghost_pods.filter(
|
||||
(ghostPod) => ghostPod.pod_type === selectedType,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (podTypes.length) {
|
||||
setSelectedType(podTypes[0]);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Stack fill>
|
||||
<Stack.Item basis="30%">
|
||||
<PodSelectionList
|
||||
podTypes={podTypes}
|
||||
selectedPod={selectedType}
|
||||
onSelectedPod={setSelectedType}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<SelectionList
|
||||
allPods={selectedGhostPods}
|
||||
userZ={user_z}
|
||||
selectedType={selectedType}
|
||||
selectedPod={selectedPod}
|
||||
onSelectedPod={setSelectedPod}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
import { useState } from 'react';
|
||||
import { Button, Input, Section, Stack } from 'tgui-core/components';
|
||||
import { createSearch } from 'tgui-core/string';
|
||||
|
||||
export const PodSelectionList = (props: {
|
||||
podTypes: string[];
|
||||
selectedPod: string;
|
||||
onSelectedPod: React.Dispatch<React.SetStateAction<string>>;
|
||||
}) => {
|
||||
const { podTypes, selectedPod, onSelectedPod } = props;
|
||||
|
||||
const [searchText, setSearchText] = useState<string>('');
|
||||
|
||||
const searcher = createSearch(searchText, (element: string) => {
|
||||
return element;
|
||||
});
|
||||
|
||||
const filtered = podTypes?.filter(searcher);
|
||||
|
||||
return (
|
||||
<Section title="Ghost Pod Types" fill>
|
||||
<Stack vertical fill>
|
||||
<Stack.Item>
|
||||
<Input
|
||||
fluid
|
||||
value={searchText}
|
||||
placeholder="Search for types..."
|
||||
onChange={(value: string) => setSearchText(value)}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item grow>
|
||||
<Section fill scrollable>
|
||||
<Stack vertical fill>
|
||||
{!!filtered &&
|
||||
filtered.map((pod_type) => (
|
||||
<Stack.Item key={pod_type}>
|
||||
<Button
|
||||
fluid
|
||||
selected={selectedPod === pod_type}
|
||||
onClick={() => onSelectedPod(pod_type)}
|
||||
>
|
||||
{pod_type}
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
))}
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,105 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Button, Input, Section, Stack } from 'tgui-core/components';
|
||||
import { createSearch } from 'tgui-core/string';
|
||||
|
||||
import type { PodData } from '../types';
|
||||
|
||||
export const SelectionList = (props: {
|
||||
allPods: PodData[];
|
||||
userZ: number;
|
||||
selectedType: string;
|
||||
selectedPod: string;
|
||||
onSelectedPod: React.Dispatch<React.SetStateAction<string>>;
|
||||
}) => {
|
||||
const { act } = useBackend();
|
||||
const { allPods, userZ, selectedType, selectedPod, onSelectedPod } = props;
|
||||
|
||||
const [searchText, setSearchText] = useState<string>('');
|
||||
const [usePlayerZ, setUsePlayerZ] = useState<boolean>(false);
|
||||
|
||||
const searcher = createSearch(searchText, (element: PodData) => {
|
||||
return element.pod_name;
|
||||
});
|
||||
|
||||
const filtered = allPods
|
||||
?.filter(searcher)
|
||||
.sort((a, b) =>
|
||||
a.remains_active === b.remains_active
|
||||
? 0
|
||||
: a.remains_active
|
||||
? -1
|
||||
: 1 || a.z_level - b.z_level,
|
||||
)
|
||||
.filter((pod) => {
|
||||
if (usePlayerZ) {
|
||||
return pod.z_level === userZ;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (filtered.length) {
|
||||
onSelectedPod(filtered[0].ref);
|
||||
}
|
||||
}, [selectedType]);
|
||||
|
||||
return (
|
||||
<Section
|
||||
title="Active Pods"
|
||||
fill
|
||||
buttons={
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<Button.Checkbox
|
||||
checked={usePlayerZ}
|
||||
onClick={() => setUsePlayerZ(!usePlayerZ)}
|
||||
>
|
||||
Player Z ({userZ})
|
||||
</Button.Checkbox>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
disabled={!selectedPod}
|
||||
onClick={() => act('select_pod', { selected_pod: selectedPod })}
|
||||
>
|
||||
Jump to Pod
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
<Stack vertical fill>
|
||||
<Stack.Item>
|
||||
<Input
|
||||
fluid
|
||||
value={searchText}
|
||||
placeholder="Search for pods..."
|
||||
onChange={(value: string) => setSearchText(value)}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item grow>
|
||||
<Section fill scrollable>
|
||||
<Stack vertical fill>
|
||||
{!!filtered &&
|
||||
filtered.map((our_pod) => (
|
||||
<Stack.Item key={our_pod.ref}>
|
||||
<Button
|
||||
fluid
|
||||
disabled={!our_pod.remains_active}
|
||||
color={our_pod.remains_active ? undefined : 'red'}
|
||||
selected={selectedPod === our_pod.ref}
|
||||
onClick={() => onSelectedPod(our_pod.ref)}
|
||||
>
|
||||
{our_pod.pod_name}
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
))}
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,139 @@
|
||||
import { useState } from 'react';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Box, Button, Input, Section, Stack } from 'tgui-core/components';
|
||||
import { createSearch } from 'tgui-core/string';
|
||||
|
||||
import type { DropdownEntry, VoreSpawnData } from '../types';
|
||||
|
||||
export const Vorespawn = (props: {
|
||||
all_vore_spawns: Record<string, VoreSpawnData>;
|
||||
}) => {
|
||||
const { act } = useBackend();
|
||||
const { all_vore_spawns } = props;
|
||||
|
||||
const [selectedPlayer, setSelectedPlayer] = useState<string>('');
|
||||
const [searchText, setSearchText] = useState<string>('');
|
||||
|
||||
const playerDropdown = Object.entries(all_vore_spawns).map((entry) => {
|
||||
return { displayText: entry[1].player, value: entry[0] };
|
||||
});
|
||||
|
||||
const allowSoulcatcher = all_vore_spawns[selectedPlayer]?.soulcatcher;
|
||||
const allowSoulcatcherVore =
|
||||
all_vore_spawns[selectedPlayer]?.soulcatcher_vore;
|
||||
const allowBellySpawn = all_vore_spawns[selectedPlayer]?.vorespawn;
|
||||
|
||||
const searcher = createSearch(searchText, (element: DropdownEntry) => {
|
||||
return element.displayText;
|
||||
});
|
||||
|
||||
const filtered = playerDropdown?.filter(searcher);
|
||||
|
||||
return (
|
||||
<Stack vertical fill>
|
||||
<Stack.Item>
|
||||
<Box color="red">
|
||||
{
|
||||
"This verb allows you to spawn inside someone's belly when they are in round. Make sure you to coordinate with your predator OOCly as well as roleplay approprietly. You are considered to have been in the belly entire time the predator was around and are not added to crew lists. This is not intended to be used for mechanical advantage or providing assistance, but for facilitating longterm scenes. Please do not abuse this ability."
|
||||
}
|
||||
</Box>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Stack fill>
|
||||
<Stack.Item basis="30%">
|
||||
<Section fill title="Player Selection">
|
||||
<Stack vertical fill>
|
||||
<Stack.Item>
|
||||
<Input
|
||||
fluid
|
||||
value={searchText}
|
||||
placeholder="Search for players..."
|
||||
onChange={(value: string) => setSearchText(value)}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item grow>
|
||||
<Section fill scrollable>
|
||||
<Stack vertical fill>
|
||||
{filtered.map((player) => (
|
||||
<Stack.Item key={player.value}>
|
||||
<Button
|
||||
fluid
|
||||
selected={player.value === selectedPlayer}
|
||||
onClick={() => setSelectedPlayer(player.value)}
|
||||
>
|
||||
{player.displayText}
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
))}
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Section fill title="Spawn Options">
|
||||
<Stack vertical>
|
||||
<Stack.Item>
|
||||
<Stack>
|
||||
<Stack.Item basis="200px">
|
||||
<Button.Confirm
|
||||
fluid
|
||||
color={allowSoulcatcher ? 'green' : 'red'}
|
||||
disabled={!allowSoulcatcher}
|
||||
onClick={() =>
|
||||
act('soulcatcher_spawn', {
|
||||
selected_player: selectedPlayer,
|
||||
})
|
||||
}
|
||||
tooltip="Select a player with a working NIF + Soulcatcher NIFSoft to join into it."
|
||||
>
|
||||
Soulcatcher
|
||||
</Button.Confirm>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack>
|
||||
<Stack.Item basis="200px">
|
||||
<Button.Confirm
|
||||
fluid
|
||||
color={allowSoulcatcherVore ? 'green' : 'red'}
|
||||
disabled={!allowSoulcatcherVore}
|
||||
onClick={() =>
|
||||
act('soulcatcher_vore_spawn', {
|
||||
selected_player: selectedPlayer,
|
||||
})
|
||||
}
|
||||
tooltip="Select a player with enabled Vore Soulcatcher to join."
|
||||
>
|
||||
Soulcatcher (Vore)
|
||||
</Button.Confirm>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack>
|
||||
<Stack.Item basis="200px">
|
||||
<Button.Confirm
|
||||
fluid
|
||||
color={allowBellySpawn ? 'green' : 'red'}
|
||||
disabled={!allowBellySpawn}
|
||||
onClick={() =>
|
||||
act('bellyspawn', { selected_player: selectedPlayer })
|
||||
}
|
||||
>
|
||||
Spawn In Belly
|
||||
</Button.Confirm>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,115 @@
|
||||
import type { describeReturnData, DroneData, MouseData } from './types';
|
||||
|
||||
export function describeMouseData(
|
||||
data: MouseData,
|
||||
banned: boolean,
|
||||
): describeReturnData {
|
||||
const returnData = { text: 'Spawn as mouse.', state: true };
|
||||
if (data.disabled) {
|
||||
returnData.text = 'Spawning as a mouse is currently disabled.';
|
||||
returnData.state = false;
|
||||
return returnData;
|
||||
}
|
||||
if (banned) {
|
||||
returnData.text =
|
||||
'You cannot become a mouse because you are banned from playing ghost roles.';
|
||||
returnData.state = false;
|
||||
return returnData;
|
||||
}
|
||||
if (data.bad_turf) {
|
||||
returnData.text = 'You may not spawn as a mouse on this Z-level.';
|
||||
returnData.state = false;
|
||||
return returnData;
|
||||
}
|
||||
if (data.respawn_time) {
|
||||
returnData.text =
|
||||
'You must wait ' +
|
||||
data.respawn_time +
|
||||
' before being able to respawn again.';
|
||||
returnData.state = false;
|
||||
return returnData;
|
||||
}
|
||||
if (!data.found_vents) {
|
||||
returnData.text = 'Unable to find any unwelded vents to spawn mice at.';
|
||||
returnData.state = false;
|
||||
return returnData;
|
||||
}
|
||||
return returnData;
|
||||
}
|
||||
|
||||
export function describeDroneData(
|
||||
data: DroneData,
|
||||
banned: boolean,
|
||||
): describeReturnData {
|
||||
const returnData = {
|
||||
text: 'If there is a powered, enabled fabricator in the game world with a prepared chassis, join as a maintenance drone.',
|
||||
state: true,
|
||||
};
|
||||
if (data.disabled) {
|
||||
returnData.text = 'Spawning as a drone is currently disabled.';
|
||||
returnData.state = false;
|
||||
return returnData;
|
||||
}
|
||||
if (banned) {
|
||||
returnData.text =
|
||||
'You are banned from playing synthetics and cannot spawn as a drone.';
|
||||
returnData.state = false;
|
||||
return returnData;
|
||||
}
|
||||
if (data.days_to_play) {
|
||||
returnData.text =
|
||||
'You have not been playing on the server long enough to join as drone.';
|
||||
returnData.state = false;
|
||||
return returnData;
|
||||
}
|
||||
if (data.respawn_time) {
|
||||
returnData.text =
|
||||
'You must wait ' +
|
||||
data.respawn_time +
|
||||
' before being able to respawn again.';
|
||||
returnData.state = false;
|
||||
return returnData;
|
||||
}
|
||||
if (!Object.keys(data.fabricators).length) {
|
||||
returnData.text = 'There are no available drone spawn points, sorry.';
|
||||
returnData.state = false;
|
||||
return returnData;
|
||||
}
|
||||
return returnData;
|
||||
}
|
||||
|
||||
export function describeSpecialData(
|
||||
role: string,
|
||||
banned: boolean,
|
||||
slots: number,
|
||||
spawn_exists: boolean,
|
||||
respawn: string,
|
||||
action: string,
|
||||
): describeReturnData {
|
||||
const text = 'Span as ' + role.toLowerCase() + '.';
|
||||
const returnData = { text: text, state: true, name: role, action: action };
|
||||
if (banned) {
|
||||
returnData.text =
|
||||
'You are banned from playing as ' + role.toLowerCase() + '.';
|
||||
returnData.state = false;
|
||||
return returnData;
|
||||
}
|
||||
if (!spawn_exists) {
|
||||
returnData.text =
|
||||
'There are no available ' + role.toLowerCase() + ' spawn points, sorry.';
|
||||
returnData.state = false;
|
||||
return returnData;
|
||||
}
|
||||
if (respawn) {
|
||||
returnData.text =
|
||||
'You must wait ' + respawn + ' before being able to respawn again.';
|
||||
returnData.state = false;
|
||||
return returnData;
|
||||
}
|
||||
if (slots < 0) {
|
||||
returnData.text = 'There are no available ghost role slots, sorry.';
|
||||
returnData.state = false;
|
||||
return returnData;
|
||||
}
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Window } from 'tgui/layouts';
|
||||
import { Stack, Tabs } from 'tgui-core/components';
|
||||
|
||||
import { GhostJoin } from './GhostJoin/GhostJoin';
|
||||
import { GhostPod } from './GhostPod/GhostPod';
|
||||
import type { Data } from './types';
|
||||
import { Vorespawn } from './Vorespawn/Vorespawn';
|
||||
|
||||
export const GhostSpawn = (props) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
const {
|
||||
all_ghost_pods,
|
||||
all_vore_spawns,
|
||||
all_ghost_join_options,
|
||||
user_z,
|
||||
active_tab,
|
||||
} = data;
|
||||
|
||||
const tabs: (React.JSX.Element | undefined)[] = [];
|
||||
|
||||
tabs[0] = all_ghost_pods && (
|
||||
<GhostPod all_ghost_pods={all_ghost_pods} user_z={user_z} />
|
||||
);
|
||||
tabs[1] = all_ghost_join_options && (
|
||||
<GhostJoin all_ghost_join_options={all_ghost_join_options} />
|
||||
);
|
||||
tabs[2] = all_vore_spawns && <Vorespawn all_vore_spawns={all_vore_spawns} />;
|
||||
|
||||
return (
|
||||
<Window width={650} height={510} theme="abstract">
|
||||
<Window.Content>
|
||||
<Stack vertical fill>
|
||||
<Stack.Item>
|
||||
<Tabs>
|
||||
<Tabs.Tab
|
||||
selected={active_tab === 0}
|
||||
onClick={() => act('set_tab', { val: 0 })}
|
||||
>
|
||||
Ghost Pods
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
selected={active_tab === 1}
|
||||
onClick={() => act('set_tab', { val: 1 })}
|
||||
>
|
||||
Ghost Join
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
selected={active_tab === 2}
|
||||
onClick={() => act('set_tab', { val: 2 })}
|
||||
>
|
||||
Vorespawn
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>{tabs[active_tab] || 'Invalid Tab Data'}</Stack.Item>
|
||||
</Stack>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
import type { BooleanLike } from 'tgui-core/react';
|
||||
|
||||
export type Data = {
|
||||
user_z: number;
|
||||
active_tab: number;
|
||||
all_ghost_pods?: PodData[];
|
||||
all_vore_spawns?: Record<string, VoreSpawnData>;
|
||||
all_ghost_join_options?: GhostJoinData;
|
||||
};
|
||||
|
||||
export type PodData = {
|
||||
pod_type: string;
|
||||
pod_name: string;
|
||||
z_level: number;
|
||||
ref: string;
|
||||
remains_active: BooleanLike;
|
||||
};
|
||||
|
||||
export type VoreSpawnData = {
|
||||
player: string;
|
||||
soulcatcher: BooleanLike;
|
||||
soulcatcher_vore: BooleanLike;
|
||||
vorespawn: BooleanLike;
|
||||
};
|
||||
|
||||
export type DropdownEntry = {
|
||||
displayText: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type GhostJoinData = {
|
||||
mouse_data: MouseData;
|
||||
drone_data: DroneData;
|
||||
vr_data: VRData;
|
||||
cyborg_banned: BooleanLike;
|
||||
ghost_banned: BooleanLike;
|
||||
may_respawn: BooleanLike;
|
||||
special_role_respawn: string;
|
||||
existing_ghost_spawnpoints: BooleanLike;
|
||||
remaining_ghost_roles: number;
|
||||
};
|
||||
|
||||
export type MouseData = {
|
||||
disabled: BooleanLike;
|
||||
bad_turf: BooleanLike;
|
||||
respawn_time: string;
|
||||
found_vents: BooleanLike;
|
||||
};
|
||||
|
||||
export type DroneData = {
|
||||
disabled: BooleanLike;
|
||||
days_to_play: number;
|
||||
respawn_time: string;
|
||||
fabricators: Record<string, string>;
|
||||
};
|
||||
|
||||
export type VRData = {
|
||||
record_found: BooleanLike;
|
||||
vr_landmarks: Record<string, string>;
|
||||
};
|
||||
|
||||
export type describeReturnData = {
|
||||
text: string;
|
||||
state: boolean;
|
||||
name?: string;
|
||||
action?: string;
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Button, Divider, Input, Section, Stack } from 'tgui-core/components';
|
||||
import { Button, Input, Section, Stack } from 'tgui-core/components';
|
||||
import { capitalize } from 'tgui-core/string';
|
||||
|
||||
export const WikiSearchList = (props: {
|
||||
@@ -41,7 +41,7 @@ export const WikiSearchList = (props: {
|
||||
onChange={(value: string) => onSearchText(value)}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Divider />
|
||||
<Stack.Divider />
|
||||
<Stack.Item grow>
|
||||
<Section fill scrollable>
|
||||
<Stack vertical fill>
|
||||
|
||||
@@ -16,8 +16,6 @@ export const IconSection = (props: {
|
||||
<Section
|
||||
title="Sprite"
|
||||
fill
|
||||
scrollable
|
||||
width="40%"
|
||||
buttons={
|
||||
<Button disabled={!sprite} onClick={() => act('confirm')}>
|
||||
Confirm
|
||||
|
||||
@@ -20,28 +20,33 @@ export const ModuleSection = (props: {
|
||||
const filtered = sortable?.filter(searcher);
|
||||
|
||||
return (
|
||||
<Section title={title} fill scrollable width="30%">
|
||||
<Stack.Item mb={'10px'}>
|
||||
<Input
|
||||
fluid
|
||||
value={searchText}
|
||||
placeholder="Search for modules..."
|
||||
onChange={(value: string) => setSearchText(value)}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack vertical fill>
|
||||
{!!filtered &&
|
||||
filtered.map((filter) => (
|
||||
<SelectorElement
|
||||
key={filter}
|
||||
option={filter}
|
||||
action="pick_module"
|
||||
selected={selected}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Section title={title} fill>
|
||||
<Stack vertical fill>
|
||||
<Stack.Item>
|
||||
<Input
|
||||
fluid
|
||||
value={searchText}
|
||||
placeholder="Search for modules..."
|
||||
onChange={(value: string) => setSearchText(value)}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item grow>
|
||||
<Section fill scrollable>
|
||||
<Stack vertical fill>
|
||||
{!!filtered &&
|
||||
filtered.map((filter) => (
|
||||
<SelectorElement
|
||||
key={filter}
|
||||
option={filter}
|
||||
action="pick_module"
|
||||
selected={selected}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -28,8 +28,6 @@ export const SpriteSection = (props: {
|
||||
<Section
|
||||
title={title}
|
||||
fill
|
||||
scrollable
|
||||
width="30%"
|
||||
buttons={
|
||||
<Stack g={0.2}>
|
||||
<Stack.Item>
|
||||
@@ -59,28 +57,33 @@ export const SpriteSection = (props: {
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
<Stack.Item mb={'10px'}>
|
||||
<Input
|
||||
fluid
|
||||
value={searchText}
|
||||
placeholder="Search for sprites..."
|
||||
onChange={(value: string) => setSearchText(value)}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack vertical>
|
||||
{!!filtered &&
|
||||
filtered.map((filter) => (
|
||||
<SelectorElement
|
||||
key={filter.sprite}
|
||||
option={filter.sprite}
|
||||
action="pick_icon"
|
||||
selected={selected}
|
||||
belly={filter.belly}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack vertical fill>
|
||||
<Stack.Item>
|
||||
<Input
|
||||
fluid
|
||||
value={searchText}
|
||||
placeholder="Search for sprites..."
|
||||
onChange={(value: string) => setSearchText(value)}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item grow>
|
||||
<Section fill scrollable>
|
||||
<Stack vertical fill>
|
||||
{!!filtered &&
|
||||
filtered.map((filter) => (
|
||||
<SelectorElement
|
||||
key={filter.sprite}
|
||||
option={filter.sprite}
|
||||
action="pick_icon"
|
||||
selected={selected}
|
||||
belly={filter.belly}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -27,23 +27,29 @@ export const RobotChoose = (props) => {
|
||||
<Window width={800} height={605} theme={theme || 'ntos'}>
|
||||
<Window.Content>
|
||||
<Stack fill>
|
||||
<ModuleSection
|
||||
title="Modules"
|
||||
sortable={possible_modules}
|
||||
selected={selected_module}
|
||||
/>
|
||||
<SpriteSection
|
||||
title="Sprites"
|
||||
sortable={possible_sprites}
|
||||
selected={sprite_datum}
|
||||
/>
|
||||
<IconSection
|
||||
currentName={currentName}
|
||||
mindName={mind_name}
|
||||
isDefaultName={isDefaultName}
|
||||
sprite={sprite_datum_class}
|
||||
size={sprite_datum_size}
|
||||
/>
|
||||
<Stack.Item basis="30%">
|
||||
<ModuleSection
|
||||
title="Modules"
|
||||
sortable={possible_modules}
|
||||
selected={selected_module}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item basis="30%">
|
||||
<SpriteSection
|
||||
title="Sprites"
|
||||
sortable={possible_sprites}
|
||||
selected={sprite_datum}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<IconSection
|
||||
currentName={currentName}
|
||||
mindName={mind_name}
|
||||
isDefaultName={isDefaultName}
|
||||
sprite={sprite_datum_class}
|
||||
size={sprite_datum_size}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
|
||||
+3
-1
@@ -480,6 +480,8 @@
|
||||
#include "code\datums\forensics_crime.dm"
|
||||
#include "code\datums\ghost_query.dm"
|
||||
#include "code\datums\ghost_query_vr.dm"
|
||||
#include "code\datums\ghost_spawn.dm"
|
||||
#include "code\datums\ghost_spawn_options.dm"
|
||||
#include "code\datums\hierarchy.dm"
|
||||
#include "code\datums\json_savefile.dm"
|
||||
#include "code\datums\mind.dm"
|
||||
@@ -2001,6 +2003,7 @@
|
||||
#include "code\modules\admin\verbs\smite.dm"
|
||||
#include "code\modules\admin\verbs\special_verbs.dm"
|
||||
#include "code\modules\admin\verbs\striketeam.dm"
|
||||
#include "code\modules\admin\verbs\tgui_verbs.dm"
|
||||
#include "code\modules\admin\verbs\tripAI.dm"
|
||||
#include "code\modules\admin\verbs\SDQL2\SDQL_2.dm"
|
||||
#include "code\modules\admin\verbs\SDQL2\SDQL_2_parser.dm"
|
||||
@@ -4450,7 +4453,6 @@
|
||||
#include "code\modules\vore\eating\soulcatcher.dm"
|
||||
#include "code\modules\vore\eating\soulcatcher_import.dm"
|
||||
#include "code\modules\vore\eating\soulcatcher_mob.dm"
|
||||
#include "code\modules\vore\eating\soulcatcher_observer.dm"
|
||||
#include "code\modules\vore\eating\special_bellies.dm"
|
||||
#include "code\modules\vore\eating\stumblevore_vr.dm"
|
||||
#include "code\modules\vore\eating\transforming_vr.dm"
|
||||
|
||||
Reference in New Issue
Block a user