Merge remote-tracking branch 'citadel/master' into pp_fix
This commit is contained in:
@@ -167,4 +167,4 @@
|
||||
///obj/item/pipe = 2)
|
||||
time = 80
|
||||
category = CAT_WEAPONRY
|
||||
subcategory = CAT_WEAPON
|
||||
subcategory = CAT_MELEE
|
||||
|
||||
@@ -432,15 +432,18 @@
|
||||
/obj/item/shield/changeling
|
||||
name = "shield-like mass"
|
||||
desc = "A mass of tough, boney tissue. You can still see the fingers as a twisted pattern in the shield."
|
||||
item_flags = ABSTRACT | DROPDEL
|
||||
item_flags = ABSTRACT | DROPDEL | ITEM_CAN_BLOCK
|
||||
icon = 'icons/obj/items_and_weapons.dmi'
|
||||
icon_state = "ling_shield"
|
||||
lefthand_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/antag/changeling_righthand.dmi'
|
||||
block_chance = 50
|
||||
block_parry_data = /datum/block_parry_data/shield/changeling
|
||||
|
||||
var/remaining_uses //Set by the changeling ability.
|
||||
|
||||
/datum/block_parry_data/shield/changeling
|
||||
block_slowdown = 0
|
||||
|
||||
/obj/item/shield/changeling/Initialize(mapload)
|
||||
. = ..()
|
||||
ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
|
||||
@@ -451,7 +454,7 @@
|
||||
block_return[BLOCK_RETURN_BLOCK_CAPACITY] = (block_return[BLOCK_RETURN_BLOCK_CAPACITY] || 0) + remaining_uses
|
||||
return ..()
|
||||
|
||||
/obj/item/shield/changeling/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
/obj/item/shield/changeling/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(--remaining_uses < 1)
|
||||
if(ishuman(loc))
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
return 1
|
||||
return ..()
|
||||
|
||||
/obj/structure/destructible/clockwork/attacked_by(obj/item/I, mob/living/user)
|
||||
/obj/structure/destructible/clockwork/attacked_by(obj/item/I, mob/living/user, attackchain_flags = NONE, damage_multiplier = 1)
|
||||
if(is_servant_of_ratvar(user) && immune_to_servant_attacks)
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
@@ -273,6 +273,7 @@
|
||||
knockdown = 20
|
||||
|
||||
/obj/item/restraints/legcuffs/bola/cult/pickup(mob/living/user)
|
||||
. = ..()
|
||||
if(!iscultist(user))
|
||||
to_chat(user, "<span class='warning'>The bola seems to take on a life of its own!</span>")
|
||||
ensnare(user)
|
||||
|
||||
@@ -116,8 +116,7 @@
|
||||
/mob/living/carbon/true_devil/get_ear_protection()
|
||||
return 2
|
||||
|
||||
|
||||
/mob/living/carbon/true_devil/attacked_by(obj/item/I, mob/living/user, def_zone)
|
||||
/mob/living/carbon/true_devil/attacked_by(obj/item/I, mob/living/user, def_zone, attackchain_flags = NONE, damage_multiplier = 1)
|
||||
var/totitemdamage = pre_attacked_by(I, user)
|
||||
totitemdamage *= check_weakness(I, user)
|
||||
apply_damage(totitemdamage, I.damtype, def_zone)
|
||||
|
||||
@@ -162,6 +162,9 @@
|
||||
to_chat(user, "<span class='danger'>Access denied.</span>")
|
||||
return UI_CLOSE
|
||||
|
||||
/obj/machinery/atmospherics/components/attack_ghost(mob/dead/observer/O)
|
||||
. = ..()
|
||||
atmosanalyzer_scan(airs, O, src, FALSE)
|
||||
|
||||
// Tool acts
|
||||
|
||||
|
||||
@@ -111,3 +111,10 @@
|
||||
pipe_color = paint_color
|
||||
update_node_icon()
|
||||
return TRUE
|
||||
|
||||
/obj/machinery/atmospherics/pipe/attack_ghost(mob/dead/observer/O)
|
||||
. = ..()
|
||||
if(parent)
|
||||
atmosanalyzer_scan(parent.air, O, src, FALSE)
|
||||
else
|
||||
to_chat(O, "<span class='warning'>[src] doesn't have a pipenet, which is probably a bug.</span>")
|
||||
|
||||
@@ -147,10 +147,14 @@
|
||||
/obj/machinery/portable_atmospherics/analyzer_act(mob/living/user, obj/item/I)
|
||||
atmosanalyzer_scan(air_contents, user, src)
|
||||
|
||||
/obj/machinery/portable_atmospherics/attacked_by(obj/item/I, mob/user)
|
||||
/obj/machinery/portable_atmospherics/attacked_by(obj/item/I, mob/user, attackchain_flags = NONE, damage_multiplier = 1)
|
||||
if(I.force < 10 && !(stat & BROKEN))
|
||||
take_damage(0)
|
||||
else
|
||||
investigate_log("was smacked with \a [I] by [key_name(user)].", INVESTIGATE_ATMOS)
|
||||
add_fingerprint(user)
|
||||
..()
|
||||
|
||||
/obj/machinery/portable_atmospherics/attack_ghost(mob/dead/observer/O)
|
||||
. = ..()
|
||||
atmosanalyzer_scan(air_contents, O, src, FALSE)
|
||||
|
||||
@@ -133,3 +133,6 @@
|
||||
var/parallax_movedir = 0
|
||||
var/parallax_layers_max = 3
|
||||
var/parallax_animate_timer
|
||||
|
||||
//world.time of when the crew manifest can be accessed
|
||||
var/crew_manifest_delay
|
||||
|
||||
@@ -1638,19 +1638,19 @@ GLOBAL_LIST_EMPTY(preferences_datums)
|
||||
age = max(min( round(text2num(new_age)), AGE_MAX),AGE_MIN)
|
||||
|
||||
if("flavor_text")
|
||||
var/msg = stripped_multiline_input(usr, "Set the flavor text in your 'examine' verb. This can also be used for OOC notes and preferences!", "Flavor Text", features["flavor_text"], MAX_FLAVOR_LEN, TRUE)
|
||||
var/msg = stripped_multiline_input(usr, "Set the flavor text in your 'examine' verb. This can also be used for OOC notes and preferences!", "Flavor Text", html_decode(features["flavor_text"]), MAX_FLAVOR_LEN, TRUE)
|
||||
if(!isnull(msg))
|
||||
features["flavor_text"] = html_decode(msg)
|
||||
features["flavor_text"] = msg
|
||||
|
||||
if("silicon_flavor_text")
|
||||
var/msg = stripped_multiline_input(usr, "Set the silicon flavor text in your 'examine' verb. This can also be used for OOC notes and preferences!", "Silicon Flavor Text", features["silicon_flavor_text"], MAX_FLAVOR_LEN, TRUE)
|
||||
var/msg = stripped_multiline_input(usr, "Set the silicon flavor text in your 'examine' verb. This can also be used for OOC notes and preferences!", "Silicon Flavor Text", html_decode(features["silicon_flavor_text"]), MAX_FLAVOR_LEN, TRUE)
|
||||
if(!isnull(msg))
|
||||
features["silicon_flavor_text"] = html_decode(msg)
|
||||
features["silicon_flavor_text"] = msg
|
||||
|
||||
if("ooc_notes")
|
||||
var/msg = stripped_multiline_input(usr, "Set always-visible OOC notes related to content preferences. THIS IS NOT FOR CHARACTER DESCRIPTIONS!", "OOC notes", features["ooc_notes"], MAX_FLAVOR_LEN, TRUE)
|
||||
var/msg = stripped_multiline_input(usr, "Set always-visible OOC notes related to content preferences. THIS IS NOT FOR CHARACTER DESCRIPTIONS!", "OOC notes", html_decode(features["ooc_notes"]), MAX_FLAVOR_LEN, TRUE)
|
||||
if(!isnull(msg))
|
||||
features["ooc_notes"] = html_decode(msg)
|
||||
features["ooc_notes"] = msg
|
||||
|
||||
if("hair")
|
||||
var/new_hair = input(user, "Choose your character's hair colour:", "Character Preference","#"+hair_color) as color|null
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// You do not need to raise this if you are adding new values that have sane defaults.
|
||||
// Only raise this value when changing the meaning/format/name/layout of an existing value
|
||||
// where you would want the updater procs below to run
|
||||
#define SAVEFILE_VERSION_MAX 32
|
||||
#define SAVEFILE_VERSION_MAX 33
|
||||
|
||||
/*
|
||||
SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Carn
|
||||
@@ -194,6 +194,11 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
if(current_version < 31)
|
||||
S["wing_color"] >> features["wings_color"]
|
||||
S["horn_color"] >> features["horns_color"]
|
||||
|
||||
if(current_version < 33)
|
||||
features["flavor_text"] = html_encode(features["flavor_text"])
|
||||
features["silicon_flavor_text"] = html_encode(features["silicon_flavor_text"])
|
||||
features["ooc_notes"] = html_encode(features["ooc_notes"])
|
||||
|
||||
/datum/preferences/proc/load_path(ckey,filename="preferences.sav")
|
||||
if(!ckey)
|
||||
|
||||
@@ -469,7 +469,7 @@ Contains:
|
||||
desc = "Voices echo from the hardsuit, driving the user insane. This one is pretty battle-worn, but still fearsome."
|
||||
armor = list("melee" = 55, "bullet" = 40, "laser" = 40, "energy" = 40, "bomb" = 40, "bio" = 80, "rad" = 80, "fire" = 60, "acid" = 60)
|
||||
slowdown = 0.8
|
||||
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/inquisitor/old
|
||||
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/beserker/old
|
||||
charges = 6
|
||||
|
||||
/obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/beserker/old
|
||||
|
||||
@@ -330,8 +330,8 @@
|
||||
if(!override)
|
||||
qdel(src)
|
||||
|
||||
/obj/structure/spacevine/attacked_by(obj/item/I, mob/living/user)
|
||||
var/damage_dealt = I.force
|
||||
/obj/structure/spacevine/attacked_by(obj/item/I, mob/living/user, attackchain_flags = NONE, damage_multiplier = 1)
|
||||
var/damage_dealt = I.force * damage_multiplier
|
||||
if(I.get_sharpness())
|
||||
damage_dealt *= 4
|
||||
if(I.damtype == BURN)
|
||||
|
||||
@@ -54,6 +54,15 @@
|
||||
tastes = list("fish" = 1, "chips" = 1)
|
||||
foodtype = MEAT | VEGETABLES | FRIED
|
||||
|
||||
/obj/item/reagent_containers/food/snacks/fishfry
|
||||
name = "fish fry"
|
||||
desc = "All that and no bag of chips..."
|
||||
icon_state = "fish_fry"
|
||||
list_reagents = list (/datum/reagent/consumable/nutriment = 6, /datum/reagent/consumable/nutriment/vitamin = 3)
|
||||
filling_color = "#ee7676"
|
||||
tastes = list("fish" = 1, "pan seared vegtables" = 1)
|
||||
foodtype = MEAT | VEGETABLES | FRIED
|
||||
|
||||
/obj/item/reagent_containers/food/snacks/sushi_basic
|
||||
name = "funa hosomaki"
|
||||
desc = "A small cylindrical kudzu skin, filled with rice and fish."
|
||||
|
||||
@@ -144,6 +144,15 @@
|
||||
is_decorated = TRUE
|
||||
filling_color = "#879630"
|
||||
|
||||
/obj/item/reagent_containers/food/snacks/donut/laugh
|
||||
name = "sweet pea donut"
|
||||
desc = "Goes great with a glass of Bastion Burbon!"
|
||||
icon_state = "donut_laugh"
|
||||
bonus_reagents = list(/datum/reagent/consumable/laughter = 3)
|
||||
tastes = list("donut" = 3, "fizzy tutti frutti" = 1,)
|
||||
is_decorated = TRUE
|
||||
filling_color = "#803280"
|
||||
|
||||
//////////////////////JELLY DONUTS/////////////////////////
|
||||
|
||||
/obj/item/reagent_containers/food/snacks/donut/jelly
|
||||
@@ -234,6 +243,15 @@
|
||||
is_decorated = TRUE
|
||||
filling_color = "#879630"
|
||||
|
||||
/obj/item/reagent_containers/food/snacks/donut/jelly/laugh
|
||||
name = "sweet pea jelly donut"
|
||||
desc = "Goes great with a glass of Bastion Burbon!"
|
||||
icon_state = "jelly_laugh"
|
||||
bonus_reagents = list(/datum/reagent/consumable/laughter = 3)
|
||||
tastes = list("jelly" = 3, "donut" = 1, "fizzy tutti frutti" = 1)
|
||||
is_decorated = TRUE
|
||||
filling_color = "#803280"
|
||||
|
||||
//////////////////////////SLIME DONUTS/////////////////////////
|
||||
|
||||
/obj/item/reagent_containers/food/snacks/donut/jelly/slimejelly
|
||||
@@ -315,6 +333,15 @@
|
||||
is_decorated = TRUE
|
||||
filling_color = "#879630"
|
||||
|
||||
/obj/item/reagent_containers/food/snacks/donut/jelly/slimejelly/laugh
|
||||
name = "sweet pea jelly donut"
|
||||
desc = "Goes great with a glass of Bastion Burbon!"
|
||||
icon_state = "jelly_laugh"
|
||||
bonus_reagents = list(/datum/reagent/consumable/laughter = 3)
|
||||
tastes = list("jelly" = 3, "donut" = 1, "fizzy tutti frutti" = 1)
|
||||
is_decorated = TRUE
|
||||
filling_color = "#803280"
|
||||
|
||||
/obj/item/reagent_containers/food/snacks/donut/glaze
|
||||
name = "glazed donut"
|
||||
desc = "A sugar glazed donut."
|
||||
|
||||
@@ -124,4 +124,22 @@
|
||||
trash = /obj/item/kitchen/knife
|
||||
bonus_reagents = list(/datum/reagent/medicine/earthsblood = 1, /datum/reagent/iron = 4)
|
||||
tastes = list("iron" = 1, "conspiracy" = 1)
|
||||
foodtype = VEGETABLES
|
||||
foodtype = VEGETABLES
|
||||
|
||||
/obj/item/reagent_containers/food/snacks/salad/edensalad
|
||||
name = "\improper Salad of Eden"
|
||||
desc = "A salad brimming with untapped potential."
|
||||
icon_state = "eden_salad"
|
||||
trash = /obj/item/reagent_containers/glass/bowl
|
||||
list_reagents = list(/datum/reagent/consumable/nutriment = 7, /datum/reagent/consumable/nutriment/vitamin = 5, /datum/reagent/medicine/earthsblood = 3, /datum/reagent/medicine/omnizine = 5, /datum/reagent/drug/happiness = 2)
|
||||
tastes = list("hope" = 1)
|
||||
foodtype = VEGETABLES
|
||||
|
||||
/obj/item/reagent_containers/food/snacks/salad/gumbo
|
||||
name = "black eyed gumbo"
|
||||
desc = "A spicy and savory meat and rice dish."
|
||||
icon_state = "gumbo"
|
||||
trash = /obj/item/reagent_containers/glass/bowl
|
||||
list_reagents = list(/datum/reagent/consumable/capsaicin = 2, /datum/reagent/consumable/nutriment/vitamin = 3, /datum/reagent/consumable/nutriment = 5)
|
||||
tastes = list("building heat" = 2, "savory meat and vegtables" = 1)
|
||||
foodtype = GRAIN | MEAT | VEGETABLES
|
||||
|
||||
@@ -262,3 +262,13 @@
|
||||
tastes = list("bungo" = 2, "hot curry" = 4, "tropical sweetness" = 1)
|
||||
filling_color = "#E6A625"
|
||||
foodtype = VEGETABLES | FRUIT | DAIRY
|
||||
|
||||
/obj/item/reagent_containers/food/snacks/soup/peasoup
|
||||
name = "pea soup"
|
||||
desc = "A humble split pea soup."
|
||||
icon_state = "peasoup"
|
||||
bonus_reagents = list (/datum/reagent/consumable/nutriment/vitamin = 6, /datum/reagent/medicine/oculine = 2)
|
||||
list_reagents = list (/datum/reagent/consumable/nutriment = 8)
|
||||
tastes = list("creamy peas"= 2, "parsnip" = 1)
|
||||
filling_color = "#9dc530"
|
||||
foodtype = VEGETABLES
|
||||
|
||||
@@ -121,6 +121,15 @@ datum/crafting_recipe/food/donut/meat
|
||||
)
|
||||
result = /obj/item/reagent_containers/food/snacks/donut/matcha
|
||||
|
||||
|
||||
/datum/crafting_recipe/food/donut/laugh
|
||||
name = "Sweet Pea Donut"
|
||||
reqs = list(
|
||||
/datum/reagent/consumable/laughsyrup = 3,
|
||||
/obj/item/reagent_containers/food/snacks/donut/plain = 1
|
||||
)
|
||||
result = /obj/item/reagent_containers/food/snacks/donut/laugh
|
||||
|
||||
////////////////////////////////////////////////////JELLY DONUTS///////////////////////////////////////////////////////
|
||||
|
||||
/datum/crafting_recipe/food/donut/jelly/apple
|
||||
@@ -187,6 +196,14 @@ datum/crafting_recipe/food/donut/meat
|
||||
)
|
||||
result = /obj/item/reagent_containers/food/snacks/donut/jelly/trumpet
|
||||
|
||||
/datum/crafting_recipe/food/donut/jelly/laugh
|
||||
name = "Sweet Pea Jelly Donut"
|
||||
reqs = list(
|
||||
/datum/reagent/consumable/laughsyrup = 3,
|
||||
/obj/item/reagent_containers/food/snacks/donut/jelly/plain = 1
|
||||
)
|
||||
result = /obj/item/reagent_containers/food/snacks/donut/jelly/laugh
|
||||
|
||||
////////////////////////////////////////////////////SLIME DONUTS///////////////////////////////////////////////////////
|
||||
|
||||
/datum/crafting_recipe/food/donut/slimejelly/apple
|
||||
@@ -253,3 +270,11 @@ datum/crafting_recipe/food/donut/meat
|
||||
/obj/item/reagent_containers/food/snacks/donut/jelly/slimejelly/plain = 1
|
||||
)
|
||||
result = /obj/item/reagent_containers/food/snacks/donut/jelly/slimejelly/matcha
|
||||
|
||||
/datum/crafting_recipe/food/donut/slimejelly/laugh
|
||||
name = "Sweet Pea Jelly Donut"
|
||||
reqs = list(
|
||||
/datum/reagent/consumable/laughsyrup = 3,
|
||||
/obj/item/reagent_containers/food/snacks/donut/jelly/slimejelly/plain = 1
|
||||
)
|
||||
result = /obj/item/reagent_containers/food/snacks/donut/jelly/slimejelly/laugh
|
||||
|
||||
@@ -171,3 +171,14 @@
|
||||
)
|
||||
result = /obj/item/reagent_containers/food/snacks/salad/ricepork
|
||||
subcategory = CAT_MEAT
|
||||
|
||||
/datum/crafting_recipe/food/gumbo
|
||||
name = "Black eyed gumbo"
|
||||
reqs = list(
|
||||
/obj/item/reagent_containers/food/snacks/salad/boiledrice = 1,
|
||||
/obj/item/reagent_containers/food/snacks/grown/peas = 1,
|
||||
/obj/item/reagent_containers/food/snacks/grown/chili = 1,
|
||||
/obj/item/reagent_containers/food/snacks/meat/cutlet = 1
|
||||
)
|
||||
result = /obj/item/reagent_containers/food/snacks/salad/gumbo
|
||||
subcategory = CAT_MEAT
|
||||
|
||||
@@ -93,4 +93,17 @@
|
||||
/obj/item/reagent_containers/food/snacks/grown/cabbage = 1
|
||||
)
|
||||
result = /obj/item/reagent_containers/food/snacks/salad/caesar
|
||||
subcategory = CAT_SALAD
|
||||
subcategory = CAT_SALAD
|
||||
|
||||
|
||||
/datum/crafting_recipe/food/edensalad
|
||||
name = "Salad of Eden"
|
||||
reqs = list(
|
||||
/obj/item/reagent_containers/glass/bowl =1,
|
||||
/obj/item/reagent_containers/food/snacks/grown/ambrosia/vulgaris = 1,
|
||||
/obj/item/reagent_containers/food/snacks/grown/ambrosia/deus = 1,
|
||||
/obj/item/reagent_containers/food/snacks/grown/ambrosia/gaia = 1,
|
||||
/obj/item/reagent_containers/food/snacks/grown/peace = 1
|
||||
)
|
||||
result = /obj/item/reagent_containers/food/snacks/salad/edensalad
|
||||
subcategory = CAT_SALAD
|
||||
|
||||
@@ -135,4 +135,14 @@
|
||||
/obj/item/reagent_containers/food/snacks/carpmeat = 1
|
||||
)
|
||||
result = /obj/item/reagent_containers/food/snacks/fishandchips
|
||||
subcategory = CAT_SEAFOOD
|
||||
subcategory = CAT_SEAFOOD
|
||||
|
||||
/datum/crafting_recipe/food/fishfry
|
||||
name = "Fish fry"
|
||||
reqs = list(
|
||||
/obj/item/reagent_containers/food/snacks/grown/corn = 1,
|
||||
/obj/item/reagent_containers/food/snacks/grown/peas =1,
|
||||
/obj/item/reagent_containers/food/snacks/carpmeat = 1
|
||||
)
|
||||
result = /obj/item/reagent_containers/food/snacks/fishfry
|
||||
subcategory = CAT_SEAFOOD
|
||||
|
||||
@@ -255,4 +255,16 @@
|
||||
/obj/item/reagent_containers/glass/bowl = 1
|
||||
)
|
||||
result= /obj/item/reagent_containers/food/snacks/soup/wish
|
||||
subcategory = CAT_SOUP
|
||||
subcategory = CAT_SOUP
|
||||
|
||||
|
||||
/datum/crafting_recipe/food/peasoup
|
||||
name = "Pea soup"
|
||||
reqs = list(
|
||||
/datum/reagent/water = 10,
|
||||
/obj/item/reagent_containers/food/snacks/grown/peas = 2,
|
||||
/obj/item/reagent_containers/food/snacks/grown/parsnip = 1,
|
||||
/obj/item/reagent_containers/food/snacks/grown/carrot = 1
|
||||
)
|
||||
result = /obj/item/reagent_containers/food/snacks/soup/peasoup
|
||||
subcategory = CAT_SOUP
|
||||
|
||||
@@ -189,6 +189,14 @@
|
||||
begin_day = 22
|
||||
begin_month = APRIL
|
||||
|
||||
/datum/holiday/lesbianvisibility
|
||||
name = "Lesbian Visibility Day"
|
||||
begin_day = 26
|
||||
begin_month = APRIL
|
||||
|
||||
/datum/holiday/lesbianvisibility/greet()
|
||||
return "Today is Lesbian Visibility Day!"
|
||||
|
||||
/datum/holiday/labor
|
||||
name = "Labor Day"
|
||||
begin_day = 1
|
||||
@@ -292,6 +300,14 @@
|
||||
/datum/holiday/programmers/getStationPrefix()
|
||||
return pick("span>","DEBUG: ","null","/list","EVENT PREFIX NOT FOUND") //Portability
|
||||
|
||||
/datum/holiday/bivisibility
|
||||
name = "Bisexual Visibility Day"
|
||||
begin_day = 23
|
||||
begin_month = SEPTEMBER
|
||||
|
||||
/datum/holiday/bivisibility/greet()
|
||||
return "Today is Bisexual Visibility Day!"
|
||||
|
||||
/datum/holiday/questions
|
||||
name = "Stupid-Questions Day"
|
||||
begin_day = 28
|
||||
@@ -314,12 +330,25 @@
|
||||
begin_month = OCTOBER
|
||||
drone_hat = /obj/item/clothing/head/papersack/smiley
|
||||
|
||||
/datum/holiday/comingoutday
|
||||
name = "Coming Out Day"
|
||||
begin_day = 11
|
||||
begin_month = OCTOBER
|
||||
|
||||
/datum/holiday/boss
|
||||
name = "Boss' Day"
|
||||
begin_day = 16
|
||||
begin_month = OCTOBER
|
||||
drone_hat = /obj/item/clothing/head/that
|
||||
|
||||
/datum/holiday/intersexawareness
|
||||
name = "Intersex Awareness Day"
|
||||
begin_day = 26
|
||||
begin_month = OCTOBER
|
||||
|
||||
/datum/holiday/intersexawareness/greet()
|
||||
return "Today is Intersex Awareness Day! It has been [text2num(time2text(world.timeofday, "YYYY")) - 1996] years since the first public protest speaking out against the human rights issues faced by intersex people."
|
||||
|
||||
/datum/holiday/halloween
|
||||
name = HALLOWEEN
|
||||
begin_day = 28
|
||||
@@ -359,6 +388,23 @@
|
||||
begin_month = NOVEMBER
|
||||
drone_hat = /obj/item/reagent_containers/food/snacks/grown/moonflower
|
||||
|
||||
/datum/holiday/transawareness
|
||||
name = "Transgender Awareness Week"
|
||||
begin_day = 13
|
||||
begin_month = NOVEMBER
|
||||
end_day = 19
|
||||
|
||||
/datum/holiday/transawareness/greet()
|
||||
return "This week is Transgender Awareness Week!"
|
||||
|
||||
/datum/holiday/transremembrance
|
||||
name = "Transgender Day of Remembrance"
|
||||
begin_day = 20
|
||||
begin_month = NOVEMBER
|
||||
|
||||
/datum/holiday/transremembrance/greet()
|
||||
return "Today is the Transgender Day of Remembrance."
|
||||
|
||||
/datum/holiday/hello
|
||||
name = "Saying-'Hello' Day"
|
||||
begin_day = 21
|
||||
@@ -397,6 +443,26 @@
|
||||
begin_month = OCTOBER
|
||||
begin_weekday = MONDAY
|
||||
|
||||
/datum/holiday/aceawareness
|
||||
name = "Asexual Awareness Week"
|
||||
begin_month = OCTOBER
|
||||
|
||||
/datum/holiday/aceawareness/greet()
|
||||
return "This week is Asexual Awareness Week!"
|
||||
|
||||
/datum/holiday/aceawareness/shouldCelebrate(dd, mm, yy, ww, ddd) //Ace awareness week falls on the last full week of October.
|
||||
if(mm != begin_month)
|
||||
return FALSE //it's not even the right month
|
||||
var/daypointer = world.timeofday - ((WEEKDAY2NUM(ddd) - 1) * 24 HOURS)
|
||||
if(text2num(time2text(daypointer, "MM")) != mm)
|
||||
return FALSE //it's the beginning of the month and it isn't even a full week
|
||||
daypointer += (24 HOURS * 6)
|
||||
if(text2num(time2text(daypointer, "MM")) != mm)
|
||||
return FALSE //this is the end of the month, and it is not a full week.
|
||||
daypointer += (24 HOURS * 7)
|
||||
if(text2num(time2text(daypointer, "MM")) != mm)
|
||||
return TRUE //the end of next week falls on a different month, meaning that the current week is the last full week
|
||||
|
||||
/datum/holiday/mother
|
||||
name = "Mother's Day"
|
||||
begin_week = 2
|
||||
@@ -421,11 +487,30 @@
|
||||
/datum/holiday/pride/getStationPrefix()
|
||||
return pick("Pride", "Gay", "Bi", "Trans", "Lesbian", "Ace", "Aro", "Agender", pick("Enby", "Enbie"), "Pan", "Intersex", "Demi", "Poly", "Closeted", "Genderfluid")
|
||||
|
||||
/datum/holiday/stonewall
|
||||
name = "Stonewall Riots Anniversary"
|
||||
begin_day = 28
|
||||
begin_month = JUNE
|
||||
|
||||
/datum/holiday/stonewall/greet() //Not gonna lie, I was fairly tempted to make this use the IC year instead of the IRL year, but I was worried that it would have caused too much confusion.
|
||||
return "Today marks the [text2num(time2text(world.timeofday, "YYYY")) - 1969]\th anniversary of the riots at the Stonewall Inn!"
|
||||
|
||||
/datum/holiday/moth
|
||||
name = "Moth Week"
|
||||
begin_month = JULY
|
||||
|
||||
/datum/holiday/moth/shouldCelebrate(dd, mm, yy, ww, ddd) //National Moth Week falls on the last full week of July
|
||||
return mm == JULY && (ww == 4 || (ww == 5 && ddd == SUNDAY))
|
||||
if(mm != begin_month)
|
||||
return FALSE //it's not even the right month
|
||||
var/daypointer = world.timeofday - ((WEEKDAY2NUM(ddd) - 1) * 24 HOURS)
|
||||
if(text2num(time2text(daypointer, "MM")) != mm)
|
||||
return FALSE //it's the beginning of the month and it isn't even a full week
|
||||
daypointer += (24 HOURS * 6)
|
||||
if(text2num(time2text(daypointer, "MM")) != mm)
|
||||
return FALSE //this is the end of the month, and it is not a full week.
|
||||
daypointer += (24 HOURS * 7)
|
||||
if(text2num(time2text(daypointer, "MM")) != mm)
|
||||
return TRUE //the end of next week falls on a different month, meaning that the current week is the last full week
|
||||
|
||||
/datum/holiday/moth/getStationPrefix()
|
||||
return pick("Mothball","Lepidopteran","Lightbulb","Moth","Giant Atlas","Twin-spotted Sphynx","Madagascan Sunset","Luna","Death's Head","Emperor Gum","Polyphenus","Oleander Hawk","Io","Rosy Maple","Cecropia","Noctuidae","Giant Leopard","Dysphania Militaris","Garden Tiger")
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#define CATEGORY_MISC "MISC"
|
||||
#define CATEGORY_MOVEMENT "MOVEMENT"
|
||||
#define CATEGORY_TARGETING "TARGETING"
|
||||
#define CATEGORY_COMBAT "COMBAT"
|
||||
|
||||
#define WEIGHT_HIGHEST 0
|
||||
#define WEIGHT_ADMIN 10
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/datum/keybinding/living/toggle_combat_mode
|
||||
hotkey_keys = list("C")
|
||||
name = "toggle_combat_mode"
|
||||
full_name = "Toggle combat mode"
|
||||
category = CATEGORY_COMBAT
|
||||
description = "Toggles whether or not you're in combat mode."
|
||||
|
||||
/datum/keybinding/living/toggle_combat_mode/down(client/user)
|
||||
SEND_SIGNAL(user.mob, COMSIG_TOGGLE_COMBAT_MODE)
|
||||
return TRUE
|
||||
|
||||
/datum/keybinding/living/active_block
|
||||
hotkey_keys = list("Northwest", "F") // HOME
|
||||
name = "active_block"
|
||||
full_name = "Block (Hold)"
|
||||
category = CATEGORY_COMBAT
|
||||
description = "Hold down to actively block with your currently in-hand object."
|
||||
|
||||
/datum/keybinding/living/active_block/down(client/user)
|
||||
var/mob/living/L = user.mob
|
||||
L.keybind_start_active_blocking()
|
||||
return TRUE
|
||||
|
||||
/datum/keybinding/living/active_block/up(client/user)
|
||||
var/mob/living/L = user.mob
|
||||
L.keybind_stop_active_blocking()
|
||||
|
||||
/datum/keybinding/living/active_parry
|
||||
hotkey_keys = list("Insert", "G")
|
||||
name = "active_parry"
|
||||
full_name = "Parry"
|
||||
category = CATEGORY_COMBAT
|
||||
description = "Press to initiate a parry sequence with your currently in-hand object."
|
||||
|
||||
/datum/keybinding/living/active_parry/down(client/user)
|
||||
var/mob/living/L = user.mob
|
||||
L.keybind_parry()
|
||||
return TRUE
|
||||
@@ -16,16 +16,6 @@
|
||||
L.resist()
|
||||
return TRUE
|
||||
|
||||
/datum/keybinding/living/toggle_combat_mode
|
||||
hotkey_keys = list("C")
|
||||
name = "toggle_combat_mode"
|
||||
full_name = "Toggle combat mode"
|
||||
description = "Toggles whether or not you're in combat mode."
|
||||
|
||||
/datum/keybinding/living/toggle_combat_mode/down(client/user)
|
||||
SEND_SIGNAL(user.mob, COMSIG_TOGGLE_COMBAT_MODE)
|
||||
return TRUE
|
||||
|
||||
/datum/keybinding/living/toggle_resting
|
||||
hotkey_keys = list("V")
|
||||
name = "toggle_resting"
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
return TRUE
|
||||
|
||||
/datum/keybinding/mob/cycle_intent_right
|
||||
hotkey_keys = list("Northwest", "F") // HOME
|
||||
hotkey_keys = list("Unbound")
|
||||
name = "cycle_intent_right"
|
||||
full_name = "Cycle Action Intent Right"
|
||||
description = ""
|
||||
@@ -28,7 +28,7 @@
|
||||
return TRUE
|
||||
|
||||
/datum/keybinding/mob/cycle_intent_left
|
||||
hotkey_keys = list("Insert", "G")
|
||||
hotkey_keys = list("Unbound")
|
||||
name = "cycle_intent_left"
|
||||
full_name = "Cycle Action Intent Left"
|
||||
description = ""
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
plane = EMISSIVE_BLOCKER_PLANE
|
||||
layer = EMISSIVE_BLOCKER_LAYER
|
||||
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
||||
rad_flags = RAD_NO_CONTAMINATE | RAD_PROTECT_CONTENTS
|
||||
//Why?
|
||||
//render_targets copy the transform of the target as well, but vis_contents also applies the transform
|
||||
//to what's in it. Applying RESET_TRANSFORM here makes vis_contents not apply the transform.
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -582,6 +579,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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -432,10 +432,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))
|
||||
|
||||
@@ -76,11 +76,13 @@
|
||||
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]
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -78,7 +78,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 +95,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 +214,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
|
||||
|
||||
@@ -1700,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
|
||||
@@ -1720,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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -78,8 +78,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 +106,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)
|
||||
|
||||
@@ -64,6 +64,8 @@
|
||||
|
||||
handle_gravity()
|
||||
|
||||
handle_block_parry(seconds)
|
||||
|
||||
if(machine)
|
||||
machine.check_eye(src)
|
||||
|
||||
|
||||
@@ -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 ..()
|
||||
@@ -66,22 +66,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
|
||||
@@ -111,10 +119,13 @@
|
||||
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
|
||||
total_damage = block_calculate_resultant_damage(total_damage, block_return)
|
||||
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
|
||||
@@ -143,7 +154,7 @@
|
||||
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 +324,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 +343,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 +360,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)
|
||||
|
||||
@@ -29,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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -396,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)
|
||||
|
||||
@@ -117,7 +117,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 ..()
|
||||
|
||||
@@ -103,7 +103,7 @@ 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
|
||||
|
||||
@@ -1086,3 +1086,10 @@ GLOBAL_VAR_INIT(exploit_warn_spam_prevention, 0)
|
||||
for(var/obj/item/I in held_items)
|
||||
if(I.item_flags & SLOWS_WHILE_IN_HAND)
|
||||
. += I.slowdown
|
||||
|
||||
/**
|
||||
* Mostly called by doUnEquip()
|
||||
* Like item dropped() on mob side.
|
||||
*/
|
||||
/mob/proc/on_item_dropped(obj/item/I)
|
||||
return
|
||||
|
||||
@@ -119,3 +119,7 @@
|
||||
/datum/movespeed_modifier/liver_cirrhosis
|
||||
blacklisted_movetypes = FLOATING
|
||||
variable = TRUE
|
||||
|
||||
/datum/movespeed_modifier/active_block
|
||||
variable = TRUE
|
||||
flags = IGNORE_NOSLOW
|
||||
|
||||
@@ -506,7 +506,7 @@
|
||||
cell = null
|
||||
qdel(src)
|
||||
|
||||
/obj/machinery/light/attacked_by(obj/item/I, mob/living/user)
|
||||
/obj/machinery/light/attacked_by(obj/item/I, mob/living/user, attackchain_flags = NONE, damage_multiplier = 1)
|
||||
..()
|
||||
if(status == LIGHT_BROKEN || status == LIGHT_EMPTY)
|
||||
if(on && (I.flags_1 & CONDUCT_1))
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
desc = "A 9mm incendiary bullet casing."
|
||||
projectile_type = /obj/item/projectile/bullet/incendiary/c9mm
|
||||
|
||||
|
||||
// .50AE (Desert Eagle)
|
||||
|
||||
/obj/item/ammo_casing/a50AE
|
||||
@@ -53,3 +52,16 @@
|
||||
caliber = ".50"
|
||||
projectile_type = /obj/item/projectile/bullet/a50AE
|
||||
|
||||
// .32 ACP (Improvised Pistol)
|
||||
|
||||
/obj/item/ammo_casing/c32acp
|
||||
name = ".32 bullet casing"
|
||||
desc = "A .32 bullet casing."
|
||||
caliber = "c32acp"
|
||||
projectile_type = /obj/item/projectile/bullet/c32acp
|
||||
|
||||
/obj/item/ammo_casing/r32acp
|
||||
name = ".32 rubber bullet casing"
|
||||
desc = "A .32 rubber bullet casing."
|
||||
caliber = "c32acp"
|
||||
projectile_type = /obj/item/projectile/bullet/r32acp
|
||||
|
||||
@@ -12,6 +12,15 @@
|
||||
e_cost = 200
|
||||
select_name = "kill"
|
||||
|
||||
/obj/item/ammo_casing/energy/lasergun/improvised
|
||||
projectile_type = /obj/item/projectile/beam/weak/improvised
|
||||
e_cost = 200
|
||||
select_name = "kill"
|
||||
|
||||
/obj/item/ammo_casing/energy/lasergun/improvised/upgraded
|
||||
projectile_type = /obj/item/projectile/beam/weak
|
||||
e_cost = 100
|
||||
|
||||
/obj/item/ammo_casing/energy/laser/hos
|
||||
e_cost = 100
|
||||
|
||||
|
||||
@@ -40,6 +40,18 @@
|
||||
desc = "Designed to quickly reload revolvers. Iceblox bullets contain a cryogenic payload."
|
||||
ammo_type = /obj/item/ammo_casing/c38/iceblox
|
||||
|
||||
/obj/item/ammo_box/c32mm
|
||||
name = "ammo box (.32 acp)"
|
||||
desc = "Lethal .32 acp bullets, there's forty in the box."
|
||||
ammo_type = /obj/item/ammo_casing/c32acp
|
||||
max_ammo = 40
|
||||
|
||||
/obj/item/ammo_box/r32mm
|
||||
name = "ammo box (rubber .32 acp)"
|
||||
desc = "Non-lethal .32 acp bullets, there's forty in the box."
|
||||
ammo_type = /obj/item/ammo_casing/r32acp
|
||||
max_ammo = 40
|
||||
|
||||
/obj/item/ammo_box/c9mm
|
||||
name = "ammo box (9mm)"
|
||||
icon_state = "9mmbox"
|
||||
|
||||
@@ -66,3 +66,15 @@
|
||||
caliber = ".50"
|
||||
max_ammo = 7
|
||||
multiple_sprites = 1
|
||||
|
||||
/obj/item/ammo_box/magazine/m32acp
|
||||
name = "pistol magazine (.32)"
|
||||
desc = "A crudely construction pistol magazine that holds .32 ACP rounds. It looks like it can only fit eight bullets."
|
||||
icon_state = "32acp"
|
||||
ammo_type = /obj/item/ammo_casing/c32acp
|
||||
caliber = "c32acp"
|
||||
max_ammo = 8
|
||||
multiple_sprites = 2
|
||||
|
||||
/obj/item/ammo_box/magazine/m32acp/empty
|
||||
start_empty = 1
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
..()
|
||||
|
||||
/obj/item/minigunpack/dropped(mob/user)
|
||||
. = ..()
|
||||
if(armed)
|
||||
user.dropItemToGround(gun, TRUE)
|
||||
|
||||
@@ -125,6 +126,7 @@
|
||||
return
|
||||
|
||||
/obj/item/gun/ballistic/minigun/dropped(mob/user)
|
||||
. = ..()
|
||||
if(ammo_pack)
|
||||
ammo_pack.attach_gun(user)
|
||||
else
|
||||
@@ -144,4 +146,5 @@
|
||||
. = ..()
|
||||
|
||||
/obj/item/gun/ballistic/minigun/dropped(mob/living/user)
|
||||
. = ..()
|
||||
ammo_pack.attach_gun(user)
|
||||
|
||||
@@ -115,6 +115,7 @@
|
||||
icon_state = "flatgun"
|
||||
|
||||
/obj/item/gun/ballistic/automatic/pistol/stickman/pickup(mob/living/user)
|
||||
. = ..()
|
||||
to_chat(user, "<span class='notice'>As you try to pick up [src], it slips out of your grip..</span>")
|
||||
if(prob(50))
|
||||
to_chat(user, "<span class='notice'>..and vanishes from your vision! Where the hell did it go?</span>")
|
||||
@@ -155,3 +156,19 @@
|
||||
name = "Syndicate Anti Tank Pistol"
|
||||
desc = "A massively impractical and silly monstrosity of a pistol that fires .50 calliber rounds. The recoil is likely to dislocate a variety of joints without proper bracing."
|
||||
pin = /obj/item/firing_pin/implant/pindicate
|
||||
|
||||
////////////Improvised Pistol////////////
|
||||
|
||||
/obj/item/gun/ballistic/automatic/pistol/improvised
|
||||
name = "Improvised Pistol"
|
||||
desc = "An improvised pocket-sized pistol that fires .32 calibre rounds. It looks incredibly flimsy."
|
||||
icon_state = "ipistol"
|
||||
item_state = "pistol"
|
||||
mag_type = /obj/item/ammo_box/magazine/m32acp
|
||||
fire_delay = 7.5
|
||||
can_suppress = FALSE
|
||||
w_class = WEIGHT_CLASS_SMALL
|
||||
spread = 15 // Keep the spread between 15 and 20. This hardlocks it into being a mid-range pistol, the magazine size means you're allowed to miss. Fills the mid-range niche that slugs/rifle and buckshot doesn't fill.
|
||||
|
||||
/obj/item/gun/ballistic/automatic/pistol/improvised/nomag
|
||||
spawnwithmagazine = FALSE // For crafting as you shouldn't get eight bullets for free otherwise people will reaper reload.
|
||||
|
||||
@@ -319,7 +319,6 @@
|
||||
|
||||
/obj/item/gun/ballistic/revolver/doublebarrel/improvised
|
||||
name = "improvised shotgun"
|
||||
desc = "Essentially a tube that aims shotgun shells."
|
||||
desc = "A shoddy break-action breechloaded shotgun. Its lacklustre construction will probably result in it hurting people less than a normal shotgun."
|
||||
icon_state = "ishotgun"
|
||||
item_state = "shotgun"
|
||||
@@ -330,8 +329,9 @@
|
||||
mag_type = /obj/item/ammo_box/magazine/internal/shot/improvised
|
||||
sawn_desc = "I'm just here for the gasoline."
|
||||
unique_reskin = null
|
||||
projectile_damage_multiplier = 0.8
|
||||
projectile_damage_multiplier = 0.9
|
||||
var/slung = FALSE
|
||||
weapon_weight = WEAPON_HEAVY
|
||||
|
||||
/obj/item/gun/ballistic/revolver/doublebarrel/improvised/attackby(obj/item/A, mob/user, params)
|
||||
..()
|
||||
@@ -358,7 +358,7 @@
|
||||
|
||||
/obj/item/gun/ballistic/revolver/doublebarrel/improvised/sawn
|
||||
name = "sawn-off improvised shotgun"
|
||||
desc = "A single-shot shotgun. Better not miss."
|
||||
desc = "The barrel and stock have been sawn and filed down; it can fit in backpacks. You still need two hands to fire this, if you value unbroken wrists."
|
||||
icon_state = "ishotgun"
|
||||
item_state = "gun"
|
||||
w_class = WEIGHT_CLASS_NORMAL
|
||||
|
||||
@@ -131,6 +131,7 @@
|
||||
desc = "A bolt-action breechloaded rifle that takes 7.62mm bullets."
|
||||
mag_type = /obj/item/ammo_box/magazine/internal/boltaction/improvised
|
||||
can_bayonet = FALSE
|
||||
var/slung = FALSE
|
||||
|
||||
/obj/item/gun/ballistic/shotgun/boltaction/pump(mob/M)
|
||||
playsound(M, 'sound/weapons/shotgunpump.ogg', 60, 1)
|
||||
@@ -152,6 +153,22 @@
|
||||
. = ..()
|
||||
. += "The bolt is [bolt_open ? "open" : "closed"]."
|
||||
|
||||
/obj/item/gun/ballistic/shotgun/boltaction/improvised/attackby(obj/item/A, mob/user, params)
|
||||
..()
|
||||
if(istype(A, /obj/item/stack/cable_coil) && !sawn_off)
|
||||
if(A.use_tool(src, user, 0, 10, max_level = JOB_SKILL_BASIC))
|
||||
slot_flags = ITEM_SLOT_BACK
|
||||
to_chat(user, "<span class='notice'>You tie the lengths of cable to the rifle, making a sling.</span>")
|
||||
slung = TRUE
|
||||
update_icon()
|
||||
else
|
||||
to_chat(user, "<span class='warning'>You need at least ten lengths of cable if you want to make a sling!</span>")
|
||||
|
||||
/obj/item/gun/ballistic/shotgun/boltaction/improvised/update_icon()
|
||||
..()
|
||||
if(slung)
|
||||
icon_state += "sling"
|
||||
|
||||
/obj/item/gun/ballistic/shotgun/boltaction/enchanted
|
||||
name = "enchanted bolt action rifle"
|
||||
desc = "Careful not to lose your head."
|
||||
|
||||
@@ -240,3 +240,20 @@
|
||||
chambered.BB.damage *= 5
|
||||
|
||||
process_fire(target, user, TRUE, params)
|
||||
|
||||
////////////////
|
||||
// IMPROVISED //
|
||||
////////////////
|
||||
|
||||
/obj/item/gun/energy/e_gun/old/improvised
|
||||
name = "improvised energy rifle"
|
||||
desc = "A crude imitation of an energy gun. It works, however the beams are poorly focused and most of the energy is wasted before it reaches the target. Welp, it still burns things."
|
||||
icon_state = "improvised"
|
||||
ammo_x_offset = 1
|
||||
shaded_charge = 1
|
||||
ammo_type = list(/obj/item/ammo_casing/energy/lasergun/improvised)
|
||||
|
||||
/obj/item/gun/energy/e_gun/old/improvised/upgraded
|
||||
name = "makeshift energy rifle"
|
||||
desc = "The new lens and upgraded parts gives this a higher capacity and more energy output, however, the shoddy construction still leaves it inferior to Nanotrasen's own energy weapons."
|
||||
ammo_type = list(/obj/item/ammo_casing/energy/lasergun/improvised/upgraded)
|
||||
|
||||
@@ -164,7 +164,15 @@
|
||||
/obj/item/projectile/proc/prehit(atom/target)
|
||||
return TRUE
|
||||
|
||||
/obj/item/projectile/proc/on_hit(atom/target, blocked = FALSE)
|
||||
/**
|
||||
* Called when we hit something.
|
||||
*
|
||||
* @params
|
||||
* * target - what we hit
|
||||
* * blocked - 0 to 100 percentage mitigation/block
|
||||
* * def zone - where we hit if we hit a mob.
|
||||
*/
|
||||
/obj/item/projectile/proc/on_hit(atom/target, blocked = 0, def_zone)
|
||||
if(fired_from)
|
||||
SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_ON_HIT, firer, target, Angle)
|
||||
var/turf/target_loca = get_turf(target)
|
||||
|
||||
@@ -39,6 +39,9 @@
|
||||
/obj/item/projectile/beam/weak
|
||||
damage = 15
|
||||
|
||||
/obj/item/projectile/beam/weak/improvised
|
||||
damage = 10
|
||||
|
||||
/obj/item/projectile/beam/weak/penetrator
|
||||
armour_penetration = 50
|
||||
|
||||
|
||||
@@ -47,4 +47,16 @@
|
||||
if(L.getStaminaLoss() >= 60)
|
||||
L.Sleeping(300)
|
||||
else
|
||||
L.adjustStaminaLoss(25)
|
||||
L.adjustStaminaLoss(25)
|
||||
|
||||
// .32 ACP (Improvised Pistol)
|
||||
|
||||
/obj/item/projectile/bullet/c32acp
|
||||
name = ".32 bullet"
|
||||
damage = 13
|
||||
|
||||
/obj/item/projectile/bullet/r32acp
|
||||
name = ".32 rubber bullet"
|
||||
damage = 3
|
||||
eyeblur = 1
|
||||
stamina = 20
|
||||
|
||||
@@ -857,3 +857,13 @@
|
||||
taste_mult = 2.5 //sugar's 1.5, capsacin's 1.5, so a good middle ground.
|
||||
taste_description = "smokey sweetness"
|
||||
value = REAGENT_VALUE_COMMON
|
||||
|
||||
/datum/reagent/consumable/laughsyrup
|
||||
name = "Laughin' Syrup"
|
||||
description = "The product of juicing Laughin' Peas. Fizzy, and seems to change flavour based on what it's used with!"
|
||||
nutriment_factor = 5 * REAGENTS_METABOLISM
|
||||
color = "#803280"
|
||||
taste_mult = 2
|
||||
taste_description = "fizzy sweetness"
|
||||
value = REAGENT_VALUE_COMMON
|
||||
|
||||
+29
-1
@@ -30,6 +30,14 @@
|
||||
build_path = /obj/item/ammo_box/c38
|
||||
category = list("initial", "Security")
|
||||
|
||||
/datum/design/r32acp
|
||||
name = "Rubber Pistol Bullet (.32)"
|
||||
id = "r32acp"
|
||||
build_type = AUTOLATHE
|
||||
materials = list(/datum/material/iron = 250)
|
||||
build_path = /obj/item/ammo_casing/r32acp
|
||||
category = list("initial", "Security")
|
||||
|
||||
/////////////////
|
||||
///Hacked Gear //
|
||||
/////////////////
|
||||
@@ -78,7 +86,7 @@
|
||||
name = "Rifle Receiver"
|
||||
id = "rifle_receiver"
|
||||
build_type = AUTOLATHE
|
||||
materials = list(/datum/material/iron = 40000)
|
||||
materials = list(/datum/material/iron = 24000)
|
||||
build_path = /obj/item/weaponcrafting/improvised_parts/rifle_receiver
|
||||
category = list("hacked", "Security")
|
||||
|
||||
@@ -197,3 +205,23 @@
|
||||
materials = list(/datum/material/iron = 5500)
|
||||
build_path = /obj/item/clothing/head/foilhat
|
||||
category = list("hacked", "Misc")
|
||||
|
||||
/datum/design/c32acp
|
||||
name = "Pistol Bullet (.32)"
|
||||
id = "c32acp"
|
||||
build_type = AUTOLATHE
|
||||
materials = list(/datum/material/iron = 500)
|
||||
build_path = /obj/item/ammo_casing/c32acp
|
||||
category = list("hacked", "Security")
|
||||
|
||||
/////////////////
|
||||
// Magazines //
|
||||
/////////////////
|
||||
|
||||
/datum/design/m32acp
|
||||
name = "Empty .32 Magazine"
|
||||
id = "m32acp"
|
||||
build_type = AUTOLATHE
|
||||
materials = list(/datum/material/iron = 10000)
|
||||
build_path = /obj/item/ammo_box/magazine/m32acp/empty
|
||||
category = list("hacked", "Security")
|
||||
|
||||
@@ -281,3 +281,10 @@
|
||||
build_path = /obj/item/weaponcrafting/improvised_parts/trigger_assembly
|
||||
category = list("initial", "Misc")
|
||||
|
||||
/datum/design/focusing_lens
|
||||
name = "Makeshift Lens"
|
||||
id = "makeshift_lens"
|
||||
build_type = AUTOLATHE
|
||||
materials = list(/datum/material/iron = 2000, /datum/material/glass = 4000)
|
||||
build_path = /obj/item/weaponcrafting/improvised_parts/makeshift_lens
|
||||
category = list("initial", "Misc")
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
mob_exit(M, silent)
|
||||
return TRUE
|
||||
|
||||
/obj/vehicle/sealed/car/attacked_by(obj/item/I, mob/living/user)
|
||||
/obj/vehicle/sealed/car/attacked_by(obj/item/I, mob/living/user, attackchain_flags = NONE, damage_multiplier = 1)
|
||||
if(!I.force)
|
||||
return FALSE
|
||||
if(occupants[user])
|
||||
@@ -85,4 +85,4 @@
|
||||
if(!silent)
|
||||
M.visible_message("<span class='warning'>[M] is forced into \the [src]!</span>")
|
||||
M.forceMove(src)
|
||||
add_occupant(M, VEHICLE_CONTROL_KIDNAPPED)
|
||||
add_occupant(M, VEHICLE_CONTROL_KIDNAPPED)
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
visible_message("<span class='danger'>[src] spews out a ton of space lube!</span>")
|
||||
new /obj/effect/particle_effect/foam(loc) //YEET
|
||||
|
||||
/obj/vehicle/sealed/car/clowncar/attacked_by(obj/item/I, mob/living/user)
|
||||
/obj/vehicle/sealed/car/clowncar/attacked_by(obj/item/I, mob/living/user, attackchain_flags = NONE, damage_multiplier = 1)
|
||||
. = ..()
|
||||
if(istype(I, /obj/item/reagent_containers/food/snacks/grown/banana))
|
||||
var/obj/item/reagent_containers/food/snacks/grown/banana/banana = I
|
||||
@@ -129,4 +129,4 @@
|
||||
icon_state = initial(icon_state)
|
||||
|
||||
/obj/vehicle/sealed/car/clowncar/proc/StopDroppingOil()
|
||||
droppingoil = FALSE
|
||||
droppingoil = FALSE
|
||||
|
||||
@@ -20,7 +20,11 @@
|
||||
/obj/item/clothing/under/misc/gear_harness = 10,
|
||||
/obj/item/dildo/custom = 5,
|
||||
/obj/item/electropack/shockcollar = 3,
|
||||
/obj/item/assembly/signaler = 3
|
||||
/obj/item/assembly/signaler = 3,
|
||||
/obj/item/clothing/under/shorts/polychromic/pantsu,
|
||||
/obj/item/clothing/under/misc/poly_bottomless,
|
||||
/obj/item/clothing/under/misc/poly_tanktop,
|
||||
/obj/item/clothing/under/misc/poly_tanktop/female
|
||||
)
|
||||
contraband = list(
|
||||
/obj/item/clothing/neck/petcollar/locked = 2,
|
||||
|
||||
Reference in New Issue
Block a user