From 433f4d5b3aa2c32866c1de0a9cf0a5f167cbfea5 Mon Sep 17 00:00:00 2001
From: kevinz000 <2003111+kevinz000@users.noreply.github.com>
Date: Sun, 3 Nov 2019 06:14:13 -0700
Subject: [PATCH] Ports "Replaces the rpg loot datum with a component and makes
some suffixes have real effects" & co
---
code/__DEFINES/fantasy_affixes.dm | 5 +
code/__DEFINES/is_helpers.dm | 4 +
code/datums/components/bane.dm | 45 +++++
code/datums/components/fantasy/_fantasy.dm | 140 +++++++++++++++
code/datums/components/fantasy/affix.dm | 13 ++
code/datums/components/fantasy/prefixes.dm | 68 +++++++
code/datums/components/fantasy/suffixes.dm | 170 ++++++++++++++++++
code/datums/components/igniter.dm | 37 ++++
code/datums/components/knockback.dm | 44 +++++
code/datums/components/lifesteal.dm | 39 ++++
code/datums/components/shrapnel.dm | 39 ++++
code/datums/components/summoning.dm | 69 +++++++
code/datums/components/tactical.dm | 42 +++++
.../machinery/porta_turret/portable_turret.dm | 1 +
code/game/objects/items.dm | 6 +-
code/game/objects/items/robot/robot_items.dm | 4 +-
code/game/objects/structures/flora.dm | 15 +-
code/modules/events/wizard/rpgloot.dm | 81 +--------
.../living/simple_animal/hostile/hostile.dm | 4 +-
code/modules/power/singularity/emitter.dm | 1 +
.../modules/projectiles/ammunition/_firing.dm | 7 +-
.../ammunition/caseless/_caseless.dm | 2 +-
code/modules/projectiles/gun.dm | 4 +-
code/modules/projectiles/projectile.dm | 5 +
tgstation.dme | 12 ++
25 files changed, 759 insertions(+), 98 deletions(-)
create mode 100644 code/__DEFINES/fantasy_affixes.dm
create mode 100644 code/datums/components/bane.dm
create mode 100644 code/datums/components/fantasy/_fantasy.dm
create mode 100644 code/datums/components/fantasy/affix.dm
create mode 100644 code/datums/components/fantasy/prefixes.dm
create mode 100644 code/datums/components/fantasy/suffixes.dm
create mode 100644 code/datums/components/igniter.dm
create mode 100644 code/datums/components/knockback.dm
create mode 100644 code/datums/components/lifesteal.dm
create mode 100644 code/datums/components/shrapnel.dm
create mode 100644 code/datums/components/summoning.dm
create mode 100644 code/datums/components/tactical.dm
diff --git a/code/__DEFINES/fantasy_affixes.dm b/code/__DEFINES/fantasy_affixes.dm
new file mode 100644
index 000000000..709d414d1
--- /dev/null
+++ b/code/__DEFINES/fantasy_affixes.dm
@@ -0,0 +1,5 @@
+#define AFFIX_PREFIX (1 << 0)
+#define AFFIX_SUFFIX (1 << 1)
+
+#define AFFIX_GOOD (1 << 0)
+#define AFFIX_EVIL (1 << 1)
\ No newline at end of file
diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index 96190e6de..fd97a8f03 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -253,6 +253,10 @@ GLOBAL_LIST_INIT(pointed_types, typecacheof(list(
#define isbodypart(A) (istype(A, /obj/item/bodypart))
+#define isprojectile(A) (istype(A, /obj/item/projectile))
+
+#define isgun(A) (istype(A, /obj/item/gun))
+
//Assemblies
#define isassembly(O) (istype(O, /obj/item/assembly))
diff --git a/code/datums/components/bane.dm b/code/datums/components/bane.dm
new file mode 100644
index 000000000..84f801027
--- /dev/null
+++ b/code/datums/components/bane.dm
@@ -0,0 +1,45 @@
+/datum/component/bane
+ dupe_mode = COMPONENT_DUPE_ALLOWED
+
+ var/mobtype
+ var/speciestype
+ var/damage_multiplier
+
+/datum/component/bane/Initialize(mobtype, damage_multiplier=1)
+ if(!isitem(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ if(ispath(mobtype, /mob/living))
+ src.mobtype = mobtype
+ else if(ispath(mobtype, /datum/species))
+ speciestype = mobtype
+ else
+ return COMPONENT_INCOMPATIBLE
+
+ src.damage_multiplier = damage_multiplier
+
+/datum/component/bane/RegisterWithParent()
+ if(speciestype)
+ RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, .proc/speciesCheck)
+ else
+ RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, .proc/mobCheck)
+
+/datum/component/bane/UnregisterFromParent()
+ UnregisterSignal(parent, COMSIG_ITEM_AFTERATTACK)
+
+/datum/component/bane/proc/speciesCheck(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters)
+ if(!is_species(target, speciestype))
+ return
+ activate(source, target, user)
+
+/datum/component/bane/proc/mobCheck(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters)
+ if(!istype(target, mobtype))
+ return
+ activate(source, target, user)
+
+/datum/component/bane/proc/activate(obj/item/source, mob/living/target, mob/attacker)
+ if(attacker.a_intent != INTENT_HARM)
+ return
+
+ var/extra_damage = max(0, source.force * damage_multiplier)
+ target.apply_damage(extra_damage, source.damtype, attacker.zone_selected)
diff --git a/code/datums/components/fantasy/_fantasy.dm b/code/datums/components/fantasy/_fantasy.dm
new file mode 100644
index 000000000..86e016784
--- /dev/null
+++ b/code/datums/components/fantasy/_fantasy.dm
@@ -0,0 +1,140 @@
+/datum/component/fantasy
+ dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
+
+ var/quality
+
+ var/canFail
+ var/announce
+
+ var/originalName
+ var/list/affixes
+ var/list/appliedComponents
+
+ var/static/list/affixListing
+
+/datum/component/fantasy/Initialize(quality, list/affixes = list(), canFail=FALSE, announce=FALSE)
+ if(!isitem(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ src.quality = quality || randomQuality()
+ src.canFail = canFail
+ src.announce = announce
+
+ src.affixes = affixes
+ appliedComponents = list()
+ randomAffixes()
+
+/datum/component/fantasy/Destroy()
+ unmodify()
+ affixes = null
+ return ..()
+
+/datum/component/fantasy/RegisterWithParent()
+ var/obj/item/master = parent
+ originalName = master.name
+ modify()
+
+/datum/component/fantasy/UnregisterFromParent()
+ unmodify()
+
+/datum/component/fantasy/InheritComponent(datum/component/fantasy/newComp, original, list/arguments)
+ unmodify()
+ if(newComp)
+ quality += newComp.quality
+ canFail = newComp.canFail
+ announce = newComp.announce
+ else
+ arguments.len = 5 // This is done to replicate what happens when an arglist smaller than the necessary arguments is given
+ quality += arguments[1]
+ canFail = arguments[4] || canFail
+ announce = arguments[5] || announce
+ modify()
+
+/datum/component/fantasy/proc/randomQuality()
+ var/quality = pick(1;15, 2;14, 2;13, 2;12, 3;11, 3;10, 3;9, 4;8, 4;7, 4;6, 5;5, 5;4, 5;3, 6;2, 6;1, 6;0)
+ if(prob(50))
+ quality = -quality
+ return quality
+
+/datum/component/fantasy/proc/randomAffixes(force)
+ if(!affixListing)
+ affixListing = list()
+ for(var/T in subtypesof(/datum/fantasy_affix))
+ var/datum/fantasy_affix/affix = new T
+ affixListing[affix] = affix.weight
+
+ if(length(affixes))
+ if(!force)
+ return
+ affixes = list()
+
+ var/alignment
+ if(quality >= 0)
+ alignment |= AFFIX_GOOD
+ if(quality <= 0)
+ alignment |= AFFIX_EVIL
+
+ var/usedSlots = NONE
+ for(var/i in 1 to max(1, abs(quality))) // We want at least 1 affix applied
+ var/datum/fantasy_affix/affix = pickweight(affixListing)
+ if(affix.placement & usedSlots)
+ continue
+ if(!(affix.alignment & alignment))
+ continue
+ if(!affix.validate(src))
+ continue
+ affixes += affix
+ usedSlots |= affix.placement
+
+/datum/component/fantasy/proc/modify()
+ var/obj/item/master = parent
+
+ master.force = max(0, master.force + quality)
+ master.throwforce = max(0, master.throwforce + quality)
+ master.armor = master.armor?.modifyAllRatings(quality)
+
+ var/newName = originalName
+ for(var/i in affixes)
+ var/datum/fantasy_affix/affix = i
+ newName = affix.apply(src, newName)
+
+ if(quality != 0)
+ newName = "[newName] [quality > 0 ? "+" : ""][quality]"
+
+ if(canFail && prob((quality - 9)*10))
+ var/turf/place = get_turf(parent)
+ place.visible_message("[parent] violently glows blue for a while, then evaporates.")
+ master.burn()
+ return
+ else if(announce)
+ announce()
+
+ master.name = newName
+
+/datum/component/fantasy/proc/unmodify()
+ var/obj/item/master = parent
+
+ for(var/i in affixes)
+ var/datum/fantasy_affix/affix = i
+ affix.remove(src)
+ for(var/i in appliedComponents)
+ qdel(i)
+
+ master.force = max(0, master.force - quality)
+ master.throwforce = max(0, master.throwforce - quality)
+ master.armor = master.armor?.modifyAllRatings(-quality)
+
+ master.name = originalName
+
+/datum/component/fantasy/proc/announce()
+ var/turf/location = get_turf(parent)
+ var/span
+ var/effect_description
+ if(quality >= 0)
+ span = ""
+ effect_description = "shimmering golden glow"
+ else
+ span = ""
+ effect_description = "mottled black glow"
+
+ location.visible_message("[span][originalName] is covered by a [effect_description] and then transforms into [parent]!")
diff --git a/code/datums/components/fantasy/affix.dm b/code/datums/components/fantasy/affix.dm
new file mode 100644
index 000000000..ad1f44ce4
--- /dev/null
+++ b/code/datums/components/fantasy/affix.dm
@@ -0,0 +1,13 @@
+/datum/fantasy_affix
+ var/placement // A bitflag of "slots" this affix takes up, for example pre/suffix
+ var/alignment
+ var/weight = 10
+
+// For those occasional affixes which only make sense in certain circumstances
+/datum/fantasy_affix/proc/validate(datum/component/fantasy/comp)
+ return TRUE
+
+/datum/fantasy_affix/proc/apply(datum/component/fantasy/comp, newName)
+ return newName
+
+/datum/fantasy_affix/proc/remove(datum/component/fantasy/comp)
diff --git a/code/datums/components/fantasy/prefixes.dm b/code/datums/components/fantasy/prefixes.dm
new file mode 100644
index 000000000..702ec9329
--- /dev/null
+++ b/code/datums/components/fantasy/prefixes.dm
@@ -0,0 +1,68 @@
+/datum/fantasy_affix/cosmetic_prefixes
+ placement = AFFIX_PREFIX
+ alignment = AFFIX_GOOD | AFFIX_EVIL
+
+ var/list/goodPrefixes
+ var/list/badPrefixes
+
+/datum/fantasy_affix/cosmetic_prefixes/New()
+ goodPrefixes = list(
+ "greater",
+ "major",
+ "blessed",
+ "superior",
+ "empowered",
+ "honed",
+ "true",
+ "glorious",
+ "robust",
+ )
+ badPrefixes = list(
+ "lesser",
+ "minor",
+ "blighted",
+ "inferior",
+ "enfeebled",
+ "rusted",
+ "unsteady",
+ "tragic",
+ "gimped",
+ "cursed",
+ )
+
+ weight = (length(goodPrefixes) + length(badPrefixes)) * 10
+
+/datum/fantasy_affix/cosmetic_prefixes/apply(datum/component/fantasy/comp, newName)
+ if(comp.quality > 0 || (comp.quality == 0 && prob(50)))
+ return "[pick(goodPrefixes)] [newName]"
+ else
+ return "[pick(badPrefixes)] [newName]"
+
+/datum/fantasy_affix/tactical
+ placement = AFFIX_PREFIX
+ alignment = AFFIX_GOOD
+ weight = 1 // Very powerful, no one should have such power
+
+/datum/fantasy_affix/tactical/apply(datum/component/fantasy/comp, newName)
+ var/obj/item/master = comp.parent
+ comp.appliedComponents += master.AddComponent(/datum/component/tactical)
+ return "tactical [newName]"
+
+/datum/fantasy_affix/pyromantic
+ placement = AFFIX_PREFIX
+ alignment = AFFIX_GOOD
+
+/datum/fantasy_affix/pyromantic/apply(datum/component/fantasy/comp, newName)
+ var/obj/item/master = comp.parent
+ comp.appliedComponents += master.AddComponent(/datum/component/igniter, CLAMP(comp.quality, 1, 10))
+ return "pyromantic [newName]"
+
+/datum/fantasy_affix/vampiric
+ placement = AFFIX_PREFIX
+ alignment = AFFIX_GOOD
+ weight = 5
+
+/datum/fantasy_affix/vampiric/apply(datum/component/fantasy/comp, newName)
+ var/obj/item/master = comp.parent
+ comp.appliedComponents += master.AddComponent(/datum/component/lifesteal, comp.quality)
+ return "vampiric [newName]"
\ No newline at end of file
diff --git a/code/datums/components/fantasy/suffixes.dm b/code/datums/components/fantasy/suffixes.dm
new file mode 100644
index 000000000..f32ca5059
--- /dev/null
+++ b/code/datums/components/fantasy/suffixes.dm
@@ -0,0 +1,170 @@
+/datum/fantasy_affix/cosmetic_suffixes
+ placement = AFFIX_SUFFIX
+ alignment = AFFIX_GOOD | AFFIX_EVIL
+
+ var/list/goodSuffixes
+ var/list/badSuffixes
+
+/datum/fantasy_affix/cosmetic_suffixes/New()
+ goodSuffixes = list(
+ "dexterity",
+ "constitution",
+ "intelligence",
+ "wisdom",
+ "charisma",
+ "the forest",
+ "the hills",
+ "the plains",
+ "the sea",
+ "the sun",
+ "the moon",
+ "the void",
+ "the world",
+ "many secrets",
+ "many tales",
+ "many colors",
+ "rending",
+ "sundering",
+ "the night",
+ "the day",
+ )
+ badSuffixes = list(
+ "draining",
+ "burden",
+ "discomfort",
+ "awkwardness",
+ "poor hygiene",
+ "timidity",
+ )
+
+ weight = (length(goodSuffixes) + length(badSuffixes)) * 10
+
+/datum/fantasy_affix/cosmetic_suffixes/apply(datum/component/fantasy/comp, newName)
+ if(comp.quality > 0 || (comp.quality == 0 && prob(50)))
+ return "[newName] of [pick(goodSuffixes)]"
+ else
+ return "[newName] of [pick(badSuffixes)]"
+
+//////////// Good suffixes
+/datum/fantasy_affix/bane
+ placement = AFFIX_SUFFIX
+ alignment = AFFIX_GOOD
+ weight = 20
+
+/datum/fantasy_affix/bane/apply(datum/component/fantasy/comp, newName)
+ . = ..()
+ // This is set up to be easy to add to these lists as I expect it will need modifications
+ var/static/list/possible_mobtypes
+ if(!possible_mobtypes)
+ // The base list of allowed mob/species types
+ possible_mobtypes = typecacheof(list(
+ /mob/living/simple_animal,
+ /mob/living/carbon,
+ /datum/species,
+ ))
+ // Some particular types to disallow if they're too broad/abstract
+ possible_mobtypes -= list(
+ /mob/living/simple_animal/hostile,
+ )
+ // Some types to remove them and their subtypes
+ possible_mobtypes -= typecacheof(list(
+ /mob/living/carbon/human/species,
+ ))
+
+ var/mob/picked_mobtype = pick(possible_mobtypes)
+ // This works even with the species picks since we're only accessing the name
+
+ var/obj/item/master = comp.parent
+ comp.appliedComponents += master.AddComponent(/datum/component/bane, picked_mobtype)
+ return "[newName] of [initial(picked_mobtype.name)] slaying"
+
+/datum/fantasy_affix/summoning
+ placement = AFFIX_SUFFIX
+ alignment = AFFIX_GOOD
+ weight = 5
+
+/datum/fantasy_affix/summoning/apply(datum/component/fantasy/comp, newName)
+ . = ..()
+ // This is set up to be easy to add to these lists as I expect it will need modifications
+ var/static/list/possible_mobtypes
+ if(!possible_mobtypes)
+ // The base list of allowed mob/species types
+ possible_mobtypes = typecacheof(list(
+ /mob/living/simple_animal,
+ /mob/living/carbon,
+ /datum/species,
+ ))
+ // Some particular types to disallow if they're too broad/abstract
+ possible_mobtypes -= list(
+ /mob/living/simple_animal/hostile,
+ )
+ // Some types to remove them and their subtypes
+ possible_mobtypes -= typecacheof(list(
+ /mob/living/carbon/human/species,
+ /mob/living/simple_animal/hostile/megafauna,
+ ))
+
+ var/mob/picked_mobtype = pick(possible_mobtypes)
+ // This works even with the species picks since we're only accessing the name
+
+ var/obj/item/master = comp.parent
+ var/max_mobs = max(CEILING(comp.quality/2, 1), 1)
+ var/spawn_delay = 300 - 30 * comp.quality
+ comp.appliedComponents += master.AddComponent(/datum/component/summoning, list(picked_mobtype), 100, max_mobs, spawn_delay)
+ return "[newName] of [initial(picked_mobtype.name)] summoning"
+
+/datum/fantasy_affix/shrapnel
+ placement = AFFIX_SUFFIX
+ alignment = AFFIX_GOOD
+
+/datum/fantasy_affix/shrapnel/validate(datum/component/fantasy/comp)
+ if(isgun(comp.parent))
+ return TRUE
+ return FALSE
+
+/datum/fantasy_affix/shrapnel/apply(datum/component/fantasy/comp, newName)
+ . = ..()
+ // higher means more likely
+ var/list/weighted_projectile_types = list(/obj/item/projectile/meteor = 1,
+ /obj/item/projectile/energy/nuclear_particle = 1,
+ /obj/item/projectile/beam/pulse = 1,
+ /obj/item/projectile/bullet/honker = 15,
+ /obj/item/projectile/temp = 15,
+ /obj/item/projectile/ion = 15,
+ /obj/item/projectile/magic/door = 15,
+ /obj/item/projectile/magic/locker = 15,
+// /obj/item/projectile/magic/fetch = 15,
+ /obj/item/projectile/beam/emitter = 15,
+// /obj/item/projectile/magic/flying = 15,
+ /obj/item/projectile/energy/net = 15,
+ /obj/item/projectile/bullet/incendiary/c9mm = 15,
+ /obj/item/projectile/temp/hot = 15,
+ /obj/item/projectile/beam/disabler = 15)
+
+ var/obj/item/projectile/picked_projectiletype = pickweight(weighted_projectile_types)
+
+ var/obj/item/master = comp.parent
+ comp.appliedComponents += master.AddComponent(/datum/component/shrapnel, picked_projectiletype)
+ return "[newName] of [initial(picked_projectiletype.name)] shrapnel"
+
+/datum/fantasy_affix/strength
+ placement = AFFIX_SUFFIX
+ alignment = AFFIX_GOOD
+
+/datum/fantasy_affix/strength/apply(datum/component/fantasy/comp, newName)
+ . = ..()
+ var/obj/item/master = comp.parent
+ comp.appliedComponents += master.AddComponent(/datum/component/knockback, CEILING(comp.quality/2, 1), FLOOR(comp.quality/10, 1))
+ return "[newName] of strength"
+
+//////////// Bad suffixes
+
+/datum/fantasy_affix/fool
+ placement = AFFIX_SUFFIX
+ alignment = AFFIX_EVIL
+
+/datum/fantasy_affix/fool/apply(datum/component/fantasy/comp, newName)
+ . = ..()
+ var/obj/item/master = comp.parent
+ comp.appliedComponents += master.AddComponent(/datum/component/squeak, list('sound/items/bikehorn.ogg'=1), 50)
+ return "[newName] of the fool"
diff --git a/code/datums/components/igniter.dm b/code/datums/components/igniter.dm
new file mode 100644
index 000000000..b40383e82
--- /dev/null
+++ b/code/datums/components/igniter.dm
@@ -0,0 +1,37 @@
+
+/datum/component/igniter
+ var/fire_stacks
+
+/datum/component/igniter/Initialize(fire_stacks=1)
+ if(!isitem(parent) && !ishostile(parent) && !isgun(parent) && !ismachinery(parent) && !isstructure(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ src.fire_stacks = fire_stacks
+
+/datum/component/igniter/RegisterWithParent()
+ if(ismachinery(parent) || isstructure(parent) || isgun(parent)) // turrets, etc
+ RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, .proc/projectile_hit)
+ else if(isitem(parent))
+ RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, .proc/item_afterattack)
+ else if(ishostile(parent))
+ RegisterSignal(parent, COMSIG_HOSTILE_ATTACKINGTARGET, .proc/hostile_attackingtarget)
+
+/datum/component/igniter/UnregisterFromParent()
+ UnregisterSignal(parent, list(COMSIG_ITEM_AFTERATTACK, COMSIG_HOSTILE_ATTACKINGTARGET, COMSIG_PROJECTILE_ON_HIT))
+
+/datum/component/igniter/proc/item_afterattack(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters)
+ if(!proximity_flag)
+ return
+ do_igniter(target)
+
+/datum/component/igniter/proc/hostile_attackingtarget(mob/living/simple_animal/hostile/attacker, atom/target)
+ do_igniter(target)
+
+/datum/component/igniter/proc/projectile_hit(atom/fired_from, atom/movable/firer, atom/target, Angle)
+ do_igniter(target)
+
+/datum/component/igniter/proc/do_igniter(atom/target)
+ if(isliving(target))
+ var/mob/living/L = target
+ L.adjust_fire_stacks(fire_stacks)
+ L.IgniteMob()
\ No newline at end of file
diff --git a/code/datums/components/knockback.dm b/code/datums/components/knockback.dm
new file mode 100644
index 000000000..b4fcaa2dd
--- /dev/null
+++ b/code/datums/components/knockback.dm
@@ -0,0 +1,44 @@
+/datum/component/knockback
+ var/throw_distance
+ var/throw_anchored
+
+/datum/component/knockback/Initialize(throw_distance=1)
+ if(!isitem(parent) && !ishostile(parent) && !isgun(parent) && !ismachinery(parent) && !isstructure(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ src.throw_distance = throw_distance
+ src.throw_anchored = throw_anchored
+
+/datum/component/knockback/RegisterWithParent()
+ if(ismachinery(parent) || isstructure(parent) || isgun(parent)) // turrets, etc
+ RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, .proc/projectile_hit)
+ else if(isitem(parent))
+ RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, .proc/item_afterattack)
+ else if(ishostile(parent))
+ RegisterSignal(parent, COMSIG_HOSTILE_ATTACKINGTARGET, .proc/hostile_attackingtarget)
+
+/datum/component/knockback/UnregisterFromParent()
+ UnregisterSignal(parent, list(COMSIG_ITEM_AFTERATTACK, COMSIG_HOSTILE_ATTACKINGTARGET, COMSIG_PROJECTILE_ON_HIT))
+
+/datum/component/knockback/proc/item_afterattack(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters)
+ if(!proximity_flag)
+ return
+ do_knockback(target, user, get_dir(source, target))
+
+/datum/component/knockback/proc/hostile_attackingtarget(mob/living/simple_animal/hostile/attacker, atom/target)
+ do_knockback(target, attacker, get_dir(attacker, target))
+
+/datum/component/knockback/proc/projectile_hit(atom/fired_from, atom/movable/firer, atom/target, Angle)
+ do_knockback(target, null, angle2dir(Angle))
+
+/datum/component/knockback/proc/do_knockback(atom/target, mob/thrower, throw_dir)
+ if(!ismovableatom(target) || throw_dir == null)
+ return
+ var/atom/movable/throwee = target
+ if(throwee.anchored && !throw_anchored)
+ return
+ if(throw_distance < 0)
+ throw_dir = turn(throw_dir, 180)
+ throw_distance *= -1
+ var/atom/throw_target = get_edge_target_turf(throwee, throw_dir)
+ throwee.safe_throw_at(throw_target, throw_distance, 1, thrower)
diff --git a/code/datums/components/lifesteal.dm b/code/datums/components/lifesteal.dm
new file mode 100644
index 000000000..c7a78e10a
--- /dev/null
+++ b/code/datums/components/lifesteal.dm
@@ -0,0 +1,39 @@
+
+/datum/component/lifesteal
+ var/flat_heal // heals a constant amount every time a hit occurs
+ var/static/list/damage_heal_order = list(BRUTE, BURN, OXY)
+
+/datum/component/lifesteal/Initialize(flat_heal=0)
+ if(!isitem(parent) && !ishostile(parent) && !isgun(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ src.flat_heal = flat_heal
+
+/datum/component/lifesteal/RegisterWithParent()
+ if(isgun(parent))
+ RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, .proc/projectile_hit)
+ else if(isitem(parent))
+ RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, .proc/item_afterattack)
+ else if(ishostile(parent))
+ RegisterSignal(parent, COMSIG_HOSTILE_ATTACKINGTARGET, .proc/hostile_attackingtarget)
+
+/datum/component/lifesteal/UnregisterFromParent()
+ UnregisterSignal(parent, list(COMSIG_ITEM_AFTERATTACK, COMSIG_HOSTILE_ATTACKINGTARGET, COMSIG_PROJECTILE_ON_HIT))
+
+/datum/component/lifesteal/proc/item_afterattack(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters)
+ if(!proximity_flag)
+ return
+ do_lifesteal(user, target)
+
+/datum/component/lifesteal/proc/hostile_attackingtarget(mob/living/simple_animal/hostile/attacker, atom/target)
+ do_lifesteal(attacker, target)
+
+/datum/component/lifesteal/proc/projectile_hit(atom/fired_from, atom/movable/firer, atom/target, Angle)
+ do_lifesteal(firer, target)
+
+/datum/component/lifesteal/proc/do_lifesteal(atom/heal_target, atom/damage_target)
+ if(isliving(heal_target) && isliving(damage_target))
+ var/mob/living/healing = heal_target
+ var/mob/living/damaging = damage_target
+ if(damaging.stat != DEAD)
+ healing.heal_ordered_damage(flat_heal, damage_heal_order)
diff --git a/code/datums/components/shrapnel.dm b/code/datums/components/shrapnel.dm
new file mode 100644
index 000000000..a911221f2
--- /dev/null
+++ b/code/datums/components/shrapnel.dm
@@ -0,0 +1,39 @@
+
+/datum/component/shrapnel
+ var/projectile_type
+ var/radius // shoots a projectile for every turf on this radius from the hit target
+ var/override_projectile_range
+
+/datum/component/shrapnel/Initialize(projectile_type, radius=1, override_projectile_range)
+ if(!isgun(parent) && !ismachinery(parent) && !isstructure(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ src.projectile_type = projectile_type
+ src.radius = radius
+ src.override_projectile_range = override_projectile_range
+
+/datum/component/shrapnel/RegisterWithParent()
+ if(ismachinery(parent) || isstructure(parent) || isgun(parent)) // turrets, etc
+ RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, .proc/projectile_hit)
+
+/datum/component/shrapnel/UnregisterFromParent()
+ UnregisterSignal(parent, list(COMSIG_PROJECTILE_ON_HIT))
+
+/datum/component/shrapnel/proc/projectile_hit(atom/fired_from, atom/movable/firer, atom/target, Angle)
+ do_shrapnel(firer, target)
+
+/datum/component/shrapnel/proc/do_shrapnel(mob/firer, atom/target)
+ if(radius < 1)
+ return
+ var/turf/target_turf = get_turf(target)
+ for(var/turf/shootat_turf in RANGE_TURFS(radius, target) - RANGE_TURFS(radius-1, target))
+ var/obj/item/projectile/P = new projectile_type(target_turf)
+
+ //Shooting Code:
+ P.range = radius+1
+ if(override_projectile_range)
+ P.range = override_projectile_range
+ P.preparePixelProjectile(shootat_turf, target)
+ P.firer = firer // don't hit ourself that would be really annoying
+ P.permutated += target // don't hit the target we hit already with the flak
+ P.fire()
diff --git a/code/datums/components/summoning.dm b/code/datums/components/summoning.dm
new file mode 100644
index 000000000..552959603
--- /dev/null
+++ b/code/datums/components/summoning.dm
@@ -0,0 +1,69 @@
+
+/datum/component/summoning
+ var/list/mob_types = list()
+ var/spawn_chance // chance for the mob to spawn on hit in percent
+ var/max_mobs
+ var/spawn_delay // delay in spawning between mobs (deciseconds)
+ var/spawn_text
+ var/spawn_sound
+ var/list/faction
+
+ var/last_spawned_time = 0
+ var/list/spawned_mobs = list()
+
+/datum/component/summoning/Initialize(mob_types, spawn_chance=100, max_mobs=3, spawn_delay=100, spawn_text="appears out of nowhere", spawn_sound='sound/magic/summon_magic.ogg', faction)
+ if(!isitem(parent) && !ishostile(parent) && !isgun(parent) && !ismachinery(parent) && !isstructure(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ src.mob_types = mob_types
+ src.spawn_chance = spawn_chance
+ src.max_mobs = max_mobs
+ src.spawn_delay = spawn_delay
+ src.spawn_text = spawn_text
+ src.spawn_sound = spawn_sound
+ src.faction = faction
+
+/datum/component/summoning/RegisterWithParent()
+ if(ismachinery(parent) || isstructure(parent) || isgun(parent)) // turrets, etc
+ RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, .proc/projectile_hit)
+ else if(isitem(parent))
+ RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, .proc/item_afterattack)
+ else if(ishostile(parent))
+ RegisterSignal(parent, COMSIG_HOSTILE_ATTACKINGTARGET, .proc/hostile_attackingtarget)
+
+/datum/component/summoning/UnregisterFromParent()
+ UnregisterSignal(parent, list(COMSIG_ITEM_AFTERATTACK, COMSIG_HOSTILE_ATTACKINGTARGET, COMSIG_PROJECTILE_ON_HIT))
+
+/datum/component/summoning/proc/item_afterattack(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters)
+ if(!proximity_flag)
+ return
+ do_spawn_mob(get_turf(target), user)
+
+/datum/component/summoning/proc/hostile_attackingtarget(mob/living/simple_animal/hostile/attacker, atom/target)
+ do_spawn_mob(get_turf(target), attacker)
+
+/datum/component/summoning/proc/projectile_hit(atom/fired_from, atom/movable/firer, atom/target, Angle)
+ do_spawn_mob(get_turf(target), firer)
+
+/datum/component/summoning/proc/do_spawn_mob(atom/spawn_location, summoner)
+ if(spawned_mobs.len >= max_mobs)
+ return 0
+ if(last_spawned_time > world.time)
+ return 0
+ if(!prob(spawn_chance))
+ return 0
+ last_spawned_time = world.time + spawn_delay
+ var/chosen_mob_type = pick(mob_types)
+ var/mob/living/simple_animal/L = new chosen_mob_type(spawn_location)
+ if(ishostile(L))
+ var/mob/living/simple_animal/hostile/H = L
+ H.friends += summoner // do not attack our summon boy
+ spawned_mobs += L
+ if(faction != null)
+ L.faction = faction
+ RegisterSignal(L, COMSIG_MOB_DEATH, .proc/on_spawned_death) // so we can remove them from the list, etc (for mobs with corpses)
+ playsound(spawn_location,spawn_sound, 50, 1)
+ spawn_location.visible_message("[L] [spawn_text].")
+
+/datum/component/summoning/proc/on_spawned_death(mob/killed, gibbed)
+ spawned_mobs -= killed
\ No newline at end of file
diff --git a/code/datums/components/tactical.dm b/code/datums/components/tactical.dm
new file mode 100644
index 000000000..5917fc300
--- /dev/null
+++ b/code/datums/components/tactical.dm
@@ -0,0 +1,42 @@
+
+/datum/component/tactical
+ var/allowed_slot
+
+/datum/component/tactical/Initialize(allowed_slot)
+ if(!isitem(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ src.allowed_slot = allowed_slot
+
+/datum/component/tactical/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/modify)
+ RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/unmodify)
+
+/datum/component/tactical/UnregisterFromParent()
+ UnregisterSignal(parent, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED))
+ unmodify()
+
+/datum/component/fantasy/Destroy()
+ unmodify()
+ return ..()
+
+/datum/component/tactical/proc/modify(obj/item/source, mob/user, slot)
+ if(allowed_slot && slot != allowed_slot)
+ unmodify()
+ return
+
+ var/obj/item/master = parent
+ var/image/I = image(icon = master.icon, icon_state = master.icon_state, loc = user)
+ I.copy_overlays(master)
+ I.override = TRUE
+ source.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/everyone, "sneaking_mission", I)
+ I.layer = ABOVE_MOB_LAYER
+
+/datum/component/tactical/proc/unmodify(obj/item/source, mob/user)
+ var/obj/item/master = source || parent
+ if(!user)
+ if(!ismob(master.loc))
+ return
+ user = master.loc
+
+ user.remove_alt_appearance("sneaking_mission")
diff --git a/code/game/machinery/porta_turret/portable_turret.dm b/code/game/machinery/porta_turret/portable_turret.dm
index bdd42f62d..60d5390e0 100644
--- a/code/game/machinery/porta_turret/portable_turret.dm
+++ b/code/game/machinery/porta_turret/portable_turret.dm
@@ -559,6 +559,7 @@
//Shooting Code:
A.preparePixelProjectile(target, T)
A.firer = src
+ A.fired_from = src
A.fire()
return A
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 43ac13fbc..0daccde53 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -107,9 +107,6 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
// non-clothing items
var/datum/dog_fashion/dog_fashion = null
- var/datum/rpg_loot/rpg_loot = null
-
-
//Tooltip vars
var/force_string //string form of an item's force. Edit this var only to set a custom force string
var/last_force_string_check = 0
@@ -140,7 +137,7 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
actions_types = null
if(GLOB.rpg_loot_items)
- rpg_loot = new(src)
+ AddComponent(/datum/component/fantasy)
if(force_string)
item_flags |= FORCE_STRING_OVERRIDE
@@ -165,7 +162,6 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
m.temporarilyRemoveItemFromInventory(src, TRUE)
for(var/X in actions)
qdel(X)
- QDEL_NULL(rpg_loot)
return ..()
/obj/item/proc/check_allowed_items(atom/target, not_inside, target_self)
diff --git a/code/game/objects/items/robot/robot_items.dm b/code/game/objects/items/robot/robot_items.dm
index a2be7aea5..9ed3794fd 100644
--- a/code/game/objects/items/robot/robot_items.dm
+++ b/code/game/objects/items/robot/robot_items.dm
@@ -426,7 +426,7 @@
A.BB.nodamage = FALSE
A.BB.speed = 0.5
playsound(src.loc, 'sound/machines/click.ogg', 50, 1)
- A.fire_casing(target, user, params, 0, 0, null, 0)
+ A.fire_casing(target, user, params, 0, 0, null, 0, src)
user.visible_message("[user] blasts a flying lollipop at [target]!")
check_amount()
@@ -442,7 +442,7 @@
A.BB.speed = 0.5
A.BB.color = rgb(rand(0, 255), rand(0, 255), rand(0, 255))
playsound(src.loc, 'sound/weapons/bulletflyby3.ogg', 50, 1)
- A.fire_casing(target, user, params, 0, 0, null, 0)
+ A.fire_casing(target, user, params, 0, 0, null, 0, src)
user.visible_message("[user] shoots a high-velocity gumball at [target]!")
check_amount()
diff --git a/code/game/objects/structures/flora.dm b/code/game/objects/structures/flora.dm
index 68f3c1d3d..fa11f9da1 100644
--- a/code/game/objects/structures/flora.dm
+++ b/code/game/objects/structures/flora.dm
@@ -313,18 +313,9 @@
throw_speed = 2
throw_range = 4
-
-/obj/item/twohanded/required/kirbyplants/equipped(mob/living/user)
- var/image/I = image(icon = 'icons/obj/flora/plants.dmi' , icon_state = src.icon_state, loc = user)
- I.copy_overlays(src)
- I.override = 1
- add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/everyone, "sneaking_mission", I)
- I.layer = ABOVE_MOB_LAYER
- ..()
-
-/obj/item/twohanded/required/kirbyplants/dropped(mob/living/user)
- ..()
- user.remove_alt_appearance("sneaking_mission")
+/obj/item/twohanded/required/kirbyplants/Initialize()
+ . = ..()
+ AddComponent(/datum/component/tactical)
/obj/item/twohanded/required/kirbyplants/random
icon = 'icons/obj/flora/_flora.dmi'
diff --git a/code/modules/events/wizard/rpgloot.dm b/code/modules/events/wizard/rpgloot.dm
index 3d560aefb..bf3104450 100644
--- a/code/modules/events/wizard/rpgloot.dm
+++ b/code/modules/events/wizard/rpgloot.dm
@@ -8,8 +8,11 @@
/datum/round_event/wizard/rpgloot/start()
var/upgrade_scroll_chance = 0
for(var/obj/item/I in world)
- if(!istype(I.rpg_loot))
- I.rpg_loot = new(I)
+ CHECK_TICK
+ if(!(I.flags_1 & INITIALIZED_1))
+ continue
+
+ I.AddComponent(/datum/component/fantasy)
if(istype(I, /obj/item/storage))
var/obj/item/storage/S = I
@@ -31,86 +34,20 @@
var/upgrade_amount = 1
var/can_backfire = TRUE
- var/one_use = TRUE
+ var/uses = 1
/obj/item/upgradescroll/afterattack(obj/item/target, mob/user , proximity)
. = ..()
if(!proximity || !istype(target))
return
- var/datum/rpg_loot/rpg_loot_datum = target.rpg_loot
- if(!istype(rpg_loot_datum))
- target.rpg_loot = rpg_loot_datum = new /datum/rpg_loot(target)
+ target.AddComponent(/datum/component/fantasy, upgrade_amount, null, null, can_backfire, TRUE)
- var/quality = rpg_loot_datum.quality
-
- if(can_backfire && (quality > 9 && prob((quality - 9)*10)))
- to_chat(user, "[target] violently glows blue for a while, then evaporates.")
- target.burn()
- else
- to_chat(user, "[target] glows blue and seems vaguely \"better\"!")
- rpg_loot_datum.modify(upgrade_amount)
-
- if(one_use)
+ if(--uses <= 0)
qdel(src)
/obj/item/upgradescroll/unlimited
name = "unlimited foolproof item fortification scroll"
desc = "Somehow, this piece of paper can be applied to items to make them \"better\". This scroll is made from the tongues of dead paper wizards, and can be used an unlimited number of times, with no drawbacks."
- one_use = FALSE
+ uses = INFINITY
can_backfire = FALSE
-
-/datum/rpg_loot
- var/positive_prefix = "okay"
- var/negative_prefix = "weak"
- var/suffix = "something profound"
- var/quality = 0
-
- var/obj/item/attached
- var/original_name
-
-/datum/rpg_loot/New(attached_item=null)
- attached = attached_item
-
- randomise()
-
-/datum/rpg_loot/Destroy()
- attached = null
-
-/datum/rpg_loot/proc/randomise()
- var/static/list/prefixespositive = list("greater", "major", "blessed", "superior", "empowered", "honed", "true", "glorious", "robust")
- var/static/list/prefixesnegative = list("lesser", "minor", "blighted", "inferior", "enfeebled", "rusted", "unsteady", "tragic", "gimped")
- var/static/list/suffixes = list("orc slaying", "elf slaying", "corgi slaying", "strength", "dexterity", "constitution", "intelligence", "wisdom", "charisma", "the forest", "the hills", "the plains", "the sea", "the sun", "the moon", "the void", "the world", "the fool", "many secrets", "many tales", "many colors", "rending", "sundering", "the night", "the day")
-
- var/new_quality = pick(1;15, 2;14, 2;13, 2;12, 3;11, 3;10, 3;9, 4;8, 4;7, 4;6, 5;5, 5;4, 5;3, 6;2, 6;1, 6;0)
-
- suffix = pick(suffixes)
- positive_prefix = pick(prefixespositive)
- negative_prefix = pick(prefixesnegative)
-
- if(prob(50))
- new_quality = -new_quality
-
- modify(new_quality)
-
-/datum/rpg_loot/proc/rename()
- var/obj/item/I = attached
- if(!original_name)
- original_name = I.name
- if(quality < 0)
- I.name = "[negative_prefix] [original_name] of [suffix] [quality]"
- else if(quality == 0)
- I.name = "[original_name] of [suffix]"
- else if(quality > 0)
- I.name = "[positive_prefix] [original_name] of [suffix] +[quality]"
-
-/datum/rpg_loot/proc/modify(quality_mod)
- var/obj/item/I = attached
- quality += quality_mod
-
- I.force = max(0,I.force + quality_mod)
- I.throwforce = max(0,I.throwforce + quality_mod)
-
- I.armor = I.armor.modifyAllRatings(quality)
-
- rename()
diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm
index 276d59892..e73751038 100644
--- a/code/modules/mob/living/simple_animal/hostile/hostile.dm
+++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm
@@ -347,6 +347,7 @@
/mob/living/simple_animal/hostile/proc/AttackingTarget()
+ SEND_SIGNAL(src, COMSIG_HOSTILE_ATTACKINGTARGET, target)
in_melee = TRUE
if(vore_active)
if(isliving(target))
@@ -429,12 +430,13 @@
if(casingtype)
var/obj/item/ammo_casing/casing = new casingtype(startloc)
playsound(src, projectilesound, 100, 1)
- casing.fire_casing(targeted_atom, src, null, null, null, ran_zone())
+ casing.fire_casing(targeted_atom, src, null, null, null, ran_zone(), src)
else if(projectiletype)
var/obj/item/projectile/P = new projectiletype(startloc)
playsound(src, projectilesound, 100, 1)
P.starting = startloc
P.firer = src
+ P.fired_from = src
P.yo = targeted_atom.y - startloc.y
P.xo = targeted_atom.x - startloc.x
if(AIStatus != AI_ON)//Don't want mindless mobs to have their movement screwed up firing in space
diff --git a/code/modules/power/singularity/emitter.dm b/code/modules/power/singularity/emitter.dm
index c9102e959..4caf969ee 100644
--- a/code/modules/power/singularity/emitter.dm
+++ b/code/modules/power/singularity/emitter.dm
@@ -199,6 +199,7 @@
if(prob(35))
sparks.start()
P.firer = user ? user : src
+ P.fired_from = src
if(last_projectile_params)
P.p_x = last_projectile_params[2]
P.p_y = last_projectile_params[3]
diff --git a/code/modules/projectiles/ammunition/_firing.dm b/code/modules/projectiles/ammunition/_firing.dm
index a56f7ff51..848163d65 100644
--- a/code/modules/projectiles/ammunition/_firing.dm
+++ b/code/modules/projectiles/ammunition/_firing.dm
@@ -1,8 +1,8 @@
-/obj/item/ammo_casing/proc/fire_casing(atom/target, mob/living/user, params, distro, quiet, zone_override, spread)
+/obj/item/ammo_casing/proc/fire_casing(atom/target, mob/living/user, params, distro, quiet, zone_override, spread, atom/fired_from)
distro += variance
for (var/i = max(1, pellets), i > 0, i--)
var/targloc = get_turf(target)
- ready_proj(target, user, quiet, zone_override)
+ ready_proj(target, user, quiet, zone_override, fired_from)
if(distro) //We have to spread a pixel-precision bullet. throw_proj was called before so angles should exist by now...
if(randomspread)
spread = round((rand() - 0.5) * distro)
@@ -20,11 +20,12 @@
update_icon()
return 1
-/obj/item/ammo_casing/proc/ready_proj(atom/target, mob/living/user, quiet, zone_override = "")
+/obj/item/ammo_casing/proc/ready_proj(atom/target, mob/living/user, quiet, zone_override = "", fired_from)
if (!BB)
return
BB.original = target
BB.firer = user
+ BB.fired_from = fired_from
if (zone_override)
BB.def_zone = zone_override
else
diff --git a/code/modules/projectiles/ammunition/caseless/_caseless.dm b/code/modules/projectiles/ammunition/caseless/_caseless.dm
index 5d590d542..db1aa6562 100644
--- a/code/modules/projectiles/ammunition/caseless/_caseless.dm
+++ b/code/modules/projectiles/ammunition/caseless/_caseless.dm
@@ -3,7 +3,7 @@
firing_effect_type = null
heavy_metal = FALSE
-/obj/item/ammo_casing/caseless/fire_casing(atom/target, mob/living/user, params, distro, quiet, zone_override, spread)
+/obj/item/ammo_casing/caseless/fire_casing(atom/target, mob/living/user, params, distro, quiet, zone_override, spread, atom/fired_from)
if (..()) //successfully firing
moveToNullspace()
QDEL_NULL(src)
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index 7edbbf15c..f0b212af7 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -239,7 +239,7 @@
else //Smart spread
sprd = round((((rand_spr/burst_size) * iteration) - (0.5 + (rand_spr * 0.25))) * (randomized_gun_spread + randomized_bonus_spread), 1)
before_firing(target,user)
- if(!chambered.fire_casing(target, user, params, ,suppressed, zone_override, sprd))
+ if(!chambered.fire_casing(target, user, params, ,suppressed, zone_override, sprd, src))
shoot_with_empty_chamber(user)
firing_burst = FALSE
return FALSE
@@ -287,7 +287,7 @@
return
sprd = round((rand() - 0.5) * DUALWIELD_PENALTY_EXTRA_MULTIPLIER * (randomized_gun_spread + randomized_bonus_spread))
before_firing(target,user)
- if(!chambered.fire_casing(target, user, params, , suppressed, zone_override, sprd))
+ if(!chambered.fire_casing(target, user, params, , suppressed, zone_override, sprd, src))
shoot_with_empty_chamber(user)
return
else
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index 4ab195159..63bd0bc52 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -17,6 +17,7 @@
resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
var/def_zone = "" //Aiming at
var/atom/movable/firer = null//Who shot it
+ var/atom/fired_from = null // the atom that the projectile was fired from (gun, turret)
var/suppressed = FALSE //Attack message
var/candink = FALSE //Can this projectile play the dink sound when hitting the head?
var/yo = null
@@ -131,6 +132,8 @@
return TRUE
/obj/item/projectile/proc/on_hit(atom/target, blocked = FALSE)
+ if(fired_from)
+ SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_ON_HIT, firer, target, Angle)
var/turf/target_loca = get_turf(target)
var/hitx
@@ -347,6 +350,8 @@
pixel_move(1, FALSE)
/obj/item/projectile/proc/fire(angle, atom/direct_target)
+ if(fired_from)
+ SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_BEFORE_FIRE, src, original)
//If no angle needs to resolve it from xo/yo!
if(!log_override && firer && original)
log_combat(firer, original, "fired at", src, "from [get_area_name(src, TRUE)]")
diff --git a/tgstation.dme b/tgstation.dme
index 10a170781..ad48f5e34 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -45,6 +45,7 @@
#include "code\__DEFINES\DNA.dm"
#include "code\__DEFINES\events.dm"
#include "code\__DEFINES\exports.dm"
+#include "code\__DEFINES\fantasy_affixes.dm"
#include "code\__DEFINES\flags.dm"
#include "code\__DEFINES\food.dm"
#include "code\__DEFINES\footsteps.dm"
@@ -359,6 +360,7 @@
#include "code\datums\components\anti_magic.dm"
#include "code\datums\components\armor_plate.dm"
#include "code\datums\components\art.dm"
+#include "code\datums\components\bane.dm"
#include "code\datums\components\bouncy.dm"
#include "code\datums\components\butchering.dm"
#include "code\datums\components\caltrop.dm"
@@ -370,9 +372,12 @@
#include "code\datums\components\empprotection.dm"
#include "code\datums\components\footstep.dm"
#include "code\datums\components\forced_gravity.dm"
+#include "code\datums\components\igniter.dm"
#include "code\datums\components\infective.dm"
#include "code\datums\components\jousting.dm"
+#include "code\datums\components\knockback.dm"
#include "code\datums\components\knockoff.dm"
+#include "code\datums\components\lifesteal.dm"
#include "code\datums\components\lockon_aiming.dm"
#include "code\datums\components\magnetic_catch.dm"
#include "code\datums\components\material_container.dm"
@@ -388,13 +393,16 @@
#include "code\datums\components\remote_materials.dm"
#include "code\datums\components\riding.dm"
#include "code\datums\components\rotation.dm"
+#include "code\datums\components\shrapnel.dm"
#include "code\datums\components\sizzle.dm"
#include "code\datums\components\slippery.dm"
#include "code\datums\components\spawner.dm"
#include "code\datums\components\spooky.dm"
#include "code\datums\components\squeak.dm"
#include "code\datums\components\stationloving.dm"
+#include "code\datums\components\summoning.dm"
#include "code\datums\components\swarming.dm"
+#include "code\datums\components\tactical.dm"
#include "code\datums\components\thermite.dm"
#include "code\datums\components\twitch_plays.dm"
#include "code\datums\components\uplink.dm"
@@ -408,6 +416,10 @@
#include "code\datums\components\crafting\recipes\recipes_primal.dm"
#include "code\datums\components\crafting\recipes\recipes_robot.dm"
#include "code\datums\components\crafting\recipes\recipes_weapon_and_ammo.dm"
+#include "code\datums\components\fantasy\_fantasy.dm"
+#include "code\datums\components\fantasy\affix.dm"
+#include "code\datums\components\fantasy\prefixes.dm"
+#include "code\datums\components\fantasy\suffixes.dm"
#include "code\datums\components\storage\storage.dm"
#include "code\datums\components\storage\concrete\_concrete.dm"
#include "code\datums\components\storage\concrete\bag_of_holding.dm"