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"