diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index d89743d2d2..d595abd3ec 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -427,6 +427,7 @@ GLOBAL_LIST_INIT(pda_reskins, list(PDA_SKIN_CLASSIC = 'icons/obj/pda.dmi', PDA_S
//Dummy mob reserve slots
#define DUMMY_HUMAN_SLOT_PREFERENCES "dummy_preference_preview"
+#define DUMMY_HUMAN_SLOT_HOLOFORM "dummy_holoform_generation"
#define DUMMY_HUMAN_SLOT_ADMIN "admintools"
#define DUMMY_HUMAN_SLOT_MANIFEST "dummy_manifest_generation"
@@ -529,4 +530,10 @@ GLOBAL_LIST_INIT(pda_reskins, list(PDA_SKIN_CLASSIC = 'icons/obj/pda.dmi', PDA_S
#define CRYOMOBS 'icons/obj/cryo_mobs.dmi'
+#define CUSTOM_HOLOFORM_DELAY 10 SECONDS //prevents spamming to make lag. it's pretty expensive to do this.
+
+#define HOLOFORM_FILTER_AI "FILTER_AI"
+#define HOLOFORM_FILTER_PAI "FILTER_PAI"
+#define HOLOFORM_FILTER_STATIC "FILTER_STATIC"
+
#define CANT_REENTER_ROUND -1
diff --git a/code/__HELPERS/custom_holoforms.dm b/code/__HELPERS/custom_holoforms.dm
new file mode 100644
index 0000000000..0e97314f1f
--- /dev/null
+++ b/code/__HELPERS/custom_holoforms.dm
@@ -0,0 +1,62 @@
+// Generates a holoform appearance
+// Equipment list is slot = path.
+/proc/generate_custom_holoform_from_prefs(datum/preferences/prefs, list/equipment_by_slot, list/inhand_equipment, copy_job = FALSE, apply_loadout = FALSE)
+ ASSERT(prefs)
+ var/mob/living/carbon/human/dummy/mannequin = generate_or_wait_for_human_dummy(DUMMY_HUMAN_SLOT_HOLOFORM)
+ prefs.copy_to(mannequin)
+ if(apply_loadout && prefs.parent)
+ SSjob.equip_loadout(prefs.parent.mob, mannequin, bypass_prereqs = TRUE)
+ if(copy_job)
+ var/datum/job/highest = prefs.get_highest_job()
+ if(highest && !istype(highest, /datum/job/ai) && !istype(highest, /datum/job/cyborg))
+ highest.equip(mannequin, TRUE, preference_source = prefs.parent)
+
+ if(length(equipment_by_slot))
+ for(var/slot in equipment_by_slot)
+ var/obj/item/I = new equipment_by_slot[slot]
+ mannequin.equip_to_slot_if_possible(I, slot, TRUE, TRUE, TRUE, TRUE)
+ if(length(inhand_equipment))
+ for(var/path in inhand_equipment)
+ var/obj/item/I = new path
+ mannequin.equip_to_slot_if_possible(I, SLOT_HANDS, TRUE, TRUE, TRUE, TRUE)
+
+
+ var/icon/combined = new
+ for(var/d in GLOB.cardinals)
+ mannequin.setDir(d)
+ COMPILE_OVERLAYS(mannequin)
+ CHECK_TICK
+ var/icon/capture = getFlatIcon(mannequin)
+ CHECK_TICK
+ combined.Insert(capture, dir = d)
+ CHECK_TICK
+
+ unset_busy_human_dummy(DUMMY_HUMAN_SLOT_HOLOFORM)
+ return combined
+
+/proc/process_holoform_icon_filter(icon/I, filter_type, clone = TRUE)
+ if(clone)
+ I = icon(I) //Clone
+ switch(filter_type)
+ if(HOLOFORM_FILTER_AI)
+ I = getHologramIcon(I)
+ if(HOLOFORM_FILTER_STATIC)
+ I = getStaticIcon(I)
+ if(HOLOFORM_FILTER_PAI)
+ I = getPAIHologramIcon(I)
+ return I
+
+//Errors go to user.
+/proc/generate_custom_holoform_from_prefs_safe(datum/preferences/prefs, mob/user)
+ if(user)
+ if(user.client.prefs.last_custom_holoform > world.time - CUSTOM_HOLOFORM_DELAY)
+ to_chat(user, "You are attempting to set your custom holoform too fast!")
+ return
+ return generate_custom_holoform_from_prefs(prefs, null, null, TRUE, TRUE)
+
+//Prompts this client for custom holoform parameters.
+/proc/user_interface_custom_holoform(client/C)
+ var/datum/preferences/target_prefs = C.prefs
+ ASSERT(target_prefs)
+ //In the future, maybe add custom path allowances a la admin create outfit but for now..
+ return generate_custom_holoform_from_prefs_safe(target_prefs, C.mob)
diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm
index 5fb64004c0..1af3143d9d 100644
--- a/code/__HELPERS/icons.dm
+++ b/code/__HELPERS/icons.dm
@@ -939,7 +939,7 @@ world
I.pixel_y++
add_overlay(I)//And finally add the overlay.
-/proc/getHologramIcon(icon/A, safety=1)//If safety is on, a new icon is not created.
+/proc/getHologramIcon(icon/A, safety = TRUE)//If safety is on, a new icon is not created.
var/icon/flat_icon = safety ? A : new(A)//Has to be a new icon to not constantly change the same icon.
flat_icon.ColorTone(rgb(125,180,225))//Let's make it bluish.
flat_icon.ChangeOpacity(0.5)//Make it half transparent.
@@ -947,6 +947,14 @@ world
flat_icon.AddAlphaMask(alpha_mask)//Finally, let's mix in a distortion effect.
return flat_icon
+/proc/getPAIHologramIcon(icon/A, safety = TRUE)
+ var/icon/flat_icon = safety? A : new(A)
+ flat_icon.SetIntensity(0.75, 1, 0.75)
+ flat_icon.ChangeOpacity(0.7)
+ var/icon/alpha_mask = new('icons/effects/effects.dmi', "scanlineslow")//Scanline effect.
+ flat_icon.AddAlphaMask(alpha_mask)//Finally, let's mix in a distortion effect.
+ return flat_icon
+
//What the mob looks like as animated static
//By vg's ComicIronic
/proc/getStaticIcon(icon/A, safety = TRUE)
diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm
index 61b5788ef8..b95ef43d37 100644
--- a/code/controllers/configuration/entries/game_options.dm
+++ b/code/controllers/configuration/entries/game_options.dm
@@ -386,6 +386,8 @@
config_entry_value = 72000
min_val = 0
+/datum/config_entry/flag/pai_custom_holoforms
+
/datum/config_entry/number/marauder_delay_non_reebe
config_entry_value = 1800
min_val = 0
diff --git a/code/controllers/subsystem/job.dm b/code/controllers/subsystem/job.dm
index af7ae825f4..7597a9229a 100644
--- a/code/controllers/subsystem/job.dm
+++ b/code/controllers/subsystem/job.dm
@@ -658,7 +658,7 @@ SUBSYSTEM_DEF(job)
message_admins(msg)
CRASH(msg)
-/datum/controller/subsystem/job/proc/equip_loadout(mob/dead/new_player/N, mob/living/M, equipbackpackstuff)
+/datum/controller/subsystem/job/proc/equip_loadout(mob/dead/new_player/N, mob/living/M, equipbackpackstuff, bypass_prereqs = FALSE)
var/mob/the_mob = N
if(!the_mob)
the_mob = M // cause this doesn't get assigned if player is a latejoiner
@@ -671,7 +671,7 @@ SUBSYSTEM_DEF(job)
if(!G)
continue
var/permitted = TRUE
- if(G.restricted_roles && G.restricted_roles.len && !(M.mind.assigned_role in G.restricted_roles))
+ if(!bypass_prereqs && G.restricted_roles && G.restricted_roles.len && !(M.mind.assigned_role in G.restricted_roles))
permitted = FALSE
if(G.donoritem && !G.donator_ckey_check(the_mob.client.ckey))
permitted = FALSE
diff --git a/code/datums/emotes.dm b/code/datums/emotes.dm
index f0fca5db5a..599c492d4c 100644
--- a/code/datums/emotes.dm
+++ b/code/datums/emotes.dm
@@ -136,9 +136,10 @@
/datum/emote/sound
var/sound //Sound to play when emote is called
var/vary = FALSE //used for the honk borg emote
+ var/volume = 50
mob_type_allowed_typecache = list(/mob/living/brain, /mob/living/silicon)
/datum/emote/sound/run_emote(mob/user, params)
. = ..()
if(.)
- playsound(user.loc, sound, 50, vary)
+ playsound(user.loc, sound, volume, vary)
diff --git a/code/game/machinery/hologram.dm b/code/game/machinery/hologram.dm
index 1e7d720ee9..b1783ba940 100644
--- a/code/game/machinery/hologram.dm
+++ b/code/game/machinery/hologram.dm
@@ -500,7 +500,7 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/
else
return FALSE
-/obj/machinery/holopad/proc/move_hologram(mob/living/user, turf/new_turf)
+/obj/machinery/holopad/proc/move_hologram(mob/living/user, turf/new_turf, direction)
if(LAZYLEN(masters) && masters[user])
var/obj/effect/overlay/holo_pad_hologram/holo = masters[user]
var/transfered = FALSE
@@ -512,6 +512,8 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/
transfered = TRUE
//All is good.
holo.forceMove(new_turf)
+ if(direction)
+ holo.setDir(direction)
if(!transfered)
update_holoray(user,new_turf)
return TRUE
diff --git a/code/game/objects/buckling.dm b/code/game/objects/buckling.dm
index 9c3df5395f..416644cada 100644
--- a/code/game/objects/buckling.dm
+++ b/code/game/objects/buckling.dm
@@ -107,7 +107,7 @@
//Wrapper procs that handle sanity and user feedback
/atom/movable/proc/user_buckle_mob(mob/living/M, mob/user, check_loc = TRUE)
- if(!in_range(user, src) || !isturf(user.loc) || user.incapacitated() || M.anchored)
+ if(!in_range(user, src) || !isturf(user.loc) || user.incapacitated() || M.anchored || !user.can_buckle_others(M, src))
return FALSE
add_fingerprint(user)
diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm
index 7dc2f1d60b..3ee4a14a91 100644
--- a/code/game/objects/items/devices/paicard.dm
+++ b/code/game/objects/items/devices/paicard.dm
@@ -8,7 +8,8 @@
w_class = WEIGHT_CLASS_SMALL
slot_flags = ITEM_SLOT_BELT
var/mob/living/silicon/pai/pai
- resistance_flags = FIRE_PROOF | ACID_PROOF | INDESTRUCTIBLE
+ resistance_flags = FIRE_PROOF | ACID_PROOF
+ max_integrity = 200
/obj/item/paicard/suicide_act(mob/living/user)
user.visible_message("[user] is staring sadly at [src]! [user.p_they()] can't keep living without real human intimacy!")
@@ -45,6 +46,8 @@
dat += "Radio Uplink
"
dat += "Transmit: [(pai.radio.wires.is_cut(WIRE_TX)) ? "Disabled" : "Enabled"]
"
dat += "Receive: [(pai.radio.wires.is_cut(WIRE_RX)) ? "Disabled" : "Enabled"]
"
+ if(pai.radio_short)
+ dat += "Reset radio short: \[RESET\]
"
else
dat += "Radio Uplink
"
dat += "Radio firmware not loaded. Please install a pAI personality to load firmware.
"
@@ -82,7 +85,6 @@
pai.master = M.real_name
pai.master_dna = M.dna.unique_enzymes
to_chat(pai, "You have been bound to a new master.")
- pai.emittersemicd = FALSE
if(href_list["wipe"])
var/confirm = input("Are you CERTAIN you wish to delete the current personality? This action cannot be undone.", "Personality Wipe") in list("Yes", "No")
if(confirm == "Yes")
@@ -96,6 +98,8 @@
var/wire = text2num(href_list["wires"])
if(pai.radio)
pai.radio.wires.cut(wire)
+ if(href_list["reset_radio_short"])
+ pai.unshort_radio()
if(href_list["setlaws"])
var/newlaws = copytext(sanitize(input("Enter any additional directives you would like your pAI personality to follow. Note that these directives will not override the personality's allegiance to its imprinted master. Conflicting directives will be ignored.", "pAI Directive Configuration", pai.laws.supplied[1]) as message),1,MAX_MESSAGE_LEN)
if(newlaws && pai)
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 96a32d50e6..7b7cf3413e 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -21,6 +21,10 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/last_ip
var/last_id
+ var/icon/custom_holoform_icon
+ var/list/cached_holoform_icons
+ var/last_custom_holoform = 0
+
//Cooldowns for saving/loading. These are four are all separate due to loading code calling these one after another
var/saveprefcooldown
var/loadprefcooldown
@@ -2422,3 +2426,11 @@ GLOBAL_LIST_EMPTY(preferences_datums)
return
else
custom_names[name_id] = sanitized_name
+
+/datum/preferences/proc/get_filtered_holoform(filter_type)
+ if(!custom_holoform_icon)
+ return
+ LAZYINITLIST(cached_holoform_icons)
+ if(!cached_holoform_icons[filter_type])
+ cached_holoform_icons[filter_type] = process_holoform_icon_filter(custom_holoform_icon, filter_type)
+ return cached_holoform_icons[filter_type]
diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm
index d9e967e54d..6fd1584e75 100644
--- a/code/modules/clothing/clothing.dm
+++ b/code/modules/clothing/clothing.dm
@@ -57,7 +57,6 @@
//No idea what this is but eh -tori
var/force_alternate_icon = FALSE
-
/obj/item/clothing/Initialize()
. = ..()
if(CHECK_BITFIELD(clothing_flags, VOICEBOX_TOGGLABLE))
diff --git a/code/modules/mob/dead/new_player/preferences_setup.dm b/code/modules/mob/dead/new_player/preferences_setup.dm
index 7aae26526e..5b664dc01a 100644
--- a/code/modules/mob/dead/new_player/preferences_setup.dm
+++ b/code/modules/mob/dead/new_player/preferences_setup.dm
@@ -27,12 +27,7 @@
/datum/preferences/proc/update_preview_icon(equip_job = TRUE)
// Determine what job is marked as 'High' priority, and dress them up as such.
- var/datum/job/previewJob
- var/highest_pref = 0
- for(var/job in job_preferences)
- if(job_preferences["[job]"] > highest_pref)
- previewJob = SSjob.GetJob(job)
- highest_pref = job_preferences["[job]"]
+ var/datum/job/previewJob = get_highest_job()
if(previewJob)
// Silicons only need a very basic preview since there is no customization for them.
@@ -57,3 +52,11 @@
parent.show_character_previews(new /mutable_appearance(mannequin))
unset_busy_human_dummy(DUMMY_HUMAN_SLOT_PREFERENCES)
+/datum/preferences/proc/get_highest_job()
+ var/highest_pref = 0
+ var/datum/job/highest_job
+ for(var/job in job_preferences)
+ if(job_preferences["[job]"] > highest_pref)
+ highest_job = SSjob.GetJob(job)
+ highest_pref = job_preferences["[job]"]
+ return highest_job
diff --git a/code/modules/mob/living/carbon/alien/larva/larva.dm b/code/modules/mob/living/carbon/alien/larva/larva.dm
index e1e079cbae..8e1e5aab8d 100644
--- a/code/modules/mob/living/carbon/alien/larva/larva.dm
+++ b/code/modules/mob/living/carbon/alien/larva/larva.dm
@@ -14,10 +14,9 @@
var/max_grown = 100
var/time_of_birth
- rotate_on_lying = 0
+ rotate_on_lying = FALSE
bodyparts = list(/obj/item/bodypart/chest/larva, /obj/item/bodypart/head/larva)
-
//This is fine right now, if we're adding organ specific damage this needs to be updated
/mob/living/carbon/alien/larva/Initialize()
diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm
index a1184613fe..9629dc3484 100644
--- a/code/modules/mob/living/carbon/carbon_defines.dm
+++ b/code/modules/mob/living/carbon/carbon_defines.dm
@@ -40,7 +40,7 @@
var/gib_type = /obj/effect/decal/cleanable/blood/gibs
- var/rotate_on_lying = 1
+ rotate_on_lying = TRUE
var/tinttotal = 0 // Total level of visualy impairing items
diff --git a/code/modules/mob/living/carbon/emote.dm b/code/modules/mob/living/carbon/emote.dm
index 345279df29..559c2ebcdd 100644
--- a/code/modules/mob/living/carbon/emote.dm
+++ b/code/modules/mob/living/carbon/emote.dm
@@ -22,6 +22,7 @@
muzzle_ignore = TRUE
restraint_check = TRUE
emote_type = EMOTE_AUDIBLE
+ mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/carbon/clap/run_emote(mob/living/user, params)
. = ..()
diff --git a/code/modules/mob/living/carbon/update_icons.dm b/code/modules/mob/living/carbon/update_icons.dm
index 66be8058f1..0155f2575d 100644
--- a/code/modules/mob/living/carbon/update_icons.dm
+++ b/code/modules/mob/living/carbon/update_icons.dm
@@ -1,31 +1,3 @@
-//IMPORTANT: Multiple animate() calls do not stack well, so try to do them all at once if you can.
-/mob/living/carbon/update_transform()
- var/matrix/ntransform = matrix(transform) //aka transform.Copy()
- var/final_pixel_y = pixel_y
- var/final_dir = dir
- var/changed = 0
- if(lying != lying_prev && rotate_on_lying)
- changed++
- ntransform.TurnTo(lying_prev,lying)
- if(lying == 0) //Lying to standing
- final_pixel_y = get_standard_pixel_y_offset()
- else //if(lying != 0)
- if(lying_prev == 0) //Standing to lying
- pixel_y = get_standard_pixel_y_offset()
- final_pixel_y = get_standard_pixel_y_offset(lying)
- if(dir & (EAST|WEST)) //Facing east or west
- final_dir = pick(NORTH, SOUTH) //So you fall on your side rather than your face or ass
-
- if(resize != RESIZE_DEFAULT_SIZE)
- changed++
- ntransform.Scale(resize)
- resize = RESIZE_DEFAULT_SIZE
-
- if(changed)
- animate(src, transform = ntransform, time = 2, pixel_y = final_pixel_y, dir = final_dir, easing = EASE_IN|EASE_OUT)
- setMovetype(movement_type & ~FLOATING) // If we were without gravity, the bouncing animation got stopped, so we make sure we restart it in next life().
-
-
/mob/living/carbon
var/list/overlays_standing[TOTAL_LAYERS]
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index 4d2a36907d..783e044492 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -111,4 +111,6 @@
var/list/diseases = list() // list of all diseases in a mob
var/list/disease_resistances = list()
- var/drag_slowdown = TRUE //Whether the mob is slowed down when dragging another prone mob
\ No newline at end of file
+ var/drag_slowdown = TRUE //Whether the mob is slowed down when dragging another prone mob
+
+ var/rotate_on_lying = FALSE
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index ac67d16d35..1694d04b82 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -672,18 +672,23 @@
"green face" = 'icons/mob/ai.dmi',
"xeno queen" = 'icons/mob/alien.dmi',
"horror" = 'icons/mob/ai.dmi',
- "creature" = 'icons/mob/ai.dmi'
+ "creature" = 'icons/mob/ai.dmi',
+ "custom"
)
input = input("Please select a hologram:") as null|anything in icon_list
if(input)
qdel(holo_icon)
switch(input)
- if("xeno queen")
+ if("custom")
+ if(client?.prefs?.custom_holoform_icon)
+ holo_icon = client.prefs.get_filtered_holoform(HOLOFORM_FILTER_AI)
+ else
+ holo_icon = getHologramIcon(icon('icons/mob/ai.dmi', "female"))
+ else if("xeno queen")
holo_icon = getHologramIcon(icon(icon_list[input],"alienq"))
else
holo_icon = getHologramIcon(icon(icon_list[input], input))
- return
/mob/living/silicon/ai/proc/corereturn()
set category = "Malfunction"
diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm
index 7477ba7713..38afaf8313 100644
--- a/code/modules/mob/living/silicon/ai/freelook/eye.dm
+++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm
@@ -68,7 +68,7 @@
// Use this when setting the aiEye's location.
// It will also stream the chunk that the new loc is in.
-/mob/camera/aiEye/proc/setLoc(T, force_update = FALSE)
+/mob/camera/aiEye/proc/setLoc(T, force_update = FALSE, dir)
if(ai)
if(!isturf(ai.loc))
return
@@ -88,7 +88,7 @@
//Holopad
if(istype(ai.current, /obj/machinery/holopad))
var/obj/machinery/holopad/H = ai.current
- H.move_hologram(ai, T)
+ H.move_hologram(ai, T, dir)
if(ai.camera_light_on)
ai.light_cameras()
if(ai.master_multicam)
@@ -139,7 +139,7 @@
for(var/i = 0; i < max(user.sprint, initial); i += 20)
var/turf/step = get_turf(get_step(user.eyeobj, direct))
if(step)
- user.eyeobj.setLoc(step)
+ user.eyeobj.setLoc(step, null, direct)
user.cooldown = world.timeofday + 5
if(user.acceleration)
diff --git a/code/modules/mob/living/silicon/custom_holoform.dm b/code/modules/mob/living/silicon/custom_holoform.dm
new file mode 100644
index 0000000000..8ce6e9a29a
--- /dev/null
+++ b/code/modules/mob/living/silicon/custom_holoform.dm
@@ -0,0 +1,28 @@
+/mob/living/silicon/verb/clear_custom_holoform()
+ set name = "Clear Custom Holoform"
+ set desc = "Clear your current custom holoform"
+ set category = "OOC"
+
+ if(!client.prefs)
+ to_chat(src, "No preferences datum on your client, contact an admin/coder!")
+ return
+ client.prefs.custom_holoform_icon = null
+ client.prefs.cached_holoform_icons = null
+ to_chat(src, "Holoform removed.")
+
+/mob/living/silicon/verb/set_custom_holoform()
+ set name = "Set Custom Holoform"
+ set desc = "Set your custom holoform using your current preferences slot and a specified set of gear."
+ set category = "OOC"
+
+ if(!client.prefs)
+ to_chat(src, "No preferences datum on your client, contact an admin/coder!")
+ return
+ if(client.prefs.last_custom_holoform > world.time - CUSTOM_HOLOFORM_DELAY)
+ to_chat(src, "You are attempting to change custom holoforms too fast!")
+ var/icon/new_holoform = user_interface_custom_holoform(client)
+ if(new_holoform)
+ client.prefs.custom_holoform_icon = new_holoform
+ client.prefs.cached_holoform_icons = null
+ client.prefs.last_custom_holoform = world.time
+ to_chat(src, "Holoform set.")
diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm
index 7ec63cf415..7055838039 100644
--- a/code/modules/mob/living/silicon/pai/pai.dm
+++ b/code/modules/mob/living/silicon/pai/pai.dm
@@ -58,10 +58,12 @@
var/canholo = TRUE
var/obj/item/card/id/access_card = null
var/chassis = "repairbot"
- var/list/possible_chassis = list("cat" = TRUE, "mouse" = TRUE, "monkey" = TRUE, "corgi" = FALSE,
- "fox" = FALSE, "repairbot" = TRUE, "rabbit" = TRUE, "borgi" = FALSE ,
- "parrot" = FALSE, "bear" = FALSE , "mushroom" = FALSE, "crow" = FALSE ,
- "fairy" = FALSE , "spiderbot" = FALSE) //assoc value is whether it can be picked up.
+ var/dynamic_chassis
+ var/dynamic_chassis_sit = FALSE //whether we're sitting instead of resting spritewise
+ var/dynamic_chassis_bellyup = FALSE //whether we're lying down bellyup
+ var/list/possible_chassis //initialized in initialize.
+ var/list/dynamic_chassis_icons //ditto.
+ var/list/chassis_pixel_offsets_x //stupid dogborgs
var/static/item_head_icon = 'icons/mob/pai_item_head.dmi'
var/static/item_lh_icon = 'icons/mob/pai_item_lh.dmi'
var/static/item_rh_icon = 'icons/mob/pai_item_rh.dmi'
@@ -69,22 +71,20 @@
var/emitterhealth = 20
var/emittermaxhealth = 20
var/emitterregen = 0.25
+ var/emitter_next_use = 0
+ var/emitter_emp_cd = 300
var/emittercd = 50
var/emitteroverloadcd = 100
- var/emittersemicd = FALSE
- var/overload_ventcrawl = 0
- var/overload_bulletblock = 0 //Why is this a good idea?
- var/overload_maxhealth = 0
+ var/radio_short = FALSE
+ var/radio_short_cooldown = 5 MINUTES
+ var/radio_short_timerid
+
canmove = FALSE
var/silent = FALSE
var/brightness_power = 5
-/mob/living/silicon/pai/can_unbuckle()
- return FALSE
-
-/mob/living/silicon/pai/can_buckle()
- return FALSE
+ var/icon/custom_holoform_icon
/mob/living/silicon/pai/Destroy()
if (loc != card)
@@ -118,6 +118,13 @@
pda.owner = text("[]", src)
pda.name = pda.owner + " (" + pda.ownjob + ")"
+ possible_chassis = typelist(NAMEOF(src, possible_chassis), list("cat" = TRUE, "mouse" = TRUE, "monkey" = TRUE, "corgi" = FALSE,
+ "fox" = FALSE, "repairbot" = TRUE, "rabbit" = TRUE, "borgi" = FALSE ,
+ "parrot" = FALSE, "bear" = FALSE , "mushroom" = FALSE, "crow" = FALSE ,
+ "fairy" = FALSE , "spiderbot" = FALSE)) //assoc value is whether it can be picked up.
+ dynamic_chassis_icons = typelist(NAMEOF(src, dynamic_chassis_icons), initialize_dynamic_chassis_icons())
+ chassis_pixel_offsets_x = typelist(NAMEOF(src, chassis_pixel_offsets_x), default_chassis_pixel_offsets_x())
+
. = ..()
var/datum/action/innate/pai/software/SW = new
@@ -133,8 +140,7 @@
AR.Grant(src)
AL.Grant(src)
ALM.Grant(src)
- emittersemicd = TRUE
- addtimer(CALLBACK(src, .proc/emittercool), 600)
+ emitter_next_use = world.time + 10 SECONDS
/mob/living/silicon/pai/Life()
if(hacking)
@@ -228,7 +234,7 @@
/datum/action/innate/pai/shell/Trigger()
..()
if(P.holoform)
- P.fold_in(0)
+ P.fold_in(FALSE)
else
P.fold_out()
@@ -292,3 +298,132 @@
/mob/living/silicon/pai/process()
emitterhealth = CLAMP((emitterhealth + emitterregen), -50, emittermaxhealth)
+
+/mob/living/silicon/pai/proc/short_radio()
+ if(radio_short_timerid)
+ deltimer(radio_short_timerid)
+ radio_short = TRUE
+ to_chat(src, "Your radio shorts out!")
+ radio_short_timerid = addtimer(CALLBACK(src, .proc/unshort_radio), radio_short_cooldown, flags = TIMER_STOPPABLE)
+
+/mob/living/silicon/pai/proc/unshort_radio()
+ radio_short = FALSE
+ to_chat(src, "You feel your radio is operational once more.")
+ if(radio_short_timerid)
+ deltimer(radio_short_timerid)
+
+/mob/living/silicon/pai/proc/initialize_dynamic_chassis_icons()
+ . = list()
+ var/icon/curr //for inserts
+
+ //This is a horrible system and I wish I was not as lazy and did something smarter, like just generating a new icon in memory which is probably more efficient.
+
+ //Basic /tg/ cyborgs
+ .["Cyborg - Engineering (default)"] = process_holoform_icon_filter(icon('icons/mob/robots.dmi', "engineer"), HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Engineering (loaderborg)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "loaderborg"), HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Engineering (handyeng)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "handyeng"), HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Engineering (sleekeng)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "sleekeng"), HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Engineering (marinaeng)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "marinaeng"), HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Medical (default)"] = process_holoform_icon_filter(icon('icons/mob/robots.dmi', "medical"), HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Medical (marinamed)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "marinamed"), HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Medical (eyebotmed)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "eyebotmed"), HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Security (default)"] = process_holoform_icon_filter(icon('icons/mob/robots.dmi', "sec"), HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Security (sleeksec)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "sleeksec"), HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Security (marinasec)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "marinasec"), HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Clown (default)"] = process_holoform_icon_filter(icon('icons/mob/robots.dmi', "clown"), HOLOFORM_FILTER_PAI, FALSE)
+
+ //Citadel dogborgs
+ //Engi
+ curr = icon('modular_citadel/icons/mob/widerobot.dmi', "valeeng")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeeng-rest"), "rest")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeeng-sit"), "sit")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeeng-bellyup"), "bellyup")
+ process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Engineering (dog - valeeng)"] = curr
+ curr = icon('modular_citadel/icons/mob/widerobot.dmi', "pupdozer")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "pupdozer-rest"), "rest")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "pupdozer-sit"), "sit")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "pupdozer-bellyup"), "bellyup")
+ process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Engineering (dog - pupdozer)"] = curr
+ //Med
+ curr = icon('modular_citadel/icons/mob/widerobot.dmi', "medihound")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "medihound-rest"), "rest")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "medihound-sit"), "sit")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "medihound-bellyup"), "bellyup")
+ process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Medical (dog - medihound)"] = curr
+ curr = icon('modular_citadel/icons/mob/widerobot.dmi', "medihounddark")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "medihounddark-rest"), "rest")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "medihounddark-sit"), "sit")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "medihounddark-bellyup"), "bellyup")
+ process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Medical (dog - medihounddark)"] = curr
+ curr = icon('modular_citadel/icons/mob/widerobot.dmi', "valemed")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valemed-rest"), "rest")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valemed-sit"), "sit")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valemed-bellyup"), "bellyup")
+ process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Medical (dog - valemed)"] = curr
+ //Sec
+ curr = icon('modular_citadel/icons/mob/widerobot.dmi', "k9")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "k9-rest"), "rest")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "k9-sit"), "sit")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "k9-bellyup"), "bellyup")
+ process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Security (dog - k9)"] = curr
+ curr = icon('modular_citadel/icons/mob/widerobot.dmi', "k9dark")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "k9dark-rest"), "rest")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "k9dark-sit"), "sit")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "k9dark-bellyup"), "bellyup")
+ process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Security (dog - k9dark)"] = curr
+ curr = icon('modular_citadel/icons/mob/widerobot.dmi', "valesec")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valesec-rest"), "rest")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valesec-sit"), "sit")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valesec-bellyup"), "bellyup")
+ process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Security (dog - valesec)"] = curr
+ //Service
+ curr = icon('modular_citadel/icons/mob/widerobot.dmi', "valeserv")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeserv-rest"), "rest")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeserv-sit"), "sit")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeserv-bellyup"), "bellyup")
+ process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Service (dog - valeserv)"] = curr
+ curr = icon('modular_citadel/icons/mob/widerobot.dmi', "valeservdark")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeservdark-rest"), "rest")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeservdark-sit"), "sit")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeservdark-bellyup"), "bellyup")
+ process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Service (dog - valeservdark)"] = curr
+ //Sci
+ curr = icon('modular_citadel/icons/mob/widerobot.dmi', "valesci")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valesci-rest"), "rest")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valesci-sit"), "sit")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valesci-bellyup"), "bellyup")
+ process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Science (dog - valesci)"] = curr
+ //Misc
+ .["Cyborg - Misc (dog - blade)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/widerobot.dmi', "blade"), HOLOFORM_FILTER_PAI, FALSE)
+
+/mob/living/silicon/pai/proc/default_chassis_pixel_offsets_x()
+ . = list()
+ //Engi
+ .["Cyborg - Engineering (dog - valeeng)"] = -16
+ .["Cyborg - Engineering (dog - pupdozer)"] = -16
+ //Med
+ .["Cyborg - Medical (dog - medihound)"] = -16
+ .["Cyborg - Medical (dog - medihounddark)"] = -16
+ .["Cyborg - Medical (dog - valemed)"] = -16
+ //Sec
+ .["Cyborg - Security (dog - k9)"] = -16
+ .["Cyborg - Security (dog - valesec)"] = -16
+ .["Cyborg - Security (dog - k9dark)"] = -16
+ //Service
+ .["Cyborg - Service (dog - valeserv)"] = -16
+ .["Cyborg - Service (dog - valeservdark)"] = -16
+ //Sci
+ .["Cyborg - Security (dog - valesci)"] = -16
+ //Misc
+ .["Cyborg - Misc (dog - blade)"] = -16
diff --git a/code/modules/mob/living/silicon/pai/pai_defense.dm b/code/modules/mob/living/silicon/pai/pai_defense.dm
index 5ac841326b..c727e100d8 100644
--- a/code/modules/mob/living/silicon/pai/pai_defense.dm
+++ b/code/modules/mob/living/silicon/pai/pai_defense.dm
@@ -8,9 +8,10 @@
return
take_holo_damage(50/severity)
Knockdown(400/severity)
- silent = max(30/severity, silent)
+ silent = max((3 MINUTES)/severity, silent)
if(holoform)
fold_in(force = TRUE)
+ emitter_next_use = world.time + emitter_emp_cd
//Need more effects that aren't instadeath or permanent law corruption.
/mob/living/silicon/pai/ex_act(severity, target)
diff --git a/code/modules/mob/living/silicon/pai/pai_shell.dm b/code/modules/mob/living/silicon/pai/pai_shell.dm
index 164a3e7389..6577c26402 100644
--- a/code/modules/mob/living/silicon/pai/pai_shell.dm
+++ b/code/modules/mob/living/silicon/pai/pai_shell.dm
@@ -12,12 +12,11 @@
. = fold_in(force)
return
- if(emittersemicd)
+ if(world.time < emitter_next_use)
to_chat(src, "Error: Holochassis emitters recycling. Please try again later.")
return FALSE
- emittersemicd = TRUE
- addtimer(CALLBACK(src, .proc/emittercool), emittercd)
+ emitter_next_use = world.time + emittercd
canmove = TRUE
density = TRUE
if(istype(card.loc, /obj/item/pda))
@@ -46,20 +45,17 @@
visible_message("[src] folds out its holochassis emitter and forms a holoshell around itself!")
holoform = TRUE
-/mob/living/silicon/pai/proc/emittercool()
- emittersemicd = FALSE
-
/mob/living/silicon/pai/proc/fold_in(force = FALSE)
- emittersemicd = TRUE
- if(!force)
- addtimer(CALLBACK(src, .proc/emittercool), emittercd)
- else
- addtimer(CALLBACK(src, .proc/emittercool), emitteroverloadcd)
+ emitter_next_use = world.time + (force? emitteroverloadcd : emittercd)
icon_state = "[chassis]"
if(!holoform)
. = fold_out(force)
return
- visible_message("[src] deactivates its holochassis emitter and folds back into a compact card!")
+ if(force)
+ short_radio()
+ visible_message("[src] shorts out, collapsing back into their storage card, sparks emitted from their radio antenna!")
+ else
+ visible_message("[src] deactivates its holochassis emitter and folds back into a compact card!")
stop_pulling()
if(client)
client.perspective = EYE_PERSPECTIVE
@@ -78,28 +74,39 @@
if(!isturf(loc) && loc != card)
to_chat(src, "You can not change your holochassis composite while not on the ground or in your card!")
return FALSE
- var/choice = input(src, "What would you like to use for your holochassis composite?") as null|anything in possible_chassis
- if(!choice)
+ var/list/choices = list("Preset - Basic", "Preset - Dynamic")
+ if(CONFIG_GET(flag/pai_custom_holoforms))
+ choices += "Custom"
+ var/choicetype = input(src, "What type of chassis do you want to use?") as null|anything in choices
+ if(!choicetype)
return FALSE
- chassis = choice
- icon_state = "[chassis]"
- if(resting)
- icon_state = "[chassis]_rest"
+ switch(choicetype)
+ if("Custom")
+ chassis = "custom"
+ if("Preset - Basic")
+ var/choice = input(src, "What would you like to use for your holochassis composite?") as null|anything in possible_chassis
+ if(!choice)
+ return FALSE
+ chassis = choice
+ if("Preset - Dynamic")
+ var/choice = input(src, "What would you like to use for your holochassis composite?") as null|anything in dynamic_chassis_icons
+ if(!choice)
+ return FALSE
+ chassis = "dynamic"
+ dynamic_chassis = choice
+ resist_a_rest(FALSE, TRUE)
+ update_icon()
to_chat(src, "You switch your holochassis projection composite to [chassis]")
/mob/living/silicon/pai/lay_down()
- ..()
- update_resting_icon(resting)
-
-/mob/living/silicon/pai/proc/update_resting_icon(rest)
- if(rest)
- icon_state = "[chassis]_rest"
- else
- icon_state = "[chassis]"
+ . = ..()
if(loc != card)
- visible_message("[src] [rest? "lays down for a moment..." : "perks up from the ground"]")
+ visible_message("[src] [resting? "lays down for a moment..." : "perks up from the ground"]")
+ update_icon()
-/mob/living/silicon/pai/start_pulling(atom/movable/AM)
+/mob/living/silicon/pai/start_pulling(atom/movable/AM, gs)
+ if(ispAI(AM))
+ return ..()
return FALSE
/mob/living/silicon/pai/proc/toggle_integrated_light()
@@ -122,3 +129,22 @@
to_chat(user, "[src]'s current form isn't able to be carried!")
return FALSE
return ..()
+
+/mob/living/silicon/pai/verb/toggle_chassis_sit()
+ set name = "Toggle Chassis Sit"
+ set category = "IC"
+ set desc = "Whether or not to try to use a sitting icon versus a resting icon. Takes priority over belly-up resting."
+ dynamic_chassis_sit = !dynamic_chassis_sit
+ to_chat(usr, "You are now [dynamic_chassis_sit? "sitting" : "lying down"].")
+ update_icon()
+
+/mob/living/silicon/pai/verb/toggle_chassis_bellyup()
+ set name = "Toggle Chassis Belly Up"
+ set category = "IC"
+ set desc = "Whether or not to try to use a belly up icon while resting. Overridden by sitting."
+ dynamic_chassis_bellyup = !dynamic_chassis_bellyup
+ to_chat(usr, "You are now lying on your [dynamic_chassis_bellyup? "back" : "front"].")
+ update_icon()
+
+/mob/living/silicon/pai/can_buckle_others(mob/living/target, atom/buckle_to)
+ return ispAI(target) && ..()
diff --git a/code/modules/mob/living/silicon/pai/say.dm b/code/modules/mob/living/silicon/pai/say.dm
index 9f07900a25..deedaa1382 100644
--- a/code/modules/mob/living/silicon/pai/say.dm
+++ b/code/modules/mob/living/silicon/pai/say.dm
@@ -5,4 +5,11 @@
..(message)
/mob/living/silicon/pai/binarycheck()
- return 0
+ return FALSE
+
+/mob/living/silicon/pai/radio(message, message_mode, list/spans, language)
+ if((message_mode == "robot") || (message_mode in GLOB.radiochannels))
+ if(radio_short)
+ to_chat(src, "Your radio is shorted out!")
+ return ITALICS | REDUCE_RANGE
+ return ..()
diff --git a/code/modules/mob/living/silicon/pai/software.dm b/code/modules/mob/living/silicon/pai/software.dm
index 9882d028d1..3ea018ccf6 100644
--- a/code/modules/mob/living/silicon/pai/software.dm
+++ b/code/modules/mob/living/silicon/pai/software.dm
@@ -283,7 +283,10 @@
dat += "Refresh
"
// Built-in
dat += "Directives
"
- dat += "Radio Configuration
"
+ if(radio_short)
+ dat += "\[RADIO SHORTED - Recalibrating!\]"
+ else
+ dat += "Radio Configuration
"
dat += "Screen Display
"
//dat += "Text Messaging
"
dat += "
"
diff --git a/code/modules/mob/living/silicon/pai/update_icon.dm b/code/modules/mob/living/silicon/pai/update_icon.dm
new file mode 100644
index 0000000000..9e40b71c5d
--- /dev/null
+++ b/code/modules/mob/living/silicon/pai/update_icon.dm
@@ -0,0 +1,27 @@
+/mob/living/silicon/pai/update_icon()
+ if(chassis == "custom") //Make sure custom exists if it's set to custom
+ custom_holoform_icon = client?.prefs?.get_filtered_holoform(HOLOFORM_FILTER_PAI)
+ if(!custom_holoform_icon)
+ chassis = pick(possible_chassis - "custom")
+ if(chassis == "dynamic") //handle dynamic generated icons
+ icon = dynamic_chassis_icons[dynamic_chassis]
+ var/list/states = icon_states(icon)
+ icon_state = ""
+ if(resting) //The next line is some bullshit but I can make it worse if you want and make it a single line instead of four.. :)
+ if(dynamic_chassis_sit && ("sit") in states)
+ icon_state = "sit"
+ else if(dynamic_chassis_bellyup && ("bellyup") in states)
+ icon_state = "bellyup"
+ else if("rest" in states)
+ icon_state = "rest"
+ rotate_on_lying = FALSE
+ else if(chassis == "custom")
+ icon = custom_holoform_icon
+ icon_state = ""
+ rotate_on_lying = TRUE
+ else
+ icon = initial(icon)
+ icon_state = "[chassis][resting? "_rest" : (stat == DEAD? "_dead" : "")]"
+ rotate_on_lying = FALSE
+ pixel_x = ((chassis == "dynamic") && chassis_pixel_offsets_x[dynamic_chassis]) || 0
+ update_transform()
diff --git a/code/modules/mob/living/update_icons.dm b/code/modules/mob/living/update_icons.dm
new file mode 100644
index 0000000000..8bf0dc98a9
--- /dev/null
+++ b/code/modules/mob/living/update_icons.dm
@@ -0,0 +1,26 @@
+//IMPORTANT: Multiple animate() calls do not stack well, so try to do them all at once if you can.
+/mob/living/update_transform()
+ var/matrix/ntransform = matrix(transform) //aka transform.Copy()
+ var/final_pixel_y = pixel_y
+ var/final_dir = dir
+ var/changed = 0
+ if(lying != lying_prev && rotate_on_lying)
+ changed++
+ ntransform.TurnTo(lying_prev,lying)
+ if(lying == 0) //Lying to standing
+ final_pixel_y = get_standard_pixel_y_offset()
+ else //if(lying != 0)
+ if(lying_prev == 0) //Standing to lying
+ pixel_y = get_standard_pixel_y_offset()
+ final_pixel_y = get_standard_pixel_y_offset(lying)
+ if(dir & (EAST|WEST)) //Facing east or west
+ final_dir = pick(NORTH, SOUTH) //So you fall on your side rather than your face or ass
+
+ if(resize != RESIZE_DEFAULT_SIZE)
+ changed++
+ ntransform.Scale(resize)
+ resize = RESIZE_DEFAULT_SIZE
+
+ if(changed)
+ animate(src, transform = ntransform, time = 2, pixel_y = final_pixel_y, dir = final_dir, easing = EASE_IN|EASE_OUT)
+ setMovetype(movement_type & ~FLOATING) // If we were without gravity, the bouncing animation got stopped, so we make sure we restart it in next life().
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 427e30f2e2..fad9431b61 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -802,11 +802,14 @@ GLOBAL_VAR_INIT(exploit_warn_spam_prevention, 0)
//can the mob be buckled to something by default?
/mob/proc/can_buckle()
- return 1
+ return TRUE
//can the mob be unbuckled from something by default?
/mob/proc/can_unbuckle()
- return 1
+ return TRUE
+
+/mob/proc/can_buckle_others(mob/living/target, atom/buckle_to)
+ return TRUE
//Can the mob interact() with an atom?
/mob/proc/can_interact_with(atom/A)
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index e13ce10347..51e9453087 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/modular_citadel/code/modules/mob/cit_emotes.dm b/modular_citadel/code/modules/mob/cit_emotes.dm
index 7e7a77538a..d0ad0d3232 100644
--- a/modular_citadel/code/modules/mob/cit_emotes.dm
+++ b/modular_citadel/code/modules/mob/cit_emotes.dm
@@ -70,113 +70,124 @@
message = "makes a very loud noise."
. = ..()
-/datum/emote/sound/carbon/snap
- key = "snap"
- key_third_person = "snaps"
- muzzle_ignore = TRUE
- restraint_check = TRUE
- emote_type = EMOTE_AUDIBLE
- sound = 'sound/effects/snap01.ogg'
- mob_type_allowed_typecache = list(/mob/living/carbon/)
-
-
/datum/emote/living/snap
key = "snap"
key_third_person = "snaps"
message = "snaps their fingers."
emote_type = EMOTE_AUDIBLE
+ muzzle_ignore = TRUE
+ restraint_check = TRUE
+ mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/snap/run_emote(mob/living/user, params)
- if(ishuman(user))
- if(user.nextsoundemote >= world.time)
- return
- user.nextsoundemote = world.time + 7
- playsound(user, 'modular_citadel/sound/voice/snap.ogg', 50, 1, -1)
- . = ..()
+ if(!(. = ..()))
+ return
+ if(user.nextsoundemote >= world.time)
+ return
+ user.nextsoundemote = world.time + 7
+ playsound(user, 'modular_citadel/sound/voice/snap.ogg', 50, 1, -1)
/datum/emote/living/snap2
key = "snap2"
key_third_person = "snaps twice"
message = "snaps twice."
emote_type = EMOTE_AUDIBLE
+ muzzle_ignore = TRUE
+ restraint_check = TRUE
+ mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/snap2/run_emote(mob/living/user, params)
- if(ishuman(user))
- if(user.nextsoundemote >= world.time)
- return
- user.nextsoundemote = world.time + 7
- playsound(user, 'modular_citadel/sound/voice/snap2.ogg', 50, 1, -1)
- . = ..()
+ if(!(. = ..()))
+ return
+ if(user.nextsoundemote >= world.time)
+ return
+ user.nextsoundemote = world.time + 7
+ playsound(user, 'modular_citadel/sound/voice/snap2.ogg', 50, 1, -1)
/datum/emote/living/snap3
key = "snap3"
key_third_person = "snaps thrice"
message = "snaps thrice."
emote_type = EMOTE_AUDIBLE
+ muzzle_ignore = TRUE
+ restraint_check = TRUE
+ mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/snap3/run_emote(mob/living/user, params)
- if(ishuman(user))
- if(user.nextsoundemote >= world.time)
- return
- user.nextsoundemote = world.time + 7
- playsound(user, 'modular_citadel/sound/voice/snap3.ogg', 50, 1, -1)
- . = ..()
+ if(!(. = ..()))
+ return
+ if(user.nextsoundemote >= world.time)
+ return
+ user.nextsoundemote = world.time + 7
+ playsound(user, 'modular_citadel/sound/voice/snap3.ogg', 50, 1, -1)
/datum/emote/living/awoo
key = "awoo"
key_third_person = "lets out an awoo"
message = "lets out an awoo!"
emote_type = EMOTE_AUDIBLE
+ muzzle_ignore = FALSE
+ restraint_check = FALSE
+ mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/awoo/run_emote(mob/living/user, params)
- if(ishuman(user))
- if(user.nextsoundemote >= world.time)
- return
- user.nextsoundemote = world.time + 7
- playsound(user, 'modular_citadel/sound/voice/awoo.ogg', 50, 1, -1)
- . = ..()
+ if(!(. = ..()))
+ return
+ if(user.nextsoundemote >= world.time)
+ return
+ user.nextsoundemote = world.time + 7
+ playsound(user, 'modular_citadel/sound/voice/awoo.ogg', 50, 1, -1)
/datum/emote/living/nya
key = "nya"
key_third_person = "lets out a nya"
message = "lets out a nya!"
emote_type = EMOTE_AUDIBLE
+ muzzle_ignore = FALSE
+ restraint_check = FALSE
+ mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/nya/run_emote(mob/living/user, params)
- if(ishuman(user))
- if(user.nextsoundemote >= world.time)
- return
- user.nextsoundemote = world.time + 7
- playsound(user, 'modular_citadel/sound/voice/nya.ogg', 50, 1, -1)
- . = ..()
+ if(!(. = ..()))
+ return
+ if(user.nextsoundemote >= world.time)
+ return
+ user.nextsoundemote = world.time + 7
+ playsound(user, 'modular_citadel/sound/voice/nya.ogg', 50, 1, -1)
/datum/emote/living/weh
key = "weh"
key_third_person = "lets out a weh"
message = "lets out a weh!"
emote_type = EMOTE_AUDIBLE
+ muzzle_ignore = FALSE
+ restraint_check = FALSE
+ mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/weh/run_emote(mob/living/user, params)
- if(ishuman(user))
- if(user.nextsoundemote >= world.time)
- return
- user.nextsoundemote = world.time + 7
- playsound(user, 'modular_citadel/sound/voice/weh.ogg', 50, 1, -1)
- . = ..()
+ if(!(. = ..()))
+ return
+ if(user.nextsoundemote >= world.time)
+ return
+ user.nextsoundemote = world.time + 7
+ playsound(user, 'modular_citadel/sound/voice/weh.ogg', 50, 1, -1)
/datum/emote/living/peep
key = "peep"
key_third_person = "peeps like a bird"
message = "peeps like a bird!"
emote_type = EMOTE_AUDIBLE
+ muzzle_ignore = FALSE
+ restraint_check = FALSE
+ mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/peep/run_emote(mob/living/user, params)
- if(ishuman(user))
- if(user.nextsoundemote >= world.time)
- return
- user.nextsoundemote = world.time + 7
- playsound(user, 'modular_citadel/sound/voice/peep.ogg', 50, 1, -1)
- . = ..()
+ if(!(. = ..()))
+ return
+ if(user.nextsoundemote >= world.time)
+ return
+ user.nextsoundemote = world.time + 7
+ playsound(user, 'modular_citadel/sound/voice/peep.ogg', 50, 1, -1)
/datum/emote/living/dab
key = "dab"
@@ -190,41 +201,49 @@
key_third_person = "lets out a tiny squeak"
message = "lets out a tiny squeak!"
emote_type = EMOTE_AUDIBLE
- mob_type_allowed_typecache = list(/mob/living/carbon)
+ muzzle_ignore = FALSE
+ restraint_check = FALSE
+ mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/mothsqueak/run_emote(mob/living/user, params)
- if(ishuman(user))
- if(user.nextsoundemote >= world.time)
- return
- user.nextsoundemote = world.time + 7
- playsound(user, 'modular_citadel/sound/voice/mothsqueak.ogg', 50, 1, -1)
- . = ..()
+ if(!(. = ..()))
+ return
+ if(user.nextsoundemote >= world.time)
+ return
+ user.nextsoundemote = world.time + 7
+ playsound(user, 'modular_citadel/sound/voice/mothsqueak.ogg', 50, 1, -1)
/datum/emote/living/merp
key = "merp"
key_third_person = "merps"
message = "merps!"
emote_type = EMOTE_AUDIBLE
+ muzzle_ignore = FALSE
+ restraint_check = FALSE
+ mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/merp/run_emote(mob/living/user, params)
- if(ishuman(user))
- if(user.nextsoundemote >= world.time)
- return
- user.nextsoundemote = world.time + 7
- playsound(user, 'modular_citadel/sound/voice/merp.ogg', 50, 1, -1)
- . = ..()
+ if(!(. = ..()))
+ return
+ if(user.nextsoundemote >= world.time)
+ return
+ user.nextsoundemote = world.time + 7
+ playsound(user, 'modular_citadel/sound/voice/merp.ogg', 50, 1, -1)
/datum/emote/living/bark
key = "bark"
key_third_person = "barks"
message = "barks!"
emote_type = EMOTE_AUDIBLE
+ muzzle_ignore = FALSE
+ restraint_check = FALSE
+ mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/bark/run_emote(mob/living/user, params)
- if(ishuman(user))
- if(user.nextsoundemote >= world.time)
- return
- user.nextsoundemote = world.time + 7
- var/sound = pick('modular_citadel/sound/voice/bark1.ogg', 'modular_citadel/sound/voice/bark2.ogg')
- playsound(user, sound, 50, 1, -1)
- . = ..()
\ No newline at end of file
+ if(!(. = ..()))
+ return
+ if(user.nextsoundemote >= world.time)
+ return
+ user.nextsoundemote = world.time + 7
+ var/sound = pick('modular_citadel/sound/voice/bark1.ogg', 'modular_citadel/sound/voice/bark2.ogg')
+ playsound(user, sound, 50, 1, -1)
diff --git a/tgstation.dme b/tgstation.dme
index 30fcc602ce..7e7d69d6f1 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -120,6 +120,7 @@
#include "code\__HELPERS\areas.dm"
#include "code\__HELPERS\AStar.dm"
#include "code\__HELPERS\cmp.dm"
+#include "code\__HELPERS\custom_holoforms.dm"
#include "code\__HELPERS\dates.dm"
#include "code\__HELPERS\donator_groupings.dm"
#include "code\__HELPERS\files.dm"
@@ -2111,6 +2112,7 @@
#include "code\modules\mob\living\say.dm"
#include "code\modules\mob\living\status_procs.dm"
#include "code\modules\mob\living\taste.dm"
+#include "code\modules\mob\living\update_icons.dm"
#include "code\modules\mob\living\ventcrawling.dm"
#include "code\modules\mob\living\brain\brain.dm"
#include "code\modules\mob\living\brain\brain_item.dm"
@@ -2217,6 +2219,7 @@
#include "code\modules\mob\living\carbon\monkey\monkey_defense.dm"
#include "code\modules\mob\living\carbon\monkey\punpun.dm"
#include "code\modules\mob\living\carbon\monkey\update_icons.dm"
+#include "code\modules\mob\living\silicon\custom_holoform.dm"
#include "code\modules\mob\living\silicon\damage_procs.dm"
#include "code\modules\mob\living\silicon\death.dm"
#include "code\modules\mob\living\silicon\examine.dm"
@@ -2248,6 +2251,7 @@
#include "code\modules\mob\living\silicon\pai\personality.dm"
#include "code\modules\mob\living\silicon\pai\say.dm"
#include "code\modules\mob\living\silicon\pai\software.dm"
+#include "code\modules\mob\living\silicon\pai\update_icon.dm"
#include "code\modules\mob\living\silicon\robot\death.dm"
#include "code\modules\mob\living\silicon\robot\emote.dm"
#include "code\modules\mob\living\silicon\robot\examine.dm"