diff --git a/code/__defines/mobs_vr.dm b/code/__defines/mobs_vr.dm
index 0d32000341..eee6220f8b 100644
--- a/code/__defines/mobs_vr.dm
+++ b/code/__defines/mobs_vr.dm
@@ -26,3 +26,4 @@
#define SPECIES_XENOHYBRID "Xenomorph Hybrid"
#define SPECIES_ZORREN_FLAT "Flatland Zorren"
#define SPECIES_ZORREN_HIGH "Highlander Zorren"
+#define SPECIES_CUSTOM "Custom Species"
diff --git a/code/_helpers/global_lists.dm b/code/_helpers/global_lists.dm
index b0d1667fc4..903c7d3d68 100644
--- a/code/_helpers/global_lists.dm
+++ b/code/_helpers/global_lists.dm
@@ -32,7 +32,7 @@ var/global/list/all_species[0]
var/global/list/all_languages[0]
var/global/list/language_keys[0] // Table of say codes for all languages
var/global/list/whitelisted_species = list(SPECIES_HUMAN) // Species that require a whitelist check.
-var/global/list/playable_species = list("Custom Species", SPECIES_HUMAN) // A list of ALL playable species, whitelisted, latejoin or otherwise. //VOREStation Edit - Making sure custom species is obvious.
+var/global/list/playable_species = list(SPECIES_CUSTOM, SPECIES_HUMAN) // A list of ALL playable species, whitelisted, latejoin or otherwise. //VOREStation Edit - Making sure custom species is obvious.
var/list/mannequins_
diff --git a/code/_helpers/global_lists_vr.dm b/code/_helpers/global_lists_vr.dm
index f54d8d6b18..64393508af 100644
--- a/code/_helpers/global_lists_vr.dm
+++ b/code/_helpers/global_lists_vr.dm
@@ -11,6 +11,8 @@ var/global/list/positive_traits = list() // Positive custom species traits, inde
var/global/list/traits_costs = list() // Just path = cost list, saves time in char setup
var/global/list/all_traits = list() // All of 'em at once (same instances)
+var/global/list/custom_species_bases = list() // Species that can be used for a Custom Species icon base
+
//stores numeric player size options indexed by name
var/global/list/player_sizes_list = list(
"Macro" = RESIZE_HUGE,
@@ -195,4 +197,14 @@ var/global/list/edible_trash = list(/obj/item/trash,
if(0.1 to INFINITY)
positive_traits[path] = instance
- return 1 // Hooks must return 1
\ No newline at end of file
+ // Custom species icon bases
+ var/list/blacklisted_icons = list(SPECIES_CUSTOM,SPECIES_PROMETHEAN) //Just ones that won't work well.
+ for(var/species_name in playable_species)
+ if(species_name in blacklisted_icons)
+ continue
+ var/datum/species/S = all_species[species_name]
+ if(S.spawn_flags & SPECIES_IS_WHITELISTED)
+ continue
+ custom_species_bases += species_name
+
+ return 1 // Hooks must return 1
diff --git a/code/_macros_vr.dm b/code/_macros_vr.dm
index ca7bccedbe..284b6623fc 100644
--- a/code/_macros_vr.dm
+++ b/code/_macros_vr.dm
@@ -1,3 +1,4 @@
-#define isbelly(A) istype(A, /obj/belly)
-#define isstorage(A) istype(A, /obj/item/weapon/storage)
-#define isitem(A) istype(A, /obj/item)
+#define isbelly(A) istype(A, /obj/belly)
+#define isstorage(A) istype(A, /obj/item/weapon/storage)
+#define isitem(A) istype(A, /obj/item)
+#define isTaurTail(A) istype(A, /datum/sprite_accessory/tail/taur)
diff --git a/code/_onclick/hud/other_mobs.dm b/code/_onclick/hud/other_mobs.dm
index 325a4a4b7e..f1b9165a26 100644
--- a/code/_onclick/hud/other_mobs.dm
+++ b/code/_onclick/hud/other_mobs.dm
@@ -93,9 +93,12 @@
return
-/mob/living/simple_animal/construct/instantiate_hud(var/datum/hud/HUD)
- HUD.construct_hud()
+/mob/living/simple_animal/construct/instantiate_hud(var/datum/hud/HUD)
+ ..(HUD)
+
+// HUD.construct_hud() //Archaic.
+/*
/datum/hud/proc/construct_hud()
var/constructtype
@@ -109,30 +112,28 @@
constructtype = "harvester"
if(constructtype)
- mymob.fire = new /obj/screen()
+
mymob.fire.icon = 'icons/mob/screen1_construct.dmi'
mymob.fire.icon_state = "fire0"
mymob.fire.name = "fire"
mymob.fire.screen_loc = ui_construct_fire
- mymob.healths = new /obj/screen()
mymob.healths.icon = 'icons/mob/screen1_construct.dmi'
mymob.healths.icon_state = "[constructtype]_health0"
mymob.healths.name = "health"
mymob.healths.screen_loc = ui_construct_health
- mymob.pullin = new /obj/screen()
mymob.pullin.icon = 'icons/mob/screen1_construct.dmi'
mymob.pullin.icon_state = "pull0"
mymob.pullin.name = "pull"
mymob.pullin.screen_loc = ui_construct_pull
- mymob.zone_sel = new /obj/screen/zone_sel()
+
mymob.zone_sel.icon = 'icons/mob/screen1_construct.dmi'
mymob.zone_sel.overlays.len = 0
mymob.zone_sel.overlays += image('icons/mob/zone_sel.dmi', "[mymob.zone_sel.selecting]")
- mymob.purged = new /obj/screen()
+
mymob.purged.icon = 'icons/mob/screen1_construct.dmi'
mymob.purged.icon_state = "purge0"
mymob.purged.name = "purged"
@@ -142,3 +143,4 @@
mymob.client.screen += list(mymob.fire, mymob.healths, mymob.pullin, mymob.zone_sel, mymob.purged)
mymob.client.screen += mymob.client.void
+*/
diff --git a/code/_onclick/other_mobs.dm b/code/_onclick/other_mobs.dm
index e2a4c28c6b..729330ddaa 100644
--- a/code/_onclick/other_mobs.dm
+++ b/code/_onclick/other_mobs.dm
@@ -92,9 +92,11 @@
if(spattack_min_range <= 1)
SpecialAtkTarget()
+
else if(melee_damage_upper == 0 && istype(A,/mob/living))
custom_emote(1,"[pick(friendly)] [A]!")
+
else
DoPunch(A)
diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm
index 3576c66dfe..d2f99a347b 100644
--- a/code/datums/helper_datums/teleport.dm
+++ b/code/datums/helper_datums/teleport.dm
@@ -1,5 +1,5 @@
//wrapper
-/proc/do_teleport(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null)
+/proc/do_teleport(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null, local=TRUE) //VOREStation Edit
new /datum/teleport/instant/science(arglist(args))
return
@@ -12,15 +12,16 @@
var/soundin //soundfile to play before teleportation
var/soundout //soundfile to play after teleportation
var/force_teleport = 1 //if false, teleport will use Move() proc (dense objects will prevent teleportation)
+ var/local = TRUE //VOREStation Add - If false, can teleport from/to any z-level
-/datum/teleport/New(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null)
+/datum/teleport/New(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null, local=TRUE) //VOREStation Edit
..()
if(!initTeleport(arglist(args)))
return 0
return 1
-/datum/teleport/proc/initTeleport(ateleatom,adestination,aprecision,afteleport,aeffectin,aeffectout,asoundin,asoundout)
+/datum/teleport/proc/initTeleport(ateleatom,adestination,aprecision,afteleport,aeffectin,aeffectout,asoundin,asoundout,local) //VOREStation Edit
if(!setTeleatom(ateleatom))
return 0
if(!setDestination(adestination))
@@ -30,6 +31,7 @@
setEffects(aeffectin,aeffectout)
setForceTeleport(afteleport)
setSounds(asoundin,asoundout)
+ src.local = local // VOREStation Add
return 1
//must succeed
@@ -186,7 +188,7 @@
return 0
*/ //VOREStation Removal End
//VOREStation Edit Start
- if(destination.z in using_map.player_levels)
+ if(!local || (destination.z in using_map.player_levels)) //VOREStation Edit
return 1
if(istype(teleatom, /mob/living))
to_chat(teleatom, "The portal refuses to carry you that far away!")
diff --git a/code/datums/supplypacks/recreation_vr.dm b/code/datums/supplypacks/recreation_vr.dm
index 8f0d775797..cf12b4815b 100644
--- a/code/datums/supplypacks/recreation_vr.dm
+++ b/code/datums/supplypacks/recreation_vr.dm
@@ -18,7 +18,7 @@
/obj/item/stack/cable_coil/random,
/obj/item/clothing/accessory/collar/shock,
/obj/item/clothing/suit/straight_jacket,
- /obj/item/weapon/handcuffs/legcuffs,
+ /obj/item/weapon/handcuffs/legcuffs/fuzzy,
/obj/item/weapon/melee/fluff/holochain/mass,
/obj/item/weapon/material/twohanded/fluff/riding_crop,
/obj/item/clothing/under/fluff/latexmaid
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 5311d642a2..48ffacb335 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -18,6 +18,7 @@
var/icon_scale = 1 // Used to scale icons up or down in update_transform().
var/old_x = 0
var/old_y = 0
+ var/datum/riding/riding_datum //VOREStation Add - Moved from /obj/vehicle
/atom/movable/Destroy()
. = ..()
@@ -37,6 +38,7 @@
if (pulledby.pulling == src)
pulledby.pulling = null
pulledby = null
+ qdel_null(riding_datum) //VOREStation Add
/atom/movable/Bump(var/atom/A, yes)
if(src.throwing)
diff --git a/code/game/atoms_movable_vr.dm b/code/game/atoms_movable_vr.dm
index d529122029..0cc6d144be 100644
--- a/code/game/atoms_movable_vr.dm
+++ b/code/game/atoms_movable_vr.dm
@@ -1,2 +1,12 @@
/atom/movable/proc/Bump_vr(var/atom/A, yes)
return
+
+/atom/movable/set_dir(newdir)
+ . = ..(newdir)
+ if(riding_datum)
+ riding_datum.handle_vehicle_offsets()
+
+/atom/movable/relaymove(mob/user, direction)
+ . = ..()
+ if(riding_datum)
+ riding_datum.handle_ride(user, direction)
diff --git a/code/game/gamemodes/cult/construct_spells.dm b/code/game/gamemodes/cult/construct_spells.dm
new file mode 100644
index 0000000000..f8ff57fc35
--- /dev/null
+++ b/code/game/gamemodes/cult/construct_spells.dm
@@ -0,0 +1,698 @@
+//cast_method flags, needs to be up to date with Technomancer's. They were, for some reason, not working outside it.
+#define CAST_USE 1 // Clicking the spell in your hand.
+#define CAST_MELEE 2 // Clicking an atom in melee range.
+#define CAST_RANGED 4 // Clicking an atom beyond melee range.
+#define CAST_THROW 8 // Throwing the spell and hitting an atom.
+#define CAST_COMBINE 16 // Clicking another spell with this spell.
+#define CAST_INNATE 32 // Activates upon verb usage, used for mobs without hands.
+
+//Aspects
+#define ASPECT_FIRE "fire" //Damage over time and raising body-temp. Firesuits protect from this.
+#define ASPECT_FROST "frost" //Slows down the affected, also involves imbedding with icicles. Winter coats protect from this.
+#define ASPECT_SHOCK "shock" //Energy-expensive, usually stuns. Insulated armor protects from this.
+#define ASPECT_AIR "air" //Mostly involves manipulation of atmos, useless in a vacuum. Magboots protect from this.
+#define ASPECT_FORCE "force" //Manipulates gravity to push things away or towards a location.
+#define ASPECT_TELE "tele" //Teleportation of self, other objects, or other people.
+#define ASPECT_DARK "dark" //Makes all those photons vanish using magic-- WITH SCIENCE. Used for sneaky stuff.
+#define ASPECT_LIGHT "light" //The opposite of dark, usually blinds, makes holo-illusions, or makes laser lightshows.
+#define ASPECT_BIOMED "biomed" //Mainly concerned with healing and restoration.
+#define ASPECT_EMP "emp" //Unused now.
+#define ASPECT_UNSTABLE "unstable" //Heavily RNG-based, causes instability to the victim.
+#define ASPECT_CHROMATIC "chromatic" //Used to combine with other spells.
+#define ASPECT_UNHOLY "unholy" //Involves the dead, blood, and most things against divine beings.
+
+//////////////////////////////Construct Spells/////////////////////////
+
+proc/findNullRod(var/atom/target)
+ if(istype(target,/obj/item/weapon/nullrod))
+ return 1
+ else if(target.contents)
+ for(var/atom/A in target.contents)
+ if(findNullRod(A))
+ return 1
+ return 0
+
+/spell/aoe_turf/conjure/construct
+ name = "Artificer"
+ desc = "This spell conjures a construct which may be controlled by Shades"
+
+ school = "conjuration"
+ charge_max = 600
+ spell_flags = 0
+ invocation = "none"
+ invocation_type = SpI_NONE
+ range = 0
+
+ summon_type = list(/obj/structure/constructshell)
+
+ hud_state = "artificer"
+
+/spell/aoe_turf/conjure/construct/lesser
+ charge_max = 1800
+ summon_type = list(/obj/structure/constructshell/cult)
+ hud_state = "const_shell"
+ override_base = "const"
+
+/spell/aoe_turf/conjure/floor
+ name = "Floor Construction"
+ desc = "This spell constructs a cult floor"
+
+ charge_max = 20
+ spell_flags = Z2NOCAST | CONSTRUCT_CHECK
+ invocation = "none"
+ invocation_type = SpI_NONE
+ range = 0
+ summon_type = list(/turf/simulated/floor/cult)
+
+ hud_state = "const_floor"
+
+/spell/aoe_turf/conjure/floor/conjure_animation(var/atom/movable/overlay/animation, var/turf/target)
+ animation.icon_state = "cultfloor"
+ flick("cultfloor",animation)
+ spawn(10)
+ qdel(animation)
+
+/spell/aoe_turf/conjure/wall
+ name = "Lesser Construction"
+ desc = "This spell constructs a cult wall"
+
+ charge_max = 100
+ spell_flags = Z2NOCAST | CONSTRUCT_CHECK
+ invocation = "none"
+ invocation_type = SpI_NONE
+ range = 0
+ summon_type = list(/turf/simulated/wall/cult)
+
+ hud_state = "const_wall"
+
+/spell/aoe_turf/conjure/wall/conjure_animation(var/atom/movable/overlay/animation, var/turf/target)
+ animation.icon_state = "cultwall"
+ flick("cultwall",animation)
+ spawn(10)
+ qdel(animation)
+
+/spell/aoe_turf/conjure/wall/reinforced
+ name = "Greater Construction"
+ desc = "This spell constructs a reinforced metal wall"
+
+ charge_max = 300
+ spell_flags = Z2NOCAST
+ invocation = "none"
+ invocation_type = SpI_NONE
+ range = 0
+ cast_delay = 50
+
+ summon_type = list(/turf/simulated/wall/r_wall)
+
+/spell/aoe_turf/conjure/soulstone
+ name = "Summon Soulstone"
+ desc = "This spell reaches into Nar-Sie's realm, summoning one of the legendary fragments across time and space"
+
+ charge_max = 3000
+ spell_flags = 0
+ invocation = "none"
+ invocation_type = SpI_NONE
+ range = 0
+
+ summon_type = list(/obj/item/device/soulstone)
+
+ hud_state = "const_stone"
+ override_base = "const"
+
+/spell/aoe_turf/conjure/pylon
+ name = "Red Pylon"
+ desc = "This spell conjures a fragile crystal from Nar-Sie's realm. Makes for a convenient light source."
+
+ charge_max = 200
+ spell_flags = CONSTRUCT_CHECK
+ invocation = "none"
+ invocation_type = SpI_NONE
+ range = 0
+
+ summon_type = list(/obj/structure/cult/pylon)
+
+ hud_state = "const_pylon"
+
+/spell/aoe_turf/conjure/pylon/cast(list/targets)
+ ..()
+ var/turf/spawn_place = pick(targets)
+ for(var/obj/structure/cult/pylon/P in spawn_place.contents)
+ if(P.isbroken)
+ P.repair(usr)
+ continue
+ return
+
+/spell/aoe_turf/conjure/door
+ name = "Stone Door"
+ desc = "This spell conjures a massive stone door."
+
+ charge_max = 100
+ spell_flags = CONSTRUCT_CHECK
+ invocation = "none"
+ invocation_type = SpI_NONE
+ range = 0
+
+ summon_type = list(/obj/structure/simple_door/cult)
+
+ hud_state = "const_door"
+
+/spell/aoe_turf/conjure/grille
+ name = "Arcane Grille"
+ desc = "This spell conjures an airtight grille."
+
+ charge_max = 100
+ spell_flags = CONSTRUCT_CHECK
+ invocation = "none"
+ invocation_type = SpI_NONE
+ range = 0
+
+ summon_type = list(/obj/structure/grille/cult)
+
+ hud_state = "const_grille"
+
+/spell/aoe_turf/conjure/forcewall/lesser
+ name = "Shield"
+ desc = "Allows you to pull up a shield to protect yourself and allies from incoming threats"
+
+ charge_max = 300
+ spell_flags = 0
+ invocation = "none"
+ invocation_type = SpI_NONE
+ range = 0
+ summon_type = list(/obj/effect/forcefield/cult)
+ duration = 200
+
+ hud_state = "const_juggwall"
+
+//Code for the Juggernaut construct's forcefield, that seemed like a good place to put it.
+/obj/effect/forcefield/cult
+ desc = "That eerie looking obstacle seems to have been pulled from another dimension through sheer force"
+ name = "Juggerwall"
+ icon = 'icons/effects/effects.dmi'
+ icon_state = "m_shield_cult"
+ light_color = "#B40000"
+ light_range = 2
+
+/obj/effect/forcefield/cult/cultify()
+ return
+
+/spell/aoe_turf/knock/harvester
+ name = "Force Doors"
+ desc = "Mortal portals are no match for your occult might."
+
+ spell_flags = CONSTRUCT_CHECK
+
+ charge_max = 100
+ invocation = ""
+ invocation_type = "silent"
+ range = 4
+
+ hud_state = "const_knock"
+
+/spell/aoe_turf/knock/harvester/cast(list/targets)
+/* for(var/turf/T in targets) //Disintigrating doors is bad, okay.
+ for(var/obj/machinery/door/door in T.contents)
+ spawn door.cultify()
+ return */
+ for(var/turf/T in targets)
+ for(var/obj/machinery/door/door in T.contents)
+ spawn(1)
+ if(istype(door,/obj/machinery/door/airlock))
+ var/obj/machinery/door/airlock/AL = door
+ AL.locked = 0 //The spirits of the damned care not for your locks.
+ AL.welded = 0 //Or your welding tools.
+ else if(istype(door, /obj/machinery/door/firedoor))
+ var/obj/machinery/door/firedoor/FD = door
+ FD.blocked = 0
+ door.open(1)
+ return
+
+/*
+ *
+ * Self-targeting spells. Modifiers, auras, instants, etc.
+ *
+ */
+
+/spell/targeted/ethereal_jaunt/shift
+ name = "Phase Shift"
+ desc = "This spell allows you to pass through walls"
+
+ charge_max = 200
+ spell_flags = Z2NOCAST | INCLUDEUSER | CONSTRUCT_CHECK
+ invocation_type = SpI_NONE
+ range = -1
+ duration = 50 //in deciseconds
+
+ hud_state = "const_shift"
+
+/spell/targeted/ethereal_jaunt/shift/jaunt_disappear(var/atom/movable/overlay/animation, var/mob/living/target)
+ animation.icon_state = "phase_shift"
+ animation.dir = target.dir
+ flick("phase_shift",animation)
+
+/spell/targeted/ethereal_jaunt/shift/jaunt_reappear(var/atom/movable/overlay/animation, var/mob/living/target)
+ animation.icon_state = "phase_shift2"
+ animation.dir = target.dir
+ flick("phase_shift2",animation)
+
+/spell/targeted/ethereal_jaunt/shift/jaunt_steam(var/mobloc)
+ return
+
+/*
+ * Harvest has been disabled due to the lack of Nar'Sie. Here for posterity / future rework.
+ */
+
+/*
+/spell/targeted/harvest
+ name = "Harvest"
+ desc = "Back to where I come from, and you're coming with me."
+
+ school = "transmutation"
+ charge_max = 200
+ spell_flags = Z2NOCAST | CONSTRUCT_CHECK | INCLUDEUSER
+ invocation = ""
+ invocation_type = SpI_NONE
+ range = 0
+ max_targets = 0
+
+ overlay = 1
+ overlay_icon = 'icons/effects/effects.dmi'
+ overlay_icon_state = "rune_teleport"
+ overlay_lifespan = 0
+
+ hud_state = "const_harvest"
+
+/spell/targeted/harvest/cast(list/targets, mob/user)//because harvest is already a proc
+ ..()
+
+ var/destination = null
+ for(var/obj/singularity/narsie/large/N in narsie_list)
+ destination = N.loc
+ break
+ if(destination)
+ var/prey = 0
+ for(var/mob/living/M in targets)
+ if(!findNullRod(M))
+ M.forceMove(destination)
+ if(M != user)
+ prey = 1
+ user << "You warp back to Nar-Sie[prey ? " along with your prey":""]."
+ else
+ user << "...something's wrong!"//There shouldn't be an instance of Harvesters when Nar-Sie isn't in the world.
+*/
+
+/spell/targeted/fortify
+ name = "Fortify Shell"
+ desc = "Emit a field of energy around your shell to reduce incoming damage incredibly, while decreasing your mobility."
+
+ range = -1
+ school = "evocation"
+ charge_type = Sp_RECHARGE
+ invocation_type = SpI_NONE
+
+ spell_flags = CONSTRUCT_CHECK | INCLUDEUSER
+
+ hud_state = "const_fortify"
+ smoke_amt = 0
+
+ charge_max = 600
+
+/spell/targeted/fortify/cast(list/targets, mob/living/user)
+ if(findNullRod(user) || user.has_modifier_of_type(/datum/modifier/fortify))
+ charge_counter = 400
+ return
+ user.add_modifier(/datum/modifier/fortify, 1 MINUTES)
+
+/spell/targeted/occult_repair_aura
+ name = "Repair Aura"
+ desc = "Emit a field of energy around your shell to repair nearby constructs at range."
+
+ range = -1
+ school = "evocation"
+ charge_type = Sp_RECHARGE
+ invocation_type = SpI_NONE
+
+ spell_flags = CONSTRUCT_CHECK | INCLUDEUSER
+
+ hud_state = "const_repairaura"
+ smoke_amt = 0
+
+ charge_max = 600
+
+/spell/targeted/occult_repair_aura/cast(list/targets, mob/living/user)
+ if(findNullRod(user) || user.has_modifier_of_type(/datum/modifier/repair_aura))
+ charge_counter = 300
+ return
+ user.add_modifier(/datum/modifier/repair_aura, 30 SECONDS)
+
+/spell/targeted/ambush_mode
+ name = "Toggle Ambush"
+ desc = "Phase yourself mostly out of this reality, minimizing your combat ability, but allowing for employance of ambush tactics."
+
+ range = -1
+ school = "evocation"
+ charge_type = Sp_RECHARGE
+ invocation_type = SpI_NONE
+
+ spell_flags = CONSTRUCT_CHECK | INCLUDEUSER
+
+ hud_state = "const_ambush"
+ smoke_amt = 0
+
+ charge_max = 100
+
+/spell/targeted/ambush_mode/cast(list/targets, mob/living/user)
+ if(findNullRod(user))
+ charge_counter = 50
+ return
+ if(user.has_modifier_of_type(/datum/modifier/ambush))
+ user.remove_modifiers_of_type(/datum/modifier/ambush)
+ return
+ user.add_modifier(/datum/modifier/ambush, 0)
+
+/*
+ *
+ * These are the spells that place spell-objects into the construct's hands akin to technomancers.
+ *
+ */
+
+/spell/targeted/construct_advanced
+ name = "Base Construct Spell"
+ desc = "If you see this, please tell a developer!"
+
+ range = -1
+ school = "evocation"
+ charge_type = Sp_RECHARGE
+ invocation_type = SpI_NONE
+
+ spell_flags = CONSTRUCT_CHECK | INCLUDEUSER
+
+ hud_state = "const_rune"
+ smoke_amt = 0
+
+ charge_max = 10
+
+ var/obj/item/weapon/spell/construct/spell_obj = null //This is the var that determines what Technomancer-style spell is put into their hands.
+
+/spell/targeted/construct_advanced/cast(list/targets, mob/living/user)
+ if(!findNullRod(user))
+ user.place_spell_in_hand(spell_obj)
+
+/spell/targeted/construct_advanced/inversion_beam
+ name = "Inversion Beam"
+ desc = "Fire a searing beam of darkness at your foes."
+
+ hud_state = "const_beam"
+ spell_obj = /obj/item/weapon/spell/construct/projectile/inverted_beam
+
+/spell/targeted/construct_advanced/mend_acolyte
+ name = "Mend Acolyte"
+ desc = "Mend a target acolyte or construct over time."
+
+ charge_max = 100
+
+ hud_state = "const_mend"
+ spell_obj = /obj/item/weapon/spell/construct/mend_occult
+
+/spell/targeted/construct_advanced/agonizing_sphere
+ name = "Sphere of Agony"
+ desc = "Rend a portal into a plane of naught but pain at the target location."
+
+ charge_max = 100
+
+ hud_state = "const_harvest"
+ spell_obj = /obj/item/weapon/spell/construct/spawner/agonizing_sphere
+
+/spell/targeted/construct_advanced/slam
+ name = "Slam"
+ desc = "Empower your FIST."
+
+ charge_max = 300
+
+ hud_state = "const_fist"
+ spell_obj = /obj/item/weapon/spell/construct/slam
+
+/*
+ *
+ * These are the spell objects that go into the construct's hands.
+ *
+ */
+
+/*
+ * Base advanced construct spell types.
+ */
+
+/obj/item/weapon/spell/construct //Energy costs are in units of blood, in the event a cultist gets one of these.
+ name = "unholy energy"
+ desc = "Your hands appear to be screaming. This is a debug text, you should probably tell a developer!"
+ icon = 'icons/obj/spells.dmi'
+ icon_state = "generic"
+ item_icons = list(
+ slot_l_hand_str = 'icons/mob/items/lefthand_spells.dmi',
+ slot_r_hand_str = 'icons/mob/items/righthand_spells.dmi',
+ )
+ throwforce = 0
+ force = 0
+ show_examine = FALSE
+ owner = null
+ core = null
+ cast_methods = null // Controls how the spell is casted.
+ aspect = ASPECT_UNHOLY // Used for combining spells. Pretty much any cult spell is unholy.
+ toggled = 0 // Mainly used for overlays.
+ cooldown = 0 // If set, will add a cooldown overlay and adjust click delay. Must be a multiple of 5 for overlays.
+ cast_sound = null // Sound file played when this is used.
+ var/last_castcheck = null // The last time this spell was cast.
+
+/obj/item/weapon/spell/construct/New()
+ //..() //This kills the spell, because super on this calls the default spell's New, which checks for a core. Can't have that.
+ if(isliving(loc))
+ owner = loc
+ if(!owner)
+ qdel(src)
+ update_icon()
+
+/obj/item/weapon/spell/construct/adjust_instability(var/amount) //The only drawback to the boons of the geometer is the use of a mortal's blood as fuel. Constructs have already paid that price long ago.
+ return
+
+/obj/item/weapon/spell/construct/run_checks()
+ if(owner)
+ if((iscultist(owner) || istype(owner, /mob/living/simple_animal/construct)) && (world.time >= (last_castcheck + cooldown))) //Are they a cultist or a construct, and has the cooldown time passed?
+ last_castcheck = world.time
+ return 1
+ return 0
+
+/obj/item/weapon/spell/construct/pay_energy(var/amount)
+ if(owner)
+ if(istype(owner, /mob/living/simple_animal/construct))
+ return 1
+ if(iscultist(owner) && pay_blood(amount))
+ return 1
+ return 0
+
+/obj/item/weapon/spell/construct/proc/pay_blood(var/amount) //If, for some reason, this is put into the hands of a cultist, by a talisnam or whatever.
+ if(ishuman(owner))
+ var/mob/living/carbon/human/H = owner
+ if(!H.should_have_organ(O_HEART))
+ return 1
+ if(H.vessel.remove_reagent("blood", amount))
+ return 1
+ return 0
+
+/obj/item/weapon/spell/construct/afterattack(atom/target, mob/user, proximity_flag, click_parameters) //Not overriding it caused runtimes, because cooldown checked for core.
+ if(!run_checks())
+ return
+ if(!proximity_flag)
+ if(cast_methods & CAST_RANGED)
+ on_ranged_cast(target, user)
+ else
+ if(istype(target, /obj/item/weapon/spell))
+ var/obj/item/weapon/spell/spell = target
+ if(spell.cast_methods & CAST_COMBINE)
+ spell.on_combine_cast(src, user)
+ return
+ if(cast_methods & CAST_MELEE)
+ on_melee_cast(target, user)
+ else if(cast_methods & CAST_RANGED) //Try to use a ranged method if a melee one doesn't exist.
+ on_ranged_cast(target, user)
+ if(cooldown)
+ var/effective_cooldown = round(cooldown, 5)
+ user.setClickCooldown(effective_cooldown)
+ flick("cooldown_[effective_cooldown]",src)
+
+/obj/item/weapon/spell/construct/projectile //This makes me angry, but we need the template, and we can't use it because special check overrides on the base.
+ name = "construct projectile template"
+ icon_state = "generic"
+ desc = "This is a generic template that shoots projectiles. If you can read this, the game broke!"
+ cast_methods = CAST_RANGED
+ var/obj/item/projectile/spell_projectile = null
+ var/pre_shot_delay = 0
+ var/fire_sound = null
+ var/energy_cost_per_shot = 5
+
+/obj/item/weapon/spell/construct/projectile/on_ranged_cast(atom/hit_atom, mob/living/user)
+ if(set_up(hit_atom, user))
+ var/obj/item/projectile/new_projectile = make_projectile(spell_projectile, user)
+ new_projectile.launch(hit_atom)
+ log_and_message_admins("has casted [src] at \the [hit_atom].")
+ if(fire_sound)
+ playsound(get_turf(src), fire_sound, 75, 1)
+ return 1
+ return 0
+
+/obj/item/weapon/spell/construct/projectile/proc/make_projectile(obj/item/projectile/projectile_type, mob/living/user)
+ var/obj/item/projectile/P = new projectile_type(get_turf(user))
+ return P
+
+/obj/item/weapon/spell/construct/projectile/proc/set_up(atom/hit_atom, mob/living/user)
+ if(spell_projectile)
+ if(pay_energy(energy_cost_per_shot))
+ if(pre_shot_delay)
+ var/image/target_image = image(icon = 'icons/obj/spells.dmi', loc = get_turf(hit_atom), icon_state = "target")
+ user << target_image
+ user.Stun(pre_shot_delay / 10)
+ sleep(pre_shot_delay)
+ qdel(target_image)
+ if(owner)
+ return TRUE
+ return FALSE // We got dropped before the firing occured.
+ return TRUE // No delay, no need to check.
+ return FALSE
+
+/obj/item/weapon/spell/construct/spawner
+ name = "spawner template"
+ desc = "If you see me, someone messed up."
+ icon_state = "darkness"
+ cast_methods = CAST_RANGED
+ var/obj/effect/spawner_type = null
+
+/obj/item/weapon/spell/construct/spawner/on_ranged_cast(atom/hit_atom, mob/user)
+ var/turf/T = get_turf(hit_atom)
+ if(T)
+ new spawner_type(T)
+ to_chat(user, "You shift \the [src] onto \the [T].")
+ log_and_message_admins("has casted [src] at [T.x],[T.y],[T.z].")
+ qdel(src)
+
+//Harvester Laser.
+
+/obj/item/weapon/spell/construct/projectile/inverted_beam
+ name = "inversion beam"
+ icon_state = "generic"
+ desc = "Your manipulators fire searing beams of inverted light."
+ cast_methods = CAST_RANGED
+ spell_projectile = /obj/item/projectile/beam/inversion
+ pre_shot_delay = 0
+ cooldown = 5
+ fire_sound = 'sound/weapons/spiderlunge.ogg'
+
+/obj/item/projectile/beam/inversion
+ name = "inversion beam"
+ icon_state = "invert"
+ fire_sound = 'sound/weapons/spiderlunge.ogg'
+ damage = 15
+ damage_type = BURN
+ check_armour = "laser"
+ armor_penetration = 60
+ light_range = 2
+ light_power = -2
+ light_color = "#FFFFFF"
+
+ muzzle_type = /obj/effect/projectile/inversion/muzzle
+ tracer_type = /obj/effect/projectile/inversion/tracer
+ impact_type = /obj/effect/projectile/inversion/impact
+
+//Harvester Pain Orb
+
+/obj/item/weapon/spell/construct/spawner/agonizing_sphere
+ name = "sphere of agony"
+ desc = "Call forth a portal to a dimension of naught but pain at your target."
+
+ spawner_type = /obj/effect/temporary_effect/pulsar/agonizing_sphere
+
+/obj/item/weapon/spell/construct/spawner/agonizing_sphere/on_ranged_cast(atom/hit_atom, mob/user)
+ if(within_range(hit_atom) && pay_energy(10))
+ ..()
+
+/obj/item/weapon/spell/construct/spawner/agonizing_sphere/on_throw_cast(atom/hit_atom, mob/user)
+ pay_energy(5)
+ if(isliving(hit_atom))
+ var/mob/living/L = hit_atom
+ L.add_modifier(/datum/modifier/agonize, 10 SECONDS)
+
+/obj/effect/temporary_effect/pulsar/agonizing_sphere
+ name = "agonizing sphere"
+ desc = "A portal to some hellish place. Its screams wrack your body with pain.."
+ icon_state = "red_static_sphere"
+ time_to_die = null
+ light_range = 4
+ light_power = 5
+ light_color = "#FF0000"
+ pulses_remaining = 10
+
+/obj/effect/temporary_effect/pulsar/agonizing_sphere/pulse_loop()
+ while(pulses_remaining)
+ sleep(1 SECONDS)
+ spawn()
+ for(var/mob/living/L in view(4,src))
+ if(!iscultist(L) && !istype(L, /mob/living/simple_animal/construct))
+ L.add_modifier(/datum/modifier/agonize, 2 SECONDS)
+ if(L.isSynthetic())
+ to_chat(L, "Your chassis warps as the [src] pulses!")
+ L.adjustFireLoss(4)
+ pulses_remaining--
+ qdel(src)
+
+//Artificer Heal
+
+/obj/item/weapon/spell/construct/mend_occult
+ name = "mend acolyte"
+ desc = "Mend the wounds of a cultist, or construct, over time."
+ icon_state = "mend_wounds"
+ cast_methods = CAST_MELEE
+ aspect = ASPECT_UNHOLY
+ light_color = "#FF5C5C"
+ light_power = -2
+
+/obj/item/weapon/spell/construct/mend_occult/on_melee_cast(atom/hit_atom, mob/living/user, def_zone)
+ if(isliving(hit_atom))
+ var/mob/living/L = hit_atom
+ L.add_modifier(/datum/modifier/mend_occult, 150)
+ qdel(src)
+
+//Juggernaut + Behemoth Slam
+/obj/item/weapon/spell/construct/slam
+ name = "slam"
+ desc = "Empower your FIST, to send an opponent flying."
+ icon_state = "toggled_old"
+ cast_methods = CAST_MELEE
+ aspect = ASPECT_UNHOLY
+ light_color = "#FF5C5C"
+ light_power = -2
+ cooldown = 15
+
+/obj/item/weapon/spell/construct/slam/on_melee_cast(atom/hit_atom, mob/living/user, def_zone)
+ var/attack_message = "slams"
+ if(istype(user, /mob/living/simple_animal))
+ var/mob/living/simple_animal/S = user
+ attack_message = pick(S.attacktext)
+ if(isliving(hit_atom))
+ var/mob/living/L = hit_atom
+ L.visible_message("\The [user] [attack_message] \the [L], sending them flying!")
+ playsound(src, "punch", 50, 1)
+ L.Weaken(2)
+ L.adjustBruteLoss(rand(30, 50))
+ var/throwdir = get_dir(src, L)
+ L.throw_at(get_edge_target_turf(L, throwdir), 3, 1, src)
+ if(istype(hit_atom, /turf/simulated/wall))
+ var/turf/simulated/wall/W = hit_atom
+ user.visible_message("\The [user] rears its fist, preparing to hit \the [W]!")
+ var/windup = cooldown
+ if(W.reinf_material)
+ windup = cooldown * 2
+ if(do_after(user, windup))
+ W.visible_message("\The [user] [attack_message] \the [W], obliterating it!")
+ W.dismantle_wall(1)
+ else
+ user.visible_message("\The [user] lowers its fist.")
+ return
+ qdel(src)
\ No newline at end of file
diff --git a/code/game/gamemodes/cult/cult_items.dm b/code/game/gamemodes/cult/cult_items.dm
index cecb7b87c7..9d8eb8dc91 100644
--- a/code/game/gamemodes/cult/cult_items.dm
+++ b/code/game/gamemodes/cult/cult_items.dm
@@ -14,20 +14,21 @@
return
/obj/item/weapon/melee/cultblade/attack(mob/living/M, mob/living/user, var/target_zone)
- if(iscultist(user))
+ if(iscultist(user) && !istype(user, /mob/living/simple_animal/construct))
return ..()
var/zone = (user.hand ? "l_arm":"r_arm")
if(ishuman(user))
var/mob/living/carbon/human/H = user
var/obj/item/organ/external/affecting = H.get_organ(zone)
- user << "An unexplicable force rips through your [affecting.name], tearing the sword from your grasp!"
+ to_chat(user, "An inexplicable force rips through your [affecting.name], tearing the sword from your grasp!")
+ //random amount of damage between half of the blade's force and the full force of the blade.
+ user.apply_damage(rand(force/2, force), BRUTE, zone, 0, sharp=1, edge=1)
+ user.Weaken(5)
+ else if(!istype(user, /mob/living/simple_animal/construct))
+ to_chat(user, "An inexplicable force rips through you, tearing the sword from your grasp!")
else
- user << "An unexplicable force rips through you, tearing the sword from your grasp!"
-
- //random amount of damage between half of the blade's force and the full force of the blade.
- user.apply_damage(rand(force/2, force), BRUTE, zone, 0, sharp=1, edge=1)
- user.Weaken(5)
+ to_chat(user, "The blade hisses, forcing itself from your manipulators. \The [src] will only allow mortals to wield it against foes, not kin.")
user.drop_from_inventory(src, src.loc)
throw_at(get_edge_target_turf(src, pick(alldirs)), rand(1,3), throw_speed)
@@ -38,10 +39,11 @@
return 1
/obj/item/weapon/melee/cultblade/pickup(mob/living/user as mob)
- if(!iscultist(user))
- user << "An overwhelming feeling of dread comes over you as you pick up the cultist's sword. It would be wise to be rid of this blade quickly."
+ if(!iscultist(user) && !istype(user, /mob/living/simple_animal/construct))
+ to_chat(user, "An overwhelming feeling of dread comes over you as you pick up the cultist's sword. It would be wise to be rid of this blade quickly.")
user.make_dizzy(120)
-
+ if(istype(user, /mob/living/simple_animal/construct))
+ to_chat(user, "\The [src] hisses, as it is discontent with your acquisition of it. It would be wise to return it to a worthy mortal quickly.")
/obj/item/clothing/head/culthood
name = "cult hood"
diff --git a/code/game/gamemodes/endgame/supermatter_cascade/portal.dm b/code/game/gamemodes/endgame/supermatter_cascade/portal.dm
index 2c47b8e162..7f7310f996 100644
--- a/code/game/gamemodes/endgame/supermatter_cascade/portal.dm
+++ b/code/game/gamemodes/endgame/supermatter_cascade/portal.dm
@@ -38,13 +38,13 @@
var/mob/living/L = A
if(L.buckled && istype(L.buckled,/obj/structure/bed/))
var/turf/O = L.buckled
- do_teleport(O, pick(endgame_safespawns))
+ do_teleport(O, pick(endgame_safespawns), local = FALSE) //VOREStation Edit
L.loc = O.loc
else
- do_teleport(L, pick(endgame_safespawns)) //dead-on precision
+ do_teleport(L, pick(endgame_safespawns), local = FALSE) //dead-on precision //VOREStation Edit
else if (istype(A, /obj/mecha/))
- do_teleport(A, pick(endgame_safespawns)) //dead-on precision
+ do_teleport(A, pick(endgame_safespawns), local = FALSE) //dead-on precision //VOREStation Edit
else if (isturf(A))
var/turf/T = A
diff --git a/code/game/gamemodes/technomancer/spell_objs.dm b/code/game/gamemodes/technomancer/spell_objs.dm
index 2a917b4fd8..681d29da50 100644
--- a/code/game/gamemodes/technomancer/spell_objs.dm
+++ b/code/game/gamemodes/technomancer/spell_objs.dm
@@ -19,6 +19,7 @@
#define ASPECT_EMP "emp" //Unused now.
#define ASPECT_UNSTABLE "unstable" //Heavily RNG-based, causes instability to the victim.
#define ASPECT_CHROMATIC "chromatic" //Used to combine with other spells.
+#define ASPECT_UNHOLY "unholy" //Involves the dead, blood, and most things against divine beings.
/obj/item/weapon/spell
name = "glowing particles"
diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm
index 30955879f8..1496ca1a1f 100644
--- a/code/game/machinery/camera/camera.dm
+++ b/code/game/machinery/camera/camera.dm
@@ -142,6 +142,18 @@
add_hiddenprint(user)
destroy()
+/obj/machinery/camera/attack_generic(mob/user as mob)
+ if(isanimal(user))
+ var/mob/living/simple_animal/S = user
+ set_status(0)
+ S.do_attack_animation(src)
+ S.setClickCooldown(user.get_attack_speed())
+ visible_message("\The [user] [pick(S.attacktext)] \the [src]!")
+ playsound(src.loc, S.attack_sound, 100, 1)
+ add_hiddenprint(user)
+ destroy()
+ ..()
+
/obj/machinery/camera/attackby(obj/item/W as obj, mob/living/user as mob)
update_coverage()
// DECONSTRUCTION
diff --git a/code/game/machinery/doors/blast_door.dm b/code/game/machinery/doors/blast_door.dm
index 54b5513cce..58ce8b282c 100644
--- a/code/game/machinery/doors/blast_door.dm
+++ b/code/game/machinery/doors/blast_door.dm
@@ -7,6 +7,9 @@
// as they lack any ID scanning system, they just handle remote control signals. Subtypes have
// different icons, which are defined by set of variables. Subtypes are on bottom of this file.
+// UPDATE 06.04.2018
+// The emag thing wasn't working as intended, manually overwrote it.
+
/obj/machinery/door/blast
name = "Blast Door"
desc = "That looks like it doesn't open easily."
@@ -56,6 +59,10 @@
radiation_repository.resistance_cache.Remove(get_turf(src))
return
+// Has to be in here, comment at the top is older than the emag_act code on doors proper
+/obj/machinery/door/blast/emag_act()
+ return -1
+
// Proc: force_open()
// Parameters: None
// Description: Opens the door. No checks are done inside this proc.
@@ -197,6 +204,27 @@
return
..()
+// Proc: attack_generic()
+// Parameters: Attacking simple mob, incoming damage.
+// Description: Checks the power or integrity of the blast door, if either have failed, chekcs the damage to determine if the creature would be able to open the door by force. Otherwise, super.
+/obj/machinery/door/blast/attack_generic(var/mob/user, var/damage)
+ if(stat & (BROKEN|NOPOWER))
+ if(damage >= 10)
+ if(src.density)
+ visible_message("\The [user] starts forcing \the [src] open!")
+ if(do_after(user, 5 SECONDS, src))
+ visible_message("\The [user] forces \the [src] open!")
+ force_open(1)
+ else
+ visible_message("\The [user] starts forcing \the [src] closed!")
+ if(do_after(user, 2 SECONDS, src))
+ visible_message("\The [user] forces \the [src] closed!")
+ force_close(1)
+ else
+ visible_message("\The [user] strains fruitlessly to force \the [src] [density ? "open" : "closed"].")
+ return
+ ..()
+
// Proc: open()
// Parameters: None
// Description: Opens the door. Does necessary checks. Automatically closes if autoclose is true
diff --git a/code/game/machinery/doors/brigdoors.dm b/code/game/machinery/doors/brigdoors.dm
index 27ce595a77..e639571cb4 100644
--- a/code/game/machinery/doors/brigdoors.dm
+++ b/code/game/machinery/doors/brigdoors.dm
@@ -25,34 +25,39 @@
var/releasetime = 0 // when world.timeofday reaches it - release the prisoner
var/timing = 1 // boolean, true/1 timer is on, false/0 means it's not timing
var/picture_state // icon_state of alert picture, if not displaying text/numbers
- var/list/obj/machinery/targets = list()
+ var/list/obj/machinery/targets
var/timetoset = 0 // Used to set releasetime upon starting the timer
maptext_height = 26
maptext_width = 32
-/obj/machinery/door_timer/New()
+/obj/machinery/door_timer/initialize()
..()
+ //Doors need to go first, and can't rely on init order, so come back to me.
+ return INITIALIZE_HINT_LATELOAD
- spawn(20)
- for(var/obj/machinery/door/window/brigdoor/M in machines)
- if (M.id == src.id)
- targets += M
+/obj/machinery/door_timer/LateInitialize()
+ . = ..()
- for(var/obj/machinery/flasher/F in machines)
- if(F.id == src.id)
- targets += F
+ for(var/obj/machinery/door/window/brigdoor/M in machines)
+ if (M.id == src.id)
+ LAZYADD(targets,M)
- for(var/obj/structure/closet/secure_closet/brig/C in world)
- if(C.id == src.id)
- targets += C
+ for(var/obj/machinery/flasher/F in machines)
+ if(F.id == src.id)
+ LAZYADD(targets,F)
- if(targets.len==0)
- stat |= BROKEN
- update_icon()
- return
- return
+ for(var/obj/structure/closet/secure_closet/brig/C in world)
+ if(C.id == src.id)
+ LAZYADD(targets,C)
+ if(!LAZYLEN(targets))
+ stat |= BROKEN
+ update_icon()
+
+/obj/machinery/door_timer/Destroy()
+ LAZYCLEARLIST(targets)
+ return ..()
//Main door timer loop, if it's timing and time is >0 reduce time by 1.
// if it's less than 0, open door, reset timer
@@ -95,6 +100,7 @@
// Closes and locks doors, power check
/obj/machinery/door_timer/proc/timer_start()
if(stat & (NOPOWER|BROKEN)) return 0
+ if(!LAZYLEN(targets)) return 0
// Set releasetime
releasetime = world.timeofday + timetoset
@@ -115,6 +121,7 @@
// Opens and unlocks doors, power check
/obj/machinery/door_timer/proc/timer_end()
if(stat & (NOPOWER|BROKEN)) return 0
+ if(!LAZYLEN(targets)) return 0
// Reset releasetime
releasetime = 0
@@ -197,11 +204,12 @@
dat += "- - + +
"
// Mounted flash controls
- for(var/obj/machinery/flasher/F in targets)
- if(F.last_flash && (F.last_flash + 150) > world.time)
- dat += "
Flash Charging"
- else
- dat += "
Activate Flash"
+ if(LAZYLEN(targets))
+ for(var/obj/machinery/flasher/F in targets)
+ if(F.last_flash && (F.last_flash + 150) > world.time)
+ dat += "
Flash Charging"
+ else
+ dat += "
Activate Flash"
dat += "
Close"
dat += "