diff --git a/code/__defines/gamemode.dm b/code/__defines/gamemode.dm
index 94c9d41639a..680717a8e9a 100644
--- a/code/__defines/gamemode.dm
+++ b/code/__defines/gamemode.dm
@@ -33,31 +33,6 @@
#define DEFAULT_TELECRYSTAL_AMOUNT 130
#define IMPLANT_TELECRYSTAL_AMOUNT(x) (round(x * 0.49)) // If this cost is ever greater than half of DEFAULT_TELECRYSTAL_AMOUNT then it is possible to buy more TC than you spend
-// SPELL FLAGS
-#define Z2NOCAST BITFLAG(0) //if this is added, the spell can't be cast at centcomm
-#define INCLUDEUSER BITFLAG(1) //does the spell include the caster in its target selection?
-#define IGNOREDENSE BITFLAG(2) //are dense turfs ignored in selection?
-
-//End split flags
-#define CONSTRUCT_CHECK BITFLAG(12) //used by construct spells - checks for nullrods
-#define NO_BUTTON BITFLAG(13) //spell won't show up in the HUD with this
-
-//invocation
-#define SpI_SHOUT "shout"
-#define SpI_WHISPER "whisper"
-#define SpI_EMOTE "emote"
-#define SpI_NONE "none"
-
-//upgrading
-#define Sp_SPEED "speed"
-#define Sp_POWER "power"
-#define Sp_TOTAL "total"
-
-//casting costs
-#define Sp_RECHARGE "recharge"
-#define Sp_CHARGES "charges"
-#define Sp_HOLDVAR "holdervar"
-
//Voting-related
#define VOTE_PROCESS_ABORT 1
#define VOTE_PROCESS_COMPLETE 2
diff --git a/code/_helpers/time.dm b/code/_helpers/time.dm
index f53718da51b..90210911f1e 100644
--- a/code/_helpers/time.dm
+++ b/code/_helpers/time.dm
@@ -72,6 +72,35 @@ var/global/next_duration_update = 0
var/global/last_round_duration = 0
var/global/round_start_time = 0
+/proc/ticks2shortreadable(tick_time, separator = ":")
+ var/hours = round(tick_time / (1 HOUR))
+ var/minutes = round((tick_time % (1 HOUR)) / (1 MINUTE))
+ var/seconds = round((tick_time % (1 MINUTE)) / (1 SECOND))
+ var/out = list()
+
+ if(hours > 0)
+ out += "[hours]"
+
+ if(minutes > 0)
+ if(minutes < 10 && hours > 0)
+ out += "0[minutes]"
+ else
+ out += "[minutes]"
+ else if(hours > 0)
+ out += "00"
+
+ if(seconds > 0)
+ if(seconds < 10 && (minutes > 0 || hours > 0))
+ out += "0[seconds]"
+ else
+ out += "[seconds]"
+ else if(minutes > 0 || hours > 0)
+ out += "00"
+
+ if(length(out))
+ return jointext(out, separator)
+ return null
+
/proc/ticks2readable(tick_time)
var/hours = round(tick_time / (1 HOUR))
var/minutes = round((tick_time % (1 HOUR)) / (1 MINUTE))
diff --git a/code/_onclick/hud/screen/screen_abilities.dm b/code/_onclick/hud/screen/screen_abilities.dm
deleted file mode 100644
index 414458af058..00000000000
--- a/code/_onclick/hud/screen/screen_abilities.dm
+++ /dev/null
@@ -1,288 +0,0 @@
-/obj/screen/ability_master
- name = "Abilities"
- icon = 'icons/mob/screen/spells.dmi'
- icon_state = "grey_spell_ready"
- screen_loc = ui_ability_master
- requires_ui_style = FALSE
- var/list/obj/screen/ability/ability_objects = list()
- var/list/obj/screen/ability/spell_objects = list()
- var/showing = FALSE // If we're 'open' or not.
- var/const/abilities_per_row = 7
- var/open_state = "master_open" // What the button looks like when it's 'open', showing the other buttons.
- var/closed_state = "master_closed" // Button when it's 'closed', hiding everything else.
-
-/obj/screen/ability_master/Initialize(mapload, mob/_owner, ui_style, ui_color, ui_alpha, ui_cat)
- . = ..()
- if(. != INITIALIZE_HINT_QDEL)
- update_abilities(0, _owner)
-
-/obj/screen/ability_master/Destroy()
- // Get rid of the ability objects.
- remove_all_abilities()
- ability_objects.Cut()
- // After that, remove ourselves from the mob seeing us, so we can qdel cleanly.
- var/mob/owner = owner_ref?.resolve()
- if(istype(owner) && owner.ability_master == src)
- owner.ability_master = null
- return ..()
-
-/obj/screen/ability_master/handle_mouse_drop(atom/over, mob/user, params)
- if(showing)
- return FALSE
- . = ..()
-
-/obj/screen/ability_master/handle_click(mob/user, params)
- if(length(ability_objects)) // If we're empty for some reason.
- toggle_open()
-
-/obj/screen/ability_master/proc/toggle_open(var/forced_state = 0)
- var/mob/owner = owner_ref?.resolve()
- if(!istype(owner) || QDELETED(owner))
- return
- if(showing && (forced_state != 2)) // We are closing the ability master, hide the abilities.
- if(owner?.client)
- for(var/obj/screen/ability/O in ability_objects)
- owner.client.screen -= O
-// O.handle_icon_updates = 0
- showing = 0
- overlays.len = 0
- overlays.Add(closed_state)
- else if(forced_state != 1) // We're opening it, show the icons.
- open_ability_master()
- update_abilities(1)
- showing = 1
- overlays.len = 0
- overlays.Add(open_state)
- update_icon()
-
-/obj/screen/ability_master/proc/open_ability_master()
-
- var/client/owner_client
- var/mob/owner = owner_ref?.resolve()
- if(istype(owner) && !QDELETED(owner))
- owner_client = owner.client
-
- for(var/i = 1 to length(ability_objects))
- var/obj/screen/ability/A = ability_objects[i]
- var/row = round(i/abilities_per_row)
- A.screen_loc = "RIGHT-[(i-(row*abilities_per_row))+2]:16,TOP-[row+1]:16"
- if(owner_client)
- owner_client.screen += A
-
-/obj/screen/ability_master/proc/update_abilities(forced = 0, mob/user)
- update_icon()
- if(user && user.client)
- if(!(src in user.client.screen))
- user.client.screen += src
- var/i = 1
- for(var/obj/screen/ability/ability in ability_objects)
- ability.update_icon(forced)
- ability.maptext = "[i]" // Slot number
- i++
-
-/obj/screen/ability_master/on_update_icon()
- if(ability_objects.len)
- set_invisibility(INVISIBILITY_NONE)
- else
- set_invisibility(INVISIBILITY_ABSTRACT)
-
-/obj/screen/ability_master/proc/add_ability(var/name_given)
- if(!name_given)
- return
- var/obj/screen/ability/new_button = new /obj/screen/ability
- new_button.ability_master = src
- new_button.SetName(name_given)
- new_button.ability_icon_state = name_given
- new_button.update_icon(1)
- ability_objects.Add(new_button)
- var/mob/living/owner = owner_ref?.resolve()
- if(istype(owner) && !QDELETED(owner) && owner.client)
- toggle_open(2) //forces the icons to refresh on screen
-
-/obj/screen/ability_master/proc/remove_ability(var/obj/screen/ability/ability)
- if(!ability)
- return
- ability_objects.Remove(ability)
- if(istype(ability,/obj/screen/ability/spell))
- spell_objects.Remove(ability)
- qdel(ability)
-
-
- if(ability_objects.len)
- toggle_open(showing + 1)
- update_icon()
-// else
-// qdel(src)
-
-/obj/screen/ability_master/proc/remove_all_abilities()
- for(var/obj/screen/ability/A in ability_objects)
- remove_ability(A)
-
-/obj/screen/ability_master/proc/get_ability_by_name(name_to_search)
- for(var/obj/screen/ability/A in ability_objects)
- if(A.name == name_to_search)
- return A
- return null
-
-/obj/screen/ability_master/proc/get_ability_by_instance(var/obj/instance/)
- for(var/obj/screen/ability/obj_based/O in ability_objects)
- if(O.object == instance)
- return O
- return null
-
-/obj/screen/ability_master/proc/get_ability_by_spell(var/spell/s)
- for(var/screen in spell_objects)
- var/obj/screen/ability/spell/S = screen
- if(S.spell == s)
- return S
- return null
-
-/obj/screen/ability_master/proc/synch_spells_to_mind(var/datum/mind/M)
- if(!M)
- return
- LAZYINITLIST(M.learned_spells)
- for(var/obj/screen/ability/spell/screen in spell_objects)
- var/spell/S = screen.spell
- M.learned_spells |= S
-
-///////////ACTUAL ABILITIES////////////
-//This is what you click to do things//
-///////////////////////////////////////
-/obj/screen/ability
- icon = 'icons/mob/screen/spells.dmi'
- icon_state = "grey_spell_base"
- maptext_x = 3
- requires_owner = FALSE
- requires_ui_style = FALSE
- var/background_base_state = "grey"
- var/ability_icon_state = null
- var/obj/screen/ability_master/ability_master
-
-/obj/screen/ability/Destroy()
- if(ability_master)
- ability_master.ability_objects -= src
- var/mob/owner = ability_master.owner_ref?.resolve()
- if(istype(owner) && owner.client)
- owner.client.screen -= src
- if(ability_master && !ability_master.ability_objects.len)
- ability_master.update_icon()
-// qdel(ability_master)
- ability_master = null
- return ..()
-
-/obj/screen/ability/on_update_icon()
- overlays.Cut()
- icon_state = "[background_base_state]_spell_base"
-
- overlays += ability_icon_state
-
-/obj/screen/ability/handle_click(mob/user, params)
- activate()
-
-// Makes the ability be triggered. The subclasses of this are responsible for carrying it out in whatever way it needs to.
-/obj/screen/ability/proc/activate()
- to_world("[src] had activate() called.")
- return
-
-/////////Obj Abilities////////
-//Buttons to trigger objects//
-//////////////////////////////
-
-/obj/screen/ability/obj_based
- var/obj/object = null
-
-/obj/screen/ability/obj_based/activate()
- if(object)
- object.Click()
-
-/obj/screen/ability/spell
- var/spell/spell
- var/spell_base
- var/last_charge = 0
- var/icon/last_charged_icon
-
-/obj/screen/ability/spell/Destroy()
- if(spell)
- spell.connected_button = null
- spell = null
- return ..()
-
-/obj/screen/ability_master/proc/add_spell(var/spell/spell)
- if(!spell) return
-
- if(spell.spell_flags & NO_BUTTON) //no button to add if we don't get one
- return
-
- if(get_ability_by_spell(spell))
- return
-
- var/obj/screen/ability/spell/A = new(null)
- A.ability_master = src
- A.spell = spell
- A.SetName(spell.name)
-
- if(!spell.override_base) //if it's not set, we do basic checks
- A.spell_base = "const" //construct spells
- else
- A.spell_base = spell.override_base
- A.update_charge(1)
- spell_objects.Add(A)
- ability_objects.Add(A)
- var/mob/owner = owner_ref?.resolve()
- if(istype(owner) && !QDELETED(owner) && owner.client)
- toggle_open(2) //forces the icons to refresh on screen
-
-/obj/screen/ability_master/proc/update_spells(var/forced = 0)
- for(var/obj/screen/ability/spell/spell in spell_objects)
- spell.update_charge(forced)
-
-/obj/screen/ability/spell/proc/update_charge(var/forced_update = 0)
- if(!spell)
- qdel(src)
- return
-
- if(last_charge == spell.charge_counter && !forced_update)
- return //nothing to see here
-
- overlays -= spell.hud_state
-
- if(spell.charge_type == Sp_RECHARGE || spell.charge_type == Sp_CHARGES)
- if(spell.charge_counter < spell.charge_max)
- icon_state = "[spell_base]_spell_base"
- if(spell.charge_counter > 0)
- var/icon/partial_charge = icon(src.icon, "[spell_base]_spell_ready")
- partial_charge.Crop(1, 1, partial_charge.Width(), round(partial_charge.Height() * spell.charge_counter / spell.charge_max))
- overlays += partial_charge
- if(last_charged_icon)
- overlays -= last_charged_icon
- last_charged_icon = partial_charge
- else if(last_charged_icon)
- overlays -= last_charged_icon
- last_charged_icon = null
- else
- icon_state = "[spell_base]_spell_ready"
- if(last_charged_icon)
- overlays -= last_charged_icon
- else
- icon_state = "[spell_base]_spell_ready"
-
- overlays += spell.hud_state
-
- last_charge = spell.charge_counter
-
- overlays -= "silence"
- if(spell.silenced)
- overlays += "silence"
-
-/obj/screen/ability/spell/on_update_icon(var/forced = 0)
- update_charge(forced)
- return
-
-/obj/screen/ability/spell/activate()
- spell.perform(usr)
-
-/obj/screen/ability_master/proc/silence_spells(var/amount)
- for(var/obj/screen/ability/spell/spell in spell_objects)
- spell.spell.silenced = amount
- spell.spell.process()
- spell.update_charge(1)
diff --git a/code/datums/extensions/abilities/abilities.dm b/code/datums/extensions/abilities/abilities.dm
index 3995c524dc4..21e02b0126b 100644
--- a/code/datums/extensions/abilities/abilities.dm
+++ b/code/datums/extensions/abilities/abilities.dm
@@ -26,7 +26,7 @@
/// Clicking a grab on the currently grabbed mob.
/datum/extension/abilities/proc/do_grabbed_invocation(atom/target)
- if(isliving(holder) && istype(target) && LAZYLEN(ability_handlers))
+ if(isliving(holder) && istype(target) && LAZYLEN(ability_handlers) && !istype(target, /obj/screen))
for(var/datum/ability_handler/handler in ability_handlers)
if(handler.can_do_grabbed_invocation(holder, target) && handler.do_grabbed_invocation(holder, target))
return TRUE
@@ -34,7 +34,7 @@
/// Clicking an adjacent target (UnarmedAttack())
/datum/extension/abilities/proc/do_melee_invocation(atom/target)
- if(isliving(holder) && istype(target) && LAZYLEN(ability_handlers))
+ if(isliving(holder) && istype(target) && LAZYLEN(ability_handlers) && !istype(target, /obj/screen))
for(var/datum/ability_handler/handler in ability_handlers)
if(handler.can_do_melee_invocation(holder, target) && handler.do_melee_invocation(holder, target))
return TRUE
@@ -42,7 +42,7 @@
/// Clicking a distant target (RangedAttack())
/datum/extension/abilities/proc/do_ranged_invocation(atom/target)
- if(isliving(holder) && istype(target) && LAZYLEN(ability_handlers))
+ if(isliving(holder) && istype(target) && LAZYLEN(ability_handlers) && !istype(target, /obj/screen))
for(var/datum/ability_handler/handler in ability_handlers)
if(handler.can_do_ranged_invocation(holder, target) && handler.do_ranged_invocation(holder, target))
return TRUE
@@ -54,3 +54,8 @@
for(var/datum/ability_handler/handler in ability_handlers)
handler.refresh_login()
+/datum/extension/abilities/proc/refresh_element_positioning()
+ var/row = 0
+ for(var/datum/ability_handler/handler in ability_handlers)
+ if(length(handler.screen_elements))
+ row += handler.refresh_element_positioning(row)
diff --git a/code/datums/extensions/abilities/abilities_mob.dm b/code/datums/extensions/abilities/abilities_mob.dm
index b72d0ac2c5f..4879b2d05c4 100644
--- a/code/datums/extensions/abilities/abilities_mob.dm
+++ b/code/datums/extensions/abilities/abilities_mob.dm
@@ -18,6 +18,7 @@
return FALSE
handler = new handler_type(abilities, src)
LAZYADD(abilities.ability_handlers, handler)
+ handler.finalize_ability_handler()
return handler
/mob/proc/remove_ability_handler(handler_type)
@@ -28,6 +29,75 @@
if(!handler)
return FALSE
LAZYREMOVE(abilities.ability_handlers, handler)
+ qdel(handler)
if(!LAZYLEN(abilities.ability_handlers))
remove_extension(src, /datum/extension/abilities)
return TRUE
+
+/mob/living/proc/copy_abilities_from(mob/living/donor)
+ var/datum/extension/abilities/abilities = get_extension(src, /datum/extension/abilities)
+ if(!abilities)
+ return FALSE
+ . = FALSE
+ for(var/datum/ability_handler/handler in abilities.ability_handlers)
+ if(handler.copy_abilities_to(src))
+ . = TRUE
+
+/mob/living/proc/disable_abilities(var/amount = 0)
+ if(amount < 0)
+ return
+ var/datum/extension/abilities/abilities = get_extension(src, /datum/extension/abilities)
+ for(var/datum/ability_handler/handler in abilities?.ability_handlers)
+ handler.disable_abilities(amount)
+
+/mob/living/proc/copy_abilities_to(mob/living/target)
+ var/datum/extension/abilities/abilities = get_extension(src, /datum/extension/abilities)
+ for(var/datum/ability_handler/handler in abilities?.ability_handlers)
+ handler.copy_abilities_to(target)
+
+/mob/proc/add_ability(ability_type, list/metadata)
+ var/decl/ability/ability = GET_DECL(ability_type)
+ if(!istype(ability) || !ability.associated_handler_type)
+ return FALSE
+ var/datum/ability_handler/handler = get_ability_handler(ability.associated_handler_type, create_if_missing = TRUE)
+ return handler.add_ability(ability_type, metadata)
+
+/mob/proc/remove_ability(ability_type)
+ var/decl/ability/ability = GET_DECL(ability_type)
+ if(!istype(ability) || !ability.associated_handler_type)
+ return FALSE
+ var/datum/extension/abilities/abilities = get_extension(src, /datum/extension/abilities)
+ var/datum/ability_handler/handler = locate(ability.associated_handler_type) in abilities?.ability_handlers
+ return handler?.remove_ability(ability_type)
+
+/mob/proc/get_ability_metadata(ability_type)
+ var/decl/ability/ability = GET_DECL(ability_type)
+ if(!istype(ability) || !ability.associated_handler_type)
+ return null
+ var/datum/extension/abilities/abilities = get_extension(src, /datum/extension/abilities)
+ var/datum/ability_handler/handler = locate(ability.associated_handler_type) in abilities?.ability_handlers
+ return handler?.get_metadata(ability_type, create_if_missing = TRUE)
+
+/mob/proc/has_ability(ability_type)
+ var/decl/ability/ability = GET_DECL(ability_type)
+ if(!istype(ability) || !ability.associated_handler_type)
+ return null
+ var/datum/extension/abilities/abilities = get_extension(src, /datum/extension/abilities)
+ var/datum/ability_handler/handler = locate(ability.associated_handler_type) in abilities?.ability_handlers
+ return handler?.provides_ability(ability_type)
+
+/mob/Stat()
+ if((. = ..()) && client)
+ var/datum/extension/abilities/abilities = get_extension(src, /datum/extension/abilities)
+ for(var/datum/ability_handler/handler in abilities?.ability_handlers)
+ handler.show_stat_string(src)
+
+/mob/proc/get_all_abilities()
+ var/datum/extension/abilities/abilities_extension = get_extension(src, /datum/extension/abilities)
+ if(!istype(abilities_extension))
+ return
+ for(var/datum/ability_handler/handler as anything in abilities_extension.ability_handlers)
+ for(var/ability_type in handler.known_abilities)
+ var/decl/ability/ability = GET_DECL(ability_type)
+ if(istype(ability))
+ LAZYDISTINCTADD(., ability)
diff --git a/code/datums/extensions/abilities/abilities_predator.dm b/code/datums/extensions/abilities/abilities_predator.dm
index 066a3feac92..4fc37d4a828 100644
--- a/code/datums/extensions/abilities/abilities_predator.dm
+++ b/code/datums/extensions/abilities/abilities_predator.dm
@@ -1,11 +1,16 @@
/datum/ability_handler/predator
+ category_toggle_type = null
var/max_dismember_size = MOB_SIZE_SMALL
/datum/ability_handler/predator/can_do_melee_invocation(mob/user, atom/target)
- return istype(user) && !user.incapacitated() && isatom(target) && target.Adjacent(user)
+ return ..() || (istype(user) && !user.incapacitated() && isatom(target) && target.Adjacent(user))
/datum/ability_handler/predator/do_melee_invocation(mob/user, atom/target)
+ . = ..()
+ if(.)
+ return
+
// Nibbles!
if(user.check_intent(I_FLAG_HARM))
if(isliving(target))
diff --git a/code/datums/extensions/abilities/ability_button.dm b/code/datums/extensions/abilities/ability_button.dm
new file mode 100644
index 00000000000..5ab2d44e66b
--- /dev/null
+++ b/code/datums/extensions/abilities/ability_button.dm
@@ -0,0 +1,108 @@
+/obj/screen/ability
+ requires_ui_style = FALSE
+ requires_owner = FALSE
+ icon_state = "ability"
+ icon = 'icons/mob/screen/abilities.dmi'
+ abstract_type = /obj/screen/ability
+ var/datum/ability_handler/owning_handler
+
+/obj/screen/ability/Destroy()
+ remove_from_handler()
+ return ..()
+
+/obj/screen/ability/proc/remove_from_handler()
+ owning_handler = null
+
+/obj/screen/ability/on_update_icon()
+ invisibility = (isnull(owning_handler) || owning_handler.showing_abilities) ? 0 : INVISIBILITY_ABSTRACT
+
+/obj/screen/ability/handle_click(mob/user, params)
+ to_chat(user, "Click!")
+
+/obj/screen/ability/button
+ icon_state = "button"
+ requires_owner = TRUE
+ maptext_y = -3
+ var/decl/ability/ability
+
+/obj/screen/ability/button/Initialize(mapload, mob/_owner, decl/ui_style/ui_style, ui_color, ui_alpha, ui_cat)
+ . = ..()
+ START_PROCESSING(SSfastprocess, src)
+ on_update_icon()
+
+/obj/screen/ability/button/Destroy()
+ if(is_processing)
+ STOP_PROCESSING(SSfastprocess, src)
+ return ..()
+
+/obj/screen/ability/button/Process()
+ // We've been broken or deleted.
+ if(QDELETED(src) || !ability || !owning_handler)
+ return PROCESS_KILL
+ // No reason to run periodic updates.
+ if(!ability.ability_cooldown_time && !ability.max_charge)
+ return PROCESS_KILL
+ // Something is broken.
+ var/list/metadata = owning_handler.get_metadata(ability.type, create_if_missing = FALSE)
+ if(!metadata)
+ return PROCESS_KILL
+ maptext = ""
+ if(ability.ability_cooldown_time)
+ var/next_cast = metadata["next_cast"]
+ if(world.time < next_cast)
+ maptext = ticks2shortreadable(next_cast - world.time)
+ if(ability.max_charge)
+ maptext += "
"
+ if(ability.max_charge)
+ maptext += "x[max(0, metadata["charges"])]"
+ if(maptext)
+ maptext = STYLE_SMALLFONTS_OUTLINE("
[maptext]", 7, COLOR_WHITE, COLOR_BLACK)
+
+/obj/screen/ability/button/remove_from_handler()
+ owning_handler?.remove_screen_element(src, ability)
+ return ..()
+
+/obj/screen/ability/button/handle_click(mob/user, params)
+ if(owning_handler.prepared_ability == ability)
+ owning_handler.cancel_prepared_ability()
+ else if(ability.use_ability(user, get_turf(user), owning_handler)) // tmp, needs better/multi-step target selection
+ update_icon()
+ addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, update_icon)), ability.get_cooldown_time(ability.get_metadata_for_user(user)) + 1)
+
+/obj/screen/ability/button/proc/set_ability(decl/ability/_ability)
+ if(ability == _ability)
+ return
+ ability = _ability
+ if(istype(ability))
+ SetName(ability.name)
+ else
+ SetName(initial(name))
+ update_icon()
+
+/obj/screen/ability/button/on_update_icon()
+ . = ..()
+ icon_state = initial(icon_state)
+ cut_overlays()
+ if(istype(ability))
+ if(owning_handler && owning_handler.prepared_ability == ability)
+ icon_state = "[icon_state]-active"
+ if(ability.ability_icon && ability.ability_icon_state)
+ add_overlay(overlay_image(ability.ability_icon, ability.ability_icon_state, COLOR_WHITE, (RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM)))
+
+/obj/screen/ability/category
+ name = "Toggle Ability Category"
+ icon_state = "category"
+
+/obj/screen/ability/category/remove_from_handler()
+ owning_handler?.remove_screen_element(src, "toggle")
+ return ..()
+
+/obj/screen/ability/category/Initialize(mapload, mob/_owner, decl/ui_style/ui_style, ui_color, ui_alpha, ui_cat)
+ . = ..()
+ update_icon()
+
+/obj/screen/ability/category/handle_click(mob/user, params)
+ owning_handler?.toggle_category_visibility()
+
+/obj/screen/ability/category/on_update_icon()
+ icon_state = owning_handler?.showing_abilities ? initial(icon_state) : "[initial(icon_state)]-off"
diff --git a/code/datums/extensions/abilities/ability_decl.dm b/code/datums/extensions/abilities/ability_decl.dm
new file mode 100644
index 00000000000..9f271846750
--- /dev/null
+++ b/code/datums/extensions/abilities/ability_decl.dm
@@ -0,0 +1,410 @@
+/decl/ability
+ abstract_type = /decl/ability
+ /// A descriptive identifier string.
+ var/name
+ /// A descriptive string about the ability.
+ var/desc
+ /// An associated handler type, used in add_ability().
+ var/associated_handler_type
+ /// Should this ability be copied between mobs when mind is transferred?
+ var/copy_with_mind = FALSE
+
+ /// If TRUE, ability is toggled on and will fire when a target is clicked, instead of instantaneously on use.
+ var/prep_cast = FALSE
+ /// Used in conjunction with prep_cast, if TRUE, ability will be untoggled after cast.
+ var/end_prep_on_cast = TRUE
+ /// Ability items invoking this ability will GC afterwards.
+ var/item_end_on_cast = TRUE
+
+ /// Does this invocation trigger on a proximity click, if prepared?
+ var/is_melee_invocation = FALSE
+ /// Does this invocation trigger on a non-proximity click, if prepared?
+ var/is_ranged_invocation = FALSE
+
+ // Projectile created and used to propagate this ability, if it is a ranged ability.
+ /// If set, this ability will create a projectile rather than applying the effect directly.
+ var/projectile_type
+ /// Sets the projectile step_delay.
+ var/projectile_step_delay = 1
+ /// Determines the lifetime of the projectile.
+ var/projectile_duration = 1 SECOND
+ /// If not set, the ability will have documentation generated for the codex.
+ var/hidden_from_codex = FALSE
+ /// If set, this ability will be silenced by null rod and similar mechanics.
+ var/is_supernatural = FALSE
+
+ // Visual/audible state for the on-turf effect of casting the spell on something.
+ /// If set, will attempt to draw from this icon on the turf where the ability is used.
+ var/overlay_icon
+ /// If set, will attempt to draw this icon_state on the turf where the ability is used.
+ var/overlay_icon_state
+ /// Will delete the overlay after this time.
+ var/overlay_lifespan = 1 SECOND
+ /// If set, will play a sound when used.
+ var/use_sound
+ /// Volume for above.
+ var/use_sound_volume = 50
+
+ // Visual state for the ability HUD element.
+ /// Type of button to use for the UI. Should be /obj/screen/ability/button or subtype.
+ var/ui_element_type = /obj/screen/ability/button
+ /// Icon to draw on the ability HUD, if any.
+ var/ability_icon
+ /// Icon state to draw on the ability HUD, if any.
+ var/ability_icon_state
+
+ // Various restrictions on how and when the ability is used.
+ /// A decl type that handles retrieving and validating targets.
+ var/decl/ability_targeting/target_selector = /decl/ability_targeting
+ /// If set to a numeric value, the ability cannot be used before the cooldown has expired.
+ var/ability_cooldown_time = 5 SECONDS
+ /// If set, a do_after() will be applied to this spell.
+ var/ability_use_channel
+ /// Maximum charges that can be held of this item at a time. If unset, item does not accumulate charges.
+ var/max_charge
+ /// How long it takes between charges.
+ var/charge_delay = 1 SECOND
+ /// What flags to check before an ability can be used, if any.
+ var/check_incapacitated = (INCAPACITATION_STUNNED|INCAPACITATION_RESTRAINED|INCAPACITATION_BUCKLED_FULLY|INCAPACITATION_FORCELYING|INCAPACITATION_KNOCKOUT)
+ /// What type of mob is required to use this ability.
+ var/mob/expected_mob_type = /mob/living
+ /// If set, this ability can only be used while standing on a turf (not in an atom's contents or null loc).
+ var/requires_turf = TRUE
+ /// If set, this ability cannot be used on the admin z-level.
+ var/admin_blocked = TRUE
+
+ // Various failure messages.
+ /// Failed due to purged/null rod.
+ var/cast_failed_purged_str = "Another power interferes with your own!"
+ /// Failed due to non-turf loc.
+ var/cast_failed_no_turf = "You must be standing on solid ground to use this ability."
+ /// Failed due to being on admin level
+ var/cast_failed_no_admin_level = "This ability cannot be used on the admin z-level."
+ /// Failed due to being invalid mob type
+ var/cast_failed_wrong_mob_type = "This ability may only be used by living creature."
+ /// Failed due to being downed/buckled
+ var/cast_failed_incapacitated = "You are in no state to use that ability."
+ /// Failed due to still being on cooldown from last use
+ var/cast_failed_on_cooldown = "You cannot use that ability again just yet."
+ /// Failed due to still recharging
+ var/cast_failed_no_charges = "You are out of charges for that ability."
+ /// Failed due to being silenced/disabled
+ var/cast_failed_disabled_str = "You are unable to use that ability for another $TIME$!"
+
+ // Various casting messages.
+ /// "$USER$ begins preparing to use an ability on $TARGET$."
+ var/prepare_message_3p_str
+ /// "You begin preparing to use an ability on $TARGET$."
+ var/prepare_message_1p_str
+ /// "$USER$ uses an ability."
+ var/cast_message_3p_str
+ /// "You use an ability."
+ var/cast_message_1p_str
+ /// "You ready yourself to use an ability."
+ var/ready_ability_1p_str
+ /// "You cease readying yourself to use an ability."
+ var/cancel_ability_1p_str
+ /// "You fail to cast an ability."
+ var/fail_cast_1p_str
+
+/decl/ability/Initialize()
+ target_selector = GET_DECL(target_selector)
+ . = ..()
+
+/decl/ability/validate()
+ . = ..()
+ if(!istype(target_selector, /decl/ability_targeting))
+ . += "null or invalid target_selector: '[target_selector || "NULL"]'"
+ if(!findtext(cast_failed_disabled_str, "$TIME$"))
+ . += "missing $TIME$ token in cast_failed_disabled_str"
+ if(!ispath(associated_handler_type, /datum/ability_handler))
+ . += "null or invalid associated_handler_type '[associated_handler_type]'"
+ if(!ability_icon)
+ . += "null ability_icon"
+ else if(!istext(ability_icon_state))
+ . += "null or non-text ability_icon_state"
+ else if(!check_state_in_icon(ability_icon_state, ability_icon))
+ . += "missing ability_icon_state '[ability_icon_state]' in icon '[ability_icon]'"
+
+/decl/ability/proc/get_cooldown_time(list/metadata)
+ return ability_cooldown_time
+
+/decl/ability/proc/has_valid_targets(user, atom/target, list/metadata)
+ // Projectiles just need something to shoot at.
+ if(projectile_type)
+ return isturf(target) || isturf(target.loc)
+ // Abilities need at least one valid target.
+ return target_selector.validate_initial_target(user, target, metadata, src)
+
+/decl/ability/proc/get_metadata_for(mob/user)
+ if(!istype(user))
+ CRASH("get_metadata_for() called with null or invalid user!")
+ var/datum/ability_handler/handler = user.get_ability_handler(associated_handler_type, create_if_missing = FALSE)
+ if(istype(handler))
+ . = handler.get_metadata(src)
+ if(!islist(.))
+ PRINT_STACK_TRACE("get_metadata_for() returning null or non-list metadata!")
+ else
+ PRINT_STACK_TRACE("get_metadata_for() called by mob with no handler of associated type!")
+
+// This is the main entrypoint for the ability use chain.
+/decl/ability/proc/use_ability(mob/user, atom/target, datum/ability_handler/handler)
+
+ if(!istype(user))
+ return
+
+ var/list/metadata = get_metadata_for(user)
+ if(!islist(metadata) || !can_use_ability(user, metadata))
+ return
+
+ if(prep_cast && handler.prepared_ability != src)
+ handler.prepare_ability(src)
+ return
+
+ // Resolve our clicked target to the appropriate turf.
+ target = target_selector.resolve_initial_target(target)
+ if(!istype(target))
+ to_chat(user, SPAN_WARNING("You cannot see a target for [name]."))
+ return
+
+ if(!has_valid_targets(user, target, metadata))
+ to_chat(user, SPAN_WARNING("You cannot use [name] on \the [target]."))
+ return
+
+ if(!prepare_to_cast(user, target, metadata, handler))
+ if(fail_cast_1p_str)
+ to_chat(user, SPAN_WARNING(capitalize(emote_replace_user_tokens(fail_cast_1p_str, user))))
+ return
+
+ if(projectile_type)
+ // Fire a projectile if that is how this ability works.
+ fire_projectile_at(user, target, metadata)
+
+ else
+ // Otherwise, just apply to the target directly.
+ apply_effect(user, target, metadata)
+
+ if(end_prep_on_cast && handler.prepared_ability == src)
+ handler.cancel_prepared_ability()
+
+/decl/ability/proc/fire_projectile_at(mob/user, atom/target, list/metadata)
+ var/obj/item/projectile/projectile = new projectile_type(get_turf(user))
+ if(istype(projectile, /obj/item/projectile/ability))
+ var/obj/item/projectile/ability/ability_projectile = projectile
+ ability_projectile.owner = user
+ ability_projectile.ability_metadata = metadata
+ ability_projectile.carried_ability = src
+ projectile.original = target
+ projectile.starting = get_turf(user)
+ projectile.shot_from = user
+ projectile.current = projectile.original
+ projectile.yo = target.y - user.y
+ projectile.xo = target.x - user.x
+ projectile.life_span = projectile_duration
+ projectile.hitscan = !projectile_step_delay
+ projectile.step_delay = projectile_step_delay
+ projectile.launch(target)
+ return projectile
+
+/decl/ability/proc/show_cast_channel_msg(mob/user, atom/target, list/metadata)
+ if(prepare_message_3p_str && prepare_message_1p_str)
+ user.visible_message(
+ SPAN_NOTICE(capitalize(emote_replace_target_tokens(emote_replace_user_tokens(prepare_message_3p_str, user), target))),
+ SPAN_NOTICE(capitalize(emote_replace_target_tokens(prepare_message_1p_str, target)))
+ )
+ else if(prepare_message_1p_str)
+ user.visible_message(SPAN_NOTICE(capitalize(emote_replace_target_tokens(prepare_message_1p_str, target))))
+ else if(prepare_message_3p_str)
+ user.visible_message(SPAN_NOTICE(capitalize(emote_replace_target_tokens(emote_replace_user_tokens(prepare_message_3p_str, user), target))))
+
+/decl/ability/proc/show_ability_cast_msg(mob/user, list/targets, list/metadata)
+ var/atom/target = targets[1]
+ if(cast_message_3p_str && cast_message_1p_str)
+ user.visible_message(
+ SPAN_NOTICE(capitalize(emote_replace_target_tokens(emote_replace_user_tokens(cast_message_3p_str, user), target))),
+ SPAN_NOTICE(capitalize(emote_replace_target_tokens(cast_message_1p_str, target)))
+ )
+ else if(cast_message_1p_str)
+ user.visible_message(SPAN_NOTICE(capitalize(emote_replace_target_tokens(cast_message_1p_str, target))))
+ else if(cast_message_3p_str)
+ user.visible_message(SPAN_NOTICE(capitalize(emote_replace_target_tokens(emote_replace_user_tokens(cast_message_3p_str, user), target))))
+
+/decl/ability/proc/prepare_to_cast(mob/user, atom/target, list/metadata, datum/ability_handler/handler)
+ var/use_cooldown_time = get_cooldown_time(metadata)
+ if(ability_use_channel)
+ if(world.time < handler.next_channel)
+ return FALSE
+ handler.next_channel = world.time + ability_use_channel
+ show_cast_channel_msg(user, target, metadata)
+ if(!do_after(user, ability_use_channel, target) || !can_use_ability(user, metadata))
+ handler.next_channel = 0 // Don't make them sit out the entire channel period, it's just a debounce/duplicate ability preventative
+ return FALSE
+ if(use_cooldown_time > 0)
+ metadata["next_cast"] = world.time + use_cooldown_time
+ return TRUE
+
+/decl/ability/proc/check_equipment(mob/user, list/metadata, silent = FALSE)
+ return TRUE
+
+/decl/ability/proc/get_metadata_for_user(mob/user)
+ if(!user.has_ability(type))
+ return null
+
+ var/datum/ability_handler/handler = user.get_ability_handler(associated_handler_type, create_if_missing = FALSE)
+ if(!istype(handler))
+ CRASH("get_metadata_for_user() called by mob with no handler of associated type!")
+
+ return handler.get_metadata(src)
+
+/decl/ability/proc/can_use_ability(mob/user, list/metadata, silent = FALSE)
+
+ if(!user.has_ability(type))
+ error("\The [user] utilized the ability '[type]' without having access to it.")
+ if(!silent)
+ to_chat(user, SPAN_WARNING("You shouldn't have this ability! Please notify a developer or raise an issue ticket."))
+ return FALSE
+
+ var/turf/my_turf = get_turf(user)
+ if(requires_turf)
+ if(!istype(my_turf))
+ if(!silent)
+ to_chat(user, SPAN_WARNING(cast_failed_no_turf))
+ return FALSE
+ if(admin_blocked && isAdminLevel(my_turf.z))
+ if(!silent)
+ to_chat(user, SPAN_WARNING(cast_failed_no_admin_level))
+ return FALSE
+
+ if(!istype(user, expected_mob_type))
+ if(!silent)
+ to_chat(user, SPAN_WARNING(cast_failed_wrong_mob_type))
+ return FALSE
+
+ if(!isnull(check_incapacitated) && user.incapacitated(check_incapacitated))
+ if(!silent)
+ to_chat(user, SPAN_WARNING(cast_failed_incapacitated))
+ return FALSE
+
+ if(!check_equipment(user, metadata, silent))
+ return FALSE
+
+ if(ability_cooldown_time && world.time < metadata["next_cast"])
+ if(!silent)
+ to_chat(user, SPAN_WARNING(cast_failed_on_cooldown))
+ return FALSE
+
+ if(max_charge && metadata["charges"] <= 0)
+ if(!silent)
+ to_chat(user, SPAN_WARNING(cast_failed_no_charges))
+ return FALSE
+
+ if(is_supernatural)
+
+ var/is_purged = FALSE
+ if(isanimal(user))
+ var/mob/living/simple_animal/critter = user
+ is_purged = !!critter.purge
+
+ if(!is_purged)
+ for(var/turf/turf in range(user, 1))
+ if(turf.is_purged())
+ is_purged = TRUE
+ break
+
+ if(is_purged)
+ if(!silent)
+ to_chat(user, SPAN_WARNING(cast_failed_purged_str))
+ return FALSE
+
+ var/disabled_time = metadata["disabled"]
+ if(world.time < disabled_time)
+ if(!silent)
+ var/remaining_time = ceil((disabled_time - world.time) / 10)
+ to_chat(user, SPAN_WARNING(replacetext(cast_failed_disabled_str, "$TIME$", "[remaining_time] second\s")))
+ return FALSE
+
+ return TRUE
+
+/decl/ability/proc/apply_effect(mob/user, atom/hit_target, list/metadata, obj/item/projectile/ability/projectile)
+ SHOULD_CALL_PARENT(TRUE)
+ if(use_sound)
+ playsound(get_turf(user), use_sound, use_sound_volume, 1)
+ if(istype(projectile))
+ projectile.expended = TRUE
+
+ admin_attacker_log(user, "attempted to use ability [src] on [hit_target]")
+
+ var/list/targets = target_selector.get_affected(user, hit_target, metadata, src, projectile)
+ if(length(targets))
+ show_ability_cast_msg(user, targets, metadata)
+ while(length(targets))
+ var/target = targets[1]
+ apply_effect_to(user, target, metadata)
+ targets = prune_targets(user, target, targets, metadata)
+ finish_casting(user, hit_target, metadata)
+
+/decl/ability/proc/finish_casting(mob/user, atom/hit_target, list/metadata)
+ return
+
+/decl/ability/proc/prune_targets(user, previous_target, list/targets, list/metadata)
+ if(!length(targets))
+ return null
+ if(previous_target)
+ LAZYREMOVE(targets, previous_target)
+ return targets
+
+/decl/ability/proc/apply_visuals(mob/user, atom/target, list/metadata)
+ if(!overlay_icon || !overlay_lifespan)
+ return
+ var/turf/overlay_loc = get_turf(target)
+ if(!isturf(overlay_loc) || locate(/obj/effect/overlay) in overlay_loc)
+ return
+ var/obj/effect/overlay/ability_overlay = new(overlay_loc)
+ ability_overlay.icon = overlay_icon
+ ability_overlay.icon_state = overlay_icon_state
+ ability_overlay.anchored = TRUE
+ ability_overlay.set_density(FALSE)
+ QDEL_IN(ability_overlay, overlay_lifespan)
+
+/decl/ability/proc/apply_effect_to(mob/user, atom/target, list/metadata)
+ SHOULD_CALL_PARENT(TRUE)
+ SHOULD_NOT_SLEEP(TRUE)
+ apply_visuals(user, target, metadata)
+
+/decl/ability/proc/get_default_metadata()
+ . = list()
+ if(ability_cooldown_time)
+ .["next_cast"] = 0
+ if(max_charge)
+ .["charges"] = max_charge
+ .["next_charge"] = 0
+
+/decl/ability/proc/recharge(mob/owner, list/metadata)
+ if(max_charge <= 0 || metadata["charges"] >= max_charge)
+ return FALSE
+ if(world.time < metadata["next_charge"])
+ return TRUE
+ metadata["next_charge"] = world.time + charge_delay
+ metadata["charges"]++
+ return TRUE
+
+/decl/ability/proc/get_stat_strings(list/metadata)
+ var/use_name = metadata["ability_name"] || name
+ if(ability_cooldown_time)
+ var/on_cooldown = metadata["next_cast"] - world.time
+ if(on_cooldown > 0)
+ return list(
+ use_name,
+ "[ceil(on_cooldown/10)]s"
+ )
+ if(max_charge)
+ return list(
+ use_name,
+ "[metadata["charges"]]/[max_charge]"
+ )
+
+/decl/ability/ranged
+ abstract_type = /decl/ability/ranged
+ projectile_type = /obj/item/projectile/ability
+ is_ranged_invocation = TRUE
+ prep_cast = TRUE
\ No newline at end of file
diff --git a/code/datums/extensions/abilities/ability_handler.dm b/code/datums/extensions/abilities/ability_handler.dm
index 8dc7ee1e5de..d215868a4c5 100644
--- a/code/datums/extensions/abilities/ability_handler.dm
+++ b/code/datums/extensions/abilities/ability_handler.dm
@@ -1,8 +1,17 @@
/datum/ability_handler
abstract_type = /datum/ability_handler
+ var/showing_abilities = FALSE
var/mob/owner
- var/list/ability_items
var/datum/extension/abilities/master
+ var/list/ability_items
+ var/list/screen_elements
+ var/list/known_abilities
+ var/list/recharging_abilities
+ var/stat_panel_type = "Abilities"
+ /// UI element for showing or hiding this ability category. Should be /obj/screen/ability/category or subtype.
+ var/category_toggle_type = /obj/screen/ability/category
+ var/decl/ability/prepared_ability
+ var/next_channel = 0
/datum/ability_handler/New(_master)
master = _master
@@ -12,16 +21,179 @@
if(!istype(owner))
CRASH("Ability handler received invalid owner!")
..()
+ refresh_login()
+
+/datum/ability_handler/Process()
+
+ if(!length(recharging_abilities))
+ return PROCESS_KILL
+
+ for(var/decl/ability/ability as anything in recharging_abilities)
+ if(!ability.recharge(owner, get_metadata(ability)))
+ LAZYREMOVE(recharging_abilities, ability)
+
+/datum/ability_handler/proc/get_metadata(decl/ability/ability, create_if_missing = TRUE)
+ if(istype(ability))
+ . = known_abilities[ability.type]
+ if(!islist(.) && create_if_missing)
+ . = ability.get_default_metadata()
+ known_abilities[ability.type] = .
+ else if(ispath(ability, /decl/ability))
+ . = known_abilities[ability]
+ if(!islist(.) && create_if_missing)
+ ability = GET_DECL(ability)
+ if(!istype(ability))
+ return list()
+ . = ability.get_default_metadata()
+ known_abilities[ability] = .
+ else if(create_if_missing)
+ PRINT_STACK_TRACE("ability metadata retrieval passed invalid ability type: '[ability]'")
+ . = list()
/datum/ability_handler/Destroy()
+ recharging_abilities = null
+ known_abilities = null
QDEL_NULL_LIST(ability_items)
+
+ for(var/ability in screen_elements)
+ var/obj/element = screen_elements[ability]
+ if(istype(element))
+ qdel(element)
+ screen_elements = null
+
if(master)
LAZYREMOVE(master.ability_handlers, src)
master.update()
master = null
owner = null
+ if(is_processing)
+ STOP_PROCESSING(SSprocessing, src)
return ..()
+/datum/ability_handler/proc/add_ability(ability_type, list/metadata)
+ if(provides_ability(ability_type))
+ return FALSE
+ var/decl/ability/ability = GET_DECL(ability_type)
+ if(!istype(ability))
+ return FALSE
+ if(islist(metadata))
+ metadata = metadata.Copy() // Prevent mutating during copy from other mobs.
+ else
+ metadata = ability.get_default_metadata() // Always unique, no copy needed.
+
+ if(ability.ui_element_type && !istype(LAZYACCESS(screen_elements, ability), ability.ui_element_type))
+ var/existing = screen_elements[ability]
+ if(existing)
+ remove_screen_element(existing, ability, FALSE)
+ var/obj/screen/ability/button/button = new ability.ui_element_type(null, owner, null, null, null, null, null)
+ button.set_ability(ability)
+ add_screen_element(button, ability, TRUE)
+
+ LAZYSET(known_abilities, ability_type, metadata)
+ if(ability.max_charge)
+ LAZYDISTINCTADD(recharging_abilities, ability_type)
+ if(!is_processing)
+ START_PROCESSING(SSprocessing, src)
+ return TRUE
+
+/datum/ability_handler/proc/remove_ability(ability_type)
+ if(!provides_ability(ability_type))
+ return FALSE
+ LAZYREMOVE(known_abilities, ability_type)
+ LAZYREMOVE(recharging_abilities, ability_type)
+
+ var/decl/ability/ability = GET_DECL(ability_type)
+ if(ability?.ui_element_type)
+ var/obj/screen/existing_button = LAZYACCESS(screen_elements, ability)
+ if(istype(existing_button))
+ remove_screen_element(existing_button, ability)
+
+ if(!LAZYLEN(recharging_abilities) && is_processing)
+ STOP_PROCESSING(SSprocessing, src)
+
+ if(!LAZYLEN(known_abilities))
+ owner.remove_ability_handler(type)
+
+ return TRUE
+
+/datum/ability_handler/proc/provides_ability(ability_type)
+ return (ability_type in known_abilities)
+
+/datum/ability_handler/proc/finalize_ability_handler()
+ if(category_toggle_type)
+ var/obj/screen/ability/category/category_toggle = new category_toggle_type(null, null, null, null, null, null, null)
+ add_screen_element(category_toggle, "toggle", TRUE)
+ toggle_category_visibility(TRUE)
+
+/datum/ability_handler/proc/refresh_element_positioning(row = 1, col = 1)
+ if(!LAZYLEN(screen_elements))
+ return 0
+ var/button_pos = col
+ var/button_row = row
+ . = 1
+ for(var/ability in screen_elements)
+ var/obj/screen/element = screen_elements[ability]
+ if(istype(element, /obj/screen/ability/category))
+ element.screen_loc = "RIGHT-[col]:-4,TOP-[row]"
+ else if(!element.invisibility)
+ button_pos++
+ if((button_pos-col) > 5)
+ button_row++
+ .++
+ button_pos = col+1
+ element.screen_loc = "RIGHT-[button_pos]:-4,TOP-[button_row]"
+
+/datum/ability_handler/proc/toggle_category_visibility(force_state)
+ showing_abilities = isnull(force_state) ? !showing_abilities : force_state
+ update_screen_elements()
+ if(master)
+ master.refresh_element_positioning()
+
+/datum/ability_handler/proc/update_screen_elements()
+ for(var/ability in screen_elements)
+ var/obj/screen/ability/ability_button = screen_elements[ability]
+ ability_button.update_icon()
+
+/datum/ability_handler/proc/copy_abilities_to(mob/living/target)
+ for(var/decl/ability/ability as anything in known_abilities)
+ if(!ability.copy_with_mind)
+ continue
+ if(target.add_ability(ability, get_metadata(ability, create_if_missing = FALSE)))
+ . = TRUE
+
+/datum/ability_handler/proc/disable_abilities(amount)
+ for(var/ability in known_abilities)
+ var/list/metadata = get_metadata(ability)
+ metadata["disabled"] = max(metadata["disabled"], (world.time + amount))
+
+/datum/ability_handler/proc/enable_abilities(amount)
+ for(var/ability in known_abilities)
+ var/list/metadata = get_metadata(ability)
+ metadata["disabled"] = 0
+
+/datum/ability_handler/proc/add_screen_element(atom/element, decl/ability/ability, update_positions = TRUE)
+ if(isnull(ability) || isnum(ability))
+ return
+ LAZYSET(screen_elements, ability, element)
+ owner?.client?.screen |= element
+ if(istype(element, /obj/screen/ability))
+ var/obj/screen/ability/ability_button = element
+ ability_button.owning_handler = src
+ if(update_positions && master && length(screen_elements))
+ master.refresh_element_positioning()
+
+/datum/ability_handler/proc/remove_screen_element(atom/element, decl/ability/ability, update_positions = TRUE)
+ if(isnull(ability) || isnum(ability))
+ return
+ LAZYREMOVE(screen_elements, ability)
+ owner?.client?.screen -= element
+ if(istype(element, /obj/screen/ability))
+ var/obj/screen/ability/ability_button = element
+ if(ability_button.owning_handler == src)
+ ability_button.owning_handler = null
+ if(update_positions && master && LAZYLEN(screen_elements))
+ master.refresh_element_positioning()
+
/datum/ability_handler/proc/cancel()
if(LAZYLEN(ability_items))
for(var/thing in ability_items)
@@ -29,9 +201,48 @@
qdel(thing)
ability_items = null
+/datum/ability_handler/proc/show_stat_string(mob/user)
+ if(!stat_panel_type || !statpanel(stat_panel_type))
+ return
+ for(var/ability_type in known_abilities)
+ var/decl/ability/ability = GET_DECL(ability_type)
+ var/list/stat_strings = ability.get_stat_strings(get_metadata(ability))
+ if(length(stat_strings) >= 2)
+ stat(stat_strings[1], stat_strings[2])
+
/// Individual ability methods/disciplines (psioncs, etc.) so that mobs can have multiple.
/datum/ability_handler/proc/refresh_login()
- return
+ SHOULD_CALL_PARENT(TRUE)
+ if(LAZYLEN(screen_elements))
+ var/list/add_elements = list()
+ for(var/ability in screen_elements)
+ var/atom/element = screen_elements[ability]
+ if(istype(element))
+ add_elements |= element
+ if(length(add_elements))
+ owner?.client?.screen |= add_elements
+
+/datum/ability_handler/proc/cancel_prepared_ability()
+ if(!prepared_ability)
+ return FALSE
+ if(prepared_ability.cancel_ability_1p_str)
+ to_chat(owner, capitalize(emote_replace_user_tokens(prepared_ability.cancel_ability_1p_str), owner))
+ var/obj/screen/ability/button/button = LAZYACCESS(screen_elements, prepared_ability)
+ prepared_ability = null
+ if(istype(button))
+ button.update_icon()
+ return TRUE
+
+/datum/ability_handler/proc/prepare_ability(decl/ability/ability)
+ if(prepared_ability && !cancel_prepared_ability())
+ return FALSE
+ prepared_ability = ability
+ if(ability.ready_ability_1p_str)
+ to_chat(owner, capitalize(emote_replace_user_tokens(ability.ready_ability_1p_str), owner))
+ var/obj/screen/ability/button/button = LAZYACCESS(screen_elements, ability)
+ if(istype(button))
+ button.update_icon()
+ return TRUE
/datum/ability_handler/proc/can_do_self_invocation(mob/user)
return FALSE
@@ -46,13 +257,23 @@
return FALSE
/datum/ability_handler/proc/can_do_melee_invocation(mob/user, atom/target)
- return FALSE
+ SHOULD_CALL_PARENT(TRUE)
+ return prepared_ability ? prepared_ability.is_melee_invocation : FALSE
/datum/ability_handler/proc/do_melee_invocation(mob/user, atom/target)
+ SHOULD_CALL_PARENT(TRUE)
+ if(prepared_ability)
+ prepared_ability.use_ability(user, target, src)
+ return TRUE
return FALSE
/datum/ability_handler/proc/can_do_ranged_invocation(mob/user, atom/target)
- return FALSE
+ SHOULD_CALL_PARENT(TRUE)
+ return prepared_ability ? prepared_ability.is_ranged_invocation : FALSE
/datum/ability_handler/proc/do_ranged_invocation(mob/user, atom/target)
+ SHOULD_CALL_PARENT(TRUE)
+ if(prepared_ability)
+ prepared_ability.use_ability(user, target, src)
+ return TRUE
return FALSE
diff --git a/code/datums/extensions/abilities/ability_item.dm b/code/datums/extensions/abilities/ability_item.dm
index 98bb2b8d43b..b4838fd4145 100644
--- a/code/datums/extensions/abilities/ability_item.dm
+++ b/code/datums/extensions/abilities/ability_item.dm
@@ -1,26 +1,37 @@
/obj/item/ability
- simulated = 1
- obj_flags = OBJ_FLAG_NO_STORAGE
- anchored = TRUE
+ simulated = FALSE
+ icon = 'icons/mob/screen/ability_inhand.dmi'
+ obj_flags = OBJ_FLAG_NO_STORAGE
+ anchored = TRUE
pickup_sound = null
drop_sound = null
equip_sound = null
is_spawnable_type = FALSE
abstract_type = /obj/item/ability
- var/mob/living/owner
+ var/decl/ability/ability
+ var/weakref/owner_ref
var/handler_type
-/obj/item/ability/Initialize()
- owner = loc
- if(!istype(owner))
+/obj/item/ability/Initialize(ml, decl/ability/_ability)
+ var/mob/living/owner = loc
+ var/datum/ability_handler/handler = istype(owner) && owner.get_ability_handler(handler_type, FALSE)
+ if(!istype(handler))
return INITIALIZE_HINT_QDEL
- return ..()
+ if(_ability)
+ ability = _ability
+ if(!istype(ability))
+ return INITIALIZE_HINT_QDEL
+ owner_ref = weakref(owner)
+ LAZYDISTINCTADD(handler.ability_items, src)
+ . = ..()
+ owner.put_in_hands(src)
/obj/item/ability/Destroy()
+ var/mob/living/owner = owner_ref?.resolve()
var/datum/ability_handler/handler = istype(owner) && owner.get_ability_handler(handler_type, FALSE)
- if(handler)
+ if(istype(handler))
LAZYREMOVE(handler.ability_items, src)
- . = ..()
+ return ..()
/obj/item/ability/dropped()
..()
@@ -29,3 +40,23 @@
/obj/item/ability/attack_self(var/mob/user)
user?.drop_from_inventory(src)
return TRUE
+
+/obj/item/ability/use_on_mob(mob/living/target, mob/living/user, animate)
+ return FALSE
+
+/obj/item/ability/afterattack(atom/target, mob/user, proximity)
+ if(QDELETED(src) || !istype(ability))
+ return TRUE
+ var/list/metadata = ability.get_metadata_for(user)
+ if(!islist(metadata))
+ return TRUE
+ if(ability.projectile_type)
+ // Fire a projectile if that is how this ability works.
+ ability.fire_projectile_at(user, target, metadata)
+ else
+ // Otherwise, apply to the target. Range checking etc. will be handled in apply_effect().
+ ability.apply_effect(user, target, metadata)
+
+ // Clean up our item if needed.
+ if(ability.item_end_on_cast)
+ qdel(src)
diff --git a/code/datums/extensions/abilities/ability_projectile.dm b/code/datums/extensions/abilities/ability_projectile.dm
new file mode 100644
index 00000000000..896acba2e95
--- /dev/null
+++ b/code/datums/extensions/abilities/ability_projectile.dm
@@ -0,0 +1,31 @@
+/obj/item/projectile/ability
+ name = "ability"
+ // The projectile is functionally a tracer, the ability deals the damage.
+ nodamage = TRUE
+ penetrating = FALSE
+
+ /// Default; can be set by the ability.
+ life_span = 1 SECOND
+
+ var/expended = FALSE
+ var/mob/owner
+ var/list/ability_metadata
+ var/decl/ability/carried_ability
+
+/obj/item/projectile/ability/Destroy()
+ owner = null
+ carried_ability = null
+ return ..()
+
+/obj/item/projectile/ability/explosion_act()
+ SHOULD_CALL_PARENT(FALSE)
+
+/obj/item/projectile/ability/Bump(var/atom/A, forced=0)
+ if(loc && carried_ability && !expended)
+ carried_ability.apply_effect(owner, A, ability_metadata, src)
+ return TRUE
+
+/obj/item/projectile/ability/on_impact(var/atom/A)
+ if(loc && carried_ability && !expended)
+ carried_ability.apply_effect(owner, A, ability_metadata, src)
+ return TRUE
diff --git a/code/datums/extensions/abilities/ability_targeting.dm b/code/datums/extensions/abilities/ability_targeting.dm
new file mode 100644
index 00000000000..52f7ee8166c
--- /dev/null
+++ b/code/datums/extensions/abilities/ability_targeting.dm
@@ -0,0 +1,91 @@
+/decl/ability_targeting
+ abstract_type = /decl/ability_targeting
+ /// If set, this ability is applied to a square of this radius.
+ var/effect_radius = 0
+ /// Set to except the inner space of the spell from target checks.
+ var/effect_inner_radius = -1
+ /// If set, user will be excepted from targets.
+ var/user_is_immune = FALSE
+ /// If set, this ability will never target our faction.
+ var/faction_immune = FALSE
+ /// If set, this ability will only target our faction.
+ var/faction_only = FALSE
+ /// If set, this ability will resolve targets to turfs before doing any assessment or targetting.
+ var/target_turf = TRUE
+ /// If set along with target turf type, will include dense turfs.
+ var/ignore_dense_turfs = TRUE
+ /// If set along target turf type, will include space turfs.
+ var/ignore_space_turfs = FALSE
+
+/decl/ability_targeting/proc/get_effect_radius(mob/user, atom/hit_target, list/metadata)
+ return effect_radius
+
+/// This proc exists so that subtypes can override it and take advantage of the speed benefits of `for(var/mob in range())` and similar optimizations.
+/// It should ONLY ever be called in get_affected().
+/decl/ability_targeting/proc/get_affected_atoms(atom/center, new_effect_radius)
+ PROTECTED_PROC(TRUE)
+ . = list()
+ for(var/atom/target in range(center, new_effect_radius))
+ . += target
+
+/decl/ability_targeting/proc/get_affected(mob/user, atom/hit_target, list/metadata, decl/ability/ability, obj/item/projectile/ability/projectile)
+ if(effect_radius <= 0)
+ return validate_target(user, hit_target, metadata, ability) ? list(hit_target) : null
+ var/atom/center = projectile || hit_target
+ var/new_effect_radius = get_effect_radius(user, hit_target, metadata)
+ var/list/except_atoms = effect_inner_radius >= 0 ? range(effect_inner_radius, center) : null
+ var/list/target_candidates = get_affected_atoms(center, new_effect_radius)
+ for(var/atom/target in except_atoms ? target_candidates - except_atoms : target_candidates) // sloooooow...
+ if(validate_target(user, target, metadata, ability))
+ LAZYADD(., target)
+
+/decl/ability_targeting/proc/resolve_initial_target(atom/target)
+ if(target_turf)
+ return get_turf(target)
+ return target
+
+/decl/ability_targeting/proc/validate_initial_target(mob/user, atom/target, list/metadata, decl/ability/ability)
+ return validate_target(user, target, metadata, ability)
+
+/decl/ability_targeting/proc/validate_target(mob/user, atom/target, list/metadata, decl/ability/ability)
+ if(target == user && !user_is_immune)
+ return FALSE
+ if(target_turf && !isturf(target))
+ return FALSE
+ if(user.faction)
+ if(faction_immune && ismob(target))
+ var/mob/target_mob = target
+ if(target_mob.faction == user.faction)
+ return FALSE
+ if(faction_only)
+ if(!ismob(target))
+ return FALSE
+ var/mob/target_mob = target
+ if(target_mob.faction != user.faction)
+ return FALSE
+ else if(faction_only)
+ return FALSE
+ if(isturf(target))
+ if(ignore_dense_turfs && target.density)
+ return FALSE
+ if(ignore_space_turfs && istype(target, /turf/space))
+ return FALSE
+ return TRUE
+
+/decl/ability_targeting/clear_turf
+ ignore_dense_turfs = TRUE
+
+/decl/ability_targeting/clear_turf/validate_target(mob/user, atom/target, list/metadata, decl/ability/ability)
+ . = ..() && isturf(target)
+ if(.)
+ var/turf/target_turf = target
+ return !target_turf.contains_dense_objects(user)
+
+/decl/ability_targeting/living_mob
+ target_turf = FALSE
+
+/decl/ability_targeting/living_mob/validate_target(mob/user, atom/target, list/metadata, decl/ability/ability)
+ . = ..() && isliving(target)
+ if(.)
+ var/mob/living/victim = target
+ return victim.simulated
diff --git a/code/datums/extensions/abilities/readme.dm b/code/datums/extensions/abilities/readme.dm
new file mode 100644
index 00000000000..b7e17f8d59e
--- /dev/null
+++ b/code/datums/extensions/abilities/readme.dm
@@ -0,0 +1,26 @@
+/*
+
+ ABILITY DECL SYSTEM NOTES
+
+ - Mobs have an extension, /datum/extension/abilities
+ - This extension has a list of associated handlers, /datum/ability_handler
+ - The handlers have a list of associated ability decls, /decl/ability, which are indexes for associative metadata lists.
+ - The abilities have an associated targeting handler, /decl/ability_targeting, which handles single target, turf target, AOE, etc.
+ - Handlers are added/removed with mob.add_ability_handler(handler_type) and mob.remove_ability_handler(handler_type)
+ - The extension will be added to the mob automatically when adding a handler, and removed if the last handler is removed.
+ - Abilities are added/removed with mob.add_ability(ability_type, preset metadata if any) and mob.remove_ability(ability_type)
+ - Handlers for abilities will be inferred from the /decl and added to the mob automatically.
+ - Metadata is retrieved with handler.get_metadata(ability type or instance)
+
+ - Upon invocation, an ability will:
+ - retrieve handler and metadata from the user mob
+ - validate the handler/metadata/user against whatever requirements the ability has
+ - resolve the initial click target to the appropriate target for the ability (turf under the clicked target for example)
+ - check any additional requirements like charges, cooldowns, etc.
+ - if a projectile ability, spawn and launch a projectile that will carry the ability and metadata to the destination target.
+ - apply the ability to the destination target
+ - while applying the ability, the targeting decl will be used to grab all applicable targets at or near the point of use (projectile hit or clicked target)
+ - the ability effects will then get applied (fire, ice, explosion, so on)
+ - the ability will then set cooldown as appropriate in metadata, deduct charges, etc
+
+*/
\ No newline at end of file
diff --git a/code/datums/mind/mind.dm b/code/datums/mind/mind.dm
index cb9ad6c9539..1db34888001 100644
--- a/code/datums/mind/mind.dm
+++ b/code/datums/mind/mind.dm
@@ -75,7 +75,6 @@
/datum/mind/proc/handle_mob_deletion(mob/living/deleted_mob)
if (current == deleted_mob)
current = null
-
/datum/mind/proc/transfer_to(mob/living/new_character)
if(!istype(new_character))
to_world_log("## DEBUG: transfer_to(): Some idiot has tried to transfer_to() a non mob/living mob. Please inform Carn")
@@ -83,6 +82,8 @@
if(current?.mind == src)
current.mind = null
SSnano.user_transferred(current, new_character) // transfer active NanoUI instances to new user
+ if(istype(current)) // exclude new_players and observers
+ current.copy_abilities_to(new_character)
if(new_character.mind) //remove any mind currently in our new body's mind variable
new_character.mind.current = null
@@ -91,9 +92,6 @@
current = new_character //link ourself to our new body
new_character.mind = src //and link our new body to ourself
- if(learned_spells && learned_spells.len)
- restore_spells(new_character)
-
if(active)
new_character.key = key //now transfer the key to link the client to our new body
diff --git a/code/game/machinery/ai_slipper.dm b/code/game/machinery/ai_slipper.dm
index dc389cb9dec..e571411e973 100644
--- a/code/game/machinery/ai_slipper.dm
+++ b/code/game/machinery/ai_slipper.dm
@@ -7,9 +7,9 @@
var/uses = 20
var/disabled = 1
var/locked = 1
- var/cooldown_time = 0
- var/cooldown_timeleft = 0
- var/cooldown_on = 0
+ var/slip_cooldown_time = 0
+ var/slip_cooldown_timeleft = 0
+ var/slip_cooldown_on = 0
initial_access = list(access_ai_upload)
/obj/machinery/ai_slipper/on_update_icon()
@@ -52,13 +52,11 @@
if(!area || !isturf(loc))
return
var/t = "AI Liquid Dispenser ([area.proper_name])
"
-
if(src.locked && (!issilicon(user)))
t += "(Swipe ID card to unlock control panel.)
"
else
t += text("Dispenser [] - []?
\n", src.disabled?"deactivated":"activated", src, src.disabled?"Enable":"Disable")
t += text("Uses Left: [uses]. Activate the dispenser?
\n")
-
show_browser(user, t, "window=computer;size=575x450")
onclose(user, "computer")
@@ -74,30 +72,26 @@
update_icon()
. = TOPIC_REFRESH
if (href_list["toggleUse"])
- if(!(cooldown_on || disabled))
+ if(!(slip_cooldown_on || disabled))
new /obj/effect/effect/foam(src.loc)
src.uses--
- cooldown_on = 1
- cooldown_time = world.timeofday + 100
+ slip_cooldown_on = 1
+ slip_cooldown_time = world.timeofday + 100
slip_process()
. = TOPIC_REFRESH
-
if(. == TOPIC_REFRESH)
ui_interact(user)
/obj/machinery/ai_slipper/proc/slip_process()
- while(cooldown_time - world.timeofday > 0)
- var/ticksleft = cooldown_time - world.timeofday
-
+ while(slip_cooldown_time - world.timeofday > 0)
+ var/ticksleft = slip_cooldown_time - world.timeofday
if(ticksleft > 1e5)
- cooldown_time = world.timeofday + 10 // midnight rollover
-
-
- cooldown_timeleft = (ticksleft / 10)
+ slip_cooldown_time = world.timeofday + 10 // midnight rollover
+ slip_cooldown_timeleft = (ticksleft / 10)
sleep(5)
if (uses <= 0)
return
if (uses >= 0)
- cooldown_on = 0
+ slip_cooldown_on = 0
src.power_change()
return
diff --git a/code/game/objects/effects/overlays.dm b/code/game/objects/effects/overlays.dm
index 4a78d796efd..6ac93e8fee2 100644
--- a/code/game/objects/effects/overlays.dm
+++ b/code/game/objects/effects/overlays.dm
@@ -54,3 +54,13 @@
. = ..()
pixel_x += rand(-10, 10)
pixel_y += rand(-10, 10)
+
+/// Set and cleaned up by moving projectiles for the most part.
+/obj/effect/overlay/projectile_trail
+ var/obj/item/projectile/master
+
+/obj/effect/overlay/projectile_trail/Destroy()
+ if(master)
+ LAZYREMOVE(master.proj_trails, src)
+ master = null
+ return ..()
diff --git a/code/game/objects/items/weapons/weaponry.dm b/code/game/objects/items/weapons/weaponry.dm
index 1945ad56201..243fe2bd7e8 100644
--- a/code/game/objects/items/weapons/weaponry.dm
+++ b/code/game/objects/items/weapons/weaponry.dm
@@ -34,8 +34,7 @@
return ..()
/obj/item/nullrod/proc/holy_act(mob/living/target, mob/living/user)
- if(target.mind && LAZYLEN(target.mind.learned_spells))
- target.silence_spells(30 SECONDS)
+ if(target.disable_abilities(30 SECONDS))
to_chat(target, SPAN_DANGER("You've been silenced!"))
return TRUE
return FALSE
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index 4bea938640e..30e070676f1 100644
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -395,11 +395,13 @@
L.Add(t)
return L
-/turf/proc/contains_dense_objects()
+/turf/proc/contains_dense_objects(exceptions)
if(density)
return 1
for(var/atom/A in src)
if(A.density && !(A.atom_flags & ATOM_FLAG_CHECKS_BORDER))
+ if(exceptions && (exceptions == A || (A in exceptions)))
+ continue
return 1
return 0
@@ -863,3 +865,5 @@
/turf/take_vaporized_reagent(reagent, amount)
return assume_gas(reagent, round(amount / REAGENT_UNITS_PER_GAS_MOLE))
+
+/turf/proc/is_purged()
\ No newline at end of file
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index b93ccd2bde9..aba99df3baf 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -883,16 +883,6 @@ var/global/list/admin_verbs_mod = list(
log_and_message_admins("told everyone to man up and deal with it.")
-/client/proc/give_spell(mob/T as mob in SSmobs.mob_list) // -- Urist
- set category = "Fun"
- set name = "Give Spell"
- set desc = "Gives a spell to a mob."
- var/spell/S = input("Choose the spell to give to that guy", "ABRAKADABRA") as null|anything in spells
- if(!S) return
- T.add_spell(new S)
- SSstatistics.add_field_details("admin_verb","GS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
- log_and_message_admins("gave [key_name(T)] the spell [S].")
-
/client/proc/change_lobby_screen()
set name = "Lobby Screen: Change"
set category = "Fun"
diff --git a/code/modules/admin/view_variables/helpers.dm b/code/modules/admin/view_variables/helpers.dm
index 8e30b30b4d7..959b1b0c011 100644
--- a/code/modules/admin/view_variables/helpers.dm
+++ b/code/modules/admin/view_variables/helpers.dm
@@ -37,7 +37,6 @@
return ..() + {"
-
@@ -50,6 +49,9 @@
+
+
+
diff --git a/code/modules/admin/view_variables/topic.dm b/code/modules/admin/view_variables/topic.dm
index f1118e682c9..09d5d1cc8c1 100644
--- a/code/modules/admin/view_variables/topic.dm
+++ b/code/modules/admin/view_variables/topic.dm
@@ -1,4 +1,3 @@
-
/client/proc/view_var_Topic(href, href_list, hsrc)
//This should all be moved over to datum/admins/Topic() or something ~Carn
if( (usr.client != src) || !src.holder )
@@ -97,17 +96,6 @@
src.holder.show_player_panel(victim)
href_list["datumrefresh"] = href_list["mob_player_panel"]
- else if(href_list["give_spell"])
- if(!check_rights(R_ADMIN|R_FUN)) return
-
- var/mob/victim = locate(href_list["give_spell"])
- if(!istype(victim))
- to_chat(usr, "This can only be used on instances of type /mob")
- return
-
- src.give_spell(victim)
- href_list["datumrefresh"] = href_list["give_spell"]
-
else if(href_list["godmode"])
if(!check_rights(R_REJUVENATE)) return
@@ -690,6 +678,33 @@
item.set_material(new_material.type)
to_chat(usr, "Set material of [item] to [item.get_material()].")
+ else if(href_list["give_ability"])
+ var/mob/target = locate(href_list["give_ability"])
+ if(!istype(target) || QDELETED(target))
+ to_chat(usr, "Mob no longer exists.")
+ else
+ var/list/abilities = decls_repository.get_decls_of_type_unassociated(/decl/ability)
+ abilities = abilities.Copy()
+ abilities -= target.get_all_abilities()
+ var/decl/ability/ability = input(usr, "Which ability do you wish to grant?", "Give Ability") as null|anything in abilities
+ if(istype(ability) && !QDELETED(usr) && !QDELETED(target))
+ if(target.add_ability(ability.type))
+ log_and_message_admins("has given [ability] to [key_name(target)].")
+ else
+ to_chat(usr, SPAN_WARNING("Failed to give [ability] to [target]!"))
+
+ else if(href_list["remove_ability"])
+ var/mob/target = locate(href_list["remove_ability"])
+ if(!istype(target) || QDELETED(target))
+ to_chat(usr, "Mob no longer exists.")
+ else
+ var/decl/ability/ability = input(usr, "Which ability do you wish to remove?", "Remove Ability") as null|anything in target.get_all_abilities()
+ if(istype(ability) && !QDELETED(usr) && !QDELETED(target))
+ if(target.remove_ability(ability.type))
+ log_and_message_admins("has removed [ability] from [key_name(target)].")
+ else
+ to_chat(usr, SPAN_WARNING("Failed to remove [ability] from [target]!"))
+
if(href_list["datumrefresh"])
var/datum/datum_to_refresh = locate(href_list["datumrefresh"])
if(istype(datum_to_refresh, /datum) || istype(datum_to_refresh, /client))
diff --git a/code/modules/codex/categories/category_phenomena.dm b/code/modules/codex/categories/category_phenomena.dm
index f9777ed43fa..e2637bf9e73 100644
--- a/code/modules/codex/categories/category_phenomena.dm
+++ b/code/modules/codex/categories/category_phenomena.dm
@@ -4,16 +4,16 @@
/decl/codex_category/phenomena/Populate()
- // This needs duplicate checking but I resent even having to spend time on spellcode.
- var/list/spells = list()
- for(var/thing in subtypesof(/spell))
- var/spell/spell = thing
- if(!initial(spell.hidden_from_codex) && initial(spell.desc) && initial(spell.name))
- spells["[initial(spell.name)] (phenomena)"] = initial(spell.desc)
- for(var/spell in spells)
+ var/list/abilities = list()
+ for(var/decl/ability/ability in decls_repository.get_decls_of_subtype_unassociated(/decl/ability))
+ if(ability.hidden_from_codex || !ability.is_supernatural || !ability.desc)
+ continue
+ abilities["[ability.name] (phenomena)"] = ability.desc
+
+ for(var/ability in abilities)
var/datum/codex_entry/entry = new(
- _display_name = spell,
- _antag_text = spells[spell]
+ _display_name = ability,
+ _antag_text = abilities[ability]
)
items |= entry.name
. = ..()
diff --git a/code/modules/emotes/emote_define.dm b/code/modules/emotes/emote_define.dm
index 2a191251fa0..0223e3f3185 100644
--- a/code/modules/emotes/emote_define.dm
+++ b/code/modules/emotes/emote_define.dm
@@ -145,9 +145,7 @@ var/global/list/_emotes_by_key
var/emote_string = all_strings[string_key]
if(!length(emote_string))
continue
- emote_string = emote_replace_target_tokens(emote_string, dummy_emote_target)
- emote_string = emote_replace_user_tokens(emote_string, dummy_emote_user)
- emote_string = uppertext(emote_string)
+ emote_string = uppertext(emote_replace_target_tokens(emote_replace_user_tokens(emote_string, dummy_emote_user), dummy_emote_target))
for(var/token in tokens)
if(findtext(emote_string, token))
. += "malformed emote token [token] in [string_key]"
diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm
index 7903631bb68..a89cd071479 100644
--- a/code/modules/mob/login.dm
+++ b/code/modules/mob/login.dm
@@ -113,11 +113,6 @@
update_action_buttons()
update_mouse_pointer()
- if(ability_master)
- ability_master.update_abilities(TRUE, src)
- ability_master.toggle_open(1)
- ability_master.synch_spells_to_mind(mind)
-
if(get_preference_value(/datum/client_preference/show_status_markers) == PREF_SHOW)
if(status_markers?.mob_image_personal)
client.images |= status_markers.mob_image_personal
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 03827667a28..99c505013c3 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -17,8 +17,6 @@
QDEL_NULL(hud_used)
if(active_storage)
active_storage.close(src)
- if(istype(ability_master))
- QDEL_NULL(ability_master)
if(istype(skillset))
QDEL_NULL(skillset)
QDEL_NULL_LIST(grabbed_by)
@@ -57,7 +55,6 @@
QDEL_NULL_SCREEN(radio_use_icon)
QDEL_NULL_SCREEN(gun_move_icon)
QDEL_NULL_SCREEN(gun_setting_icon)
- QDEL_NULL_SCREEN(ability_master)
QDEL_NULL_SCREEN(zone_sel)
/mob/Initialize()
@@ -68,7 +65,6 @@
if(!istype(move_intent))
move_intent = GET_DECL(move_intent)
. = ..()
- ability_master = new(null, src)
refresh_ai_handler()
START_PROCESSING(SSmobs, src)
@@ -242,8 +238,6 @@
SHOULD_NOT_SLEEP(TRUE)
if(QDELETED(src))
return PROCESS_KILL
- if(ability_master)
- ability_master.update_spells(0)
#define UNBUCKLED 0
#define PARTIALLY_BUCKLED 1
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index 04318afb971..ec19dd3daed 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -60,7 +60,6 @@
var/obj/screen/gun/radio/radio_use_icon
var/obj/screen/gun/move/gun_move_icon
var/obj/screen/gun/mode/gun_setting_icon
- var/obj/screen/ability_master/ability_master
/*A bunch of this stuff really needs to go under their own defines instead of being globally attached to mob.
A variable should only be globally attached to turfs/objects/whatever, when it is in fact needed as such.
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index 21c41101077..92ddfd3f4ce 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -268,7 +268,7 @@
/mob/proc/set_move_intent(var/decl/move_intent/next_intent)
if(next_intent && move_intent != next_intent && next_intent.can_be_used_by(src))
move_intent = next_intent
- if(istype(hud_used))
+ if(istype(hud_used) && hud_used.move_intent)
hud_used.move_intent.icon_state = move_intent.hud_icon_state
return TRUE
return FALSE
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index 07ad3c6c686..152cb7a6ab5 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -14,6 +14,18 @@
is_spawnable_type = FALSE
atom_damage_type = BRUTE //BRUTE, BURN, TOX, OXY, CLONE, ELECTROCUTE are the only things that should be in here, Try not to use PAIN as it doesn't go through stun_effect_act
+ // Code for handling tails, if any.
+ /// If the projectile leaves a trail.
+ var/proj_trail = FALSE
+ /// How long the trail lasts.
+ var/proj_trail_lifespan = 0
+ /// What icon to use for the projectile trail.
+ var/proj_trail_icon = 'icons/effects/projectiles/trail.dmi'
+ /// What icon_state to use for the projectile trail.
+ var/proj_trail_icon_state = "trail"
+ /// Any extant trail effects.
+ var/list/proj_trails
+
var/bumped = 0 //Prevents it from hitting more than one guy at once
var/def_zone = "" //Aiming at
var/atom/movable/firer = null//Who shot it
@@ -316,7 +328,15 @@
return
/obj/item/projectile/proc/before_move()
- return
+ if(!proj_trail || !isturf(loc) || !proj_trail_icon || !proj_trail_icon_state || !proj_trail_lifespan)
+ return
+ var/obj/effect/overlay/projectile_trail/trail = new(loc)
+ trail.master = src
+ trail.icon = proj_trail_icon
+ trail.icon_state = proj_trail_icon_state
+ trail.set_density(FALSE)
+ LAZYADD(proj_trails, trail)
+ QDEL_IN(trail, proj_trail_lifespan)
/obj/item/projectile/proc/after_move()
if(hitscan && tracer_type && !(locate(/obj/effect/projectile) in loc))
@@ -609,6 +629,7 @@
trajectory.initialize_location(target.x, target.y, target.z, 0, 0)
/obj/item/projectile/Destroy()
+ QDEL_NULL_LIST(proj_trails)
if(hitscan)
if(loc && trajectory)
var/datum/point/pcache = trajectory.copy_to()
@@ -650,4 +671,4 @@
QDEL_NULL(beam_index)
/obj/item/projectile/proc/update_effect(var/obj/effect/projectile/effect)
- return
\ No newline at end of file
+ return
diff --git a/code/modules/projectiles/projectile/change.dm b/code/modules/projectiles/projectile/change.dm
index 8e241456d22..ff77df0fa33 100644
--- a/code/modules/projectiles/projectile/change.dm
+++ b/code/modules/projectiles/projectile/change.dm
@@ -55,12 +55,9 @@
var/mob/living/new_mob = apply_transformation(M, choice)
if(new_mob)
new_mob.set_intent(I_FLAG_HARM)
- if(M.mind)
- for (var/spell/S in M.mind.learned_spells)
- new_mob.add_spell(new S.type)
- new_mob.set_intent(I_FLAG_HARM)
- transfer_key_from_mob_to_mob(M, new_mob)
- to_chat(new_mob, "Your form morphs into that of \a [choice].")
+ new_mob.copy_abilities_from(M)
+ transfer_key_from_mob_to_mob(M, new_mob)
+ to_chat(new_mob, "Your form morphs into that of \a [choice].")
else
new_mob = M
if(new_mob)
@@ -68,4 +65,3 @@
if(new_mob != M && !QDELETED(M))
qdel(M)
-
diff --git a/code/modules/reagents/reagent_containers/spray.dm b/code/modules/reagents/reagent_containers/spray.dm
index 9cd81a4c437..1fa06b9a486 100644
--- a/code/modules/reagents/reagent_containers/spray.dm
+++ b/code/modules/reagents/reagent_containers/spray.dm
@@ -36,9 +36,6 @@
if(A?.storage || istype(A, /obj/structure/table) || istype(A, /obj/structure/closet) || istype(A, /obj/item/chems) || istype(A, /obj/structure/hygiene/sink) || istype(A, /obj/structure/janitorialcart))
return
- if(istype(A, /spell))
- return
-
if(proximity)
if(standard_dispenser_refill(user, A))
return
diff --git a/code/modules/spells/aoe_turf/aoe_turf.dm b/code/modules/spells/aoe_turf/aoe_turf.dm
deleted file mode 100644
index 0f7a721de80..00000000000
--- a/code/modules/spells/aoe_turf/aoe_turf.dm
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
-Aoe turf spells target a ring of tiles around the user
-This ring has an outer radius (range) and an inner radius (inner_radius)
-Aoe turf spells have a useful flag: IGNOREDENSE. It is explained in setup.dm
-*/
-
-/spell/aoe_turf //affects all turfs in view or range (depends)
- spell_flags = IGNOREDENSE
- var/inner_radius = -1 //for all your ring spell needs
-
-/spell/aoe_turf/choose_targets(mob/user = usr)
- var/list/targets = list()
-
- for(var/turf/target in view_or_range(range, holder, selection_type))
- if(!(target in view_or_range(inner_radius, holder, selection_type)))
- if(target.density && (spell_flags & IGNOREDENSE))
- continue
- targets += target
-
- if(!targets.len) //doesn't waste the spell
- return
-
- return targets
\ No newline at end of file
diff --git a/code/modules/spells/aoe_turf/conjure/conjure.dm b/code/modules/spells/aoe_turf/conjure/conjure.dm
deleted file mode 100644
index 8dd91146310..00000000000
--- a/code/modules/spells/aoe_turf/conjure/conjure.dm
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
-Conjure spells spawn things (mobs, objs, turfs) in their summon_type
-How they spawn stuff is decided by behaviour vars, which are explained below
-*/
-
-/spell/aoe_turf/conjure
- name = "Conjure"
- desc = "This spell conjures objs of the specified types in range."
-
- school = "conjuration" //funny, that
-
- var/list/summon_type = list() //determines what exactly will be summoned
- //should NOT be text, like list(/obj/machinery/bot/ed209)
-
- range = 0 //default values: only spawn on the player tile
- selection_type = "view"
-
- duration = 0 // 0=permanent, any other time in deciseconds - how long the summoned objects last for
- var/summon_amt = 1 //amount of objects summoned
- var/summon_exclusive = 0 //spawn one of everything, instead of random things
-
- var/list/newVars = list() //vars of the summoned objects will be replaced with those where they meet
- //should have format of list("emagged" = 1,"name" = "Justicebot"), for example
-
- cast_sound = 'sound/magic/castsummon.ogg'
-
-/spell/aoe_turf/conjure/cast(list/targets, mob/user)
-
- for(var/i=1,i <= summon_amt,i++)
- if(!targets.len)
- break
- var/summoned_object_type
- if(summon_exclusive)
- if(!summon_type.len)
- break
- summoned_object_type = summon_type[1]
- summon_type -= summoned_object_type
- else
- summoned_object_type = pick(summon_type)
- var/turf/spawn_place = pick(targets)
- var/atom/summoned_object
- if(ispath(summoned_object_type,/turf))
- spawn_place.ChangeTurf(summoned_object_type)
- summoned_object = spawn_place
- else
- summoned_object = new summoned_object_type(spawn_place)
- var/atom/movable/overlay/animation = new /atom/movable/overlay(summoned_object)
- animation.SetName("conjure")
- animation.set_density(0)
- animation.anchored = TRUE
- animation.icon = 'icons/effects/effects.dmi'
- animation.layer = BASE_HUMAN_LAYER
- if(ismob(summoned_object)) //we want them to NOT attack us.
- var/mob/M = summoned_object
- M.faction = user.faction
- apply_vars(summoned_object, user)
-
- if(duration)
- spawn(duration)
- if(summoned_object && !isturf(summoned_object))
- qdel(summoned_object)
- conjure_animation(animation, spawn_place)
- return
-
-/spell/aoe_turf/conjure/proc/conjure_animation(var/atom/movable/overlay/animation, var/turf/target)
- qdel(animation)
-
-/spell/aoe_turf/conjure/proc/apply_vars(atom/summoned_object, mob/caster)
- if(!istype(summoned_object) || !length(newVars))
- return
- for(var/varName in newVars)
- if(varName in summoned_object.vars)
- summoned_object.vars[varName] = newVars[varName]
diff --git a/code/modules/spells/construct_spells.dm b/code/modules/spells/construct_spells.dm
deleted file mode 100644
index 0c705aff4fc..00000000000
--- a/code/modules/spells/construct_spells.dm
+++ /dev/null
@@ -1,10 +0,0 @@
-//////////////////////////////Construct Spells/////////////////////////
-
-/proc/findNullRod(var/atom/target)
- if(istype(target,/obj/item/nullrod))
- return 1
- else if(target.contents)
- for(var/atom/A in target.contents)
- if(findNullRod(A))
- return 1
- return 0
diff --git a/code/modules/spells/spell_code.dm b/code/modules/spells/spell_code.dm
deleted file mode 100644
index bfcf1f4d8f6..00000000000
--- a/code/modules/spells/spell_code.dm
+++ /dev/null
@@ -1,385 +0,0 @@
-var/global/list/spells = typesof(/spell) //needed for the badmin verb for now
-
-/spell
- var/name
- var/desc
- var/feedback = "" //what gets sent if this spell gets chosen by the spellbook.
- parent_type = /datum
- var/panel = "Spells"//What panel the proc holder needs to go on.
-
- var/school = "evocation" //not relevant at now, but may be important later if there are changes to how spells work. the ones I used for now will probably be changed... maybe spell presets? lacking flexibility but with some other benefit?
- /*Spell schools as follows:
- Racial - Only tagged to spells gained for being a certain race
- Conjuration - Creating an object or transporting it.
- Transmutation - Modifying an object or transforming it.
- Illusion - Altering perception or thought.
- */
- var/charge_type = Sp_RECHARGE //can be recharge or charges, see charge_max and charge_counter descriptions; can also be based on the holder's vars now, use "holder_var" for that
-
- var/charge_max = 100 //recharge time in deciseconds if charge_type = Sp_RECHARGE or starting charges if charge_type = Sp_CHARGES
- var/charge_counter = 0 //can only cast spells if it equals recharge, ++ each decisecond if charge_type = Sp_RECHARGE or -- each cast if charge_type = Sp_CHARGES
- var/still_recharging_msg = "The spell is still recharging."
-
- var/silenced = 0 //not a binary - the length of time we can't cast this for
- var/processing = 0 //are we processing already? Mainly used so that silencing a spell doesn't call process() again. (and inadvertedly making it run twice as fast)
-
- var/holder_var_type = "bruteloss" //only used if charge_type equals to "holder_var"
- var/holder_var_amount = 20 //same. The amount adjusted with the mob's var when the spell is used
-
- var/spell_flags = 0
- var/invocation = "HURP DURP" //what is uttered when the wizard casts the spell
- var/invocation_type = SpI_NONE //can be none, whisper, shout, and emote
- var/range = 7 //the range of the spell; outer radius for aoe spells
- var/message = "" //whatever it says to the guy affected by it
- var/selection_type = "view" //can be "range" or "view"
- var/atom/movable/holder //where the spell is. Normally the user, can be an item
- var/duration = 0 //how long the spell lasts
-
- var/list/spell_levels = list(Sp_SPEED = 0, Sp_POWER = 0) //the current spell levels - total spell levels can be obtained by just adding the two values
- var/list/level_max = list(Sp_TOTAL = 4, Sp_SPEED = 4, Sp_POWER = 0) //maximum possible levels in each category. Total does cover both.
- var/cooldown_reduc = 0 //If set, defines how much charge_max drops by every speed upgrade
- var/delay_reduc = 0
- var/cooldown_min = 0 //minimum possible cooldown for a charging spell
-
- var/overlay = 0
- var/overlay_icon = 'icons/obj/wizard.dmi'
- var/overlay_icon_state = "spell"
- var/overlay_lifespan = 0
-
- var/sparks_spread = 0
- var/sparks_amt = 0 //cropped at 10
- var/smoke_spread = 0 //1 - harmless, 2 - harmful
- var/smoke_amt = 0 //cropped at 10
-
- var/critfailchance = 0
- var/time_between_channels = 0 //Delay between casts
- var/number_of_channels = 1 //How many times can we channel?
-
- var/cast_delay = 1
- var/cast_sound = ""
-
- var/hud_state = "" //name of the icon used in generating the spell hud object
- var/override_base = ""
-
-
- var/obj/screen/connected_button
-
- var/hidden_from_codex = FALSE
-
-///////////////////////
-///SETUP AND PROCESS///
-///////////////////////
-
-/spell/New()
- ..()
-
- //still_recharging_msg = "[name] is still recharging."
- charge_counter = charge_max
-
-/spell/proc/process()
- if(processing)
- return
- processing = 1
- spawn(0)
- while(charge_counter < charge_max || silenced > 0)
- charge_counter = min(charge_max,charge_counter+1)
- silenced = max(0,silenced-1)
- sleep(1)
- if(connected_button)
- var/obj/screen/ability/spell/S = connected_button
- if(!istype(S))
- return
- S.update_charge(1)
- processing = 0
- return
-
-/////////////////
-/////CASTING/////
-/////////////////
-
-/spell/proc/choose_targets(mob/user = usr) //depends on subtype - see targeted.dm, aoe_turf.dm, dumbfire.dm, or code in general folder
- return
-
-/spell/proc/perform(mob/user = usr, skipcharge = 0) //if recharge is started is important for the trigger spells
- if(!holder)
- holder = user //just in case
- if(!cast_check(skipcharge, user))
- return
- to_chat(user, SPAN_NOTICE("You start casting \the [name]..."))
- if(cast_delay && !spell_do_after(user, cast_delay))
- return
- var/list/targets = choose_targets(user)
- if(!check_valid_targets(targets))
- to_chat(user, SPAN_WARNING("\The [name] fizzles. There are no valid targets nearby."))
- return
- var/time = 0
- admin_attacker_log(user, "attempted to cast the spell [name]")
- do
- time++
- if(!check_valid_targets(targets)) //make sure we HAVE something
- break
- if(cast_check(1,user, targets)) //we check again, otherwise you can choose a target and then wait for when you are no longer able to cast (I.E. Incapacitated) to use it.
- invocation(user, targets)
- take_charge(user, skipcharge)
- before_cast(targets) //applies any overlays and effects
- if(prob(critfailchance))
- critfail(targets, user)
- else
- cast(targets, user, time)
- after_cast(targets) //generates the sparks, smoke, target messages etc.
- else
- break
- while(time != number_of_channels && do_after(user, time_between_channels, incapacitation_flags = INCAPACITATION_KNOCKOUT|INCAPACITATION_FORCELYING|INCAPACITATION_STUNNED, same_direction=1))
- after_spell(targets, user, time) //When we are done with the spell completely.
-
-
-
-/spell/proc/cast(list/targets, mob/user, var/channel_duration) //the actual meat of the spell
- return
-
-/spell/proc/critfail(list/targets, mob/user) //the wizman has fucked up somehow
- return
-
-/spell/proc/after_spell(var/list/targets, var/mob/user, var/channel_duration) //After everything else is done.
- return
-
-/spell/proc/adjust_var(mob/living/target = usr, type, amount) //handles the adjustment of the var when the spell is used. has some hardcoded types
- switch(type)
- if("bruteloss")
- target.take_damage(amount)
- if("fireloss")
- target.take_damage(amount, BURN)
- if("toxloss")
- target.take_damage(amount, TOX)
- if("oxyloss")
- target.take_damage(amount, OXY)
- if("brainloss")
- target.take_damage(amount, BRAIN)
- if("stunned")
- ADJ_STATUS(target, STAT_STUN, amount)
- if("weakened")
- ADJ_STATUS(target, STAT_WEAK, amount)
- if("paralysis")
- ADJ_STATUS(target, STAT_PARA, amount)
- else
- target.vars[type] += amount //I bear no responsibility for the runtimes that'll happen if you try to adjust non-numeric or even non-existant vars
- return
-
-///////////////////////////
-/////CASTING WRAPPERS//////
-///////////////////////////
-
-/spell/proc/before_cast(list/targets)
- for(var/atom/target in targets)
- if(overlay)
- var/location
- if(isliving(target))
- location = target.loc
- else if(isturf(target))
- location = target
- var/obj/effect/overlay/spell = new /obj/effect/overlay(location)
- spell.icon = overlay_icon
- spell.icon_state = overlay_icon_state
- spell.anchored = TRUE
- spell.set_density(0)
- spawn(overlay_lifespan)
- qdel(spell)
-
-/spell/proc/after_cast(list/targets)
- if(cast_sound)
- playsound(get_turf(holder),cast_sound,50,1)
- for(var/atom/target in targets)
- var/location = get_turf(target)
- if(isliving(target) && message)
- to_chat(target, text("[message]"))
- if(sparks_spread)
- spark_at(location, amount = sparks_amt)
- if(smoke_spread)
- if(smoke_spread == 1)
- var/datum/effect/effect/system/smoke_spread/smoke = new /datum/effect/effect/system/smoke_spread()
- smoke.set_up(smoke_amt, 0, location) //no idea what the 0 is
- smoke.start()
- else if(smoke_spread == 2)
- var/datum/effect/effect/system/smoke_spread/bad/smoke = new /datum/effect/effect/system/smoke_spread/bad()
- smoke.set_up(smoke_amt, 0, location) //no idea what the 0 is
- smoke.start()
-
-/////////////////////
-////CASTING TOOLS////
-/////////////////////
-/*Checkers, cost takers, message makers, etc*/
-
-/spell/proc/cast_check(skipcharge = 0,mob/user = usr, var/list/targets) //checks if the spell can be cast based on its settings; skipcharge is used when an additional cast_check is called inside the spell
-
- if(silenced > 0)
- return 0
-
- if(!(src in user.mind.learned_spells) && holder == user && !(isanimal(user)))
- error("[user] utilized the spell '[src]' without having it.")
- to_chat(user, "You shouldn't have this spell! Something's wrong.")
- return 0
-
- var/turf/user_turf = get_turf(user)
- if(!user_turf)
- to_chat(user, "You cannot cast spells in null space!")
-
- if((spell_flags & Z2NOCAST) && isAdminLevel(user_turf.z)) //Certain spells are not allowed on the centcomm zlevel
- return 0
-
- if(spell_flags & CONSTRUCT_CHECK)
- for(var/turf/T in range(holder, 1))
- if(findNullRod(T))
- return 0
-
- if(!src.check_charge(skipcharge, user)) //sees if we can cast based on charges alone
- return 0
-
- if(holder == user)
- if(isanimal(user))
- var/mob/living/simple_animal/SA = user
- if(SA.purge)
- to_chat(SA, "The null sceptre's power interferes with your own!")
- return 0
-
- var/mob/living/L = user
- if(L.incapacitated(INCAPACITATION_STUNNED|INCAPACITATION_RESTRAINED|INCAPACITATION_BUCKLED_FULLY|INCAPACITATION_FORCELYING|INCAPACITATION_KNOCKOUT))
- to_chat(user, "You can't cast spells while incapacitated!")
- return 0
-
- if(ishuman(user) && !(invocation_type in list(SpI_EMOTE, SpI_NONE)) && user.get_item_blocking_speech())
- to_chat(user, "Mmmf mrrfff!")
- return 0
-
- return 1
-
-/spell/proc/check_charge(var/skipcharge, mob/user)
- if(!skipcharge)
- switch(charge_type)
- if(Sp_RECHARGE)
- if(charge_counter < charge_max)
- to_chat(user, still_recharging_msg)
- return 0
- if(Sp_CHARGES)
- if(!charge_counter)
- to_chat(user, "[name] has no charges left.")
- return 0
- return 1
-
-/spell/proc/take_charge(mob/user = user, var/skipcharge)
- if(!skipcharge)
- switch(charge_type)
- if(Sp_RECHARGE)
- charge_counter = 0 //doesn't start recharging until the targets selecting ends
- src.process()
- return 1
- if(Sp_CHARGES)
- charge_counter-- //returns the charge if the targets selecting fails
- return 1
- if(Sp_HOLDVAR)
- adjust_var(user, holder_var_type, holder_var_amount)
- return 1
- return 0
- return 1
-
-/spell/proc/check_valid_targets(var/list/targets)
- if(!targets)
- return 0
- if(!islist(targets))
- targets = list(targets)
- else if(!targets.len)
- return 0
-
- var/list/valid_targets = view_or_range(range, holder, selection_type)
- for(var/target in targets)
- if(!(target in valid_targets))
- return 0
- return 1
-
-/spell/proc/invocation(mob/user = usr, var/list/targets) //spelling the spell out and setting it on recharge/reducing charges amount
-
- switch(invocation_type)
- if(SpI_SHOUT)
- if(prob(50))//Auto-mute? Fuck that noise
- user.say(invocation)
- else
- user.say(replacetext(invocation," ","`"))
- if(SpI_WHISPER)
- if(prob(50))
- user.whisper(invocation)
- else
- user.whisper(replacetext(invocation," ","`"))
- if(SpI_EMOTE)
- user.custom_emote(VISIBLE_MESSAGE, invocation)
-
-/////////////////////
-///UPGRADING PROCS///
-/////////////////////
-
-/spell/proc/can_improve(var/upgrade_type)
- if(level_max[Sp_TOTAL] <= ( spell_levels[Sp_SPEED] + spell_levels[Sp_POWER] )) //too many levels, can't do it
- return 0
-
- //if(upgrade_type && spell_levels[upgrade_type] && level_max[upgrade_type])
- if(upgrade_type && spell_levels[upgrade_type] >= level_max[upgrade_type])
- return 0
-
- return 1
-
-/spell/proc/empower_spell()
- if(!can_improve(Sp_POWER))
- return 0
-
- spell_levels[Sp_POWER]++
-
- return 1
-
-/spell/proc/quicken_spell()
- if(!can_improve(Sp_SPEED))
- return 0
-
- spell_levels[Sp_SPEED]++
-
- if(delay_reduc && cast_delay)
- cast_delay = max(0, cast_delay - delay_reduc)
- else if(cast_delay)
- cast_delay = round( max(0, initial(cast_delay) * ((level_max[Sp_SPEED] - spell_levels[Sp_SPEED]) / level_max[Sp_SPEED] ) ) )
-
- if(charge_type == Sp_RECHARGE)
- if(cooldown_reduc)
- charge_max = max(cooldown_min, charge_max - cooldown_reduc)
- else
- charge_max = round( max(cooldown_min, initial(charge_max) * ((level_max[Sp_SPEED] - spell_levels[Sp_SPEED]) / level_max[Sp_SPEED] ) ) ) //the fraction of the way you are to max speed levels is the fraction you lose
- if(charge_max < charge_counter)
- charge_counter = charge_max
-
- var/temp = ""
- name = initial(name)
- switch(level_max[Sp_SPEED] - spell_levels[Sp_SPEED])
- if(3)
- temp = "You have improved [name] into Efficient [name]."
- name = "Efficient [name]"
- if(2)
- temp = "You have improved [name] into Quickened [name]."
- name = "Quickened [name]"
- if(1)
- temp = "You have improved [name] into Free [name]."
- name = "Free [name]"
- if(0)
- temp = "You have improved [name] into Instant [name]."
- name = "Instant [name]"
-
- return temp
-
-/spell/proc/spell_do_after(var/mob/user, delay, var/numticks = 5)
- if(!user || isnull(user))
- return 0
-
- var/incap_flags = INCAPACITATION_STUNNED|INCAPACITATION_RESTRAINED|INCAPACITATION_BUCKLED_FULLY|INCAPACITATION_FORCELYING|INCAPACITATION_KNOCKOUT
- return do_after(user,delay, incapacitation_flags = incap_flags)
-
-/proc/view_or_range(distance = world.view , center = usr , type)
- switch(type)
- if("view")
- . = view(distance,center)
- if("range")
- . = range(distance,center)
\ No newline at end of file
diff --git a/code/modules/spells/spells.dm b/code/modules/spells/spells.dm
deleted file mode 100644
index ad550747a7f..00000000000
--- a/code/modules/spells/spells.dm
+++ /dev/null
@@ -1,60 +0,0 @@
-/datum/mind
- var/list/learned_spells
-
-/mob/Stat()
- . = ..()
- if(. && ability_master && ability_master.spell_objects)
- for(var/obj/screen/ability/spell/screen in ability_master.spell_objects)
- var/spell/S = screen.spell
- if((!S.connected_button) || !statpanel(S.panel))
- continue //Not showing the noclothes spell
- switch(S.charge_type)
- if(Sp_RECHARGE)
- statpanel(S.panel,"[S.charge_counter/10.0]/[S.charge_max/10]",S.connected_button)
- if(Sp_CHARGES)
- statpanel(S.panel,"[S.charge_counter]/[S.charge_max]",S.connected_button)
- if(Sp_HOLDVAR)
- statpanel(S.panel,"[S.holder_var_type] [S.holder_var_amount]",S.connected_button)
-
-/proc/restore_spells(var/mob/H)
- if(H.mind && H.mind.learned_spells)
- var/list/spells = list()
- for(var/spell/spell_to_remove in H.mind.learned_spells) //remove all the spells from other people.
- if(ismob(spell_to_remove.holder))
- var/mob/M = spell_to_remove.holder
- spells += spell_to_remove
- M.remove_spell(spell_to_remove)
-
- for(var/spell/spell_to_add in spells)
- H.add_spell(spell_to_add)
- H.ability_master.update_abilities(0,H)
-
-/mob/proc/add_spell(var/spell/spell_to_add, var/spell_base = "wiz_spell_ready")
- if(!ability_master)
- ability_master = new(null, src)
- spell_to_add.holder = src
- if(mind)
- if(!mind.learned_spells)
- mind.learned_spells = list()
- mind.learned_spells |= spell_to_add
- ability_master.add_spell(spell_to_add, spell_base)
- return 1
-
-/mob/proc/remove_spell(var/spell/spell_to_remove)
- if(!spell_to_remove || !istype(spell_to_remove))
- return
-
- if(mind)
- mind.learned_spells -= spell_to_remove
- if (ability_master)
- ability_master.remove_ability(ability_master.get_ability_by_spell(spell_to_remove))
- return 1
-
-/mob/proc/silence_spells(var/amount = 0)
- if(amount < 0)
- return
-
- if(!ability_master)
- return
-
- ability_master.silence_spells(amount)
\ No newline at end of file
diff --git a/code/modules/spells/targeted/ethereal_jaunt.dm b/code/modules/spells/targeted/ethereal_jaunt.dm
deleted file mode 100644
index 55a0ed83359..00000000000
--- a/code/modules/spells/targeted/ethereal_jaunt.dm
+++ /dev/null
@@ -1,120 +0,0 @@
-/spell/targeted/ethereal_jaunt
- name = "Ethereal Jaunt"
- desc = "This spell creates your ethereal form, temporarily making you invisible and able to pass through walls."
- feedback = "EJ"
- school = "transmutation"
- charge_max = 30 SECONDS
- spell_flags = Z2NOCAST | INCLUDEUSER
- invocation = "none"
- invocation_type = SpI_NONE
- range = 0
- max_targets = 1
- level_max = list(Sp_TOTAL = 4, Sp_SPEED = 4, Sp_POWER = 3)
- cooldown_min = 10 SECONDS //50 deciseconds reduction per rank
- duration = 5 SECONDS
-
- hud_state = "wiz_jaunt"
-
-/spell/targeted/ethereal_jaunt/cast(list/targets) //magnets, so mostly hardcoded
- for(var/mob/living/target in targets)
- if(HAS_TRANSFORMATION_MOVEMENT_HANDLER(target))
- continue
-
- if(target.buckled)
- target.buckled.unbuckle_mob()
- spawn(0)
- var/mobloc = get_turf(target.loc)
- var/obj/effect/dummy/spell_jaunt/holder = new /obj/effect/dummy/spell_jaunt( mobloc )
- var/atom/movable/overlay/animation = new /atom/movable/overlay(holder)
- animation.SetName("water")
- animation.set_density(0)
- animation.anchored = TRUE
- animation.icon = 'icons/mob/mob.dmi'
- animation.layer = FLY_LAYER
- target.extinguish_fire()
- if(target.buckled)
- target.buckled = null
- jaunt_disappear(animation, target)
- target.forceMove(holder)
- jaunt_steam(mobloc)
- sleep(duration)
- mobloc = holder.last_valid_turf
- animation.forceMove(mobloc)
- jaunt_steam(mobloc)
- holder.reappearing = 1
- sleep(20)
- jaunt_reappear(animation, target)
- sleep(5)
- if(!target.forceMove(mobloc))
- for(var/direction in list(1,2,4,8,5,6,9,10))
- var/turf/T = get_step(mobloc, direction)
- if(T)
- if(target.forceMove(T))
- break
- target.client.eye = target
- qdel(animation)
- qdel(holder)
-
-/spell/targeted/ethereal_jaunt/empower_spell()
- if(!..())
- return 0
- duration += 2 SECONDS
-
- return "[src] now lasts longer."
-
-/spell/targeted/ethereal_jaunt/proc/jaunt_disappear(var/atom/movable/overlay/animation, var/mob/living/target)
- animation.icon_state = "liquify"
- flick("liquify",animation)
- playsound(get_turf(target), 'sound/magic/ethereal_enter.ogg', 30)
-
-/spell/targeted/ethereal_jaunt/proc/jaunt_reappear(var/atom/movable/overlay/animation, var/mob/living/target)
- flick("reappear",animation)
- playsound(get_turf(target), 'sound/magic/ethereal_exit.ogg', 30)
-
-/spell/targeted/ethereal_jaunt/proc/jaunt_steam(var/mobloc)
- var/datum/effect/effect/system/steam_spread/steam = new /datum/effect/effect/system/steam_spread()
- steam.set_up(10, 0, mobloc)
- steam.start()
-
-/obj/effect/dummy/spell_jaunt
- name = "water"
- icon = 'icons/effects/effects.dmi'
- icon_state = "nothing"
- var/canmove = 1
- var/reappearing = 0
- density = FALSE
- anchored = TRUE
- var/turf/last_valid_turf
-
-/obj/effect/dummy/spell_jaunt/Initialize()
- . = ..()
- last_valid_turf = get_turf(loc)
-
-/obj/effect/dummy/spell_jaunt/Destroy()
- // Eject contents if deleted somehow
- for(var/atom/movable/AM in src)
- AM.dropInto(loc)
- return ..()
-
-/obj/effect/dummy/spell_jaunt/relaymove(var/mob/user, direction)
- if (!canmove || reappearing) return
- var/turf/newLoc = get_step(src, direction)
- if(!(newLoc.turf_flags & TURF_FLAG_NOJAUNT))
- forceMove(newLoc)
- var/turf/T = get_turf(loc)
- if(!T.contains_dense_objects())
- last_valid_turf = T
- else
- to_chat(user, "Some strange aura is blocking the way!")
- canmove = 0
- addtimer(CALLBACK(src, PROC_REF(allow_move)), 2)
-
-/obj/effect/dummy/spell_jaunt/proc/allow_move()
- canmove = TRUE
-
-/obj/effect/dummy/spell_jaunt/explosion_act(blah)
- SHOULD_CALL_PARENT(FALSE)
- return
-
-/obj/effect/dummy/spell_jaunt/bullet_act(blah)
- return
diff --git a/code/modules/spells/targeted/shift.dm b/code/modules/spells/targeted/shift.dm
deleted file mode 100644
index 2bcc684939a..00000000000
--- a/code/modules/spells/targeted/shift.dm
+++ /dev/null
@@ -1,24 +0,0 @@
-/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.set_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.set_dir(target.dir)
- flick("phase_shift2",animation)
-
-/spell/targeted/ethereal_jaunt/shift/jaunt_steam(var/mobloc)
- return
diff --git a/code/modules/spells/targeted/targeted.dm b/code/modules/spells/targeted/targeted.dm
deleted file mode 100644
index 733bbf1e540..00000000000
--- a/code/modules/spells/targeted/targeted.dm
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
-Targeted spells (with the exception of dumbfire) select from all the mobs in the defined range
-Targeted spells have two useful flags: INCLUDEUSER and SELECTABLE. These are explained in setup.dm
-*/
-
-/spell/targeted //can mean aoe for mobs (limited/unlimited number) or one target mob
- var/max_targets = 1 //leave 0 for unlimited targets in range, more for limited number of casts (can all target one guy, depends on target_ignore_prev) in range
- var/target_ignore_prev = 1 //only important if max_targets > 1, affects if the spell can be cast multiple times at one person from one cast
-
-
- var/amt_weakened = 0
- var/amt_paralysis = 0
- var/amt_stunned = 0
-
- var/amt_dizziness = 0
- var/amt_confused = 0
- var/amt_stuttering = 0
-
- //set to negatives for healing unless commented otherwise
- var/amt_dam_fire = 0
- var/amt_dam_brute = 0
- var/amt_dam_oxy = 0
- var/amt_dam_tox = 0
- var/amt_dam_robo = 0
- var/amt_brain = 0
- var/amt_radiation = 0
- var/amt_blood = 0 //Positive numbers to add blood
- var/amt_organ = 0 //Positive numbers for healing
-
- var/amt_eye_blind = 0
- var/amt_eye_blurry = 0
-
- var/effect_state = null //What effect to show on each, if any
- var/effect_duration = 0
- var/effect_color = "#ffffff"
-
- var/list/compatible_mobs = list()
-
-
-/spell/targeted/choose_targets(mob/user = usr)
- var/list/targets = list()
-
- if(max_targets == 0) //unlimited
- if(range == -2)
- targets = global.living_mob_list_
- else
- for(var/mob/living/target in view_or_range(range, holder, selection_type))
- targets += target
-
- else if(max_targets == 1) //single target can be picked
- if((range == 0 || range == -1) && spell_flags & INCLUDEUSER)
- targets += user
- else
- var/list/possible_targets = list()
- var/list/starting_targets
- if(range == -2)
- starting_targets = global.living_mob_list_
- else
- starting_targets = view_or_range(range, holder, selection_type)
-
- for(var/mob/living/M in starting_targets)
- if(!(spell_flags & INCLUDEUSER) && M == user)
- continue
- if(compatible_mobs && compatible_mobs.len)
- if(!is_type_in_list(M, compatible_mobs)) continue
- if(compatible_mobs && compatible_mobs.len && !is_type_in_list(M, compatible_mobs))
- continue
- possible_targets += M
-
- if(possible_targets.len)
- targets += pick(possible_targets)
- //Adds a safety check post-input to make sure those targets are actually in range.
-
-
- else
- var/list/possible_targets = list()
- var/list/starting_targets
-
- if(range == -2)
- starting_targets = global.living_mob_list_
- else
- starting_targets = view_or_range(range, holder, selection_type)
-
- for(var/mob/living/target in starting_targets)
- if(!(spell_flags & INCLUDEUSER) && target == user)
- continue
- if(compatible_mobs && !is_type_in_list(target, compatible_mobs))
- continue
- possible_targets += target
-
- for(var/i=1,i<=max_targets,i++)
- if(!possible_targets.len)
- break
- if(target_ignore_prev)
- var/target = pick(possible_targets)
- possible_targets -= target
- targets += target
- else
- targets += pick(possible_targets)
-
- if(!(spell_flags & INCLUDEUSER) && (user in targets))
- targets -= user
-
- if(compatible_mobs && compatible_mobs.len)
- for(var/mob/living/target in targets) //filters out all the non-compatible mobs
- if(!is_type_in_list(target, compatible_mobs))
- targets -= target
-
- return targets
-
-/spell/targeted/cast(var/list/targets, mob/user)
- for(var/mob/living/target in targets)
- if(range >= 0)
- if(!(target in view_or_range(range, holder, selection_type))) //filter at time of casting
- targets -= target
- continue
- apply_spell_damage(target)
-
-/spell/targeted/proc/apply_spell_damage(mob/living/target)
- target.take_damage(amt_dam_brute, do_update_health = FALSE)
- target.take_damage(amt_dam_fire, BURN, do_update_health = FALSE)
- target.take_damage(amt_dam_tox, TOX, do_update_health = FALSE)
- target.take_damage(amt_dam_oxy, OXY)
- if(ishuman(target))
- var/mob/living/human/H = target
- for(var/obj/item/organ/internal/affecting in H.get_internal_organs())
- if(affecting && istype(affecting))
- affecting.heal_damage(amt_organ, amt_organ)
- for(var/obj/item/organ/external/affecting in H.get_external_organs())
- if(affecting && istype(affecting))
- var/dam = BP_IS_PROSTHETIC(affecting) ? -amt_dam_robo : amt_organ
- affecting.heal_damage(dam, dam, robo_repair = BP_IS_PROSTHETIC(affecting))
- H.adjust_blood(amt_blood)
- H.take_damage(amt_brain, BRAIN)
- H.radiation += min(H.radiation, amt_radiation)
-
- target.update_icon()
- //disabling
- SET_STATUS_MAX(target, STAT_WEAK, amt_weakened)
- SET_STATUS_MAX(target, STAT_PARA, amt_paralysis)
- SET_STATUS_MAX(target, STAT_STUN, amt_stunned)
- if(amt_weakened || amt_paralysis || amt_stunned)
- if(target.buckled)
- target.buckled = null
- ADJ_STATUS(target, STAT_BLIND, amt_eye_blind)
- ADJ_STATUS(target, STAT_BLURRY, amt_eye_blurry)
- ADJ_STATUS(target, STAT_DIZZY, amt_dizziness)
- ADJ_STATUS(target, STAT_CONFUSE, amt_confused)
- ADJ_STATUS(target, STAT_STUTTER, amt_stuttering)
- if(effect_state)
- var/obj/o = new /obj/effect/temporary(get_turf(target), effect_duration, 'icons/effects/effects.dmi', effect_state)
- o.color = effect_color
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index 78c4315424a..5502343f0d5 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/icons/effects/projectiles/trail.dmi b/icons/effects/projectiles/trail.dmi
new file mode 100644
index 00000000000..4e058a3f1bf
Binary files /dev/null and b/icons/effects/projectiles/trail.dmi differ
diff --git a/icons/mob/screen/abilities.dmi b/icons/mob/screen/abilities.dmi
new file mode 100644
index 00000000000..ca7d904bd71
Binary files /dev/null and b/icons/mob/screen/abilities.dmi differ
diff --git a/icons/mob/screen/ability_inhand.dmi b/icons/mob/screen/ability_inhand.dmi
new file mode 100644
index 00000000000..63e1b630dc0
Binary files /dev/null and b/icons/mob/screen/ability_inhand.dmi differ
diff --git a/icons/mob/screen/phenomena.dmi b/icons/mob/screen/phenomena.dmi
deleted file mode 100644
index f1d05fa9730..00000000000
Binary files a/icons/mob/screen/phenomena.dmi and /dev/null differ
diff --git a/icons/mob/screen/spells.dmi b/icons/mob/screen/spells.dmi
deleted file mode 100644
index d574921544d..00000000000
Binary files a/icons/mob/screen/spells.dmi and /dev/null differ
diff --git a/icons/obj/wizard.dmi b/icons/obj/wizard.dmi
index 49d879d47d8..b8ebfc01108 100644
Binary files a/icons/obj/wizard.dmi and b/icons/obj/wizard.dmi differ
diff --git a/maps/exodus/exodus-admin.dmm b/maps/exodus/exodus-admin.dmm
index 9fcc6687e4d..f8b0550efc0 100644
--- a/maps/exodus/exodus-admin.dmm
+++ b/maps/exodus/exodus-admin.dmm
@@ -2422,7 +2422,8 @@
/turf/unsimulated/floor/vault,
/area/tdome)
"lSj" = (
-/obj/effect/forcefield{
+/obj/effect{
+ density = 1;
desc = "You can't get in. Heh.";
name = "Blocker"
},
@@ -2434,7 +2435,8 @@
/turf/unsimulated/floor/dark,
/area/tdome)
"mgi" = (
-/obj/effect/forcefield{
+/obj/effect{
+ density = 1;
desc = "You can't get in. Heh.";
name = "Blocker"
},
@@ -2799,7 +2801,8 @@
/turf/floor/tiled,
/area/shuttle/escape_shuttle)
"xSD" = (
-/obj/effect/forcefield{
+/obj/effect{
+ density = 1;
desc = "You can't get in. Heh.";
name = "Blocker"
},
diff --git a/mods/content/psionics/_psionics.dm b/mods/content/psionics/_psionics.dm
index 9092fd88ae6..fa58ed18f58 100644
--- a/mods/content/psionics/_psionics.dm
+++ b/mods/content/psionics/_psionics.dm
@@ -30,3 +30,12 @@
var/datum/ability_handler/psionics/psi = !is_preview_copy && istype(character) && character.get_ability_handler(/datum/ability_handler/psionics)
if(psi)
psi.update()
+
+/decl/ability/can_use_ability(mob/user, list/metadata, silent = FALSE)
+ . = ..()
+ if(. && is_supernatural)
+ var/spell_leech = user.disrupts_psionics()
+ if(spell_leech)
+ if(!silent)
+ to_chat(user, SPAN_WARNING("You try to marshal your energy, but find it leeched away by \the [spell_leech]!"))
+ return FALSE
diff --git a/mods/content/psionics/_psionics.dme b/mods/content/psionics/_psionics.dme
index b978b8a0c7b..d52fd095516 100644
--- a/mods/content/psionics/_psionics.dme
+++ b/mods/content/psionics/_psionics.dme
@@ -9,7 +9,6 @@
#include "datum\jobs.dm"
#include "datum\mind.dm"
#include "datum\security_state.dm"
-#include "datum\spells.dm"
#include "datum\surgery.dm"
#include "datum\uplink.dm"
#include "datum\antagonists\foundation.dm"
diff --git a/mods/content/psionics/datum/spells.dm b/mods/content/psionics/datum/spells.dm
deleted file mode 100644
index 7c02c352000..00000000000
--- a/mods/content/psionics/datum/spells.dm
+++ /dev/null
@@ -1,7 +0,0 @@
-/spell/cast_check(skipcharge = 0,mob/user = usr, var/list/targets) //checks if the spell can be cast based on its settings; skipcharge is used when an additional cast_check is called inside the spell
- var/spell_leech = user.disrupts_psionics()
- if(spell_leech)
- to_chat(user, SPAN_WARNING("You try to marshal your energy, but find it leeched away by \the [spell_leech]!"))
- return 0
- . = ..()
-
\ No newline at end of file
diff --git a/mods/content/psionics/system/psionics/complexus/complexus.dm b/mods/content/psionics/system/psionics/complexus/complexus.dm
index 990edecd13e..0ce5f4cfc18 100644
--- a/mods/content/psionics/system/psionics/complexus/complexus.dm
+++ b/mods/content/psionics/system/psionics/complexus/complexus.dm
@@ -1,5 +1,4 @@
/datum/ability_handler/psionics
-
var/announced = FALSE // Whether or not we have been announced to our holder yet.
var/suppressed = TRUE // Whether or not we are suppressing our psi powers.
var/use_psi_armour = TRUE // Whether or not we should automatically deflect/block incoming damage.
diff --git a/mods/content/psionics/system/psionics/equipment/psipower.dm b/mods/content/psionics/system/psionics/equipment/psipower.dm
index 3d850f27cf2..8aa0afca352 100644
--- a/mods/content/psionics/system/psionics/equipment/psipower.dm
+++ b/mods/content/psionics/system/psionics/equipment/psipower.dm
@@ -15,7 +15,9 @@
. = ..()
/obj/item/ability/psionic/attack_self(var/mob/user)
- sound_to(owner, 'sound/effects/psi/power_fail.ogg')
+ var/mob/owner = owner_ref?.resolve()
+ if(istype(owner))
+ sound_to(owner, 'sound/effects/psi/power_fail.ogg')
. = ..()
/obj/item/ability/psionic/use_on_mob(mob/living/target, mob/living/user, animate = TRUE)
@@ -31,6 +33,7 @@
. = ..(target, user, proximity)
/obj/item/ability/psionic/Process()
+ var/mob/living/owner = owner_ref?.resolve()
var/datum/ability_handler/psionics/psi = istype(owner) && owner.get_ability_handler(/datum/ability_handler/psionics)
psi?.spend_power(maintain_cost, backblast_on_failure = FALSE)
if((!owner || owner.do_psionics_check(maintain_cost, owner) || loc != owner || !(src in owner.get_held_items())) && !QDELETED(src))
diff --git a/mods/content/psionics/system/psionics/equipment/psipower_tk.dm b/mods/content/psionics/system/psionics/equipment/psipower_tk.dm
index 787d2a59050..ee21afaa907 100644
--- a/mods/content/psionics/system/psionics/equipment/psipower_tk.dm
+++ b/mods/content/psionics/system/psionics/equipment/psipower_tk.dm
@@ -9,6 +9,7 @@
. = ..()
/obj/item/ability/psionic/telekinesis/Process()
+ var/mob/living/owner = owner_ref?.resolve()
var/datum/ability_handler/psionics/psi = istype(owner) && owner.get_ability_handler(/datum/ability_handler/psionics)
if(!focus || !isturf(focus.loc) || get_dist(get_turf(focus), get_turf(owner)) > psi?.get_rank(PSI_PSYCHOKINESIS))
owner.drop_from_inventory(src)
@@ -30,6 +31,7 @@
else
return FALSE
+ var/mob/living/owner = owner_ref?.resolve()
var/datum/ability_handler/psionics/psi = istype(owner) && owner.get_ability_handler(/datum/ability_handler/psionics)
if(_focus.anchored || (check_paramount && psi?.get_rank(PSI_PSYCHOKINESIS) < PSI_RANK_PARAMOUNT))
focus = _focus
@@ -88,7 +90,7 @@
else
if(!focus.anchored)
var/user_rank = psi?.get_rank(PSI_PSYCHOKINESIS)
- focus.throw_at(target, user_rank*2, user_rank*10, owner)
+ focus.throw_at(target, user_rank*2, user_rank*10, owner_ref?.resolve())
sleep(1)
sparkle()
diff --git a/mods/content/psionics/system/psionics/mob/mob.dm b/mods/content/psionics/system/psionics/mob/mob.dm
index c85902acde4..15cc7914742 100644
--- a/mods/content/psionics/system/psionics/mob/mob.dm
+++ b/mods/content/psionics/system/psionics/mob/mob.dm
@@ -1,4 +1,5 @@
/datum/ability_handler/psionics/refresh_login()
+ . = ..()
update(TRUE)
if(!suppressed)
show_auras()
diff --git a/mods/content/psionics/system/psionics/mob/mob_interactions.dm b/mods/content/psionics/system/psionics/mob/mob_interactions.dm
index 366adb30882..aba8eeb102c 100644
--- a/mods/content/psionics/system/psionics/mob/mob_interactions.dm
+++ b/mods/content/psionics/system/psionics/mob/mob_interactions.dm
@@ -6,8 +6,6 @@
power.handle_post_power(user, target); \
if(istype(result)) { \
sound_to(user, sound('sound/effects/psi/power_evoke.ogg')); \
- LAZYADD(ability_items, result); \
- user.put_in_hands(result); \
} \
return TRUE; \
} \
@@ -28,15 +26,19 @@
INVOKE_PSI_POWERS(user, get_grab_powers(SSpsi.get_faculty_by_intent(user.get_intent())), target)
/datum/ability_handler/psionics/can_do_melee_invocation(mob/user, atom/target)
+ SHOULD_CALL_PARENT(FALSE)
return TRUE
/datum/ability_handler/psionics/do_melee_invocation(mob/user, atom/target)
+ SHOULD_CALL_PARENT(FALSE)
INVOKE_PSI_POWERS(user, get_melee_powers(SSpsi.get_faculty_by_intent(user.get_intent())), target)
/datum/ability_handler/psionics/can_do_ranged_invocation(mob/user, atom/target)
+ SHOULD_CALL_PARENT(FALSE)
return TRUE
/datum/ability_handler/psionics/do_ranged_invocation(mob/user, atom/target)
+ SHOULD_CALL_PARENT(FALSE)
INVOKE_PSI_POWERS(user, get_ranged_powers(SSpsi.get_faculty_by_intent(user.get_intent())), target)
-#undef INVOKE_PSI_POWERS
\ No newline at end of file
+#undef INVOKE_PSI_POWERS
diff --git a/mods/gamemodes/cult/_cult.dme b/mods/gamemodes/cult/_cult.dme
index 82d4e19aed5..02138255d74 100644
--- a/mods/gamemodes/cult/_cult.dme
+++ b/mods/gamemodes/cult/_cult.dme
@@ -21,7 +21,11 @@
#include "special_role.dm"
#include "structures.dm"
#include "talisman.dm"
-#include "cultify\de-cultify.dm"
+#include "abilities\_handler.dm"
+#include "abilities\construct.dm"
+#include "abilities\harvest.dm"
+#include "abilities\shade.dm"
+#include "cultify\de-cultify.dm"
#include "cultify\defile.dm"
#include "cultify\mob.dm"
#include "cultify\turf.dm"
@@ -29,7 +33,12 @@
#include "mobs\shade.dm"
#include "mobs\constructs\constructs.dm"
#include "mobs\constructs\soulstone.dm"
-#include "spells\construct.dm"
-#include "spells\harvest.dm"
// END_INCLUDE
-#endif
\ No newline at end of file
+#endif
+// BEGIN_INTERNALS
+// END_INTERNALS
+// BEGIN_FILE_DIR
+#define FILE_DIR .
+// END_FILE_DIR
+// BEGIN_PREFERENCES
+// END_PREFERENCES
diff --git a/mods/gamemodes/cult/abilities/_handler.dm b/mods/gamemodes/cult/abilities/_handler.dm
new file mode 100644
index 00000000000..f346bb73fb4
--- /dev/null
+++ b/mods/gamemodes/cult/abilities/_handler.dm
@@ -0,0 +1,21 @@
+/obj/screen/ability/category/cult
+ name = "Toggle Construct Abilities"
+ icon = 'mods/gamemodes/cult/icons/abilities.dmi'
+
+/obj/screen/ability/button/cult
+ icon = 'mods/gamemodes/cult/icons/abilities.dmi'
+
+/datum/ability_handler/cult
+ category_toggle_type = /obj/screen/ability/category/cult
+
+/decl/ability/cult
+ abstract_type = /decl/ability/cult
+ ability_icon = 'mods/gamemodes/cult/icons/abilities.dmi'
+ ability_icon_state = "artificer"
+ associated_handler_type = /datum/ability_handler/cult
+ ui_element_type = /obj/screen/ability/button/cult
+ ability_cooldown_time = 60 SECONDS
+
+/obj/item/ability/cult
+ icon = 'mods/gamemodes/cult/icons/ability_item.dmi'
+ color = COLOR_RED
diff --git a/mods/gamemodes/cult/abilities/construct.dm b/mods/gamemodes/cult/abilities/construct.dm
new file mode 100644
index 00000000000..60138eaba35
--- /dev/null
+++ b/mods/gamemodes/cult/abilities/construct.dm
@@ -0,0 +1,131 @@
+//////////////////////////////Construct Spells/////////////////////////
+/decl/ability/cult/construct
+ name = "Artificer"
+ desc = "This spell conjures a construct which may be controlled by shades."
+ target_selector = /decl/ability_targeting/clear_turf
+ overlay_icon = 'mods/gamemodes/cult/icons/effects.dmi'
+ overlay_icon_state = "sparkles"
+ target_selector = /decl/ability_targeting/clear_turf/construct
+ var/summon_type = /obj/structure/constructshell
+
+/decl/ability_targeting/clear_turf/construct/validate_target(mob/user, atom/target, list/metadata, decl/ability/ability)
+ var/decl/ability/cult/construct/cult_ability = ability
+ if(!istype(cult_ability))
+ return FALSE
+ return ..() && !istype(target, cult_ability.summon_type) && !(locate(cult_ability.summon_type) in target)
+
+/decl/ability/cult/construct/apply_effect(mob/user, atom/hit_target, list/metadata, obj/item/projectile/ability/projectile)
+ . = ..()
+ var/turf/target_turf = get_turf(hit_target)
+ if(istype(target_turf))
+ if(ispath(summon_type, /turf))
+ target_turf = target_turf.ChangeTurf(summon_type, TRUE, FALSE, TRUE, TRUE, FALSE)
+ if(target_turf) // We reapply effects as target no longer exists.
+ apply_effect_to(user, target_turf, metadata)
+ else if(ispath(summon_type, /atom))
+ new summon_type(target_turf)
+
+/decl/ability/cult/construct/lesser
+ ability_cooldown_time = 2 MINUTES
+ summon_type = /obj/structure/constructshell/cult
+ ability_icon_state = "const_shell"
+
+/decl/ability/cult/construct/floor
+ name = "Floor Construction"
+ desc = "This spell constructs a cult floor"
+ ability_cooldown_time = 2 SECONDS
+ summon_type = /turf/floor/cult
+ ability_icon_state = "const_floor"
+ overlay_icon_state = "cultfloor"
+
+/decl/ability/cult/construct/wall
+ name = "Lesser Construction"
+ desc = "This spell constructs a cult wall"
+ ability_cooldown_time = 10 SECONDS
+ summon_type = /turf/wall/cult
+ ability_icon_state = "const_wall"
+ overlay_icon_state = "cultwall"
+
+/decl/ability/cult/construct/wall/reinforced
+ name = "Greater Construction"
+ desc = "This spell constructs a reinforced metal wall"
+ ability_cooldown_time = 30 SECONDS
+ summon_type = /turf/wall/r_wall
+
+/decl/ability/cult/construct/soulstone
+ name = "Summon Soulstone"
+ desc = "This spell reaches into Nar-Sie's realm, summoning one of the legendary fragments across time and space."
+ ability_cooldown_time = 5 MINUTES
+ summon_type = /obj/item/soulstone
+ ability_icon_state = "const_stone"
+
+/decl/ability/cult/construct/pylon
+ name = "Red Pylon"
+ desc = "This spell conjures a fragile crystal from Nar-Sie's realm. Makes for a convenient light source."
+ ability_cooldown_time = 20 SECONDS
+ summon_type = /obj/structure/cult/pylon
+ ability_icon_state = "const_pylon"
+ target_selector = /decl/ability_targeting/pylon
+ is_melee_invocation = TRUE
+ prep_cast = TRUE
+
+/decl/ability_targeting/pylon/validate_target(mob/user, atom/target, list/metadata, decl/ability/ability)
+ . = ..()
+ if(!.)
+ return
+ if(istype(target, /obj/structure/cult/pylon))
+ return TRUE
+ if(isturf(target))
+ var/turf/target_turf = target
+ // We can repair pylons, so let us target turfs containing broken pylons.
+ if(target_turf.contains_dense_objects(user))
+ for(var/obj/structure/cult/pylon/pylon in target_turf)
+ if(pylon.isbroken)
+ return TRUE
+ return FALSE
+ // We can summon pylons in empty turfs.
+ return TRUE
+ return FALSE
+
+/decl/ability/cult/construct/pylon/apply_effect(mob/user, atom/hit_target, list/metadata, obj/item/projectile/ability/projectile)
+ for(var/obj/structure/cult/pylon/P in get_turf(hit_target))
+ if(P.isbroken)
+ P.repair(user)
+ return TRUE
+ . = ..()
+
+/decl/ability/cult/construct/forcewall/lesser
+ name = "Force Shield"
+ desc = "Allows you to pull up a shield to protect yourself and allies from incoming threats"
+ summon_type = /obj/effect/cult_force_wall
+ ability_cooldown_time = 30 SECONDS
+ ability_use_channel = 20 SECONDS
+ ability_icon_state = "const_juggwall"
+ prepare_message_3p_str = "$USER$ begins to twist and warp space around $TARGET$, building a wall of force."
+ prepare_message_1p_str = "You begin the lengthy process of warping local space to form a wall of force."
+ cast_message_3p_str = "$USER$ completes a wall of force!"
+ cast_message_1p_str = "You complete a wall of force!"
+ fail_cast_1p_str = "The screaming fabric of spacetime escapes your grip, and the wall of force vanishes."
+
+//Code for the Juggernaut construct's forcefield, that seemed like a good place to put it.
+/obj/effect/cult_force_wall
+ desc = "This eerie-looking obstacle seems to have been pulled from another dimension through sheer force."
+ name = "wall of force"
+ icon = 'mods/gamemodes/cult/icons/effects.dmi'
+ icon_state = "m_shield_cult"
+ light_color = "#b40000"
+ light_range = 2
+ anchored = TRUE
+ opacity = FALSE
+ density = TRUE
+
+/obj/effect/cult_force_wall/Initialize(mapload)
+ . = ..()
+ addtimer(CALLBACK(src, PROC_REF(vanish)), 30 SECONDS)
+
+/obj/effect/cult_force_wall/proc/vanish()
+ density = FALSE
+ icon_state = "m_shield_cult_vanish"
+ sleep(12)
+ if(!QDELETED(src))
+ qdel(src)
diff --git a/mods/gamemodes/cult/abilities/harvest.dm b/mods/gamemodes/cult/abilities/harvest.dm
new file mode 100644
index 00000000000..49569d6a25e
--- /dev/null
+++ b/mods/gamemodes/cult/abilities/harvest.dm
@@ -0,0 +1,41 @@
+/decl/ability/cult/construct/harvest
+ name = "Harvest"
+ desc = "Back to where I come from, and you're coming with me."
+ ability_cooldown_time = 20 SECONDS
+ ability_use_channel = 10 SECONDS
+ overlay_icon_state = "rune_teleport"
+ overlay_lifespan = 0
+ ability_icon_state = "const_harvest"
+ prepare_message_3p_str = "Space around $USER$ begins to bubble and decay as a terrible vista begins to intrude..."
+ prepare_message_1p_str = "You bore through space and time, seeking the essence of the Geometer of Blood."
+ fail_cast_1p_str = "Reality reasserts itself, preventing your return to Nar-Sie."
+ target_selector = /decl/ability_targeting/living_mob
+
+/decl/ability/cult/construct/harvest/can_use_ability(mob/user, list/metadata, silent)
+ . = ..()
+ if(.)
+ var/destination
+ for(var/obj/effect/narsie/N in global.narsie_list)
+ destination = N.loc
+ break
+ if(!destination)
+ to_chat(user, SPAN_DANGER("You cannot sense the Geometer of Blood!"))
+ return FALSE
+
+/decl/ability/cult/construct/harvest/apply_effect(mob/user, atom/hit_target, list/metadata, obj/item/projectile/ability/projectile)
+ ..()
+ var/destination = null
+ for(var/obj/effect/narsie/N in global.narsie_list)
+ destination = N.loc
+ break
+ if(!destination)
+ to_chat(user, SPAN_DANGER("You cannot sense the Geometer of Blood!"))
+ return
+ if(ismob(hit_target) && hit_target != user)
+ var/mob/living/victim = hit_target
+ to_chat(user, SPAN_SINISTER("You warp back to Nar-Sie along with your prey."))
+ to_chat(victim, SPAN_SINISTER("You are wrenched through time and space and thrown into chaos!"))
+ victim.dropInto(destination)
+ else
+ to_chat(user, SPAN_SINISTER("You warp back to Nar-Sie."))
+ user.dropInto(destination)
diff --git a/mods/gamemodes/cult/abilities/shade.dm b/mods/gamemodes/cult/abilities/shade.dm
new file mode 100644
index 00000000000..5fab626d14d
--- /dev/null
+++ b/mods/gamemodes/cult/abilities/shade.dm
@@ -0,0 +1 @@
+/decl/ability/cult/construct/shift
\ No newline at end of file
diff --git a/mods/gamemodes/cult/icons/abilities.dmi b/mods/gamemodes/cult/icons/abilities.dmi
new file mode 100644
index 00000000000..f1d5d8bc121
Binary files /dev/null and b/mods/gamemodes/cult/icons/abilities.dmi differ
diff --git a/mods/gamemodes/cult/icons/ability_item.dmi b/mods/gamemodes/cult/icons/ability_item.dmi
new file mode 100644
index 00000000000..e22793f4f6b
Binary files /dev/null and b/mods/gamemodes/cult/icons/ability_item.dmi differ
diff --git a/mods/gamemodes/cult/icons/effects.dmi b/mods/gamemodes/cult/icons/effects.dmi
new file mode 100644
index 00000000000..9e238941454
Binary files /dev/null and b/mods/gamemodes/cult/icons/effects.dmi differ
diff --git a/mods/gamemodes/cult/icons/forcewall.dmi b/mods/gamemodes/cult/icons/forcewall.dmi
new file mode 100644
index 00000000000..04b8560cdac
Binary files /dev/null and b/mods/gamemodes/cult/icons/forcewall.dmi differ
diff --git a/icons/effects/uristrunes.dmi b/mods/gamemodes/cult/icons/runes.dmi
similarity index 100%
rename from icons/effects/uristrunes.dmi
rename to mods/gamemodes/cult/icons/runes.dmi
diff --git a/mods/gamemodes/cult/mobs/constructs/constructs.dm b/mods/gamemodes/cult/mobs/constructs/constructs.dm
index 1f10369a694..1eb37f6980b 100644
--- a/mods/gamemodes/cult/mobs/constructs/constructs.dm
+++ b/mods/gamemodes/cult/mobs/constructs/constructs.dm
@@ -5,7 +5,6 @@
speak_emote = list("hisses")
base_animal_type = /mob/living/simple_animal/construct
base_movement_delay = -1
-
response_help_1p = "You think better of touching $TARGET$."
response_help_3p = "$USER$ thinks better of touching $TARGET$."
response_disarm = "flails at"
@@ -31,7 +30,7 @@
z_flags = ZMM_MANGLE_PLANES
glowing_eyes = TRUE
ai = /datum/mob_controller/aggressive/construct
- var/list/construct_spells = list()
+ var/list/construct_spells
/datum/mob_controller/aggressive/construct
emote_speech = list("Hsssssssszsht.", "Hsssssssss...", "Tcshsssssssszht!")
@@ -54,7 +53,7 @@
add_language(/decl/language/cultcommon)
add_language(/decl/language/cult)
for(var/spell in construct_spells)
- src.add_spell(new spell, "const_spell_ready")
+ add_ability(spell)
set_light(1.5, -2, COLOR_WHITE)
update_icon()
@@ -107,7 +106,9 @@
environment_smash = 2
status_flags = 0
resistance = 10
- construct_spells = list(/spell/aoe_turf/conjure/forcewall/lesser)
+ construct_spells = list(
+ /decl/ability/cult/construct/forcewall/lesser
+ )
hud_used = /datum/hud/construct/juggernaut
base_movement_delay = 2
ai = /datum/mob_controller/aggressive/construct_armoured
@@ -163,7 +164,9 @@
natural_weapon = /obj/item/natural_weapon/wraith
environment_smash = 1
see_in_dark = 7
- construct_spells = list(/spell/targeted/ethereal_jaunt/shift)
+ construct_spells = list(
+ /decl/ability/cult/construct/shift
+ )
hud_used = /datum/hud/construct/wraith
/obj/item/natural_weapon/wraith
@@ -194,11 +197,11 @@
natural_weapon = /obj/item/natural_weapon/cult_builder
environment_smash = 1
construct_spells = list(
- /spell/aoe_turf/conjure/construct/lesser,
- /spell/aoe_turf/conjure/wall,
- /spell/aoe_turf/conjure/floor,
- /spell/aoe_turf/conjure/soulstone,
- /spell/aoe_turf/conjure/pylon
+ /decl/ability/cult/construct/lesser,
+ /decl/ability/cult/construct/wall,
+ /decl/ability/cult/construct/floor,
+ /decl/ability/cult/construct/soulstone,
+ /decl/ability/cult/construct/pylon
)
hud_used = /datum/hud/construct/artificer
base_movement_delay = 0
@@ -228,7 +231,9 @@
natural_weapon = /obj/item/natural_weapon/juggernaut/behemoth
environment_smash = 2
resistance = 10
- construct_spells = list(/spell/aoe_turf/conjure/forcewall/lesser)
+ construct_spells = list(
+ /decl/ability/cult/construct/lesser
+ )
hud_used = /datum/hud/construct/juggernaut
base_movement_delay = 2
ai = /datum/mob_controller/aggressive/construct_armoured
@@ -249,7 +254,7 @@
see_in_dark = 7
hud_used = /datum/hud/construct/harvester
construct_spells = list(
- /spell/targeted/harvest
+ /decl/ability/cult/construct/harvest
)
/obj/item/natural_weapon/harvester
@@ -264,14 +269,14 @@
/mob/living/simple_animal/construct/handle_regular_status_updates()
. = ..()
if(.)
- silence_spells(purge)
+ disable_abilities(purge)
/mob/living/simple_animal/construct/handle_regular_hud_updates()
. = ..()
if(.)
if(fire)
fire.icon_state = "fire[!!GET_HUD_ALERT(src, /decl/hud_element/condition/fire)]"
- silence_spells(purge)
+ disable_abilities(purge)
if(healths)
switch(current_health)
if(250 to INFINITY) healths.icon_state = "health0"
diff --git a/mods/gamemodes/cult/runes.dm b/mods/gamemodes/cult/runes.dm
index 161d225a648..6724fbae270 100644
--- a/mods/gamemodes/cult/runes.dm
+++ b/mods/gamemodes/cult/runes.dm
@@ -2,7 +2,7 @@
name = "rune"
desc = "A strange collection of symbols drawn in blood."
anchored = TRUE
- icon = 'icons/effects/uristrunes.dmi'
+ icon = 'mods/gamemodes/cult/icons/runes.dmi'
icon_state = "blank"
layer = RUNE_LAYER
@@ -24,7 +24,7 @@
if(cult.rune_strokes[type])
var/list/f = cult.rune_strokes[type]
for(var/i in f)
- var/image/t = image('icons/effects/uristrunes.dmi', "rune-[i]")
+ var/image/t = image(icon, "rune-[i]")
overlays += t
else
var/list/q = list(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
@@ -33,7 +33,7 @@
var/j = pick(q)
f += j
q -= f
- var/image/t = image('icons/effects/uristrunes.dmi', "rune-[j]")
+ var/image/t = image(icon, "rune-[j]")
overlays += t
cult.rune_strokes[type] = f.Copy()
color = bcolor
@@ -480,7 +480,6 @@
var/list/mob/living/casters = get_cultists()
if(casters.len < 3)
break
- //T.turf_animation('icons/effects/effects.dmi', "rune_sac")
victim.set_fire_intensity(max(2, victim.get_fire_intensity()))
victim.ignite_fire()
var/dam_amt = 2 + length(casters)
diff --git a/mods/gamemodes/cult/spells/construct.dm b/mods/gamemodes/cult/spells/construct.dm
deleted file mode 100644
index 05c35c46785..00000000000
--- a/mods/gamemodes/cult/spells/construct.dm
+++ /dev/null
@@ -1,122 +0,0 @@
-//////////////////////////////Construct Spells/////////////////////////
-
-/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/floor/cult)
-
- hud_state = "const_floor"
-
-/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/wall/cult)
-
- hud_state = "const_wall"
-
-/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/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/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, mob/user)
- ..()
- var/turf/spawn_place = pick(targets)
- for(var/obj/structure/cult/pylon/P in spawn_place.contents)
- if(P.isbroken)
- P.repair(user)
- continue
- return
-
-/spell/aoe_turf/conjure/forcewall/lesser
- name = "Force 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
\ No newline at end of file
diff --git a/mods/gamemodes/cult/spells/harvest.dm b/mods/gamemodes/cult/spells/harvest.dm
deleted file mode 100644
index 41854b772bc..00000000000
--- a/mods/gamemodes/cult/spells/harvest.dm
+++ /dev/null
@@ -1,37 +0,0 @@
-/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/effect/narsie/N in global.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
- to_chat(user, "You warp back to Nar-Sie[prey ? " along with your prey":""].")
- else
- to_chat(user, "...something's wrong!")//There shouldn't be an instance of Harvesters when Nar-Sie isn't in the world.
-
diff --git a/mods/gamemodes/cult/structures.dm b/mods/gamemodes/cult/structures.dm
index 51db7add603..26eec8bb217 100644
--- a/mods/gamemodes/cult/structures.dm
+++ b/mods/gamemodes/cult/structures.dm
@@ -13,10 +13,10 @@
desc = "A floating crystal that hums with an unearthly energy."
icon = 'icons/obj/structures/pylon.dmi'
icon_state = "pylon"
- var/isbroken = 0
light_power = 0.5
light_range = 13
light_color = "#3e0000"
+ var/isbroken = FALSE
/obj/structure/cult/pylon/attack_hand(mob/M)
SHOULD_CALL_PARENT(FALSE)
@@ -42,7 +42,7 @@
)
user.do_attack_animation(src)
playsound(get_turf(src), 'sound/effects/Glassbr3.ogg', 75, 1)
- isbroken = 1
+ isbroken = TRUE
set_density(0)
icon_state = "pylon-broken"
set_light(0)
@@ -61,10 +61,12 @@
/obj/structure/cult/pylon/proc/repair(mob/user)
if(isbroken)
to_chat(user, "You repair the pylon.")
- isbroken = 0
+ isbroken = FALSE
set_density(1)
icon_state = "pylon"
set_light(13, 0.5)
+ return TRUE
+ return FALSE
/obj/structure/cult/pylon/get_artifact_scan_data()
return "Tribal pylon - subject resembles statues/emblems built by cargo cult civilisations to honour energy systems from post-warp civilisations."
diff --git a/mods/species/ascent/mobs/bodyparts.dm b/mods/species/ascent/mobs/bodyparts.dm
index a86a379a15c..dba73c0e121 100644
--- a/mods/species/ascent/mobs/bodyparts.dm
+++ b/mods/species/ascent/mobs/bodyparts.dm
@@ -5,7 +5,7 @@
var/list/existing_webs = list()
var/max_webs = 4
var/web_weave_time = 20 SECONDS
- var/cooldown
+ var/organ_cooldown
/obj/item/organ/external/groin/insectoid/mantid/gyne
max_webs = 8
@@ -20,12 +20,12 @@
/obj/item/organ/external/groin/insectoid/mantid/refresh_action_button()
. = ..()
if(.)
- action.button_icon_state = "weave-web-[cooldown ? "off" : "on"]"
+ action.button_icon_state = "weave-web-[organ_cooldown ? "off" : "on"]"
action.button?.update_icon()
/obj/item/organ/external/groin/insectoid/mantid/attack_self(var/mob/user)
. = ..()
- if(. && !cooldown)
+ if(. && !organ_cooldown)
if(!isturf(owner.loc))
to_chat(owner, SPAN_WARNING("You cannot use this ability in this location."))
@@ -41,7 +41,7 @@
playsound(user, 'mods/species/ascent/sounds/razorweb_hiss.ogg', 70)
owner.visible_message(SPAN_WARNING("\The [owner] separates their jaws and begins to weave a web of crystalline filaments..."))
- cooldown = TRUE
+ organ_cooldown = TRUE
refresh_action_button()
addtimer(CALLBACK(src, PROC_REF(reset_cooldown)), web_weave_time)
if(do_after(owner, web_weave_time) && length(existing_webs) < max_webs)
@@ -52,27 +52,27 @@
web.owner = owner
/obj/item/organ/external/groin/insectoid/mantid/proc/reset_cooldown()
- cooldown = FALSE
+ organ_cooldown = FALSE
refresh_action_button()
/obj/item/organ/external/head/insectoid/mantid
name = "crested head"
action_button_name = "Spit Razorweb"
default_action_type = /datum/action/item_action/organ/ascent
- var/cooldown_time = 2.5 MINUTES
- var/cooldown
+ var/organ_cooldown_time = 2.5 MINUTES
+ var/organ_cooldown
/obj/item/organ/external/head/insectoid/mantid/refresh_action_button()
. = ..()
if(.)
- action.button_icon_state = "shot-web-[cooldown ? "off" : "on"]"
+ action.button_icon_state = "shot-web-[organ_cooldown ? "off" : "on"]"
action.button?.update_icon()
/obj/item/organ/external/head/insectoid/mantid/attack_self(var/mob/user)
. = ..()
if(.)
- if(cooldown)
+ if(organ_cooldown)
to_chat(owner, SPAN_WARNING("Your filament channel hasn't refilled yet!"))
return
@@ -81,12 +81,12 @@
playsound(user, 'mods/species/ascent/sounds/razorweb.ogg', 100)
to_chat(owner, SPAN_WARNING("You spit up a wad of razorweb, ready to throw!"))
owner.toggle_throw_mode(TRUE)
- cooldown = TRUE
+ organ_cooldown = TRUE
refresh_action_button()
- addtimer(CALLBACK(src, PROC_REF(reset_cooldown)), cooldown_time)
+ addtimer(CALLBACK(src, PROC_REF(reset_cooldown)), organ_cooldown_time)
else
qdel(web)
/obj/item/organ/external/head/insectoid/mantid/proc/reset_cooldown()
- cooldown = FALSE
+ organ_cooldown = FALSE
refresh_action_button()
diff --git a/mods/species/drakes/_overrides.dm b/mods/species/drakes/_overrides.dm
index a4c564def3c..a4246927c61 100644
--- a/mods/species/drakes/_overrides.dm
+++ b/mods/species/drakes/_overrides.dm
@@ -2,7 +2,7 @@
var/_drake_onmob_icon
var/_drake_hatchling_onmob_icon
-/obj/item/backpack/setup_sprite_sheets()
+/obj/item/setup_sprite_sheets()
. = ..()
if(_drake_onmob_icon)
LAZYSET(sprite_sheets, BODYTYPE_GRAFADREKA, _drake_onmob_icon)
diff --git a/mods/species/drakes/drake_abilities.dm b/mods/species/drakes/drake_abilities.dm
index 85b589c73f6..fb4d7b0fcab 100644
--- a/mods/species/drakes/drake_abilities.dm
+++ b/mods/species/drakes/drake_abilities.dm
@@ -6,9 +6,11 @@
spit_projectile_type = /obj/item/projectile/drake_spit/weak
/datum/ability_handler/predator/grafadreka/can_do_ranged_invocation(mob/user, atom/target)
- return istype(user) && user.check_intent(I_FLAG_HARM) && !user.incapacitated() && isatom(target)
+ return ..() || (istype(user) && user.check_intent(I_FLAG_HARM) && !user.incapacitated() && isatom(target))
/datum/ability_handler/predator/grafadreka/do_ranged_invocation(mob/user, atom/target)
+ if((. = ..()))
+ return
if(world.time < next_spit)
to_chat(user, SPAN_WARNING("You cannot spit again so soon!"))
return TRUE
diff --git a/nebula.dme b/nebula.dme
index 8993c444e42..d65b8291796 100644
--- a/nebula.dme
+++ b/nebula.dme
@@ -192,7 +192,6 @@
#include "code\_onclick\hud\robot.dm"
#include "code\_onclick\hud\skybox.dm"
#include "code\_onclick\hud\screen\_screen.dm"
-#include "code\_onclick\hud\screen\screen_abilities.dm"
#include "code\_onclick\hud\screen\screen_action_button.dm"
#include "code\_onclick\hud\screen\screen_ai_button.dm"
#include "code\_onclick\hud\screen\screen_attack_selector.dm"
@@ -427,8 +426,13 @@
#include "code\datums\extensions\abilities\abilities.dm"
#include "code\datums\extensions\abilities\abilities_mob.dm"
#include "code\datums\extensions\abilities\abilities_predator.dm"
+#include "code\datums\extensions\abilities\ability_button.dm"
+#include "code\datums\extensions\abilities\ability_decl.dm"
#include "code\datums\extensions\abilities\ability_handler.dm"
#include "code\datums\extensions\abilities\ability_item.dm"
+#include "code\datums\extensions\abilities\ability_projectile.dm"
+#include "code\datums\extensions\abilities\ability_targeting.dm"
+#include "code\datums\extensions\abilities\readme.dm"
#include "code\datums\extensions\appearance\appearance.dm"
#include "code\datums\extensions\appearance\base_icon_state.dm"
#include "code\datums\extensions\appearance\cardborg.dm"
@@ -3780,14 +3784,6 @@
#include "code\modules\species\station\human_bodytypes.dm"
#include "code\modules\species\station\monkey.dm"
#include "code\modules\species\station\monkey_bodytypes.dm"
-#include "code\modules\spells\construct_spells.dm"
-#include "code\modules\spells\spell_code.dm"
-#include "code\modules\spells\spells.dm"
-#include "code\modules\spells\aoe_turf\aoe_turf.dm"
-#include "code\modules\spells\aoe_turf\conjure\conjure.dm"
-#include "code\modules\spells\targeted\ethereal_jaunt.dm"
-#include "code\modules\spells\targeted\shift.dm"
-#include "code\modules\spells\targeted\targeted.dm"
#include "code\modules\sprite_accessories\_accessory.dm"
#include "code\modules\sprite_accessories\_accessory_category.dm"
#include "code\modules\sprite_accessories\cosmetics\_accessory_cosmetics.dm"