diff --git a/code/__DEFINES/flags/shields.dm b/code/__DEFINES/flags/shields.dm new file mode 100644 index 0000000000..f6de4226e8 --- /dev/null +++ b/code/__DEFINES/flags/shields.dm @@ -0,0 +1,18 @@ +/// Transparent, let beams pass +#define SHIELD_TRANSPARENT (1<<0) +/// Can shield bash +#define SHIELD_CAN_BASH (1<<1) +/// Shield bash knockdown on wall hit +#define SHIELD_BASH_WALL_KNOCKDOWN (1<<2) +/// Shield bash always knockdown +#define SHIELD_BASH_ALWAYS_KNOCKDOWN (1<<3) +/// Shield bash disarm on wall hit +#define SHIELD_BASH_WALL_DISARM (1<<4) +/// Shield bash always disarm +#define SHIELD_BASH_ALWAYS_DISARM (1<<5) +/// You can shieldbash target someone on the ground for ground slam +#define SHIELD_BASH_GROUND_SLAM (1<<6) +/// Shield bashing someone on the ground will disarm +#define SHIELD_BASH_GROUND_SLAM_DISARM (1<<7) + +#define SHIELD_FLAGS_DEFAULT (SHIELD_CAN_BASH | SHIELD_BASH_WALL_KNOCKDOWN | SHIELD_BASH_WALL_DISARM | SHIELD_BASH_GROUND_SLAM) diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index f0c854c398..c1ca671044 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -250,6 +250,16 @@ GLOBAL_LIST_INIT(bitfields, list( "COMBAT_FLAG_INTENTIONALLY_RESTING" = COMBAT_FLAG_INTENTIONALLY_RESTING, "COMBAT_FLAG_RESISTING_REST" = COMBAT_FLAG_RESISTING_REST ), + "shield_flags" = list( + "SHIELD_TRANSPARENT" = SHIELD_TRANSPARENT, + "SHIELD_CAN_BASH" = SHIELD_CAN_BASH, + "SHIELD_BASH_WALL_KNOCKDOWN" = SHIELD_BASH_WALL_KNOCKDOWN, + "SHIELD_BASH_ALWAYS_KNOCKDOWN" = SHIELD_BASH_ALWAYS_KNOCKDOWN, + "SHIELD_BASH_WALL_DISARM" = SHIELD_BASH_WALL_DISARM, + "SHIELD_BASH_ALWAYS_DISARM" = SHIELD_BASH_ALWAYS_DISARM, + "SHIELD_BASH_GROUND_SLAM" = SHIELD_BASH_GROUND_SLAM, + "SHIELD_BASH_GROUND_SLAM_DISARM" = SHIELD_BASH_GROUND_SLAM_DISARM + ), "storage_flags" = list( "STORAGE_LIMIT_MAX_ITEMS" = STORAGE_LIMIT_MAX_ITEMS, "STORAGE_LIMIT_MAX_W_CLASS" = STORAGE_LIMIT_MAX_W_CLASS, diff --git a/code/game/objects/items/shields.dm b/code/game/objects/items/shields.dm index 37dfc417d5..dc11e55754 100644 --- a/code/game/objects/items/shields.dm +++ b/code/game/objects/items/shields.dm @@ -3,11 +3,151 @@ icon = 'icons/obj/items_and_weapons.dmi' block_chance = 50 armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70) - var/transparent = FALSE // makes beam projectiles pass through the shield + /// Shield flags + var/shield_flags = SHIELD_FLAGS_DEFAULT + /// Last shieldbash world.time + var/last_shieldbash = 0 + /// Shieldbashing cooldown + var/shieldbash_cooldown = 5 SECONDS + /// Shieldbashing stamina cost + var/shieldbash_stamcost = 7.5 + /// Shieldbashing knockback + var/shieldbash_knockback = 2 + /// Shield bashing brute damage + var/shieldbash_brutedamage = 5 + /// Shield bashing stamina damage + var/shieldbash_stamdmg = 15 + /// Shield bashing stagger duration + var/shieldbash_stagger_duration = 3.5 SECONDS + /// Shield bashing push distance + var/shieldbash_push_distance = 1 + +/obj/item/shield/examine(mob/user) + . = ..() + if(shield_flags & SHIELD_CAN_BASH) + . += "Right click on combat mode attack with [src] to shield bash!" + if(shield_flags & SHIELD_BASH_GROUND_SLAM) + . += "Directly rightclicking on a downed target with [src] will slam them instead of bashing." /obj/item/shield/proc/on_shield_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance) return TRUE +/obj/item/shield/alt_pre_attack(atom/A, mob/living/user, params) + user_shieldbash(user, A, user.a_intent != INTENT_HARM) + return TRUE + +/obj/item/shield/altafterattack(atom/target, mob/user, proximity_flag, click_parameters) + user_shieldbash(user, target, user.a_intent != INTENT_HARM) + return TRUE + +/obj/item/shield/proc/do_shieldbash_effect(mob/living/user, dir, harmful) + var/px = 0 + var/py = 0 + switch(dir) + if(NORTH) + py = 12 + if(SOUTH) + py = -12 + if(EAST) + px = 12 + if(WEST) + px = -12 + var/oldpx = user.pixel_x + var/oldpy = user.pixel_y + animate(user, pixel_x = px, pixel_y = py, time = 3, easing = SINE_EASING | EASE_OUT, flags = ANIMATION_END_NOW) + animate(user, pixel_x = oldpx, pixel_y = oldpy, time = 3) + user.visible_message("[user] [harmful? "charges forwards with" : "sweeps"] [src]!") + var/obj/effect/temp_visual/dir_setting/shield_bash/effect = new(user.loc, dir) + animate(effect, alpha = 0, pixel_x = px + 4, pixel_y = py + 4, time = 3) + +/obj/item/shield/proc/bash_target(mob/living/user, mob/living/target, bashdir, harmful) + if(!(target.status_flags & CANKNOCKDOWN) || HAS_TRAIT(src, TRAIT_STUNIMMUNE)) // should probably add stun absorption check at some point I guess.. + // unified stun absorption system when lol + target.visible_message("[user] slams [target] with [src], but [target] doesn't falter!", "[user] slams you with [src], but it barely fazes you!") + return FALSE + var/target_downed = !CHECK_MOBILITY(target, MOBILITY_STAND) + var/wallhit = FALSE + var/turf/target_current_turf = get_turf(target) + if(harmful) + target.visible_message("[target_downed? "[user] slams [src] into [target]" : "[user] bashes [target] with [src]"]!", + "[target_downed? "[user] slams [src] into you" : "[user] bashes you with [src]"]!") + else + target.visible_message("[user] shoves [target] with [src]!", + "[user] shoves you with [src]!") + for(var/i in 1 to harmful? shieldbash_knockback : shieldbash_push_distance) + var/turf/new_turf = get_step(target, bashdir) + var/mob/living/carbon/human/H = locate() in new_turf + if(H && harmful) + H.visible_message("[target] is sent crashing into [H]!", + "[target] is sent crashing into you!") + H.KnockToFloor() + wallhit = TRUE + break + else + step(target, bashdir) + if(get_turf(target) == target_current_turf) + wallhit = TRUE + break + else + target_current_turf = get_turf(target) + var/disarming = (target_downed && (shield_flags & SHIELD_BASH_GROUND_SLAM_DISARM)) || (shield_flags & SHIELD_BASH_ALWAYS_DISARM) || (wallhit && (shield_flags & SHIELD_BASH_WALL_DISARM)) + var/knockdown = !target_downed && ((shield_flags & SHIELD_BASH_ALWAYS_KNOCKDOWN) || (wallhit && (shield_flags & SHIELD_BASH_WALL_KNOCKDOWN))) + if(shieldbash_stagger_duration || knockdown) + target.visible_message("[target] is knocked [knockdown? "to the floor" : "off balanace"]!", + "You are knocked [knockdown? "to the floor" : "off balanace"]!") + if(knockdown) + target.KnockToFloor(disarming) + else if(disarming) + target.drop_all_held_items() + + if(harmful) + target.apply_damage(shieldbash_stamdmg, STAMINA, BODY_ZONE_CHEST) + target.apply_damage(shieldbash_brutedamage, BRUTE, BODY_ZONE_CHEST) + target.Stagger(shieldbash_stagger_duration) + return TRUE + +/obj/item/shield/proc/user_shieldbash(mob/living/user, atom/target, harmful) + if(!(shield_flags & SHIELD_CAN_BASH)) + to_chat(user, "[src] can't be used to shield bash!") + return FALSE + if(world.time < last_shieldbash + shieldbash_cooldown) + to_chat(user, "You can't bash with [src] again so soon!") + return FALSE + if(isliving(target)) //GROUND SLAAAM + if(!(shield_flags & SHIELD_BASH_GROUND_SLAM)) + to_chat(user, "You can't ground slam with [src]!") + return FALSE + bash_target(user, target, NONE, harmful) + user.do_attack_animation(target, used_item = src) + playsound(src, harmful? "swing_hit" : 'sound/weapons/thudswoosh.ogg', 75, 1) + last_shieldbash = world.time + user.adjustStaminaLossBuffered(shieldbash_stamcost) + return 1 + // Directional sweep! + last_shieldbash = world.time + user.adjustStaminaLossBuffered(shieldbash_stamcost) + // Since we are in combat mode, we can probably safely use the user's dir instead of getting their mouse pointing cardinal dir. + var/bashdir = user.dir + do_shieldbash_effect(user, bashdir, harmful) + var/list/checking = list(get_step(user, user.dir), get_step(user, turn(user.dir, 45)), get_step(user, turn(user.dir, -45))) + var/list/victims = list() + for(var/i in checking) + var/turf/T = i + for(var/mob/living/L in T.contents) + victims += L + if(length(victims)) + for(var/i in victims) + bash_target(user, i, bashdir, harmful) + playsound(src, harmful? "swing_hit" : 'sound/weapons/thudswoosh.ogg', 75, 1) + else + playsound(src, 'sound/weapons/punchmiss.ogg', 75, 1) + return length(victims) + +/obj/effect/temp_visual/dir_setting/shield_bash + icon = 'icons/effects/96x96_attack_sweep.dmi' + icon_state = "shield_bash" + duration = 3 + /obj/item/shield/riot name = "riot shield" desc = "A shield adept at blocking blunt objects from connecting with the torso of the shield wielder." @@ -23,13 +163,13 @@ custom_materials = list(/datum/material/glass=7500, /datum/material/iron=1000) attack_verb = list("shoved", "bashed") var/cooldown = 0 //shield bash cooldown. based on world.time - transparent = TRUE + shield_flags = SHIELD_FLAGS_DEFAULT | SHIELD_TRANSPARENT max_integrity = 75 /obj/item/shield/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) if(ismovableatom(object)) var/atom/movable/AM = object - if(transparent && (AM.pass_flags & PASSGLASS)) + if(CHECK_BITFIELD(shield_flags, SHIELD_TRANSPARENT) && (AM.pass_flags & PASSGLASS)) return BLOCK_NONE if(attack_type & ATTACK_TYPE_THROWN) final_block_chance += 30 @@ -88,7 +228,7 @@ item_state = "roman_shield" lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' - transparent = FALSE + shield_flags = SHIELD_FLAGS_DEFAULT max_integrity = 65 /obj/item/shield/riot/roman/fake @@ -111,7 +251,7 @@ custom_materials = null resistance_flags = FLAMMABLE block_chance = 30 - transparent = FALSE + shield_flags = SHIELD_FLAGS_DEFAULT max_integrity = 55 /obj/item/shield/riot/buckler/shatter(mob/living/carbon/human/owner) @@ -238,7 +378,7 @@ throwforce = 15 //Massive pice of metal w_class = WEIGHT_CLASS_HUGE item_flags = SLOWS_WHILE_IN_HAND - transparent = FALSE + shield_flags = SHIELD_FLAGS_DEFAULT /obj/item/shield/riot/implant name = "riot tower shield" @@ -248,7 +388,7 @@ icon = 'icons/obj/items_and_weapons.dmi' block_chance = 30 //May be big but hard to move around to block. slowdown = 1 - transparent = FALSE + shield_flags = SHIELD_FLAGS_DEFAULT item_flags = SLOWS_WHILE_IN_HAND /obj/item/shield/riot/implant/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 40b8de36c5..25f0cb5b89 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -206,8 +206,6 @@ newtonian_move(get_dir(target, src)) thrown_thing.safe_throw_at(target, thrown_thing.throw_range, thrown_thing.throw_speed, src, null, null, null, move_force, random_turn) - - /mob/living/carbon/restrained(ignore_grab) . = (handcuffed || (!ignore_grab && pulledby && pulledby.grab_state >= GRAB_AGGRESSIVE)) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm index 0d4b7d1b1f..5e75088f53 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm @@ -103,7 +103,7 @@ Difficulty: Hard if(.) SSshuttle.shuttle_purchase_requirements_met |= "bubblegum" -/mob/living/simple_animal/hostile/megafauna/bubblegum/do_attack_animation(atom/A) +/mob/living/simple_animal/hostile/megafauna/bubblegum/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect) if(charging) return ..() diff --git a/icons/effects/96x96_attack_sweep.dmi b/icons/effects/96x96_attack_sweep.dmi new file mode 100644 index 0000000000..25547822f1 Binary files /dev/null and b/icons/effects/96x96_attack_sweep.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 26bb4e2e5a..b358ca8c59 100755 --- a/tgstation.dme +++ b/tgstation.dme @@ -123,6 +123,7 @@ #include "code\__DEFINES\dcs\flags.dm" #include "code\__DEFINES\dcs\helpers.dm" #include "code\__DEFINES\dcs\signals.dm" +#include "code\__DEFINES\flags\shields.dm" #include "code\__HELPERS\_cit_helpers.dm" #include "code\__HELPERS\_lists.dm" #include "code\__HELPERS\_logging.dm"