Merge branch 'upstream-6-2-2026'

# Conflicts:
#	_maps/shuttles/zubbers/emergency_millionaire.dmm
This commit is contained in:
nevimer
2026-06-11 16:01:25 -04:00
1403 changed files with 51191 additions and 28938 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
var/mob/living/source_mob = src
source += " ([source_mob.ckey ? source_mob.ckey : "*no key*"])"
WRITE_FILE(F, "[time_stamp(format = "YYYY-MM-DD hh:mm:ss")] [REF(src)] ([x],[y],[z]) || [source] [message]<br>")
WRITE_FILE(F, "[server_timestamp(format = "YYYY-MM-DD hh:mm:ss")] [REF(src)] ([x],[y],[z]) || [source] [message]<br>")
ADMIN_VERB(investigate_show, R_NONE, "Investigate", "Browse various detailed logs.", ADMIN_CATEGORY_GAME)
var/static/list/investigates = list(
+85 -12
View File
@@ -1,3 +1,6 @@
#define STEALTH_MODE_TRAIT "stealth_mode"
/client/proc/add_admin_verbs()
control_freak = CONTROL_FREAK_SKIN | CONTROL_FREAK_MACROS
SSadmin_verbs.assosciate_admin(src)
@@ -27,31 +30,93 @@ ADMIN_VERB(admin_ghost, R_ADMIN, "AGhost", "Become a ghost without DNR.", ADMIN_
ghost.reenter_corpse()
BLACKBOX_LOG_ADMIN_VERB("Admin Reenter")
else if(isnewplayer(user.mob))
to_chat(user, "<font color='red'>Error: Aghost: Can't admin-ghost whilst in the lobby. Join or Observe first.</font>", confidential = TRUE)
to_chat(user, span_warning("Error: Aghost: Can't admin-ghost whilst in the lobby. Join or Observe first."), confidential = TRUE)
return FALSE
else
//ghostize
log_admin("[key_name(user)] admin ghosted.")
message_admins("[key_name_admin(user)] admin ghosted.")
var/mob/body = user.mob
body.ghostize(TRUE, TRUE)
var/mob/dead/observer/ghost = body.ghostize(TRUE, TRUE)
user.init_verbs()
if(body && !body.key)
body.key = "@[user.key]" //Haaaaaaaack. But the people have spoken. If it breaks; blame adminbus
// Carry over invisimin to their aghost
var/is_stealth_mode = user.holder.fakekey
var/is_invisimin = HAS_TRAIT_FROM(body, TRAIT_INVISIMIN, ADMIN_TRAIT)
if(is_stealth_mode || is_invisimin)
if(is_invisimin)
ADD_TRAIT(ghost, TRAIT_INVISIMIN, ADMIN_TRAIT)
ghost.SetInvisibility(INVISIBILITY_ADMIN, INVISIBILITY_SOURCE_INVISIMIN, INVISIBILITY_PRIORITY_ADMIN)
else
ghost.SetInvisibility(INVISIBILITY_ABSTRACT, INVISIBILITY_SOURCE_STEALTHMODE, INVISIBILITY_PRIORITY_ADMIN)
ghost.name = " "
ghost.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
ghost.alpha = 0
ghost.remove_from_all_data_huds()
ADD_TRAIT(ghost, TRAIT_ORBITING_FORBIDDEN, is_invisimin ? ADMIN_TRAIT : STEALTH_MODE_TRAIT)
QDEL_NULL(ghost.orbiters)
BLACKBOX_LOG_ADMIN_VERB("Admin Ghost")
ADMIN_VERB(invisimin, R_ADMIN, "Invisimin", "Toggles ghost-like invisibility.", ADMIN_CATEGORY_GAME)
// Toggle OFF
if(HAS_TRAIT(user.mob, TRAIT_INVISIMIN))
REMOVE_TRAIT(user.mob, TRAIT_INVISIMIN, ADMIN_TRAIT)
user.mob.remove_traits(list(
TRAIT_INVISIMIN,
TRAIT_ORBITING_FORBIDDEN,
TRAIT_MOVE_PHASING,
TRAIT_PIERCEIMMUNE,
TRAIT_INVISIBLE_TO_CAMERA,
), ADMIN_TRAIT)
user.mob.add_to_all_human_data_huds()
user.mob.RemoveInvisibility(INVISIBILITY_SOURCE_INVISIMIN)
to_chat(user, span_adminnotice(span_bold("Invisimin off. Invisibility reset.")), confidential = TRUE)
if(isobserver(user.mob) && !user.holder.fakekey) // Set the alpha back if we're not still stealth mode
user.mob.alpha = initial(user.mob.alpha)
return
ADD_TRAIT(user.mob, TRAIT_INVISIMIN, ADMIN_TRAIT)
// Toggle ON
user.mob.add_traits(list(
TRAIT_INVISIMIN,
TRAIT_ORBITING_FORBIDDEN,
TRAIT_MOVE_PHASING,
TRAIT_PIERCEIMMUNE,
TRAIT_INVISIBLE_TO_CAMERA,
), ADMIN_TRAIT)
user.mob.remove_from_all_data_huds()
user.mob.SetInvisibility(INVISIBILITY_OBSERVER, INVISIBILITY_SOURCE_INVISIMIN, INVISIBILITY_PRIORITY_ADMIN)
to_chat(user, span_adminnotice(span_bold("Invisimin on. You are now as invisible as a ghost.")), confidential = TRUE)
user.mob.SetInvisibility(INVISIBILITY_ADMIN, INVISIBILITY_SOURCE_INVISIMIN, INVISIBILITY_PRIORITY_ADMIN)
if(isobserver(user.mob))
user.mob.alpha = 0
QDEL_NULL(user.mob.orbiters)
to_chat(user, span_adminnotice(span_bold("Invisimin on. You are now invisible to players and ghosts.")), confidential = TRUE)
ADMIN_VERB(toggle_admin_esp, R_ADMIN, "Toggle Admin ESP", "Toggle to be able to see ghosts and invisimins.", ADMIN_CATEGORY_GAME)
// Toggle OFF
if(HAS_TRAIT(user.mob, TRAIT_ADMIN_ESP))
if(isliving(user.mob))
var/mob/living/living_user = user.mob
living_user.remove_status_effect(/datum/status_effect/admin_esp)
else if(isobserver(user.mob))
user.mob.set_invis_see(SEE_INVISIBLE_OBSERVER)
else
user.mob.set_invis_see(SEE_INVISIBLE_LIVING)
REMOVE_TRAIT(user.mob, TRAIT_ADMIN_ESP, ADMIN_TRAIT)
to_chat(user.mob, span_adminnotice("Admin ESP off. You will no longer see [isliving(user.mob) ? "ghosts or " : ""]invisimins."), confidential = TRUE)
return
// Toggle ON
if(isliving(user.mob))
var/mob/living/living_user = user.mob
living_user.apply_status_effect(/datum/status_effect/admin_esp)
else if(ismob(user.mob))
user.mob.set_invis_see(SEE_INVISIBLE_ADMIN)
else
to_chat(user.mob, span_warning("Admin ESP only works if you are in a mob!"), confidential = TRUE)
return
ADD_TRAIT(user.mob, TRAIT_ADMIN_ESP, ADMIN_TRAIT)
to_chat(user.mob, span_adminnotice("Admin ESP on. You will now be able to see [isliving(user.mob) ? "ghosts and " : ""]invisimins."), confidential = TRUE)
ADMIN_VERB(check_antagonists, R_ADMIN, "Check Antagonists", "See all antagonists for the round.", ADMIN_CATEGORY_GAME)
user.holder.check_antagonists()
@@ -141,8 +206,6 @@ ADMIN_VERB(stealth, R_STEALTH, "Stealth Mode", "Toggle stealth.", ADMIN_CATEGORY
BLACKBOX_LOG_ADMIN_VERB("Stealth Mode")
#define STEALTH_MODE_TRAIT "stealth_mode"
/client/proc/enable_stealth_mode()
var/new_key = ckeyEx(stripped_input(usr, "Enter your desired display name.", "Fake Key", key, 26))
if(!new_key)
@@ -165,7 +228,8 @@ ADMIN_VERB(stealth, R_STEALTH, "Stealth Mode", "Toggle stealth.", ADMIN_CATEGORY
holder.fakekey = null
if(isobserver(mob))
mob.RemoveInvisibility(INVISIBILITY_SOURCE_STEALTHMODE)
mob.alpha = initial(mob.alpha)
if(!HAS_TRAIT_FROM(mob, TRAIT_INVISIMIN, ADMIN_TRAIT)) // Don't reset our alpha if we're also invisimin'd
mob.alpha = initial(mob.alpha)
if(mob.mind)
if(mob.mind.ghostname)
mob.name = mob.mind.ghostname
@@ -180,8 +244,6 @@ ADMIN_VERB(stealth, R_STEALTH, "Stealth Mode", "Toggle stealth.", ADMIN_CATEGORY
log_admin("[key_name(usr)] has turned stealth mode OFF")
message_admins("[key_name_admin(usr)] has turned stealth mode OFF")
#undef STEALTH_MODE_TRAIT
ADMIN_VERB(drop_bomb, R_FUN, "Drop Bomb", "Cause an explosion of varying strength at your location", ADMIN_CATEGORY_FUN)
var/list/choices = list("Small Bomb (1, 2, 3, 3)", "Medium Bomb (2, 3, 4, 4)", "Big Bomb (3, 5, 7, 5)", "Maxcap", "Custom Bomb")
var/choice = tgui_input_list(user, "What size explosion would you like to produce? NOTE: You can do all this rapidly and in an IC manner (using cruise missiles!) with the Config/Launch Supplypod verb. WARNING: These ignore the maxcap", "Drop Bomb", choices)
@@ -803,11 +865,18 @@ ADMIN_VERB(give_ai_speech, R_FUN, "Give Random AI Speech", ADMIN_VERB_NO_DESCRIP
return
our_controller.planning_subtrees = list(GLOB.ai_subtrees[/datum/ai_planning_subtree/random_speech/blackboard]) + our_controller.planning_subtrees
ADMIN_VERB(open_event_logger, R_DEBUG, "Open Event Logger", "Open the event logger interface.", ADMIN_CATEGORY_DEBUG)
GLOB.event_logger.ui_interact(user.mob)
ADMIN_VERB(new_blackmarket_item, R_BUILD, "Create Black Market Item", "Add an item to the black market for purchase.", ADMIN_CATEGORY_EVENTS, object as text)
if(!object)
to_chat(user, span_boldwarning("Failed! Provide a full or partial typepath!"))
return
//first: have admins select a typepath for the item they want to offer.
var/obj/chosen = pick_closest_path(object, make_types_fancy(subtypesof(/obj)))
// second: poll admins for the name, description, price, and quantity.
if(isnull(chosen))
return
var/name = tgui_input_text(user, "Name of the item to sell?", "Item listing name", "Arcane Object", max_length = MAX_NAME_LEN)
if(isnull(name))
return
@@ -842,5 +911,9 @@ ADMIN_VERB(new_blackmarket_item, R_BUILD, "Create Black Market Item", "Add an it
SSmarket.initialize_admin_item(admin_item)
log_admin("[key_name(user)] created a new black market item: [name] ([chosen]) for [price] credits, of quantity [quantity].")
message_admins("[key_name(user)] created a new black market item: [name] ([chosen]) for [price] credits, of quantity [quantity].")
BLACKBOX_LOG_ADMIN_VERB("Create Black Market Item")
#undef STEALTH_MODE_TRAIT
+551
View File
@@ -0,0 +1,551 @@
/// Global event logger datum, accessible anywhere for debug logging.
GLOBAL_DATUM_INIT(event_logger, /datum/event_logger, new())
/// Enables event logging for a datum. If display_on is supplied then it will report the events as if theyre happening on that datum
/datum/proc/enable_evlogging(datum/display_on = null)
if(display_on)
GLOB.event_logger.display_map[REF(src)] = REF(display_on)
if(datum_flags & DF_EVLOGGING)
return
datum_flags |= DF_EVLOGGING
SEND_SIGNAL(src, COMSIG_EVLOGGING_ENABLED)
/// Disables event logging for a datum. If display_on was supplied during enable_evlogging, it will also remove the display mapping.
/datum/proc/disable_evlogging()
GLOB.event_logger.display_map -= REF(src)
if(!(datum_flags & DF_EVLOGGING))
return
datum_flags &= ~DF_EVLOGGING
SEND_SIGNAL(src, COMSIG_EVLOGGING_DISABLED)
///Datum that holds info for a single track of events in the logger. Each track represents a datum that is being tracked. This track is shown in the timeline and when selected shows information on the track.
/datum/event_logger_track
/// Display name for this track row.
var/name
/// String ref key used to identify this track.
var/ref
/// List of event assoc lists: id, tick, category, kind, info, + kind-specific fields.
var/list/events = list()
/// Assoc list of string (Title) -> string (Info) displayed in the info panel when this track is selected.
var/list/info = list()
/datum/event_logger_track/New(track_ref, track_name, list/info_data = list())
ref = track_ref
name = track_name
info = info_data
/datum/event_logger_track/Destroy()
events = null
info = null
return ..()
///Datum for the event logger. One of these is made, and everyone shares it. So be nice. This keeps tracks of everything related to the logger
/datum/event_logger
/// Whether the logger is actively recording.
var/running = FALSE
/// world.time at which logging first started (null until first start).
var/time_start = null
/// Assoc list: category name -> enabled (TRUE/FALSE).
var/list/categories = list()
/// Assoc list: category name -> hex color, assigned when a category is first seen.
var/list/category_colors = list()
/// List of colors we use for categories. We loop around once we run out
var/list/category_palette = list(
"#4fc3f7", "#81c784", "#ffb74d", "#e57373", "#ba68c8",
"#4dd0e1", "#fff176", "#f06292", "#a1887f", "#90a4ae",
)
/// Assoc list: ref string -> /datum/event_logger_track
var/list/tracks = list()
/// Assoc list: string event id -> event assoc list, for quick look-up when selecting events
var/list/events_by_id = list()
/// Incrementing id assigned to each logged event.
var/next_event_id = 0
/// Ref string of the currently selected track (for the info panel).
var/selected_ref = null
/// Assoc list: mob -> list of /image, for overlay cleanup.
var/list/user_overlays = list()
/// The mob currently waiting to click a target for pick-target mode, or null.
var/mob/awaiting_pick_user = null
/// Assoc list: REF(source datum) -> REF(display datum). Events from the source appear under the display datum's track.
var/list/display_map = list()
/datum/event_logger/Destroy()
_clear_all_overlays()
QDEL_LIST_ASSOC_VAL(tracks)
tracks = null
categories = null
user_overlays = null
display_map = null
return ..()
/datum/event_logger/proc/start()
if(running)
return
running = TRUE
if(isnull(time_start))
time_start = world.time
/datum/event_logger/proc/stop()
running = FALSE
/// Enter pick-target mode: the next atom the user clicks gets DF_EVLOGGING set.
/datum/event_logger/proc/toggle_pick_target(mob/user)
if(awaiting_pick_user)
_end_pick_target()
else
awaiting_pick_user = user
RegisterSignal(user, COMSIG_MOB_CLICKON, PROC_REF(on_pick_target_click))
/// Signal handler: fired when the user clicks something while in pick-target mode.
/datum/event_logger/proc/on_pick_target_click(mob/source, atom/clicked, list/modifiers)
SIGNAL_HANDLER
clicked.enable_evlogging()
_end_pick_target()
return NONE
/// Ends pick-target mode, unregistering the click signal and clearing the awaiting_pick_user var.
/datum/event_logger/proc/_end_pick_target()
if(isnull(awaiting_pick_user))
return
UnregisterSignal(awaiting_pick_user, COMSIG_MOB_CLICKON)
awaiting_pick_user = null
/// Ensures a track exists for ref_string. Safe to call multiple times.
/datum/event_logger/proc/add_track(ref_string, track_name, list/info_data)
if(tracks[ref_string])
return
tracks[ref_string] = new /datum/event_logger_track(ref_string, track_name, info_data)
/// Internal: Sets the event up, making a track if necesary. Also turns the instance into a REF() at this point
/datum/event_logger/proc/_add_event(datum/source, list/event_data)
if(!running)
return
// Resolve display routing: if source has a display_on target, log under that datum's track instead
var/datum/display_target
var/display_ref = display_map[REF(source)]
if(display_ref)
display_target = locate(display_ref)
if(!display_target) // display_on was deleted, remove the stale entry. Yeah this is a bit lame, but I felt like registering QDEL might be more of a hassle if we're using REF() anyway. Feel free to @ me over it
display_map -= REF(source)
var/datum/track_datum = display_target || source
var/ref_string = REF(track_datum)
if(!tracks[ref_string])
var/track_name
if(isatom(track_datum))
var/atom/resolved_atom = track_datum
track_name = "[ref_string] [resolved_atom] [track_datum.type]"
else
track_name = "[ref_string] [track_datum.type]"
add_track(ref_string, track_name, null)
var/datum/event_logger_track/track = tracks[ref_string]
event_data["id"] = ++next_event_id
event_data["tick"] = world.time
var/category = event_data["category"]
if(!isnull(category) && isnull(categories[category]))
categories[category] = TRUE
var/selected_index = (length(category_colors) + 1) % (length(category_palette) + 1)
category_colors[category] = category_palette[selected_index]
track.events += list(event_data)
events_by_id["[event_data["id"]]"] = event_data
SEND_SIGNAL(source, COMSIG_EVLOG_EVENT_ADDED, track, event_data)
if(display_target)
SEND_SIGNAL(display_target, COMSIG_EVLOG_EVENT_ADDED, track, event_data)
/// Log a plain text event. Has no world-visuals, just puts text into the menu
/datum/event_logger/proc/log_event_text(datum/source, category, info_string)
_add_event(source, list(
"log_type" = EVLOG_TYPE_TEXT,
"category" = category,
"info" = info_string,
))
/// Log a location event (highlights a single tile). I should remove this one as turfs does the same thing essentially
/datum/event_logger/proc/log_event_location(datum/source, category, info_string, turf/T)
_add_event(source, list(
"log_type" = EVLOG_TYPE_LOCATION,
"category" = category,
"info" = info_string,
"x" = T.x,
"y" = T.y,
"z" = T.z,
))
/// Log a turfs event (highlights a set of tiles).
/datum/event_logger/proc/log_event_turfs(datum/source, category, info_string, list/turfs)
var/list/coords = list()
for(var/turf/T as anything in turfs)
coords += list(list("x" = T.x, "y" = T.y, "z" = T.z))
_add_event(source, list(
"log_type" = EVLOG_TYPE_TURFS,
"category" = category,
"info" = info_string,
"coords" = coords,
))
/// Log a line event (draws a line between 2 turfs).
/datum/event_logger/proc/log_event_lines(datum/source, category, info_string, turf/A, turf/B)
_add_event(source, list(
"log_type" = EVLOG_TYPE_LINES,
"category" = category,
"info" = info_string,
"x1" = A.x,
"y1" = A.y,
"z1" = A.z,
"x2" = B.x,
"y2" = B.y,
"z2" = B.z,
))
/// Log a path event (renders directional arrows + start/end markers for a list of turfs in order from start to finish).
/datum/event_logger/proc/log_event_path(datum/source, category, info_string, list/turfs)
var/list/coords = list()
for(var/turf/T as anything in turfs)
coords += list(list("x" = T.x, "y" = T.y, "z" = T.z))
_add_event(source, list(
"log_type" = EVLOG_TYPE_PATH,
"category" = category,
"info" = info_string,
"coords" = coords,
))
/// Log a maptext event (renders a floating text label at the turf when selected). text_string is the string to display at the turf.
/datum/event_logger/proc/log_event_maptext(datum/source, category, info_string, turf/T, text_string)
_add_event(source, list(
"log_type" = EVLOG_TYPE_MAPTEXT,
"category" = category,
"info" = info_string,
"x" = T.x,
"y" = T.y,
"z" = T.z,
"text" = text_string,
))
/// Remove all overlays for a specific user.
/datum/event_logger/proc/_clear_user_overlays(mob/user)
if(!user_overlays[user])
return
if(user.client)
user.client.images -= user_overlays[user]
user_overlays -= user
/// Remove all overlays for all users.
/datum/event_logger/proc/_clear_all_overlays()
for(var/mob/user as anything in user_overlays)
_clear_user_overlays(user)
/// Push world-space highlight overlays to the user's client for the given events.
/datum/event_logger/proc/_apply_event_overlays(mob/user, list/events_to_show)
_clear_user_overlays(user)
if(!user.client || !length(events_to_show))
return
var/list/images = list()
for(var/list/evt as anything in events_to_show)
var/color = get_category_color(evt["category"])
var/log_type = evt["log_type"]
if(log_type == EVLOG_TYPE_LOCATION)
var/turf/found_turf = locate(evt["x"], evt["y"], evt["z"])
if(found_turf)
images += _make_tile_image(found_turf, "box_overlay", color, 0.3)
else if(log_type == EVLOG_TYPE_TURFS)
var/list/coords = evt["coords"]
for(var/list/coord as anything in coords)
var/turf/found_turf = locate(coord["x"], coord["y"], coord["z"])
if(found_turf)
images += _make_tile_image(found_turf, "box_overlay", color, 0.3)
else if(log_type == EVLOG_TYPE_LINES)
var/turf/turf_A = locate(evt["x1"], evt["y1"], evt["z1"])
var/turf/turf_B = locate(evt["x2"], evt["y2"], evt["z2"])
if(turf_A && turf_B && turf_A != turf_B)
images += _make_line_images(turf_A, turf_B, color)
//Use SSPathfinder to render the full path
else if(log_type == EVLOG_TYPE_PATH)
var/list/path_turfs = list()
var/list/coords = evt["coords"]
for(var/list/coord as anything in coords)
var/turf/found_turf = locate(coord["x"], coord["y"], coord["z"])
if(found_turf)
path_turfs += found_turf
images += SSpathfinder.render_path_images_full(path_turfs)
//Draw maptext on a turf which lets us show important events in-world
else if(log_type == EVLOG_TYPE_MAPTEXT)
var/turf/found_turf = locate(evt["x"], evt["y"], evt["z"])
if(found_turf)
var/image/img = image('icons/turf/debug.dmi', found_turf, "circle", PATH_DEBUG_LAYER)
SET_PLANE_EXPLICIT(img, BALLOON_CHAT_PLANE, found_turf)
img.color = color
img.maptext = MAPTEXT(evt["text"])
img.maptext_width = 200
img.maptext_height = 64
img.maptext_x = 0
img.maptext_y = 32
images += img
if(length(images))
user_overlays[user] = images
user.client.images += images
///Draw a line of images similar to beams but client-side. I couldn't find anything like this yet so here we are. Maybe making this a global is a good idea.
/datum/event_logger/proc/_make_line_images(turf/turf_A, turf/turf_B, color)
var/list/images = list()
var/beam_icon = 'icons/turf/debug.dmi'
var/beam_icon_state = "beam"
var/origin_px = turf_A.pixel_x + turf_A.pixel_w
var/origin_py = turf_A.pixel_y + turf_A.pixel_z
var/target_px = turf_B.pixel_x + turf_B.pixel_w
var/target_py = turf_B.pixel_y + turf_B.pixel_z
var/Angle = get_angle_raw(turf_A.x, turf_A.y, origin_px, origin_py, turf_B.x, turf_B.y, target_px, target_py)
var/matrix/rot_matrix = matrix()
rot_matrix.Turn(Angle)
var/DX = (ICON_SIZE_X * turf_B.x + target_px) - (ICON_SIZE_X * turf_A.x + origin_px)
var/DY = (ICON_SIZE_Y * turf_B.y + target_py) - (ICON_SIZE_Y * turf_A.y + origin_py)
var/line_length = round(sqrt(DX ** 2 + DY ** 2))
for(var/N in 0 to line_length - 1 step 32)
var/Pixel_x
var/Pixel_y
if(DX == 0)
Pixel_x = 0
else
Pixel_x = round(sin(Angle) + ICON_SIZE_X * sin(Angle) * (N + 16) / 32)
if(DY == 0)
Pixel_y = 0
else
Pixel_y = round(cos(Angle) + ICON_SIZE_Y * cos(Angle) * (N + 16) / 32)
var/final_x = turf_A.x
var/final_y = turf_A.y
if(abs(Pixel_x) > ICON_SIZE_X)
final_x += Pixel_x > 0 ? round(Pixel_x / ICON_SIZE_X) : ceil(Pixel_x / ICON_SIZE_X)
Pixel_x %= ICON_SIZE_X
if(abs(Pixel_y) > ICON_SIZE_Y)
final_y += Pixel_y > 0 ? round(Pixel_y / ICON_SIZE_Y) : ceil(Pixel_y / ICON_SIZE_Y)
Pixel_y %= ICON_SIZE_Y
var/turf/seg_turf = locate(final_x, final_y, turf_A.z)
if(!seg_turf)
continue
var/image/img = image(beam_icon, seg_turf, beam_icon_state, PATH_DEBUG_LAYER)
SET_PLANE_EXPLICIT(img, BALLOON_CHAT_PLANE, seg_turf)
if(N + 32 > line_length)
// Terminal segment: crop the icon to avoid overshooting the target
var/icon/clipped = new(beam_icon, beam_icon_state)
clipped.DrawBox(null, 1, (line_length - N), 32, 32)
img.icon = clipped
img.color = color
img.transform = rot_matrix
img.pixel_x = origin_px + Pixel_x
img.pixel_y = origin_py + Pixel_y
images += img
return images
/// Creates a single coloured tile overlay image.
/datum/event_logger/proc/_make_tile_image(turf/selected_turf, icon_state, color, alpha_fraction)
var/image/img = image('icons/turf/debug.dmi', selected_turf, icon_state, PATH_DEBUG_LAYER)
SET_PLANE_EXPLICIT(img, BALLOON_CHAT_PLANE, selected_turf)
img.color = color
img.alpha = round(alpha_fraction * 255)
return img
/// Returns the hex color string for a category, or white if unknown.
/datum/event_logger/proc/get_category_color(category)
return category_colors[category] || "#ffffff"
/// Clears all tracks, categories and overlays.
/datum/event_logger/proc/clear()
_clear_all_overlays()
QDEL_LIST_ASSOC_VAL(tracks)
tracks = list()
events_by_id = list()
categories = list()
category_colors = list()
next_event_id = 0
selected_ref = null
time_start = null
running = FALSE
///Tgui stuff below!!
/datum/event_logger/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/simple/chat_dark),
)
/datum/event_logger/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "EventLogger", "Event Logger")
ui.open()
/datum/event_logger/ui_close(mob/user)
. = ..()
_clear_user_overlays(user)
if(awaiting_pick_user == user)
_end_pick_target()
/datum/event_logger/ui_state(mob/user)
return ADMIN_STATE(R_DEBUG)
/datum/event_logger/ui_data(mob/user)
var/list/data = list()
data["running"] = running
data["time_start"] = time_start
data["time_current"] = world.time
data["selected_ref"] = selected_ref
data["awaiting_pick"] = !isnull(awaiting_pick_user)
// Categories
var/list/cats = list() //meow
for(var/cat, enabled in categories)
cats += list(list("name" = cat, "enabled" = enabled))
data["categories"] = cats
// Tracks
var/list/track_list = list()
for(var/ref, track_val in tracks)
var/datum/event_logger_track/track = track_val
// Converts track info to a list for tgui
var/list/info_pairs = list()
for(var/title, entry in track.info)
info_pairs += list(list("title" = title, "entry" = entry))
track_list += list(list(
"name" = track.name,
"ref" = track.ref,
"events" = track.events,
"info" = info_pairs,
))
data["tracks"] = track_list
return data
/datum/event_logger/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
var/mob/user = ui.user
switch(action)
if("toggle_running") //Start or stop the logger
if(running)
stop()
else
start()
return TRUE
if("toggle_category") //Enable categories
var/cat = params["name"]
if(!isnull(categories[cat]))
categories[cat] = !categories[cat]
return TRUE
if("select_track") //Select a track on the timeline
selected_ref = params["ref"]
return TRUE
if("select_events") ///We selected events on the timeline, clear old overlays, add new ones.
var/list/ids = params["ids"]
if(!islist(ids) || !length(ids))
_clear_user_overlays(user)
return TRUE
var/list/matched = list()
for(var/id in ids)
var/list/evt = events_by_id["[id]"]
if(evt)
matched += list(evt)
_apply_event_overlays(user, matched)
return TRUE
if("clear")
clear()
return TRUE
if("start_pick_target")
toggle_pick_target(user)
return TRUE
if("disable_evlogging")
var/track_ref = params["ref"]
var/datum/target = locate(track_ref)
if(!target)
return FALSE
if(tgui_alert(user, "Stop tracking [target]?", "Confirm", list("Stop Tracking", "Cancel")) != "Stop Tracking")
return FALSE
target.disable_evlogging()
return TRUE
if("remove_track") // Removes a track and all its events from the logger entirely, and stops tracking the datum
var/track_ref = params["ref"]
var/datum/event_logger_track/track = tracks[track_ref]
if(!track)
return FALSE
// Remove all events from events_by_id
for(var/list/evt as anything in track.events)
events_by_id -= "[evt["id"]]"
// Disable evlogging on the datum if it still exists
var/datum/target = locate(track_ref)
if(target)
target.disable_evlogging()
// Clear overlays and deselect if this was the selected track
_clear_user_overlays(user)
if(selected_ref == track_ref)
selected_ref = null
qdel(track)
tracks -= track_ref
return TRUE
if("teleport_to_event")
var/target_id = params["id"]
var/list/found_evt = events_by_id["[target_id]"]
if(!found_evt)
return FALSE
var/dest_x
var/dest_y
var/dest_z
switch(found_evt["log_type"])
if(EVLOG_TYPE_LOCATION, EVLOG_TYPE_MAPTEXT)
dest_x = found_evt["x"]
dest_y = found_evt["y"]
dest_z = found_evt["z"]
if(EVLOG_TYPE_TURFS, EVLOG_TYPE_PATH)
var/list/coords = found_evt["coords"]
if(!length(coords))
return FALSE
var/list/first = coords[1]
dest_x = first["x"]
dest_y = first["y"]
dest_z = first["z"]
if(EVLOG_TYPE_LINES)
dest_x = found_evt["x1"]
dest_y = found_evt["y1"]
dest_z = found_evt["z1"]
else
return FALSE
var/turf/dest = locate(dest_x, dest_y, dest_z)
if(!dest)
return FALSE
user.forceMove(dest)
return TRUE
+1 -1
View File
@@ -348,7 +348,7 @@ This is highly likely to cause massive amounts of lag as every object in the gam
return
while(initial(current.greyscale_config) == initial(parent.greyscale_config))
current = parent
parent = type2parent(current)
parent = current::parent_type
config_owner_type = current
/// Used for spray painting items in the gags_recolorable component
+5
View File
@@ -36,6 +36,7 @@ GLOBAL_PROTECT(href_token)
var/datum/particle_editor/particle_test
var/datum/colorblind_tester/color_test
var/datum/plane_master_debug/plane_debug
var/datum/appearance_debugger/appearance_debug
var/obj/machinery/computer/libraryconsole/admin_only_do_not_map_in_you_fucker/library_manager
var/datum/pathfind_debug/path_debug
var/datum/spawn_menu/spawn_menu
@@ -98,6 +99,7 @@ GLOBAL_PROTECT(href_token)
GLOB.admin_datums[target] = src
deadmined = FALSE
plane_debug = new(src)
appearance_debug = new(src)
if (GLOB.directory[target])
associate(GLOB.directory[target]) //find the client for a ckey if they are connected and associate them with us
@@ -109,6 +111,7 @@ GLOBAL_PROTECT(href_token)
GLOB.deadmins[target] = src
GLOB.admin_datums -= target
QDEL_NULL(plane_debug)
QDEL_NULL(appearance_debug)
QDEL_NULL(path_debug)
deadmined = TRUE
@@ -119,6 +122,7 @@ GLOBAL_PROTECT(href_token)
add_verb(client, /client/proc/readmin)
client.disable_combo_hud()
client.update_special_keybinds()
client.set_stat_panel()
/datum/admins/proc/associate(client/client)
if(IsAdminAdvancedProcCall())
@@ -159,6 +163,7 @@ GLOBAL_PROTECT(href_token)
owner.init_verbs() //re-initialize the verb list
owner.update_special_keybinds()
GLOB.admins |= client
client.set_stat_panel()
try_give_profiling()
+20
View File
@@ -0,0 +1,20 @@
/datum/smite/hitsplat
name = "Hitsplat"
/datum/smite/hitsplat/effect(client/user, mob/living/target)
. = ..()
target.AddComponent(/datum/component/hitsplat, /obj/effect/overlay/vis/hitsplat/lore_accurate)
// Dragon dagger spec spam.
/datum/smite/hitsplat/stackout
name = "Stackout (Hitsplat)"
/datum/smite/hitsplat/stackout/effect(client/user, mob/living/target)
. = ..()
for(var/attack in 1 to 4)
playsound(target, SFX_SWING_HIT, 50, TRUE)
target.apply_damage(rand(0, 50))
target.apply_damage(rand(0, 50))
// dragon dagger has a 4 tick attack
sleep(2.4 SECONDS)
@@ -96,8 +96,8 @@
pod = new()
for(var/i in 1 to amount)
if(istype(atom_to_spawn, /turf))
var/turf/original_turf = target
if(ispath(atom_to_spawn, /turf))
var/turf/original_turf = get_turf(target) //Get_turf is required here, because if target is not a turf we are cooked chat.
var/turf/created_turf = original_turf.ChangeTurf(atom_to_spawn.type)
if(created_turf && atom_name)
created_turf.name = atom_name
@@ -196,9 +196,9 @@
admin_client.mouse_pointer_icon = admin_client.mouse_override_icon
admin_client.click_intercept = src
winset(admin_client, "mapwindow.map", "right-click=true")
winset(admin_client, SKIN_MAPWINDOW_MAP, "right-click=true")
else
winset(admin_client, "mapwindow.map", "right-click=false")
winset(admin_client, SKIN_MAPWINDOW_MAP, "right-click=false")
var/mob/holder_mob = admin_client.mob
holder_mob?.update_mouse_pointer()
+1
View File
@@ -426,6 +426,7 @@
ROLE_MALF,
ROLE_NINJA,
ROLE_OPERATIVE,
ROLE_CLOWN_OPERATIVE,
ROLE_OVERTHROW,
ROLE_PARADOX_CLONE,
ROLE_REV,
+1 -1
View File
@@ -311,7 +311,7 @@ ADMIN_VERB(access_news_network, R_ADMIN, "Access Newscaster Network", "Allows yo
var/datum/feed_comment/new_feed_comment = new /datum/feed_comment
new_feed_comment.author = "Centcom Official"
new_feed_comment.body = comment_text
new_feed_comment.time_stamp = station_time_timestamp()
new_feed_comment.time_stamp = round_timestamp()
current_message.comments += new_feed_comment
GLOB.news_network.last_action ++
usr.log_message("(as an admin) commented on message [current_message.return_body(-1)] -- [current_message.body]", LOG_COMMENT)
+2 -1
View File
@@ -315,7 +315,7 @@ ADMIN_VERB(respawn_character, R_ADMIN, "Respawn Character", "Respawn a player th
if(record_found)//If they have a record we can determine a few things.
new_character.real_name = record_found.name
new_character.gender = LOWER_TEXT(record_found.gender)
new_character.gender = record_found.gender
new_character.age = record_found.age
var/datum/dna/found_dna = record_found.locked_dna
new_character.hardset_dna(found_dna.unique_identity, found_dna.mutation_index, null, record_found.name, record_found.blood_type, new record_found.species_type, found_dna.features)
@@ -514,6 +514,7 @@ ADMIN_VERB(lag_switch_panel, R_ADMIN, "Show Lag Switches", "Display the controls
dat += "<br/><b>Measures below can be bypassed with a <abbr title='TRAIT_BYPASS_MEASURES'><u>special trait</u></abbr></b><br/>"
dat += "Slowmode say verb (informs world): <a href='byond://?_src_=holder;[HrefToken()];change_lag_switch=[SLOWMODE_SAY]'><b>[SSlag_switch.measures[SLOWMODE_SAY] ? "On" : "Off"]</b></a><br/>"
dat += "Disable runechat: <a href='byond://?_src_=holder;[HrefToken()];change_lag_switch=[DISABLE_RUNECHAT]'><b>[SSlag_switch.measures[DISABLE_RUNECHAT] ? "On" : "Off"]</b></a> - <span style='font-size:80%'>trait applies to speaker</span><br/>"
dat += "Disable dead runechat: <a href='byond://?_src_=holder;[HrefToken()];change_lag_switch=[DISABLE_DEAD_RUNECHAT]'><b>[SSlag_switch.measures[DISABLE_DEAD_RUNECHAT] ? "On" : "Off"]</b></a> - <span style='font-size:80%'>trait applies to speaker</span><br/>"
dat += "Disable examine icons: <a href='byond://?_src_=holder;[HrefToken()];change_lag_switch=[DISABLE_USR_ICON2HTML]'><b>[SSlag_switch.measures[DISABLE_USR_ICON2HTML] ? "On" : "Off"]</b></a> - <span style='font-size:80%'>trait applies to examiner</span><br/>"
dat += "Disable parallax: <a href='byond://?_src_=holder;[HrefToken()];change_lag_switch=[DISABLE_PARALLAX]'><b>[SSlag_switch.measures[DISABLE_PARALLAX] ? "On" : "Off"]</b></a> - <span style='font-size:80%'>trait applies to character</span><br />"
dat += "Disable footsteps: <a href='byond://?_src_=holder;[HrefToken()];change_lag_switch=[DISABLE_FOOTSTEPS]'><b>[SSlag_switch.measures[DISABLE_FOOTSTEPS] ? "On" : "Off"]</b></a> - <span style='font-size:80%'>trait applies to character</span><br />"
+7 -7
View File
@@ -291,7 +291,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
"PLAYERS" = player_count,
"ROUND STATE" = round_state,
"ROUND ID" = GLOB.round_id,
"ROUND TIME" = ROUND_TIME(),
"ROUND TIME" = round_timestamp(),
"MESSAGE" = message,
"ADMINS" = admin_text,
)
@@ -377,9 +377,9 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
heard_by_no_admins = FALSE
send2adminchat(initiator_ckey, "Ticket #[id]: Answered by [key_name(usr)]")
ticket_interactions += "[time_stamp()]: [formatted_message]"
ticket_interactions += "[server_timestamp()]: [formatted_message]"
if (!isnull(player_message))
player_interactions += "[time_stamp()]: [player_message]"
player_interactions += "[server_timestamp()]: [player_message]"
//Removes the ahelp verb and returns it after 2 minutes
/datum/admin_help/proc/TimeoutVerb()
@@ -610,9 +610,9 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
dat += "[FOURSPACES][TicketHref("Refresh", ref_src)][FOURSPACES][TicketHref("Re-Title", ref_src, "retitle")]"
if(state != AHELP_ACTIVE)
dat += "[FOURSPACES][TicketHref("Reopen", ref_src, "reopen")]"
dat += "<br><br>Opened at: [gameTimestamp(wtime = opened_at)] (Approx [DisplayTimeText(world.time - opened_at)] ago)"
dat += "<br><br>Opened at: [round_timestamp(wtime = opened_at)] (Approx [DisplayTimeText(world.time - opened_at)] ago)"
if(closed_at)
dat += "<br>Closed at: [gameTimestamp(wtime = closed_at)] (Approx [DisplayTimeText(world.time - closed_at)] ago)"
dat += "<br>Closed at: [round_timestamp(wtime = closed_at)] (Approx [DisplayTimeText(world.time - closed_at)] ago)"
dat += "<br><br>"
if(initiator)
dat += "<b>Actions:</b> [FullMonty(ref_src)]<br>"
@@ -725,9 +725,9 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
else
dat += "UNKNOWN</b>"
dat += "\n[FOURSPACES]<A href='byond://?_src_=usr;player_ticket_panel=1'>Refresh</A>"
dat += "<br><br>Opened at: [gameTimestamp("hh:mm:ss", opened_at)] (Approx [DisplayTimeText(world.time - opened_at)] ago)"
dat += "<br><br>Opened at: [round_timestamp("hh:mm:ss", opened_at)] (Approx [DisplayTimeText(world.time - opened_at)] ago)"
if(closed_at)
dat += "<br>Closed at: [gameTimestamp("hh:mm:ss", closed_at)] (Approx [DisplayTimeText(world.time - closed_at)] ago)"
dat += "<br>Closed at: [round_timestamp("hh:mm:ss", closed_at)] (Approx [DisplayTimeText(world.time - closed_at)] ago)"
dat += "<br><br>"
dat += "<br><b>Log:</b><br><br>"
for (var/interaction in player_interactions)
@@ -0,0 +1,243 @@
/// Allows developers to see a breakdown of an atom or a specific target and edit some of the values
/datum/appearance_debugger
var/datum/admins/owner
/// Currently debugged atom or mutable appearance, hence /datum
var/datum/debug_target
/// A list of copies of the currently debugged appearance and its children for access from the UI
var/list/mutable_appearance/appearance_copies
/// Assoc list of ref -> appearance as to prevent refreshing of dynamic appearances
var/list/mutable_appearance/appearance_cache
/// Mapview used to display the hovered over appearance
var/atom/movable/screen/map_view/proxy_view_hover
/// Mapview used to display the selected appearance
var/atom/movable/screen/map_view/proxy_view_selected
/// Assoc list of ref -> list of atoms to be displayed in said ref atom's vis_contents
var/list/atom/movable/fake_vis
/// Should we display a popup that the appearance in-game has updated?
var/update_warning = FALSE
/datum/appearance_debugger/New(datum/admins/owner)
src.owner = owner
proxy_view_hover = new()
proxy_view_hover.generate_view("appearance_debugger_[REF(owner)]_proxy_hover")
proxy_view_selected = new()
proxy_view_selected.generate_view("appearance_debugger_[REF(owner)]_proxy_selected")
/datum/appearance_debugger/Destroy()
if (owner)
owner.appearance_debug = null
owner = null
if (fake_vis)
for (var/ref_id in fake_vis)
var/list/all_fakes = fake_vis[ref_id]
QDEL_LIST(all_fakes)
fake_vis.Cut()
QDEL_NULL(proxy_view_hover)
QDEL_NULL(proxy_view_selected)
return ..()
/datum/appearance_debugger/proc/get_appearance_data(atom/appearance_owner)
var/mutable_appearance/target = appearance_owner
if (isatom(appearance_owner))
target = appearance_cache["[REF(appearance_owner)]"] || new /mutable_appearance(appearance_owner.appearance)
appearance_cache["[REF(appearance_owner)]"] = target
if (!fake_vis["[REF(appearance_owner)]"] && (ismovable(appearance_owner) || isturf(appearance_owner)))
var/atom/movable/as_movable = appearance_owner
var/list/false_vis_contents = list()
fake_vis["[REF(appearance_owner)]"] = false_vis_contents
for (var/atom/something in (isturf(as_movable) ? (as_movable.vis_contents + as_movable.contents) : as_movable.vis_contents))
var/atom/movable/mimic = new()
mimic.appearance = new /mutable_appearance(something.appearance)
false_vis_contents += mimic
var/list/data = list(
"type" = isatom(appearance_owner) ? "atom" : (isimage(appearance_owner) ? "image" : "appearance"),
"alpha" = target.alpha,
"flags" = target.appearance_flags,
"blend_mode" = target.blend_mode,
"color" = target.color,
"dir" = target.dir,
"icon" = length("[target.icon]") ? "[target.icon]" : null,
"icon_state" = target.icon_state,
"invisibility" = target.invisibility,
"layer" = target.layer,
"name" = target.name,
"maptext" = target.maptext,
"maptext_width" = target.maptext_width,
"maptext_height" = target.maptext_height,
"maptext_x" = target.maptext_x,
"maptext_y" = target.maptext_y,
"mouse_opacity" = target.mouse_opacity,
"pixel_x" = target.pixel_x,
"pixel_y" = target.pixel_y,
"pixel_w" = target.pixel_w,
"pixel_z" = target.pixel_z,
"plane" = target.plane,
"plane_true" = PLANE_TO_TRUE(target.plane),
"render_source" = target.render_source,
"render_target" = target.render_target,
"screen_loc" = target.screen_loc,
)
if (!(target in appearance_copies))
appearance_copies += target
data["id"] = length(appearance_copies)
else
data["id"] = appearance_copies.Find(target)
var/list/filter_data = list()
for (var/list/our_filter as anything in target.filter_data)
filter_data += list(our_filter)
data["filters"] = filter_data
var/list/underlay_data = list()
for (var/mutable_appearance/underlay as anything in target.underlays)
underlay_data += list(get_appearance_data(underlay))
data["underlays"] = underlay_data
var/list/overlay_data = list()
for (var/mutable_appearance/overlay as anything in target.overlays)
overlay_data += list(get_appearance_data(overlay))
data["overlays"] = overlay_data
// Display previews if it is either an instance icon or a file and we have icon_state set
if ((isicon(target.icon) && !isfile(target.icon)) || (target.icon && target.icon_state))
var/icon/used_icon = icon(target.icon, target.icon_state, (isimage(target) && target.dir) ? target.dir : SOUTH, frame = 1)
if (istext(target.color))
used_icon.Blend(target.color, ICON_MULTIPLY)
data["embed_icon"] = icon2base64(used_icon)
data["transform"] = list(target.transform.a, target.transform.b, target.transform.c, target.transform.d, target.transform.e, target.transform.f)
// Turfs aren't movables but they still can have vis_flags/contents so lets just count them as such
if (ismovable(appearance_owner) || isturf(appearance_owner))
var/atom/movable/as_movable = appearance_owner
data["vis_flags"] = as_movable.vis_flags
// Maybe should be cached but I'm too lazy and don't think this'll matter enough
var/list/vis_data = list()
// Turfs also inherit their contents as vis_contents
for (var/atom/vis_thing as anything in isturf(as_movable) ? (as_movable.vis_contents + as_movable.contents) : as_movable.vis_contents)
vis_data += list(get_appearance_data(vis_thing))
data["vis_contents"] = vis_data
// Handle all dynamically modified layers
if (target.layer > FLOOR_EMISSIVE_START_LAYER && target.layer < FLOOR_EMISSIVE_END_LAYER)
data["layer_text_override"] = "FLOOR_EMISSIVE_LAYER (+[target.layer - FLOOR_EMISSIVE_START_LAYER])"
else if (target.layer > GAS_PIPE_HIDDEN_LAYER && target.layer < GAS_PIPE_HIDDEN_LAYER + 0.006)
data["layer_text_override"] = "GAS_PIPE_HIDDEN_LAYER (+[target.layer - GAS_PIPE_HIDDEN_LAYER])"
else if (target.layer > GAS_PIPE_VISIBLE_LAYER && target.layer < GAS_PIPE_VISIBLE_LAYER + 0.006)
data["layer_text_override"] = "GAS_PIPE_VISIBLE_LAYER (+[target.layer - GAS_PIPE_VISIBLE_LAYER])"
else if (target.layer > PLUMBING_PIPE_VISIBILE_LAYER && target.layer < PLUMBING_PIPE_VISIBILE_LAYER + (FIFTH_DUCT_LAYER * 2 / 3333))
data["layer_text_override"] = "PLUMBING_PIPE_VISIBILE_LAYER (+[target.layer - PLUMBING_PIPE_VISIBILE_LAYER])"
return data
/datum/appearance_debugger/ui_data(mob/user)
return list(
"updateWarning" = update_warning,
)
/datum/appearance_debugger/ui_static_data(mob/user)
return list(
"mainAppearance" = get_appearance_data(debug_target),
"planeToText" = GLOB.admin_readable_planes,
"layerToText" = GLOB.admin_readable_layers,
"flagsToText" = get_valid_bitflags("appearance_flags"),
"visToText" = get_valid_bitflags("vis_flags"),
"blendToText" = GLOB.blend_names,
"mapRefHover" = proxy_view_hover.assigned_map,
"mapRefSelected" = proxy_view_selected.assigned_map,
)
/datum/appearance_debugger/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
switch(action)
if("swapMapViewHover")
var/appearance_id = text2num(params["id"])
proxy_view_hover.appearance = appearance_copies[appearance_id]
for (var/atom/movable/something as anything in proxy_view_hover.vis_contents)
proxy_view_hover.vis_contents -= something
for (var/ref_id in appearance_cache)
if (appearance_copies[appearance_id] != appearance_cache[ref_id])
continue
if (fake_vis[ref_id])
proxy_view_hover.vis_contents |= fake_vis[ref_id]
// Needs screenloc to be set since we're setting the appearance and carrying over target's screenloc
proxy_view_hover.set_position(1, 1)
if("swapMapViewSelected")
var/appearance_id = text2num(params["id"])
proxy_view_selected.appearance = appearance_copies[appearance_id]
for (var/atom/movable/something as anything in proxy_view_selected.vis_contents)
proxy_view_selected.vis_contents -= something
for (var/ref_id in appearance_cache)
if (appearance_copies[appearance_id] != appearance_cache[ref_id])
continue
if (fake_vis[ref_id])
proxy_view_selected.vis_contents |= fake_vis[ref_id]
proxy_view_selected.set_position(1, 1)
if("refreshAppearance")
update_warning = FALSE
appearance_copies = list()
appearance_cache = list()
for (var/ref_id in fake_vis)
var/list/all_fakes = fake_vis[ref_id]
QDEL_LIST(all_fakes)
fake_vis = list()
update_static_data_for_all_viewers()
if("vvAppearance")
if (!check_rights(R_VAREDIT))
return
var/appearance_id = text2num(params["id"])
usr.client.debug_variables(appearance_copies[appearance_id])
/datum/appearance_debugger/ui_assets(mob/user)
return list(get_asset_datum(/datum/asset/simple/plane_background))
/datum/appearance_debugger/ui_state(mob/user)
return ADMIN_STATE(R_DEBUG)
/datum/appearance_debugger/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "AppearanceDebug")
ui.open()
proxy_view_hover.display_to(user, ui.window)
proxy_view_selected.display_to(user, ui.window)
/datum/appearance_debugger/ui_close(mob/user)
. = ..()
// Reset appearances when the UI is closed
proxy_view_hover.appearance = new /mutable_appearance()
proxy_view_selected.appearance = new /mutable_appearance()
proxy_view_hover.vis_contents.Cut()
proxy_view_selected.vis_contents.Cut()
if (isatom(debug_target))
UnregisterSignal(debug_target, COMSIG_ATOM_UPDATE_APPEARANCE)
/datum/appearance_debugger/proc/set_target(mutable_appearance/new_target)
if (isatom(debug_target))
UnregisterSignal(debug_target, COMSIG_ATOM_UPDATE_APPEARANCE)
update_warning = FALSE
// Can be an atom!
debug_target = new_target
if (isatom(debug_target))
RegisterSignal(debug_target, COMSIG_ATOM_UPDATE_APPEARANCE, PROC_REF(warn_update))
appearance_copies = list()
appearance_cache = list()
for (var/ref_id in fake_vis)
var/list/all_fakes = fake_vis[ref_id]
QDEL_LIST(all_fakes)
fake_vis = list()
update_static_data_for_all_viewers()
/datum/appearance_debugger/proc/warn_update()
SIGNAL_HANDLER
update_warning = TRUE
+2 -14
View File
@@ -103,22 +103,10 @@ GLOBAL_DATUM_INIT(pathfind_dude, /obj/pathfind_guy, new())
return
/datum/action/innate/path_debug/proc/render_path(list/turf/draw_list)
if(!length(draw_list))
return list()
var/list/image/turf_images = list()
// Render everything but the first and last
for(var/i in 1 to (length(draw_list) - 1))
var/turf/problem_child = draw_list[i]
var/turf/next = draw_list[i + 1]
turf_images += render_turf(problem_child, get_dir(problem_child, next))
return turf_images
return SSpathfinder.render_path_images(draw_list)
/datum/action/innate/path_debug/proc/render_turf(turf/draw, direction)
var/image/arrow = image('icons/turf/debug.dmi', draw, "arrow", PATH_ARROW_DEBUG_LAYER, direction)
SET_PLANE_EXPLICIT(arrow, BALLOON_CHAT_PLANE, draw)
return arrow
return SSpathfinder.render_path_arrow(draw, direction)
/datum/action/innate/path_debug/jps
name = "JPS Test"
+19 -20
View File
@@ -1,51 +1,50 @@
/mob/verb/pray(msg as text)
set category = "IC"
set name = "Pray"
/mob/verb/pray(message as text)
set name = VERB_PRAY
if(GLOB.say_disabled) //This is here to try to identify lag problems
to_chat(usr, span_danger("Speech is currently admin-disabled."), confidential = TRUE)
to_chat(src, span_danger("Speech is currently admin-disabled."), confidential = TRUE)
return
msg = copytext_char(sanitize(msg), 1, MAX_MESSAGE_LEN)
if(!msg)
message = copytext_char(sanitize(message), 1, MAX_MESSAGE_LEN)
if(!message)
return
log_prayer("[src.key]/([src.name]): [msg]")
if(usr.client)
if(usr.client.prefs.muted & MUTE_PRAY)
to_chat(usr, span_danger("You cannot pray (muted)."), confidential = TRUE)
log_prayer("[src.key]/([src.name]): [message]")
if(src.client)
if(src.client.prefs.muted & MUTE_PRAY)
to_chat(src, span_danger("You cannot pray (muted)."), confidential = TRUE)
return
if(src.client.handle_spam_prevention(msg,MUTE_PRAY))
if(src.client.handle_spam_prevention(message, MUTE_PRAY))
return
var/mutable_appearance/cross = mutable_appearance('icons/obj/storage/book.dmi', "bible")
var/font_color = "purple"
var/prayer_type = "PRAYER"
var/deity
if(usr.job == JOB_CHAPLAIN)
if(src.job == JOB_CHAPLAIN)
cross.icon_state = "kingyellow"
font_color = "blue"
prayer_type = "CHAPLAIN PRAYER"
if(GLOB.deity)
deity = GLOB.deity
else if(IS_CULTIST(usr))
else if(IS_CULTIST(src))
cross.icon_state = "tome"
font_color = "red"
prayer_type = "CULTIST PRAYER"
deity = "Nar'Sie"
else if(isliving(usr))
var/mob/living/L = usr
else if(isliving(src))
var/mob/living/L = src
if(HAS_TRAIT(L, TRAIT_SPIRITUAL))
cross.icon_state = "holylight"
font_color = "blue"
prayer_type = "SPIRITUAL PRAYER"
var/msg_tmp = msg
GLOB.requests.pray(usr.client, msg, usr.job == JOB_CHAPLAIN)
msg = span_adminnotice("[icon2html(cross, GLOB.admins)]<b><font color=[font_color]>[prayer_type][deity ? " (to [deity])" : ""]: </font>[ADMIN_FULLMONTY(src)] [ADMIN_SC(src)]:</b> [span_linkify(msg)]")
var/msg_tmp = message
GLOB.requests.pray(src.client, message, src.job == JOB_CHAPLAIN)
message = span_adminnotice("[icon2html(cross, GLOB.admins)]<b><font color=[font_color]>[prayer_type][deity ? " (to [deity])" : ""]: </font>[ADMIN_FULLMONTY(src)] [ADMIN_SC(src)]:</b> [span_linkify(message)]")
for(var/client/C in GLOB.admins)
if(get_chat_toggles(C) & CHAT_PRAYER)
to_chat(C, msg, type = MESSAGE_TYPE_PRAYER, confidential = TRUE)
to_chat(usr, span_info("You pray to the gods: \"[msg_tmp]\""), confidential = TRUE)
to_chat(C, message, type = MESSAGE_TYPE_PRAYER, confidential = TRUE)
to_chat(src, span_info("You pray to the gods: \"[msg_tmp]\""), confidential = TRUE)
BLACKBOX_LOG_ADMIN_VERB("Prayer")
+21 -15
View File
@@ -163,21 +163,6 @@ ADMIN_VERB(secrets, R_NONE, "Secrets", "Abuse harder than you ever have before w
log_admin("[key_name(holder)] reset the station name.")
message_admins(span_adminnotice("[key_name_admin(holder)] reset the station name."))
priority_announce("[command_name()] has renamed the station to \"[new_name]\".")
if("night_shift_set")
var/val = tgui_alert(holder, "What do you want to set night shift to? This will override the automatic system until set to automatic again.", "Night Shift", list("On", "Off", "Automatic"))
switch(val)
if("Automatic")
if(CONFIG_GET(flag/enable_night_shifts))
SSnightshift.can_fire = TRUE
SSnightshift.fire()
else
SSnightshift.update_nightshift(active = FALSE, announce = TRUE, forced = TRUE)
if("On")
SSnightshift.can_fire = FALSE
SSnightshift.update_nightshift(active = TRUE, announce = TRUE, forced = TRUE)
if("Off")
SSnightshift.can_fire = FALSE
SSnightshift.update_nightshift(active = FALSE, announce = TRUE, forced = TRUE)
if("moveferry")
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Send CentCom Ferry"))
if(!SSshuttle.toggleShuttle("ferry","ferry_home","ferry_away"))
@@ -703,6 +688,27 @@ ADMIN_VERB(secrets, R_NONE, "Secrets", "Abuse harder than you ever have before w
return
return
if("meteormode")
if(!is_funmin)
return
if(GLOB.meteor_mode)
QDEL_NULL(GLOB.meteor_mode)
message_admins("[key_name_admin(holder)] disabled meteor mode.")
return TRUE
GLOB.meteor_mode = new()
var/start_when = tgui_input_number(usr, "Meteors will start in (this many seconds):", "Meteor Mode: Start", (5 MINUTES) / 10, (24 HOURS) / 10, 0)
var/ramp_speed = tgui_input_number(usr, "Meteors will worsen every (this many seconds):", "Meteor Mode: Intensity", (5 MINUTES) / 10, (24 HOURS) / 10, (30 SECONDS) / 10)
if(!is_funmin) // deadminnied?
QDEL_NULL(GLOB.meteor_mode)
return TRUE
GLOB.meteor_mode.meteordelay = world.time + (start_when * 10)
GLOB.meteor_mode.rampupdelta = ramp_speed / 60
GLOB.meteor_mode.start_meteor()
message_admins("[key_name_admin(holder)] enabled meteor mode. \
Meteors will start [start_when ? "in [DisplayTimeText(start_when * 10)]" : "immediately"], and will worsen every [DisplayTimeText(ramp_speed * 10)].")
return TRUE
if("fix_gravity")
if(!is_funmin)
return
+1 -1
View File
@@ -11,7 +11,7 @@ GLOBAL_DATUM_INIT(sprite_auditor, /datum/sprite_auditor, new)
"name" = author.real_name,
"ckey" = author.ckey,
"appearance" = icon_appearance,
"timestamp" = gameTimestamp(),
"timestamp" = round_timestamp(),
)))
SStgui.update_uis(src)
@@ -64,6 +64,10 @@
var/value = vars[var_name]
return "<li style='backgroundColor:white'>(READ ONLY) [var_name] = [_debug_variable_value(var_name, value, 0, src, sanitize = TRUE, display_flags = NONE)]</li>"
/mutable_appearance/vv_get_dropdown()
. = ..()
VV_DROPDOWN_OPTION(VV_HK_DEBUG_APPEARANCE, "Debug Appearance")
/mutable_appearance/appearance_mirror/vv_get_dropdown()
SHOULD_CALL_PARENT(FALSE)
@@ -74,6 +78,7 @@
VV_DROPDOWN_OPTION(VV_HK_TAG, "Tag Datum")
VV_DROPDOWN_OPTION(VV_HK_DELETE, "Delete")
VV_DROPDOWN_OPTION(VV_HK_EXPOSE, "Show VV To Player")
VV_DROPDOWN_OPTION(VV_HK_DEBUG_APPEARANCE, "Debug Appearance")
/proc/get_vv_appearance(mutable_appearance/appearance) // actually appearance yadeeyada
return new /mutable_appearance/appearance_mirror(appearance)
@@ -103,20 +103,14 @@
return "<a href='byond://?_src_=vars;[HrefToken()];[link_vars]'>/list ([list_value.len])</a>"
// if it's a number, is it a bitflag?
var/list/valid_bitflags
if(!isnum(name))
valid_bitflags = get_valid_bitflags(name)
var/list/matching_bitflags = get_matching_bitflags(name, value)
if(!length(valid_bitflags))
return "<span class='value'>[VV_HTML_ENCODE(value)]</span>"
if(!isnull(matching_bitflags))
if(length(matching_bitflags))
return "[VV_HTML_ENCODE(matching_bitflags.Join(", "))]"
return "NONE"
var/list/flags = list()
for (var/bit_name in valid_bitflags)
if (value & valid_bitflags[bit_name])
flags += bit_name
if(length(flags))
return "[VV_HTML_ENCODE(flags.Join(", "))]"
return "NONE"
return "<span class='value'>[VV_HTML_ENCODE(value)]</span>"
/datum/proc/debug_variable_value(name, level, datum/owner, sanitize, display_flags)
if("[src]" != "[type]") // If we have a name var, let's use it.
@@ -19,6 +19,7 @@ GLOBAL_ALIST_EMPTY(reftracker_skip_typecache_b)
/icon,
/matrix,
/regex,
/atom/movable/lighting_object, // only contains turf and MA refs
/atom/movable/mirage_holder,
/atom/movable/render_step/emissive_blocker,
/datum/armor,
@@ -31,7 +32,6 @@ GLOBAL_ALIST_EMPTY(reftracker_skip_typecache_b)
/datum/greyscale_layer,
/datum/icon_transformer,
/datum/instrument_key,
/datum/lighting_object, // only contains turf and MA refs
/datum/movespeed_modifier,
/datum/painting,
/datum/paper_input,
@@ -139,7 +139,7 @@
return
to_chat(user, span_notice("You activate [src] and wait for confirmation."))
var/mob/chosen_one = SSpolling.poll_ghost_candidates("Do you want to play as a reinforcement [special_role_name]?", check_jobban = ROLE_OPERATIVE, role = ROLE_OPERATIVE, poll_time = 15 SECONDS, ignore_category = POLL_IGNORE_SYNDICATE, alert_pic = src, role_name_text = special_role_name, amount_to_pick = 1)
var/mob/chosen_one = SSpolling.poll_ghost_candidates("Do you want to play as a reinforcement [special_role_name]?", check_jobban = ROLE_OPERATIVE_MIDROUND, role = ROLE_OPERATIVE_MIDROUND, poll_time = 15 SECONDS, ignore_category = POLL_IGNORE_SYNDICATE, alert_pic = src, role_name_text = special_role_name, amount_to_pick = 1)
if(chosen_one)
if(QDELETED(src) || !check_usability(user))
return
@@ -185,8 +185,8 @@
/obj/item/antag_spawner/nuke_ops/overwatch/Initialize(mapload)
. = ..()
if(length(GLOB.nukeop_overwatch_start)) //Otherwise, it will default to the datum's spawn point anyways
spawn_location = pick(GLOB.nukeop_overwatch_start)
if(length(GLOB.nukeop_base_overwatch_start)) //Otherwise, it will default to the datum's spawn point anyways
spawn_location = pick(GLOB.nukeop_base_overwatch_start)
//////CLOWN OP
/obj/item/antag_spawner/nuke_ops/clown
@@ -323,8 +323,6 @@
var/pod_style = /datum/pod_style/syndicate
/// Do we use a random subtype of the outfit?
var/use_subtypes = TRUE
/// The antag role we check if the ghosts have enabled to get the poll.
var/poll_role_check = ROLE_TRAITOR
/// The mind's special role.
var/role_to_play = ROLE_SYNDICATE_MONKEY
/// What category to ignore the poll
@@ -351,8 +349,7 @@
to_chat(user, span_notice("You activate [src] and wait for confirmation."))
var/mob/chosen_one = SSpolling.poll_ghost_candidates(
check_jobban = poll_role_check,
role = poll_role_check,
check_jobban = role_to_play,
poll_time = 10 SECONDS,
ignore_category = poll_ignore_category,
alert_pic = src,
@@ -409,7 +406,7 @@
outfit = /datum/outfit/contractor_partner
use_subtypes = FALSE
antag_datum = /datum/antagonist/traitor/contractor_support
poll_ignore_category = ROLE_TRAITOR
poll_ignore_category = POLL_IGNORE_CONTRACTOR_SUPPORT
role_to_play = ROLE_CONTRACTOR_SUPPORT
/obj/item/antag_spawner/loadout/contractor/do_special_things(mob/living/carbon/human/contractor_support, mob/user)
@@ -426,9 +423,8 @@
outfit = /datum/outfit/syndicate_monkey
antag_datum = /datum/antagonist/syndicate_monkey
use_subtypes = FALSE
poll_role_check = ROLE_TRAITOR
role_to_play = ROLE_SYNDICATE_MONKEY
poll_ignore_category = POLL_IGNORE_SYNDICATE
poll_ignore_category = POLL_IGNORE_SYNDICATE_MONKEY
fail_text = "Unable to connect to the Animal Rights Consortium's Banana Ops. Please wait and try again later or use the beacon on your uplink to get your points refunded."
/obj/item/antag_spawner/loadout/monkey_man/do_special_things(mob/living/carbon/human/monkey_man, mob/user)
+32 -14
View File
@@ -61,6 +61,9 @@ GLOBAL_LIST_EMPTY(blob_nodes)
/// The list of strains the blob can reroll for.
var/list/strain_choices
/// Adminbus potential - if this is set to FALSE, the blob won't end the round upon reaching critical mass.
var/end_round_on_victory = TRUE
/mob/eye/blob/Initialize(mapload, starting_points = OVERMIND_STARTING_POINTS)
ADD_TRAIT(src, TRAIT_BLOB_ALLY, INNATE_TRAIT)
validate_location()
@@ -160,16 +163,8 @@ GLOBAL_LIST_EMPTY(blob_nodes)
qdel(src)
else if(!victory_in_progress && (blobs_legit.len >= blobwincount))
victory_in_progress = TRUE
priority_announce("Biohazard has reached critical mass. Station loss is imminent.", "Biohazard Alert")
SSsecurity_level.set_level(SEC_LEVEL_DELTA)
begin_victory()
// Set status displays to biohazard alert - critical level
send_status_display_biohazard_alert()
max_blob_points = INFINITY
blob_points = INFINITY
addtimer(CALLBACK(src, PROC_REF(victory)), 45 SECONDS)
else if(!free_strain_rerolls && (last_reroll_time + BLOB_POWER_REROLL_FREE_TIME<world.time))
to_chat(src, span_boldnotice("You have gained another free strain re-roll."))
free_strain_rerolls = 1
@@ -199,6 +194,22 @@ GLOBAL_LIST_EMPTY(blob_nodes)
/mob/eye/blob/proc/clear_biohazard_display()
clear_status_display_biohazard()
/// Announce the blob's victory! Tell everyone that they're about to explode and/or turn into biomass soup and give the overmind a victory lap.
/mob/eye/blob/proc/begin_victory()
victory_in_progress = TRUE
priority_announce("Biohazard has reached critical mass. Station loss is imminent.", "Biohazard Alert")
SSsecurity_level.set_level(SEC_LEVEL_DELTA)
// Set status displays to biohazard alert - critical level
send_status_display_biohazard_alert()
if(end_round_on_victory) // Assuming they actually *are* about to turn the station into soup...
max_blob_points = INFINITY
blob_points = INFINITY
else
to_chat(src, span_blob("You've reached critical mass, but something feels terribly wrong, stopping you from expanding further. All you can do now is fight as long as you can..."))
addtimer(CALLBACK(src, PROC_REF(victory)), 45 SECONDS)
/// Actually *do* the blob's victory: give them their greentext and, depending on the end_round_on_victory variable, decide if everyone dies or if it's just a jumpscare.
/mob/eye/blob/proc/victory()
// Set victory flags immediately
var/datum/antagonist/blob/B = mind.has_antag_datum(/datum/antagonist/blob)
@@ -207,13 +218,20 @@ GLOBAL_LIST_EMPTY(blob_nodes)
if(main_objective)
main_objective.completed = TRUE
to_chat(world, span_blobannounce("[real_name] consumed the station in an unstoppable tide!"))
SSticker.news_report = BLOB_WIN
SSticker.force_ending = FORCE_END_ROUND
if(end_round_on_victory)
to_chat(world, span_blobannounce("[real_name] consumed the station in an unstoppable tide!"))
SSticker.news_report = BLOB_WIN
SSticker.force_ending = FORCE_END_ROUND
// Handle the heavy victory operations asynchronously
INVOKE_ASYNC(src, PROC_REF(victory_sequence))
// Handle the heavy victory operations (where everyone dies) asynchronously
INVOKE_ASYNC(src, PROC_REF(victory_sequence))
else
// Is the station going boom? No. But is the station still going to get yelled at? Yes.
priority_announce("Experimental, classified, and very expensive emergency countermeasures have been activated to prevent total station loss, \
but the initial failure to contain the viral biohazard will be noted on the station's performance report. Expect further penalties.", \
"Emergency Biohazard Countermeasure Alert")
/// Kill everyone who's still on the station area and not already part of the blob's faction, and cover every station area with blob icons. Everyone's soup now.
/mob/eye/blob/proc/victory_sequence()
sound_to_playing_players('sound/announcer/alarm/nuke_alarm.ogg', 70)
sleep(10 SECONDS)
@@ -157,7 +157,19 @@
O.do_attack_animation(A) //visually attack the whatever
return O //just in case you want to do something to the animation.
/obj/structure/blob/proc/expand(turf/T = null, controller = null, expand_reaction = 1)
/// Can this blob structure make further blobs? For special cases (e.g. blobs not going past crit mass count if not ending the round)
/obj/structure/blob/proc/can_make_blob(mob/eye/blob/controller = null)
// If it's not being done with a controller, it's being done automatically. Because it's not player-controlled, it's not worth worrying about.
if(!controller)
return TRUE
// If it's not supposed to end the round and it's at the win count, don't make more. (400 tiles is still a lot to fight through...)
if(!controller.end_round_on_victory && (controller.blobs_legit.len >= controller.blobwincount))
balloon_alert(controller, "max tiles reached!")
return FALSE
// Otherwise, it's probably fine.
return TRUE
/obj/structure/blob/proc/expand(turf/T = null, mob/eye/blob/controller = null, expand_reaction = 1)
if(!T)
var/list/dirs = list(1,2,4,8)
for(var/i = 1 to 4)
@@ -172,6 +184,9 @@
return
var/make_blob = TRUE //can we make a blob?
if(!can_make_blob(controller))
make_blob = FALSE
if(isspaceturf(T) && !(locate(/obj/structure/lattice) in T) && prob(80))
make_blob = FALSE
playsound(src.loc, 'sound/effects/splat.ogg', 50, TRUE) //Let's give some feedback that we DID try to spawn in space, since players are used to it
@@ -407,3 +407,9 @@
/obj/structure/blood_worm_cocoon/adult/examine(mob/user)
return ..() + span_warning("It can be broken to prevent the blood worm from reproducing, but it looks extremely tough.")
/datum/action/cooldown/mob_cooldown/blood_worm/cocoon/hatchling/polymorph
new_worm_type = /mob/living/basic/blood_worm/juvenile/polymorph
/datum/action/cooldown/mob_cooldown/blood_worm/cocoon/juvenile/polymorph
new_worm_type = /mob/living/basic/blood_worm/adult/polymorph
@@ -22,6 +22,7 @@
RegisterSignal(host, COMSIG_LIVING_ADJUST_OXY_DAMAGE, PROC_REF(on_host_adjust_oxy_damage))
RegisterSignal(host, COMSIG_LIVING_PRE_UPDATE_BLOOD_STATUS, PROC_REF(on_host_pre_update_blood_status))
RegisterSignal(host, COMSIG_MOB_GET_STATUS_TAB_ITEMS, PROC_REF(on_host_get_status_tab_items))
client?.set_stat_panel()
RegisterSignal(host, COMSIG_MOB_EXAMINING, PROC_REF(on_host_examining))
START_PROCESSING(SSfastprocess, src)
@@ -118,6 +119,7 @@
COMSIG_MOB_EXAMINING,
COMSIG_MOB_HUD_CREATED,
))
client?.set_stat_panel()
STOP_PROCESSING(SSfastprocess, src)
@@ -398,3 +398,12 @@
transfuse_action = /datum/action/cooldown/mob_cooldown/blood_worm/inject/adult
regen_rate = 0.5 // 360 seconds to recover from 0 to 180, or exactly 6 minutes.
/mob/living/basic/blood_worm/hatchling/polymorph
cocoon_action = /datum/action/cooldown/mob_cooldown/blood_worm/cocoon/hatchling/polymorph
/mob/living/basic/blood_worm/juvenile/polymorph
cocoon_action = /datum/action/cooldown/mob_cooldown/blood_worm/cocoon/juvenile/polymorph
/mob/living/basic/blood_worm/adult/polymorph
cocoon_action = null
@@ -42,6 +42,7 @@
var/list/ability_data = list(
"name" = initial(ability_path.name),
"desc" = initial(ability_path.desc),
"icon" = initial(ability_path.button_icon_state),
"helptext" = initial(ability_path.helptext),
"path" = ability_path,
"genetic_point_required" = dna_cost,
@@ -216,20 +216,16 @@
if(href_list["exit_hivemind"] && !QDELETED(src))
exit_hivemind()
/mob/eye/imaginary_friend/hivemind/verb/exit_hivemind()
set category = "IC"
set name = "Exit Hivemind"
set desc = "Leave the hivemind behind and enter the land of the dead."
/mob/eye/imaginary_friend/hivemind/proc/exit_hivemind()
var/response = tgui_alert(src, "Are you sure you want to exit the hivemind? \
You can't re-enter it, though you can still be revived.", "Confirm Exit", list("Exit", "Stay"))
if(response != "Exit" || QDELETED(src))
return
ghostize(TRUE)
/mob/eye/imaginary_friend/hivemind/ghostize(can_reenter_corpse, admin_ghost)
/mob/eye/imaginary_friend/hivemind/ghostize(can_reenter_corpse, forced)
. = ..()
if(admin_ghost)
if(forced)
return
alert_hivemind("You sense the presence of [real_name] disappear from the hivemind.")
@@ -478,7 +478,7 @@
return BULLET_ACT_HIT
/obj/projectile/tentacle/Destroy()
qdel(chain)
QDEL_NULL(chain)
source = null
return ..()
@@ -1,5 +1,5 @@
/datum/action/changeling/sting//parent path, not meant for users afaik
name = "Tiny Prick" //cellularemporium uses `nameToIconState` to button icon state must match this, on top of matching the hud below.
name = "Tiny Prick"
desc = "Stabby stabby"
category = "stings"
button_icon_state = "sting_null" //This must be equal to the icon state for `/atom/movable/screen/ling/sting`
+1 -1
View File
@@ -9,7 +9,7 @@
/datum/outfit/cultist/post_equip(mob/living/carbon/human/equipped, visuals_only)
equipped.set_eye_color(BLOODCULT_EYE)
equipped.update_body()
equipped.update_eyes()
///Returns whether the given mob is convertable to the blood cult
/proc/is_convertable_to_cult(mob/living/target, datum/team/cult/specific_cult)
@@ -1,6 +1,5 @@
/// Some defines for items the daemon forge can create.
#define NARSIE_ARMOR "Nar'Sien Hardened Armor"
#define FLAGELLANT_ARMOR "Flagellant's Robe"
#define ELDRITCH_SWORD "Eldritch Longsword"
#define CURSED_BLADE "Cursed Ritual Blade"
@@ -8,7 +7,7 @@
/obj/structure/destructible/cult/item_dispenser/forge
name = "daemon forge"
desc = "A forge used in crafting the unholy weapons used by the armies of Nar'Sie."
cult_examine_tip = "Can be used to create Nar'Sien hardened armor, flagellant's robes, and eldritch longswords."
cult_examine_tip = "Can be used to create Nar'Sien hardened armor and eldritch longswords."
icon_state = "forge"
light_range = 2
light_color = LIGHT_COLOR_LAVA
@@ -56,6 +55,5 @@
debris = list()
#undef NARSIE_ARMOR
#undef FLAGELLANT_ARMOR
#undef ELDRITCH_SWORD
#undef CURSED_BLADE
@@ -345,7 +345,9 @@
qdel(path)
if(give_objectives)
forge_primary_objectives(heretic_shops[HERETIC_KNOWLEDGE_TREE])
var/datum/objective/pick_path/pick_a_path = new()
pick_a_path.owner = owner
objectives += pick_a_path
for(var/starting_knowledge in GLOB.heretic_start_knowledge)
gain_knowledge(starting_knowledge, HERETIC_KNOWLEDGE_START, update = FALSE)
@@ -374,6 +376,7 @@
var/mob/living/our_mob = mob_override || owner.current
handle_clown_mutation(our_mob, "Ancient knowledge described to you has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.")
our_mob.add_faction(FACTION_HERETIC)
our_mob.apply_status_effect(/datum/status_effect/grouped/heretic_dreams, type)
if(!issilicon(our_mob))
GLOB.reality_smash_track.add_tracked_mind(owner)
@@ -397,6 +400,7 @@
var/mob/living/our_mob = mob_override || owner.current
handle_clown_mutation(our_mob, removing = FALSE)
our_mob.remove_faction(FACTION_HERETIC)
our_mob.remove_status_effect(/datum/status_effect/grouped/heretic_dreams, type)
if(owner in GLOB.reality_smash_track.tracked_heretics)
GLOB.reality_smash_track.remove_tracked_mind(owner)
@@ -411,10 +415,12 @@
COMSIG_LIVING_POST_FULLY_HEAL,
COMSIG_LIVING_CULT_SACRIFICED,
COMSIG_ATOM_EXAMINE,
COMSIG_ATOM_UPDATE_OVERLAYS,
SIGNAL_ADDTRAIT(TRAIT_HERETIC_AURA_HIDDEN),
SIGNAL_REMOVETRAIT(TRAIT_HERETIC_AURA_HIDDEN)
SIGNAL_REMOVETRAIT(TRAIT_HERETIC_AURA_HIDDEN),
)
)
our_mob.update_appearance(UPDATE_OVERLAYS)
/// Removes the ability to blade break, removes cloak of shadows and removes the cap on how many blades you can craft
/datum/antagonist/heretic/proc/disable_blade_breaking()
@@ -709,8 +715,13 @@
/**
* Create our objectives for our heretic.
*/
/datum/antagonist/heretic/proc/forge_primary_objectives(heretic_research_tree)
var/datum/objective/heretic_research/research_objective = new(heretic_research_tree = heretic_research_tree)
/datum/antagonist/heretic/proc/forge_primary_objectives()
for(var/datum/objective/pick_path/filler in objectives)
filler.owner = null
objectives -= filler
qdel(filler)
var/datum/objective/heretic_research/research_objective = new(heretic_research_tree = heretic_shops)
research_objective.owner = owner
objectives += research_objective
@@ -1099,6 +1110,10 @@
return HERETIC_HAS_LIVING_HEART
/datum/objective/pick_path
name = "pick a path"
explanation_text = "Pick a path to pursue."
/// Heretic's minor sacrifice objective. "Minor sacrifices" includes anyone.
/datum/objective/minor_sacrifice
name = "minor sacrifice"
@@ -1133,29 +1148,21 @@
/// Heretic's research objective. "Research" is heretic knowledge nodes (You start with some).
/datum/objective/heretic_research
name = "research"
/// The length of a main path. Calculated once in New().
var/static/main_path_length = 0
target_amount = 1 // You spawn with 1 point
/datum/objective/heretic_research/New(text, heretic_research_tree)
/datum/objective/heretic_research/New(text, list/heretic_research_tree = list())
. = ..()
if(!main_path_length)
// Let's find the length of a main path. We'll use rust because it's the coolest.
// (All the main paths are (should be) the same length, so it doesn't matter.)
var/rust_paths_found = 0
for(var/datum/heretic_knowledge/knowledge as anything in subtypesof(/datum/heretic_knowledge))
var/list/knowledge_data = heretic_research_tree[knowledge]
if(knowledge_data && knowledge_data[HKT_ROUTE] == PATH_RUST)
rust_paths_found++
main_path_length = rust_paths_found
// Factor in the length of the main path first.
target_amount = main_path_length
// Add in the base research we spawn with, otherwise it'd be too easy.
// Factor in the length of the main path
target_amount += length(heretic_research_tree[HERETIC_KNOWLEDGE_TREE]) || 10
// Factor in base research we spawn with (otherwise it'd be too easy)
target_amount += length(GLOB.heretic_start_knowledge)
// And add in some buffer, to require some sidepathing, especially since heretics get some free side paths.
// Factor in free knowledge, no challenge there
target_amount += ceil(length(heretic_research_tree[HERETIC_KNOWLEDGE_DRAFT]) / 3) || 4
// The actual challenge factor is introduced here, adding a random amount of additional knowledge needed
target_amount += rand(2, 4)
update_explanation_text()
/datum/objective/heretic_research/update_explanation_text()
@@ -292,7 +292,9 @@
our_heretic.heretic_shops[HERETIC_KNOWLEDGE_DRAFT],
)
SEND_SIGNAL(src, COMSIG_HERETIC_SHOP_SETUP)
if(our_heretic.give_objectives)
our_heretic.forge_primary_objectives()
our_heretic.owner.announce_objectives()
/datum/heretic_knowledge/limited_amount/starting/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
RegisterSignals(user, list(COMSIG_HERETIC_MANSUS_GRASP_ATTACK, COMSIG_LIONHUNTER_ON_HIT), PROC_REF(on_mansus_grasp))
@@ -553,7 +555,7 @@
to_chat(user, span_boldnotice("[name] completed!"))
to_chat(user, span_hypnophrase(span_big("[pick_list(HERETIC_INFLUENCE_FILE, "drain_message")]")))
desc += " (Completed!)"
log_heretic_knowledge("[key_name(user)] completed a [name] at [gameTimestamp()].")
log_heretic_knowledge("[key_name(user)] completed a [name] at [round_timestamp()].")
user.add_mob_memory(/datum/memory/heretic_knowledge_ritual)
SEND_SIGNAL(our_heretic, COMSIG_HERETIC_PASSIVE_UPGRADE_FINAL)
return TRUE
@@ -584,7 +586,7 @@
var/list/cost = our_heretic.researched_knowledge[knowledge][HKT_COST]
total_points += cost
log_heretic_knowledge("[key_name(user)] gained knowledge of their final ritual at [gameTimestamp()]. \
log_heretic_knowledge("[key_name(user)] gained knowledge of their final ritual at [round_timestamp()]. \
They have [length(our_heretic.researched_knowledge)] knowledge nodes researched, totalling [total_points] points \
and have sacrificed [our_heretic.total_sacrifices] people ([our_heretic.high_value_sacrifices] of which were high value)")
@@ -634,7 +636,7 @@
human_user.physiology.burn_mod *= 0.5
SSblackbox.record_feedback("tally", "heretic_ascended", 1, heretic_datum.heretic_path.route)
log_heretic_knowledge("[key_name(user)] completed their final ritual at [gameTimestamp()].")
log_heretic_knowledge("[key_name(user)] completed their final ritual at [round_timestamp()].")
notify_ghosts(
"[user.real_name] has completed an ascension ritual!",
source = user,
@@ -151,8 +151,7 @@
else
human_user.gib(DROP_ALL_REMAINS)
human_user.investigate_log("has died from using telekinesis on a heretic influence.", INVESTIGATE_DEATHS)
var/datum/effect_system/reagents_explosion/explosion = new(get_turf(human_user), 1, 1, 1)
explosion.start(src)
dyn_explosion(get_turf(human_user), 1, flash_range = 1, flame_range = 1)
*/
/obj/effect/visible_heretic_influence/examine(mob/living/user)
@@ -251,8 +251,7 @@
breather.emote("cough");
var/chosen_gas = pick_weight(gas_types)
var/datum/gas_mixture/mix_to_spawn = new()
mix_to_spawn.add_gas(pick(chosen_gas))
mix_to_spawn.gases[chosen_gas][MOLES] = gas_amount
mix_to_spawn.adjust_gas(pick(chosen_gas), gas_amount)
mix_to_spawn.temperature = breather.bodytemperature
log_atmos("[owner] coughed some gas into the air due to their corrupted lungs.", mix_to_spawn)
var/turf/open/our_turf = get_turf(breather)
@@ -1114,13 +1114,13 @@
// Void cloak. Turns invisible with the hood up, lets you hide stuff.
/obj/item/clothing/head/hooded/cult_hoodie/void
name = "void hood"
icon = 'icons/obj/clothing/head/helmet.dmi'
worn_icon = 'icons/mob/clothing/head/helmet.dmi'
desc = "Black like tar, reflecting no light. Runic symbols line the outside. \
With each flash you lose comprehension of what you are seeing."
icon = 'icons/obj/clothing/head/helmet.dmi'
worn_icon = 'icons/mob/clothing/head/helmet.dmi'
icon_state = "void_cloak"
flags_inv = NONE
flags_cover = NONE
flags_cover = ALLOW_SURGERY_THROUGH
armor_type = /datum/armor/cult_hoodie_void
/datum/armor/cult_hoodie_void
@@ -1221,6 +1221,7 @@
/obj/item/clothing/suit/hooded/cultrobes/void/proc/make_invisible()
add_traits(list(TRAIT_NO_STRIP, TRAIT_EXAMINE_SKIP), REF(src))
RemoveElement(/datum/element/heretic_focus)
flags_cover |= ALLOW_SURGERY_THROUGH
if(isliving(loc))
loc.remove_traits(list(TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTCOLD), REF(src))
@@ -1232,6 +1233,7 @@
/obj/item/clothing/suit/hooded/cultrobes/void/proc/make_visible()
remove_traits(list(TRAIT_NO_STRIP, TRAIT_EXAMINE_SKIP), REF(src))
AddElement(/datum/element/heretic_focus)
flags_cover &= ~ALLOW_SURGERY_THROUGH
if(isliving(loc))
loc.add_traits(list(TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTCOLD), REF(src))
@@ -22,6 +22,9 @@
/obj/item/grenade/chem_grenade/rust_sower/Initialize(mapload)
. = ..()
AddElement(/datum/element/tool_blocker, TOOL_SCREWDRIVER, TOOL_ACT_PRIMARY)
AddElement(/datum/element/tool_blocker, TOOL_WRENCH, TOOL_ACT_PRIMARY)
AddElement(/datum/element/tool_blocker, TOOL_MULTITOOL, TOOL_ACT_PRIMARY)
RegisterSignal(src, COMSIG_ITEM_ON_GRIND, PROC_REF(on_try_grind))
var/obj/item/reagent_containers/cup/beaker/large/beaker_one = new(src)
var/obj/item/reagent_containers/cup/beaker/large/beaker_two = new(src)
@@ -39,15 +42,6 @@
playsound(src, 'sound/items/weapons/rust_sower_explode.ogg', 70, FALSE)
qdel(src)
/obj/item/grenade/chem_grenade/rust_sower/screwdriver_act(mob/living/user, obj/item/tool)
return NONE
/obj/item/grenade/chem_grenade/rust_sower/wrench_act(mob/living/user, obj/item/tool)
return NONE
/obj/item/grenade/chem_grenade/rust_sower/multitool_act(mob/living/user, obj/item/tool)
return NONE
/// Returns -1 so that you cant extract the chems
/obj/item/grenade/chem_grenade/rust_sower/proc/on_try_grind()
SIGNAL_HANDLER
@@ -95,6 +95,7 @@
///An ID card capable of shapeshifting to other IDs given by the Key Keepers Burden knowledge
/obj/item/card/id/advanced/heretic
access = list(ACCESS_HERETIC)
///List of IDs this card consumed
var/list/obj/item/card/id/fused_ids = list()
///The first portal in the portal pair, so we can clear it later
@@ -119,8 +119,9 @@
research_tree_icon_frame = 9
/datum/heretic_knowledge/armor/moon
desc = "Allows you to transmute a table (or a suit), a mask and two sheets of glass to create a Resplendant Regalia, this robe will render the user fully immune to disabling effects and convert all forms of damage into brain damage, while also pacifying the user and render him unable to use ranged weapons (Moon blade will bypass pacifism). \
Acts as a focus while hooded."
desc = "Allows you to transmute a table (or a suit), a mask and two sheets of glass to create a Resplendant Regalia. \
This robe will render the user fully immune to disabling effects and convert all forms of damage into brain damage, while also pacifying the user and rendering them unable to use ranged weapons. \
A Moonlight Amulet will be necessary to use blades while wearing it."
gain_text = "Trails of light and mirth flowed from every arm of this magnificent attire. \
The troupe twirled in irridescent cascades, dazzling onlookers with the truth they sought. \
I observed, basking in the light, to find my self."
@@ -26,7 +26,7 @@
/// A weakref to the mind of our heretic.
var/datum/mind/heretic_mind
/// Lazylist of minds that we won't pick as targets.
var/list/datum/mind/target_blacklist
var/static/list/datum/mind/target_blacklist
/// An assoc list of [ref] to [timers] - a list of all the timers of people in the shadow realm currently
var/list/return_timers
/// Evil organs we can put in people
@@ -42,7 +42,6 @@
/datum/heretic_knowledge/hunt_and_sacrifice/Destroy(force)
heretic_mind = null
LAZYCLEARLIST(target_blacklist)
return ..()
/datum/heretic_knowledge/hunt_and_sacrifice/on_research(mob/user, datum/antagonist/heretic/our_heretic)
@@ -211,8 +210,8 @@
if(sacrifice.mind)
LAZYADD(target_blacklist, sacrifice.mind)
heretic_datum.remove_sacrifice_target(sacrifice)
for(var/datum/antagonist/heretic/all_heretic in GLOB.antagonists)
all_heretic.remove_sacrifice_target(sacrifice)
var/feedback = "Your patrons accept your offer"
var/sac_job_flag = sacrifice.mind?.assigned_role?.job_flags | sacrifice.last_mind?.assigned_role?.job_flags
@@ -100,3 +100,136 @@
research_tree_icon_path = 'icons/obj/economy.dmi'
research_tree_icon_state = "coin_heretic"
drafting_tier = 1
/**
* This allows heretics to choose if they want to rush all the influences and take them stealthily, or
* Construct a codex and take what's left with more points.
* Another downside to having the book is strip searches, which means that it's not just a free nab, at least until you get exposed - and when you do, you'll probably need the faster drawing speed.
* Overall, it's a tradeoff between speed and stealth or power.
*/
/datum/heretic_knowledge/codex_cicatrix
name = "Codex Cicatrix"
desc = "Allows you to transmute a book, any pen, and your pick from any carcass (animal or human), leather, or hide to create a Codex Cicatrix. \
The Codex Cicatrix can be used when draining influences to gain additional knowledge, but comes at greater risk of being noticed. \
It can also be used to draw and remove transmutation runes easier, and as a spell focus in a pinch."
gain_text = "The occult leaves fragments of knowledge and power anywhere and everywhere. The Codex Cicatrix is one such example. \
Within the leather-bound faces and age old pages, a path into the Mansus is revealed."
required_atoms = list(
list(/obj/item/toy/eldritch_book, /obj/item/book) = 1,
/obj/item/pen = 1,
list(/mob/living, /obj/item/stack/sheet/leather, /obj/item/stack/sheet/animalhide, /obj/item/food/deadmouse) = 1,
)
result_atoms = list(/obj/item/codex_cicatrix)
cost = 1
priority = MAX_KNOWLEDGE_PRIORITY - 4
drafting_tier = 1
is_shop_only = TRUE
research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
research_tree_icon_state = "book"
var/static/list/non_mob_bindings = typecacheof(list(
/obj/item/stack/sheet/leather,
/obj/item/stack/sheet/animalhide,
/obj/item/food/deadmouse,
))
/datum/heretic_knowledge/codex_cicatrix/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
. = ..()
if(!.)
return FALSE
for(var/thingy in atoms)
if(is_type_in_typecache(thingy, non_mob_bindings))
selected_atoms += thingy
return TRUE
else if(isliving(thingy))
var/mob/living/body = thingy
if(body.stat != DEAD)
continue
selected_atoms += body
return TRUE
return FALSE
/datum/heretic_knowledge/codex_cicatrix/cleanup_atoms(list/selected_atoms)
var/mob/living/body = locate() in selected_atoms
if(!body)
return ..()
// A golem or an android doesn't have skin!
var/exterior_text = "skin"
// If carbon, it's the limb. If not, it's the body.
var/atom/movable/ripped_thing = body
// We will check if it's a carbon's body.
// If it is, we will damage a random bodypart, and check that bodypart for its body type, to select between 'skin' or 'exterior'.
if(iscarbon(body))
var/mob/living/carbon/carbody = body
var/obj/item/bodypart/bodypart = pick(carbody.get_bodyparts())
ripped_thing = bodypart
carbody.apply_damage(25, BRUTE, bodypart, sharpness = SHARP_EDGED)
if(!(bodypart.bodytype & BODYTYPE_ORGANIC))
exterior_text = "exterior"
else
body.apply_damage(25, BRUTE, sharpness = SHARP_EDGED)
// If it is not a carbon mob, we will just check biotypes and damage it directly.
if(body.mob_biotypes & (MOB_MINERAL|MOB_ROBOTIC))
exterior_text = "exterior"
// Procure book for flavor text. This is why we call parent at the end.
var/obj/item/book/le_book = locate() in selected_atoms
if(!le_book)
stack_trace("Somehow, no book in codex cicatrix selected atoms! [english_list(selected_atoms)]")
playsound(body, 'sound/items/poster/poster_ripped.ogg', 100, TRUE)
body.do_jitter_animation()
body.visible_message(span_danger("An awful ripping sound is heard as [ripped_thing]'s [exterior_text] is ripped straight out, wrapping around [le_book || "the book"], turning into an eldritch shade of blue!"))
return ..()
/**
* Warren King's Welcome
* Offers an alternative way besides stealing an ID or visiting the HoP to gain access to maintenance
* Additionally changes all nearby airlock's access's to ACCESS_HERETIC
*/
/datum/heretic_knowledge/bookworm
name = "Warren King's Welcome"
desc = "Allows you to transmute 10 cable pieces, a piece of paper, and a multitool to brand nearby ID cards and airlocks. \
Branded ID cards will gain access to maintenance, external airlocks, as well to branded airlocks. \
Branded airlocks will only be accessible by those with a branded ID card."
gain_text = "Gnawed into vicious-stained fingerbones, my grim invitation snaps my nauseous and clouded mind towards the heavy-set door. \
Slowly, the light dances between a crawling darkness, blanketing the fetid promenade with infinite machinations. \
But the King will soon take his pound of flesh. Even here, the taxman takes their cut. For there are a thousands mouths to feed."
required_atoms = list(
/obj/item/stack/cable_coil = 10,
/obj/item/paper = 1,
/obj/item/multitool = 1,
)
cost = 1
priority = MAX_KNOWLEDGE_PRIORITY - 3
drafting_tier = 1
research_tree_icon_path = 'icons/obj/card.dmi'
research_tree_icon_state = "eldritch"
/datum/heretic_knowledge/bookworm/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
. = ..()
for(var/obj/item/card/id/used_id in atoms)
selected_atoms += used_id
var/obj/item/card/user_card = user.get_idcard(hand_first = TRUE)
if(user_card)
selected_atoms += user_card
/datum/heretic_knowledge/bookworm/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc)
. = ..()
for(var/obj/item/card/id/improved_id in selected_atoms)
improved_id.add_access(list(ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_HERETIC), mode = FORCE_ADD_ALL)
selected_atoms -= improved_id
for(var/obj/machinery/door/airlock/door in view(7, loc))
door.req_one_access = null
door.req_access = list(ACCESS_HERETIC)
door.wires?.cut(WIRE_AI)
new /obj/effect/temp_visual/eldritch_sparks(door.loc)
var/obj/effect/light_emitter/light = new(door.loc)
light.set_light(1.75, 1.5, COLOR_PUCE)
QDEL_IN(light, 1 SECONDS)
playsound(door, 'sound/effects/magic.ogg', 20, vary = TRUE, extrarange = SILENCED_SOUND_EXTRARANGE, ignore_walls = FALSE)
playsound(door, SFX_SPARKS, 33, vary = TRUE, extrarange = SILENCED_SOUND_EXTRARANGE, ignore_walls = FALSE)
return TRUE
@@ -200,84 +200,6 @@ GLOBAL_LIST_INIT(heretic_start_knowledge, initialize_starting_knowledge())
cost = 0
is_starting_knowledge = TRUE
/**
* Codex Cicatrixi is available at the start:
* This allows heretics to choose if they want to rush all the influences and take them stealthily, or
* Construct a codex and take what's left with more points.
* Another downside to having the book is strip searches, which means that it's not just a free nab, at least until you get exposed - and when you do, you'll probably need the faster drawing speed.
* Overall, it's a tradeoff between speed and stealth or power.
*/
/datum/heretic_knowledge/codex_cicatrix
name = "Codex Cicatrix"
desc = "Allows you to transmute a book, any pen, and your pick from any carcass (animal or human), leather, or hide to create a Codex Cicatrix. \
The Codex Cicatrix can be used when draining influences to gain additional knowledge, but comes at greater risk of being noticed. \
It can also be used to draw and remove transmutation runes easier, and as a spell focus in a pinch."
gain_text = "The occult leaves fragments of knowledge and power anywhere and everywhere. The Codex Cicatrix is one such example. \
Within the leather-bound faces and age old pages, a path into the Mansus is revealed."
required_atoms = list(
list(/obj/item/toy/eldritch_book, /obj/item/book) = 1,
/obj/item/pen = 1,
list(/mob/living, /obj/item/stack/sheet/leather, /obj/item/stack/sheet/animalhide, /obj/item/food/deadmouse) = 1,
)
result_atoms = list(/obj/item/codex_cicatrix)
cost = 1
is_starting_knowledge = TRUE
priority = MAX_KNOWLEDGE_PRIORITY - 4 // Least priority out of the starting knowledges, as it's an optional boon.
var/static/list/non_mob_bindings = typecacheof(list(/obj/item/stack/sheet/leather, /obj/item/stack/sheet/animalhide, /obj/item/food/deadmouse))
research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
research_tree_icon_state = "book"
/datum/heretic_knowledge/codex_cicatrix/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
. = ..()
if(!.)
return FALSE
for(var/thingy in atoms)
if(is_type_in_typecache(thingy, non_mob_bindings))
selected_atoms += thingy
return TRUE
else if(isliving(thingy))
var/mob/living/body = thingy
if(body.stat != DEAD)
continue
selected_atoms += body
return TRUE
return FALSE
/datum/heretic_knowledge/codex_cicatrix/cleanup_atoms(list/selected_atoms)
var/mob/living/body = locate() in selected_atoms
if(!body)
return ..()
// A golem or an android doesn't have skin!
var/exterior_text = "skin"
// If carbon, it's the limb. If not, it's the body.
var/atom/movable/ripped_thing = body
// We will check if it's a carbon's body.
// If it is, we will damage a random bodypart, and check that bodypart for its body type, to select between 'skin' or 'exterior'.
if(iscarbon(body))
var/mob/living/carbon/carbody = body
var/obj/item/bodypart/bodypart = pick(carbody.get_bodyparts())
ripped_thing = bodypart
carbody.apply_damage(25, BRUTE, bodypart, sharpness = SHARP_EDGED)
if(!(bodypart.bodytype & BODYTYPE_ORGANIC))
exterior_text = "exterior"
else
body.apply_damage(25, BRUTE, sharpness = SHARP_EDGED)
// If it is not a carbon mob, we will just check biotypes and damage it directly.
if(body.mob_biotypes & (MOB_MINERAL|MOB_ROBOTIC))
exterior_text = "exterior"
// Procure book for flavor text. This is why we call parent at the end.
var/obj/item/book/le_book = locate() in selected_atoms
if(!le_book)
stack_trace("Somehow, no book in codex cicatrix selected atoms! [english_list(selected_atoms)]")
playsound(body, 'sound/items/poster/poster_ripped.ogg', 100, TRUE)
body.do_jitter_animation()
body.visible_message(span_danger("An awful ripping sound is heard as [ripped_thing]'s [exterior_text] is ripped straight out, wrapping around [le_book || "the book"], turning into an eldritch shade of blue!"))
return ..()
/datum/heretic_knowledge/feast_of_owls
name = "Feast of Owls"
desc = "Allows you to undergo a ritual that gives you 5 knowledge points but locks you out of ascension. This can only be done once and cannot be reverted."
@@ -322,42 +244,3 @@ GLOBAL_LIST_INIT(heretic_start_knowledge, initialize_starting_knowledge())
var/drain_message = pick_list(HERETIC_INFLUENCE_FILE, "drain_message")
to_chat(user, span_hypnophrase(span_big("[drain_message]")))
return .
/**
* Warren King's Welcome
* Ritual available at the start. So that heretics can easily gain access to maintenance airlocks without having to rely on a HoP or having to off some poor assistant.
* Gives access to solars since those doors are especially useful to get in or out of space.
*/
/datum/heretic_knowledge/bookworm
name = "Warren King's Welcome"
desc = "Allows you to transmute 5 cable pieces and a piece of paper to infuse any ID with maintenace and external airlock access."
gain_text = "Gnawed into vicious-stained fingerbones, my grim invitation snaps my nauseous and clouded mind towards the heavy-set door. \
Slowly, the light dances between a crawling darkness, blanketing the fetid promenade with infinite machinations. \
But the King will soon take his pound of flesh. Even here, the taxman takes their cut. For there are a thousands mouths to feed."
required_atoms = list(
/obj/item/stack/cable_coil = 5,
/obj/item/paper = 1,
)
cost = 1
is_starting_knowledge = TRUE
priority = MAX_KNOWLEDGE_PRIORITY - 3
research_tree_icon_path = 'icons/obj/card.dmi'
research_tree_icon_state = "eldritch"
/datum/heretic_knowledge/bookworm/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
. = ..()
for(var/obj/item/card/id/used_id in atoms)
if((ACCESS_MAINT_TUNNELS in used_id.access) && (ACCESS_EXTERNAL_AIRLOCKS in used_id.access)) // If we can't give any access we aren't elligible
continue
selected_atoms += used_id
return TRUE
user.balloon_alert(user, "ritual failed, no ID lacking access!")
return FALSE
/datum/heretic_knowledge/bookworm/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc)
. = ..()
var/obj/item/card/id/improved_id = locate() in selected_atoms
improved_id.add_access(list(ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS), mode = FORCE_ADD_ALL)
selected_atoms -= improved_id
return TRUE
@@ -17,5 +17,5 @@
/mob/living/basic/pet/cat,
/mob/living/basic/pet/dog/corgi,
/mob/living/basic/pet/fox,
/mob/living/simple_animal/bot/secbot,
/mob/living/basic/bot/secbot,
)
@@ -17,6 +17,7 @@
var/datum/objective/lunatic/lunatic_obj
/datum/antagonist/lunatic/on_gain()
owner.current.log_message("has become a Lunatic!", LOG_ATTACK, color="red")
// Masters gain an objective before so we dont want duplicates
for(var/objective in objectives)
if(!istype(objective, /datum/objective/lunatic))
@@ -27,6 +28,10 @@
lunatic_obj = loony
return ..()
/datum/antagonist/lunatic/on_removal()
owner?.current?.log_message("lost their Lunatic status!", LOG_ATTACK, color="red")
return ..()
/// Runs when the moon heretic creates us, used to give the lunatic a master
/datum/antagonist/lunatic/proc/set_master(datum/mind/heretic_master, mob/living/carbon/human/heretic_body)
src.ascended_heretic = heretic_master
@@ -0,0 +1,118 @@
/datum/status_effect/grouped/heretic_dreams
id = "heretic_dreams"
duration = STATUS_EFFECT_PERMANENT
tick_interval = STATUS_EFFECT_NO_TICK
alert_type = null
/// Cooldown between allowed dreams
COOLDOWN_DECLARE(dreaming_cooldown)
/datum/status_effect/grouped/heretic_dreams/on_apply()
. = ..()
RegisterSignal(owner, COMSIG_PRE_DREAMING, PROC_REF(add_heretic_dream))
RegisterSignal(owner, COMSIG_START_DREAMING, PROC_REF(start_heretic_dream))
/datum/status_effect/grouped/heretic_dreams/on_remove()
. = ..()
UnregisterSignal(owner, COMSIG_PRE_DREAMING)
UnregisterSignal(owner, COMSIG_START_DREAMING)
/datum/status_effect/grouped/heretic_dreams/proc/add_heretic_dream(mob/living/dreamer, list/dream_pool)
SIGNAL_HANDLER
if(!COOLDOWN_FINISHED(src, dreaming_cooldown))
return
var/atom/dream_center = get_dream_center(dreamer)
if(isnull(dream_center))
return
dream_pool[new /datum/dream/heretic(dream_center)] = 200
/datum/status_effect/grouped/heretic_dreams/proc/start_heretic_dream(mob/living/dreamer, datum/dream/current_dream)
SIGNAL_HANDLER
if(!istype(current_dream, /datum/dream/heretic))
return
COOLDOWN_START(src, dreaming_cooldown, /datum/mood_event/mansus_dream_fatigue::timeout)
dreamer.add_mood_event("mansus_dream_fatigue", /datum/mood_event/mansus_dream_fatigue)
/datum/status_effect/grouped/heretic_dreams/proc/get_dream_center(mob/living/dreamer)
// Select a random influence as the center of the dream
if(length(GLOB.reality_smash_track.smashes))
return pick(GLOB.reality_smash_track.smashes)
// If there are no influences, either don't trigger the dream (if we are a heretic) or pick a completely random locale (if we aren't)
if(IS_HERETIC(dreamer))
return null
return get_safe_random_station_turf_equal_weight()
/// Heretics can see dreams about random machinery from the perspective of a random unused influence
/datum/dream/heretic
sleep_until_finished = TRUE
/// The location of the influence (or lack thereof in the case of a fake dream) we will be dreaming about
var/atom/dream_center
/// The distance to the objects visible from the influence during the dream
var/dream_view_range = 5
var/list/what_you_can_see = list(
/obj/item,
/obj/structure,
/obj/machinery,
)
var/static/list/what_you_cant_see = typecacheof(list(
// Underfloor stuff and default wallmounts
/obj/item/radio/intercom,
/obj/structure/cable,
/obj/structure/disposalpipe/segment,
/obj/machinery/atmospherics/pipe/smart/manifold4w,
/obj/machinery/atmospherics/components/unary/vent_scrubber,
/obj/machinery/atmospherics/components/unary/vent_pump,
/obj/machinery/duct,
/obj/machinery/navbeacon,
/obj/machinery/power/terminal,
/obj/machinery/power/apc,
/obj/machinery/light_switch,
/obj/machinery/light,
/obj/machinery/camera,
/obj/machinery/door/firedoor,
/obj/machinery/firealarm,
/obj/machinery/airalarm,
/obj/structure/window/fulltile,
/obj/structure/window/reinforced/fulltile,
))
/// Cached list of allowed typecaches for each type in what_you_can_see
var/static/list/allowed_typecaches_by_root_type = null
/datum/dream/heretic/New(atom/dream_center)
src.dream_center = dream_center
/datum/dream/heretic/GenerateDream(mob/living/carbon/dreamer)
. = list()
. += "you wander through the forest of Mansus"
. += "there is a " + pick("pond", "well", "lake", "puddle", "stream", "spring", "brook", "marsh")
if(isnull(allowed_typecaches_by_root_type))
allowed_typecaches_by_root_type = list()
for(var/type in what_you_can_see)
allowed_typecaches_by_root_type[type] = typecacheof(type) - what_you_cant_see
var/list/all_objects = oview(dream_view_range, dream_center)
var/something_found = FALSE
for(var/object_type in allowed_typecaches_by_root_type)
var/list/filtered_objects = typecache_filter_list(all_objects, allowed_typecaches_by_root_type[object_type])
if(filtered_objects.len)
if (!something_found)
. += "its waters reflect"
something_found = TRUE
var/obj/found_object = pick(filtered_objects)
. += initial(found_object.name)
if(!something_found)
. += pick("it's pitch black", "ihe reflections are vague", "you stroll aimlessly")
else
. += "the images fade in the ripples"
. += "you feel exhausted"
/datum/mood_event/mansus_dream_fatigue
description = "I must recover before I can dream of Mansus again."
mood_change = -2
timeout = 5 MINUTES
@@ -653,7 +653,6 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module/malf))
name = "Robotic Factory (Removes Shunting)"
description = "Build a machine anywhere, using expensive nanomachines, that will slowly create loyal cyborgs for you." // SKYRAT EDIT
cost = 100
minimum_apcs = 10 // So you can't speedrun this
power_type = /datum/action/innate/ai/place_transformer
unlock_text = span_notice("You make contact with Space Amazon and request a robotics factory for delivery.")
unlock_sound = 'sound/machines/ping.ogg'
@@ -759,7 +758,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module/malf))
power_type = /datum/action/innate/ai/break_fire_alarms
unlock_text = span_notice("You replace the thermal sensing capabilities of all fire alarms with a manual override, \
allowing you to turn them off at will.")
unlock_sound = 'sound/machines/fire_alarm/FireAlarm1.ogg'
unlock_sound = 'sound/machines/fire_alarm/fire_alarm1.ogg'
/datum/action/innate/ai/break_fire_alarms
name = "Override Thermal Sensors"
@@ -15,6 +15,8 @@
var/datum/team/nuclear/nuke_team
/// Should the user be moved to default spawnpoint after being granted this datum.
var/send_to_spawnpoint = TRUE
/// Should the nukie get a little bonus tc depending on how many players there are
var/give_bonus_tc = TRUE
var/job_type = /datum/job/nuclear_operative
/// The DEFAULT outfit we will give to players granted this datum
@@ -49,11 +51,11 @@
equip_op()
if(send_to_spawnpoint)
move_to_spawnpoint()
// grant extra TC for the people who start in the nukie base ie. not the lone op
var/extra_tc = CEILING(GLOB.joined_player_list.len/5, 5)
if(give_bonus_tc)
var/extra_tc = CEILING(GLOB.joined_player_list.len / 5, 5)
var/datum/component/uplink/uplink = owner.find_syndicate_uplink()
if (uplink)
uplink.uplink_handler.add_telecrystals(extra_tc)
uplink?.uplink_handler.add_telecrystals(extra_tc)
var/datum/component/uplink/uplink = owner.find_syndicate_uplink()
if(uplink)
var/datum/team/nuclear/nuke_team = get_team()
@@ -81,7 +83,7 @@
objectives |= nuke_team.objectives
/datum/antagonist/nukeop/leader/get_spawnpoint()
return pick(GLOB.nukeop_leader_start)
return pick(GLOB.nukeop_base_leader_start)
/datum/antagonist/nukeop/create_team(datum/team/nuclear/new_team)
if(!new_team)
@@ -146,7 +148,7 @@
return TRUE
/datum/antagonist/nukeop/proc/admin_send_to_base(mob/admin)
owner.current.forceMove(pick(GLOB.nukeop_start))
owner.current.forceMove(pick(GLOB.nukeop_base_start))
/datum/antagonist/nukeop/proc/admin_tell_code(mob/admin)
var/code
@@ -192,7 +194,7 @@
if(nuke_team)
team_number = nuke_team.members.Find(owner)
return GLOB.nukeop_start[((team_number - 1) % GLOB.nukeop_start.len) + 1]
return GLOB.nukeop_base_start[((team_number - 1) % GLOB.nukeop_base_start.len) + 1]
/datum/antagonist/nukeop/proc/spawn_infiltrator()
var/datum/map_template/shuttle/infiltrator/ship = SSmapping.shuttle_templates[infiltrator_id]
@@ -221,6 +223,6 @@
mobile_port.setTimer(mobile_port.ignitionTime)
/datum/antagonist/nukeop/on_respawn(mob/new_character)
new_character.forceMove(pick(GLOB.nukeop_start))
new_character.forceMove(pick(GLOB.nukeop_base_start))
equip_op()
return TRUE
@@ -1,6 +1,8 @@
/datum/antagonist/nukeop/leader
name = "Nuclear Operative Leader"
nukeop_outfit = /datum/outfit/syndicate/leader
/// Whether to spawn the infiltrator
var/spawn_ship = TRUE
/// Randomly chosen honorific, for distinction
var/title
/// The nuclear challenge remote we will spawn this player with.
@@ -63,7 +65,8 @@
return capitalize(newname)
/datum/antagonist/nukeop/leader/create_team(datum/team/nuclear/new_team)
spawn_infiltrator()
if(spawn_ship)
spawn_infiltrator()
if(new_team)
return ..()
// Leaders always make new teams
@@ -1,6 +1,7 @@
/datum/antagonist/nukeop/lone
name = "Lone Operative"
send_to_spawnpoint = FALSE //Handled by event
give_bonus_tc = FALSE
nukeop_outfit = /datum/outfit/syndicate/full/loneop
preview_outfit = /datum/outfit/nuclear_operative
preview_outfit_behind = null
@@ -2,6 +2,7 @@
name = "Nuclear Operative Reinforcement"
show_in_antagpanel = FALSE
send_to_spawnpoint = FALSE
give_bonus_tc = FALSE
nukeop_outfit = /datum/outfit/syndicate/reinforcement
/datum/antagonist/nukeop/reinforcement/cyborg
@@ -32,7 +32,7 @@
owner.current.grant_language(/datum/language/codespeak)
/datum/antagonist/nukeop/support/get_spawnpoint()
return pick(GLOB.nukeop_overwatch_start)
return pick(GLOB.nukeop_base_overwatch_start)
/datum/antagonist/nukeop/support/forge_objectives()
var/datum/objective/overwatch/objective = new
@@ -18,7 +18,7 @@
objectives += maingoal
// when a nuke team is created, the infiltrator has not loaded in yet - it takes some time. so no nuke, we have to wait
addtimer(CALLBACK(src, PROC_REF(assign_nuke_delayed)), 4 SECONDS)
addtimer(CALLBACK(src, PROC_REF(assign_nuke_delayed)), 5 SECONDS)
/datum/team/nuclear/roundend_report()
var/list/parts = list()
@@ -192,7 +192,7 @@
infil_or_nukebase = SPAWN_AT_BASE
if(infil_or_nukebase == SPAWN_AT_BASE)
spawn_loc = pick(GLOB.nukeop_start)
spawn_loc = pick(GLOB.nukeop_base_start)
var/mob/living/carbon/human/nukie = new(spawn_loc)
chosen_one.client.prefs.safe_transfer_prefs_to(nukie, is_antag = TRUE)
@@ -200,6 +200,7 @@
var/datum/antagonist/nukeop/antag_datum = new()
antag_datum.send_to_spawnpoint = FALSE
antag_datum.give_bonus_tc = FALSE
antag_datum.nukeop_outfit = /datum/outfit/syndicate/reinforcement
nukie.mind.add_antag_datum(antag_datum, src)
@@ -313,20 +314,25 @@
..()
SEND_SIGNAL(src, COMSIG_NUKE_TEAM_ADDITION, new_member.current)
/datum/team/nuclear/proc/assign_nuke_delayed()
assign_nuke()
if(tracked_nuke && memorized_code)
for(var/datum/mind/synd_mind in members)
var/datum/antagonist/nukeop/synd_datum = synd_mind.has_antag_datum(/datum/antagonist/nukeop)
synd_datum?.memorize_code()
/datum/team/nuclear/proc/assign_nuke_delayed(attempts = 0)
if(!assign_nuke())
if(attempts > 5)
stack_trace("Failed to assign nuke to team after multiple attempts. \
This likely means the nuke was not found. Please investigate.")
else
addtimer(CALLBACK(src, PROC_REF(assign_nuke_delayed), attempts + 1), 5 SECONDS)
return
for(var/datum/mind/synd_mind as anything in members)
var/datum/antagonist/nukeop/synd_datum = synd_mind.has_antag_datum(/datum/antagonist/nukeop)
synd_datum?.memorize_code()
/datum/team/nuclear/proc/assign_nuke()
memorized_code = random_nukecode()
var/obj/machinery/nuclearbomb/syndicate/nuke = locate() in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/nuclearbomb/syndicate)
if(!nuke)
stack_trace("Syndicate nuke not found during nuke team creation.")
memorized_code = null
return
return FALSE
memorized_code = random_nukecode()
tracked_nuke = nuke
if(nuke.r_code == NUKE_CODE_UNSET)
nuke.r_code = memorized_code
@@ -334,6 +340,7 @@
memorized_code = nuke.r_code
for(var/obj/machinery/nuclearbomb/beer/beernuke as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/nuclearbomb/beer))
beernuke.r_code = memorized_code
return TRUE
#undef SPAWN_AT_BASE
#undef SPAWN_AT_INFILTRATOR
@@ -341,14 +348,14 @@
/datum/team/nuclear/loneop
/datum/team/nuclear/loneop/assign_nuke()
memorized_code = random_nukecode()
var/obj/machinery/nuclearbomb/selfdestruct/nuke = locate() in SSmachines.get_machines_by_type(/obj/machinery/nuclearbomb/selfdestruct)
if(nuke)
tracked_nuke = nuke
if(nuke.r_code == NUKE_CODE_UNSET)
nuke.r_code = memorized_code
else //Already set by admins/something else?
memorized_code = nuke.r_code
else
stack_trace("Station self-destruct not found during lone op team creation.")
memorized_code = null
if(!nuke)
return FALSE
memorized_code = random_nukecode()
tracked_nuke = nuke
if(nuke.r_code == NUKE_CODE_UNSET)
nuke.r_code = memorized_code
else //Already set by admins/something else?
memorized_code = nuke.r_code
return TRUE
+4 -5
View File
@@ -144,11 +144,10 @@
/datum/outfit/syndicate/reinforcement/cybersun
name = "Syndicate Operative - Cybersun Reinforcement"
uniform = /obj/item/clothing/under/syndicate/combat
suit = /obj/item/clothing/suit/jacket/oversized
gloves = /obj/item/clothing/gloves/fingerless
glasses = /obj/item/clothing/glasses/sunglasses
mask = /obj/item/cigarette/cigar
uniform = /obj/item/clothing/under/syndicate/cybersun
gloves = /obj/item/clothing/gloves/combat
shoes = /obj/item/clothing/shoes/laceup
mask = /obj/item/clothing/mask/gas/syndicate/cybersun
faction = "Cybersun Industries"
/datum/outfit/syndicate/reinforcement/donk
@@ -173,7 +173,7 @@
/obj/machinery/piratepad
name = "cargo hold pad"
icon = 'icons/obj/machines/telepad.dmi'
icon_state = "lpad-idle-off"
icon_state = "lpad-off"
base_icon_state = "lpad"
/// Determines what icon is being shown
VAR_PRIVATE/is_sending = FALSE
@@ -213,11 +213,11 @@
/obj/machinery/piratepad/update_icon_state()
. = ..()
if(panel_open)
icon_state = "[base_icon_state]-idle-open"
icon_state = "[base_icon_state]-open"
else if(is_sending)
icon_state = "[base_icon_state]-idle"
icon_state = "[base_icon_state]"
else
icon_state = "[base_icon_state]-idle-off"
icon_state = "[base_icon_state]-off"
/obj/machinery/computer/piratepad_control
name = "cargo hold control terminal"
@@ -342,7 +342,7 @@
/obj/structure/carp_rift/hitby(atom/movable/hit_by, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
if(HAS_TRAIT(hit_by, TRAIT_TELEKINESIS_CONTROLLED))
var/mob/thrower = throwingdatum.thrower.resolve()
var/mob/thrower = throwingdatum.thrower?.resolve()
if(thrower && ismob(thrower))
to_chat(thrower, span_warning("The gravitational field of [src] interferes with the telekenetic control of [hit_by], nullifying the hit!"))
return
@@ -51,7 +51,7 @@
if(!IS_SPACE_NINJA(user))
say("Access denied.")
return FALSE
if(!check_loc(user))
if(!check_loc(bomb_target, user))
return FALSE
if(!..())
return FALSE
@@ -59,7 +59,7 @@
return TRUE
/obj/item/grenade/c4/ninja/detonate(mob/living/lanced_by)
if(!check_loc(detonator.resolve())) // if its moved, deactivate the c4
if(!check_loc(target, detonator.resolve())) // if its moved, deactivate the c4
var/obj/item/grenade/c4/ninja/new_c4 = new /obj/item/grenade/c4/ninja(target.loc)
new_c4.detonation_area = detonation_area
new_c4.say("Invalid location!")
@@ -87,11 +87,11 @@
* Arguments
* * mob/user - The planter of the c4
*/
/obj/item/grenade/c4/ninja/proc/check_loc(mob/user)
/obj/item/grenade/c4/ninja/proc/check_loc(atom/bomb_target, mob/user)
if(isnull(detonation_area))
balloon_alert(user, "no location set!")
return FALSE
if((get_area(target) != detonation_area) && (get_area(src) != detonation_area))
if(get_area(bomb_target) != detonation_area)
if (!active)
balloon_alert(user, "wrong location!")
return FALSE
+1
View File
@@ -196,6 +196,7 @@
var/mob/living/carbon/human/dummy/consistent/dummy = new()
dummy.set_haircolor(COLOR_SILVER, update = FALSE)
dummy.set_hairstyle("CIA", update = FALSE)
dummy.update_hair()
var/datum/universal_icon/dummy_icon = render_preview_outfit(preview_outfit, dummy)
qdel(dummy)
return finish_preview_icon(dummy_icon)
+4 -4
View File
@@ -716,23 +716,23 @@
/datum/spy_bounty/some_bot/beepsky
difficulty = SPY_DIFFICULTY_MEDIUM // gotta get him to stand still
bot_type = /mob/living/simple_animal/bot/secbot/beepsky/officer
bot_type = /mob/living/basic/bot/secbot/beepsky/officer
help = "Abduct Officer Beepsky - commonly found patrolling the station. \
Watch out, they may not take kindly to being scanned."
/datum/spy_bounty/some_bot/ofitser
difficulty = SPY_DIFFICULTY_EASY
bot_type = /mob/living/simple_animal/bot/secbot/beepsky/ofitser
bot_type = /mob/living/basic/bot/secbot/beepsky/ofitser
help = "Abduct Prison Ofitser - commonly found guarding the Gulag."
/datum/spy_bounty/some_bot/armsky
difficulty = SPY_DIFFICULTY_HARD
bot_type = /mob/living/simple_animal/bot/secbot/beepsky/armsky
bot_type = /mob/living/basic/bot/secbot/beepsky/armsky
help = "Abduct Sergeant-At-Armsky - commonly found guarding the station's Armory."
/datum/spy_bounty/some_bot/pingsky
difficulty = SPY_DIFFICULTY_HARD
bot_type = /mob/living/simple_animal/bot/secbot/pingsky
bot_type = /mob/living/basic/bot/secbot/pingsky
help = "Abduct Officer Pingsky - commonly found protecting the station's AI."
/datum/spy_bounty/some_bot/scrubs
@@ -40,6 +40,11 @@
hud_possible = list(ANTAG_HUD)
sight = SEE_TURFS | SEE_MOBS
//purplish tint night vision because the voidwalker is purple
lighting_cutoff_red = 30
lighting_cutoff_green = 15
lighting_cutoff_blue = 30
/// Color of our regen outline
var/regenerate_colour = COLOR_GRAY
@@ -139,7 +144,7 @@
/mob/living/basic/voidwalker/early_melee_attack(atom/target, list/modifiers, ignore_cooldown)
. = ..()
if(!. || !can_do_abductions)
if(. || !can_do_abductions)
return
if(ishuman(target))
@@ -151,12 +156,12 @@
hewmon.apply_status_effect(/datum/status_effect/void_chomped)
if(!should_attack)
return FALSE
return BASIC_MOB_CONTINUE_ATTACK_CHAIN
if(hewmon.stat == HARD_CRIT && !hewmon.has_trauma_type(/datum/brain_trauma/voided))
hewmon.balloon_alert(src, "is in crit!")
hewmon.Stun(5 SECONDS) // blocks some crit movement mechanics from a bunch of sources
return FALSE
return BASIC_MOB_END_ATTACK_CHAIN_COOLDOWN
// left click
if(LAZYACCESS(modifiers, LEFT_CLICK))
@@ -167,9 +172,9 @@
melee_damage_type = rclick_damage_type
if(!istype(target, /turf/closed/wall))
return
return BASIC_MOB_CONTINUE_ATTACK_CHAIN
INVOKE_ASYNC(src, PROC_REF(try_convert_wall), target)
return TRUE
return BASIC_MOB_CONTINUE_ATTACK_CHAIN
/// Called by the regenerator component so we only regen in space
/mob/living/basic/voidwalker/proc/can_regen()
@@ -208,6 +208,11 @@
log_combat(user, M, "captured [M.name]'s soul", src)
capture_soul(M, user)
/obj/item/soulstone/suicide_act(mob/living/user)
. = ..()
user.visible_message(span_suicide("[user] is capturing [user.p_their()] own soul with [src]! It looks like [user.p_theyre()] trying to commit suicide!"))
return capture_soul(user, null, TRUE) ? MANUAL_SUICIDE : BRUTELOSS
///////////////////Options for using captured souls///////////////////////////////////////
/obj/item/soulstone/attack_self(mob/living/user)
@@ -55,7 +55,7 @@
it will become easier for others to find your item of power."
spell_type = /datum/action/cooldown/spell/lichdom
category = SPELLBOOK_CATEGORY_DEFENSIVE
no_coexistence_typecache = list(/datum/action/cooldown/spell/splattercasting, /datum/spellbook_entry/perks/wormborn)
no_coexistence_typecache = list(/datum/action/cooldown/spell/splattercasting, /datum/spellbook_entry/perks/wormborn, /datum/spellbook_entry/ghostliness)
/datum/spellbook_entry/chuunibyou
name = "Chuuni Invocations"
@@ -117,13 +117,36 @@
/datum/spellbook_entry/item/wands
name = "Wand Assortment"
desc = "A collection of wands that allow for a wide variety of utility. \
Wands have a limited number of charges, so be conservative with their use. Comes in a handy belt."
Wands have a limited number of charges, so be conservative with their use. \
Comes in a handy belt, or a fancy bandolier if you are already wearing one."
item_path = /obj/item/storage/belt/wands/full
category = SPELLBOOK_CATEGORY_DEFENSIVE
/datum/spellbook_entry/item/wands/try_equip_item(mob/living/carbon/human/user, obj/item/to_equip)
var/was_equipped = user.equip_to_slot_if_possible(to_equip, ITEM_SLOT_BELT, disable_warning = TRUE)
to_chat(user, span_notice("\A [to_equip.name] has been summoned [was_equipped ? "on your waist" : "at your feet"]."))
if (!istype(user.belt, /obj/item/storage/belt/wands))
var/was_equipped = user.equip_to_slot_if_possible(to_equip, ITEM_SLOT_BELT, disable_warning = TRUE)
to_chat(user, span_notice("\A [to_equip.name] has been summoned [was_equipped ? "on your waist" : "at your feet"]."))
return
// If you already have a wand belt you get a cool bandolier instead for your copious amount of wands
var/obj/item/storage/belt/wand_bandolier/bandolier = new(user.drop_location())
for (var/obj/item/wand_presumably in to_equip.atom_storage.real_location)
bandolier.atom_storage.attempt_insert(wand_presumably, user, messages = FALSE)
qdel(to_equip)
var/was_equipped = user.equip_to_slot_if_possible(bandolier, ITEM_SLOT_SUITSTORE, disable_warning = TRUE)
to_chat(user, span_notice("\A [bandolier.name] has been summoned [was_equipped ? "across your chest" : "at your feet"]."))
/datum/spellbook_entry/item/wands/discount
name = "Wand Assortment (Bargain Bin)"
desc = "A random collection of wands sourced from apprentice wandmaking studies. \
You're never quite sure what you're going to get. \
Comes in a handy belt, or a fancy bandolier if you are already wearing one."
cost = 1
item_path = /obj/item/storage/belt/wands/full/discount
category = SPELLBOOK_CATEGORY_DEFENSIVE
/datum/spellbook_entry/item/armor
name = "Mastercrafted Armor Set"
@@ -59,4 +59,13 @@
cost = 2 // Puts it at 3 cost if you go for safety instant summons, but teleporting anywhere on screen is pretty good.
category = SPELLBOOK_CATEGORY_MOBILITY
/datum/spellbook_entry/ghostliness
name = "Forsake Body"
desc = "A necromantic spell which permanently severs your soul from your body, and partially anchors it to the material plane. \
In this state, you can enter a state of incorporeality, allowing you to pass through solid matter. This, however, includes \
most such matter on or inside of you."
spell_type = /datum/action/cooldown/spell/ghostliness
category = SPELLBOOK_CATEGORY_MOBILITY
no_coexistence_typecache = list(/datum/spellbook_entry/lichdom, /datum/spellbook_entry/splattercasting)
#undef SPELLBOOK_CATEGORY_MOBILITY
@@ -99,7 +99,7 @@
draining from you over time. You can replenish it from your victims, specifically their necks."
spell_type = /datum/action/cooldown/spell/splattercasting
category = SPELLBOOK_CATEGORY_OFFENSIVE
no_coexistence_typecache = list(/datum/action/cooldown/spell/lichdom)
no_coexistence_typecache = list(/datum/action/cooldown/spell/lichdom, /datum/spellbook_entry/ghostliness)
/datum/spellbook_entry/sanguine_strike
name = "Exsanguinating Strike"
@@ -104,9 +104,10 @@
/datum/spellbook_entry/perks/heart_eater
name = "Heart Eater"
desc = "Gives you ability to obtain a person's life force by eating their heart. \
desc = "Gives you the ability to obtain a sentient being's life force by eating their heart. \
Additionally, you can tear a vulnerable being's heart out with your bare hands. \
By eating someone's heart you can increase your damage resistance or gain random mutation. \
Heart also give strong healing buff."
Hearts also provide strong healing."
hud_icon = "hearteater"
/datum/spellbook_entry/perks/heart_eater/buy_spell(mob/living/carbon/human/user, obj/item/spellbook/book, log_buy)
@@ -119,7 +119,7 @@
inhand_icon_state = "wizrobe"
body_parts_covered = CHEST|GROIN|ARMS|LEGS
armor_type = /datum/armor/suit_wizrobe
allowed = list(/obj/item/teleportation_scroll, /obj/item/highfrequencyblade/wizard)
allowed = list(/obj/item/highfrequencyblade/wizard, /obj/item/storage/belt/wand_bandolier, /obj/item/teleportation_scroll)
flags_inv = HIDEJUMPSUIT
strip_delay = 5 SECONDS
equip_delay_other = 5 SECONDS
@@ -113,16 +113,15 @@
var/atom/throw_target = get_edge_target_turf(target, get_dir(src, get_step_away(target, src)))
target.throw_at(throw_target, 200, 4)
/obj/item/mjollnir/attack(mob/living/target_mob, mob/user)
..()
if(QDELETED(target_mob))
/obj/item/mjollnir/afterattack(atom/target, mob/user)
if(QDELETED(target))
return
if(HAS_TRAIT(user, TRAIT_PACIFISM))
return
if(HAS_TRAIT(src, TRAIT_WIELDED))
yeet_shock(target_mob)
if(HAS_TRAIT(src, TRAIT_WIELDED) && isliving(target))
yeet_shock(target)
/obj/item/mjollnir/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
. = ..()
if(.)
return
if(!QDELETED(hit_atom) && isliving(hit_atom))
yeet_shock(hit_atom)
+5 -2
View File
@@ -332,14 +332,17 @@
. = ..()
AddElement(/datum/element/eyestab)
AddElement(/datum/element/wall_engraver)
//deals 200 damage to statues, meaning you can actually kill one in ~250 hits
AddElement(/datum/element/bane, target_type = /mob/living/basic/statue, damage_multiplier = 40)
AddComponent(/datum/component/bane, damage_multiplier = 40, should_bane_callback = CALLBACK(src, PROC_REF(bane_check)), label_text = "statues")
/obj/item/chisel/Destroy()
prepared_block = null
tracked_user = null
return ..()
/// Bane component callback
/obj/item/chisel/proc/bane_check(mob/living/target)
return istype(target, /mob/living/basic/statue)
/*
Hit the block to start
Point with the chisel at the target to choose what to sculpt or hit block to choose from preset statue types.
+6
View File
@@ -33,3 +33,9 @@
)
#endif
/datum/asset/simple/chat_dark
keep_local_name = FALSE
assets = list(
"tgui-chat-dark.bundle.css" = file("tgui/public/tgui-chat-dark.bundle.css"),
)
@@ -172,6 +172,40 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache())
SEND_SIGNAL(src, COMSIG_GASMIX_MERGED)
return TRUE
// Set the gas specie within the gas mix to a set amount, if there is none it will be created at the target temp
/datum/gas_mixture/proc/set_gas(gas_specie, amount)
ASSERT_GAS(gas_specie, src)
gases[gas_specie][MOLES] = amount
garbage_collect()
/datum/gas_mixture/proc/set_temperature(target_temp)
temperature = target_temp
/// Add a specific amount of moles to specified gas or add a new gas to the mix
/// amount is added so make it negative to remove
/datum/gas_mixture/proc/adjust_gas(gas, amount)
ASSERT_GAS(gas, src)
gases[gas][MOLES] += QUANTIZE(amount)
garbage_collect()
/// Add a specific amount of moles to all the gasses present or add a new gas to the mix
///gases_moles is an associative list of gas species to their amount to be added
/datum/gas_mixture/proc/adjust_multiple_gases(list/gases_moles)
for(var/gas_specie in gases_moles)
ASSERT_GAS(gas_specie, src)
gases[gas_specie][MOLES] += gases_moles[gas_specie]
garbage_collect()
/// Modify the gas list as to convert moles of gas species A to gas species B
/// reactant and product are the gas species to convert and conversion_amount is the amount to be converted
/datum/gas_mixture/proc/convert_gas(datum/gas/reactant, datum/gas/product, conversion_amount)
var/list/cached_gases = gases
assert_gases(reactant, product)
cached_gases[reactant][MOLES] -= QUANTIZE(conversion_amount)
cached_gases[product][MOLES] += QUANTIZE(conversion_amount)
garbage_collect()
///Proportionally removes amount of gas from the gas_mixture.
///Returns: gas_mixture with the gases removed
/datum/gas_mixture/proc/remove(amount)
@@ -764,3 +798,4 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache())
atmos_contents += temperature_str
return atmos_contents.Join(";")
@@ -220,7 +220,7 @@
plasma_burn_rate = min(plasma_burn_rate, plasma[MOLES], oxygen[MOLES] * INVERSE(oxygen_burn_ratio)) //Ensures matter is conserved properly
plasma[MOLES] = QUANTIZE(plasma[MOLES] - plasma_burn_rate)
oxygen[MOLES] = QUANTIZE(oxygen[MOLES] - (plasma_burn_rate * oxygen_burn_ratio))
if (super_saturation)
if(super_saturation)
ASSERT_GAS(/datum/gas/tritium, air)
cached_gases[/datum/gas/tritium][MOLES] += plasma_burn_rate
else
@@ -229,6 +229,7 @@
cached_gases[/datum/gas/carbon_dioxide][MOLES] += plasma_burn_rate * 0.75
cached_gases[/datum/gas/water_vapor][MOLES] += plasma_burn_rate * 0.25
SET_REACTION_RESULTS((plasma_burn_rate) * (1 + oxygen_burn_ratio))
var/energy_released = FIRE_PLASMA_ENERGY_RELEASED * plasma_burn_rate
var/new_heat_capacity = air.heat_capacity()
@@ -583,6 +584,8 @@
nitrous_oxide[MOLES] -= 0.4 * bz_formed
plasma[MOLES] -= 0.8 * bz_formed * (1-nitrous_oxide_decomposed_factor)
SET_REACTION_RESULTS(bz_formed)
var/energy_released = bz_formed * (BZ_FORMATION_ENERGY + nitrous_oxide_decomposed_factor * (N2O_DECOMPOSITION_ENERGY - BZ_FORMATION_ENERGY))
var/new_heat_capacity = air.heat_capacity()
@@ -681,6 +684,7 @@
bz[MOLES] -= heat_efficiency * 0.05 //bz gets consumed to balance the nitrium production and not make it too common and/or easy
cached_gases[/datum/gas/nitrium][MOLES] += heat_efficiency
SET_REACTION_RESULTS(heat_efficiency)
var/energy_used = heat_efficiency * NITRIUM_FORMATION_ENERGY
var/new_heat_capacity = air.heat_capacity()
@@ -829,6 +833,7 @@
tritium[MOLES] -= 5 * nob_formed * reduction_factor
nitrogen[MOLES] -= 10 * nob_formed
cached_gases[/datum/gas/hypernoblium][MOLES] += nob_formed // I'm not going to nitpick, but N20H10 feels like it should be an explosive more than anything.
SET_REACTION_RESULTS(nob_formed)
var/energy_released = nob_formed * NOBLIUM_FORMATION_ENERGY / max(bz[MOLES], 1)
var/new_heat_capacity = air.heat_capacity()
@@ -1254,6 +1259,7 @@
continue
gas[MOLES] -= reaction_rate * gas[MOLES] / total_not_antinoblium_moles
antinoblium[MOLES] += reaction_rate
SET_REACTION_RESULTS(reaction_rate)
var/new_heat_capacity = air.heat_capacity()
if(new_heat_capacity > MINIMUM_HEAT_CAPACITY)
@@ -14,6 +14,7 @@
/obj/machinery/atmospherics/components/binary/temperature_pump/Initialize(mapload)
. = ..()
AddComponent(/datum/component/usb_port, typecacheof(list(/obj/item/circuit_component/atmos_temperature_pump), only_root_path = TRUE))
register_context()
/obj/machinery/atmospherics/components/binary/temperature_pump/add_context(atom/source, list/context, obj/item/held_item, mob/user)
@@ -268,8 +268,7 @@
internal_pressure = internal_pressure > airs[i].return_pressure() ? internal_pressure : airs[i].return_pressure()
if(!filled_pipe)
default_deconstruction_crowbar(tool)
return ITEM_INTERACT_SUCCESS
return default_deconstruction_crowbar(user, tool)
to_chat(user, span_notice("You begin to unfasten \the [src]..."))
@@ -64,11 +64,11 @@ GLOBAL_LIST_INIT(electrolyzer_reactions, electrolyzer_reactions_list())
/datum/electrolyzer_reaction/h2o_conversion/react(datum/gas_mixture/air_mixture, working_power, list/electrolyzer_args = list())
var/old_heat_capacity = air_mixture.heat_capacity()
air_mixture.assert_gases(/datum/gas/water_vapor, /datum/gas/oxygen, /datum/gas/hydrogen)
var/proportion = min(air_mixture.gases[/datum/gas/water_vapor][MOLES] * INVERSE(2), (2.5 * (working_power ** 2)))
air_mixture.gases[/datum/gas/water_vapor][MOLES] -= proportion * 2
air_mixture.gases[/datum/gas/oxygen][MOLES] += proportion
air_mixture.gases[/datum/gas/hydrogen][MOLES] += proportion * 2
air_mixture.adjust_gas(/datum/gas/water_vapor, -proportion * 2)
air_mixture.adjust_gas(/datum/gas/oxygen, proportion)
air_mixture.adjust_gas(/datum/gas/hydrogen, proportion * 2)
var/new_heat_capacity = air_mixture.heat_capacity()
if(new_heat_capacity > MINIMUM_HEAT_CAPACITY)
air_mixture.temperature = max(air_mixture.temperature * old_heat_capacity / new_heat_capacity, TCMB)
@@ -100,8 +100,10 @@ GLOBAL_LIST_INIT(electrolyzer_reactions, electrolyzer_reactions_list())
var/list/hypernoblium = cached_gases[/datum/gas/hypernoblium]
var/list/antinoblium = cached_gases[/datum/gas/antinoblium]
var/electrolysed = hypernoblium[MOLES] * clamp(supermatter_power - POWER_PENALTY_THRESHOLD, 0, CRITICAL_POWER_PENALTY_THRESHOLD - POWER_PENALTY_THRESHOLD) / (CRITICAL_POWER_PENALTY_THRESHOLD - POWER_PENALTY_THRESHOLD)
hypernoblium[MOLES] -= electrolysed
antinoblium[MOLES] += electrolysed
air_mixture.adjust_gas(/datum/gas/hypernoblium, -electrolysed)
air_mixture.adjust_gas(/datum/gas/antinoblium, electrolysed)
var/new_heat_capacity = old_heat_capacity + electrolysed * (antinoblium[GAS_META][META_GAS_SPECIFIC_HEAT] - hypernoblium[GAS_META][META_GAS_SPECIFIC_HEAT])
if(new_heat_capacity > MINIMUM_HEAT_CAPACITY)
air_mixture.temperature = max(air_mixture.temperature * old_heat_capacity / new_heat_capacity, TCMB)
@@ -126,9 +128,11 @@ GLOBAL_LIST_INIT(electrolyzer_reactions, electrolyzer_reactions_list())
var/old_heat_capacity = air_mixture.heat_capacity()
air_mixture.assert_gases(/datum/gas/bz, /datum/gas/oxygen, /datum/gas/halon)
var/reaction_efficency = min(air_mixture.gases[/datum/gas/bz][MOLES] * (1 - NUM_E ** (-0.5 * air_mixture.temperature * working_power / FIRE_MINIMUM_TEMPERATURE_TO_EXIST)), air_mixture.gases[/datum/gas/bz][MOLES])
air_mixture.gases[/datum/gas/bz][MOLES] -= reaction_efficency
air_mixture.gases[/datum/gas/oxygen][MOLES] += reaction_efficency * 0.2
air_mixture.gases[/datum/gas/halon][MOLES] += reaction_efficency * 2
air_mixture.adjust_gas(/datum/gas/bz, -reaction_efficency)
air_mixture.adjust_gas(/datum/gas/oxygen, reaction_efficency * 0.2)
air_mixture.adjust_gas(/datum/gas/halon, reaction_efficency * 2)
var/energy_used = reaction_efficency * HALON_FORMATION_ENERGY
var/new_heat_capacity = air_mixture.heat_capacity()
if(new_heat_capacity > MINIMUM_HEAT_CAPACITY)
@@ -250,37 +250,37 @@
var/scaled_production = production_amount * selected_fuel.gas_production_multiplier
for(var/gas_id in fuel.requirements)
internal_fusion.gases[gas_id][MOLES] -= min(fuel_list[gas_id], fuel_consumption)
internal_fusion.adjust_gas(gas_id, -min(fuel_list[gas_id], fuel_consumption))
for(var/gas_id in fuel.primary_products)
internal_fusion.gases[gas_id][MOLES] += fuel_consumption * 0.5
internal_fusion.adjust_gas(gas_id, fuel_consumption * 0.5)
// Each recipe provides a tier list of six output gases.
// Which gases are produced depend on what the fusion level is.
var/list/tier = fuel.secondary_products
switch(power_level)
if(1)
moderator_internal.gases[tier[1]][MOLES] += scaled_production * 0.95
moderator_internal.gases[tier[2]][MOLES] += scaled_production * 0.75
moderator_internal.adjust_gas(tier[1], scaled_production * 0.95)
moderator_internal.adjust_gas(tier[2], scaled_production * 0.75)
if(2)
moderator_internal.gases[tier[1]][MOLES] += scaled_production * 1.65
moderator_internal.gases[tier[2]][MOLES] += scaled_production
moderator_internal.adjust_gas(tier[1], scaled_production * 1.65)
moderator_internal.adjust_gas(tier[2], scaled_production)
if(moderator_list[/datum/gas/plasma] > 50)
moderator_internal.gases[tier[3]][MOLES] += scaled_production * 1.15
moderator_internal.adjust_gas(tier[3], scaled_production * 1.15)
if(3)
moderator_internal.gases[tier[2]][MOLES] += scaled_production * 0.5
moderator_internal.gases[tier[3]][MOLES] += scaled_production * 0.45
moderator_internal.adjust_gas(tier[2], scaled_production * 0.5)
moderator_internal.adjust_gas(tier[3], scaled_production * 0.45)
if(4)
moderator_internal.gases[tier[3]][MOLES] += scaled_production * 1.65
moderator_internal.gases[tier[4]][MOLES] += scaled_production * 1.25
moderator_internal.adjust_gas(tier[3], scaled_production * 1.65)
moderator_internal.adjust_gas(tier[4], scaled_production * 1.25)
if(moderator_list[/datum/gas/plasma] > 50)
moderator_internal.gases[tier[5]][MOLES] += scaled_production * 1.15
moderator_internal.adjust_gas(tier[5], scaled_production * 1.15)
if(5)
moderator_internal.gases[tier[4]][MOLES] += scaled_production * 0.65
moderator_internal.gases[tier[5]][MOLES] += scaled_production
moderator_internal.gases[tier[6]][MOLES] += scaled_production * 0.75
moderator_internal.adjust_gas(tier[4], scaled_production * 0.65)
moderator_internal.adjust_gas(tier[5], scaled_production)
moderator_internal.adjust_gas(tier[6], scaled_production * 0.75)
if(6)
moderator_internal.gases[tier[5]][MOLES] += scaled_production * 0.35
moderator_internal.gases[tier[6]][MOLES] += scaled_production
moderator_internal.adjust_gas(tier[5], scaled_production * 0.35)
moderator_internal.adjust_gas(tier[6], scaled_production)
/**
* Perform common fusion actions:
@@ -293,94 +293,81 @@
switch(power_level)
if(1)
if(moderator_list[/datum/gas/plasma] > 100)
internal_output.assert_gases(/datum/gas/nitrous_oxide)
internal_output.gases[/datum/gas/nitrous_oxide] += scaled_production * 0.5
moderator_internal.gases[/datum/gas/plasma][MOLES] -= min(moderator_internal.gases[/datum/gas/plasma][MOLES], scaled_production * 0.85)
internal_output.adjust_gas(/datum/gas/nitrous_oxide, scaled_production * 0.5)
moderator_internal.adjust_gas(/datum/gas/plasma, min(moderator_internal.gases[/datum/gas/plasma][MOLES], scaled_production * 0.85))
if(moderator_list[/datum/gas/bz] > 150)
internal_output.assert_gases(/datum/gas/halon)
internal_output.gases[/datum/gas/halon][MOLES] += scaled_production * 0.55
moderator_internal.gases[/datum/gas/bz][MOLES] -= min(moderator_internal.gases[/datum/gas/bz][MOLES], scaled_production * 0.95)
internal_output.adjust_gas(/datum/gas/halon, scaled_production * 0.55)
moderator_internal.adjust_gas(/datum/gas/bz, min(moderator_internal.gases[/datum/gas/bz][MOLES], scaled_production * 0.95))
if(2)
if(moderator_list[/datum/gas/plasma] > 50)
internal_output.assert_gases(/datum/gas/bz)
internal_output.gases[/datum/gas/bz][MOLES] += scaled_production * 1.8
moderator_internal.gases[/datum/gas/plasma][MOLES] -= min(moderator_internal.gases[/datum/gas/plasma][MOLES], scaled_production * 1.75)
internal_output.adjust_gas(/datum/gas/bz, scaled_production * 1.8)
moderator_internal.adjust_gas(/datum/gas/plasma, min(moderator_internal.gases[/datum/gas/plasma][MOLES], scaled_production * 1.75))
if(moderator_list[/datum/gas/proto_nitrate] > 20)
radiation *= 1.55
heat_output *= 1.025
internal_output.assert_gases(/datum/gas/nitrium)
internal_output.gases[/datum/gas/nitrium][MOLES] += scaled_production * 1.05
moderator_internal.gases[/datum/gas/proto_nitrate][MOLES] -= min(moderator_internal.gases[/datum/gas/proto_nitrate][MOLES], scaled_production * 1.35)
internal_output.adjust_gas(/datum/gas/nitrium, scaled_production * 1.05)
moderator_internal.adjust_gas(/datum/gas/proto_nitrate, min(moderator_internal.gases[/datum/gas/proto_nitrate][MOLES], scaled_production * 1.35))
if(3, 4)
if(moderator_list[/datum/gas/plasma] > 10)
internal_output.assert_gases(/datum/gas/freon, /datum/gas/nitrium)
internal_output.gases[/datum/gas/freon][MOLES] += scaled_production * 0.15
internal_output.gases[/datum/gas/nitrium][MOLES] += scaled_production * 1.05
moderator_internal.gases[/datum/gas/plasma][MOLES] -= min(moderator_internal.gases[/datum/gas/plasma][MOLES], scaled_production * 0.45)
var/list/new_gases = list(/datum/gas/freon = scaled_production * 0.15, /datum/gas/nitrium = scaled_production * 1.05)
internal_output.adjust_multiple_gases(new_gases)
moderator_internal.adjust_gas(/datum/gas/plasma, min(moderator_internal.gases[/datum/gas/plasma][MOLES], scaled_production * 0.45))
if(moderator_list[/datum/gas/freon] > 50)
heat_output *= 0.9
radiation *= 0.8
if(moderator_list[/datum/gas/proto_nitrate]> 15)
internal_output.assert_gases(/datum/gas/nitrium, /datum/gas/halon)
internal_output.gases[/datum/gas/nitrium][MOLES] += scaled_production * 1.25
internal_output.gases[/datum/gas/halon][MOLES] += scaled_production * 1.15
moderator_internal.gases[/datum/gas/proto_nitrate][MOLES] -= min(moderator_internal.gases[/datum/gas/proto_nitrate][MOLES], scaled_production * 1.55)
var/list/new_gases = list(/datum/gas/nitrium = scaled_production * 1.25, /datum/gas/halon = scaled_production * 1.15)
internal_output.adjust_multiple_gases(new_gases)
moderator_internal.adjust_gas(/datum/gas/proto_nitrate, min(moderator_internal.gases[/datum/gas/proto_nitrate][MOLES], scaled_production * 1.55))
radiation *= 1.95
heat_output *= 1.25
if(moderator_list[/datum/gas/bz] > 100)
internal_output.assert_gases(/datum/gas/healium, /datum/gas/proto_nitrate)
internal_output.gases[/datum/gas/proto_nitrate][MOLES] += scaled_production * 1.5
internal_output.gases[/datum/gas/healium][MOLES] += scaled_production * 1.5
var/list/new_gases = list(/datum/gas/healium = scaled_production * 1.5, /datum/gas/proto_nitrate = scaled_production * 1.5)
internal_output.adjust_multiple_gases(new_gases)
visible_hallucination_pulse(src, HALLUCINATION_HFR(heat_output), 100 SECONDS * power_level * seconds_per_tick)
if(5)
if(moderator_list[/datum/gas/plasma] > 15)
internal_output.assert_gases(/datum/gas/freon)
internal_output.gases[/datum/gas/freon][MOLES] += scaled_production *0.25
moderator_internal.gases[/datum/gas/plasma][MOLES] -= min(moderator_internal.gases[/datum/gas/plasma][MOLES], scaled_production * 1.45)
internal_output.adjust_gas(/datum/gas/freon, scaled_production *0.25)
moderator_internal.adjust_gas(/datum/gas/plasma,min(moderator_internal.gases[/datum/gas/plasma][MOLES], scaled_production * 1.45))
if(moderator_list[/datum/gas/freon] > 500)
heat_output *= 0.5
radiation *= 0.2
if(moderator_list[/datum/gas/proto_nitrate] > 50)
internal_output.assert_gases(/datum/gas/nitrium, /datum/gas/pluoxium)
internal_output.gases[/datum/gas/nitrium][MOLES] += scaled_production * 1.95
internal_output.gases[/datum/gas/pluoxium][MOLES] += scaled_production
moderator_internal.gases[/datum/gas/proto_nitrate][MOLES] -= min(moderator_internal.gases[/datum/gas/proto_nitrate][MOLES], scaled_production * 1.35)
var/list/new_gases = list(/datum/gas/nitrium = scaled_production * 1.95, /datum/gas/pluoxium = scaled_production)
internal_output.adjust_multiple_gases(new_gases)
moderator_internal.adjust_gas(/datum/gas/proto_nitrate, min(moderator_internal.gases[/datum/gas/proto_nitrate][MOLES], scaled_production * 1.35))
radiation *= 1.95
heat_output *= 1.25
if(moderator_list[/datum/gas/bz] > 100)
internal_output.assert_gases(/datum/gas/healium, /datum/gas/freon)
internal_output.gases[/datum/gas/healium][MOLES] += scaled_production
var/list/new_gases = list(/datum/gas/healium = scaled_production, /datum/gas/freon = scaled_production * 1.15)
internal_output.adjust_multiple_gases(new_gases)
visible_hallucination_pulse(src, HALLUCINATION_HFR(heat_output), 100 SECONDS * power_level * seconds_per_tick)
internal_output.gases[/datum/gas/freon][MOLES] += scaled_production * 1.15
if(moderator_list[/datum/gas/healium] > 100)
if(critical_threshold_proximity > 400)
critical_threshold_proximity = max(critical_threshold_proximity - (moderator_list[/datum/gas/healium] / 100 * seconds_per_tick ), 0)
moderator_internal.gases[/datum/gas/healium][MOLES] -= min(moderator_internal.gases[/datum/gas/healium][MOLES], scaled_production * 20)
moderator_internal.adjust_gas(/datum/gas/healium, -min(moderator_internal.gases[/datum/gas/healium][MOLES], scaled_production * 20))
if(moderator_internal.temperature < 1e7 || (moderator_list[/datum/gas/plasma] > 100 && moderator_list[/datum/gas/bz] > 50))
internal_output.assert_gases(/datum/gas/antinoblium)
internal_output.gases[/datum/gas/antinoblium][MOLES] += dirty_production_rate * 0.9 / 0.065 * seconds_per_tick
internal_output.adjust_gas(/datum/gas/antinoblium, dirty_production_rate * 0.9 / 0.065 * seconds_per_tick)
if(6)
internal_output.assert_gases(/datum/gas/antinoblium)
if(moderator_list[/datum/gas/plasma] > 30)
internal_output.assert_gases(/datum/gas/bz)
internal_output.gases[/datum/gas/bz][MOLES] += scaled_production * 1.15
moderator_internal.gases[/datum/gas/plasma][MOLES] -= min(moderator_internal.gases[/datum/gas/plasma][MOLES], scaled_production * 1.45)
internal_output.adjust_gas(/datum/gas/bz, scaled_production * 1.15)
moderator_internal.adjust_gas(/datum/gas/plasma, -min(moderator_internal.gases[/datum/gas/plasma][MOLES], scaled_production * 1.45))
if(moderator_list[/datum/gas/proto_nitrate])
internal_output.assert_gases(/datum/gas/zauker, /datum/gas/nitrium)
internal_output.gases[/datum/gas/zauker][MOLES] += scaled_production * 5.35
internal_output.gases[/datum/gas/nitrium][MOLES] += scaled_production * 2.15
moderator_internal.gases[/datum/gas/proto_nitrate][MOLES] -= min(moderator_internal.gases[/datum/gas/proto_nitrate][MOLES], scaled_production * 3.35)
var/list/new_gases = list(/datum/gas/zauker = scaled_production * 5.35, /datum/gas/nitrium = scaled_production * 2.15)
internal_output.adjust_multiple_gases(new_gases)
moderator_internal.adjust_gas(/datum/gas/proto_nitrate, -min(moderator_internal.gases[/datum/gas/proto_nitrate][MOLES], scaled_production * 3.35))
radiation *= 2
heat_output *= 2.25
if(moderator_list[/datum/gas/bz])
visible_hallucination_pulse(src, HALLUCINATION_HFR(heat_output), 100 SECONDS * power_level * seconds_per_tick)
internal_output.gases[/datum/gas/antinoblium][MOLES] += clamp(dirty_production_rate / 0.045, 0, 10) * seconds_per_tick
internal_output.adjust_gas(/datum/gas/antinoblium, clamp(dirty_production_rate / 0.045, 0, 10) * seconds_per_tick)
if(moderator_list[/datum/gas/healium] > 100)
if(critical_threshold_proximity > 400)
critical_threshold_proximity = max(critical_threshold_proximity - (moderator_list[/datum/gas/healium] / 100 * seconds_per_tick ), 0)
moderator_internal.gases[/datum/gas/healium][MOLES] -= min(moderator_internal.gases[/datum/gas/healium][MOLES], scaled_production * 20)
internal_fusion.gases[/datum/gas/antinoblium][MOLES] += dirty_production_rate * 0.01 / 0.095 * seconds_per_tick
moderator_internal.adjust_gas(/datum/gas/healium, -min(moderator_internal.gases[/datum/gas/healium][MOLES], scaled_production * 20))
internal_fusion.adjust_gas(/datum/gas/antinoblium, dirty_production_rate * 0.01 / 0.095 * seconds_per_tick)
//Modifies the internal_fusion temperature with the amount of heat output
var/temperature_modifier = selected_fuel.temperature_change_multiplier
@@ -413,7 +400,7 @@
var/max_iron_removable = IRON_OXYGEN_HEAL_PER_SECOND
var/iron_removed = min(max_iron_removable * seconds_per_tick, iron_content)
iron_content -= iron_removed
moderator_internal.gases[/datum/gas/oxygen][MOLES] -= iron_removed * OXYGEN_MOLES_CONSUMED_PER_IRON_HEAL
moderator_internal.adjust_gas(/datum/gas/oxygen, -iron_removed * OXYGEN_MOLES_CONSUMED_PER_IRON_HEAL)
check_gravity_pulse(seconds_per_tick)
@@ -55,12 +55,15 @@
if(TOOL_SCREWDRIVER)
context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] panel"
if(TOOL_WRENCH)
context[SCREENTIP_CONTEXT_RMB] = "Rotate"
context[SCREENTIP_CONTEXT_LMB] = "Rotate"
return CONTEXTUAL_SCREENTIP_SET
/obj/machinery/atmospherics/components/binary/crystallizer/screwdriver_act(mob/living/user, obj/item/tool)
return on ? NONE : default_deconstruction_screwdriver(user, tool)
/obj/machinery/atmospherics/components/binary/crystallizer/wrench_act(mob/living/user, obj/item/tool)
return default_change_direction_wrench(user, tool)
/obj/machinery/atmospherics/components/binary/crystallizer/crowbar_act(mob/living/user, obj/item/tool)
return crowbar_deconstruction_act(user, tool, internal.return_pressure())
@@ -143,8 +143,8 @@
var/pressure_limit = max_pressure * safety_margin
var/moles_to_add = (pressure_limit * air_contents.volume) / (R_IDEAL_GAS_EQUATION * air_contents.temperature)
air_contents.assert_gas(gastype)
air_contents.gases[gastype][MOLES] += moles_to_add
air_contents.adjust_gas(gastype, moles_to_add)
air_contents.archive()
/obj/machinery/atmospherics/components/tank/process_atmos()
@@ -33,6 +33,9 @@
var/datum/gas_mixture/external = location.return_air()
var/datum/gas_mixture/internal = airs[1]
if(!internal.volume || !external.volume)
return
if(internal.equalize(external))
air_update_turf(FALSE, FALSE)
update_parents()
@@ -43,6 +43,8 @@
/obj/machinery/atmospherics/components/unary/thermomachine/Initialize(mapload)
. = ..()
update_appearance(UPDATE_ICON)
if(interactive)
AddComponent(/datum/component/usb_port, typecacheof(list(/obj/item/circuit_component/thermomachine), only_root_path = TRUE))
register_context()
/obj/machinery/atmospherics/components/unary/thermomachine/add_context(atom/source, list/context, obj/item/held_item, mob/user)
@@ -308,9 +308,8 @@
pressure_limit = 1e14
/obj/machinery/portable_atmospherics/canister/fusion_test/create_gas()
air_contents.add_gases(/datum/gas/hydrogen, /datum/gas/tritium)
air_contents.gases[/datum/gas/hydrogen][MOLES] = 300
air_contents.gases[/datum/gas/tritium][MOLES] = 300
air_contents.adjust_gas(/datum/gas/hydrogen, 300)
air_contents.adjust_gas(/datum/gas/tritium, 300)
air_contents.temperature = 10000
SSair.start_processing_machine(src)
@@ -323,9 +322,8 @@
greyscale_colors = "#9fba6c#3d4680"
/obj/machinery/portable_atmospherics/canister/anesthetic_mix/create_gas()
air_contents.add_gases(/datum/gas/oxygen, /datum/gas/nitrous_oxide)
air_contents.gases[/datum/gas/oxygen][MOLES] = (O2_ANESTHETIC * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)
air_contents.gases[/datum/gas/nitrous_oxide][MOLES] = (N2O_ANESTHETIC * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)
air_contents.adjust_gas(/datum/gas/oxygen, (O2_ANESTHETIC * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature))
air_contents.adjust_gas(/datum/gas/nitrous_oxide, (N2O_ANESTHETIC * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature))
SSair.start_processing_machine(src)
/**
@@ -335,14 +333,12 @@
/obj/machinery/portable_atmospherics/canister/proc/create_gas()
if(!gas_type)
return
air_contents.add_gas(gas_type)
air_contents.gases[gas_type][MOLES] = (maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)
air_contents.adjust_gas(gas_type, (maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature))
SSair.start_processing_machine(src)
/obj/machinery/portable_atmospherics/canister/air/create_gas()
air_contents.add_gases(/datum/gas/oxygen, /datum/gas/nitrogen)
air_contents.gases[/datum/gas/oxygen][MOLES] = (O2STANDARD * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)
air_contents.gases[/datum/gas/nitrogen][MOLES] = (N2STANDARD * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)
air_contents.adjust_gas(/datum/gas/oxygen, (O2STANDARD * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature))
air_contents.adjust_gas(/datum/gas/nitrogen, (N2STANDARD * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature))
SSair.start_processing_machine(src)
/obj/machinery/portable_atmospherics/canister/update_icon_state()
@@ -93,6 +93,7 @@
filtered.temperature = filtering.temperature
for(var/gas in filtering.gases & scrubbing)
filtered.add_gas(gas)
filtered.gases[gas][MOLES] = filtering.gases[gas][MOLES] // Shuffle the "bad" gasses to the filtered mixture.
filtering.gases[gas][MOLES] = 0
filtering.garbage_collect() // Now that the gasses are set to 0, clean up the mixture.
@@ -84,7 +84,7 @@
//contains all of the gas we're sucking out of the tile, gets put into our parent pipenet
var/datum/gas_mixture/filtered_out = new
var/list/filtered_gases = filtered_out.gases
filtered_out.temperature = environment.temperature
//maximum percentage of the turfs gas we can filter
@@ -102,8 +102,8 @@
filtered_out.add_gas(gas)
var/transferred_moles = max(QUANTIZE(env_gases[gas][MOLES] * removal_ratio * (env_gases[gas][MOLES] / total_moles_to_remove)), min(MOLAR_ACCURACY*1000, env_gases[gas][MOLES]))
filtered_gases[gas][MOLES] = transferred_moles
env_gases[gas][MOLES] -= transferred_moles
filtered_out.adjust_gas(gas, transferred_moles)
environment.adjust_gas(gas, -transferred_moles)
environment.garbage_collect()
+1 -1
View File
@@ -11,7 +11,7 @@
var/obj/parent = new
for (var/obj/machinery/vending/vending_type as anything in sort_list(subtypesof(/obj/machinery/vending), GLOBAL_PROC_REF(cmp_typepaths_asc)))
var/obj/machinery/vending/parent_machine = type2parent(vending_type)
var/obj/machinery/vending/parent_machine = vending_type::parent_type
if(initial(parent_machine.name) == initial(vending_type.name))
continue //Same name, likely just a slightly touched up subtype for specific maps.
var/obj/machinery/vending/vending_machine = new vending_type(parent)
@@ -0,0 +1,36 @@
/obj/machinery/deployable_turret/naval_cannon
name = "Bofors Naval Cannon"
desc = "A naval cannon of some variety, it probably won't feel good to be on the other end of this thing."
icon = 'icons/obj/weapons/naval_cannon.dmi'
icon_state = "turret_bofors"
max_integrity = 5000 //its a naval cannon, but its not unbreakable
projectile_type = /obj/projectile/bullet/mm40
anchored = TRUE //nobody should be bringing this back to station, this is a set piece.
number_of_shots = 1
firesound = 'sound/items/weapons/gun/general/cannon.ogg'
overheatsound = 'sound/items/tools/ratchet_slow.ogg'
can_be_undeployed = FALSE
always_anchored = TRUE
/obj/item/ammo_casing/mm40
name = "40mm shell"
desc = "A 40mm shell."
caliber = CALIBER_40MM_SHELL
projectile_type = /obj/projectile/bullet/mm40
icon_state = "40mm-shell"
newtonian_force = 10
slowdown = 2 //tank shells weigh a lot
/obj/projectile/bullet/mm40
name ="40mm shell"
icon_state = "40mm"
speed = 2
range = 400
damage = 600
paralyze = 5 SECONDS
armour_penetration = 50
/obj/projectile/bullet/mm40/on_hit(atom/target, blocked = 0, pierce_hit)
..()
explosion(target, devastation_range = -1, heavy_impact_range = 2, light_impact_range = 3, flame_range = 4, flash_range = 6, adminlog = FALSE)
return BULLET_ACT_HIT
@@ -12,11 +12,10 @@
obj_flags = CONDUCTS_ELECTRICITY
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
/obj/structure/grille/indestructible/screwdriver_act(mob/living/user, obj/item/tool)
return NONE
/obj/structure/grille/indestructible/wirecutter_act(mob/living/user, obj/item/tool)
return NONE
/obj/structure/grille/indestructible/Initialize(mapload)
. = ..()
AddElement(/datum/element/tool_blocker, TOOL_SCREWDRIVER, TOOL_ACT_PRIMARY)
AddElement(/datum/element/tool_blocker, TOOL_WIRECUTTER, TOOL_ACT_PRIMARY)
/obj/effect/spawner/structure/window/reinforced/indestructible
spawn_list = list(/obj/structure/grille/indestructible, /obj/structure/window/reinforced/fulltile/indestructible)
@@ -188,7 +188,11 @@
if(damage > 30 && prob(30))
INVOKE_ASYNC(old_body, TYPE_PROC_REF(/mob/living, emote), "scream")
old_body.apply_damage(damage, damage_type, def_zone, blocked, wound_bonus = CANT_WOUND)
var/zone = def_zone
if(isbodypart(def_zone)) // If the defined zone is a bodypart, then it is the bodypart of the domain avatar. We don't want that bodypart, we want the real body's bodypart, so we go back to zone.
zone = astype(def_zone, /obj/item/bodypart).body_zone
old_body.apply_damage(damage, damage_type, zone, blocked, wound_bonus = CANT_WOUND)
if(old_body.stat > SOFT_CRIT) // KO!
full_avatar_disconnect(cause_damage = TRUE)
@@ -1,36 +0,0 @@
/// Attaches to a turf so it spawns a crate when a certain amount of points are added to it.
/datum/component/bitrunning_points
/// The amount required to spawn a crate
var/points_goal = 10
/// A special condition limits this from spawning a crate
var/points_received = 0
/datum/component/bitrunning_points/Initialize(datum/lazy_template/virtual_domain/domain)
if(!isturf(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(domain, COMSIG_BITRUNNER_GOAL_POINT, PROC_REF(on_add_points))
/// Listens for points to be added which will eventually spawn a crate.
/datum/component/bitrunning_points/proc/on_add_points(datum/source, points_to_add)
SIGNAL_HANDLER
points_received += points_to_add
if(points_received < points_goal)
return
reveal()
/// Spawns the crate with some effects
/datum/component/bitrunning_points/proc/reveal()
playsound(src, 'sound/effects/magic/blink.ogg', 50, TRUE)
var/turf/tile = parent
var/obj/structure/closet/crate/secure/bitrunning/encrypted/crate = new()
crate.forceMove(tile) // Triggers any on-move effects on that turf
do_sparks(5, FALSE, tile, spark_type = /datum/effect_system/basic/spark_spread/quantum)
qdel(src)
+1 -1
View File
@@ -186,7 +186,7 @@
/obj/item/borg/upgrade/modkit/range,
/obj/item/borg/upgrade/modkit/damage,
/obj/item/borg/upgrade/modkit/cooldown,
/obj/item/borg/upgrade/modkit/aoe/mobs,
/obj/item/borg/upgrade/modkit/cooldown/aoe/mobs,
/obj/item/borg/upgrade/modkit/human_passthrough,
)
@@ -64,6 +64,14 @@
if(isnull(user.mind))
return
var/obj/machinery/quantum_server/our_server = server_ref.resolve()
if(!our_server.domain_complete)
for(var/datum/weakref/ghostrole_weakref as anything in our_server.spawned_threat_refs)
var/mob/living/ghostrole = ghostrole_weakref.resolve()
if(ghostrole?.stat == CONSCIOUS && ghostrole.client && ghostrole.mind.has_antag_datum(/datum/antagonist/bitrunning_glitch))
to_chat(user, span_danger("A being in the simulation is preventing your retreat. You must either complete your mission or remove the obstacle before safe exit will be possible."))
return
if(!HAS_TRAIT(user, TRAIT_TEMPORARY_BODY))
balloon_alert(user, "no connection detected")
return
@@ -16,6 +16,8 @@
var/datum/lazy_template/virtual_domain/generated_domain
/// If the current domain was a random selection
var/domain_randomized = FALSE
/// Whether the domain is finished, so the bitrunners can leave despite glitches
var/domain_complete = FALSE
/// Prevents multiple user actions. Handled by loading domains and cooldowns
var/is_ready = TRUE
/// Chance multipled by threat to spawn a glitch
+2
View File
@@ -80,6 +80,8 @@
generated_domain.disk_reward_spawned = TRUE
chosen_forge.start_to_spawn(reward_cache)
domain_complete = TRUE
return TRUE
@@ -53,12 +53,12 @@
scrub_vdom()
is_ready = TRUE
return FALSE
//We will want to record how the domain was selected. Either entirely randomly, with the name redacted, or with full information.
//Without this, it is difficult to determine what domains are selected more often intentionallly, vs unintentionally.
var/selection_type
if(was_random_selection)
selection_type = "random selection"
else
@@ -168,8 +168,7 @@
continue
if(istype(thing, /obj/effect/landmark/bitrunning/loot_signal))
var/turf/signaler_turf = get_turf(thing)
signaler_turf.AddComponent(/datum/component/bitrunning_points, generated_domain)
generated_domain.main_crate_loc = thing.loc
qdel(thing)
continue
@@ -200,6 +199,7 @@
/// Stops the current virtual domain and disconnects all users
/obj/machinery/quantum_server/proc/reset(fast = FALSE)
is_ready = FALSE
domain_complete = FALSE
sever_connections()
+16
View File
@@ -142,6 +142,22 @@
aas.broadcast("QUANTUM SERVER ALERT: Fabrication protocols have crashed unexpectedly. Please evacuate the area.", list(RADIO_CHANNEL_SUPPLY))
timeout = 10 SECONDS
var/bitrunners_alive = 0
var/island_brawl_exception = istype(generated_domain, /datum/lazy_template/virtual_domain/island_brawl)
for(var/datum/weakref/bitrunner_ref in avatar_connection_refs)
var/mob/living/bitrunner = astype(bitrunner_ref.resolve(), /datum/component/avatar_connection)?.parent
if(!bitrunner)
continue
if((bitrunner.stat > CONSCIOUS) || !bitrunner.client)
continue
if(island_brawl_exception)
timeout *= max(5 - generated_domain.main_crate_points, 1)
continue
bitrunners_alive++
timeout *= 5
if(bitrunners_alive)
to_chat(antag, span_warning("[bitrunners_alive] criminals still remain here, pilfering your domain. It will be more difficult to leave until they are handled."))
if(!do_after(antag, timeout) || QDELETED(chosen_forge) || QDELETED(antag) || QDELETED(src) || !is_ready || !is_operational)
chosen_forge.setup_particles()
return
@@ -52,6 +52,12 @@
var/secondary_loot_generated
/// Has this domain been beaten with high enough score to spawn a tech disk?
var/disk_reward_spawned = FALSE
/// The amount of points towards the spawning of the main crate, on maps using points.
var/main_crate_points = 0
/// The amount of points required to spawn the main crate, on maps using points.
var/main_crate_point_goal = 10
/// The location the crate will spawn when enough points are accumulated, on maps using points.
var/main_crate_loc
/**
* Modularity
@@ -99,7 +105,19 @@
/// Sends a point to any loot signals on the map
/datum/lazy_template/virtual_domain/proc/add_points(points_to_add = 1)
SEND_SIGNAL(src, COMSIG_BITRUNNER_GOAL_POINT, points_to_add)
main_crate_points += points_to_add
if(main_crate_points >= main_crate_point_goal)
reveal()
/datum/lazy_template/virtual_domain/proc/reveal()
if(!main_crate_loc)
return
var/turf/spawn_loc = get_turf(main_crate_loc)
playsound(spawn_loc, 'sound/effects/magic/blink.ogg', 50, TRUE)
var/obj/structure/closet/crate/secure/bitrunning/encrypted/crate = new()
crate.forceMove(spawn_loc) // Triggers any on-move effects on that turf
do_sparks(5, FALSE, spawn_loc, spark_type = /datum/effect_system/basic/spark_spread/quantum)
main_crate_loc = null
/// Loads the ghost candidates.
/datum/lazy_template/virtual_domain/proc/load_advanced_npcs(list/mob/lucky_ghosts)
+4 -5
View File
@@ -461,11 +461,10 @@
/obj/structure/table/reinforced/ctf
resistance_flags = INDESTRUCTIBLE
/obj/structure/table/reinforced/ctf/wrench_act_secondary(mob/living/user, obj/item/tool)
return NONE
/obj/structure/table/reinforced/ctf/screwdriver_act_secondary(mob/living/user, obj/item/tool)
return NONE
/obj/structure/table/reinforced/ctf/Initialize(mapload)
. = ..()
AddElement(/datum/element/tool_blocker, TOOL_SCREWDRIVER, TOOL_ACT_SECONDARY)
AddElement(/datum/element/tool_blocker, TOOL_WRENCH, TOOL_ACT_SECONDARY)
#define CTF_LOADING_UNLOADED 0
#define CTF_LOADING_LOADING 1
+1
View File
@@ -68,6 +68,7 @@
start_tracking(id_card)
/datum/bounty/patrol/proc/start_tracking(obj/item/card/id/id_card)
contribution |= id_card.registered_account
tracker = AddComponent(/datum/component/connect_containers, id_card, list(COMSIG_MOVABLE_MOVED = PROC_REF(on_card_moved)))
RegisterSignal(id_card, COMSIG_MOVABLE_MOVED, PROC_REF(on_card_moved))
-26
View File
@@ -14,7 +14,6 @@
interface_type = "CargoExpress"
var/message
var/list/meme_pack_data
/// The linked supplypod beacon
var/obj/item/supplypod_beacon/beacon
/// Where we droppin boys
@@ -31,16 +30,11 @@
/obj/machinery/computer/cargo/express/Initialize(mapload)
. = ..()
packin_up()
landingzone = GLOB.areas_by_type[landingzone]
if (isnull(landingzone))
WARNING("[src] couldnt find a Quartermaster/Storage (aka cargobay) area on the station, and as such it has set the supplypod landingzone to the area it resides in.")
landingzone = get_area(src)
/obj/machinery/computer/cargo/express/on_construction(mob/user)
. = ..()
packin_up()
/obj/machinery/computer/cargo/express/Destroy()
if(beacon)
beacon.unlink_console()
@@ -87,23 +81,8 @@
var/obj/item/circuitboard/computer/cargo/board = circuit
board.obj_flags |= EMAGGED
board.contraband = TRUE
packin_up()
return TRUE
/obj/machinery/computer/cargo/express/proc/packin_up(forced = FALSE) // oh shit, I'm sorry
meme_pack_data = list() // sorry for what?
if(!forced && !SSshuttle.initialized) // our quartermaster taught us not to be ashamed of our supply packs
SSshuttle.express_consoles += src // specially since they're such a good price and all
return // yeah, I see that, your quartermaster gave you good advice
// it gets cheaper when I return it
for(var/pack_id in SSshuttle.supply_packs) // mmhm
var/datum/supply_pack/pack = SSshuttle.supply_packs[pack_id] // sometimes, I return it so much, I rip the manifest
if(!meme_pack_data[pack.group]) // see, my quartermaster taught me a few things too
meme_pack_data[pack.group] = list( // like, how not to rip the manifest
"name" = pack.group, // by using someone else's crate
"packs" = get_packs_data(pack.group, express = TRUE), // will you show me?
) // i'd be right happy to
/obj/machinery/computer/cargo/express/ui_data(mob/user)
var/canBeacon = beacon && (isturf(beacon.loc) || ismob(beacon.loc))//is the beacon in a valid location?
var/list/data = list()
@@ -124,7 +103,6 @@
data["hasBeacon"] = beacon != null//is there a linked beacon?
data["beaconName"] = beacon ? beacon.name : "No Beacon Found"
data["printMsg"] = COOLDOWN_FINISHED(src, beacon_print_cooldown) ? "Print Beacon for [BEACON_COST] [MONEY_NAME]" : "Print Beacon for [BEACON_COST] [MONEY_NAME] ([COOLDOWN_TIMELEFT(src, beacon_print_cooldown)])" //buttontext for printing beacons
data["supplies"] = list()
message = "Sales are near-instantaneous - please choose carefully."
if(SSshuttle.supply_blocked)
message = blockade_warning
@@ -135,10 +113,6 @@
if(obj_flags & EMAGGED)
message = "(&!#@ERROR: R0UTING_#PRO7O&OL MALF(*CT#ON. $UG%ESTE@ ACT#0N: !^/PULS3-%E)ET CIR*)ITB%ARD."
data["message"] = message
if(!meme_pack_data)
packin_up()
stack_trace("There was no pack data for [src]")
data["supplies"] = meme_pack_data
return data
/obj/machinery/computer/cargo/express/get_discount()
+13 -2
View File
@@ -203,6 +203,12 @@
cost = PAYCHECK_CREW * 3
contains = list(/obj/item/storage/toolbox/mechanical)
/datum/supply_pack/goody/autolatheboard
name = "Autolathe Circuit Board"
desc = "A single autolathe circuit board for your construction needs."
cost = PAYCHECK_CREW * 2
contains = list(/obj/item/circuitboard/machine/autolathe)
/datum/supply_pack/goody/valentine
name = "Valentine Card"
desc = "Make an impression on that special someone! Comes with one valentine card and a free candy heart!"
@@ -435,12 +441,17 @@
cost = PAYCHECK_CREW * 5
contains = list(/obj/item/key/golfcart)
/datum/supply_pack/goody/handheld_crew_monitor
name = "Handheld Crew Monitor"
desc = "A crate containing a handheld crew monitor"
desc = "A crate containing a handheld crew monitor."
cost = /obj/item/sensor_device::custom_premium_price * 1.25 // 1.25X base vending machine value
contains = list(
/obj/item/sensor_device,
)
crate_name = "handheld crew monitor crate"
/datum/supply_pack/goody/camera
name = "Broadcast Camera"
desc = "A single broadcast camera which broadcasts to the station's entertainment monitors, for all your theatrical needs."
cost = PAYCHECK_COMMAND * 8
contains = list(/obj/item/broadcast_camera/cargo)
@@ -112,3 +112,14 @@
price_max = CARGO_CRATE_VALUE * 4.5
stock_max = 3
availability_prob = 60
/datum/market_item/clothing/totally_normal_pet_collar
name = "Strange Pet Collar"
desc = "We found these in the back of a militarized shuttle destined for New Osaka. Who the hell transports pet collars \
in an armored vessel? Whatever, if Cybersun Industries didn't want you to have them, that must mean they're REAL special! \
Which is why we're charging you extra for them."
item = /obj/item/clothing/neck/petcollar/wearable/cyber
price_min = CARGO_CRATE_VALUE * 5
price_max = CARGO_CRATE_VALUE * 10
stock_max = 5
availability_prob = 20
@@ -153,3 +153,13 @@
price_max = CARGO_CRATE_VALUE * 5
stock_max = 2
availability_prob = 80
/datum/market_item/weapon/earthcracker
name = "E-1 Earthcracker"
desc = "Surplus mining equipment from one of the many, nameless asteroid mining companies bought out by Nanotrasen in this sector.\
While they were decent enough at mining asteroids, these things really shine at leaving large cracks on station-class hulls when deployed these days."
item = /obj/item/earthcracker/small
price_min = CARGO_CRATE_VALUE
price_max = CARGO_CRATE_VALUE * 3
stock_max = 1
availability_prob = 30

Some files were not shown because too many files have changed in this diff Show More