diff --git a/_maps/map_files/BoxStation/BoxStation.dmm b/_maps/map_files/BoxStation/BoxStation.dmm
index f6d73c48c5..9ace5cf95b 100644
--- a/_maps/map_files/BoxStation/BoxStation.dmm
+++ b/_maps/map_files/BoxStation/BoxStation.dmm
@@ -10444,7 +10444,7 @@
/area/maintenance/port/fore)
"avV" = (
/obj/structure/table/wood,
-/obj/item/reagent_containers/food/snacks/burger/ghost,
+/obj/item/ectoplasm,
/turf/open/floor/wood,
/area/maintenance/port/fore)
"avW" = (
diff --git a/_maps/map_files/generic/CentCom.dmm b/_maps/map_files/generic/CentCom.dmm
index d4cad66beb..4fb880fb4d 100644
--- a/_maps/map_files/generic/CentCom.dmm
+++ b/_maps/map_files/generic/CentCom.dmm
@@ -16329,7 +16329,7 @@
/obj/docking_port/stationary{
dwidth = 1;
height = 4;
- id = "pod4_away";
+ id = "pod_4_away";
name = "recovery ship";
width = 3
},
@@ -16339,7 +16339,7 @@
/obj/docking_port/stationary{
dwidth = 1;
height = 4;
- id = "pod3_away";
+ id = "pod_3_away";
name = "recovery ship";
width = 3
},
@@ -16489,7 +16489,7 @@
dir = 4;
dwidth = 1;
height = 4;
- id = "pod2_away";
+ id = "pod_2_away";
name = "recovery ship";
width = 3
},
diff --git a/_maps/shuttles/emergency_airless.dmm b/_maps/shuttles/emergency_construction.dmm
similarity index 100%
rename from _maps/shuttles/emergency_airless.dmm
rename to _maps/shuttles/emergency_construction.dmm
diff --git a/code/__DEFINES/cargo.dm b/code/__DEFINES/cargo.dm
index 40a50fe1e6..85e5e9d2ac 100644
--- a/code/__DEFINES/cargo.dm
+++ b/code/__DEFINES/cargo.dm
@@ -60,6 +60,5 @@ GLOBAL_LIST_INIT(podstyles, list(\
))
//cit
-#define PACK_GOODY_NONE 0
-#define PACK_GOODY_PUBLIC 1 //can be bought by both privates and cargo
-#define PACK_GOODY_PRIVATE 2 //can be bought only by privates
+#define PACK_GOODY_NONE 0 // can be bought by cargo and privates
+#define PACK_GOODY_PRIVATE 1 // can be bought only by privates
diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm
index d12fe77448..d219736abe 100644
--- a/code/__DEFINES/combat.dm
+++ b/code/__DEFINES/combat.dm
@@ -159,9 +159,6 @@
#define SHOVE_STAGGER_DURATION 35
/// how long they're off balance for
#define SHOVE_OFFBALANCE_DURATION 30
-//Shove disarming item list
-GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list(
- /obj/item/gun)))
//Embedded objects
diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index 7933fab83f..88f9759940 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -266,6 +266,7 @@
#define COMSIG_MOB_GET_VISIBLE_MESSAGE "mob_get_visible_message" //from base of atom/visible_message(): (atom/A, msg, range, ignored_mobs)
#define COMPONENT_NO_VISIBLE_MESSAGE 1 //exactly what's said on the tin.
#define COMSIG_MOB_ANTAG_ON_GAIN "mob_antag_on_gain" //from base of /datum/antagonist/on_gain(): (antag_datum)
+#define COMSIG_MOB_APPLY_DAMAGE "mob_apply_damage" //from base of /mob/living/proc/apply_damage(): (damage, damagetype, def_zone, wound_bonus, bare_wound_bonus, sharpness)
#define COMSIG_MOB_SPELL_CAN_CAST "mob_spell_can_cast" //from base of /obj/effect/proc_holder/spell/can_cast(): (spell)
#define COMSIG_MOB_SWAP_HANDS "mob_swap_hands" //from base of mob/swap_hand(): (obj/item)
@@ -303,6 +304,10 @@
#define COMSIG_LIVING_ACTIVE_PARRY_START "active_parry_start" //from base of mob/living/initiate_parry_sequence(): (parrying_method, datum/parrying_item_mob_or_art, list/backup_items, list/override)
#define COMPONENT_PREVENT_PARRY_START 1
+#define COMSIG_LIVING_ATTACKER_SET "living_attacker_set" // from base of /mob/living/set_last_attacker(): (attacker)
+
+#define COMSIG_LIVING_SET_AS_ATTACKER "living_set_as_attacker" // from base of /mob/living/set_last_attacker(): (target)
+
//ALL OF THESE DO NOT TAKE INTO ACCOUNT WHETHER AMOUNT IS 0 OR LOWER AND ARE SENT REGARDLESS!
#define COMSIG_LIVING_STATUS_STUN "living_stun" //from base of mob/living/Stun() (amount, update, ignore)
#define COMSIG_LIVING_STATUS_KNOCKDOWN "living_knockdown" //from base of mob/living/Knockdown() (amount, update, ignore)
@@ -357,7 +362,6 @@
// /obj/item signals
#define COMSIG_ITEM_ATTACK "item_attack" //from base of obj/item/attack(): (/mob/living/target, /mob/living/user)
-#define COMSIG_MOB_APPLY_DAMGE "mob_apply_damage" //from base of /mob/living/proc/apply_damage(): (damage, damagetype, def_zone)
#define COMSIG_ITEM_ATTACK_SELF "item_attack_self" //from base of obj/item/attack_self(): (/mob)
#define COMPONENT_NO_INTERACT 1
#define COMSIG_ITEM_ATTACK_OBJ "item_attack_obj" //from base of obj/item/attack_obj(): (/obj, /mob)
@@ -455,6 +459,10 @@
// /datum/mutation signals
#define COMSIG_HUMAN_MUTATION_LOSS "human_mutation_loss" //from datum/mutation/human/on_losing(): (datum/mutation/human/lost_mutation)
+///from base of mob/living/death(): (gibbed)
+// Sent before any of the other death code has run, mob is still alive.
+#define COMSIG_LIVING_PREDEATH "living_predeath"
+
/*******Component Specific Signals*******/
//Janitor
#define COMSIG_TURF_IS_WET "check_turf_wet" //(): Returns bitflags of wet values.
diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index fe139fff10..32d961d77d 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -159,7 +159,7 @@ GLOBAL_LIST_EMPTY(bloody_footprints_cache)
#define BLOOD_COLOR_SLIME "#00ff90"
#define BLOOD_COLOR_LIZARD "#db004D"
#define BLOOD_COLOR_UNIVERSAL "#db3300"
-#define BLOOD_COLOR_BUG "#a37c0f"
+#define BLOOD_COLOR_BUG "#ffc933"
#define BLOOD_COLOR_PLANT "#3d610e"
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index e27c42ff75..ded183ba0b 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -215,6 +215,7 @@
#define TRAIT_NO_STAMINA_BUFFER_REGENERATION "block_stamina_buffer_regen" /// Prevents stamina buffer regeneration
#define TRAIT_NO_STAMINA_REGENERATION "block_stamina_regen" /// Prevents stamina regeneration
#define TRAIT_ARMOR_BROKEN "armor_broken" //acts as if you are wearing no clothing when taking damage, does not affect non-clothing sources of protection
+#define TRAIT_IWASBATONED "iwasbatoned" //some dastardly fellow has struck you with a baton and thought to use another to strike you again, the rogue
/// forces update_density to make us not dense
#define TRAIT_LIVING_NO_DENSITY "living_no_density"
/// forces us to not render our overlays
diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm
index 99a2e9d0ab..a04f02bd6a 100644
--- a/code/__DEFINES/vv.dm
+++ b/code/__DEFINES/vv.dm
@@ -136,3 +136,6 @@
// paintings
#define VV_HK_REMOVE_PAINTING "remove_painting"
+
+//outfits
+#define VV_HK_TO_OUTFIT_EDITOR "outfit_editor"
diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm
index 31f34c5d4c..7e2abebcf9 100644
--- a/code/__HELPERS/_lists.dm
+++ b/code/__HELPERS/_lists.dm
@@ -15,7 +15,7 @@
#define LAZYREMOVE(L, I) if(L) { L -= I; if(!length(L)) { L = null; } }
#define LAZYADD(L, I) if(!L) { L = list(); } L += I;
#define LAZYOR(L, I) if(!L) { L = list(); } L |= I;
-#define LAZYFIND(L, V) L ? L.Find(V) : 0
+#define LAZYFIND(L, V) (L ? L.Find(V) : 0)
#define LAZYACCESS(L, I) (L ? (isnum(I) ? (I > 0 && I <= length(L) ? L[I] : null) : L[I]) : null)
#define LAZYSET(L, K, V) if(!L) { L = list(); } L[K] = V;
#define LAZYLEN(L) length(L)
diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm
index 1d37f639bf..fabe70c929 100644
--- a/code/__HELPERS/icons.dm
+++ b/code/__HELPERS/icons.dm
@@ -1051,7 +1051,7 @@ GLOBAL_LIST_EMPTY(friendly_animal_types)
return 0
//For creating consistent icons for human looking simple animals
-/proc/get_flat_human_icon(icon_id, datum/job/J, datum/preferences/prefs, dummy_key, showDirs = GLOB.cardinals, outfit_override = null)
+/proc/get_flat_human_icon(icon_id, datum/job/J, datum/preferences/prefs, dummy_key, showDirs = GLOB.cardinals, outfit_override = null, no_anim = FALSE)
var/static/list/humanoid_icon_cache = list()
if(!icon_id || !humanoid_icon_cache[icon_id])
var/mob/living/carbon/human/dummy/body = generate_or_wait_for_human_dummy(dummy_key)
@@ -1065,10 +1065,9 @@ GLOBAL_LIST_EMPTY(friendly_animal_types)
var/icon/out_icon = icon('icons/effects/effects.dmi', "nothing")
+ COMPILE_OVERLAYS(body)
for(var/D in showDirs)
- body.setDir(D)
- COMPILE_OVERLAYS(body)
- var/icon/partial = getFlatIcon(body)
+ var/icon/partial = getFlatIcon(body, defdir = D, no_anim = no_anim)
out_icon.Insert(partial,dir=D)
humanoid_icon_cache[icon_id] = out_icon
diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm
index b9cbfb7e35..9c6f16e724 100644
--- a/code/_globalvars/bitfields.dm
+++ b/code/_globalvars/bitfields.dm
@@ -11,6 +11,23 @@ GLOBAL_LIST_INIT(bitfields, list(
"TILE_BOUND" = TILE_BOUND,
"PIXEL_SCALE" = PIXEL_SCALE
),
+ "area_flags" = list(
+ "ABDUCTOR_PROOF" = ABDUCTOR_PROOF,
+ "BLOBS_ALLOWED" = BLOBS_ALLOWED,
+ "BLOCK_SUICIDE" = BLOCK_SUICIDE,
+ // "CULT_PERMITTED" = CULT_PERMITTED,
+ "FLORA_ALLOWED" = FLORA_ALLOWED,
+ "HIDDEN_AREA" = HIDDEN_AREA,
+ "MEGAFAUNA_SPAWN_ALLOWED" = MEGAFAUNA_SPAWN_ALLOWED,
+ "MOB_SPAWN_ALLOWED" = MOB_SPAWN_ALLOWED,
+ "NO_ALERTS" = NO_ALERTS,
+ "NOTELEPORT" = NOTELEPORT,
+ "CAVES_ALLOWED" = CAVES_ALLOWED,
+ "UNIQUE_AREA" = UNIQUE_AREA,
+ "VALID_TERRITORY" = VALID_TERRITORY,
+ "XENOBIOLOGY_COMPATIBLE" = XENOBIOLOGY_COMPATIBLE,
+ "NO_ALERTS" = NO_ALERTS,
+ ) ,
"sight" = list(
"SEE_INFRA" = SEE_INFRA,
"SEE_SELF" = SEE_SELF,
@@ -32,7 +49,12 @@ GLOBAL_LIST_INIT(bitfields, list(
"UNIQUE_RENAME" = UNIQUE_RENAME,
"USES_TGUI" = USES_TGUI,
"FROZEN" = FROZEN,
- "SHOVABLE_ONTO" = SHOVABLE_ONTO
+ "SHOVABLE_ONTO" = SHOVABLE_ONTO,
+ "BLOCK_Z_OUT_DOWN" = BLOCK_Z_OUT_DOWN,
+ "BLOCK_Z_OUT_UP" = BLOCK_Z_OUT_UP,
+ "BLOCK_Z_IN_DOWN" = BLOCK_Z_IN_DOWN,
+ "BLOCK_Z_IN_UP" = BLOCK_Z_IN_UP,
+ "EXAMINE_SKIP" = EXAMINE_SKIP
),
"datum_flags" = list(
"DF_USE_TAG" = DF_USE_TAG,
diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm
index ac6ea4e25c..26e0197deb 100644
--- a/code/_globalvars/traits.dm
+++ b/code/_globalvars/traits.dm
@@ -123,7 +123,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_HIGH_BLOOD" = TRAIT_HIGH_BLOOD,
"TRAIT_EMPATH" = TRAIT_EMPATH,
"TRAIT_FRIENDLY" = TRAIT_FRIENDLY,
- "TRAIT_NICE_SHOT" = TRAIT_NICE_SHOT
+ "TRAIT_IWASBATONED" = TRAIT_IWASBATONED
),
/obj/item/bodypart = list(
"TRAIT_PARALYSIS" = TRAIT_PARALYSIS
diff --git a/code/_onclick/hud/screen_objects/storage.dm b/code/_onclick/hud/screen_objects/storage.dm
index 72b2d035a3..1ce1df24c6 100644
--- a/code/_onclick/hud/screen_objects/storage.dm
+++ b/code/_onclick/hud/screen_objects/storage.dm
@@ -85,21 +85,16 @@
makeItemInactive()
/obj/screen/storage/volumetric_box/proc/makeItemInactive()
- if(!our_item)
- return
- our_item.layer = VOLUMETRIC_STORAGE_ITEM_LAYER
- our_item.plane = VOLUMETRIC_STORAGE_ITEM_PLANE
+ return
/obj/screen/storage/volumetric_box/proc/makeItemActive()
- if(!our_item)
- return
- our_item.layer = VOLUMETRIC_STORAGE_ACTIVE_ITEM_LAYER //make sure we display infront of the others!
- our_item.plane = VOLUMETRIC_STORAGE_ACTIVE_ITEM_PLANE
+ return
/obj/screen/storage/volumetric_box/center
icon_state = "stored_continue"
var/obj/screen/storage/volumetric_edge/stored_left/left
var/obj/screen/storage/volumetric_edge/stored_right/right
+ var/obj/screen/storage/item_holder/holder
var/pixel_size
/obj/screen/storage/volumetric_box/center/Initialize(mapload, new_master, our_item)
@@ -110,6 +105,9 @@
/obj/screen/storage/volumetric_box/center/Destroy()
QDEL_NULL(left)
QDEL_NULL(right)
+ vis_contents.Cut()
+ if(holder)
+ QDEL_NULL(holder)
return ..()
/obj/screen/storage/volumetric_box/center/proc/on_screen_objects()
@@ -123,13 +121,36 @@
return
pixel_size = pixels
cut_overlays()
+ vis_contents.Cut()
//our icon size is 32 pixels.
- transform = matrix((pixels - (VOLUMETRIC_STORAGE_BOX_BORDER_SIZE * 2)) / VOLUMETRIC_STORAGE_BOX_ICON_SIZE, 0, 0, 0, 1, 0)
+ var/multiplier = (pixels - (VOLUMETRIC_STORAGE_BOX_BORDER_SIZE * 2)) / VOLUMETRIC_STORAGE_BOX_ICON_SIZE
+ transform = matrix(multiplier, 0, 0, 0, 1, 0)
+ if(our_item)
+ if(holder)
+ qdel(holder)
+ holder = new(null, src, our_item)
+ holder.transform = matrix(1 / multiplier, 0, 0, 0, 1, 0)
+ holder.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ holder.appearance_flags &= ~RESET_TRANSFORM
+ makeItemInactive()
+ vis_contents += holder
left.pixel_x = -((pixels - VOLUMETRIC_STORAGE_BOX_ICON_SIZE) * 0.5) - VOLUMETRIC_STORAGE_BOX_BORDER_SIZE
right.pixel_x = ((pixels - VOLUMETRIC_STORAGE_BOX_ICON_SIZE) * 0.5) + VOLUMETRIC_STORAGE_BOX_BORDER_SIZE
add_overlay(left)
add_overlay(right)
+/obj/screen/storage/volumetric_box/center/makeItemInactive()
+ if(!holder)
+ return
+ holder.layer = VOLUMETRIC_STORAGE_ITEM_LAYER
+ holder.plane = VOLUMETRIC_STORAGE_ITEM_PLANE
+
+/obj/screen/storage/volumetric_box/center/makeItemActive()
+ if(!holder)
+ return
+ holder.our_item.layer = VOLUMETRIC_STORAGE_ACTIVE_ITEM_LAYER //make sure we display infront of the others!
+ holder.our_item.plane = VOLUMETRIC_STORAGE_ACTIVE_ITEM_PLANE
+
/obj/screen/storage/volumetric_edge
layer = VOLUMETRIC_STORAGE_BOX_LAYER
plane = VOLUMETRIC_STORAGE_BOX_PLANE
@@ -157,3 +178,20 @@
/obj/screen/storage/volumetric_edge/stored_right
icon_state = "stored_end"
appearance_flags = APPEARANCE_UI | KEEP_APART | RESET_TRANSFORM
+
+/obj/screen/storage/item_holder
+ var/obj/item/our_item
+ vis_flags = NONE
+
+/obj/screen/storage/item_holder/Initialize(mapload, new_master, obj/item/I)
+ . = ..()
+ our_item = I
+ vis_contents += I
+
+/obj/screen/storage/item_holder/Destroy()
+ vis_contents.Cut()
+ our_item = null
+ return ..()
+
+/obj/screen/storage/item_holder/Click(location, control, params)
+ return our_item.Click(location, control, params)
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index f63e594675..faf911e55b 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -94,8 +94,7 @@
else if(hitsound)
playsound(loc, hitsound, get_clamped_volume(), 1, -1)
- M.lastattacker = user.real_name
- M.lastattackerckey = user.ckey
+ M.set_last_attacker(user)
if(force && M == user && user.client)
user.client.give_award(/datum/award/achievement/misc/selfouch, user)
diff --git a/code/datums/components/activity.dm b/code/datums/components/activity.dm
new file mode 100644
index 0000000000..ae18ca01a2
--- /dev/null
+++ b/code/datums/components/activity.dm
@@ -0,0 +1,63 @@
+/datum/component/activity
+ var/activity_level = 0
+ var/not_moved_counter = 0
+ var/list/historical_activity_levels = list()
+
+/datum/component/activity/Initialize()
+ if(!isliving(parent))
+ return COMPONENT_INCOMPATIBLE
+ var/mob/living/L = parent
+
+ RegisterSignal(L, COMSIG_LIVING_SET_AS_ATTACKER, .proc/on_set_as_attacker)
+ RegisterSignal(L, COMSIG_LIVING_ATTACKER_SET, .proc/on_attacker_set)
+ RegisterSignal(L, COMSIG_MOB_DEATH, .proc/on_death)
+ RegisterSignal(L, COMSIG_EXIT_AREA, .proc/on_exit_area)
+ RegisterSignal(L, COMSIG_LIVING_LIFE, .proc/on_life)
+ RegisterSignal(L, list(COMSIG_MOB_ITEM_ATTACK, COMSIG_MOB_ATTACK_RANGED, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, COMSIG_MOB_ATTACK_HAND, COMSIG_MOB_THROW, COMSIG_MOVABLE_TELEPORTED, COMSIG_LIVING_GUN_PROCESS_FIRE, COMSIG_MOB_APPLY_DAMAGE), .proc/minor_activity)
+
+/datum/component/activity/proc/log_activity()
+ historical_activity_levels[world.time] = activity_level
+
+/datum/component/activity/proc/minor_activity(datum/source)
+ activity_level += 1
+
+/datum/component/activity/proc/on_attacker_set(datum/source, mob/attacker)
+ activity_level += 10
+ if(attacker?.mind)
+ activity_level += 10
+ log_activity()
+
+/datum/component/activity/proc/on_set_as_attacker(datum/source, mob/target)
+ activity_level += 10
+ if(target?.mind)
+ activity_level += 20
+ log_activity()
+
+/datum/component/activity/proc/on_death(datum/source)
+ activity_level += 100 // dying means you're doing SOMETHING
+ log_activity()
+
+/datum/component/activity/proc/on_exit_area(datum/source)
+ activity_level += 1
+ not_moved_counter = 0
+
+/datum/component/activity/proc/on_life(datum/source, seconds, times_fired)
+ var/mob/living/L = source
+ if(L.stat >= UNCONSCIOUS) // can't expect the unconscious to move
+ return
+ not_moved_counter += seconds
+ var/should_log = FALSE
+ switch(not_moved_counter)
+ if(60 to 120)
+ activity_level -= 1
+ if(120 to 600)
+ activity_level -= 5
+ if(600 to 1200)
+ activity_level -= 10
+ should_log = TRUE
+ if(1200 to INFINITY)
+ activity_level -= 20
+ should_log = TRUE
+ activity_level = max(activity_level, 0)
+ if(should_log)
+ log_activity()
diff --git a/code/datums/components/explodable.dm b/code/datums/components/explodable.dm
index 2e5834c025..6e4be8497d 100644
--- a/code/datums/components/explodable.dm
+++ b/code/datums/components/explodable.dm
@@ -56,10 +56,10 @@
detonate()
/datum/component/explodable/proc/on_equip(datum/source, mob/equipper, slot)
- RegisterSignal(equipper, COMSIG_MOB_APPLY_DAMGE, .proc/explodable_attack_zone, TRUE)
+ RegisterSignal(equipper, COMSIG_MOB_APPLY_DAMAGE, .proc/explodable_attack_zone, TRUE)
/datum/component/explodable/proc/on_drop(datum/source, mob/user)
- UnregisterSignal(user, COMSIG_MOB_APPLY_DAMGE)
+ UnregisterSignal(user, COMSIG_MOB_APPLY_DAMAGE)
/// Checks if we're hitting the zone this component is covering
/datum/component/explodable/proc/is_hitting_zone(def_zone)
diff --git a/code/datums/components/radioactive.dm b/code/datums/components/radioactive.dm
index 8afa4353b2..4c418bb8dc 100644
--- a/code/datums/components/radioactive.dm
+++ b/code/datums/components/radioactive.dm
@@ -52,7 +52,7 @@
return
strength -= strength / hl3_release_date
if(strength <= RAD_BACKGROUND_RADIATION)
- return PROCESS_KILL
+ qdel(src)
/datum/component/radioactive/proc/glow_loop(atom/movable/master)
diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm
index 2fa5a20d7a..7968caed54 100644
--- a/code/datums/components/storage/storage.dm
+++ b/code/datums/components/storage/storage.dm
@@ -47,18 +47,8 @@
var/display_numerical_stacking = FALSE //stack things of the same type and show as a single object with a number.
- /// "legacy"/default view mode's storage "boxes"
- var/obj/screen/storage/boxes/ui_boxes
- /// New volumetric storage display mode's left side
- var/obj/screen/storage/left/ui_left
- /// New volumetric storage display mode's center 'blocks'
- var/obj/screen/storage/continuous/ui_continuous
- /// The close button, used in all modes. Frames right side in volumetric mode.
- var/obj/screen/storage/close/ui_close
- /// Associative list of list(item = screen object) for volumetric storage item screen blocks
- var/list/ui_item_blocks
-
- var/current_maxscreensize
+ /// Ui objects by person. mob = list(objects)
+ var/list/ui_by_mob = list()
var/allow_big_nesting = FALSE //allow storage objects of the same or greater size.
@@ -125,18 +115,16 @@
/datum/component/storage/Destroy()
close_all()
- QDEL_NULL(ui_boxes)
- QDEL_NULL(ui_close)
- QDEL_NULL(ui_continuous)
- QDEL_NULL(ui_left)
- // DO NOT USE QDEL_LIST_ASSOC.
- if(ui_item_blocks)
- for(var/i in ui_item_blocks)
- qdel(ui_item_blocks[i]) //qdel the screen object not the item
- ui_item_blocks.Cut()
+ wipe_ui_objects()
LAZYCLEARLIST(is_using)
return ..()
+/datum/component/storage/proc/wipe_ui_objects()
+ for(var/i in ui_by_mob)
+ var/list/objects = ui_by_mob[i]
+ QDEL_LIST(objects)
+ ui_by_mob.Cut()
+
/datum/component/storage/PreTransfer()
update_actions()
@@ -351,13 +339,6 @@
return master._removal_reset(thing)
/datum/component/storage/proc/_remove_and_refresh(datum/source, atom/movable/thing)
- if(LAZYACCESS(ui_item_blocks, thing))
- var/obj/screen/storage/volumetric_box/center/C = ui_item_blocks[thing]
- for(var/i in can_see_contents()) //runtimes result if mobs can access post deletion.
- var/mob/M = i
- M.client?.screen -= C.on_screen_objects()
- ui_item_blocks -= thing
- qdel(C)
_removal_reset(thing) // THIS NEEDS TO HAPPEN AFTER SO LAYERING DOESN'T BREAK!
refresh_mob_views()
@@ -467,14 +448,14 @@
return
A.add_fingerprint(M)
-/datum/component/storage/proc/user_show_to_mob(mob/M, force = FALSE, ghost = FALSE)
+/datum/component/storage/proc/user_show_to_mob(mob/M, force = FALSE)
var/atom/A = parent
if(!istype(M))
return FALSE
A.add_fingerprint(M)
if(!force && (check_locked(null, M) || !M.CanReach(parent, view_only = TRUE)))
return FALSE
- ui_show(M, !ghost)
+ ui_show(M)
/datum/component/storage/proc/mousedrop_receive(datum/source, atom/movable/O, mob/M)
if(isitem(O))
@@ -596,7 +577,7 @@
return can_be_inserted(I, silent, M)
/datum/component/storage/proc/show_to_ghost(datum/source, mob/dead/observer/M)
- return user_show_to_mob(M, TRUE, TRUE)
+ return user_show_to_mob(M, TRUE)
/datum/component/storage/proc/signal_show_attempt(datum/source, mob/showto, force = FALSE)
return user_show_to_mob(showto, force)
diff --git a/code/datums/components/storage/ui.dm b/code/datums/components/storage/ui.dm
index c7ac0d3549..044afa2850 100644
--- a/code/datums/components/storage/ui.dm
+++ b/code/datums/components/storage/ui.dm
@@ -7,7 +7,7 @@
if(QDELETED(I))
continue
if(!.[I.type])
- .[I.type] = new /datum/numbered_display(I, 1)
+ .[I.type] = new /datum/numbered_display(I, 1, src)
else
var/datum/numbered_display/ND = .[I.type]
ND.number++
@@ -20,6 +20,8 @@
. = list()
var/list/accessible_contents = accessible_items()
var/adjusted_contents = length(accessible_contents)
+ var/obj/screen/storage/close/ui_close
+ var/obj/screen/storage/boxes/ui_boxes
//Numbered contents display
var/list/datum/numbered_display/numbered_contents
@@ -60,12 +62,13 @@
for(var/obj/O in accessible_items())
if(QDELETED(O))
continue
- O.mouse_opacity = MOUSE_OPACITY_OPAQUE //This is here so storage items that spawn with contents correctly have the "click around item to equip"
- O.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]"
+ var/obj/screen/storage/item_holder/D = new(null, src, O)
+ D.mouse_opacity = MOUSE_OPACITY_OPAQUE //This is here so storage items that spawn with contents correctly have the "click around item to equip"
+ D.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]"
O.maptext = ""
O.layer = ABOVE_HUD_LAYER
O.plane = ABOVE_HUD_PLANE
- . += O
+ . += D
cx++
if(cx - screen_start_x >= columns)
cx = screen_start_x
@@ -78,6 +81,9 @@
*/
/datum/component/storage/proc/orient2hud_volumetric(mob/user, maxcolumns)
. = list()
+ var/obj/screen/storage/left/ui_left
+ var/obj/screen/storage/continuous/ui_continuous
+ var/obj/screen/storage/close/ui_close
// Generate ui_item_blocks for missing ones and render+orient.
var/list/atom/contents = accessible_items()
@@ -128,14 +134,10 @@
var/first = TRUE
var/row = 1
- LAZYINITLIST(ui_item_blocks)
-
for(var/i in percentage_by_item)
I = i
var/percent = percentage_by_item[I]
- if(!ui_item_blocks[I])
- ui_item_blocks[I] = new /obj/screen/storage/volumetric_box/center(null, src, I)
- var/obj/screen/storage/volumetric_box/center/B = ui_item_blocks[I]
+ var/obj/screen/storage/volumetric_box/center/B = new /obj/screen/storage/volumetric_box/center(null, src, I)
var/pixels_to_use = overrun? MINIMUM_PIXELS_PER_ITEM : max(using_horizontal_pixels * percent, MINIMUM_PIXELS_PER_ITEM)
var/addrow = FALSE
if(CEILING(pixels_to_use, 1) >= FLOOR(horizontal_pixels - current_pixel - VOLUMETRIC_STORAGE_EDGE_PADDING, 1))
@@ -143,25 +145,17 @@
addrow = TRUE
// now that we have pixels_to_use, place our thing and add it to the returned list.
- B.screen_loc = I.screen_loc = "[screen_start_x]:[round(current_pixel + (pixels_to_use * 0.5) + (first? 0 : VOLUMETRIC_STORAGE_ITEM_PADDING), 1)],[screen_start_y+row-1]:[screen_pixel_y]"
+ B.screen_loc = "[screen_start_x]:[round(current_pixel + (pixels_to_use * 0.5) + (first? 0 : VOLUMETRIC_STORAGE_ITEM_PADDING), 1)],[screen_start_y+row-1]:[screen_pixel_y]"
// add the used pixels to pixel after we place the object
current_pixel += pixels_to_use + (first? 0 : VOLUMETRIC_STORAGE_ITEM_PADDING)
first = FALSE //apply padding to everything after this
// set various things
B.set_pixel_size(pixels_to_use)
- B.layer = VOLUMETRIC_STORAGE_BOX_LAYER
- B.plane = VOLUMETRIC_STORAGE_BOX_PLANE
B.name = I.name
- I.mouse_opacity = MOUSE_OPACITY_ICON
- I.maptext = ""
- I.layer = VOLUMETRIC_STORAGE_ITEM_LAYER
- I.plane = VOLUMETRIC_STORAGE_ITEM_PLANE
-
// finally add our things.
. += B.on_screen_objects()
- . += I
// go up a row if needed
if(addrow)
@@ -185,18 +179,19 @@
/**
* Shows our UI to a mob.
*/
-/datum/component/storage/proc/ui_show(mob/M, set_screen_size = TRUE)
+/datum/component/storage/proc/ui_show(mob/M)
if(!M.client)
return FALSE
+ if(ui_by_mob[M] || LAZYFIND(is_using, M))
+ // something went horribly wrong
+ // hide first
+ ui_hide(M)
var/list/cview = getviewsize(M.client.view)
// in tiles
var/maxallowedscreensize = cview[1]-8
- if(set_screen_size)
- current_maxscreensize = maxallowedscreensize
- else if(current_maxscreensize)
- maxallowedscreensize = current_maxscreensize
// we got screen size, register signal
RegisterSignal(M, COMSIG_MOB_CLIENT_LOGOUT, .proc/on_logout, override = TRUE)
+ RegisterSignal(M, COMSIG_PARENT_QDELETING, .proc/on_logout, override = TRUE)
if(M.active_storage != src)
if(M.active_storage)
M.active_storage.ui_hide(M)
@@ -204,10 +199,14 @@
LAZYOR(is_using, M)
if(!M.client?.prefs?.no_tetris_storage && volumetric_ui())
//new volumetric ui bay-style
- M.client.screen |= orient2hud_volumetric(M, maxallowedscreensize)
+ var/list/objects = orient2hud_volumetric(M, maxallowedscreensize)
+ M.client.screen |= objects
+ ui_by_mob[M] = objects
else
//old ui
- M.client.screen |= orient2hud_legacy(M, maxallowedscreensize)
+ var/list/objects = orient2hud_legacy(M, maxallowedscreensize)
+ M.client.screen |= objects
+ ui_by_mob[M] = objects
return TRUE
/**
@@ -236,8 +235,10 @@
/datum/component/storage/proc/ui_hide(mob/M)
if(!M.client)
return TRUE
- UnregisterSignal(M, COMSIG_MOB_CLIENT_LOGOUT)
- M.client.screen -= list(ui_boxes, ui_close, ui_left, ui_continuous) + get_ui_item_objects_hide(M)
+ UnregisterSignal(M, list(COMSIG_PARENT_QDELETING, COMSIG_MOB_CLIENT_LOGOUT))
+ M.client.screen -= ui_by_mob[M]
+ var/list/objects = ui_by_mob[M]
+ QDEL_LIST(objects)
if(M.active_storage == src)
M.active_storage = null
LAZYREMOVE(is_using, M)
@@ -250,48 +251,26 @@
var/atom/real_location = real_location()
return (storage_flags & STORAGE_LIMIT_VOLUME) && (length(real_location.contents) <= MAXIMUM_VOLUMETRIC_ITEMS) && !display_numerical_stacking
-/**
- * Gets the ui item objects to ui_hide.
- */
-/datum/component/storage/proc/get_ui_item_objects_hide(mob/M)
- if(!volumetric_ui() || M.client?.prefs?.no_tetris_storage)
- var/atom/real_location = real_location()
- return real_location.contents
- else
- . = list()
- for(var/i in ui_item_blocks)
- // get both the box and the item
- . += ui_item_blocks[i]
- . += i
-
/**
* Gets our ui_boxes, making it if it doesn't exist.
*/
/datum/component/storage/proc/get_ui_boxes()
- if(!ui_boxes)
- ui_boxes = new(null, src)
- return ui_boxes
+ return new /obj/screen/storage/boxes(null, src)
/**
* Gets our ui_left, making it if it doesn't exist.
*/
/datum/component/storage/proc/get_ui_left()
- if(!ui_left)
- ui_left = new(null, src)
- return ui_left
+ return new /obj/screen/storage/left(null, src)
/**
* Gets our ui_close, making it if it doesn't exist.
*/
/datum/component/storage/proc/get_ui_close()
- if(!ui_close)
- ui_close = new(null, src)
- return ui_close
+ return new /obj/screen/storage/close(null, src)
/**
* Gets our ui_continuous, making it if it doesn't exist.
*/
/datum/component/storage/proc/get_ui_continuous()
- if(!ui_continuous)
- ui_continuous = new(null, src)
- return ui_continuous
+ return new /obj/screen/storage/continuous(null, src)
diff --git a/code/datums/dna.dm b/code/datums/dna.dm
index b6b93bdddc..248b669ab1 100644
--- a/code/datums/dna.dm
+++ b/code/datums/dna.dm
@@ -329,12 +329,13 @@
uni_identity = generate_uni_identity()
unique_enzymes = generate_unique_enzymes()
-/datum/dna/proc/initialize_dna(newblood_type)
+/datum/dna/proc/initialize_dna(newblood_type, skip_index = FALSE)
if(newblood_type)
blood_type = newblood_type
unique_enzymes = generate_unique_enzymes()
uni_identity = generate_uni_identity()
- generate_dna_blocks()
+ if(!skip_index) //I hate this
+ generate_dna_blocks()
features = random_features(species?.id, holder?.gender)
diff --git a/code/datums/elements/wuv.dm b/code/datums/elements/wuv.dm
index 6476a204cf..d4f1955afc 100644
--- a/code/datums/elements/wuv.dm
+++ b/code/datums/elements/wuv.dm
@@ -34,13 +34,13 @@
. = ..()
UnregisterSignal(source, COMSIG_MOB_ATTACK_HAND)
-/datum/element/wuv/proc/on_attack_hand(datum/source, mob/user)
+/datum/element/wuv/proc/on_attack_hand(datum/source, mob/user, act_intent)
var/mob/living/L = source
if(L.stat == DEAD)
return
//we want to delay the effect to be displayed after the mob is petted, not before.
- switch(user.a_intent)
+ switch(act_intent)
if(INTENT_HARM)
addtimer(CALLBACK(src, .proc/kick_the_dog, source, user), 1)
if(INTENT_HELP)
diff --git a/code/datums/numbered_display.dm b/code/datums/numbered_display.dm
index 9aa880aa75..fc2035b39f 100644
--- a/code/datums/numbered_display.dm
+++ b/code/datums/numbered_display.dm
@@ -3,8 +3,8 @@
var/obj/item/sample_object
var/number
-/datum/numbered_display/New(obj/item/sample, _number = 1)
+/datum/numbered_display/New(obj/item/sample, _number = 1, datum/component/storage/parent)
if(!istype(sample))
qdel(src)
- sample_object = sample
+ sample_object = new /obj/screen/storage/item_holder(null, parent, sample)
number = _number
diff --git a/code/datums/outfit.dm b/code/datums/outfit.dm
index da379b9851..0b46629365 100755
--- a/code/datums/outfit.dm
+++ b/code/datums/outfit.dm
@@ -1,71 +1,185 @@
+/**
+ * # Outfit datums
+ *
+ * This is a clean system of applying outfits to mobs, if you need to equip someone in a uniform
+ * this is the way to do it cleanly and properly.
+ *
+ * You can also specify an outfit datum on a job to have it auto equipped to the mob on join
+ *
+ * /mob/living/carbon/human/proc/equipOutfit(outfit) is the mob level proc to equip an outfit
+ * and you pass it the relevant datum outfit
+ *
+ * outfits can also be saved as json blobs downloadable by a client and then can be uploaded
+ * by that user to recreate the outfit, this is used by admins to allow for custom event outfits
+ * that can be restored at a later date
+ */
/datum/outfit
+ ///Name of the outfit (shows up in the equip admin verb)
var/name = "Naked"
- var/uniform = null
- var/suit = null
- var/toggle_helmet = TRUE
- var/back = null
- var/belt = null
- var/gloves = null
- var/shoes = null
- var/head = null
- var/mask = null
- var/neck = null
- var/ears = null
- var/glasses = null
+ /// Type path of item to go in the idcard slot
var/id = null
- var/l_pocket = null
- var/r_pocket = null
+
+ /// Type path of item to go in uniform slot
+ var/uniform = null
+
+ /// Type path of item to go in suit slot
+ var/suit = null
+
+ /**
+ * Type path of item to go in suit storage slot
+ *
+ * (make sure it's valid for that suit)
+ */
var/suit_store = null
- var/r_hand = null
+
+ /// Type path of item to go in back slot
+ var/back = null
+
+ /**
+ * list of items that should go in the backpack of the user
+ *
+ * Format of this list should be: list(path=count,otherpath=count)
+ */
+ var/list/backpack_contents = null
+
+ /// Type path of item to go in belt slot
+ var/belt = null
+
+ /// Type path of item to go in ears slot
+ var/ears = null
+
+ /// Type path of item to go in the glasses slot
+ var/glasses = null
+
+ /// Type path of item to go in gloves slot
+ var/gloves = null
+
+ /// Type path of item to go in head slot
+ var/head = null
+
+ /// Type path of item to go in mask slot
+ var/mask = null
+
+ /// Type path of item to go in neck slot
+ var/neck = null
+
+ /// Type path of item to go in shoes slot
+ var/shoes = null
+
+ /// Type path of item for left pocket slot
+ var/l_pocket = null
+
+ /// Type path of item for right pocket slot
+ var/r_pocket = null
+
+ ///Type path of item to go in the right hand
var/l_hand = null
- var/internals_slot = null //ID of slot containing a gas tank
- var/list/backpack_contents = null // In the list(path=count,otherpath=count) format
- var/box // Internals box. Will be inserted at the start of backpack_contents
- var/list/implants = null
+
+ //Type path of item to go in left hand
+ var/r_hand = null
+
+ /// Any clothing accessory item
var/accessory = null
- var/can_be_admin_equipped = TRUE // Set to FALSE if your outfit requires runtime parameters
- var/list/chameleon_extras //extra types for chameleon outfit changes, mostly guns
+ /// Internals box. Will be inserted at the start of backpack_contents
+ var/box
+ /**
+ * extra types for chameleon outfit changes, mostly guns
+ *
+ * Format of this list is (typepath, typepath, typepath)
+ *
+ * These are all added and returns in the list for get_chamelon_diguise_info proc
+ */
+ var/list/chameleon_extras
+
+ /**
+ * Any implants the mob should start implanted with
+ *
+ * Format of this list is (typepath, typepath, typepath)
+ */
+ var/list/implants = null
+
+ ///ID of the slot containing a gas tank
+ var/internals_slot = null
+
+ /// Should the toggle helmet proc be called on the helmet during equip
+ var/toggle_helmet = TRUE
+
+ /// Any undershirt. While on humans it is a string, here we use paths to stay consistent with the rest of the equips.
+ var/datum/sprite_accessory/undershirt = null
+
+/**
+ * Called at the start of the equip proc
+ *
+ * Override to change the value of the slots depending on client prefs, species and
+ * other such sources of change
+ *
+ * Extra Arguments
+ * * visualsOnly true if this is only for display (in the character setup screen)
+ *
+ * If visualsOnly is true, you can omit any work that doesn't visually appear on the character sprite
+ */
/datum/outfit/proc/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE, client/preference_source)
//to be overridden for customization depending on client prefs,species etc
return
+/**
+ * Called after the equip proc has finished
+ *
+ * All items are on the mob at this point, use this proc to toggle internals
+ * fiddle with id bindings and accesses etc
+ *
+ * Extra Arguments
+ * * visualsOnly true if this is only for display (in the character setup screen)
+ *
+ * If visualsOnly is true, you can omit any work that doesn't visually appear on the character sprite
+ */
/datum/outfit/proc/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE, client/preference_source)
//to be overridden for toggling internals, id binding, access etc
return
+/**
+ * Equips all defined types and paths to the mob passed in
+ *
+ * Extra Arguments
+ * * visualsOnly true if this is only for display (in the character setup screen)
+ *
+ * If visualsOnly is true, you can omit any work that doesn't visually appear on the character sprite
+ */
/datum/outfit/proc/equip(mob/living/carbon/human/H, visualsOnly = FALSE, client/preference_source)
pre_equip(H, visualsOnly, preference_source)
//Start with uniform,suit,backpack for additional slots
if(uniform)
- H.equip_to_slot_or_del(new uniform(H),SLOT_W_UNIFORM)
+ H.equip_to_slot_or_del(new uniform(H), SLOT_W_UNIFORM, TRUE)
if(suit)
- H.equip_to_slot_or_del(new suit(H),SLOT_WEAR_SUIT)
+ H.equip_to_slot_or_del(new suit(H), SLOT_WEAR_SUIT, TRUE)
if(back)
- H.equip_to_slot_or_del(new back(H),SLOT_BACK)
+ H.equip_to_slot_or_del(new back(H), SLOT_BACK, TRUE)
if(belt)
- H.equip_to_slot_or_del(new belt(H),SLOT_BELT)
+ H.equip_to_slot_or_del(new belt(H), SLOT_BELT, TRUE)
if(gloves)
- H.equip_to_slot_or_del(new gloves(H),SLOT_GLOVES)
+ H.equip_to_slot_or_del(new gloves(H), SLOT_GLOVES, TRUE)
if(shoes)
- H.equip_to_slot_or_del(new shoes(H),SLOT_SHOES)
+ H.equip_to_slot_or_del(new shoes(H), SLOT_SHOES, TRUE)
if(head)
- H.equip_to_slot_or_del(new head(H),SLOT_HEAD)
+ H.equip_to_slot_or_del(new head(H), SLOT_HEAD, TRUE)
if(mask)
- H.equip_to_slot_or_del(new mask(H),SLOT_WEAR_MASK)
+ H.equip_to_slot_or_del(new mask(H), SLOT_WEAR_MASK, TRUE)
if(neck)
- H.equip_to_slot_or_del(new neck(H),SLOT_NECK)
+ H.equip_to_slot_or_del(new neck(H), SLOT_NECK, TRUE)
if(ears)
- H.equip_to_slot_or_del(new ears(H),SLOT_EARS)
+ H.equip_to_slot_or_del(new ears(H), SLOT_EARS, TRUE)
if(glasses)
- H.equip_to_slot_or_del(new glasses(H),SLOT_GLASSES)
+ H.equip_to_slot_or_del(new glasses(H), SLOT_GLASSES, TRUE)
if(id)
- H.equip_to_slot_or_del(new id(H),SLOT_WEAR_ID)
+ H.equip_to_slot_or_del(new id(H), SLOT_WEAR_ID, TRUE)
if(suit_store)
- H.equip_to_slot_or_del(new suit_store(H),SLOT_S_STORE)
+ H.equip_to_slot_or_del(new suit_store(H), SLOT_S_STORE, TRUE)
+ if(undershirt)
+ H.undershirt = initial(undershirt.name)
if(accessory)
var/obj/item/clothing/under/U = H.w_uniform
@@ -81,9 +195,9 @@
if(!visualsOnly) // Items in pockets or backpack don't show up on mob's icon.
if(l_pocket)
- H.equip_to_slot_or_del(new l_pocket(H),SLOT_L_STORE)
+ H.equip_to_slot_or_del(new l_pocket(H), SLOT_L_STORE, TRUE)
if(r_pocket)
- H.equip_to_slot_or_del(new r_pocket(H),SLOT_R_STORE)
+ H.equip_to_slot_or_del(new r_pocket(H), SLOT_R_STORE, TRUE)
if(box)
if(!backpack_contents)
@@ -97,7 +211,7 @@
if(!isnum(number))//Default to 1
number = 1
for(var/i in 1 to number)
- H.equip_to_slot_or_del(new path(H),SLOT_IN_BACKPACK)
+ H.equip_to_slot_or_del(new path(H), SLOT_IN_BACKPACK, TRUE)
if(!H.head && toggle_helmet && istype(H.wear_suit, /obj/item/clothing/suit/space/hardsuit))
var/obj/item/clothing/suit/space/hardsuit/HS = H.wear_suit
@@ -112,55 +226,178 @@
H.update_action_buttons_icon()
if(implants)
for(var/implant_type in implants)
- var/obj/item/implant/I = new implant_type
+ var/obj/item/implant/I = new implant_type(H)
I.implant(H, null, TRUE)
H.update_body()
return TRUE
+/**
+ * Apply a fingerprint from the passed in human to all items in the outfit
+ *
+ * Used for forensics setup when the mob is first equipped at roundstart
+ * essentially calls add_fingerprint to every defined item on the human
+ *
+ */
/datum/outfit/proc/apply_fingerprints(mob/living/carbon/human/H)
if(!istype(H))
return
if(H.back)
- H.back.add_fingerprint(H,1) //The 1 sets a flag to ignore gloves
+ H.back.add_fingerprint(H, ignoregloves = TRUE)
for(var/obj/item/I in H.back.contents)
- I.add_fingerprint(H,1)
+ I.add_fingerprint(H, ignoregloves = TRUE)
if(H.wear_id)
- H.wear_id.add_fingerprint(H,1)
+ H.wear_id.add_fingerprint(H, ignoregloves = TRUE)
if(H.w_uniform)
- H.w_uniform.add_fingerprint(H,1)
+ H.w_uniform.add_fingerprint(H, ignoregloves = TRUE)
if(H.wear_suit)
- H.wear_suit.add_fingerprint(H,1)
+ H.wear_suit.add_fingerprint(H, ignoregloves = TRUE)
if(H.wear_mask)
- H.wear_mask.add_fingerprint(H,1)
+ H.wear_mask.add_fingerprint(H, ignoregloves = TRUE)
if(H.wear_neck)
- H.wear_neck.add_fingerprint(H,1)
+ H.wear_neck.add_fingerprint(H, ignoregloves = TRUE)
if(H.head)
- H.head.add_fingerprint(H,1)
+ H.head.add_fingerprint(H, ignoregloves = TRUE)
if(H.shoes)
- H.shoes.add_fingerprint(H,1)
+ H.shoes.add_fingerprint(H, ignoregloves = TRUE)
if(H.gloves)
- H.gloves.add_fingerprint(H,1)
+ H.gloves.add_fingerprint(H, ignoregloves = TRUE)
if(H.ears)
- H.ears.add_fingerprint(H,1)
+ H.ears.add_fingerprint(H, ignoregloves = TRUE)
if(H.glasses)
- H.glasses.add_fingerprint(H,1)
+ H.glasses.add_fingerprint(H, ignoregloves = TRUE)
if(H.belt)
- H.belt.add_fingerprint(H,1)
+ H.belt.add_fingerprint(H, ignoregloves = TRUE)
for(var/obj/item/I in H.belt.contents)
- I.add_fingerprint(H,1)
+ I.add_fingerprint(H, ignoregloves = TRUE)
if(H.s_store)
- H.s_store.add_fingerprint(H,1)
+ H.s_store.add_fingerprint(H, ignoregloves = TRUE)
if(H.l_store)
- H.l_store.add_fingerprint(H,1)
+ H.l_store.add_fingerprint(H, ignoregloves = TRUE)
if(H.r_store)
- H.r_store.add_fingerprint(H,1)
+ H.r_store.add_fingerprint(H, ignoregloves = TRUE)
for(var/obj/item/I in H.held_items)
- I.add_fingerprint(H,1)
- return 1
+ I.add_fingerprint(H, ignoregloves = TRUE)
+ return TRUE
+/// Return a list of all the types that are required to disguise as this outfit type
/datum/outfit/proc/get_chameleon_disguise_info()
var/list/types = list(uniform, suit, back, belt, gloves, shoes, head, mask, neck, ears, glasses, id, l_pocket, r_pocket, suit_store, r_hand, l_hand)
types += chameleon_extras
listclearnulls(types)
return types
+
+/// Return a json list of this outfit
+/datum/outfit/proc/get_json_data()
+ . = list()
+ .["outfit_type"] = type
+ .["name"] = name
+ .["uniform"] = uniform
+ .["suit"] = suit
+ .["toggle_helmet"] = toggle_helmet
+ .["back"] = back
+ .["belt"] = belt
+ .["gloves"] = gloves
+ .["shoes"] = shoes
+ .["head"] = head
+ .["mask"] = mask
+ .["neck"] = neck
+ .["ears"] = ears
+ .["glasses"] = glasses
+ .["id"] = id
+ .["l_pocket"] = l_pocket
+ .["r_pocket"] = r_pocket
+ .["suit_store"] = suit_store
+ .["r_hand"] = r_hand
+ .["l_hand"] = l_hand
+ .["internals_slot"] = internals_slot
+ .["backpack_contents"] = backpack_contents
+ .["box"] = box
+ .["implants"] = implants
+ .["accessory"] = accessory
+
+/// Copy most vars from another outfit to this one
+/datum/outfit/proc/copy_from(datum/outfit/target)
+ name = target.name
+ uniform = target.uniform
+ suit = target.suit
+ toggle_helmet = target.toggle_helmet
+ back = target.back
+ belt = target.belt
+ gloves = target.gloves
+ shoes = target.shoes
+ head = target.head
+ mask = target.mask
+ neck = target.neck
+ ears = target.ears
+ glasses = target.glasses
+ id = target.id
+ l_pocket = target.l_pocket
+ r_pocket = target.r_pocket
+ suit_store = target.suit_store
+ r_hand = target.r_hand
+ l_hand = target.l_hand
+ internals_slot = target.internals_slot
+ backpack_contents = target.backpack_contents
+ box = target.box
+ implants = target.implants
+ accessory = target.accessory
+
+/// Prompt the passed in mob client to download this outfit as a json blob
+/datum/outfit/proc/save_to_file(mob/admin)
+ var/stored_data = get_json_data()
+ var/json = json_encode(stored_data)
+ //Kinda annoying but as far as i can tell you need to make actual file.
+ var/f = file("data/TempOutfitUpload")
+ fdel(f)
+ WRITE_FILE(f,json)
+ admin << ftp(f,"[name].json")
+
+/// Create an outfit datum from a list of json data
+/datum/outfit/proc/load_from(list/outfit_data)
+ //This could probably use more strict validation
+ name = outfit_data["name"]
+ uniform = text2path(outfit_data["uniform"])
+ suit = text2path(outfit_data["suit"])
+ toggle_helmet = outfit_data["toggle_helmet"]
+ back = text2path(outfit_data["back"])
+ belt = text2path(outfit_data["belt"])
+ gloves = text2path(outfit_data["gloves"])
+ shoes = text2path(outfit_data["shoes"])
+ head = text2path(outfit_data["head"])
+ mask = text2path(outfit_data["mask"])
+ neck = text2path(outfit_data["neck"])
+ ears = text2path(outfit_data["ears"])
+ glasses = text2path(outfit_data["glasses"])
+ id = text2path(outfit_data["id"])
+ l_pocket = text2path(outfit_data["l_pocket"])
+ r_pocket = text2path(outfit_data["r_pocket"])
+ suit_store = text2path(outfit_data["suit_store"])
+ r_hand = text2path(outfit_data["r_hand"])
+ l_hand = text2path(outfit_data["l_hand"])
+ internals_slot = outfit_data["internals_slot"]
+ var/list/backpack = outfit_data["backpack_contents"]
+ backpack_contents = list()
+ for(var/item in backpack)
+ var/itype = text2path(item)
+ if(itype)
+ backpack_contents[itype] = backpack[item]
+ box = text2path(outfit_data["box"])
+ var/list/impl = outfit_data["implants"]
+ implants = list()
+ for(var/I in impl)
+ var/imptype = text2path(I)
+ if(imptype)
+ implants += imptype
+ accessory = text2path(outfit_data["accessory"])
+ return TRUE
+
+/datum/outfit/vv_get_dropdown()
+ . = ..()
+ VV_DROPDOWN_OPTION("", "---")
+ VV_DROPDOWN_OPTION(VV_HK_TO_OUTFIT_EDITOR, "Outfit Editor")
+
+/datum/outfit/vv_do_topic(list/href_list)
+ . = ..()
+ if(href_list[VV_HK_TO_OUTFIT_EDITOR])
+ usr.client.open_outfit_editor(src)
diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm
index a958d20276..1c49f61996 100644
--- a/code/datums/status_effects/debuffs.dm
+++ b/code/datums/status_effects/debuffs.dm
@@ -108,7 +108,7 @@
/datum/status_effect/off_balance/on_remove()
var/active_item = owner.get_active_held_item()
- if(is_type_in_typecache(active_item, GLOB.shove_disarming_types))
+ if(active_item)
owner.visible_message("[owner.name] regains their grip on \the [active_item]!", "You regain your grip on \the [active_item]", null, COMBAT_MESSAGE_RANGE)
return ..()
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index 674787fbd4..fcfddb2788 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -412,6 +412,24 @@ If not set, defaults to check_completion instead. Set it. It's used by cryo.
counter++
return counter >= 8
+/datum/objective/freedom
+ name = "freedom"
+ explanation_text = "Don't get captured by nanotrasen."
+ team_explanation_text = "Have all members of your team free of nanotrasen custody."
+
+/datum/objective/freedom/check_completion()
+ var/list/datum/mind/owners = get_owners()
+ for(var/m in owners)
+ var/datum/mind/M = m
+ if(!considered_alive(M))
+ return FALSE
+ 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/red/brig)) // Fails if they are in the shuttle brig
+ return FALSE
+ return TRUE
+
/datum/objective/escape
name = "escape"
explanation_text = "Escape on the shuttle or an escape pod alive and without being in custody."
diff --git a/code/game/machinery/computer/cloning.dm b/code/game/machinery/computer/cloning.dm
index adadb40bc9..d034ba382e 100644
--- a/code/game/machinery/computer/cloning.dm
+++ b/code/game/machinery/computer/cloning.dm
@@ -13,7 +13,7 @@
var/temp = "Inactive"
var/scantemp_ckey
var/scantemp_name
- var/scantemp = "Ready to Scan"
+ var/scantemp = "Inactive"
var/menu = 1 //Which menu screen to display
var/datum/data/record/active_record = null
var/obj/item/disk/data/diskette = null //Mostly so the geneticist can steal everything.
@@ -132,7 +132,6 @@
src.diskette = W
to_chat(user, "You insert [W].")
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
- src.updateUsrDialog()
else if(W.tool_behaviour == TOOL_MULTITOOL)
if(istype(W.buffer, clonepod_type))
if(get_area(W.buffer) != get_area(src))
@@ -151,311 +150,233 @@
else
return ..()
-/obj/machinery/computer/cloning/ui_interact(mob/user)
+/obj/machinery/computer/cloning/AltClick(mob/user)
. = ..()
+ EjectDisk(user)
- updatemodules(TRUE)
+/obj/machinery/computer/cloning/proc/EjectDisk(mob/user)
+ if(diskette)
+ scantemp = "Disk Ejected"
+ diskette.forceMove(drop_location())
+ usr.put_in_active_hand(diskette)
+ diskette = null
+ playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
- var/dat = ""
- dat += "Refresh"
+/obj/machinery/computer/cloning/proc/Save(mob/user, target)
+ var/datum/data/record/GRAB = null
+ for(var/datum/data/record/record in records)
+ if(record.fields["id"] == target)
+ GRAB = record
+ break
+ else
+ continue
+ if(!GRAB || !GRAB.fields)
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
+ scantemp = "Failed saving to disk: Data Corruption"
+ return FALSE
+ if(!diskette || diskette.read_only)
+ scantemp = !diskette ? "Failed saving to disk: No disk." : "Failed saving to disk: Disk refuses override attempt."
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
+ return
+ diskette.fields = GRAB.fields.Copy()
+ diskette.name = "data disk - '[src.diskette.fields["name"]]'"
+ scantemp = "Saved to disk successfully."
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
+/obj/machinery/computer/cloning/proc/DeleteRecord(mob/user, target)
+ var/datum/data/record/GRAB = null
+ for(var/datum/data/record/record in records)
+ if(record.fields["id"] == target)
+ GRAB = record
+ break
+ else
+ continue
+ if(!GRAB)
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
+ scantemp = "Cannot delete: Data Corrupted."
+ return FALSE
+ var/obj/item/card/id/C = usr.get_idcard(hand_first = TRUE)
+ if(istype(C) || istype(C, /obj/item/pda) || istype(C, /obj/item/modular_computer/tablet))
+ if(check_access(C))
+ scantemp = "[GRAB.fields["name"]] => Record deleted."
+ records.Remove(GRAB)
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
+ var/obj/item/circuitboard/computer/cloning/board = circuit
+ board.records = records
+ return TRUE
+ scantemp = "Cannot delete: Access Denied."
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
+
+/obj/machinery/computer/cloning/proc/Load(mob/user)
+ if(!diskette || !istype(diskette.fields) || !diskette.fields["name"] || !diskette.fields)
+ scantemp = "Failed loading: Load error."
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
+ return
+ for(var/datum/data/record/R in records)
+ if(R.fields["key"] == diskette.fields["key"])
+ scantemp = "Failed loading: Data already exists!"
+ return FALSE
+ var/datum/data/record/R = new(src)
+ for(var/key in diskette.fields)
+ R.fields[key] = diskette.fields[key]
+ records += R
+ scantemp = "Loaded into internal storage successfully."
+ var/obj/item/circuitboard/computer/cloning/board = circuit
+ board.records = records
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
+
+/obj/machinery/computer/cloning/proc/Clone(mob/user, target)
+ var/datum/data/record/C = find_record("id", target, records)
+ //Look for that player! They better be dead!
+ if(C)
+ var/obj/machinery/clonepod/pod = GetAvailablePod()
+ //Can't clone without someone to clone. Or a pod. Or if the pod is busy. Or full of gibs.
+ if(!LAZYLEN(pods))
+ temp = "Error: No Clonepods detected."
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
+ else if(!pod)
+ temp = "Error: No Clonepods available."
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
+ else if(!CONFIG_GET(flag/revival_cloning))
+ temp = "Error: Unable to initiate cloning cycle."
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
+ else if(pod.occupant)
+ temp = "Warning: Cloning cycle already in progress."
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
+ else if(pod.growclone(C.fields["ckey"], C.fields["name"], C.fields["UI"], C.fields["SE"], C.fields["mind"], C.fields["blood_type"], C.fields["mrace"], C.fields["features"], C.fields["factions"], C.fields["quirks"], C.fields["bank_account"], C.fields["traumas"]))
+ temp = "Notice: [C.fields["name"]] => Cloning cycle in progress..."
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
+ records.Remove(C)
+ else
+ temp = "Error: [C.fields["name"]] => Initialisation failure."
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
+
+ else
+ temp = "Failed to clone: Data corrupted."
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
+ . = TRUE
+
+/obj/machinery/computer/cloning/proc/Toggle_lock(mob/user)
+ if(!scanner.is_operational())
+ return
+ if(!scanner.locked && !scanner.occupant) //I figured out that if you're fast enough, you can lock an open pod
+ return
+ scanner.locked = !scanner.locked
+ playsound(src, scanner.locked ? 'sound/machines/terminal_prompt_deny.ogg' : 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
+ . = TRUE
+
+/obj/machinery/computer/cloning/proc/Scan(mob/user)
+ if(!scanner.is_operational() || !scanner.occupant)
+ return
+ scantemp = "[scantemp_name] => Scanning..."
+ loading = TRUE
+ playsound(src, 'sound/machines/terminal_prompt.ogg', 50, 0)
+ say("Initiating scan...")
+ var/prev_locked = scanner.locked
+ scanner.locked = TRUE
+ addtimer(CALLBACK(src, .proc/finish_scan, scanner.occupant, prev_locked), 2 SECONDS)
+ . = TRUE
+
+/obj/machinery/computer/cloning/proc/Toggle_autoprocess(mob/user)
+ autoprocess = !autoprocess
+ if(autoprocess)
+ START_PROCESSING(SSmachines, src)
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
+ else
+ STOP_PROCESSING(SSmachines, src)
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
+ . = TRUE
+
+/obj/machinery/computer/cloning/ui_data(mob/user)
+ var/list/data = list()
+ data["useRecords"] = use_records
+ var/list/records_to_send = list()
if(use_records)
if(scanner && HasEfficientPod() && scanner.scan_level >= AUTOCLONING_MINIMAL_LEVEL)
- if(!autoprocess)
- dat += "Autoclone"
- else
- dat += "Stop autoclone"
- else
- dat += "Autoclone"
- dat += "
Cloning Pod Status
"
- dat += "
[temp]
"
-
- switch(src.menu)
- if(1)
- // Modules
- if (isnull(src.scanner) || !LAZYLEN(pods))
- dat += "
Modules
"
- //dat += "Reload Modules"
- if (isnull(src.scanner))
- dat += "ERROR: No Scanner detected! "
- if (!LAZYLEN(pods))
- dat += "ERROR: No Pod detected "
-
- // Scanner
- if (!isnull(src.scanner))
- var/mob/living/scanner_occupant = get_mob_or_brainmob(scanner.occupant)
-
- dat += "
"
- if (src.records.len && src.records.len > 0)
- dat += "View Records ([src.records.len]) "
- else
- dat += "View Records (0) "
- if (src.diskette)
- dat += "Eject Disk "
-
-
-
- if(2)
- dat += "