New Wizard spell "branch": Vendormancy (#75679)

## About The Pull Request
New item for wizards, ~~the Staff~~ Scepter of Runic Vendormancy.

With it, you can summon Runic Vending machines to block your enemies,
push them 2 tiles back around the summoning tile, throw the vendors 4
tiles away to squash them or simple detonate the vendors for direct
damage against enemies within a 2 tile range.

The scepter has 3 charges that can be recharged after a "long" channel
so while powerful, it is a tactical weapon and wizards can't directly
steamroll the crew with endless vendors. (Unless they buy multiple
scepters, but that is just funny.)

Also, there is a bug with the throw... I copied how baseball bats deal
with knockback, but they consistently don't push the vendors back, just
spin them on the same tile... I appreciate if anyone has any idea on how
to fix or change that to a better system.

## New changes I made
The vendor has a random set of REAL wizard robes and hat, sandals and a
foam vendor scepter as products to sell now.
This gives the crew some real armor, and if it is considered too much, I
can swap it for the fake versions.
IMO the real clothes work as the perfect bait for the crew to approach
the vendors and get exploded in the process, and while a random
assistant might get real wizard armor to go valid hunt the wizard, the
crew might just mistake them for the real wizard and beat them to death,
which is too funny.
## Why It's Good For The Game

![vendormancerPR](https://github.com/tgstation/tgstation/assets/55374212/f9d98f3e-5916-4a17-987e-249f4cdb7185)

About a year ago I played Stoneshard, and it has such an amazing
Geomancy Wizard that I wanted to port some of its gameplay to SS13 as
our wizards, while funny and destructive, are kinda simple to play...

Summoning and blowing up rocks was nice, but I randomly had the idea of
summoning Vendors while at work and vendors squashing people has become
such an iconic SS13 thing to me that I had to stop being lazy and start
working on this.

Something, something, enviromental combat wizard.
## Changelog
Gonna polish the changelog later too...
🆑 Guillaume Prata
add: New Wizard spell branch: Vendormacy! Summon runic vending machines
with your Vending Scepter, force push them on your enemies to squish
them or blow them up while they are busy buying from the machines.
/🆑

---------

Co-authored-by: Time-Green <7501474+Time-Green@users.noreply.github.com>
This commit is contained in:
GuillaumePrata
2023-06-23 05:56:18 -03:00
committed by GitHub
parent 342e4e3968
commit 867c217c57
10 changed files with 241 additions and 0 deletions
@@ -390,3 +390,126 @@
whistle.whistler = null
whistle = null
return ..()
/////////////////////////////////////////Scepter of Vendormancy///////////////////
#define RUNIC_SCEPTER_MAX_CHARGES 3
#define RUNIC_SCEPTER_MAX_RANGE 7
/obj/item/runic_vendor_scepter
name = "scepter of runic vendormancy"
desc = "This scepter allows you to conjure, force push and detonate Runic Vendors. It can hold up to 3 charges that can be recovered with a simple magical channeling. A modern spin on the old Geomancy spells."
icon_state = "vendor_staff"
inhand_icon_state = "vendor_staff"
lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi'
icon = 'icons/obj/weapons/guns/magic.dmi'
slot_flags = ITEM_SLOT_BACK
w_class = WEIGHT_CLASS_NORMAL
force = 10
damtype = BRUTE
resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
attack_verb_continuous = list("smacks", "clubs", "wacks")
attack_verb_simple = list("smack", "club", "wacks")
/// Range cap on where you can summon vendors.
var/max_summon_range = RUNIC_SCEPTER_MAX_RANGE
/// Channeling time to summon a vendor.
var/summoning_time = 1 SECONDS
/// Checks if the scepter is channeling a vendor already.
var/scepter_is_busy_summoning = FALSE
/// Checks if the scepter is busy channeling recharges
var/scepter_is_busy_recharging = FALSE
///Number of summoning charges left.
var/summon_vendor_charges = RUNIC_SCEPTER_MAX_CHARGES
/obj/item/runic_vendor_scepter/Initialize(mapload)
. = ..()
RegisterSignal(src, COMSIG_ITEM_MAGICALLY_CHARGED, PROC_REF(on_magic_charge))
var/static/list/loc_connections = list(
COMSIG_ITEM_MAGICALLY_CHARGED = PROC_REF(on_magic_charge),
)
/obj/item/runic_vendor_scepter/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
if(scepter_is_busy_recharging)
user.balloon_alert(user, "busy!")
return
if(!check_allowed_items(target, not_inside = TRUE))
return
. |= AFTERATTACK_PROCESSED_ITEM
var/turf/afterattack_turf = get_turf(target)
if(istype(target, /obj/machinery/vending/runic_vendor))
var/obj/machinery/vending/runic_vendor/runic_explosion_target = target
runic_explosion_target.runic_explosion()
return
var/obj/machinery/vending/runic_vendor/vendor_on_turf = locate() in afterattack_turf
if(vendor_on_turf)
vendor_on_turf.runic_explosion()
return
if(!summon_vendor_charges)
user.balloon_alert(user, "no charges!")
return
if(get_dist(afterattack_turf,src) > max_summon_range)
user.balloon_alert(user, "too far!")
return
if(get_turf(src) == afterattack_turf)
user.balloon_alert(user, "too close!")
return
if(scepter_is_busy_summoning)
user.balloon_alert(user, "already summoning!")
return
if(afterattack_turf.is_blocked_turf(TRUE))
user.balloon_alert(user, "blocked!")
return
if(summoning_time)
scepter_is_busy_summoning = TRUE
user.balloon_alert(user, "summoning...")
if(!do_after(user, summoning_time, target = target))
scepter_is_busy_summoning = FALSE
return
scepter_is_busy_summoning = FALSE
if(summon_vendor_charges)
playsound(src,'sound/weapons/resonator_fire.ogg',50,TRUE)
user.visible_message(span_warning("[user] summons a runic vendor!"))
new /obj/machinery/vending/runic_vendor(afterattack_turf)
summon_vendor_charges--
user.changeNext_move(CLICK_CD_MELEE)
return
return ..()
/obj/item/runic_vendor_scepter/attack_self(mob/user, modifiers)
. = ..()
user.balloon_alert(user, "recharging...")
scepter_is_busy_recharging = TRUE
if(!do_after(user, 5 SECONDS))
scepter_is_busy_recharging = FALSE
return
user.balloon_alert(user, "fully charged")
scepter_is_busy_recharging = FALSE
summon_vendor_charges = RUNIC_SCEPTER_MAX_CHARGES
/obj/item/runic_vendor_scepter/afterattack_secondary(atom/target, mob/user, proximity_flag, click_parameters)
var/turf/afterattack_secondary_turf = get_turf(target)
var/obj/machinery/vending/runic_vendor/vendor_on_turf = locate() in afterattack_secondary_turf
if(istype(target, /obj/machinery/vending/runic_vendor))
var/obj/machinery/vending/runic_vendor/vendor_being_throw = target
vendor_being_throw.throw_at(get_edge_target_turf(target, get_cardinal_dir(src, target)), 4, 20, user)
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
if(vendor_on_turf)
vendor_on_turf.throw_at(get_edge_target_turf(target, get_cardinal_dir(src, target)), 4, 20, user)
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
/obj/item/runic_vendor_scepter/proc/on_magic_charge(datum/source, datum/action/cooldown/spell/charge/spell, mob/living/caster)
SIGNAL_HANDLER
if(!ismovable(loc))
return
. = COMPONENT_ITEM_CHARGED
summon_vendor_charges = RUNIC_SCEPTER_MAX_CHARGES
return .
#undef RUNIC_SCEPTER_MAX_CHARGES
#undef RUNIC_SCEPTER_MAX_RANGE
@@ -97,3 +97,12 @@
limit = 3
category = "Assistance"
refundable = TRUE
/datum/spellbook_entry/item/vendormancer
name = "Scepter of Vendormancy"
desc = "A scepter containing the power of Runic Vendormancy.\
It can summon up to 3 Runic Vendors that decay over time, but can be \
throw around to squash oponents or be directly detonated. When out of \
charges a long channel will restore the charges."
item_path = /obj/item/runic_vendor_scepter
category = "Assistance"
+91
View File
@@ -0,0 +1,91 @@
#define PULSE_DISTANCE_RANGE 2
/obj/machinery/vending/runic_vendor
name = "\improper Runic Vending Machine"
desc = "This vending machine was designed for warfare! A perfect bait for Nanotrasen's crew thirst for consumerism."
icon_state = "RunicVendor"
panel_type = "panel10"
product_slogans = "Come get free magic!;50% off on Mjollnirs today!; Buy a warp whistle and get another one free!"
vend_reply = "Please, stand still near the vending machine for your special package!"
resistance_flags = FIRE_PROOF
light_mask = "RunicVendor-light-mask"
/// How long the vendor stays up before it decays.
var/time_to_decay = 30 SECONDS
/// Area around the vendor that will pushback nearby mobs.
var/pulse_distance = PULSE_DISTANCE_RANGE
/obj/machinery/vending/runic_vendor/Initialize(mapload)
if(mapload)
log_mapping("[type] is not supposed to be mapped it, it decays after a set time")
stack_trace("Someone mapped in the meme vending machine the wizard scepter spawns, please remove it")
addtimer(CALLBACK(src, PROC_REF(decay)), time_to_decay, TIMER_STOPPABLE)
INVOKE_ASYNC(src, PROC_REF(runic_pulse))
switch(pick(1,3))
if(1)
products = list(
/obj/item/clothing/head/wizard = 1,
/obj/item/clothing/suit/wizrobe = 1,
/obj/item/clothing/shoes/sandal/magic = 1,
/obj/item/toy/foam_runic_scepter = 1,
)
if(2)
products = list(
/obj/item/clothing/head/wizard/red = 1,
/obj/item/clothing/suit/wizrobe/red = 1,
/obj/item/clothing/shoes/sandal/magic = 1,
/obj/item/toy/foam_runic_scepter = 1,
)
if(3)
products = list(
/obj/item/clothing/head/wizard/yellow = 1,
/obj/item/clothing/suit/wizrobe/yellow = 1,
/obj/item/clothing/shoes/sandal/magic = 1,
/obj/item/toy/foam_runic_scepter = 1,
)
return ..()
/obj/machinery/vending/runic_vendor/add_context(atom/source, list/context, obj/item/held_item, mob/user)
. = ..()
if(held_item)
if(istype(held_item, /obj/item/runic_vendor_scepter))
context[SCREENTIP_CONTEXT_LMB] = "Detonate"
context[SCREENTIP_CONTEXT_RMB] = "Force push"
return CONTEXTUAL_SCREENTIP_SET
return .
/obj/machinery/vending/runic_vendor/Destroy()
visible_message(span_warning("[src] flickers and disappears!"))
playsound(src,'sound/weapons/resonator_blast.ogg',25,TRUE)
return ..()
/obj/machinery/vending/runic_vendor/proc/runic_explosion()
explosion(src, light_impact_range = 2)
qdel(src)
/obj/machinery/vending/runic_vendor/proc/runic_pulse()
var/pulse_locs = spiral_range_turfs(pulse_distance, get_turf(src))
var/list/hit_things = list()
for(var/turf/pulsing_turf in pulse_locs)
for(var/mob/living/mob_to_be_pulsed_back in pulsing_turf.contents)
hit_things += mob_to_be_pulsed_back
var/atom/target = get_edge_target_turf(mob_to_be_pulsed_back, get_dir(src, get_step_away(mob_to_be_pulsed_back, src)))
to_chat(mob_to_be_pulsed_back, span_userdanger("The field repels you with tremendous force!"))
playsound(src, 'sound/effects/gravhit.ogg', 50, TRUE)
mob_to_be_pulsed_back.throw_at(target, 4, 4)
/obj/machinery/vending/runic_vendor/screwdriver_act(mob/living/user, obj/item/I)
explosion(src, light_impact_range = 2)
qdel(src)
/obj/machinery/vending/runic_vendor/proc/decay()
qdel(src)
#undef PULSE_DISTANCE_RANGE