Merge branch 'master' of https://github.com/Citadel-Station-13/Citadel-Station-13 into Ghommie-cit800
This commit is contained in:
@@ -68,7 +68,7 @@ INITIALIZE_IMMEDIATE(/mob/dead)
|
||||
set category = "OOC"
|
||||
set name = "Server Hop!"
|
||||
set desc= "Jump to the other server"
|
||||
if(notransform)
|
||||
if(mob_transforming)
|
||||
return
|
||||
var/list/csa = CONFIG_GET(keyed_list/cross_server)
|
||||
var/pick
|
||||
@@ -93,9 +93,9 @@ INITIALIZE_IMMEDIATE(/mob/dead)
|
||||
to_chat(C, "<span class='notice'>Sending you to [pick].</span>")
|
||||
new /obj/screen/splash(C)
|
||||
|
||||
notransform = TRUE
|
||||
mob_transforming = TRUE
|
||||
sleep(29) //let the animation play
|
||||
notransform = FALSE
|
||||
mob_transforming = FALSE
|
||||
|
||||
if(!C)
|
||||
return
|
||||
|
||||
@@ -176,9 +176,6 @@
|
||||
SSticker.queue_delay = 4
|
||||
qdel(src)
|
||||
|
||||
if(!ready && href_list["preference"])
|
||||
if(client)
|
||||
client.prefs.process_link(src, href_list)
|
||||
else if(!href_list["late_join"])
|
||||
new_player_panel()
|
||||
|
||||
@@ -584,6 +581,12 @@
|
||||
qdel(src)
|
||||
|
||||
/mob/dead/new_player/proc/ViewManifest()
|
||||
if(!client)
|
||||
return
|
||||
if(world.time < client.crew_manifest_delay)
|
||||
return
|
||||
client.crew_manifest_delay = world.time + (1 SECONDS)
|
||||
|
||||
var/dat = "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'></head><body>"
|
||||
dat += "<h4>Crew Manifest</h4>"
|
||||
dat += GLOB.data_core.get_manifest(OOC = 1)
|
||||
|
||||
@@ -189,6 +189,26 @@
|
||||
name = "Coffee House"
|
||||
icon_state = "hair_coffeehouse"
|
||||
|
||||
/datum/sprite_accessory/hair/cornrows1
|
||||
name = "Cornrows"
|
||||
icon_state = "hair_cornrows"
|
||||
|
||||
/datum/sprite_accessory/hair/cornrows2
|
||||
name = "Cornrows 2"
|
||||
icon_state = "hair_cornrows2"
|
||||
|
||||
/datum/sprite_accessory/hair/cornrowbun
|
||||
name = "Cornrow Bun"
|
||||
icon_state = "hair_cornrowbun"
|
||||
|
||||
/datum/sprite_accessory/hair/cornrowbraid
|
||||
name = "Cornrow Braid"
|
||||
icon_state = "hair_cornrowbraid"
|
||||
|
||||
/datum/sprite_accessory/hair/cornrowdualtail
|
||||
name = "Cornrow Tail"
|
||||
icon_state = "hair_cornrowtail"
|
||||
|
||||
/datum/sprite_accessory/hair/country
|
||||
name = "Country"
|
||||
icon_state = "hair_country"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/datum/sprite_accessory/horns
|
||||
icon = 'icons/mob/mutant_bodyparts.dmi'
|
||||
color_src = HORNCOLOR
|
||||
relevant_layers = list(BODY_ADJ_LAYER)
|
||||
relevant_layers = list(HORNS_LAYER)
|
||||
|
||||
/datum/sprite_accessory/horns/none
|
||||
name = "None"
|
||||
|
||||
@@ -717,6 +717,12 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
|
||||
set name = "View Crew Manifest"
|
||||
set category = "Ghost"
|
||||
|
||||
if(!client)
|
||||
return
|
||||
if(world.time < client.crew_manifest_delay)
|
||||
return
|
||||
client.crew_manifest_delay = world.time + (1 SECONDS)
|
||||
|
||||
var/dat
|
||||
dat += "<h4>Crew Manifest</h4>"
|
||||
dat += GLOB.data_core.get_manifest()
|
||||
|
||||
@@ -333,6 +333,7 @@
|
||||
I.moveToNullspace()
|
||||
else
|
||||
I.forceMove(newloc)
|
||||
on_item_dropped(I)
|
||||
if(I.dropped(src) == ITEM_RELOCATED_BY_DROPPED)
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
@@ -94,7 +94,9 @@
|
||||
|
||||
//We want an accurate reading of .len
|
||||
listclearnulls(BP.embedded_objects)
|
||||
temp_bleed += 0.5 * BP.embedded_objects.len
|
||||
for(var/obj/item/embeddies in BP.embedded_objects)
|
||||
if(!embeddies.isEmbedHarmless())
|
||||
temp_bleed += 0.5
|
||||
|
||||
if(brutedamage >= 20)
|
||||
temp_bleed += (brutedamage * 0.013)
|
||||
|
||||
@@ -37,10 +37,10 @@
|
||||
C.put_in_hands(B1)
|
||||
C.put_in_hands(B2)
|
||||
C.regenerate_icons()
|
||||
src.notransform = TRUE
|
||||
src.mob_transforming = TRUE
|
||||
spawn(0)
|
||||
bloodpool_sink(B)
|
||||
src.notransform = FALSE
|
||||
src.mob_transforming = FALSE
|
||||
return 1
|
||||
|
||||
/mob/living/proc/bloodpool_sink(obj/effect/decal/cleanable/B)
|
||||
@@ -155,7 +155,7 @@
|
||||
addtimer(CALLBACK(src, /atom/.proc/remove_atom_colour, TEMPORARY_COLOUR_PRIORITY, newcolor), 6 SECONDS)
|
||||
|
||||
/mob/living/proc/phasein(obj/effect/decal/cleanable/B)
|
||||
if(src.notransform)
|
||||
if(src.mob_transforming)
|
||||
to_chat(src, "<span class='warning'>Finish eating first!</span>")
|
||||
return 0
|
||||
B.visible_message("<span class='warning'>[B] starts to bubble...</span>")
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
|
||||
/mob/living/brain/Life()
|
||||
set invisibility = 0
|
||||
if (notransform)
|
||||
/mob/living/brain/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(!loc)
|
||||
return
|
||||
. = ..()
|
||||
handle_emp_damage()
|
||||
|
||||
/mob/living/brain/update_stat()
|
||||
|
||||
@@ -8,9 +8,6 @@
|
||||
/mob/living/carbon/alien/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
|
||||
return ..(AM, skipcatch = TRUE, hitpush = FALSE)
|
||||
|
||||
/mob/living/carbon/alien/can_embed(obj/item/I)
|
||||
return FALSE
|
||||
|
||||
/*Code for aliens attacking aliens. Because aliens act on a hivemind, I don't see them as very aggressive with each other.
|
||||
As such, they can either help or harm other aliens. Help works like the human help command while harm is a simple nibble.
|
||||
In all, this is a lot like the monkey code. /N
|
||||
@@ -54,6 +51,8 @@ In all, this is a lot like the monkey code. /N
|
||||
return
|
||||
switch(M.a_intent)
|
||||
if(INTENT_HELP)
|
||||
if(M == src && check_self_for_injuries())
|
||||
return
|
||||
help_shake_act(M)
|
||||
if(INTENT_GRAB)
|
||||
grabbedby(M)
|
||||
|
||||
@@ -77,20 +77,18 @@
|
||||
return TRUE
|
||||
|
||||
/mob/living/carbon/alien/humanoid/get_standard_pixel_y_offset(lying = 0)
|
||||
. = ..()
|
||||
if(leaping)
|
||||
return -32
|
||||
else if(custom_pixel_y_offset)
|
||||
return custom_pixel_y_offset
|
||||
else
|
||||
return initial(pixel_y)
|
||||
. -= 32
|
||||
if(custom_pixel_y_offset)
|
||||
. += custom_pixel_y_offset
|
||||
|
||||
/mob/living/carbon/alien/humanoid/get_standard_pixel_x_offset(lying = 0)
|
||||
. = ..()
|
||||
if(leaping)
|
||||
return -32
|
||||
else if(custom_pixel_x_offset)
|
||||
return custom_pixel_x_offset
|
||||
else
|
||||
return initial(pixel_x)
|
||||
. -= 32
|
||||
if(custom_pixel_x_offset)
|
||||
. += custom_pixel_x_offset
|
||||
|
||||
/mob/living/carbon/alien/humanoid/get_permeability_protection(list/target_zones)
|
||||
return 0.8
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
|
||||
|
||||
/mob/living/carbon/alien/larva/Life()
|
||||
set invisibility = 0
|
||||
if (notransform)
|
||||
/mob/living/carbon/alien/larva/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(..()) //not dead
|
||||
// GROW!
|
||||
if(amount_grown < max_grown)
|
||||
amount_grown++
|
||||
update_icons()
|
||||
// GROW!
|
||||
if(amount_grown < max_grown)
|
||||
amount_grown++
|
||||
update_icons()
|
||||
|
||||
|
||||
/mob/living/carbon/alien/larva/update_stat()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/mob/living/carbon/alien/Life()
|
||||
/mob/living/carbon/alien/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
findQueen()
|
||||
return..()
|
||||
|
||||
/mob/living/carbon/alien/check_breath(datum/gas_mixture/breath)
|
||||
if(status_flags & GODMODE)
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
ghost.transfer_ckey(new_xeno, FALSE)
|
||||
SEND_SOUND(new_xeno, sound('sound/voice/hiss5.ogg',0,0,0,100)) //To get the player's attention
|
||||
new_xeno.Paralyze(6)
|
||||
new_xeno.notransform = TRUE
|
||||
new_xeno.mob_transforming = TRUE
|
||||
new_xeno.invisibility = INVISIBILITY_MAXIMUM
|
||||
|
||||
sleep(6)
|
||||
@@ -102,7 +102,7 @@
|
||||
|
||||
if(new_xeno)
|
||||
new_xeno.SetParalyzed(0)
|
||||
new_xeno.notransform = FALSE
|
||||
new_xeno.mob_transforming = FALSE
|
||||
new_xeno.invisibility = 0
|
||||
|
||||
var/mob/living/carbon/old_owner = owner
|
||||
|
||||
@@ -247,7 +247,7 @@
|
||||
/mob/living/carbon/Topic(href, href_list)
|
||||
..()
|
||||
//strip panel
|
||||
if(usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY))
|
||||
if(usr.canUseTopic(src, BE_CLOSE))
|
||||
if(href_list["internal"] && !HAS_TRAIT(src, TRAIT_NO_INTERNALS))
|
||||
var/slot = text2num(href_list["internal"])
|
||||
var/obj/item/ITEM = get_item_by_slot(slot)
|
||||
@@ -267,7 +267,15 @@
|
||||
visible_message("<span class='danger'>[usr] [internal ? "opens" : "closes"] the valve on [src]'s [ITEM.name].</span>", \
|
||||
"<span class='userdanger'>[usr] [internal ? "opens" : "closes"] the valve on your [ITEM.name].</span>", \
|
||||
target = usr, target_message = "<span class='danger'>You [internal ? "opens" : "closes"] the valve on [src]'s [ITEM.name].</span>")
|
||||
|
||||
if(href_list["embedded_object"] && usr.canUseTopic(src, BE_CLOSE))
|
||||
var/obj/item/bodypart/L = locate(href_list["embedded_limb"]) in bodyparts
|
||||
if(!L)
|
||||
return
|
||||
var/obj/item/I = locate(href_list["embedded_object"]) in L.embedded_objects
|
||||
if(!I || I.loc != src) //no item, no limb, or item is not in limb or in the person anymore
|
||||
return
|
||||
SEND_SIGNAL(src, COMSIG_CARBON_EMBED_RIP, I, L)
|
||||
return
|
||||
|
||||
/mob/living/carbon/fall(forced)
|
||||
loc.handle_fall(src, forced)//it's loc so it doesn't call the mob's handle_fall which does nothing
|
||||
@@ -432,10 +440,9 @@
|
||||
return
|
||||
|
||||
/mob/living/carbon/get_standard_pixel_y_offset(lying = 0)
|
||||
. = ..()
|
||||
if(lying)
|
||||
return -6
|
||||
else
|
||||
return initial(pixel_y)
|
||||
. -= 6
|
||||
|
||||
/mob/living/carbon/proc/accident(obj/item/I)
|
||||
if(!I || (I.item_flags & ABSTRACT) || HAS_TRAIT(I, TRAIT_NODROP))
|
||||
|
||||
@@ -65,22 +65,13 @@
|
||||
throw_mode_off()
|
||||
return TRUE
|
||||
|
||||
/mob/living/carbon/embed_item(obj/item/I)
|
||||
throw_alert("embeddedobject", /obj/screen/alert/embeddedobject)
|
||||
var/obj/item/bodypart/L = pick(bodyparts)
|
||||
L.embedded_objects |= I
|
||||
I.add_mob_blood(src)//it embedded itself in you, of course it's bloody!
|
||||
I.forceMove(src)
|
||||
I.embedded()
|
||||
L.receive_damage(I.w_class*I.embedding.embedded_impact_pain_multiplier)
|
||||
visible_message("<span class='danger'>[I] embeds itself in [src]'s [L.name]!</span>","<span class='userdanger'>[I] embeds itself in your [L.name]!</span>")
|
||||
SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "embedded", /datum/mood_event/embedded)
|
||||
|
||||
/mob/living/carbon/attacked_by(obj/item/I, mob/living/user)
|
||||
var/totitemdamage = pre_attacked_by(I, user)
|
||||
/mob/living/carbon/attacked_by(obj/item/I, mob/living/user, attackchain_flags = NONE, damage_multiplier = 1)
|
||||
var/totitemdamage = pre_attacked_by(I, user) * damage_multiplier
|
||||
var/impacting_zone = (user == src)? check_zone(user.zone_selected) : ran_zone(user.zone_selected)
|
||||
if((user != src) && (mob_run_block(I, totitemdamage, "the [I]", ATTACK_TYPE_MELEE, I.armour_penetration, user, impacting_zone, null) & BLOCK_SUCCESS))
|
||||
var/list/block_return = list()
|
||||
if((user != src) && (mob_run_block(I, totitemdamage, "the [I]", ((attackchain_flags & ATTACKCHAIN_PARRY_COUNTERATTACK)? ATTACK_TYPE_PARRY_COUNTERATTACK : NONE) | ATTACK_TYPE_MELEE, I.armour_penetration, user, impacting_zone, block_return) & BLOCK_SUCCESS))
|
||||
return FALSE
|
||||
totitemdamage = block_calculate_resultant_damage(totitemdamage, block_return)
|
||||
var/obj/item/bodypart/affecting = get_bodypart(impacting_zone)
|
||||
if(!affecting) //missing limb? we select the first bodypart (you can never have zero, because of chest)
|
||||
affecting = bodyparts[1]
|
||||
@@ -272,6 +263,9 @@
|
||||
to_chat(M, "<span class='warning'>You can't put [p_them()] out with just your bare hands!</span>")
|
||||
return
|
||||
|
||||
if(M == src && check_self_for_injuries())
|
||||
return
|
||||
|
||||
if(health >= 0 && !(HAS_TRAIT(src, TRAIT_FAKEDEATH)))
|
||||
var/friendly_check = FALSE
|
||||
if(mob_run_block(M, 0, M.name, ATTACK_TYPE_UNARMED, 0, null, null, null))
|
||||
@@ -339,6 +333,26 @@
|
||||
update_mobility()
|
||||
playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1)
|
||||
|
||||
/// Check ourselves to see if we've got any shrapnel, return true if we do. This is a much simpler version of what humans do, we only indicate we're checking ourselves if there's actually shrapnel
|
||||
/mob/living/carbon/proc/check_self_for_injuries()
|
||||
if(stat == DEAD || stat == UNCONSCIOUS)
|
||||
return
|
||||
|
||||
var/embeds = FALSE
|
||||
for(var/X in bodyparts)
|
||||
var/obj/item/bodypart/LB = X
|
||||
for(var/obj/item/I in LB.embedded_objects)
|
||||
if(!embeds)
|
||||
embeds = TRUE
|
||||
// this way, we only visibly try to examine ourselves if we have something embedded, otherwise we'll still hug ourselves :)
|
||||
visible_message("<span class='notice'>[src] examines [p_them()]self.</span>", \
|
||||
"<span class='notice'>You check yourself for shrapnel.</span>")
|
||||
if(I.isEmbedHarmless())
|
||||
to_chat(src, "\t <a href='?src=[REF(src)];embedded_object=[REF(I)];embedded_limb=[REF(LB)]' class='warning'>There is \a [I] stuck to your [LB.name]!</a>")
|
||||
else
|
||||
to_chat(src, "\t <a href='?src=[REF(src)];embedded_object=[REF(I)];embedded_limb=[REF(LB)]' class='warning'>There is \a [I] embedded in your [LB.name]!</a>")
|
||||
|
||||
return embeds
|
||||
|
||||
/mob/living/carbon/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0)
|
||||
. = ..()
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
if(istype(J) && (movement_dir || J.stabilizers) && J.allow_thrust(0.01, src))
|
||||
return 1
|
||||
|
||||
/mob/living/carbon/Move(NewLoc, direct)
|
||||
/mob/living/carbon/Moved()
|
||||
. = ..()
|
||||
if(. && (movement_type & FLOATING)) //floating is easy
|
||||
if(HAS_TRAIT(src, TRAIT_NOHUNGER))
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
|
||||
|
||||
/mob/living/carbon/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE)
|
||||
/mob/living/carbon/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE)
|
||||
SEND_SIGNAL(src, COMSIG_MOB_APPLY_DAMGE, damage, damagetype, def_zone)
|
||||
var/hit_percent = (100-blocked)/100
|
||||
if(!forced && hit_percent <= 0)
|
||||
return 0
|
||||
|
||||
var/obj/item/bodypart/BP = null
|
||||
if(isbodypart(def_zone)) //we specified a bodypart object
|
||||
BP = def_zone
|
||||
else
|
||||
if(!def_zone)
|
||||
def_zone = ran_zone(def_zone)
|
||||
BP = get_bodypart(check_zone(def_zone))
|
||||
if(!BP)
|
||||
BP = bodyparts[1]
|
||||
if(!spread_damage)
|
||||
if(isbodypart(def_zone)) //we specified a bodypart object
|
||||
BP = def_zone
|
||||
else
|
||||
if(!def_zone)
|
||||
def_zone = ran_zone(def_zone)
|
||||
BP = get_bodypart(check_zone(def_zone))
|
||||
if(!BP)
|
||||
BP = bodyparts[1]
|
||||
|
||||
var/damage_amount = forced ? damage : damage * hit_percent
|
||||
switch(damagetype)
|
||||
|
||||
@@ -31,14 +31,35 @@
|
||||
else if(get_bodypart(BODY_ZONE_HEAD))
|
||||
. += "<span class='deadsay'>It appears that [t_his] brain is missing...</span>"
|
||||
|
||||
var/list/missing = get_missing_limbs()
|
||||
var/list/msg = list("<span class='warning'>")
|
||||
var/list/missing = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG)
|
||||
var/list/disabled = list()
|
||||
for(var/X in bodyparts)
|
||||
var/obj/item/bodypart/BP = X
|
||||
if(BP.disabled)
|
||||
disabled += BP
|
||||
missing -= BP.body_zone
|
||||
for(var/obj/item/I in BP.embedded_objects)
|
||||
if(I.isEmbedHarmless())
|
||||
msg += "<B>[t_He] [t_has] \a [icon2html(I, user)] [I] stuck to [t_his] [BP.name]!</B>\n"
|
||||
else
|
||||
msg += "<B>[t_He] [t_has] \a [icon2html(I, user)] [I] embedded in [t_his] [BP.name]!</B>\n"
|
||||
|
||||
for(var/X in disabled)
|
||||
var/obj/item/bodypart/BP = X
|
||||
var/damage_text
|
||||
if(!(BP.get_damage(include_stamina = FALSE) >= BP.max_damage)) //Stamina is disabling the limb
|
||||
damage_text = "limp and lifeless"
|
||||
else
|
||||
damage_text = (BP.brute_dam >= BP.burn_dam) ? BP.heavy_brute_msg : BP.heavy_burn_msg
|
||||
msg += "<B>[capitalize(t_his)] [BP.name] is [damage_text]!</B>\n"
|
||||
|
||||
for(var/t in missing)
|
||||
if(t==BODY_ZONE_HEAD)
|
||||
. += "<span class='deadsay'><B>[t_His] [parse_zone(t)] is missing!</B></span>"
|
||||
msg += "<span class='deadsay'><B>[t_His] [parse_zone(t)] is missing!</B></span>\n"
|
||||
continue
|
||||
. += "<span class='warning'><B>[t_His] [parse_zone(t)] is missing!</B></span>"
|
||||
msg += "<span class='warning'><B>[t_His] [parse_zone(t)] is missing!</B></span>\n"
|
||||
|
||||
var/list/msg = list()
|
||||
var/temp = getBruteLoss()
|
||||
if(!(user == src && src.hal_screwyhud == SCREWYHUD_HEALTHY)) //fake healthy
|
||||
if(temp)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
|
||||
/mob/living/carbon/human/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE)
|
||||
/mob/living/carbon/human/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage)
|
||||
// depending on the species, it will run the corresponding apply_damage code there
|
||||
return dna.species.apply_damage(damage, damagetype, def_zone, blocked, src, forced)
|
||||
return dna.species.apply_damage(damage, damagetype, def_zone, blocked, src, forced, spread_damage)
|
||||
|
||||
@@ -159,7 +159,10 @@
|
||||
disabled += BP
|
||||
missing -= BP.body_zone
|
||||
for(var/obj/item/I in BP.embedded_objects)
|
||||
msg += "<B>[t_He] [t_has] \a [icon2html(I, user)] [I] embedded in [t_his] [BP.name]!</B>\n"
|
||||
if(I.isEmbedHarmless())
|
||||
msg += "<B>[t_He] [t_has] \a [icon2html(I, user)] [I] stuck to [t_his] [BP.name]!</B>\n"
|
||||
else
|
||||
msg += "<B>[t_He] [t_has] \a [icon2html(I, user)] [I] embedded in [t_his] [BP.name]!</B>\n"
|
||||
|
||||
for(var/X in disabled)
|
||||
var/obj/item/bodypart/BP = X
|
||||
|
||||
@@ -220,22 +220,7 @@
|
||||
var/obj/item/I = locate(href_list["embedded_object"]) in L.embedded_objects
|
||||
if(!I || I.loc != src) //no item, no limb, or item is not in limb or in the person anymore
|
||||
return
|
||||
var/time_taken = I.embedding.embedded_unsafe_removal_time/I.w_class //Citadel Change from * to /
|
||||
usr.visible_message("<span class='warning'>[usr] attempts to remove [I] from [usr.p_their()] [L.name].</span>","<span class='notice'>You attempt to remove [I] from your [L.name]... (It will take [DisplayTimeText(time_taken)].)</span>")
|
||||
if(do_after(usr, time_taken, needhand = 1, target = src))
|
||||
remove_embedded_unsafe(L, I, usr)
|
||||
/* CITADEL EDIT: remove_embedded_unsafe replaces this code
|
||||
if(!I || !L || I.loc != src || !(I in L.embedded_objects))
|
||||
return
|
||||
L.embedded_objects -= I
|
||||
L.receive_damage(I.embedding.embedded_unsafe_removal_pain_multiplier*I.w_class)//It hurts to rip it out, get surgery you dingus.
|
||||
I.forceMove(get_turf(src))
|
||||
usr.put_in_hands(I)
|
||||
usr.emote("scream")
|
||||
usr.visible_message("[usr] successfully rips [I] out of [usr.p_their()] [L.name]!","<span class='notice'>You successfully remove [I] from your [L.name].</span>")
|
||||
if(!has_embedded_objects())
|
||||
clear_alert("embeddedobject")
|
||||
SEND_SIGNAL(usr, COMSIG_CLEAR_MOOD_EVENT, "embedded") */
|
||||
SEND_SIGNAL(src, COMSIG_CARBON_EMBED_RIP, I, L)
|
||||
return
|
||||
|
||||
if(href_list["item"])
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
/mob/living/carbon/human/get_blocking_items()
|
||||
. = ..()
|
||||
if(wear_suit)
|
||||
. |= wear_suit
|
||||
if(!.[wear_suit])
|
||||
.[wear_suit] = wear_suit.block_priority
|
||||
if(w_uniform)
|
||||
. |= w_uniform
|
||||
if(!.[w_uniform])
|
||||
.[w_uniform] = w_uniform.block_priority
|
||||
if(wear_neck)
|
||||
. |= wear_neck
|
||||
if(!.[wear_neck])
|
||||
.[wear_neck] = wear_neck.block_priority
|
||||
|
||||
@@ -52,11 +52,6 @@
|
||||
return martial_art_result
|
||||
return ..()
|
||||
|
||||
/mob/living/carbon/human/can_embed(obj/item/I)
|
||||
if(I.get_sharpness() || is_pointed(I) || is_type_in_typecache(I, GLOB.can_embed_types))
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/mob/living/carbon/human/proc/check_martial_melee_block()
|
||||
if(mind)
|
||||
if(mind.martial_art && prob(mind.martial_art.block_chance) && mind.martial_art.can_use(src) && in_throw_mode && !incapacitated(FALSE, TRUE))
|
||||
@@ -78,7 +73,7 @@
|
||||
..()
|
||||
|
||||
|
||||
/mob/living/carbon/human/attacked_by(obj/item/I, mob/living/user)
|
||||
/mob/living/carbon/human/attacked_by(obj/item/I, mob/living/user, attackchain_flags = NONE, damage_multiplier = 1)
|
||||
if(!I || !user)
|
||||
return 0
|
||||
|
||||
@@ -95,7 +90,7 @@
|
||||
SSblackbox.record_feedback("tally", "zone_targeted", 1, target_area)
|
||||
|
||||
// the attacked_by code varies among species
|
||||
return dna.species.spec_attacked_by(I, user, affecting, a_intent, src)
|
||||
return dna.species.spec_attacked_by(I, user, affecting, a_intent, src, attackchain_flags, damage_multiplier)
|
||||
|
||||
/mob/living/carbon/human/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE)
|
||||
if(user.a_intent == INTENT_HARM)
|
||||
@@ -214,7 +209,7 @@
|
||||
/mob/living/carbon/human/attack_animal(mob/living/simple_animal/M)
|
||||
. = ..()
|
||||
if(.)
|
||||
var/damage = rand(M.melee_damage_lower, M.melee_damage_upper)
|
||||
var/damage = .
|
||||
var/dam_zone = dismembering_strike(M, pick(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG))
|
||||
if(!dam_zone) //Dismemberment successful
|
||||
return TRUE
|
||||
@@ -620,7 +615,10 @@
|
||||
to_send += "\t <span class='[no_damage ? "notice" : "warning"]'>Your [LB.name] [HAS_TRAIT(src, TRAIT_SELF_AWARE) ? "has" : "is"] [status].</span>\n"
|
||||
|
||||
for(var/obj/item/I in LB.embedded_objects)
|
||||
to_send += "\t <a href='?src=[REF(src)];embedded_object=[REF(I)];embedded_limb=[REF(LB)]' class='warning'>There is \a [I] embedded in your [LB.name]!</a>\n"
|
||||
if(I.isEmbedHarmless())
|
||||
to_chat(src, "\t <a href='?src=[REF(src)];embedded_object=[REF(I)];embedded_limb=[REF(LB)]' class='warning'>There is \a [I] stuck to your [LB.name]!</a>")
|
||||
else
|
||||
to_chat(src, "\t <a href='?src=[REF(src)];embedded_object=[REF(I)];embedded_limb=[REF(LB)]' class='warning'>There is \a [I] embedded in your [LB.name]!</a>")
|
||||
|
||||
for(var/t in missing)
|
||||
to_send += "<span class='boldannounce'>Your [parse_zone(t)] is missing!</span>\n"
|
||||
|
||||
@@ -18,36 +18,21 @@
|
||||
#define THERMAL_PROTECTION_HAND_LEFT 0.025
|
||||
#define THERMAL_PROTECTION_HAND_RIGHT 0.025
|
||||
|
||||
/mob/living/carbon/human/Life(seconds, times_fired)
|
||||
set invisibility = 0
|
||||
if (notransform)
|
||||
/mob/living/carbon/human/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
handle_active_genes()
|
||||
//heart attack stuff
|
||||
handle_heart()
|
||||
dna.species.spec_life(src) // for mutantraces
|
||||
return (stat != DEAD) && !QDELETED(src)
|
||||
|
||||
. = ..()
|
||||
|
||||
if (QDELETED(src))
|
||||
return 0
|
||||
|
||||
if(.) //not dead
|
||||
handle_active_genes()
|
||||
|
||||
if(stat != DEAD)
|
||||
//heart attack stuff
|
||||
handle_heart()
|
||||
|
||||
if(stat != DEAD)
|
||||
//Stuff jammed in your limbs hurts
|
||||
handle_embedded_objects()
|
||||
|
||||
/mob/living/carbon/human/PhysicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
//Update our name based on whether our face is obscured/disfigured
|
||||
name = get_visible_name()
|
||||
|
||||
dna.species.spec_life(src) // for mutantraces
|
||||
|
||||
if(stat != DEAD)
|
||||
return 1
|
||||
|
||||
|
||||
/mob/living/carbon/human/calculate_affecting_pressure(pressure)
|
||||
var/headless = !get_bodypart(BODY_ZONE_HEAD) //should the mob be perennially headless (see dullahans), we only take the suit into account, so they can into space.
|
||||
if (wear_suit && istype(wear_suit, /obj/item/clothing) && (headless || (head && istype(head, /obj/item/clothing))))
|
||||
@@ -303,25 +288,6 @@
|
||||
return TRUE
|
||||
return ..()
|
||||
|
||||
|
||||
/mob/living/carbon/human/proc/handle_embedded_objects()
|
||||
for(var/X in bodyparts)
|
||||
var/obj/item/bodypart/BP = X
|
||||
for(var/obj/item/I in BP.embedded_objects)
|
||||
if(prob(I.embedding.embedded_pain_chance))
|
||||
BP.receive_damage(I.w_class*I.embedding.embedded_pain_multiplier)
|
||||
to_chat(src, "<span class='userdanger'>[I] embedded in your [BP.name] hurts!</span>")
|
||||
|
||||
if(prob(I.embedding.embedded_fall_chance))
|
||||
BP.receive_damage(I.w_class*I.embedding.embedded_fall_pain_multiplier)
|
||||
BP.embedded_objects -= I
|
||||
I.forceMove(drop_location())
|
||||
I.unembedded()
|
||||
visible_message("<span class='danger'>[I] falls out of [name]'s [BP.name]!</span>","<span class='userdanger'>[I] falls out of your [BP.name]!</span>")
|
||||
if(!has_embedded_objects())
|
||||
clear_alert("embeddedobject")
|
||||
SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "embedded")
|
||||
|
||||
/mob/living/carbon/human/proc/handle_active_genes()
|
||||
if(HAS_TRAIT(src, TRAIT_MUTATION_STASIS))
|
||||
return
|
||||
|
||||
@@ -628,6 +628,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
H.remove_overlay(BODY_ADJ_LAYER)
|
||||
H.remove_overlay(BODY_ADJ_UPPER_LAYER)
|
||||
H.remove_overlay(BODY_FRONT_LAYER)
|
||||
H.remove_overlay(HORNS_LAYER)
|
||||
|
||||
if(!mutant_bodyparts)
|
||||
return
|
||||
@@ -839,8 +840,13 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
if(!S.mutant_part_string)
|
||||
dna_feature_as_text_string[S] = bodypart
|
||||
|
||||
var/static/list/layer_text = list("[BODY_BEHIND_LAYER]" = "BEHIND", "[BODY_ADJ_LAYER]" = "ADJ", \
|
||||
"[BODY_ADJ_UPPER_LAYER]" = "ADJUP", "[BODY_FRONT_LAYER]" = "FRONT")
|
||||
var/static/list/layer_text = list(
|
||||
"[BODY_BEHIND_LAYER]" = "BEHIND",
|
||||
"[BODY_ADJ_LAYER]" = "ADJ",
|
||||
"[BODY_ADJ_UPPER_LAYER]" = "ADJUP",
|
||||
"[BODY_FRONT_LAYER]" = "FRONT",
|
||||
"[HORNS_LAYER]" = "HORNS",
|
||||
)
|
||||
|
||||
var/g = (H.dna.features["body_model"] == FEMALE) ? "f" : "m"
|
||||
var/list/colorlist = list()
|
||||
@@ -1020,6 +1026,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
H.apply_overlay(BODY_ADJ_LAYER)
|
||||
H.apply_overlay(BODY_ADJ_UPPER_LAYER)
|
||||
H.apply_overlay(BODY_FRONT_LAYER)
|
||||
H.apply_overlay(HORNS_LAYER)
|
||||
|
||||
|
||||
/*
|
||||
@@ -1485,12 +1492,12 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
|
||||
var/miss_chance = 100//calculate the odds that a punch misses entirely. considers stamina and brute damage of the puncher. punches miss by default to prevent weird cases
|
||||
if(user.dna.species.punchdamagelow)
|
||||
if(HAS_TRAIT(user, TRAIT_PUGILIST)) //pugilists have a flat 10% miss chance
|
||||
miss_chance = 10
|
||||
if(atk_verb == ATTACK_EFFECT_KICK) //kicks never miss (provided your species deals more than 0 damage)
|
||||
miss_chance = 0
|
||||
else if(HAS_TRAIT(user, TRAIT_PUGILIST)) //pugilists have a flat 10% miss chance
|
||||
miss_chance = 10
|
||||
else
|
||||
miss_chance = min(10 + ((puncherstam + puncherbrute)*0.5), 100) //probability of miss has a base of 10, and modified based on half brute total. Capped at max 100 to prevent weirdness in prob()
|
||||
miss_chance = min(10 + max(puncherstam * 0.5, puncherbrute * 0.5), 100) //probability of miss has a base of 10, and modified based on half brute total. Capped at max 100 to prevent weirdness in prob()
|
||||
|
||||
if(!damage || !affecting || prob(miss_chance))//future-proofing for species that have 0 damage/weird cases where no zone is targeted
|
||||
playsound(target.loc, user.dna.species.miss_sound, 25, TRUE, -1)
|
||||
@@ -1693,12 +1700,14 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
if("disarm")
|
||||
disarm(M, H, attacker_style)
|
||||
|
||||
/datum/species/proc/spec_attacked_by(obj/item/I, mob/living/user, obj/item/bodypart/affecting, intent, mob/living/carbon/human/H)
|
||||
var/totitemdamage = H.pre_attacked_by(I, user)
|
||||
/datum/species/proc/spec_attacked_by(obj/item/I, mob/living/user, obj/item/bodypart/affecting, intent, mob/living/carbon/human/H, attackchain_flags = NONE, damage_multiplier = 1)
|
||||
var/totitemdamage = H.pre_attacked_by(I, user) * damage_multiplier
|
||||
// Allows you to put in item-specific reactions based on species
|
||||
if(user != H)
|
||||
if(H.mob_run_block(I, totitemdamage, "the [I.name]", ATTACK_TYPE_MELEE, I.armour_penetration, user, affecting.body_zone, null) & BLOCK_SUCCESS)
|
||||
var/list/block_return = list()
|
||||
if(H.mob_run_block(I, totitemdamage, "the [I.name]", ((attackchain_flags & ATTACKCHAIN_PARRY_COUNTERATTACK)? ATTACK_TYPE_PARRY_COUNTERATTACK : NONE) | ATTACK_TYPE_MELEE, I.armour_penetration, user, affecting.body_zone, block_return) & BLOCK_SUCCESS)
|
||||
return 0
|
||||
totitemdamage = block_calculate_resultant_damage(totitemdamage, block_return)
|
||||
if(H.check_martial_melee_block())
|
||||
H.visible_message("<span class='warning'>[H] blocks [I]!</span>")
|
||||
return 0
|
||||
@@ -1713,6 +1722,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
var/armor_block = H.run_armor_check(affecting, "melee", "<span class='notice'>Your armor has protected your [hit_area].</span>", "<span class='notice'>Your armor has softened a hit to your [hit_area].</span>",I.armour_penetration)
|
||||
armor_block = min(90,armor_block) //cap damage reduction at 90%
|
||||
var/Iforce = I.force //to avoid runtimes on the forcesay checks at the bottom. Some items might delete themselves if you drop them. (stunning yourself, ninja swords)
|
||||
|
||||
var/weakness = H.check_weakness(I, user)
|
||||
apply_damage(totitemdamage * weakness, I.damtype, def_zone, armor_block, H) //CIT CHANGE - replaces I.force with totitemdamage
|
||||
|
||||
@@ -1929,7 +1939,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
append_message += ", causing them to drop [target_held_item]"
|
||||
log_combat(user, target, "shoved", append_message)
|
||||
|
||||
/datum/species/proc/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, forced = FALSE)
|
||||
/datum/species/proc/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, forced = FALSE, spread_damage = FALSE)
|
||||
SEND_SIGNAL(src, COMSIG_MOB_APPLY_DAMGE, damage, damagetype, def_zone)
|
||||
var/hit_percent = (100-(blocked+armor))/100
|
||||
hit_percent = (hit_percent * (100-H.physiology.damage_resistance))/100
|
||||
@@ -1937,20 +1947,20 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
return 0
|
||||
|
||||
var/obj/item/bodypart/BP = null
|
||||
if(isbodypart(def_zone))
|
||||
if(damagetype == STAMINA && istype(def_zone, /obj/item/bodypart/head))
|
||||
BP = H.get_bodypart(check_zone(BODY_ZONE_CHEST))
|
||||
if(!spread_damage)
|
||||
if(isbodypart(def_zone))
|
||||
if(damagetype == STAMINA && istype(def_zone, /obj/item/bodypart/head))
|
||||
BP = H.get_bodypart(check_zone(BODY_ZONE_CHEST))
|
||||
else
|
||||
BP = def_zone
|
||||
else
|
||||
BP = def_zone
|
||||
else
|
||||
if(!def_zone)
|
||||
def_zone = ran_zone(def_zone)
|
||||
if(damagetype == STAMINA && def_zone == BODY_ZONE_HEAD)
|
||||
def_zone = BODY_ZONE_CHEST
|
||||
BP = H.get_bodypart(check_zone(def_zone))
|
||||
|
||||
if(!BP)
|
||||
BP = H.bodyparts[1]
|
||||
if(!def_zone)
|
||||
def_zone = ran_zone(def_zone)
|
||||
if(damagetype == STAMINA && def_zone == BODY_ZONE_HEAD)
|
||||
def_zone = BODY_ZONE_CHEST
|
||||
BP = H.get_bodypart(check_zone(def_zone))
|
||||
if(!BP)
|
||||
BP = H.bodyparts[1]
|
||||
|
||||
switch(damagetype)
|
||||
if(BRUTE)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/datum/species/insect
|
||||
name = "Anthromorphic Insect"
|
||||
id = "insect"
|
||||
say_mod = "chitters"
|
||||
default_color = "00FF00"
|
||||
species_traits = list(LIPS,EYECOLOR,HAIR,FACEHAIR,MUTCOLORS,HORNCOLOR,WINGCOLOR)
|
||||
inherent_biotypes = MOB_ORGANIC|MOB_HUMANOID|MOB_BUG
|
||||
@@ -13,6 +14,7 @@
|
||||
liked_food = MEAT | FRUIT
|
||||
disliked_food = TOXIC
|
||||
icon_limbs = DEFAULT_BODYPART_ICON_CITADEL
|
||||
exotic_bloodtype = "BUG"
|
||||
|
||||
/datum/species/insect/spec_death(gibbed, mob/living/carbon/human/H)
|
||||
if(H)
|
||||
@@ -42,4 +44,4 @@
|
||||
H.update_body()
|
||||
|
||||
/datum/species/insect/qualifies_for_rank(rank, list/features)
|
||||
return TRUE
|
||||
return TRUE
|
||||
|
||||
@@ -280,25 +280,15 @@
|
||||
. = ..()
|
||||
C.faction |= "plants"
|
||||
C.faction |= "vines"
|
||||
C.AddElement(/datum/element/photosynthesis)
|
||||
|
||||
/datum/species/golem/wood/on_species_loss(mob/living/carbon/C)
|
||||
. = ..()
|
||||
C.faction -= "plants"
|
||||
C.faction -= "vines"
|
||||
C.RemoveElement(/datum/element/photosynthesis)
|
||||
|
||||
/datum/species/golem/wood/spec_life(mob/living/carbon/human/H)
|
||||
if(H.stat == DEAD)
|
||||
return
|
||||
var/light_amount = 0 //how much light there is in the place, affects receiving nutrition and healing
|
||||
if(isturf(H.loc)) //else, there's considered to be no light
|
||||
var/turf/T = H.loc
|
||||
light_amount = min(1,T.get_lumcount()) - 0.5
|
||||
H.adjust_nutrition(light_amount * 4, NUTRITION_LEVEL_FULL)
|
||||
if(light_amount > 0.2) //if there's enough light, heal
|
||||
H.heal_overall_damage(1,1)
|
||||
H.adjustToxLoss(-1)
|
||||
H.adjustOxyLoss(-1)
|
||||
|
||||
if(H.nutrition < NUTRITION_LEVEL_STARVING + 50)
|
||||
H.take_overall_damage(2,0)
|
||||
|
||||
|
||||
@@ -239,7 +239,7 @@
|
||||
"<span class='notice'>You focus intently on moving your body while \
|
||||
standing perfectly still...</span>")
|
||||
|
||||
H.notransform = TRUE
|
||||
H.mob_transforming = TRUE
|
||||
|
||||
if(do_after(owner, delay=60, needhand=FALSE, target=owner, progress=TRUE))
|
||||
if(H.blood_volume >= BLOOD_VOLUME_SLIME_SPLIT)
|
||||
@@ -249,7 +249,7 @@
|
||||
else
|
||||
to_chat(H, "<span class='warning'>...but fail to stand perfectly still!</span>")
|
||||
|
||||
H.notransform = FALSE
|
||||
H.mob_transforming = FALSE
|
||||
|
||||
/datum/action/innate/split_body/proc/make_dupe()
|
||||
var/mob/living/carbon/human/H = owner
|
||||
@@ -267,7 +267,7 @@
|
||||
spare.Move(get_step(H.loc, pick(NORTH,SOUTH,EAST,WEST)))
|
||||
|
||||
H.blood_volume *= 0.45
|
||||
H.notransform = 0
|
||||
H.mob_transforming = 0
|
||||
|
||||
var/datum/species/jelly/slime/origin_datum = H.dna.species
|
||||
origin_datum.bodies |= spare
|
||||
@@ -459,8 +459,18 @@
|
||||
|
||||
/datum/action/innate/slime_change/proc/change_form()
|
||||
var/mob/living/carbon/human/H = owner
|
||||
var/select_alteration = input(owner, "Select what part of your form to alter", "Form Alteration", "cancel") in list("Hair Style", "Genitals", "Tail", "Snout", "Markings", "Ears", "Taur body", "Penis", "Vagina", "Penis Length", "Breast Size", "Breast Shape", "Cancel")
|
||||
if(select_alteration == "Hair Style")
|
||||
var/select_alteration = input(owner, "Select what part of your form to alter", "Form Alteration", "cancel") in list("Body Color","Hair Style", "Genitals", "Tail", "Snout", "Markings", "Ears", "Taur body", "Penis", "Vagina", "Penis Length", "Breast Size", "Breast Shape", "Cancel")
|
||||
|
||||
if(select_alteration == "Body Color")
|
||||
var/new_color = input(owner, "Choose your skin color:", "Race change","#"+H.dna.features["mcolor"]) as color|null
|
||||
if(new_color)
|
||||
var/temp_hsv = RGBtoHSV(new_color)
|
||||
if(ReadHSV(temp_hsv)[3] >= ReadHSV("#7F7F7F")[3]) // mutantcolors must be bright
|
||||
H.dna.features["mcolor"] = sanitize_hexcolor(new_color, 6)
|
||||
H.update_body()
|
||||
else
|
||||
to_chat(H, "<span class='notice'>Invalid color. Your color is not bright enough.</span>")
|
||||
else if(select_alteration == "Hair Style")
|
||||
if(H.gender == MALE)
|
||||
var/new_style = input(owner, "Select a facial hair style", "Hair Alterations") as null|anything in GLOB.facial_hair_styles_list
|
||||
if(new_style)
|
||||
|
||||
@@ -14,34 +14,24 @@
|
||||
liked_food = VEGETABLES | FRUIT | GRAIN
|
||||
species_language_holder = /datum/language_holder/sylvan
|
||||
var/light_nutrition_gain_factor = 4
|
||||
var/light_toxheal = 1
|
||||
var/light_oxyheal = 1
|
||||
var/light_burnheal = 1
|
||||
var/light_bruteheal = 1
|
||||
var/light_toxheal = -1
|
||||
var/light_oxyheal = -1
|
||||
var/light_burnheal = -1
|
||||
var/light_bruteheal = -1
|
||||
|
||||
/datum/species/pod/on_species_gain(mob/living/carbon/C, datum/species/old_species)
|
||||
. = ..()
|
||||
C.faction |= "plants"
|
||||
C.faction |= "vines"
|
||||
C.AddElement(/datum/element/photosynthesis, light_bruteheal, light_burnheal, light_toxheal, light_oxyheal, light_nutrition_gain_factor)
|
||||
|
||||
/datum/species/pod/on_species_loss(mob/living/carbon/C)
|
||||
. = ..()
|
||||
C.faction -= "plants"
|
||||
C.faction -= "vines"
|
||||
C.RemoveElement(/datum/element/photosynthesis, light_bruteheal, light_burnheal, light_toxheal, light_oxyheal, light_nutrition_gain_factor)
|
||||
|
||||
/datum/species/pod/spec_life(mob/living/carbon/human/H)
|
||||
if(H.stat == DEAD)
|
||||
return
|
||||
var/light_amount = 0 //how much light there is in the place, affects receiving nutrition and healing
|
||||
if(isturf(H.loc)) //else, there's considered to be no light
|
||||
var/turf/T = H.loc
|
||||
light_amount = min(1,T.get_lumcount()) - 0.5
|
||||
H.adjust_nutrition(light_amount * light_nutrition_gain_factor, NUTRITION_LEVEL_FULL)
|
||||
if(light_amount > 0.2) //if there's enough light, heal
|
||||
H.heal_overall_damage(light_bruteheal, light_burnheal)
|
||||
H.adjustToxLoss(-light_toxheal)
|
||||
H.adjustOxyLoss(-light_oxyheal)
|
||||
|
||||
if(H.nutrition < NUTRITION_LEVEL_STARVING + 50)
|
||||
H.take_overall_damage(2,0)
|
||||
|
||||
@@ -77,9 +67,9 @@
|
||||
mutant_bodyparts = list("mcolor" = "FFF","mcolor2" = "FFF","mcolor3" = "FFF", "mam_snouts" = "Husky", "mam_tail" = "Husky", "mam_ears" = "Husky", "mam_body_markings" = "Husky", "taur" = "None", "legs" = "Normal Legs")
|
||||
limbs_id = "pod"
|
||||
light_nutrition_gain_factor = 3
|
||||
light_bruteheal = 0.2
|
||||
light_burnheal = 0.2
|
||||
light_toxheal = 0.7
|
||||
light_bruteheal = -0.2
|
||||
light_burnheal = -0.2
|
||||
light_toxheal = -0.7
|
||||
|
||||
/datum/species/pod/pseudo_weak/spec_death(gibbed, mob/living/carbon/human/H)
|
||||
if(H)
|
||||
|
||||
@@ -15,16 +15,13 @@
|
||||
dangerous_existence = 1
|
||||
mutanteyes = /obj/item/organ/eyes/night_vision
|
||||
|
||||
/datum/species/shadow/on_species_gain(mob/living/carbon/C, datum/species/old_species)
|
||||
. = ..()
|
||||
C.AddElement(/datum/element/photosynthesis, 1, 1, 0, 0, 0, 0, SHADOW_SPECIES_LIGHT_THRESHOLD, SHADOW_SPECIES_LIGHT_THRESHOLD)
|
||||
|
||||
/datum/species/shadow/spec_life(mob/living/carbon/human/H)
|
||||
var/turf/T = H.loc
|
||||
if(istype(T))
|
||||
var/light_amount = T.get_lumcount()
|
||||
|
||||
if(light_amount > SHADOW_SPECIES_LIGHT_THRESHOLD) //if there's enough light, start dying
|
||||
H.take_overall_damage(1,1)
|
||||
else if (light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) //heal in the dark
|
||||
H.heal_overall_damage(1,1)
|
||||
/datum/species/shadow/on_species_loss(mob/living/carbon/C)
|
||||
. = ..()
|
||||
C.RemoveElement(/datum/element/photosynthesis, 1, 1, 0, 0, 0, 0, SHADOW_SPECIES_LIGHT_THRESHOLD, SHADOW_SPECIES_LIGHT_THRESHOLD)
|
||||
|
||||
/datum/species/shadow/check_roundstart_eligible()
|
||||
if(SSevents.holidays && SSevents.holidays[HALLOWEEN])
|
||||
|
||||
@@ -1,29 +1,19 @@
|
||||
/mob/living/carbon/Life()
|
||||
set invisibility = 0
|
||||
|
||||
if(notransform)
|
||||
return
|
||||
|
||||
if(damageoverlaytemp)
|
||||
damageoverlaytemp = 0
|
||||
update_damage_hud()
|
||||
|
||||
/mob/living/carbon/BiologicalLife(seconds, times_fired)
|
||||
if(stat == DEAD)
|
||||
return FALSE
|
||||
//Reagent processing needs to come before breathing, to prevent edge cases.
|
||||
handle_organs()
|
||||
|
||||
. = ..()
|
||||
|
||||
if (QDELETED(src))
|
||||
if(!(. = ..()))
|
||||
return
|
||||
|
||||
if(.) //not dead
|
||||
handle_blood()
|
||||
|
||||
handle_blood()
|
||||
// handle_blood *could* kill us.
|
||||
// we should probably have a better system for if we need to check for death or something in the future hmw
|
||||
if(stat != DEAD)
|
||||
var/bprv = handle_bodyparts()
|
||||
if(bprv & BODYPART_LIFE_UPDATE_HEALTH)
|
||||
updatehealth()
|
||||
update_stamina()
|
||||
doSprintBufferRegen()
|
||||
|
||||
if(stat != DEAD)
|
||||
handle_brain_damage()
|
||||
@@ -35,12 +25,17 @@
|
||||
stop_sound_channel(CHANNEL_HEARTBEAT)
|
||||
handle_death()
|
||||
rot()
|
||||
. = FALSE
|
||||
|
||||
//Updates the number of stored chemicals for powers
|
||||
handle_changeling()
|
||||
|
||||
if(stat != DEAD)
|
||||
return 1
|
||||
/mob/living/carbon/PhysicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(damageoverlaytemp)
|
||||
damageoverlaytemp = 0
|
||||
update_damage_hud()
|
||||
|
||||
//Procs called while dead
|
||||
/mob/living/carbon/proc/handle_death()
|
||||
|
||||
@@ -3,30 +3,26 @@
|
||||
/mob/living/carbon/monkey
|
||||
|
||||
|
||||
/mob/living/carbon/monkey/Life()
|
||||
set invisibility = 0
|
||||
|
||||
if (notransform)
|
||||
/mob/living/carbon/monkey/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
|
||||
if(..())
|
||||
|
||||
if(!client)
|
||||
if(stat == CONSCIOUS)
|
||||
if(on_fire || buckled || restrained() || (!CHECK_MOBILITY(src, MOBILITY_STAND) && CHECK_MOBILITY(src, MOBILITY_MOVE))) //CIT CHANGE - makes it so monkeys attempt to resist if they're resting)
|
||||
if(!resisting && prob(MONKEY_RESIST_PROB))
|
||||
resisting = TRUE
|
||||
walk_to(src,0)
|
||||
resist()
|
||||
else if(resisting)
|
||||
resisting = FALSE
|
||||
else if((mode == MONKEY_IDLE && !pickupTarget && !prob(MONKEY_SHENANIGAN_PROB)) || !handle_combat())
|
||||
if(prob(25) && CHECK_MOBILITY(src, MOBILITY_MOVE) && isturf(loc) && !pulledby)
|
||||
step(src, pick(GLOB.cardinals))
|
||||
else if(prob(1))
|
||||
emote(pick("scratch","jump","roll","tail"))
|
||||
else
|
||||
if(client)
|
||||
return
|
||||
if(stat == CONSCIOUS)
|
||||
if(on_fire || buckled || restrained() || (!CHECK_MOBILITY(src, MOBILITY_STAND) && CHECK_MOBILITY(src, MOBILITY_MOVE))) //CIT CHANGE - makes it so monkeys attempt to resist if they're resting)
|
||||
if(!resisting && prob(MONKEY_RESIST_PROB))
|
||||
resisting = TRUE
|
||||
walk_to(src,0)
|
||||
resist()
|
||||
else if(resisting)
|
||||
resisting = FALSE
|
||||
else if((mode == MONKEY_IDLE && !pickupTarget && !prob(MONKEY_SHENANIGAN_PROB)) || !handle_combat())
|
||||
if(prob(25) && CHECK_MOBILITY(src, MOBILITY_MOVE) && isturf(loc) && !pulledby)
|
||||
step(src, pick(GLOB.cardinals))
|
||||
else if(prob(1))
|
||||
emote(pick("scratch","jump","roll","tail"))
|
||||
else
|
||||
walk_to(src,0)
|
||||
|
||||
/mob/living/carbon/monkey/handle_mutations_and_radiation()
|
||||
if(radiation)
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
/mob/living/carbon/monkey/attack_animal(mob/living/simple_animal/M)
|
||||
. = ..()
|
||||
if(.)
|
||||
var/damage = rand(M.melee_damage_lower, M.melee_damage_upper)
|
||||
var/damage = .
|
||||
var/dam_zone = dismembering_strike(M, pick(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG))
|
||||
if(!dam_zone) //Dismemberment successful
|
||||
return TRUE
|
||||
|
||||
@@ -31,11 +31,12 @@
|
||||
if(relic_mask)
|
||||
equip_to_slot_or_del(new relic_mask, SLOT_WEAR_MASK)
|
||||
|
||||
/mob/living/carbon/monkey/punpun/Life()
|
||||
/mob/living/carbon/monkey/punpun/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(!stat && SSticker.current_state == GAME_STATE_FINISHED && !memory_saved)
|
||||
Write_Memory(FALSE, FALSE)
|
||||
memory_saved = TRUE
|
||||
..()
|
||||
|
||||
/mob/living/carbon/monkey/punpun/death(gibbed)
|
||||
if(!memory_saved)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
overlays_standing[cache_index] = null
|
||||
|
||||
/mob/living/carbon/regenerate_icons()
|
||||
if(notransform)
|
||||
if(mob_transforming)
|
||||
return 1
|
||||
update_inv_hands()
|
||||
update_inv_handcuffed()
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
|
||||
/*
|
||||
apply_damage(a,b,c)
|
||||
args
|
||||
a:damage - How much damage to take
|
||||
b:damage_type - What type of damage to take, brute, burn
|
||||
c:def_zone - Where to take the damage if its brute or burn
|
||||
Returns
|
||||
standard 0 if fail
|
||||
*/
|
||||
/mob/living/proc/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE)
|
||||
/**
|
||||
* Applies damage to this mob
|
||||
*
|
||||
* Sends [COMSIG_MOB_APPLY_DAMGE]
|
||||
*
|
||||
* Arguuments:
|
||||
* * damage - amount of damage
|
||||
* * damagetype - one of [BRUTE], [BURN], [TOX], [OXY], [CLONE], [STAMINA]
|
||||
* * def_zone - zone that is being hit if any
|
||||
* * blocked - armor value applied
|
||||
* * forced - bypass hit percentage
|
||||
* * spread_damage - used in overrides
|
||||
*
|
||||
* Returns TRUE if damage applied
|
||||
*/
|
||||
/mob/living/proc/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE)
|
||||
var/hit_percent = (100-blocked)/100
|
||||
if(!damage || (hit_percent <= 0))
|
||||
return 0
|
||||
@@ -78,8 +84,6 @@
|
||||
apply_damage(brain, BRAIN, def_zone, blocked)
|
||||
return 1
|
||||
|
||||
|
||||
|
||||
/mob/living/proc/apply_effect(effect = 0,effecttype = EFFECT_STUN, blocked = FALSE, knockdown_stamoverride, knockdown_stammax)
|
||||
var/hit_percent = (100-blocked)/100
|
||||
if(!effect || (hit_percent <= 0))
|
||||
@@ -108,7 +112,7 @@
|
||||
return 1
|
||||
|
||||
|
||||
/mob/living/proc/apply_effects(stun = 0, knockdown = 0, unconscious = 0, irradiate = 0, slur = 0, stutter = 0, eyeblur = 0, drowsy = 0, blocked = FALSE, stamina = 0, jitter = 0, kd_stamoverride, kd_stammax)
|
||||
/mob/living/proc/apply_effects(stun = 0, knockdown = 0, unconscious = 0, irradiate = 0, slur = 0, stutter = 0, eyeblur = 0, drowsy = 0, blocked = 0, stamina = 0, jitter = 0, kd_stamoverride, kd_stammax)
|
||||
if(blocked >= 100)
|
||||
return BULLET_ACT_BLOCK
|
||||
if(stun)
|
||||
|
||||
@@ -457,6 +457,7 @@
|
||||
message = params
|
||||
if(type_override)
|
||||
emote_type = type_override
|
||||
message = user.say_emphasis(message)
|
||||
. = ..()
|
||||
message = null
|
||||
emote_type = EMOTE_VISIBLE
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
/**
|
||||
* Called by SSmobs at (hopefully) an interval of 1 second.
|
||||
* Splits off into PhysicalLife() and BiologicalLife(). Override those instead of this.
|
||||
*/
|
||||
/mob/living/proc/Life(seconds, times_fired)
|
||||
set waitfor = FALSE
|
||||
set invisibility = 0
|
||||
set waitfor = FALSE // yeah hey we're kind of on a subsystem, no sleeping will be tolerated here!
|
||||
if(mob_transforming)
|
||||
return
|
||||
|
||||
if(digitalinvis)
|
||||
handle_diginvis() //AI becomes unable to see mob
|
||||
. = SEND_SIGNAL(src, COMSIG_LIVING_LIFE, seconds, times_fired)
|
||||
if(!(. & COMPONENT_INTERRUPT_LIFE_PHYSICAL))
|
||||
PhysicalLife(seconds, times_fired)
|
||||
if(!(. & COMPONENT_INTERRUPT_LIFE_BIOLOGICAL))
|
||||
BiologicalLife(seconds, times_fired)
|
||||
|
||||
if((movement_type & FLYING) && !(movement_type & FLOATING)) //TODO: Better floating
|
||||
float(on = TRUE)
|
||||
// CODE BELOW SHOULD ONLY BE THINGS THAT SHOULD HAPPEN NO MATTER WHAT AND CAN NOT BE SUSPENDED!
|
||||
// Otherwise, it goes into one of the two split Life procs!
|
||||
|
||||
if (client)
|
||||
var/turf/T = get_turf(src)
|
||||
@@ -30,28 +38,54 @@
|
||||
log_game("Z-TRACKING: [src] of type [src.type] has a Z-registration despite not having a client.")
|
||||
update_z(null)
|
||||
|
||||
if (notransform)
|
||||
return
|
||||
if(!loc)
|
||||
return
|
||||
var/datum/gas_mixture/environment = loc.return_air()
|
||||
|
||||
if(stat != DEAD)
|
||||
//Mutations and radiation
|
||||
handle_mutations_and_radiation()
|
||||
|
||||
if(stat != DEAD)
|
||||
//Breathing, if applicable
|
||||
handle_breathing(times_fired)
|
||||
|
||||
/**
|
||||
* Handles biological life processes like chemical metabolism, breathing, etc
|
||||
* Returns TRUE or FALSE based on if we were interrupted. This is used by overridden variants to check if they should stop.
|
||||
*/
|
||||
/mob/living/proc/BiologicalLife(seconds, times_fired)
|
||||
handle_diseases()// DEAD check is in the proc itself; we want it to spread even if the mob is dead, but to handle its disease-y properties only if you're not.
|
||||
|
||||
if (QDELETED(src)) // diseases can qdel the mob via transformations
|
||||
return
|
||||
// Everything after this shouldn't process while dead (as of the time of writing)
|
||||
if(stat == DEAD)
|
||||
return FALSE
|
||||
|
||||
if(stat != DEAD)
|
||||
//Random events (vomiting etc)
|
||||
handle_random_events()
|
||||
//Mutations and radiation
|
||||
handle_mutations_and_radiation()
|
||||
|
||||
//Breathing, if applicable
|
||||
handle_breathing(times_fired)
|
||||
|
||||
if (QDELETED(src)) // diseases can qdel the mob via transformations
|
||||
return FALSE
|
||||
|
||||
//Random events (vomiting etc)
|
||||
handle_random_events()
|
||||
|
||||
//stuff in the stomach
|
||||
handle_stomach()
|
||||
|
||||
handle_block_parry(seconds)
|
||||
|
||||
// These two MIGHT need to be moved to base Life() if we get any in the future that's a "physical" effect that needs to fire even while in stasis.
|
||||
handle_traits() // eye, ear, brain damages
|
||||
handle_status_effects() //all special effects, stun, knockdown, jitteryness, hallucination, sleeping, etc
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Handles physical life processes like being on fire. Don't ask why this is considered "Life".
|
||||
* Returns TRUE or FALSE based on if we were interrupted. This is used by overridden variants to check if they should stop.
|
||||
*/
|
||||
/mob/living/proc/PhysicalLife(seconds, times_fired)
|
||||
if(digitalinvis)
|
||||
handle_diginvis() //AI becomes unable to see mob
|
||||
|
||||
if((movement_type & FLYING) && !(movement_type & FLOATING)) //TODO: Better floating
|
||||
float(on = TRUE)
|
||||
|
||||
if(!loc)
|
||||
return FALSE
|
||||
|
||||
var/datum/gas_mixture/environment = loc.return_air()
|
||||
|
||||
//Handle temperature/pressure differences between body and environment
|
||||
if(environment)
|
||||
@@ -59,21 +93,11 @@
|
||||
|
||||
handle_fire()
|
||||
|
||||
//stuff in the stomach
|
||||
handle_stomach()
|
||||
|
||||
handle_gravity()
|
||||
|
||||
if(machine)
|
||||
machine.check_eye(src)
|
||||
|
||||
if(stat != DEAD)
|
||||
handle_traits() // eye, ear, brain damages
|
||||
if(stat != DEAD)
|
||||
handle_status_effects() //all special effects, stun, knockdown, jitteryness, hallucination, sleeping, etc
|
||||
|
||||
if(stat != DEAD)
|
||||
return 1
|
||||
return TRUE
|
||||
|
||||
/mob/living/proc/handle_breathing(times_fired)
|
||||
return
|
||||
@@ -166,4 +190,4 @@
|
||||
/mob/living/proc/handle_high_gravity(gravity)
|
||||
if(gravity >= GRAVITY_DAMAGE_TRESHOLD) //Aka gravity values of 3 or more
|
||||
var/grav_stregth = gravity - GRAVITY_DAMAGE_TRESHOLD
|
||||
adjustBruteLoss(min(grav_stregth,3))
|
||||
adjustBruteLoss(min(grav_stregth,3))
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
med_hud_set_status()
|
||||
|
||||
/mob/living/Destroy()
|
||||
end_parry_sequence()
|
||||
stop_active_blocking()
|
||||
if(LAZYLEN(status_effects))
|
||||
for(var/s in status_effects)
|
||||
var/datum/status_effect/S = s
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
// Active directional block system. Shared code is in [living_blocking_parrying.dm]
|
||||
/mob/living/proc/stop_active_blocking(was_forced = FALSE)
|
||||
if(!(combat_flags & (COMBAT_FLAG_ACTIVE_BLOCK_STARTING | COMBAT_FLAG_ACTIVE_BLOCKING)))
|
||||
return FALSE
|
||||
var/obj/item/I = active_block_item
|
||||
combat_flags &= ~(COMBAT_FLAG_ACTIVE_BLOCKING | COMBAT_FLAG_ACTIVE_BLOCK_STARTING)
|
||||
active_block_effect_end()
|
||||
active_block_item = null
|
||||
REMOVE_TRAIT(src, TRAIT_MOBILITY_NOUSE, ACTIVE_BLOCK_TRAIT)
|
||||
REMOVE_TRAIT(src, TRAIT_SPRINT_LOCKED, ACTIVE_BLOCK_TRAIT)
|
||||
remove_movespeed_modifier(/datum/movespeed_modifier/active_block)
|
||||
var/datum/block_parry_data/data = I.get_block_parry_data()
|
||||
if(timeToNextMove() < data.block_end_click_cd_add)
|
||||
changeNext_move(data.block_end_click_cd_add)
|
||||
return TRUE
|
||||
|
||||
/mob/living/proc/start_active_blocking(obj/item/I)
|
||||
if(combat_flags & (COMBAT_FLAG_ACTIVE_BLOCK_STARTING | COMBAT_FLAG_ACTIVE_BLOCKING))
|
||||
return FALSE
|
||||
if(!(I in held_items))
|
||||
return FALSE
|
||||
var/datum/block_parry_data/data = I.get_block_parry_data()
|
||||
if(!istype(data)) //Typecheck because if an admin/coder screws up varediting or something we do not want someone being broken forever, the CRASH logs feedback so we know what happened.
|
||||
CRASH("start_active_blocking called with an item with no valid data: [I] --> [I.block_parry_data]!")
|
||||
combat_flags |= COMBAT_FLAG_ACTIVE_BLOCKING
|
||||
active_block_item = I
|
||||
if(data.block_lock_attacking)
|
||||
ADD_TRAIT(src, TRAIT_MOBILITY_NOUSE, ACTIVE_BLOCK_TRAIT) //probably should be something else at some point
|
||||
if(data.block_lock_sprinting)
|
||||
ADD_TRAIT(src, TRAIT_SPRINT_LOCKED, ACTIVE_BLOCK_TRAIT)
|
||||
add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/active_block, multiplicative_slowdown = data.block_slowdown)
|
||||
active_block_effect_start()
|
||||
return TRUE
|
||||
|
||||
/// Visual effect setup for starting a directional block
|
||||
/mob/living/proc/active_block_effect_start()
|
||||
visible_message("<span class='warning'>[src] raises their [active_block_item], dropping into a defensive stance!</span>")
|
||||
animate(src, pixel_x = get_standard_pixel_x_offset(), pixel_y = get_standard_pixel_y_offset(), time = 2.5, FALSE, SINE_EASING | EASE_OUT)
|
||||
|
||||
/// Visual effect cleanup for starting a directional block
|
||||
/mob/living/proc/active_block_effect_end()
|
||||
visible_message("<span class='warning'>[src] lowers their [active_block_item].</span>")
|
||||
animate(src, pixel_x = get_standard_pixel_x_offset(), pixel_y = get_standard_pixel_y_offset(), time = 2.5, FALSE, SINE_EASING | EASE_IN)
|
||||
|
||||
/mob/living/proc/continue_starting_active_block()
|
||||
if(SEND_SIGNAL(src, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_INACTIVE))
|
||||
return DO_AFTER_STOP
|
||||
return (combat_flags & COMBAT_FLAG_ACTIVE_BLOCK_STARTING)? DO_AFTER_CONTINUE : DO_AFTER_STOP
|
||||
|
||||
/mob/living/get_standard_pixel_x_offset()
|
||||
. = ..()
|
||||
if(combat_flags & (COMBAT_FLAG_ACTIVE_BLOCK_STARTING | COMBAT_FLAG_ACTIVE_BLOCKING))
|
||||
if(dir & EAST)
|
||||
. += 8
|
||||
if(dir & WEST)
|
||||
. -= 8
|
||||
|
||||
/mob/living/get_standard_pixel_y_offset()
|
||||
. = ..()
|
||||
if(combat_flags & (COMBAT_FLAG_ACTIVE_BLOCK_STARTING | COMBAT_FLAG_ACTIVE_BLOCKING))
|
||||
if(dir & NORTH)
|
||||
. += 8
|
||||
if(dir & SOUTH)
|
||||
. -= 8
|
||||
|
||||
/**
|
||||
* Proc called by keybindings to toggle active blocking.
|
||||
*/
|
||||
/mob/living/proc/keybind_toggle_active_blocking()
|
||||
if(combat_flags & (COMBAT_FLAG_ACTIVE_BLOCK_STARTING | COMBAT_FLAG_ACTIVE_BLOCKING))
|
||||
return keybind_stop_active_blocking()
|
||||
else
|
||||
return keybind_start_active_blocking()
|
||||
|
||||
/**
|
||||
* Proc called by keybindings to start active blocking.
|
||||
*/
|
||||
/mob/living/proc/keybind_start_active_blocking()
|
||||
if(combat_flags & (COMBAT_FLAG_ACTIVE_BLOCK_STARTING | COMBAT_FLAG_ACTIVE_BLOCKING))
|
||||
return FALSE
|
||||
if(!(combat_flags & COMBAT_FLAG_BLOCK_CAPABLE))
|
||||
to_chat(src, "<span class='warning'>You're not something that can actively block.</span>")
|
||||
return FALSE
|
||||
// QOL: Instead of trying to just block with held item, grab first available item.
|
||||
var/obj/item/I = find_active_block_item()
|
||||
if(!I)
|
||||
to_chat(src, "<span class='warning'>You can't block with your bare hands!</span>")
|
||||
return
|
||||
if(!I.can_active_block())
|
||||
to_chat(src, "<span class='warning'>[I] is either not capable of being used to actively block, or is not currently in a state that can! (Try wielding it if it's twohanded, for example.)</span>")
|
||||
return
|
||||
// QOL: Attempt to toggle on combat mode if it isn't already
|
||||
SEND_SIGNAL(src, COMSIG_ENABLE_COMBAT_MODE)
|
||||
if(!SEND_SIGNAL(src, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_ACTIVE))
|
||||
to_chat(src, "<span class='warning'>You must be in combat mode to actively block!</span>")
|
||||
return FALSE
|
||||
var/datum/block_parry_data/data = I.get_block_parry_data()
|
||||
var/delay = data.block_start_delay
|
||||
combat_flags |= COMBAT_FLAG_ACTIVE_BLOCK_STARTING
|
||||
animate(src, pixel_x = get_standard_pixel_x_offset(), pixel_y = get_standard_pixel_y_offset(), time = delay, FALSE, SINE_EASING | EASE_IN)
|
||||
if(!do_after_advanced(src, delay, src, DO_AFTER_REQUIRES_USER_ON_TURF|DO_AFTER_NO_COEFFICIENT, CALLBACK(src, .proc/continue_starting_active_block), MOBILITY_USE, null, null, I))
|
||||
to_chat(src, "<span class='warning'>You fail to raise [I].</span>")
|
||||
combat_flags &= ~(COMBAT_FLAG_ACTIVE_BLOCK_STARTING)
|
||||
animate(src, pixel_x = get_standard_pixel_x_offset(), pixel_y = get_standard_pixel_y_offset(), time = 2.5, FALSE, SINE_EASING | EASE_IN, ANIMATION_END_NOW)
|
||||
return
|
||||
combat_flags &= ~(COMBAT_FLAG_ACTIVE_BLOCK_STARTING)
|
||||
start_active_blocking(I)
|
||||
|
||||
/**
|
||||
* Gets the first item we can that can block, but if that fails, default to active held item.COMSIG_ENABLE_COMBAT_MODE
|
||||
*/
|
||||
/mob/living/proc/find_active_block_item()
|
||||
var/obj/item/held = get_active_held_item()
|
||||
if(!held?.can_active_block())
|
||||
for(var/obj/item/I in held_items - held)
|
||||
if(I.can_active_block())
|
||||
return I
|
||||
return held
|
||||
|
||||
/**
|
||||
* Proc called by keybindings to stop active blocking.
|
||||
*/
|
||||
/mob/living/proc/keybind_stop_active_blocking()
|
||||
combat_flags &= ~(COMBAT_FLAG_ACTIVE_BLOCK_STARTING)
|
||||
if(combat_flags & COMBAT_FLAG_ACTIVE_BLOCKING)
|
||||
stop_active_blocking(FALSE)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Returns if we can actively block.
|
||||
*/
|
||||
/obj/item/proc/can_active_block()
|
||||
return block_parry_data && (item_flags & ITEM_CAN_BLOCK)
|
||||
|
||||
/**
|
||||
* Calculates FINAL ATTACK DAMAGE after mitigation
|
||||
*/
|
||||
/obj/item/proc/active_block_calculate_final_damage(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
var/datum/block_parry_data/data = get_block_parry_data()
|
||||
var/absorption = data.attack_type_list_scan(data.block_damage_absorption_override, attack_type)
|
||||
var/efficiency = data.attack_type_list_scan(data.block_damage_multiplier_override, attack_type)
|
||||
var/limit = data.attack_type_list_scan(data.block_damage_limit_override, attack_type)
|
||||
// must use isnulls to handle 0's.
|
||||
if(isnull(absorption))
|
||||
absorption = data.block_damage_absorption
|
||||
if(isnull(efficiency))
|
||||
efficiency = data.block_damage_multiplier
|
||||
if(isnull(limit))
|
||||
limit = data.block_damage_limit
|
||||
// now we calculate damage to reduce.
|
||||
var/final_damage = 0
|
||||
// apply limit
|
||||
if(damage > limit) //clamp and apply overrun
|
||||
final_damage += (damage - limit)
|
||||
damage = limit
|
||||
// apply absorption
|
||||
damage -= min(absorption, damage) //this way if damage is less than absorption it 0's properly.
|
||||
// apply multiplier to remaining
|
||||
final_damage += (damage * efficiency)
|
||||
return final_damage
|
||||
|
||||
/// Amount of stamina from damage blocked. Note that the damage argument is damage_blocked.
|
||||
/obj/item/proc/active_block_stamina_cost(mob/living/owner, atom/object, damage_blocked, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
var/datum/block_parry_data/data = get_block_parry_data()
|
||||
var/efficiency = data.attack_type_list_scan(data.block_stamina_efficiency_override, attack_type)
|
||||
if(isnull(efficiency))
|
||||
efficiency = data.block_stamina_efficiency
|
||||
var/multiplier = 1
|
||||
if(!CHECK_MOBILITY(owner, MOBILITY_STAND))
|
||||
multiplier = data.attack_type_list_scan(data.block_resting_stamina_penalty_multiplier_override, attack_type)
|
||||
if(isnull(multiplier))
|
||||
multiplier = data.block_resting_stamina_penalty_multiplier
|
||||
return (damage_blocked / efficiency) * multiplier
|
||||
|
||||
/// Apply the stamina damage to our user, notice how damage argument is stamina_amount.
|
||||
/obj/item/proc/active_block_do_stamina_damage(mob/living/owner, atom/object, stamina_amount, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
var/datum/block_parry_data/data = get_block_parry_data()
|
||||
if(iscarbon(owner))
|
||||
var/mob/living/carbon/C = owner
|
||||
var/held_index = C.get_held_index_of_item(src)
|
||||
var/obj/item/bodypart/BP = C.hand_bodyparts[held_index]
|
||||
if(!BP?.body_zone)
|
||||
return C.adjustStaminaLossBuffered(stamina_amount) //nah
|
||||
var/zone = BP.body_zone
|
||||
var/stamina_to_zone = data.block_stamina_limb_ratio * stamina_amount
|
||||
var/stamina_to_chest = stamina_amount - stamina_to_zone
|
||||
var/stamina_buffered = stamina_to_chest * data.block_stamina_buffer_ratio
|
||||
stamina_to_chest -= stamina_buffered
|
||||
C.apply_damage(stamina_to_zone, STAMINA, zone)
|
||||
C.apply_damage(stamina_to_chest, STAMINA, BODY_ZONE_CHEST)
|
||||
C.adjustStaminaLossBuffered(stamina_buffered)
|
||||
else
|
||||
owner.adjustStaminaLossBuffered(stamina_amount)
|
||||
|
||||
/obj/item/proc/on_active_block(mob/living/owner, atom/object, damage, damage_blocked, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return, override_direction)
|
||||
return
|
||||
|
||||
/obj/item/proc/active_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return, override_direction)
|
||||
if(!can_active_block())
|
||||
return BLOCK_NONE
|
||||
var/datum/block_parry_data/data = get_block_parry_data()
|
||||
if(attack_type && !(attack_type & data.can_block_attack_types))
|
||||
return BLOCK_NONE
|
||||
var/incoming_direction
|
||||
if(isnull(override_direction))
|
||||
if(istype(object, /obj/item/projectile))
|
||||
var/obj/item/projectile/P = object
|
||||
incoming_direction = angle2dir(P.Angle)
|
||||
else
|
||||
incoming_direction = get_dir(get_turf(attacker) || get_turf(object), src)
|
||||
if(!CHECK_MOBILITY(owner, MOBILITY_STAND) && !(data.block_resting_attack_types_anydir & attack_type) && (!(data.block_resting_attack_types_directional & attack_type) || !can_block_direction(owner.dir, incoming_direction)))
|
||||
return BLOCK_NONE
|
||||
else if(!can_block_direction(owner.dir, incoming_direction))
|
||||
return BLOCK_NONE
|
||||
block_return[BLOCK_RETURN_ACTIVE_BLOCK] = TRUE
|
||||
var/final_damage = active_block_calculate_final_damage(owner, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return)
|
||||
var/damage_blocked = damage - final_damage
|
||||
var/stamina_cost = active_block_stamina_cost(owner, object, damage_blocked, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return)
|
||||
active_block_do_stamina_damage(owner, object, stamina_cost, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return)
|
||||
block_return[BLOCK_RETURN_ACTIVE_BLOCK_DAMAGE_MITIGATED] = damage - final_damage
|
||||
block_return[BLOCK_RETURN_SET_DAMAGE_TO] = final_damage
|
||||
. = BLOCK_SHOULD_CHANGE_DAMAGE
|
||||
if((final_damage <= 0) || (damage <= 0))
|
||||
. |= BLOCK_SUCCESS //full block
|
||||
owner.visible_message("<span class='warning'>[owner] blocks \the [attack_text] with [src]!</span>")
|
||||
else
|
||||
owner.visible_message("<span class='warning'>[owner] dampens \the [attack_text] with [src]!</span>")
|
||||
block_return[BLOCK_RETURN_PROJECTILE_BLOCK_PERCENTAGE] = data.block_projectile_mitigation
|
||||
if(length(data.block_sounds))
|
||||
playsound(loc, pickweight(data.block_sounds), 75, TRUE)
|
||||
on_active_block(owner, object, damage, damage_blocked, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return, override_direction)
|
||||
|
||||
/obj/item/proc/check_active_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
if(!can_active_block())
|
||||
return
|
||||
var/incoming_direction = get_dir(get_turf(attacker) || get_turf(object), src)
|
||||
if(!can_block_direction(owner.dir, incoming_direction))
|
||||
return
|
||||
block_return[BLOCK_RETURN_ACTIVE_BLOCK] = TRUE
|
||||
block_return[BLOCK_RETURN_ACTIVE_BLOCK_DAMAGE_MITIGATED] = damage - active_block_calculate_final_damage(owner, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return)
|
||||
|
||||
/**
|
||||
* Gets the block direction bitflags of what we can block.
|
||||
*/
|
||||
/obj/item/proc/blockable_directions()
|
||||
var/datum/block_parry_data/data = get_block_parry_data()
|
||||
return data.can_block_directions
|
||||
|
||||
/**
|
||||
* Checks if we can block from a specific direction from our direction.
|
||||
*
|
||||
* @params
|
||||
* * our_dir - our direction.
|
||||
* * their_dir - their direction. Must be a single direction, or NONE for an attack from the same tile. This is incoming direction.
|
||||
*/
|
||||
/obj/item/proc/can_block_direction(our_dir, their_dir)
|
||||
their_dir = turn(their_dir, 180)
|
||||
if(our_dir != NORTH)
|
||||
var/turn_angle = dir2angle(our_dir)
|
||||
// dir2angle(), ss13 proc is clockwise so dir2angle(EAST) == 90
|
||||
// turn(), byond proc is counterclockwise so turn(NORTH, 90) == WEST
|
||||
their_dir = turn(their_dir, turn_angle)
|
||||
return (DIR2BLOCKDIR(their_dir) & blockable_directions())
|
||||
|
||||
/**
|
||||
* can_block_direction but for "compound" directions to check all of them and return the number of directions that were blocked.
|
||||
*
|
||||
* @params
|
||||
* * our_dir - our direction.
|
||||
* * their_dirs - list of their directions as we cannot use bitfields here.
|
||||
*/
|
||||
/obj/item/proc/can_block_directions_multiple(our_dir, list/their_dirs)
|
||||
. = FALSE
|
||||
for(var/i in their_dirs)
|
||||
. |= can_block_direction(our_dir, i)
|
||||
@@ -0,0 +1,332 @@
|
||||
// Active parry system goes in here.
|
||||
/**
|
||||
* Determines if we can actively parry.
|
||||
*/
|
||||
/obj/item/proc/can_active_parry()
|
||||
return block_parry_data && (item_flags & ITEM_CAN_PARRY)
|
||||
|
||||
/**
|
||||
* Called from keybindings.
|
||||
*/
|
||||
/mob/living/proc/keybind_parry()
|
||||
initiate_parry_sequence()
|
||||
|
||||
/**
|
||||
* Initiates a parrying sequence.
|
||||
*/
|
||||
/mob/living/proc/initiate_parry_sequence()
|
||||
if(parrying)
|
||||
return // already parrying
|
||||
if(!(combat_flags & COMBAT_FLAG_PARRY_CAPABLE))
|
||||
to_chat(src, "<span class='warning'>You are not something that can parry attacks.</span>")
|
||||
return
|
||||
// Prioritize item, then martial art, then unarmed.
|
||||
// yanderedev else if time
|
||||
var/obj/item/using_item = get_active_held_item()
|
||||
var/datum/block_parry_data/data
|
||||
var/method
|
||||
if(using_item?.can_active_parry())
|
||||
data = using_item.block_parry_data
|
||||
method = ITEM_PARRY
|
||||
else if(mind?.martial_art?.can_martial_parry)
|
||||
data = mind.martial_art.block_parry_data
|
||||
method = MARTIAL_PARRY
|
||||
else if(combat_flags & COMBAT_FLAG_UNARMED_PARRY)
|
||||
data = block_parry_data
|
||||
method = UNARMED_PARRY
|
||||
else
|
||||
// QOL: If none of the above work, try to find another item.
|
||||
var/obj/item/backup = find_backup_parry_item()
|
||||
if(!backup)
|
||||
to_chat(src, "<span class='warning'>You have nothing to parry with!</span>")
|
||||
return FALSE
|
||||
data = backup.block_parry_data
|
||||
using_item = backup
|
||||
method = ITEM_PARRY
|
||||
//QOL: Try to enable combat mode if it isn't already
|
||||
SEND_SIGNAL(src, COMSIG_ENABLE_COMBAT_MODE)
|
||||
if(!SEND_SIGNAL(src, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_ACTIVE))
|
||||
to_chat(src, "<span class='warning'>You must be in combat mode to parry!</span>")
|
||||
return FALSE
|
||||
data = return_block_parry_datum(data)
|
||||
var/full_parry_duration = data.parry_time_windup + data.parry_time_active + data.parry_time_spindown
|
||||
// no system in place to "fallback" if out of the 3 the top priority one can't parry due to constraints but something else can.
|
||||
// can always implement it later, whatever.
|
||||
if((data.parry_respect_clickdelay && (next_move > world.time)) || ((parry_end_time_last + data.parry_cooldown) > world.time))
|
||||
to_chat(src, "<span class='warning'>You are not ready to parry (again)!</span>")
|
||||
return
|
||||
// Point of no return, make sure everything is set.
|
||||
parrying = method
|
||||
if(method == ITEM_PARRY)
|
||||
active_parry_item = using_item
|
||||
adjustStaminaLossBuffered(data.parry_stamina_cost)
|
||||
parry_start_time = world.time
|
||||
successful_parries = list()
|
||||
addtimer(CALLBACK(src, .proc/end_parry_sequence), full_parry_duration)
|
||||
if(data.parry_flags & PARRY_LOCK_ATTACKING)
|
||||
ADD_TRAIT(src, TRAIT_MOBILITY_NOUSE, ACTIVE_PARRY_TRAIT)
|
||||
if(data.parry_flags & PARRY_LOCK_SPRINTING)
|
||||
ADD_TRAIT(src, TRAIT_SPRINT_LOCKED, ACTIVE_PARRY_TRAIT)
|
||||
handle_parry_starting_effects(data)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Tries to find a backup parry item.
|
||||
* Does not look at active held item.
|
||||
*/
|
||||
/mob/living/proc/find_backup_parry_item()
|
||||
for(var/obj/item/I in held_items - get_active_held_item())
|
||||
if(I.can_active_parry())
|
||||
return I
|
||||
|
||||
/**
|
||||
* Called via timer when the parry sequence ends.
|
||||
*/
|
||||
/mob/living/proc/end_parry_sequence()
|
||||
if(!parrying)
|
||||
return
|
||||
REMOVE_TRAIT(src, TRAIT_MOBILITY_NOUSE, ACTIVE_PARRY_TRAIT)
|
||||
REMOVE_TRAIT(src, TRAIT_SPRINT_LOCKED, ACTIVE_PARRY_TRAIT)
|
||||
if(parry_visual_effect)
|
||||
QDEL_NULL(parry_visual_effect)
|
||||
var/datum/block_parry_data/data = get_parry_data()
|
||||
var/list/effect_text = list()
|
||||
var/successful = FALSE
|
||||
for(var/efficiency in successful_parries)
|
||||
if(efficiency >= data.parry_efficiency_considered_successful)
|
||||
successful = TRUE
|
||||
break
|
||||
if(!successful) // didn't parry anything successfully
|
||||
if(data.parry_failed_stagger_duration)
|
||||
Stagger(data.parry_failed_stagger_duration)
|
||||
effect_text += "staggering themselves"
|
||||
if(data.parry_failed_clickcd_duration)
|
||||
changeNext_move(data.parry_failed_clickcd_duration)
|
||||
effect_text += "throwing themselves off balance"
|
||||
handle_parry_ending_effects(data, effect_text)
|
||||
parrying = NOT_PARRYING
|
||||
parry_start_time = 0
|
||||
parry_end_time_last = world.time
|
||||
successful_parries = null
|
||||
|
||||
/**
|
||||
* Handles starting effects for parrying.
|
||||
*/
|
||||
/mob/living/proc/handle_parry_starting_effects(datum/block_parry_data/data)
|
||||
playsound(src, data.parry_start_sound, 75, 1)
|
||||
parry_visual_effect = new /obj/effect/abstract/parry/main(null, TRUE, src, data.parry_effect_icon_state, data.parry_time_windup_visual_override || data.parry_time_windup, data.parry_time_active_visual_override || data.parry_time_active, data.parry_time_spindown_visual_override || data.parry_time_spindown)
|
||||
switch(parrying)
|
||||
if(ITEM_PARRY)
|
||||
visible_message("<span class='warning'>[src] swings [active_parry_item]!</span>")
|
||||
else
|
||||
visible_message("<span class='warning'>[src] rushes forwards!</span>")
|
||||
|
||||
/**
|
||||
* Handles ending effects for parrying.
|
||||
*/
|
||||
/mob/living/proc/handle_parry_ending_effects(datum/block_parry_data/data, list/failed_effect_text)
|
||||
if(length(successful_parries))
|
||||
return
|
||||
visible_message("<span class='warning'>[src] fails to connect their parry[failed_effect_text? ", [english_list(failed_effect_text)]" : ""]!")
|
||||
|
||||
/**
|
||||
* Gets this item's datum/block_parry_data
|
||||
*/
|
||||
/obj/item/proc/get_block_parry_data()
|
||||
return return_block_parry_datum(block_parry_data)
|
||||
|
||||
//Stubs.
|
||||
|
||||
/**
|
||||
* Called when an attack is parried using this, whether or not the parry was successful.
|
||||
*/
|
||||
/obj/item/proc/on_active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return, parry_efficiency, parry_time)
|
||||
|
||||
/**
|
||||
* Called when an attack is parried innately, whether or not the parry was successful.
|
||||
*/
|
||||
/mob/living/proc/on_active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return, parry_efficiency, parry_time)
|
||||
|
||||
/**
|
||||
* Called when an attack is parried using this, whether or not the parry was successful.
|
||||
*/
|
||||
/datum/martial_art/proc/on_active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return, parry_efficiency, parry_time)
|
||||
|
||||
/**
|
||||
* Called when an attack is parried and block_parra_data indicates to use a proc to handle counterattack.
|
||||
*/
|
||||
/obj/item/proc/active_parry_reflex_counter(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list, parry_efficiency, list/effect_text)
|
||||
|
||||
/**
|
||||
* Called when an attack is parried and block_parra_data indicates to use a proc to handle counterattack.
|
||||
*/
|
||||
/mob/living/proc/active_parry_reflex_counter(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list, parry_efficiency, list/effect_text)
|
||||
|
||||
/**
|
||||
* Called when an attack is parried and block_parra_data indicates to use a proc to handle counterattack.
|
||||
*/
|
||||
/datum/martial_art/proc/active_parry_reflex_counter(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list, parry_efficiency, list/effect_text)
|
||||
|
||||
/**
|
||||
* Gets the stage of our parry sequence we're currently in.
|
||||
*/
|
||||
/mob/living/proc/get_parry_stage()
|
||||
if(!parrying)
|
||||
return NOT_PARRYING
|
||||
var/datum/block_parry_data/data = get_parry_data()
|
||||
var/windup_end = data.parry_time_windup
|
||||
var/active_end = windup_end + data.parry_time_active
|
||||
var/spindown_end = active_end + data.parry_time_spindown
|
||||
var/current_time = get_parry_time()
|
||||
// Not a switch statement because byond switch statements don't support floats at time of writing with "to" keyword.
|
||||
if(current_time < 0)
|
||||
return NOT_PARRYING
|
||||
else if(current_time < windup_end)
|
||||
return PARRY_WINDUP
|
||||
else if(current_time <= active_end) // this uses <= on purpose, give a slight bit of advantage because time is rounded to world.tick_lag
|
||||
return PARRY_ACTIVE
|
||||
else if(current_time <= spindown_end)
|
||||
return PARRY_SPINDOWN
|
||||
else
|
||||
return NOT_PARRYING
|
||||
|
||||
/**
|
||||
* Gets the current decisecond "frame" of an active parry.
|
||||
*/
|
||||
/mob/living/proc/get_parry_time()
|
||||
return world.time - parry_start_time
|
||||
|
||||
/// same return values as normal blocking, called with absolute highest priority in the block "chain".
|
||||
/mob/living/proc/run_parry(atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list())
|
||||
var/stage = get_parry_stage()
|
||||
if(stage != PARRY_ACTIVE)
|
||||
return BLOCK_NONE
|
||||
var/datum/block_parry_data/data = get_parry_data()
|
||||
if(attack_type && (!(attack_type & data.parry_attack_types) || (attack_type & ATTACK_TYPE_PARRY_COUNTERATTACK))) // if this attack is from a parry do not parry it lest we infinite loop.
|
||||
return BLOCK_NONE
|
||||
var/efficiency = data.get_parry_efficiency(attack_type, get_parry_time())
|
||||
switch(parrying)
|
||||
if(ITEM_PARRY)
|
||||
if(!active_parry_item.can_active_parry())
|
||||
return BLOCK_NONE
|
||||
. = active_parry_item.on_active_parry(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency, get_parry_time())
|
||||
if(UNARMED_PARRY)
|
||||
. = on_active_parry(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency, get_parry_time())
|
||||
if(MARTIAL_PARRY)
|
||||
. = mind.martial_art.on_active_parry(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency, get_parry_time())
|
||||
if(!isnull(return_list[BLOCK_RETURN_OVERRIDE_PARRY_EFFICIENCY])) // one of our procs overrode
|
||||
efficiency = return_list[BLOCK_RETURN_OVERRIDE_PARRY_EFFICIENCY]
|
||||
if(efficiency <= 0) // Do not allow automatically handled/standardized parries that increase damage for now.
|
||||
return
|
||||
. |= BLOCK_SHOULD_PARTIAL_MITIGATE
|
||||
if(isnull(return_list[BLOCK_RETURN_MITIGATION_PERCENT])) // if one of the on_active_parry procs overrode. We don't have to worry about interference since parries are the first thing checked in the [do_run_block()] sequence.
|
||||
return_list[BLOCK_RETURN_MITIGATION_PERCENT] = clamp(efficiency, 0, 100) // do not allow > 100% or < 0% for now.
|
||||
if((return_list[BLOCK_RETURN_MITIGATION_PERCENT] >= 100) || (damage <= 0))
|
||||
. |= BLOCK_SUCCESS
|
||||
var/list/effect_text
|
||||
if(efficiency >= data.parry_efficiency_to_counterattack)
|
||||
run_parry_countereffects(object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency)
|
||||
if(data.parry_flags & PARRY_DEFAULT_HANDLE_FEEDBACK)
|
||||
handle_parry_feedback(object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency, effect_text)
|
||||
successful_parries += efficiency
|
||||
if(length(successful_parries) >= data.parry_max_attacks)
|
||||
end_parry_sequence()
|
||||
|
||||
/mob/living/proc/handle_parry_feedback(atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list(), parry_efficiency, list/effect_text)
|
||||
var/datum/block_parry_data/data = get_parry_data()
|
||||
if(data.parry_sounds)
|
||||
playsound(src, pick(data.parry_sounds), 75)
|
||||
visible_message("<span class='danger'>[src] parries \the [attack_text][length(effect_text)? ", [english_list(effect_text)] [attacker]" : ""]!</span>")
|
||||
|
||||
/// Run counterattack if any
|
||||
/mob/living/proc/run_parry_countereffects(atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list(), parry_efficiency)
|
||||
if(!isliving(attacker))
|
||||
return
|
||||
var/mob/living/L = attacker
|
||||
var/datum/block_parry_data/data = get_parry_data()
|
||||
var/list/effect_text = list()
|
||||
// Always proc so items can override behavior easily
|
||||
switch(parrying)
|
||||
if(ITEM_PARRY)
|
||||
active_parry_item.active_parry_reflex_counter(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, parry_efficiency, effect_text)
|
||||
if(UNARMED_PARRY)
|
||||
active_parry_reflex_counter(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, parry_efficiency, effect_text)
|
||||
if(MARTIAL_PARRY)
|
||||
mind.martial_art.active_parry_reflex_counter(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, parry_efficiency, effect_text)
|
||||
if(Adjacent(attacker) || data.parry_data[PARRY_COUNTERATTACK_IGNORE_ADJACENCY])
|
||||
if(data.parry_data[PARRY_COUNTERATTACK_MELEE_ATTACK_CHAIN])
|
||||
switch(parrying)
|
||||
if(ITEM_PARRY)
|
||||
active_parry_item.melee_attack_chain(src, attacker, null, ATTACKCHAIN_PARRY_COUNTERATTACK, data.parry_data[PARRY_COUNTERATTACK_MELEE_ATTACK_CHAIN])
|
||||
effect_text += "reflexively counterattacking with [active_parry_item]"
|
||||
if(UNARMED_PARRY) // WARNING: If you are using these two, the attackchain parry counterattack flags and damage multipliers are unimplemented. Be careful with how you handle this.
|
||||
UnarmedAttack(attacker)
|
||||
effect_text += "reflexively counterattacking in the process"
|
||||
if(MARTIAL_PARRY) // Not well implemeneted, recommend custom implementation using the martial art datums.
|
||||
UnarmedAttack(attacker)
|
||||
effect_text += "reflexively maneuvering to retaliate"
|
||||
if(data.parry_data[PARRY_DISARM_ATTACKER])
|
||||
L.drop_all_held_items()
|
||||
effect_text += "disarming"
|
||||
if(data.parry_data[PARRY_KNOCKDOWN_ATTACKER])
|
||||
L.DefaultCombatKnockdown(data.parry_data[PARRY_KNOCKDOWN_ATTACKER])
|
||||
effect_text += "knocking them to the ground"
|
||||
if(data.parry_data[PARRY_STAGGER_ATTACKER])
|
||||
L.Stagger(data.parry_data[PARRY_STAGGER_ATTACKER])
|
||||
effect_text += "staggering"
|
||||
if(data.parry_data[PARRY_DAZE_ATTACKER])
|
||||
L.Daze(data.parry_data[PARRY_DAZE_ATTACKER])
|
||||
effect_text += "dazing"
|
||||
return effect_text
|
||||
|
||||
/// Gets the datum/block_parry_data we're going to use to parry.
|
||||
/mob/living/proc/get_parry_data()
|
||||
if(parrying == ITEM_PARRY)
|
||||
return active_parry_item.get_block_parry_data()
|
||||
else if(parrying == UNARMED_PARRY)
|
||||
return return_block_parry_datum(block_parry_data)
|
||||
else if(parrying == MARTIAL_PARRY)
|
||||
return return_block_parry_datum(mind.martial_art.block_parry_data)
|
||||
|
||||
/// Effects
|
||||
/obj/effect/abstract/parry
|
||||
icon = 'icons/effects/block_parry.dmi'
|
||||
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
||||
layer = FLOAT_LAYER
|
||||
plane = FLOAT_PLANE
|
||||
vis_flags = VIS_INHERIT_LAYER|VIS_INHERIT_PLANE
|
||||
/// The person we're on
|
||||
var/mob/living/owner
|
||||
|
||||
/obj/effect/abstract/parry/main
|
||||
name = null
|
||||
|
||||
/obj/effect/abstract/parry/main/Initialize(mapload, autorun, mob/living/owner, set_icon_state, windup, active, spindown)
|
||||
. = ..()
|
||||
icon_state = set_icon_state
|
||||
if(owner)
|
||||
attach_to(owner)
|
||||
if(autorun)
|
||||
INVOKE_ASYNC(src, .proc/run_animation, windup, active, spindown)
|
||||
|
||||
/obj/effect/abstract/parry/main/Destroy()
|
||||
detach_from(owner)
|
||||
return ..()
|
||||
|
||||
/obj/effect/abstract/parry/main/proc/attach_to(mob/living/attaching)
|
||||
if(owner)
|
||||
detach_from(owner)
|
||||
owner = attaching
|
||||
owner.vis_contents += src
|
||||
|
||||
/obj/effect/abstract/parry/main/proc/detach_from(mob/living/detaching)
|
||||
if(detaching == owner)
|
||||
owner = null
|
||||
detaching.vis_contents -= src
|
||||
|
||||
/obj/effect/abstract/parry/main/proc/run_animation(windup_time = 2, active_time = 5, spindown_time = 3)
|
||||
var/matrix/current = transform
|
||||
transform = matrix(0.1, 0, 0, 0, 0.1, 0)
|
||||
animate(src, transform = current, time = windup_time)
|
||||
sleep(active_time)
|
||||
animate(src, alpha = 0, spindown_time)
|
||||
@@ -1,31 +1,6 @@
|
||||
// This file has a weird name, but it's for anything related to the checks for shields, blocking, dodging,
|
||||
// and similar "stop this attack before it actually impacts the target" as opposed to "defend once it has hit".
|
||||
|
||||
/*
|
||||
/// You can find the mob_check_block() and mob_run_block() macros in __DEFINES/combat.dm
|
||||
|
||||
/// Bitflags for check_block() and run_block(). Meant to be combined. You can be hit and still reflect, for example, if you do not use BLOCK_SUCCESS.
|
||||
/// Attack was not blocked
|
||||
#define BLOCK_NONE NONE
|
||||
/// Attack was blocked, do not do damage. THIS FLAG MUST BE THERE FOR DAMAGE/EFFECT PREVENTION!
|
||||
#define BLOCK_SUCCESS (1<<1)
|
||||
|
||||
/// The below are for "metadata" on "how" the attack was blocked.
|
||||
|
||||
/// Attack was and should be reflected (NOTE: the SHOULD here is important, as it says "the thing blocking isn't handling the reflecting for you so do it yourself"!)
|
||||
#define BLOCK_SHOULD_REFLECT (1<<2)
|
||||
/// Attack was manually redirected (including reflected) by any means by the defender. For when YOU are handling the reflection, rather than the thing hitting you. (see sleeping carp)
|
||||
#define BLOCK_REDIRECTED (1<<3)
|
||||
/// Attack was blocked by something like a shield.
|
||||
#define BLOCK_PHYSICAL_EXTERNAL (1<<4)
|
||||
/// Attack was blocked by something worn on you.
|
||||
#define BLOCK_PHYSICAL_INTERNAL (1<<5)
|
||||
/// Attack should pass through. Like SHOULD_REFLECT but for.. well, passing through harmlessly.
|
||||
#define BLOCK_SHOULD_PASSTHROUGH (1<<6)
|
||||
/// Attack outright missed because the target dodged. Should usually be combined with SHOULD_PASSTHROUGH or something (see martial arts)
|
||||
#define BLOCK_TARGET_DODGED (1<<7)
|
||||
*/
|
||||
|
||||
/** The actual proc for block checks. DO NOT USE THIS DIRECTLY UNLESS YOU HAVE VERY GOOD REASON TO. To reduce copypaste for differences between handling for real attacks and virtual checks.
|
||||
* Automatically checks all held items for /obj/item/proc/run_block() with the same parameters.
|
||||
* @params
|
||||
@@ -39,21 +14,31 @@
|
||||
* attacker - Set to the mob attacking IF KNOWN. Do not expect this to always be set!
|
||||
* def_zone - The zone this'll impact.
|
||||
* return_list - If something wants to grab things from what items/whatever put into list/block_return on obj/item/run_block and the comsig, pass in a list so you can grab anything put in it after block runs.
|
||||
* attack_direction - Direction of the attack. It is highly recommended to put this in, as the automatic guesswork that's done otherwise is quite inaccurate at times.
|
||||
*/
|
||||
/mob/living/proc/do_run_block(real_attack = TRUE, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list())
|
||||
/mob/living/proc/do_run_block(real_attack = TRUE, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list(), attack_direction)
|
||||
if(real_attack)
|
||||
. = run_parry(object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list) //Parry - Highest priority!
|
||||
if((. & BLOCK_SUCCESS) && !(. & BLOCK_CONTINUE_CHAIN))
|
||||
return
|
||||
// Component signal block runs have highest priority.. for now.
|
||||
. = SEND_SIGNAL(src, COMSIG_LIVING_RUN_BLOCK, real_attack, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list)
|
||||
. = SEND_SIGNAL(src, COMSIG_LIVING_RUN_BLOCK, real_attack, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, attack_direction)
|
||||
if((. & BLOCK_SUCCESS) && !(. & BLOCK_CONTINUE_CHAIN))
|
||||
return_list[BLOCK_RETURN_PROJECTILE_BLOCK_PERCENTAGE] = 100
|
||||
return
|
||||
var/list/obj/item/tocheck = get_blocking_items()
|
||||
sortTim(tocheck, /proc/cmp_item_block_priority_asc)
|
||||
sortTim(tocheck, /proc/cmp_numeric_dsc, TRUE)
|
||||
// i don't like this
|
||||
var/block_chance_modifier = round(damage / -3)
|
||||
if(real_attack)
|
||||
for(var/obj/item/I in tocheck)
|
||||
// i don't like this too
|
||||
var/final_block_chance = I.block_chance - (clamp((armour_penetration-I.armour_penetration)/2,0,100)) + block_chance_modifier //So armour piercing blades can still be parried by other blades, for example
|
||||
var/results = I.run_block(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, return_list)
|
||||
var/results
|
||||
if(I == active_block_item)
|
||||
results = I.active_block(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, return_list, attack_direction)
|
||||
else
|
||||
results = I.run_block(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, return_list)
|
||||
. |= results
|
||||
if((results & BLOCK_SUCCESS) && !(results & BLOCK_CONTINUE_CHAIN))
|
||||
break
|
||||
@@ -61,17 +46,29 @@
|
||||
for(var/obj/item/I in tocheck)
|
||||
// i don't like this too
|
||||
var/final_block_chance = I.block_chance - (clamp((armour_penetration-I.armour_penetration)/2,0,100)) + block_chance_modifier //So armour piercing blades can still be parried by other blades, for example
|
||||
I.check_block(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, return_list)
|
||||
if(I == active_block_item) //block is long termed enough we give a damn. parry, not so much.
|
||||
I.check_active_block(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, return_list, attack_direction)
|
||||
else
|
||||
I.check_block(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, return_list)
|
||||
if(. & BLOCK_SUCCESS)
|
||||
return_list[BLOCK_RETURN_PROJECTILE_BLOCK_PERCENTAGE] = 100
|
||||
else if(isnull(return_list[BLOCK_RETURN_PROJECTILE_BLOCK_PERCENTAGE]))
|
||||
return_list[BLOCK_RETURN_PROJECTILE_BLOCK_PERCENTAGE] = return_list[BLOCK_RETURN_MITIGATION_PERCENT]
|
||||
|
||||
/// Gets an unsortedlist of objects to run block checks on.
|
||||
/// Gets an unsortedlist of objects to run block checks on. List must have associative values for priorities!
|
||||
/mob/living/proc/get_blocking_items()
|
||||
. = list()
|
||||
if(active_block_item)
|
||||
var/datum/block_parry_data/data = active_block_item.get_block_parry_data()
|
||||
.[active_block_item] = data.block_active_priority
|
||||
SEND_SIGNAL(src, COMSIG_LIVING_GET_BLOCKING_ITEMS, .)
|
||||
for(var/obj/item/I in held_items)
|
||||
// this is a bad check but i am not removing it until a better catchall is made
|
||||
if(istype(I, /obj/item/clothing))
|
||||
continue
|
||||
. |= I
|
||||
if(.[I]) //don't override block/parry.
|
||||
continue
|
||||
.[I] = I.block_priority
|
||||
|
||||
/obj/item
|
||||
/// The 0% to 100% chance for the default implementation of random block rolls.
|
||||
@@ -95,3 +92,15 @@
|
||||
SEND_SIGNAL(src, COMSIG_ITEM_CHECK_BLOCK, owner, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return)
|
||||
var/existing = block_return[BLOCK_RETURN_NORMAL_BLOCK_CHANCE]
|
||||
block_return[BLOCK_RETURN_NORMAL_BLOCK_CHANCE] = max(existing || 0, final_block_chance)
|
||||
|
||||
// HELPER PROCS
|
||||
|
||||
/**
|
||||
* Considers a block return_list and calculates damage to use from that.
|
||||
*/
|
||||
/proc/block_calculate_resultant_damage(damage, list/block_return)
|
||||
if(!isnull(block_return[BLOCK_RETURN_SET_DAMAGE_TO])) // higher priority
|
||||
return block_return[BLOCK_RETURN_SET_DAMAGE_TO]
|
||||
else if(!isnull(block_return[BLOCK_RETURN_MITIGATION_PERCENT]))
|
||||
return damage * ((100 - block_return[BLOCK_RETURN_MITIGATION_PERCENT]) * 0.01)
|
||||
return damage
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
// yell at me later for file naming
|
||||
// This file contains stuff relating to the new directional blocking and parry system.
|
||||
GLOBAL_LIST_EMPTY(block_parry_data)
|
||||
|
||||
/proc/return_block_parry_datum(datum/block_parry_data/type_id_datum)
|
||||
if(istype(type_id_datum))
|
||||
return type_id_datum
|
||||
if(ispath(type_id_datum))
|
||||
. = GLOB.block_parry_data["[type_id_datum]"]
|
||||
if(!.)
|
||||
. = GLOB.block_parry_data["[type_id_datum]"] = new type_id_datum
|
||||
else //text id
|
||||
return GLOB.block_parry_data["[type_id_datum]"]
|
||||
|
||||
/proc/set_block_parry_datum(id, datum/block_parry_data/data)
|
||||
if(ispath(id))
|
||||
CRASH("Path-fetching of block parry data is only to grab static data, do not attempt to modify global caches of paths. Use string IDs.")
|
||||
GLOB.block_parry_data["[id]"] = data
|
||||
|
||||
/// Carries data like list data that would be a waste of memory if we initialized the list on every /item as we can cache datums easier.
|
||||
/datum/block_parry_data
|
||||
/////////// BLOCKING ////////////
|
||||
|
||||
/// NOTE: FOR ATTACK_TYPE_DEFINE, you MUST wrap it in "[DEFINE_HERE]"! The defines are bitflags, and therefore, NUMBERS!
|
||||
|
||||
/// See defines. Point of reference is someone facing north.
|
||||
var/can_block_directions = BLOCK_DIR_NORTH | BLOCK_DIR_NORTHEAST | BLOCK_DIR_NORTHWEST
|
||||
/// Attacks we can block
|
||||
var/can_block_attack_types = ALL
|
||||
/// Our slowdown added while blocking
|
||||
var/block_slowdown = 1
|
||||
/// Clickdelay added to user after block ends
|
||||
var/block_end_click_cd_add = 0
|
||||
/// Disallow attacking during block
|
||||
var/block_lock_attacking = TRUE
|
||||
/// Disallow sprinting during block
|
||||
var/block_lock_sprinting = FALSE
|
||||
/// The priority we get in [mob/do_run_block()] while we're being used to parry.
|
||||
var/block_active_priority = BLOCK_PRIORITY_ACTIVE_BLOCK
|
||||
/// Windup before we have our blocking active.
|
||||
var/block_start_delay = 5
|
||||
|
||||
/// Amount of "free" damage blocking absorbs
|
||||
var/block_damage_absorption = 10
|
||||
/// Override absorption, list("[ATTACK_TYPE_DEFINE]" = absorption), see [block_damage_absorption]
|
||||
var/list/block_damage_absorption_override
|
||||
|
||||
/// Ratio of damage to allow through above absorption and below limit. Multiplied by damage to determine how much to let through. Lower is better.
|
||||
var/block_damage_multiplier = 0.5
|
||||
/// Override damage overrun efficiency, list("[ATTACK_TYPE_DEFINE]" = absorption), see [block_damage_efficiency]
|
||||
var/list/block_damage_multiplier_override
|
||||
|
||||
/// Upper bound of damage block, anything above this will go right through.
|
||||
var/block_damage_limit = 80
|
||||
/// Override upper bound of damage block, list("[ATTACK_TYPE_DEFINE]" = absorption), see [block_damage_limit]
|
||||
var/list/block_damage_limit_override
|
||||
|
||||
/// The blocked variable of on_hit() on projectiles is impacted by this. Higher is better, 0 to 100, percentage.
|
||||
var/block_projectile_mitigation = 50
|
||||
|
||||
/*
|
||||
* NOTE: Overrides for attack types for most the block_stamina variables were removed,
|
||||
* because at the time of writing nothing needed to use it. Add them if you need it,
|
||||
* it should be pretty easy, just copy [active_block_damage_mitigation]
|
||||
* for how to override with list.
|
||||
*/
|
||||
|
||||
/// Default damage-to-stamina coefficient, higher is better. This is based on amount of damage BLOCKED, not initial damage, to prevent damage from "double dipping".
|
||||
var/block_stamina_efficiency = 2
|
||||
/// Override damage-to-stamina coefficient, see [block_efficiency], this should be list("[ATTACK_TYPE_DEFINE]" = coefficient_number)
|
||||
var/list/block_stamina_efficiency_override
|
||||
/// Ratio of stamina incurred by blocking that goes to the arm holding the object instead of the chest. Has no effect if this is not held in hand.
|
||||
var/block_stamina_limb_ratio = 0.5
|
||||
/// Ratio of stamina incurred by chest (so after [block_stamina_limb_ratio] runs) that is buffered.
|
||||
var/block_stamina_buffer_ratio = 1
|
||||
|
||||
/// Stamina dealt directly via adjustStaminaLossBuffered() per SECOND of block.
|
||||
var/block_stamina_cost_per_second = 1.5
|
||||
|
||||
/// Bitfield for attack types that we can block while down. This will work in any direction.
|
||||
var/block_resting_attack_types_anydir = ATTACK_TYPE_MELEE | ATTACK_TYPE_UNARMED | ATTACK_TYPE_TACKLE
|
||||
/// Bitfield for attack types that we can block while down but only in our normal directions.
|
||||
var/block_resting_attack_types_directional = ATTACK_TYPE_PROJECTILE | ATTACK_TYPE_THROWN
|
||||
/// Multiplier to stamina damage taken for attacks blocked while downed.
|
||||
var/block_resting_stamina_penalty_multiplier = 1.5
|
||||
/// Override list for multiplier to stamina damage taken for attacks blocked while down. list("[ATTACK_TYPE_DEFINE]" = multiplier_number)
|
||||
var/list/block_resting_stamina_penalty_multiplier_override
|
||||
|
||||
/// Sounds for blocking
|
||||
var/list/block_sounds = list('sound/block_parry/block_metal1.ogg' = 1, 'sound/block_parry/block_metal1.ogg' = 1)
|
||||
|
||||
/////////// PARRYING ////////////
|
||||
/// Prioriry for [mob/do_run_block()] while we're being used to parry.
|
||||
// None - Parry is always highest priority!
|
||||
/// Parry doesn't work if you aren't able to otherwise attack due to clickdelay
|
||||
var/parry_respect_clickdelay = TRUE
|
||||
/// Parry stamina cost
|
||||
var/parry_stamina_cost = 5
|
||||
/// Attack types we can block
|
||||
var/parry_attack_types = ALL
|
||||
/// Parry flags
|
||||
var/parry_flags = PARRY_DEFAULT_HANDLE_FEEDBACK | PARRY_LOCK_ATTACKING
|
||||
|
||||
/// Parry windup duration in deciseconds. 0 to this is windup, afterwards is main stage.
|
||||
var/parry_time_windup = 2
|
||||
/// Parry spindown duration in deciseconds. main stage end to this is the spindown stage, afterwards the parry fully ends.
|
||||
var/parry_time_spindown = 3
|
||||
/// Main parry window in deciseconds. This is between [parry_time_windup] and [parry_time_spindown]
|
||||
var/parry_time_active = 5
|
||||
// Visual overrides
|
||||
/// If set, overrides visual duration of windup
|
||||
var/parry_time_windup_visual_override
|
||||
/// If set, overrides visual duration of active period
|
||||
var/parry_time_active_visual_override
|
||||
/// If set, overrides visual duration of spindown
|
||||
var/parry_time_spindown_visual_override
|
||||
/// Perfect parry window in deciseconds from the start of the main window. 3 with main 5 = perfect on third decisecond of main window.
|
||||
var/parry_time_perfect = 2.5
|
||||
/// Time on both sides of perfect parry that still counts as part of the perfect window.
|
||||
var/parry_time_perfect_leeway = 1
|
||||
/// [parry_time_perfect_leeway] override for attack types, list("[ATTACK_TYPE_DEFINE]" = deciseconds)
|
||||
var/list/parry_time_perfect_leeway_override
|
||||
/// Parry "efficiency" falloff in percent per decisecond once perfect window is over.
|
||||
var/parry_imperfect_falloff_percent = 20
|
||||
/// [parry_imperfect_falloff_percent] override for attack types, list("[ATTACK_TYPE_DEFINE]" = deciseconds)
|
||||
var/list/parry_imperfect_falloff_percent_override
|
||||
/// Efficiency in percent on perfect parry.
|
||||
var/parry_efficiency_perfect = 120
|
||||
/// Parry effect data.
|
||||
var/list/parry_data = list(
|
||||
PARRY_COUNTERATTACK_MELEE_ATTACK_CHAIN = 1
|
||||
)
|
||||
/// Efficiency must be at least this to be considered successful
|
||||
var/parry_efficiency_considered_successful = 0.1
|
||||
/// Efficiency must be at least this to run automatic counterattack
|
||||
var/parry_efficiency_to_counterattack = 0.1
|
||||
/// Maximum attacks to parry successfully or unsuccessfully (but not efficiency < 0) during active period, hitting this immediately ends the sequence.
|
||||
var/parry_max_attacks = INFINITY
|
||||
/// Visual icon state override for parrying
|
||||
var/parry_effect_icon_state = "parry_bm_hold"
|
||||
/// Parrying cooldown, separate of clickdelay. It must be this much deciseconds since their last parry for them to parry with this object.
|
||||
var/parry_cooldown = 0
|
||||
/// Parry start sound
|
||||
var/parry_start_sound = 'sound/block_parry/sfx-parry.ogg'
|
||||
/// Sounds for parrying
|
||||
var/list/parry_sounds = list('sound/block_parry/block_metal1.ogg' = 1, 'sound/block_parry/block_metal1.ogg' = 1)
|
||||
/// Stagger duration post-parry if you fail to parry an attack
|
||||
var/parry_failed_stagger_duration = 3.5 SECONDS
|
||||
/// Clickdelay duration post-parry if you fail to parry an attack
|
||||
var/parry_failed_clickcd_duration = 2 SECONDS
|
||||
|
||||
/**
|
||||
* Quirky proc to get average of flags in list that are in attack_type because why is attack_type a flag.
|
||||
*/
|
||||
/datum/block_parry_data/proc/attack_type_list_scan(list/L, attack_type)
|
||||
var/total = 0
|
||||
var/div = 0
|
||||
for(var/flagtext in L)
|
||||
if(attack_type & text2num(flagtext))
|
||||
total += L[flagtext]
|
||||
div++
|
||||
// if none, return null.
|
||||
if(!div)
|
||||
return
|
||||
return total/div //groan
|
||||
|
||||
|
||||
/**
|
||||
* Gets the percentage efficiency of our parry.
|
||||
*
|
||||
* Returns a percentage in normal 0 to 100 scale, but not clamped to just 0 to 100.
|
||||
* This is a proc to allow for overriding.
|
||||
* @params
|
||||
* * attack_type - int, bitfield of the attack type(s)
|
||||
* * parry_time - deciseconds since start of the parry.
|
||||
*/
|
||||
/datum/block_parry_data/proc/get_parry_efficiency(attack_type, parry_time)
|
||||
var/difference = abs(parry_time - (parry_time_perfect + parry_time_windup))
|
||||
var/leeway = attack_type_list_scan(parry_time_perfect_leeway_override, attack_type)
|
||||
if(isnull(leeway))
|
||||
leeway = parry_time_perfect_leeway
|
||||
difference -= leeway
|
||||
. = parry_efficiency_perfect
|
||||
if(difference <= 0)
|
||||
return
|
||||
var/falloff = attack_type_list_scan(parry_imperfect_falloff_percent_override, attack_type)
|
||||
if(isnull(falloff))
|
||||
falloff = parry_imperfect_falloff_percent
|
||||
. -= falloff * difference
|
||||
|
||||
#define RENDER_VARIABLE_SIMPLE(varname, desc) dat += "<tr><th>[#varname]<br><i>[desc]</i></th><th>[varname]</th></tr>"
|
||||
#define RENDER_OVERRIDE_LIST(varname, desc) \
|
||||
dat += "<tr><th>[#varname]<br><i>[desc]</i></th><th>"; \
|
||||
var/list/assembled__##varname = list(); \
|
||||
for(var/textbit in varname){ \
|
||||
assembled__##varname += "[GLOB.attack_type_names[textbit]] = [varname[textbit]]"; \
|
||||
} \
|
||||
dat += "[english_list(assembled__##varname)]</th>";
|
||||
#define RENDER_ATTACK_TYPES(varname, desc) dat += "<tr><th>[#varname]<br><i>[desc]</i></th><th>"; \
|
||||
var/list/assembled__##varname = list(); \
|
||||
for(var/bit in bitfield2list(varname)){ \
|
||||
var/name = GLOB.attack_type_names[num2text(bit)]; \
|
||||
if(name){ \
|
||||
assembled__##varname += "[name]"; \
|
||||
} \
|
||||
} \
|
||||
dat += "[english_list(assembled__##varname)]</th>";
|
||||
#define RENDER_BLOCK_DIRECTIONS(varname, desc) \
|
||||
dat += "<tr><th>[#varname]<br><i>[desc]</i></th><th>"; \
|
||||
var/list/assembled__##varname = list(); \
|
||||
for(var/bit in bitfield2list(varname)){ \
|
||||
var/name = GLOB.block_direction_names[num2text(bit)]; \
|
||||
if(name){ \
|
||||
assembled__##varname += "[name]"; \
|
||||
} \
|
||||
} \
|
||||
dat += "[english_list(assembled__##varname)]</th>";
|
||||
|
||||
/datum/block_parry_data/Topic(href, href_list)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
if(href_list["render"])
|
||||
var/datum/browser/B = new(usr, REF(src), href_list["name"], 800, 1000)
|
||||
B.set_content(render_html_readout(href_list["block"], href_list["parry"]))
|
||||
B.open()
|
||||
|
||||
/**
|
||||
* Generates a HTML render of this datum for self-documentation
|
||||
* Maybe make this tgui-next someday haha god this is ugly as sin.
|
||||
* Does NOT include the popout or title or anything. Just the variables and explanations..
|
||||
*/
|
||||
/datum/block_parry_data/proc/render_html_readout(block_data = FALSE, parry_data = FALSE)
|
||||
var/list/dat = list()
|
||||
if(block_data)
|
||||
dat += "<div class='statusDisplay'><h3>Block Stats</h3><table style='width:100%'><tr><th>Name/Description</th><th>Value</th></tr>"
|
||||
RENDER_BLOCK_DIRECTIONS(can_block_directions, "Which directions this can block in.")
|
||||
RENDER_ATTACK_TYPES(can_block_attack_types, "The kinds of attacks this can block.")
|
||||
RENDER_VARIABLE_SIMPLE(block_slowdown, "How much slowdown is applied to the user while blocking. Lower is better.")
|
||||
RENDER_VARIABLE_SIMPLE(block_end_click_cd_add, "How much click delay in deciseconds is applied to the user when blocking ends. Lower is better.")
|
||||
RENDER_VARIABLE_SIMPLE(block_lock_attacking, "Whether or not (1 or 0) the user is locked from atacking and/or item usage while blocking.")
|
||||
RENDER_VARIABLE_SIMPLE(block_active_priority, "The priority of this item in the block sequence. This will probably mean nothing to you unless you are a coder.")
|
||||
RENDER_VARIABLE_SIMPLE(block_start_delay, "The amount of time in deciseconds it takes to start a block with this item. Lower is better.")
|
||||
RENDER_VARIABLE_SIMPLE(block_damage_absorption, "The amount of damage that is absorbed by default. Higher is better.")
|
||||
RENDER_OVERRIDE_LIST(block_damage_absorption_override, "Overrides for the above for each attack type")
|
||||
RENDER_VARIABLE_SIMPLE(block_damage_multiplier, "Damage between absorption and limit is multiplied by this. Lower is better.")
|
||||
RENDER_OVERRIDE_LIST(block_damage_multiplier_override, "Overrides for the above for each attack type")
|
||||
RENDER_VARIABLE_SIMPLE(block_damage_limit, "Damage above this passes right through and is not impacted. Higher is better.")
|
||||
RENDER_OVERRIDE_LIST(block_damage_limit_override, "Overrides for the above for each attack type.")
|
||||
RENDER_VARIABLE_SIMPLE(block_stamina_efficiency, "Coefficient for stamina damage dealt to user by damage blocked. Higher is better.")
|
||||
RENDER_OVERRIDE_LIST(block_stamina_efficiency_override, "Overrides for the above for each attack type.")
|
||||
RENDER_VARIABLE_SIMPLE(block_stamina_limb_ratio, "The ratio of stamina that is applied to the limb holding this object (if applicable) rather than whole body/chest.")
|
||||
RENDER_VARIABLE_SIMPLE(block_stamina_buffer_ratio, "The ratio of stamina incurred by chest/whole body that is buffered rather than direct (buffer = your stamina buffer, direct = direct stamina damage like from a disabler.)")
|
||||
RENDER_VARIABLE_SIMPLE(block_stamina_cost_per_second, "The buffered stamina damage the user incurs per second of block. Lower is better.")
|
||||
RENDER_ATTACK_TYPES(block_resting_attack_types_anydir, "The kinds of attacks you can block while resting/otherwise knocked to the floor from any direction. can_block_attack_types takes precedence.")
|
||||
RENDER_ATTACK_TYPES(block_resting_attack_types_directional, "The kinds of attacks you can block wihle resting/otherwise knocked to the floor that are directional only. can_block_attack_types takes precedence.")
|
||||
RENDER_VARIABLE_SIMPLE(block_resting_stamina_penalty_multiplier, "Multiplier to stamina damage incurred from blocking while downed. Lower is better.")
|
||||
RENDER_OVERRIDE_LIST(block_resting_stamina_penalty_multiplier, "Overrides for the above for each attack type.")
|
||||
dat += "</div></table>"
|
||||
if(parry_data)
|
||||
dat += "<div class='statusDisplay'><h3>Parry Stats</h3><table style='width:100%'><tr><th>Name/Description</th><th>Value</th></tr>"
|
||||
RENDER_VARIABLE_SIMPLE(parry_respect_clickdelay, "Whether or not (1 or 0) you can only parry if your attack cooldown isn't in effect.")
|
||||
RENDER_VARIABLE_SIMPLE(parry_stamina_cost, "Buffered stamina damage incurred by you for parrying with this.")
|
||||
RENDER_ATTACK_TYPES(parry_attack_types, "Attack types you can parry.")
|
||||
// parry_flags
|
||||
dat += ""
|
||||
RENDER_VARIABLE_SIMPLE(parry_time_windup, "Deciseconds of parry windup.")
|
||||
RENDER_VARIABLE_SIMPLE(parry_time_spindown, "Deciseconds of parry spindown.")
|
||||
RENDER_VARIABLE_SIMPLE(parry_time_active, "Deciseconds of active parry window - This is the ONLY time your parry is active.")
|
||||
RENDER_VARIABLE_SIMPLE(parry_time_windup_visual_override, "Visual effect length override")
|
||||
RENDER_VARIABLE_SIMPLE(parry_time_spindown_visual_override, "Visual effect length override")
|
||||
RENDER_VARIABLE_SIMPLE(parry_time_active_visual_override, "Visual effect length override")
|
||||
RENDER_VARIABLE_SIMPLE(parry_time_perfect, "Deciseconds <b>into the active window</b> considered the 'center' of the perfect period.")
|
||||
RENDER_VARIABLE_SIMPLE(parry_time_perfect_leeway, "Leeway on both sides of the perfect period's center still considered perfect.")
|
||||
RENDER_OVERRIDE_LIST(parry_time_perfect_leeway_override, "Override for the above for each attack type")
|
||||
RENDER_VARIABLE_SIMPLE(parry_imperfect_falloff_percent, "Linear falloff in percent per decisecond for attacks parried outside of perfect window.")
|
||||
RENDER_OVERRIDE_LIST(parry_imperfect_falloff_percent_override, "Override for the above for each attack type")
|
||||
RENDER_VARIABLE_SIMPLE(parry_efficiency_perfect, "Efficiency in percentage a parry in the perfect window is considered.")
|
||||
// parry_data
|
||||
dat += ""
|
||||
RENDER_VARIABLE_SIMPLE(parry_efficiency_considered_successful, "Minimum parry efficiency to be considered a successful parry.")
|
||||
RENDER_VARIABLE_SIMPLE(parry_efficiency_to_counterattack, "Minimum parry efficiency to trigger counterattack effects.")
|
||||
RENDER_VARIABLE_SIMPLE(parry_max_attacks, "Max attacks parried per parry cycle.")
|
||||
RENDER_VARIABLE_SIMPLE(parry_effect_icon_state, "Parry effect image name")
|
||||
RENDER_VARIABLE_SIMPLE(parry_cooldown, "Deciseconds it has to be since the last time a parry sequence <b>ended</b> for you before you can parry again.")
|
||||
RENDER_VARIABLE_SIMPLE(parry_failed_stagger_duration, "Deciseconds you are staggered for at the of the parry sequence if you do not successfully parry anything.")
|
||||
RENDER_VARIABLE_SIMPLE(parry_failed_clickcd_duration, "Deciseconds you are put on attack cooldown at the end of the parry sequence if you do not successfully parry anything.")
|
||||
dat += "</div></table>"
|
||||
return dat.Join("")
|
||||
#undef RENDER_VARIABLE_SIMPLE
|
||||
#undef RENDER_OVERRIDE_LIST
|
||||
#undef RENDER_ATTACK_TYPES
|
||||
#undef RENDER_BLOCK_DIRECTIONS
|
||||
|
||||
// MOB PROCS
|
||||
|
||||
/**
|
||||
* Called every life tick to handle blocking/parrying effects.
|
||||
*/
|
||||
/mob/living/proc/handle_block_parry(seconds = 1)
|
||||
if(combat_flags & COMBAT_FLAG_ACTIVE_BLOCKING)
|
||||
var/datum/block_parry_data/data = return_block_parry_datum(active_block_item.block_parry_data)
|
||||
adjustStaminaLossBuffered(data.block_stamina_cost_per_second * seconds)
|
||||
|
||||
/mob/living/on_item_dropped(obj/item/I)
|
||||
if(I == active_block_item)
|
||||
stop_active_blocking()
|
||||
if(I == active_parry_item)
|
||||
end_parry_sequence()
|
||||
return ..()
|
||||
@@ -1,7 +1,10 @@
|
||||
|
||||
/mob/living/proc/run_armor_check(def_zone = null, attack_flag = "melee", absorb_text = "Your armor absorbs the blow!", soften_text = "Your armor softens the blow!", armour_penetration, penetrated_text = "Your armor was penetrated!")
|
||||
/mob/living/proc/run_armor_check(def_zone = null, attack_flag = "melee", absorb_text = "Your armor absorbs the blow!", soften_text = "Your armor softens the blow!", armour_penetration, penetrated_text = "Your armor was penetrated!", silent=FALSE)
|
||||
var/armor = getarmor(def_zone, attack_flag)
|
||||
|
||||
if(silent)
|
||||
return max(0, armor - armour_penetration)
|
||||
|
||||
//the if "armor" check is because this is used for everything on /living, including humans
|
||||
if(armor && armour_penetration)
|
||||
armor = max(0, armor - armour_penetration)
|
||||
@@ -66,22 +69,30 @@
|
||||
CRASH("Invalid rediretion mode [redirection_mode]")
|
||||
|
||||
/mob/living/bullet_act(obj/item/projectile/P, def_zone)
|
||||
var/totaldamage = P.damage
|
||||
var/final_percent = 0
|
||||
if(P.original != src || P.firer != src) //try to block or reflect the bullet, can't do so when shooting oneself
|
||||
var/list/returnlist = list()
|
||||
var/returned = mob_run_block(P, P.damage, "the [P.name]", ATTACK_TYPE_PROJECTILE, P.armour_penetration, P.firer, def_zone, returnlist)
|
||||
final_percent = returnlist[BLOCK_RETURN_PROJECTILE_BLOCK_PERCENTAGE]
|
||||
if(returned & BLOCK_SHOULD_REDIRECT)
|
||||
handle_projectile_attack_redirection(P, returnlist[BLOCK_RETURN_REDIRECT_METHOD])
|
||||
if(returned & BLOCK_REDIRECTED)
|
||||
return BULLET_ACT_FORCE_PIERCE
|
||||
if(returned & BLOCK_SUCCESS)
|
||||
P.on_hit(src, 100, def_zone)
|
||||
P.on_hit(src, final_percent, def_zone)
|
||||
return BULLET_ACT_BLOCK
|
||||
totaldamage = block_calculate_resultant_damage(totaldamage, returnlist)
|
||||
var/armor = run_armor_check(def_zone, P.flag, null, null, P.armour_penetration, null)
|
||||
if(!P.nodamage)
|
||||
apply_damage(P.damage, P.damage_type, def_zone, armor)
|
||||
apply_damage(totaldamage, P.damage_type, def_zone, armor)
|
||||
if(P.dismemberment)
|
||||
check_projectile_dismemberment(P, def_zone)
|
||||
return P.on_hit(src, armor) ? BULLET_ACT_HIT : BULLET_ACT_BLOCK
|
||||
var/missing = 100 - final_percent
|
||||
var/armor_ratio = armor * 0.01
|
||||
if(missing > 0)
|
||||
final_percent += missing * armor_ratio
|
||||
return P.on_hit(src, final_percent, def_zone) ? BULLET_ACT_HIT : BULLET_ACT_BLOCK
|
||||
|
||||
/mob/living/proc/check_projectile_dismemberment(obj/item/projectile/P, def_zone)
|
||||
return 0
|
||||
@@ -97,12 +108,6 @@
|
||||
/mob/living/proc/catch_item(obj/item/I, skip_throw_mode_check = FALSE)
|
||||
return FALSE
|
||||
|
||||
/mob/living/proc/embed_item(obj/item/I)
|
||||
return
|
||||
|
||||
/mob/living/proc/can_embed(obj/item/I)
|
||||
return FALSE
|
||||
|
||||
/mob/living/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked = FALSE, datum/thrownthing/throwingdatum)
|
||||
// Throwingdatum can be null if someone had an accident() while slipping with an item in hand.
|
||||
var/obj/item/I
|
||||
@@ -111,39 +116,29 @@
|
||||
I = AM
|
||||
throwpower = I.throwforce
|
||||
var/impacting_zone = ran_zone(BODY_ZONE_CHEST, 65)//Hits a random part of the body, geared towards the chest
|
||||
if(mob_run_block(AM, throwpower, "\the [AM.name]", ATTACK_TYPE_THROWN, 0, throwingdatum?.thrower, impacting_zone, null) & BLOCK_SUCCESS)
|
||||
var/list/block_return = list()
|
||||
var/total_damage = I.throwforce
|
||||
if(mob_run_block(AM, throwpower, "\the [AM.name]", ATTACK_TYPE_THROWN, 0, throwingdatum?.thrower, impacting_zone, block_return) & BLOCK_SUCCESS)
|
||||
hitpush = FALSE
|
||||
skipcatch = TRUE
|
||||
blocked = TRUE
|
||||
else if(I && I.throw_speed >= EMBED_THROWSPEED_THRESHOLD && can_embed(I, src) && prob(I.embedding.embed_chance) && !HAS_TRAIT(src, TRAIT_PIERCEIMMUNE) && (!HAS_TRAIT(src, TRAIT_AUTO_CATCH_ITEM) || incapacitated() || get_active_held_item()))
|
||||
embed_item(I)
|
||||
hitpush = FALSE
|
||||
skipcatch = TRUE //can't catch the now embedded item
|
||||
total_damage = block_calculate_resultant_damage(total_damage, block_return)
|
||||
if(I)
|
||||
var/nosell_hit = SEND_SIGNAL(I, COMSIG_MOVABLE_IMPACT_ZONE, src, impacting_zone, throwingdatum, FALSE, blocked)
|
||||
if(nosell_hit)
|
||||
skipcatch = TRUE
|
||||
hitpush = FALSE
|
||||
if(!skipcatch && isturf(I.loc) && catch_item(I))
|
||||
return TRUE
|
||||
var/dtype = BRUTE
|
||||
var/volume = I.get_volume_by_throwforce_and_or_w_class()
|
||||
SEND_SIGNAL(I, COMSIG_MOVABLE_IMPACT_ZONE, src, impacting_zone)
|
||||
|
||||
dtype = I.damtype
|
||||
|
||||
if (I.throwforce > 0) //If the weapon's throwforce is greater than zero...
|
||||
if (I.throwhitsound) //...and throwhitsound is defined...
|
||||
playsound(loc, I.throwhitsound, volume, 1, -1) //...play the weapon's throwhitsound.
|
||||
else if(I.hitsound) //Otherwise, if the weapon's hitsound is defined...
|
||||
playsound(loc, I.hitsound, volume, 1, -1) //...play the weapon's hitsound.
|
||||
else if(!I.throwhitsound) //Otherwise, if throwhitsound isn't defined...
|
||||
playsound(loc, 'sound/weapons/genhit.ogg',volume, 1, -1) //...play genhit.ogg.
|
||||
|
||||
else if(!I.throwhitsound && I.throwforce > 0) //Otherwise, if the item doesn't have a throwhitsound and has a throwforce greater than zero...
|
||||
playsound(loc, 'sound/weapons/genhit.ogg', volume, 1, -1)//...play genhit.ogg
|
||||
if(!I.throwforce)// Otherwise, if the item's throwforce is 0...
|
||||
playsound(loc, 'sound/weapons/throwtap.ogg', 1, volume, -1)//...play throwtap.ogg.
|
||||
if(!blocked)
|
||||
visible_message("<span class='danger'>[src] has been hit by [I].</span>", \
|
||||
"<span class='userdanger'>You have been hit by [I].</span>")
|
||||
var/armor = run_armor_check(impacting_zone, "melee", "Your armor has protected your [parse_zone(impacting_zone)].", "Your armor has softened hit to your [parse_zone(impacting_zone)].",I.armour_penetration)
|
||||
apply_damage(I.throwforce, dtype, impacting_zone, armor)
|
||||
apply_damage(total_damage, dtype, impacting_zone, armor)
|
||||
if(I.thrownby)
|
||||
log_combat(I.thrownby, src, "threw and hit", I)
|
||||
else
|
||||
@@ -313,8 +308,10 @@
|
||||
var/damage = rand(5, 35)
|
||||
if(M.is_adult)
|
||||
damage = rand(20, 40)
|
||||
if(mob_run_block(M, damage, "the [M.name]", ATTACK_TYPE_MELEE, null, M, check_zone(M.zone_selected), null) & BLOCK_SUCCESS)
|
||||
var/list/block_return = list()
|
||||
if(mob_run_block(M, damage, "the [M.name]", ATTACK_TYPE_MELEE, null, M, check_zone(M.zone_selected), block_return) & BLOCK_SUCCESS)
|
||||
return FALSE
|
||||
damage = block_calculate_resultant_damage(damage, block_return)
|
||||
|
||||
if (stat != DEAD)
|
||||
log_combat(M, src, "attacked")
|
||||
@@ -330,13 +327,16 @@
|
||||
M.visible_message("<span class='notice'>\The [M] [M.friendly_verb_continuous] [src]!</span>",
|
||||
"<span class='notice'>You [M.friendly_verb_simple] [src]!</span>", target = src,
|
||||
target_message = "<span class='notice'>\The [M] [M.friendly_verb_continuous] you!</span>")
|
||||
return FALSE
|
||||
return 0
|
||||
else
|
||||
if(HAS_TRAIT(M, TRAIT_PACIFISM))
|
||||
to_chat(M, "<span class='notice'>You don't want to hurt anyone!</span>")
|
||||
return FALSE
|
||||
if(mob_run_block(M, rand(M.melee_damage_lower, M.melee_damage_upper), "the [M.name]", ATTACK_TYPE_MELEE, M.armour_penetration, M, check_zone(M.zone_selected), null) & BLOCK_SUCCESS)
|
||||
return FALSE
|
||||
var/damage = rand(M.melee_damage_lower, M.melee_damage_upper)
|
||||
var/list/return_list = list()
|
||||
if(mob_run_block(M, damage, "the [M.name]", ATTACK_TYPE_MELEE, M.armour_penetration, M, check_zone(M.zone_selected), return_list) & BLOCK_SUCCESS)
|
||||
return 0
|
||||
damage = block_calculate_resultant_damage(damage, return_list)
|
||||
if(M.attack_sound)
|
||||
playsound(loc, M.attack_sound, 50, 1, 1)
|
||||
M.do_attack_animation(src)
|
||||
@@ -344,7 +344,7 @@
|
||||
"<span class='userdanger'>\The [M] [M.attack_verb_continuous] you!</span>", null, COMBAT_MESSAGE_RANGE, null,
|
||||
M, "<span class='danger'>You [M.attack_verb_simple] [src]!</span>")
|
||||
log_combat(M, src, "attacked")
|
||||
return TRUE
|
||||
return damage
|
||||
|
||||
/mob/living/attack_paw(mob/living/carbon/monkey/M)
|
||||
if (M.a_intent == INTENT_HARM)
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
typing_indicator_enabled = TRUE
|
||||
|
||||
var/last_click_move = 0 // Stores the previous next_move value.
|
||||
|
||||
var/resize = 1 //Badminnery resize
|
||||
var/lastattacker = null
|
||||
var/lastattackerckey = null
|
||||
@@ -27,6 +29,26 @@
|
||||
|
||||
var/mobility_flags = MOBILITY_FLAGS_DEFAULT
|
||||
|
||||
// Combat - Blocking/Parrying system
|
||||
/// Our block_parry_data for unarmed blocks/parries. Currently only used for parrying, as unarmed block isn't implemented yet. YOU MUST RUN [get_block_parry_data(this)] INSTEAD OF DIRECTLY ACCESSING!
|
||||
var/datum/block_parry_data/block_parry_data = /datum/block_parry_data // defaults to *something* because [combat_flags] dictates whether or not we can unarmed block/parry.
|
||||
// Blocking
|
||||
/// The item the user is actively blocking with if any.
|
||||
var/obj/item/active_block_item
|
||||
// Parrying
|
||||
/// Whether or not the user is in the middle of an active parry. Set to [UNARMED_PARRY], [ITEM_PARRY], [MARTIAL_PARRY] if parrying.
|
||||
var/parrying = FALSE
|
||||
/// The itme the user is currently parrying with, if any.
|
||||
var/obj/item/active_parry_item
|
||||
/// world.time of parry action start
|
||||
var/parry_start_time = 0
|
||||
/// Current parry effect.
|
||||
var/obj/effect/abstract/parry/parry_visual_effect
|
||||
/// world.time of last parry end
|
||||
var/parry_end_time_last = 0
|
||||
/// Successful parries within the current parry cycle. It's a list of efficiency percentages.
|
||||
var/list/successful_parries
|
||||
|
||||
var/confused = 0 //Makes the mob move in random directions.
|
||||
|
||||
var/hallucination = 0 //Directly affects how long a mob will hallucinate for
|
||||
|
||||
@@ -3,10 +3,21 @@
|
||||
update_turf_movespeed(loc)
|
||||
//Hide typing indicator if we move.
|
||||
clear_typing_indicator()
|
||||
if(is_shifted)
|
||||
is_shifted = FALSE
|
||||
pixel_x = get_standard_pixel_x_offset(lying)
|
||||
pixel_y = get_standard_pixel_y_offset(lying)
|
||||
update_pixel_shifting(TRUE)
|
||||
|
||||
/mob/living/setDir(newdir, ismousemovement)
|
||||
. = ..()
|
||||
if(ismousemovement)
|
||||
update_pixel_shifting()
|
||||
|
||||
/mob/living/proc/update_pixel_shifting(moved = FALSE)
|
||||
if(combat_flags & COMBAT_FLAG_ACTIVE_BLOCKING)
|
||||
animate(src, pixel_x = get_standard_pixel_x_offset(), pixel_y = get_standard_pixel_y_offset(), time = 2.5, flags = ANIMATION_END_NOW)
|
||||
else if(moved)
|
||||
if(is_shifted)
|
||||
is_shifted = FALSE
|
||||
pixel_x = get_standard_pixel_x_offset(lying)
|
||||
pixel_y = get_standard_pixel_y_offset(lying)
|
||||
|
||||
/mob/living/CanPass(atom/movable/mover, turf/target)
|
||||
if((mover.pass_flags & PASSMOB))
|
||||
|
||||
@@ -285,8 +285,6 @@
|
||||
. = ..()
|
||||
var/turf/ai = get_turf(src)
|
||||
var/turf/target = get_turf(A)
|
||||
if (.)
|
||||
return
|
||||
|
||||
if(!target)
|
||||
return
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/mob/living/silicon/ai/attacked_by(obj/item/I, mob/living/user, def_zone)
|
||||
/mob/living/silicon/ai/attacked_by(obj/item/I, mob/living/user, def_zone, attackchain_flags = NONE, damage_multiplier = 1)
|
||||
. = ..()
|
||||
if(!.)
|
||||
return FALSE
|
||||
|
||||
@@ -3,48 +3,48 @@
|
||||
#define POWER_RESTORATION_SEARCH_APC 2
|
||||
#define POWER_RESTORATION_APC_FOUND 3
|
||||
|
||||
/mob/living/silicon/ai/Life()
|
||||
if (stat == DEAD)
|
||||
/mob/living/silicon/ai/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
else //I'm not removing that shitton of tabs, unneeded as they are. -- Urist
|
||||
//I'm not removing that shitton of tabs, unneeded as they are. -- Urist
|
||||
//Being dead doesn't mean your temperature never changes
|
||||
|
||||
update_gravity(mob_has_gravity())
|
||||
update_gravity(mob_has_gravity())
|
||||
|
||||
handle_status_effects()
|
||||
handle_status_effects()
|
||||
|
||||
if(malfhack && malfhack.aidisabled)
|
||||
deltimer(malfhacking)
|
||||
// This proc handles cleanup of screen notifications and
|
||||
// messenging the client
|
||||
malfhacked(malfhack)
|
||||
if(malfhack && malfhack.aidisabled)
|
||||
deltimer(malfhacking)
|
||||
// This proc handles cleanup of screen notifications and
|
||||
// messenging the client
|
||||
malfhacked(malfhack)
|
||||
|
||||
if(isturf(loc) && (QDELETED(eyeobj) || !eyeobj.loc))
|
||||
view_core()
|
||||
if(isturf(loc) && (QDELETED(eyeobj) || !eyeobj.loc))
|
||||
view_core()
|
||||
|
||||
if(machine)
|
||||
machine.check_eye(src)
|
||||
if(machine)
|
||||
machine.check_eye(src)
|
||||
|
||||
// Handle power damage (oxy)
|
||||
if(aiRestorePowerRoutine)
|
||||
// Lost power
|
||||
adjustOxyLoss(1)
|
||||
else
|
||||
// Gain Power
|
||||
if(getOxyLoss())
|
||||
adjustOxyLoss(-1)
|
||||
// Handle power damage (oxy)
|
||||
if(aiRestorePowerRoutine)
|
||||
// Lost power
|
||||
adjustOxyLoss(1)
|
||||
else
|
||||
// Gain Power
|
||||
if(getOxyLoss())
|
||||
adjustOxyLoss(-1)
|
||||
|
||||
if(!lacks_power())
|
||||
var/area/home = get_area(src)
|
||||
if(home.powered(EQUIP))
|
||||
home.use_power(1000, EQUIP)
|
||||
if(!lacks_power())
|
||||
var/area/home = get_area(src)
|
||||
if(home.powered(EQUIP))
|
||||
home.use_power(1000, EQUIP)
|
||||
|
||||
if(aiRestorePowerRoutine >= POWER_RESTORATION_SEARCH_APC)
|
||||
ai_restore_power()
|
||||
return
|
||||
if(aiRestorePowerRoutine >= POWER_RESTORATION_SEARCH_APC)
|
||||
ai_restore_power()
|
||||
return
|
||||
|
||||
else if(!aiRestorePowerRoutine)
|
||||
ai_lose_power()
|
||||
else if(!aiRestorePowerRoutine)
|
||||
ai_lose_power()
|
||||
|
||||
/mob/living/silicon/ai/proc/lacks_power()
|
||||
var/turf/T = get_turf(src)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
/mob/living/silicon/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE)
|
||||
/mob/living/silicon/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE)
|
||||
var/hit_percent = (100-blocked)/100
|
||||
if(!damage || (!forced && hit_percent <= 0))
|
||||
return 0
|
||||
|
||||
@@ -146,10 +146,11 @@
|
||||
if(possible_chassis[chassis])
|
||||
AddElement(/datum/element/mob_holder, chassis, 'icons/mob/pai_item_head.dmi', 'icons/mob/pai_item_rh.dmi', 'icons/mob/pai_item_lh.dmi', ITEM_SLOT_HEAD)
|
||||
|
||||
/mob/living/silicon/pai/Life()
|
||||
/mob/living/silicon/pai/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(hacking)
|
||||
process_hack()
|
||||
return ..()
|
||||
|
||||
/mob/living/silicon/pai/proc/process_hack()
|
||||
|
||||
@@ -282,17 +283,19 @@
|
||||
. = ..()
|
||||
. += "A personal AI in holochassis mode. Its master ID string seems to be [master]."
|
||||
|
||||
/mob/living/silicon/pai/Life()
|
||||
if(stat == DEAD)
|
||||
return
|
||||
/mob/living/silicon/pai/PhysicalLife()
|
||||
. = ..()
|
||||
if(cable)
|
||||
if(get_dist(src, cable) > 1)
|
||||
var/turf/T = get_turf(src.loc)
|
||||
T.visible_message("<span class='warning'>[src.cable] rapidly retracts back into its spool.</span>", "<span class='italics'>You hear a click and the sound of wire spooling rapidly.</span>")
|
||||
qdel(src.cable)
|
||||
cable = null
|
||||
|
||||
/mob/living/silicon/pai/BiologicalLife()
|
||||
if(!(. = ..()))
|
||||
return
|
||||
silent = max(silent - 1, 0)
|
||||
. = ..()
|
||||
|
||||
/mob/living/silicon/pai/updatehealth()
|
||||
if(status_flags & GODMODE)
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
/mob/living/silicon/robot/Life()
|
||||
set invisibility = 0
|
||||
if (src.notransform)
|
||||
/mob/living/silicon/robot/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
|
||||
..()
|
||||
adjustOxyLoss(-10) //we're a robot!
|
||||
handle_robot_hud_updates()
|
||||
handle_robot_cell()
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
maxHealth = 100
|
||||
health = 100
|
||||
|
||||
combat_flags = COMBAT_FLAGS_DEFAULT
|
||||
|
||||
var/custom_name = ""
|
||||
var/braintype = "Cyborg"
|
||||
var/obj/item/robot_suit/robot_suit = null //Used for deconstruction to remember what the borg was constructed out of..
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
return new storage_type(src)
|
||||
|
||||
/obj/item/robot_module/proc/add_module(obj/item/I, nonstandard, requires_rebuild)
|
||||
rad_flags |= RAD_NO_CONTAMINATE
|
||||
if(istype(I, /obj/item/stack))
|
||||
var/obj/item/stack/S = I
|
||||
|
||||
@@ -258,7 +259,7 @@
|
||||
var/prev_locked_down = R.locked_down
|
||||
sleep(1)
|
||||
flick("[cyborg_base_icon]_transform", R)
|
||||
R.notransform = TRUE
|
||||
R.mob_transforming = TRUE
|
||||
R.SetLockdown(1)
|
||||
R.anchored = TRUE
|
||||
sleep(1)
|
||||
@@ -269,7 +270,7 @@
|
||||
R.SetLockdown(0)
|
||||
R.setDir(SOUTH)
|
||||
R.anchored = FALSE
|
||||
R.notransform = FALSE
|
||||
R.mob_transforming = FALSE
|
||||
R.update_headlamp()
|
||||
R.notify_ai(NEW_MODULE)
|
||||
if(R.hud_used)
|
||||
@@ -358,7 +359,8 @@
|
||||
"Sleek" = image(icon = 'modular_citadel/icons/mob/robots.dmi', icon_state = "sleekmed"),
|
||||
"Marina" = image(icon = 'modular_citadel/icons/mob/robots.dmi', icon_state = "marinamed"),
|
||||
"Eyebot" = image(icon = 'modular_citadel/icons/mob/robots.dmi', icon_state = "eyebotmed"),
|
||||
"Heavy" = image(icon = 'modular_citadel/icons/mob/robots.dmi', icon_state = "heavymed")
|
||||
"Heavy" = image(icon = 'modular_citadel/icons/mob/robots.dmi', icon_state = "heavymed"),
|
||||
"Zoomba" = image(icon = 'icons/mob/robots.dmi', icon_state = "zoomba_med")
|
||||
)
|
||||
var/list/L = list("Medihound" = "medihound", "Medihound Dark" = "medihounddark", "Vale" = "valemed")
|
||||
for(var/a in L)
|
||||
@@ -374,6 +376,8 @@
|
||||
switch(med_borg_icon)
|
||||
if("Default")
|
||||
cyborg_base_icon = "medical"
|
||||
if("Zoomba")
|
||||
cyborg_base_icon = "zoomba_med"
|
||||
if("Droid")
|
||||
cyborg_base_icon = "medical"
|
||||
cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi'
|
||||
@@ -475,7 +479,8 @@
|
||||
"Can" = image(icon = 'modular_citadel/icons/mob/robots.dmi', icon_state = "caneng"),
|
||||
"Marina" = image(icon = 'modular_citadel/icons/mob/robots.dmi', icon_state = "marinaeng"),
|
||||
"Spider" = image(icon = 'modular_citadel/icons/mob/robots.dmi', icon_state = "spidereng"),
|
||||
"Heavy" = image(icon = 'modular_citadel/icons/mob/robots.dmi', icon_state = "heavyeng")
|
||||
"Heavy" = image(icon = 'modular_citadel/icons/mob/robots.dmi', icon_state = "heavyeng"),
|
||||
"Zoomba" = image(icon = 'icons/mob/robots.dmi', icon_state = "zoomba_engi")
|
||||
)
|
||||
var/list/L = list("Pup Dozer" = "pupdozer", "Vale" = "valeeng")
|
||||
for(var/a in L)
|
||||
@@ -491,6 +496,8 @@
|
||||
switch(engi_borg_icon)
|
||||
if("Default")
|
||||
cyborg_base_icon = "engineer"
|
||||
if("Zoomba")
|
||||
cyborg_base_icon = "zoomba_engi"
|
||||
if("Default - Treads")
|
||||
cyborg_base_icon = "engi-tread"
|
||||
special_light_key = "engineer"
|
||||
@@ -571,7 +578,8 @@
|
||||
"Can" = image(icon = 'modular_citadel/icons/mob/robots.dmi', icon_state = "cansec"),
|
||||
"Marina" = image(icon = 'modular_citadel/icons/mob/robots.dmi', icon_state = "marinasec"),
|
||||
"Spider" = image(icon = 'modular_citadel/icons/mob/robots.dmi', icon_state = "spidersec"),
|
||||
"Heavy" = image(icon = 'modular_citadel/icons/mob/robots.dmi', icon_state = "heavysec")
|
||||
"Heavy" = image(icon = 'modular_citadel/icons/mob/robots.dmi', icon_state = "heavysec"),
|
||||
"Zoomba" = image(icon = 'icons/mob/robots.dmi', icon_state = "zoomba_sec")
|
||||
)
|
||||
var/list/L = list("K9" = "k9", "Vale" = "valesec", "K9 Dark" = "k9dark")
|
||||
for(var/a in L)
|
||||
@@ -587,6 +595,8 @@
|
||||
switch(sec_borg_icon)
|
||||
if("Default")
|
||||
cyborg_base_icon = "sec"
|
||||
if("Zoomba")
|
||||
cyborg_base_icon = "zoomba_sec"
|
||||
if("Default - Treads")
|
||||
cyborg_base_icon = "sec-tread"
|
||||
special_light_key = "sec"
|
||||
@@ -826,6 +836,7 @@
|
||||
"(Janitor) Sleek" = image(icon = 'modular_citadel/icons/mob/robots.dmi', icon_state = "sleekjan"),
|
||||
"(Janitor) Can" = image(icon = 'modular_citadel/icons/mob/robots.dmi', icon_state = "canjan"),
|
||||
"(Janitor) Heavy" = image(icon = 'modular_citadel/icons/mob/robots.dmi', icon_state = "heavyjan"),
|
||||
"Zoomba" = image(icon = 'icons/mob/robots.dmi', icon_state = "zoomba_jani")
|
||||
)
|
||||
var/list/L = list("(Service) DarkK9" = "k50", "(Service) Vale" = "valeserv", "(Service) ValeDark" = "valeservdark",
|
||||
"(Janitor) Scrubpuppy" = "scrubpup")
|
||||
@@ -840,6 +851,8 @@
|
||||
service_icons = sortList(service_icons)
|
||||
var/service_robot_icon = show_radial_menu(R, R , service_icons, custom_check = CALLBACK(src, .proc/check_menu, R), radius = 42, require_near = TRUE)
|
||||
switch(service_robot_icon)
|
||||
if("Zoomba")
|
||||
cyborg_base_icon = "zoomba_jani"
|
||||
if("(Service) Waitress")
|
||||
cyborg_base_icon = "service_f"
|
||||
special_light_key = "service"
|
||||
@@ -943,6 +956,7 @@
|
||||
"Marina" = image(icon = 'modular_citadel/icons/mob/robots.dmi', icon_state = "marinamin"),
|
||||
"Can" = image(icon = 'modular_citadel/icons/mob/robots.dmi', icon_state = "canmin"),
|
||||
"Heavy" = image(icon = 'modular_citadel/icons/mob/robots.dmi', icon_state = "heavymin"),
|
||||
"Zoomba" = image(icon = 'icons/mob/robots.dmi', icon_state = "zoomba_miner")
|
||||
)
|
||||
var/list/L = list("Blade" = "blade", "Vale" = "valemine")
|
||||
for(var/a in L)
|
||||
@@ -986,6 +1000,8 @@
|
||||
cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi'
|
||||
sleeper_overlay = "valeminesleeper"
|
||||
dogborg = TRUE
|
||||
if("Zoomba")
|
||||
cyborg_base_icon = "zoomba_miner"
|
||||
else
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
@@ -56,6 +56,12 @@
|
||||
diag_hud_set_status()
|
||||
diag_hud_set_health()
|
||||
|
||||
/mob/living/silicon/ComponentInitialize()
|
||||
. = ..()
|
||||
AddElement(/datum/element/flavor_text, _name = "Silicon Flavor Text", _save_key = "silicon_flavor_text")
|
||||
AddElement(/datum/element/flavor_text, "", "Temporary Flavor Text", "This should be used only for things pertaining to the current round!")
|
||||
AddElement(/datum/element/flavor_text, _name = "OOC Notes", _addendum = "Put information on ERP/vore/lewd-related preferences here. THIS SHOULD NOT CONTAIN REGULAR FLAVORTEXT!!", _always_show = TRUE, _save_key = "ooc_notes", _examine_no_preview = TRUE)
|
||||
|
||||
/mob/living/silicon/med_hud_set_health()
|
||||
return //we use a different hud
|
||||
|
||||
@@ -390,6 +396,12 @@
|
||||
return aicamera.selectpicture(user)
|
||||
|
||||
/mob/living/silicon/proc/ai_roster()
|
||||
if(!client)
|
||||
return
|
||||
if(world.time < client.crew_manifest_delay)
|
||||
return
|
||||
client.crew_manifest_delay = world.time + (1 SECONDS)
|
||||
|
||||
var/dat = "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'><title>Crew Roster</title></head><body><b>Crew Roster:</b><br><br>"
|
||||
|
||||
dat += GLOB.data_core.get_manifest()
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
/mob/living/silicon/attack_animal(mob/living/simple_animal/M)
|
||||
. = ..()
|
||||
if(.)
|
||||
var/damage = rand(M.melee_damage_lower, M.melee_damage_upper)
|
||||
var/damage = .
|
||||
if(prob(damage))
|
||||
for(var/mob/living/N in buckled_mobs)
|
||||
N.DefaultCombatKnockdown(20)
|
||||
@@ -120,6 +120,7 @@
|
||||
flash_act(affect_silicon = 1)
|
||||
|
||||
/mob/living/silicon/bullet_act(obj/item/projectile/P, def_zone)
|
||||
var/totaldamage = P.damage
|
||||
if(P.original != src || P.firer != src) //try to block or reflect the bullet, can't do so when shooting oneself
|
||||
var/list/returnlist = list()
|
||||
var/returned = mob_run_block(P, P.damage, "the [P.name]", ATTACK_TYPE_PROJECTILE, P.armour_penetration, P.firer, def_zone, returnlist)
|
||||
@@ -128,22 +129,18 @@
|
||||
if(returned & BLOCK_REDIRECTED)
|
||||
return BULLET_ACT_FORCE_PIERCE
|
||||
if(returned & BLOCK_SUCCESS)
|
||||
P.on_hit(src, 100, def_zone)
|
||||
P.on_hit(src, returnlist[BLOCK_RETURN_PROJECTILE_BLOCK_PERCENTAGE], def_zone)
|
||||
return BULLET_ACT_BLOCK
|
||||
totaldamage = block_calculate_resultant_damage(totaldamage, returnlist)
|
||||
if((P.damage_type == BRUTE || P.damage_type == BURN))
|
||||
adjustBruteLoss(P.damage)
|
||||
if(prob(P.damage*1.5))
|
||||
for(var/mob/living/M in buckled_mobs)
|
||||
M.visible_message("<span class='boldwarning'>[M] is knocked off of [src]!</span>",
|
||||
"<span class='boldwarning'>You are knocked off of [src]!</span>")
|
||||
unbuckle_mob(M)
|
||||
M.DefaultCombatKnockdown(40)
|
||||
if(P.stun || P.knockdown)
|
||||
adjustBruteLoss(totaldamage)
|
||||
if((P.damage >= 10) || P.stun || P.knockdown || (P.stamina >= 20))
|
||||
for(var/mob/living/M in buckled_mobs)
|
||||
unbuckle_mob(M)
|
||||
M.visible_message("<span class='boldwarning'>[M] is knocked off of [src] by the [P]!</span>",
|
||||
"<span class='boldwarning'>You are knocked off of [src] by the [P]!</span>")
|
||||
P.on_hit(src)
|
||||
unbuckle_mob(M)
|
||||
M.DefaultCombatKnockdown(40)
|
||||
P.on_hit(src, 0, def_zone)
|
||||
return BULLET_ACT_HIT
|
||||
|
||||
/mob/living/silicon/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /obj/screen/fullscreen/flash/static)
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
/mob/living/simple_animal/attack_animal(mob/living/simple_animal/M)
|
||||
. = ..()
|
||||
if(.)
|
||||
var/damage = rand(M.melee_damage_lower, M.melee_damage_upper)
|
||||
var/damage = .
|
||||
return attack_threshold_check(damage, M.melee_damage_type)
|
||||
|
||||
/mob/living/simple_animal/attack_slime(mob/living/simple_animal/slime/M)
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
log_reagent("FERMICHEM: [src] has astrally transmitted [message] into [A]")
|
||||
|
||||
//Delete the mob if there's no mind! Pay that mob no mind.
|
||||
/mob/living/simple_animal/astral/Life()
|
||||
if(!mind)
|
||||
qdel(src)
|
||||
/mob/living/simple_animal/astral/PhysicalLife(seconds, times_fired)
|
||||
. = ..()
|
||||
if(!mind && !QDELETED(src))
|
||||
qdel(src)
|
||||
|
||||
@@ -98,6 +98,10 @@
|
||||
|
||||
hud_possible = list(DIAG_STAT_HUD, DIAG_BOT_HUD, DIAG_HUD, DIAG_PATH_HUD = HUD_LIST_LIST) //Diagnostic HUD views
|
||||
|
||||
var/commissioned = FALSE // Will other (noncommissioned) bots salute this bot?
|
||||
var/can_salute = TRUE
|
||||
var/salute_delay = 60 SECONDS
|
||||
|
||||
/mob/living/simple_animal/bot/proc/get_mode()
|
||||
if(client) //Player bots do not have modes, thus the override. Also an easy way for PDA users/AI to know when a bot is a player.
|
||||
if(paicard)
|
||||
@@ -251,6 +255,14 @@
|
||||
if(!on || client)
|
||||
return
|
||||
|
||||
if(!commissioned && can_salute)
|
||||
for(var/mob/living/simple_animal/bot/B in get_hearers_in_view(5, get_turf(src)))
|
||||
if(B.commissioned)
|
||||
visible_message("<b>[src]</b> performs an elaborate salute for [B]!")
|
||||
can_salute = FALSE
|
||||
addtimer(VARSET_CALLBACK(src, can_salute, TRUE), salute_delay)
|
||||
break
|
||||
|
||||
switch(mode) //High-priority overrides are processed first. Bots can do nothing else while under direct command.
|
||||
if(BOT_RESPONDING) //Called by the AI.
|
||||
call_mode()
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
model = "Cleanbot"
|
||||
bot_core_type = /obj/machinery/bot_core/cleanbot
|
||||
window_id = "autoclean"
|
||||
window_name = "Automatic Station Cleaner v1.2"
|
||||
window_name = "Automatic Station Cleaner v1.3"
|
||||
pass_flags = PASSMOB
|
||||
path_image_color = "#993299"
|
||||
weather_immunities = list("lava","ash")
|
||||
@@ -36,8 +36,62 @@
|
||||
var/next_dest
|
||||
var/next_dest_loc
|
||||
|
||||
var/obj/item/weapon
|
||||
var/weapon_orig_force = 0
|
||||
var/chosen_name
|
||||
|
||||
var/list/stolen_valor
|
||||
|
||||
var/static/list/officers = list("Captain", "Head of Personnel", "Head of Security")
|
||||
var/static/list/command = list("Captain" = "Cpt.","Head of Personnel" = "Lt.")
|
||||
var/static/list/security = list("Head of Security" = "Maj.", "Warden" = "Sgt.", "Detective" = "Det.", "Security Officer" = "Officer")
|
||||
var/static/list/engineering = list("Chief Engineer" = "Chief Engineer", "Station Engineer" = "Engineer", "Atmospherics Technician" = "Technician")
|
||||
var/static/list/medical = list("Chief Medical Officer" = "C.M.O.", "Medical Doctor" = "M.D.", "Chemist" = "Pharm.D.")
|
||||
var/static/list/research = list("Research Director" = "Ph.D.", "Roboticist" = "M.S.", "Scientist" = "B.S.")
|
||||
var/static/list/legal = list("Lawyer" = "Esq.")
|
||||
|
||||
var/list/prefixes
|
||||
var/list/suffixes
|
||||
|
||||
/mob/living/simple_animal/bot/cleanbot/proc/deputize(obj/item/W, mob/user)
|
||||
if(in_range(src, user))
|
||||
to_chat(user, "<span class='notice'>You attach \the [W] to \the [src].</span>")
|
||||
user.transferItemToLoc(W, src)
|
||||
weapon = W
|
||||
weapon_orig_force = weapon.force
|
||||
if(!emagged)
|
||||
weapon.force = weapon.force / 2
|
||||
add_overlay(image(icon=weapon.lefthand_file,icon_state=weapon.item_state))
|
||||
|
||||
/mob/living/simple_animal/bot/cleanbot/proc/update_titles()
|
||||
var/working_title = ""
|
||||
|
||||
for(var/pref in prefixes)
|
||||
for(var/title in pref)
|
||||
if(title in stolen_valor)
|
||||
working_title += pref[title] + " "
|
||||
if(title in officers)
|
||||
commissioned = TRUE
|
||||
break
|
||||
|
||||
working_title += chosen_name
|
||||
|
||||
for(var/suf in suffixes)
|
||||
for(var/title in suf)
|
||||
if(title in stolen_valor)
|
||||
working_title += " " + suf[title]
|
||||
break
|
||||
|
||||
name = working_title
|
||||
|
||||
/mob/living/simple_animal/bot/cleanbot/examine(mob/user)
|
||||
. = ..()
|
||||
if(weapon)
|
||||
. += " <span class='warning'>Is that \a [weapon] taped to it...?</span>"
|
||||
|
||||
/mob/living/simple_animal/bot/cleanbot/Initialize()
|
||||
. = ..()
|
||||
chosen_name = name
|
||||
get_targets()
|
||||
icon_state = "cleanbot[on]"
|
||||
|
||||
@@ -45,6 +99,18 @@
|
||||
access_card.access += J.get_access()
|
||||
prev_access = access_card.access
|
||||
|
||||
stolen_valor = list()
|
||||
|
||||
prefixes = list(command, security, engineering)
|
||||
suffixes = list(research, medical, legal)
|
||||
|
||||
/mob/living/simple_animal/bot/cleanbot/Destroy()
|
||||
if(weapon)
|
||||
var/atom/Tsec = drop_location()
|
||||
weapon.force = weapon_orig_force
|
||||
drop_part(weapon, Tsec)
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/bot/cleanbot/turn_on()
|
||||
..()
|
||||
icon_state = "cleanbot[on]"
|
||||
@@ -57,6 +123,8 @@
|
||||
|
||||
/mob/living/simple_animal/bot/cleanbot/bot_reset()
|
||||
..()
|
||||
if(weapon && (emagged == 2))
|
||||
weapon.force = weapon_orig_force
|
||||
ignore_list = list() //Allows the bot to clean targets it previously ignored due to being unreachable.
|
||||
target = null
|
||||
oldloc = null
|
||||
@@ -66,6 +134,22 @@
|
||||
text_dehack = "[name]'s software has been reset!"
|
||||
text_dehack_fail = "[name] does not seem to respond to your repair code!"
|
||||
|
||||
/mob/living/simple_animal/bot/cleanbot/Crossed(atom/movable/AM)
|
||||
. = ..()
|
||||
|
||||
zone_selected = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
|
||||
if(weapon && has_gravity() && ismob(AM))
|
||||
var/mob/living/carbon/C = AM
|
||||
if(!istype(C))
|
||||
return
|
||||
|
||||
if(!(C.job in stolen_valor))
|
||||
stolen_valor += C.job
|
||||
update_titles()
|
||||
|
||||
weapon.attack(C, src)
|
||||
C.Knockdown(20)
|
||||
|
||||
/mob/living/simple_animal/bot/cleanbot/attackby(obj/item/W, mob/user, params)
|
||||
if(istype(W, /obj/item/card/id)||istype(W, /obj/item/pda))
|
||||
if(bot_core.allowed(user) && !open && !emagged)
|
||||
@@ -79,6 +163,11 @@
|
||||
else
|
||||
to_chat(user, "<span class='notice'>The [src] doesn't seem to respect your authority.</span>")
|
||||
|
||||
else if(istype(W, /obj/item/kitchen/knife) && user.a_intent != INTENT_HARM)
|
||||
to_chat(user, "<span class='notice'>You start attaching \the [W] to \the [src]...</span>")
|
||||
if(do_after(user, 25, target = src))
|
||||
deputize(W, user)
|
||||
|
||||
else if(istype(W, /obj/item/mop/advanced))
|
||||
if(bot_core.allowed(user) && open && !CHECK_BITFIELD(upgrades,UPGRADE_CLEANER_ADVANCED_MOP))
|
||||
to_chat(user, "<span class='notice'>You replace \the [src] old mop with a new better one!</span>")
|
||||
@@ -87,10 +176,10 @@
|
||||
window_name = "Automatic Station Cleaner v2.1 BETA" //New!
|
||||
qdel(W)
|
||||
if(!open)
|
||||
to_chat(user, "<span class='notice'>The [src] access pannle is not open!</span>")
|
||||
to_chat(user, "<span class='notice'>The [src] access panel is not open!</span>")
|
||||
return
|
||||
if(!bot_core.allowed(user))
|
||||
to_chat(user, "<span class='notice'>The [src] access pannel locked off to you!</span>")
|
||||
to_chat(user, "<span class='notice'>The [src] access panel locked off to you!</span>")
|
||||
return
|
||||
else
|
||||
to_chat(user, "<span class='notice'>The [src] already has this mop!</span>")
|
||||
@@ -116,6 +205,8 @@
|
||||
/mob/living/simple_animal/bot/cleanbot/emag_act(mob/user)
|
||||
. = ..()
|
||||
if(emagged == 2)
|
||||
if(weapon)
|
||||
weapon.force = weapon_orig_force
|
||||
if(user)
|
||||
to_chat(user, "<span class='danger'>[src] buzzes and beeps.</span>")
|
||||
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
//MEDBOT
|
||||
//MEDBOT PATHFINDING
|
||||
//MEDBOT ASSEMBLY
|
||||
|
||||
#define MEDBOT_PANIC_NONE 0
|
||||
#define MEDBOT_PANIC_LOW 15
|
||||
#define MEDBOT_PANIC_MED 35
|
||||
#define MEDBOT_PANIC_HIGH 55
|
||||
#define MEDBOT_PANIC_FUCK 70
|
||||
#define MEDBOT_PANIC_ENDING 90
|
||||
#define MEDBOT_PANIC_END 100
|
||||
|
||||
/mob/living/simple_animal/bot/medbot
|
||||
name = "\improper Medibot"
|
||||
@@ -64,6 +70,13 @@
|
||||
var/upgraded_dispenser_3 //Do we have the nicer chemicals? - replaces bic with sal acid
|
||||
var/upgraded_dispenser_4 //Do we have the nicer chemicals? - replaces charcoal/toxin with pentetic acid / pentetic jelly
|
||||
|
||||
//How panicked we are about being tipped over (why would you do this?)
|
||||
var/tipped_status = MEDBOT_PANIC_NONE
|
||||
//The name we got when we were tipped
|
||||
var/tipper_name
|
||||
//The last time we were tipped/righted and said a voice line, to avoid spam
|
||||
var/last_tipping_action_voice = 0
|
||||
|
||||
/mob/living/simple_animal/bot/medbot/mysterious
|
||||
name = "\improper Mysterious Medibot"
|
||||
desc = "International Medibot of mystery."
|
||||
@@ -373,10 +386,91 @@
|
||||
else
|
||||
return
|
||||
|
||||
/mob/living/simple_animal/bot/medbot/proc/tip_over(mob/user)
|
||||
mobility_flags &= ~MOBILITY_MOVE
|
||||
playsound(src, 'sound/machines/warning-buzzer.ogg', 50)
|
||||
user.visible_message("<span class='danger'>[user] tips over [src]!</span>", "<span class='danger'>You tip [src] over!</span>")
|
||||
mode = BOT_TIPPED
|
||||
var/matrix/mat = transform
|
||||
transform = mat.Turn(180)
|
||||
|
||||
/mob/living/simple_animal/bot/medbot/proc/set_right(mob/user)
|
||||
mobility_flags &= MOBILITY_MOVE
|
||||
var/list/messagevoice
|
||||
if(user)
|
||||
user.visible_message("<span class='notice'>[user] sets [src] right-side up!</span>", "<span class='green'>You set [src] right-side up!</span>")
|
||||
if(user.name == tipper_name)
|
||||
messagevoice = list("I forgive you." = 'sound/voice/medbot/forgive.ogg')
|
||||
else
|
||||
messagevoice = list("Thank you!" = 'sound/voice/medbot/thank_you.ogg', "You are a good person." = 'sound/voice/medbot/youre_good.ogg')
|
||||
else
|
||||
visible_message("<span class='notice'>[src] manages to writhe wiggle enough to right itself.</span>")
|
||||
messagevoice = list("Fuck you." = 'sound/voice/medbot/fuck_you.ogg', "Your behavior has been reported, have a nice day." = 'sound/voice/medbot/reported.ogg')
|
||||
|
||||
tipper_name = null
|
||||
if(world.time > last_tipping_action_voice + 15 SECONDS)
|
||||
last_tipping_action_voice = world.time
|
||||
var/message = pick(messagevoice)
|
||||
speak(message)
|
||||
playsound(src, messagevoice[message], 70)
|
||||
tipped_status = MEDBOT_PANIC_NONE
|
||||
mode = BOT_IDLE
|
||||
transform = matrix()
|
||||
|
||||
// if someone tipped us over, check whether we should ask for help or just right ourselves eventually
|
||||
/mob/living/simple_animal/bot/medbot/proc/handle_panic()
|
||||
tipped_status++
|
||||
var/list/messagevoice
|
||||
switch(tipped_status)
|
||||
if(MEDBOT_PANIC_LOW)
|
||||
messagevoice = list("I require assistance." = 'sound/voice/medbot/i_require_asst.ogg')
|
||||
if(MEDBOT_PANIC_MED)
|
||||
messagevoice = list("Please put me back." = 'sound/voice/medbot/please_put_me_back.ogg')
|
||||
if(MEDBOT_PANIC_HIGH)
|
||||
messagevoice = list("Please, I am scared!" = 'sound/voice/medbot/please_im_scared.ogg')
|
||||
if(MEDBOT_PANIC_FUCK)
|
||||
messagevoice = list("I don't like this, I need help!" = 'sound/voice/medbot/dont_like.ogg', "This hurts, my pain is real!" = 'sound/voice/medbot/pain_is_real.ogg')
|
||||
if(MEDBOT_PANIC_ENDING)
|
||||
messagevoice = list("Is this the end?" = 'sound/voice/medbot/is_this_the_end.ogg', "Nooo!" = 'sound/voice/medbot/nooo.ogg')
|
||||
if(MEDBOT_PANIC_END)
|
||||
speak("PSYCH ALERT: Crewmember [tipper_name] recorded displaying antisocial tendencies torturing bots in [get_area(src)]. Please schedule psych evaluation.", radio_channel)
|
||||
set_right() // strong independent medbot
|
||||
|
||||
if(prob(tipped_status))
|
||||
do_jitter_animation(tipped_status * 0.1)
|
||||
|
||||
if(messagevoice)
|
||||
var/message = pick(messagevoice)
|
||||
speak(message)
|
||||
playsound(src, messagevoice[message], 70)
|
||||
else if(prob(tipped_status * 0.2))
|
||||
playsound(src, 'sound/machines/warning-buzzer.ogg', 30, extrarange=-2)
|
||||
|
||||
/mob/living/simple_animal/bot/medbot/examine(mob/user)
|
||||
. = ..()
|
||||
if(tipped_status == MEDBOT_PANIC_NONE)
|
||||
return
|
||||
|
||||
switch(tipped_status)
|
||||
if(MEDBOT_PANIC_NONE to MEDBOT_PANIC_LOW)
|
||||
. += "It appears to be tipped over, and is quietly waiting for someone to set it right."
|
||||
if(MEDBOT_PANIC_LOW to MEDBOT_PANIC_MED)
|
||||
. += "It is tipped over and requesting help."
|
||||
if(MEDBOT_PANIC_MED to MEDBOT_PANIC_HIGH)
|
||||
. += "They are tipped over and appear visibly distressed." // now we humanize the medbot as a they, not an it
|
||||
if(MEDBOT_PANIC_HIGH to MEDBOT_PANIC_FUCK)
|
||||
. += "<span class='warning'>They are tipped over and visibly panicking!</span>"
|
||||
if(MEDBOT_PANIC_FUCK to INFINITY)
|
||||
. += "<span class='warning'><b>They are freaking out from being tipped over!</b></span>"
|
||||
|
||||
/mob/living/simple_animal/bot/medbot/handle_automated_action()
|
||||
if(!..())
|
||||
return
|
||||
|
||||
if(mode == BOT_TIPPED)
|
||||
handle_panic()
|
||||
return
|
||||
|
||||
if(mode == BOT_HEALING)
|
||||
return
|
||||
|
||||
@@ -392,10 +486,14 @@
|
||||
|
||||
if(QDELETED(patient))
|
||||
if(!shut_up && prob(1))
|
||||
var/list/messagevoice = list("Radar, put a mask on!" = 'sound/voice/medbot/radar.ogg',"There's always a catch, and I'm the best there is." = 'sound/voice/medbot/catch.ogg',"I knew it, I should've been a plastic surgeon." = 'sound/voice/medbot/surgeon.ogg',"What kind of medbay is this? Everyone's dropping like flies." = 'sound/voice/medbot/flies.ogg',"Delicious!" = 'sound/voice/medbot/delicious.ogg')
|
||||
var/message = pick(messagevoice)
|
||||
speak(message)
|
||||
playsound(loc, messagevoice[message], 50, 0)
|
||||
if(emagged && prob(30))
|
||||
var/list/i_need_scissors = list('sound/voice/medbot/fuck_you.ogg', 'sound/voice/medbot/turn_off.ogg', 'sound/voice/medbot/im_different.ogg', 'sound/voice/medbot/close.ogg', 'sound/voice/medbot/shindemashou.ogg')
|
||||
playsound(src, pick(i_need_scissors), 70)
|
||||
else
|
||||
var/list/messagevoice = list("Radar, put a mask on!" = 'sound/voice/medbot/radar.ogg',"There's always a catch, and I'm the best there is." = 'sound/voice/medbot/catch.ogg',"I knew it, I should've been a plastic surgeon." = 'sound/voice/medbot/surgeon.ogg',"What kind of medbay is this? Everyone's dropping like flies." = 'sound/voice/medbot/flies.ogg',"Delicious!" = 'sound/voice/medbot/delicious.ogg', "Why are we still here? Just to suffer?" = 'sound/voice/medbot/why.ogg')
|
||||
var/message = pick(messagevoice)
|
||||
speak(message)
|
||||
playsound(src, messagevoice[message], 50)
|
||||
var/scan_range = (stationary_mode ? 1 : DEFAULT_SCAN_RANGE) //If in stationary mode, scan range is limited to adjacent patients.
|
||||
patient = scan(/mob/living/carbon/human, oldpatient, scan_range)
|
||||
oldpatient = patient
|
||||
@@ -506,6 +604,27 @@
|
||||
/mob/living/simple_animal/bot/medbot/proc/get_healchem_toxin(mob/M)
|
||||
return HAS_TRAIT(M, TRAIT_TOXINLOVER)? treatment_tox_toxlover : treatment_tox
|
||||
|
||||
/mob/living/simple_animal/bot/medbot/attack_hand(mob/living/carbon/human/H)
|
||||
if(H.a_intent == INTENT_DISARM && mode != BOT_TIPPED)
|
||||
H.visible_message("<span class='danger'>[H] begins tipping over [src].</span>", "<span class='warning'>You begin tipping over [src]...</span>")
|
||||
|
||||
if(world.time > last_tipping_action_voice + 15 SECONDS)
|
||||
last_tipping_action_voice = world.time // message for tipping happens when we start interacting, message for righting comes after finishing
|
||||
var/list/messagevoice = list("Hey, wait..." = 'sound/voice/medbot/hey_wait.ogg',"Please don't..." = 'sound/voice/medbot/please_dont.ogg',"I trusted you..." = 'sound/voice/medbot/i_trusted_you.ogg', "Nooo..." = 'sound/voice/medbot/nooo.ogg', "Oh fuck-" = 'sound/voice/medbot/oh_fuck.ogg')
|
||||
var/message = pick(messagevoice)
|
||||
speak(message)
|
||||
playsound(src, messagevoice[message], 70, FALSE)
|
||||
|
||||
if(do_after(H, 3 SECONDS, target=src))
|
||||
tip_over(H)
|
||||
|
||||
else if(H.a_intent == INTENT_HELP && mode == BOT_TIPPED)
|
||||
H.visible_message("<span class='notice'>[H] begins righting [src].</span>", "<span class='notice'>You begin righting [src]...</span>")
|
||||
if(do_after(H, 3 SECONDS, target=src))
|
||||
set_right(H)
|
||||
else
|
||||
..()
|
||||
|
||||
/mob/living/simple_animal/bot/medbot/UnarmedAttack(atom/A)
|
||||
if(iscarbon(A))
|
||||
var/mob/living/carbon/C = A
|
||||
@@ -664,3 +783,11 @@
|
||||
|
||||
/obj/machinery/bot_core/medbot
|
||||
req_one_access = list(ACCESS_MEDICAL, ACCESS_ROBOTICS)
|
||||
|
||||
#undef MEDBOT_PANIC_NONE
|
||||
#undef MEDBOT_PANIC_LOW
|
||||
#undef MEDBOT_PANIC_MED
|
||||
#undef MEDBOT_PANIC_HIGH
|
||||
#undef MEDBOT_PANIC_FUCK
|
||||
#undef MEDBOT_PANIC_ENDING
|
||||
#undef MEDBOT_PANIC_END
|
||||
@@ -115,13 +115,14 @@
|
||||
Read_Memory()
|
||||
. = ..()
|
||||
|
||||
/mob/living/simple_animal/pet/cat/Runtime/Life()
|
||||
/mob/living/simple_animal/pet/cat/Runtime/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(!cats_deployed && SSticker.current_state >= GAME_STATE_SETTING_UP)
|
||||
Deploy_The_Cats()
|
||||
if(!stat && SSticker.current_state == GAME_STATE_FINISHED && !memory_saved)
|
||||
Write_Memory()
|
||||
memory_saved = TRUE
|
||||
..()
|
||||
|
||||
/mob/living/simple_animal/pet/cat/Runtime/make_babies()
|
||||
var/mob/baby = ..()
|
||||
@@ -177,7 +178,9 @@
|
||||
gold_core_spawnable = NO_SPAWN
|
||||
unique_pet = TRUE
|
||||
|
||||
/mob/living/simple_animal/pet/cat/Life()
|
||||
/mob/living/simple_animal/pet/cat/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(!stat && !buckled && !client)
|
||||
if(prob(1))
|
||||
emote("me", EMOTE_VISIBLE, pick("stretches out for a belly rub.", "wags its tail.", "lies down."))
|
||||
@@ -269,8 +272,9 @@
|
||||
to_chat(src, "<span class='notice'>Your name is now <b>\"new_name\"</b>!</span>")
|
||||
name = new_name
|
||||
|
||||
/mob/living/simple_animal/pet/cat/cak/Life()
|
||||
..()
|
||||
/mob/living/simple_animal/pet/cat/cak/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(stat)
|
||||
return
|
||||
if(health < maxHealth)
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
speak_emote = list("chitters")
|
||||
density = FALSE
|
||||
ventcrawler = VENTCRAWLER_ALWAYS
|
||||
mob_size = MOB_SIZE_TINY
|
||||
gold_core_spawnable = FRIENDLY_SPAWN
|
||||
verb_say = "chitters"
|
||||
verb_ask = "chitters inquisitively"
|
||||
|
||||
@@ -22,12 +22,14 @@
|
||||
friendly_verb_continuous = "pinches"
|
||||
friendly_verb_simple = "pinch"
|
||||
ventcrawler = VENTCRAWLER_ALWAYS
|
||||
mob_size = MOB_SIZE_TINY
|
||||
var/obj/item/inventory_head
|
||||
var/obj/item/inventory_mask
|
||||
gold_core_spawnable = FRIENDLY_SPAWN
|
||||
|
||||
/mob/living/simple_animal/crab/Life()
|
||||
..()
|
||||
/mob/living/simple_animal/crab/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
//CRAB movement
|
||||
if(!ckey && !stat)
|
||||
if(isturf(loc) && !resting && !buckled) //This is so it only moves if it's not inside a closet, gentics machine, etc.
|
||||
|
||||
@@ -366,11 +366,12 @@
|
||||
RemoveElement(/datum/element/mob_holder, held_icon)
|
||||
AddElement(/datum/element/mob_holder, "old_corgi")
|
||||
|
||||
/mob/living/simple_animal/pet/dog/corgi/Ian/Life()
|
||||
/mob/living/simple_animal/pet/dog/corgi/Ian/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(!stat && SSticker.current_state == GAME_STATE_FINISHED && !memory_saved)
|
||||
Write_Memory(FALSE)
|
||||
memory_saved = TRUE
|
||||
..()
|
||||
|
||||
/mob/living/simple_animal/pet/dog/corgi/Ian/death()
|
||||
if(!memory_saved)
|
||||
@@ -419,8 +420,9 @@
|
||||
fdel(json_file)
|
||||
WRITE_FILE(json_file, json_encode(file_data))
|
||||
|
||||
/mob/living/simple_animal/pet/dog/corgi/Ian/Life()
|
||||
..()
|
||||
/mob/living/simple_animal/pet/dog/corgi/Ian/BiologicalLife()
|
||||
if(!(. = ..()))
|
||||
return
|
||||
|
||||
//Feeding, chasing food, FOOOOODDDD
|
||||
if(!stat && CHECK_MULTIPLE_BITFIELDS(mobility_flags, MOBILITY_STAND|MOBILITY_MOVE) && !buckled)
|
||||
@@ -490,8 +492,9 @@
|
||||
nofur = TRUE
|
||||
unique_pet = TRUE
|
||||
|
||||
/mob/living/simple_animal/pet/dog/corgi/narsie/Life()
|
||||
..()
|
||||
/mob/living/simple_animal/pet/dog/corgi/narsie/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
for(var/mob/living/simple_animal/pet/P in range(1, src))
|
||||
if(P != src && prob(5))
|
||||
visible_message("<span class='warning'>[src] devours [P]!</span>", \
|
||||
@@ -615,8 +618,9 @@
|
||||
return
|
||||
..()
|
||||
|
||||
/mob/living/simple_animal/pet/dog/corgi/Lisa/Life()
|
||||
..()
|
||||
/mob/living/simple_animal/pet/dog/corgi/Lisa/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
|
||||
make_babies()
|
||||
|
||||
@@ -628,8 +632,9 @@
|
||||
setDir(i)
|
||||
sleep(1)
|
||||
|
||||
/mob/living/simple_animal/pet/dog/pug/Life()
|
||||
..()
|
||||
/mob/living/simple_animal/pet/dog/pug/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(!stat && CHECK_MULTIPLE_BITFIELDS(mobility_flags, MOBILITY_STAND|MOBILITY_MOVE) && !buckled)
|
||||
if(prob(1))
|
||||
emote("me", EMOTE_VISIBLE, pick("chases its tail."))
|
||||
|
||||
@@ -33,11 +33,12 @@
|
||||
stop_automated_movement_when_pulled = 1
|
||||
blood_volume = BLOOD_VOLUME_NORMAL
|
||||
var/obj/item/udder/udder = null
|
||||
var/datum/reagent/milk_reagent = /datum/reagent/consumable/milk
|
||||
|
||||
footstep_type = FOOTSTEP_MOB_SHOE
|
||||
|
||||
/mob/living/simple_animal/hostile/retaliate/goat/Initialize()
|
||||
udder = new()
|
||||
/mob/living/simple_animal/hostile/retaliate/goat/Initialize(/datum/reagent/milk_reagent)
|
||||
udder = new (null, milk_reagent)
|
||||
. = ..()
|
||||
|
||||
/mob/living/simple_animal/hostile/retaliate/goat/Destroy()
|
||||
@@ -45,9 +46,10 @@
|
||||
udder = null
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/hostile/retaliate/goat/Life()
|
||||
. = ..()
|
||||
if(.)
|
||||
/mob/living/simple_animal/hostile/retaliate/goat/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(stat == CONSCIOUS)
|
||||
//chance to go crazy and start wacking stuff
|
||||
if(!enemies.len && prob(1))
|
||||
Retaliate()
|
||||
@@ -56,8 +58,7 @@
|
||||
enemies = list()
|
||||
LoseTarget()
|
||||
src.visible_message("<span class='notice'>[src] calms down.</span>")
|
||||
if(stat == CONSCIOUS)
|
||||
udder.generateMilk()
|
||||
udder.generateMilk(milk_reagent)
|
||||
eat_plants()
|
||||
if(!pulledby)
|
||||
for(var/direction in shuffle(list(1,2,4,8,5,6,9,10)))
|
||||
@@ -137,13 +138,14 @@
|
||||
health = 50
|
||||
maxHealth = 50
|
||||
var/obj/item/udder/udder = null
|
||||
var/datum/reagent/milk_reagent = /datum/reagent/consumable/milk
|
||||
gold_core_spawnable = FRIENDLY_SPAWN
|
||||
blood_volume = BLOOD_VOLUME_NORMAL
|
||||
|
||||
footstep_type = FOOTSTEP_MOB_SHOE
|
||||
|
||||
/mob/living/simple_animal/cow/Initialize()
|
||||
udder = new()
|
||||
udder = new(null, milk_reagent)
|
||||
. = ..()
|
||||
|
||||
/mob/living/simple_animal/cow/Destroy()
|
||||
@@ -158,10 +160,11 @@
|
||||
else
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/cow/Life()
|
||||
. = ..()
|
||||
/mob/living/simple_animal/cow/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(stat == CONSCIOUS)
|
||||
udder.generateMilk()
|
||||
udder.generateMilk(milk_reagent)
|
||||
|
||||
/mob/living/simple_animal/cow/attack_hand(mob/living/carbon/M)
|
||||
if(!stat && M.a_intent == INTENT_DISARM && icon_state != icon_dead)
|
||||
@@ -189,6 +192,19 @@
|
||||
else
|
||||
..()
|
||||
|
||||
//Wisdom cow, speaks and bestows great wisdoms
|
||||
/mob/living/simple_animal/cow/wisdom
|
||||
name = "wisdom cow"
|
||||
desc = "Known for its wisdom, shares it with all"
|
||||
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/wisdomcow = 1) //truly the best meat
|
||||
gold_core_spawnable = FALSE
|
||||
speak_chance = 30 //the cow is eager to share its wisdom!
|
||||
milk_reagent = /datum/reagent/medicine/liquid_wisdom
|
||||
|
||||
/mob/living/simple_animal/cow/wisdom/Initialize()
|
||||
. = ..()
|
||||
speak = GLOB.wisdoms //Done here so it's setup properly
|
||||
|
||||
/mob/living/simple_animal/chick
|
||||
name = "\improper chick"
|
||||
desc = "Adorable! They make such a racket though."
|
||||
@@ -229,9 +245,8 @@
|
||||
pixel_x = rand(-6, 6)
|
||||
pixel_y = rand(0, 10)
|
||||
|
||||
/mob/living/simple_animal/chick/Life()
|
||||
. =..()
|
||||
if(!.)
|
||||
/mob/living/simple_animal/chick/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(!stat && !ckey)
|
||||
amount_grown += rand(1,2)
|
||||
@@ -239,8 +254,9 @@
|
||||
new /mob/living/simple_animal/chicken(src.loc)
|
||||
qdel(src)
|
||||
|
||||
/mob/living/simple_animal/chick/holo/Life()
|
||||
..()
|
||||
/mob/living/simple_animal/chick/holo/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
amount_grown = 0
|
||||
|
||||
/mob/living/simple_animal/chicken
|
||||
@@ -313,9 +329,8 @@
|
||||
else
|
||||
..()
|
||||
|
||||
/mob/living/simple_animal/chicken/Life()
|
||||
. =..()
|
||||
if(!.)
|
||||
/mob/living/simple_animal/chicken/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if((!stat && prob(3) && eggsleft > 0) && egg_type)
|
||||
visible_message("<span class='alertalien'>[src] [pick(layMessage)]</span>")
|
||||
@@ -388,9 +403,8 @@
|
||||
. = ..()
|
||||
++kiwi_count
|
||||
|
||||
/mob/living/simple_animal/kiwi/Life()
|
||||
. =..()
|
||||
if(!.)
|
||||
/mob/living/simple_animal/kiwi/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if((!stat && prob(3) && eggsleft > 0) && egg_type)
|
||||
visible_message("[src] [pick(layMessage)]")
|
||||
@@ -463,9 +477,8 @@
|
||||
pixel_x = rand(-6, 6)
|
||||
pixel_y = rand(0, 10)
|
||||
|
||||
/mob/living/simple_animal/babyKiwi/Life()
|
||||
. =..()
|
||||
if(!.)
|
||||
/mob/living/simple_animal/babyKiwi/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(!stat && !ckey)
|
||||
amount_grown += rand(1,2)
|
||||
@@ -481,14 +494,16 @@
|
||||
/obj/item/udder
|
||||
name = "udder"
|
||||
|
||||
/obj/item/udder/Initialize()
|
||||
/obj/item/udder/Initialize(loc, milk_reagent)
|
||||
if(!milk_reagent)
|
||||
milk_reagent = /datum/reagent/consumable/milk
|
||||
create_reagents(50, NONE, NO_REAGENTS_VALUE)
|
||||
reagents.add_reagent(/datum/reagent/consumable/milk, 20)
|
||||
reagents.add_reagent(milk_reagent, 20)
|
||||
. = ..()
|
||||
|
||||
/obj/item/udder/proc/generateMilk()
|
||||
/obj/item/udder/proc/generateMilk(datum/reagent/milk_reagent)
|
||||
if(prob(5))
|
||||
reagents.add_reagent(/datum/reagent/consumable/milk, rand(5, 10))
|
||||
reagents.add_reagent(milk_reagent, rand(5, 10))
|
||||
|
||||
/obj/item/udder/proc/milkAnimal(obj/O, mob/user)
|
||||
var/obj/item/reagent_containers/glass/G = O
|
||||
@@ -529,4 +544,4 @@
|
||||
health = 75
|
||||
maxHealth = 75
|
||||
blood_volume = BLOOD_VOLUME_NORMAL
|
||||
footstep_type = FOOTSTEP_MOB_SHOE
|
||||
footstep_type = FOOTSTEP_MOB_SHOE
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
var/body_color //brown, gray and white, leave blank for random
|
||||
gold_core_spawnable = FRIENDLY_SPAWN
|
||||
var/chew_probability = 1
|
||||
faction = list("rat")
|
||||
|
||||
/mob/living/simple_animal/mouse/Initialize()
|
||||
. = ..()
|
||||
@@ -59,11 +60,15 @@
|
||||
else
|
||||
..(gibbed)
|
||||
|
||||
|
||||
/mob/living/simple_animal/mouse/Crossed(AM as mob|obj)
|
||||
if( ishuman(AM) )
|
||||
if(!stat)
|
||||
var/mob/M = AM
|
||||
to_chat(M, "<span class='notice'>[icon2html(src, M)] Squeak!</span>")
|
||||
if(istype(AM, /obj/item/reagent_containers/food/snacks/royalcheese))
|
||||
evolve()
|
||||
qdel(AM)
|
||||
..()
|
||||
|
||||
/mob/living/simple_animal/mouse/handle_automated_action()
|
||||
@@ -84,6 +89,40 @@
|
||||
C.deconstruct()
|
||||
visible_message("<span class='warning'>[src] chews through the [C].</span>")
|
||||
|
||||
for(var/obj/item/reagent_containers/food/snacks/cheesewedge/cheese in range(1, src))
|
||||
if(prob(10))
|
||||
be_fruitful()
|
||||
qdel(cheese)
|
||||
return
|
||||
for(var/obj/item/reagent_containers/food/snacks/royalcheese/bigcheese in range(1, src))
|
||||
qdel(bigcheese)
|
||||
evolve()
|
||||
return
|
||||
|
||||
/**
|
||||
*Checks the mouse cap, if it's above the cap, doesn't spawn a mouse. If below, spawns a mouse and adds it to cheeserats.
|
||||
*/
|
||||
|
||||
/mob/living/simple_animal/mouse/proc/be_fruitful()
|
||||
var/cap = CONFIG_GET(number/ratcap)
|
||||
if(LAZYLEN(SSmobs.cheeserats) >= cap)
|
||||
visible_message("<span class='warning'>[src] carefully eats the cheese, hiding it from the [cap] mice on the station!</span>")
|
||||
return
|
||||
var/mob/living/newmouse = new /mob/living/simple_animal/mouse(loc)
|
||||
SSmobs.cheeserats += newmouse
|
||||
visible_message("<span class='notice'>[src] nibbles through the cheese, attracting another mouse!</span>")
|
||||
|
||||
/**
|
||||
*Spawns a new regal rat, says some good jazz, and if sentient, transfers the relivant mind.
|
||||
*/
|
||||
/mob/living/simple_animal/mouse/proc/evolve()
|
||||
var/mob/living/simple_animal/hostile/regalrat = new /mob/living/simple_animal/hostile/regalrat(loc)
|
||||
visible_message("<span class='warning'>[src] devours the cheese! He morphs into something... greater!</span>")
|
||||
regalrat.say("RISE, MY SUBJECTS! SCREEEEEEE!")
|
||||
if(mind)
|
||||
mind.transfer_to(regalrat)
|
||||
qdel(src)
|
||||
|
||||
/*
|
||||
* Mouse types
|
||||
*/
|
||||
@@ -100,6 +139,10 @@
|
||||
body_color = "brown"
|
||||
icon_state = "mouse_brown"
|
||||
|
||||
/mob/living/simple_animal/mouse/Destroy()
|
||||
SSmobs.cheeserats -= src
|
||||
return ..()
|
||||
|
||||
GLOBAL_VAR(tom_existed)
|
||||
|
||||
//TOM IS ALIVE! SQUEEEEEEEE~K :)
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
//simplemob plushie that can be controlled by players
|
||||
/mob/living/simple_animal/pet/plushie
|
||||
name = "Plushie"
|
||||
desc = "A living plushie!"
|
||||
icon = 'icons/obj/plushes.dmi'
|
||||
icon_state = "debug"
|
||||
icon_living = "debug"
|
||||
icon_dead = "debug"
|
||||
speak_emote = list("squeaks")
|
||||
maxHealth = 50
|
||||
health = 50
|
||||
density = FALSE
|
||||
pass_flags = PASSTABLE | PASSGRILLE | PASSMOB
|
||||
mob_size = MOB_SIZE_TINY
|
||||
mob_biotypes = MOB_ORGANIC
|
||||
verb_say = "squeaks"
|
||||
verb_ask = "squeaks inquisitively"
|
||||
verb_exclaim = "squeaks intensely"
|
||||
verb_yell = "squeaks intensely"
|
||||
attack_sound = 'sound/items/toysqueak1.ogg'
|
||||
attacked_sound = 'sound/items/toysqueak1.ogg'
|
||||
melee_damage_type = STAMINA
|
||||
melee_damage_lower = 0
|
||||
melee_damage_upper = 1
|
||||
attack_verb_continuous = "squeaks"
|
||||
attack_verb_simple = "squeak"
|
||||
deathmessage = "lets out a faint squeak as the glint in its eyes disappears"
|
||||
footstep_type = FOOTSTEP_MOB_BAREFOOT
|
||||
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
|
||||
pressure_resistance = 200
|
||||
|
||||
/mob/living/simple_animal/pet/plushie/ComponentInitialize()
|
||||
. = ..()
|
||||
AddElement(/datum/element/mob_holder, "plushie")
|
||||
|
||||
//shell that lets people turn into the plush or poll for ghosts
|
||||
/obj/item/toy/plushie_shell
|
||||
name = "Plushie Shell"
|
||||
desc = "A plushie. Its eyes seem to be staring right back at you. Something isn't quite right."
|
||||
icon = 'icons/obj/plushes.dmi'
|
||||
icon_state = "debug"
|
||||
var/obj/item/toy/plush/stored_plush = null
|
||||
|
||||
//attacking yourself transfers your mind into the plush!
|
||||
/obj/item/toy/plushie_shell/attack_self(mob/user)
|
||||
if(user.mind)
|
||||
var/safety = alert(user, "The plushie is staring back at you intensely, it seems cursed! (Permanently become a plushie)", "Hugging this is a bad idea.", "Hug it!", "Cancel")
|
||||
if(safety == "Cancel" || !in_range(src, user))
|
||||
return
|
||||
to_chat(user, "<span class='userdanger'>You hug the strange plushie. You fool.</span>")
|
||||
|
||||
//setup the mob
|
||||
var/mob/living/simple_animal/pet/plushie/new_plushie = new /mob/living/simple_animal/pet/plushie/(user.loc)
|
||||
new_plushie.icon = src.icon
|
||||
new_plushie.icon_living = src.icon_state
|
||||
new_plushie.icon_dead = src.icon_state
|
||||
new_plushie.icon_state = src.icon_state
|
||||
new_plushie.name = src.name
|
||||
|
||||
//make the mob sentient
|
||||
user.mind.transfer_to(new_plushie)
|
||||
|
||||
//add sounds to mob
|
||||
new_plushie.AddComponent(/datum/component/squeak, stored_plush.squeak_override)
|
||||
if(length(stored_plush.squeak_override) > 0)
|
||||
new_plushie.attack_sound = stored_plush.squeak_override[1]
|
||||
new_plushie.attacked_sound = stored_plush.squeak_override[1]
|
||||
|
||||
//take care of the user's old body and the old shell
|
||||
user.dust(drop_items = TRUE)
|
||||
qdel(src)
|
||||
|
||||
//low regen over time
|
||||
/mob/living/simple_animal/pet/plushie/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(health < maxHealth)
|
||||
heal_overall_damage(5) //Decent life regen, they're not able to hurt anyone so this shouldn't be an issue (butterbear for reference has 10 regen)
|
||||
@@ -17,6 +17,7 @@
|
||||
response_disarm_simple = "gently push aside"
|
||||
response_harm_continuous = "kicks"
|
||||
response_harm_simple = "kick"
|
||||
mob_size = MOB_SIZE_SMALL
|
||||
mob_biotypes = MOB_ORGANIC|MOB_BEAST
|
||||
gold_core_spawnable = FRIENDLY_SPAWN
|
||||
melee_damage_lower = 18
|
||||
|
||||
@@ -160,7 +160,7 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
|
||||
to_chat(src, "<span class='notice'>Your new name <span class='name'>[new_name]</span> anchors itself in your mind.</span>")
|
||||
fully_replace_character_name(null, new_name)
|
||||
|
||||
/mob/living/simple_animal/hostile/guardian/Life() //Dies if the summoner dies
|
||||
/mob/living/simple_animal/hostile/guardian/PhysicalLife() //Dies if the summoner dies
|
||||
. = ..()
|
||||
update_health_hud() //we need to update all of our health displays to match our summoner and we can't practically give the summoner a hook to do it
|
||||
med_hud_set_health()
|
||||
|
||||
@@ -19,8 +19,9 @@
|
||||
. = ..()
|
||||
stealthcooldown = 0
|
||||
|
||||
/mob/living/simple_animal/hostile/guardian/assassin/Life()
|
||||
. = ..()
|
||||
/mob/living/simple_animal/hostile/guardian/assassin/PhysicalLife()
|
||||
if(!(. = ..()))
|
||||
return
|
||||
updatestealthalert()
|
||||
if(loc == summoner && toggle)
|
||||
ToggleMode(0)
|
||||
|
||||
@@ -11,8 +11,9 @@
|
||||
var/charging = 0
|
||||
var/obj/screen/alert/chargealert
|
||||
|
||||
/mob/living/simple_animal/hostile/guardian/charger/Life()
|
||||
. = ..()
|
||||
/mob/living/simple_animal/hostile/guardian/charger/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(ranged_cooldown <= world.time)
|
||||
if(!chargealert)
|
||||
chargealert = throw_alert("charge", /obj/screen/alert/cancharge)
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
tech_fluff_string = "<span class='holoparasite'>Boot sequence complete. Crowd control modules activated. Holoparasite swarm online.</span>"
|
||||
carp_fluff_string = "<span class='holoparasite'>CARP CARP CARP! You caught one! OH GOD, EVERYTHING'S ON FIRE. Except you and the fish.</span>"
|
||||
|
||||
/mob/living/simple_animal/hostile/guardian/fire/Life()
|
||||
. = ..()
|
||||
/mob/living/simple_animal/hostile/guardian/fire/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(summoner)
|
||||
summoner.ExtinguishMob()
|
||||
summoner.adjust_fire_stacks(-20)
|
||||
|
||||
@@ -125,7 +125,7 @@ mob/living/simple_animal/hostile/bear/butter //The mighty companion to Cak. Seve
|
||||
attack_verb_continuous = "slaps"
|
||||
attack_verb_simple = "slap"
|
||||
|
||||
/mob/living/simple_animal/hostile/bear/butter/Life() //Heals butter bear really fast when he takes damage.
|
||||
/mob/living/simple_animal/hostile/bear/butter/BiologicalLife(seconds, times_fired) //Heals butter bear really fast when he takes damage.
|
||||
if(stat)
|
||||
return
|
||||
if(health < maxHealth)
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
name = "A Perfectly Generic Boss Placeholder"
|
||||
desc = ""
|
||||
threat = 10
|
||||
robust_searching = 1
|
||||
robust_searching = TRUE
|
||||
stat_attack = UNCONSCIOUS
|
||||
status_flags = 0
|
||||
status_flags = NONE
|
||||
a_intent = INTENT_HARM
|
||||
gender = NEUTER
|
||||
has_field_of_vision = FALSE //You are a frikkin boss
|
||||
var/list/boss_abilities = list() //list of /datum/action/boss
|
||||
var/datum/boss_active_timed_battle/atb
|
||||
var/point_regen_delay = 1
|
||||
var/point_regen_delay = 20
|
||||
var/point_regen_amount = 1
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/boss/Initialize()
|
||||
@@ -18,6 +19,7 @@
|
||||
|
||||
atb = new()
|
||||
atb.point_regen_delay = point_regen_delay
|
||||
atb.point_regen_amount = point_regen_amount
|
||||
atb.boss = src
|
||||
|
||||
for(var/ab in boss_abilities)
|
||||
@@ -40,6 +42,7 @@
|
||||
required_mobility_flags = NONE
|
||||
var/boss_cost = 100 //Cost of usage for the boss' AI 1-100
|
||||
var/usage_probability = 100
|
||||
var/list/req_statuses //If set, will only trigger if the mob AI status is present in this list.
|
||||
var/mob/living/simple_animal/hostile/boss/boss
|
||||
var/boss_type = /mob/living/simple_animal/hostile/boss
|
||||
var/needs_target = TRUE //Does the boss need to have a target? (Only matters for the AI)
|
||||
@@ -57,7 +60,7 @@
|
||||
. = ..()
|
||||
boss = null
|
||||
|
||||
/datum/action/boss/Trigger()
|
||||
/datum/action/boss/IsAvailable(silent = FALSE)
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
@@ -69,6 +72,11 @@
|
||||
return FALSE
|
||||
if(!boss.client && needs_target && !boss.target)
|
||||
return FALSE
|
||||
|
||||
/datum/action/boss/Trigger()
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
if(!boss.atb.spend(boss_cost))
|
||||
return FALSE
|
||||
if(say_when_triggered)
|
||||
@@ -85,7 +93,8 @@
|
||||
//Designed for boss mobs only
|
||||
/datum/boss_active_timed_battle
|
||||
var/list/abilities //a list of /datum/action/boss owned by a boss mob
|
||||
var/point_regen_delay = 5
|
||||
var/point_regen_delay = 20
|
||||
var/point_regen_amount = 1
|
||||
var/max_points = 100
|
||||
var/points = 50 //start with 50 so we can use some abilities but not insta-buttfug somebody
|
||||
var/next_point_time = 0
|
||||
@@ -93,48 +102,45 @@
|
||||
var/highest_cost = 0
|
||||
var/mob/living/simple_animal/hostile/boss/boss
|
||||
|
||||
|
||||
/datum/boss_active_timed_battle/New()
|
||||
..()
|
||||
START_PROCESSING(SSobj, src)
|
||||
|
||||
|
||||
/datum/boss_active_timed_battle/proc/assign_abilities(list/L)
|
||||
if(!L)
|
||||
return 0
|
||||
return FALSE
|
||||
abilities = L
|
||||
for(var/ab in abilities)
|
||||
var/datum/action/boss/AB = ab
|
||||
if(AB.boss_cost > highest_cost)
|
||||
highest_cost = AB.boss_cost
|
||||
|
||||
|
||||
/datum/boss_active_timed_battle/proc/spend(cost)
|
||||
if(cost <= points)
|
||||
points = max(0,points-cost)
|
||||
points -= cost
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
|
||||
/datum/boss_active_timed_battle/proc/refund(cost)
|
||||
points = min(points+cost, max_points)
|
||||
|
||||
|
||||
/datum/boss_active_timed_battle/process()
|
||||
if(world.time >= next_point_time && points < max_points)
|
||||
next_point_time = world.time + point_regen_delay
|
||||
points = min(max_points, ++points) //has to be out of 100
|
||||
points = min(max_points, points + point_regen_amount)
|
||||
|
||||
if(abilities)
|
||||
chance_to_hold_onto_points = highest_cost*0.5
|
||||
if(points != max_points && prob(chance_to_hold_onto_points))
|
||||
return //Let's save our points for a better ability (unless we're at max points, in which case we can't save anymore!)
|
||||
if(!boss.client)
|
||||
abilities = shuffle(abilities)
|
||||
for(var/ab in abilities)
|
||||
var/datum/action/boss/AB = ab
|
||||
if(prob(AB.usage_probability) && AB.Trigger())
|
||||
break
|
||||
if(!abilities)
|
||||
return
|
||||
chance_to_hold_onto_points = highest_cost*0.5
|
||||
if(points != max_points && prob(chance_to_hold_onto_points))
|
||||
return //Let's save our points for a better ability (unless we're at max points, in which case we can't save anymore!)
|
||||
if(!boss.client)
|
||||
abilities = shuffle(abilities)
|
||||
for(var/ab in abilities)
|
||||
var/datum/action/boss/AB = ab
|
||||
if(!boss.client && (!AB.req_statuses || (boss.AIStatus in AB.req_statuses)) && prob(AB.usage_probability) && AB.Trigger())
|
||||
break
|
||||
AB.UpdateButtonIcon(TRUE)
|
||||
|
||||
|
||||
/datum/boss_active_timed_battle/Destroy()
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
boss_cost = 30
|
||||
boss_type = /mob/living/simple_animal/hostile/boss/paper_wizard
|
||||
needs_target = FALSE
|
||||
req_statuses = list(AI_ON)
|
||||
say_when_triggered = "Rise, my creations! Jump off your pages and into this realm!"
|
||||
var/list/summoned_minions = list()
|
||||
var/maximum_stickmen = 6
|
||||
@@ -85,6 +86,7 @@
|
||||
usage_probability = 30
|
||||
boss_cost = 40
|
||||
boss_type = /mob/living/simple_animal/hostile/boss/paper_wizard
|
||||
req_statuses = list(AI_ON)
|
||||
say_when_triggered = ""
|
||||
|
||||
/datum/action/boss/wizard_mimic/Trigger()
|
||||
|
||||
@@ -47,8 +47,9 @@
|
||||
if(regen_amount)
|
||||
regen_cooldown = world.time + REGENERATION_DELAY
|
||||
|
||||
/mob/living/simple_animal/hostile/carp/Life()
|
||||
. = ..()
|
||||
/mob/living/simple_animal/hostile/carp/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(regen_amount && regen_cooldown < world.time)
|
||||
heal_overall_damage(regen_amount)
|
||||
|
||||
|
||||
@@ -67,11 +67,10 @@
|
||||
foes = null
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/hostile/Life()
|
||||
. = ..()
|
||||
if(!.) //dead
|
||||
/mob/living/simple_animal/hostile/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
walk(src, 0) //stops walking
|
||||
return 0
|
||||
return
|
||||
|
||||
/mob/living/simple_animal/hostile/handle_automated_action()
|
||||
if(AIStatus == AI_OFF)
|
||||
@@ -117,7 +116,7 @@
|
||||
Move(get_step(src,chosen_dir))
|
||||
face_atom(target) //Looks better if they keep looking at you when dodging
|
||||
|
||||
/mob/living/simple_animal/hostile/attacked_by(obj/item/I, mob/living/user)
|
||||
/mob/living/simple_animal/hostile/attacked_by(obj/item/I, mob/living/user, attackchain_flags = NONE, damage_multiplier = 1)
|
||||
if(stat == CONSCIOUS && !target && AIStatus != AI_OFF && !client && user)
|
||||
FindTarget(list(user), 1)
|
||||
return ..()
|
||||
|
||||
@@ -23,13 +23,12 @@
|
||||
deathmessage = "vanishes into thin air! It was a fake!"
|
||||
has_field_of_vision = FALSE //not meant to be played anyway.
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/illusion/Life()
|
||||
..()
|
||||
/mob/living/simple_animal/hostile/illusion/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(world.time > life_span)
|
||||
death()
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/illusion/proc/Copy_Parent(mob/living/original, life = 50, hp = 100, damage = 0, replicate = 0 )
|
||||
appearance = original.appearance
|
||||
parent_mob = original
|
||||
|
||||
@@ -166,8 +166,9 @@
|
||||
if(!hopping)
|
||||
Hop()
|
||||
|
||||
/mob/living/simple_animal/hostile/jungle/leaper/Life()
|
||||
. = ..()
|
||||
/mob/living/simple_animal/hostile/jungle/leaper/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
update_icons()
|
||||
|
||||
/mob/living/simple_animal/hostile/jungle/leaper/adjustHealth(amount, updating_health = TRUE, forced = FALSE)
|
||||
@@ -197,7 +198,7 @@
|
||||
hopping = TRUE
|
||||
density = FALSE
|
||||
pass_flags |= PASSMOB
|
||||
notransform = TRUE
|
||||
mob_transforming = TRUE
|
||||
var/turf/new_turf = locate((target.x + rand(-3,3)),(target.y + rand(-3,3)),target.z)
|
||||
if(player_hop)
|
||||
new_turf = get_turf(target)
|
||||
@@ -209,7 +210,7 @@
|
||||
|
||||
/mob/living/simple_animal/hostile/jungle/leaper/proc/FinishHop()
|
||||
density = TRUE
|
||||
notransform = FALSE
|
||||
mob_transforming = FALSE
|
||||
pass_flags &= ~PASSMOB
|
||||
hopping = FALSE
|
||||
playsound(src.loc, 'sound/effects/meteorimpact.ogg', 100, 1)
|
||||
@@ -220,7 +221,7 @@
|
||||
/mob/living/simple_animal/hostile/jungle/leaper/proc/BellyFlop()
|
||||
var/turf/new_turf = get_turf(target)
|
||||
hopping = TRUE
|
||||
notransform = TRUE
|
||||
mob_transforming = TRUE
|
||||
new /obj/effect/temp_visual/leaper_crush(new_turf)
|
||||
addtimer(CALLBACK(src, .proc/BellyFlopHop, new_turf), 30)
|
||||
|
||||
@@ -231,7 +232,7 @@
|
||||
/mob/living/simple_animal/hostile/jungle/leaper/proc/Crush()
|
||||
hopping = FALSE
|
||||
density = TRUE
|
||||
notransform = FALSE
|
||||
mob_transforming = FALSE
|
||||
playsound(src, 'sound/effects/meteorimpact.ogg', 200, 1)
|
||||
for(var/mob/living/L in orange(1, src))
|
||||
L.adjustBruteLoss(35)
|
||||
|
||||
@@ -27,8 +27,9 @@
|
||||
|
||||
footstep_type = FOOTSTEP_MOB_CLAW
|
||||
|
||||
/mob/living/simple_animal/hostile/jungle/mega_arachnid/Life()
|
||||
..()
|
||||
/mob/living/simple_animal/hostile/jungle/mega_arachnid/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(target && ranged_cooldown > world.time && iscarbon(target))
|
||||
var/mob/living/carbon/C = target
|
||||
if(!C.legcuffed && C.health < 50)
|
||||
@@ -40,7 +41,6 @@
|
||||
minimum_distance = 0
|
||||
alpha = 255
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/jungle/mega_arachnid/Aggro()
|
||||
..()
|
||||
alpha = 255
|
||||
|
||||
@@ -65,8 +65,9 @@ Difficulty: Hard
|
||||
desc = "You're not quite sure how a signal can be bloody."
|
||||
invisibility = 100
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/bubblegum/Life()
|
||||
..()
|
||||
/mob/living/simple_animal/hostile/megafauna/bubblegum/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
move_to_delay = clamp(round((health/maxHealth) * 10), 3, 10)
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/bubblegum/OpenFire()
|
||||
|
||||
@@ -731,7 +731,7 @@ Difficulty: Very Hard
|
||||
/obj/structure/closet/stasis/Entered(atom/A)
|
||||
if(isliving(A) && holder_animal)
|
||||
var/mob/living/L = A
|
||||
L.notransform = 1
|
||||
L.mob_transforming = 1
|
||||
ADD_TRAIT(L, TRAIT_MUTE, STASIS_MUTE)
|
||||
L.status_flags |= GODMODE
|
||||
L.mind.transfer_to(holder_animal)
|
||||
@@ -744,7 +744,7 @@ Difficulty: Very Hard
|
||||
for(var/mob/living/L in src)
|
||||
REMOVE_TRAIT(L, TRAIT_MUTE, STASIS_MUTE)
|
||||
L.status_flags &= ~GODMODE
|
||||
L.notransform = 0
|
||||
L.mob_transforming = 0
|
||||
if(holder_animal)
|
||||
holder_animal.mind.transfer_to(L)
|
||||
L.mind.RemoveSpell(/obj/effect/proc_holder/spell/targeted/exit_possession)
|
||||
|
||||
@@ -88,9 +88,10 @@ Difficulty: Normal
|
||||
/mob/living/simple_animal/hostile/megafauna/hierophant/spawn_crusher_loot()
|
||||
new /obj/item/crusher_trophy/vortex_talisman(get_turf(spawned_beacon))
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/hierophant/Life()
|
||||
. = ..()
|
||||
if(. && spawned_beacon && !QDELETED(spawned_beacon) && !client)
|
||||
/mob/living/simple_animal/hostile/megafauna/hierophant/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(spawned_beacon && !QDELETED(spawned_beacon) && !client)
|
||||
if(target || loc == spawned_beacon.loc)
|
||||
timeout_time = initial(timeout_time)
|
||||
else
|
||||
|
||||
@@ -73,14 +73,13 @@ GLOBAL_LIST_INIT(AISwarmerCapsByType, list(/mob/living/simple_animal/hostile/swa
|
||||
step(R, ddir) //Step the swarmers, instead of spawning them there, incase the turf is solid
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/swarmer_swarm_beacon/Life()
|
||||
. = ..()
|
||||
if(.)
|
||||
var/createtype = GetUncappedAISwarmerType()
|
||||
if(createtype && world.time > swarmer_spawn_cooldown && GLOB.AISwarmers.len < (GetTotalAISwarmerCap()*0.5))
|
||||
swarmer_spawn_cooldown = world.time + swarmer_spawn_cooldown_amt
|
||||
new createtype(loc)
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/swarmer_swarm_beacon/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
var/createtype = GetUncappedAISwarmerType()
|
||||
if(createtype && world.time > swarmer_spawn_cooldown && GLOB.AISwarmers.len < (GetTotalAISwarmerCap()*0.5))
|
||||
swarmer_spawn_cooldown = world.time + swarmer_spawn_cooldown_amt
|
||||
new createtype(loc)
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/swarmer_swarm_beacon/adjustHealth(amount, updating_health = TRUE, forced = FALSE)
|
||||
. = ..()
|
||||
|
||||
@@ -118,8 +118,9 @@ GLOBAL_LIST_INIT(protected_objects, list(/obj/structure/table, /obj/structure/ca
|
||||
overlay_googly_eyes = FALSE
|
||||
CopyObject(copy, creator, destroy_original)
|
||||
|
||||
/mob/living/simple_animal/hostile/mimic/copy/Life()
|
||||
..()
|
||||
/mob/living/simple_animal/hostile/mimic/copy/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(idledamage && !target && !ckey) //Objects eventually revert to normal if no one is around to terrorize
|
||||
adjustBruteLoss(1)
|
||||
for(var/mob/living/M in contents) //a fix for animated statues from the flesh to stone spell
|
||||
|
||||
@@ -88,8 +88,9 @@
|
||||
wanted_objects = list(/obj/item/pen/survival, /obj/item/stack/ore/diamond)
|
||||
field_of_vision_type = FOV_270_DEGREES //Obviously, it's one eyeball.
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/basilisk/watcher/Life()
|
||||
. = ..()
|
||||
/mob/living/simple_animal/hostile/asteroid/basilisk/watcher/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(stat == CONSCIOUS)
|
||||
consume_bait()
|
||||
|
||||
|
||||
@@ -103,8 +103,9 @@ IGNORE_PROC_IF_NOT_TARGET(attack_slime)
|
||||
return BULLET_ACT_FORCE_PIERCE
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/curseblob/attacked_by(obj/item/I, mob/living/L)
|
||||
/mob/living/simple_animal/hostile/asteroid/curseblob/attacked_by(obj/item/I, mob/living/L, attackchain_flags = NONE, damage_multiplier = 1)
|
||||
if(L != set_target)
|
||||
L.changeNext_move(I.click_delay) //pre_attacked_by not called
|
||||
return
|
||||
return ..()
|
||||
|
||||
|
||||
+2
-3
@@ -97,9 +97,8 @@
|
||||
if(CALL_CHILDREN)
|
||||
call_children()
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/broodmother/Life()
|
||||
. = ..()
|
||||
if(!.) //Checks if they are dead as a rock.
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/broodmother/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(health < maxHealth * 0.5 && rand_tent < world.time)
|
||||
rand_tent = world.time + 30
|
||||
|
||||
@@ -95,8 +95,9 @@
|
||||
if(AOE_SQUARES)
|
||||
aoe_squares(target)
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/pandora/Life()
|
||||
. = ..()
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/pandora/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(health >= maxHealth * 0.5)
|
||||
cooldown_time = 20
|
||||
return
|
||||
|
||||
@@ -39,8 +39,9 @@
|
||||
|
||||
footstep_type = FOOTSTEP_MOB_HEAVY
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/goliath/Life()
|
||||
. = ..()
|
||||
/mob/living/simple_animal/hostile/asteroid/goliath/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
handle_preattack()
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/goliath/proc/handle_preattack()
|
||||
@@ -129,9 +130,8 @@
|
||||
var/turf/last_location
|
||||
var/tentacle_recheck_cooldown = 100
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/goliath/beast/ancient/Life()
|
||||
. = ..()
|
||||
if(!.) // dead
|
||||
/mob/living/simple_animal/hostile/asteroid/goliath/beast/ancient/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(isturf(loc))
|
||||
if(!LAZYLEN(cached_tentacle_turfs) || loc != last_location || tentacle_recheck_cooldown <= world.time)
|
||||
|
||||
@@ -114,8 +114,9 @@
|
||||
name = "guthen"
|
||||
gender = FEMALE
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/gutlunch/guthen/Life()
|
||||
..()
|
||||
/mob/living/simple_animal/hostile/asteroid/gutlunch/guthen/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(udder.reagents.total_volume == udder.reagents.maximum_volume) //Only breed when we're full.
|
||||
make_babies()
|
||||
|
||||
|
||||
@@ -196,12 +196,13 @@
|
||||
swarming = TRUE
|
||||
var/can_infest_dead = FALSE
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/Life()
|
||||
/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(isturf(loc))
|
||||
for(var/mob/living/carbon/human/H in view(src,1)) //Only for corpse right next to/on same tile
|
||||
if(H.stat == UNCONSCIOUS || (can_infest_dead && H.stat == DEAD))
|
||||
infest(H)
|
||||
..()
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/proc/infest(mob/living/carbon/human/H)
|
||||
visible_message("<span class='warning'>[name] burrows into the flesh of [H]!</span>")
|
||||
|
||||
@@ -62,9 +62,10 @@
|
||||
SLEEP_CHECK_DEATH(8)
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/ice_demon/Life()
|
||||
. = ..()
|
||||
if(!. || target)
|
||||
/mob/living/simple_animal/hostile/asteroid/ice_demon/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(target)
|
||||
return
|
||||
adjustHealth(-maxHealth*0.025)
|
||||
|
||||
|
||||
@@ -43,9 +43,10 @@
|
||||
var/list/burn_turfs = getline(src, T) - get_turf(src)
|
||||
dragon_fire_line(src, burn_turfs)
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/ice_whelp/Life()
|
||||
. = ..()
|
||||
if(!. || target)
|
||||
/mob/living/simple_animal/hostile/asteroid/ice_whelp/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(target)
|
||||
return
|
||||
adjustHealth(-maxHealth*0.025)
|
||||
|
||||
|
||||
@@ -44,9 +44,10 @@
|
||||
aggressive_message_said = TRUE
|
||||
rapid_melee = 2
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/polarbear/Life()
|
||||
. = ..()
|
||||
if(!. || target)
|
||||
/mob/living/simple_animal/hostile/asteroid/polarbear/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(target)
|
||||
return
|
||||
adjustHealth(-maxHealth*0.025)
|
||||
aggressive_message_said = FALSE
|
||||
|
||||
@@ -51,9 +51,10 @@
|
||||
retreat_message_said = TRUE
|
||||
retreat_distance = 30
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/wolf/Life()
|
||||
. = ..()
|
||||
if(!. || target)
|
||||
/mob/living/simple_animal/hostile/asteroid/wolf/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(target)
|
||||
return
|
||||
adjustHealth(-maxHealth*0.025)
|
||||
retreat_message_said = FALSE
|
||||
|
||||
@@ -48,8 +48,9 @@
|
||||
else
|
||||
. += "<span class='info'>It looks like it's been roughed up.</span>"
|
||||
|
||||
/mob/living/simple_animal/hostile/mushroom/Life()
|
||||
..()
|
||||
/mob/living/simple_animal/hostile/mushroom/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(!stat)//Mushrooms slowly regenerate if conscious, for people who want to save them from being eaten
|
||||
adjustBruteLoss(-2)
|
||||
|
||||
|
||||
@@ -46,8 +46,9 @@
|
||||
var/chosen_sound = pick(migo_sounds)
|
||||
playsound(src, chosen_sound, 100, TRUE)
|
||||
|
||||
/mob/living/simple_animal/hostile/netherworld/migo/Life()
|
||||
..()
|
||||
/mob/living/simple_animal/hostile/netherworld/migo/BiologicalLife(seconds, times_fired)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
if(stat)
|
||||
return
|
||||
if(prob(10))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user