diff --git a/code/game/area/Space_Station_13_areas.dm b/code/game/area/Space_Station_13_areas.dm
index 1bd30a77f5..8a40fc8b56 100644
--- a/code/game/area/Space_Station_13_areas.dm
+++ b/code/game/area/Space_Station_13_areas.dm
@@ -499,18 +499,18 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
icon_state = "Theatre"
/area/library
- name = "Library"
- icon_state = "library"
- flags_1 = NONE
+ name = "Library"
+ icon_state = "library"
+ flags_1 = NONE
/area/library/lounge
- name = "Library Lounge"
- icon_state = "library"
+ name = "Library Lounge"
+ icon_state = "library"
/area/library/abandoned
- name = "Abandoned Library"
- icon_state = "library"
- flags_1 = NONE
+ name = "Abandoned Library"
+ icon_state = "library"
+ flags_1 = NONE
/area/chapel
icon_state = "chapel"
@@ -559,9 +559,9 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
icon_state = "engine"
/area/engine/atmos
- name = "Atmospherics"
- icon_state = "atmos"
- flags_1 = NONE
+ name = "Atmospherics"
+ icon_state = "atmos"
+ flags_1 = NONE
/area/engine/atmospherics_engine
name = "Atmospherics Engine"
@@ -1297,7 +1297,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
clockwork_warp_allowed = FALSE
clockwork_warp_fail = "For safety reasons, warping here is disallowed; the radio and bluespace noise could cause catastrophic results."
ambientsounds = list('sound/ambience/ambisin2.ogg', 'sound/ambience/signal.ogg', 'sound/ambience/signal.ogg', 'sound/ambience/ambigen10.ogg', 'sound/ambience/ambitech.ogg',\
- 'sound/ambience/ambitech2.ogg', 'sound/ambience/ambitech3.ogg', 'sound/ambience/ambimystery.ogg')
+ 'sound/ambience/ambitech2.ogg', 'sound/ambience/ambitech3.ogg', 'sound/ambience/ambimystery.ogg')
/area/tcommsat/entrance
name = "Telecomms Teleporter"
diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm
index 8aa910b2e2..10650153d6 100644
--- a/code/game/area/areas.dm
+++ b/code/game/area/areas.dm
@@ -29,8 +29,6 @@
var/outdoors = FALSE //For space, the asteroid, lavaland, etc. Used with blueprints to determine if we are adding a new area (vs editing a station room)
- var/totalbeauty = 0 //All beauty in this area combined, only includes indoor area.
- var/beauty = 0 // Beauty average per open turf in the area
var/areasize = 0 //Size of the area in open turfs, only calculated for indoors areas.
var/power_equip = TRUE
@@ -48,6 +46,8 @@
var/noteleport = FALSE //Are you forbidden from teleporting to the area? (centcom, mobs, wizard, hand teleporter)
var/hidden = FALSE //Hides area from player Teleport function.
var/safe = FALSE //Is the area teleport-safe: no space / radiation / aggresive mobs / other dangers
+ /// If false, loading multiple maps with this area type will create multiple instances.
+ var/unique = TRUE
var/no_air = null
@@ -84,6 +84,12 @@ GLOBAL_LIST_EMPTY(teleportlocs)
// ===
+/area/New()
+ // This interacts with the map loader, so it needs to be set immediately
+ // rather than waiting for atoms to initialize.
+ if (unique)
+ GLOB.areas_by_type[type] = src
+ return ..()
/area/Initialize()
icon_state = ""
@@ -135,9 +141,10 @@ GLOBAL_LIST_EMPTY(teleportlocs)
/area/LateInitialize()
power_change() // all machines set to current power level, also updates icon
- update_beauty()
/area/Destroy()
+ if(GLOB.areas_by_type[type] == src)
+ GLOB.areas_by_type[type] = null
STOP_PROCESSING(SSobj, src)
return ..()
@@ -448,26 +455,22 @@ GLOBAL_LIST_EMPTY(teleportlocs)
if(!T)
return 0
- //Gravity forced on the atom
- var/datum/component/forced_gravity/FG = GetComponent(/datum/component/forced_gravity)
- if(FG)
- if(!FG.ignore_space && isspaceturf(T))
- return 0
- else
- return FG.gravity
+ var/list/forced_gravity = list()
+ SEND_SIGNAL(src, COMSIG_ATOM_HAS_GRAVITY, T, forced_gravity)
+ if(!forced_gravity.len)
+ SEND_SIGNAL(T, COMSIG_TURF_HAS_GRAVITY, src, forced_gravity)
+ if(forced_gravity.len)
+ var/max_grav
+ for(var/i in forced_gravity)
+ max_grav = max(max_grav, i)
+ if(max_grav)
+ return max_grav
- //Gravity forced on the turf
- FG = T.GetComponent(/datum/component/forced_gravity)
- if(FG)
- if(!FG.ignore_space && isspaceturf(T))
- return 0
- else
- return FG.gravity
-
- var/area/A = get_area(T)
if(isspaceturf(T)) // Turf never has gravity
return 0
- else if(A.has_gravity) // Areas which always has gravity
+
+ var/area/A = get_area(T)
+ if(A.has_gravity) // Areas which always has gravity
return A.has_gravity
else
// There's a gravity generator on our z level
@@ -488,11 +491,6 @@ GLOBAL_LIST_EMPTY(teleportlocs)
blob_allowed = FALSE
addSorted()
-/area/proc/update_beauty()
- if(!areasize)
- return FALSE
- beauty = totalbeauty / areasize
-
/area/proc/update_areasize()
if(outdoors)
return FALSE
diff --git a/code/game/area/areas/shuttles.dm b/code/game/area/areas/shuttles.dm
index 8e23614db0..ab4fa10d44 100644
--- a/code/game/area/areas/shuttles.dm
+++ b/code/game/area/areas/shuttles.dm
@@ -10,6 +10,8 @@
always_unpowered = FALSE
valid_territory = FALSE
icon_state = "shuttle"
+ // Loading the same shuttle map at a different time will produce distinct area instances.
+ unique = FALSE
/area/shuttle/Initialize()
if(!canSmoothWithAreas)
@@ -62,6 +64,35 @@
name = "Pirate Shuttle Vault"
requires_power = FALSE
+////////////////////////////White Ship////////////////////////////
+
+/area/shuttle/abandoned
+ name = "Abandoned Ship"
+ blob_allowed = FALSE
+ requires_power = TRUE
+ canSmoothWithAreas = /area/shuttle/abandoned
+
+/area/shuttle/abandoned/bridge
+ name = "Abandoned Ship Bridge"
+
+/area/shuttle/abandoned/engine
+ name = "Abandoned Ship Engine"
+
+/area/shuttle/abandoned/bar
+ name = "Abandoned Ship Bar"
+
+/area/shuttle/abandoned/crew
+ name = "Abandoned Ship Crew Quarters"
+
+/area/shuttle/abandoned/cargo
+ name = "Abandoned Ship Cargo Bay"
+
+/area/shuttle/abandoned/medbay
+ name = "Abandoned Ship Medbay"
+
+/area/shuttle/abandoned/pod
+ name = "Abandoned Ship Pod"
+
////////////////////////////Single-area shuttles////////////////////////////
/area/shuttle/transit
@@ -74,6 +105,7 @@
/area/shuttle/arrival
name = "Arrival Shuttle"
+ unique = TRUE // SSjob refers to this area for latejoiners
/area/shuttle/pod_1
name = "Escape Pod One"
@@ -125,10 +157,6 @@
name = "Steel Rain"
blob_allowed = FALSE
-/area/shuttle/abandoned
- name = "Abandoned Ship"
- blob_allowed = FALSE
-
/area/shuttle/sbc_starfury
name = "SBC Starfury"
blob_allowed = FALSE
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 68089905b6..c036127fac 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -24,6 +24,8 @@
var/list/remove_overlays // a very temporary list of overlays to remove
var/list/add_overlays // a very temporary list of overlays to add
+ var/list/managed_vis_overlays //vis overlays managed by SSvis_overlays to automaticaly turn them like other overlays
+
var/datum/proximity_monitor/proximity_monitor
var/buckle_message_cooldown = 0
var/fingerprintslast
@@ -45,10 +47,6 @@
//we were deleted
return
- var/list/created = SSatoms.created_atoms
- if(created)
- created += src
-
//Called after New if the map is being loaded. mapload = TRUE
//Called from base of New if the map is not being loaded. mapload = FALSE
//This base must be called or derivatives must set initialized to TRUE
@@ -157,7 +155,7 @@
SEND_SIGNAL(src, COMSIG_ATOM_HULK_ATTACK, user)
if(does_attack_animation)
user.changeNext_move(CLICK_CD_MELEE)
- add_logs(user, src, "punched", "hulk powers")
+ log_combat(user, src, "punched", "hulk powers")
user.do_attack_animation(src, ATTACK_EFFECT_SMASH)
/atom/proc/CheckParts(list/parts_list)
@@ -191,10 +189,8 @@
/atom/proc/check_eye(mob/user)
return
-
-/atom/proc/CollidedWith(atom/movable/AM)
+/atom/proc/Bumped(atom/movable/AM)
set waitfor = FALSE
- return
// Convenience procs to see if a container is open for chemistry handling
/atom/proc/is_open_container()
@@ -571,7 +567,7 @@
return
/atom/proc/screwdriver_act(mob/living/user, obj/item/I)
- return
+ SEND_SIGNAL(src, COMSIG_ATOM_SCREWDRIVER_ACT, user, I)
/atom/proc/wrench_act(mob/living/user, obj/item/I)
return
@@ -588,6 +584,99 @@
/atom/proc/GenerateTag()
return
+// Generic logging helper
+/atom/proc/log_message(message, message_type, color=null, log_globally=TRUE)
+ if(!log_globally)
+ return
+
+ var/log_text = "[key_name(src)] [message] [loc_name(src)]"
+ switch(message_type)
+ if(LOG_ATTACK)
+ log_attack(log_text)
+ if(LOG_SAY)
+ log_say(log_text)
+ if(LOG_WHISPER)
+ log_whisper(log_text)
+ if(LOG_EMOTE)
+ log_emote(log_text)
+ if(LOG_SUBTLE)
+ log_subtle(log_text)
+ if(LOG_DSAY)
+ log_dsay(log_text)
+ if(LOG_PDA)
+ log_pda(log_text)
+ if(LOG_CHAT)
+ log_chat(log_text)
+ if(LOG_COMMENT)
+ log_comment(log_text)
+ if(LOG_TELECOMMS)
+ log_telecomms(log_text)
+ if(LOG_OOC)
+ log_ooc(log_text)
+ if(LOG_LOOC)
+ log_looc(log_text)
+ if(LOG_ADMIN)
+ log_admin(log_text)
+ if(LOG_ADMIN_PRIVATE)
+ log_admin_private(log_text)
+ if(LOG_ASAY)
+ log_adminsay(log_text)
+ if(LOG_OWNERSHIP)
+ log_game(log_text)
+ if(LOG_GAME)
+ log_game(log_text)
+ else
+ stack_trace("Invalid individual logging type: [message_type]. Defaulting to [LOG_GAME] (LOG_GAME).")
+ log_game(log_text)
+
+// Helper for logging chat messages or other logs with arbitrary inputs (e.g. announcements)
+/atom/proc/log_talk(message, message_type, tag=null, log_globally=TRUE, forced_by=null)
+ var/prefix = tag ? "([tag]) " : ""
+ var/suffix = forced_by ? " FORCED by [forced_by]" : ""
+ log_message("[prefix]\"[message]\"[suffix]", message_type, log_globally=log_globally)
+
+// Helper for logging of messages with only one sender and receiver
+/proc/log_directed_talk(atom/source, atom/target, message, message_type, tag)
+ if(!tag)
+ stack_trace("Unspecified tag for private message")
+ tag = "UNKNOWN"
+
+ source.log_talk(message, message_type, tag="[tag] to [key_name(target)]")
+ if(source != target)
+ target.log_talk(message, message_type, tag="[tag] from [key_name(source)]", log_globally=FALSE)
+
+/*
+Proc for attack log creation, because really why not
+1 argument is the actor performing the action
+2 argument is the target of the action
+3 is a verb describing the action (e.g. punched, throwed, kicked, etc.)
+4 is a tool with which the action was made (usually an item)
+5 is any additional text, which will be appended to the rest of the log line
+*/
+
+/proc/log_combat(atom/user, atom/target, what_done, atom/object=null, addition=null)
+ var/ssource = key_name(user)
+ var/starget = key_name(target)
+
+ var/mob/living/living_target = target
+ var/hp = istype(living_target) ? " (NEWHP: [living_target.health]) " : ""
+
+ var/sobject = ""
+ if(object)
+ sobject = " with [key_name(object)]"
+ var/saddition = ""
+ if(addition)
+ saddition = " [addition]"
+
+ var/postfix = "[sobject][saddition][hp]"
+
+ var/message = "has [what_done] [starget][postfix]"
+ user.log_message(message, LOG_ATTACK, color="red")
+
+ if(user != target)
+ var/reverse_message = "has been [what_done] by [ssource][postfix]"
+ target.log_message(reverse_message, LOG_ATTACK, color="orange", log_globally=FALSE)
+
// Filter stuff
/atom/movable/proc/add_filter(name,priority,list/params)
if(!filter_data)
@@ -608,10 +697,4 @@
/atom/movable/proc/get_filter(name)
if(filter_data && filter_data[name])
- return filters[filter_data.Find(name)]
-
-/atom/movable/proc/remove_filter(name)
- if(filter_data[name])
- filter_data -= name
- update_filters()
- return TRUE
\ No newline at end of file
+ return filters[filter_data.Find(name)]
\ No newline at end of file
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 366e1ad539..ba840d0771 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -1,6 +1,7 @@
/atom/movable
layer = OBJ_LAYER
var/last_move = null
+ var/last_move_time = 0
var/anchored = FALSE
var/datum/thrownthing/throwing = null
var/throw_speed = 2 //How many tiles to move per ds when being thrown. Float values are fully supported
@@ -58,9 +59,12 @@
return TRUE
return FALSE
if("loc")
- if(var_value == null || istype(var_value, /atom))
+ if(istype(var_value, /atom))
forceMove(var_value)
return TRUE
+ else if(isnull(var_value))
+ moveToNullspace()
+ return TRUE
return FALSE
return ..()
@@ -84,14 +88,14 @@
return TRUE
stop_pulling()
if(AM.pulledby)
- add_logs(AM, AM.pulledby, "pulled from", src)
+ log_combat(AM, AM.pulledby, "pulled from", src)
AM.pulledby.stop_pulling() //an object can't be pulled by two mobs at once.
pulling = AM
AM.pulledby = src
grab_state = gs
if(ismob(AM))
var/mob/M = AM
- add_logs(src, M, "grabbed", addition="passive grab")
+ log_combat(src, M, "grabbed", addition="passive grab")
visible_message("[src] has grabbed [M] passively!")
return TRUE
@@ -139,10 +143,54 @@
stop_pulling()
return
+////////////////////////////////////////
+// Here's where we rewrite how byond handles movement except slightly different
+// To be removed on step_ conversion
+// All this work to prevent a second bump
+/atom/movable/Move(atom/newloc, direct=0)
+ . = FALSE
+ if(!newloc || newloc == loc)
+ return
+ if(!direct)
+ direct = get_dir(src, newloc)
+ setDir(direct)
+ if(!loc.Exit(src, newloc))
+ return
-/atom/movable/Move(atom/newloc, direct = 0)
+ if(!newloc.Enter(src, src.loc))
+ return
+
+ // Past this is the point of no return
+ var/atom/oldloc = loc
+ var/area/oldarea = get_area(oldloc)
+ var/area/newarea = get_area(newloc)
+ loc = newloc
+ . = TRUE
+ oldloc.Exited(src, newloc)
+ if(oldarea != newarea)
+ oldarea.Exited(src, newloc)
+
+ for(var/i in oldloc)
+ if(i == src) // Multi tile objects
+ continue
+ var/atom/movable/thing = i
+ thing.Uncrossed(src)
+
+ newloc.Entered(src, oldloc)
+ if(oldarea != newarea)
+ newarea.Entered(src, oldloc)
+
+ for(var/i in loc)
+ if(i == src) // Multi tile objects
+ continue
+ var/atom/movable/thing = i
+ thing.Crossed(src)
+//
+////////////////////////////////////////
+
+/atom/movable/Move(atom/newloc, direct)
var/atom/movable/pullee = pulling
var/turf/T = loc
if(pulling)
@@ -163,7 +211,7 @@
moving_diagonally = FIRST_DIAG_STEP
var/first_step_dir
// The `&& moving_diagonally` checks are so that a forceMove taking
- // place due to a Crossed, Collided, etc. call will interrupt
+ // place due to a Crossed, Bumped, etc. call will interrupt
// the second half of the diagonal movement, or the second attempt
// at a first half if step() fails because we hit something.
if (direct & NORTH)
@@ -257,7 +305,6 @@
return 1
/atom/movable/Destroy(force)
-
QDEL_NULL(proximity_monitor)
QDEL_NULL(language_holder)
@@ -265,6 +312,10 @@
. = ..()
if(loc)
+ //Restore air flow if we were blocking it (movables with ATMOS_PASS_PROC will need to do this manually if necessary)
+ if(((CanAtmosPass == ATMOS_PASS_DENSITY && density) || CanAtmosPass == ATMOS_PASS_NO) && isturf(loc))
+ CanAtmosPass = ATMOS_PASS_YES
+ air_update_turf(TRUE)
loc.handle_atom_del(src)
for(var/atom/movable/AM in contents)
qdel(AM)
@@ -273,28 +324,38 @@
if(pulledby)
pulledby.stop_pulling()
+// Make sure you know what you're doing if you call this, this is intended to only be called by byond directly.
+// You probably want CanPass()
+/atom/movable/Cross(atom/movable/AM)
+ . = TRUE
+ SEND_SIGNAL(src, COMSIG_MOVABLE_CROSS, AM)
+ return CanPass(AM, AM.loc, TRUE)
-// Previously known as HasEntered()
-// This is automatically called when something enters your square
//oldloc = old location on atom, inserted when forceMove is called and ONLY when forceMove is called!
/atom/movable/Crossed(atom/movable/AM, oldloc)
SEND_SIGNAL(src, COMSIG_MOVABLE_CROSSED, AM)
+/atom/movable/Uncross(atom/movable/AM, atom/newloc)
+ . = ..()
+ if(SEND_SIGNAL(src, COMSIG_MOVABLE_UNCROSS, AM) & COMPONENT_MOVABLE_BLOCK_UNCROSS)
+ return FALSE
+ if(isturf(newloc) && !CheckExit(AM, newloc))
+ return FALSE
+
/atom/movable/Uncrossed(atom/movable/AM)
SEND_SIGNAL(src, COMSIG_MOVABLE_UNCROSSED, AM)
-//This is tg's equivalent to the byond bump, it used to be called bump with a second arg
-//to differentiate it, naturally everyone forgot about this immediately and so some things
-//would bump twice, so now it's called Collide
-/atom/movable/proc/Collide(atom/A)
- SEND_SIGNAL(src, COMSIG_MOVABLE_COLLIDE, A)
- if(A)
- if(throwing)
- throwing.hit_atom(A)
- . = TRUE
- if(!A || QDELETED(A))
- return
- A.CollidedWith(src)
+/atom/movable/Bump(atom/A)
+ if(!A)
+ CRASH("Bump was called with no argument.")
+ SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, A)
+ . = ..()
+ if(!QDELETED(throwing))
+ throwing.hit_atom(A)
+ . = TRUE
+ if(QDELETED(A))
+ return
+ A.Bumped(src)
/atom/movable/proc/forceMove(atom/destination)
. = FALSE
@@ -322,8 +383,10 @@
if(!same_loc)
if(oldloc)
oldloc.Exited(src, destination)
- if(old_area)
+ if(old_area && old_area != destarea)
old_area.Exited(src, destination)
+ for(var/atom/movable/AM in oldloc)
+ AM.Uncrossed(src)
var/turf/oldturf = get_turf(oldloc)
var/turf/destturf = get_turf(destination)
var/old_z = (oldturf ? oldturf.z : null)
@@ -413,6 +476,9 @@
if (!target || speed <= 0)
return
+ if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_THROW, args) & COMPONENT_CANCEL_THROW)
+ return
+
if (pulledby)
pulledby.stop_pulling()
@@ -482,12 +548,11 @@
if(spin)
SpinAnimation(5, 1)
- SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_THROW, TT, spin) //CITADEL - THIS IS BORKED. PENDING SYNC.
+ SEND_SIGNAL(src, COMSIG_MOVABLE_POST_THROW, TT, spin)
SSthrowing.processing[src] = TT
if (SSthrowing.state == SS_PAUSED && length(SSthrowing.currentrun))
SSthrowing.currentrun[src] = TT
TT.tick()
- SEND_SIGNAL(src, COMSIG_MOVABLE_POST_THROW, TT, spin)
/atom/movable/proc/handle_buckled_mob_movement(newloc,direct)
for(var/m in buckled_mobs)
@@ -721,3 +786,36 @@
if(anchored || throwing)
return FALSE
return TRUE
+
+
+/obj/item/proc/do_pickup_animation(atom/target)
+ set waitfor = FALSE
+ if(!istype(loc, /turf))
+ return
+ var/image/I = image(icon = src, loc = loc, layer = layer + 0.1)
+ I.plane = GAME_PLANE
+ I.transform *= 0.75
+ I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
+ var/turf/T = get_turf(src)
+ var/direction
+ var/to_x = 0
+ var/to_y = 0
+
+ if(!QDELETED(T) && !QDELETED(target))
+ direction = get_dir(T, target)
+ if(direction & NORTH)
+ to_y = 32
+ else if(direction & SOUTH)
+ to_y = -32
+ if(direction & EAST)
+ to_x = 32
+ else if(direction & WEST)
+ to_x = -32
+ if(!direction)
+ to_y = 16
+ flick_overlay(I, GLOB.clients, 6)
+ var/matrix/M = new
+ M.Turn(pick(-30, 30))
+ animate(I, alpha = 175, pixel_x = to_x, pixel_y = to_y, time = 3, transform = M, easing = CUBIC_EASING)
+ sleep(1)
+ animate(I, alpha = 0, transform = matrix(), time = 1)
diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm
index d372986569..7a341ad69a 100644
--- a/code/game/data_huds.dm
+++ b/code/game/data_huds.dm
@@ -65,7 +65,7 @@
/datum/atom_hud/sentient_disease
hud_icons = list(SENTIENT_DISEASE_HUD)
-/*
+
/datum/atom_hud/ai_detector
hud_icons = list(AI_DETECT_HUD)
@@ -74,7 +74,7 @@
if(M && (hudusers.len == 1))
for(var/V in GLOB.aiEyes)
var/mob/camera/aiEye/E = V
- E.update_ai_detect_hud()*/
+ E.update_ai_detect_hud()
/* MED/SEC/DIAG HUD HOOKS */
diff --git a/code/game/gamemodes/clock_cult/clock_cult.dm b/code/game/gamemodes/clock_cult/clock_cult.dm
index 1403990c65..f0ccf235ae 100644
--- a/code/game/gamemodes/clock_cult/clock_cult.dm
+++ b/code/game/gamemodes/clock_cult/clock_cult.dm
@@ -4,11 +4,11 @@ GLOBAL_VAR_INIT(servants_active, FALSE) //This var controls whether or not a lot
CLOCKWORK CULT: Based off of the failed pull requests from /vg/
-While Nar-Sie is the oldest and most prominent of the elder gods, there are other forces at work in the universe.
-Ratvar, the Clockwork Justiciar, a homage to Nar-Sie granted sentience by its own power, is one such other force.
+While Nar'Sie is the oldest and most prominent of the elder gods, there are other forces at work in the universe.
+Ratvar, the Clockwork Justiciar, a homage to Nar'Sie granted sentience by its own power, is one such other force.
Imprisoned within a massive construct known as the Celestial Derelict - or Reebe - an intense hatred of the Blood God festers.
Ratvar, unable to act in the mortal plane, seeks to return and forms covenants with mortals in order to bolster his influence.
-Due to his mechanical nature, Ratvar is also capable of influencing silicon-based lifeforms, unlike Nar-Sie, who can only influence natural life.
+Due to his mechanical nature, Ratvar is also capable of influencing silicon-based lifeforms, unlike Nar'Sie, who can only influence natural life.
This is a team-based gamemode, and the team's objective is shared by all cultists. Their goal is to defend an object called the Ark on a separate z-level.
@@ -148,11 +148,13 @@ Credit where due:
/datum/game_mode/clockwork_cult/pre_setup()
var/list/errorList = list()
- SSmapping.LoadGroup(errorList, "Reebe", "map_files/generic", "City_of_Cogs.dmm", default_traits = ZTRAITS_REEBE, silent = TRUE)
+ var/list/reebes = SSmapping.LoadGroup(errorList, "Reebe", "map_files/generic", "City_of_Cogs.dmm", default_traits = ZTRAITS_REEBE, silent = TRUE)
if(errorList.len) // reebe failed to load
message_admins("Reebe failed to load!")
log_game("Reebe failed to load!")
return FALSE
+ for(var/datum/parsed_map/PM in reebes)
+ PM.initTemplateBounds()
if(CONFIG_GET(flag/protect_roles_from_antagonist))
restricted_jobs += protected_jobs
if(CONFIG_GET(flag/protect_assistant_from_antagonist))
diff --git a/code/game/gamemodes/clown_ops/clown_weapons.dm b/code/game/gamemodes/clown_ops/clown_weapons.dm
index bf401e5ad2..a3440fad39 100644
--- a/code/game/gamemodes/clown_ops/clown_weapons.dm
+++ b/code/game/gamemodes/clown_ops/clown_weapons.dm
@@ -74,7 +74,7 @@
. = ..()
AddComponent(/datum/component/slippery, 60, GALOSHES_DONT_HELP)
GET_COMPONENT(slipper, /datum/component/slippery)
- slipper.enabled = active
+ slipper.signal_enabled = active
/obj/item/melee/transforming/energy/sword/bananium/attack(mob/living/M, mob/living/user)
..()
@@ -99,7 +99,7 @@
/obj/item/melee/transforming/energy/sword/bananium/transform_weapon(mob/living/user, supress_message_text)
..()
GET_COMPONENT(slipper, /datum/component/slippery)
- slipper.enabled = active
+ slipper.signal_enabled = active
/obj/item/melee/transforming/energy/sword/bananium/ignition_effect(atom/A, mob/user)
return ""
@@ -131,12 +131,12 @@
. = ..()
AddComponent(/datum/component/slippery, 60, GALOSHES_DONT_HELP)
GET_COMPONENT(slipper, /datum/component/slippery)
- slipper.enabled = active
+ slipper.signal_enabled = active
/obj/item/shield/energy/bananium/attack_self(mob/living/carbon/human/user)
..()
GET_COMPONENT(slipper, /datum/component/slippery)
- slipper.enabled = active
+ slipper.signal_enabled = active
/obj/item/shield/energy/bananium/throw_at(atom/target, range, speed, mob/thrower, spin=1)
if(active)
diff --git a/code/game/gamemodes/cult/cult.dm b/code/game/gamemodes/cult/cult.dm
index 9d828fb662..13ceff2451 100644
--- a/code/game/gamemodes/cult/cult.dm
+++ b/code/game/gamemodes/cult/cult.dm
@@ -43,8 +43,8 @@
enemy_minimum_age = 14
announce_span = "cult"
- announce_text = "Some crew members are trying to start a cult to Nar-Sie!\n\
- Cultists: Carry out Nar-Sie's will.\n\
+ announce_text = "Some crew members are trying to start a cult to Nar'Sie!\n\
+ Cultists: Carry out Nar'Sie's will.\n\
Crew: Prevent the cult from expanding and drive it out."
var/finished = 0
@@ -158,7 +158,7 @@
/datum/game_mode/cult/generate_report()
return "Some stations in your sector have reported evidence of blood sacrifice and strange magic. Ties to the Wizards' Federation have been proven not to exist, and many employees \
have disappeared; even Central Command employees light-years away have felt strange presences and at times hysterical compulsions. Interrogations point towards this being the work of \
- the cult of Nar-Sie. If evidence of this cult is discovered aboard your station, extreme caution and extreme vigilance must be taken going forward, and all resources should be \
+ the cult of Nar'Sie. If evidence of this cult is discovered aboard your station, extreme caution and extreme vigilance must be taken going forward, and all resources should be \
devoted to stopping this cult. Note that holy water seems to weaken and eventually return the minds of cultists that ingest it, and mindshield implants will prevent conversion \
altogether."
diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm
index 85c2ddd877..d16cbebb2a 100644
--- a/code/game/gamemodes/game_mode.dm
+++ b/code/game/gamemodes/game_mode.dm
@@ -47,9 +47,8 @@
var/allow_persistence_save = TRUE
var/gamemode_ready = FALSE //Is the gamemode all set up and ready to start checking for ending conditions.
-
- var/flipseclevel = FALSE //CIT CHANGE - adds a 10% chance for the alert level to be the opposite of what the gamemode is supposed to have
var/setup_error //What stopepd setting up the mode.
+ var/flipseclevel = FALSE //CIT CHANGE - adds a 10% chance for the alert level to be the opposite of what the gamemode is supposed to have
/datum/game_mode/proc/announce() //Shows the gamemode's name and a fast description.
to_chat(world, "The gamemode is: [name]!")
@@ -87,7 +86,6 @@
if(prob(20)) //CIT CHANGE - adds a 20% chance for the security level to be the opposite of what it normally is
flipseclevel = TRUE
-
if(SSdbcore.Connect())
var/sql
if(SSticker.mode)
@@ -181,12 +179,13 @@
round_converted = 0
return
//somewhere between 1 and 3 minutes from now
- if(!CONFIG_GET(keyed_flag_list/midround_antag)[SSticker.mode.config_tag])
+ if(!CONFIG_GET(keyed_list/midround_antag)[SSticker.mode.config_tag])
round_converted = 0
return 1
for(var/mob/living/carbon/human/H in antag_candidates)
if(H.client)
replacementmode.make_antag_chance(H)
+ replacementmode.gamemode_ready = TRUE //Awful but we're not doing standard setup here.
round_converted = 2
message_admins("-- IMPORTANT: The roundtype has been converted to [replacementmode.name], antagonists may have been created! --")
@@ -209,8 +208,8 @@
return TRUE
if(station_was_nuked)
return TRUE
- var/list/continuous = CONFIG_GET(keyed_flag_list/continuous)
- var/list/midround_antag = CONFIG_GET(keyed_flag_list/midround_antag)
+ var/list/continuous = CONFIG_GET(keyed_list/continuous)
+ var/list/midround_antag = CONFIG_GET(keyed_list/midround_antag)
if(!round_converted && (!continuous[config_tag] || (continuous[config_tag] && midround_antag[config_tag]))) //Non-continuous or continous with replacement antags
if(!continuous_sanity_checked) //make sure we have antags to be checking in the first place
for(var/mob/Player in GLOB.mob_list)
@@ -226,7 +225,7 @@
return 0
- if(living_antag_player && living_antag_player.mind && isliving(living_antag_player) && living_antag_player.stat != DEAD && !isnewplayer(living_antag_player) &&!isbrain(living_antag_player))
+ if(living_antag_player && living_antag_player.mind && isliving(living_antag_player) && living_antag_player.stat != DEAD && !isnewplayer(living_antag_player) &&!isbrain(living_antag_player) && (living_antag_player.mind.special_role || LAZYLEN(living_antag_player.mind.antag_datums)))
return 0 //A resource saver: once we find someone who has to die for all antags to be dead, we can just keep checking them, cycling over everyone only when we lose our mark.
for(var/mob/Player in GLOB.alive_mob_list)
diff --git a/code/game/gamemodes/meteor/meteors.dm b/code/game/gamemodes/meteor/meteors.dm
index b68d55f9dd..426d23b4ab 100644
--- a/code/game/gamemodes/meteor/meteors.dm
+++ b/code/game/gamemodes/meteor/meteors.dm
@@ -132,7 +132,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
timerid = QDEL_IN(src, lifetime)
chase_target(target)
-/obj/effect/meteor/Collide(atom/A)
+/obj/effect/meteor/Bump(atom/A)
if(A)
ram_turf(get_turf(A))
playsound(src.loc, meteorsound, 40, 1)
@@ -301,7 +301,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
if(!isspaceturf(T))
new /obj/effect/decal/cleanable/blood(T)
-/obj/effect/meteor/meaty/Collide(atom/A)
+/obj/effect/meteor/meaty/Bump(atom/A)
A.ex_act(hitpwr)
get_hit()
@@ -340,7 +340,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
..()
explosion(src.loc, 5, 10, 15, 20, 0)
-/obj/effect/meteor/tunguska/Collide()
+/obj/effect/meteor/tunguska/Bump()
..()
if(prob(20))
explosion(src.loc,2,4,6,8)
diff --git a/code/game/gamemodes/nuclear/nuclear.dm b/code/game/gamemodes/nuclear/nuclear.dm
index 4eb5dc1c6e..b269a5abe5 100644
--- a/code/game/gamemodes/nuclear/nuclear.dm
+++ b/code/game/gamemodes/nuclear/nuclear.dm
@@ -64,17 +64,13 @@
return FALSE
return TRUE
-/datum/game_mode/nuclear/check_finished() //to be called by SSticker
- if(replacementmode && round_converted == 2)
- return replacementmode.check_finished()
- if((SSshuttle.emergency.mode == SHUTTLE_ENDGAME) || station_was_nuked)
- return TRUE
+/datum/game_mode/nuclear/check_finished()
+ //Keep the round going if ops are dead but bomb is ticking.
if(nuke_team.operatives_dead())
- var/obj/machinery/nuclearbomb/N
- pass(N) //suppress unused warning
- if(N.bomb_set) //snaaaaaaaaaake! It's not over yet!
- return FALSE //its a static var btw
- ..()
+ for(var/obj/machinery/nuclearbomb/N in GLOB.nuke_list)
+ if(N.proper_bomb && (N.timing || N.exploding))
+ return FALSE
+ return ..()
/datum/game_mode/nuclear/set_round_result()
..()
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index b32460dabc..41e429319c 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -25,12 +25,14 @@ GLOBAL_LIST_EMPTY(objectives)
/datum/objective/proc/considered_escaped(datum/mind/M)
if(!considered_alive(M))
return FALSE
+ if(M.force_escaped)
+ return TRUE
if(SSticker.force_ending || SSticker.mode.station_was_nuked) // Just let them win.
return TRUE
if(SSshuttle.emergency.mode != SHUTTLE_ENDGAME)
return FALSE
var/turf/location = get_turf(M.current)
- if(!location || istype(location, /turf/open/floor/plasteel/shuttle/red) || istype(location, /turf/open/floor/mineral/plastitanium/brig)) // Fails if they are in the shuttle brig
+ if(!location || istype(location, /turf/open/floor/plasteel/shuttle/red) || istype(location, /turf/open/floor/mineral/plastitanium/red/brig)) // Fails if they are in the shuttle brig
return FALSE
return location.onCentCom() || location.onSyndieBase()
@@ -161,7 +163,7 @@ GLOBAL_LIST_EMPTY(objectives)
if(!target || !considered_alive(target) || considered_afk(target))
return TRUE
var/turf/T = get_turf(target.current)
- return T && !is_station_level(T.z)
+ return !T || !is_station_level(T.z)
/datum/objective/mutiny/update_explanation_text()
..()
@@ -546,7 +548,7 @@ GLOBAL_LIST_EMPTY(possible_items_special)
/datum/objective/capture/check_completion()//Basically runs through all the mobs in the area to determine how much they are worth.
var/captured_amount = 0
- var/area/centcom/holding/A = locate() in GLOB.sortedAreas
+ var/area/centcom/holding/A = GLOB.areas_by_type[/area/centcom/holding]
for(var/mob/living/carbon/human/M in A)//Humans.
if(M.stat == DEAD)//Dead folks are worth less.
captured_amount+=0.5
diff --git a/code/game/gamemodes/overthrow/objective.dm b/code/game/gamemodes/overthrow/objective.dm
new file mode 100644
index 0000000000..75b7f9f3b8
--- /dev/null
+++ b/code/game/gamemodes/overthrow/objective.dm
@@ -0,0 +1,152 @@
+// This is a point based objective. You can only lose if you fail to even handle one single command personnel, else you win. But, if you win, you're given a certain number of points,
+// based on the role of the head (Captain, HoP/HoS, other heads, warden, security officers) and whether you converted them, exiled or just killed (applying a modifier of 1.5, 1 and 0.5 respectively)
+// because this is meant for the overthrow gamemode, which is a bloodless coup, unlike revs.
+
+// Point system:
+// Base points for each role:
+// AI, Captain = 5;
+// Head of Personnel, Head of Security, target = 4;
+// Chief Engineer, Chief Medical Officer, Research Director = 3;
+
+// Modifiers:
+// Converting: 1.5 for the converting team, 1 for all the other ones;
+// Exiling: 1;
+// Killing: 0.5
+
+#define CAPPTS 5
+#define AIPTS 5
+#define HOPPTS 4
+#define HOSPTS 4
+#define TARGETPTS 4
+#define CEPTS 3
+#define CMOPTS 3
+#define RDPTS 3
+
+#define CONVERTED_OURS 1.5
+#define CONVERTED 1
+#define EXILED 1
+#define KILLED 0.5
+
+// Parent type holding the get_points proc used for round end log.
+/datum/objective/overthrow
+
+/datum/objective/overthrow/check_completion()
+ return get_points() ? TRUE : FALSE
+
+/datum/objective/overthrow/proc/get_points()
+ return 0 // int, not bool
+
+/datum/objective/overthrow/proc/result_points(datum/mind/the_dude, base_points) // App
+ var/initial_points = base_points
+ if(the_dude)
+ var/datum/antagonist/overthrow/O = the_dude.has_antag_datum(/datum/antagonist/overthrow)
+ if(!the_dude.current || the_dude.current.stat == DEAD)
+ initial_points *= KILLED
+ else if(!is_station_level(the_dude.current.z) && !is_centcom_level(the_dude.current.z)) // exiled.
+ initial_points *= EXILED
+ else if(O)
+ initial_points *= CONVERTED
+ if(team == O.team)
+ initial_points *= CONVERTED_OURS
+ else
+ initial_points = 0
+ else
+ initial_points = 0
+ return initial_points
+
+// Heads overthrow objective. This targets the heads only, assigning points based on the rank of the head, captain being the highest target.
+/datum/objective/overthrow/heads
+ var/list/targets = list() // We want one objective for all the heads, instead of 1 objective per head like how it's done for revs, because you don't lose if you get atleast one head.
+ // Also, this is an associative list, target = role. Modifiers (defines) are applied on points calculation at round end.
+
+/datum/objective/overthrow/heads/proc/find_targets()
+ var/list/datum/mind/owners = get_owners()
+ for(var/datum/mind/possible_target in get_crewmember_minds()) // i would use SSjob.get_all_heads() but jesus christ that proc's shit, i ain't using it
+ if(!(possible_target in owners) && ishuman(possible_target.current))
+ if(possible_target.assigned_role in GLOB.command_positions)
+ targets[possible_target] = possible_target.assigned_role
+ update_explanation_text()
+
+/datum/objective/overthrow/heads/update_explanation_text()
+ if(targets.len)
+ explanation_text = "Work with your team to convert, exile or kill "
+ explanation_text += english_list(targets)
+ explanation_text += ". Converting to your team will give you more points, whereas killing will give you the least. Syndicates don't want to stir up too many troubles."
+ else
+ explanation_text = "Wait until any heads arrive. Once that happens, check your objectives again to see the updated objective. It may require around [OBJECTIVE_UPDATING_TIME] seconds to update."
+
+/datum/objective/overthrow/heads/check_completion()
+ if(!targets.len)
+ return TRUE
+ . = ..()
+
+// Amount of points = foreach head, result += head basepoints * modifier.
+/datum/objective/overthrow/heads/get_points()
+ var/base_points = 0
+ for(var/i in targets)
+ var/datum/mind/M = i
+ if(M)
+ var/target_points
+ var/role = targets[M]
+ switch(role)
+ if("Captain")
+ target_points = CAPPTS
+ if("Head of Personnel")
+ target_points = HOPPTS
+ if("Head of Security")
+ target_points = HOSPTS
+ if("Chief Engineer")
+ target_points = CEPTS
+ if("Research Director")
+ target_points = RDPTS
+ if("Chief Medical Officer")
+ target_points = CMOPTS
+ base_points += result_points(M, target_points)
+ return base_points
+
+// AI converting objective. The team who managed to convert the AI with the overthrow module gets the normal 1.5x boost.
+/datum/objective/overthrow/AI
+ explanation_text = "Enslave the AIs to your team using the special AI module board in your storage implant. It is required you use said module."
+
+/datum/objective/overthrow/AI/get_points() // If you simply kill the Ai you get nothing, you need it to overthrow the heads.
+ . = 0 // Support for multiple AIs. More AIs means more control over the station.
+ for(var/i in GLOB.ai_list)
+ var/mob/living/silicon/ai/AI = i
+ if(AI.mind)
+ var/datum/mind/M = AI.mind
+ var/datum/antagonist/overthrow/O = M.has_antag_datum(/datum/antagonist/overthrow)
+ if(M)
+ . += (O.team == team) ? AIPTS*CONVERTED_OURS : AIPTS
+
+/datum/objective/overthrow/AI/update_explanation_text()
+ if(!GLOB.ai_list.len)
+ explanation_text = "Nothing."
+ else
+ explanation_text = "Enslave the AIs to your team using the special AI module board in your storage implant. It is required you use said module."
+
+/datum/objective/overthrow/AI/check_completion()
+ if(!GLOB.ai_list.len)
+ return TRUE
+ . = ..()
+
+// Overthrow target objective. A crewmember in particular has a certain bond with some centcom officials, and the Syndicate want you to target him in particular, even though he's not a head.
+/datum/objective/overthrow/target
+
+/datum/objective/overthrow/target/update_explanation_text()
+ if(target)
+ explanation_text = "Work with your team to convert, exile or kill [target.name], the [target.assigned_role]. Converting to your team will give you more points, whereas killing will give you the least. Syndicates don't want to stir up too many troubles."
+ else
+ explanation_text = "Nothing."
+
+/datum/objective/overthrow/target/is_unique_objective(datum/mind/possible_target)
+ if(possible_target.assigned_role in GLOB.command_positions)
+ return FALSE
+ return TRUE
+
+/datum/objective/overthrow/target/check_completion()
+ if(!target)
+ return TRUE
+ . = ..()
+
+/datum/objective/overthrow/target/get_points()
+ return result_points(target, TARGETPTS)
diff --git a/code/game/gamemodes/overthrow/overthrow.dm b/code/game/gamemodes/overthrow/overthrow.dm
new file mode 100644
index 0000000000..1548556515
--- /dev/null
+++ b/code/game/gamemodes/overthrow/overthrow.dm
@@ -0,0 +1,76 @@
+// Overthrow gamemode, based on the sleeping agent antagonist.
+/datum/game_mode/overthrow
+ name = "overthrow"
+ config_tag = "overthrow"
+ antag_flag = ROLE_OVERTHROW
+ restricted_jobs = list("Security Officer", "Warden", "Detective", "AI", "Cyborg","Captain", "Head of Personnel", "Head of Security", "Chief Engineer", "Research Director", "Chief Medical Officer")
+ required_players = 20 // the core idea is of a swift, bloodless coup, so it shouldn't be as chaotic as revs.
+ required_enemies = 2 // minimum two teams, otherwise it's just nerfed revs.
+ recommended_enemies = 4
+
+ announce_span = "danger"
+ announce_text = "There are sleeping Syndicate agents on the station who are trying to stage a coup!\n\
+ Agents: Accomplish your objectives, convert heads and targets, take control of the AI.\n\
+ Crew: Do not let the agents succeed!"
+ var/list/initial_agents = list() // Why doesn't this exist at /game_mode level? Literally every gamemode has some sort of version for this, what the fuck
+
+/datum/game_mode/overthrow/pre_setup()
+
+ if(CONFIG_GET(flag/protect_roles_from_antagonist))
+ restricted_jobs += protected_jobs
+
+ if(CONFIG_GET(flag/protect_assistant_from_antagonist))
+ restricted_jobs += "Assistant"
+
+ var/sleeping_agents = required_enemies + round(num_players()*0.05) // At 100 players, it'd be 2 + 5 = 7 teams existing.
+
+ for (var/i in 1 to sleeping_agents)
+ if (!antag_candidates.len)
+ break
+ var/datum/mind/sleeping_agent = antag_pick(antag_candidates)
+ antag_candidates -= sleeping_agent
+ initial_agents += sleeping_agent
+ sleeping_agent.restricted_roles = restricted_jobs
+ sleeping_agent.special_role = ROLE_OVERTHROW
+
+ if(initial_agents.len < required_enemies)
+ setup_error = "Not enough initial sleeping agents candidates"
+ return FALSE
+ return TRUE
+
+/datum/game_mode/overthrow/post_setup()
+ for(var/i in initial_agents) // each agent will have its own team.
+ var/datum/mind/agent = i
+ var/datum/antagonist/overthrow/O = agent.add_antag_datum(/datum/antagonist/overthrow) // create_team called on_gain will create the team
+ O.equip_initial_overthrow_agent()
+ return ..()
+
+/datum/game_mode/overthrow/generate_report()
+ return "Some sleeping agents have managed to get aboard. Their objective is to stage a coup and take over the station stealthly."
+
+// Calculates points for each team and displays the winners.
+/datum/game_mode/overthrow/special_report() // so many for loops, I am deeply sorry
+ var/list/teams = list()
+ for(var/datum/antagonist/overthrow/I in GLOB.antagonists)
+ var/datum/team/overthrow/Oteam = I.team
+ if(istype(Oteam)) // same
+ teams |= Oteam
+ var/max_points = 0 // the maximum amount of points reached
+ for(var/j in teams)
+ var/datum/team/T = j
+ var/points = 0 // Sum of points of all the objectives done
+ for(var/k in T.objectives)
+ var/datum/objective/overthrow/obj = k
+ if(istype(obj))
+ points += obj.get_points()
+ if(max_points < points)
+ max_points = points
+ teams[T] = points
+ // Now we will have a list of team=points and a max_points var. Let's fetch all the teams with points=maxpoints and display them as winner. This code allows multiple teams to win if they both achieved
+ // the same amount of points and they got the most points out of all the teams.
+ var/list/winners = list()
+ for(var/l in teams)
+ var/datum/team/Tagain = l
+ if(teams[Tagain] == max_points)
+ winners += Tagain.name
+ return "The [english_list(winners)] team[winners.len > 1 ? "s tied" : " won"] with [max_points] points!"
diff --git a/code/game/gamemodes/revolution/revolution.dm b/code/game/gamemodes/revolution/revolution.dm
index bf8f5903b5..b0eee4e912 100644
--- a/code/game/gamemodes/revolution/revolution.dm
+++ b/code/game/gamemodes/revolution/revolution.dm
@@ -133,7 +133,7 @@
//Checks if the round is over//
///////////////////////////////
/datum/game_mode/revolution/check_finished()
- if(CONFIG_GET(keyed_flag_list/continuous)["revolution"])
+ if(CONFIG_GET(keyed_list/continuous)["revolution"])
if(finished)
SSshuttle.clearHostileEnvironment(src)
return ..()
diff --git a/code/game/gamemodes/traitor/double_agents.dm b/code/game/gamemodes/traitor/double_agents.dm
index 4d8b57536e..3811fa9562 100644
--- a/code/game/gamemodes/traitor/double_agents.dm
+++ b/code/game/gamemodes/traitor/double_agents.dm
@@ -11,6 +11,7 @@
recommended_enemies = 8
reroll_friendly = 0
traitor_name = "Nanotrasen Internal Affairs Agent"
+ antag_flag = ROLE_INTERNAL_AFFAIRS
traitors_possible = 10 //hard limit on traitors if scaling is turned off
num_modifier = 4 // Four additional traitors
@@ -28,7 +29,7 @@
i++
if(i + 1 > pre_traitors.len)
i = 0
- target_list[traitor] = pre_traitors[i+1]
+ target_list[traitor] = pre_traitors[i+1]
..()
diff --git a/code/game/gamemodes/traitor/traitor.dm b/code/game/gamemodes/traitor/traitor.dm
index b5b5e7a706..789737ec0f 100644
--- a/code/game/gamemodes/traitor/traitor.dm
+++ b/code/game/gamemodes/traitor/traitor.dm
@@ -84,7 +84,7 @@
if((SSticker.mode.traitors.len + pre_traitors.len) >= traitorcap) //Upper cap for number of latejoin antagonists
return
if((SSticker.mode.traitors.len + pre_traitors.len) <= (traitorcap - 2) || prob(100 / (tsc * 2)))
- if(ROLE_TRAITOR in character.client.prefs.be_special)
+ if(antag_flag in character.client.prefs.be_special)
if(!jobban_isbanned(character, ROLE_TRAITOR) && !QDELETED(character) && !jobban_isbanned(character, ROLE_SYNDICATE) && !QDELETED(character))
if(age_check(character.client))
if(!(character.job in restricted_jobs))
diff --git a/code/game/machinery/PDApainter.dm b/code/game/machinery/PDApainter.dm
index d5cf016816..53aebc53dc 100644
--- a/code/game/machinery/PDApainter.dm
+++ b/code/game/machinery/PDApainter.dm
@@ -130,7 +130,7 @@
set category = "Object"
set src in oview(1)
- if(usr.stat || usr.restrained() || !usr.canmove)
+ if(usr.stat || usr.restrained())
return
if(storedpda)
diff --git a/code/game/machinery/Sleeper.dm b/code/game/machinery/Sleeper.dm
index f167fa0b38..4d3fd006d8 100644
--- a/code/game/machinery/Sleeper.dm
+++ b/code/game/machinery/Sleeper.dm
@@ -28,6 +28,7 @@
/obj/machinery/sleeper/Initialize()
. = ..()
+ occupant_typecache = GLOB.typecache_living
update_icon()
reset_chem_buttons()
@@ -147,7 +148,7 @@
data["occupant"]["cloneLoss"] = mob_occupant.getCloneLoss()
data["occupant"]["brainLoss"] = mob_occupant.getBrainLoss()
data["occupant"]["reagents"] = list()
- if(occupant.reagents.reagent_list.len)
+ if(mob_occupant.reagents && mob_occupant.reagents.reagent_list.len)
for(var/datum/reagent/R in mob_occupant.reagents.reagent_list)
data["occupant"]["reagents"] += list(list("name" = R.name, "volume" = R.volume))
return data
@@ -183,12 +184,12 @@
if((chem in available_chems) && chem_allowed(chem))
occupant.reagents.add_reagent(chem_buttons[chem], 10) //emag effect kicks in here so that the "intended" chem is used for all checks, for extra FUUU
if(user)
- add_logs(user, occupant, "injected [chem] into", addition = "via [src]")
+ log_combat(user, occupant, "injected [chem] into", addition = "via [src]")
return TRUE
/obj/machinery/sleeper/proc/chem_allowed(chem)
var/mob/living/mob_occupant = occupant
- if(!mob_occupant)
+ if(!mob_occupant || !mob_occupant.reagents)
return
var/amount = mob_occupant.reagents.get_reagent_amount(chem) + 10 <= 20 * efficiency
var/occ_health = mob_occupant.health > min_health || chem == "epinephrine"
@@ -211,6 +212,17 @@
icon_state = "sleeper_s"
controls_inside = TRUE
+/obj/machinery/sleeper/syndie/fullupgrade/Initialize()
+ . = ..()
+ component_parts = list()
+ component_parts += new /obj/item/circuitboard/machine/sleeper(null)
+ component_parts += new /obj/item/stock_parts/matter_bin/bluespace(null)
+ component_parts += new /obj/item/stock_parts/manipulator/femto(null)
+ component_parts += new /obj/item/stack/sheet/glass(null)
+ component_parts += new /obj/item/stack/sheet/glass(null)
+ component_parts += new /obj/item/stack/cable_coil(null)
+ RefreshParts()
+
/obj/machinery/sleeper/clockwork
name = "soothing sleeper"
desc = "A large cryogenics unit built from brass. Its surface is pleasantly cool the touch."
diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm
index b7de63a2d0..69edfcd5ec 100644
--- a/code/game/machinery/_machinery.dm
+++ b/code/game/machinery/_machinery.dm
@@ -120,7 +120,7 @@ Class Procs:
armor = list("melee" = 25, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 70)
. = ..()
GLOB.machines += src
-
+
if(ispath(circuit, /obj/item/circuitboard))
circuit = new circuit
circuit.apply_default_parts(src)
@@ -130,7 +130,7 @@ Class Procs:
else
START_PROCESSING(SSfastprocess, src)
power_change()
- AddComponent(/datum/component/redirect, list(COMSIG_ENTER_AREA), CALLBACK(src, .proc/power_change))
+ AddComponent(/datum/component/redirect, list(COMSIG_ENTER_AREA = CALLBACK(src, .proc/power_change)))
if (occupant_typecache)
occupant_typecache = typecacheof(occupant_typecache)
@@ -218,10 +218,17 @@ Class Procs:
if(panel_open && !(interaction_flags_machine & INTERACT_MACHINE_OPEN))
if(!silicon || !(interaction_flags_machine & INTERACT_MACHINE_OPEN_SILICON))
return FALSE
- if(!silicon && (interaction_flags_machine & INTERACT_MACHINE_REQUIRES_SILICON))
- return FALSE
- else if(silicon && !(interaction_flags_machine & INTERACT_MACHINE_ALLOW_SILICON))
- return FALSE
+
+ if(silicon)
+ if(!(interaction_flags_machine & INTERACT_MACHINE_ALLOW_SILICON))
+ return FALSE
+ else
+ if(interaction_flags_machine & INTERACT_MACHINE_REQUIRES_SILICON)
+ return FALSE
+ if(!Adjacent(user))
+ var/mob/living/carbon/H = user
+ if(!(istype(H) && H.has_dna() && H.dna.check_mutation(TK)))
+ return FALSE
return TRUE
////////////////////////////////////////////////////////////////////////////////////////////
@@ -232,11 +239,6 @@ Class Procs:
user.set_machine(src)
. = ..()
-/obj/machinery/ui_status(mob/user)
- if(can_interact(user))
- return ..()
- return UI_CLOSE
-
/obj/machinery/ui_act(action, params)
add_fingerprint(usr)
return ..()
diff --git a/code/game/machinery/aug_manipulator.dm b/code/game/machinery/aug_manipulator.dm
index a168cdd4d5..99200e510e 100644
--- a/code/game/machinery/aug_manipulator.dm
+++ b/code/game/machinery/aug_manipulator.dm
@@ -12,7 +12,8 @@
/obj/machinery/aug_manipulator/examine(mob/user)
..()
- to_chat(user, "Alt-click to eject the limb.")
+ if(storedpart)
+ to_chat(user, "Alt-click to eject the limb.")
/obj/machinery/aug_manipulator/Initialize()
initial_icon_state = initial(icon_state)
diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm
index 3e8e3afc98..a3e766d404 100644
--- a/code/game/machinery/camera/camera.dm
+++ b/code/game/machinery/camera/camera.dm
@@ -83,6 +83,7 @@
if(bug.current == src)
bug.current = null
bug = null
+ cancelCameraAlarm()
return ..()
/obj/machinery/camera/emp_act(severity)
@@ -139,6 +140,8 @@
// Construction/Deconstruction
/obj/machinery/camera/screwdriver_act(mob/living/user, obj/item/I)
+ if(..())
+ return TRUE
panel_open = !panel_open
to_chat(user, "You screw the camera's panel [panel_open ? "open" : "closed"].")
I.play_tool_sound(src)
diff --git a/code/game/machinery/camera/camera_assembly.dm b/code/game/machinery/camera/camera_assembly.dm
index 47bd250091..b120720992 100644
--- a/code/game/machinery/camera/camera_assembly.dm
+++ b/code/game/machinery/camera/camera_assembly.dm
@@ -87,6 +87,9 @@
return TRUE
/obj/structure/camera_assembly/screwdriver_act(mob/user, obj/item/tool)
+ . = ..()
+ if(.)
+ return TRUE
if(state != 3)
return FALSE
diff --git a/code/game/machinery/camera/motion.dm b/code/game/machinery/camera/motion.dm
index 45f0268a00..6c134417bb 100644
--- a/code/game/machinery/camera/motion.dm
+++ b/code/game/machinery/camera/motion.dm
@@ -42,6 +42,7 @@
localMotionTargets = null
if(istype(A))
A.motioncameras -= src
+ cancelAlarm()
return ..()
/obj/machinery/camera/proc/lostTargetRef(datum/weakref/R)
diff --git a/code/game/machinery/cell_charger.dm b/code/game/machinery/cell_charger.dm
index 9a59d4aea5..fb384d55ae 100644
--- a/code/game/machinery/cell_charger.dm
+++ b/code/game/machinery/cell_charger.dm
@@ -8,6 +8,7 @@
active_power_usage = 60
power_channel = EQUIP
circuit = /obj/item/circuitboard/machine/cell_charger
+ pass_flags = PASSTABLE
var/obj/item/stock_parts/cell/charging = null
var/chargelevel = -1
var/charge_rate = 500
diff --git a/code/game/machinery/computer/Operating.dm b/code/game/machinery/computer/Operating.dm
index f5ad3843a5..73fdcd5e8b 100644
--- a/code/game/machinery/computer/Operating.dm
+++ b/code/game/machinery/computer/Operating.dm
@@ -1,3 +1,6 @@
+#define MENU_OPERATION 1
+#define MENU_SURGERIES 2
+
/obj/machinery/computer/operating
name = "operating computer"
desc = "Monitors patient vitals and displays surgery steps. Can be loaded with surgery disks to perform experimental procedures."
@@ -7,10 +10,13 @@
var/mob/living/carbon/human/patient
var/obj/structure/table/optable/table
var/list/advanced_surgeries = list()
+ var/datum/techweb/linked_techweb
+ var/menu = MENU_OPERATION
light_color = LIGHT_COLOR_BLUE
/obj/machinery/computer/operating/Initialize()
. = ..()
+ linked_techweb = SSresearch.science_tech
find_table()
/obj/machinery/computer/operating/attackby(obj/item/O, mob/user, params)
@@ -24,6 +30,13 @@
return TRUE
return ..()
+/obj/machinery/computer/operating/proc/sync_surgeries()
+ for(var/i in linked_techweb.researched_designs)
+ var/datum/design/surgery/D = linked_techweb.researched_designs[i]
+ if(!istype(D))
+ continue
+ advanced_surgeries |= D.surgery
+
/obj/machinery/computer/operating/proc/find_table()
for(var/direction in GLOB.cardinals)
table = locate(/obj/structure/table/optable, get_step(src, direction))
@@ -41,6 +54,17 @@
var/list/data = list()
data["table"] = table
if(table)
+ data["menu"] = menu
+
+ var/list/surgeries = list()
+ for(var/X in advanced_surgeries)
+ var/datum/surgery/S = X
+ var/list/surgery = list()
+ surgery["name"] = initial(S.name)
+ surgery["desc"] = initial(S.desc)
+ surgeries += list(surgery)
+ data["surgeries"] = surgeries
+
data["patient"] = list()
if(table.check_patient())
patient = table.patient
@@ -86,4 +110,19 @@
"alternative_step" = alternative_step,
"alt_chems_needed" = alt_chems_needed
))
- return data
\ No newline at end of file
+ return data
+
+/obj/machinery/computer/operating/ui_act(action, params)
+ if(..())
+ return
+ switch(action)
+ if("change_menu")
+ menu = text2num(params["menu"])
+ . = TRUE
+ if("sync")
+ sync_surgeries()
+ . = TRUE
+ . = TRUE
+
+#undef MENU_OPERATION
+#undef MENU_SURGERIES
\ No newline at end of file
diff --git a/code/game/machinery/computer/_computer.dm b/code/game/machinery/computer/_computer.dm
index 74e117356d..3c3f0e7f0f 100644
--- a/code/game/machinery/computer/_computer.dm
+++ b/code/game/machinery/computer/_computer.dm
@@ -9,7 +9,7 @@
max_integrity = 200
integrity_failure = 100
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 40, "acid" = 20)
- var/brightness_on = 2
+ var/brightness_on = 1
var/icon_keyboard = "generic_key"
var/icon_screen = "generic"
var/clockwork = FALSE
@@ -49,14 +49,19 @@
/obj/machinery/computer/update_icon()
cut_overlays()
+ SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays)
if(stat & NOPOWER)
add_overlay("[icon_keyboard]_off")
return
add_overlay(icon_keyboard)
+
+ // This whole block lets screens ignore lighting and be visible even in the darkest room
+ // We can't do this for many things that emit light unfortunately because it layers over things that would be on top of it
+ var/overlay_state = icon_screen
if(stat & BROKEN)
- add_overlay("[icon_state]_broken")
- else
- add_overlay(icon_screen)
+ overlay_state = "[icon_state]_broken"
+ SSvis_overlays.add_vis_overlay(src, icon, overlay_state, layer, plane, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, overlay_state, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir, alpha=128)
/obj/machinery/computer/power_change()
..()
@@ -68,6 +73,8 @@
return
/obj/machinery/computer/screwdriver_act(mob/living/user, obj/item/I)
+ if(..())
+ return TRUE
if(circuit && !(flags_1&NODECONSTRUCT_1))
to_chat(user, "You start to disconnect the monitor...")
if(I.use_tool(src, user, 20, volume=50))
@@ -109,7 +116,7 @@
if(!(flags_1 & NODECONSTRUCT_1))
if(circuit) //no circuit, no computer frame
var/obj/structure/frame/computer/A = new /obj/structure/frame/computer(src.loc)
- A.dir = dir
+ A.setDir(dir)
A.circuit = circuit
A.setAnchored(TRUE)
if(stat & BROKEN)
diff --git a/code/game/machinery/computer/apc_control.dm b/code/game/machinery/computer/apc_control.dm
index 30574fdae4..8615af205b 100644
--- a/code/game/machinery/computer/apc_control.dm
+++ b/code/game/machinery/computer/apc_control.dm
@@ -28,6 +28,7 @@
playsound(active_apc, 'sound/machines/terminal_alert.ogg', 50, 0)
active_apc.locked = TRUE
active_apc.update_icon()
+ active_apc.remote_control = null
active_apc = null
/obj/machinery/computer/apc_control/attack_ai(mob/user)
@@ -121,10 +122,12 @@
playsound(active_apc, 'sound/machines/terminal_alert.ogg', 50, 0)
active_apc.locked = TRUE
active_apc.update_icon()
+ active_apc.remote_control = null
active_apc = null
to_chat(usr, "[icon2html(src, usr)] Connected to APC in [get_area_name(APC.area, TRUE)]. Interface request sent.")
log_activity("remotely accessed APC in [get_area_name(APC.area, TRUE)]")
- APC.ui_interact(usr, state = GLOB.not_incapacitated_state)
+ APC.remote_control = src
+ APC.ui_interact(usr)
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
message_admins("[ADMIN_LOOKUPFLW(usr)] remotely accessed [APC] from [src] at [AREACOORD(src)].")
log_game("[key_name(usr)] remotely accessed [APC] from [src] at [AREACOORD(src)].")
diff --git a/code/game/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm
index 13da773868..88f609c7f5 100644
--- a/code/game/machinery/computer/arcade.dm
+++ b/code/game/machinery/computer/arcade.dm
@@ -367,6 +367,15 @@
var/gameStatus = ORION_STATUS_START
var/canContinueEvent = 0
+/obj/machinery/computer/arcade/orion_trail/kobayashi
+ name = "Kobayashi Maru control computer"
+ desc = "A test for cadets"
+ icon = 'icons/obj/machines/particle_accelerator.dmi'
+ icon_state = "control_boxp"
+ events = list("Raiders" = 3, "Interstellar Flux" = 1, "Illness" = 3, "Breakdown" = 2, "Malfunction" = 2, "Collision" = 1, "Spaceport" = 2)
+ prizes = list(/obj/item/paper/fluff/holodeck/trek_diploma = 1)
+ settlers = list("Kirk","Worf","Gene")
+
/obj/machinery/computer/arcade/orion_trail/Reset()
// Sets up the main trail
stops = list("Pluto","Asteroid Belt","Proxima Centauri","Dead Space","Rigel Prime","Tau Ceti Beta","Black Hole","Space Outpost Beta-9","Orion Prime")
@@ -704,7 +713,7 @@
say("WEEWOO! WEEWOO! Spaceport security en route!")
playsound(src, 'sound/items/weeoo1.ogg', 100, FALSE)
for(var/i, i<=3, i++)
- var/mob/living/simple_animal/hostile/syndicate/ranged/orion/O = new/mob/living/simple_animal/hostile/syndicate/ranged/orion(get_turf(src))
+ var/mob/living/simple_animal/hostile/syndicate/ranged/smg/orion/O = new/mob/living/simple_animal/hostile/syndicate/ranged/smg/orion(get_turf(src))
O.target = usr
@@ -1047,7 +1056,7 @@
newgame()
obj_flags |= EMAGGED
-/mob/living/simple_animal/hostile/syndicate/ranged/orion
+/mob/living/simple_animal/hostile/syndicate/ranged/smg/orion
name = "spaceport security"
desc = "Premier corporate security forces for all spaceports found along the Orion Trail."
faction = list("orion")
diff --git a/code/game/machinery/computer/atmos_control.dm b/code/game/machinery/computer/atmos_control.dm
index 7eb8e206b5..baa6fa577e 100644
--- a/code/game/machinery/computer/atmos_control.dm
+++ b/code/game/machinery/computer/atmos_control.dm
@@ -272,7 +272,7 @@ GLOBAL_LIST_EMPTY(atmos_air_controllers)
/obj/machinery/computer/atmos_control/tank/ui_act(action, params)
if(..() || !radio_connection)
return
- var/datum/signal/signal = new(list("sigtype" = "command"))
+ var/datum/signal/signal = new(list("sigtype" = "command", "user" = usr))
switch(action)
if("reconnect")
reconnect(usr)
diff --git a/code/game/machinery/computer/buildandrepair.dm b/code/game/machinery/computer/buildandrepair.dm
index a151b0e0c0..c8a4d68575 100644
--- a/code/game/machinery/computer/buildandrepair.dm
+++ b/code/game/machinery/computer/buildandrepair.dm
@@ -114,7 +114,7 @@
P.play_tool_sound(src)
to_chat(user, "You connect the monitor.")
var/obj/B = new circuit.build_path (loc, circuit)
- B.dir = dir
+ B.setDir(dir)
transfer_fingerprints_to(B)
qdel(src)
return
diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm
index 696c9442b9..7dcd85d310 100644
--- a/code/game/machinery/computer/camera.dm
+++ b/code/game/machinery/computer/camera.dm
@@ -161,6 +161,7 @@
icon_keyboard = null
icon_screen = "detective_tv"
clockwork = TRUE //it'd look weird
+ pass_flags = PASSTABLE
/obj/machinery/computer/security/mining
name = "outpost camera console"
diff --git a/code/game/machinery/computer/camera_advanced.dm b/code/game/machinery/computer/camera_advanced.dm
index e561c6466f..86fcefb340 100644
--- a/code/game/machinery/computer/camera_advanced.dm
+++ b/code/game/machinery/computer/camera_advanced.dm
@@ -54,9 +54,13 @@
var/datum/action/A = V
A.Remove(user)
actions.Cut()
+ for(var/V in eyeobj.visibleCameraChunks)
+ var/datum/camerachunk/C = V
+ C.remove(eyeobj)
if(user.client)
user.reset_perspective(null)
- eyeobj.RemoveImages()
+ if(eyeobj.visible_icon && user.client)
+ user.client.images -= eyeobj.user_image
eyeobj.eye_user = null
user.remote_control = null
@@ -105,7 +109,7 @@
if(!eyeobj.eye_initialized)
var/camera_location
var/turf/myturf = get_turf(src)
- if(eyeobj.use_static)
+ if(eyeobj.use_static != USE_STATIC_NONE)
if((!z_lock.len || (myturf.z in z_lock)) && GLOB.cameranet.checkTurfVis(myturf))
camera_location = myturf
else
@@ -148,6 +152,7 @@
/mob/camera/aiEye/remote
name = "Inactive Camera Eye"
+ ai_detector_visible = FALSE
var/sprint = 10
var/cooldown = 0
var/acceleration = 1
@@ -163,17 +168,12 @@
user.see_in_dark = 2
return 1
-/mob/camera/aiEye/remote/RemoveImages()
- ..()
- if(visible_icon)
- var/client/C = GetViewerClient()
- if(C)
- C.images -= user_image
-
/mob/camera/aiEye/remote/Destroy()
- eye_user = null
+ if(origin && eye_user)
+ origin.remove_eye_control(eye_user)
origin = null
- return ..()
+ . = ..()
+ eye_user = null
/mob/camera/aiEye/remote/GetViewerClient()
if(eye_user)
@@ -182,15 +182,14 @@
/mob/camera/aiEye/remote/setLoc(T)
if(eye_user)
- if(!isturf(eye_user.loc))
- return
T = get_turf(T)
if (T)
forceMove(T)
else
moveToNullspace()
- if(use_static)
- GLOB.cameranet.visibility(src, GetViewerClient())
+ update_ai_detect_hud()
+ if(use_static != USE_STATIC_NONE)
+ GLOB.cameranet.visibility(src, GetViewerClient(), null, use_static)
if(visible_icon)
if(eye_user.client)
eye_user.client.images -= user_image
diff --git a/code/game/machinery/computer/cloning.dm b/code/game/machinery/computer/cloning.dm
index 4846610671..00a5c8941d 100644
--- a/code/game/machinery/computer/cloning.dm
+++ b/code/game/machinery/computer/cloning.dm
@@ -468,6 +468,7 @@
// We store the instance rather than the path, because some
// species (abductors, slimepeople) store state in their
// species datums
+ dna.delete_species = FALSE
R.fields["mrace"] = dna.species
else
var/datum/species/rando_race = pick(GLOB.roundstart_races)
diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm
index 24b392ec8f..596300097b 100755
--- a/code/game/machinery/computer/communications.dm
+++ b/code/game/machinery/computer/communications.dm
@@ -52,6 +52,8 @@
/obj/machinery/computer/communications/Topic(href, href_list)
if(..())
return
+ if(!usr.canUseTopic(src))
+ return
if(!is_station_level(z) && !is_reserved_level(z)) //Can only use in transit and on SS13
to_chat(usr, "Unable to establish a connection: \black You're too far away from the station!")
return
@@ -144,7 +146,7 @@
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
send2otherserver("[station_name()]", input,"Comms_Console")
minor_announce(input, title = "Outgoing message to allied station")
- log_talk(usr,"[key_name(usr)] has sent a message to the other server: [input]",LOGSAY)
+ usr.log_talk(input, LOG_SAY, tag="message to the other server")
message_admins("[ADMIN_LOOKUPFLW(usr)] has sent a message to the other server.")
deadchat_broadcast("[usr.real_name] has sent an outgoing message to the other station(s).", usr)
CM.lastTimeUsed = world.time
@@ -198,10 +200,7 @@
state = STATE_CANCELSHUTTLE
if("cancelshuttle2")
if(authenticated)
- if(world.time > SSshuttle.auto_call)
- say("Warning: Emergency shuttle recalls have been blocked by Central Command due to ongoing crew transfer procedures.")
- else
- SSshuttle.cancelEvac(usr)
+ SSshuttle.cancelEvac(usr)
state = STATE_DEFAULT
if("messagelist")
currmsg = 0
@@ -230,12 +229,14 @@
var/answer = text2num(href_list["answer"])
if(!currmsg || !answer || currmsg.possible_answers.len < answer)
state = STATE_MESSAGELIST
- currmsg.answered = answer
- log_game("[key_name(usr)] answered [currmsg.title] comm message. Answer : [currmsg.answered]")
- if(currmsg)
- currmsg.answer_callback.Invoke()
-
- state = STATE_VIEWMESSAGE
+ else
+ if(!currmsg.answered)
+ currmsg.answered = answer
+ log_game("[key_name(usr)] answered [currmsg.title] comm message. Answer : [currmsg.answered]")
+ if(currmsg)
+ currmsg.answer_callback.InvokeAsync()
+ state = STATE_VIEWMESSAGE
+ updateDialog()
if("status")
state = STATE_STATUSDISPLAY
if("securitylevel")
@@ -291,7 +292,7 @@
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
CentCom_announce(input, usr)
to_chat(usr, "Message transmitted to Central Command.")
- log_talk(usr,"[key_name(usr)] has made a CentCom announcement: [input]",LOGSAY)
+ usr.log_talk(input, LOG_SAY, tag="CentCom announcement")
deadchat_broadcast("[usr.real_name] has messaged CentCom, \"[input]\" at [get_area_name(usr, TRUE)].", usr)
CM.lastTimeUsed = world.time
@@ -308,7 +309,7 @@
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
Syndicate_announce(input, usr)
to_chat(usr, "SYSERR @l(19833)of(transmit.dm): !@$ MESSAGE TRANSMITTED TO SYNDICATE COMMAND.")
- log_talk(usr,"[key_name(usr)] has made a Syndicate announcement: [input]",LOGSAY)
+ usr.log_talk(input, LOG_SAY, tag="Syndicate announcement")
deadchat_broadcast("[usr.real_name] has messaged the Syndicate, \"[input]\" at [get_area_name(usr, TRUE)].", usr)
CM.lastTimeUsed = world.time
@@ -328,7 +329,7 @@
return
Nuke_request(input, usr)
to_chat(usr, "Request sent.")
- log_talk(usr,"[key_name(usr)] has requested the nuclear codes from CentCom",LOGSAY)
+ usr.log_message("has requested the nuclear codes from CentCom", LOG_SAY)
priority_announce("The codes for the on-station nuclear self-destruct have been requested by [usr]. Confirmation or denial of this request will be sent shortly.", "Nuclear Self Destruct Codes Requested",'sound/ai/commandreport.ogg')
CM.lastTimeUsed = world.time
@@ -366,11 +367,13 @@
var/answer = text2num(href_list["answer"])
if(!aicurrmsg || !answer || aicurrmsg.possible_answers.len < answer)
aistate = STATE_MESSAGELIST
- aicurrmsg.answered = answer
- log_game("[key_name(usr)] answered [aicurrmsg.title] comm message. Answer : [aicurrmsg.answered]")
- if(aicurrmsg.answer_callback)
- aicurrmsg.answer_callback.Invoke()
- aistate = STATE_VIEWMESSAGE
+ else
+ if(!aicurrmsg.answered)
+ aicurrmsg.answered = answer
+ log_game("[key_name(usr)] answered [aicurrmsg.title] comm message. Answer : [aicurrmsg.answered]")
+ if(aicurrmsg.answer_callback)
+ aicurrmsg.answer_callback.InvokeAsync()
+ aistate = STATE_VIEWMESSAGE
if("ai-status")
aistate = STATE_STATUSDISPLAY
if("ai-announce")
@@ -476,7 +479,7 @@
if (authenticated==2)
dat += "
Captain Functions"
dat += "
\[ Make a Captain's Announcement \]"
- var/cross_servers_count = length(CONFIG_GET(keyed_string_list/cross_server))
+ var/cross_servers_count = length(CONFIG_GET(keyed_list/cross_server))
if(cross_servers_count)
dat += "
\[ Send a message to [cross_servers_count == 1 ? "an " : ""]allied station[cross_servers_count > 1 ? "s" : ""] \]"
if(SSmapping.config.allow_custom_shuttles)
diff --git a/code/game/machinery/computer/medical.dm b/code/game/machinery/computer/medical.dm
index 2f1c0020b9..d65fb49161 100644
--- a/code/game/machinery/computer/medical.dm
+++ b/code/game/machinery/computer/medical.dm
@@ -596,3 +596,4 @@
icon_screen = "medlaptop"
icon_keyboard = "laptop_key"
clockwork = TRUE //it'd look weird
+ pass_flags = PASSTABLE
diff --git a/code/game/machinery/computer/prisoner.dm b/code/game/machinery/computer/prisoner.dm
index 1c1944e73e..798b0e4c65 100644
--- a/code/game/machinery/computer/prisoner.dm
+++ b/code/game/machinery/computer/prisoner.dm
@@ -136,7 +136,7 @@
if(I && istype(I) && I.imp_in)
var/mob/living/R = I.imp_in
to_chat(R, "You hear a voice in your head saying: '[warning]'")
- log_talk(usr,"[key_name(usr)] sent an implant message to [key_name(R)]: '[warning]'",LOGSAY)
+ log_directed_talk(usr, R, warning, LOG_SAY, "implant message")
src.add_fingerprint(usr)
src.updateUsrDialog()
diff --git a/code/game/machinery/computer/security.dm b/code/game/machinery/computer/security.dm
index 666966ee2e..bacedad6c0 100644
--- a/code/game/machinery/computer/security.dm
+++ b/code/game/machinery/computer/security.dm
@@ -38,6 +38,7 @@
icon_screen = "seclaptop"
icon_keyboard = "laptop_key"
clockwork = TRUE //it'd look weird
+ pass_flags = PASSTABLE
/obj/machinery/computer/secure_data/attackby(obj/item/O, mob/user, params)
if(istype(O, /obj/item/card/id))
diff --git a/code/game/machinery/defibrillator_mount.dm b/code/game/machinery/defibrillator_mount.dm
index a95270f131..7b7c8a3411 100644
--- a/code/game/machinery/defibrillator_mount.dm
+++ b/code/game/machinery/defibrillator_mount.dm
@@ -141,4 +141,4 @@
materials = list(MAT_METAL = 300, MAT_GLASS = 100)
w_class = WEIGHT_CLASS_BULKY
result_path = /obj/machinery/defibrillator_mount
- pixel_shift = -28
+ pixel_shift = -28
\ No newline at end of file
diff --git a/code/game/machinery/dish_drive.dm b/code/game/machinery/dish_drive.dm
index 7e960d4ebc..66aa1347cb 100644
--- a/code/game/machinery/dish_drive.dm
+++ b/code/game/machinery/dish_drive.dm
@@ -9,6 +9,7 @@
active_power_usage = 13 //10 with default parts
density = FALSE
circuit = /obj/item/circuitboard/machine/dish_drive
+ pass_flags = PASSTABLE
var/static/list/item_types = list(/obj/item/trash/waffles,
/obj/item/trash/plate,
/obj/item/trash/tray,
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index 45355ffac7..7f45f88dcd 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -99,7 +99,7 @@
/obj/machinery/door/airlock/Initialize()
. = ..()
- wires = new wiretypepath(src) //CIT CHANGE - makes it possible for airlocks to have different wire datums
+ wires = new /datum/wires/airlock(src)
if(frequency)
set_frequency(frequency)
@@ -147,7 +147,6 @@
welded = TRUE
if(24 to 30)
panel_open = TRUE
-
update_icon()
/obj/machinery/door/airlock/ComponentInitialize()
@@ -362,6 +361,7 @@
/obj/machinery/door/airlock/proc/regainMainPower()
if(src.secondsMainPowerLost > 0)
src.secondsMainPowerLost = 0
+ update_icon()
/obj/machinery/door/airlock/proc/handlePowerRestore()
var/cont = TRUE
@@ -382,6 +382,7 @@
cont = TRUE
spawnPowerRestoreRunning = FALSE
updateDialog()
+ update_icon()
/obj/machinery/door/airlock/proc/loseMainPower()
if(secondsMainPowerLost <= 0)
@@ -391,6 +392,7 @@
if(!spawnPowerRestoreRunning)
spawnPowerRestoreRunning = TRUE
INVOKE_ASYNC(src, .proc/handlePowerRestore)
+ update_icon()
/obj/machinery/door/airlock/proc/loseBackupPower()
if(src.secondsBackupPowerLost < 60)
@@ -398,10 +400,12 @@
if(!spawnPowerRestoreRunning)
spawnPowerRestoreRunning = TRUE
INVOKE_ASYNC(src, .proc/handlePowerRestore)
+ update_icon()
/obj/machinery/door/airlock/proc/regainBackupPower()
if(src.secondsBackupPowerLost > 0)
src.secondsBackupPowerLost = 0
+ update_icon()
// shock user with probability prb (if all connections & power are working)
// returns TRUE if shocked, FALSE otherwise
@@ -575,6 +579,7 @@
add_overlay(sparks_overlay)
add_overlay(damag_overlay)
add_overlay(note_overlay)
+ check_unres()
/proc/get_airlock_overlay(icon_state, icon_file)
var/obj/machinery/door/airlock/A
@@ -584,6 +589,31 @@
if((!(. = airlock_overlays[iconkey])))
. = airlock_overlays[iconkey] = mutable_appearance(icon_file, icon_state)
+/obj/machinery/door/airlock/proc/check_unres() //unrestricted sides. This overlay indicates which directions the player can access even without an ID
+ if(hasPower() && unres_sides)
+ if(unres_sides & NORTH)
+ var/image/I = image(icon='icons/obj/doors/airlocks/station/overlays.dmi', icon_state="unres_n") //layer=src.layer+1
+ I.pixel_y = 32
+ set_light(l_range = 2, l_power = 1)
+ add_overlay(I)
+ if(unres_sides & SOUTH)
+ var/image/I = image(icon='icons/obj/doors/airlocks/station/overlays.dmi', icon_state="unres_s") //layer=src.layer+1
+ I.pixel_y = -32
+ set_light(l_range = 2, l_power = 1)
+ add_overlay(I)
+ if(unres_sides & EAST)
+ var/image/I = image(icon='icons/obj/doors/airlocks/station/overlays.dmi', icon_state="unres_e") //layer=src.layer+1
+ I.pixel_x = 32
+ set_light(l_range = 2, l_power = 1)
+ add_overlay(I)
+ if(unres_sides & WEST)
+ var/image/I = image(icon='icons/obj/doors/airlocks/station/overlays.dmi', icon_state="unres_w") //layer=src.layer+1
+ I.pixel_x = -32
+ set_light(l_range = 2, l_power = 1)
+ add_overlay(I)
+ else
+ set_light(0)
+
/obj/machinery/door/airlock/do_animate(animation)
switch(animation)
if("opening")
@@ -634,6 +664,12 @@
else
to_chat(user, "It looks very robust.")
+ if(issilicon(user) && (!stat & BROKEN))
+ to_chat(user, "Shift-click [src] to [ density ? "open" : "close"] it.")
+ to_chat(user, "Ctrl-click [src] to [ locked ? "raise" : "drop"] its bolts.")
+ to_chat(user, "Alt-click [src] to [ secondsElectrified ? "un-electrify" : "permanently electrify"] it.")
+ to_chat(user, "Ctrl-Shift-click [src] to [ emergency ? "disable" : "enable"] emergency access.")
+
/obj/machinery/door/airlock/attack_ai(mob/user)
if(!src.canAIControl(user))
if(src.canAIHack())
@@ -785,7 +821,7 @@
if(do_after(user, 20, 1, target = src))
if(!panel_open || !S.use(2))
return
- user.visible_message("[user] reinforce \the [src] with metal.",
+ user.visible_message("[user] reinforces \the [src] with metal.",
"You reinforce \the [src] with metal.")
security_level = AIRLOCK_SECURITY_METAL
update_icon()
@@ -799,7 +835,7 @@
if(do_after(user, 20, 1, target = src))
if(!panel_open || !S.use(2))
return
- user.visible_message("[user] reinforce \the [src] with plasteel.",
+ user.visible_message("[user] reinforces \the [src] with plasteel.",
"You reinforce \the [src] with plasteel.")
security_level = AIRLOCK_SECURITY_PLASTEEL
modify_max_integrity(normal_integrity * AIRLOCK_INTEGRITY_MULTIPLIER)
@@ -1550,7 +1586,7 @@
to_chat(user, "The electrification wire has been cut")
else
LAZYADD(shockedby, "\[[time_stamp()]\] [key_name(user)]")
- add_logs(user, src, "electrified")
+ log_combat(user, src, "electrified")
set_electrified(AI_ELECTRIFY_DOOR_TIME)
/obj/machinery/door/airlock/proc/shock_perm(mob/user)
@@ -1560,7 +1596,7 @@
to_chat(user, "The electrification wire has been cut")
else
LAZYADD(shockedby, text("\[[time_stamp()]\] [key_name(user)]"))
- add_logs(user, src, "electrified")
+ log_combat(user, src, "electrified")
set_electrified(ELECTRIFIED_PERMANENT)
/obj/machinery/door/airlock/proc/emergency_on(mob/user)
diff --git a/code/game/machinery/doors/airlock_electronics.dm b/code/game/machinery/doors/airlock_electronics.dm
index f52ffd42fe..cfaa94b842 100644
--- a/code/game/machinery/doors/airlock_electronics.dm
+++ b/code/game/machinery/doors/airlock_electronics.dm
@@ -4,6 +4,7 @@
var/list/accesses = list()
var/one_access = 0
+ var/unres_sides = 0 //unrestricted sides, or sides of the airlock that will open regardless of access
/obj/item/electronics/airlock/examine(mob/user)
..()
@@ -34,6 +35,7 @@
regions[++regions.len] = region
data["regions"] = regions
data["oneAccess"] = one_access
+ data["unres_direction"] = unres_sides
return data
@@ -55,3 +57,7 @@
else
accesses -= access
. = TRUE
+ if("direc_set")
+ var/unres_direction = text2num(params["unres_direction"])
+ unres_sides ^= unres_direction //XOR, toggles only the bit that was clicked
+ . = TRUE
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index e169c66c24..222caeb595 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -34,6 +34,7 @@
var/real_explosion_block //ignore this, just use explosion_block
var/red_alert_access = FALSE //if TRUE, this door will always open on red alert
var/poddoor = FALSE
+ var/unres_sides = 0 //Unrestricted sides. A bitflag for which direction (if any) can open the door with no access
/obj/machinery/door/examine(mob/user)
..()
@@ -69,9 +70,11 @@
else
layer = initial(layer)
+/obj/machinery/door/power_change()
+ ..()
+ update_icon()
+
/obj/machinery/door/Destroy()
- density = FALSE
- air_update_turf(1)
update_freelook_sight()
GLOB.airlocks -= src
if(spark_system)
@@ -79,7 +82,7 @@
spark_system = null
return ..()
-/obj/machinery/door/CollidedWith(atom/movable/AM)
+/obj/machinery/door/Bumped(atom/movable/AM)
if(operating || (obj_flags & EMAGGED))
return
if(ismob(AM))
@@ -163,8 +166,13 @@
/obj/machinery/door/allowed(mob/M)
if(emergency)
return TRUE
+ if(unrestricted_side(M))
+ return TRUE
return ..()
+/obj/machinery/door/proc/unrestricted_side(mob/M) //Allows for specific side of airlocks to be unrestrected (IE, can exit maint freely, but need access to enter)
+ return get_dir(src, M) & unres_sides
+
/obj/machinery/door/proc/try_to_weld(obj/item/weldingtool/W, mob/user)
return
diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm
index dc7056784a..507769f9a3 100644
--- a/code/game/machinery/doors/firedoor.dm
+++ b/code/game/machinery/doors/firedoor.dm
@@ -67,7 +67,7 @@
affecting_areas.Cut()
return ..()
-/obj/machinery/door/firedoor/CollidedWith(atom/movable/AM)
+/obj/machinery/door/firedoor/Bumped(atom/movable/AM)
if(panel_open || operating)
return
if(!density)
diff --git a/code/game/machinery/doors/passworddoor.dm b/code/game/machinery/doors/passworddoor.dm
index 06c53c4e1f..fcbb214bb6 100644
--- a/code/game/machinery/doors/passworddoor.dm
+++ b/code/game/machinery/doors/passworddoor.dm
@@ -29,7 +29,7 @@
if(findtext(raw_message,password))
open()
-/obj/machinery/door/password/CollidedWith(atom/movable/AM)
+/obj/machinery/door/password/Bumped(atom/movable/AM)
return !density && ..()
/obj/machinery/door/password/try_to_activate_door(mob/user)
diff --git a/code/game/machinery/doors/poddoor.dm b/code/game/machinery/doors/poddoor.dm
index a581b20f52..ebc1e94e97 100644
--- a/code/game/machinery/doors/poddoor.dm
+++ b/code/game/machinery/doors/poddoor.dm
@@ -56,7 +56,7 @@
name = "combustion chamber vent"
id = INCINERATOR_SYNDICATELAVA_AUXVENT
-/obj/machinery/door/poddoor/CollidedWith(atom/movable/AM)
+/obj/machinery/door/poddoor/Bumped(atom/movable/AM)
if(density)
return 0
else
@@ -84,7 +84,7 @@
icon_state = "open"
/obj/machinery/door/poddoor/try_to_activate_door(mob/user)
- return
+ return
/obj/machinery/door/poddoor/try_to_crowbar(obj/item/I, mob/user)
if(stat & NOPOWER)
diff --git a/code/game/machinery/doors/unpowered.dm b/code/game/machinery/doors/unpowered.dm
index 2960335813..2e7e7d5cba 100644
--- a/code/game/machinery/doors/unpowered.dm
+++ b/code/game/machinery/doors/unpowered.dm
@@ -1,6 +1,6 @@
/obj/machinery/door/unpowered
-/obj/machinery/door/unpowered/CollidedWith(atom/movable/AM)
+/obj/machinery/door/unpowered/Bumped(atom/movable/AM)
if(src.locked)
return
..()
diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm
index 0f84817b5a..97ab664b85 100644
--- a/code/game/machinery/doors/windowdoor.dm
+++ b/code/game/machinery/doors/windowdoor.dm
@@ -36,6 +36,10 @@
if(cable)
debris += new /obj/item/stack/cable_coil(src, cable)
+/obj/machinery/door/window/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/ntnet_interface)
+
/obj/machinery/door/window/Destroy()
density = FALSE
QDEL_LIST(debris)
@@ -58,7 +62,7 @@
sleep(20)
close()
-/obj/machinery/door/window/CollidedWith(atom/movable/AM)
+/obj/machinery/door/window/Bumped(atom/movable/AM)
if( operating || !src.density )
return
if (!( ismob(AM) ))
@@ -298,6 +302,35 @@
if("deny")
flick("[src.base_state]deny", src)
+/obj/machinery/door/window/check_access_ntnet(datum/netdata/data)
+ return !requiresID() || ..()
+
+/obj/machinery/door/window/ntnet_receive(datum/netdata/data)
+ // Check if the airlock is powered.
+ if(!hasPower())
+ return
+
+ // Check packet access level.
+ if(!check_access_ntnet(data))
+ return
+
+ // Handle received packet.
+ var/command = lowertext(data.data["data"])
+ var/command_value = lowertext(data.data["data_secondary"])
+ switch(command)
+ if("open")
+ if(command_value == "on" && !density)
+ return
+
+ if(command_value == "off" && density)
+ return
+
+ if(density)
+ INVOKE_ASYNC(src, .proc/open)
+ else
+ INVOKE_ASYNC(src, .proc/close)
+ if("touch")
+ INVOKE_ASYNC(src, .proc/open_and_close)
/obj/machinery/door/window/brigdoor
name = "secure door"
diff --git a/code/game/machinery/doppler_array.dm b/code/game/machinery/doppler_array.dm
index 940734c0c2..9d522f8f1c 100644
--- a/code/game/machinery/doppler_array.dm
+++ b/code/game/machinery/doppler_array.dm
@@ -129,4 +129,4 @@ GLOBAL_LIST_EMPTY(doppler_arrays)
linked_techweb = SSresearch.science_tech
/proc/techweb_scale_bomb(lightradius)
- return lightradius ** 1.36
+ return (lightradius ** 0.5) * 3000
diff --git a/code/game/machinery/droneDispenser.dm b/code/game/machinery/droneDispenser.dm
index ffe7961534..7c92c158b3 100644
--- a/code/game/machinery/droneDispenser.dm
+++ b/code/game/machinery/droneDispenser.dm
@@ -50,7 +50,7 @@
/obj/machinery/droneDispenser/Initialize()
. = ..()
- var/datum/component/material_container/materials = AddComponent(/datum/component/material_container, list(MAT_METAL, MAT_GLASS), MINERAL_MATERIAL_AMOUNT * MAX_STACK_SIZE * 2, TRUE, list(/obj/item/stack))
+ var/datum/component/material_container/materials = AddComponent(/datum/component/material_container, list(MAT_METAL, MAT_GLASS), MINERAL_MATERIAL_AMOUNT * MAX_STACK_SIZE * 2, TRUE, /obj/item/stack)
materials.insert_amount(starting_amount)
materials.precise_insertion = TRUE
using_materials = list(MAT_METAL=metal_cost, MAT_GLASS=glass_cost)
diff --git a/code/game/machinery/firealarm.dm b/code/game/machinery/firealarm.dm
index 34a5659f3f..c3ce4c6a01 100644
--- a/code/game/machinery/firealarm.dm
+++ b/code/game/machinery/firealarm.dm
@@ -285,3 +285,40 @@
set_light(l_power = 0.8)
else
set_light(l_power = 0)
+
+/*
+ * Return of Party button
+ */
+
+/area
+ var/party = FALSE
+
+/obj/machinery/firealarm/partyalarm
+ name = "\improper PARTY BUTTON"
+ desc = "Cuban Pete is in the house!"
+ var/static/party_overlay
+
+/obj/machinery/firealarm/partyalarm/reset()
+ if (stat & (NOPOWER|BROKEN))
+ return
+ var/area/A = get_area(src)
+ if (!A || !A.party)
+ return
+ A.party = FALSE
+ A.cut_overlay(party_overlay)
+
+/obj/machinery/firealarm/partyalarm/alarm()
+ if (stat & (NOPOWER|BROKEN))
+ return
+ var/area/A = get_area(src)
+ if (!A || A.party || A.name == "Space")
+ return
+ A.party = TRUE
+ if (!party_overlay)
+ party_overlay = iconstate2appearance('icons/turf/areas.dmi', "party")
+ A.add_overlay(party_overlay)
+
+/obj/machinery/firealarm/partyalarm/ui_data(mob/user)
+ . = ..()
+ var/area/A = get_area(src)
+ .["alarm"] = A && A.party
diff --git a/code/game/machinery/flasher.dm b/code/game/machinery/flasher.dm
index 089abdb26b..12516c603d 100644
--- a/code/game/machinery/flasher.dm
+++ b/code/game/machinery/flasher.dm
@@ -100,19 +100,23 @@
if(!bulb.flash_recharge(30)) //Bulb can burn out if it's used too often too fast
power_change()
return
- bulb.times_used ++
playsound(src.loc, 'sound/weapons/flash.ogg', 100, 1)
flick("[base_state]_flash", src)
last_flash = world.time
use_power(1000)
+ var/flashed = FALSE
for (var/mob/living/L in viewers(src, null))
if (get_dist(src, L) > range)
continue
if(L.flash_act(affect_silicon = 1))
L.Knockdown(strength)
+ flashed = TRUE
+
+ if(flashed)
+ bulb.times_used++
return 1
diff --git a/code/game/machinery/harvester.dm b/code/game/machinery/harvester.dm
new file mode 100644
index 0000000000..17f355b5ad
--- /dev/null
+++ b/code/game/machinery/harvester.dm
@@ -0,0 +1,184 @@
+/obj/machinery/harvester
+ name = "organ harvester"
+ desc = "An advanced machine used for harvesting organs and limbs from the deceased."
+ density = TRUE
+ icon = 'icons/obj/machines/harvester.dmi'
+ icon_state = "harvester"
+ verb_say = "states"
+ state_open = FALSE
+ idle_power_usage = 50
+ circuit = /obj/item/circuitboard/machine/harvester
+ light_color = LIGHT_COLOR_BLUE
+ var/interval = 20
+ var/harvesting = FALSE
+ var/list/operation_order = list() //Order of wich we harvest limbs.
+ var/allow_clothing = FALSE
+ var/allow_living = FALSE
+
+/obj/machinery/harvester/Initialize()
+ . = ..()
+ if(prob(1))
+ name = "auto-autopsy"
+
+/obj/machinery/harvester/RefreshParts()
+ interval = 0
+ var/max_time = 40
+ for(var/obj/item/stock_parts/micro_laser/L in component_parts)
+ max_time -= L.rating
+ interval = max(max_time,1)
+
+/obj/machinery/harvester/update_icon(warming_up)
+ if(warming_up)
+ icon_state = initial(icon_state)+"-charging"
+ return
+ if(state_open)
+ icon_state = initial(icon_state)+"-open"
+ else if(harvesting)
+ icon_state = initial(icon_state)+"-active"
+ else
+ icon_state = initial(icon_state)
+
+/obj/machinery/harvester/open_machine(drop = TRUE)
+ if(panel_open)
+ return
+ . = ..()
+ harvesting = FALSE
+
+/obj/machinery/harvester/attack_hand(mob/user)
+ if(state_open)
+ close_machine()
+ else if(!harvesting)
+ open_machine()
+
+/obj/machinery/harvester/AltClick(mob/user)
+ if(harvesting || !user || !isliving(user) || state_open)
+ return
+ if(can_harvest())
+ start_harvest()
+
+/obj/machinery/harvester/proc/can_harvest()
+ if(!powered(EQUIP) || state_open || !occupant || !iscarbon(occupant))
+ return
+ var/mob/living/carbon/C = occupant
+ if(!allow_clothing)
+ for(var/A in C.held_items + C.get_equipped_items())
+ if(!isitem(A))
+ continue
+ var/obj/item/I = A
+ if(!(I.item_flags & NODROP))
+ say("Subject may not have abiotic items on.")
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 30, 1)
+ return
+ if(!(MOB_ORGANIC in C.mob_biotypes))
+ say("Subject is not organic.")
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 30, 1)
+ return
+ if(!allow_living && !(C.stat == DEAD || C.has_trait(TRAIT_FAKEDEATH))) //I mean, the machines scanners arent advanced enough to tell you're alive
+ say("Subject is still alive.")
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 30, 1)
+ return
+ return TRUE
+
+/obj/machinery/harvester/proc/start_harvest()
+ if(!occupant || !iscarbon(occupant))
+ return
+ var/mob/living/carbon/C = occupant
+ operation_order = reverseList(C.bodyparts) //Chest and head are first in bodyparts, so we invert it to make them suffer more
+ harvesting = TRUE
+ visible_message("The [name] begins warming up!")
+ say("Initializing harvest protocol.")
+ update_icon(TRUE)
+ addtimer(CALLBACK(src, .proc/harvest), interval)
+
+/obj/machinery/harvester/proc/harvest()
+ update_icon()
+ if(!harvesting || state_open || !powered(EQUIP) || !occupant || !iscarbon(occupant))
+ return
+ playsound(src, 'sound/machines/juicer.ogg', 20, 1)
+ var/mob/living/carbon/C = occupant
+ if(!LAZYLEN(operation_order)) //The list is empty, so we're done here
+ end_harvesting()
+ return
+ var/turf/target
+ for(var/adir in list(EAST,NORTH,SOUTH,WEST))
+ var/turf/T = get_step(src,adir)
+ if(!T)
+ continue
+ if(istype(T, /turf/closed))
+ continue
+ target = T
+ break
+ if(!target)
+ target = get_turf(src)
+ for(var/obj/item/bodypart/BP in operation_order) //first we do non-essential limbs
+ BP.drop_limb()
+ C.emote("scream")
+ if(BP.body_zone != "chest")
+ BP.forceMove(target) //Move the limbs right next to it, except chest, that's a weird one
+ BP.drop_organs()
+ else
+ for(var/obj/item/organ/O in BP.dismember())
+ O.forceMove(target) //Some organs, like chest ones, are different so we need to manually move them
+ operation_order.Remove(BP)
+ break
+ use_power(5000)
+ addtimer(CALLBACK(src, .proc/harvest), interval)
+
+/obj/machinery/harvester/proc/end_harvesting()
+ harvesting = FALSE
+ open_machine()
+ say("Subject has been succesfuly harvested.")
+ playsound(src, 'sound/machines/microwave/microwave-end.ogg', 100, 0)
+
+/obj/machinery/harvester/screwdriver_act(mob/living/user, obj/item/I)
+ . = ..()
+ if(.)
+ return TRUE
+ if(!state_open && !occupant)
+ if(default_deconstruction_screwdriver(user, "[initial(icon_state)]-o", initial(icon_state), I))
+ return
+
+/obj/machinery/harvester/crowbar_act(mob/living/user, obj/item/I)
+ if(default_pry_open(I))
+ return
+ if(default_deconstruction_crowbar(I))
+ return
+
+/obj/machinery/harvester/default_pry_open(obj/item/I) //wew
+ . = !(state_open || panel_open || (flags_1 & NODECONSTRUCT_1)) && I.tool_behaviour == TOOL_CROWBAR //We removed is_operational() here
+ if(.)
+ I.play_tool_sound(src, 50)
+ visible_message("[usr] pries open \the [src].", "You pry open [src].")
+ open_machine()
+
+/obj/machinery/harvester/emag_act(mob/user)
+ if(obj_flags & EMAGGED)
+ return
+ obj_flags |= EMAGGED
+ allow_living = TRUE
+ to_chat(user, "You overload [src]'s lifesign scanners.")
+
+/obj/machinery/harvester/container_resist(mob/living/user)
+ if(!harvesting)
+ visible_message("[occupant] emerges from [src]!",
+ "You climb out of [src]!")
+ open_machine()
+ else
+ to_chat(user,"[src] is active and can't be opened!") //rip
+
+/obj/machinery/harvester/Exited(atom/movable/user)
+ if (!state_open && user == occupant)
+ container_resist(user)
+
+/obj/machinery/harvester/relaymove(mob/user)
+ if (!state_open)
+ container_resist(user)
+
+/obj/machinery/harvester/examine(mob/user)
+ ..()
+ if(stat & BROKEN)
+ return
+ if(state_open)
+ to_chat(user, "[src] must be closed before harvesting.")
+ else if(!harvesting)
+ to_chat(user, "Alt-click [src] to start harvesting.")
diff --git a/code/game/machinery/launch_pad.dm b/code/game/machinery/launch_pad.dm
index 0c1b67a63c..6b39c1b58f 100644
--- a/code/game/machinery/launch_pad.dm
+++ b/code/game/machinery/launch_pad.dm
@@ -11,7 +11,7 @@
var/stationary = TRUE //to prevent briefcase pad deconstruction and such
var/display_name = "Launchpad"
var/teleport_speed = 35
- var/range = 5
+ var/range = 15
var/teleporting = FALSE //if it's in the process of teleporting
var/power_efficiency = 1
var/x_offset = 0
@@ -148,18 +148,17 @@
idle_power_usage = 0
active_power_usage = 0
teleport_speed = 20
- range = 3
+ range = 8
stationary = FALSE
var/closed = TRUE
- var/obj/item/briefcase_launchpad/briefcase
+ var/obj/item/storage/briefcase/launchpad/briefcase
-/obj/machinery/launchpad/briefcase/Initialize()
- . = ..()
- if(istype(loc, /obj/item/briefcase_launchpad))
- briefcase = loc
- else
- log_game("[src] has been spawned without a briefcase.")
- return INITIALIZE_HINT_QDEL
+/obj/machinery/launchpad/briefcase/Initialize(mapload, briefcase)
+ . = ..()
+ if(!briefcase)
+ log_game("[src] has been spawned without a briefcase.")
+ return INITIALIZE_HINT_QDEL
+ src.briefcase = briefcase
/obj/machinery/launchpad/briefcase/Destroy()
QDEL_NULL(briefcase)
@@ -180,47 +179,37 @@
usr.visible_message("[usr] starts closing [src]...", "You start closing [src]...")
if(do_after(usr, 30, target = usr))
usr.put_in_hands(briefcase)
- forceMove(briefcase)
+ moveToNullspace() //hides it from suitcase contents
closed = TRUE
/obj/machinery/launchpad/briefcase/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/launchpad_remote))
var/obj/item/launchpad_remote/L = I
+ if(L.pad == src) //do not attempt to link when already linked
+ return ..()
L.pad = src
to_chat(user, "You link [src] to [L].")
else
return ..()
//Briefcase item that contains the launchpad.
-/obj/item/briefcase_launchpad
- name = "briefcase"
- desc = "It's made of AUTHENTIC faux-leather and has a price-tag still attached. Its owner must be a real professional."
- icon = 'icons/obj/storage.dmi'
- icon_state = "briefcase"
- lefthand_file = 'icons/mob/inhands/equipment/briefcase_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/briefcase_righthand.dmi'
- flags_1 = CONDUCT_1
- force = 8
- hitsound = "swing_hit"
- throw_speed = 2
- throw_range = 4
- w_class = WEIGHT_CLASS_BULKY
- attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "whacked")
- resistance_flags = FLAMMABLE
- max_integrity = 150
+/obj/item/storage/briefcase/launchpad
var/obj/machinery/launchpad/briefcase/pad
-/obj/item/briefcase_launchpad/Initialize()
+/obj/item/storage/briefcase/launchpad/Initialize()
+ pad = new(null, src) //spawns pad in nullspace to hide it from briefcase contents
. = ..()
- pad = new(src)
-/obj/item/briefcase_launchpad/Destroy()
+/obj/item/storage/briefcase/launchpad/Destroy()
if(!QDELETED(pad))
- qdel(pad)
- pad = null
+ QDEL_NULL(pad)
return ..()
-/obj/item/briefcase_launchpad/attack_self(mob/user)
+/obj/item/storage/briefcase/launchpad/PopulateContents()
+ new /obj/item/pen(src)
+ new /obj/item/launchpad_remote(src, pad)
+
+/obj/item/storage/briefcase/launchpad/attack_self(mob/user)
if(!isturf(user.loc)) //no setting up in a locker
return
add_fingerprint(user)
@@ -229,25 +218,36 @@
pad.forceMove(get_turf(src))
pad.closed = FALSE
user.transferItemToLoc(src, pad, TRUE)
+ SEND_SIGNAL(src, COMSIG_TRY_STORAGE_HIDE_ALL)
-/obj/item/briefcase_launchpad/attackby(obj/item/I, mob/user, params)
+/obj/item/storage/briefcase/launchpad/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/launchpad_remote))
var/obj/item/launchpad_remote/L = I
+ if(L.pad == src.pad) //do not attempt to link when already linked
+ return ..()
L.pad = src.pad
to_chat(user, "You link [pad] to [L].")
else
- return ..()
+ return ..()
/obj/item/launchpad_remote
- name = "\improper Launchpad Control Remote"
- desc = "Used to teleport objects to and from a portable launchpad."
- icon = 'icons/obj/telescience.dmi'
- icon_state = "blpad-remote"
+ name = "folder"
+ desc = "A folder."
+ icon = 'icons/obj/bureaucracy.dmi'
+ icon_state = "folder"
w_class = WEIGHT_CLASS_SMALL
- slot_flags = ITEM_SLOT_BELT
var/sending = TRUE
var/obj/machinery/launchpad/briefcase/pad
+/obj/item/launchpad_remote/Initialize(mapload, pad) //remote spawns linked to the briefcase pad
+ . = ..()
+ src.pad = pad
+
+/obj/item/launchpad_remote/attack_self(mob/user)
+ . = ..()
+ ui_interact(user)
+ to_chat(user, "[src] projects a display onto your retina.")
+
/obj/item/launchpad_remote/ui_interact(mob/user, ui_key = "launchpad_remote", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
@@ -340,7 +340,7 @@
if("rename")
. = TRUE
- var/new_name = stripped_input(usr, "How do you want to rename the launchpad?", "Launchpad", pad.display_name, 15) as text|null
+ var/new_name = stripped_input(usr, "How do you want to rename the launchpad?", "Launchpad", pad.display_name, 15)
if(!new_name)
return
pad.display_name = new_name
diff --git a/code/game/machinery/newscaster.dm b/code/game/machinery/newscaster.dm
index bb4868d054..86e8fc8af5 100644
--- a/code/game/machinery/newscaster.dm
+++ b/code/game/machinery/newscaster.dm
@@ -118,17 +118,17 @@ GLOBAL_LIST_EMPTY(allCasters)
newChannel.is_admin_channel = adminChannel
network_channels += newChannel
-/datum/newscaster/feed_network/proc/SubmitArticle(msg, author, channel_name, obj/item/photo/photo, adminMessage = 0, allow_comments = 1)
+/datum/newscaster/feed_network/proc/SubmitArticle(msg, author, channel_name, datum/picture/picture, adminMessage = 0, allow_comments = 1)
var/datum/newscaster/feed_message/newMsg = new /datum/newscaster/feed_message
newMsg.author = author
newMsg.body = msg
newMsg.time_stamp = "[station_time_timestamp()]"
newMsg.is_admin_message = adminMessage
newMsg.locked = !allow_comments
- if(photo)
- newMsg.img = photo.picture.picture_image
- newMsg.caption = photo.scribble
- newMsg.photo_file = save_photo(photo.picture.picture_image)
+ if(picture)
+ newMsg.img = picture.picture_image
+ newMsg.caption = picture.caption
+ newMsg.photo_file = save_photo(picture.picture_image)
for(var/datum/newscaster/feed_channel/FC in network_channels)
if(FC.channel_name == channel_name)
FC.messages += newMsg
@@ -138,15 +138,15 @@ GLOBAL_LIST_EMPTY(allCasters)
lastAction ++
newMsg.creationTime = lastAction
-/datum/newscaster/feed_network/proc/submitWanted(criminal, body, scanned_user, obj/item/photo/photo, adminMsg = 0, newMessage = 0)
+/datum/newscaster/feed_network/proc/submitWanted(criminal, body, scanned_user, datum/picture/picture, adminMsg = 0, newMessage = 0)
wanted_issue.active = 1
wanted_issue.criminal = criminal
wanted_issue.body = body
wanted_issue.scannedUser = scanned_user
wanted_issue.isAdminMsg = adminMsg
- if(photo)
- wanted_issue.img = photo.picture.picture_image
- wanted_issue.photo_file = save_photo(photo.picture.picture_image)
+ if(picture)
+ wanted_issue.img = picture.picture_image
+ wanted_issue.photo_file = save_photo(picture.picture_image)
if(newMessage)
for(var/obj/machinery/newscaster/N in GLOB.allCasters)
N.newsAlert()
@@ -197,7 +197,7 @@ GLOBAL_LIST_EMPTY(allCasters)
var/alert = FALSE
var/scanned_user = "Unknown"
var/msg = ""
- var/obj/item/photo/photo = null
+ var/datum/picture/picture
var/channel_name = ""
var/c_locked=0
var/datum/newscaster/feed_channel/viewing_channel = null
@@ -221,7 +221,7 @@ GLOBAL_LIST_EMPTY(allCasters)
/obj/machinery/newscaster/Destroy()
GLOB.allCasters -= src
viewing_channel = null
- photo = null
+ picture = null
return ..()
/obj/machinery/newscaster/update_icon()
@@ -298,7 +298,7 @@ GLOBAL_LIST_EMPTY(allCasters)
if(CHANNEL.is_admin_channel)
dat+="[CHANNEL.channel_name]
"
else
- dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : ()]
"
+ dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : ""]
"
dat+="
" \
- + "", "window=pdaphoto;size=[picture.psize_x]x[picture.psize_y]")
+ + "