Merge branch 'master' into upstream-merge-32206

This commit is contained in:
LetterJay
2017-10-29 23:36:39 -04:00
committed by GitHub
28 changed files with 13888 additions and 61 deletions
+2
View File
@@ -10,6 +10,8 @@
#define T20C 293.15 // 20degC
#define TCMB 2.7 // -270.3degC
#define SHORT_REAL_LIMIT 16777216
//"fancy" math for calculating time in ms from tick_usage percentage and the length of ticks
//percent_of_tick_used * (ticklag * 100(to convert to ms)) / 100(percent ratio)
//collapsed to percent_of_tick_used * tick_lag
+29
View File
@@ -0,0 +1,29 @@
#define PROFILE_START ;PROFILE_STORE = list();PROFILE_SET;
#define PROFILE_STOP ;PROFILE_STORE = null;
#define PROFILE_SET ;PROFILE_TIME = TICK_USAGE_REAL; PROFILE_LINE = __LINE__; PROFILE_FILE = __FILE__; PROFILE_SLEEPCHECK = world.time;
#define PROFILE_TICK ;\
if (PROFILE_STORE) {\
var/PROFILE_TICK_USAGE_REAL = TICK_USAGE_REAL;\
if (PROFILE_SLEEPCHECK == world.time) {\
var/PROFILE_STRING = "[PROFILE_FILE]:[PROFILE_LINE] - [__FILE__]:[__LINE__]";\
var/list/PROFILE_ITEM = PROFILE_STORE[PROFILE_STRING];\
if (!PROFILE_ITEM) {\
PROFILE_ITEM = new(PROFILE_ITEM_LEN);\
PROFILE_STORE[PROFILE_STRING] = PROFILE_ITEM;\
PROFILE_ITEM[PROFILE_ITEM_TIME] = 0;\
PROFILE_ITEM[PROFILE_ITEM_COUNT] = 0;\
};\
PROFILE_ITEM[PROFILE_ITEM_TIME] += TICK_DELTA_TO_MS(PROFILE_TICK_USAGE_REAL-PROFILE_TIME);\
var/PROFILE_INCR_AMOUNT = min(1, 2**round(PROFILE_ITEM[PROFILE_ITEM_COUNT]/SHORT_REAL_LIMIT));\
if (prob(100/PROFILE_INCR_AMOUNT)) {\
PROFILE_ITEM[PROFILE_ITEM_COUNT] += PROFILE_INCR_AMOUNT;\
};\
};\
PROFILE_SET;\
};
#define PROFILE_ITEM_LEN 2
#define PROFILE_ITEM_TIME 1
#define PROFILE_ITEM_COUNT 2
+9
View File
@@ -58,4 +58,13 @@ GLOBAL_VAR_INIT(cmp_field, "name")
. = B.failures - A.failures
if (!.)
. = B.qdels - A.qdels
/proc/cmp_profile_avg_time_dsc(list/A, list/B)
return (B[PROFILE_ITEM_TIME]/(B[PROFILE_ITEM_COUNT] || 1)) - (A[PROFILE_ITEM_TIME]/(A[PROFILE_ITEM_COUNT] || 1))
/proc/cmp_profile_time_dsc(list/A, list/B)
return B[PROFILE_ITEM_TIME] - A[PROFILE_ITEM_TIME]
/proc/cmp_profile_count_dsc(list/A, list/B)
return B[PROFILE_ITEM_COUNT] - A[PROFILE_ITEM_COUNT]
+18
View File
@@ -1411,3 +1411,21 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
//checks if a turf is in the planet z list.
/proc/turf_z_is_planet(turf/T)
return GLOB.z_is_planet["[T.z]"]
//returns a GUID like identifier (using a mostly made up record format)
//guids are not on their own suitable for access or security tokens, as most of their bits are predictable.
// (But may make a nice salt to one)
/proc/GUID()
var/const/GUID_VERSION = "b"
var/const/GUID_VARIANT = "d"
var/node_id = copytext(md5("[rand()*rand(1,9999999)][world.name][world.hub][world.hub_password][world.internet_address][world.address][world.contents.len][world.status][world.port][rand()*rand(1,9999999)]"), 1, 13)
var/time_high = "[num2hex(text2num(time2text(world.realtime,"YYYY")), 2)][num2hex(world.realtime, 6)]"
var/time_mid = num2hex(world.timeofday, 4)
var/time_low = num2hex(world.time, 3)
var/time_clock = num2hex(TICK_DELTA_TO_MS(world.tick_usage), 3)
return "{[time_high]-[time_mid]-[GUID_VERSION][time_low]-[GUID_VARIANT][time_clock]-[node_id]}"
+2 -1
View File
@@ -15,7 +15,7 @@
Override makes it so the alert is not replaced until cleared by a clear_alert with clear_override, and it's used for hallucinations.
*/
if(!category)
if(!category || QDELETED(src))
return
var/obj/screen/alert/thealert
@@ -629,4 +629,5 @@ so as to remain in compliance with the most up-to-date laws."
. = ..()
severity = 0
master = null
mob_viewer = null
screen_loc = ""
+60
View File
@@ -0,0 +1,60 @@
/datum/component/spooky
var/too_spooky = TRUE //will it spawn a new instrument?
/datum/component/spooky/Initialize()
RegisterSignal(COMSIG_ITEM_ATTACK, .proc/spectral_attack)
/datum/component/spooky/proc/spectral_attack(mob/living/carbon/C, mob/user)
if(ishuman(user)) //this weapon wasn't meant for mortals.
var/mob/living/carbon/human/U = user
if(!istype(U.dna.species, /datum/species/skeleton))
U.adjustStaminaLoss(35) //Extra Damage
U.Jitter(35)
U.stuttering = 20
if(U.getStaminaLoss() > 95)
to_chat(U, "<font color ='red', size ='4'><B>Your ears weren't meant for this spectral sound.</B></font>")
spectral_change(U)
return
if(ishuman(C))
var/mob/living/carbon/human/H = C
if(istype(H.dna.species, /datum/species/skeleton))
return ..() //undeads are unaffected by the spook-pocalypse.
if(istype(H.dna.species, /datum/species/zombie))
H.adjustStaminaLoss(25)
H.Knockdown(15) //zombies can't resist the doot
C.Jitter(35)
C.stuttering = 20
if((!istype(H.dna.species, /datum/species/skeleton)) && (!istype(H.dna.species, /datum/species/golem)) && (!istype(H.dna.species, /datum/species/android)) && (!istype(H.dna.species, /datum/species/jelly)))
C.adjustStaminaLoss(25) //boneless humanoids don't lose the will to live
to_chat(C, "<font color='red' size='4'><B>DOOT</B></span>")
spectral_change(H)
else //the sound will spook monkeys.
C.Jitter(15)
C.stuttering = 20
/datum/component/spooky/proc/spectral_change(mob/living/carbon/human/H, mob/user)
if((H.getStaminaLoss() > 95) && (!istype(H.dna.species, /datum/species/skeleton)) && (!istype(H.dna.species, /datum/species/golem)) && (!istype(H.dna.species, /datum/species/android)) && (!istype(H.dna.species, /datum/species/jelly)))
H.Knockdown(20)
H.set_species(/datum/species/skeleton)
H.visible_message("<span class='warning'>[H] has given up on life as a mortal.</span>")
var/T = get_turf(H)
if(too_spooky)
if(prob(30))
new/obj/item/device/instrument/saxophone/spectral(T)
else if(prob(30))
new/obj/item/device/instrument/trumpet/spectral(T)
else if(prob(30))
new/obj/item/device/instrument/trombone/spectral(T)
else
to_chat(H, "The spooky gods forgot to ship your instrument. Better luck next unlife.")
to_chat(H, "<B>You are the spooky skeleton!</B>")
to_chat(H, "A new life and identity has begun. Help your fellow skeletons into bringing out the spooky-pocalypse. You haven't forgotten your past life, and are still beholden to past loyalties.")
change_name(H) //time for a new name!
/datum/component/spooky/proc/change_name(mob/living/carbon/human/H)
var/t = stripped_input(H, "Enter your new skeleton name", H.real_name, null, MAX_NAME_LEN)
if(!t)
t = "spooky skeleton"
H.fully_replace_character_name(H.real_name, t)
+6 -12
View File
@@ -13,7 +13,6 @@
//Order matters here.
var/list/transition_config = list(CENTCOM = SELFLOOPING,
CITY_OF_COGS = SELFLOOPING,
MAIN_STATION = CROSSLINKED,
EMPTY_AREA_1 = CROSSLINKED,
EMPTY_AREA_2 = CROSSLINKED,
@@ -69,12 +68,11 @@
map_path = json["map_path"]
map_file = json["map_file"]
minetype = json["minetype"]
allow_custom_shuttles = json["allow_custom_shuttles"]
minetype = json["minetype"] || minetype
allow_custom_shuttles = json["allow_custom_shuttles"] == TRUE
var/list/jtcl = json["transition_config"]
if(jtcl != "default")
var/jtcl = json["transition_config"]
if(jtcl && jtcl != "default")
transition_config.Cut()
for(var/I in jtcl)
@@ -87,9 +85,6 @@
CHECK_EXISTS("map_name")
CHECK_EXISTS("map_path")
CHECK_EXISTS("map_file")
CHECK_EXISTS("minetype")
CHECK_EXISTS("transition_config")
CHECK_EXISTS("allow_custom_shuttles")
var/path = GetFullMapPath(json["map_path"], json["map_file"])
if(!fexists(path))
@@ -102,11 +97,10 @@
log_world("transition_config is not a list!")
return
var/list/jtcl = json["transition_config"]
for(var/I in jtcl)
for(var/I in tc)
if(isnull(TransitionStringToEnum(I)))
log_world("Invalid transition_config option: [I]!")
if(isnull(TransitionStringToEnum(jtcl[I])))
if(isnull(TransitionStringToEnum(tc[I])))
log_world("Invalid transition_config option: [I]!")
return TRUE
+18
View File
@@ -0,0 +1,18 @@
//these are real globals so you can use profiling to profile early world init stuff.
GLOBAL_REAL_VAR(list/PROFILE_STORE)
GLOBAL_REAL_VAR(PROFILE_LINE)
GLOBAL_REAL_VAR(PROFILE_FILE)
GLOBAL_REAL_VAR(PROFILE_SLEEPCHECK)
GLOBAL_REAL_VAR(PROFILE_TIME)
/proc/profile_show(user, sort = /proc/cmp_profile_avg_time_dsc)
sortTim(PROFILE_STORE, sort, TRUE)
var/list/lines = list()
for (var/entry in PROFILE_STORE)
var/list/data = PROFILE_STORE[entry]
lines += "[entry] => [num2text(data[PROFILE_ITEM_TIME], 10)]ms ([data[PROFILE_ITEM_COUNT]]) (avg:[num2text(data[PROFILE_ITEM_TIME]/(data[PROFILE_ITEM_COUNT] || 1), 99)])"
user << browse("<ol><li>[lines.Join("</li><li>")]</li></ol>", "window=[url_encode(GUID())]")
+4 -2
View File
@@ -98,7 +98,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
var/z_original = ZLEVEL_STATION_PRIMARY
var/threat = 0 // used for determining which meteors are most interesting
var/lifetime = DEFAULT_METEOR_LIFETIME
var/timerid = null
var/list/meteordrop = list(/obj/item/ore/iron)
var/dropamt = 2
@@ -117,6 +117,8 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
get_hit()
/obj/effect/meteor/Destroy()
if (timerid)
deltimer(timerid)
GLOB.meteor_list -= src
SSaugury.unregister_doom(src)
walk(src,0) //this cancels the walk_towards() proc
@@ -127,7 +129,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
GLOB.meteor_list += src
SSaugury.register_doom(src, threat)
SpinAnimation()
QDEL_IN(src, lifetime)
timerid = QDEL_IN(src, lifetime)
chase_target(target)
/obj/effect/meteor/Collide(atom/A)
+58 -1
View File
@@ -110,6 +110,30 @@
item_state = "accordion"
instrumentId = "accordion"
/obj/item/device/instrument/trumpet
name = "trumpet"
desc = "To announce the arrival of the king!"
icon_state = "trumpet"
item_state = "trombone"
instrumentId = "trombone"
/obj/item/device/instrument/trumpet/spectral
name = "spectral trumpet"
desc = "Things are about to get spooky!"
icon_state = "trumpet"
item_state = "trombone"
force = 0
instrumentId = "trombone"
attack_verb = list("played","jazzed","trumpeted","mourned","dooted","spooked")
/obj/item/device/instrument/trumpet/spectral/Initialize()
. = ..()
AddComponent(/datum/component/spooky)
/obj/item/device/instrument/trumpet/spectral/attack(mob/living/carbon/C, mob/user)
playsound (loc, 'sound/instruments/trombone/En4.mid', 100,1,-1)
..()
/obj/item/device/instrument/saxophone
name = "saxophone"
desc = "This soothing sound will be sure to leave your audience in tears."
@@ -117,6 +141,23 @@
item_state = "saxophone"
instrumentId = "saxophone"
/obj/item/device/instrument/saxophone/spectral
name = "spectral saxophone"
desc = "This spooky sound will be sure to leave mortals in bones."
icon_state = "saxophone"
item_state = "saxophone"
instrumentId = "saxophone"
force = 0
attack_verb = list("played","jazzed","saxxed","mourned","dooted","spooked")
/obj/item/device/instrument/saxophone/spectral/Initialize()
. = ..()
AddComponent(/datum/component/spooky)
/obj/item/device/instrument/saxophone/spectral/attack(mob/living/carbon/C, mob/user)
playsound (loc, 'sound/instruments/saxophone/En4.mid', 100,1,-1)
..()
/obj/item/device/instrument/trombone
name = "trombone"
desc = "How can any pool table ever hope to compete?"
@@ -124,6 +165,23 @@
item_state = "trombone"
instrumentId = "trombone"
/obj/item/device/instrument/trombone/spectral
name = "spectral trombone"
desc = "A skeleton's favorite instrument. Apply directly on the mortals."
instrumentId = "trombone"
icon_state = "trombone"
item_state = "trombone"
force = 0
attack_verb = list("played","jazzed","tromboned","mourned","dooted","spooked")
/obj/item/device/instrument/trombone/spectral/Initialize()
. = ..()
AddComponent(/datum/component/spooky)
/obj/item/device/instrument/trombone/spectral/attack(mob/living/carbon/C, mob/user)
playsound (loc, 'sound/instruments/trombone/Cn4.mid', 100,1,-1)
..()
/obj/item/device/instrument/recorder
name = "recorder"
desc = "Just like in school, playing ability and all."
@@ -163,4 +221,3 @@
throw_speed = 3
throw_range = 15
hitsound = 'sound/items/bikehorn.ogg'
+40 -1
View File
@@ -780,7 +780,7 @@ GLOBAL_PROTECT(LastAdminCalledProc)
/client/proc/cmd_display_init_log()
set category = "Debug"
set name = "Display Initialzie() Log"
set name = "Display Initialize() Log"
set desc = "Displays a list of things that didn't handle Initialize() properly"
usr << browse(replacetext(SSatoms.InitLog(), "\n", "<br>"), "window=initlog")
@@ -873,3 +873,42 @@ GLOBAL_PROTECT(LastAdminCalledProc)
message_admins("<span class='adminnotice'>[key_name_admin(src)] pumped a random event.</span>")
SSblackbox.add_details("admin_verb","Pump Random Event")
log_admin("[key_name(src)] pumped a random event.")
/client/proc/start_line_profiling()
set category = "Profile"
set name = "Start Line Profiling"
set desc = "Starts tracking line by line profiling for code lines that support it"
PROFILE_START
message_admins("<span class='adminnotice'>[key_name_admin(src)] started line by line profiling.</span>")
SSblackbox.add_details("admin_verb","Start Line Profiling")
log_admin("[key_name(src)] started line by line profiling.")
/client/proc/stop_line_profiling()
set category = "Profile"
set name = "Stops Line Profiling"
set desc = "Stops tracking line by line profiling for code lines that support it"
PROFILE_STOP
message_admins("<span class='adminnotice'>[key_name_admin(src)] stopped line by line profiling.</span>")
SSblackbox.add_details("admin_verb","stop Line Profiling")
log_admin("[key_name(src)] stopped line by line profiling.")
/client/proc/show_line_profiling()
set category = "Profile"
set name = "Show Line Profiling"
set desc = "Shows tracked profiling info from code lines that support it"
var/sortlist = list(
"Avg time" = /proc/cmp_profile_avg_time_dsc,
"Total Time" = /proc/cmp_profile_time_dsc,
"Call Count" = /proc/cmp_profile_count_dsc
)
var/sort = input(src, "Sort type?", "Sort Type", "Avg time") as null|anything in sortlist
if (!sort)
return
sort = sortlist[sort]
profile_show(src, sort)
+4 -1
View File
@@ -42,7 +42,10 @@ GLOBAL_LIST_INIT(admin_verbs_debug_mapping, list(
/client/proc/print_pointers,
/client/proc/cmd_show_at_list,
/client/proc/cmd_show_at_markers,
/client/proc/manipulate_organs
/client/proc/manipulate_organs,
/client/proc/start_line_profiling,
/client/proc/stop_line_profiling,
/client/proc/show_line_profiling
))
/obj/effect/debugging/mapfix_marker
+1 -1
View File
@@ -40,7 +40,7 @@
/datum/round_event_control/proc/canSpawnEvent(var/players_amt, var/gamemode)
if(occurrences >= max_occurrences)
return FALSE
if(earliest_start >= world.time)
if(earliest_start >= world.time-SSticker.round_start_time)
return FALSE
if(wizardevent != SSevents.wizardmode)
return FALSE
@@ -0,0 +1,149 @@
/datum/species/dullahan
name = "dullahan"
id = "dullahan"
default_color = "FFFFFF"
species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,NOBREATH,NOHUNGER)
mutant_bodyparts = list("tail_human", "ears", "wings")
default_features = list("mcolor" = "FFF", "tail_human" = "None", "ears" = "None", "wings" = "None")
use_skintones = TRUE
mutant_brain = /obj/item/organ/brain/dullahan
mutanteyes = /obj/item/organ/eyes/dullahan
mutanttongue = /obj/item/organ/tongue/dullahan
mutantears = /obj/item/organ/ears/dullahan
blacklisted = TRUE
limbs_id = "human"
skinned_type = /obj/item/stack/sheet/animalhide/human
var/obj/item/dullahan_relay/myhead
/datum/species/dullahan/check_roundstart_eligible()
if(SSevents.holidays && SSevents.holidays[HALLOWEEN])
return TRUE
return FALSE
/datum/species/dullahan/on_species_gain(mob/living/carbon/human/H, datum/species/old_species)
. = ..()
var/obj/item/bodypart/head/head = H.get_bodypart("head")
if(head)
head.drop_limb()
head.flags_1 = HEAR_1
head.throwforce = 25
myhead = new /obj/item/dullahan_relay (head, H)
H.put_in_hands(head)
/datum/species/dullahan/on_species_loss(mob/living/carbon/human/H)
if(myhead)
var/obj/item/dullahan_relay/DR = myhead
myhead = null
DR.owner = null
qdel(DR)
..()
/datum/species/dullahan/spec_life(mob/living/carbon/human/H)
if(!QDELETED(myhead))
update_vision_perspective(H)
var/turf/Me = get_turf(H)
var/turf/Head = get_turf(myhead)
if(Me == Head || (Head in oview(7, Me)))
H.disabilities &= ~DEAF
else
H.disabilities |= DEAF
else
myhead = null
H.gib()
var/obj/item/bodypart/head/head2 = H.get_bodypart("head")
if(head2)
myhead = null
H.gib()
/datum/species/dullahan/proc/update_vision_perspective(mob/living/carbon/human/H)
var/obj/item/organ/eyes/eyes = H.getorganslot(ORGAN_SLOT_EYES)
if(eyes)
H.update_tint()
if(eyes.tint)
H.reset_perspective(H)
else
H.reset_perspective(myhead)
/obj/item/organ/brain/dullahan
decoy_override = TRUE
vital = FALSE
/obj/item/organ/tongue/dullahan
zone = "abstract"
/obj/item/organ/tongue/dullahan/TongueSpeech(var/message)
if(ishuman(owner))
var/mob/living/carbon/human/H = owner
if(H.dna.species.id == "dullahan")
var/datum/species/dullahan/D = H.dna.species
if(isobj(D.myhead.loc))
var/obj/O = D.myhead.loc
O.say(message)
message = ""
return message
/obj/item/organ/ears/dullahan
zone = "abstract"
/obj/item/organ/eyes/dullahan
name = "head vision"
desc = "An abstraction."
actions_types = list(/datum/action/item_action/organ_action/dullahan)
zone = "abstract"
/datum/action/item_action/organ_action/dullahan
name = "Toggle Perspective"
desc = "Switch between seeing normally from your head, or blindly from your body."
/datum/action/item_action/organ_action/dullahan/Trigger()
. = ..()
var/obj/item/organ/eyes/dullahan/DE = target
if(DE.tint)
DE.tint = 0
else
DE.tint = INFINITY
if(ishuman(owner))
var/mob/living/carbon/human/H = owner
if(H.dna.species.id == "dullahan")
var/datum/species/dullahan/D = H.dna.species
D.update_vision_perspective(H)
/obj/item/dullahan_relay
var/mob/living/owner
flags_1 = HEAR_1
/obj/item/dullahan_relay/Initialize(mapload,new_owner)
. = ..()
owner = new_owner
START_PROCESSING(SSobj, src)
/obj/item/dullahan_relay/process()
if(!istype(loc, /obj/item/bodypart/head) || QDELETED(owner))
. = PROCESS_KILL
qdel(src)
/obj/item/dullahan_relay/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
if(!QDELETED(owner))
var/turf/T = get_turf(speaker)
var/turf/owner_turf = get_turf(owner)
if(T == owner_turf || (T in oview(7, owner_turf))) //Do not relay things we can already hear
return
message = compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode)
to_chat(owner,message)
else
qdel(src)
/obj/item/dullahan_relay/Destroy()
if(!QDELETED(owner))
var/mob/living/carbon/human/H = owner
if(H.dna.species.id == "dullahan")
var/datum/species/dullahan/D = H.dna.species
D.myhead = null
owner.gib()
owner = null
..()
@@ -24,9 +24,8 @@
to_chat(C, "[info_text]")
C.skin_tone = "albino"
C.update_body(0)
if(C.mind)
var/obj/effect/proc_holder/spell/targeted/shapeshift/bat/B = new
C.mind.AddSpell(B)
var/obj/effect/proc_holder/spell/targeted/shapeshift/bat/B = new
C.AddSpell(B)
/datum/species/vampire/on_species_loss(mob/living/carbon/C)
. = ..()
@@ -45,7 +44,7 @@
C.adjustOxyLoss(-4)
C.adjustCloneLoss(-4)
return
C.blood_volume -= 1.5
C.blood_volume -= 0.75
if(C.blood_volume <= BLOOD_VOLUME_SURVIVE)
to_chat(C, "<span class='danger'>You ran out of blood!</span>")
C.dust()
@@ -123,8 +122,4 @@
invocation = "Squeak!"
charge_max = 50
cooldown_min = 50
shapeshift_type = /mob/living/simple_animal/hostile/retaliate/bat
current_shapes = list(/mob/living/simple_animal/hostile/retaliate/bat)
current_casters = list()
possible_shapes = list(/mob/living/simple_animal/hostile/retaliate/bat)
@@ -34,4 +34,7 @@
//Space bats need no air to fly in.
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
minbodytemp = 0
minbodytemp = 0
/mob/living/simple_animal/hostile/retaliate/bat/Process_Spacemove(movement_dir = 0)
return 1
+2
View File
@@ -4,6 +4,8 @@
GLOB.living_mob_list -= src
GLOB.all_clockwork_mobs -= src
GLOB.mob_directory -= tag
for (var/alert in alerts)
clear_alert(alert, TRUE)
if(observers && observers.len)
for(var/M in observers)
var/mob/dead/observe = M
@@ -22,6 +22,11 @@
/mob/living/simple_animal/hostile/construct/armored)
/obj/effect/proc_holder/spell/targeted/shapeshift/cast(list/targets,mob/user = usr)
if(src in user.mob_spell_list)
user.mob_spell_list.Remove(src)
user.mind.AddSpell(src)
if(user.buckled)
user.buckled.unbuckle_mob(src,force=TRUE)
for(var/mob/living/M in targets)
if(!shapeshift_type)
var/list/animal_list = list()