Files
Bubberstation/code/game/objects/items/shields.dm
DrTuxedo 159561437b New Shields and Sprites for them (#81615)
## About The Pull Request
Updates shield sprites to new more polished and 3/4 perspective ones.

<details>

![shielding
show](https://github.com/tgstation/tgstation/assets/42353186/823d1d24-6fdd-40c9-84b4-2b9b61d70813)

</details>

Adds 2 new shield types:
- **Improvised Shield.** Made out of 10 iron sheets and 2 sticky tape
pieces. Weaker then buckler shield and breaks after 2 gunshots or 4
baton hits, but has a 50% (BASE FOR ALL OTHER SHIELDS) blocking chance
instead of 30% that buckler has. Bulky
- **Ballistic Shield.** Printed at Security Techfab for a lot of
titanium after getting weapon research. Strong against projectiles, but
weaker than riot shield against melee. Bulky

Both of these shields break, and both of them are their own subtype. As
such you can still only craft strobe shield with a riot shield.
## Why It's Good For The Game

The sprites of shields were very ancient and extremely flat, this gives
them a more refreshed look.

Ballistic Shield is added because Riot Shield was weakened against
projectiles, Ballistic Shield gives the crew a way to get access to
protection against projectiles at some point in the round.
Improvised Shield adds a second improvised shield in the game (after
Buckler Shield). It's balanced by being weaker than Buckler, but higher
block chance, this adds an interesting choice for players on which
shield to craft.
## Changelog
🆑
add: Adds 2 new shields to the game! Ballistic Shield - researched by
Science, and Improvised Shield - made out of iron and sticky tape
image: Riot, Strobe, Telescopic, Energy shields got new less flat
sprites!
/🆑
2024-03-01 14:25:13 +01:00

445 lines
16 KiB
Plaintext

#define BATON_BASH_COOLDOWN (3 SECONDS)
/obj/item/shield
name = "shield"
icon = 'icons/obj/weapons/shields.dmi'
lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi'
block_chance = 50
slot_flags = ITEM_SLOT_BACK
force = 10
throwforce = 5
throw_speed = 2
throw_range = 3
w_class = WEIGHT_CLASS_BULKY
attack_verb_continuous = list("shoves", "bashes")
attack_verb_simple = list("shove", "bash")
armor_type = /datum/armor/item_shield
block_sound = 'sound/weapons/block_shield.ogg'
/// makes beam projectiles pass through the shield
var/transparent = FALSE
/// if the shield will break by sustaining damage
var/breakable_by_damage = TRUE
/// what the shield leaves behind when it breaks
var/shield_break_leftover = /obj/item/stack/sheet/mineral/wood
/// sound the shield makes when it breaks
var/shield_break_sound = 'sound/effects/bang.ogg'
/// baton bash cooldown
COOLDOWN_DECLARE(baton_bash)
/datum/armor/item_shield
melee = 50
bullet = 50
laser = 50
bomb = 30
fire = 80
acid = 70
/obj/item/shield/Initialize(mapload)
. = ..()
AddElement(/datum/element/disarm_attack)
/obj/item/shield/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK, damage_type = BRUTE)
if(transparent && (hitby.pass_flags & PASSGLASS))
return FALSE
if(attack_type == THROWN_PROJECTILE_ATTACK)
final_block_chance += 30
if(attack_type == LEAP_ATTACK)
final_block_chance = 100
. = ..()
if(.)
on_shield_block(owner, hitby, attack_text, damage, attack_type, damage_type)
/obj/item/shield/examine(mob/user)
. = ..()
var/healthpercent = round((atom_integrity/max_integrity) * 100, 1)
switch(healthpercent)
if(50 to 99)
. += span_info("It looks slightly damaged.")
if(25 to 50)
. += span_info("It appears heavily damaged.")
if(0 to 25)
. += span_warning("It's falling apart!")
/obj/item/shield/proc/on_shield_block(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0, attack_type = MELEE_ATTACK, damage_type = BRUTE)
if(!breakable_by_damage || (damage_type != BRUTE && damage_type != BURN))
return TRUE
var/penetration = 0
var/armor_flag = MELEE
if(isprojectile(hitby))
var/obj/projectile/bang_bang = hitby
armor_flag = bang_bang.armor_flag
penetration = bang_bang.armour_penetration
else if(isitem(hitby))
var/obj/item/weapon = hitby
penetration = weapon.armour_penetration
else if(isanimal(hitby))
var/mob/living/simple_animal/critter = hitby
penetration = critter.armour_penetration
else if(isbasicmob(hitby))
var/mob/living/basic/critter = hitby
penetration = critter.armour_penetration
take_damage(damage, damage_type, armor_flag, armour_penetration = penetration)
/obj/item/shield/atom_destruction(damage_flag)
playsound(src, shield_break_sound, 50)
new shield_break_leftover(get_turf(src))
if(isliving(loc))
loc.balloon_alert(loc, "shield broken!")
return ..()
/obj/item/shield/buckler
name = "wooden buckler"
desc = "A medieval wooden buckler."
icon_state = "buckler"
inhand_icon_state = "buckler"
custom_materials = list(/datum/material/wood = SHEET_MATERIAL_AMOUNT * 10)
resistance_flags = FLAMMABLE
block_chance = 30
max_integrity = 55
w_class = WEIGHT_CLASS_NORMAL
/obj/item/shield/roman
name = "\improper Roman shield"
desc = "Bears an inscription on the inside: <i>\"Romanes venio domus\"</i>."
icon_state = "roman_shield"
inhand_icon_state = "roman_shield"
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 4.25)
max_integrity = 65
shield_break_sound = 'sound/effects/grillehit.ogg'
shield_break_leftover = /obj/item/stack/sheet/iron
/obj/item/shield/roman/fake
desc = "Bears an inscription on the inside: <i>\"Romanes venio domus\"</i>. It appears to be a bit flimsy."
block_chance = 0
armor_type = /datum/armor/none
max_integrity = 30
/datum/armor/item_shield/riot
melee = 80
bullet = 20
laser = 20
/obj/item/shield/riot
name = "riot shield"
desc = "A shield adept at blocking blunt objects from connecting with the torso of the shield wielder, less so bullets and laser beams."
icon_state = "riot"
inhand_icon_state = "riot"
custom_materials = list(/datum/material/glass= SHEET_MATERIAL_AMOUNT * 3.75, /datum/material/iron= HALF_SHEET_MATERIAL_AMOUNT)
transparent = TRUE
max_integrity = 75
shield_break_sound = 'sound/effects/glassbr3.ogg'
shield_break_leftover = /obj/item/shard
armor_type = /datum/armor/item_shield/riot
/obj/item/shield/riot/Initialize(mapload)
. = ..()
var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/strobeshield)
AddComponent(
/datum/component/slapcrafting,\
slapcraft_recipes = slapcraft_recipe_list,\
)
/obj/item/shield/riot/attackby(obj/item/attackby_item, mob/user, params)
if(istype(attackby_item, /obj/item/melee/baton))
if(!COOLDOWN_FINISHED(src, baton_bash))
return
user.visible_message(span_warning("[user] bashes [src] with [attackby_item]!"))
playsound(user.loc, 'sound/effects/shieldbash.ogg', 50, TRUE)
COOLDOWN_START(src, baton_bash, BATON_BASH_COOLDOWN)
return
if(istype(attackby_item, /obj/item/stack/sheet/mineral/titanium))
if (atom_integrity >= max_integrity)
to_chat(user, span_warning("[src] is already in perfect condition."))
return
var/obj/item/stack/sheet/mineral/titanium/titanium_sheet = attackby_item
titanium_sheet.use(1)
atom_integrity = max_integrity
to_chat(user, span_notice("You repair [src] with [titanium_sheet]."))
return
return ..()
/obj/item/shield/riot/flash
name = "strobe shield"
desc = "A shield with a built in, high intensity light capable of blinding and disorienting suspects. Takes regular handheld flashes as bulbs."
icon_state = "flashshield"
inhand_icon_state = "flashshield"
var/obj/item/assembly/flash/handheld/embedded_flash = /obj/item/assembly/flash/handheld
/obj/item/shield/riot/flash/Initialize(mapload)
. = ..()
AddElement(/datum/element/update_icon_updates_onmob)
if(embedded_flash)
embedded_flash = new(src)
embedded_flash.set_light_flags(embedded_flash.light_flags | LIGHT_ATTACHED)
update_appearance(UPDATE_ICON)
/obj/item/shield/riot/flash/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs)
if(istype(arrived, /obj/item/assembly/flash/handheld))
embedded_flash = arrived
embedded_flash.set_light_flags(embedded_flash.light_flags | LIGHT_ATTACHED)
update_appearance(UPDATE_ICON)
return ..()
/obj/item/shield/riot/flash/Exited(atom/movable/gone, direction)
if(gone == embedded_flash)
embedded_flash.set_light_flags(embedded_flash.light_flags & ~LIGHT_ATTACHED)
embedded_flash = null
update_appearance(UPDATE_ICON)
return ..()
/obj/item/shield/riot/flash/vv_edit_var(vname, vval)
. = ..()
if(vname == NAMEOF(src, embedded_flash))
update_appearance(UPDATE_ICON)
/obj/item/shield/riot/flash/Destroy(force)
QDEL_NULL(embedded_flash)
return ..()
/obj/item/shield/riot/flash/attack(mob/living/target_mob, mob/living/user)
if(user.combat_mode)
return ..()
flash_away(user, target_mob)
/obj/item/shield/riot/flash/attack_self(mob/living/carbon/user)
flash_away(user)
/obj/item/shield/riot/flash/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
. = ..()
if(.)
flash_away(owner)
///Handles calls for the actual flash object + plays the flashing animations.
/obj/item/shield/riot/flash/proc/flash_away(mob/owner, mob/target, animation_only)
if(QDELETED(embedded_flash) || (embedded_flash.burnt_out && !animation_only))
return
var/flick = animation_only ? TRUE : (target ? embedded_flash.attack(target, owner) : embedded_flash.AOE_flash(user = owner))
if(!flick && !embedded_flash.burnt_out)
return
flick("flashshield_flash", src)
inhand_icon_state = "flashshield_flash"
owner?.update_held_items()
addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, update_appearance)), 0.5 SECONDS, (TIMER_UNIQUE|TIMER_OVERRIDE)) //.5 second delay so the inhands sprite finishes its anim since inhands don't support flick().
/obj/item/shield/riot/flash/attackby(obj/item/attackby_item, mob/user)
if(istype(attackby_item, /obj/item/assembly/flash/handheld))
var/obj/item/assembly/flash/handheld/flash = attackby_item
if(flash.burnt_out)
to_chat(user, span_warning("No sense replacing it with a broken bulb!"))
return
else
to_chat(user, span_notice("You begin to replace the bulb..."))
if(do_after(user, 20, target = user))
if(QDELETED(flash) || flash.burnt_out)
return
playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE)
qdel(embedded_flash)
flash.forceMove(src)
return
return ..()
/obj/item/shield/riot/flash/emp_act(severity)
. = ..()
if(QDELETED(embedded_flash) || embedded_flash.burnt_out)
return
embedded_flash.emp_act(severity)
if(embedded_flash.burnt_out) // a little hacky but no good way to check otherwise.
flash_away((ismob(loc) ? loc : null), animation_only = TRUE)
/obj/item/shield/riot/flash/update_icon_state()
if(QDELETED(embedded_flash) || embedded_flash.burnt_out)
icon_state = "riot"
inhand_icon_state = "riot"
else
icon_state = "flashshield"
inhand_icon_state = "flashshield"
return ..()
/obj/item/shield/riot/flash/examine(mob/user)
. = ..()
if (embedded_flash?.burnt_out)
. += span_info("The mounted bulb has burnt out. You can try replacing it with a new <b>flash</b>.")
/obj/item/shield/energy
name = "combat energy shield"
desc = "A hardlight shield capable of reflecting blocked energy projectiles, as well las providing well-rounded defense from most all other attacks."
icon_state = "eshield"
inhand_icon_state = "eshield"
w_class = WEIGHT_CLASS_TINY
attack_verb_continuous = list("shoves", "bashes")
attack_verb_simple = list("shove", "bash")
throw_range = 5
force = 3
throwforce = 3
throw_speed = 3
breakable_by_damage = FALSE
block_sound = 'sound/weapons/block_blade.ogg'
/// Force of the shield when active.
var/active_force = 10
/// Throwforce of the shield when active.
var/active_throwforce = 8
/// Throwspeed of ethe shield when active.
var/active_throw_speed = 2
/// Whether clumsy people can transform this without side effects.
var/can_clumsy_use = FALSE
/// The chance for projectiles to be reflected by the shield
var/reflection_probability = 50
/obj/item/shield/energy/Initialize(mapload)
. = ..()
AddComponent( \
/datum/component/transforming, \
force_on = active_force, \
throwforce_on = active_throwforce, \
throw_speed_on = active_throw_speed, \
hitsound_on = hitsound, \
clumsy_check = !can_clumsy_use, \
)
RegisterSignal(src, COMSIG_TRANSFORMING_ON_TRANSFORM, PROC_REF(on_transform))
RegisterSignal(src, COMSIG_ITEM_CAN_DISARM_ATTACK, PROC_REF(can_disarm_attack))
/obj/item/shield/energy/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK, damage_type = BRUTE)
if(!HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE))
return FALSE
if(attack_type == PROJECTILE_ATTACK)
var/obj/projectile/our_projectile = hitby
if(our_projectile.reflectable) //We handle this via IsReflect() instead.
final_block_chance = 0
return ..()
/obj/item/shield/energy/IsReflect()
return HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE) && prob(reflection_probability)
/*
* Signal proc for [COMSIG_TRANSFORMING_ON_TRANSFORM].
*/
/obj/item/shield/energy/proc/on_transform(obj/item/source, mob/user, active)
SIGNAL_HANDLER
if(user)
balloon_alert(user, active ? "activated" : "deactivated")
playsound(src, active ? 'sound/weapons/saberon.ogg' : 'sound/weapons/saberoff.ogg', 35, TRUE)
return COMPONENT_NO_DEFAULT_MESSAGE
/obj/item/shield/energy/proc/can_disarm_attack(datum/source, mob/living/victim, mob/living/user, send_message = TRUE)
SIGNAL_HANDLER
if(!HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE))
if(send_message)
balloon_alert(user, "activate it first!")
return COMPONENT_BLOCK_ITEM_DISARM_ATTACK
/obj/item/shield/energy/advanced
name = "advanced combat energy shield"
desc = "A hardlight shield capable of reflecting all energy projectiles, as well las providing well-rounded defense from most all other attacks. \
Often employed by Nanotrasen deathsquads."
icon_state = "advanced_eshield"
inhand_icon_state = "advanced_eshield"
reflection_probability = 100 //Guaranteed reflection
/obj/item/shield/riot/tele
name = "telescopic shield"
desc = "An advanced riot shield made of lightweight materials that collapses for easy storage."
icon_state = "teleriot"
inhand_icon_state = "teleriot"
worn_icon_state = "teleriot"
custom_materials = list(/datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 3.6, /datum/material/glass = HALF_SHEET_MATERIAL_AMOUNT * 3.6, /datum/material/silver = SMALL_MATERIAL_AMOUNT * 2.7, /datum/material/titanium = SMALL_MATERIAL_AMOUNT * 1.8)
slot_flags = null
force = 3
throwforce = 3
throw_speed = 3
throw_range = 4
w_class = WEIGHT_CLASS_NORMAL
/obj/item/shield/riot/tele/Initialize(mapload)
. = ..()
AddComponent( \
/datum/component/transforming, \
force_on = 8, \
throwforce_on = 5, \
throw_speed_on = 2, \
hitsound_on = hitsound, \
w_class_on = WEIGHT_CLASS_BULKY, \
attack_verb_continuous_on = list("smacks", "strikes", "cracks", "beats"), \
attack_verb_simple_on = list("smack", "strike", "crack", "beat"), \
)
RegisterSignal(src, COMSIG_TRANSFORMING_ON_TRANSFORM, PROC_REF(on_transform))
RegisterSignal(src, COMSIG_ITEM_CAN_DISARM_ATTACK, PROC_REF(can_disarm_attack))
/obj/item/shield/riot/tele/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK, damage_type = BRUTE)
if(HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE))
return ..()
return FALSE
/**
* Signal proc for [COMSIG_TRANSFORMING_ON_TRANSFORM].
*
* Allows it to be placed on back slot when active.
*/
/obj/item/shield/riot/tele/proc/on_transform(obj/item/source, mob/user, active)
SIGNAL_HANDLER
slot_flags = active ? ITEM_SLOT_BACK : null
if(user)
balloon_alert(user, active ? "extended" : "collapsed")
playsound(src, 'sound/weapons/batonextend.ogg', 50, TRUE)
return COMPONENT_NO_DEFAULT_MESSAGE
/obj/item/shield/riot/tele/proc/can_disarm_attack(datum/source, mob/living/victim, mob/living/user, send_message = TRUE)
SIGNAL_HANDLER
if(!HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE))
if(send_message)
balloon_alert(user, "extend it first!")
return COMPONENT_BLOCK_ITEM_DISARM_ATTACK
/datum/armor/item_shield/ballistic
melee = 30
bullet = 85
bomb = 10
laser = 80
/obj/item/shield/ballistic
name = "ballistic shield"
desc = "A heavy shield designed for blocking projectiles, weaker to melee."
icon_state = "ballistic"
inhand_icon_state = "ballistic"
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 2, /datum/material/glass = SHEET_MATERIAL_AMOUNT * 2, /datum/material/titanium =SHEET_MATERIAL_AMOUNT)
max_integrity = 75
shield_break_leftover = /obj/item/stack/rods/ten
armor_type = /datum/armor/item_shield/ballistic
/obj/item/shield/ballistic/attackby(obj/item/attackby_item, mob/user, params)
if(istype(attackby_item, /obj/item/stack/sheet/mineral/titanium))
if (atom_integrity >= max_integrity)
to_chat(user, span_warning("[src] is already in perfect condition."))
return
var/obj/item/stack/sheet/mineral/titanium/titanium_sheet = attackby_item
titanium_sheet.use(1)
atom_integrity = max_integrity
to_chat(user, span_notice("You repair [src] with [titanium_sheet]."))
return
return ..()
/datum/armor/item_shield/improvised
melee = 40
bullet = 30
laser = 30
/obj/item/shield/improvised
name = "improvised shield"
desc = "A crude shield made out of several sheets of iron taped together, not very durable."
icon_state = "improvised"
inhand_icon_state = "improvised"
custom_materials = list(/datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 2)
max_integrity = 35
shield_break_leftover = /obj/item/stack/rods/two
armor_type = /datum/armor/item_shield/improvised
block_sound = 'sound/items/trayhit2.ogg'
#undef BATON_BASH_COOLDOWN