Files
MrMelbertandGitHub 4b9b820390 Replaces bespoke holy whip-vampire interaction with silver material interaction (#96967)
## About The Pull Request

The holy whip no longer arbitrarily does 2x damage to vampires

The holy whip is now made out of silver, and thus applies bonus burn
damage when hitting vampires

The damage amount has gone down slightly (36 brute -> 18 brute + 12
burn) but it's slightly offset by the fact that the silver burn damage
is calculated against different armor

Silver itself now does more damage on average (from 5 flat to 4-16 based
on material volume) and is no longer completely blocked by any form of
clothing, instead blocked by wound armor

## Why It's Good For The Game

Tying these hardcoded debuffs to more broad game systems is more
sandboxy, which I think is more fun. It establishes consistent rules
that players can follow without needing niche code knowledge or extra
examine tooltips

Yeah it's a bit weaker in practice... but the balance doesn't really
matter anyways since it's a niche Halloween interaction

## Changelog

🆑 Melbert
balance: "Holy whip" null rod no longer arbitrarily does 2x brute damage
to vampires
balance: "Holy whip" null rod is now made of silver, which does bonus
burn damage to vampires (~1.67x damage now)
balance: Silver material weapons deal more burn damage to vampires on
average now (from 5 flat to 4-16, depending on volume of silver in the
weapon)
balance: Silver material weapons are no longer completely blocked by any
form of clothing, instead now reduced / blocked by wound armor
/🆑
2026-07-16 16:06:09 +02:00

218 lines
8.1 KiB
Plaintext

// Optional properties, not guaranteed to be present on all materials
/// Minimum flammability value required to make an object made out of this material flammable
#define MINIMUM_FLAMMABILITY 4
/// If a material has this property, it is flammable and has reduced fire protection.
/datum/material_property/flammability
name = "Flammability"
id = MATERIAL_FLAMMABILITY
/datum/material_property/flammability/get_descriptor(value)
switch(value)
if (0)
return "fireproof"
if (0 to 1)
return "nonflammable"
if (1 to 2)
return "mostly nonflammable"
if (2 to 3)
return "slightly fire-resistant"
if (3 to 4)
return "flammable"
if (4 to 6)
return "highly flammable"
if (6 to 8)
return "extremely flammable"
if (8 to INFINITY)
return "insanely flammable"
/datum/material_property/flammability/attach_to(datum/material/material)
. = ..()
RegisterSignal(material, COMSIG_MATERIAL_APPLIED, PROC_REF(on_applied))
RegisterSignal(material, COMSIG_MATERIAL_REMOVED, PROC_REF(on_removed))
/datum/material_property/flammability/proc/on_applied(datum/material/source, atom/new_atom, mat_amount, multiplier, from_slot)
SIGNAL_HANDLER
if (isobj(new_atom) && (new_atom.material_flags & MATERIAL_AFFECT_STATISTICS) && source.get_property(id) >= MINIMUM_FLAMMABILITY)
new_atom.resistance_flags |= FLAMMABLE
/datum/material_property/flammability/proc/on_removed(datum/material/source, atom/old_atom, mat_amount, multiplier, from_slot)
SIGNAL_HANDLER
if (isobj(old_atom) && (old_atom.material_flags & MATERIAL_AFFECT_STATISTICS) && source.get_property(id) >= MINIMUM_FLAMMABILITY && !(initial(old_atom.resistance_flags) & FLAMMABLE))
old_atom.resistance_flags &= ~FLAMMABLE
#undef MINIMUM_FLAMMABILITY
// An average value of 4 would equate to URANIUM_IRRADIATION_CHANCE radioactivity
#define URANIUM_RADIOACTIVITY 4
/datum/material_property/radioactivity
name = "Radioactivity"
id = MATERIAL_RADIOACTIVITY
/datum/material_property/radioactivity/get_descriptor(value)
switch(value)
if (0)
return null
if (0 to 2)
return "slightly radioactive"
if (2 to 4)
return "radioactive"
if (4 to 6)
return "highly radioactive"
if (6 to 8)
return "extremely radioactive"
if (8 to INFINITY)
return "insanely radioactive"
/datum/material_property/radioactivity/attach_to(datum/material/material)
. = ..()
RegisterSignal(material, COMSIG_MATERIAL_APPLIED, PROC_REF(on_applied))
RegisterSignal(material, COMSIG_MATERIAL_REMOVED, PROC_REF(on_removed))
/datum/material_property/radioactivity/proc/on_applied(datum/material/source, atom/new_atom, mat_amount, multiplier, from_slot)
SIGNAL_HANDLER
// Uranium structures should irradiate, but not items, because item irradiation is a lot more annoying.
if (!isitem(new_atom))
new_atom.AddElement(/datum/element/radioactive, chance = source.get_property(id) / URANIUM_RADIOACTIVITY * URANIUM_IRRADIATION_CHANCE * multiplier)
/datum/material_property/radioactivity/proc/on_removed(datum/material/source, atom/old_atom, mat_amount, multiplier, from_slot)
SIGNAL_HANDLER
if (!isitem(old_atom))
old_atom.RemoveElement(/datum/element/radioactive, chance = source.get_property(id) / URANIUM_RADIOACTIVITY * URANIUM_IRRADIATION_CHANCE * multiplier)
#undef URANIUM_RADIOACTIVITY
/// Applies firestacks to affected mobs
/datum/material_property/firestacker
name = "Igniting"
id = MATERIAL_FIRESTACKER
/datum/material_property/firestacker/get_descriptor(value)
return "igniting"
/datum/material_property/firestacker/get_tooltip(value)
return "Applies [value] firestacks to affected mobs"
/datum/material_property/firestacker/attach_to(datum/material/material)
. = ..()
material.track_flags |= MATERIAL_TRACK_CONTACT | MATERIAL_TRACK_IMPACT
var/static/list/interaction_signals = list(
COMSIG_MATERIAL_EFFECT_TOUCH,
COMSIG_MATERIAL_EFFECT_STEP,
COMSIG_MATERIAL_EFFECT_HIT,
COMSIG_MATERIAL_EFFECT_THROW_IMPACT,
)
RegisterSignals(material, interaction_signals, PROC_REF(on_contact))
/datum/material_property/firestacker/proc/on_contact(datum/material/source, atom/object, mob/living/target, mob/living/user, def_zone, skin_contact)
SIGNAL_HANDLER
// Floors don't trigger if you're wearing shoes because it'd be too cancer
if (isfloorturf(object) && !skin_contact && !source.get_property(MATERIAL_PENETRATING))
return
if (isliving(target))
target.adjust_fire_stacks(source.get_property(id))
/// Deals additional burn damage to vampires, property value times material volume determines damage
/datum/material_property/vampires_bane
name = "Vampires' Bane"
id = MATERIAL_VAMPIRES_BANE
/datum/material_property/vampires_bane/get_descriptor(value)
return "vampires' bane"
/datum/material_property/vampires_bane/get_tooltip(value)
return "Deals [value * 0.5] to [value * 2] additional burn damage to vampires on contact (based on material volume)"
/datum/material_property/vampires_bane/attach_to(datum/material/material)
. = ..()
material.track_flags |= MATERIAL_TRACK_CONTACT | MATERIAL_TRACK_IMPACT
var/static/list/interaction_signals = list(
COMSIG_MATERIAL_EFFECT_TOUCH,
COMSIG_MATERIAL_EFFECT_STEP,
COMSIG_MATERIAL_EFFECT_HIT,
COMSIG_MATERIAL_EFFECT_THROW_IMPACT,
)
RegisterSignals(material, interaction_signals, PROC_REF(on_contact))
/datum/material_property/vampires_bane/proc/on_contact(datum/material/source, atom/object, mob/living/target, mob/living/user, def_zone, skin_contact)
SIGNAL_HANDLER
if (!isvampire(target))
return
var/burn_damage = source.get_property(id) * clamp((object.custom_materials[source] / (2 * SHEET_MATERIAL_AMOUNT)), 0.5, 2)
var/armor_block = 0
if(!skin_contact && !source.get_property(MATERIAL_PENETRATING))
armor_block = target.run_armor_check(def_zone, WOUND, armour_penetration = astype(object, /obj/item)?.armour_penetration || 0, silent = TRUE, weak_against_armour = TRUE)
if(armor_block > 50)
return
to_chat(target, span_userdanger("Contact with [object] sears your undead flesh!"))
playsound(target, SFX_SIZZLE, 33, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
target.apply_damage(burn_damage, BURN, def_zone, armor_block, wound_bonus = 10, attacking_item = object, attack_direction = get_dir(user, target), wound_clothing = FALSE)
/// Teleports targets who come into active contact with the material around, property value determines teleport radius and damage taken per teleport
/datum/material_property/teleporting
name = "Teleporting"
id = MATERIAL_TELEPORTING
/datum/material_property/teleporting/get_descriptor(value)
return "dimensionally unstable"
/datum/material_property/teleporting/get_tooltip(value)
return "Randomly teleports whoever comes into contact with it in a [value] tile radius"
/datum/material_property/teleporting/attach_to(datum/material/material)
. = ..()
material.track_flags |= MATERIAL_TRACK_CONTACT | MATERIAL_TRACK_IMPACT
var/static/list/interaction_signals = list(
COMSIG_MATERIAL_EFFECT_TOUCH,
COMSIG_MATERIAL_EFFECT_STEP,
COMSIG_MATERIAL_EFFECT_HIT,
COMSIG_MATERIAL_EFFECT_THROW_IMPACT,
)
RegisterSignals(material, interaction_signals, PROC_REF(on_contact))
/datum/material_property/teleporting/proc/on_contact(datum/material/source, atom/object, atom/target, mob/living/user, def_zone, skin_contact)
SIGNAL_HANDLER
if (!ismovable(target))
return
var/atom/movable/as_movable = target
// Don't teleport airlocks around please
if (as_movable.anchored || as_movable.move_resist >= INFINITY)
return
// Floors don't trigger if you're wearing shoes because it'd be too cancer
if (isfloorturf(object) && !skin_contact && !source.get_property(MATERIAL_PENETRATING))
return
var/value = source.get_property(id)
do_teleport(target, get_turf(target), value, channel = TELEPORT_CHANNEL_BLUESPACE)
if (isstack(object))
var/obj/item/stack/as_stack = object
as_stack.use(1)
else if (object.uses_integrity)
object.take_damage(object.max_integrity * value * 0.01)
/// Makes all contact count as skin contact
/datum/material_property/penetrating
name = "Penetrating"
id = MATERIAL_PENETRATING
/datum/material_property/penetrating/get_descriptor(value)
return "dimensionally penetrating"
/datum/material_property/penetrating/get_tooltip(value)
return "Ignores all means of skin protection when triggering other material effects"