mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-23 21:18:37 +01:00
Merge branch 'upstream-6-2-2026'
# Conflicts: # _maps/shuttles/zubbers/emergency_millionaire.dmm
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -426,6 +426,7 @@
|
||||
ROLE_MALF,
|
||||
ROLE_NINJA,
|
||||
ROLE_OPERATIVE,
|
||||
ROLE_CLOWN_OPERATIVE,
|
||||
ROLE_OVERTHROW,
|
||||
ROLE_PARADOX_CLONE,
|
||||
ROLE_REV,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 />"
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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."
|
||||
|
||||
+3
-4
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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]..."))
|
||||
|
||||
|
||||
+13
-9
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user